@garygentry/feature-forge 0.3.8 → 0.3.9

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 (196) hide show
  1. package/README.md +1 -1
  2. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  3. package/adapters/claude/.feature-forge-bundle.json +1 -1
  4. package/adapters/claude/hooks/hooks.json +15 -0
  5. package/adapters/claude/references/forge-config-schema.json +2 -2
  6. package/adapters/claude/references/preflight-and-self-heal.md +1 -1
  7. package/adapters/claude/references/ralph-loop-contract.md +5 -3
  8. package/adapters/claude/references/stage-exit-protocol.md +1 -1
  9. package/adapters/claude/references/vendor-construct-inventory.md +1 -1
  10. package/adapters/claude/scripts/forge-session.py +6 -1
  11. package/adapters/claude/scripts/forge_session/cli.py +2 -1
  12. package/adapters/claude/scripts/forge_session/doctor.py +42 -2
  13. package/adapters/claude/scripts/forge_session/exit.py +5 -5
  14. package/adapters/claude/scripts/forge_session/routes.py +30 -3
  15. package/adapters/claude/scripts/session-check.sh +16 -0
  16. package/adapters/claude/skills/forge/references/stage-exit-protocol.md +1 -1
  17. package/adapters/claude/skills/forge-0-epic/references/stage-exit-protocol.md +1 -1
  18. package/adapters/claude/skills/forge-1-prd/references/stage-exit-protocol.md +1 -1
  19. package/adapters/claude/skills/forge-2-tech/references/stage-exit-protocol.md +1 -1
  20. package/adapters/claude/skills/forge-3-specs/references/stage-exit-protocol.md +1 -1
  21. package/adapters/claude/skills/forge-4-backlog/references/stage-exit-protocol.md +1 -1
  22. package/adapters/claude/skills/forge-5-loop/SKILL.md +12 -22
  23. package/adapters/claude/skills/forge-5-loop/references/preflight-and-self-heal.md +1 -1
  24. package/adapters/claude/skills/forge-5-loop/references/ralph-loop-contract.md +5 -3
  25. package/adapters/claude/skills/forge-5-loop/references/result-reporting.md +78 -16
  26. package/adapters/claude/skills/forge-5-loop/references/runner-contract.md +111 -12
  27. package/adapters/claude/skills/forge-5-loop/references/stage-exit-protocol.md +1 -1
  28. package/adapters/claude/skills/forge-6-docs/references/stage-exit-protocol.md +1 -1
  29. package/adapters/claude/skills/forge-fix/references/stage-exit-protocol.md +1 -1
  30. package/adapters/claude/skills/forge-guide/references/forge-config-schema.json +2 -2
  31. package/adapters/claude/skills/forge-guide/references/preflight-and-self-heal.md +1 -1
  32. package/adapters/claude/skills/forge-guide/references/ralph-loop-contract.md +5 -3
  33. package/adapters/claude/skills/forge-init/SKILL.md +7 -5
  34. package/adapters/claude/skills/forge-init/references/preflight-and-self-heal.md +1 -1
  35. package/adapters/claude/skills/forge-verify/references/stage-exit-protocol.md +1 -1
  36. package/adapters/codex/.feature-forge-bundle.json +1 -1
  37. package/adapters/codex/references/forge-config-schema.json +2 -2
  38. package/adapters/codex/references/preflight-and-self-heal.md +1 -1
  39. package/adapters/codex/references/ralph-loop-contract.md +5 -3
  40. package/adapters/codex/references/stage-exit-protocol.md +1 -1
  41. package/adapters/codex/references/vendor-construct-inventory.md +1 -1
  42. package/adapters/codex/scripts/forge-session.py +6 -1
  43. package/adapters/codex/scripts/forge_session/cli.py +2 -1
  44. package/adapters/codex/scripts/forge_session/doctor.py +42 -2
  45. package/adapters/codex/scripts/forge_session/exit.py +5 -5
  46. package/adapters/codex/scripts/forge_session/routes.py +30 -3
  47. package/adapters/codex/skills/forge/references/stage-exit-protocol.md +1 -1
  48. package/adapters/codex/skills/forge-0-epic/references/stage-exit-protocol.md +1 -1
  49. package/adapters/codex/skills/forge-1-prd/references/stage-exit-protocol.md +1 -1
  50. package/adapters/codex/skills/forge-2-tech/references/stage-exit-protocol.md +1 -1
  51. package/adapters/codex/skills/forge-3-specs/references/stage-exit-protocol.md +1 -1
  52. package/adapters/codex/skills/forge-4-backlog/references/stage-exit-protocol.md +1 -1
  53. package/adapters/codex/skills/forge-5-loop/SKILL.md +12 -22
  54. package/adapters/codex/skills/forge-5-loop/references/preflight-and-self-heal.md +1 -1
  55. package/adapters/codex/skills/forge-5-loop/references/ralph-loop-contract.md +5 -3
  56. package/adapters/codex/skills/forge-5-loop/references/result-reporting.md +78 -16
  57. package/adapters/codex/skills/forge-5-loop/references/runner-contract.md +111 -12
  58. package/adapters/codex/skills/forge-5-loop/references/stage-exit-protocol.md +1 -1
  59. package/adapters/codex/skills/forge-6-docs/references/stage-exit-protocol.md +1 -1
  60. package/adapters/codex/skills/forge-fix/references/stage-exit-protocol.md +1 -1
  61. package/adapters/codex/skills/forge-guide/references/forge-config-schema.json +2 -2
  62. package/adapters/codex/skills/forge-guide/references/preflight-and-self-heal.md +1 -1
  63. package/adapters/codex/skills/forge-guide/references/ralph-loop-contract.md +5 -3
  64. package/adapters/codex/skills/forge-init/SKILL.md +7 -5
  65. package/adapters/codex/skills/forge-init/references/preflight-and-self-heal.md +1 -1
  66. package/adapters/codex/skills/forge-verify/references/stage-exit-protocol.md +1 -1
  67. package/adapters/copilot/.feature-forge-bundle.json +1 -1
  68. package/adapters/copilot/references/forge-config-schema.json +2 -2
  69. package/adapters/copilot/references/preflight-and-self-heal.md +1 -1
  70. package/adapters/copilot/references/ralph-loop-contract.md +5 -3
  71. package/adapters/copilot/references/stage-exit-protocol.md +1 -1
  72. package/adapters/copilot/references/vendor-construct-inventory.md +1 -1
  73. package/adapters/copilot/scripts/forge-session.py +6 -1
  74. package/adapters/copilot/scripts/forge_session/cli.py +2 -1
  75. package/adapters/copilot/scripts/forge_session/doctor.py +42 -2
  76. package/adapters/copilot/scripts/forge_session/exit.py +5 -5
  77. package/adapters/copilot/scripts/forge_session/routes.py +30 -3
  78. package/adapters/copilot/skills/forge/references/stage-exit-protocol.md +1 -1
  79. package/adapters/copilot/skills/forge-0-epic/references/stage-exit-protocol.md +1 -1
  80. package/adapters/copilot/skills/forge-1-prd/references/stage-exit-protocol.md +1 -1
  81. package/adapters/copilot/skills/forge-2-tech/references/stage-exit-protocol.md +1 -1
  82. package/adapters/copilot/skills/forge-3-specs/references/stage-exit-protocol.md +1 -1
  83. package/adapters/copilot/skills/forge-4-backlog/references/stage-exit-protocol.md +1 -1
  84. package/adapters/copilot/skills/forge-5-loop/forge-5-loop.md +12 -22
  85. package/adapters/copilot/skills/forge-5-loop/references/preflight-and-self-heal.md +1 -1
  86. package/adapters/copilot/skills/forge-5-loop/references/ralph-loop-contract.md +5 -3
  87. package/adapters/copilot/skills/forge-5-loop/references/result-reporting.md +78 -16
  88. package/adapters/copilot/skills/forge-5-loop/references/runner-contract.md +111 -12
  89. package/adapters/copilot/skills/forge-5-loop/references/stage-exit-protocol.md +1 -1
  90. package/adapters/copilot/skills/forge-6-docs/references/stage-exit-protocol.md +1 -1
  91. package/adapters/copilot/skills/forge-fix/references/stage-exit-protocol.md +1 -1
  92. package/adapters/copilot/skills/forge-guide/references/forge-config-schema.json +2 -2
  93. package/adapters/copilot/skills/forge-guide/references/preflight-and-self-heal.md +1 -1
  94. package/adapters/copilot/skills/forge-guide/references/ralph-loop-contract.md +5 -3
  95. package/adapters/copilot/skills/forge-init/forge-init.md +7 -5
  96. package/adapters/copilot/skills/forge-init/references/preflight-and-self-heal.md +1 -1
  97. package/adapters/copilot/skills/forge-verify/references/stage-exit-protocol.md +1 -1
  98. package/adapters/cursor/.feature-forge-bundle.json +1 -1
  99. package/adapters/cursor/references/forge-config-schema.json +2 -2
  100. package/adapters/cursor/references/preflight-and-self-heal.md +1 -1
  101. package/adapters/cursor/references/ralph-loop-contract.md +5 -3
  102. package/adapters/cursor/references/stage-exit-protocol.md +1 -1
  103. package/adapters/cursor/references/vendor-construct-inventory.md +1 -1
  104. package/adapters/cursor/scripts/forge-session.py +6 -1
  105. package/adapters/cursor/scripts/forge_session/cli.py +2 -1
  106. package/adapters/cursor/scripts/forge_session/doctor.py +42 -2
  107. package/adapters/cursor/scripts/forge_session/exit.py +5 -5
  108. package/adapters/cursor/scripts/forge_session/routes.py +30 -3
  109. package/adapters/cursor/skills/forge/references/stage-exit-protocol.md +1 -1
  110. package/adapters/cursor/skills/forge-0-epic/references/stage-exit-protocol.md +1 -1
  111. package/adapters/cursor/skills/forge-1-prd/references/stage-exit-protocol.md +1 -1
  112. package/adapters/cursor/skills/forge-2-tech/references/stage-exit-protocol.md +1 -1
  113. package/adapters/cursor/skills/forge-3-specs/references/stage-exit-protocol.md +1 -1
  114. package/adapters/cursor/skills/forge-4-backlog/references/stage-exit-protocol.md +1 -1
  115. package/adapters/cursor/skills/forge-5-loop/forge-5-loop.mdc +12 -22
  116. package/adapters/cursor/skills/forge-5-loop/references/preflight-and-self-heal.md +1 -1
  117. package/adapters/cursor/skills/forge-5-loop/references/ralph-loop-contract.md +5 -3
  118. package/adapters/cursor/skills/forge-5-loop/references/result-reporting.md +78 -16
  119. package/adapters/cursor/skills/forge-5-loop/references/runner-contract.md +111 -12
  120. package/adapters/cursor/skills/forge-5-loop/references/stage-exit-protocol.md +1 -1
  121. package/adapters/cursor/skills/forge-6-docs/references/stage-exit-protocol.md +1 -1
  122. package/adapters/cursor/skills/forge-fix/references/stage-exit-protocol.md +1 -1
  123. package/adapters/cursor/skills/forge-guide/references/forge-config-schema.json +2 -2
  124. package/adapters/cursor/skills/forge-guide/references/preflight-and-self-heal.md +1 -1
  125. package/adapters/cursor/skills/forge-guide/references/ralph-loop-contract.md +5 -3
  126. package/adapters/cursor/skills/forge-init/forge-init.mdc +7 -5
  127. package/adapters/cursor/skills/forge-init/references/preflight-and-self-heal.md +1 -1
  128. package/adapters/cursor/skills/forge-verify/references/stage-exit-protocol.md +1 -1
  129. package/adapters/gemini/.feature-forge-bundle.json +1 -1
  130. package/adapters/gemini/gemini-extension.json +1 -1
  131. package/adapters/gemini/references/forge-config-schema.json +2 -2
  132. package/adapters/gemini/references/preflight-and-self-heal.md +1 -1
  133. package/adapters/gemini/references/ralph-loop-contract.md +5 -3
  134. package/adapters/gemini/references/stage-exit-protocol.md +1 -1
  135. package/adapters/gemini/references/vendor-construct-inventory.md +1 -1
  136. package/adapters/gemini/scripts/forge-session.py +6 -1
  137. package/adapters/gemini/scripts/forge_session/cli.py +2 -1
  138. package/adapters/gemini/scripts/forge_session/doctor.py +42 -2
  139. package/adapters/gemini/scripts/forge_session/exit.py +5 -5
  140. package/adapters/gemini/scripts/forge_session/routes.py +30 -3
  141. package/adapters/gemini/skills/forge/references/stage-exit-protocol.md +1 -1
  142. package/adapters/gemini/skills/forge-0-epic/references/stage-exit-protocol.md +1 -1
  143. package/adapters/gemini/skills/forge-1-prd/references/stage-exit-protocol.md +1 -1
  144. package/adapters/gemini/skills/forge-2-tech/references/stage-exit-protocol.md +1 -1
  145. package/adapters/gemini/skills/forge-3-specs/references/stage-exit-protocol.md +1 -1
  146. package/adapters/gemini/skills/forge-4-backlog/references/stage-exit-protocol.md +1 -1
  147. package/adapters/gemini/skills/forge-5-loop/forge-5-loop.md +12 -22
  148. package/adapters/gemini/skills/forge-5-loop/references/preflight-and-self-heal.md +1 -1
  149. package/adapters/gemini/skills/forge-5-loop/references/ralph-loop-contract.md +5 -3
  150. package/adapters/gemini/skills/forge-5-loop/references/result-reporting.md +78 -16
  151. package/adapters/gemini/skills/forge-5-loop/references/runner-contract.md +111 -12
  152. package/adapters/gemini/skills/forge-5-loop/references/stage-exit-protocol.md +1 -1
  153. package/adapters/gemini/skills/forge-6-docs/references/stage-exit-protocol.md +1 -1
  154. package/adapters/gemini/skills/forge-fix/references/stage-exit-protocol.md +1 -1
  155. package/adapters/gemini/skills/forge-guide/references/forge-config-schema.json +2 -2
  156. package/adapters/gemini/skills/forge-guide/references/preflight-and-self-heal.md +1 -1
  157. package/adapters/gemini/skills/forge-guide/references/ralph-loop-contract.md +5 -3
  158. package/adapters/gemini/skills/forge-init/forge-init.md +7 -5
  159. package/adapters/gemini/skills/forge-init/references/preflight-and-self-heal.md +1 -1
  160. package/adapters/gemini/skills/forge-verify/references/stage-exit-protocol.md +1 -1
  161. package/adapters/pi/.feature-forge-bundle.json +1 -1
  162. package/adapters/pi/references/forge-config-schema.json +2 -2
  163. package/adapters/pi/references/preflight-and-self-heal.md +1 -1
  164. package/adapters/pi/references/ralph-loop-contract.md +5 -3
  165. package/adapters/pi/references/stage-exit-protocol.md +1 -1
  166. package/adapters/pi/references/vendor-construct-inventory.md +1 -1
  167. package/adapters/pi/scripts/forge-session.py +6 -1
  168. package/adapters/pi/scripts/forge_session/cli.py +2 -1
  169. package/adapters/pi/scripts/forge_session/doctor.py +42 -2
  170. package/adapters/pi/scripts/forge_session/exit.py +5 -5
  171. package/adapters/pi/scripts/forge_session/routes.py +30 -3
  172. package/adapters/pi/skills/forge/references/stage-exit-protocol.md +1 -1
  173. package/adapters/pi/skills/forge-0-epic/references/stage-exit-protocol.md +1 -1
  174. package/adapters/pi/skills/forge-1-prd/references/stage-exit-protocol.md +1 -1
  175. package/adapters/pi/skills/forge-2-tech/references/stage-exit-protocol.md +1 -1
  176. package/adapters/pi/skills/forge-3-specs/references/stage-exit-protocol.md +1 -1
  177. package/adapters/pi/skills/forge-4-backlog/references/stage-exit-protocol.md +1 -1
  178. package/adapters/pi/skills/forge-5-loop/SKILL.md +12 -22
  179. package/adapters/pi/skills/forge-5-loop/references/preflight-and-self-heal.md +1 -1
  180. package/adapters/pi/skills/forge-5-loop/references/ralph-loop-contract.md +5 -3
  181. package/adapters/pi/skills/forge-5-loop/references/result-reporting.md +78 -16
  182. package/adapters/pi/skills/forge-5-loop/references/runner-contract.md +111 -12
  183. package/adapters/pi/skills/forge-5-loop/references/stage-exit-protocol.md +1 -1
  184. package/adapters/pi/skills/forge-6-docs/references/stage-exit-protocol.md +1 -1
  185. package/adapters/pi/skills/forge-fix/references/stage-exit-protocol.md +1 -1
  186. package/adapters/pi/skills/forge-guide/references/forge-config-schema.json +2 -2
  187. package/adapters/pi/skills/forge-guide/references/preflight-and-self-heal.md +1 -1
  188. package/adapters/pi/skills/forge-guide/references/ralph-loop-contract.md +5 -3
  189. package/adapters/pi/skills/forge-init/SKILL.md +7 -5
  190. package/adapters/pi/skills/forge-init/references/preflight-and-self-heal.md +1 -1
  191. package/adapters/pi/skills/forge-verify/references/stage-exit-protocol.md +1 -1
  192. package/dist/manifest.d.ts +1 -1
  193. package/dist/rauf.d.ts +3 -3
  194. package/dist/rauf.js +2 -2
  195. package/dist/types.d.ts +1 -1
  196. package/package.json +1 -1
@@ -34,6 +34,16 @@ Runner review pass: {itemsCreated} fix item(s) created and implemented.
34
34
  Omit this line when no `review_completed` event was emitted (no review flag passed).
35
35
  The created items are already counted in the totals above.
36
36
 
37
+ **Review pending** (`reviewPending: true` — the review pass failed or was interrupted,
38
+ and the user chose **Stop here** at the Pending review offer in
39
+ `references/runner-contract.md`). Never render the all-done report for this run, even
40
+ when every item is `done`:
41
+ ```
42
+ Loop finished the backlog for {feature}, but its review pass did not complete.
43
+ Completed: {done}/{total}
44
+ Review pending: {reviewItemIds count} item(s) ({reason from the last `review_failed` event, if any})
45
+ ```
46
+
37
47
  **Some items need a human:**
38
48
  ```
39
49
  Loop completed for {feature}.
@@ -71,8 +81,11 @@ Loop completed for {feature}.
71
81
  Pending: {pending} items ({cause})
72
82
  Blocked: {blocked} items
73
83
  ```
74
- Render `{cause}` as "iteration limit reached" **only** when `iteration == maxIterations`
75
- AND `selectable > 0` — cite the `iteration`/`maxIterations` counters from
84
+ Render `{cause}` from the runner's terminal state first (see **Runner terminal states**
85
+ below): "stopped on request", "the run crashed (stale lock)", or "usage limit — resume
86
+ after {sleepUntil}". Otherwise render it as "iteration limit reached" **only** when
87
+ `loopState` is `ITERATIONS_COMPLETE` (the runner's own attestation, when reported) or
88
+ `iteration == maxIterations`, AND `selectable > 0` — cite the `iteration`/`maxIterations` counters from
76
89
  `{loopRunner.stateDir}/state.json` and `selectable` from `backlog-topology --items-stdin
77
90
  --json` run over the same authoritative item JSON as the counts above. Otherwise —
78
91
  `selectable == 0` with items still pending while `iteration < maxIterations` — the
@@ -93,6 +106,37 @@ the `backlog-topology` output (`selectable`, `blockingRoots`, `gatedCount`,
93
106
  `itemCount`). A cause any of those counters contradicts — e.g. "iteration limit
94
107
  reached" while `iteration < maxIterations` — is a reportable defect.
95
108
 
109
+ ## Runner terminal states (Step 4a)
110
+
111
+ `status --json` may also carry `loopState`, `lock`, `sleepUntil`, `reviewPending` and
112
+ `reviewItemIds`. All are optional: when a field is absent, skip its row and read the
113
+ counts as before. These mirror the rows of rauf's supervisor decision table
114
+ (`drive-rauf-loop`) that forge-5-loop decides the same way.
115
+
116
+ **Clean runner finish.** When `loopState` is reported, only `COMPLETE` or `IDLE` is a
117
+ clean terminal success. Every other value — `ERROR`, `PAUSED` (on request or with a
118
+ stale lock), `ITERATIONS_COMPLETE`, `PAUSED_USAGE_LIMIT`, `WEEKLY_LIMIT`,
119
+ `SLEEPING_LIMIT`, `PAUSED_HUMAN`, a still-live `RUNNING`/`REVIEWING`, or anything
120
+ unrecognized — means the runner did **not** finish cleanly, and the run is **never**
121
+ `complete`, even when `done == total > 0` (e.g. a process killed after its last commit
122
+ but before writing its final state reads `PAUSED` + stale lock with every item done).
123
+ The ladder's rung 2 (runner not finished) catches it, above the needs-human / blocked / deferred rungs. When `loopState` is absent (an older or
124
+ non-rauf runner), this gate does not apply and the counts decide, as before.
125
+
126
+ | Runner state | What it means here |
127
+ |---|---|
128
+ | `backlogSummary.total == 0` (any `loopState`) | Empty **or unreadable** backlog — a read failure reports all-zero counts. Never `complete`: run the **validate command** and treat it as an **operational failure** (below). |
129
+ | `reviewPending: true` | Not complete, whatever the counts: offer the resume (**Pending review**, `references/runner-contract.md`). If the user stops here, ladder rung 2 closes `partial --cause review-pending` — even with blocked, needs-human or deferred items, which are still reported alongside. |
130
+ | `ITERATIONS_COMPLETE` | Iteration budget spent with eligible work left — the "iteration limit reached" `partial` cause. The next loop run gets a fresh budget. |
131
+ | `PAUSED`, `lock.stale` not true | Stopped on request (Ctrl-C, `SIGTERM`, a stop command). Report "stopped on request"; do not offer to relaunch unless the user asks. |
132
+ | `PAUSED`, `lock.stale: true` | The run died mid-iteration and left its lock. rauf's `{bin} resume . --backlog {backlogDir}` clears the stale lock and continues. Never reach for `--force` or `reset` first. |
133
+ | `PAUSED_USAGE_LIMIT` / `WEEKLY_LIMIT` | Halted on a usage limit: "resume after {sleepUntil}". Not a failure. |
134
+ | `ERROR` | Crash or circuit-breaker halt (`loop_error`). Surface the error alongside the count reports; never `complete`. With work left, the next loop run re-runs it; with none, re-entry offers rauf's resume (**Unfinished runner**, `references/runner-contract.md`). |
135
+ | `COMPLETE` with `done < total` | No eligible work left, but items are unfinished (blocked, needs-human, deferred, or pending behind a blocked dependency). The ladder's non-complete rungs apply; never reset the backlog. |
136
+
137
+ **Exit codes are not the outcome.** rauf exits 1 both for a crash and for a failed review
138
+ pass, and 0 for a requested stop or a spent budget. Read the fields above, never `$?`.
139
+
96
140
  ## Selecting the one `LoopOutcome` (Step 7)
97
141
 
98
142
  After Step 5's `state-complete`, select exactly **one** `LoopOutcome` from Step 4a's
@@ -106,26 +150,43 @@ authoritative final counts. Walk this ladder in order and stop at the first matc
106
150
  stop the recovery just cleared is not re-reported as still needing a human. (Step
107
151
  4c runs the procedure on every close, so an empty affected set is the common case —
108
152
  it never selects `resolved`; fall through.)
109
- 2. **`needs-human`** — otherwise, `needsHuman > 0`. This wins even when blocked
153
+ 2. **`partial` (runner not finished)** — otherwise, `reviewPending` is true, **or**
154
+ `loopState` is reported and is not a clean finish (**Clean runner finish**, above).
155
+ Pass `--cause review-pending` when `reviewPending` is true. Otherwise pass
156
+ `--cause runner-stopped`, except for `ITERATIONS_COMPLETE`, which is the plain
157
+ iteration-limit `partial` (no `--cause`). This rung fires **whatever the counts
158
+ say** — including `done == total`, and **above** the needs-human / blocked / deferred
159
+ rungs, following rauf's supervisor table: a crashed, stopped, limit-halted or
160
+ budget-spent runner, or an unfinished review, is recovered or resumed first. It does
161
+ not hide the backlog: the Step 4b needs-human / blocked / deferred reports still
162
+ render alongside it, so every set-aside item is surfaced in the same close (e.g.
163
+ **Stop here** on a pending review with blocked items closes `partial --cause
164
+ review-pending` and still lists the blocked items).
165
+ 3. **`needs-human`** — otherwise, `needsHuman > 0`. This wins even when blocked
110
166
  items also exist: a decision only a human can make outranks work that merely
111
167
  could not proceed.
112
- 3. **`blocked`** — otherwise, genuine `blocked > 0`.
113
- 4. **`deferred`** — otherwise, runner-deferred items exist (the "false blocks" the
168
+ 4. **`blocked`** — otherwise, genuine `blocked > 0`.
169
+ 5. **`deferred`** — otherwise, runner-deferred items exist (the "false blocks" the
114
170
  runner gave up on after retries).
115
- 5. **`partial`** — otherwise, `pending`/`in_progress` items remain because the
116
- iteration limit was reached.
117
- 6. **`complete`** — otherwise, and **only** when every item is `done`.
171
+ 6. **`partial`** — otherwise, `pending`/`in_progress` items remain (the report above
172
+ names the cause). Pass `--cause dependency-starvation` only on the starvation report.
173
+ 7. **`complete`** — otherwise, and **only** when `total > 0`, every item is `done`, no
174
+ review is pending, and the runner finished cleanly (or reports no `loopState`).
118
175
 
119
176
  This is a priority order, not a set. A run reporting both a needs-human and a blocked
120
- count renders both reports above and still exits `needs-human`.
177
+ count renders both reports above and still exits `needs-human`; a run whose runner did
178
+ not finish renders every applicable count report and still exits rung 2's `partial`.
121
179
 
122
180
  **The runner's process exit code is not the outcome.** A loop runner that exits 0 has
123
181
  reported only that its process finished; the final backlog state decides. A clean
124
- exit 0 that still leaves pending items is `partial`, never `complete` — and
125
- `complete` is legitimate only when the counts show every item `done`.
126
-
127
- **Retrying the non-complete outcomes.** `partial`, `deferred`, and `resolved` fence
128
- the loop resume; `blocked` and `needs-human` fence the navigator. Whichever you land on, the
182
+ exit 0 that still leaves pending items is `partial`, never `complete`; an exit 1 from a
183
+ failed review with every item `done` is `partial` (`--cause review-pending`), not an
184
+ error; and a crash or stale-lock stop with every item `done` is `partial` (`--cause
185
+ runner-stopped`). `complete` is legitimate only when the counts show every item `done`,
186
+ no review is pending, and the runner finished cleanly.
187
+
188
+ **Retrying the non-complete outcomes.** `partial` (every cause), `deferred`, and
189
+ `resolved` fence the loop resume; `blocked` and `needs-human` fence the navigator. Whichever you land on, the
129
190
  runner's own retry flags still apply to the next run — e.g. rauf's `--retry-blocked`
130
191
  picks the set-aside blocked and deferred items back up at Step 2d. Mention that as
131
192
  plain prose in the report if it helps; never as a second command block.
@@ -133,8 +194,9 @@ plain prose in the report if it helps; never as a second command block.
133
194
  ## Operational failure before the counts are known
134
195
 
135
196
  If the run cannot produce authoritative counts at all — the status/list command fails,
136
- its output does not parse, the state directory is gone, or the process died in a way
137
- that leaves the backlog unreadable — **do not pick an outcome and do not close the
197
+ its output does not parse, the state directory is gone, the summary reports
198
+ `total == 0` (an empty or unreadable backlog — show the validate command's output), or
199
+ the process died in a way that leaves the backlog unreadable — **do not pick an outcome and do not close the
138
200
  stage.** There is nothing to select from, and guessing one would record a pipeline
139
201
  position that never happened.
140
202
 
@@ -152,12 +152,18 @@ maximum `timeout_ms` (1 hour), and a bounded timeout would silently stop watchin
152
152
  still-running loop.
153
153
 
154
154
  **Coverage-complete filter (silence is not success).** The filter MUST match every
155
- terminal and exception state, not just the happy path — otherwise a crash or hang
156
- looks identical to "still running." Monitor command (NDJSON path):
155
+ terminal, exception, and pause/limit event, not just the happy path — otherwise a crash,
156
+ hang, or hours-long usage sleep looks identical to "still running." The list below covers
157
+ every such type in rauf's event schema (terminal: `loop_completed` / `loop_error` /
158
+ `loop_cancelled` / `loop_paused`; exception: `item_blocked` / `needs_human` /
159
+ `review_failed` / `llm_stuck_warning`; pause/limit: `usage_limit_hit` /
160
+ `usage_limit_cleared` / `sleep_start` / `sleep_end`); only per-iteration narration
161
+ (`iteration_start`, `llm_*` activity, `item_selected`, …) is left out. Monitor command
162
+ (NDJSON path):
157
163
 
158
164
  ```
159
165
  tail -n +1 -F {backlogDir}/{loopRunner.stateDir}/events.ndjson 2>/dev/null \
160
- | jq -rc --unbuffered 'select(.type | test("item_completed|item_blocked|needs_human|signal_parsed|loop_completed|loop_error|loop_cancelled|llm_stuck_warning"))'
166
+ | jq -rc --unbuffered 'select(.type | test("item_completed|item_blocked|needs_human|signal_parsed|loop_completed|loop_error|loop_cancelled|loop_paused|review_failed|llm_stuck_warning|usage_limit_hit|usage_limit_cleared|sleep_start|sleep_end"))'
161
167
  ```
162
168
 
163
169
  > **Use `tail -F` (follow by name), not `-f` (follow by descriptor).** The runner
@@ -174,15 +180,20 @@ tail -n +1 -F {backlogDir}/{loopRunner.stateDir}/events.ndjson 2>/dev/null \
174
180
 
175
181
  ```
176
182
  tail -n +1 -F {backlogDir}/{loopRunner.stateDir}/{loopRunner.logFile} 2>/dev/null \
177
- | grep -E --line-buffered 'Item [^ ]+ (completed|blocked):|Item [^ ]+ needs human input|Loop completed|Loop error:|Circuit breaker:'
183
+ | grep -E --line-buffered 'Item [^ ]+ (completed|blocked):|Item [^ ]+ needs human input|Loop completed|Loop error:|Circuit breaker:|Loop cancelled|Review pass (cancelled|stopped)|Review returned unexpected signal|for review:|[Uu]sage limit'
178
184
  ```
179
185
 
180
186
  (Match `needs human input` **without** a trailing colon — the runner writes
181
- `needs human input (set aside):`.)
187
+ `needs human input (set aside):`. `Loop cancelled` also matches *"Loop cancelled during
188
+ review pass (review pending)"* and the between-iteration / mid-sleep cancels;
189
+ `[Uu]sage limit` matches the 5-hour sleep, weekly limit, detection and wake lines.)
182
190
 
183
191
  If the Monitor is ever auto-stopped for event volume, re-arm with a tighter filter
184
192
  (drop `item_completed`, keep the exception/terminal events).
185
193
 
194
+ An older runner that never emits a listed type (e.g. `review_failed`) simply never
195
+ matches it — the filter needs no version gate.
196
+
186
197
  ## React to events as they land (Step 3e)
187
198
 
188
199
  Each Monitor event arrives as a message. React per type — but keep the user signal
@@ -210,13 +221,101 @@ high and the noise low:
210
221
  - **`loop_error`** → a real failure (this is also what a circuit-breaker halt — too many
211
222
  consecutive infra failures — emits). Surface now and `PushNotification`. Offer
212
223
  inspection / `--force` / re-run as appropriate.
213
- - **Stall detection** → rauf emits an **`llm_stuck_warning`** event when an iteration
214
- stops making progress; the filter above includes it, so surface it live (a hang
215
- warning, not yet a failure) and offer `--force` if it persists. If you instead want to
216
- probe on quiet, run `{rendered watchCommand}` (or read
217
- `{backlogDir}/{loopRunner.stateDir}/iteration-status.json`) and key off its
218
- `stuckWarning` flag. Do **not** infer a stall from `state.json.updatedAt` alone — it is
219
- not a liveness proof.
224
+ - **`review_failed`** → the post-loop review pass failed or was stopped by a usage limit
225
+ (payload `reason`). Surface it now with a `PushNotification`. The run is **not**
226
+ complete: the review stays pending (`status --json` → `reviewPending: true`). A failed
227
+ review makes `loop run --review` exit **1**; a usage-limit stop is a resumable limit
228
+ stop, not that exit-1 case. A review **cancelled** mid-pass emits **no**
229
+ `review_failed` — only `loop_cancelled` (human log: *"Loop cancelled during review pass
230
+ (review pending)"*) with `reviewPending` still set — so never rely on this event alone.
231
+ Nothing to do live — Step 4a reads `reviewPending` and offers the resume (**Pending
232
+ review**, below).
233
+ - **Stall detection** → rauf emits an **`llm_stuck_warning`** event when an iteration's
234
+ output stream goes silent. Surface it live as a stall warning, not yet a failure. Read
235
+ its optional `currentTool` / `toolRunningMs` (absent on older runners — then report
236
+ the plain `silentMs` and treat it as a possible hang):
237
+ - `currentTool: null` → **the model itself went silent** with no tool in flight — a
238
+ likely hang. Offer `--force` / re-run if it persists.
239
+ - `currentTool` set → **a quiet tool call outlived the tool ceiling** — report it as
240
+ e.g. *"Bash running 31m"* (`toolRunningMs` rounded to minutes). This is usually a
241
+ slow verification gate, not a hung LLM: say so, and let it run unless it is clearly
242
+ wedged.
243
+
244
+ The thresholds are the runner's `.rauf.json` `options.stuckThresholdMs` (silence
245
+ before the warning, default 5 min) and `options.toolStuckThresholdMs` (how long a
246
+ quiet in-flight tool holds the warning off, default 30 min, measured from the tool's
247
+ start). A repo whose verification gate legitimately runs longer than 30 min should
248
+ raise `toolStuckThresholdMs` rather than learn to ignore the warning. If you instead
249
+ want to probe on quiet, run `{rendered watchCommand}` and key off `health.stuckWarning`.
250
+ Do **not** infer a stall from `state.json.updatedAt` alone — it is not a liveness
251
+ proof.
252
+ - **Usage-limit waits are not stalls.** `usage_limit_hit` (`limitType`, optional
253
+ `reason: "usage_api_disagreement"`) followed by `sleep_start` (`sleepUntil`, `reason`)
254
+ means the runner is sleeping until the limit resets (`SLEEPING_LIMIT`) and will resume
255
+ on its own: surface it once with its `sleepUntil`, and send a `PushNotification` when
256
+ the sleep is long (a 5-hour window). A `sleep_start` whose `reason` begins *"Usage-limit
257
+ banner unconfirmed"* is a 30 s / 60 s backoff (`status --json` still `RUNNING` with
258
+ `sleepUntil`) — narrate it at most briefly. `sleep_end` / `usage_limit_cleared` → the
259
+ loop is working again. A weekly or no-sleep limit halts the run instead (the process
260
+ exits; Step 4a reads `WEEKLY_LIMIT` / `PAUSED_USAGE_LIMIT`). None of these is a stall:
261
+ never offer `--force`.
262
+ - **`loop_paused`** (`reason: "needs_human"`, `itemId`) → the run halted on a needs-human
263
+ item (only under rauf's opt-in `--pause-on-needs-human`; forge does not pass it by
264
+ default). Unlike the default set-aside mode, the loop **is** now stopped: surface it
265
+ with a `PushNotification`; the process exits and Step 4c's recovery pass handles the
266
+ answer.
267
+
268
+ **Pending review (Steps 2a / 4a, rauf).** A rauf `--review` run whose review pass failed, was cancelled, or was stopped by a usage
269
+ limit leaves `status --json` with `reviewPending: true` and `reviewItemIds` (the review's
270
+ exact scope). The loop state can read `COMPLETE`/`IDLE` with every item `done`, or
271
+ `PAUSED`/`PAUSED_USAGE_LIMIT` — either way the run is **not complete**, and the process
272
+ exit code (1 for a failed review) does not decide it. Both fields are optional: a runner
273
+ that never reports them never takes this path.
274
+
275
+ rauf's `resume` re-runs exactly that review (`rauf loop review --items <reviewItemIds>`),
276
+ not a fresh loop. So when Step 4a or Step 2a sees `reviewPending: true`:
277
+
278
+ 1. Report it: *"The review pass for {feature} did not finish ({reason from the last
279
+ `review_failed` event, if any}); it covers {reviewItemIds}."*
280
+ 2. Via `AskUserQuestion`, offer **Resume the review now (recommended)** · **Stop here**. On a
281
+ usage-limit stop (`PAUSED_USAGE_LIMIT` / `WEEKLY_LIMIT`), say the resume only helps
282
+ once the limit resets (`sleepUntil`). A stop the user requested (`PAUSED`, lock
283
+ released) is theirs: offer, never auto-resume.
284
+ 3. **Resume:** launch `{bin} resume . --backlog {backlogDir}` backgrounded, exactly as a
285
+ run command (Step 3b launch guards, 3d Monitor, 3f completion), then return to Step 4a.
286
+ A successful review may file fix items (`review_completed.itemsCreated`); those are
287
+ ordinary pending work for the next loop run. rauf reviews **before** relaunching: when
288
+ pending items also remain (the review ran after the iteration budget ran out, or
289
+ blocked items strand their dependents), `resume` re-runs only the review and the
290
+ remaining items wait for the next loop run — say so, never launch a fresh `loop run`
291
+ first (it overwrites the state and drops the pending review).
292
+ 4. **Stop here:** Step 7 closes `partial` with `--cause review-pending` (see
293
+ `references/result-reporting.md`) — a resume route whose Step 2a re-offers this.
294
+
295
+ At rung 3 (no question mechanism), do not launch: print the rendered resume command and
296
+ close as in 4.
297
+
298
+ **Unfinished runner (Step 2a re-entry, rauf).** When Step 2a finds no pending or
299
+ in_progress items and no pending review, but `status --json` reports a `loopState` that
300
+ is not a clean finish (**Clean runner finish** in `references/result-reporting.md` —
301
+ e.g. `ERROR`, `PAUSED` with `lock.stale`, `ITERATIONS_COMPLETE`, a usage halt), the
302
+ prior run closed as `partial` with `--cause runner-stopped` (or the plain iteration-limit
303
+ `partial`), and "Nothing to run" would strand it. Instead:
304
+
305
+ 1. Report the state: *"The last run for {feature} did not finish cleanly ({loopState}
306
+ {— stale lock | — resets {sleepUntil}})."*
307
+ 2. Via `AskUserQuestion`, offer **Resume the runner (recommended)** · **Stop here**. For a
308
+ usage halt, the resume helps only once the limit resets; for `PAUSED` on request
309
+ (lock released), the stop was the user's — offer, never auto-resume.
310
+ 3. **Resume:** launch `{bin} resume . --backlog {backlogDir}` backgrounded exactly as in
311
+ **Pending review** step 3 (it clears a stale lock and finishes the run's bookkeeping,
312
+ including any post-loop review), then continue at Step 4a. rauf's recovery for
313
+ `ERROR` is `resume` or `reset` + re-run — never reach for `reset` or `--force` first.
314
+ 4. **Stop here**, or rung 3 (print the rendered command, do not launch): STOP without
315
+ touching the stage — it is already recorded `in-progress`.
316
+
317
+ When `loopState` is absent, this never fires: an older or non-rauf runner keeps the
318
+ plain "Nothing to run" stop.
220
319
 
221
320
  ## Inform-user output template (Step 3c)
222
321
 
@@ -47,7 +47,7 @@ an epic member. Only the flags below are stage-specific; pass no others.
47
47
  |---|---|---|
48
48
  | `forge-0-epic` | `forge-0-epic` | `--next-feature "{member}"` when a concrete member exists |
49
49
  | `forge-1-prd` … `forge-4-backlog` | that stage's own id | none beyond identity/capability |
50
- | `forge-5-loop` | `forge-5-loop` | `--outcome` — one of `complete`, `partial`, `blocked`, `needs-human`, `deferred`, `resolved`; optional `--cause dependency-starvation` with `--outcome partial` |
50
+ | `forge-5-loop` | `forge-5-loop` | `--outcome` — one of `complete`, `partial`, `blocked`, `needs-human`, `deferred`, `resolved`; optional `--cause dependency-starvation`, `review-pending` or `runner-stopped` with `--outcome partial` |
51
51
  | `forge-6-docs` | `forge-6-docs` | `--outcome` — `complete`, `blocked`, or `skipped` (deliberate docs skip, persisted via `state-skip` before the exit; routes like `complete` with honest wording) |
52
52
  | direct `forge-verify` | `forge-verify` | `--owner direct`, `--outcome` (`passed`, `findings`, `skipped`, `failed`), and served-stage metadata |
53
53
  | nested `forge-verify` | `forge-verify` | `--owner nested`, plus the same outcome and served-stage metadata |
@@ -47,7 +47,7 @@ an epic member. Only the flags below are stage-specific; pass no others.
47
47
  |---|---|---|
48
48
  | `forge-0-epic` | `forge-0-epic` | `--next-feature "{member}"` when a concrete member exists |
49
49
  | `forge-1-prd` … `forge-4-backlog` | that stage's own id | none beyond identity/capability |
50
- | `forge-5-loop` | `forge-5-loop` | `--outcome` — one of `complete`, `partial`, `blocked`, `needs-human`, `deferred`, `resolved`; optional `--cause dependency-starvation` with `--outcome partial` |
50
+ | `forge-5-loop` | `forge-5-loop` | `--outcome` — one of `complete`, `partial`, `blocked`, `needs-human`, `deferred`, `resolved`; optional `--cause dependency-starvation`, `review-pending` or `runner-stopped` with `--outcome partial` |
51
51
  | `forge-6-docs` | `forge-6-docs` | `--outcome` — `complete`, `blocked`, or `skipped` (deliberate docs skip, persisted via `state-skip` before the exit; routes like `complete` with honest wording) |
52
52
  | direct `forge-verify` | `forge-verify` | `--owner direct`, `--outcome` (`passed`, `findings`, `skipped`, `failed`), and served-stage metadata |
53
53
  | nested `forge-verify` | `forge-verify` | `--owner nested`, plus the same outcome and served-stage metadata |
@@ -47,7 +47,7 @@ an epic member. Only the flags below are stage-specific; pass no others.
47
47
  |---|---|---|
48
48
  | `forge-0-epic` | `forge-0-epic` | `--next-feature "{member}"` when a concrete member exists |
49
49
  | `forge-1-prd` … `forge-4-backlog` | that stage's own id | none beyond identity/capability |
50
- | `forge-5-loop` | `forge-5-loop` | `--outcome` — one of `complete`, `partial`, `blocked`, `needs-human`, `deferred`, `resolved`; optional `--cause dependency-starvation` with `--outcome partial` |
50
+ | `forge-5-loop` | `forge-5-loop` | `--outcome` — one of `complete`, `partial`, `blocked`, `needs-human`, `deferred`, `resolved`; optional `--cause dependency-starvation`, `review-pending` or `runner-stopped` with `--outcome partial` |
51
51
  | `forge-6-docs` | `forge-6-docs` | `--outcome` — `complete`, `blocked`, or `skipped` (deliberate docs skip, persisted via `state-skip` before the exit; routes like `complete` with honest wording) |
52
52
  | direct `forge-verify` | `forge-verify` | `--owner direct`, `--outcome` (`passed`, `findings`, `skipped`, `failed`), and served-stage metadata |
53
53
  | nested `forge-verify` | `forge-verify` | `--owner nested`, plus the same outcome and served-stage metadata |
@@ -235,8 +235,8 @@
235
235
  },
236
236
  "installHint": {
237
237
  "type": "string",
238
- "default": "Provision rauf for a multi-agent setup with the cross-agent installer: `npx @garygentry/feature-forge install` (records the pinned @garygentry/rauf@0.16.1 default). Or install/upgrade just the rauf CLI: `npx @garygentry/rauf@0.16.1 --version`, or `curl -fsSL https://raw.githubusercontent.com/garygentry/rauf/main/scripts/install-binary.sh | bash`.",
239
- "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.16.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."
238
+ "default": "Provision rauf for a multi-agent setup with the cross-agent installer: `npx @garygentry/feature-forge install` (records the pinned @garygentry/rauf@0.17.1 default). Or install/upgrade just the rauf CLI: `npx @garygentry/rauf@0.17.1 --version`, or `curl -fsSL https://raw.githubusercontent.com/garygentry/rauf/main/scripts/install-binary.sh | bash`.",
239
+ "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.17.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."
240
240
  },
241
241
  "schemaVersion": {
242
242
  "type": "string",
@@ -6,7 +6,7 @@ silent unasked mutation. It runs whenever a skill gates on `doctor`'s structured
6
6
  `checks[]` (`roadmap/self-healing-resilience.md` §5.2); today that is `forge-5-loop`'s
7
7
  gates 1c/1d (`skills/forge-5-loop/SKILL.md`), which resolve the loop runner **before**
8
8
  touching it, `forge-init`'s install preflight (`skills/forge-init/SKILL.md`), where
9
- neither check is a stop, and `forge-guide --doctor` (`skills/forge-guide/SKILL.md`), the
9
+ no check is a stop, and `forge-guide --doctor` (`skills/forge-guide/SKILL.md`), the
10
10
  operator-facing repair surface, which gates nothing at all. All three are callers, not
11
11
  the procedure's scope: any skill that gates on `checks[]` follows it in full. Its seven
12
12
  ordered steps: **enumerate → cluster → consolidated prompts → record → apply → prove →
@@ -40,9 +40,11 @@ defined authoritatively in rauf's
40
40
  - **A machine-readable event stream** for live supervision (`loopRunner.eventStreamCommand`,
41
41
  rauf: `loop run … --ndjson`): one JSON event per line with a stable `type`
42
42
  vocabulary — `item_completed` / `item_blocked` / `needs_human` / `signal_parsed`
43
- / `loop_completed` / `loop_error` / `loop_cancelled` / `llm_stuck_warning` (a
44
- circuit-breaker halt surfaces as `loop_error`) — plus a
45
- derived-status JSON (`loopRunner.statusJsonCommand`, rauf: `status … --json`) and
43
+ / `loop_completed` / `loop_error` / `loop_cancelled` / `review_failed` /
44
+ `llm_stuck_warning` (a circuit-breaker halt surfaces as `loop_error`) — plus a
45
+ derived-status JSON (`loopRunner.statusJsonCommand`, rauf: `status … --json`;
46
+ forge-5-loop also reads its optional `loopState` / `lock` / `sleepUntil` /
47
+ `reviewPending` fields when present, and degrades to the counts when absent) and
46
48
  per-iteration telemetry with a `stuckWarning` flag (`loopRunner.watchCommand`,
47
49
  rauf: `status … --json` — the `loop watch` verb was removed in v0.5.0). `forge-5-loop` supervises the run through these,
48
50
  **not** by parsing the human log. `followCommand` / `logCommand` are
@@ -79,11 +79,11 @@ Once the config exists, check the tooling this project's pipeline will lean on:
79
79
  R="$(bash -c '[ -z "${FEATURE_FORGE_ROOT:-}" ] || [ -x "$FEATURE_FORGE_ROOT/scripts/forge-root.sh" ] || { echo "feature-forge: FEATURE_FORGE_ROOT=$FEATURE_FORGE_ROOT has no scripts/forge-root.sh" >&2; exit 2; }; for d in "${FEATURE_FORGE_ROOT:-}" "${CLAUDE_PLUGIN_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')"
80
80
  [ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
81
81
  python3 "$R/scripts/forge-session.py" doctor --json \
82
- --check root-version-skew --check gh-available
82
+ --check plugin-root --check root-version-skew --check gh-available
83
83
  ```
84
84
 
85
85
  Follow `references/preflight-and-self-heal.md` with that result: all `ok`/`na` → say nothing and
86
- move on. Otherwise cluster and report. **Neither check is a stop for `forge-init`** — report and
86
+ move on. Otherwise cluster and report. **No check here is a stop for `forge-init`** — report and
87
87
  continue; a `warn` here never aborts initialization (the procedure returns statuses; the caller
88
88
  owns its own gate). `gh-available` matters because the tooling-feedback step below tells agents
89
89
  to file issues with `gh issue create`; when `gh` is missing or unauthenticated, say so once — its
@@ -94,9 +94,11 @@ interrogated about it on every init.
94
94
  `root-version-skew` is reported the same way: a second install shadowing this one is worth
95
95
  knowing about before the first stage runs.
96
96
 
97
- (`plugin-root` is deliberately **not** in that list. The prelude above already exits 1 when the
98
- resolver fails, so by the time `doctor` runs the check can only be `ok` — narrowing to checks
99
- that cannot fire would be theatre.)
97
+ `plugin-root` is reported the same way, **advisory here despite its `blocking` severity**. The
98
+ prelude above already exits 1 when the resolver fails, so below it the check warns only when the
99
+ resolved root is un-built canon (no `.feature-forge-bundle.json` — a repo checkout loaded
100
+ directly): its skills cannot read their shared references and will break mid-stage. Say so once
101
+ with the check's `remedy.description` (advise-only, `network`) and continue.
100
102
 
101
103
  ## Root hygiene — tooling feedback
102
104
 
@@ -6,7 +6,7 @@ silent unasked mutation. It runs whenever a skill gates on `doctor`'s structured
6
6
  `checks[]` (`roadmap/self-healing-resilience.md` §5.2); today that is `forge-5-loop`'s
7
7
  gates 1c/1d (`skills/forge-5-loop/SKILL.md`), which resolve the loop runner **before**
8
8
  touching it, `forge-init`'s install preflight (`skills/forge-init/SKILL.md`), where
9
- neither check is a stop, and `forge-guide --doctor` (`skills/forge-guide/SKILL.md`), the
9
+ no check is a stop, and `forge-guide --doctor` (`skills/forge-guide/SKILL.md`), the
10
10
  operator-facing repair surface, which gates nothing at all. All three are callers, not
11
11
  the procedure's scope: any skill that gates on `checks[]` follows it in full. Its seven
12
12
  ordered steps: **enumerate → cluster → consolidated prompts → record → apply → prove →
@@ -47,7 +47,7 @@ an epic member. Only the flags below are stage-specific; pass no others.
47
47
  |---|---|---|
48
48
  | `forge-0-epic` | `forge-0-epic` | `--next-feature "{member}"` when a concrete member exists |
49
49
  | `forge-1-prd` … `forge-4-backlog` | that stage's own id | none beyond identity/capability |
50
- | `forge-5-loop` | `forge-5-loop` | `--outcome` — one of `complete`, `partial`, `blocked`, `needs-human`, `deferred`, `resolved`; optional `--cause dependency-starvation` with `--outcome partial` |
50
+ | `forge-5-loop` | `forge-5-loop` | `--outcome` — one of `complete`, `partial`, `blocked`, `needs-human`, `deferred`, `resolved`; optional `--cause dependency-starvation`, `review-pending` or `runner-stopped` with `--outcome partial` |
51
51
  | `forge-6-docs` | `forge-6-docs` | `--outcome` — `complete`, `blocked`, or `skipped` (deliberate docs skip, persisted via `state-skip` before the exit; routes like `complete` with honest wording) |
52
52
  | direct `forge-verify` | `forge-verify` | `--owner direct`, `--outcome` (`passed`, `findings`, `skipped`, `failed`), and served-stage metadata |
53
53
  | nested `forge-verify` | `forge-verify` | `--owner nested`, plus the same outcome and served-stage metadata |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feature-forge",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "agent": "codex",
5
5
  "generatedBy": "python3 scripts/build-adapters.py"
6
6
  }
@@ -235,8 +235,8 @@
235
235
  },
236
236
  "installHint": {
237
237
  "type": "string",
238
- "default": "Provision rauf for a multi-agent setup with the cross-agent installer: `npx @garygentry/feature-forge install` (records the pinned @garygentry/rauf@0.16.1 default). Or install/upgrade just the rauf CLI: `npx @garygentry/rauf@0.16.1 --version`, or `curl -fsSL https://raw.githubusercontent.com/garygentry/rauf/main/scripts/install-binary.sh | bash`.",
239
- "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.16.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."
238
+ "default": "Provision rauf for a multi-agent setup with the cross-agent installer: `npx @garygentry/feature-forge install` (records the pinned @garygentry/rauf@0.17.1 default). Or install/upgrade just the rauf CLI: `npx @garygentry/rauf@0.17.1 --version`, or `curl -fsSL https://raw.githubusercontent.com/garygentry/rauf/main/scripts/install-binary.sh | bash`.",
239
+ "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.17.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."
240
240
  },
241
241
  "schemaVersion": {
242
242
  "type": "string",
@@ -6,7 +6,7 @@ silent unasked mutation. It runs whenever a skill gates on `doctor`'s structured
6
6
  `checks[]` (`roadmap/self-healing-resilience.md` §5.2); today that is `forge-5-loop`'s
7
7
  gates 1c/1d (`skills/forge-5-loop/SKILL.md`), which resolve the loop runner **before**
8
8
  touching it, `forge-init`'s install preflight (`skills/forge-init/SKILL.md`), where
9
- neither check is a stop, and `forge-guide --doctor` (`skills/forge-guide/SKILL.md`), the
9
+ no check is a stop, and `forge-guide --doctor` (`skills/forge-guide/SKILL.md`), the
10
10
  operator-facing repair surface, which gates nothing at all. All three are callers, not
11
11
  the procedure's scope: any skill that gates on `checks[]` follows it in full. Its seven
12
12
  ordered steps: **enumerate → cluster → consolidated prompts → record → apply → prove →
@@ -40,9 +40,11 @@ defined authoritatively in rauf's
40
40
  - **A machine-readable event stream** for live supervision (`loopRunner.eventStreamCommand`,
41
41
  rauf: `loop run … --ndjson`): one JSON event per line with a stable `type`
42
42
  vocabulary — `item_completed` / `item_blocked` / `needs_human` / `signal_parsed`
43
- / `loop_completed` / `loop_error` / `loop_cancelled` / `llm_stuck_warning` (a
44
- circuit-breaker halt surfaces as `loop_error`) — plus a
45
- derived-status JSON (`loopRunner.statusJsonCommand`, rauf: `status … --json`) and
43
+ / `loop_completed` / `loop_error` / `loop_cancelled` / `review_failed` /
44
+ `llm_stuck_warning` (a circuit-breaker halt surfaces as `loop_error`) — plus a
45
+ derived-status JSON (`loopRunner.statusJsonCommand`, rauf: `status … --json`;
46
+ forge-5-loop also reads its optional `loopState` / `lock` / `sleepUntil` /
47
+ `reviewPending` fields when present, and degrades to the counts when absent) and
46
48
  per-iteration telemetry with a `stuckWarning` flag (`loopRunner.watchCommand`,
47
49
  rauf: `status … --json` — the `loop watch` verb was removed in v0.5.0). `forge-5-loop` supervises the run through these,
48
50
  **not** by parsing the human log. `followCommand` / `logCommand` are
@@ -47,7 +47,7 @@ an epic member. Only the flags below are stage-specific; pass no others.
47
47
  |---|---|---|
48
48
  | `forge-0-epic` | `forge-0-epic` | `--next-feature "{member}"` when a concrete member exists |
49
49
  | `forge-1-prd` … `forge-4-backlog` | that stage's own id | none beyond identity/capability |
50
- | `forge-5-loop` | `forge-5-loop` | `--outcome` — one of `complete`, `partial`, `blocked`, `needs-human`, `deferred`, `resolved`; optional `--cause dependency-starvation` with `--outcome partial` |
50
+ | `forge-5-loop` | `forge-5-loop` | `--outcome` — one of `complete`, `partial`, `blocked`, `needs-human`, `deferred`, `resolved`; optional `--cause dependency-starvation`, `review-pending` or `runner-stopped` with `--outcome partial` |
51
51
  | `forge-6-docs` | `forge-6-docs` | `--outcome` — `complete`, `blocked`, or `skipped` (deliberate docs skip, persisted via `state-skip` before the exit; routes like `complete` with honest wording) |
52
52
  | direct `forge-verify` | `forge-verify` | `--owner direct`, `--outcome` (`passed`, `findings`, `skipped`, `failed`), and served-stage metadata |
53
53
  | nested `forge-verify` | `forge-verify` | `--owner nested`, plus the same outcome and served-stage metadata |
@@ -27,7 +27,7 @@ vocabulary defined in `00-core-definitions.md` §8. No free-form values are perm
27
27
  | `${CLAUDE_PLUGIN_ROOT}` — sanctioned residual | 1 occurrence in `scripts/forge-root.sh` (env-fallback, REQ-RES-02 step 3) | `preserved-as-spec-allowed` | The single sanctioned residual: the resolver's documented Claude-compat fallback (REQ-RES-03 / REQ-RES-05). Exempt from the residual-var scan (`00-core-definitions.md` §6 `RESIDUAL_VAR_EXEMPT`). |
28
28
  | `${CLAUDE_PLUGIN_ROOT:-}` — bootstrap-prelude first-hint (Chunk 2b) | 1 occurrence per prelude across every canonical stamp site (the byte-pinned `BOOTSTRAP_PRELUDE`) | `preserved-as-spec-allowed` | The prelude's first resolver candidate — exact, glob-free root resolution on any Claude layout; expands to empty and is skipped when unset. Rule 3 allows it by stripping the byte-pinned prelude before its scan (detection is by `${CLAUDE_PLUGIN_ROOT` prefix, so the `:-}` default form is not an escape hatch elsewhere). `forge-agent-adapters-build` translates it to `${FEATURE_FORGE_ROOT:-}` in non-Claude bundles. |
29
29
  | `${CLAUDE_PLUGIN_ROOT}` — in `hooks/hooks.json` | 1 occurrence in `hooks/hooks.json` | `out-of-canon` | Non-canonical Claude artifact (REQ-VND-04). Not a canonical surface; exempt from the REQ-RES-03 scan. Left in place. |
30
- | `hooks/hooks.json` SessionStart wiring | 1 file (`hooks/hooks.json`) — Claude `SessionStart` → `bash ${CLAUDE_PLUGIN_ROOT}/scripts/session-check.sh` | `out-of-canon` | Claude-specific plugin hook wiring (REQ-VND-04, decision D3). Preserved + documented so `forge-agent-adapters-build` treats it as a Claude artifact, not portable canon. |
30
+ | `hooks/hooks.json` SessionStart wiring | 1 file (`hooks/hooks.json`) — Claude `SessionStart` → `bash "${CLAUDE_PLUGIN_ROOT}/scripts/session-check.sh"` | `out-of-canon` | Claude-specific plugin hook wiring (REQ-VND-04, decision D3). Preserved + documented so `forge-agent-adapters-build` treats it as a Claude artifact, not portable canon. |
31
31
  | (contingency) any other vendor invocation directive | none found in the audit | — | REQ-VND-02 contingency did not fire (see Notes). If one is later surfaced, add a row with `removed` or `out-of-canon` per `02-frontmatter-purity-and-inventory.md` §3. |
32
32
 
33
33
  ## Notes
@@ -14,7 +14,8 @@ root navigator:
14
14
  python3 forge-session.py check-epic-base --feature F [--specs-dir DIR] \
15
15
  [--config FILE] [--epic E] [--json]
16
16
  python3 forge-session.py stage-exit --feature F --stage S [--owner direct|nested] \
17
- [--outcome O] [--cause dependency-starvation] [--verify-mode M] \
17
+ [--outcome O] [--cause dependency-starvation|review-pending|runner-stopped] \
18
+ [--verify-mode M] \
18
19
  [--served-stage S] [--verify-capability interactive|manual] [--specs-dir DIR] \
19
20
  [--config FILE] [--epic E] [--next-feature N] [--host claude|generic|pi] [--json]
20
21
  python3 forge-session.py select-outcome --feature F --served-stage S \
@@ -470,6 +471,8 @@ from forge_session.routes import ( # noqa: E402
470
471
  _LOOP_COMPLETE_SETTLED,
471
472
  _LOOP_COMPLETE_TEXT,
472
473
  _LOOP_OUTCOME_TEXT,
474
+ _LOOP_PARTIAL_REVIEW_PENDING_TEXT,
475
+ _LOOP_PARTIAL_RUNNER_STOPPED_TEXT,
473
476
  _LOOP_PARTIAL_STARVED_TEXT,
474
477
  _LOOP_ROUTE_KIND,
475
478
  _NO_FINDINGS_RESOLVED_TEXT,
@@ -767,6 +770,8 @@ __all__ = [
767
770
  "_LOOP_COMPLETE_SETTLED",
768
771
  "_LOOP_COMPLETE_TEXT",
769
772
  "_LOOP_OUTCOME_TEXT",
773
+ "_LOOP_PARTIAL_REVIEW_PENDING_TEXT",
774
+ "_LOOP_PARTIAL_RUNNER_STOPPED_TEXT",
770
775
  "_LOOP_PARTIAL_STARVED_TEXT",
771
776
  "_LOOP_ROUTE_KIND",
772
777
  "_NO_FINDINGS_RESOLVED_TEXT",
@@ -467,7 +467,8 @@ def main() -> int:
467
467
  p_exit.add_argument("--outcome", default=None,
468
468
  help="Stage-specific outcome (loop/docs/verify/fix only)")
469
469
  p_exit.add_argument(
470
- "--cause", default=None, dest="cause", choices=("dependency-starvation",),
470
+ "--cause", default=None, dest="cause",
471
+ choices=("dependency-starvation", "review-pending", "runner-stopped"),
471
472
  help="Pending-attribution cause; valid only with "
472
473
  "--stage forge-5-loop --outcome partial",
473
474
  )
@@ -686,8 +686,37 @@ def _feature_label(feat: dict) -> str:
686
686
  return feat["name"] + (f" [{feat['epic']}]" if feat.get("epic") else "")
687
687
 
688
688
 
689
+ #: Remedy for a resolved root that is un-built canon. Every route loads a BUILT bundle and names
690
+ #: what it writes (#283/#317) — the npx installer's writes mirror ``installer/src`` (the sibling
691
+ #: ``manifestPath`` manifest + every ``resolvePlacements`` destination, both scopes), pinned by
692
+ #: ``installer/test/doctor-canon-remedy.test.ts`` against the installer's typed contract.
693
+ #: Tier = the most conservative route (``network``: the marketplace and npm fetch).
694
+ _CANON_ROOT_REMEDY: Final[str] = (
695
+ "Load a built bundle, not the repo checkout. Install: the Claude marketplace "
696
+ "(`/plugin install feature-forge@feature-forge`, ships ./adapters/claude; writes under "
697
+ "~/.claude/plugins and enables it in ~/.claude/settings.json) or "
698
+ "`npx @garygentry/feature-forge install` (under the install scope's root — the project, or ~ "
699
+ "for a global install — writes the host's bundle dir, e.g. .claude/skills/feature-forge, plus "
700
+ "a sibling .feature-forge.<scope>.json manifest; also Codex agent files in .codex/agents, a "
701
+ "managed block in .github/copilot-instructions.md for Copilot, Pi agent files in .pi/agents "
702
+ "(global: ~/.pi/agent/agents)). Source dogfood: rebuild with "
703
+ "`python3 scripts/build-adapters.py` (rewrites adapters/), then `claude --plugin-dir "
704
+ "adapters/claude` or FEATURE_FORGE_ROOT=adapters/<host> (neither writes files), "
705
+ "`pi install ./adapters/pi -l` (writes .pi/settings.json), or `scripts/dev-plugin.sh` "
706
+ "(writes an out-of-repo plugin dir, default ~/.cache/feature-forge-dev/claude) — see "
707
+ "docs/DOGFOODING.md"
708
+ )
709
+
710
+
689
711
  def _check_plugin_root(ctx: _CheckContext) -> dict:
690
- """The sibling ``forge-root.sh`` resolves an install root (never ``na``)."""
712
+ """The sibling ``forge-root.sh`` resolves an install root (never ``na``).
713
+
714
+ A resolved root without the neutral ``.feature-forge-bundle.json`` sentinel is
715
+ un-built canon (the repo checkout): its skills' prose ``Read references/X`` resolves
716
+ skill-local, where canon carries none of the shared refs the build fans in, so they
717
+ dead-reference mid-stage (#122/#305/#314). That warns — never fails (#244 policy), and
718
+ doctor still exits 0 (INV-3), so the repo's own ``doctor --json`` smoke stays green.
719
+ """
691
720
  root = ctx.plugin_root
692
721
  evidence = dict(root)
693
722
  if root.get("resolved"):
@@ -695,7 +724,18 @@ def _check_plugin_root(ctx: _CheckContext) -> dict:
695
724
  channel = root.get("channel")
696
725
  suffix = f" (version {version})" if version else " (no version manifest)"
697
726
  chan = f" via {channel}" if channel else ""
698
- return _result("ok", f"resolved {root.get('root')}{chan}{suffix}", evidence)
727
+ built = (Path(str(root.get("root"))) / ".feature-forge-bundle.json").is_file()
728
+ evidence["bundleSentinel"] = built
729
+ if built:
730
+ return _result("ok", f"resolved {root.get('root')}{chan}{suffix}", evidence)
731
+ return _result(
732
+ "warn",
733
+ f"resolved {root.get('root')}{chan}{suffix} is un-built canon (no "
734
+ ".feature-forge-bundle.json): skills loaded from it cannot Read their shared "
735
+ "references (references/shared-conventions.md, …) skill-local (#305/#314)",
736
+ evidence,
737
+ _remedy(_CANON_ROOT_REMEDY, None, "network"),
738
+ )
699
739
  return _result(
700
740
  "warn",
701
741
  f"plugin root unresolved: {root.get('error', 'unknown')}",