@garygentry/feature-forge 0.2.2 → 0.2.3

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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/adapters/claude/references/forge-config-schema.json +20 -3
  3. package/adapters/claude/references/shared-conventions.md +3 -0
  4. package/adapters/claude/scripts/forge-init.sh +7 -1
  5. package/adapters/claude/scripts/forge-session.py +491 -0
  6. package/adapters/claude/skills/forge/SKILL.md +43 -1
  7. package/adapters/claude/skills/forge-5-loop/SKILL.md +2 -1
  8. package/adapters/claude/skills/forge-5-loop/references/runner-contract.md +41 -15
  9. package/adapters/claude/skills/forge-init/SKILL.md +3 -0
  10. package/adapters/codex/references/forge-config-schema.json +20 -3
  11. package/adapters/codex/references/shared-conventions.md +3 -0
  12. package/adapters/codex/scripts/forge-init.sh +7 -1
  13. package/adapters/codex/scripts/forge-session.py +491 -0
  14. package/adapters/codex/skills/forge/SKILL.md +43 -1
  15. package/adapters/codex/skills/forge-5-loop/SKILL.md +2 -1
  16. package/adapters/codex/skills/forge-5-loop/references/runner-contract.md +41 -15
  17. package/adapters/codex/skills/forge-init/SKILL.md +3 -0
  18. package/adapters/copilot/references/forge-config-schema.json +20 -3
  19. package/adapters/copilot/references/shared-conventions.md +3 -0
  20. package/adapters/copilot/scripts/forge-init.sh +7 -1
  21. package/adapters/copilot/scripts/forge-session.py +491 -0
  22. package/adapters/copilot/skills/forge/forge.md +43 -1
  23. package/adapters/copilot/skills/forge-5-loop/forge-5-loop.md +2 -1
  24. package/adapters/copilot/skills/forge-5-loop/references/runner-contract.md +41 -15
  25. package/adapters/copilot/skills/forge-init/forge-init.md +3 -0
  26. package/adapters/cursor/references/forge-config-schema.json +20 -3
  27. package/adapters/cursor/references/shared-conventions.md +3 -0
  28. package/adapters/cursor/scripts/forge-init.sh +7 -1
  29. package/adapters/cursor/scripts/forge-session.py +491 -0
  30. package/adapters/cursor/skills/forge/forge.mdc +43 -1
  31. package/adapters/cursor/skills/forge-5-loop/forge-5-loop.mdc +2 -1
  32. package/adapters/cursor/skills/forge-5-loop/references/runner-contract.md +41 -15
  33. package/adapters/cursor/skills/forge-init/forge-init.mdc +3 -0
  34. package/adapters/gemini/references/forge-config-schema.json +20 -3
  35. package/adapters/gemini/references/shared-conventions.md +3 -0
  36. package/adapters/gemini/scripts/forge-init.sh +7 -1
  37. package/adapters/gemini/scripts/forge-session.py +491 -0
  38. package/adapters/gemini/skills/forge/forge.md +43 -1
  39. package/adapters/gemini/skills/forge-5-loop/forge-5-loop.md +2 -1
  40. package/adapters/gemini/skills/forge-5-loop/references/runner-contract.md +41 -15
  41. package/adapters/gemini/skills/forge-init/forge-init.md +3 -0
  42. package/dist/manifest.d.ts +1 -1
  43. package/dist/rauf.d.ts +4 -4
  44. package/dist/rauf.js +3 -3
  45. package/dist/types.d.ts +1 -1
  46. package/package.json +1 -1
@@ -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** (`run_in_background: true`) so it survives session end and does not block the session, and prefer the machine-readable event stream (`loopRunner.eventStreamCommand`, default for rauf) redirected to a stable `events.ndjson` so the session can supervise it live; fall back to the plain `runCommand` (tailing the human log) when no `eventStreamCommand` is configured. 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 event-stream vs. log-fallback detail, read `references/runner-contract.md`.
200
+ Launch the loop **backgrounded** (`run_in_background: true`) 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`.
201
201
 
202
202
  ### 3c. Inform User
203
203
 
@@ -295,6 +295,7 @@ Update `{resolvedFeatureDir}/.pipeline-state.json`:
295
295
 
296
296
  ## Gotchas
297
297
 
298
+ - **Plugin-root discovery (1b-epic helper) covers installed paths, not workspace-dev checkouts.** The `forge-root.sh` search in 1b-epic probes `~/.claude/skills/feature-forge`, `~/.claude/plugins/*/feature-forge`, and `./.agents/skills/feature-forge` — the locations of an **installed** plugin. A feature-forge **source checkout** (e.g. `~/workspace/feature-forge`) is not on that list, so the helper exits "cannot locate plugin root." That is expected in a dev environment, not a bug; run the epic-manifest script from the checkout directly (`python3 <checkout>/scripts/epic-manifest.py …`). Note the `~/.claude/plugins/*/feature-forge` glob can also print a zsh "no matches found" line when that dir is empty — harmless.
298
299
  - `{backlogDir}` is a **directory path**, not a file path. Pass `specs/auth`, not `specs/auth/backlog.json`.
299
300
  - rauf resolves `RAUF.md` with fallback: checks `{backlogDir}/.rauf/RAUF.md` first, then the project's `.rauf/RAUF.md`. As long as the runner is installed in the project, the prompt template will be found.
300
301
  - State files (state.json, {loopRunner.logFile}, etc.) are created at `{backlogDir}/{loopRunner.stateDir}/` — this is within the feature's spec directory and is expected. State is isolated per backlog dir, so concurrent features don't collide.
@@ -121,8 +121,7 @@ user requests additional flags, append them to the rendered run command.
121
121
  ## Launch detail (Step 3b — background process)
122
122
 
123
123
  Launch the loop **backgrounded** so it survives session end and does not block the
124
- session, and prefer the machine-readable event stream so the session can supervise
125
- it live.
124
+ session, then supervise it live via the runner's structured event file.
126
125
 
127
126
  > **Clean-tree precondition.** rauf refuses to run with uncommitted changes
128
127
  > (*"Refusing to run the loop with uncommitted changes… pass --force"*). Step 3a's
@@ -132,22 +131,41 @@ it live.
132
131
  > changes after that commit, surface it and let the user commit/stash or pass
133
132
  > `--force`; never auto-pass `--force`.
134
133
 
135
- - **If `loopRunner.eventStreamCommand` is configured (default for rauf):** render it
136
- (it appends `--ndjson` to the run) and launch via the Bash tool with
137
- `run_in_background: true`, redirecting stdout to a stable events file:
134
+ **Do NOT redirect the run's stdout into `{loopRunner.stateDir}`.** rauf **persists
135
+ its own** `{stateDir}/events.ndjson` (structured) and `{stateDir}/{logFile}` (human)
136
+ natively, and **rotates** them at the start of every run (the prior run's files are
137
+ renamed into `{stateDir}/archive/`). A redirect like `… --ndjson >
138
+ {stateDir}/events.ndjson` therefore (a) is **redundant** — the runner writes that
139
+ file regardless — and (b) **collides** with the runner's own writer: the shell holds
140
+ a descriptor on the file the runner immediately rotates away, so the redirected
141
+ `--ndjson` stdout is orphaned into a bogus `archive/` file while the live
142
+ `events.ndjson` is the runner's native stream. It only *looks* clean by accident of
143
+ rotation timing. So:
144
+
145
+ - **Self-persisting runner (default — rauf writes `{stateDir}/events.ndjson`):**
146
+ launch the **plain `runCommand`** with `run_in_background: true` and **no
147
+ redirect** — the Bash tool already captures the run's stdout/stderr to the
148
+ background task's output file (use it to diagnose a launch refusal). Supervise by
149
+ arming the Monitor on the runner's **native** `{backlogDir}/{stateDir}/events.ndjson`
150
+ (Step 3d). Guard the very first run with the state dir:
138
151
 
139
152
  ```
140
- mkdir -p {backlogDir}/{loopRunner.stateDir} && {rendered eventStreamCommand} > {backlogDir}/{loopRunner.stateDir}/events.ndjson 2>&1
153
+ mkdir -p {backlogDir}/{loopRunner.stateDir} && {rendered runCommand}
141
154
  ```
142
155
 
143
- (The `mkdir -p` guards the very first run, before the runner has created its
144
- state dir.) This emits one JSON event per line **and** keeps the loop detached. The background
145
- task's exit notification remains the single authoritative terminal signal (Step 4).
146
- - **Fallback (runner has no `eventStreamCommand`):** launch the plain `runCommand`
147
- with `run_in_background: true`. The session will then supervise by tailing the
148
- human log (3d fallback) instead of the NDJSON file.
156
+ (Note: the `--ndjson` stdout stream and `loopRunner.eventStreamCommand` are **not**
157
+ used on this path — the native file already carries the same structured records.)
158
+ - **Stdout-only runner (no native event file):** render `eventStreamCommand` (it adds
159
+ `--ndjson`) and redirect its stdout to a file **outside `{stateDir}`** so it cannot
160
+ collide with any native file or be swept into `archive/`, then Monitor that file:
149
161
 
150
- Loop runs can take significant time (minutes to hours depending on backlog size).
162
+ ```
163
+ mkdir -p {backlogDir}/{loopRunner.stateDir} && {rendered eventStreamCommand} > {backlogDir}/forge-events.ndjson 2>&1
164
+ ```
165
+
166
+ The background task's exit notification remains the single authoritative terminal
167
+ signal (Step 4). Loop runs can take significant time (minutes to hours depending on
168
+ backlog size).
151
169
 
152
170
  ## Arm a Monitor on the event stream (Step 3d)
153
171
 
@@ -161,16 +179,24 @@ terminal and exception state, not just the happy path — otherwise a crash or h
161
179
  looks identical to "still running." Monitor command (NDJSON path):
162
180
 
163
181
  ```
164
- tail -n +1 -f {backlogDir}/{loopRunner.stateDir}/events.ndjson 2>&1 \
182
+ tail -n +1 -F {backlogDir}/{loopRunner.stateDir}/events.ndjson 2>/dev/null \
165
183
  | jq -rc --unbuffered 'select(.type | test("item_completed|item_blocked|needs_human|signal_parsed|loop_completed|loop_error|loop_cancelled|llm_stuck_warning"))'
166
184
  ```
167
185
 
186
+ > **Use `tail -F` (follow by name), not `-f` (follow by descriptor).** The runner
187
+ > **rotates** `events.ndjson` at the start of each run (renames the prior file into
188
+ > `archive/`, creates a fresh one). A Monitor that attaches with `-f` during that
189
+ > brief rotation window would follow the **archived** inode and then see silence —
190
+ > indistinguishable from a healthy quiet loop. `-F` re-opens the live file by name,
191
+ > so it always tracks the runner's current native stream. (Send `tail`'s own
192
+ > rotation chatter to `/dev/null` so it can't reach the `jq` filter.)
193
+
168
194
  - **Fallback (log tail, no NDJSON):** match the runner's **structured prose
169
195
  prefixes**, never the `RAUF_*` tokens (those leak inside agent output and
170
196
  false-match). For rauf:
171
197
 
172
198
  ```
173
- tail -n +1 -f {backlogDir}/{loopRunner.stateDir}/{loopRunner.logFile} \
199
+ tail -n +1 -F {backlogDir}/{loopRunner.stateDir}/{loopRunner.logFile} 2>/dev/null \
174
200
  | grep -E --line-buffered 'Item [^ ]+ (completed|blocked):|Item [^ ]+ needs human input|Loop completed|Loop error:|Circuit breaker:'
175
201
  ```
176
202
 
@@ -23,6 +23,9 @@ After initialization, the config file will contain defaults for:
23
23
  - `stack`: `null` (detected during `/feature-forge:forge-2-tech`)
24
24
  - `typeCheckCommand`: `null` (set during `/feature-forge:forge-2-tech`)
25
25
  - `testCommand`: `null` (set during `/feature-forge:forge-2-tech`)
26
+ - `autoInvokeNextStage`: `true` (the navigator auto-starts the next stage after you confirm; set `false` to only print the command)
27
+ - `contextWindowTokens`: `null` (the navigator infers the context window; set to your model's window, e.g. `1000000` for a 1M-context model, for accurate context-usage advice)
28
+ - `contextWarnThreshold`: `0.7` (fraction of the window past which the navigator suggests a clean session)
26
29
 
27
30
  If `forge.config.json` already exists, the script will not overwrite it.
28
31
 
@@ -56,6 +56,23 @@
56
56
  "minimum": 1,
57
57
  "description": "Multiplier applied to pending backlog item count to calculate loop iterations. Higher values allow more retries. Default: 1.5 (e.g., 10 items = 15 iterations)."
58
58
  },
59
+ "autoInvokeNextStage": {
60
+ "type": "boolean",
61
+ "default": true,
62
+ "description": "When true (default), the /feature-forge:forge navigator auto-invokes the next pipeline stage via the Skill tool after the user confirms it, instead of only printing the command to copy. Set false to keep the old copy-paste behavior (the navigator suggests the command but never launches it). Ignored on non-Claude hosts, which always fall back to printing the command."
63
+ },
64
+ "contextWindowTokens": {
65
+ "type": ["integer", "null"],
66
+ "default": null,
67
+ "description": "Context window size (tokens) used by the navigator's context-usage check to compute how full the current session is. Null (default) lets the helper infer from the session model and fall back to 200000 — and if observed usage already exceeds 200000 it auto-bumps the assumed window to 1000000 (proof a 1M-beta window is active). Set this explicitly to your model's window (e.g. 1000000 for a 1M-context model) for accurate percentages below 200000 too, since 1M cannot be detected from the transcript until usage crosses 200000."
68
+ },
69
+ "contextWarnThreshold": {
70
+ "type": "number",
71
+ "default": 0.7,
72
+ "minimum": 0,
73
+ "maximum": 1,
74
+ "description": "Fraction of the context window (0-1) past which the navigator recommends starting the next stage in a clean session rather than continuing. Default: 0.7."
75
+ },
59
76
  "workspaces": {
60
77
  "type": "array",
61
78
  "description": "Monorepo members. Absent for single-package projects.",
@@ -94,7 +111,7 @@
94
111
  "eventStreamCommand": {
95
112
  "type": "string",
96
113
  "default": "{bin} loop run . --backlog {backlogDir} --iterations {iterations} --ndjson",
97
- "description": "PREFERRED launch command for forge-5. Same as runCommand but emits one machine-readable JSON event per stdout line (NDJSON): item_completed / item_blocked / needs_human / signal_parsed / loop_completed / loop_error / loop_cancelled / llm_stuck_warning, each with {type, timestamp, projectPath} plus payload (a circuit-breaker halt surfaces as loop_error). forge-5 redirects this stdout to {backlogDir}/{stateDir}/events.ndjson and arms a Monitor on it for live, structured supervision. If a runner cannot emit NDJSON, omit this field — forge-5 falls back to runCommand + tailing the human log."
114
+ "description": "Stdout NDJSON launch command for a runner that does NOT persist its own event file — same as runCommand but emits one machine-readable JSON event per stdout line: item_completed / item_blocked / needs_human / signal_parsed / loop_completed / loop_error / loop_cancelled / llm_stuck_warning, each with {type, timestamp, projectPath} plus payload (a circuit-breaker halt surfaces as loop_error). NOTE: rauf (the default runner) ALREADY persists {stateDir}/events.ndjson natively and rotates it per run, so forge-5 launches the plain runCommand and monitors that native file — it does NOT use this field, and must NOT redirect --ndjson into {stateDir} (redundant, and it collides with the runner's own writer / archive rotation). This field is only for a stdout-only runner with no native event file; forge-5 then redirects its stdout to a file OUTSIDE {stateDir} and monitors that. Omit it entirely for a runner that self-persists or cannot emit NDJSON."
98
115
  },
99
116
  "validateCommand": {
100
117
  "type": "string",
@@ -173,8 +190,8 @@
173
190
  },
174
191
  "installHint": {
175
192
  "type": "string",
176
- "default": "Provision rauf for a multi-agent setup with the cross-agent installer: `npx @garygentry/feature-forge install` (records the pinned @garygentry/rauf@0.10.1 default). Or install/upgrade just the rauf CLI: `npx @garygentry/rauf@0.10.1 --version`, or `curl -fsSL https://raw.githubusercontent.com/garygentry/rauf/main/scripts/install-binary.sh | bash`.",
177
- "description": "Shown when the runner BINARY is missing or too old (version gate fails, minRunnerVersion floor) — how to obtain/upgrade the CLI itself. Names two distinct binary-provisioning paths: (1) the cross-agent installer (`npx @garygentry/feature-forge install`, the multi-agent provisioning path that pins @garygentry/rauf@0.10.1), and (2) the direct rauf-CLI install/upgrade one-liner. Distinct from setupHint (which installs per-project artifacts); a version-gate failure is ALWAYS this hint, never setupHint."
193
+ "default": "Provision rauf for a multi-agent setup with the cross-agent installer: `npx @garygentry/feature-forge install` (records the pinned @garygentry/rauf@0.11.0 default). Or install/upgrade just the rauf CLI: `npx @garygentry/rauf@0.11.0 --version`, or `curl -fsSL https://raw.githubusercontent.com/garygentry/rauf/main/scripts/install-binary.sh | bash`.",
194
+ "description": "Shown when the runner BINARY is missing or too old (version gate fails, minRunnerVersion floor) — how to obtain/upgrade the CLI itself. Names two distinct binary-provisioning paths: (1) the cross-agent installer (`npx @garygentry/feature-forge install`, the multi-agent provisioning path that pins @garygentry/rauf@0.11.0), and (2) the direct rauf-CLI install/upgrade one-liner. Distinct from setupHint (which installs per-project artifacts); a version-gate failure is ALWAYS this hint, never setupHint."
178
195
  },
179
196
  "schemaVersion": {
180
197
  "type": "string",
@@ -68,6 +68,9 @@ Extract these config values (use defaults if not present):
68
68
  - `branchPerFeature` (default: true)
69
69
  - `branchPrefix` (default: `forge/`)
70
70
  - `loopIterationMultiplier` (default: `1.5`)
71
+ - `autoInvokeNextStage` (default: `true` — the `/feature-forge:forge` navigator auto-invokes the next stage via the `Skill` tool after the user confirms; `false` keeps copy-paste behavior. Navigator-only.)
72
+ - `contextWindowTokens` (default: `null` — context window used by the navigator's context-usage check; `null` infers from the session model and falls back to 200000. Set to the model's window, e.g. `1000000` on a 1M model. Navigator-only.)
73
+ - `contextWarnThreshold` (default: `0.7` — fraction of the window past which the navigator recommends a clean session. Navigator-only.)
71
74
  - `loopRunner` (optional object — the loop runner to drive; **defaults to rauf** when absent, with every command templated. See `references/forge-config-schema.json` and `references/ralph-loop-contract.md`.)
72
75
 
73
76
  ## Feature Directory Resolution
@@ -21,7 +21,10 @@ cat > "$CONFIG_FILE" << 'EOF'
21
21
  "stack": null,
22
22
  "typeCheckCommand": null,
23
23
  "testCommand": null,
24
- "loopIterationMultiplier": 1.5
24
+ "loopIterationMultiplier": 1.5,
25
+ "autoInvokeNextStage": true,
26
+ "contextWindowTokens": null,
27
+ "contextWarnThreshold": 0.7
25
28
  }
26
29
  EOF
27
30
 
@@ -37,6 +40,9 @@ echo " stack: null (auto-detected during forge-2-tech)"
37
40
  echo " typeCheckCommand: null (auto-detected during forge-2-tech)"
38
41
  echo " testCommand: null (auto-detected during forge-2-tech)"
39
42
  echo " loopIterationMultiplier: 1.5 (multiplier for loop iterations)"
43
+ echo " autoInvokeNextStage: true (navigator auto-starts the next stage after you confirm)"
44
+ echo " contextWindowTokens: null (infer; set to 1000000 on a 1M-context model)"
45
+ echo " contextWarnThreshold: 0.7 (suggest a clean session past this fraction of the window)"
40
46
  echo ""
41
47
  echo "The loop runner defaults to rauf. To target a different ralph-style runner,"
42
48
  echo "add a \"loopRunner\" block (see references/forge-config-schema.json)."