@garygentry/feature-forge 0.3.6 → 0.3.8

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 (753) hide show
  1. package/README.md +1 -1
  2. package/adapters/GENERATION-REPORT.md +20 -0
  3. package/adapters/claude/.claude-plugin/plugin.json +16 -0
  4. package/adapters/claude/.feature-forge-bundle.json +1 -1
  5. package/adapters/claude/agents/forge-verifier.md +16 -5
  6. package/adapters/claude/references/forge-config-schema.json +3 -3
  7. package/adapters/claude/references/portable-root.md +20 -16
  8. package/adapters/claude/references/preflight-and-self-heal.md +183 -0
  9. package/adapters/claude/references/process-overview.md +1 -1
  10. package/adapters/claude/references/select-outcome.md +138 -0
  11. package/adapters/claude/references/shared-conventions.md +175 -22
  12. package/adapters/claude/references/skill-frontmatter.schema.json +3 -1
  13. package/adapters/claude/references/stack-resolution.md +1 -1
  14. package/adapters/claude/references/stage-exit-protocol.md +19 -4
  15. package/adapters/claude/references/templates/root-hygiene/AGENTS.md +14 -0
  16. package/adapters/claude/references/templates/root-hygiene/CLAUDE.md +14 -0
  17. package/adapters/claude/references/templates/specs-hygiene/AGENTS.md +3 -1
  18. package/adapters/claude/references/templates/specs-hygiene/CLAUDE.md +2 -1
  19. package/adapters/claude/references/verifier-patterns/MEMORY.md +35 -0
  20. package/adapters/claude/references/verifier-patterns/pattern_absence_claims.md +36 -0
  21. package/adapters/claude/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  22. package/adapters/claude/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  23. package/adapters/claude/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  24. package/adapters/claude/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  25. package/adapters/claude/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  26. package/adapters/claude/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  27. package/adapters/claude/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  28. package/adapters/claude/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  29. package/adapters/claude/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  30. package/adapters/claude/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  31. package/adapters/claude/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  32. package/adapters/claude/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  33. package/adapters/claude/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  34. package/adapters/claude/references/verifier-patterns/pattern_test_count_units.md +20 -0
  35. package/adapters/claude/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  36. package/adapters/claude/references/verify-state.md +98 -0
  37. package/adapters/claude/scripts/forge-bootstrap.py +23 -2
  38. package/adapters/claude/scripts/forge-root.sh +104 -21
  39. package/adapters/claude/scripts/forge-session.py +620 -6973
  40. package/adapters/claude/scripts/forge_session/__init__.py +11 -0
  41. package/adapters/claude/scripts/forge_session/_common.py +1566 -0
  42. package/adapters/claude/scripts/forge_session/_doctor_util.py +218 -0
  43. package/adapters/claude/scripts/forge_session/cli.py +1067 -0
  44. package/adapters/claude/scripts/forge_session/decisions.py +355 -0
  45. package/adapters/claude/scripts/forge_session/discover.py +472 -0
  46. package/adapters/claude/scripts/forge_session/doctor.py +2144 -0
  47. package/adapters/claude/scripts/forge_session/exit.py +1081 -0
  48. package/adapters/claude/scripts/forge_session/outcomes.py +574 -0
  49. package/adapters/claude/scripts/forge_session/routes.py +1162 -0
  50. package/adapters/claude/scripts/forge_session/state.py +1479 -0
  51. package/adapters/claude/scripts/forge_session/topology.py +443 -0
  52. package/adapters/claude/skills/forge/SKILL.md +7 -7
  53. package/adapters/claude/skills/forge/references/process-overview.md +1 -1
  54. package/adapters/claude/skills/forge/references/shared-conventions.md +175 -22
  55. package/adapters/claude/skills/forge/references/stage-exit-protocol.md +19 -4
  56. package/adapters/claude/skills/forge-0-epic/SKILL.md +7 -7
  57. package/adapters/claude/skills/forge-0-epic/references/edit-mode.md +9 -5
  58. package/adapters/claude/skills/forge-0-epic/references/epic-manifest-subcommands.md +6 -1
  59. package/adapters/claude/skills/forge-0-epic/references/portable-root.md +20 -16
  60. package/adapters/claude/skills/forge-0-epic/references/shared-conventions.md +175 -22
  61. package/adapters/claude/skills/forge-0-epic/references/stage-exit-protocol.md +19 -4
  62. package/adapters/claude/skills/forge-1-prd/SKILL.md +7 -7
  63. package/adapters/claude/skills/forge-1-prd/references/shared-conventions.md +175 -22
  64. package/adapters/claude/skills/forge-1-prd/references/stage-exit-protocol.md +19 -4
  65. package/adapters/claude/skills/forge-2-tech/SKILL.md +7 -7
  66. package/adapters/claude/skills/forge-2-tech/references/shared-conventions.md +175 -22
  67. package/adapters/claude/skills/forge-2-tech/references/stack-resolution.md +1 -1
  68. package/adapters/claude/skills/forge-2-tech/references/stage-exit-protocol.md +19 -4
  69. package/adapters/claude/skills/forge-3-specs/SKILL.md +5 -5
  70. package/adapters/claude/skills/forge-3-specs/references/shared-conventions.md +175 -22
  71. package/adapters/claude/skills/forge-3-specs/references/stage-exit-protocol.md +19 -4
  72. package/adapters/claude/skills/forge-4-backlog/SKILL.md +16 -8
  73. package/adapters/claude/skills/forge-4-backlog/references/shared-conventions.md +175 -22
  74. package/adapters/claude/skills/forge-4-backlog/references/stage-exit-protocol.md +19 -4
  75. package/adapters/claude/skills/forge-4-backlog/references/verify-state.md +98 -0
  76. package/adapters/claude/skills/forge-5-loop/SKILL.md +36 -41
  77. package/adapters/claude/skills/forge-5-loop/references/agent-selection.md +4 -0
  78. package/adapters/claude/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
  79. package/adapters/claude/skills/forge-5-loop/references/recovery-procedure.md +10 -0
  80. package/adapters/claude/skills/forge-5-loop/references/runner-contract.md +15 -5
  81. package/adapters/claude/skills/forge-5-loop/references/shared-conventions.md +175 -22
  82. package/adapters/claude/skills/forge-5-loop/references/stage-exit-protocol.md +19 -4
  83. package/adapters/claude/skills/forge-5-loop/references/verify-state.md +98 -0
  84. package/adapters/claude/skills/forge-6-docs/SKILL.md +21 -14
  85. package/adapters/claude/skills/forge-6-docs/references/shared-conventions.md +175 -22
  86. package/adapters/claude/skills/forge-6-docs/references/stage-exit-protocol.md +19 -4
  87. package/adapters/claude/skills/forge-6-docs/references/verify-state.md +98 -0
  88. package/adapters/claude/skills/forge-bootstrap/SKILL.md +19 -10
  89. package/adapters/claude/skills/forge-bootstrap/references/shared-conventions.md +572 -0
  90. package/adapters/claude/skills/forge-fix/SKILL.md +15 -18
  91. package/adapters/claude/skills/forge-fix/references/select-outcome.md +138 -0
  92. package/adapters/claude/skills/forge-fix/references/shared-conventions.md +175 -22
  93. package/adapters/claude/skills/forge-fix/references/stage-exit-protocol.md +19 -4
  94. package/adapters/claude/skills/forge-guide/SKILL.md +87 -1
  95. package/adapters/claude/skills/forge-guide/references/forge-config-schema.json +3 -3
  96. package/adapters/claude/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
  97. package/adapters/claude/skills/forge-guide/references/process-overview.md +1 -1
  98. package/adapters/claude/skills/forge-guide/references/shared-conventions.md +175 -22
  99. package/adapters/claude/skills/forge-guide/references/stack-resolution.md +1 -1
  100. package/adapters/claude/skills/forge-init/SKILL.md +58 -7
  101. package/adapters/claude/skills/forge-init/references/preflight-and-self-heal.md +183 -0
  102. package/adapters/claude/skills/forge-init/references/shared-conventions.md +572 -0
  103. package/adapters/claude/skills/forge-verify/SKILL.md +20 -23
  104. package/adapters/claude/skills/forge-verify/references/findings-template.md +2 -2
  105. package/adapters/claude/skills/forge-verify/references/select-outcome.md +138 -0
  106. package/adapters/claude/skills/forge-verify/references/shared-conventions.md +175 -22
  107. package/adapters/claude/skills/forge-verify/references/stage-exit-protocol.md +19 -4
  108. package/adapters/claude/skills/forge-verify/references/verification-checklists/epic.md +1 -1
  109. package/adapters/claude/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
  110. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
  111. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  112. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  113. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  114. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  115. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  116. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  117. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  118. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  119. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  120. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  121. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  122. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  123. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  124. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
  125. package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  126. package/adapters/codex/.feature-forge-bundle.json +1 -1
  127. package/adapters/codex/agents/forge-researcher.toml +1 -1
  128. package/adapters/codex/agents/forge-verifier.toml +17 -6
  129. package/adapters/codex/references/forge-config-schema.json +5 -5
  130. package/adapters/codex/references/portable-root.md +20 -16
  131. package/adapters/codex/references/preflight-and-self-heal.md +183 -0
  132. package/adapters/codex/references/process-overview.md +10 -10
  133. package/adapters/codex/references/select-outcome.md +138 -0
  134. package/adapters/codex/references/shared-conventions.md +188 -35
  135. package/adapters/codex/references/skill-frontmatter.schema.json +3 -1
  136. package/adapters/codex/references/stack-resolution.md +1 -1
  137. package/adapters/codex/references/stage-exit-protocol.md +22 -7
  138. package/adapters/codex/references/templates/root-hygiene/AGENTS.md +14 -0
  139. package/adapters/codex/references/templates/root-hygiene/CLAUDE.md +14 -0
  140. package/adapters/codex/references/templates/specs-hygiene/AGENTS.md +3 -1
  141. package/adapters/codex/references/templates/specs-hygiene/CLAUDE.md +2 -1
  142. package/adapters/codex/references/verifier-patterns/MEMORY.md +35 -0
  143. package/adapters/codex/references/verifier-patterns/pattern_absence_claims.md +36 -0
  144. package/adapters/codex/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  145. package/adapters/codex/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  146. package/adapters/codex/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  147. package/adapters/codex/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  148. package/adapters/codex/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  149. package/adapters/codex/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  150. package/adapters/codex/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  151. package/adapters/codex/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  152. package/adapters/codex/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  153. package/adapters/codex/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  154. package/adapters/codex/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  155. package/adapters/codex/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  156. package/adapters/codex/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  157. package/adapters/codex/references/verifier-patterns/pattern_test_count_units.md +20 -0
  158. package/adapters/codex/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  159. package/adapters/codex/references/verify-state.md +98 -0
  160. package/adapters/codex/scripts/forge-bootstrap.py +23 -2
  161. package/adapters/codex/scripts/forge-root.sh +104 -21
  162. package/adapters/codex/scripts/forge-session.py +620 -6973
  163. package/adapters/codex/scripts/forge_session/__init__.py +11 -0
  164. package/adapters/codex/scripts/forge_session/_common.py +1566 -0
  165. package/adapters/codex/scripts/forge_session/_doctor_util.py +218 -0
  166. package/adapters/codex/scripts/forge_session/cli.py +1067 -0
  167. package/adapters/codex/scripts/forge_session/decisions.py +355 -0
  168. package/adapters/codex/scripts/forge_session/discover.py +472 -0
  169. package/adapters/codex/scripts/forge_session/doctor.py +2144 -0
  170. package/adapters/codex/scripts/forge_session/exit.py +1081 -0
  171. package/adapters/codex/scripts/forge_session/outcomes.py +574 -0
  172. package/adapters/codex/scripts/forge_session/routes.py +1162 -0
  173. package/adapters/codex/scripts/forge_session/state.py +1479 -0
  174. package/adapters/codex/scripts/forge_session/topology.py +443 -0
  175. package/adapters/codex/skills/forge/SKILL.md +40 -40
  176. package/adapters/codex/skills/forge/references/process-overview.md +10 -10
  177. package/adapters/codex/skills/forge/references/shared-conventions.md +188 -35
  178. package/adapters/codex/skills/forge/references/stage-exit-protocol.md +22 -7
  179. package/adapters/codex/skills/forge-0-epic/SKILL.md +15 -15
  180. package/adapters/codex/skills/forge-0-epic/references/edit-mode.md +14 -10
  181. package/adapters/codex/skills/forge-0-epic/references/epic-manifest-subcommands.md +8 -3
  182. package/adapters/codex/skills/forge-0-epic/references/portable-root.md +20 -16
  183. package/adapters/codex/skills/forge-0-epic/references/shared-conventions.md +188 -35
  184. package/adapters/codex/skills/forge-0-epic/references/stage-exit-protocol.md +22 -7
  185. package/adapters/codex/skills/forge-1-prd/SKILL.md +15 -15
  186. package/adapters/codex/skills/forge-1-prd/references/shared-conventions.md +188 -35
  187. package/adapters/codex/skills/forge-1-prd/references/stage-exit-protocol.md +22 -7
  188. package/adapters/codex/skills/forge-2-tech/SKILL.md +15 -15
  189. package/adapters/codex/skills/forge-2-tech/references/shared-conventions.md +188 -35
  190. package/adapters/codex/skills/forge-2-tech/references/stack-resolution.md +1 -1
  191. package/adapters/codex/skills/forge-2-tech/references/stage-exit-protocol.md +22 -7
  192. package/adapters/codex/skills/forge-3-specs/SKILL.md +10 -10
  193. package/adapters/codex/skills/forge-3-specs/references/shared-conventions.md +188 -35
  194. package/adapters/codex/skills/forge-3-specs/references/stage-exit-protocol.md +22 -7
  195. package/adapters/codex/skills/forge-4-backlog/SKILL.md +19 -11
  196. package/adapters/codex/skills/forge-4-backlog/references/shared-conventions.md +188 -35
  197. package/adapters/codex/skills/forge-4-backlog/references/stage-exit-protocol.md +22 -7
  198. package/adapters/codex/skills/forge-4-backlog/references/verify-state.md +98 -0
  199. package/adapters/codex/skills/forge-5-loop/SKILL.md +48 -53
  200. package/adapters/codex/skills/forge-5-loop/references/agent-selection.md +5 -1
  201. package/adapters/codex/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
  202. package/adapters/codex/skills/forge-5-loop/references/recovery-procedure.md +14 -4
  203. package/adapters/codex/skills/forge-5-loop/references/runner-contract.md +18 -8
  204. package/adapters/codex/skills/forge-5-loop/references/shared-conventions.md +188 -35
  205. package/adapters/codex/skills/forge-5-loop/references/stage-exit-protocol.md +22 -7
  206. package/adapters/codex/skills/forge-5-loop/references/verify-state.md +98 -0
  207. package/adapters/codex/skills/forge-6-docs/SKILL.md +27 -20
  208. package/adapters/codex/skills/forge-6-docs/references/shared-conventions.md +188 -35
  209. package/adapters/codex/skills/forge-6-docs/references/stage-exit-protocol.md +22 -7
  210. package/adapters/codex/skills/forge-6-docs/references/verify-state.md +98 -0
  211. package/adapters/codex/skills/forge-bootstrap/SKILL.md +23 -15
  212. package/adapters/codex/skills/forge-bootstrap/references/shared-conventions.md +572 -0
  213. package/adapters/codex/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +2 -2
  214. package/adapters/codex/skills/forge-fix/SKILL.md +27 -30
  215. package/adapters/codex/skills/forge-fix/references/select-outcome.md +138 -0
  216. package/adapters/codex/skills/forge-fix/references/shared-conventions.md +188 -35
  217. package/adapters/codex/skills/forge-fix/references/stage-exit-protocol.md +22 -7
  218. package/adapters/codex/skills/forge-guide/SKILL.md +84 -7
  219. package/adapters/codex/skills/forge-guide/references/forge-config-schema.json +5 -5
  220. package/adapters/codex/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
  221. package/adapters/codex/skills/forge-guide/references/process-overview.md +10 -10
  222. package/adapters/codex/skills/forge-guide/references/shared-conventions.md +188 -35
  223. package/adapters/codex/skills/forge-guide/references/stack-resolution.md +1 -1
  224. package/adapters/codex/skills/forge-init/SKILL.md +62 -12
  225. package/adapters/codex/skills/forge-init/references/preflight-and-self-heal.md +183 -0
  226. package/adapters/codex/skills/forge-init/references/shared-conventions.md +572 -0
  227. package/adapters/codex/skills/forge-verify/SKILL.md +27 -30
  228. package/adapters/codex/skills/forge-verify/references/findings-template.md +2 -2
  229. package/adapters/codex/skills/forge-verify/references/select-outcome.md +138 -0
  230. package/adapters/codex/skills/forge-verify/references/shared-conventions.md +188 -35
  231. package/adapters/codex/skills/forge-verify/references/stage-exit-protocol.md +22 -7
  232. package/adapters/codex/skills/forge-verify/references/verification-checklists/epic.md +2 -2
  233. package/adapters/codex/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
  234. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
  235. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  236. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  237. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  238. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  239. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  240. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  241. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  242. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  243. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  244. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  245. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  246. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  247. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  248. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
  249. package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  250. package/adapters/copilot/.feature-forge-bundle.json +1 -1
  251. package/adapters/copilot/agents/forge-researcher.md +1 -1
  252. package/adapters/copilot/agents/forge-verifier.md +17 -6
  253. package/adapters/copilot/references/forge-config-schema.json +5 -5
  254. package/adapters/copilot/references/portable-root.md +20 -16
  255. package/adapters/copilot/references/preflight-and-self-heal.md +183 -0
  256. package/adapters/copilot/references/process-overview.md +10 -10
  257. package/adapters/copilot/references/select-outcome.md +138 -0
  258. package/adapters/copilot/references/shared-conventions.md +188 -35
  259. package/adapters/copilot/references/skill-frontmatter.schema.json +3 -1
  260. package/adapters/copilot/references/stack-resolution.md +1 -1
  261. package/adapters/copilot/references/stage-exit-protocol.md +22 -7
  262. package/adapters/copilot/references/templates/root-hygiene/AGENTS.md +14 -0
  263. package/adapters/copilot/references/templates/root-hygiene/CLAUDE.md +14 -0
  264. package/adapters/copilot/references/templates/specs-hygiene/AGENTS.md +3 -1
  265. package/adapters/copilot/references/templates/specs-hygiene/CLAUDE.md +2 -1
  266. package/adapters/copilot/references/verifier-patterns/MEMORY.md +35 -0
  267. package/adapters/copilot/references/verifier-patterns/pattern_absence_claims.md +36 -0
  268. package/adapters/copilot/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  269. package/adapters/copilot/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  270. package/adapters/copilot/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  271. package/adapters/copilot/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  272. package/adapters/copilot/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  273. package/adapters/copilot/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  274. package/adapters/copilot/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  275. package/adapters/copilot/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  276. package/adapters/copilot/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  277. package/adapters/copilot/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  278. package/adapters/copilot/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  279. package/adapters/copilot/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  280. package/adapters/copilot/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  281. package/adapters/copilot/references/verifier-patterns/pattern_test_count_units.md +20 -0
  282. package/adapters/copilot/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  283. package/adapters/copilot/references/verify-state.md +98 -0
  284. package/adapters/copilot/scripts/forge-bootstrap.py +23 -2
  285. package/adapters/copilot/scripts/forge-root.sh +104 -21
  286. package/adapters/copilot/scripts/forge-session.py +620 -6973
  287. package/adapters/copilot/scripts/forge_session/__init__.py +11 -0
  288. package/adapters/copilot/scripts/forge_session/_common.py +1566 -0
  289. package/adapters/copilot/scripts/forge_session/_doctor_util.py +218 -0
  290. package/adapters/copilot/scripts/forge_session/cli.py +1067 -0
  291. package/adapters/copilot/scripts/forge_session/decisions.py +355 -0
  292. package/adapters/copilot/scripts/forge_session/discover.py +472 -0
  293. package/adapters/copilot/scripts/forge_session/doctor.py +2144 -0
  294. package/adapters/copilot/scripts/forge_session/exit.py +1081 -0
  295. package/adapters/copilot/scripts/forge_session/outcomes.py +574 -0
  296. package/adapters/copilot/scripts/forge_session/routes.py +1162 -0
  297. package/adapters/copilot/scripts/forge_session/state.py +1479 -0
  298. package/adapters/copilot/scripts/forge_session/topology.py +443 -0
  299. package/adapters/copilot/skills/forge/forge.md +40 -40
  300. package/adapters/copilot/skills/forge/references/process-overview.md +10 -10
  301. package/adapters/copilot/skills/forge/references/shared-conventions.md +188 -35
  302. package/adapters/copilot/skills/forge/references/stage-exit-protocol.md +22 -7
  303. package/adapters/copilot/skills/forge-0-epic/forge-0-epic.md +15 -15
  304. package/adapters/copilot/skills/forge-0-epic/references/edit-mode.md +14 -10
  305. package/adapters/copilot/skills/forge-0-epic/references/epic-manifest-subcommands.md +8 -3
  306. package/adapters/copilot/skills/forge-0-epic/references/portable-root.md +20 -16
  307. package/adapters/copilot/skills/forge-0-epic/references/shared-conventions.md +188 -35
  308. package/adapters/copilot/skills/forge-0-epic/references/stage-exit-protocol.md +22 -7
  309. package/adapters/copilot/skills/forge-1-prd/forge-1-prd.md +15 -15
  310. package/adapters/copilot/skills/forge-1-prd/references/shared-conventions.md +188 -35
  311. package/adapters/copilot/skills/forge-1-prd/references/stage-exit-protocol.md +22 -7
  312. package/adapters/copilot/skills/forge-2-tech/forge-2-tech.md +15 -15
  313. package/adapters/copilot/skills/forge-2-tech/references/shared-conventions.md +188 -35
  314. package/adapters/copilot/skills/forge-2-tech/references/stack-resolution.md +1 -1
  315. package/adapters/copilot/skills/forge-2-tech/references/stage-exit-protocol.md +22 -7
  316. package/adapters/copilot/skills/forge-3-specs/forge-3-specs.md +10 -10
  317. package/adapters/copilot/skills/forge-3-specs/references/shared-conventions.md +188 -35
  318. package/adapters/copilot/skills/forge-3-specs/references/stage-exit-protocol.md +22 -7
  319. package/adapters/copilot/skills/forge-4-backlog/forge-4-backlog.md +19 -11
  320. package/adapters/copilot/skills/forge-4-backlog/references/shared-conventions.md +188 -35
  321. package/adapters/copilot/skills/forge-4-backlog/references/stage-exit-protocol.md +22 -7
  322. package/adapters/copilot/skills/forge-4-backlog/references/verify-state.md +98 -0
  323. package/adapters/copilot/skills/forge-5-loop/forge-5-loop.md +48 -53
  324. package/adapters/copilot/skills/forge-5-loop/references/agent-selection.md +5 -1
  325. package/adapters/copilot/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
  326. package/adapters/copilot/skills/forge-5-loop/references/recovery-procedure.md +14 -4
  327. package/adapters/copilot/skills/forge-5-loop/references/runner-contract.md +18 -8
  328. package/adapters/copilot/skills/forge-5-loop/references/shared-conventions.md +188 -35
  329. package/adapters/copilot/skills/forge-5-loop/references/stage-exit-protocol.md +22 -7
  330. package/adapters/copilot/skills/forge-5-loop/references/verify-state.md +98 -0
  331. package/adapters/copilot/skills/forge-6-docs/forge-6-docs.md +27 -20
  332. package/adapters/copilot/skills/forge-6-docs/references/shared-conventions.md +188 -35
  333. package/adapters/copilot/skills/forge-6-docs/references/stage-exit-protocol.md +22 -7
  334. package/adapters/copilot/skills/forge-6-docs/references/verify-state.md +98 -0
  335. package/adapters/copilot/skills/forge-bootstrap/forge-bootstrap.md +23 -15
  336. package/adapters/copilot/skills/forge-bootstrap/references/shared-conventions.md +572 -0
  337. package/adapters/copilot/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +2 -2
  338. package/adapters/copilot/skills/forge-fix/forge-fix.md +27 -30
  339. package/adapters/copilot/skills/forge-fix/references/select-outcome.md +138 -0
  340. package/adapters/copilot/skills/forge-fix/references/shared-conventions.md +188 -35
  341. package/adapters/copilot/skills/forge-fix/references/stage-exit-protocol.md +22 -7
  342. package/adapters/copilot/skills/forge-guide/forge-guide.md +84 -7
  343. package/adapters/copilot/skills/forge-guide/references/forge-config-schema.json +5 -5
  344. package/adapters/copilot/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
  345. package/adapters/copilot/skills/forge-guide/references/process-overview.md +10 -10
  346. package/adapters/copilot/skills/forge-guide/references/shared-conventions.md +188 -35
  347. package/adapters/copilot/skills/forge-guide/references/stack-resolution.md +1 -1
  348. package/adapters/copilot/skills/forge-init/forge-init.md +62 -12
  349. package/adapters/copilot/skills/forge-init/references/preflight-and-self-heal.md +183 -0
  350. package/adapters/copilot/skills/forge-init/references/shared-conventions.md +572 -0
  351. package/adapters/copilot/skills/forge-verify/forge-verify.md +27 -30
  352. package/adapters/copilot/skills/forge-verify/references/findings-template.md +2 -2
  353. package/adapters/copilot/skills/forge-verify/references/select-outcome.md +138 -0
  354. package/adapters/copilot/skills/forge-verify/references/shared-conventions.md +188 -35
  355. package/adapters/copilot/skills/forge-verify/references/stage-exit-protocol.md +22 -7
  356. package/adapters/copilot/skills/forge-verify/references/verification-checklists/epic.md +2 -2
  357. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
  358. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
  359. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  360. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  361. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  362. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  363. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  364. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  365. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  366. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  367. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  368. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  369. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  370. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  371. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  372. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
  373. package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  374. package/adapters/cursor/.feature-forge-bundle.json +1 -1
  375. package/adapters/cursor/agents/forge-researcher.mdc +1 -1
  376. package/adapters/cursor/agents/forge-verifier.mdc +17 -6
  377. package/adapters/cursor/references/forge-config-schema.json +5 -5
  378. package/adapters/cursor/references/portable-root.md +20 -16
  379. package/adapters/cursor/references/preflight-and-self-heal.md +183 -0
  380. package/adapters/cursor/references/process-overview.md +10 -10
  381. package/adapters/cursor/references/select-outcome.md +138 -0
  382. package/adapters/cursor/references/shared-conventions.md +188 -35
  383. package/adapters/cursor/references/skill-frontmatter.schema.json +3 -1
  384. package/adapters/cursor/references/stack-resolution.md +1 -1
  385. package/adapters/cursor/references/stage-exit-protocol.md +22 -7
  386. package/adapters/cursor/references/templates/root-hygiene/AGENTS.md +14 -0
  387. package/adapters/cursor/references/templates/root-hygiene/CLAUDE.md +14 -0
  388. package/adapters/cursor/references/templates/specs-hygiene/AGENTS.md +3 -1
  389. package/adapters/cursor/references/templates/specs-hygiene/CLAUDE.md +2 -1
  390. package/adapters/cursor/references/verifier-patterns/MEMORY.md +35 -0
  391. package/adapters/cursor/references/verifier-patterns/pattern_absence_claims.md +36 -0
  392. package/adapters/cursor/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  393. package/adapters/cursor/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  394. package/adapters/cursor/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  395. package/adapters/cursor/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  396. package/adapters/cursor/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  397. package/adapters/cursor/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  398. package/adapters/cursor/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  399. package/adapters/cursor/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  400. package/adapters/cursor/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  401. package/adapters/cursor/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  402. package/adapters/cursor/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  403. package/adapters/cursor/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  404. package/adapters/cursor/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  405. package/adapters/cursor/references/verifier-patterns/pattern_test_count_units.md +20 -0
  406. package/adapters/cursor/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  407. package/adapters/cursor/references/verify-state.md +98 -0
  408. package/adapters/cursor/scripts/forge-bootstrap.py +23 -2
  409. package/adapters/cursor/scripts/forge-root.sh +104 -21
  410. package/adapters/cursor/scripts/forge-session.py +620 -6973
  411. package/adapters/cursor/scripts/forge_session/__init__.py +11 -0
  412. package/adapters/cursor/scripts/forge_session/_common.py +1566 -0
  413. package/adapters/cursor/scripts/forge_session/_doctor_util.py +218 -0
  414. package/adapters/cursor/scripts/forge_session/cli.py +1067 -0
  415. package/adapters/cursor/scripts/forge_session/decisions.py +355 -0
  416. package/adapters/cursor/scripts/forge_session/discover.py +472 -0
  417. package/adapters/cursor/scripts/forge_session/doctor.py +2144 -0
  418. package/adapters/cursor/scripts/forge_session/exit.py +1081 -0
  419. package/adapters/cursor/scripts/forge_session/outcomes.py +574 -0
  420. package/adapters/cursor/scripts/forge_session/routes.py +1162 -0
  421. package/adapters/cursor/scripts/forge_session/state.py +1479 -0
  422. package/adapters/cursor/scripts/forge_session/topology.py +443 -0
  423. package/adapters/cursor/skills/forge/forge.mdc +40 -40
  424. package/adapters/cursor/skills/forge/references/process-overview.md +10 -10
  425. package/adapters/cursor/skills/forge/references/shared-conventions.md +188 -35
  426. package/adapters/cursor/skills/forge/references/stage-exit-protocol.md +22 -7
  427. package/adapters/cursor/skills/forge-0-epic/forge-0-epic.mdc +15 -15
  428. package/adapters/cursor/skills/forge-0-epic/references/edit-mode.md +14 -10
  429. package/adapters/cursor/skills/forge-0-epic/references/epic-manifest-subcommands.md +8 -3
  430. package/adapters/cursor/skills/forge-0-epic/references/portable-root.md +20 -16
  431. package/adapters/cursor/skills/forge-0-epic/references/shared-conventions.md +188 -35
  432. package/adapters/cursor/skills/forge-0-epic/references/stage-exit-protocol.md +22 -7
  433. package/adapters/cursor/skills/forge-1-prd/forge-1-prd.mdc +15 -15
  434. package/adapters/cursor/skills/forge-1-prd/references/shared-conventions.md +188 -35
  435. package/adapters/cursor/skills/forge-1-prd/references/stage-exit-protocol.md +22 -7
  436. package/adapters/cursor/skills/forge-2-tech/forge-2-tech.mdc +15 -15
  437. package/adapters/cursor/skills/forge-2-tech/references/shared-conventions.md +188 -35
  438. package/adapters/cursor/skills/forge-2-tech/references/stack-resolution.md +1 -1
  439. package/adapters/cursor/skills/forge-2-tech/references/stage-exit-protocol.md +22 -7
  440. package/adapters/cursor/skills/forge-3-specs/forge-3-specs.mdc +10 -10
  441. package/adapters/cursor/skills/forge-3-specs/references/shared-conventions.md +188 -35
  442. package/adapters/cursor/skills/forge-3-specs/references/stage-exit-protocol.md +22 -7
  443. package/adapters/cursor/skills/forge-4-backlog/forge-4-backlog.mdc +19 -11
  444. package/adapters/cursor/skills/forge-4-backlog/references/shared-conventions.md +188 -35
  445. package/adapters/cursor/skills/forge-4-backlog/references/stage-exit-protocol.md +22 -7
  446. package/adapters/cursor/skills/forge-4-backlog/references/verify-state.md +98 -0
  447. package/adapters/cursor/skills/forge-5-loop/forge-5-loop.mdc +48 -53
  448. package/adapters/cursor/skills/forge-5-loop/references/agent-selection.md +5 -1
  449. package/adapters/cursor/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
  450. package/adapters/cursor/skills/forge-5-loop/references/recovery-procedure.md +14 -4
  451. package/adapters/cursor/skills/forge-5-loop/references/runner-contract.md +18 -8
  452. package/adapters/cursor/skills/forge-5-loop/references/shared-conventions.md +188 -35
  453. package/adapters/cursor/skills/forge-5-loop/references/stage-exit-protocol.md +22 -7
  454. package/adapters/cursor/skills/forge-5-loop/references/verify-state.md +98 -0
  455. package/adapters/cursor/skills/forge-6-docs/forge-6-docs.mdc +27 -20
  456. package/adapters/cursor/skills/forge-6-docs/references/shared-conventions.md +188 -35
  457. package/adapters/cursor/skills/forge-6-docs/references/stage-exit-protocol.md +22 -7
  458. package/adapters/cursor/skills/forge-6-docs/references/verify-state.md +98 -0
  459. package/adapters/cursor/skills/forge-bootstrap/forge-bootstrap.mdc +23 -15
  460. package/adapters/cursor/skills/forge-bootstrap/references/shared-conventions.md +572 -0
  461. package/adapters/cursor/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +2 -2
  462. package/adapters/cursor/skills/forge-fix/forge-fix.mdc +27 -30
  463. package/adapters/cursor/skills/forge-fix/references/select-outcome.md +138 -0
  464. package/adapters/cursor/skills/forge-fix/references/shared-conventions.md +188 -35
  465. package/adapters/cursor/skills/forge-fix/references/stage-exit-protocol.md +22 -7
  466. package/adapters/cursor/skills/forge-guide/forge-guide.mdc +84 -7
  467. package/adapters/cursor/skills/forge-guide/references/forge-config-schema.json +5 -5
  468. package/adapters/cursor/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
  469. package/adapters/cursor/skills/forge-guide/references/process-overview.md +10 -10
  470. package/adapters/cursor/skills/forge-guide/references/shared-conventions.md +188 -35
  471. package/adapters/cursor/skills/forge-guide/references/stack-resolution.md +1 -1
  472. package/adapters/cursor/skills/forge-init/forge-init.mdc +62 -12
  473. package/adapters/cursor/skills/forge-init/references/preflight-and-self-heal.md +183 -0
  474. package/adapters/cursor/skills/forge-init/references/shared-conventions.md +572 -0
  475. package/adapters/cursor/skills/forge-verify/forge-verify.mdc +27 -30
  476. package/adapters/cursor/skills/forge-verify/references/findings-template.md +2 -2
  477. package/adapters/cursor/skills/forge-verify/references/select-outcome.md +138 -0
  478. package/adapters/cursor/skills/forge-verify/references/shared-conventions.md +188 -35
  479. package/adapters/cursor/skills/forge-verify/references/stage-exit-protocol.md +22 -7
  480. package/adapters/cursor/skills/forge-verify/references/verification-checklists/epic.md +2 -2
  481. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
  482. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
  483. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  484. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  485. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  486. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  487. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  488. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  489. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  490. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  491. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  492. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  493. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  494. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  495. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  496. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
  497. package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  498. package/adapters/gemini/.feature-forge-bundle.json +1 -1
  499. package/adapters/gemini/agents/forge-researcher.md +1 -1
  500. package/adapters/gemini/agents/forge-verifier.md +17 -6
  501. package/adapters/gemini/gemini-extension.json +13 -13
  502. package/adapters/gemini/references/forge-config-schema.json +5 -5
  503. package/adapters/gemini/references/portable-root.md +20 -16
  504. package/adapters/gemini/references/preflight-and-self-heal.md +183 -0
  505. package/adapters/gemini/references/process-overview.md +10 -10
  506. package/adapters/gemini/references/select-outcome.md +138 -0
  507. package/adapters/gemini/references/shared-conventions.md +188 -35
  508. package/adapters/gemini/references/skill-frontmatter.schema.json +3 -1
  509. package/adapters/gemini/references/stack-resolution.md +1 -1
  510. package/adapters/gemini/references/stage-exit-protocol.md +22 -7
  511. package/adapters/gemini/references/templates/root-hygiene/AGENTS.md +14 -0
  512. package/adapters/gemini/references/templates/root-hygiene/CLAUDE.md +14 -0
  513. package/adapters/gemini/references/templates/specs-hygiene/AGENTS.md +3 -1
  514. package/adapters/gemini/references/templates/specs-hygiene/CLAUDE.md +2 -1
  515. package/adapters/gemini/references/verifier-patterns/MEMORY.md +35 -0
  516. package/adapters/gemini/references/verifier-patterns/pattern_absence_claims.md +36 -0
  517. package/adapters/gemini/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  518. package/adapters/gemini/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  519. package/adapters/gemini/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  520. package/adapters/gemini/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  521. package/adapters/gemini/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  522. package/adapters/gemini/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  523. package/adapters/gemini/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  524. package/adapters/gemini/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  525. package/adapters/gemini/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  526. package/adapters/gemini/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  527. package/adapters/gemini/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  528. package/adapters/gemini/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  529. package/adapters/gemini/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  530. package/adapters/gemini/references/verifier-patterns/pattern_test_count_units.md +20 -0
  531. package/adapters/gemini/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  532. package/adapters/gemini/references/verify-state.md +98 -0
  533. package/adapters/gemini/scripts/forge-bootstrap.py +23 -2
  534. package/adapters/gemini/scripts/forge-root.sh +104 -21
  535. package/adapters/gemini/scripts/forge-session.py +620 -6973
  536. package/adapters/gemini/scripts/forge_session/__init__.py +11 -0
  537. package/adapters/gemini/scripts/forge_session/_common.py +1566 -0
  538. package/adapters/gemini/scripts/forge_session/_doctor_util.py +218 -0
  539. package/adapters/gemini/scripts/forge_session/cli.py +1067 -0
  540. package/adapters/gemini/scripts/forge_session/decisions.py +355 -0
  541. package/adapters/gemini/scripts/forge_session/discover.py +472 -0
  542. package/adapters/gemini/scripts/forge_session/doctor.py +2144 -0
  543. package/adapters/gemini/scripts/forge_session/exit.py +1081 -0
  544. package/adapters/gemini/scripts/forge_session/outcomes.py +574 -0
  545. package/adapters/gemini/scripts/forge_session/routes.py +1162 -0
  546. package/adapters/gemini/scripts/forge_session/state.py +1479 -0
  547. package/adapters/gemini/scripts/forge_session/topology.py +443 -0
  548. package/adapters/gemini/skills/forge/forge.md +40 -40
  549. package/adapters/gemini/skills/forge/references/process-overview.md +10 -10
  550. package/adapters/gemini/skills/forge/references/shared-conventions.md +188 -35
  551. package/adapters/gemini/skills/forge/references/stage-exit-protocol.md +22 -7
  552. package/adapters/gemini/skills/forge-0-epic/forge-0-epic.md +15 -15
  553. package/adapters/gemini/skills/forge-0-epic/references/edit-mode.md +14 -10
  554. package/adapters/gemini/skills/forge-0-epic/references/epic-manifest-subcommands.md +8 -3
  555. package/adapters/gemini/skills/forge-0-epic/references/portable-root.md +20 -16
  556. package/adapters/gemini/skills/forge-0-epic/references/shared-conventions.md +188 -35
  557. package/adapters/gemini/skills/forge-0-epic/references/stage-exit-protocol.md +22 -7
  558. package/adapters/gemini/skills/forge-1-prd/forge-1-prd.md +15 -15
  559. package/adapters/gemini/skills/forge-1-prd/references/shared-conventions.md +188 -35
  560. package/adapters/gemini/skills/forge-1-prd/references/stage-exit-protocol.md +22 -7
  561. package/adapters/gemini/skills/forge-2-tech/forge-2-tech.md +15 -15
  562. package/adapters/gemini/skills/forge-2-tech/references/shared-conventions.md +188 -35
  563. package/adapters/gemini/skills/forge-2-tech/references/stack-resolution.md +1 -1
  564. package/adapters/gemini/skills/forge-2-tech/references/stage-exit-protocol.md +22 -7
  565. package/adapters/gemini/skills/forge-3-specs/forge-3-specs.md +10 -10
  566. package/adapters/gemini/skills/forge-3-specs/references/shared-conventions.md +188 -35
  567. package/adapters/gemini/skills/forge-3-specs/references/stage-exit-protocol.md +22 -7
  568. package/adapters/gemini/skills/forge-4-backlog/forge-4-backlog.md +19 -11
  569. package/adapters/gemini/skills/forge-4-backlog/references/shared-conventions.md +188 -35
  570. package/adapters/gemini/skills/forge-4-backlog/references/stage-exit-protocol.md +22 -7
  571. package/adapters/gemini/skills/forge-4-backlog/references/verify-state.md +98 -0
  572. package/adapters/gemini/skills/forge-5-loop/forge-5-loop.md +48 -53
  573. package/adapters/gemini/skills/forge-5-loop/references/agent-selection.md +5 -1
  574. package/adapters/gemini/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
  575. package/adapters/gemini/skills/forge-5-loop/references/recovery-procedure.md +14 -4
  576. package/adapters/gemini/skills/forge-5-loop/references/runner-contract.md +18 -8
  577. package/adapters/gemini/skills/forge-5-loop/references/shared-conventions.md +188 -35
  578. package/adapters/gemini/skills/forge-5-loop/references/stage-exit-protocol.md +22 -7
  579. package/adapters/gemini/skills/forge-5-loop/references/verify-state.md +98 -0
  580. package/adapters/gemini/skills/forge-6-docs/forge-6-docs.md +27 -20
  581. package/adapters/gemini/skills/forge-6-docs/references/shared-conventions.md +188 -35
  582. package/adapters/gemini/skills/forge-6-docs/references/stage-exit-protocol.md +22 -7
  583. package/adapters/gemini/skills/forge-6-docs/references/verify-state.md +98 -0
  584. package/adapters/gemini/skills/forge-bootstrap/forge-bootstrap.md +23 -15
  585. package/adapters/gemini/skills/forge-bootstrap/references/shared-conventions.md +572 -0
  586. package/adapters/gemini/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +2 -2
  587. package/adapters/gemini/skills/forge-fix/forge-fix.md +27 -30
  588. package/adapters/gemini/skills/forge-fix/references/select-outcome.md +138 -0
  589. package/adapters/gemini/skills/forge-fix/references/shared-conventions.md +188 -35
  590. package/adapters/gemini/skills/forge-fix/references/stage-exit-protocol.md +22 -7
  591. package/adapters/gemini/skills/forge-guide/forge-guide.md +84 -7
  592. package/adapters/gemini/skills/forge-guide/references/forge-config-schema.json +5 -5
  593. package/adapters/gemini/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
  594. package/adapters/gemini/skills/forge-guide/references/process-overview.md +10 -10
  595. package/adapters/gemini/skills/forge-guide/references/shared-conventions.md +188 -35
  596. package/adapters/gemini/skills/forge-guide/references/stack-resolution.md +1 -1
  597. package/adapters/gemini/skills/forge-init/forge-init.md +62 -12
  598. package/adapters/gemini/skills/forge-init/references/preflight-and-self-heal.md +183 -0
  599. package/adapters/gemini/skills/forge-init/references/shared-conventions.md +572 -0
  600. package/adapters/gemini/skills/forge-verify/forge-verify.md +27 -30
  601. package/adapters/gemini/skills/forge-verify/references/findings-template.md +2 -2
  602. package/adapters/gemini/skills/forge-verify/references/select-outcome.md +138 -0
  603. package/adapters/gemini/skills/forge-verify/references/shared-conventions.md +188 -35
  604. package/adapters/gemini/skills/forge-verify/references/stage-exit-protocol.md +22 -7
  605. package/adapters/gemini/skills/forge-verify/references/verification-checklists/epic.md +2 -2
  606. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
  607. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
  608. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  609. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  610. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  611. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  612. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  613. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  614. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  615. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  616. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  617. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  618. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  619. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  620. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  621. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
  622. package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  623. package/adapters/pi/.feature-forge-bundle.json +1 -1
  624. package/adapters/pi/agents/forge-verifier.md +16 -5
  625. package/adapters/pi/extensions/forge-invocation-args/README.md +106 -0
  626. package/adapters/pi/extensions/forge-invocation-args/index.ts +31 -0
  627. package/adapters/pi/extensions/forge-invocation-args/wiring.ts +172 -0
  628. package/adapters/pi/package.json +1 -0
  629. package/adapters/pi/references/forge-config-schema.json +3 -3
  630. package/adapters/pi/references/portable-root.md +20 -16
  631. package/adapters/pi/references/preflight-and-self-heal.md +183 -0
  632. package/adapters/pi/references/select-outcome.md +138 -0
  633. package/adapters/pi/references/shared-conventions.md +175 -22
  634. package/adapters/pi/references/skill-frontmatter.schema.json +3 -1
  635. package/adapters/pi/references/stack-resolution.md +1 -1
  636. package/adapters/pi/references/stage-exit-protocol.md +19 -4
  637. package/adapters/pi/references/templates/root-hygiene/AGENTS.md +14 -0
  638. package/adapters/pi/references/templates/root-hygiene/CLAUDE.md +14 -0
  639. package/adapters/pi/references/templates/specs-hygiene/AGENTS.md +3 -1
  640. package/adapters/pi/references/templates/specs-hygiene/CLAUDE.md +2 -1
  641. package/adapters/pi/references/verifier-patterns/MEMORY.md +35 -0
  642. package/adapters/pi/references/verifier-patterns/pattern_absence_claims.md +36 -0
  643. package/adapters/pi/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  644. package/adapters/pi/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  645. package/adapters/pi/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  646. package/adapters/pi/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  647. package/adapters/pi/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  648. package/adapters/pi/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  649. package/adapters/pi/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  650. package/adapters/pi/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  651. package/adapters/pi/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  652. package/adapters/pi/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  653. package/adapters/pi/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  654. package/adapters/pi/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  655. package/adapters/pi/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  656. package/adapters/pi/references/verifier-patterns/pattern_test_count_units.md +20 -0
  657. package/adapters/pi/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  658. package/adapters/pi/references/verify-state.md +98 -0
  659. package/adapters/pi/scripts/forge-bootstrap.py +23 -2
  660. package/adapters/pi/scripts/forge-root.sh +104 -21
  661. package/adapters/pi/scripts/forge-session.py +620 -6973
  662. package/adapters/pi/scripts/forge_session/__init__.py +11 -0
  663. package/adapters/pi/scripts/forge_session/_common.py +1566 -0
  664. package/adapters/pi/scripts/forge_session/_doctor_util.py +218 -0
  665. package/adapters/pi/scripts/forge_session/cli.py +1067 -0
  666. package/adapters/pi/scripts/forge_session/decisions.py +355 -0
  667. package/adapters/pi/scripts/forge_session/discover.py +472 -0
  668. package/adapters/pi/scripts/forge_session/doctor.py +2144 -0
  669. package/adapters/pi/scripts/forge_session/exit.py +1081 -0
  670. package/adapters/pi/scripts/forge_session/outcomes.py +574 -0
  671. package/adapters/pi/scripts/forge_session/routes.py +1162 -0
  672. package/adapters/pi/scripts/forge_session/state.py +1479 -0
  673. package/adapters/pi/scripts/forge_session/topology.py +443 -0
  674. package/adapters/pi/skills/forge/SKILL.md +9 -8
  675. package/adapters/pi/skills/forge/references/shared-conventions.md +175 -22
  676. package/adapters/pi/skills/forge/references/stage-exit-protocol.md +19 -4
  677. package/adapters/pi/skills/forge-0-epic/SKILL.md +9 -8
  678. package/adapters/pi/skills/forge-0-epic/references/edit-mode.md +9 -5
  679. package/adapters/pi/skills/forge-0-epic/references/epic-manifest-subcommands.md +6 -1
  680. package/adapters/pi/skills/forge-0-epic/references/portable-root.md +20 -16
  681. package/adapters/pi/skills/forge-0-epic/references/shared-conventions.md +175 -22
  682. package/adapters/pi/skills/forge-0-epic/references/stage-exit-protocol.md +19 -4
  683. package/adapters/pi/skills/forge-1-prd/SKILL.md +9 -8
  684. package/adapters/pi/skills/forge-1-prd/references/shared-conventions.md +175 -22
  685. package/adapters/pi/skills/forge-1-prd/references/stage-exit-protocol.md +19 -4
  686. package/adapters/pi/skills/forge-2-tech/SKILL.md +8 -7
  687. package/adapters/pi/skills/forge-2-tech/references/shared-conventions.md +175 -22
  688. package/adapters/pi/skills/forge-2-tech/references/stack-resolution.md +1 -1
  689. package/adapters/pi/skills/forge-2-tech/references/stage-exit-protocol.md +19 -4
  690. package/adapters/pi/skills/forge-3-specs/SKILL.md +7 -6
  691. package/adapters/pi/skills/forge-3-specs/references/shared-conventions.md +175 -22
  692. package/adapters/pi/skills/forge-3-specs/references/stage-exit-protocol.md +19 -4
  693. package/adapters/pi/skills/forge-4-backlog/SKILL.md +18 -9
  694. package/adapters/pi/skills/forge-4-backlog/references/shared-conventions.md +175 -22
  695. package/adapters/pi/skills/forge-4-backlog/references/stage-exit-protocol.md +19 -4
  696. package/adapters/pi/skills/forge-4-backlog/references/verify-state.md +98 -0
  697. package/adapters/pi/skills/forge-5-loop/SKILL.md +38 -42
  698. package/adapters/pi/skills/forge-5-loop/references/agent-selection.md +4 -0
  699. package/adapters/pi/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
  700. package/adapters/pi/skills/forge-5-loop/references/recovery-procedure.md +10 -0
  701. package/adapters/pi/skills/forge-5-loop/references/runner-contract.md +15 -5
  702. package/adapters/pi/skills/forge-5-loop/references/shared-conventions.md +175 -22
  703. package/adapters/pi/skills/forge-5-loop/references/stage-exit-protocol.md +19 -4
  704. package/adapters/pi/skills/forge-5-loop/references/verify-state.md +98 -0
  705. package/adapters/pi/skills/forge-6-docs/SKILL.md +23 -15
  706. package/adapters/pi/skills/forge-6-docs/references/shared-conventions.md +175 -22
  707. package/adapters/pi/skills/forge-6-docs/references/stage-exit-protocol.md +19 -4
  708. package/adapters/pi/skills/forge-6-docs/references/verify-state.md +98 -0
  709. package/adapters/pi/skills/forge-bootstrap/SKILL.md +20 -11
  710. package/adapters/pi/skills/forge-bootstrap/references/shared-conventions.md +572 -0
  711. package/adapters/pi/skills/forge-fix/SKILL.md +17 -19
  712. package/adapters/pi/skills/forge-fix/references/select-outcome.md +138 -0
  713. package/adapters/pi/skills/forge-fix/references/shared-conventions.md +175 -22
  714. package/adapters/pi/skills/forge-fix/references/stage-exit-protocol.md +19 -4
  715. package/adapters/pi/skills/forge-guide/SKILL.md +79 -1
  716. package/adapters/pi/skills/forge-guide/references/forge-config-schema.json +3 -3
  717. package/adapters/pi/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
  718. package/adapters/pi/skills/forge-guide/references/shared-conventions.md +175 -22
  719. package/adapters/pi/skills/forge-guide/references/stack-resolution.md +1 -1
  720. package/adapters/pi/skills/forge-init/SKILL.md +59 -8
  721. package/adapters/pi/skills/forge-init/references/preflight-and-self-heal.md +183 -0
  722. package/adapters/pi/skills/forge-init/references/shared-conventions.md +572 -0
  723. package/adapters/pi/skills/forge-verify/SKILL.md +17 -19
  724. package/adapters/pi/skills/forge-verify/references/findings-template.md +1 -1
  725. package/adapters/pi/skills/forge-verify/references/select-outcome.md +138 -0
  726. package/adapters/pi/skills/forge-verify/references/shared-conventions.md +175 -22
  727. package/adapters/pi/skills/forge-verify/references/stage-exit-protocol.md +19 -4
  728. package/adapters/pi/skills/forge-verify/references/verification-checklists/epic.md +1 -1
  729. package/adapters/pi/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
  730. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
  731. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
  732. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
  733. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
  734. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
  735. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
  736. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
  737. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
  738. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
  739. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
  740. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
  741. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
  742. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
  743. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
  744. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
  745. package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
  746. package/dist/manifest.d.ts +1 -1
  747. package/dist/rauf.d.ts +3 -3
  748. package/dist/rauf.js +2 -2
  749. package/dist/source.d.ts +5 -2
  750. package/dist/source.js +5 -2
  751. package/dist/types.d.ts +1 -1
  752. package/dist/types.js +1 -1
  753. package/package.json +2 -1
@@ -0,0 +1,1162 @@
1
+ """Stage-exit routing engine — the branch/docs/loop terminus builders (#279).
2
+
3
+ Extracted verbatim from the ``forge-session.py`` monolith (issue #279, item 007):
4
+ ``stage_exit`` (in :mod:`forge_session.exit`) consumes these; they consume only
5
+ shared primitives from :mod:`forge_session._common` and the write helpers in
6
+ :mod:`forge_session.state`. Nothing here imports the shim, so there is no circular
7
+ import. Behaviour is FROZEN: branch routing, exit codes, and the JSON payload shape
8
+ are unchanged from the monolith.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import subprocess
15
+ import sys
16
+ from pathlib import Path
17
+ from typing import Final, NoReturn
18
+
19
+ from forge_session._common import (
20
+ AUTO_VERIFY_DEBT_METADATA_DIAGNOSTIC,
21
+ NEXT_STEPS_SENTINEL,
22
+ PRODUCTION_STAGES,
23
+ UsageError,
24
+ _commit_state,
25
+ _now_iso,
26
+ _scheduled_stage_version,
27
+ _stage_version,
28
+ _verify_entry,
29
+ auto_pending_message,
30
+ )
31
+ from forge_session.state import _load_verify_target, _verify_result_entry
32
+
33
+ #: Bundled beside ``forge-session.py`` (one dir above this package module), the
34
+ #: docs router shells out to the sibling ``epic-manifest.py`` there — never a bare
35
+ #: ``python3``. ``__file__`` here is ``<scripts>/forge_session/routes.py``, so the
36
+ #: sibling resolves one directory up.
37
+ _SCRIPTS_DIR: Final = Path(__file__).resolve().parent.parent
38
+
39
+ #: The route each branch outcome takes. Every value is a COMPLETE map over
40
+ #: ``EXIT_OUTCOMES[stage]``: REQ-ROUTE-05/06 require a terminus for every outcome and
41
+ #: forbid a fall-through, so a missing key is a bug, not a default. The four kinds:
42
+ #:
43
+ #: ``successor`` rejoin the live production position after the served stage
44
+ #: ``fix`` ``/skill:forge-fix FEATURE --served-stage SERVED``
45
+ #: ``verify`` ``/skill:forge-verify FEATURE --served-stage SERVED``
46
+ #: ``verify-if-owed`` ``verify`` while verification is still owed, else ``successor``
47
+ #:
48
+ #: Only ``successor`` advances. ``decisions``, ``failed``, ``deferred``, and
49
+ #: ``reverify-findings`` are deliberately absent from it: unresolved work never
50
+ #: reaches a production stage.
51
+ _BRANCH_ROUTE_KIND: Final[dict[str, dict[str, str]]] = {
52
+ "forge-verify": {
53
+ "passed": "successor",
54
+ "findings": "fix",
55
+ "skipped": "successor",
56
+ "failed": "verify",
57
+ },
58
+ "forge-fix": {
59
+ "no-findings": "verify-if-owed",
60
+ "decisions": "fix",
61
+ "failed": "fix",
62
+ # `applied` is NOT `reverified`: the writer clears `verifiedStageVersion`, so
63
+ # re-verification is mandatory and this may never route to production.
64
+ "applied": "verify",
65
+ "reverified": "successor",
66
+ "reverify-findings": "fix",
67
+ "deferred": "fix",
68
+ },
69
+ }
70
+ #: The deterministic sentence each branch outcome renders inside its NEXT-STEPS block
71
+ #: (``_next_steps_block(..., outcome_text=...)``). Every non-advancing outcome names
72
+ #: the unresolved work explicitly, which is what is required of `decisions`,
73
+ #: `failed`, and `deferred`, and what is required of a `failed` verification.
74
+ _BRANCH_OUTCOME_TEXT: Final[dict[str, dict[str, str]]] = {
75
+ "forge-verify": {
76
+ "passed": (
77
+ "Verification passed for {served} — the pipeline rejoins where the "
78
+ "diversion left it."
79
+ ),
80
+ "findings": (
81
+ "Verification reported findings for {served}. They are recorded and "
82
+ "remain unresolved, so the pipeline does not advance until they are "
83
+ "fixed and re-verification passes."
84
+ ),
85
+ "skipped": (
86
+ "Verification for {served} was explicitly skipped and the skip is "
87
+ "recorded, so the pipeline may continue."
88
+ ),
89
+ "failed": (
90
+ "Verification for {served} could not run to a result — the dispatch, the "
91
+ "check, or the state write failed. Nothing advances until it does: "
92
+ "resolve the failure, then re-run the verification below."
93
+ ),
94
+ },
95
+ "forge-fix": {
96
+ "no-findings": (
97
+ "No applicable findings were found for {served}, but its verification is "
98
+ "still owed — the absence of applicable findings is not a pass, so "
99
+ "verification runs before the pipeline advances."
100
+ ),
101
+ "decisions": (
102
+ "The fix stopped on unresolved decisions for {served}. Answer them and "
103
+ "re-run the fix below; the pipeline does not advance while they are open."
104
+ ),
105
+ "failed": (
106
+ "The fix for {served} failed — a fix step, a validation, a commit, or a "
107
+ "state write did not complete. The findings remain unresolved, so the "
108
+ "pipeline does not advance; address the failure and re-run the fix below."
109
+ ),
110
+ "applied": (
111
+ "Fixes were applied for {served}, but applied is not verified: the "
112
+ "recorded freshness was cleared, so re-verification is mandatory before "
113
+ "the pipeline advances."
114
+ ),
115
+ "reverified": (
116
+ "Re-verification passed for {served} — the findings are resolved and the "
117
+ "pipeline rejoins where the diversion left it."
118
+ ),
119
+ "reverify-findings": (
120
+ "Re-verification reported further findings for {served}. They remain "
121
+ "unresolved, so the pipeline does not advance."
122
+ ),
123
+ "deferred": (
124
+ "Fix work for {served} was explicitly deferred. The findings remain "
125
+ "UNRESOLVED — the pipeline does not advance until they are fixed and "
126
+ "re-verification passes."
127
+ ),
128
+ },
129
+ }
130
+ #: `no-findings` is the one outcome whose terminus depends on live state, so it has a
131
+ #: second sentence for the already-resolved case.
132
+ _NO_FINDINGS_RESOLVED_TEXT: Final[str] = (
133
+ "No applicable findings were found for {served}, and its verification is already "
134
+ "resolved — the pipeline rejoins where the diversion left it."
135
+ )
136
+ def _host_command(command: str, host: str) -> str:
137
+ """Rewrite a `/skill:` slash command to the host's surface.
138
+
139
+ Pi's slash-command surface is `/skill:` (matching the adapter body's
140
+ `/skill:` -> `/skill:` translation). The scripted stage-exit output bypasses
141
+ that body translation, so it rewrites the commands it emits here. No-op for
142
+ claude/generic, which keep the canonical `/skill:` form.
143
+ """
144
+ return command.replace("/skill:", "/skill:") if host == "pi" else command
145
+ def _next_steps_block(
146
+ primary_command: str | None,
147
+ host: str,
148
+ reconcile: dict | None = None,
149
+ deferred_command: str | None = None,
150
+ outcome_text: str | None = None,
151
+ ) -> str:
152
+ """Render one sentinel-terminated terminal block.
153
+
154
+ Args:
155
+ primary_command: The sole fenced action, or None for a TERMINAL block — a
156
+ finished epic (#248), where every command this exit could fence is the
157
+ dashboard it was just run from, so fencing one is a self-loop. A terminal
158
+ block carries NO fenced command; `outcome_text` states what finished and
159
+ names any optional follow-on as inline prose.
160
+ host: Command and fresh-session wording target.
161
+ reconcile: Existing epic-backflow override metadata.
162
+ deferred_command: Optional production action allowed only after the primary
163
+ verification/recovery action succeeds.
164
+ outcome_text: Optional deterministic loop/docs/branch outcome explanation.
165
+
166
+ Returns:
167
+ A string whose final line is exactly `NEXT_STEPS_SENTINEL`.
168
+
169
+ The Claude wording uses the literal ``/clear`` slash-command; the generic
170
+ wording is host-neutral (matching the adapter build's host-term table, so
171
+ a non-Claude bundle invoking ``--host generic`` never instructs a fake
172
+ slash-command).
173
+
174
+ ``deferred_command`` is the caller's signal that ``primary_command`` is a
175
+ verification/recovery action standing in front of a production successor: it
176
+ is rendered only as unfenced conditional prose, and the fresh-session wording
177
+ follows the primary action instead of promising "the next stage below"
178
+ (REQ-EXIT-06). It is NEVER fenced, so it cannot be mistaken for the
179
+ primary action.
180
+
181
+ ``reconcile`` carries the epic-backflow routing (§Epic backflow in
182
+ ``references/stage-exit-protocol.md``). When it marks a **blocking** request
183
+ (``required: true``) AND the caller made the reconcile command primary, the
184
+ fence carries it and the normal next stage is demoted to a follow-up line.
185
+ When verification is still outstanding the caller keeps the verify command
186
+ primary instead; the reconcile then becomes the FIRST deferred action, ahead
187
+ of the ordinary production successor. When only **non-blocking**
188
+ requests are present (``reminder: true``), a reminder line is appended.
189
+ Either way the added prose is host-neutral (no literal ``/clear``) so it
190
+ survives verbatim into a generic bundle.
191
+ """
192
+ verify_first = deferred_command is not None
193
+ if host == "claude":
194
+ clear_line = (
195
+ "1. `/clear` — recommended unconditionally at this stage boundary; "
196
+ "every artifact is on disk, so the work survives the clear. "
197
+ "I can't `/clear` for you — you have to run it yourself."
198
+ )
199
+ navigator = "`/skill:forge`"
200
+ fresh_prefix = "2. Then start a fresh session and run"
201
+ elif host == "pi":
202
+ # Pi's fresh-session command is `/new` (not `/clear`); its slash-command
203
+ # surface is `/skill:` (the fenced command below is rewritten to match).
204
+ clear_line = (
205
+ "1. `/new` — recommended unconditionally at this stage boundary; every "
206
+ "artifact is on disk, so the work survives starting a fresh session. "
207
+ "I can't run `/new` for you — you have to run it yourself."
208
+ )
209
+ navigator = "`/skill:forge`"
210
+ fresh_prefix = "2. Then, in the new session, run"
211
+ else:
212
+ clear_line = (
213
+ "1. Clear your session / start a fresh session — recommended "
214
+ "unconditionally at this stage boundary; every artifact is on "
215
+ "disk, so the work survives it."
216
+ )
217
+ navigator = None
218
+ fresh_prefix = "2. Then start a fresh session and run"
219
+ resume = (
220
+ f"re-run {navigator} to let the navigator resume from disk."
221
+ if navigator
222
+ else "re-run the forge navigator skill to resume from disk."
223
+ )
224
+ # The primary actionable command goes in a fenced block so mobile/remote hosts
225
+ # get a native copy button (inline code is not tap-to-copy). The CALLER decides
226
+ # which command is primary (the renderer fences exactly what it
227
+ # is given); the fence sits before the sentinel, so the sentinel remains the
228
+ # absolute last line. A terminal block fences nothing at all.
229
+ terminal = primary_command is None
230
+ fenced_command = "" if terminal else _host_command(primary_command, host)
231
+ if terminal:
232
+ # A deferred successor and a BLOCKING reconcile both presuppose a required next
233
+ # action, so neither can coexist with "nothing further is required".
234
+ # `stage_exit` already clears them; asserting it here keeps a future caller from
235
+ # rendering a block that says nothing further and then names something further.
236
+ # A non-blocking REMINDER is explicitly allowed — its inline line is an offer,
237
+ # not an action, and suppressing it would drop a recorded request on the floor.
238
+ assert deferred_command is None and not (
239
+ reconcile and reconcile.get("required")
240
+ ), "a terminal NEXT-STEPS block carries no deferred or blocking follow-up"
241
+ if verify_first:
242
+ # REQ-EXIT-06: the fresh-session guidance follows the PRIMARY action, and
243
+ # must never tell the user to clear and run the production successor first.
244
+ # It names what is actually FENCED: on a branch exit that is the fix standing
245
+ # between recorded findings and the re-verification, not a verify
246
+ # command. Every other case keeps the wording verbatim.
247
+ action_noun = "fix" if "forge-fix " in fenced_command else "verification"
248
+ next_line = (
249
+ f"{fresh_prefix} the {action_noun} below — verification is still "
250
+ "outstanding for this stage, so it comes before the next production "
251
+ f"stage. Or {resume}"
252
+ )
253
+ elif terminal:
254
+ # No "below" to point at: `fresh_prefix` numbers the step, and the sentence
255
+ # says the pipeline is done rather than naming an action that does not exist.
256
+ # The navigator resume stays available as prose — inspecting a finished epic is
257
+ # not the same as being told to run it again.
258
+ next_line = (
259
+ "2. Nothing further is required here — there is no next pipeline command. "
260
+ f"To inspect the finished state, {resume}"
261
+ )
262
+ else:
263
+ next_line = f"{fresh_prefix} the next stage below — or {resume}"
264
+ blocking = bool(reconcile and reconcile.get("required"))
265
+ reconcile_is_primary = bool(
266
+ blocking and _host_command(reconcile["command"], host) == fenced_command
267
+ )
268
+ lines = ["**Next steps**"]
269
+ if outcome_text:
270
+ lines.append(outcome_text)
271
+ lines.append(clear_line)
272
+ if reconcile_is_primary:
273
+ count = reconcile["count"]
274
+ plural = "s" if count != 1 else ""
275
+ lines.append(
276
+ f"2. Then reconcile the epic **before** the next stage — {count} "
277
+ f"blocking epic change request{plural} flagged, and proceeding would "
278
+ "build this feature's artifacts on a decomposition that is about to "
279
+ "change. Run the reconcile command below first."
280
+ )
281
+ else:
282
+ lines.append(next_line)
283
+ lines.append("")
284
+ if not terminal:
285
+ lines.append(f"```\n{fenced_command}\n```")
286
+ if blocking and not reconcile_is_primary:
287
+ # Verification outranked the reconcile, so the reconcile is the FIRST
288
+ # deferred action and the production successor stays subordinate to it.
289
+ count = reconcile["count"]
290
+ plural = "s" if count != 1 else ""
291
+ lines.append(
292
+ f"After verification passes, reconcile the epic first — {count} "
293
+ f"blocking epic change request{plural} flagged: "
294
+ f"`{_host_command(reconcile['command'], host)}`"
295
+ )
296
+ if blocking and reconcile.get("deferred"):
297
+ deferred_cmd = _host_command(reconcile["deferred"], host)
298
+ lines.append(f"After reconciling, continue the pipeline with: `{deferred_cmd}`")
299
+ elif reconcile and reconcile.get("reminder"):
300
+ count = reconcile["count"]
301
+ plural = "s" if count != 1 else ""
302
+ lines.append(
303
+ f"You also flagged {count} epic change{plural} to reconcile when "
304
+ f"convenient: `{_host_command(reconcile['command'], host)}`"
305
+ )
306
+ if verify_first and _host_command(deferred_command, host) != _host_command(
307
+ (reconcile or {}).get("deferred") or "", host
308
+ ):
309
+ # Unfenced, conditional prose only. Suppressed when the
310
+ # blocking reconcile above already demoted this same command, so one
311
+ # command never appears twice in the deferred chain.
312
+ lines.append(
313
+ "After verification passes, continue with: "
314
+ f"`{_host_command(deferred_command, host)}`"
315
+ )
316
+ lines.append(NEXT_STEPS_SENTINEL)
317
+ return "\n".join(lines)
318
+ def _branch_route(
319
+ stage: str,
320
+ outcome: str,
321
+ feature: str,
322
+ served: str,
323
+ successor_command: str | None,
324
+ resolved: bool,
325
+ ) -> tuple[str, str | None, str, bool]:
326
+ """Route one verify/fix outcome back into the pipeline — the rejoin tables.
327
+
328
+ A verify or fix diversion must rejoin the production stage it SERVED rather than
329
+ dropping the pipeline thread (issue #176), so the served stage is carried forward
330
+ in every branch command this returns. Commands are canonical, pre-`_host_command`
331
+ forms; the renderer translates them.
332
+
333
+ "Live successor" is the current production position, never a conversational
334
+ assumption: `successor_command` is already the state-aware next production action
335
+ after the served artifact, and it is None only at the end of the pipeline — a
336
+ completed stage 6, which routes to the navigator completion action rather than a
337
+ nonexistent stage 7.
338
+
339
+ Args:
340
+ stage: `forge-verify` or `forge-fix`.
341
+ outcome: A member of `EXIT_OUTCOMES[stage]`, already validated.
342
+ feature: The feature (or epic) the diversion served.
343
+ served: The resolved served production stage.
344
+ successor_command: Canonical live-successor command, or None at pipeline end.
345
+ resolved: Whether the served stage's verification is settled. Consulted only
346
+ by `no-findings`, the one outcome whose terminus depends on live state.
347
+
348
+ Returns:
349
+ `(primary_canonical, deferred_canonical, outcome_text, advancing)`.
350
+ `deferred_canonical` is the demoted production successor, rendered only as
351
+ unfenced prose, and is None whenever the primary command already advances.
352
+
353
+ Two rows carry a precondition this router does NOT re-check: `skipped` is valid
354
+ only after the skip is persisted and `reverified` only after a passing state is
355
+ recorded. Both are the CALLER's obligation — the branch skills
356
+ write through `state-verify` before invoking this exit, and a fix
357
+ that merely skips re-verification reports `deferred`, not `reverified`. Rejecting
358
+ the outcome here would make a valid member of `EXIT_OUTCOMES[stage]` exit 2, which
359
+ is a different contract from the one `stage_exit` validates.
360
+ """
361
+ kind = _BRANCH_ROUTE_KIND[stage][outcome]
362
+ if kind == "verify-if-owed":
363
+ template = (
364
+ _NO_FINDINGS_RESOLVED_TEXT if resolved else _BRANCH_OUTCOME_TEXT[stage][outcome]
365
+ )
366
+ kind = "successor" if resolved else "verify"
367
+ else:
368
+ template = _BRANCH_OUTCOME_TEXT[stage][outcome]
369
+ text = template.format(served=served)
370
+
371
+ if kind == "successor":
372
+ return successor_command or f"/skill:forge {feature}", None, text, True
373
+
374
+ branch = "forge-fix" if kind == "fix" else "forge-verify"
375
+ return (
376
+ f"/skill:{branch} {feature} --served-stage {served}",
377
+ successor_command,
378
+ text,
379
+ False,
380
+ )
381
+ #: Keys `_render_status` requires before it will route on a `render-status --json`
382
+ #: payload. `RenderStatus` is TOTAL: every key is always present, so an
383
+ #: empty `actionable` list is the answer "nothing is actionable" and a MISSING key
384
+ #: means the helper is not the contract this router was built against — an
385
+ #: actionable routing failure, never a silently-skipped check.
386
+ _RENDER_STATUS_REQUIRED: Final[tuple[str, ...]] = (
387
+ "epic",
388
+ "status",
389
+ "features",
390
+ "actionable",
391
+ "rollup",
392
+ "nextCommand",
393
+ )
394
+ #: The bound on the one subprocess the docs exit path makes. Matches every other
395
+ #: `subprocess.run` in this file (the git reads and the `forge-root.sh` resolver);
396
+ #: without it a hung or pathological epic would stall stage closure with no
397
+ #: diagnostic, defeating REQ-PERF-01.
398
+ _RENDER_STATUS_TIMEOUT: Final = 10
399
+ def _render_status_failure_detail(proc: subprocess.CompletedProcess) -> str:
400
+ """Name WHY a nonzero ``render-status --json`` failed, in one deterministic line.
401
+
402
+ Its first stderr line when it wrote one (a missing/unreadable manifest exits 2
403
+ that way), else the first validation finding from the JSON on stdout — which is
404
+ the only place an invalid graph reports itself (it exits 1 with
405
+ ``{"valid": false, "findings": [...]}`` and a silent stderr).
406
+
407
+ Args:
408
+ proc: The completed ``render-status`` process.
409
+
410
+ Returns:
411
+ A single-line detail, or ``""`` when the helper said nothing usable.
412
+ """
413
+ lines = proc.stderr.strip().splitlines()
414
+ if lines:
415
+ return lines[0]
416
+ try:
417
+ payload = json.loads(proc.stdout)
418
+ except json.JSONDecodeError:
419
+ return ""
420
+ findings = payload.get("findings") if isinstance(payload, dict) else None
421
+ if isinstance(findings, list) and findings and isinstance(findings[0], dict):
422
+ message = findings[0].get("message")
423
+ if isinstance(message, str):
424
+ return f"first finding: {message}"
425
+ return ""
426
+ def _render_status(specs_dir: Path, epic: str) -> dict:
427
+ """Read LIVE epic status from the sibling ``epic-manifest.py``.
428
+
429
+ The docs exit routes on the epic's real dependency/completion graph rather than
430
+ re-deriving it here: dependency and completion derivation belong to
431
+ ``epic-manifest.py``; duplicating them in this file is forbidden.
432
+
433
+ ``<bundle-root>`` is NOT a path this router may guess. ``forge-session.py`` is
434
+ copied verbatim into six adapter bundles and runs from an arbitrary cwd, so the
435
+ helper is resolved as a SIBLING of this file — the ``RUNTIME_HELPERS`` guarantee
436
+ that ships them together, matching the existing ``_resolve_plugin_root``
437
+ convention — and invoked with ``sys.executable`` rather than a bare ``python3``,
438
+ which may be absent or a different interpreter than the one running this script.
439
+
440
+ Args:
441
+ specs_dir: Configured specs directory, passed through to the helper.
442
+ epic: The epic name; also the subject of every failure message.
443
+
444
+ Returns:
445
+ The parsed ``RenderStatus`` dict.
446
+
447
+ Raises:
448
+ UsageError: A missing sibling helper, a non-zero exit (which covers an
449
+ invalid graph — ``render-status`` refuses to render one), a spawn
450
+ failure, a timeout at the bound, unparseable stdout, or a missing or
451
+ malformed required field. Every one is an actionable exit-2 routing
452
+ failure that names the epic and the recovery command, so the caller
453
+ emits no guessed member route and no sentinel (REQ-REL-02).
454
+
455
+ Reads only the bounded local manifest/member-state set — no network call and no
456
+ repository-history scan (REQ-PERF-01).
457
+ """
458
+ helper = _SCRIPTS_DIR / "epic-manifest.py"
459
+
460
+ def fail(reason: str) -> NoReturn:
461
+ raise UsageError(
462
+ f"cannot route the documentation exit for epic {epic!r}: {reason}. "
463
+ f"Run /skill:forge-0-epic {epic} to inspect the epic and "
464
+ "resolve it, then re-run this exit."
465
+ )
466
+
467
+ if not helper.is_file():
468
+ fail(f"the sibling epic-manifest.py is missing at {helper}")
469
+ try:
470
+ proc = subprocess.run(
471
+ [
472
+ sys.executable,
473
+ str(helper),
474
+ "render-status",
475
+ epic,
476
+ "--specs-dir",
477
+ str(specs_dir),
478
+ "--json",
479
+ ],
480
+ capture_output=True,
481
+ text=True,
482
+ timeout=_RENDER_STATUS_TIMEOUT,
483
+ check=False,
484
+ )
485
+ except subprocess.TimeoutExpired:
486
+ fail(f"render-status did not finish within {_RENDER_STATUS_TIMEOUT} seconds")
487
+ except OSError as exc:
488
+ fail(f"render-status could not be started ({exc})")
489
+ if proc.returncode != 0:
490
+ # An INVALID GRAPH exits 1 with its findings as JSON on stdout and nothing on
491
+ # stderr, so quoting stderr alone would report a bare exit code for the one
492
+ # failure the operator most needs named (REQ-OBS-02).
493
+ detail = _render_status_failure_detail(proc)
494
+ fail(f"render-status exited {proc.returncode}{f' ({detail})' if detail else ''}")
495
+ try:
496
+ status = json.loads(proc.stdout)
497
+ except json.JSONDecodeError as exc:
498
+ fail(f"render-status did not emit parseable JSON ({exc})")
499
+ if not isinstance(status, dict):
500
+ fail("render-status emitted a non-object JSON payload")
501
+ missing = [key for key in _RENDER_STATUS_REQUIRED if key not in status]
502
+ if missing:
503
+ fail(f"render-status omitted required field(s): {', '.join(missing)}")
504
+ rollup = status["rollup"]
505
+ if not isinstance(rollup, dict) or any(
506
+ not isinstance(rollup.get(key), int) or isinstance(rollup.get(key), bool)
507
+ for key in ("complete", "total")
508
+ ):
509
+ fail("render-status emitted a malformed rollup")
510
+ if not isinstance(status["actionable"], list):
511
+ fail("render-status emitted a malformed actionable list")
512
+ if status["nextCommand"] is not None and not isinstance(status["nextCommand"], str):
513
+ fail("render-status emitted a malformed nextCommand")
514
+ if not isinstance(status["status"], str):
515
+ # The manifest's own lifecycle status, which `_epic_terminal_state` reads so
516
+ # `set-status complete` / `abandoned` reach the terminal exit. A non-string is a
517
+ # torn or hand-edited manifest, not a routing answer.
518
+ fail("render-status emitted a malformed status")
519
+ return status
520
+ #: The deterministic sentence each documentation terminus renders inside its
521
+ #: NEXT-STEPS block. Every epic route names the epic; no `blocked` route claims the
522
+ #: pipeline is complete. `{new_feature}`/`{new_epic}` are host-translated INLINE
523
+ #: mentions: starting a new feature is allowed only as secondary unfenced text, and
524
+ #: `_next_steps_block` fences exactly the primary command and nothing else.
525
+ _DOCS_OUTCOME_TEXT: Final[dict[str, str]] = {
526
+ "standalone-complete": (
527
+ "Documentation is complete for {feature}, and with it the pipeline. The "
528
+ "navigator command below is the authoritative completion action — it "
529
+ "confirms the finished state from disk. Optionally, you can start a new "
530
+ "feature with `{new_feature}` or group related work into an epic with "
531
+ "`{new_epic}`; neither is required to finish here."
532
+ ),
533
+ "standalone-blocked": (
534
+ "Documentation could not be completed for {feature}, so the pipeline is NOT "
535
+ "complete. Only valid partial state was persisted. Run the navigator below "
536
+ "to see what remains and recover from there."
537
+ ),
538
+ "epic-actionable": (
539
+ "Documentation is complete for {feature}. Epic {epic} has more work that can "
540
+ "be started now ({complete}/{total} members complete), so the pipeline "
541
+ "continues with the next actionable member below."
542
+ ),
543
+ "epic-blocked-members": (
544
+ "Documentation is complete for {feature}, but no member of epic {epic} is "
545
+ "actionable right now ({complete}/{total} members complete) — the remaining "
546
+ "work is blocked by unmet dependencies. Open the epic dashboard below to see "
547
+ "what is holding it up."
548
+ ),
549
+ "epic-complete": (
550
+ "Documentation is complete for {feature}, and every member of epic {epic} is "
551
+ "now complete ({complete}/{total}). Open the epic dashboard below for its "
552
+ "completion view."
553
+ ),
554
+ "epic-blocked": (
555
+ "Documentation could not be completed for {feature}, so neither this feature "
556
+ "nor epic {epic} is complete. Only valid partial state was persisted. Open "
557
+ "the epic dashboard below to see the epic's live state and recover from there."
558
+ ),
559
+ # The `skipped` variants (#197): same routes as `complete`, honest wording — a
560
+ # deliberate skip closes the pipeline without any stage claiming artifacts it
561
+ # never produced, and the state says `skipped`, not `complete`.
562
+ "standalone-skipped": (
563
+ "Documentation was deliberately skipped for {feature} and recorded as "
564
+ "`skipped` in state, closing the pipeline without claiming docs that were "
565
+ "never written. The navigator command below is the authoritative completion "
566
+ "action — it confirms the finished state from disk. Docs can still be "
567
+ "generated later by re-running `{docs_stage}`. Optionally, you can start a "
568
+ "new feature with `{new_feature}` or group related work into an epic with "
569
+ "`{new_epic}`; neither is required to finish here."
570
+ ),
571
+ "epic-actionable-skipped": (
572
+ "Documentation was deliberately skipped for {feature} and recorded as "
573
+ "`skipped` in state. Epic {epic} has more work that can be started now "
574
+ "({complete}/{total} members complete), so the pipeline continues with the "
575
+ "next actionable member below."
576
+ ),
577
+ "epic-blocked-members-skipped": (
578
+ "Documentation was deliberately skipped for {feature} and recorded as "
579
+ "`skipped` in state, but no member of epic {epic} is actionable right now "
580
+ "({complete}/{total} members complete) — the remaining work is blocked by "
581
+ "unmet dependencies. Open the epic dashboard below to see what is holding "
582
+ "it up."
583
+ ),
584
+ "epic-complete-skipped": (
585
+ "Documentation was deliberately skipped for {feature} and recorded as "
586
+ "`skipped` in state, and every member of epic {epic} is now complete "
587
+ "({complete}/{total}). Open the epic dashboard below for its completion view."
588
+ ),
589
+ }
590
+ def _production_stage_of(command: str) -> str | None:
591
+ """The PRODUCTION stage a canonical `/skill:<stage> <name>` command runs.
592
+
593
+ Reads the stage back out of a command another component already chose, so
594
+ ``nextStage`` and ``nextCommand`` cannot disagree. None for a branch command
595
+ (``forge-verify``/``forge-fix``), the navigator, or anything unrecognised — those
596
+ are real answers with no production stage, not failures.
597
+
598
+ This is a PARSE of a decision, never a re-derivation of one: nothing here decides
599
+ where the pipeline goes (REQ-PROD-05 keeps that in `next_stage`/`epic-manifest.py`).
600
+ """
601
+ if not command.startswith("/skill:"):
602
+ return None
603
+ stage = command[len("/skill:"):].split(" ", 1)[0]
604
+ return stage if stage in PRODUCTION_STAGES else None
605
+ def _epic_terminal_state(status: dict, epic: str) -> tuple[str, dict] | None:
606
+ """Classify a `render-status` payload as a TERMINAL epic state, or not (#248).
607
+
608
+ A completed epic used to have no terminus: with nothing actionable, the exit set
609
+ ``primaryCommand`` back to ``/skill:forge-0-epic {epic}``, and re-running
610
+ that command reproduced the same exit — the operator was prompted to run the same
611
+ command indefinitely. This is the predicate that ends it.
612
+
613
+ Three independent ways to be closed, deliberately kept distinct so the wording can
614
+ stay honest about which one applies:
615
+
616
+ - the MANIFEST says ``status: "abandoned"`` — checked FIRST, ahead of the rollup,
617
+ because an abandoned epic whose members happen to be complete must not be
618
+ announced as completed work;
619
+ - the ROLLUP says every member is complete — the ordinary case, and the one that
620
+ answers the reported defect; ``total > 0`` is required so an epic with no members
621
+ declared yet is never called complete;
622
+ - the MANIFEST says ``status: "complete"`` — the operator's explicit
623
+ ``set-status complete``, which previously had NO effect on this routing at all.
624
+
625
+ Nothing actionable is required by any of them: an epic with startable work left is
626
+ not closed no matter what its manifest says. The fourth manifest status, ``paused``,
627
+ is deliberately absent: a pause is an intent to resume, and the dashboard it keeps
628
+ routing to is where resuming starts.
629
+
630
+ In practice ``"declared"`` is NARROW, and deliberately so. ``actionable`` is "not
631
+ complete and no unmet deps", so on a valid (acyclic) graph an incomplete member
632
+ always has an actionable ancestor: ``actionable == []`` with ``total > 0`` already
633
+ implies the rollup is full, and ``"complete"`` wins. What ``"declared"`` really
634
+ reaches today is the epic with NO members yet, where the rollup reads ``0/0`` and
635
+ only the operator can say it is finished. It is kept as a separate answer — rather
636
+ than folded into the rollup rule — because the manifest is an independent signal
637
+ that must not be silently ignored, and because a future derivation admitting an
638
+ unactionable incomplete member would need exactly this branch. The wording it
639
+ selects states the real counts for that reason.
640
+
641
+ Args:
642
+ status: A validated ``render-status`` payload.
643
+ epic: The epic name, carried into the wording fields.
644
+
645
+ Returns:
646
+ ``(kind, fields)`` where kind keys ``_EPIC_TERMINAL_TEXT`` and fields carry the
647
+ REAL member counts, or None when the epic is not closed.
648
+ """
649
+ if status["actionable"]:
650
+ return None
651
+ rollup = status["rollup"]
652
+ if status["status"] == "abandoned":
653
+ kind = "abandoned"
654
+ elif rollup["total"] > 0 and rollup["complete"] >= rollup["total"]:
655
+ kind = "complete"
656
+ elif status["status"] == "complete":
657
+ kind = "declared"
658
+ else:
659
+ return None
660
+ return kind, {
661
+ "epic": epic,
662
+ "complete": rollup["complete"],
663
+ "total": rollup["total"],
664
+ }
665
+ def _docs_route(
666
+ feature: str, epic: str | None, specs_dir: Path, outcome: str, host: str
667
+ ) -> tuple[str, str | None, str, bool]:
668
+ """Route the documentation exit — the live-state table.
669
+
670
+ For an epic member the route comes from the live ``render-status`` payload, so a
671
+ Step-1 snapshot taken before docs state changed is never trusted: an actionable
672
+ next member routes to that member's own live command, and anything else (blocked
673
+ remaining work, or every member complete) routes to the epic dashboard, which is
674
+ also the dashboard's completion view. A ``blocked`` docs outcome routes to
675
+ recovery and NEVER claims pipeline completion. A ``skipped`` outcome (#197)
676
+ takes exactly the routes ``complete`` takes — the pipeline still ends here —
677
+ but its wording says the docs were deliberately skipped, never that they exist.
678
+
679
+ Args:
680
+ feature: The feature whose documentation stage is closing.
681
+ epic: The owning epic, or None for a standalone feature.
682
+ specs_dir: Configured specs directory.
683
+ outcome: `complete`, `blocked`, or `skipped`, already validated.
684
+ host: Host surface, used only to translate the INLINE secondary mentions —
685
+ the primary command is translated by the renderer.
686
+
687
+ Returns:
688
+ `(primary_canonical, deferred_canonical, outcome_text, advancing)`, matching
689
+ `_branch_route`. `deferred_canonical` is always None: a docs terminus has no
690
+ production successor to demote, because the pipeline ends here.
691
+
692
+ A ``blocked`` epic exit deliberately does NOT call ``render-status``: its route is
693
+ fixed at the epic dashboard regardless of what the live graph says, and a broken
694
+ epic graph is precisely the state in which the recovery route must stay reachable
695
+ rather than converting into a second failure.
696
+ """
697
+ if epic is None:
698
+ text = _DOCS_OUTCOME_TEXT[f"standalone-{outcome}"].format(
699
+ feature=feature,
700
+ new_feature=_host_command("/skill:forge-1-prd <new-feature>", host),
701
+ new_epic=_host_command("/skill:forge-0-epic <new-epic>", host),
702
+ docs_stage=_host_command(f"/skill:forge-6-docs {feature}", host),
703
+ )
704
+ return f"/skill:forge {feature}", None, text, False
705
+
706
+ dashboard = f"/skill:forge-0-epic {epic}"
707
+ if outcome == "blocked":
708
+ text = _DOCS_OUTCOME_TEXT["epic-blocked"].format(feature=feature, epic=epic)
709
+ return dashboard, None, text, False
710
+
711
+ skip_suffix = "-skipped" if outcome == "skipped" else ""
712
+ status = _render_status(specs_dir, epic)
713
+ rollup = status["rollup"]
714
+ fields = {
715
+ "feature": feature,
716
+ "epic": epic,
717
+ "complete": rollup["complete"],
718
+ "total": rollup["total"],
719
+ }
720
+ next_command = status["nextCommand"]
721
+ if status["actionable"] and next_command:
722
+ return (
723
+ next_command,
724
+ None,
725
+ _DOCS_OUTCOME_TEXT["epic-actionable" + skip_suffix].format(**fields),
726
+ True,
727
+ )
728
+ # Nothing actionable. Under the current derivation that coincides with "every
729
+ # member complete" (a valid graph is acyclic, so an incomplete member always has
730
+ # an actionable ancestor), but the two cases are named separately and the
731
+ # rollup is the observable that tells them apart — so the blocked wording stays
732
+ # reachable if a future derivation admits an unactionable incomplete member. Both
733
+ # route to the same epic command either way; only the explanation differs.
734
+ key = "epic-complete" if rollup["complete"] >= rollup["total"] else "epic-blocked-members"
735
+ return dashboard, None, _DOCS_OUTCOME_TEXT[key + skip_suffix].format(**fields), False
736
+ #: The route each loop outcome takes. A COMPLETE map over
737
+ #: ``EXIT_OUTCOMES["forge-5-loop"]``: REQ-PROD-01/02 require a deterministic resume or
738
+ #: recovery action for every result, so a missing key is a bug, not a default.
739
+ #:
740
+ #: ``handoff`` verify-first implementation routing, then the live docs/epic handoff
741
+ #: ``resume`` ``/skill:forge-5-loop FEATURE`` — state remains resumable
742
+ #: ``recover`` ``/skill:forge FEATURE`` — the deterministic diagnostic action
743
+ #:
744
+ #: Only ``handoff`` (i.e. ``complete``) may reach a production stage. A runner's
745
+ #: successful process exit is NOT by itself ``complete``: the final backlog state
746
+ #: selects the outcome, and that selection is the skill's job.
747
+ _LOOP_ROUTE_KIND: Final[dict[str, str]] = {
748
+ "complete": "handoff",
749
+ "partial": "resume",
750
+ "deferred": "resume",
751
+ "resolved": "resume",
752
+ "blocked": "recover",
753
+ "needs-human": "recover",
754
+ }
755
+ #: The deterministic sentence each NON-complete loop outcome renders inside its
756
+ #: NEXT-STEPS block. Every one names the resume or recovery action and states that
757
+ #: nothing downstream is ready — no wording here may imply that documentation, or any
758
+ #: other downstream production stage, can start (REQ-PROD-02).
759
+ _LOOP_OUTCOME_TEXT: Final[dict[str, str]] = {
760
+ "partial": (
761
+ "The loop stopped for {feature} with backlog items still pending — the "
762
+ "iteration limit was reached before every item was done. The recorded state "
763
+ "is resumable and nothing downstream is ready: run the loop again below to "
764
+ "continue from where it stopped."
765
+ ),
766
+ "deferred": (
767
+ "The loop explicitly deferred items for {feature} — the runner gave up on "
768
+ "them after retries rather than finishing them, so they were left for "
769
+ "another pass. The recorded state is resumable and nothing downstream is "
770
+ "ready: run the loop again below to pick the deferred items back up."
771
+ ),
772
+ "blocked": (
773
+ "The loop is blocked for {feature} — one or more backlog items could not be "
774
+ "completed. Nothing downstream is ready. Run the navigator below to see the "
775
+ "live pipeline state from disk and choose how to recover."
776
+ ),
777
+ "needs-human": (
778
+ "The loop stopped for {feature} on a decision only a human can make — one or "
779
+ "more items asked a question it could not answer, and they were set aside. "
780
+ "Nothing downstream is ready until those decisions are made. Run the "
781
+ "navigator below to see the live pipeline state from disk and recover from "
782
+ "there."
783
+ ),
784
+ "resolved": (
785
+ "The needs-human stop for {feature} was resolved — the recorded decisions "
786
+ "were applied and every affected item was verified, per item, to have left "
787
+ "blocked/needsHuman, with the working tree clean. The recorded state is "
788
+ "resumable and nothing downstream is ready: run the loop again below to "
789
+ "continue from where it stopped."
790
+ ),
791
+ }
792
+ #: The starvation variant of the `partial` next-steps sentence (REQ-ATTR-02): names
793
+ #: the unblock path instead of the iteration limit, which was NOT the binding
794
+ #: constraint. Selected only by ``--cause dependency-starvation`` (REQ-ATTR-04).
795
+ _LOOP_PARTIAL_STARVED_TEXT: Final[str] = (
796
+ "The loop stopped for {feature} with backlog items still pending, but the "
797
+ "iteration limit was NOT the constraint — no pending item was selectable because "
798
+ "unblocked root items gate the rest of the backlog. The recorded state is "
799
+ "resumable and nothing downstream is ready: unblock the roots named in the "
800
+ "starvation report above, then run the loop again below to continue."
801
+ )
802
+ #: The `complete` preamble, selected by where the handoff actually lands. The epic
803
+ #: rows name the epic and its live rollup, so the operator can see WHY the handoff is
804
+ #: this member's own documentation rather than another member (or vice versa).
805
+ _LOOP_COMPLETE_TEXT: Final[dict[str, str]] = {
806
+ "standalone": "Every backlog item is done for {feature}.",
807
+ "epic-next-member": (
808
+ "Every backlog item is done for {feature}, and the live status of epic "
809
+ "{epic} ({complete}/{total} members complete) puts the next actionable work "
810
+ "below."
811
+ ),
812
+ "epic-complete-docs": (
813
+ "Every backlog item is done for {feature}, and every member of epic {epic} "
814
+ "is now complete ({complete}/{total}) — documentation is the next step below."
815
+ ),
816
+ "epic-dashboard": (
817
+ "Every backlog item is done for {feature}, and no member of epic {epic} is "
818
+ "actionable right now ({complete}/{total} members complete). Open the epic "
819
+ "dashboard below for its live state."
820
+ ),
821
+ }
822
+ #: Appended to the `complete` preamble. REQ-EXIT-06/REQ-PROD-02: while implementation
823
+ #: verification is unresolved it is THE action and the handoff is demoted to unfenced
824
+ #: prose, so documentation never becomes primary before a pass or an explicit skip.
825
+ _LOOP_COMPLETE_OUTSTANDING: Final[str] = (
826
+ " Implementation verification is still outstanding, so it comes first — nothing "
827
+ "downstream becomes the primary action until it passes or is explicitly skipped."
828
+ )
829
+ _LOOP_COMPLETE_SETTLED: Final[str] = (
830
+ " Its implementation verification is settled, so the pipeline continues with the "
831
+ "action below."
832
+ )
833
+ _LOOP_COMPLETE_FINDINGS: Final[str] = (
834
+ " Implementation verification already ran at this revision and reported findings, "
835
+ "so applying them comes first — nothing downstream becomes the primary action "
836
+ "until a re-verify passes or the verification is explicitly skipped."
837
+ )
838
+ #: The outcome sentence a loop or documentation exit renders when a blocking
839
+ #: epic change request DISPLACES its live continuation. Both route tables above name
840
+ #: that continuation "below"; once the fence carries the reconcile instead, the claim is
841
+ #: false, so the sentence is REPLACED rather than corrected after the fact. The displaced
842
+ #: command is deliberately not named here — the block's own "After reconciling, continue
843
+ #: the pipeline with" line is its single authoritative mention, so the two can never
844
+ #: disagree. Only used when the displaced command actually differs from the reconcile:
845
+ #: a route that already lands on the epic keeps its own accurate wording.
846
+ _RECONCILE_FIRST_TEXT: Final[dict[str, str]] = {
847
+ "forge-5-loop": (
848
+ "Every backlog item is done for {feature} and its implementation verification "
849
+ "is settled, but {count} blocking epic change request{plural} recorded against "
850
+ "epic {epic} must be reconciled first. Proceeding would build on a "
851
+ "decomposition that is about to change, so the reconcile below comes before "
852
+ "the continuation named under it."
853
+ ),
854
+ "forge-6-docs": (
855
+ # "closed", not "complete": this wording also serves a `skipped` docs
856
+ # outcome, which must never claim the docs exist (#197).
857
+ "The documentation stage is closed for {feature}, but {count} blocking epic "
858
+ "change request{plural} recorded against epic {epic} must be reconciled first. "
859
+ "Handing off would build the next member on a decomposition that is about to "
860
+ "change, so the reconcile below comes before the continuation named under it."
861
+ ),
862
+ }
863
+ def _promote_reconcile(
864
+ stage: str,
865
+ epic_reconcile: dict,
866
+ feature: str,
867
+ epic_name: object,
868
+ primary_canonical: str,
869
+ deferred_canonical: str | None,
870
+ outcome_text: str | None,
871
+ advancing: bool,
872
+ ) -> tuple[str, str | None, str | None]:
873
+ """Reconcile-first promotion for the loop and documentation routes.
874
+
875
+ Both routes compute their real primary from LIVE state (``render-status``), long
876
+ after ``epicReconcile["deferred"]`` was seeded from the successor table. That seed
877
+ is the wrong continuation for these two stages — for the loop it names this
878
+ feature's own documentation, which the route deliberately did not choose, and for
879
+ documentation it is None because the pipeline has no stage after it. So the
880
+ continuation is re-derived here from the route's own result, never from the
881
+ successor table (REQ-ROUTE-05/06: the live thread is what must survive).
882
+
883
+ Args:
884
+ stage: `forge-5-loop` or `forge-6-docs` — selects the replacement wording.
885
+ epic_reconcile: The blocking reconcile directive, MUTATED in place.
886
+ feature: The exiting feature.
887
+ epic_name: The epic the reconcile is recorded against.
888
+ primary_canonical: The route's own primary command.
889
+ deferred_canonical: The route's own deferred continuation, if any.
890
+ outcome_text: The route's own outcome sentence.
891
+ advancing: Whether the route's primary advances the pipeline.
892
+
893
+ Returns:
894
+ `(primary_canonical, deferred_canonical, outcome_text)` after promotion.
895
+
896
+ A route whose primary IS the epic command (the dashboard handoffs) is not
897
+ displaced by a reconcile that names the same command: promoting it would leave a
898
+ "continue the pipeline with" line pointing back at the fence, so its own accurate
899
+ wording and an absent continuation are kept instead.
900
+ """
901
+ reconcile_command = epic_reconcile["command"]
902
+ if not advancing:
903
+ # Verification (or a recovery action) outranks the reconcile, so the reconcile
904
+ # is the FIRST deferred action and the route's own continuation follows it.
905
+ # Handing the renderer the same command the caller deferred is what collapses
906
+ # the two conditional lines into one.
907
+ epic_reconcile["deferred"] = deferred_canonical
908
+ return primary_canonical, deferred_canonical, outcome_text
909
+ if primary_canonical == reconcile_command:
910
+ epic_reconcile["deferred"] = None
911
+ return primary_canonical, None, outcome_text
912
+ epic_reconcile["deferred"] = primary_canonical
913
+ count = epic_reconcile["count"]
914
+ return (
915
+ reconcile_command,
916
+ None,
917
+ _RECONCILE_FIRST_TEXT[stage].format(
918
+ feature=feature,
919
+ epic=epic_name,
920
+ count=count,
921
+ plural="s" if count != 1 else "",
922
+ ),
923
+ )
924
+ def _loop_route(
925
+ outcome: str,
926
+ feature: str,
927
+ epic: str | None,
928
+ specs_dir: Path,
929
+ successor_command: str | None,
930
+ resolved: bool,
931
+ verify_canonical: str,
932
+ fix_canonical: str | None,
933
+ cause: str | None = None,
934
+ ) -> tuple[str, str | None, str, bool]:
935
+ """Route one loop result — the outcome table.
936
+
937
+ Every outcome lands on a deterministic action. Only ``complete`` may reach a
938
+ production stage, and even then documentation is not primary until implementation
939
+ verification passes or is explicitly skipped (REQ-PROD-02, REQ-EXIT-06). The four
940
+ non-complete outcomes route to the loop resume (``partial``/``deferred``) or to
941
+ the navigator (``blocked``/``needs-human``); their caller has already stripped the
942
+ production successor, so no directive and no rendered line can imply that
943
+ documentation is ready.
944
+
945
+ Args:
946
+ outcome: A member of `EXIT_OUTCOMES["forge-5-loop"]`, already validated.
947
+ feature: The feature whose loop stage is closing.
948
+ epic: The owning epic, or None for a standalone feature.
949
+ specs_dir: Configured specs directory.
950
+ successor_command: Canonical live-successor command (documentation), or None.
951
+ resolved: Whether the implementation verification is settled.
952
+ verify_canonical: Canonical implementation-verify command.
953
+ fix_canonical: Canonical forge-fix command when a findings report is live
954
+ at the current revision (see ``live_findings_report`` in ``stage_exit``),
955
+ else None. A live report outranks a fresh verify on the ``complete``
956
+ handoff: findings already exist at this exact revision, so the fenced
957
+ action is applying them, exactly as on a production re-exit.
958
+ 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).
962
+
963
+ Returns:
964
+ `(primary_canonical, deferred_canonical, outcome_text, advancing)`, matching
965
+ `_branch_route` and `_docs_route`.
966
+
967
+ For a completed EPIC MEMBER the handoff is delegated to the live
968
+ ``render-status`` payload rather than re-deriving dependency or completion logic
969
+ here, preserving the epic handoff this stage already performed:
970
+ an actionable member routes to the epic's own live next command; nothing
971
+ actionable with every member complete routes to this member's documentation; and
972
+ anything else opens the epic dashboard. The ``total > 0`` guard is what stops an
973
+ EMPTY epic's ``0/0`` from reading as complete. A helper failure is the same
974
+ actionable ``UsageError`` the documentation exit raises, so a broken epic graph
975
+ surfaces instead of being guessed around. A NON-complete outcome never calls the
976
+ helper: a resume or recovery action must stay reachable exactly when the epic's
977
+ own state is the thing that is broken.
978
+ """
979
+ kind = _LOOP_ROUTE_KIND[outcome]
980
+ if kind != "handoff":
981
+ primary = (
982
+ f"/skill:forge-5-loop {feature}"
983
+ if kind == "resume"
984
+ else f"/skill:forge {feature}"
985
+ )
986
+ if outcome == "partial" and cause == "dependency-starvation":
987
+ text = _LOOP_PARTIAL_STARVED_TEXT.format(feature=feature)
988
+ else:
989
+ text = _LOOP_OUTCOME_TEXT[outcome].format(feature=feature)
990
+ return primary, None, text, False
991
+
992
+ handoff = successor_command or f"/skill:forge {feature}"
993
+ fields: dict[str, object] = {"feature": feature, "epic": epic}
994
+ key = "standalone"
995
+ if epic is not None:
996
+ status = _render_status(specs_dir, epic)
997
+ rollup = status["rollup"]
998
+ fields["complete"] = rollup["complete"]
999
+ fields["total"] = rollup["total"]
1000
+ next_command = status["nextCommand"]
1001
+ if status["actionable"] and next_command:
1002
+ handoff, key = next_command, "epic-next-member"
1003
+ elif rollup["total"] > 0 and rollup["complete"] >= rollup["total"]:
1004
+ # Nothing left to start and every member complete: the epic's remaining
1005
+ # work is this member's documentation, which `handoff` already names.
1006
+ key = "epic-complete-docs"
1007
+ else:
1008
+ handoff, key = f"/skill:forge-0-epic {epic}", "epic-dashboard"
1009
+
1010
+ if resolved:
1011
+ tail = _LOOP_COMPLETE_SETTLED
1012
+ elif fix_canonical is not None:
1013
+ tail = _LOOP_COMPLETE_FINDINGS
1014
+ else:
1015
+ tail = _LOOP_COMPLETE_OUTSTANDING
1016
+ text = _LOOP_COMPLETE_TEXT[key].format(**fields) + tail
1017
+ if resolved:
1018
+ return handoff, None, text, True
1019
+ if fix_canonical is not None:
1020
+ # A live findings report outranks a fresh verify, exactly as on a
1021
+ # production re-exit: the fenced action is the fix, the handoff is demoted.
1022
+ return fix_canonical, handoff, text, False
1023
+ # Verify-first ordering, applied to the loop's own handoff rather than to
1024
+ # the fixed successor: the verification is fenced and the handoff is demoted.
1025
+ return verify_canonical, handoff, text, False
1026
+ def _debt_metadata_warnings(
1027
+ entry: dict,
1028
+ verify_key: str | None,
1029
+ stage: str,
1030
+ subject: str,
1031
+ verify_command: str,
1032
+ current: int | None,
1033
+ ) -> list[str]:
1034
+ """Entries 2 and 3 of the ``warnings`` order, for owed automatic verification.
1035
+
1036
+ Entry 2 is the legacy/malformed ``scheduledStageVersion`` advisory;
1037
+ entry 3 is the scheduled-vs-current revision mismatch note. They are
1038
+ mutually exclusive by construction — a mismatch is only detectable once the
1039
+ recorded revision is usable — but the order is fixed regardless so a later
1040
+ entry can be added without re-deriving it.
1041
+
1042
+ Takes the already-resolved entry and revision rather than re-deriving them
1043
+ from a member state document: on an epic-scoped exit both come from
1044
+ ``.epic-state.json`` and the manifest revision, which a member state cannot
1045
+ supply (REQ-SEC-01).
1046
+
1047
+ Args:
1048
+ entry: The verify entry the exit routed from (``{}`` when absent).
1049
+ verify_key: Its ``forge-verify-*`` key, or None for a tokenless stage.
1050
+ stage: The production stage the debt is owed on.
1051
+ subject: The feature or epic to name.
1052
+ verify_command: The host-translated retry command.
1053
+ current: The artifact's current revision, or None when unknown.
1054
+ """
1055
+ if verify_key is None or entry.get("status") != "auto-verify-pending":
1056
+ return []
1057
+ scheduled = _scheduled_stage_version(entry)
1058
+ if scheduled is None:
1059
+ return [
1060
+ AUTO_VERIFY_DEBT_METADATA_DIAGNOSTIC.format(
1061
+ subject=subject, verify_key=verify_key, command=verify_command
1062
+ )
1063
+ ]
1064
+ if current is not None and scheduled != current:
1065
+ return [
1066
+ auto_pending_message(subject, stage, verify_command, scheduled, current)
1067
+ ]
1068
+ return []
1069
+ def _schedule_auto_verify_debt(
1070
+ specs_dir: Path, feature: str, epic: str | None, stage: str, verify_key: str
1071
+ ) -> None:
1072
+ """Persist `auto-verify-pending` for `stage` — the scheduling boundary.
1073
+
1074
+ Called immediately BEFORE `stage_exit` returns a payload carrying
1075
+ ``runInStageVerify: true``, never after, so there is no window in which the
1076
+ model is told to verify while nothing on disk records that it was owed
1077
+ (REQ-DEBT-01, REQ-REL-03). The transition itself is `cmd_state_verify`'s —
1078
+ `_load_verify_target` selects the target and `_verify_result_entry` builds the
1079
+ entry — so a scheduled marker is byte-identical to one written through the CLI.
1080
+
1081
+ Idempotent by target revision (REQ-REL-01): an entry already
1082
+ `auto-verify-pending` at the current revision returns without calling
1083
+ `_commit_state`, so `scheduledAt`, top-level `updatedAt`, and the file bytes
1084
+ are all untouched. A newer revision supersedes the older marker with exactly
1085
+ one write. The caller's `resolved` and live-report checks are what keep a
1086
+ fresh terminal entry, an explicit `skipped`, or a `findings-reported` entry
1087
+ at the current revision from ever reaching this function — the last because
1088
+ a write here REPLACES the entry and would delete its report metadata
1089
+ (REQ-EXIT-04).
1090
+
1091
+ Unlike the `state-verify` CLI, a target whose artifact revision is unknown
1092
+ (no recorded `version`, or an epic with no readable manifest) records the debt
1093
+ with a null `scheduledStageVersion` rather than refusing: the obligation is
1094
+ real either way, and an unusable schedule is already classified as
1095
+ `auto-pending` plus a warning. Forgetting the debt because its revision is
1096
+ unknown is the REQ-DEBT-02 conflation, and refusing would turn a routine stage
1097
+ closing into an exit 2.
1098
+
1099
+ Args:
1100
+ specs_dir: The configured specs directory.
1101
+ feature: The feature name, or the EPIC name for an epic-scoped exit.
1102
+ epic: The owning epic for a member, else None.
1103
+ stage: The production stage the debt is owed on (`forge-0-epic` for an
1104
+ epic-scoped exit).
1105
+ verify_key: The `forge-verify-*` key to write.
1106
+
1107
+ Raises:
1108
+ UsageError: Unsafe/ambiguous/unresolvable target, corrupt state, or an
1109
+ atomic-write failure (→ exit 2, no payload and no dispatch directive).
1110
+ """
1111
+ is_epic_target = stage == "forge-0-epic"
1112
+ state_path, state, epic_revision = _load_verify_target(
1113
+ specs_dir, feature, epic, is_epic_target
1114
+ )
1115
+ if is_epic_target:
1116
+ current = epic_revision
1117
+ else:
1118
+ version = _stage_version(state, stage)
1119
+ current = (
1120
+ version
1121
+ if isinstance(version, int) and not isinstance(version, bool) and version >= 1
1122
+ else None
1123
+ )
1124
+ prior = _verify_entry(state, verify_key)
1125
+ if (
1126
+ prior.get("status") == "auto-verify-pending"
1127
+ and _scheduled_stage_version(prior) == current
1128
+ ):
1129
+ return
1130
+ state.setdefault("stages", {})[verify_key] = _verify_result_entry(
1131
+ "auto-verify-pending", prior, current, None, None, _now_iso()
1132
+ )
1133
+ _commit_state(state_path, state)
1134
+
1135
+ __all__ = [
1136
+ "_BRANCH_ROUTE_KIND",
1137
+ "_BRANCH_OUTCOME_TEXT",
1138
+ "_NO_FINDINGS_RESOLVED_TEXT",
1139
+ "_host_command",
1140
+ "_next_steps_block",
1141
+ "_branch_route",
1142
+ "_RENDER_STATUS_REQUIRED",
1143
+ "_RENDER_STATUS_TIMEOUT",
1144
+ "_render_status_failure_detail",
1145
+ "_render_status",
1146
+ "_DOCS_OUTCOME_TEXT",
1147
+ "_production_stage_of",
1148
+ "_epic_terminal_state",
1149
+ "_docs_route",
1150
+ "_LOOP_ROUTE_KIND",
1151
+ "_LOOP_OUTCOME_TEXT",
1152
+ "_LOOP_PARTIAL_STARVED_TEXT",
1153
+ "_LOOP_COMPLETE_TEXT",
1154
+ "_LOOP_COMPLETE_OUTSTANDING",
1155
+ "_LOOP_COMPLETE_SETTLED",
1156
+ "_LOOP_COMPLETE_FINDINGS",
1157
+ "_RECONCILE_FIRST_TEXT",
1158
+ "_promote_reconcile",
1159
+ "_loop_route",
1160
+ "_debt_metadata_warnings",
1161
+ "_schedule_auto_verify_debt",
1162
+ ]