@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,1566 @@
1
+ """Low-level primitives shared across the ``forge_session`` package modules.
2
+
3
+ The foundation layer of the ``forge-session.py`` split (#279): the TypedDicts and
4
+ the ``UsageError`` exception the verbs return/raise, plus the ``VerifyStatus``
5
+ vocabulary alias several verbs share. Every other package module imports what it
6
+ needs FROM here — never from the ``forge-session.py`` shim, which re-exports these
7
+ names back out for the path-loaded test oracle and the CLI. Only genuinely shared
8
+ primitives live here; each cluster module owns its own internals. (The mirrored
9
+ ``load_json_with_duplicates``/``warn_duplicate_keys`` pair deliberately stays in the
10
+ shim body, byte-identical to ``scripts/forge-bootstrap.py`` — see
11
+ ``tests/test_json_loader_parity.py`` — so it is NOT extracted here.)
12
+
13
+ 3.10 baseline, Google-style docstrings, stdlib only — matching the conventions of
14
+ the monolith it was carved out of.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import os
21
+ import re
22
+ import subprocess
23
+ import sys
24
+ import tempfile
25
+ from datetime import datetime, timezone
26
+ from pathlib import Path
27
+ from typing import Final, Literal, TypedDict, get_args
28
+
29
+
30
+ class UsageError(Exception):
31
+ """A usage or I/O failure that must exit 2."""
32
+
33
+
34
+ #: The persisted verify-entry status vocabulary; mirrors KNOWN_VERIFY_STATUSES and
35
+ #: references/pipeline-state-schema.json's verifyEntry.status.enum.
36
+ VerifyStatus = Literal[
37
+ "pending",
38
+ "auto-verify-pending",
39
+ "passed",
40
+ "findings-reported",
41
+ "findings-applied",
42
+ "skipped",
43
+ ]
44
+
45
+
46
+ class FeatureRow(TypedDict):
47
+ """One active feature, ranked by recency, with its next actionable step."""
48
+
49
+ name: str
50
+ epic: str | None
51
+ currentStage: str
52
+ branch: str | None
53
+ updatedAt: str | None
54
+ complete: bool
55
+ nextStage: str | None
56
+ nextCommand: str | None
57
+ verifyPending: bool
58
+ verifyCommand: str | None
59
+ verifyStage: str | None
60
+ verifyState: str
61
+ autoVerify: bool
62
+ autoFix: bool
63
+ verifyGate: str
64
+
65
+
66
+ class EpicReconcile(TypedDict, total=False):
67
+ """Existing epic backflow directive retained in expanded exits.
68
+
69
+ Present only for epic members; absent entirely for a standalone feature.
70
+ """
71
+
72
+ # True when backflow must run before the member may advance; False when it is
73
+ # merely advisable. Drives whether the exit blocks or only mentions it.
74
+ required: bool
75
+ # True to surface the reminder text in the rendered block. Independent of
76
+ # `required`: a required reconcile with `reminder: False` still blocks silently
77
+ # in `--json` consumers.
78
+ reminder: bool
79
+ # Host-rendered command that performs the reconcile. Already passed through
80
+ # `_host_command`; consumers print it verbatim and never re-translate it.
81
+ command: str
82
+ # Number of member changes awaiting backflow. 0 is meaningful — it means
83
+ # reconcile was evaluated and found nothing, distinct from the key being absent
84
+ # because the feature is not an epic member.
85
+ count: int
86
+ # Canonical (untranslated) production command demoted behind a blocking
87
+ # reconcile — rendered as the unfenced "After reconciling, continue the
88
+ # pipeline with: …" line and passed through `_host_command` at render time.
89
+ # Present only when `required: True`; None/absent otherwise. It is a COMMAND,
90
+ # never a user-supplied reason: the live writer sets it to `next_command`
91
+ # (scripts/forge-session.py) and `_next_steps_block` translates it for the
92
+ # host. Repurposing it to carry prose would send free text through
93
+ # `_host_command` and strip the blocking follow-up line of its source
94
+ # (REQ-COMPAT-01).
95
+ deferred: str | None
96
+
97
+
98
+ class StageExitDirectives(TypedDict, total=False):
99
+ """Machine-readable decisions emitted by `stage_exit`.
100
+
101
+ `total=False` throughout: a key's ABSENCE means "not applicable to this exit",
102
+ which is never the same as a present-but-null value. `servedStage: None` says
103
+ the exit resolved no served stage; a missing `servedStage` says the concept does
104
+ not apply. Consumers must distinguish the two.
105
+ """
106
+
107
+ # The stage whose exit this is — always one of EXIT_STAGES. Always present.
108
+ stage: str
109
+ # Human-readable noun for this stage's artifact, used by
110
+ # references/stage-exit-protocol.md's "{stageNoun}" slots (the auto-verify
111
+ # heading and the "Verify {stageNoun} now" gate label). Always present;
112
+ # STAGE_NOUN.get(stage, stage), so it defaults to the stage id when unmapped.
113
+ # Pre-existing key, retained verbatim for REQ-COMPAT-01.
114
+ stageNoun: str
115
+ # For a verify/fix branch exit, the production stage the diversion served and
116
+ # rejoins. None on a production-stage exit, which serves only itself.
117
+ servedStage: str | None
118
+ # Verify mode in play (`prd`, `tech`, `specs`, `backlog`, `impl`, `epic`), keyed
119
+ # by VERIFY_MODE_TO_STAGE. None when this exit is not a verify/fix exit.
120
+ verifyMode: str | None
121
+ # Terminal outcome for stages with a multi-way result. Must be a member of
122
+ # EXIT_OUTCOMES[stage] — consult that table rather than this comment,
123
+ # which is deliberately not a second copy of the domain. None for stages
124
+ # whose exit has a single outcome.
125
+ outcome: str | None
126
+ # Branch ownership for a verify/fix exit — ExitOwner, i.e. exactly "direct"
127
+ # (this call owns and prints the terminal block) or "nested" (an outer
128
+ # authoring stage owns it). REQUIRED for forge-verify/forge-fix and REJECTED
129
+ # for stages 0–6, which are always direct owners.
130
+ # None only on a production-stage exit, where the concept does not apply.
131
+ owner: str | None
132
+ # Who prints the terminal block. "self" — this caller renders exactly one
133
+ # sentinel-terminated block. "outer" — a nested invocation that must print
134
+ # nothing terminal, leaving ownership with the outermost authoring stage.
135
+ terminalOwnedBy: Literal["self", "outer"]
136
+ # Feature (or epic) name this exit concerns. Always present.
137
+ feature: str
138
+ # Resolved host: "claude", "pi", or "generic". Selects command syntax and
139
+ # fresh-session wording; never inferred downstream, always decided here.
140
+ host: str
141
+ # Whether the host may dispatch a clean-room verifier subagent —
142
+ # VerifyCapability, i.e. exactly "interactive" or "manual". A manual host
143
+ # receives verify-first ordering with copy-paste commands instead of an
144
+ # interactive gate; capable Pi is interactive, not manual (REQ-EXIT-07).
145
+ # "May", not "has the tool": a session that bars unsolicited dispatch but
146
+ # offers a question tool is interactive, since the gate's prompt makes the
147
+ # dispatch solicited. Only no-question-tool-and-no-dispatch is manual.
148
+ verifyCapability: str
149
+ # Current verification state of the served artifact, as classified by
150
+ # `verify_state` — including "auto-pending" for unrun scheduled verification.
151
+ verifyState: str
152
+ # Production stage the outstanding/owed verification belongs to — the value
153
+ # `pending_verify()` returns; mirrors FeatureRow.verifyStage so navigator rows
154
+ # and stage-exit JSON report the same thing. None when nothing is outstanding.
155
+ # DISTINCT from `servedStage`, which is branch-exit-only: on a production-stage
156
+ # exit `servedStage` is None while `verifyStage` names the stage the debt is
157
+ # owed on (REQ-OBS-01, REQ-DEBT-05).
158
+ verifyStage: str | None
159
+ # Which gate form to render, derived from verifyState and verifyCapability.
160
+ verifyGate: str
161
+ # Host-rendered verify command. Present whenever verification is reachable,
162
+ # even if it is not the primary action.
163
+ verifyCommand: str
164
+ # True when the caller must run in-stage verification before returning control.
165
+ # When True, the auto-verify-pending debt write has already been attempted —
166
+ # see `autoVerifyDebtRecorded` for whether it landed.
167
+ runInStageVerify: bool
168
+ # Effective autoVerify for THIS stage after applying autoVerifyStages overrides
169
+ # over the autoVerify default. Not the raw config value.
170
+ autoVerifyEffective: bool
171
+ # True whenever `runInStageVerify` is True — the scheduling boundary
172
+ # persists the auto-verify-pending marker BEFORE this payload exists, and a
173
+ # failed debt write raises UsageError with no payload at all. So
174
+ # `runInStageVerify: True` with `autoVerifyDebtRecorded: False` is UNREACHABLE;
175
+ # the field is carried so tests and downstream tools can assert that invariant
176
+ # rather than infer it. False with `runInStageVerify: False` simply means no
177
+ # debt was owed (REQ-DEBT-01/04, REQ-REL-02).
178
+ autoVerifyDebtRecorded: bool
179
+ # True when an autoFix chain may run unattended: autoFix configured, zero
180
+ # unresolved decision points, and a clean tree at the pre-scheduling snapshot.
181
+ autoFixEligible: bool
182
+ # Next production stage in pipeline order, or None at the end of the pipeline.
183
+ # Routing introspection only — never promote it over `primaryCommand`.
184
+ nextStage: str | None
185
+ # Host-rendered command for `nextStage`. Retained for compatibility; see the
186
+ # promotion rule below. None when `nextStage` is None.
187
+ nextCommand: str | None
188
+ # THE authoritative single action. While verification is unresolved this is the
189
+ # verify command — or the forge-fix command when a findings report is live at
190
+ # the current revision — never the downstream stage. The one fenced command in
191
+ # the rendered block. None only when the pipeline has no further action: a
192
+ # FINISHED EPIC (#248), whose block fences nothing at all because every command
193
+ # such an exit could fence is the dashboard it was just run from. A null here is
194
+ # terminal, not a gap to fill from `nextCommand` (which is null too).
195
+ primaryCommand: str | None
196
+ # Post-verification guidance shown as prose, never fenced, so it cannot be
197
+ # mistaken for the primary action. None when there is nothing deferred.
198
+ deferredCommand: str | None
199
+ # Keys in autoVerifyStages that name no verify-capable stage — a config typo.
200
+ # Empty list means the config was checked and clean; the key is always present
201
+ # when config was read at all, so [] and absent differ. Each key renders as
202
+ # exactly:
203
+ # Warning: autoVerifyStages key "{key}" names no verify-capable stage; it is
204
+ # ignored. Valid keys are forge-1-prd, forge-2-tech, forge-3-specs,
205
+ # forge-4-backlog, forge-5-loop.
206
+ # Keys are rendered in sorted order, per the determinism rule
207
+ # (REQ-OBS-02, REQ-REL-01).
208
+ invalidAutoVerifyKeys: list[str]
209
+ # Whether the working directory is a git repository at all.
210
+ gitRepo: bool
211
+ # Clean-tree snapshot taken BEFORE the pending-debt write, so the sanctioned
212
+ # state mutation does not dirty its own precondition. None when `gitRepo` is
213
+ # False — unknown, not clean.
214
+ cleanTree: bool | None
215
+ # Human-readable non-fatal advisories, in a fixed deterministic order:
216
+ # (1) the epic-member unreadable-state fallback, (2) the legacy/malformed
217
+ # scheduledStageVersion metadata warning, (3) the scheduled-vs-current
218
+ # revision mismatch note. A LIST,
219
+ # not a string, because these are independently triggerable and can co-occur
220
+ # on one call; a single string would force an implementer to drop or
221
+ # concatenate them, and REQ-REL-01's byte-identical-output requirement needs a
222
+ # defined order to assert against. Mirrors RenderStatus.warnings,
223
+ # which is already a list. Empty list means checked and clean; the key is
224
+ # always present, so [] and absent differ. Each entry names its affected
225
+ # feature/stage/key AND the recovery action (REQ-OBS-02).
226
+ warnings: list[str]
227
+ # Epic backflow directive; see EpicReconcile. Absent for standalone features.
228
+ epicReconcile: EpicReconcile
229
+
230
+
231
+ class StageExitPayload(TypedDict):
232
+ """Serialized direct or nested exit result.
233
+
234
+ Total (not `total=False`): all three keys are always present, and a nested
235
+ exit carries explicit nulls rather than omitting them.
236
+ """
237
+
238
+ # Always populated, for both direct and nested exits.
239
+ directives: StageExitDirectives
240
+ # The rendered terminal block for a direct owner. MUST be None when
241
+ # `terminalOwnedBy == "outer"` — a nested caller has nothing to print.
242
+ nextSteps: str | None
243
+ # NEXT_STEPS_SENTINEL when this payload owns the terminal block, else None.
244
+ # When non-None, `nextSteps` ends with exactly this string and nothing follows
245
+ # it (REQ-EXIT-03). Carried explicitly so a consumer can verify termination
246
+ # without importing the constant.
247
+ sentinel: str | None
248
+
249
+
250
+ class VerifyEntry(TypedDict, total=False):
251
+ """Feature or epic verification state persisted by `state-verify`.
252
+
253
+ `total=False` is load-bearing: terminal writes DELETE the scheduling keys rather
254
+ than nulling them, so an absent `scheduledAt` means "not scheduled"
255
+ while a present-but-null one would be a malformed entry. Legacy entries written
256
+ before this feature simply lack the newer keys and load unmigrated
257
+ (REQ-DEBT-06).
258
+ """
259
+
260
+ # The entry's state. Always present on a written entry; a wholly absent entry
261
+ # means never verified, which is distinct from every value here.
262
+ status: VerifyStatus
263
+ # Path to the findings document, relative to the feature directory. Non-empty
264
+ # for `findings-reported`/`findings-applied`; absent otherwise.
265
+ findingsFile: str | None
266
+ # Findings count. 0 is legal and meaningful for `findings-reported` — verified
267
+ # with nothing found — and is not the same as the key being absent.
268
+ findingsCount: int | None
269
+ # UTC ISO-8601 timestamp of the terminal verification result. Absent while
270
+ # scheduling is pending.
271
+ verifiedAt: str | None
272
+ # UTC ISO-8601 timestamp set by `findings-applied`. Its presence alongside a
273
+ # deleted `verifiedStageVersion` is exactly what marks fixes-landed-but-
274
+ # unconfirmed.
275
+ fixedAt: str | None
276
+ # Full 40-character hash of the artifact commit for this entry, or null between
277
+ # commit 1 and commit 2 of the two-commit protocol. Never a short hash on a new
278
+ # write; legacy short hashes still READ (REQ-STATE-01/02).
279
+ commitHash: str | None
280
+ # Artifact revision this result verified — the production stage's `version` for
281
+ # a feature, the manifest `revision` for an epic. Deleted by `findings-applied`
282
+ # on purpose, so freshness stays unresolved until a later `passed` write.
283
+ verifiedStageVersion: int | None
284
+ # UTC ISO-8601 timestamp of the auto-verify schedule. Deleted (not nulled) by
285
+ # any terminal result.
286
+ scheduledAt: str | None
287
+ # Artifact revision current when verification was scheduled. Makes rescheduling
288
+ # idempotent — an identical revision does not rewrite the entry (REQ-REL-01) —
289
+ # and lets a read distinguish debt owed on the current artifact from debt
290
+ # stranded on an older one. Deleted by any terminal result.
291
+ scheduledStageVersion: int | None
292
+
293
+
294
+ # --------------------------------------------------------------------------- #
295
+ # Config readers and loop-runner resolution
296
+ #
297
+ # These are the shared config/state primitives the package modules need. They are
298
+ # the package-internal home for logic the ``forge-session.py`` shim also carries
299
+ # inline: the shim keeps its own copies for its not-yet-extracted body and for the
300
+ # degraded bare-copy layout (a lone forge-session.py with no sibling package), so
301
+ # the two coexist until later #279 items drain the shim's inline copies. The
302
+ # behaviour is identical either way — a pure move, never a re-derivation.
303
+ #
304
+ # The duplicate-aware JSON loader below is the package layer's copy of the pair
305
+ # mirrored with the flat ``scripts/forge-bootstrap.py`` (byte-identical per
306
+ # tests/test_json_loader_parity.py). The mirror exists because forge-bootstrap.py is
307
+ # a standalone flat script — copied verbatim into per-agent bundles, sharing no import
308
+ # module — so it cannot import this one; the drift guard keeps the two shipped copies
309
+ # in lock-step. This IS the shared import module for the package (the shim and every
310
+ # sibling verb read config through this copy rather than each carrying their own).
311
+ # --------------------------------------------------------------------------- #
312
+
313
+
314
+ #: mirrors ``load_json_with_duplicates``/``warn_duplicate_keys`` in scripts/forge-bootstrap.py
315
+ def load_json_with_duplicates(path: Path) -> tuple[object, list[str]]:
316
+ """Load JSON with last-key-wins values and ordered duplicate key names.
317
+
318
+ Args:
319
+ path: UTF-8 JSON file to read.
320
+
321
+ Returns:
322
+ The parsed JSON value and duplicate key names in deterministic decoder-hook
323
+ order. A repeated occurrence is appended whenever its key was already seen
324
+ in that same object. Objects at every nesting depth use the hook.
325
+
326
+ Raises:
327
+ OSError: The path cannot be read as UTF-8 text.
328
+ json.JSONDecodeError: The file is not valid JSON.
329
+ """
330
+ duplicate_keys: list[str] = []
331
+
332
+ def object_from_pairs(pairs: list[tuple[str, object]]) -> dict[str, object]:
333
+ result: dict[str, object] = {}
334
+ for key, value in pairs:
335
+ if key in result:
336
+ duplicate_keys.append(key)
337
+ result[key] = value
338
+ return result
339
+
340
+ text = path.read_text(encoding="utf-8")
341
+ value = json.loads(text, object_pairs_hook=object_from_pairs)
342
+ return value, duplicate_keys
343
+
344
+
345
+ def warn_duplicate_keys(path: Path, duplicate_keys: list[str]) -> None:
346
+ """Write one deterministic warning for each reported duplicate occurrence.
347
+
348
+ Args:
349
+ path: Source file whose duplicate key was accepted.
350
+ duplicate_keys: Ordered names returned by `load_json_with_duplicates`.
351
+
352
+ Raises:
353
+ OSError: The process cannot write to stderr.
354
+ """
355
+ for key in duplicate_keys:
356
+ rendered_key = json.dumps(key, ensure_ascii=False)
357
+ print(
358
+ f"Warning: duplicate JSON key {rendered_key} in {path}; "
359
+ "using the last value.",
360
+ file=sys.stderr,
361
+ )
362
+
363
+
364
+ def _load_config(config_path: Path, *, warn: bool = True) -> dict:
365
+ """Read config into a dict, warning on duplicates and tolerating bad input.
366
+
367
+ ``warn=False`` suppresses the duplicate-key warning for a re-read whose warnings a
368
+ prior load in the same process already emitted — the doctor loads the config once for
369
+ its report, then re-resolves the loopRunner layers, and the warning must fire once (#324).
370
+ """
371
+ try:
372
+ value, duplicate_keys = load_json_with_duplicates(config_path)
373
+ except (OSError, ValueError, RecursionError): # bad JSON, bad UTF-8, absurd nesting
374
+ return {}
375
+ if warn:
376
+ try:
377
+ warn_duplicate_keys(config_path, duplicate_keys)
378
+ except OSError:
379
+ pass # a diagnostic write failure must not break a total read path
380
+ return value if isinstance(value, dict) else {}
381
+
382
+
383
+ def _local_config_path(config_path: Path) -> Path:
384
+ """Sibling machine-local overlay path: ``forge.config.json`` -> ``forge.config.local.json``.
385
+
386
+ Inserts a ``.local`` segment before the suffix so the overlay sits next to the committed
387
+ file under any name (#324). The overlay is a gitignored per-machine dev fact — never
388
+ committed — so ``forge-root``/``forge-init`` never write it.
389
+ """
390
+ return config_path.with_name(f"{config_path.stem}.local{config_path.suffix}")
391
+
392
+
393
+ def _deep_merge(base: dict, overlay: dict) -> dict:
394
+ """Return a new dict: ``overlay`` deep-merged over ``base`` (overlay wins).
395
+
396
+ Nested dicts merge recursively; scalars and lists replace wholesale (a list is a value,
397
+ not a mergeable structure). Neither input is mutated. Used to layer the machine-local
398
+ config over the committed one (#324) with the same "override the leaf, keep the rest"
399
+ semantics ``resolve_loop_runner`` already applies to schema defaults.
400
+ """
401
+ result = dict(base)
402
+ for key, value in overlay.items():
403
+ existing = result.get(key)
404
+ if isinstance(existing, dict) and isinstance(value, dict):
405
+ result[key] = _deep_merge(existing, value)
406
+ else:
407
+ result[key] = value
408
+ return result
409
+
410
+
411
+ def load_effective_config(config_path: Path) -> dict:
412
+ """Committed ``forge.config.json`` with the machine-local overlay merged over it (#324).
413
+
414
+ Reads the committed config, then deep-merges an optional sibling ``forge.config.local.json``
415
+ (a gitignored per-machine dev override — pick a runner binary, a machine-specific
416
+ ``contextWindowTokens``, …) over it, local winning. An absent or empty local file yields the
417
+ committed config unchanged, so this is a drop-in for ``_load_config`` at every *config*
418
+ call site. NOT for manifest reads (``.claude-plugin/plugin.json`` / the bundle sentinel),
419
+ which have no ``.local`` overlay concept — those stay on ``_load_config``.
420
+ """
421
+ committed = _load_config(config_path)
422
+ local = _load_config(_local_config_path(config_path))
423
+ if not local:
424
+ return committed
425
+ return _deep_merge(committed, local)
426
+
427
+
428
+ def _loop_runner_defaults(schema_path: Path) -> dict[str, object]:
429
+ """Extract every ``loopRunner`` field's schema ``default``.
430
+
431
+ Reads ``properties.loopRunner.properties.<field>.default`` for each field.
432
+ Stdlib-only (``json`` + dict access), mirroring
433
+ ``tests/test_config_defaults_parity.py``. The schema is the single source of
434
+ truth; nothing here is hardcoded.
435
+
436
+ Only fields that actually declare a ``default`` keyword are included. Every
437
+ ``loopRunner`` field does today; a field losing its default would be a schema
438
+ regression the drift guard catches, not something silently patched here.
439
+
440
+ Args:
441
+ schema_path: Path to ``forge-config-schema.json``.
442
+
443
+ Returns:
444
+ A dict mapping each ``loopRunner`` field name to its declared default
445
+ value (templates such as ``"{bin} loop run …"`` are returned literally).
446
+
447
+ Raises:
448
+ UsageError: If the schema is missing, unreadable, unparseable, or lacks a
449
+ ``loopRunner.properties`` object — a deterministic failure that must
450
+ exit 2. Never returns partial/empty defaults silently.
451
+ """
452
+ try:
453
+ schema = json.loads(schema_path.read_text(encoding="utf-8"))
454
+ except OSError as exc:
455
+ raise UsageError(f"config schema unreadable: {schema_path} ({exc})") from exc
456
+ except json.JSONDecodeError as exc:
457
+ raise UsageError(f"config schema is not valid JSON: {schema_path} ({exc})") from exc
458
+
459
+ props = None
460
+ if isinstance(schema, dict):
461
+ loop_runner = schema.get("properties", {})
462
+ if isinstance(loop_runner, dict):
463
+ loop_runner = loop_runner.get("loopRunner", {})
464
+ if isinstance(loop_runner, dict):
465
+ props = loop_runner.get("properties")
466
+ if not isinstance(props, dict) or not props:
467
+ raise UsageError(f"config schema has no loopRunner.properties object: {schema_path}")
468
+
469
+ return {
470
+ field: spec["default"]
471
+ for field, spec in props.items()
472
+ if isinstance(spec, dict) and "default" in spec
473
+ }
474
+
475
+
476
+ def resolve_loop_runner(config_path: Path, schema_path: Path) -> dict[str, object]:
477
+ """Resolve the effective ``loopRunner`` block the loop consumes.
478
+
479
+ The flat projection of :func:`resolve_loop_runner_layers`: every field's resolved value,
480
+ with the precedence env > machine-local > committed > schema default applied (#324). A field
481
+ absent from every user layer keeps its schema default; ``FEATURE_FORGE_LOOP_RUNNER_BIN``
482
+ overrides ``bin`` only. Computed deterministically so no model ever merges it by hand; the
483
+ per-field provenance is available via :func:`resolve_loop_runner_layers` for ``doctor``.
484
+
485
+ Args:
486
+ config_path: Path to ``forge.config.json`` (a missing/corrupt file — and a missing
487
+ local overlay — are tolerated, yielding pure defaults).
488
+ schema_path: Path to ``forge-config-schema.json`` (source of the defaults).
489
+
490
+ Returns:
491
+ The resolved ``loopRunner`` object: every schema-defaulted field present, user
492
+ overrides applied.
493
+
494
+ Raises:
495
+ UsageError: If the schema is unreadable/unparseable (propagated from
496
+ ``_loop_runner_defaults``) — exit 2, a deterministic failure.
497
+ """
498
+ return {
499
+ field: info["value"]
500
+ for field, info in resolve_loop_runner_layers(config_path, schema_path).items()
501
+ }
502
+
503
+
504
+ #: The environment override for ``loopRunner.bin`` (#324): highest precedence, ``bin`` only.
505
+ #: A one-shot ``rauf-dev`` run or a CI pin sets this without touching any file.
506
+ LOOP_RUNNER_BIN_ENV: Final = "FEATURE_FORGE_LOOP_RUNNER_BIN"
507
+
508
+
509
+ def resolve_loop_runner_layers(
510
+ config_path: Path, schema_path: Path, *, warn: bool = True
511
+ ) -> dict[str, dict[str, object]]:
512
+ """Resolve ``loopRunner`` with per-field provenance (#324).
513
+
514
+ Returns ``{field: {"value": <value>, "layer": <layer>}}`` where ``layer`` is one of
515
+ ``"env"``, ``"local"``, ``"committed"``, ``"default"`` — the precedence order (highest
516
+ first): the ``FEATURE_FORGE_LOOP_RUNNER_BIN`` env var (``bin`` only) over the machine-local
517
+ ``forge.config.local.json`` over the committed ``forge.config.json`` over the schema
518
+ default. Fields present in a user file but not in the schema defaults (unknown keys) are
519
+ carried through with their originating layer — the config schema is the authority that
520
+ flags them at author time, exactly as the flat resolver did. ``doctor`` reports these
521
+ layers; ``resolve_loop_runner`` projects just the values.
522
+
523
+ Args:
524
+ config_path: Path to ``forge.config.json``.
525
+ schema_path: Path to ``forge-config-schema.json`` (source of the defaults).
526
+ warn: Emit the duplicate-key warning while reading the config files. ``False`` for a
527
+ re-read whose warnings a prior load already emitted (the doctor path).
528
+
529
+ Raises:
530
+ UsageError: If the schema is unreadable/unparseable (from ``_loop_runner_defaults``).
531
+ """
532
+ defaults = _loop_runner_defaults(schema_path)
533
+ committed = _load_config(config_path, warn=warn).get("loopRunner")
534
+ committed = committed if isinstance(committed, dict) else {}
535
+ local = _load_config(_local_config_path(config_path), warn=warn).get("loopRunner")
536
+ local = local if isinstance(local, dict) else {}
537
+ env_bin = os.environ.get(LOOP_RUNNER_BIN_ENV)
538
+
539
+ layered: dict[str, dict[str, object]] = {}
540
+ # Every field any layer mentions, defaults first so their order leads; dict.fromkeys
541
+ # dedupes a key that appears in more than one layer (e.g. an unknown key in both files).
542
+ fields = list(dict.fromkeys([*defaults, *committed, *local]))
543
+ for field in fields:
544
+ if field == "bin" and env_bin:
545
+ layered[field] = {"value": env_bin, "layer": "env"}
546
+ elif field in local:
547
+ layered[field] = {"value": local[field], "layer": "local"}
548
+ elif field in committed:
549
+ layered[field] = {"value": committed[field], "layer": "committed"}
550
+ else:
551
+ layered[field] = {"value": defaults[field], "layer": "default"}
552
+ return layered
553
+
554
+
555
+ # --------------------------------------------------------------------------- #
556
+ # State writes (shared machinery for the state-* and decision-* writers)
557
+ # --------------------------------------------------------------------------- #
558
+
559
+
560
+ def _now_iso() -> str:
561
+ """Return the current UTC time as a Z-suffixed, second-precision ISO-8601 string.
562
+
563
+ Matches the `.pipeline-state.json` timestamp convention already on disk (the
564
+ schema's ``format: date-time`` values; the read path normalizes a trailing
565
+ ``Z``). Second precision keeps `updatedAt`/`startedAt`/`completedAt` visually
566
+ consistent with the values other pipeline writers produce.
567
+
568
+ Returns:
569
+ A timestamp like ``"2026-07-29T03:30:00Z"``.
570
+ """
571
+ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
572
+
573
+
574
+ def _write_state(state_path: Path, state: dict) -> None:
575
+ """Atomically write a `.pipeline-state.json` (temp file + os.replace).
576
+
577
+ Mirrors epic-manifest.py's ``atomic_write``: write to a sibling temp file in
578
+ the same directory as the target, flush + fsync the bytes, then os.replace()
579
+ the temp file onto the target. os.replace is atomic on POSIX within one
580
+ filesystem, so an interrupted write never leaves a partial or corrupt state
581
+ file. Concurrent multi-session mutation is out of scope (single writer
582
+ assumed, matching epic-manifest.py; decision record:
583
+ references/decisions/single-writer-threat-model.md, issue #180).
584
+
585
+ Args:
586
+ state_path: Destination path, e.g.
587
+ ``{specsDir}/{feature}/.pipeline-state.json``.
588
+ state: The fully-formed state dict to serialize.
589
+
590
+ Raises:
591
+ UsageError: If the temp file cannot be created/written or the replace
592
+ fails (→ exit 2). The temp file is removed first, so a failed write
593
+ leaves no debris and the original target untouched.
594
+ """
595
+ try:
596
+ fd, tmp_name = tempfile.mkstemp(
597
+ prefix=f".{state_path.name}.", suffix=".tmp", dir=state_path.parent
598
+ )
599
+ except OSError as exc:
600
+ raise UsageError(f"atomic write to {state_path} failed: {exc}") from exc
601
+ tmp_path = Path(tmp_name)
602
+ try:
603
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
604
+ json.dump(state, handle, indent=2, ensure_ascii=False)
605
+ handle.write("\n")
606
+ handle.flush()
607
+ os.fsync(handle.fileno())
608
+ os.replace(tmp_path, state_path)
609
+ except OSError as exc:
610
+ tmp_path.unlink(missing_ok=True)
611
+ raise UsageError(f"atomic write to {state_path} failed: {exc}") from exc
612
+
613
+
614
+ def _commit_state(state_path: Path, state: dict) -> dict:
615
+ """Refresh ``updatedAt`` and write ``state`` atomically; return it for echo.
616
+
617
+ Every verb calls this exactly once, after its mutation, so ``updatedAt`` is
618
+ always refreshed on a successful write and the write is atomic.
619
+
620
+ Args:
621
+ state_path: The resolved state-file path — a feature's
622
+ ``.pipeline-state.json``, or an epic's ``.epic-state.json``. The helper
623
+ is target-agnostic: it stamps and writes whatever document it is given,
624
+ so an epic write reuses the same atomic mechanism without
625
+ going anywhere near the member resolver.
626
+ state: The mutated state dict.
627
+
628
+ Returns:
629
+ The same ``state`` dict (now carrying a fresh ``updatedAt``), so the verb
630
+ can echo it under ``--json``.
631
+
632
+ Raises:
633
+ UsageError: If the atomic write fails (→ exit 2).
634
+ """
635
+ state["updatedAt"] = _now_iso()
636
+ _write_state(state_path, state)
637
+ return state
638
+
639
+
640
+
641
+ # --------------------------------------------------------------------------- #
642
+ # Shared pipeline-state readers and the recency-ranked feature rows
643
+ #
644
+ # The read-side chain the discover/reconcile/epic-base cluster needs (via
645
+ # ``build_rows``) and that the state/outcome clusters will share too. Copied here
646
+ # from the ``forge-session.py`` shim, which keeps its own inline copies for its
647
+ # not-yet-extracted body and the degraded bare-copy layout (a lone forge-session.py
648
+ # whose stage-exit/doctor paths reach this chain). A pure move, byte-identical to
649
+ # the shim; later #279 items drain the shim's copies once every reader lives here.
650
+ # --------------------------------------------------------------------------- #
651
+
652
+
653
+ #: A directory is "feature-shaped" iff it directly contains this file.
654
+ PIPELINE_STATE_FILENAME: Final = ".pipeline-state.json"
655
+ #: Epic roots hold this (and no .pipeline-state.json) — never a feature.
656
+ MANIFEST_FILENAME: Final = "epic-manifest.json"
657
+ #: Epic-scoped verification state, sibling to the manifest. NEVER a member's
658
+ #: .pipeline-state.json: epic verification is epic-scoped (REQ-SEC-01).
659
+ EPIC_STATE_FILENAME: Final = ".epic-state.json"
660
+
661
+ #: A safe bare name: one kebab-case token, no separator, no traversal. Same pattern
662
+ #: epic-manifest.py applies (the flat scripts share no import module), so the epic
663
+ #: target of a state write fails closed exactly where the canonical resolver does
664
+ #: (REQ-SEC-01).
665
+ SAFE_NAME_RE: Final = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
666
+
667
+ #: New non-null commit hashes are full 40-hex only. Loaded legacy short hashes stay
668
+ #: readable — this validates WRITES, and no schema constrains commitHash.
669
+ FULL_GIT_HASH_RE: Final = re.compile(r"[0-9a-fA-F]{40}")
670
+
671
+
672
+ #: The ordered production stages. This is the ONE place stage order lives.
673
+ PRODUCTION_STAGES: Final[tuple[str, ...]] = (
674
+ "forge-1-prd",
675
+ "forge-2-tech",
676
+ "forge-3-specs",
677
+ "forge-4-backlog",
678
+ "forge-5-loop",
679
+ "forge-6-docs",
680
+ )
681
+
682
+
683
+ #: Production stage -> the verify token its findings file uses, and the
684
+ #: `forge-verify-<token>` key its state lives under. forge-6-docs has no verify.
685
+ VERIFY_TOKEN_BY_STAGE: Final[dict[str, str]] = {
686
+ "forge-1-prd": "prd",
687
+ "forge-2-tech": "tech",
688
+ "forge-3-specs": "specs",
689
+ "forge-4-backlog": "backlog",
690
+ "forge-5-loop": "impl",
691
+ }
692
+
693
+ #: The `--stage` domain for `state-verify`: forge-0-epic (whose verification lives
694
+ #: in the epic's own `.epic-state.json`) plus the five stages that carry a verify
695
+ #: token. forge-6-docs is excluded on purpose — it has no verification token, so
696
+ #: there is no `forge-verify-*` key for it to write.
697
+ VERIFY_STAGES: Final[tuple[str, ...]] = ("forge-0-epic", *VERIFY_TOKEN_BY_STAGE)
698
+
699
+
700
+ #: The terminal status the completion writer records (and the commit-hash
701
+ #: follow-up requires) — NOT the whole "done for selection" set below.
702
+ _DONE_STATUS: Final = "complete"
703
+ #: Production stage statuses that count as "done" for next-stage selection.
704
+ #: `skipped` is legal only on forge-6-docs (schema: `docsStageEntry`) — an
705
+ #: explicitly skipped documentation stage ends the pipeline without claiming
706
+ #: artifacts it never produced (#197). Selection treats the status as done
707
+ #: wherever it appears; the schema is what confines it to the docs stage.
708
+ _DONE_STATUSES: Final = frozenset({_DONE_STATUS, "skipped"})
709
+ #: The authoritative forge-verify status vocabulary. SOURCE OF TRUTH:
710
+ #: references/pipeline-state-schema.json (definitions.verifyEntry.properties.status.enum).
711
+ #: A status outside this set is unrecognized and must not be silently interpreted (#148).
712
+ #: NOTE: epic-manifest.py keeps a byte-identical copy — flat, self-contained scripts have
713
+ #: no shared import module (each is copied verbatim into per-agent adapter bundles).
714
+ KNOWN_VERIFY_STATUSES: Final = frozenset(
715
+ {
716
+ "pending",
717
+ "auto-verify-pending",
718
+ "passed",
719
+ "findings-reported",
720
+ "findings-applied",
721
+ "skipped",
722
+ }
723
+ )
724
+ #: Verify statuses that count as "resolved" (no outstanding verify needed). A STRICT
725
+ #: subset of KNOWN_VERIFY_STATUSES — not collapsible into it (different meaning).
726
+ #: `auto-verify-pending` is deliberately ABSENT: owed-but-unrun debt is not resolved.
727
+ _VERIFY_RESOLVED: Final = frozenset({"passed", "findings-applied", "skipped"})
728
+ #: Prior verify statuses a `skipped` result write may NOT replace (#203). Mirrors
729
+ #: epic-manifest.py's `_VERIFY_ORCH_COMPLETE`: these two statuses make an epic
730
+ #: member complete-for-orchestration, so silently replacing one with `skipped`
731
+ #: demoted the member out of the rollup and fabricated unmetDeps on every
732
+ #: dependent — the observed 5/6 → 1/6 collapse. A deferral over one of these
733
+ #: needs NO write: the recorded result already carries the outstanding state.
734
+ #: NOT the same set as `_VERIFY_RESOLVED` (`skipped` re-writing `skipped` is a
735
+ #: harmless idempotent refresh and stays legal).
736
+ _SKIP_PROTECTED_PRIOR: Final = frozenset({"passed", "findings-applied"})
737
+ #: The `state-verify --status` domain: every VerifyStatus a result write may record.
738
+ #: `pending` is excluded — it is the pre-existing generic/manual pending marker, not
739
+ #: a verification RESULT, and `auto-verify-pending` is the value that carries owed
740
+ #: automatic debt. Derived so the two lists cannot drift.
741
+ VERIFY_RESULT_STATUSES: Final[tuple[str, ...]] = tuple(
742
+ status for status in get_args(VerifyStatus) if status != "pending"
743
+ )
744
+
745
+
746
+ #: Per-process dedupe for the unknown-verify-status diagnostic (#148) so a single
747
+ #: bogus status is flagged once, not once per verify_state() call in a command.
748
+ _UNKNOWN_VERIFY_WARNED: set[str] = set()
749
+ #: Per-process dedupe for the auto-verify debt-metadata diagnostic, same reason.
750
+ _AUTO_VERIFY_DEBT_WARNED: set[str] = set()
751
+ #: The single normative sentence every read-side emitter uses for owed-but-unrun
752
+ #: automatic verification. One line naming the
753
+ #: subject, the served stage, and the retry command — never a state-file dump.
754
+ AUTO_PENDING_DIAGNOSTIC: Final = (
755
+ "{subject}: automatic verification is still pending for {stage}; "
756
+ "run {command} to resolve it."
757
+ )
758
+
759
+
760
+ def _read_state(state_path: Path) -> dict:
761
+ """Read a `.pipeline-state.json`, tolerating missing/corrupt files.
762
+
763
+ A missing, unreadable, or unparseable state downgrades to ``{}`` rather than
764
+ crashing the scan — the navigator simply treats that feature as not-started.
765
+ """
766
+ try:
767
+ parsed = json.loads(state_path.read_text(encoding="utf-8"))
768
+ except (OSError, ValueError, RecursionError): # bad JSON, bad UTF-8, absurd nesting
769
+ return {}
770
+ return parsed if isinstance(parsed, dict) else {}
771
+
772
+
773
+ def _scan_features(specs_dir: Path) -> list[tuple[str, str | None, dict]]:
774
+ """Find every feature-shaped dir under the specs tree (flat + nested).
775
+
776
+ Descends exactly one level below each top-level dir (never deeper), matching
777
+ ``epic-manifest.py``'s feature-shaped-dir bound.
778
+
779
+ Args:
780
+ specs_dir: The configured specs directory.
781
+
782
+ Returns:
783
+ A list of ``(feature_name, epic_name_or_None, state_dict)`` tuples. The
784
+ epic name is the parent dir name for a nested member, ``None`` for a flat
785
+ feature.
786
+ """
787
+ if not specs_dir.is_dir():
788
+ return []
789
+ out: list[tuple[str, str | None, dict]] = []
790
+ for top in sorted(p for p in specs_dir.iterdir() if p.is_dir()):
791
+ flat_state = top / PIPELINE_STATE_FILENAME
792
+ if flat_state.is_file():
793
+ out.append((top.name, None, _read_state(flat_state)))
794
+ # Descend one level for nested epic members (skip the epic root itself).
795
+ for child in sorted(p for p in top.iterdir() if p.is_dir()):
796
+ nested_state = child / PIPELINE_STATE_FILENAME
797
+ if nested_state.is_file():
798
+ out.append((child.name, top.name, _read_state(nested_state)))
799
+ return out
800
+
801
+
802
+ def _stage_status(state: dict, stage: str) -> str | None:
803
+ """Return the recorded status of a stage, or None if absent."""
804
+ stages = state.get("stages")
805
+ if not isinstance(stages, dict):
806
+ return None
807
+ entry = stages.get(stage)
808
+ if not isinstance(entry, dict):
809
+ return None
810
+ status = entry.get("status")
811
+ return status if isinstance(status, str) else None
812
+
813
+
814
+ def next_stage(state: dict) -> str | None:
815
+ """Return the first production stage that is not yet complete (the next step).
816
+
817
+ Walks ``PRODUCTION_STAGES`` in order and returns the first whose recorded
818
+ status is not in ``_DONE_STATUSES`` (a missing/pending/in-progress/stale
819
+ stage all count as "not done"; ``complete`` and a forge-6-docs ``skipped``
820
+ both count as done). Returns ``None`` when every production stage is done
821
+ (nothing left to run).
822
+
823
+ This is the derived "what runs next" value — the single source of truth for
824
+ the next stage. It is intentionally distinct from the stored
825
+ ``currentStage`` field ("where the pipeline IS"; see the schema): the next
826
+ stage is computed from ``stages[].status`` here, never read from
827
+ ``currentStage``.
828
+ """
829
+ for stage in PRODUCTION_STAGES:
830
+ if _stage_status(state, stage) not in _DONE_STATUSES:
831
+ return stage
832
+ return None
833
+
834
+
835
+ def _stage_version(state: dict, stage: str) -> int | None:
836
+ """Return the recorded ``version`` of a stage entry, or None if absent."""
837
+ stages = state.get("stages")
838
+ if not isinstance(stages, dict):
839
+ return None
840
+ entry = stages.get(stage)
841
+ if not isinstance(entry, dict):
842
+ return None
843
+ version = entry.get("version")
844
+ return version if isinstance(version, int) else None
845
+
846
+
847
+ def _verify_entry(state: dict, verify_key: str) -> dict:
848
+ """Return the ``forge-verify-*`` entry dict, or ``{}`` if absent."""
849
+ stages = state.get("stages")
850
+ if not isinstance(stages, dict):
851
+ return {}
852
+ entry = stages.get(verify_key)
853
+ return entry if isinstance(entry, dict) else {}
854
+
855
+
856
+ def _warn_unknown_verify_status(stage_name: str, status: object) -> None:
857
+ """Emit a one-time stderr diagnostic for an out-of-vocabulary verify status (#148).
858
+
859
+ The freshness classifier maps an unrecognized status to "never verified" — correct,
860
+ but silent, so a typo poisons the downstream gate (e.g. forge-5-loop's dependency
861
+ check) with no clue. Flagging it here makes the bad value visible where it is read.
862
+ """
863
+ key = f"{stage_name}={status!r}"
864
+ if key in _UNKNOWN_VERIFY_WARNED:
865
+ return
866
+ _UNKNOWN_VERIFY_WARNED.add(key)
867
+ known = ", ".join(sorted(KNOWN_VERIFY_STATUSES))
868
+ print(
869
+ f"feature-forge: unknown {stage_name} status {status!r} "
870
+ f"(treated as unverified; expected one of {known})",
871
+ file=sys.stderr,
872
+ )
873
+
874
+
875
+ def _scheduled_stage_version(entry: dict) -> int | None:
876
+ """Return an ``auto-verify-pending`` entry's usable ``scheduledStageVersion``.
877
+
878
+ ``None`` when the field is absent, a bool, a non-integer, or below 1 — i.e.
879
+ legacy state written before the scheduling fields existed, or hand-edited
880
+ state. The caller stays ``auto-pending`` either way: unusable metadata is a
881
+ reason to warn, never a reason to forget the debt.
882
+ """
883
+ version = entry.get("scheduledStageVersion")
884
+ if isinstance(version, bool) or not isinstance(version, int) or version < 1:
885
+ return None
886
+ return version
887
+
888
+
889
+ def _warn_auto_verify_debt_metadata(verify_key: str) -> None:
890
+ """Flag an ``auto-verify-pending`` entry whose scheduled revision is unusable.
891
+
892
+ Without a recorded revision the debt cannot be compared against the current
893
+ artifact, so it can be neither discharged as fresh nor described as advanced.
894
+ It REMAINS outstanding — the alternative (degrading to ``never``) is exactly
895
+ the conflation REQ-DEBT-02 forbids — but the operator needs to know why the
896
+ row carries no revision detail, so say it once per process.
897
+ """
898
+ if verify_key in _AUTO_VERIFY_DEBT_WARNED:
899
+ return
900
+ _AUTO_VERIFY_DEBT_WARNED.add(verify_key)
901
+ print(
902
+ f"feature-forge: {verify_key} is auto-verify-pending but its "
903
+ "scheduledStageVersion is missing or malformed (legacy or hand-edited "
904
+ "state); the debt stays outstanding — re-run forge-verify to resolve it "
905
+ "and record a usable schedule",
906
+ file=sys.stderr,
907
+ )
908
+
909
+
910
+ def auto_pending_message(
911
+ subject: str,
912
+ stage: str,
913
+ command: str,
914
+ scheduled_version: int | None = None,
915
+ current_version: int | None = None,
916
+ ) -> str:
917
+ """Render the diagnostic for owed-but-unrun automatic verification.
918
+
919
+ Args:
920
+ subject: The feature or epic the debt belongs to.
921
+ stage: The served production stage the debt is owed on.
922
+ command: The host-translated forge-verify retry command.
923
+ scheduled_version: Revision the debt was recorded against, if usable.
924
+ current_version: The artifact's current revision, if known.
925
+
926
+ Returns:
927
+ One sentence, with both revision numbers appended when the recorded
928
+ schedule predates the current artifact. Never a state-file dump.
929
+ """
930
+ message = AUTO_PENDING_DIAGNOSTIC.format(
931
+ subject=subject, stage=stage, command=command
932
+ )
933
+ if (
934
+ scheduled_version is not None
935
+ and current_version is not None
936
+ and scheduled_version != current_version
937
+ ):
938
+ message += (
939
+ f" The artifact has advanced since it was scheduled "
940
+ f"(scheduled at revision {scheduled_version}, now at revision "
941
+ f"{current_version})."
942
+ )
943
+ return message
944
+
945
+
946
+ def verify_state(state: dict) -> tuple[str | None, str]:
947
+ """Classify verify freshness for the most-recently-completed stage.
948
+
949
+ Returns ``(stage, state_label)`` where ``state_label`` is one of:
950
+
951
+ - ``fresh`` — the entry is ``passed`` AND its ``verifiedStageVersion`` matches
952
+ the stage's current ``version`` (so no re-verify is needed). ``passed`` is the
953
+ ONLY status that reaches ``fresh``: ``findings-applied`` and ``skipped`` are
954
+ resolved but never fresh, for the reasons given below.
955
+ - ``stale`` — verify was resolved once, but the stage version has since moved
956
+ (artifact revised) OR the entry predates the freshness ledger (no
957
+ ``verifiedStageVersion``), OR the entry is ``findings-applied``, which never
958
+ classifies ``fresh`` regardless of any version it carries (§4.2 step 4).
959
+ A revised artifact must be re-verified.
960
+ - ``failing`` — verify ran and reported findings that are not yet applied
961
+ (``findings-reported``).
962
+ - ``auto-pending`` — effective configuration scheduled unattended in-stage
963
+ verification and nothing has discharged it: the obligation is RECORDED and
964
+ owed. Deliberately distinct from ``never`` (nobody ever asked for it), from
965
+ manual ``pending`` work, and from every resolved label — a dropped
966
+ ``runInStageVerify`` directive is precisely what this makes visible (#163,
967
+ REQ-DEBT-02). Classified BEFORE the generic unresolved handling below, and
968
+ never downgraded when its scheduling metadata is missing or malformed.
969
+ - ``never`` — the stage completed but verify has not run at all.
970
+ - ``skipped`` — the user explicitly chose to proceed without verifying. A
971
+ resolved, non-pending state: it is deliberately NOT re-offered or
972
+ auto-verified, and (unlike a genuine verification result) it does not go
973
+ stale on an artifact revision — skip writers record no version to compare
974
+ against, and re-surfacing would override an explicit human decision.
975
+ - ``none`` — no completed verify-capable stage (nothing to verify), stage
976
+ is ``None``.
977
+
978
+ Only the most-recent completed production stage is considered, matching the
979
+ navigator's "verify before continuing" gate. A ``findings-applied`` entry is
980
+ treated as ``stale`` UNCONDITIONALLY — applying fixes is not verifying them —
981
+ and an absent ``verifiedStageVersion`` on a ``passed`` entry (legacy state) is
982
+ likewise ``stale``: verify rather than skip.
983
+ """
984
+ for stage in reversed(PRODUCTION_STAGES):
985
+ if _stage_status(state, stage) not in _DONE_STATUSES:
986
+ continue
987
+ token = VERIFY_TOKEN_BY_STAGE.get(stage)
988
+ if token is None:
989
+ continue # forge-6-docs has no verify step
990
+ entry = _verify_entry(state, f"forge-verify-{token}")
991
+ status = entry.get("status")
992
+ if status is not None and not isinstance(status, str):
993
+ # A torn or hand-edited entry can carry any JSON type here; an
994
+ # unhashable one would raise TypeError at the frozenset membership
995
+ # below, crashing the navigator on one bad file. Same answer as an
996
+ # absent entry — and the same #148 diagnostic as an unknown string,
997
+ # so the degradation is never silent.
998
+ _warn_unknown_verify_status(f"forge-verify-{token}", status)
999
+ return stage, "never"
1000
+ if status == "skipped":
1001
+ # An explicit skip is resolved and non-pending — preserve the user's
1002
+ # decision. It never goes stale (no recorded version to compare), so
1003
+ # the freshness check below deliberately does not apply.
1004
+ return stage, "skipped"
1005
+ if status == "auto-verify-pending":
1006
+ # Ordered ahead of the generic unresolved branch so recorded debt can
1007
+ # never fall through to "never". Unusable metadata warns and stays
1008
+ # owed; a superseded revision stays owed too.
1009
+ if _scheduled_stage_version(entry) is None:
1010
+ _warn_auto_verify_debt_metadata(f"forge-verify-{token}")
1011
+ return stage, "auto-pending"
1012
+ if status not in _VERIFY_RESOLVED:
1013
+ if status == "findings-reported":
1014
+ return stage, "failing"
1015
+ # An unrecognized status (outside KNOWN_VERIFY_STATUSES) is treated as
1016
+ # "never verified" — defensible, but flag it once so a typo (e.g. the
1017
+ # eye-slip 'findings-resolved') doesn't silently poison the gate that
1018
+ # reads this label (#148). ``pending``/``None`` are known/absent → quiet.
1019
+ if status is not None and status not in KNOWN_VERIFY_STATUSES:
1020
+ _warn_unknown_verify_status(f"forge-verify-{token}", status)
1021
+ return stage, "never"
1022
+ if status == "findings-applied":
1023
+ # Applying fixes is not verifying them: §4.2 step 4 says `findings-applied`
1024
+ # CLEARS freshness, and only a later `passed` restores it. The writer builds
1025
+ # the entry without `verifiedStageVersion`, but the read side may not rely on
1026
+ # that — REQ-DEBT-06 requires loading legacy state without migration, and a
1027
+ # pre-writer entry can still carry the key. Without this guard such an entry
1028
+ # reads `fresh`, `pending_verify` returns None, and the verification debt for
1029
+ # a fixed-but-never-re-verified stage disappears silently.
1030
+ return stage, "stale"
1031
+ verified_version = entry.get("verifiedStageVersion")
1032
+ stage_version = _stage_version(state, stage)
1033
+ if (
1034
+ isinstance(verified_version, int)
1035
+ and stage_version is not None
1036
+ and verified_version == stage_version
1037
+ ):
1038
+ return stage, "fresh"
1039
+ return stage, "stale"
1040
+ return None, "none"
1041
+
1042
+
1043
+ def _parse_ts(value: str | None) -> datetime | None:
1044
+ """Parse an ISO-8601 timestamp (tolerating a trailing 'Z'), else None."""
1045
+ if not isinstance(value, str):
1046
+ return None
1047
+ try:
1048
+ dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
1049
+ except ValueError:
1050
+ return None
1051
+ if dt.tzinfo is None:
1052
+ dt = dt.replace(tzinfo=timezone.utc)
1053
+ return dt
1054
+
1055
+
1056
+ def build_rows(specs_dir: Path, config: dict | None = None) -> list[FeatureRow]:
1057
+ """Build the recency-ranked active-feature rows (the rank-features payload).
1058
+
1059
+ Active features (``pipelineStatus == "active"``, the default when absent) are
1060
+ sorted by ``updatedAt`` descending — most recently touched first — so the
1061
+ navigator's recency default is row 0.
1062
+
1063
+ ``config`` is the loaded forge.config.json (or ``{}``); it drives the effective
1064
+ ``autoVerify``/``autoFix`` per stage so the navigator can branch without
1065
+ re-reading config.
1066
+
1067
+ A row whose verify classifies ``auto-pending`` carries recorded-but-undischarged
1068
+ automatic verification: ``verifyPending`` is True, ``verifyState`` is
1069
+ ``auto-pending``, and ``verifyCommand`` is non-null, so no consumer can read it
1070
+ as verification-complete. The named sentence goes to stderr (this is the
1071
+ one emitter that knows the feature name); stdout keeps the three stable JSON
1072
+ keys — ``verifyState``, ``verifyStage``, ``verifyCommand`` — and no prose.
1073
+ """
1074
+ config = config or {}
1075
+ # Fail closed: only a literal JSON ``true`` enables artifact-mutating autoFix.
1076
+ global_auto_fix = config.get("autoFix") is True
1077
+ rows: list[FeatureRow] = []
1078
+ for name, epic, state in _scan_features(specs_dir):
1079
+ status = state.get("pipelineStatus", "active")
1080
+ if status != "active":
1081
+ continue
1082
+ nxt = next_stage(state)
1083
+ vstage, vlabel = verify_state(state)
1084
+ verify_pending = vstage is not None and vlabel not in ("fresh", "none", "skipped")
1085
+ effective_auto_verify = auto_verify_for(config, vstage) if vstage else False
1086
+ verify_command = f"/skill:forge-verify {name}" if verify_pending else None
1087
+ if vlabel == "auto-pending" and vstage is not None and verify_command:
1088
+ token = VERIFY_TOKEN_BY_STAGE.get(vstage)
1089
+ entry = _verify_entry(state, f"forge-verify-{token}") if token else {}
1090
+ print(
1091
+ auto_pending_message(
1092
+ name,
1093
+ vstage,
1094
+ verify_command,
1095
+ _scheduled_stage_version(entry),
1096
+ _stage_version(state, vstage),
1097
+ ),
1098
+ file=sys.stderr,
1099
+ )
1100
+ branch = state.get("branch")
1101
+ updated = state.get("updatedAt")
1102
+ rows.append({
1103
+ "name": name,
1104
+ "epic": epic,
1105
+ # currentStage = "where the pipeline IS" (the recorded field). When a
1106
+ # legacy/absent state omits it, fall back to the DERIVED next stage
1107
+ # for display only — never conflate the two elsewhere (schema O1).
1108
+ "currentStage": state.get("currentStage") or (nxt or "complete"),
1109
+ "branch": branch if isinstance(branch, str) else None,
1110
+ "updatedAt": updated if isinstance(updated, str) else None,
1111
+ "complete": nxt is None,
1112
+ "nextStage": nxt,
1113
+ "nextCommand": f"/skill:{nxt} {name}" if nxt else None,
1114
+ "verifyPending": verify_pending,
1115
+ "verifyCommand": verify_command,
1116
+ "verifyStage": vstage,
1117
+ "verifyState": vlabel,
1118
+ "autoVerify": effective_auto_verify,
1119
+ "autoFix": global_auto_fix and effective_auto_verify,
1120
+ # Single resolved verify-gate classification (5b — one exit computation,
1121
+ # mirroring stage-exit's `verifyGate`): the navigator reads this instead of
1122
+ # re-deriving from verifyPending + autoVerify in prose. `auto` = the §2b
1123
+ # catch-up runs it unattended; `standard` = the §3 gate (degrades to
1124
+ # manual-print on a non-Claude host); `none` = nothing outstanding.
1125
+ "verifyGate": (
1126
+ "none" if not verify_pending
1127
+ else "auto" if effective_auto_verify
1128
+ else "standard"
1129
+ ),
1130
+ })
1131
+ # Sort by updatedAt desc; rows without a parseable timestamp sort last.
1132
+ rows.sort(
1133
+ key=lambda r: (_parse_ts(r["updatedAt"]) or datetime.min.replace(tzinfo=timezone.utc)),
1134
+ reverse=True,
1135
+ )
1136
+ return rows
1137
+
1138
+
1139
+ def auto_verify_for(config: dict, stage: str) -> bool:
1140
+ """Return the effective auto-verify setting for ``stage``.
1141
+
1142
+ Per-stage override in ``autoVerifyStages`` wins over the global ``autoVerify``;
1143
+ both default to off, so a config with neither key means "no auto-verify".
1144
+
1145
+ Parsing is strict and **fails closed**: only a literal JSON ``true`` enables
1146
+ auto-verify. A non-boolean value (e.g. the string ``"false"``, which is truthy
1147
+ in Python) is treated as off, not on. The schema already rejects non-booleans
1148
+ at author time; this guards a hand-edited config from silently enabling
1149
+ automation.
1150
+ """
1151
+ stages = config.get("autoVerifyStages")
1152
+ if isinstance(stages, dict) and stage in stages:
1153
+ return stages[stage] is True
1154
+ return config.get("autoVerify") is True
1155
+
1156
+
1157
+ def _git_output(args: list[str]) -> str | None:
1158
+ """Run a read-only git command and return stripped stdout, or None.
1159
+
1160
+ Any failure (git missing, not a repo, nonzero exit, timeout) degrades to
1161
+ ``None`` — doctor reports absence rather than crashing.
1162
+ """
1163
+ try:
1164
+ proc = subprocess.run(
1165
+ ["git", *args], capture_output=True, text=True, timeout=10,
1166
+ )
1167
+ except (OSError, ValueError, subprocess.TimeoutExpired): # ValueError: undecodable output
1168
+ return None
1169
+ if proc.returncode != 0:
1170
+ return None
1171
+ out = proc.stdout.strip()
1172
+ return out or None
1173
+
1174
+
1175
+ def _default_branch() -> str | None:
1176
+ """The repo's default branch: origin/HEAD target, else `main`/`master` if present."""
1177
+ ref = _git_output(["symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"])
1178
+ if ref:
1179
+ return ref.rsplit("/", 1)[-1]
1180
+ for cand in ("main", "master"):
1181
+ if _git_output(["rev-parse", "--verify", "--quiet", f"refs/heads/{cand}"]) is not None:
1182
+ return cand
1183
+ return None
1184
+
1185
+
1186
+ def _counts(specs_dir: Path) -> dict[str, int]:
1187
+ """Tally active/paused/abandoned pipelines across the specs tree."""
1188
+ tally = {"active": 0, "paused": 0, "abandoned": 0}
1189
+ for _name, _epic, state in _scan_features(specs_dir):
1190
+ status = state.get("pipelineStatus", "active")
1191
+ if isinstance(status, str) and status in tally:
1192
+ tally[status] += 1
1193
+ return tally
1194
+
1195
+
1196
+ def _config_duplicate_keys(config_path: Path) -> list[str]:
1197
+ """Duplicate key names in the config file, for doctor's health report.
1198
+
1199
+ Empty on a missing/unreadable/invalid config — those conditions are
1200
+ reported by doctor's ``configExists`` field, not here.
1201
+ """
1202
+ try:
1203
+ return load_json_with_duplicates(config_path)[1]
1204
+ except (OSError, ValueError, RecursionError): # bad JSON, bad UTF-8, absurd nesting
1205
+ return []
1206
+
1207
+
1208
+ def invalid_auto_verify_keys(config: dict) -> list[str]:
1209
+ """Return ``autoVerifyStages`` keys outside the verify-capable stage ids.
1210
+
1211
+ An unknown/typo key (e.g. ``forge-1-prod``) would silently never take effect,
1212
+ turning an intended off-switch into a no-op. Surfacing it lets the navigator
1213
+ warn instead of failing quietly. Mirrors the schema's ``propertyNames.enum``.
1214
+
1215
+ Sorted, not insertion-ordered: every diagnostic list must be
1216
+ sorted before rendering, so two configs that differ only in key order produce
1217
+ byte-identical output.
1218
+ """
1219
+ stages = config.get("autoVerifyStages")
1220
+ if not isinstance(stages, dict):
1221
+ return []
1222
+ return sorted(key for key in stages if key not in VERIFY_TOKEN_BY_STAGE)
1223
+
1224
+
1225
+ def _default_schema_path() -> Path:
1226
+ """Return the bundled forge-config-schema.json path (sibling references/ dir).
1227
+
1228
+ Resolved relative to this module so it works from any cwd. This module lives at
1229
+ ``<scripts>/forge_session/_common.py`` — one level deeper than the legacy shim —
1230
+ so it walks three parents to the bundle root, matching the shim's target.
1231
+ Overridable via the ``--schema`` flag (chiefly for tests).
1232
+
1233
+ Returns:
1234
+ The Path to ``references/forge-config-schema.json`` at the bundle root.
1235
+ """
1236
+ return Path(__file__).resolve().parent.parent.parent / "references" / "forge-config-schema.json"
1237
+
1238
+
1239
+ #: Verify token per exit stage. Extends the production map with the epic stage,
1240
+ #: whose verify entry is recorded under ``forge-verify-epic``. The shim keeps a
1241
+ #: byte-equal inline copy for its not-yet-extracted stage-exit routing body; this
1242
+ #: is the package-side source the outcomes module reads (#279 P4.1).
1243
+ _EXIT_VERIFY_TOKEN: Final[dict[str, str]] = {
1244
+ **VERIFY_TOKEN_BY_STAGE,
1245
+ "forge-0-epic": "epic",
1246
+ }
1247
+
1248
+ #: The one message table the three upstream-verify gates read instead of each
1249
+ #: phrasing "not verified" its own way — one canonical operator sentence per
1250
+ #: ``VerifyStateCase``. ``{subject}`` is the feature (or epic member), ``{stage}``
1251
+ #: the production stage whose verification this describes, ``{command}`` the
1252
+ #: forge-verify retry invocation. ``auto-verify-pending`` is NOT looked up here at
1253
+ #: runtime: that case routes through ``auto_pending_message()`` (which reads this
1254
+ #: same ``AUTO_PENDING_DIAGNOSTIC`` and appends the version-advance clause when the
1255
+ #: schedule predates the artifact), so the value is a *reference* to that shared
1256
+ #: constant, not a copy that could drift. It stays in the table so the map is
1257
+ #: complete: one entry per case, the invariant ``test_one_message_per_case`` pins.
1258
+ VERIFY_STATE_MESSAGES: Final[dict[str, str]] = {
1259
+ "passed": "{subject}'s {stage} verification passed; proceed.",
1260
+ "findings-reported": (
1261
+ "{subject}'s {stage} verification reported unresolved blocking findings; "
1262
+ "apply and re-verify them — run {command}."
1263
+ ),
1264
+ "findings-applied": (
1265
+ "Fixes were applied to {subject}'s {stage} but nothing re-verified them; "
1266
+ "re-verification is still outstanding — run {command}."
1267
+ ),
1268
+ "auto-verify-pending": AUTO_PENDING_DIAGNOSTIC,
1269
+ "skipped": (
1270
+ "{subject}'s {stage} verification was explicitly skipped; "
1271
+ "run {command} to verify it after all."
1272
+ ),
1273
+ "never": "{subject}'s {stage} hasn't been verified yet — run {command}.",
1274
+ }
1275
+
1276
+
1277
+ def _resolve_feature_dir(specs_dir: Path, feature: str, epic: str | None) -> Path:
1278
+ """Best-effort feature dir (flat, else unique nested, else flat literal).
1279
+
1280
+ stage-exit tolerates an unresolvable dir — the state read downgrades to
1281
+ ``{}`` and every directive still computes from defaults.
1282
+ """
1283
+ if epic:
1284
+ return specs_dir / epic / feature
1285
+ flat = specs_dir / feature
1286
+ if (flat / PIPELINE_STATE_FILENAME).is_file():
1287
+ return flat
1288
+ if specs_dir.is_dir():
1289
+ nested = [
1290
+ p for p in specs_dir.glob(f"*/{feature}")
1291
+ if (p / PIPELINE_STATE_FILENAME).is_file()
1292
+ ]
1293
+ if len(nested) == 1:
1294
+ return nested[0]
1295
+ return flat
1296
+
1297
+
1298
+
1299
+
1300
+ # --------------------------------------------------------------------------- #
1301
+ # Stage-exit domain (mirror of the shim's copies; item 007). The shim keeps its
1302
+ # own inline definitions (source-grep + parity tests read them there); these
1303
+ # copies exist so the extracted exit/routes modules import them from here
1304
+ # rather than the shim (which would be circular). Item 008 drains the shim copy.
1305
+ # --------------------------------------------------------------------------- #
1306
+
1307
+ #: The directive-facing form of the debt-metadata advisory (`warnings` entry 2).
1308
+ #: The stderr twin lives in `_warn_auto_verify_debt_metadata`; this one also
1309
+ #: names the subject and the host-translated retry command, because a `warnings` entry
1310
+ #: must carry both the affected feature/stage/key AND the recovery action (REQ-OBS-02).
1311
+ AUTO_VERIFY_DEBT_METADATA_DIAGNOSTIC: Final = (
1312
+ "{subject}: {verify_key} is auto-verify-pending but its scheduledStageVersion "
1313
+ "is missing or malformed (legacy or hand-edited state); the debt stays "
1314
+ "outstanding — run {command} to resolve it and record a usable schedule."
1315
+ )
1316
+
1317
+ #: The exact template for an `autoVerifyStages` key that names no
1318
+ #: verify-capable stage. A typo there silently never takes effect, so the exit
1319
+ #: says so — once per offending key, in sorted key order, on stderr, and
1320
+ #: WITHOUT failing the exit: an ignored config key is an advisory, not a usage
1321
+ #: error. `{valid}` is derived from `VERIFY_TOKEN_BY_STAGE` so the sentence cannot
1322
+ #: drift from the domain it describes.
1323
+ INVALID_AUTO_VERIFY_KEY_WARNING: Final = (
1324
+ 'Warning: autoVerifyStages key "{key}" names no verify-capable stage; it is '
1325
+ "ignored. Valid keys are {valid}."
1326
+ )
1327
+
1328
+ #: The exact template for an epic edit-mode member whose live pipeline state
1329
+ #: cannot be resolved. It is `warnings` entry 1 and the router's
1330
+ #: ONE tolerant new case: the exit degrades DOWN to `forge-1-prd <member>` rather
1331
+ #: than fabricating progress it could not read (REQ-PROD-06). The trailing sentence
1332
+ #: is what makes the warning name both the affected feature and the recovery action
1333
+ #: (REQ-OBS-02); `{reason}` is one of `EPIC_MEMBER_FALLBACK_REASONS`.
1334
+ EPIC_MEMBER_FALLBACK_WARNING: Final = (
1335
+ "Warning: {member}: pipeline state could not be resolved under epic {epic} "
1336
+ "({reason}); routing to forge-1-prd. Run /skill:forge {member} to "
1337
+ "inspect its state."
1338
+ )
1339
+ #: The closed reason domain for `EPIC_MEMBER_FALLBACK_WARNING`. No other
1340
+ #: value may be substituted — `tests/test_stage_exit.py` asserts the literal.
1341
+ EPIC_MEMBER_FALLBACK_REASONS: Final[tuple[str, ...]] = (
1342
+ "missing",
1343
+ "unreadable",
1344
+ "malformed",
1345
+ "not a member of this epic",
1346
+ )
1347
+
1348
+ #: The seven stages that produce a pipeline artifact. forge-0-epic participates in
1349
+ #: exit and verify routing but not the member production walk (PRODUCTION_STAGES).
1350
+ ProductionStage = Literal[
1351
+ "forge-0-epic",
1352
+ "forge-1-prd",
1353
+ "forge-2-tech",
1354
+ "forge-3-specs",
1355
+ "forge-4-backlog",
1356
+ "forge-5-loop",
1357
+ "forge-6-docs",
1358
+ ]
1359
+
1360
+ #: Every skill that closes a stage through `stage-exit` — the seven production
1361
+ #: stages plus the two branch skills.
1362
+ ExitStage = Literal[
1363
+ "forge-0-epic",
1364
+ "forge-1-prd",
1365
+ "forge-2-tech",
1366
+ "forge-3-specs",
1367
+ "forge-4-backlog",
1368
+ "forge-5-loop",
1369
+ "forge-6-docs",
1370
+ "forge-verify",
1371
+ "forge-fix",
1372
+ ]
1373
+
1374
+ #: forge-verify's mode, which selects the production stage a diversion served.
1375
+ VerifyMode = Literal["epic", "prd", "tech", "specs", "backlog", "impl"]
1376
+
1377
+ #: Who prints the terminal block for a branch exit.
1378
+ ExitOwner = Literal["direct", "nested"]
1379
+
1380
+ #: Whether the host may run an interactive verify gate + clean-room dispatch.
1381
+ VerifyCapability = Literal["interactive", "manual"]
1382
+
1383
+ #: The navigator/stage-exit freshness label for an artifact's verification.
1384
+ VerifyStateLabel = Literal[
1385
+ "fresh", "stale", "failing", "never", "auto-pending", "skipped", "none"
1386
+ ]
1387
+
1388
+ #: Which gate form a stage exit asks the caller to render.
1389
+ VerifyGate = Literal["none", "standard", "manual-print"]
1390
+
1391
+ #: The union of the verification-state cases the three upstream-verify gates branch
1392
+ #: on — the raw `forge-verify-*` statuses they key on, plus `never` for the absent/
1393
+ #: `pending`/unrecognized bucket every gate folds into "not verified". `verify-state`
1394
+ #: maps a served stage's entry to exactly one of these; `test_verify_state.py` pins
1395
+ #: the enum to the three skill bodies so a gate cannot grow a seventh case or spell an
1396
+ #: existing one differently.
1397
+ VerifyStateCase = Literal[
1398
+ "passed",
1399
+ "findings-reported",
1400
+ "findings-applied",
1401
+ "auto-verify-pending",
1402
+ "skipped",
1403
+ "never",
1404
+ ]
1405
+
1406
+ LoopOutcome = Literal[
1407
+ "complete", "partial", "blocked", "needs-human", "deferred", "resolved"
1408
+ ]
1409
+
1410
+ DocsOutcome = Literal["complete", "blocked", "skipped"]
1411
+
1412
+ VerifyOutcome = Literal["passed", "findings", "skipped", "failed"]
1413
+
1414
+ FixOutcome = Literal[
1415
+ "no-findings",
1416
+ "decisions",
1417
+ "failed",
1418
+ "applied",
1419
+ "reverified",
1420
+ "reverify-findings",
1421
+ "deferred",
1422
+ ]
1423
+
1424
+ #: Derived, never hand-listed — see the block comment above.
1425
+ EXIT_STAGES: Final[tuple[str, ...]] = get_args(ExitStage)
1426
+
1427
+ #: The stages whose exit carries a multi-way outcome, and each one's legal values.
1428
+ #: Stages absent from this table take no `--outcome` at all.
1429
+ EXIT_OUTCOMES: Final[dict[str, frozenset[str]]] = {
1430
+ "forge-5-loop": frozenset(get_args(LoopOutcome)),
1431
+ "forge-6-docs": frozenset(get_args(DocsOutcome)),
1432
+ "forge-verify": frozenset(get_args(VerifyOutcome)),
1433
+ "forge-fix": frozenset(get_args(FixOutcome)),
1434
+ }
1435
+
1436
+ #: The one domain still written twice, because neither side is a subset of the
1437
+ #: other: its keys MUST equal set(get_args(VerifyMode)) and its values MUST be a
1438
+ #: subset of get_args(ProductionStage). tests/test_stage_constants_parity.py
1439
+ #: asserts both. NOT collapsible into VERIFY_TOKEN_BY_STAGE's inverse — that map
1440
+ #: has no `epic` mode and exists to name state keys, not to route stages.
1441
+ VERIFY_MODE_TO_STAGE: Final[dict[str, str]] = {
1442
+ "epic": "forge-0-epic",
1443
+ "prd": "forge-1-prd",
1444
+ "tech": "forge-2-tech",
1445
+ "specs": "forge-3-specs",
1446
+ "backlog": "forge-4-backlog",
1447
+ "impl": "forge-5-loop",
1448
+ }
1449
+
1450
+ #: The fixed final line of the NEXT-STEPS block. The stamp instructs the skill
1451
+ #: to print the block verbatim as its absolute last output — nothing after this.
1452
+ NEXT_STEPS_SENTINEL: Final = "─ forge: end of stage ─"
1453
+
1454
+ #: The `--host` domain: command syntax and fresh-session wording only. A host NEVER
1455
+ #: implies a verification capability (REQ-EXIT-07).
1456
+ EXIT_HOSTS: Final[tuple[str, ...]] = ("claude", "generic", "pi")
1457
+
1458
+ #: Stage id -> the noun phrase gate wording uses (the old {stage} stamp slot).
1459
+ STAGE_NOUN: Final[dict[str, str]] = {
1460
+ "forge-0-epic": "the epic decomposition",
1461
+ "forge-1-prd": "the PRD",
1462
+ "forge-2-tech": "the tech spec",
1463
+ "forge-3-specs": "the implementation specs",
1464
+ "forge-4-backlog": "the backlog",
1465
+ }
1466
+
1467
+ def pending_verify(state: dict) -> str | None:
1468
+ """Return the production stage whose verify is outstanding, if any.
1469
+
1470
+ Outstanding means the most-recently-completed production stage's verify is not
1471
+ ``fresh`` (never run, scheduled-but-unrun automatic verification, reported
1472
+ findings, or gone stale after an artifact revision). An ``auto-pending`` stage
1473
+ is returned like any other outstanding one — recorded debt is owed work, and
1474
+ ``_VERIFY_RESOLVED`` deliberately excludes it.
1475
+ An explicit ``skipped`` is treated as resolved (never outstanding).
1476
+ Surfaced so the navigator can offer "verify before continuing" as an
1477
+ alternative to advancing. Returns ``None`` when the latest stage is fresh,
1478
+ skipped, or there is nothing to verify.
1479
+ """
1480
+ stage, label = verify_state(state)
1481
+ return stage if label not in ("fresh", "none", "skipped") else None
1482
+
1483
+
1484
+ __all__ = [
1485
+ "UsageError",
1486
+ "VerifyStatus",
1487
+ "FeatureRow",
1488
+ "EpicReconcile",
1489
+ "StageExitDirectives",
1490
+ "StageExitPayload",
1491
+ "VerifyEntry",
1492
+ "load_json_with_duplicates",
1493
+ "warn_duplicate_keys",
1494
+ "_load_config",
1495
+ "load_effective_config",
1496
+ "_loop_runner_defaults",
1497
+ "resolve_loop_runner",
1498
+ "resolve_loop_runner_layers",
1499
+ "LOOP_RUNNER_BIN_ENV",
1500
+ "_now_iso",
1501
+ "_write_state",
1502
+ "_commit_state",
1503
+ "PIPELINE_STATE_FILENAME",
1504
+ "MANIFEST_FILENAME",
1505
+ "EPIC_STATE_FILENAME",
1506
+ "SAFE_NAME_RE",
1507
+ "FULL_GIT_HASH_RE",
1508
+ "PRODUCTION_STAGES",
1509
+ "VERIFY_TOKEN_BY_STAGE",
1510
+ "VERIFY_STAGES",
1511
+ "KNOWN_VERIFY_STATUSES",
1512
+ "_DONE_STATUS",
1513
+ "_DONE_STATUSES",
1514
+ "_SKIP_PROTECTED_PRIOR",
1515
+ "_VERIFY_RESOLVED",
1516
+ "VERIFY_RESULT_STATUSES",
1517
+ "AUTO_PENDING_DIAGNOSTIC",
1518
+ "_UNKNOWN_VERIFY_WARNED",
1519
+ "_AUTO_VERIFY_DEBT_WARNED",
1520
+ "_read_state",
1521
+ "_scan_features",
1522
+ "_stage_status",
1523
+ "next_stage",
1524
+ "_stage_version",
1525
+ "_verify_entry",
1526
+ "_warn_unknown_verify_status",
1527
+ "_scheduled_stage_version",
1528
+ "_warn_auto_verify_debt_metadata",
1529
+ "auto_pending_message",
1530
+ "verify_state",
1531
+ "auto_verify_for",
1532
+ "_parse_ts",
1533
+ "build_rows",
1534
+ "_git_output",
1535
+ "_default_branch",
1536
+ "_counts",
1537
+ "_config_duplicate_keys",
1538
+ "invalid_auto_verify_keys",
1539
+ "_default_schema_path",
1540
+ "_EXIT_VERIFY_TOKEN",
1541
+ "VERIFY_STATE_MESSAGES",
1542
+ "_resolve_feature_dir",
1543
+ "ProductionStage",
1544
+ "ExitStage",
1545
+ "VerifyMode",
1546
+ "ExitOwner",
1547
+ "VerifyCapability",
1548
+ "VerifyStateLabel",
1549
+ "VerifyGate",
1550
+ "VerifyStateCase",
1551
+ "LoopOutcome",
1552
+ "DocsOutcome",
1553
+ "VerifyOutcome",
1554
+ "FixOutcome",
1555
+ "EXIT_STAGES",
1556
+ "EXIT_OUTCOMES",
1557
+ "VERIFY_MODE_TO_STAGE",
1558
+ "NEXT_STEPS_SENTINEL",
1559
+ "EXIT_HOSTS",
1560
+ "STAGE_NOUN",
1561
+ "AUTO_VERIFY_DEBT_METADATA_DIAGNOSTIC",
1562
+ "INVALID_AUTO_VERIFY_KEY_WARNING",
1563
+ "EPIC_MEMBER_FALLBACK_WARNING",
1564
+ "EPIC_MEMBER_FALLBACK_REASONS",
1565
+ "pending_verify",
1566
+ ]