@garygentry/feature-forge 0.3.7 → 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
@@ -329,10 +329,10 @@ def stage_exit(
329
329
  capability is permission, not tool presence: a dispatch permitted
330
330
  only once the user has asked is still `interactive`, because the
331
331
  `standard` gate's own prompt supplies that request.
332
- cause: Pending-attribution annotation (`dependency-starvation`), valid
333
- only with `--stage forge-5-loop --outcome partial` (REQ-ATTR-04).
334
- It swaps the partial next-steps sentence for the starvation variant
335
- and changes no routing.
332
+ cause: Pending-attribution annotation (`dependency-starvation`,
333
+ `review-pending` or `runner-stopped`), valid only with `--stage forge-5-loop --outcome
334
+ partial` (REQ-ATTR-04, #339). It swaps the partial next-steps sentence
335
+ for the matching variant and changes no routing.
336
336
 
337
337
  Returns:
338
338
  A JSON-serializable `StageExitPayload` dictionary.
@@ -486,7 +486,7 @@ def stage_exit(
486
486
  # argparse `choices` already restricts the value; this restricts the combination.
487
487
  if cause is not None and not (stage == "forge-5-loop" and outcome == "partial"):
488
488
  raise UsageError(
489
- "--cause dependency-starvation is valid only with "
489
+ f"--cause {cause} is valid only with "
490
490
  "--stage forge-5-loop --outcome partial"
491
491
  )
492
492
 
@@ -799,6 +799,27 @@ _LOOP_PARTIAL_STARVED_TEXT: Final[str] = (
799
799
  "resumable and nothing downstream is ready: unblock the roots named in the "
800
800
  "starvation report above, then run the loop again below to continue."
801
801
  )
802
+ #: The pending-review variant of the `partial` next-steps sentence: every item may be
803
+ #: done, but the runner's review pass failed or was interrupted, so the run is not
804
+ #: complete. Selected only by ``--cause review-pending`` (issue #339); the route is
805
+ #: still the loop resume, whose Step 2a re-runs exactly that review.
806
+ _LOOP_PARTIAL_REVIEW_PENDING_TEXT: Final[str] = (
807
+ "The loop for {feature} is not finished — its review pass failed or was "
808
+ "interrupted, so the review is still pending even if every item is done. The "
809
+ "recorded state is resumable and nothing downstream is ready: run the loop again "
810
+ "below to re-run the pending review."
811
+ )
812
+ #: The unfinished-runner variant of the `partial` next-steps sentence: the runner's
813
+ #: own terminal state (a crash, a stop, a stale lock, a limit halt) says it did not
814
+ #: finish cleanly, so the run is not complete even when every item reads `done`.
815
+ #: Selected only by ``--cause runner-stopped`` (issue #339); the route is still the
816
+ #: loop resume, whose Step 2a offers the runner's own resume.
817
+ _LOOP_PARTIAL_RUNNER_STOPPED_TEXT: Final[str] = (
818
+ "The loop runner for {feature} did not reach a clean finish — it crashed, was "
819
+ "stopped, or halted on a limit — so the run is not complete even if every item "
820
+ "reads done. The recorded state is resumable and nothing downstream is ready: run "
821
+ "the loop again below to resume the runner."
822
+ )
802
823
  #: The `complete` preamble, selected by where the handoff actually lands. The epic
803
824
  #: rows name the epic and its live rollup, so the operator can see WHY the handoff is
804
825
  #: this member's own documentation rather than another member (or vice versa).
@@ -956,9 +977,11 @@ def _loop_route(
956
977
  handoff: findings already exist at this exact revision, so the fenced
957
978
  action is applying them, exactly as on a production re-exit.
958
979
  cause: The already-validated attribution annotation — only
959
- ``"dependency-starvation"`` with ``outcome == "partial"``, else None.
960
- Swaps the partial next-steps sentence for the starvation variant; the
961
- route itself is unchanged (partial stays a resume either way).
980
+ ``"dependency-starvation"``, ``"review-pending"`` or
981
+ ``"runner-stopped"`` with
982
+ ``outcome == "partial"``, else None. Swaps the partial next-steps
983
+ sentence for the matching variant; the route itself is unchanged
984
+ (partial stays a resume either way).
962
985
 
963
986
  Returns:
964
987
  `(primary_canonical, deferred_canonical, outcome_text, advancing)`, matching
@@ -985,6 +1008,10 @@ def _loop_route(
985
1008
  )
986
1009
  if outcome == "partial" and cause == "dependency-starvation":
987
1010
  text = _LOOP_PARTIAL_STARVED_TEXT.format(feature=feature)
1011
+ elif outcome == "partial" and cause == "review-pending":
1012
+ text = _LOOP_PARTIAL_REVIEW_PENDING_TEXT.format(feature=feature)
1013
+ elif outcome == "partial" and cause == "runner-stopped":
1014
+ text = _LOOP_PARTIAL_RUNNER_STOPPED_TEXT.format(feature=feature)
988
1015
  else:
989
1016
  text = _LOOP_OUTCOME_TEXT[outcome].format(feature=feature)
990
1017
  return primary, None, text, False
@@ -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 |
@@ -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 |
@@ -109,7 +109,7 @@ The runner commits each item onto the current branch. Skip if not a git repo or
109
109
 
110
110
  ### 1g. Stranded-Work Pre-flight (if using git)
111
111
 
112
- Run `git status --porcelain`. If it reports changes **and** `{backlogDir}/{loopRunner.stateDir}/state.json` exists from a previous run, **STOP**: name that run (its `startedAt`, `currentItem`, and `blockedItems` from `state.json`) and point the user at the **Post-Run Tree Reconciliation** section of `references/recovery-procedure.md` to commit / stash / discard the stranded work before relaunch — never auto-pass `--force`. If the tree is dirty with **no** prior-run `state.json`, keep today's behavior (surface it; let the user commit/stash or pass `--force`). A clean tree is silent. rauf's own launch refusal remains the backstop.
112
+ Run `git status --porcelain`. If it reports changes **and** `{backlogDir}/{loopRunner.stateDir}/state.json` exists from a previous run, **STOP**: name that run (its `startedAt`, `currentItem`, and `blockedItems` from `state.json`) and point the user at the **Post-Run Tree Reconciliation** section of `references/recovery-procedure.md` to commit / stash / discard the stranded work before relaunch — never auto-pass `--force`. If the tree is dirty with **no** prior-run `state.json`, keep today's behavior (surface it; let the user commit/stash or pass `--force`). A clean tree is silent.
113
113
 
114
114
  ## Step 2: Construct the Loop Command
115
115
 
@@ -119,7 +119,7 @@ Run the **list command** (`loopRunner.listCommand`, default `rauf backlog list .
119
119
 
120
120
  Calculate the iteration count: `ceil((pending + in_progress) * loopIterationMultiplier)` where `loopIterationMultiplier` comes from `forge.config.json` (default: 1.5, headroom for retries).
121
121
 
122
- If there are no pending or in_progress items, STOP and tell the user: "All backlog items are already done or blocked. Nothing to run."
122
+ Then, **whatever the counts**, run the **status-json command**: `reviewPending: true` (optional field) means a prior review pass failed or was interrupted — follow **Pending review** in `references/runner-contract.md` before any fresh run (which would drop it). Otherwise, with no pending or in_progress items: a reported `loopState` that is not a clean finish → follow **Unfinished runner** in that file; else STOP and tell the user: "All backlog items are already done or blocked. Nothing to run."
123
123
 
124
124
  If there are `blocked` items, note them — the user may want `--retry-blocked`.
125
125
 
@@ -185,14 +185,14 @@ R="$(bash -c '[ -z "${FEATURE_FORGE_ROOT:-}" ] || [ -x "$FEATURE_FORGE_ROOT/scri
185
185
  python3 "$R/scripts/forge-session.py" state-enter --feature "{feature}" --stage forge-5-loop --specs-dir "{specsDir}"
186
186
  ```
187
187
 
188
- Then commit this state write before launching (mandatory). The runner refuses to run with uncommitted changes (*"…pass --force"*), and this marker is itself one — so an otherwise-clean repo fails its first launch unless committed. Commit it via the shared-conventions **Git Commit Protocol** (epic members: stage `{specsDir}/{epic}/`): `{commitPrefix}({feature}): forge-5-loop in-progress` — a launch precondition, required regardless of `gitCommitAfterStage`. Unrelated leftover changes still trip the refusal; surface it, never auto-pass `--force`. See `references/runner-contract.md`.
188
+ Then commit this state write before launching (mandatory — the runner refuses a dirty tree, and this marker is itself a change) via the shared-conventions **Git Commit Protocol** (epic members: stage `{specsDir}/{epic}/`): `{commitPrefix}({feature}): forge-5-loop in-progress`, regardless of `gitCommitAfterStage`. Unrelated leftover changes still trip the refusal; surface it, never auto-pass `--force`.
189
189
 
190
190
  ### 3b. Launch Background Process
191
191
 
192
192
  > **On Pi, do not perform Steps 3b–3f by hand.** Pi has no background or monitor surface; this bundle's `forge-loop-supervisor` extension IS the "background-execution mechanism" and "monitoring mechanism" these steps name. Call **`forge_loop_launch`** with the backlog dir (plus `review` / `agent` / `iterations` from config) — it launches the loop **detached** and supervises `events.ndjson` for you, reporting each completed item and waking this session on needs-human / blocked / stuck / review-failed / error / completion. **Read the rest of Steps 3b–3f, and the launch/monitor detail in `references/runner-contract.md`, as a description of what that tool does — not as commands to run.** Use `forge_loop_status` to check progress and `forge_loop_stop` only to deliberately stop the runner; full detail is in "Host execution notes (Pi)" at the end of this skill.
193
193
 
194
194
 
195
- Launch the loop **backgrounded** (host's background-execution mechanism: 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). 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`.
195
+ Launch the loop **backgrounded** (host's background-execution mechanism: 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 and rotates `{stateDir}/events.ndjson` natively), launch the **plain `runCommand`** with **no stdout redirect** and supervise that native file; never redirect `--ndjson` into `{stateDir}` (it collides with the runner's own writer). 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). 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`.
196
196
 
197
197
  ### 3c. Inform User
198
198
 
@@ -200,21 +200,11 @@ Follow the **Inform-user output template (Step 3c)** section of `references/runn
200
200
 
201
201
  ### 3d. Arm a Monitor on the event stream, and react to events
202
202
 
203
- Arm the **host's monitoring mechanism** on the structured event stream (the NDJSON file, or the
204
- human log as fallback) with **a continuous watch**, a coverage-complete filter
205
- matching every terminal and exception state (silence is not success), and react to
206
- each event as it arrives. Each NDJSON line is a JSON object whose event kind lives
207
- under the **`type`** field (rauf's schema — e.g. `{"type":"item_completed",…}`), so
208
- the filter must select on **`.type`**, **not** `kind`: a `kind`-keyed filter surfaces
209
- nothing (no `grep` match; `jq` aborts on the null `.kind`), so the watch stays dark. The exact Monitor
210
- commands, the filter event list, and the full per-event reaction rules (`needs_human`
211
- / `loop_error` surfaced immediately with an automatic session wake, `item_completed`
212
- coalesced into milestones, `llm_stuck_warning` as a hang warning) are in
213
- `references/runner-contract.md` — follow them verbatim.
203
+ Arm the **host's monitoring mechanism** (a continuous watch) on the structured event stream (the NDJSON file, or the human log as fallback) with a coverage-complete filter matching every terminal and exception state (silence is not success). The filter selects on each NDJSON line's **`.type`** field, **never** `kind` (a `kind`-keyed filter matches nothing and the watch stays dark). The exact commands, the filter list (incl. `review_failed` and usage-limit events), and the per-event reactions (`needs_human` / `loop_error` / `review_failed` surfaced with an automatic session wake, `item_completed` coalesced into milestones, `llm_stuck_warning` reported with its in-flight tool) are in `references/runner-contract.md` — follow them verbatim.
214
204
 
215
205
  ### 3f. Reach completion
216
206
 
217
- Step 4 is reached when the backgrounded process exits (its completion notification is authoritative); the `loop_completed` / `loop_error` / `loop_cancelled` event is the live heads-up that it's imminent. Stop the Monitor (it ends on its own when `tail` sees the process-ended log, or via the supervisor's own teardown) and proceed to Step 4. Do NOT foreground-sleep or poll — the harness drives both the Monitor events and the completion notification.
207
+ Step 4 is reached when the backgrounded process exits (its completion notification is authoritative; a `loop_completed` / `loop_error` / `loop_cancelled` event is the live heads-up). Stop the Monitor (the supervisor's own teardown if it has not ended) and proceed. Do NOT foreground-sleep or poll — the harness drives both signals.
218
208
 
219
209
  ## Step 4: Check Results
220
210
 
@@ -226,7 +216,7 @@ Run the **status-json command** (`loopRunner.statusJsonCommand`) and read
226
216
  `backlogSummary` for the authoritative counts — it separates the three non-done
227
217
  outcomes: genuine `blocked`, `needsHuman`, and runner-`deferred` ("false blocks").
228
218
  Fall back to the **list command** (`loopRunner.listCommand`) if `statusJsonCommand`
229
- is not configured. If the run used a review flag (e.g. rauf's `--review`), also read any `review_completed` event (event stream, or `{loopRunner.stateDir}/events.ndjson`) for its `itemsCreated`/`summary` to surface in 4b — see `references/result-reporting.md`.
219
+ is not configured. Also read the optional `loopState`, `reviewPending`, `lock` fields (absent on older runners = unset); **Runner terminal states** in `references/result-reporting.md` maps them. **`reviewPending: true` is never complete, whatever the counts** — offer the resume per **Pending review** in `references/runner-contract.md` before 4b. If the run used a review flag (e.g. rauf's `--review`), also read any `review_completed` event (event stream, or `{loopRunner.stateDir}/events.ndjson`) for its `itemsCreated`/`summary` to surface in 4b — see `references/result-reporting.md`.
230
220
 
231
221
  ### 4b. Report Results
232
222
 
@@ -235,11 +225,11 @@ blocked and needs-human) and render its report. The five verbatim result-report
235
225
 
236
226
  ### 4c. Post-Run Recovery Pass (unconditional)
237
227
 
238
- Run the **Post-Run Recovery Procedure** (`references/recovery-procedure.md`) now — on **every** run close, before Step 5 writes state, so the tree it inspects is exactly what the run left. The live `needs_human` handler (3d) collects answers early but is **not** the entry condition: a run that emitted no event still enters here — this is what makes the plain-blocked unblock reachable on blocked-only runs. With nothing to decide, its step 1 skips straight to the §4 tree reconciliation — silent on a clean tree. A run can strand uncommitted work with no signal (items failing a shared final acceptance criterion are never committed); this pass reconciles it — 1g's pre-flight is only the next-launch backstop. Its step-7 gate feeds Step 7's `resolved` rung; the stage still closes exactly once, in Step 7.
228
+ Run the **Post-Run Recovery Procedure** (`references/recovery-procedure.md`) now — on **every** run close, before Step 5 writes state, so the tree it inspects is exactly what the run left. The live `needs_human` handler (3d) is **not** the entry condition: a run that emitted no event still enters here, so blocked-only runs reach the unblock. With nothing to decide, its step 1 skips to the §4 tree reconciliation — silent on a clean tree — which also reconciles work a run stranded uncommitted with no signal (1g is only the next-launch backstop). Its step-7 gate feeds Step 7's `resolved` rung; the stage still closes exactly once, in Step 7.
239
229
 
240
230
  ## Step 5: Update Pipeline State
241
231
 
242
- Record completion by running `state-complete` (below). Evaluate "all backlog items are `done`" yourself and pass the result as `--status`: `complete` if every item is `done`, else `in-progress`. The verb records `completedAt`, the version, `basedOnVersions` and `artifacts`, and refreshes `updatedAt`. Add `--epic "{epic}"` when this feature is an epic member — required, per the Pipeline State Protocol.
232
+ Record completion by running `state-complete` (below). Evaluate "all backlog items are `done`" yourself and pass the result as `--status`: `complete` only when Step 7's ladder selects `complete` (every item `done`, `total > 0`, no pending review, a clean runner finish per `references/result-reporting.md`), else `in-progress`. The verb records `completedAt`, the version, `basedOnVersions` and `artifacts`, and refreshes `updatedAt`. Add `--epic "{epic}"` when this feature is an epic member — required, per the Pipeline State Protocol.
243
233
 
244
234
  ```bash
245
235
  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')"
@@ -274,7 +264,7 @@ python3 "$R/scripts/forge-session.py" state-verify --feature "{feature}" --stage
274
264
 
275
265
  Every loop run ends here, and ends here **exactly once** — standalone or epic member, complete or not.
276
266
 
277
- First select the single `LoopOutcome` with the ladder in `references/result-reporting.md` (`resolved` → `needs-human` → `blocked` → `deferred` → `partial` → `complete`, first match wins), reading it from Step 4a's authoritative counts and never from the runner's process exit code. If those counts were never obtained, follow that file's operational-failure rule instead: report the failure and its recovery and run no exit at all.
267
+ First select the single `LoopOutcome` with the ladder in `references/result-reporting.md` (`resolved` → unfinished-runner `partial` → `needs-human` → `blocked` → `deferred` → `partial` → `complete`, first match wins, plus any `--cause` it names), reading it from Step 4a's authoritative counts and never from the runner's process exit code. If those counts were never obtained, follow that file's operational-failure rule instead: report the failure and its recovery and run no exit at all.
278
268
 
279
269
  **Close this stage with the Scripted Stage Exit** (contract: `references/stage-exit-protocol.md`; do not improvise a "Next steps" list). Run:
280
270
 
@@ -293,8 +283,8 @@ Add `--epic "{epic}"` when this feature is an epic member — required, per the
293
283
  - **Plugin-root discovery (1b-epic helper) covers installed paths, not workspace-dev checkouts.** The `forge-root.sh` search probes the locations of an **installed** plugin only, so a feature-forge **source checkout** (e.g. `~/workspace/feature-forge`) exits "cannot locate plugin root" unless `FEATURE_FORGE_ROOT` points at it (#323). Expected in a dev environment; run the epic-manifest script from the checkout directly (`python3 <checkout>/scripts/epic-manifest.py …`).
294
284
  - `{backlogDir}` is a **directory path**, not a file path. Pass `specs/auth`, not `specs/auth/backlog.json`.
295
285
  - rauf resolves `RAUF.md` with fallback (`{backlogDir}/.rauf/RAUF.md` first, then the project's `.rauf/RAUF.md`). State files (state.json, {loopRunner.logFile}, etc.) land at `{backlogDir}/{loopRunner.stateDir}/`, isolated per backlog dir, so concurrent features don't collide.
296
- - If the session disconnects mid-loop, the runner process continues independently — check results later with the status / list commands. A stale lock from a previous run may need `--force` to clear.
297
- - Never run the run command in the foreground (without host's background-execution mechanism) — it blocks and will hit the Bash tool timeout for any non-trivial backlog. "Don't block the foreground" is NOT "stay silent": supervise via the host's monitoring mechanism (3d). A `needs_human`/`blocked`/`review` signal does **not** pause the loop — the runner sets the item aside and keeps going; surface it live but don't tell the user the loop is waiting. See `references/runner-contract.md` for the full monitoring rules.
286
+ - If the session disconnects mid-loop, the runner process continues independently — check results later with the status / list commands. A crashed run's stale lock (`PAUSED` + `lock.stale`) is cleared by rauf's `resume`, not `--force` (see `references/result-reporting.md`).
287
+ - Never run the run command in the foreground (without host's background-execution mechanism) — it blocks and will hit the Bash tool timeout for any non-trivial backlog. "Don't block the foreground" is NOT "stay silent": supervise via the host's monitoring mechanism (3d). A `needs_human`/`blocked`/`review` signal does **not** pause the loop — the runner sets the item aside and keeps going; surface it live but don't tell the user the loop is waiting.
298
288
  - The version gate (1c) uses the `--json` form on purpose; never parse `rauf version`'s human output.
299
289
  - **Implementation artifacts must not cite specs.** The loop should **read** specs and `backlog.json` freely — they are the source of truth, and the backlog rightly cites specs for provenance. But artifacts the loop **writes into the target repo** (source code, generated `SKILL.md`/agent files, configs, code comments) must be **self-contained**: no references to feature-forge spec files (no `See specs/{feature}/NN-*.md`, no "source spec" provenance notes) — specs are pre-implementation inputs that may be archived or deleted once the feature ships. This applies only to shipped implementation output, never to the backlog or spec documents, which keep citing specs.
300
290
 
@@ -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
@@ -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
 
@@ -155,12 +155,18 @@ maximum `timeout_ms` (1 hour), and a bounded timeout would silently stop watchin
155
155
  still-running loop.
156
156
 
157
157
  **Coverage-complete filter (silence is not success).** The filter MUST match every
158
- terminal and exception state, not just the happy path — otherwise a crash or hang
159
- looks identical to "still running." Monitor command (NDJSON path):
158
+ terminal, exception, and pause/limit event, not just the happy path — otherwise a crash,
159
+ hang, or hours-long usage sleep looks identical to "still running." The list below covers
160
+ every such type in rauf's event schema (terminal: `loop_completed` / `loop_error` /
161
+ `loop_cancelled` / `loop_paused`; exception: `item_blocked` / `needs_human` /
162
+ `review_failed` / `llm_stuck_warning`; pause/limit: `usage_limit_hit` /
163
+ `usage_limit_cleared` / `sleep_start` / `sleep_end`); only per-iteration narration
164
+ (`iteration_start`, `llm_*` activity, `item_selected`, …) is left out. Monitor command
165
+ (NDJSON path):
160
166
 
161
167
  ```
162
168
  tail -n +1 -F {backlogDir}/{loopRunner.stateDir}/events.ndjson 2>/dev/null \
163
- | jq -rc --unbuffered 'select(.type | test("item_completed|item_blocked|needs_human|signal_parsed|loop_completed|loop_error|loop_cancelled|llm_stuck_warning"))'
169
+ | 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"))'
164
170
  ```
165
171
 
166
172
  > **Use `tail -F` (follow by name), not `-f` (follow by descriptor).** The runner
@@ -177,15 +183,20 @@ tail -n +1 -F {backlogDir}/{loopRunner.stateDir}/events.ndjson 2>/dev/null \
177
183
 
178
184
  ```
179
185
  tail -n +1 -F {backlogDir}/{loopRunner.stateDir}/{loopRunner.logFile} 2>/dev/null \
180
- | grep -E --line-buffered 'Item [^ ]+ (completed|blocked):|Item [^ ]+ needs human input|Loop completed|Loop error:|Circuit breaker:'
186
+ | 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'
181
187
  ```
182
188
 
183
189
  (Match `needs human input` **without** a trailing colon — the runner writes
184
- `needs human input (set aside):`.)
190
+ `needs human input (set aside):`. `Loop cancelled` also matches *"Loop cancelled during
191
+ review pass (review pending)"* and the between-iteration / mid-sleep cancels;
192
+ `[Uu]sage limit` matches the 5-hour sleep, weekly limit, detection and wake lines.)
185
193
 
186
194
  If the Monitor is ever auto-stopped for event volume, re-arm with a tighter filter
187
195
  (drop `item_completed`, keep the exception/terminal events).
188
196
 
197
+ An older runner that never emits a listed type (e.g. `review_failed`) simply never
198
+ matches it — the filter needs no version gate.
199
+
189
200
  ## React to events as they land (Step 3e)
190
201
 
191
202
  Each Monitor event arrives as a message. React per type — but keep the user signal
@@ -213,13 +224,101 @@ high and the noise low:
213
224
  - **`loop_error`** → a real failure (this is also what a circuit-breaker halt — too many
214
225
  consecutive infra failures — emits). Surface now and an automatic session wake. Offer
215
226
  inspection / `--force` / re-run as appropriate.
216
- - **Stall detection** → rauf emits an **`llm_stuck_warning`** event when an iteration
217
- stops making progress; the filter above includes it, so surface it live (a hang
218
- warning, not yet a failure) and offer `--force` if it persists. If you instead want to
219
- probe on quiet, run `{rendered watchCommand}` (or read
220
- `{backlogDir}/{loopRunner.stateDir}/iteration-status.json`) and key off its
221
- `stuckWarning` flag. Do **not** infer a stall from `state.json.updatedAt` alone — it is
222
- not a liveness proof.
227
+ - **`review_failed`** → the post-loop review pass failed or was stopped by a usage limit
228
+ (payload `reason`). Surface it now with an automatic session wake. The run is **not**
229
+ complete: the review stays pending (`status --json` → `reviewPending: true`). A failed
230
+ review makes `loop run --review` exit **1**; a usage-limit stop is a resumable limit
231
+ stop, not that exit-1 case. A review **cancelled** mid-pass emits **no**
232
+ `review_failed` — only `loop_cancelled` (human log: *"Loop cancelled during review pass
233
+ (review pending)"*) with `reviewPending` still set — so never rely on this event alone.
234
+ Nothing to do live — Step 4a reads `reviewPending` and offers the resume (**Pending
235
+ review**, below).
236
+ - **Stall detection** → rauf emits an **`llm_stuck_warning`** event when an iteration's
237
+ output stream goes silent. Surface it live as a stall warning, not yet a failure. Read
238
+ its optional `currentTool` / `toolRunningMs` (absent on older runners — then report
239
+ the plain `silentMs` and treat it as a possible hang):
240
+ - `currentTool: null` → **the model itself went silent** with no tool in flight — a
241
+ likely hang. Offer `--force` / re-run if it persists.
242
+ - `currentTool` set → **a quiet tool call outlived the tool ceiling** — report it as
243
+ e.g. *"Bash running 31m"* (`toolRunningMs` rounded to minutes). This is usually a
244
+ slow verification gate, not a hung LLM: say so, and let it run unless it is clearly
245
+ wedged.
246
+
247
+ The thresholds are the runner's `.rauf.json` `options.stuckThresholdMs` (silence
248
+ before the warning, default 5 min) and `options.toolStuckThresholdMs` (how long a
249
+ quiet in-flight tool holds the warning off, default 30 min, measured from the tool's
250
+ start). A repo whose verification gate legitimately runs longer than 30 min should
251
+ raise `toolStuckThresholdMs` rather than learn to ignore the warning. If you instead
252
+ want to probe on quiet, run `{rendered watchCommand}` and key off `health.stuckWarning`.
253
+ Do **not** infer a stall from `state.json.updatedAt` alone — it is not a liveness
254
+ proof.
255
+ - **Usage-limit waits are not stalls.** `usage_limit_hit` (`limitType`, optional
256
+ `reason: "usage_api_disagreement"`) followed by `sleep_start` (`sleepUntil`, `reason`)
257
+ means the runner is sleeping until the limit resets (`SLEEPING_LIMIT`) and will resume
258
+ on its own: surface it once with its `sleepUntil`, and send an automatic session wake when
259
+ the sleep is long (a 5-hour window). A `sleep_start` whose `reason` begins *"Usage-limit
260
+ banner unconfirmed"* is a 30 s / 60 s backoff (`status --json` still `RUNNING` with
261
+ `sleepUntil`) — narrate it at most briefly. `sleep_end` / `usage_limit_cleared` → the
262
+ loop is working again. A weekly or no-sleep limit halts the run instead (the process
263
+ exits; Step 4a reads `WEEKLY_LIMIT` / `PAUSED_USAGE_LIMIT`). None of these is a stall:
264
+ never offer `--force`.
265
+ - **`loop_paused`** (`reason: "needs_human"`, `itemId`) → the run halted on a needs-human
266
+ item (only under rauf's opt-in `--pause-on-needs-human`; forge does not pass it by
267
+ default). Unlike the default set-aside mode, the loop **is** now stopped: surface it
268
+ with an automatic session wake; the process exits and Step 4c's recovery pass handles the
269
+ answer.
270
+
271
+ **Pending review (Steps 2a / 4a, rauf).** A rauf `--review` run whose review pass failed, was cancelled, or was stopped by a usage
272
+ limit leaves `status --json` with `reviewPending: true` and `reviewItemIds` (the review's
273
+ exact scope). The loop state can read `COMPLETE`/`IDLE` with every item `done`, or
274
+ `PAUSED`/`PAUSED_USAGE_LIMIT` — either way the run is **not complete**, and the process
275
+ exit code (1 for a failed review) does not decide it. Both fields are optional: a runner
276
+ that never reports them never takes this path.
277
+
278
+ rauf's `resume` re-runs exactly that review (`rauf loop review --items <reviewItemIds>`),
279
+ not a fresh loop. So when Step 4a or Step 2a sees `reviewPending: true`:
280
+
281
+ 1. Report it: *"The review pass for {feature} did not finish ({reason from the last
282
+ `review_failed` event, if any}); it covers {reviewItemIds}."*
283
+ 2. Via `AskUserQuestion`, offer **Resume the review now (recommended)** · **Stop here**. On a
284
+ usage-limit stop (`PAUSED_USAGE_LIMIT` / `WEEKLY_LIMIT`), say the resume only helps
285
+ once the limit resets (`sleepUntil`). A stop the user requested (`PAUSED`, lock
286
+ released) is theirs: offer, never auto-resume.
287
+ 3. **Resume:** launch `{bin} resume . --backlog {backlogDir}` backgrounded, exactly as a
288
+ run command (Step 3b launch guards, 3d Monitor, 3f completion), then return to Step 4a.
289
+ A successful review may file fix items (`review_completed.itemsCreated`); those are
290
+ ordinary pending work for the next loop run. rauf reviews **before** relaunching: when
291
+ pending items also remain (the review ran after the iteration budget ran out, or
292
+ blocked items strand their dependents), `resume` re-runs only the review and the
293
+ remaining items wait for the next loop run — say so, never launch a fresh `loop run`
294
+ first (it overwrites the state and drops the pending review).
295
+ 4. **Stop here:** Step 7 closes `partial` with `--cause review-pending` (see
296
+ `references/result-reporting.md`) — a resume route whose Step 2a re-offers this.
297
+
298
+ At rung 3 (no question mechanism), do not launch: print the rendered resume command and
299
+ close as in 4.
300
+
301
+ **Unfinished runner (Step 2a re-entry, rauf).** When Step 2a finds no pending or
302
+ in_progress items and no pending review, but `status --json` reports a `loopState` that
303
+ is not a clean finish (**Clean runner finish** in `references/result-reporting.md` —
304
+ e.g. `ERROR`, `PAUSED` with `lock.stale`, `ITERATIONS_COMPLETE`, a usage halt), the
305
+ prior run closed as `partial` with `--cause runner-stopped` (or the plain iteration-limit
306
+ `partial`), and "Nothing to run" would strand it. Instead:
307
+
308
+ 1. Report the state: *"The last run for {feature} did not finish cleanly ({loopState}
309
+ {— stale lock | — resets {sleepUntil}})."*
310
+ 2. Via `AskUserQuestion`, offer **Resume the runner (recommended)** · **Stop here**. For a
311
+ usage halt, the resume helps only once the limit resets; for `PAUSED` on request
312
+ (lock released), the stop was the user's — offer, never auto-resume.
313
+ 3. **Resume:** launch `{bin} resume . --backlog {backlogDir}` backgrounded exactly as in
314
+ **Pending review** step 3 (it clears a stale lock and finishes the run's bookkeeping,
315
+ including any post-loop review), then continue at Step 4a. rauf's recovery for
316
+ `ERROR` is `resume` or `reset` + re-run — never reach for `reset` or `--force` first.
317
+ 4. **Stop here**, or rung 3 (print the rendered command, do not launch): STOP without
318
+ touching the stage — it is already recorded `in-progress`.
319
+
320
+ When `loopState` is absent, this never fires: an older or non-rauf runner keeps the
321
+ plain "Nothing to run" stop.
223
322
 
224
323
  ## Inform-user output template (Step 3c)
225
324
 
@@ -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 |