@garygentry/feature-forge 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/claude/.feature-forge-bundle.json +1 -1
- package/adapters/claude/references/pipeline-state-schema.json +37 -1
- package/adapters/claude/references/shared-conventions.md +22 -8
- package/adapters/claude/references/stage-exit-protocol.md +18 -0
- package/adapters/claude/references/templates/specs-hygiene/AGENTS.md +9 -0
- package/adapters/claude/references/templates/specs-hygiene/CLAUDE.md +9 -0
- package/adapters/claude/scripts/forge-session.py +41 -0
- package/adapters/claude/skills/forge-0-epic/SKILL.md +2 -0
- package/adapters/claude/skills/forge-1-prd/SKILL.md +1 -1
- package/adapters/claude/skills/forge-2-tech/SKILL.md +2 -0
- package/adapters/claude/skills/forge-3-specs/SKILL.md +3 -1
- package/adapters/claude/skills/forge-4-backlog/SKILL.md +2 -0
- package/adapters/claude/skills/forge-5-loop/SKILL.md +1 -1
- package/adapters/claude/skills/forge-5-loop/references/runner-contract.md +22 -2
- package/adapters/claude/skills/forge-bootstrap/references/templates/hygiene/AGENTS.md +11 -0
- package/adapters/claude/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +11 -0
- package/adapters/codex/.feature-forge-bundle.json +1 -1
- package/adapters/codex/references/pipeline-state-schema.json +37 -1
- package/adapters/codex/references/shared-conventions.md +22 -8
- package/adapters/codex/references/stage-exit-protocol.md +18 -0
- package/adapters/codex/references/templates/specs-hygiene/AGENTS.md +9 -0
- package/adapters/codex/references/templates/specs-hygiene/CLAUDE.md +9 -0
- package/adapters/codex/scripts/forge-session.py +41 -0
- package/adapters/codex/skills/forge-0-epic/SKILL.md +2 -0
- package/adapters/codex/skills/forge-1-prd/SKILL.md +1 -1
- package/adapters/codex/skills/forge-2-tech/SKILL.md +2 -0
- package/adapters/codex/skills/forge-3-specs/SKILL.md +3 -1
- package/adapters/codex/skills/forge-4-backlog/SKILL.md +2 -0
- package/adapters/codex/skills/forge-5-loop/SKILL.md +1 -1
- package/adapters/codex/skills/forge-5-loop/references/runner-contract.md +22 -2
- package/adapters/codex/skills/forge-bootstrap/references/templates/hygiene/AGENTS.md +11 -0
- package/adapters/codex/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +11 -0
- package/adapters/copilot/.feature-forge-bundle.json +1 -1
- package/adapters/copilot/references/pipeline-state-schema.json +37 -1
- package/adapters/copilot/references/shared-conventions.md +22 -8
- package/adapters/copilot/references/stage-exit-protocol.md +18 -0
- package/adapters/copilot/references/templates/specs-hygiene/AGENTS.md +9 -0
- package/adapters/copilot/references/templates/specs-hygiene/CLAUDE.md +9 -0
- package/adapters/copilot/scripts/forge-session.py +41 -0
- package/adapters/copilot/skills/forge-0-epic/forge-0-epic.md +2 -0
- package/adapters/copilot/skills/forge-1-prd/forge-1-prd.md +1 -1
- package/adapters/copilot/skills/forge-2-tech/forge-2-tech.md +2 -0
- package/adapters/copilot/skills/forge-3-specs/forge-3-specs.md +3 -1
- package/adapters/copilot/skills/forge-4-backlog/forge-4-backlog.md +2 -0
- package/adapters/copilot/skills/forge-5-loop/forge-5-loop.md +1 -1
- package/adapters/copilot/skills/forge-5-loop/references/runner-contract.md +22 -2
- package/adapters/copilot/skills/forge-bootstrap/references/templates/hygiene/AGENTS.md +11 -0
- package/adapters/copilot/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +11 -0
- package/adapters/cursor/.feature-forge-bundle.json +1 -1
- package/adapters/cursor/references/pipeline-state-schema.json +37 -1
- package/adapters/cursor/references/shared-conventions.md +22 -8
- package/adapters/cursor/references/stage-exit-protocol.md +18 -0
- package/adapters/cursor/references/templates/specs-hygiene/AGENTS.md +9 -0
- package/adapters/cursor/references/templates/specs-hygiene/CLAUDE.md +9 -0
- package/adapters/cursor/scripts/forge-session.py +41 -0
- package/adapters/cursor/skills/forge-0-epic/forge-0-epic.mdc +2 -0
- package/adapters/cursor/skills/forge-1-prd/forge-1-prd.mdc +1 -1
- package/adapters/cursor/skills/forge-2-tech/forge-2-tech.mdc +2 -0
- package/adapters/cursor/skills/forge-3-specs/forge-3-specs.mdc +3 -1
- package/adapters/cursor/skills/forge-4-backlog/forge-4-backlog.mdc +2 -0
- package/adapters/cursor/skills/forge-5-loop/forge-5-loop.mdc +1 -1
- package/adapters/cursor/skills/forge-5-loop/references/runner-contract.md +22 -2
- package/adapters/cursor/skills/forge-bootstrap/references/templates/hygiene/AGENTS.md +11 -0
- package/adapters/cursor/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +11 -0
- package/adapters/gemini/.feature-forge-bundle.json +1 -1
- package/adapters/gemini/gemini-extension.json +1 -1
- package/adapters/gemini/references/pipeline-state-schema.json +37 -1
- package/adapters/gemini/references/shared-conventions.md +22 -8
- package/adapters/gemini/references/stage-exit-protocol.md +18 -0
- package/adapters/gemini/references/templates/specs-hygiene/AGENTS.md +9 -0
- package/adapters/gemini/references/templates/specs-hygiene/CLAUDE.md +9 -0
- package/adapters/gemini/scripts/forge-session.py +41 -0
- package/adapters/gemini/skills/forge-0-epic/forge-0-epic.md +2 -0
- package/adapters/gemini/skills/forge-1-prd/forge-1-prd.md +1 -1
- package/adapters/gemini/skills/forge-2-tech/forge-2-tech.md +2 -0
- package/adapters/gemini/skills/forge-3-specs/forge-3-specs.md +3 -1
- package/adapters/gemini/skills/forge-4-backlog/forge-4-backlog.md +2 -0
- package/adapters/gemini/skills/forge-5-loop/forge-5-loop.md +1 -1
- package/adapters/gemini/skills/forge-5-loop/references/runner-contract.md +22 -2
- package/adapters/gemini/skills/forge-bootstrap/references/templates/hygiene/AGENTS.md +11 -0
- package/adapters/gemini/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +11 -0
- package/package.json +1 -1
|
@@ -22,3 +22,14 @@ This project was scaffolded by forge-bootstrap. To continue development:
|
|
|
22
22
|
- Documents under `specs/` (PRDs, tech specs, numbered implementation specs) establish the backlog. They are **not** kept in sync with the code as it evolves.
|
|
23
23
|
- Do not flag or "fix" divergence between a finalized spec and the implementation — code is the source of truth for behavior.
|
|
24
24
|
- It's fine for `specs/` artifacts and `backlog.json` to reference specs for provenance, but implementation artifacts (source code, generated skills/agents, configs, docs) must not reference spec files, which may be archived or deleted after a feature ships.
|
|
25
|
+
|
|
26
|
+
## Tooling feedback (feature-forge / rauf)
|
|
27
|
+
|
|
28
|
+
This project is driven by the feature-forge pipeline and the rauf loop. Help improve those tools by capturing friction as you hit it — papercuts included, not just outright bugs.
|
|
29
|
+
|
|
30
|
+
- **When to flag:** any forge/rauf command, skill, agent, or prompt that is confusing, buggy, missing a capability, forces a workaround, or produces a surprising result.
|
|
31
|
+
- **Where to file** — route by which tool the friction is with:
|
|
32
|
+
- feature-forge (pipeline stages, forge skills/agents): https://github.com/garygentry/feature-forge/issues
|
|
33
|
+
- rauf (the autonomous loop runner, `rauf` CLI): https://github.com/garygentry/rauf/issues
|
|
34
|
+
- **How:** capture it while fresh — *what you ran / what you expected / what actually happened / a fix idea* — then propose a titled issue and file it with `gh issue create` **on the human's go-ahead, not silently.**
|
|
35
|
+
- **In an autonomous rauf iteration:** do **not** open issues mid-loop. Note the friction in `progress.md` for the human to triage later.
|
|
@@ -23,3 +23,14 @@ This project was scaffolded by forge-bootstrap. To continue development:
|
|
|
23
23
|
- Documents under `specs/` (PRDs, tech specs, numbered implementation specs) establish the backlog. They are **not** kept in sync with the code as it evolves.
|
|
24
24
|
- Don't flag or "fix" divergence between a finalized spec and the implementation — code is the source of truth for behavior.
|
|
25
25
|
- It's fine for `specs/` artifacts and `backlog.json` to reference specs for provenance, but implementation artifacts (source code, generated skills/agents, configs, docs) must not reference spec files, which may be archived or deleted after a feature ships.
|
|
26
|
+
|
|
27
|
+
## Tooling feedback (feature-forge / rauf)
|
|
28
|
+
|
|
29
|
+
I'm driving this project with the feature-forge pipeline and the rauf loop, and I want to keep improving them. When you hit friction with either tool, help me capture it — papercuts included, not just outright bugs.
|
|
30
|
+
|
|
31
|
+
- **When to flag:** any forge/rauf command, skill, agent, or prompt that is confusing, buggy, missing a capability, forces a workaround, or produces a surprising result.
|
|
32
|
+
- **Where to file** — route by which tool the friction is with:
|
|
33
|
+
- feature-forge (pipeline stages, `/feature-forge:*`, forge skills/agents): https://github.com/garygentry/feature-forge/issues
|
|
34
|
+
- rauf (the autonomous loop runner, `rauf` CLI): https://github.com/garygentry/rauf/issues
|
|
35
|
+
- **How:** capture it while fresh — *what you ran / what you expected / what actually happened / a fix idea* — then propose a titled issue and file it with `gh issue create` **once I give the go-ahead, not silently.**
|
|
36
|
+
- **In an autonomous rauf iteration:** don't open issues mid-loop. Note the friction in `progress.md` for me to triage later.
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"currentStage": {
|
|
34
34
|
"type": "string",
|
|
35
35
|
"enum": ["forge-1-prd", "forge-2-tech", "forge-3-specs", "forge-4-backlog", "forge-5-loop", "forge-6-docs", "complete", "forge-verify-prd", "forge-verify-tech", "forge-verify-specs", "forge-verify-backlog", "forge-verify-impl", "forge-0-epic", "forge-verify-epic"],
|
|
36
|
-
"description": "
|
|
36
|
+
"description": "Where the pipeline IS: the most recently started stage — its `stages[<currentStage>].status` is `in-progress` while that stage is being authored, then `complete` once its artifacts are committed. A stage skill sets this to its own id when it starts. This is deliberately NOT 'the next stage to run': the next stage is DERIVED, never stored — it is the first production stage whose `stages[].status` is not `complete` (see `next_stage()` in forge-session.py, surfaced as the navigator/doctor `nextStage`). Consumers that need 'what runs next' compute it from `stages[].status`, not from this field. `complete` here means the whole pipeline is done. (Legacy/absent value: tools fall back to the derived next stage for display only — `build_rows` in forge-session.py.)"
|
|
37
37
|
},
|
|
38
38
|
"notes": {
|
|
39
39
|
"type": "string",
|
|
@@ -79,6 +79,42 @@
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
|
+
"deferredDecisions": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"description": "Same-feature decisions deliberately postponed to a LATER stage of THIS feature — a structured alternative to burying them in the free-text `notes` string. Distinct from `notes` (unstructured scratch) and from `epicChangeRequests[]` (the epic DECOMPOSITION must change). Use this when a stage would otherwise be tempted to solicit a decision that properly belongs to a downstream stage (e.g. forge-1-prd deferring the concrete cache backend to forge-2-tech): record it here instead of asking now (see the deferred-decisions rule in `references/stage-exit-protocol.md`), and the target stage addresses it. Additive/optional: legacy states without it validate unchanged.",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"required": ["question", "raisedBy", "raisedAt", "status"],
|
|
88
|
+
"additionalProperties": false,
|
|
89
|
+
"properties": {
|
|
90
|
+
"question": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"description": "The decision being deferred, phrased as a concrete question for the target stage to answer."
|
|
93
|
+
},
|
|
94
|
+
"rationale": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"description": "Why it is deferred rather than decided now (e.g. depends on information the target stage produces)."
|
|
97
|
+
},
|
|
98
|
+
"targetStage": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"enum": ["forge-1-prd", "forge-2-tech", "forge-3-specs", "forge-4-backlog", "forge-5-loop", "forge-6-docs"],
|
|
101
|
+
"description": "The stage that should resolve this decision. Omit when unknown/any-later-stage."
|
|
102
|
+
},
|
|
103
|
+
"raisedBy": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"enum": ["forge-1-prd", "forge-2-tech", "forge-3-specs", "forge-4-backlog"],
|
|
106
|
+
"description": "The stage that deferred the decision."
|
|
107
|
+
},
|
|
108
|
+
"raisedAt": { "type": "string", "format": "date-time" },
|
|
109
|
+
"status": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"enum": ["open", "addressed", "dismissed"],
|
|
112
|
+
"default": "open",
|
|
113
|
+
"description": "Lifecycle: open → addressed|dismissed. The target stage flips open→addressed when it resolves the decision (or dismissed if it no longer applies); recording stages only create open entries."
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
82
118
|
"stages": {
|
|
83
119
|
"type": "object",
|
|
84
120
|
"properties": {
|
|
@@ -226,17 +226,31 @@ When `gitCommitAfterStage` is true, follow this exact order to avoid state incon
|
|
|
226
226
|
- **Nothing to commit:** If all artifacts were already committed, this is fine — mark the stage `complete`, leave `commitHash` at its existing value (or `null` if there was never an artifact commit), and skip Commit 2. There is no new artifact commit to record.
|
|
227
227
|
5. **Never** use `git add -A`, `--amend`, `--no-verify`, or `--force` flags
|
|
228
228
|
|
|
229
|
-
##
|
|
229
|
+
## Stage-Entry Guard
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
Invoke this block at the **start of an authoring stage** (`forge-1-prd`..`forge-4-backlog`), **after** Feature Directory Resolution and **before** any interview or (re-)authoring. It prevents a re-entered stage — an injected skill body or a re-invoked `Skill` — from blindly re-running the interview over an in-progress or already-complete draft. `{stage}` is the invoking skill's id (e.g. `forge-2-tech`).
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
2. **Compare against state:** Check the `artifacts` array in the pipeline state for this stage — it tracks files written incrementally during the previous run
|
|
235
|
-
3. **Present options to user:** "This stage was interrupted. Found {N} artifacts from the previous run: {list}. Would you like to resume from where it left off, or restart the stage from scratch?"
|
|
236
|
-
4. **If resume:** Skip artifact generation for files that already exist and appear complete (non-empty, properly structured). Continue from the next unwritten artifact.
|
|
237
|
-
5. **If restart:** Proceed normally. The version number will increment.
|
|
233
|
+
**Read, then classify** this stage's entry in `{resolvedFeatureDir}/.pipeline-state.json` (`stages.{stage}.status`):
|
|
238
234
|
|
|
239
|
-
|
|
235
|
+
1. **Fresh** — no state file yet, or `stages.{stage}` is absent/`pending`. First run of this stage. Proceed to the **Entry Stamp** below, then author normally. No prompt.
|
|
236
|
+
|
|
237
|
+
2. **Interrupted** (`status: "in-progress"`) — a previous run of THIS stage was interrupted before it committed (the exit commit is what flips it to `complete`, so `in-progress` on entry always means a crash/abandon). Do **not** silently re-author. Instead:
|
|
238
|
+
- **Inventory on-disk artifacts:** list the files this stage produces that already exist in `{resolvedFeatureDir}/` (e.g. `PRD.md`; `tech-spec.md`; the `##-*.md` suite + `TRACEABILITY.md`; `backlog.json`), and cross-check against the `stages.{stage}.artifacts` array (written incrementally during the previous run).
|
|
239
|
+
- **Gate via `AskUserQuestion`** (Decision Support protocol): present the inventory as text, then ask "This {stage} run was interrupted — {N} artifact(s) from the previous run are on disk: {list}. Resume the in-progress draft, or start a new version from scratch?" Options: **Resume (recommended)** — continue from the first artifact not yet written/complete, reusing the existing files; do **not** re-stamp or bump the version. · **Start a new version** — treat it as a fresh authoring pass (proceed to the Entry Stamp; the version increments at exit).
|
|
240
|
+
- Skip artifact regeneration for files that already exist and are complete (non-empty, properly structured); continue from the next unwritten artifact.
|
|
241
|
+
|
|
242
|
+
3. **Re-authoring** (`status: "complete"` or `"stale"`) — a finished draft exists. Warn via `AskUserQuestion` before overwriting: "A completed {stage} artifact already exists for '{feature}' (v{n}{, marked stale}). Continuing will create a new version. Proceed?" On confirm, proceed to the Entry Stamp and author a new version (the version increments at exit, per that stage's Update-Pipeline-State step).
|
|
243
|
+
|
|
244
|
+
**Entry Stamp** (fresh, restart, and re-author paths — NOT the resume path). Before authoring, write to `{resolvedFeatureDir}/.pipeline-state.json` and update `updatedAt`:
|
|
245
|
+
- `stages.{stage}.status` → `"in-progress"`
|
|
246
|
+
- `stages.{stage}.startedAt` → current ISO-8601 UTC timestamp
|
|
247
|
+
- top-level `currentStage` → `"{stage}"` (where the pipeline IS, per O1)
|
|
248
|
+
|
|
249
|
+
This write is **left uncommitted**: it is staged and committed as part of this stage's existing exit commit (Git Commit Protocol), so no extra commit is needed at entry. If the run is interrupted after the stamp but before the exit commit, the marker survives on disk (uncommitted) and the next entry classifies as **Interrupted** — which is exactly the intent.
|
|
250
|
+
|
|
251
|
+
**Force Mode.** When `--force` is passed, skip the interactive gate: do not prompt for resume-vs-restart or the re-author warning. Treat entry as a fresh restart — apply the Entry Stamp and author. (`--force` already skips prerequisite checks; here it likewise bypasses the self-stage gate. Existing on-disk artifacts are still loaded per Force Mode.)
|
|
252
|
+
|
|
253
|
+
**Incremental artifact tracking:** When a stage writes multiple files (e.g. forge-3-specs writing a suite of spec documents), update the `stages.{stage}.artifacts` array in `.pipeline-state.json` after writing each file — not just at stage completion. This is what makes the Interrupted inventory above precise about which files were successfully written.
|
|
240
254
|
|
|
241
255
|
## Force Mode
|
|
242
256
|
|
|
@@ -173,6 +173,24 @@ directive is informational — you do **not** re-derive the wording:
|
|
|
173
173
|
Either way the added lines are host-neutral (no literal `/clear`) and sit **above** the
|
|
174
174
|
sentinel; just print the NEXT-STEPS block verbatim as always.
|
|
175
175
|
|
|
176
|
+
### Deferred decisions — do not solicit next-stage decisions at this exit
|
|
177
|
+
|
|
178
|
+
Each stage owns its own decisions. At a stage exit, do **not** pull a *later* stage's
|
|
179
|
+
decision forward — do not ask the user (or decide unilaterally) something that properly
|
|
180
|
+
belongs to the next stage's interview (e.g. at `forge-1-prd` exit, don't settle the
|
|
181
|
+
concrete cache backend that `forge-2-tech` will design). Soliciting it here guesses ahead
|
|
182
|
+
of the stage that owns the context, and the answer has nowhere durable to live.
|
|
183
|
+
|
|
184
|
+
Instead, when you notice a decision that belongs downstream, **record it structurally** as
|
|
185
|
+
a `deferredDecisions[]` entry on this feature's `.pipeline-state.json` (schema in
|
|
186
|
+
`references/pipeline-state-schema.json`; same direct-edit path as `notes` /
|
|
187
|
+
`epicChangeRequests[]`): `question` (phrased for the target stage), optional `rationale`
|
|
188
|
+
and `targetStage`, `raisedBy` (this stage), `raisedAt` (ISO-8601 UTC), `status: "open"`.
|
|
189
|
+
This keeps the exit focused on *this* stage's next-step routing while carrying the open
|
|
190
|
+
question forward for the owning stage to resolve (it flips `status` to `addressed` when it
|
|
191
|
+
does). Prefer a `deferredDecisions[]` entry over stuffing the same thing into the free-text
|
|
192
|
+
`notes` string. This is a recording affordance, not a gate: never block the exit on it.
|
|
193
|
+
|
|
176
194
|
### The NEXT-STEPS block (always last)
|
|
177
195
|
|
|
178
196
|
Print the script's NEXT-STEPS block **verbatim as your absolute last output**. Nothing
|
|
@@ -20,4 +20,13 @@ traceability matrices, and per-feature `backlog.json` files).
|
|
|
20
20
|
reference files under `specs/`, which may be archived or deleted after a feature
|
|
21
21
|
ships.
|
|
22
22
|
|
|
23
|
+
## Tooling feedback
|
|
24
|
+
|
|
25
|
+
Friction with the feature-forge pipeline or the rauf loop — anything confusing, buggy, or
|
|
26
|
+
missing? Capture it while fresh and file it (feature-forge →
|
|
27
|
+
https://github.com/garygentry/feature-forge/issues, rauf →
|
|
28
|
+
https://github.com/garygentry/rauf/issues). See the project-root `AGENTS.md` "Tooling
|
|
29
|
+
feedback" section for the full flow. In an autonomous rauf iteration, note it in
|
|
30
|
+
`progress.md` instead of opening an issue mid-loop.
|
|
31
|
+
|
|
23
32
|
This file was generated by feature-forge. Edit or remove it to suit your project.
|
|
@@ -19,4 +19,13 @@ traceability matrices, and per-feature `backlog.json` files).
|
|
|
19
19
|
skills/agents, configs, docs) should be self-contained and should **not** reference
|
|
20
20
|
files under `specs/`, which may be archived or deleted after a feature ships.
|
|
21
21
|
|
|
22
|
+
## Tooling feedback
|
|
23
|
+
|
|
24
|
+
Friction with the feature-forge pipeline or the rauf loop — anything confusing, buggy, or
|
|
25
|
+
missing? Help me capture it while fresh and file it (feature-forge →
|
|
26
|
+
https://github.com/garygentry/feature-forge/issues, rauf →
|
|
27
|
+
https://github.com/garygentry/rauf/issues). See the project-root `CLAUDE.md` "Tooling
|
|
28
|
+
feedback" section for the full flow. In an autonomous rauf iteration, note it in
|
|
29
|
+
`progress.md` instead of opening an issue mid-loop.
|
|
30
|
+
|
|
22
31
|
This file was generated by feature-forge. Edit or remove it to suit your project.
|
|
@@ -66,6 +66,7 @@ from __future__ import annotations
|
|
|
66
66
|
|
|
67
67
|
import argparse
|
|
68
68
|
import json
|
|
69
|
+
import os
|
|
69
70
|
import subprocess
|
|
70
71
|
import sys
|
|
71
72
|
from datetime import datetime, timezone
|
|
@@ -210,6 +211,12 @@ def next_stage(state: dict) -> str | None:
|
|
|
210
211
|
status is not ``complete`` (a missing/pending/in-progress/stale stage all
|
|
211
212
|
count as "not done"). Returns ``None`` when every production stage is
|
|
212
213
|
complete (nothing left to run).
|
|
214
|
+
|
|
215
|
+
This is the derived "what runs next" value — the single source of truth for
|
|
216
|
+
the next stage. It is intentionally distinct from the stored
|
|
217
|
+
``currentStage`` field ("where the pipeline IS"; see the schema): the next
|
|
218
|
+
stage is computed from ``stages[].status`` here, never read from
|
|
219
|
+
``currentStage``.
|
|
213
220
|
"""
|
|
214
221
|
for stage in PRODUCTION_STAGES:
|
|
215
222
|
if _stage_status(state, stage) != _DONE_STATUS:
|
|
@@ -348,6 +355,9 @@ def build_rows(specs_dir: Path, config: dict | None = None) -> list[FeatureRow]:
|
|
|
348
355
|
rows.append({
|
|
349
356
|
"name": name,
|
|
350
357
|
"epic": epic,
|
|
358
|
+
# currentStage = "where the pipeline IS" (the recorded field). When a
|
|
359
|
+
# legacy/absent state omits it, fall back to the DERIVED next stage
|
|
360
|
+
# for display only — never conflate the two elsewhere (schema O1).
|
|
351
361
|
"currentStage": state.get("currentStage") or (nxt or "complete"),
|
|
352
362
|
"branch": branch if isinstance(branch, str) else None,
|
|
353
363
|
"updatedAt": updated if isinstance(updated, str) else None,
|
|
@@ -710,6 +720,27 @@ def doctor_report(specs_dir: Path, config_path: Path) -> dict:
|
|
|
710
720
|
"counts": _counts(specs_dir),
|
|
711
721
|
"features": features,
|
|
712
722
|
"invalidAutoVerifyKeys": invalid_auto_verify_keys(config),
|
|
723
|
+
"rootSandbox": _root_sandbox_status(),
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def _root_sandbox_status() -> dict:
|
|
728
|
+
"""Report the root/sandbox launch condition for forge-5-loop (issue #99).
|
|
729
|
+
|
|
730
|
+
On a hosted remote (e.g. Claude.ai) the loop runs as root, where rauf's
|
|
731
|
+
``claude --dangerously-skip-permissions`` is refused unless ``IS_SANDBOX``
|
|
732
|
+
is set. forge-5-loop exports ``IS_SANDBOX=${IS_SANDBOX:-1}`` at launch when
|
|
733
|
+
root; this surfaces the same condition as a diagnosable check. ``geteuid``
|
|
734
|
+
is absent on Windows — treat that as non-root.
|
|
735
|
+
"""
|
|
736
|
+
geteuid = getattr(os, "geteuid", None)
|
|
737
|
+
is_root = geteuid() == 0 if geteuid is not None else False
|
|
738
|
+
is_sandbox_set = os.environ.get("IS_SANDBOX") not in (None, "")
|
|
739
|
+
return {
|
|
740
|
+
"isRoot": is_root,
|
|
741
|
+
"isSandboxSet": is_sandbox_set,
|
|
742
|
+
# True only when the loop would need to supply the default at launch.
|
|
743
|
+
"loopWillSetSandbox": is_root and not is_sandbox_set,
|
|
713
744
|
}
|
|
714
745
|
|
|
715
746
|
|
|
@@ -756,6 +787,16 @@ def _print_doctor(report: dict) -> None:
|
|
|
756
787
|
invalid = report.get("invalidAutoVerifyKeys") or []
|
|
757
788
|
if invalid:
|
|
758
789
|
print(" ! invalid autoVerifyStages keys (ignored): " + ", ".join(invalid))
|
|
790
|
+
rs = report.get("rootSandbox") or {}
|
|
791
|
+
if rs.get("isRoot"):
|
|
792
|
+
if rs.get("isSandboxSet"):
|
|
793
|
+
print("root/sandbox: running as root; IS_SANDBOX already set — loop launch OK")
|
|
794
|
+
else:
|
|
795
|
+
print(
|
|
796
|
+
"root/sandbox: running as root; IS_SANDBOX not set — forge-5-loop will "
|
|
797
|
+
"export IS_SANDBOX=1 at launch so rauf's "
|
|
798
|
+
"--dangerously-skip-permissions is not refused"
|
|
799
|
+
)
|
|
759
800
|
|
|
760
801
|
|
|
761
802
|
# --------------------------------------------------------------------------- #
|
|
@@ -69,6 +69,8 @@ Resolve the epic subtree path `{specsDir}/{epic}/` and decide which branch to ru
|
|
|
69
69
|
- **NEW** (no `epic-manifest.json`) → **Creation branch** (Step C1 onward).
|
|
70
70
|
- **EXISTS** → **Edit branch** (§ Edit Mode below).
|
|
71
71
|
|
|
72
|
+
This manifest-existence dispatch **is** forge-0-epic's stage-entry guard: a re-entry on an existing epic lands in Edit Mode (helper-mutated, re-validated) rather than re-running the creation interview, so the **Stage-Entry Guard** block used by `forge-1-prd`..`forge-4-backlog` does not apply here (an epic has no self `.pipeline-state.json` to stamp — it writes member states).
|
|
73
|
+
|
|
72
74
|
3. **Pre-flight epic-name uniqueness (creation only).** Before composing anything for a NEW
|
|
73
75
|
epic, confirm the epic name itself does not collide with any existing feature or epic:
|
|
74
76
|
|
|
@@ -20,7 +20,7 @@ Invoke the **Branch Setup** block in `references/shared-conventions.md` with `{l
|
|
|
20
20
|
|
|
21
21
|
Set the working directory by invoking the **Feature Directory Resolution** block in `references/shared-conventions.md`, which yields `{resolvedFeatureDir}`. Note one PRD-specific caveat: at PRD time a brand-new standalone feature may have NO directory yet, so resolution is expected to fail `not-found` for a never-started standalone feature — in that case forge-1 creates `{specsDir}/{feature}/` as today. For an epic member the directory already exists (created empty by forge-0-epic with an `epic` back-pointer), so resolution succeeds and yields the nested path.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
After resolution, invoke the **Stage-Entry Guard** block in `references/shared-conventions.md` with `{stage}` = `forge-1-prd`. It classifies re-entry (fresh / interrupted / re-authoring), runs the resume-vs-restart gate and the "create a new version?" warning as applicable, and applies the entry stamp on the authoring paths. For a brand-new standalone feature there is no state file yet, so the guard's **fresh** arm applies with nothing to prompt; the entry stamp lands when the state file is first created in Step 6.
|
|
24
24
|
|
|
25
25
|
## Step 2: Examine Existing Context
|
|
26
26
|
|
|
@@ -19,6 +19,8 @@ Read and follow `references/shared-conventions.md` for feature name validation,
|
|
|
19
19
|
|
|
20
20
|
**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 `/feature-forge:forge-1-prd {feature}` first."
|
|
21
21
|
|
|
22
|
+
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.
|
|
23
|
+
|
|
22
24
|
Read `{resolvedFeatureDir}/PRD.md` into context. This is your foundation — every technology decision must trace back to a PRD requirement.
|
|
23
25
|
|
|
24
26
|
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.
|
|
@@ -21,6 +21,8 @@ Read and follow `references/shared-conventions.md` for feature name validation,
|
|
|
21
21
|
|
|
22
22
|
**Prerequisite check:** Read `{resolvedFeatureDir}/.pipeline-state.json`. If not in force mode, both `forge-1-prd` and `forge-2-tech` must be `complete`. If not, STOP and tell the user which prerequisites are missing.
|
|
23
23
|
|
|
24
|
+
After the prerequisite check, invoke the **Stage-Entry Guard** block in `references/shared-conventions.md` with `{stage}` = `forge-3-specs`. Because this stage writes a suite incrementally, the guard's **interrupted** arm uses the `stages.forge-3-specs.artifacts` array (already updated after each spec file — Step 3) to resume from the first unwritten document rather than regenerating the whole suite.
|
|
25
|
+
|
|
24
26
|
Read both `{resolvedFeatureDir}/PRD.md` and `{resolvedFeatureDir}/tech-spec.md` into context.
|
|
25
27
|
|
|
26
28
|
After reading the PRD and tech spec, 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 spec suite is planned.
|
|
@@ -56,7 +58,7 @@ Feature-specific:
|
|
|
56
58
|
|
|
57
59
|
**Then call the host's question mechanism** following the **Decision Support** protocol in `references/shared-conventions.md`: recommend this plan as the default (it's your evidence-backed read of the feature's complexity) and name the trade-off so the user can push back knowingly — more documents means finer separation of concerns but more to keep in sync; fewer means tighter docs but risks one document carrying multiple concerns. Lead with: "I recommend this plan. Add or remove any documents?" Note the guidance below — resist splitting a concern into a sub-50-line document.
|
|
58
60
|
|
|
59
|
-
**Incremental artifact tracking:** After each spec document is written (by you or a writer subagent), immediately update the `artifacts` array in `.pipeline-state.json` with the new file path. This enables crash recovery if the session is interrupted mid-suite (see shared-conventions.md "
|
|
61
|
+
**Incremental artifact tracking:** After each spec document is written (by you or a writer subagent), immediately update the `artifacts` array in `.pipeline-state.json` with the new file path. This enables crash recovery if the session is interrupted mid-suite (see shared-conventions.md "Stage-Entry Guard").
|
|
60
62
|
|
|
61
63
|
## Step 4: Write the Spec Suite
|
|
62
64
|
|
|
@@ -39,6 +39,8 @@ Resolve the **loop runner** from the `loopRunner` block in `forge.config.json`,
|
|
|
39
39
|
|
|
40
40
|
**Prerequisite check:** Read `{resolvedFeatureDir}/.pipeline-state.json`. If not in force mode, stages `forge-1-prd`, `forge-2-tech`, and `forge-3-specs` must all be `complete`. If not, STOP and tell the user which prerequisites are missing.
|
|
41
41
|
|
|
42
|
+
After the prerequisite check, invoke the **Stage-Entry Guard** block in `references/shared-conventions.md` with `{stage}` = `forge-4-backlog` — it detects an interrupted or complete `backlog.json`, runs the resume/restart or new-version gate, and stamps entry before Step 2 loads the specs. (The backlog is a single artifact, so "resume" means: reuse the existing `backlog.json` if the previous run wrote it, rather than re-authoring from scratch.)
|
|
43
|
+
|
|
42
44
|
**Verification check.** Check whether the specs have been verified. If not, use the host's question mechanism to warn with the cost of skipping: "Specs haven't been verified yet. Recommended: run `/feature-forge:forge-verify {feature}` first — unverified specs can carry gaps or contradictions that get baked into backlog items and only surface mid-loop, where they're far more expensive to fix. Continue anyway?" Offer **Verify first (recommended)** · **Continue without verifying**.
|
|
43
45
|
|
|
44
46
|
## Step 2: Load All Specs
|
|
@@ -197,7 +197,7 @@ Then commit this state write before launching (mandatory). The runner refuses to
|
|
|
197
197
|
|
|
198
198
|
### 3b. Launch Background Process
|
|
199
199
|
|
|
200
|
-
Launch the loop **backgrounded** (the host's background-execution mechanism) so it survives session end and does not block the session. For a runner that **persists its own structured event file** (the default — rauf writes `{stateDir}/events.ndjson` natively and rotates it per run), launch the **plain `runCommand`** with **no stdout redirect** and supervise the runner's **native** `events.ndjson` directly; do **not** redirect `--ndjson` into `{stateDir}` (it is redundant and collides with the runner's own writer — see `references/runner-contract.md`). Only a stdout-only runner (no native event file) uses `eventStreamCommand`, redirected to a file **outside** `{stateDir}`. The background task's exit notification is the single authoritative terminal signal (Step 4). Loop runs can take significant time (minutes to hours depending on backlog size). For the exact launch commands (incl. the `mkdir -p` state-dir guard) and the self-persisting vs. stdout-only detail, read `references/runner-contract.md`.
|
|
200
|
+
Launch the loop **backgrounded** (the host's background-execution mechanism) so it survives session end and does not block the session. For a runner that **persists its own structured event file** (the default — rauf writes `{stateDir}/events.ndjson` natively and rotates it per run), launch the **plain `runCommand`** with **no stdout redirect** and supervise the runner's **native** `events.ndjson` directly; do **not** redirect `--ndjson` into `{stateDir}` (it is redundant and collides with the runner's own writer — see `references/runner-contract.md`). Only a stdout-only runner (no native event file) uses `eventStreamCommand`, redirected to a file **outside** `{stateDir}`. The background task's exit notification is the single authoritative terminal signal (Step 4). Loop runs can take significant time (minutes to hours depending on backlog size). For the exact launch commands (incl. the `mkdir -p` state-dir guard and the root→`IS_SANDBOX` sandbox guard) and the self-persisting vs. stdout-only detail, read `references/runner-contract.md`.
|
|
201
201
|
|
|
202
202
|
### 3c. Inform User
|
|
203
203
|
|
|
@@ -101,6 +101,13 @@ default / `claude-cli` path skips this guard (the aliases are valid there).
|
|
|
101
101
|
> rauf `author-backlog` skill to keep `model` **provider-neutral** by default (or to
|
|
102
102
|
> document that writing a tier alias binds the backlog to Claude agents). That lives
|
|
103
103
|
> in the separate rauf plugin/repo, not feature-forge; tracked as a follow-up.
|
|
104
|
+
>
|
|
105
|
+
> **Follow-up (out of scope here — rauf repo).** The durable fix for the root/sandbox
|
|
106
|
+
> refusal (see "Root/sandbox env guard" under Step 3b) is for **rauf itself** to honor
|
|
107
|
+
> `IS_SANDBOX` when it launches `claude --dangerously-skip-permissions` as root (or to
|
|
108
|
+
> detect root+flag-refused and emit a clear error instead of an opaque circuit-break).
|
|
109
|
+
> feature-forge's launch-time export is the mitigation; the upstream fix lives in the
|
|
110
|
+
> rauf plugin/repo. Track as a follow-up.
|
|
104
111
|
|
|
105
112
|
## Optional flags catalog (Step 2d, rauf)
|
|
106
113
|
|
|
@@ -131,6 +138,19 @@ session, then supervise it live via the runner's structured event file.
|
|
|
131
138
|
> changes after that commit, surface it and let the user commit/stash or pass
|
|
132
139
|
> `--force`; never auto-pass `--force`.
|
|
133
140
|
|
|
141
|
+
> **Root/sandbox env guard.** On a hosted remote (e.g. Claude.ai) the loop often runs
|
|
142
|
+
> **as root**. rauf's default Claude launch is `claude -p --dangerously-skip-permissions
|
|
143
|
+
> …`, which the Claude CLI **refuses under root unless `IS_SANDBOX` is set** — the remote
|
|
144
|
+
> container is a legitimate ephemeral sandbox, but without the flag every spawn exits and
|
|
145
|
+
> rauf circuit-breaks (*"3 consecutive infra failures — halting"*) with no hint of the
|
|
146
|
+
> cause. So when — and only when — the launcher is root (`[ "$(id -u)" = 0 ]`), export
|
|
147
|
+
> `IS_SANDBOX="${IS_SANDBOX:-1}"` in front of the launch (an explicitly-set value is
|
|
148
|
+
> honored; the `:-1` only supplies a default). Non-root/local runs are unaffected — the
|
|
149
|
+
> guard is a no-op. **Surface a one-line note** when you set it — e.g. *"running as root →
|
|
150
|
+
> setting IS_SANDBOX=1 so the sandboxed runner can use --dangerously-skip-permissions"* —
|
|
151
|
+
> so the behavior is never silent. `forge-session.py doctor` also reports this condition.
|
|
152
|
+
> Both launch commands below already carry the guard.
|
|
153
|
+
|
|
134
154
|
**Do NOT redirect the run's stdout into `{loopRunner.stateDir}`.** rauf **persists
|
|
135
155
|
its own** `{stateDir}/events.ndjson` (structured) and `{stateDir}/{logFile}` (human)
|
|
136
156
|
natively, and **rotates** them at the start of every run (the prior run's files are
|
|
@@ -150,7 +170,7 @@ rotation timing. So:
|
|
|
150
170
|
(Step 3d). Guard the very first run with the state dir:
|
|
151
171
|
|
|
152
172
|
```
|
|
153
|
-
mkdir -p {backlogDir}/{loopRunner.stateDir} && {rendered runCommand}
|
|
173
|
+
mkdir -p {backlogDir}/{loopRunner.stateDir} && { [ "$(id -u)" = 0 ] && export IS_SANDBOX="${IS_SANDBOX:-1}" || true; } && {rendered runCommand}
|
|
154
174
|
```
|
|
155
175
|
|
|
156
176
|
(Note: the `--ndjson` stdout stream and `loopRunner.eventStreamCommand` are **not**
|
|
@@ -160,7 +180,7 @@ rotation timing. So:
|
|
|
160
180
|
collide with any native file or be swept into `archive/`, then Monitor that file:
|
|
161
181
|
|
|
162
182
|
```
|
|
163
|
-
mkdir -p {backlogDir}/{loopRunner.stateDir} && {rendered eventStreamCommand} > {backlogDir}/forge-events.ndjson 2>&1
|
|
183
|
+
mkdir -p {backlogDir}/{loopRunner.stateDir} && { [ "$(id -u)" = 0 ] && export IS_SANDBOX="${IS_SANDBOX:-1}" || true; } && {rendered eventStreamCommand} > {backlogDir}/forge-events.ndjson 2>&1
|
|
164
184
|
```
|
|
165
185
|
|
|
166
186
|
The background task's exit notification remains the single authoritative terminal
|
|
@@ -22,3 +22,14 @@ This project was scaffolded by forge-bootstrap. To continue development:
|
|
|
22
22
|
- Documents under `specs/` (PRDs, tech specs, numbered implementation specs) establish the backlog. They are **not** kept in sync with the code as it evolves.
|
|
23
23
|
- Do not flag or "fix" divergence between a finalized spec and the implementation — code is the source of truth for behavior.
|
|
24
24
|
- It's fine for `specs/` artifacts and `backlog.json` to reference specs for provenance, but implementation artifacts (source code, generated skills/agents, configs, docs) must not reference spec files, which may be archived or deleted after a feature ships.
|
|
25
|
+
|
|
26
|
+
## Tooling feedback (feature-forge / rauf)
|
|
27
|
+
|
|
28
|
+
This project is driven by the feature-forge pipeline and the rauf loop. Help improve those tools by capturing friction as you hit it — papercuts included, not just outright bugs.
|
|
29
|
+
|
|
30
|
+
- **When to flag:** any forge/rauf command, skill, agent, or prompt that is confusing, buggy, missing a capability, forces a workaround, or produces a surprising result.
|
|
31
|
+
- **Where to file** — route by which tool the friction is with:
|
|
32
|
+
- feature-forge (pipeline stages, forge skills/agents): https://github.com/garygentry/feature-forge/issues
|
|
33
|
+
- rauf (the autonomous loop runner, `rauf` CLI): https://github.com/garygentry/rauf/issues
|
|
34
|
+
- **How:** capture it while fresh — *what you ran / what you expected / what actually happened / a fix idea* — then propose a titled issue and file it with `gh issue create` **on the human's go-ahead, not silently.**
|
|
35
|
+
- **In an autonomous rauf iteration:** do **not** open issues mid-loop. Note the friction in `progress.md` for the human to triage later.
|
|
@@ -23,3 +23,14 @@ This project was scaffolded by forge-bootstrap. To continue development:
|
|
|
23
23
|
- Documents under `specs/` (PRDs, tech specs, numbered implementation specs) establish the backlog. They are **not** kept in sync with the code as it evolves.
|
|
24
24
|
- Don't flag or "fix" divergence between a finalized spec and the implementation — code is the source of truth for behavior.
|
|
25
25
|
- It's fine for `specs/` artifacts and `backlog.json` to reference specs for provenance, but implementation artifacts (source code, generated skills/agents, configs, docs) must not reference spec files, which may be archived or deleted after a feature ships.
|
|
26
|
+
|
|
27
|
+
## Tooling feedback (feature-forge / rauf)
|
|
28
|
+
|
|
29
|
+
I'm driving this project with the feature-forge pipeline and the rauf loop, and I want to keep improving them. When you hit friction with either tool, help me capture it — papercuts included, not just outright bugs.
|
|
30
|
+
|
|
31
|
+
- **When to flag:** any forge/rauf command, skill, agent, or prompt that is confusing, buggy, missing a capability, forces a workaround, or produces a surprising result.
|
|
32
|
+
- **Where to file** — route by which tool the friction is with:
|
|
33
|
+
- feature-forge (pipeline stages, `/feature-forge:*`, forge skills/agents): https://github.com/garygentry/feature-forge/issues
|
|
34
|
+
- rauf (the autonomous loop runner, `rauf` CLI): https://github.com/garygentry/rauf/issues
|
|
35
|
+
- **How:** capture it while fresh — *what you ran / what you expected / what actually happened / a fix idea* — then propose a titled issue and file it with `gh issue create` **once I give the go-ahead, not silently.**
|
|
36
|
+
- **In an autonomous rauf iteration:** don't open issues mid-loop. Note the friction in `progress.md` for me to triage later.
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"currentStage": {
|
|
34
34
|
"type": "string",
|
|
35
35
|
"enum": ["forge-1-prd", "forge-2-tech", "forge-3-specs", "forge-4-backlog", "forge-5-loop", "forge-6-docs", "complete", "forge-verify-prd", "forge-verify-tech", "forge-verify-specs", "forge-verify-backlog", "forge-verify-impl", "forge-0-epic", "forge-verify-epic"],
|
|
36
|
-
"description": "
|
|
36
|
+
"description": "Where the pipeline IS: the most recently started stage — its `stages[<currentStage>].status` is `in-progress` while that stage is being authored, then `complete` once its artifacts are committed. A stage skill sets this to its own id when it starts. This is deliberately NOT 'the next stage to run': the next stage is DERIVED, never stored — it is the first production stage whose `stages[].status` is not `complete` (see `next_stage()` in forge-session.py, surfaced as the navigator/doctor `nextStage`). Consumers that need 'what runs next' compute it from `stages[].status`, not from this field. `complete` here means the whole pipeline is done. (Legacy/absent value: tools fall back to the derived next stage for display only — `build_rows` in forge-session.py.)"
|
|
37
37
|
},
|
|
38
38
|
"notes": {
|
|
39
39
|
"type": "string",
|
|
@@ -79,6 +79,42 @@
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
|
+
"deferredDecisions": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"description": "Same-feature decisions deliberately postponed to a LATER stage of THIS feature — a structured alternative to burying them in the free-text `notes` string. Distinct from `notes` (unstructured scratch) and from `epicChangeRequests[]` (the epic DECOMPOSITION must change). Use this when a stage would otherwise be tempted to solicit a decision that properly belongs to a downstream stage (e.g. forge-1-prd deferring the concrete cache backend to forge-2-tech): record it here instead of asking now (see the deferred-decisions rule in `references/stage-exit-protocol.md`), and the target stage addresses it. Additive/optional: legacy states without it validate unchanged.",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"required": ["question", "raisedBy", "raisedAt", "status"],
|
|
88
|
+
"additionalProperties": false,
|
|
89
|
+
"properties": {
|
|
90
|
+
"question": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"description": "The decision being deferred, phrased as a concrete question for the target stage to answer."
|
|
93
|
+
},
|
|
94
|
+
"rationale": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"description": "Why it is deferred rather than decided now (e.g. depends on information the target stage produces)."
|
|
97
|
+
},
|
|
98
|
+
"targetStage": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"enum": ["forge-1-prd", "forge-2-tech", "forge-3-specs", "forge-4-backlog", "forge-5-loop", "forge-6-docs"],
|
|
101
|
+
"description": "The stage that should resolve this decision. Omit when unknown/any-later-stage."
|
|
102
|
+
},
|
|
103
|
+
"raisedBy": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"enum": ["forge-1-prd", "forge-2-tech", "forge-3-specs", "forge-4-backlog"],
|
|
106
|
+
"description": "The stage that deferred the decision."
|
|
107
|
+
},
|
|
108
|
+
"raisedAt": { "type": "string", "format": "date-time" },
|
|
109
|
+
"status": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"enum": ["open", "addressed", "dismissed"],
|
|
112
|
+
"default": "open",
|
|
113
|
+
"description": "Lifecycle: open → addressed|dismissed. The target stage flips open→addressed when it resolves the decision (or dismissed if it no longer applies); recording stages only create open entries."
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
82
118
|
"stages": {
|
|
83
119
|
"type": "object",
|
|
84
120
|
"properties": {
|
|
@@ -226,17 +226,31 @@ When `gitCommitAfterStage` is true, follow this exact order to avoid state incon
|
|
|
226
226
|
- **Nothing to commit:** If all artifacts were already committed, this is fine — mark the stage `complete`, leave `commitHash` at its existing value (or `null` if there was never an artifact commit), and skip Commit 2. There is no new artifact commit to record.
|
|
227
227
|
5. **Never** use `git add -A`, `--amend`, `--no-verify`, or `--force` flags
|
|
228
228
|
|
|
229
|
-
##
|
|
229
|
+
## Stage-Entry Guard
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
Invoke this block at the **start of an authoring stage** (`forge-1-prd`..`forge-4-backlog`), **after** Feature Directory Resolution and **before** any interview or (re-)authoring. It prevents a re-entered stage — an injected skill body or a re-invoked `Skill` — from blindly re-running the interview over an in-progress or already-complete draft. `{stage}` is the invoking skill's id (e.g. `forge-2-tech`).
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
2. **Compare against state:** Check the `artifacts` array in the pipeline state for this stage — it tracks files written incrementally during the previous run
|
|
235
|
-
3. **Present options to user:** "This stage was interrupted. Found {N} artifacts from the previous run: {list}. Would you like to resume from where it left off, or restart the stage from scratch?"
|
|
236
|
-
4. **If resume:** Skip artifact generation for files that already exist and appear complete (non-empty, properly structured). Continue from the next unwritten artifact.
|
|
237
|
-
5. **If restart:** Proceed normally. The version number will increment.
|
|
233
|
+
**Read, then classify** this stage's entry in `{resolvedFeatureDir}/.pipeline-state.json` (`stages.{stage}.status`):
|
|
238
234
|
|
|
239
|
-
|
|
235
|
+
1. **Fresh** — no state file yet, or `stages.{stage}` is absent/`pending`. First run of this stage. Proceed to the **Entry Stamp** below, then author normally. No prompt.
|
|
236
|
+
|
|
237
|
+
2. **Interrupted** (`status: "in-progress"`) — a previous run of THIS stage was interrupted before it committed (the exit commit is what flips it to `complete`, so `in-progress` on entry always means a crash/abandon). Do **not** silently re-author. Instead:
|
|
238
|
+
- **Inventory on-disk artifacts:** list the files this stage produces that already exist in `{resolvedFeatureDir}/` (e.g. `PRD.md`; `tech-spec.md`; the `##-*.md` suite + `TRACEABILITY.md`; `backlog.json`), and cross-check against the `stages.{stage}.artifacts` array (written incrementally during the previous run).
|
|
239
|
+
- **Gate via `AskUserQuestion`** (Decision Support protocol): present the inventory as text, then ask "This {stage} run was interrupted — {N} artifact(s) from the previous run are on disk: {list}. Resume the in-progress draft, or start a new version from scratch?" Options: **Resume (recommended)** — continue from the first artifact not yet written/complete, reusing the existing files; do **not** re-stamp or bump the version. · **Start a new version** — treat it as a fresh authoring pass (proceed to the Entry Stamp; the version increments at exit).
|
|
240
|
+
- Skip artifact regeneration for files that already exist and are complete (non-empty, properly structured); continue from the next unwritten artifact.
|
|
241
|
+
|
|
242
|
+
3. **Re-authoring** (`status: "complete"` or `"stale"`) — a finished draft exists. Warn via `AskUserQuestion` before overwriting: "A completed {stage} artifact already exists for '{feature}' (v{n}{, marked stale}). Continuing will create a new version. Proceed?" On confirm, proceed to the Entry Stamp and author a new version (the version increments at exit, per that stage's Update-Pipeline-State step).
|
|
243
|
+
|
|
244
|
+
**Entry Stamp** (fresh, restart, and re-author paths — NOT the resume path). Before authoring, write to `{resolvedFeatureDir}/.pipeline-state.json` and update `updatedAt`:
|
|
245
|
+
- `stages.{stage}.status` → `"in-progress"`
|
|
246
|
+
- `stages.{stage}.startedAt` → current ISO-8601 UTC timestamp
|
|
247
|
+
- top-level `currentStage` → `"{stage}"` (where the pipeline IS, per O1)
|
|
248
|
+
|
|
249
|
+
This write is **left uncommitted**: it is staged and committed as part of this stage's existing exit commit (Git Commit Protocol), so no extra commit is needed at entry. If the run is interrupted after the stamp but before the exit commit, the marker survives on disk (uncommitted) and the next entry classifies as **Interrupted** — which is exactly the intent.
|
|
250
|
+
|
|
251
|
+
**Force Mode.** When `--force` is passed, skip the interactive gate: do not prompt for resume-vs-restart or the re-author warning. Treat entry as a fresh restart — apply the Entry Stamp and author. (`--force` already skips prerequisite checks; here it likewise bypasses the self-stage gate. Existing on-disk artifacts are still loaded per Force Mode.)
|
|
252
|
+
|
|
253
|
+
**Incremental artifact tracking:** When a stage writes multiple files (e.g. forge-3-specs writing a suite of spec documents), update the `stages.{stage}.artifacts` array in `.pipeline-state.json` after writing each file — not just at stage completion. This is what makes the Interrupted inventory above precise about which files were successfully written.
|
|
240
254
|
|
|
241
255
|
## Force Mode
|
|
242
256
|
|
|
@@ -173,6 +173,24 @@ directive is informational — you do **not** re-derive the wording:
|
|
|
173
173
|
Either way the added lines are host-neutral (no literal `/clear`) and sit **above** the
|
|
174
174
|
sentinel; just print the NEXT-STEPS block verbatim as always.
|
|
175
175
|
|
|
176
|
+
### Deferred decisions — do not solicit next-stage decisions at this exit
|
|
177
|
+
|
|
178
|
+
Each stage owns its own decisions. At a stage exit, do **not** pull a *later* stage's
|
|
179
|
+
decision forward — do not ask the user (or decide unilaterally) something that properly
|
|
180
|
+
belongs to the next stage's interview (e.g. at `forge-1-prd` exit, don't settle the
|
|
181
|
+
concrete cache backend that `forge-2-tech` will design). Soliciting it here guesses ahead
|
|
182
|
+
of the stage that owns the context, and the answer has nowhere durable to live.
|
|
183
|
+
|
|
184
|
+
Instead, when you notice a decision that belongs downstream, **record it structurally** as
|
|
185
|
+
a `deferredDecisions[]` entry on this feature's `.pipeline-state.json` (schema in
|
|
186
|
+
`references/pipeline-state-schema.json`; same direct-edit path as `notes` /
|
|
187
|
+
`epicChangeRequests[]`): `question` (phrased for the target stage), optional `rationale`
|
|
188
|
+
and `targetStage`, `raisedBy` (this stage), `raisedAt` (ISO-8601 UTC), `status: "open"`.
|
|
189
|
+
This keeps the exit focused on *this* stage's next-step routing while carrying the open
|
|
190
|
+
question forward for the owning stage to resolve (it flips `status` to `addressed` when it
|
|
191
|
+
does). Prefer a `deferredDecisions[]` entry over stuffing the same thing into the free-text
|
|
192
|
+
`notes` string. This is a recording affordance, not a gate: never block the exit on it.
|
|
193
|
+
|
|
176
194
|
### The NEXT-STEPS block (always last)
|
|
177
195
|
|
|
178
196
|
Print the script's NEXT-STEPS block **verbatim as your absolute last output**. Nothing
|
|
@@ -20,4 +20,13 @@ traceability matrices, and per-feature `backlog.json` files).
|
|
|
20
20
|
reference files under `specs/`, which may be archived or deleted after a feature
|
|
21
21
|
ships.
|
|
22
22
|
|
|
23
|
+
## Tooling feedback
|
|
24
|
+
|
|
25
|
+
Friction with the feature-forge pipeline or the rauf loop — anything confusing, buggy, or
|
|
26
|
+
missing? Capture it while fresh and file it (feature-forge →
|
|
27
|
+
https://github.com/garygentry/feature-forge/issues, rauf →
|
|
28
|
+
https://github.com/garygentry/rauf/issues). See the project-root `AGENTS.md` "Tooling
|
|
29
|
+
feedback" section for the full flow. In an autonomous rauf iteration, note it in
|
|
30
|
+
`progress.md` instead of opening an issue mid-loop.
|
|
31
|
+
|
|
23
32
|
This file was generated by feature-forge. Edit or remove it to suit your project.
|