@garygentry/feature-forge 0.3.6 → 0.3.7
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.
- package/README.md +1 -1
- package/adapters/GENERATION-REPORT.md +20 -0
- package/adapters/claude/.claude-plugin/plugin.json +16 -0
- package/adapters/claude/.feature-forge-bundle.json +1 -1
- package/adapters/claude/agents/forge-verifier.md +16 -5
- package/adapters/claude/references/forge-config-schema.json +3 -3
- package/adapters/claude/references/portable-root.md +20 -16
- package/adapters/claude/references/preflight-and-self-heal.md +183 -0
- package/adapters/claude/references/process-overview.md +1 -1
- package/adapters/claude/references/select-outcome.md +138 -0
- package/adapters/claude/references/shared-conventions.md +175 -22
- package/adapters/claude/references/skill-frontmatter.schema.json +3 -1
- package/adapters/claude/references/stack-resolution.md +1 -1
- package/adapters/claude/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/references/templates/root-hygiene/AGENTS.md +14 -0
- package/adapters/claude/references/templates/root-hygiene/CLAUDE.md +14 -0
- package/adapters/claude/references/templates/specs-hygiene/AGENTS.md +3 -1
- package/adapters/claude/references/templates/specs-hygiene/CLAUDE.md +2 -1
- package/adapters/claude/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/claude/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/claude/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/claude/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/claude/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/claude/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/claude/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/claude/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/claude/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/claude/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/claude/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/claude/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/claude/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/claude/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/claude/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/claude/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/claude/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/claude/references/verify-state.md +98 -0
- package/adapters/claude/scripts/forge-bootstrap.py +23 -2
- package/adapters/claude/scripts/forge-root.sh +104 -21
- package/adapters/claude/scripts/forge-session.py +620 -6973
- package/adapters/claude/scripts/forge_session/__init__.py +11 -0
- package/adapters/claude/scripts/forge_session/_common.py +1566 -0
- package/adapters/claude/scripts/forge_session/_doctor_util.py +218 -0
- package/adapters/claude/scripts/forge_session/cli.py +1067 -0
- package/adapters/claude/scripts/forge_session/decisions.py +355 -0
- package/adapters/claude/scripts/forge_session/discover.py +472 -0
- package/adapters/claude/scripts/forge_session/doctor.py +2144 -0
- package/adapters/claude/scripts/forge_session/exit.py +1081 -0
- package/adapters/claude/scripts/forge_session/outcomes.py +574 -0
- package/adapters/claude/scripts/forge_session/routes.py +1162 -0
- package/adapters/claude/scripts/forge_session/state.py +1479 -0
- package/adapters/claude/scripts/forge_session/topology.py +443 -0
- package/adapters/claude/skills/forge/SKILL.md +7 -7
- package/adapters/claude/skills/forge/references/process-overview.md +1 -1
- package/adapters/claude/skills/forge/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/skills/forge-0-epic/SKILL.md +7 -7
- package/adapters/claude/skills/forge-0-epic/references/edit-mode.md +9 -5
- package/adapters/claude/skills/forge-0-epic/references/epic-manifest-subcommands.md +6 -1
- package/adapters/claude/skills/forge-0-epic/references/portable-root.md +20 -16
- package/adapters/claude/skills/forge-0-epic/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge-0-epic/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/skills/forge-1-prd/SKILL.md +7 -7
- package/adapters/claude/skills/forge-1-prd/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge-1-prd/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/skills/forge-2-tech/SKILL.md +7 -7
- package/adapters/claude/skills/forge-2-tech/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge-2-tech/references/stack-resolution.md +1 -1
- package/adapters/claude/skills/forge-2-tech/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/skills/forge-3-specs/SKILL.md +5 -5
- package/adapters/claude/skills/forge-3-specs/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge-3-specs/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/skills/forge-4-backlog/SKILL.md +16 -8
- package/adapters/claude/skills/forge-4-backlog/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge-4-backlog/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/skills/forge-4-backlog/references/verify-state.md +98 -0
- package/adapters/claude/skills/forge-5-loop/SKILL.md +36 -41
- package/adapters/claude/skills/forge-5-loop/references/agent-selection.md +4 -0
- package/adapters/claude/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
- package/adapters/claude/skills/forge-5-loop/references/recovery-procedure.md +10 -0
- package/adapters/claude/skills/forge-5-loop/references/runner-contract.md +15 -5
- package/adapters/claude/skills/forge-5-loop/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge-5-loop/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/skills/forge-5-loop/references/verify-state.md +98 -0
- package/adapters/claude/skills/forge-6-docs/SKILL.md +21 -14
- package/adapters/claude/skills/forge-6-docs/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge-6-docs/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/skills/forge-6-docs/references/verify-state.md +98 -0
- package/adapters/claude/skills/forge-bootstrap/SKILL.md +19 -10
- package/adapters/claude/skills/forge-bootstrap/references/shared-conventions.md +572 -0
- package/adapters/claude/skills/forge-fix/SKILL.md +15 -18
- package/adapters/claude/skills/forge-fix/references/select-outcome.md +138 -0
- package/adapters/claude/skills/forge-fix/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge-fix/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/skills/forge-guide/SKILL.md +87 -1
- package/adapters/claude/skills/forge-guide/references/forge-config-schema.json +3 -3
- package/adapters/claude/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
- package/adapters/claude/skills/forge-guide/references/process-overview.md +1 -1
- package/adapters/claude/skills/forge-guide/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge-guide/references/stack-resolution.md +1 -1
- package/adapters/claude/skills/forge-init/SKILL.md +58 -7
- package/adapters/claude/skills/forge-init/references/preflight-and-self-heal.md +183 -0
- package/adapters/claude/skills/forge-init/references/shared-conventions.md +572 -0
- package/adapters/claude/skills/forge-verify/SKILL.md +20 -23
- package/adapters/claude/skills/forge-verify/references/findings-template.md +2 -2
- package/adapters/claude/skills/forge-verify/references/select-outcome.md +138 -0
- package/adapters/claude/skills/forge-verify/references/shared-conventions.md +175 -22
- package/adapters/claude/skills/forge-verify/references/stage-exit-protocol.md +19 -4
- package/adapters/claude/skills/forge-verify/references/verification-checklists/epic.md +1 -1
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/claude/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/codex/.feature-forge-bundle.json +1 -1
- package/adapters/codex/agents/forge-researcher.toml +1 -1
- package/adapters/codex/agents/forge-verifier.toml +17 -6
- package/adapters/codex/references/forge-config-schema.json +5 -5
- package/adapters/codex/references/portable-root.md +20 -16
- package/adapters/codex/references/preflight-and-self-heal.md +183 -0
- package/adapters/codex/references/process-overview.md +10 -10
- package/adapters/codex/references/select-outcome.md +138 -0
- package/adapters/codex/references/shared-conventions.md +188 -35
- package/adapters/codex/references/skill-frontmatter.schema.json +3 -1
- package/adapters/codex/references/stack-resolution.md +1 -1
- package/adapters/codex/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/references/templates/root-hygiene/AGENTS.md +14 -0
- package/adapters/codex/references/templates/root-hygiene/CLAUDE.md +14 -0
- package/adapters/codex/references/templates/specs-hygiene/AGENTS.md +3 -1
- package/adapters/codex/references/templates/specs-hygiene/CLAUDE.md +2 -1
- package/adapters/codex/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/codex/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/codex/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/codex/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/codex/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/codex/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/codex/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/codex/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/codex/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/codex/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/codex/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/codex/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/codex/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/codex/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/codex/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/codex/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/codex/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/codex/references/verify-state.md +98 -0
- package/adapters/codex/scripts/forge-bootstrap.py +23 -2
- package/adapters/codex/scripts/forge-root.sh +104 -21
- package/adapters/codex/scripts/forge-session.py +620 -6973
- package/adapters/codex/scripts/forge_session/__init__.py +11 -0
- package/adapters/codex/scripts/forge_session/_common.py +1566 -0
- package/adapters/codex/scripts/forge_session/_doctor_util.py +218 -0
- package/adapters/codex/scripts/forge_session/cli.py +1067 -0
- package/adapters/codex/scripts/forge_session/decisions.py +355 -0
- package/adapters/codex/scripts/forge_session/discover.py +472 -0
- package/adapters/codex/scripts/forge_session/doctor.py +2144 -0
- package/adapters/codex/scripts/forge_session/exit.py +1081 -0
- package/adapters/codex/scripts/forge_session/outcomes.py +574 -0
- package/adapters/codex/scripts/forge_session/routes.py +1162 -0
- package/adapters/codex/scripts/forge_session/state.py +1479 -0
- package/adapters/codex/scripts/forge_session/topology.py +443 -0
- package/adapters/codex/skills/forge/SKILL.md +40 -40
- package/adapters/codex/skills/forge/references/process-overview.md +10 -10
- package/adapters/codex/skills/forge/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/skills/forge-0-epic/SKILL.md +15 -15
- package/adapters/codex/skills/forge-0-epic/references/edit-mode.md +14 -10
- package/adapters/codex/skills/forge-0-epic/references/epic-manifest-subcommands.md +8 -3
- package/adapters/codex/skills/forge-0-epic/references/portable-root.md +20 -16
- package/adapters/codex/skills/forge-0-epic/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge-0-epic/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/skills/forge-1-prd/SKILL.md +15 -15
- package/adapters/codex/skills/forge-1-prd/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge-1-prd/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/skills/forge-2-tech/SKILL.md +15 -15
- package/adapters/codex/skills/forge-2-tech/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge-2-tech/references/stack-resolution.md +1 -1
- package/adapters/codex/skills/forge-2-tech/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/skills/forge-3-specs/SKILL.md +10 -10
- package/adapters/codex/skills/forge-3-specs/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge-3-specs/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/skills/forge-4-backlog/SKILL.md +19 -11
- package/adapters/codex/skills/forge-4-backlog/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge-4-backlog/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/skills/forge-4-backlog/references/verify-state.md +98 -0
- package/adapters/codex/skills/forge-5-loop/SKILL.md +48 -53
- package/adapters/codex/skills/forge-5-loop/references/agent-selection.md +5 -1
- package/adapters/codex/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
- package/adapters/codex/skills/forge-5-loop/references/recovery-procedure.md +14 -4
- package/adapters/codex/skills/forge-5-loop/references/runner-contract.md +18 -8
- package/adapters/codex/skills/forge-5-loop/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge-5-loop/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/skills/forge-5-loop/references/verify-state.md +98 -0
- package/adapters/codex/skills/forge-6-docs/SKILL.md +27 -20
- package/adapters/codex/skills/forge-6-docs/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge-6-docs/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/skills/forge-6-docs/references/verify-state.md +98 -0
- package/adapters/codex/skills/forge-bootstrap/SKILL.md +23 -15
- package/adapters/codex/skills/forge-bootstrap/references/shared-conventions.md +572 -0
- package/adapters/codex/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +2 -2
- package/adapters/codex/skills/forge-fix/SKILL.md +27 -30
- package/adapters/codex/skills/forge-fix/references/select-outcome.md +138 -0
- package/adapters/codex/skills/forge-fix/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge-fix/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/skills/forge-guide/SKILL.md +84 -7
- package/adapters/codex/skills/forge-guide/references/forge-config-schema.json +5 -5
- package/adapters/codex/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
- package/adapters/codex/skills/forge-guide/references/process-overview.md +10 -10
- package/adapters/codex/skills/forge-guide/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge-guide/references/stack-resolution.md +1 -1
- package/adapters/codex/skills/forge-init/SKILL.md +62 -12
- package/adapters/codex/skills/forge-init/references/preflight-and-self-heal.md +183 -0
- package/adapters/codex/skills/forge-init/references/shared-conventions.md +572 -0
- package/adapters/codex/skills/forge-verify/SKILL.md +27 -30
- package/adapters/codex/skills/forge-verify/references/findings-template.md +2 -2
- package/adapters/codex/skills/forge-verify/references/select-outcome.md +138 -0
- package/adapters/codex/skills/forge-verify/references/shared-conventions.md +188 -35
- package/adapters/codex/skills/forge-verify/references/stage-exit-protocol.md +22 -7
- package/adapters/codex/skills/forge-verify/references/verification-checklists/epic.md +2 -2
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/codex/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/copilot/.feature-forge-bundle.json +1 -1
- package/adapters/copilot/agents/forge-researcher.md +1 -1
- package/adapters/copilot/agents/forge-verifier.md +17 -6
- package/adapters/copilot/references/forge-config-schema.json +5 -5
- package/adapters/copilot/references/portable-root.md +20 -16
- package/adapters/copilot/references/preflight-and-self-heal.md +183 -0
- package/adapters/copilot/references/process-overview.md +10 -10
- package/adapters/copilot/references/select-outcome.md +138 -0
- package/adapters/copilot/references/shared-conventions.md +188 -35
- package/adapters/copilot/references/skill-frontmatter.schema.json +3 -1
- package/adapters/copilot/references/stack-resolution.md +1 -1
- package/adapters/copilot/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/references/templates/root-hygiene/AGENTS.md +14 -0
- package/adapters/copilot/references/templates/root-hygiene/CLAUDE.md +14 -0
- package/adapters/copilot/references/templates/specs-hygiene/AGENTS.md +3 -1
- package/adapters/copilot/references/templates/specs-hygiene/CLAUDE.md +2 -1
- package/adapters/copilot/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/copilot/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/copilot/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/copilot/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/copilot/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/copilot/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/copilot/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/copilot/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/copilot/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/copilot/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/copilot/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/copilot/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/copilot/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/copilot/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/copilot/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/copilot/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/copilot/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/copilot/references/verify-state.md +98 -0
- package/adapters/copilot/scripts/forge-bootstrap.py +23 -2
- package/adapters/copilot/scripts/forge-root.sh +104 -21
- package/adapters/copilot/scripts/forge-session.py +620 -6973
- package/adapters/copilot/scripts/forge_session/__init__.py +11 -0
- package/adapters/copilot/scripts/forge_session/_common.py +1566 -0
- package/adapters/copilot/scripts/forge_session/_doctor_util.py +218 -0
- package/adapters/copilot/scripts/forge_session/cli.py +1067 -0
- package/adapters/copilot/scripts/forge_session/decisions.py +355 -0
- package/adapters/copilot/scripts/forge_session/discover.py +472 -0
- package/adapters/copilot/scripts/forge_session/doctor.py +2144 -0
- package/adapters/copilot/scripts/forge_session/exit.py +1081 -0
- package/adapters/copilot/scripts/forge_session/outcomes.py +574 -0
- package/adapters/copilot/scripts/forge_session/routes.py +1162 -0
- package/adapters/copilot/scripts/forge_session/state.py +1479 -0
- package/adapters/copilot/scripts/forge_session/topology.py +443 -0
- package/adapters/copilot/skills/forge/forge.md +40 -40
- package/adapters/copilot/skills/forge/references/process-overview.md +10 -10
- package/adapters/copilot/skills/forge/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/skills/forge-0-epic/forge-0-epic.md +15 -15
- package/adapters/copilot/skills/forge-0-epic/references/edit-mode.md +14 -10
- package/adapters/copilot/skills/forge-0-epic/references/epic-manifest-subcommands.md +8 -3
- package/adapters/copilot/skills/forge-0-epic/references/portable-root.md +20 -16
- package/adapters/copilot/skills/forge-0-epic/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge-0-epic/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/skills/forge-1-prd/forge-1-prd.md +15 -15
- package/adapters/copilot/skills/forge-1-prd/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge-1-prd/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/skills/forge-2-tech/forge-2-tech.md +15 -15
- package/adapters/copilot/skills/forge-2-tech/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge-2-tech/references/stack-resolution.md +1 -1
- package/adapters/copilot/skills/forge-2-tech/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/skills/forge-3-specs/forge-3-specs.md +10 -10
- package/adapters/copilot/skills/forge-3-specs/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge-3-specs/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/skills/forge-4-backlog/forge-4-backlog.md +19 -11
- package/adapters/copilot/skills/forge-4-backlog/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge-4-backlog/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/skills/forge-4-backlog/references/verify-state.md +98 -0
- package/adapters/copilot/skills/forge-5-loop/forge-5-loop.md +48 -53
- package/adapters/copilot/skills/forge-5-loop/references/agent-selection.md +5 -1
- package/adapters/copilot/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
- package/adapters/copilot/skills/forge-5-loop/references/recovery-procedure.md +14 -4
- package/adapters/copilot/skills/forge-5-loop/references/runner-contract.md +18 -8
- package/adapters/copilot/skills/forge-5-loop/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge-5-loop/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/skills/forge-5-loop/references/verify-state.md +98 -0
- package/adapters/copilot/skills/forge-6-docs/forge-6-docs.md +27 -20
- package/adapters/copilot/skills/forge-6-docs/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge-6-docs/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/skills/forge-6-docs/references/verify-state.md +98 -0
- package/adapters/copilot/skills/forge-bootstrap/forge-bootstrap.md +23 -15
- package/adapters/copilot/skills/forge-bootstrap/references/shared-conventions.md +572 -0
- package/adapters/copilot/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +2 -2
- package/adapters/copilot/skills/forge-fix/forge-fix.md +27 -30
- package/adapters/copilot/skills/forge-fix/references/select-outcome.md +138 -0
- package/adapters/copilot/skills/forge-fix/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge-fix/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/skills/forge-guide/forge-guide.md +84 -7
- package/adapters/copilot/skills/forge-guide/references/forge-config-schema.json +5 -5
- package/adapters/copilot/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
- package/adapters/copilot/skills/forge-guide/references/process-overview.md +10 -10
- package/adapters/copilot/skills/forge-guide/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge-guide/references/stack-resolution.md +1 -1
- package/adapters/copilot/skills/forge-init/forge-init.md +62 -12
- package/adapters/copilot/skills/forge-init/references/preflight-and-self-heal.md +183 -0
- package/adapters/copilot/skills/forge-init/references/shared-conventions.md +572 -0
- package/adapters/copilot/skills/forge-verify/forge-verify.md +27 -30
- package/adapters/copilot/skills/forge-verify/references/findings-template.md +2 -2
- package/adapters/copilot/skills/forge-verify/references/select-outcome.md +138 -0
- package/adapters/copilot/skills/forge-verify/references/shared-conventions.md +188 -35
- package/adapters/copilot/skills/forge-verify/references/stage-exit-protocol.md +22 -7
- package/adapters/copilot/skills/forge-verify/references/verification-checklists/epic.md +2 -2
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/copilot/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/cursor/.feature-forge-bundle.json +1 -1
- package/adapters/cursor/agents/forge-researcher.mdc +1 -1
- package/adapters/cursor/agents/forge-verifier.mdc +17 -6
- package/adapters/cursor/references/forge-config-schema.json +5 -5
- package/adapters/cursor/references/portable-root.md +20 -16
- package/adapters/cursor/references/preflight-and-self-heal.md +183 -0
- package/adapters/cursor/references/process-overview.md +10 -10
- package/adapters/cursor/references/select-outcome.md +138 -0
- package/adapters/cursor/references/shared-conventions.md +188 -35
- package/adapters/cursor/references/skill-frontmatter.schema.json +3 -1
- package/adapters/cursor/references/stack-resolution.md +1 -1
- package/adapters/cursor/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/references/templates/root-hygiene/AGENTS.md +14 -0
- package/adapters/cursor/references/templates/root-hygiene/CLAUDE.md +14 -0
- package/adapters/cursor/references/templates/specs-hygiene/AGENTS.md +3 -1
- package/adapters/cursor/references/templates/specs-hygiene/CLAUDE.md +2 -1
- package/adapters/cursor/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/cursor/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/cursor/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/cursor/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/cursor/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/cursor/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/cursor/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/cursor/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/cursor/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/cursor/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/cursor/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/cursor/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/cursor/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/cursor/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/cursor/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/cursor/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/cursor/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/cursor/references/verify-state.md +98 -0
- package/adapters/cursor/scripts/forge-bootstrap.py +23 -2
- package/adapters/cursor/scripts/forge-root.sh +104 -21
- package/adapters/cursor/scripts/forge-session.py +620 -6973
- package/adapters/cursor/scripts/forge_session/__init__.py +11 -0
- package/adapters/cursor/scripts/forge_session/_common.py +1566 -0
- package/adapters/cursor/scripts/forge_session/_doctor_util.py +218 -0
- package/adapters/cursor/scripts/forge_session/cli.py +1067 -0
- package/adapters/cursor/scripts/forge_session/decisions.py +355 -0
- package/adapters/cursor/scripts/forge_session/discover.py +472 -0
- package/adapters/cursor/scripts/forge_session/doctor.py +2144 -0
- package/adapters/cursor/scripts/forge_session/exit.py +1081 -0
- package/adapters/cursor/scripts/forge_session/outcomes.py +574 -0
- package/adapters/cursor/scripts/forge_session/routes.py +1162 -0
- package/adapters/cursor/scripts/forge_session/state.py +1479 -0
- package/adapters/cursor/scripts/forge_session/topology.py +443 -0
- package/adapters/cursor/skills/forge/forge.mdc +40 -40
- package/adapters/cursor/skills/forge/references/process-overview.md +10 -10
- package/adapters/cursor/skills/forge/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/skills/forge-0-epic/forge-0-epic.mdc +15 -15
- package/adapters/cursor/skills/forge-0-epic/references/edit-mode.md +14 -10
- package/adapters/cursor/skills/forge-0-epic/references/epic-manifest-subcommands.md +8 -3
- package/adapters/cursor/skills/forge-0-epic/references/portable-root.md +20 -16
- package/adapters/cursor/skills/forge-0-epic/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge-0-epic/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/skills/forge-1-prd/forge-1-prd.mdc +15 -15
- package/adapters/cursor/skills/forge-1-prd/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge-1-prd/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/skills/forge-2-tech/forge-2-tech.mdc +15 -15
- package/adapters/cursor/skills/forge-2-tech/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge-2-tech/references/stack-resolution.md +1 -1
- package/adapters/cursor/skills/forge-2-tech/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/skills/forge-3-specs/forge-3-specs.mdc +10 -10
- package/adapters/cursor/skills/forge-3-specs/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge-3-specs/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/skills/forge-4-backlog/forge-4-backlog.mdc +19 -11
- package/adapters/cursor/skills/forge-4-backlog/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge-4-backlog/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/skills/forge-4-backlog/references/verify-state.md +98 -0
- package/adapters/cursor/skills/forge-5-loop/forge-5-loop.mdc +48 -53
- package/adapters/cursor/skills/forge-5-loop/references/agent-selection.md +5 -1
- package/adapters/cursor/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
- package/adapters/cursor/skills/forge-5-loop/references/recovery-procedure.md +14 -4
- package/adapters/cursor/skills/forge-5-loop/references/runner-contract.md +18 -8
- package/adapters/cursor/skills/forge-5-loop/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge-5-loop/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/skills/forge-5-loop/references/verify-state.md +98 -0
- package/adapters/cursor/skills/forge-6-docs/forge-6-docs.mdc +27 -20
- package/adapters/cursor/skills/forge-6-docs/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge-6-docs/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/skills/forge-6-docs/references/verify-state.md +98 -0
- package/adapters/cursor/skills/forge-bootstrap/forge-bootstrap.mdc +23 -15
- package/adapters/cursor/skills/forge-bootstrap/references/shared-conventions.md +572 -0
- package/adapters/cursor/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +2 -2
- package/adapters/cursor/skills/forge-fix/forge-fix.mdc +27 -30
- package/adapters/cursor/skills/forge-fix/references/select-outcome.md +138 -0
- package/adapters/cursor/skills/forge-fix/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge-fix/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/skills/forge-guide/forge-guide.mdc +84 -7
- package/adapters/cursor/skills/forge-guide/references/forge-config-schema.json +5 -5
- package/adapters/cursor/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
- package/adapters/cursor/skills/forge-guide/references/process-overview.md +10 -10
- package/adapters/cursor/skills/forge-guide/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge-guide/references/stack-resolution.md +1 -1
- package/adapters/cursor/skills/forge-init/forge-init.mdc +62 -12
- package/adapters/cursor/skills/forge-init/references/preflight-and-self-heal.md +183 -0
- package/adapters/cursor/skills/forge-init/references/shared-conventions.md +572 -0
- package/adapters/cursor/skills/forge-verify/forge-verify.mdc +27 -30
- package/adapters/cursor/skills/forge-verify/references/findings-template.md +2 -2
- package/adapters/cursor/skills/forge-verify/references/select-outcome.md +138 -0
- package/adapters/cursor/skills/forge-verify/references/shared-conventions.md +188 -35
- package/adapters/cursor/skills/forge-verify/references/stage-exit-protocol.md +22 -7
- package/adapters/cursor/skills/forge-verify/references/verification-checklists/epic.md +2 -2
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/cursor/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/gemini/.feature-forge-bundle.json +1 -1
- package/adapters/gemini/agents/forge-researcher.md +1 -1
- package/adapters/gemini/agents/forge-verifier.md +17 -6
- package/adapters/gemini/gemini-extension.json +13 -13
- package/adapters/gemini/references/forge-config-schema.json +5 -5
- package/adapters/gemini/references/portable-root.md +20 -16
- package/adapters/gemini/references/preflight-and-self-heal.md +183 -0
- package/adapters/gemini/references/process-overview.md +10 -10
- package/adapters/gemini/references/select-outcome.md +138 -0
- package/adapters/gemini/references/shared-conventions.md +188 -35
- package/adapters/gemini/references/skill-frontmatter.schema.json +3 -1
- package/adapters/gemini/references/stack-resolution.md +1 -1
- package/adapters/gemini/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/references/templates/root-hygiene/AGENTS.md +14 -0
- package/adapters/gemini/references/templates/root-hygiene/CLAUDE.md +14 -0
- package/adapters/gemini/references/templates/specs-hygiene/AGENTS.md +3 -1
- package/adapters/gemini/references/templates/specs-hygiene/CLAUDE.md +2 -1
- package/adapters/gemini/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/gemini/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/gemini/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/gemini/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/gemini/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/gemini/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/gemini/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/gemini/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/gemini/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/gemini/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/gemini/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/gemini/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/gemini/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/gemini/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/gemini/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/gemini/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/gemini/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/gemini/references/verify-state.md +98 -0
- package/adapters/gemini/scripts/forge-bootstrap.py +23 -2
- package/adapters/gemini/scripts/forge-root.sh +104 -21
- package/adapters/gemini/scripts/forge-session.py +620 -6973
- package/adapters/gemini/scripts/forge_session/__init__.py +11 -0
- package/adapters/gemini/scripts/forge_session/_common.py +1566 -0
- package/adapters/gemini/scripts/forge_session/_doctor_util.py +218 -0
- package/adapters/gemini/scripts/forge_session/cli.py +1067 -0
- package/adapters/gemini/scripts/forge_session/decisions.py +355 -0
- package/adapters/gemini/scripts/forge_session/discover.py +472 -0
- package/adapters/gemini/scripts/forge_session/doctor.py +2144 -0
- package/adapters/gemini/scripts/forge_session/exit.py +1081 -0
- package/adapters/gemini/scripts/forge_session/outcomes.py +574 -0
- package/adapters/gemini/scripts/forge_session/routes.py +1162 -0
- package/adapters/gemini/scripts/forge_session/state.py +1479 -0
- package/adapters/gemini/scripts/forge_session/topology.py +443 -0
- package/adapters/gemini/skills/forge/forge.md +40 -40
- package/adapters/gemini/skills/forge/references/process-overview.md +10 -10
- package/adapters/gemini/skills/forge/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/skills/forge-0-epic/forge-0-epic.md +15 -15
- package/adapters/gemini/skills/forge-0-epic/references/edit-mode.md +14 -10
- package/adapters/gemini/skills/forge-0-epic/references/epic-manifest-subcommands.md +8 -3
- package/adapters/gemini/skills/forge-0-epic/references/portable-root.md +20 -16
- package/adapters/gemini/skills/forge-0-epic/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge-0-epic/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/skills/forge-1-prd/forge-1-prd.md +15 -15
- package/adapters/gemini/skills/forge-1-prd/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge-1-prd/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/skills/forge-2-tech/forge-2-tech.md +15 -15
- package/adapters/gemini/skills/forge-2-tech/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge-2-tech/references/stack-resolution.md +1 -1
- package/adapters/gemini/skills/forge-2-tech/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/skills/forge-3-specs/forge-3-specs.md +10 -10
- package/adapters/gemini/skills/forge-3-specs/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge-3-specs/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/skills/forge-4-backlog/forge-4-backlog.md +19 -11
- package/adapters/gemini/skills/forge-4-backlog/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge-4-backlog/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/skills/forge-4-backlog/references/verify-state.md +98 -0
- package/adapters/gemini/skills/forge-5-loop/forge-5-loop.md +48 -53
- package/adapters/gemini/skills/forge-5-loop/references/agent-selection.md +5 -1
- package/adapters/gemini/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
- package/adapters/gemini/skills/forge-5-loop/references/recovery-procedure.md +14 -4
- package/adapters/gemini/skills/forge-5-loop/references/runner-contract.md +18 -8
- package/adapters/gemini/skills/forge-5-loop/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge-5-loop/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/skills/forge-5-loop/references/verify-state.md +98 -0
- package/adapters/gemini/skills/forge-6-docs/forge-6-docs.md +27 -20
- package/adapters/gemini/skills/forge-6-docs/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge-6-docs/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/skills/forge-6-docs/references/verify-state.md +98 -0
- package/adapters/gemini/skills/forge-bootstrap/forge-bootstrap.md +23 -15
- package/adapters/gemini/skills/forge-bootstrap/references/shared-conventions.md +572 -0
- package/adapters/gemini/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +2 -2
- package/adapters/gemini/skills/forge-fix/forge-fix.md +27 -30
- package/adapters/gemini/skills/forge-fix/references/select-outcome.md +138 -0
- package/adapters/gemini/skills/forge-fix/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge-fix/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/skills/forge-guide/forge-guide.md +84 -7
- package/adapters/gemini/skills/forge-guide/references/forge-config-schema.json +5 -5
- package/adapters/gemini/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
- package/adapters/gemini/skills/forge-guide/references/process-overview.md +10 -10
- package/adapters/gemini/skills/forge-guide/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge-guide/references/stack-resolution.md +1 -1
- package/adapters/gemini/skills/forge-init/forge-init.md +62 -12
- package/adapters/gemini/skills/forge-init/references/preflight-and-self-heal.md +183 -0
- package/adapters/gemini/skills/forge-init/references/shared-conventions.md +572 -0
- package/adapters/gemini/skills/forge-verify/forge-verify.md +27 -30
- package/adapters/gemini/skills/forge-verify/references/findings-template.md +2 -2
- package/adapters/gemini/skills/forge-verify/references/select-outcome.md +138 -0
- package/adapters/gemini/skills/forge-verify/references/shared-conventions.md +188 -35
- package/adapters/gemini/skills/forge-verify/references/stage-exit-protocol.md +22 -7
- package/adapters/gemini/skills/forge-verify/references/verification-checklists/epic.md +2 -2
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/gemini/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/pi/.feature-forge-bundle.json +1 -1
- package/adapters/pi/agents/forge-verifier.md +16 -5
- package/adapters/pi/extensions/forge-invocation-args/README.md +106 -0
- package/adapters/pi/extensions/forge-invocation-args/index.ts +31 -0
- package/adapters/pi/extensions/forge-invocation-args/wiring.ts +172 -0
- package/adapters/pi/package.json +1 -0
- package/adapters/pi/references/forge-config-schema.json +3 -3
- package/adapters/pi/references/portable-root.md +20 -16
- package/adapters/pi/references/preflight-and-self-heal.md +183 -0
- package/adapters/pi/references/select-outcome.md +138 -0
- package/adapters/pi/references/shared-conventions.md +175 -22
- package/adapters/pi/references/skill-frontmatter.schema.json +3 -1
- package/adapters/pi/references/stack-resolution.md +1 -1
- package/adapters/pi/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/references/templates/root-hygiene/AGENTS.md +14 -0
- package/adapters/pi/references/templates/root-hygiene/CLAUDE.md +14 -0
- package/adapters/pi/references/templates/specs-hygiene/AGENTS.md +3 -1
- package/adapters/pi/references/templates/specs-hygiene/CLAUDE.md +2 -1
- package/adapters/pi/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/pi/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/pi/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/pi/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/pi/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/pi/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/pi/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/pi/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/pi/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/pi/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/pi/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/pi/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/pi/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/pi/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/pi/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/pi/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/pi/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/adapters/pi/references/verify-state.md +98 -0
- package/adapters/pi/scripts/forge-bootstrap.py +23 -2
- package/adapters/pi/scripts/forge-root.sh +104 -21
- package/adapters/pi/scripts/forge-session.py +620 -6973
- package/adapters/pi/scripts/forge_session/__init__.py +11 -0
- package/adapters/pi/scripts/forge_session/_common.py +1566 -0
- package/adapters/pi/scripts/forge_session/_doctor_util.py +218 -0
- package/adapters/pi/scripts/forge_session/cli.py +1067 -0
- package/adapters/pi/scripts/forge_session/decisions.py +355 -0
- package/adapters/pi/scripts/forge_session/discover.py +472 -0
- package/adapters/pi/scripts/forge_session/doctor.py +2144 -0
- package/adapters/pi/scripts/forge_session/exit.py +1081 -0
- package/adapters/pi/scripts/forge_session/outcomes.py +574 -0
- package/adapters/pi/scripts/forge_session/routes.py +1162 -0
- package/adapters/pi/scripts/forge_session/state.py +1479 -0
- package/adapters/pi/scripts/forge_session/topology.py +443 -0
- package/adapters/pi/skills/forge/SKILL.md +9 -8
- package/adapters/pi/skills/forge/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/skills/forge-0-epic/SKILL.md +9 -8
- package/adapters/pi/skills/forge-0-epic/references/edit-mode.md +9 -5
- package/adapters/pi/skills/forge-0-epic/references/epic-manifest-subcommands.md +6 -1
- package/adapters/pi/skills/forge-0-epic/references/portable-root.md +20 -16
- package/adapters/pi/skills/forge-0-epic/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge-0-epic/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/skills/forge-1-prd/SKILL.md +9 -8
- package/adapters/pi/skills/forge-1-prd/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge-1-prd/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/skills/forge-2-tech/SKILL.md +8 -7
- package/adapters/pi/skills/forge-2-tech/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge-2-tech/references/stack-resolution.md +1 -1
- package/adapters/pi/skills/forge-2-tech/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/skills/forge-3-specs/SKILL.md +7 -6
- package/adapters/pi/skills/forge-3-specs/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge-3-specs/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/skills/forge-4-backlog/SKILL.md +18 -9
- package/adapters/pi/skills/forge-4-backlog/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge-4-backlog/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/skills/forge-4-backlog/references/verify-state.md +98 -0
- package/adapters/pi/skills/forge-5-loop/SKILL.md +38 -42
- package/adapters/pi/skills/forge-5-loop/references/agent-selection.md +4 -0
- package/adapters/pi/skills/forge-5-loop/references/preflight-and-self-heal.md +183 -0
- package/adapters/pi/skills/forge-5-loop/references/recovery-procedure.md +10 -0
- package/adapters/pi/skills/forge-5-loop/references/runner-contract.md +15 -5
- package/adapters/pi/skills/forge-5-loop/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge-5-loop/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/skills/forge-5-loop/references/verify-state.md +98 -0
- package/adapters/pi/skills/forge-6-docs/SKILL.md +23 -15
- package/adapters/pi/skills/forge-6-docs/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge-6-docs/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/skills/forge-6-docs/references/verify-state.md +98 -0
- package/adapters/pi/skills/forge-bootstrap/SKILL.md +20 -11
- package/adapters/pi/skills/forge-bootstrap/references/shared-conventions.md +572 -0
- package/adapters/pi/skills/forge-fix/SKILL.md +17 -19
- package/adapters/pi/skills/forge-fix/references/select-outcome.md +138 -0
- package/adapters/pi/skills/forge-fix/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge-fix/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/skills/forge-guide/SKILL.md +79 -1
- package/adapters/pi/skills/forge-guide/references/forge-config-schema.json +3 -3
- package/adapters/pi/skills/forge-guide/references/preflight-and-self-heal.md +183 -0
- package/adapters/pi/skills/forge-guide/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge-guide/references/stack-resolution.md +1 -1
- package/adapters/pi/skills/forge-init/SKILL.md +59 -8
- package/adapters/pi/skills/forge-init/references/preflight-and-self-heal.md +183 -0
- package/adapters/pi/skills/forge-init/references/shared-conventions.md +572 -0
- package/adapters/pi/skills/forge-verify/SKILL.md +17 -19
- package/adapters/pi/skills/forge-verify/references/findings-template.md +1 -1
- package/adapters/pi/skills/forge-verify/references/select-outcome.md +138 -0
- package/adapters/pi/skills/forge-verify/references/shared-conventions.md +175 -22
- package/adapters/pi/skills/forge-verify/references/stage-exit-protocol.md +19 -4
- package/adapters/pi/skills/forge-verify/references/verification-checklists/epic.md +1 -1
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/MEMORY.md +35 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_absence_claims.md +36 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_carveout_sibling_semantics.md +34 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_deviation_judgment.md +36 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_enum_vocabulary_ripple.md +43 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_extend_the_existing.md +31 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_guard_substitution_detection.md +34 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_mechanical_rewrite_damage.md +30 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_postfix_reverify.md +82 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_proximity_window_guards.md +26 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_scratch_root_probes.md +40 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_self_referential_control.md +26 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_sibling_docstring_sweep.md +28 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_spec_literals_are_claims.md +45 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_stale_counts_after_split.md +36 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_test_count_units.md +20 -0
- package/adapters/pi/skills/forge-verify/references/verifier-patterns/pattern_vacuous_self_reading_tests.md +32 -0
- package/dist/manifest.d.ts +1 -1
- package/dist/rauf.d.ts +3 -3
- package/dist/rauf.js +2 -2
- package/dist/source.d.ts +5 -2
- package/dist/source.js +5 -2
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,1081 @@
|
|
|
1
|
+
"""The ``stage-exit`` verb — the Scripted Stage Exit (#279).
|
|
2
|
+
|
|
3
|
+
Extracted verbatim from the ``forge-session.py`` monolith (issue #279, item 007).
|
|
4
|
+
``stage_exit`` is the pipeline's largest verb; its routing engine lives in
|
|
5
|
+
:mod:`forge_session.routes`, its outcome logic in :mod:`forge_session.outcomes`,
|
|
6
|
+
and its write helpers in :mod:`forge_session.state`. This module imports those and
|
|
7
|
+
the shared primitives in :mod:`forge_session._common`; it never imports the shim,
|
|
8
|
+
so there is no circular import. The CLI contract — flags, branch routing, exit
|
|
9
|
+
codes, and the full JSON payload shape — is FROZEN.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Final, get_args
|
|
18
|
+
|
|
19
|
+
from forge_session._common import (
|
|
20
|
+
EPIC_MEMBER_FALLBACK_WARNING,
|
|
21
|
+
EPIC_STATE_FILENAME,
|
|
22
|
+
EXIT_HOSTS,
|
|
23
|
+
EXIT_OUTCOMES,
|
|
24
|
+
EXIT_STAGES,
|
|
25
|
+
INVALID_AUTO_VERIFY_KEY_WARNING,
|
|
26
|
+
MANIFEST_FILENAME,
|
|
27
|
+
NEXT_STEPS_SENTINEL,
|
|
28
|
+
PIPELINE_STATE_FILENAME,
|
|
29
|
+
PRODUCTION_STAGES,
|
|
30
|
+
ProductionStage,
|
|
31
|
+
SAFE_NAME_RE,
|
|
32
|
+
STAGE_NOUN,
|
|
33
|
+
StageExitPayload,
|
|
34
|
+
UsageError,
|
|
35
|
+
VERIFY_MODE_TO_STAGE,
|
|
36
|
+
VERIFY_TOKEN_BY_STAGE,
|
|
37
|
+
ExitOwner,
|
|
38
|
+
VerifyCapability,
|
|
39
|
+
_EXIT_VERIFY_TOKEN,
|
|
40
|
+
_git_output,
|
|
41
|
+
load_effective_config,
|
|
42
|
+
_read_state,
|
|
43
|
+
_resolve_feature_dir,
|
|
44
|
+
_stage_version,
|
|
45
|
+
_verify_entry,
|
|
46
|
+
auto_verify_for,
|
|
47
|
+
invalid_auto_verify_keys,
|
|
48
|
+
next_stage,
|
|
49
|
+
pending_verify,
|
|
50
|
+
)
|
|
51
|
+
from forge_session.outcomes import _classify_verify_entry, _verify_state_for
|
|
52
|
+
from forge_session.routes import (
|
|
53
|
+
_branch_route,
|
|
54
|
+
_debt_metadata_warnings,
|
|
55
|
+
_docs_route,
|
|
56
|
+
_epic_terminal_state,
|
|
57
|
+
_host_command,
|
|
58
|
+
_loop_route,
|
|
59
|
+
_next_steps_block,
|
|
60
|
+
_production_stage_of,
|
|
61
|
+
_promote_reconcile,
|
|
62
|
+
_render_status,
|
|
63
|
+
_schedule_auto_verify_debt,
|
|
64
|
+
)
|
|
65
|
+
from forge_session.state import _assert_safe_name
|
|
66
|
+
|
|
67
|
+
#: The seven production stages as a ROUTING domain. Deliberately not
|
|
68
|
+
#: ``PRODUCTION_STAGES``, which is the six-stage member walk that excludes
|
|
69
|
+
#: ``forge-0-epic``; derived from the shared ``ProductionStage`` alias so the two
|
|
70
|
+
#: cannot drift.
|
|
71
|
+
_EXIT_PRODUCTION_STAGES: Final[tuple[str, ...]] = get_args(ProductionStage)
|
|
72
|
+
#: The two direct branch skills — every exit stage that is not a production stage.
|
|
73
|
+
#: Derived, so adding a branch skill to ``ExitStage`` lands here automatically.
|
|
74
|
+
_BRANCH_STAGES: Final[tuple[str, ...]] = tuple(
|
|
75
|
+
stage for stage in EXIT_STAGES if stage not in _EXIT_PRODUCTION_STAGES
|
|
76
|
+
)
|
|
77
|
+
#: Inverse of ``VERIFY_MODE_TO_STAGE``. The mapping is injective, so the inverse is
|
|
78
|
+
#: total over its values; stages with no mode (``forge-6-docs``) are simply absent.
|
|
79
|
+
_STAGE_TO_VERIFY_MODE: Final[dict[str, str]] = {
|
|
80
|
+
stage: mode for mode, stage in VERIFY_MODE_TO_STAGE.items()
|
|
81
|
+
}
|
|
82
|
+
#: The stage each exit hands off to when pipeline state cannot say better. Also the
|
|
83
|
+
#: production-successor table a branch exit walks from its RESOLVED SERVED stage:
|
|
84
|
+
#: ``forge-6-docs`` is absent because the pipeline ends there — a docs exit routes to
|
|
85
|
+
#: a completion action, never to a nonexistent stage 7.
|
|
86
|
+
_EXIT_NEXT_STAGE: Final[dict[str, str]] = {
|
|
87
|
+
"forge-0-epic": "forge-1-prd",
|
|
88
|
+
"forge-1-prd": "forge-2-tech",
|
|
89
|
+
"forge-2-tech": "forge-3-specs",
|
|
90
|
+
"forge-3-specs": "forge-4-backlog",
|
|
91
|
+
"forge-4-backlog": "forge-5-loop",
|
|
92
|
+
"forge-5-loop": "forge-6-docs",
|
|
93
|
+
}
|
|
94
|
+
def _epic_verify_context(specs_dir: Path, epic_name: str) -> tuple[dict, int | None]:
|
|
95
|
+
"""Read an epic's verification entry and manifest revision — tolerantly.
|
|
96
|
+
|
|
97
|
+
An epic's verification state lives in ``{specsDir}/{epic}/.epic-state.json``
|
|
98
|
+
and its artifact revision is the sibling manifest's ``revision``. Neither ever
|
|
99
|
+
comes from a member ``.pipeline-state.json``, and a member production-stage
|
|
100
|
+
``version`` is never the epic's revision (REQ-SEC-01). This
|
|
101
|
+
is the READ half: it degrades to ``({}, None)`` on anything missing or
|
|
102
|
+
malformed, matching stage-exit's "never crash a stage closing" posture. The
|
|
103
|
+
strict, fail-closed resolution lives on the WRITE path
|
|
104
|
+
(``_load_epic_state_for_write``).
|
|
105
|
+
|
|
106
|
+
A legacy manifest with no ``revision`` reads as logical ``1``, matching
|
|
107
|
+
``epic-manifest.py::load_manifest``; its bytes are not rewritten
|
|
108
|
+
(REQ-DEBT-06).
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
specs_dir: The configured specs directory.
|
|
112
|
+
epic_name: The epic — what ``--feature`` carries on an epic-scoped exit.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
``(verify_entry, revision)``; ``revision`` is None when the manifest is
|
|
116
|
+
missing or its revision unusable.
|
|
117
|
+
"""
|
|
118
|
+
epic_dir = specs_dir / epic_name
|
|
119
|
+
revision: int | None = None
|
|
120
|
+
manifest = _read_state(epic_dir / MANIFEST_FILENAME)
|
|
121
|
+
if manifest:
|
|
122
|
+
raw = manifest.get("revision", 1)
|
|
123
|
+
if isinstance(raw, int) and not isinstance(raw, bool) and raw >= 1:
|
|
124
|
+
revision = raw
|
|
125
|
+
entry = _verify_entry(_read_state(epic_dir / EPIC_STATE_FILENAME), "forge-verify-epic")
|
|
126
|
+
return entry, revision
|
|
127
|
+
def _same_named_candidates(specs_dir: Path, epic: str, member: str) -> list[Path]:
|
|
128
|
+
"""Directories OTHER than ``{specsDir}/{epic}/{member}`` carrying that name's state.
|
|
129
|
+
|
|
130
|
+
Read-only, and used only to tell "no such feature anywhere" apart from "that
|
|
131
|
+
name belongs to someone else" when the selected epic does not contain the
|
|
132
|
+
member. Sorted, so the ambiguity error it feeds is deterministic.
|
|
133
|
+
"""
|
|
134
|
+
contained = specs_dir / epic / member
|
|
135
|
+
out: list[Path] = []
|
|
136
|
+
flat = specs_dir / member
|
|
137
|
+
if flat != contained and (flat / PIPELINE_STATE_FILENAME).is_file():
|
|
138
|
+
out.append(flat)
|
|
139
|
+
if specs_dir.is_dir():
|
|
140
|
+
out.extend(
|
|
141
|
+
sorted(
|
|
142
|
+
p
|
|
143
|
+
for p in specs_dir.glob(f"*/{member}")
|
|
144
|
+
if p != contained and (p / PIPELINE_STATE_FILENAME).is_file()
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
return out
|
|
148
|
+
def _epic_member_state(specs_dir: Path, epic: str, member: str) -> tuple[dict, str | None]:
|
|
149
|
+
"""Resolve ONE epic member's live pipeline state for edit-mode routing.
|
|
150
|
+
|
|
151
|
+
Identity containment comes first: the member is read from
|
|
152
|
+
``{specsDir}/{epic}/{member}`` and nowhere else, so a same-named flat feature
|
|
153
|
+
or a member of a different epic can never be substituted for it (REQ-SEC-01).
|
|
154
|
+
``_assert_safe_name`` has already rejected traversal by the time this runs.
|
|
155
|
+
|
|
156
|
+
Progress is then TOLERATED rather than demanded: an absent, unreadable,
|
|
157
|
+
malformed, or foreign-epic state yields a reason instead of an exception, so
|
|
158
|
+
the caller can degrade DOWN to ``forge-1-prd`` with a named warning rather
|
|
159
|
+
than crash a stage closing or infer progress it could not read (REQ-PROD-06).
|
|
160
|
+
This is the one documented new tolerant case.
|
|
161
|
+
|
|
162
|
+
Identity itself still fails closed: a member that is not under the selected
|
|
163
|
+
epic at all and whose bare name matches more than one other candidate cannot
|
|
164
|
+
be pinned to a single feature, and guessing would route a DIFFERENT feature's
|
|
165
|
+
pipeline (REQ-REL-02).
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
specs_dir: The configured specs directory.
|
|
169
|
+
epic: The selected epic — what ``--feature`` carries on an epic exit.
|
|
170
|
+
member: The selected member (``--next-feature``), already name-checked.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
``(state, None)`` when the member's state resolved, else ``({}, reason)``
|
|
174
|
+
where ``reason`` is a member of ``EPIC_MEMBER_FALLBACK_REASONS``.
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
UsageError: The member is not under the selected epic and its bare name is
|
|
178
|
+
ambiguous across the specs tree (→ exit 2, no route guessed).
|
|
179
|
+
"""
|
|
180
|
+
member_dir = specs_dir / epic / member
|
|
181
|
+
state_path = member_dir / PIPELINE_STATE_FILENAME
|
|
182
|
+
# ``exists`` rather than ``is_file``: something occupying the state file's name
|
|
183
|
+
# that cannot be read as one is `unreadable`, not absent.
|
|
184
|
+
if not state_path.exists():
|
|
185
|
+
if member_dir.is_dir():
|
|
186
|
+
# Contained, just not started yet — the creation-mode case.
|
|
187
|
+
return {}, "missing"
|
|
188
|
+
elsewhere = _same_named_candidates(specs_dir, epic, member)
|
|
189
|
+
if len(elsewhere) > 1:
|
|
190
|
+
listed = ", ".join(str(p) for p in elsewhere)
|
|
191
|
+
raise UsageError(
|
|
192
|
+
f"ambiguous member {member!r} for epic {epic}: it is not under "
|
|
193
|
+
f"{member_dir} and {len(elsewhere)} other directories carry a state "
|
|
194
|
+
f"file for that name ({listed}) — refusing to guess which feature to "
|
|
195
|
+
f"route to. Re-run naming the epic that owns it."
|
|
196
|
+
)
|
|
197
|
+
return {}, "not a member of this epic" if elsewhere else "missing"
|
|
198
|
+
try:
|
|
199
|
+
raw = state_path.read_text(encoding="utf-8")
|
|
200
|
+
except OSError:
|
|
201
|
+
return {}, "unreadable"
|
|
202
|
+
try:
|
|
203
|
+
parsed = json.loads(raw)
|
|
204
|
+
except json.JSONDecodeError:
|
|
205
|
+
return {}, "malformed"
|
|
206
|
+
if not isinstance(parsed, dict):
|
|
207
|
+
return {}, "malformed"
|
|
208
|
+
back_pointer = parsed.get("epic")
|
|
209
|
+
if isinstance(back_pointer, str) and back_pointer != epic:
|
|
210
|
+
# The file sits under this epic but claims another one. Trusting either
|
|
211
|
+
# side would assert progress for a feature we cannot identify.
|
|
212
|
+
return {}, "not a member of this epic"
|
|
213
|
+
return parsed, None
|
|
214
|
+
def resolve_served_stage(
|
|
215
|
+
served_stage: str | None,
|
|
216
|
+
verify_mode: str | None,
|
|
217
|
+
) -> str:
|
|
218
|
+
"""Resolve one unambiguous production stage for a branch exit.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
served_stage: Explicit production stage supplied by the branch caller.
|
|
222
|
+
verify_mode: Optional authoritative mode mapped by VERIFY_MODE_TO_STAGE.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
A member of the shared ProductionStage domain.
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
UsageError: The explicit stage is invalid, mode is invalid, both inputs
|
|
229
|
+
disagree, or neither input identifies a stage.
|
|
230
|
+
"""
|
|
231
|
+
if served_stage is not None and served_stage not in _EXIT_PRODUCTION_STAGES:
|
|
232
|
+
raise UsageError(
|
|
233
|
+
f"--served-stage {served_stage!r} is not a production stage; expected "
|
|
234
|
+
f"one of {', '.join(_EXIT_PRODUCTION_STAGES)}"
|
|
235
|
+
)
|
|
236
|
+
if verify_mode is not None and verify_mode not in VERIFY_MODE_TO_STAGE:
|
|
237
|
+
raise UsageError(
|
|
238
|
+
f"--verify-mode {verify_mode!r} is not a known verify mode; expected "
|
|
239
|
+
f"one of {', '.join(VERIFY_MODE_TO_STAGE)}"
|
|
240
|
+
)
|
|
241
|
+
if served_stage is not None and verify_mode is not None:
|
|
242
|
+
mapped = VERIFY_MODE_TO_STAGE[verify_mode]
|
|
243
|
+
if mapped != served_stage:
|
|
244
|
+
# Both were supplied and they disagree. Name both flags and both
|
|
245
|
+
# resolutions — picking one silently is exactly the guess REQ-ROUTE-03
|
|
246
|
+
# forbids.
|
|
247
|
+
raise UsageError(
|
|
248
|
+
f"--served-stage {served_stage} conflicts with --verify-mode "
|
|
249
|
+
f"{verify_mode} (which maps to {mapped}); supply one, or supply "
|
|
250
|
+
"values that agree"
|
|
251
|
+
)
|
|
252
|
+
return served_stage
|
|
253
|
+
if served_stage is not None:
|
|
254
|
+
# Explicit stage takes precedence, and accepts any ProductionStage —
|
|
255
|
+
# including forge-6-docs, which no verify mode maps to.
|
|
256
|
+
return served_stage
|
|
257
|
+
if verify_mode is not None:
|
|
258
|
+
return VERIFY_MODE_TO_STAGE[verify_mode]
|
|
259
|
+
raise UsageError(
|
|
260
|
+
"forge-verify requires --served-stage or an unambiguous --verify-mode; "
|
|
261
|
+
"rerun with the production stage this verification served"
|
|
262
|
+
)
|
|
263
|
+
#: The deterministic sentence a TERMINAL epic exit renders (issue #248). Every route
|
|
264
|
+
#: fences NOTHING — a closed epic has no next pipeline command, and re-fencing the
|
|
265
|
+
#: dashboard is exactly the self-loop this text replaces — so the start-new-work
|
|
266
|
+
#: pointers are host-translated INLINE mentions, never the primary action.
|
|
267
|
+
#:
|
|
268
|
+
#: ``complete`` every member is genuinely done (the rollup says so)
|
|
269
|
+
#: ``declared`` the operator set the manifest `status` to `complete`; the counts are
|
|
270
|
+
#: stated as they really are, so the wording never claims members are
|
|
271
|
+
#: finished when they are not
|
|
272
|
+
#: ``abandoned`` the operator set the manifest `status` to `abandoned`. Checked
|
|
273
|
+
#: FIRST, ahead of the rollup: an abandoned epic whose members happen
|
|
274
|
+
#: to be complete must not be announced as completed work.
|
|
275
|
+
_EPIC_TERMINAL_TEXT: Final[dict[str, str]] = {
|
|
276
|
+
"complete": (
|
|
277
|
+
"Epic {epic} is complete — every member is done ({complete}/{total}) and "
|
|
278
|
+
"nothing is actionable, so there is no next pipeline command to run. "
|
|
279
|
+
"Optionally, you can start a new feature with `{new_feature}` or group "
|
|
280
|
+
"related work into an epic with `{new_epic}`; neither is required to finish "
|
|
281
|
+
"here. Re-open `{dashboard}` any time to inspect the epic's state."
|
|
282
|
+
),
|
|
283
|
+
"declared": (
|
|
284
|
+
"Epic {epic} is marked complete in its manifest and nothing is actionable "
|
|
285
|
+
"({complete}/{total} members complete), so there is no next pipeline command "
|
|
286
|
+
"to run. If that count is not what you expect, re-open `{dashboard}` to "
|
|
287
|
+
"inspect the epic; otherwise you can start a new feature with "
|
|
288
|
+
"`{new_feature}` or a new epic with `{new_epic}`."
|
|
289
|
+
),
|
|
290
|
+
"abandoned": (
|
|
291
|
+
"Epic {epic} is marked abandoned in its manifest, so the pipeline stops here "
|
|
292
|
+
"and nothing further runs against it — this is NOT a completion "
|
|
293
|
+
"({complete}/{total} members had been completed before it was abandoned). "
|
|
294
|
+
"Re-open `{dashboard}` to inspect or revive it, or start a new feature with "
|
|
295
|
+
"`{new_feature}`."
|
|
296
|
+
),
|
|
297
|
+
}
|
|
298
|
+
def stage_exit(
|
|
299
|
+
feature: str,
|
|
300
|
+
stage: str,
|
|
301
|
+
specs_dir: Path,
|
|
302
|
+
config_path: Path,
|
|
303
|
+
epic: str | None,
|
|
304
|
+
host: str,
|
|
305
|
+
next_feature: str | None,
|
|
306
|
+
served_stage: str | None = None,
|
|
307
|
+
verify_mode: str | None = None,
|
|
308
|
+
outcome: str | None = None,
|
|
309
|
+
owner: str | None = None,
|
|
310
|
+
verify_capability: str = "manual",
|
|
311
|
+
cause: str | None = None,
|
|
312
|
+
) -> StageExitPayload:
|
|
313
|
+
"""Compute a deterministic stage-exit payload.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
feature: Safe feature name, or epic name for an epic-scoped exit.
|
|
317
|
+
stage: One member of `EXIT_STAGES`.
|
|
318
|
+
specs_dir: Configured specs directory.
|
|
319
|
+
config_path: Path to `forge.config.json`.
|
|
320
|
+
epic: Owning epic for a nested member, otherwise None.
|
|
321
|
+
host: Command-rendering host: `claude`, `pi`, or `generic`.
|
|
322
|
+
next_feature: Explicit epic handoff member, when applicable.
|
|
323
|
+
served_stage: Production stage served by direct verify/fix.
|
|
324
|
+
verify_mode: Verify mode used to infer `served_stage` when unique.
|
|
325
|
+
outcome: Required stage-specific outcome for loop/docs/verify/fix.
|
|
326
|
+
owner: Required for verify/fix: `direct` or `nested`.
|
|
327
|
+
verify_capability: `interactive` only when both question and clean-room
|
|
328
|
+
verifier dispatch capabilities exist; otherwise `manual`. Dispatch
|
|
329
|
+
capability is permission, not tool presence: a dispatch permitted
|
|
330
|
+
only once the user has asked is still `interactive`, because the
|
|
331
|
+
`standard` gate's own prompt supplies that request.
|
|
332
|
+
cause: Pending-attribution annotation (`dependency-starvation`), valid
|
|
333
|
+
only with `--stage forge-5-loop --outcome partial` (REQ-ATTR-04).
|
|
334
|
+
It swaps the partial next-steps sentence for the starvation variant
|
|
335
|
+
and changes no routing.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
A JSON-serializable `StageExitPayload` dictionary.
|
|
339
|
+
|
|
340
|
+
Raises:
|
|
341
|
+
UsageError: Unsafe or ambiguous identity, unsupported stage/outcome,
|
|
342
|
+
missing ownership/served-stage metadata, or conflicting inference.
|
|
343
|
+
|
|
344
|
+
Directive semantics (the contract in ``references/stage-exit-protocol.md``):
|
|
345
|
+
|
|
346
|
+
- ``runInStageVerify`` — the effective auto-verify (per-stage override,
|
|
347
|
+
else global; strict-true) is on AND this stage's verify is not already
|
|
348
|
+
resolved (fresh/skipped) AND no findings report exists at the current
|
|
349
|
+
revision (that state routes to forge-fix instead — scheduling over the
|
|
350
|
+
report would delete its metadata, REQ-EXIT-04). The skill then dispatches
|
|
351
|
+
the clean-room verify in-session (principle #2: verify before the clear).
|
|
352
|
+
- ``autoVerifyDebtRecorded`` — the ``auto-verify-pending`` marker for this
|
|
353
|
+
stage is durably on disk. Written BEFORE this payload exists, so
|
|
354
|
+
a failed write raises ``UsageError`` and returns no payload at all and
|
|
355
|
+
``runInStageVerify: True`` with ``autoVerifyDebtRecorded: False`` is
|
|
356
|
+
unreachable. Scheduling is idempotent by target revision: a repeat at the
|
|
357
|
+
same revision touches neither ``scheduledAt`` nor top-level ``updatedAt``.
|
|
358
|
+
- ``autoFixEligible`` — ``autoFix`` is strict-true AND the in-stage verify
|
|
359
|
+
runs AND the working tree is clean. Findings-level preconditions (zero
|
|
360
|
+
unresolved decisions) remain the skill's runtime check. Its clean-tree
|
|
361
|
+
snapshot is taken BEFORE the debt write, so that sanctioned control-plane
|
|
362
|
+
mutation cannot dirty its own precondition.
|
|
363
|
+
- ``verifyState``/``warnings``/``cleanTree`` — all PRE-mutation snapshots:
|
|
364
|
+
they describe the state the routing decision was made from, which is why a
|
|
365
|
+
first exit reports ``never`` while the debt it just recorded reads
|
|
366
|
+
``auto-pending`` on the next one. Only ``autoVerifyDebtRecorded`` reports
|
|
367
|
+
the write.
|
|
368
|
+
- ``verifyGate`` — ``none`` when verify is resolved (including a tokenless
|
|
369
|
+
stage), the in-stage run covers it, or a live findings report routes to
|
|
370
|
+
forge-fix (the fenced fix IS the one action — a "verify now?" prompt
|
|
371
|
+
beside it would be a second, contradictory ask); ``standard`` when
|
|
372
|
+
auto-verify is off, verification is outstanding, and the CALLER declared
|
|
373
|
+
``--verify-capability interactive``; ``manual-print`` for the same state
|
|
374
|
+
under ``manual`` (print ``verifyCommand`` instead of presenting the gate).
|
|
375
|
+
Never a function of ``--host``: capable Pi is ``standard`` and incapable
|
|
376
|
+
Claude is ``manual-print`` (REQ-EXIT-07).
|
|
377
|
+
- ``primaryCommand``/``deferredCommand`` — the verify-first pair. While
|
|
378
|
+
verification is unresolved ``primaryCommand`` is the verify command — or
|
|
379
|
+
the forge-fix command when a findings report is live at the current
|
|
380
|
+
revision — and is the ONLY fenced command; ``deferredCommand`` names the
|
|
381
|
+
production successor as unfenced conditional prose. ``nextCommand`` stays
|
|
382
|
+
compatibility/routing metadata and never overrides ``primaryCommand``
|
|
383
|
+
(REQ-EXIT-06). Both are None on the one TERMINAL exit — a finished epic
|
|
384
|
+
(see ``forge-0-epic`` below) — where the block fences nothing.
|
|
385
|
+
- ``nextStage``/``nextCommand`` — from pipeline state when it already
|
|
386
|
+
records this stage complete (first non-complete production stage), else
|
|
387
|
+
the fixed successor. ``--next-feature`` names the first actionable
|
|
388
|
+
feature for the epic handoff; without it an epic exit hands back to the
|
|
389
|
+
epic dashboard rather than naming a member it cannot resolve.
|
|
390
|
+
With it, the handoff is derived from THAT member's live state via
|
|
391
|
+
``next_stage``: a progressed member resumes where it actually is,
|
|
392
|
+
a fully complete member hands back to the epic dashboard, and a member
|
|
393
|
+
whose state cannot be resolved falls back to ``forge-1-prd`` with
|
|
394
|
+
``warnings`` entry 1 naming it.
|
|
395
|
+
- ``forge-0-epic`` — no route hands back the dashboard as the SOLE remaining
|
|
396
|
+
action (#248). An ACTIONABLE member with no next production stage owes
|
|
397
|
+
verification, so the exit routes to ``render-status``'s own answer for that
|
|
398
|
+
member (``forge-verify``/``forge-fix``, or ``forge-1-prd`` when its PRD is
|
|
399
|
+
missing) rather than to the dashboard. And a CLOSED epic exits terminally:
|
|
400
|
+
when live ``render-status`` reports nothing actionable AND the epic is
|
|
401
|
+
complete, declared complete, or abandoned (see ``_epic_terminal_state``),
|
|
402
|
+
``primaryCommand``/``nextCommand``/``nextStage`` are all None
|
|
403
|
+
and the block fences nothing. The dashboard is still the answer where it is a
|
|
404
|
+
real action rather than a re-run: a ``UsageError`` recovery, a blocking
|
|
405
|
+
reconcile, and an active epic with no members decomposed yet.
|
|
406
|
+
Previously this state handed back
|
|
407
|
+
``/feature-forge:forge-0-epic {epic}`` — the command that produced it — so the
|
|
408
|
+
operator was prompted to re-run it indefinitely. The terminal answer applies
|
|
409
|
+
only where the routing already landed on that dashboard, so verify-first
|
|
410
|
+
ordering, a live findings report, and an open epic change request all keep
|
|
411
|
+
precedence, and a ``render-status`` failure still degrades to the recoverable
|
|
412
|
+
dashboard route rather than claiming completion.
|
|
413
|
+
- ``epicReconcile`` — present only when the exiting member carries
|
|
414
|
+
``open`` ``epicChangeRequests`` (epic-backflow). ``required: true`` (any
|
|
415
|
+
``blocksCurrent: true`` request) interposes a reconcile-first exit: the
|
|
416
|
+
NEXT-STEPS primary command becomes ``/feature-forge:forge-0-epic {epic}``
|
|
417
|
+
and the normal next stage is deferred. Only non-blocking requests set
|
|
418
|
+
``reminder: true`` and append a non-blocking reminder line. Absent when
|
|
419
|
+
there are no open requests (common path) or the epic name is unresolvable.
|
|
420
|
+
- ``servedStage``/``verifyMode``/``outcome``/``owner``/``terminalOwnedBy`` —
|
|
421
|
+
branch metadata. A production exit serves only itself, so ``servedStage``
|
|
422
|
+
is None there; ``verifyStage`` is the DISTINCT value ``pending_verify``
|
|
423
|
+
returns, naming the stage outstanding verification is owed on.
|
|
424
|
+
On a branch exit the outcome table in ``_branch_route`` — not
|
|
425
|
+
verify-first ordering — supplies ``primaryCommand``: a diversion rejoins
|
|
426
|
+
the production stage it served, and every recovery/defer route carries
|
|
427
|
+
``--served-stage`` forward so the thread is never dropped (issue #176).
|
|
428
|
+
- ``forge-5-loop`` — routed by its required ``--outcome``. ``complete``
|
|
429
|
+
keeps verify-first ordering in front of the documentation/epic-member handoff,
|
|
430
|
+
which for a member is delegated to the live ``render-status`` payload rather
|
|
431
|
+
than re-derived here. The other four outcomes route to the loop resume
|
|
432
|
+
(``partial``/``deferred``) or the navigator (``blocked``/``needs-human``) and
|
|
433
|
+
suppress every downstream signal: ``nextStage``/``nextCommand`` are None,
|
|
434
|
+
``runInStageVerify`` is False, no debt is scheduled, and ``verifyGate`` is
|
|
435
|
+
``none`` — a loop still in flight has no finished implementation to verify and
|
|
436
|
+
nothing downstream may read as ready (REQ-PROD-02).
|
|
437
|
+
- ``forge-6-docs`` — the documentation terminus is decided by LIVE epic
|
|
438
|
+
state, never by the successor table: for an epic member the adjacent
|
|
439
|
+
``epic-manifest.py render-status`` supplies the next actionable member's own
|
|
440
|
+
command, and anything else routes to the epic dashboard. A ``blocked``
|
|
441
|
+
outcome routes to recovery and never claims completion. Any helper failure
|
|
442
|
+
is an actionable ``UsageError`` — exit 2 with no payload, so no guessed
|
|
443
|
+
member command and no sentinel can escape (REQ-REL-02).
|
|
444
|
+
- ``warnings`` — non-fatal advisories in the documented fixed order. Always
|
|
445
|
+
present; ``[]`` means checked-and-clean, which is not the same as absent.
|
|
446
|
+
|
|
447
|
+
Read-only and deterministic. Syntactic validation fails closed with
|
|
448
|
+
``UsageError`` (exit 2, no payload and no sentinel); everything after it
|
|
449
|
+
degrades to defaults rather than crashing a stage closing.
|
|
450
|
+
"""
|
|
451
|
+
# ---- Deterministic validation order ----------------------------------- #
|
|
452
|
+
# 1. Safe names and containment, before any strict filesystem access.
|
|
453
|
+
_assert_safe_name(feature, "--feature")
|
|
454
|
+
if epic is not None:
|
|
455
|
+
_assert_safe_name(epic, "--epic")
|
|
456
|
+
if next_feature is not None:
|
|
457
|
+
_assert_safe_name(next_feature, "--next-feature")
|
|
458
|
+
|
|
459
|
+
# 2. The stage domain itself.
|
|
460
|
+
if stage not in EXIT_STAGES:
|
|
461
|
+
raise UsageError(
|
|
462
|
+
f"unsupported --stage {stage!r}; expected one of {', '.join(EXIT_STAGES)}"
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
# 3./4. Stages 0-4 reject an outcome; loop/docs/verify/fix require their own.
|
|
466
|
+
# argparse cannot express a different enum per stage, so the domain check is here.
|
|
467
|
+
allowed_outcomes = EXIT_OUTCOMES.get(stage)
|
|
468
|
+
if allowed_outcomes is None:
|
|
469
|
+
if outcome is not None:
|
|
470
|
+
raise UsageError(
|
|
471
|
+
f"--outcome is not accepted for {stage}; its exit is state-driven "
|
|
472
|
+
"and has a single outcome"
|
|
473
|
+
)
|
|
474
|
+
elif outcome is None:
|
|
475
|
+
raise UsageError(
|
|
476
|
+
f"{stage} requires --outcome; expected one of "
|
|
477
|
+
f"{', '.join(sorted(allowed_outcomes))}"
|
|
478
|
+
)
|
|
479
|
+
elif outcome not in allowed_outcomes:
|
|
480
|
+
raise UsageError(
|
|
481
|
+
f"--outcome {outcome!r} is not valid for {stage}; expected one of "
|
|
482
|
+
f"{', '.join(sorted(allowed_outcomes))}"
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# --cause is a forge-5-loop/partial-only attribution annotation (REQ-ATTR-04).
|
|
486
|
+
# argparse `choices` already restricts the value; this restricts the combination.
|
|
487
|
+
if cause is not None and not (stage == "forge-5-loop" and outcome == "partial"):
|
|
488
|
+
raise UsageError(
|
|
489
|
+
"--cause dependency-starvation is valid only with "
|
|
490
|
+
"--stage forge-5-loop --outcome partial"
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
# 5. Ownership: required for the branch skills, rejected for stages 0-6.
|
|
494
|
+
if stage in _BRANCH_STAGES:
|
|
495
|
+
if owner is None:
|
|
496
|
+
raise UsageError(
|
|
497
|
+
f"{stage} requires --owner direct (this call prints the terminal "
|
|
498
|
+
"block) or --owner nested (an outer stage owns it)"
|
|
499
|
+
)
|
|
500
|
+
if owner not in get_args(ExitOwner):
|
|
501
|
+
raise UsageError(
|
|
502
|
+
f"--owner {owner!r} is not valid; expected direct or nested"
|
|
503
|
+
)
|
|
504
|
+
elif owner is not None:
|
|
505
|
+
raise UsageError(
|
|
506
|
+
f"--owner is not accepted for {stage}; only forge-verify and forge-fix "
|
|
507
|
+
"carry branch ownership, and stages 0-6 are always direct owners"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# 6. Host and capability, independently. A host NEVER implies a capability.
|
|
511
|
+
if host not in EXIT_HOSTS:
|
|
512
|
+
raise UsageError(
|
|
513
|
+
f"unknown --host {host!r}; expected one of {', '.join(EXIT_HOSTS)}"
|
|
514
|
+
)
|
|
515
|
+
if verify_capability not in get_args(VerifyCapability):
|
|
516
|
+
raise UsageError(
|
|
517
|
+
f"unknown --verify-capability {verify_capability!r}; expected "
|
|
518
|
+
f"{' or '.join(get_args(VerifyCapability))}"
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
# 7./8. Served stage for branch exits; branch-only flags rejected elsewhere.
|
|
522
|
+
if stage in _BRANCH_STAGES:
|
|
523
|
+
resolved_served: str | None = resolve_served_stage(served_stage, verify_mode)
|
|
524
|
+
else:
|
|
525
|
+
if served_stage is not None or verify_mode is not None:
|
|
526
|
+
raise UsageError(
|
|
527
|
+
"--served-stage and --verify-mode are branch-only; "
|
|
528
|
+
f"{stage} is a production stage and serves only itself"
|
|
529
|
+
)
|
|
530
|
+
resolved_served = None
|
|
531
|
+
if next_feature is not None and stage != "forge-0-epic":
|
|
532
|
+
raise UsageError(
|
|
533
|
+
f"--next-feature is accepted only for forge-0-epic, not {stage}"
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# A loop that did not complete has NO production successor and owes no
|
|
537
|
+
# implementation verification yet. Everything downstream is suppressed below —
|
|
538
|
+
# `nextStage`/`nextCommand`, the epic-reconcile deferred line, the in-stage
|
|
539
|
+
# verify chain, its debt write, and the verify gate — because each of them
|
|
540
|
+
# would assert that the implementation is finished enough to move on, which is
|
|
541
|
+
# exactly the readiness claim REQ-PROD-02 forbids.
|
|
542
|
+
loop_incomplete = stage == "forge-5-loop" and outcome != "complete"
|
|
543
|
+
|
|
544
|
+
config = load_effective_config(config_path)
|
|
545
|
+
invalid_keys = invalid_auto_verify_keys(config)
|
|
546
|
+
for key in invalid_keys: # already sorted; advisory, never fatal
|
|
547
|
+
print(
|
|
548
|
+
INVALID_AUTO_VERIFY_KEY_WARNING.format(
|
|
549
|
+
key=key, valid=", ".join(VERIFY_TOKEN_BY_STAGE)
|
|
550
|
+
),
|
|
551
|
+
file=sys.stderr,
|
|
552
|
+
)
|
|
553
|
+
feature_dir = _resolve_feature_dir(specs_dir, feature, epic)
|
|
554
|
+
state = _read_state(feature_dir / PIPELINE_STATE_FILENAME)
|
|
555
|
+
|
|
556
|
+
# Epic edit-mode: resolve the SELECTED member's live progress here, before
|
|
557
|
+
# the scheduling boundary below, so an ambiguous identity exits 2 without having
|
|
558
|
+
# mutated anything. `--next-feature` is accepted only for `forge-0-epic` (step 1),
|
|
559
|
+
# so this is exactly the epic edit-mode selection. Read-only: no candidate state
|
|
560
|
+
# file is opened for writing on this path.
|
|
561
|
+
member_state: dict = {}
|
|
562
|
+
member_reason: str | None = None
|
|
563
|
+
if next_feature is not None:
|
|
564
|
+
member_state, member_reason = _epic_member_state(specs_dir, feature, next_feature)
|
|
565
|
+
|
|
566
|
+
# The clean-tree snapshot is taken HERE, before the sanctioned debt write
|
|
567
|
+
# below, so the pending marker cannot dirty its own precondition.
|
|
568
|
+
# Every other directive is likewise a pre-mutation snapshot; only
|
|
569
|
+
# `autoVerifyDebtRecorded` describes what the write did.
|
|
570
|
+
git_repo = _git_output(["rev-parse", "--git-dir"]) is not None
|
|
571
|
+
clean_tree: bool | None = None
|
|
572
|
+
if git_repo:
|
|
573
|
+
porcelain = _git_output(["status", "--porcelain"])
|
|
574
|
+
clean_tree = porcelain is None or porcelain == ""
|
|
575
|
+
|
|
576
|
+
# A branch exit routes from the production stage it SERVED, never from itself:
|
|
577
|
+
# `forge-verify` has no artifact, no verify token, and no successor of its own.
|
|
578
|
+
# For a production exit the two are the same stage, so stages 0-4 are unchanged.
|
|
579
|
+
route_stage = resolved_served if resolved_served is not None else stage
|
|
580
|
+
|
|
581
|
+
# Verification context for the routed stage. An epic-scoped route reads
|
|
582
|
+
# `.epic-state.json` and the manifest revision DIRECTLY — never
|
|
583
|
+
# `_resolve_feature_dir`, never a member stage version (REQ-SEC-01).
|
|
584
|
+
verify_token = _EXIT_VERIFY_TOKEN.get(route_stage)
|
|
585
|
+
verify_key = f"forge-verify-{verify_token}" if verify_token else None
|
|
586
|
+
if route_stage == "forge-0-epic":
|
|
587
|
+
# EPIC-scoped: the entry and the revision come from `.epic-state.json` and the
|
|
588
|
+
# manifest, never from a member stage version, so this branch cannot route
|
|
589
|
+
# through the stage-scoped helper below. `forge-0-epic` always has a token.
|
|
590
|
+
verify_entry, verify_current = _epic_verify_context(specs_dir, feature)
|
|
591
|
+
verify_label = _classify_verify_entry(verify_entry, verify_key, verify_current)
|
|
592
|
+
else:
|
|
593
|
+
verify_entry = _verify_entry(state, verify_key) if verify_key else {}
|
|
594
|
+
verify_current = _stage_version(state, route_stage) if verify_key else None
|
|
595
|
+
# Classify through `_verify_state_for`, the designated stage-exit routing
|
|
596
|
+
# classifier, rather than re-deriving its two steps inline. The inline copy
|
|
597
|
+
# left `_verify_state_for` with no runtime
|
|
598
|
+
# caller, so `tests/test_auto_verify.py` could pin routing labels through a
|
|
599
|
+
# function the CLI never executed. It repeats the `_EXIT_VERIFY_TOKEN` lookup
|
|
600
|
+
# and `_classify_verify_entry` call above and returns "none" for a tokenless
|
|
601
|
+
# stage (forge-6-docs), where there is no verification to owe.
|
|
602
|
+
verify_label = _verify_state_for(state, route_stage)
|
|
603
|
+
# ``none`` is resolved for routing purposes: no verify command is promoted.
|
|
604
|
+
resolved = verify_label in ("fresh", "skipped", "none")
|
|
605
|
+
# A findings report AT THE CURRENT revision is live evidence, not owed debt.
|
|
606
|
+
# Scheduling over it would REPLACE the entry (`_verify_result_entry` builds
|
|
607
|
+
# replacements, not patches) and delete `findingsFile`/`findingsCount` —
|
|
608
|
+
# the same REQ-EXIT-04 clobber the branch-exit guard below forbids, reached
|
|
609
|
+
# instead from a production re-exit. The outstanding obligation is the FIX,
|
|
610
|
+
# so this exit routes to forge-fix and never re-schedules; a report left
|
|
611
|
+
# behind by a since-revised artifact is superseded normally.
|
|
612
|
+
reported_version = verify_entry.get("verifiedStageVersion")
|
|
613
|
+
live_findings_report = (
|
|
614
|
+
verify_label == "failing"
|
|
615
|
+
and isinstance(reported_version, int)
|
|
616
|
+
and not isinstance(reported_version, bool)
|
|
617
|
+
and verify_current is not None
|
|
618
|
+
and reported_version == verify_current
|
|
619
|
+
)
|
|
620
|
+
effective_auto_verify = auto_verify_for(config, route_stage)
|
|
621
|
+
# A BRANCH exit is already inside the verification diversion, so it never owes
|
|
622
|
+
# an in-stage verify chain and never schedules debt. Without this a
|
|
623
|
+
# `forge-verify --outcome findings` exit would both direct a re-dispatch of
|
|
624
|
+
# itself and overwrite the `findings-reported` entry it had just written with
|
|
625
|
+
# a fresh `auto-verify-pending` marker, losing the report (REQ-EXIT-04).
|
|
626
|
+
# Branch rejoin routing belongs to the outcome tables, not this boundary.
|
|
627
|
+
run_in_stage = (
|
|
628
|
+
effective_auto_verify
|
|
629
|
+
and not resolved
|
|
630
|
+
and not live_findings_report
|
|
631
|
+
and stage not in _BRANCH_STAGES
|
|
632
|
+
and not loop_incomplete
|
|
633
|
+
)
|
|
634
|
+
auto_fix_eligible = (
|
|
635
|
+
config.get("autoFix") is True and run_in_stage and clean_tree is True
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
# ---- Scheduling boundary ---------------------------------------------- #
|
|
639
|
+
# The debt lands BEFORE the payload exists, so a crash between here and the
|
|
640
|
+
# dispatch leaves durable state exposing the obligation, and a failed write
|
|
641
|
+
# raises UsageError with no payload at all — `runInStageVerify: True` with
|
|
642
|
+
# `autoVerifyDebtRecorded: False` is therefore unreachable.
|
|
643
|
+
auto_verify_debt_recorded = False
|
|
644
|
+
if run_in_stage and verify_key is not None:
|
|
645
|
+
_schedule_auto_verify_debt(specs_dir, feature, epic, route_stage, verify_key)
|
|
646
|
+
auto_verify_debt_recorded = True
|
|
647
|
+
# Priority table. The gate is a pure function of the verification state
|
|
648
|
+
# and the caller's declared capability: `--host` selects command syntax and
|
|
649
|
+
# fresh-session wording ONLY. A capable Pi session gets `standard`; an
|
|
650
|
+
# incapable Claude session gets `manual-print` (REQ-EXIT-07). Whether the
|
|
651
|
+
# caller needed user consent to dispatch is the CALLER's determination
|
|
652
|
+
# and is invisible here — a consent-required caller sends
|
|
653
|
+
# `interactive` and gets `standard`, which is the intended path.
|
|
654
|
+
#
|
|
655
|
+
# A BRANCH exit is already inside the diversion and its outcome table
|
|
656
|
+
# names the one action to take, so there is nothing left to gate: offering
|
|
657
|
+
# "verify now?" beside a fenced fix command would be a second, contradictory
|
|
658
|
+
# ask. The table's `verify` routes ARE the verification prompt.
|
|
659
|
+
#
|
|
660
|
+
# A non-complete loop outcome is gateless for the same reason it never
|
|
661
|
+
# schedules debt: there is no finished implementation to verify, so offering
|
|
662
|
+
# "verify now?" beside a fenced loop resume would ask for a verification of
|
|
663
|
+
# work that is still in flight.
|
|
664
|
+
# A live findings report is likewise gateless: the fenced forge-fix route IS
|
|
665
|
+
# the one action, and a "verify now?" prompt beside it would be a second,
|
|
666
|
+
# contradictory ask for a verification that already ran at this revision.
|
|
667
|
+
if (
|
|
668
|
+
resolved
|
|
669
|
+
or run_in_stage
|
|
670
|
+
or live_findings_report
|
|
671
|
+
or stage in _BRANCH_STAGES
|
|
672
|
+
or loop_incomplete
|
|
673
|
+
):
|
|
674
|
+
verify_gate = "none"
|
|
675
|
+
elif verify_capability == "interactive":
|
|
676
|
+
verify_gate = "standard"
|
|
677
|
+
else:
|
|
678
|
+
verify_gate = "manual-print"
|
|
679
|
+
|
|
680
|
+
next_stage_id = _EXIT_NEXT_STAGE.get(route_stage)
|
|
681
|
+
state_next = next_stage(state)
|
|
682
|
+
if (
|
|
683
|
+
route_stage in PRODUCTION_STAGES
|
|
684
|
+
and state_next is not None
|
|
685
|
+
and PRODUCTION_STAGES.index(state_next) > PRODUCTION_STAGES.index(route_stage)
|
|
686
|
+
):
|
|
687
|
+
# State records this stage complete AND its walk lands beyond it —
|
|
688
|
+
# trust it (it skips stages already completed out of order). A missing
|
|
689
|
+
# or behind-the-stage walk (state not yet flushed, corrupt file) falls
|
|
690
|
+
# back to the fixed successor, never to an earlier stage.
|
|
691
|
+
next_stage_id = state_next
|
|
692
|
+
# Keyed off the ROUTED stage, so a branch exit that served the epic decomposition
|
|
693
|
+
# hands off the same way the epic's own exit does. Identical to the previous
|
|
694
|
+
# behavior for every production exit, where `route_stage is stage`.
|
|
695
|
+
# Set when live epic state says this epic is FINISHED (#248). Carried to the single
|
|
696
|
+
# application point after the primary chain, so verify-first ordering, a live
|
|
697
|
+
# findings report, and a blocking reconcile all keep their precedence — a terminal
|
|
698
|
+
# epic is only ever allowed to replace a dashboard self-loop, never a real action.
|
|
699
|
+
epic_terminal: tuple[str, dict] | None = None
|
|
700
|
+
if route_stage == "forge-0-epic" and next_feature is None:
|
|
701
|
+
# A branch exit (verify/fix) that served forge-0-epic cannot carry
|
|
702
|
+
# --next-feature (the CLI rejects it for non-epic stages), so the member
|
|
703
|
+
# must be resolved HERE from live epic state. Creation-mode exits pass
|
|
704
|
+
# --next-feature and skip this block entirely (#230).
|
|
705
|
+
try:
|
|
706
|
+
status = _render_status(specs_dir, feature)
|
|
707
|
+
actionable = status["actionable"]
|
|
708
|
+
epic_terminal = _epic_terminal_state(status, feature)
|
|
709
|
+
if actionable:
|
|
710
|
+
resolved_member = actionable[0]
|
|
711
|
+
ms, mr = _epic_member_state(specs_dir, feature, resolved_member)
|
|
712
|
+
if mr is not None:
|
|
713
|
+
next_stage_id = "forge-1-prd"
|
|
714
|
+
next_command = f"/feature-forge:forge-1-prd {resolved_member}"
|
|
715
|
+
else:
|
|
716
|
+
member_next = next_stage(ms)
|
|
717
|
+
if member_next is None:
|
|
718
|
+
# An ACTIONABLE member with no next production stage has
|
|
719
|
+
# finished all six but is not complete for orchestration —
|
|
720
|
+
# it owes verification (`auto-verify-pending`) or carries an
|
|
721
|
+
# unapplied report (`findings-reported`). Handing back the
|
|
722
|
+
# dashboard here was the #248 self-loop on a not-quite-complete
|
|
723
|
+
# epic: the dashboard's own exit reproduces this state and
|
|
724
|
+
# re-fences itself. `render-status` already resolved the right
|
|
725
|
+
# answer for exactly this member — `forge-verify` or
|
|
726
|
+
# `forge-fix`, per its own debt rules — so it is used rather
|
|
727
|
+
# than re-derived here (derivation belongs to epic-manifest.py).
|
|
728
|
+
#
|
|
729
|
+
# It is USUALLY a branch command, but not always: the helper
|
|
730
|
+
# answers `forge-1-prd` for a member whose `PRD.md` is missing
|
|
731
|
+
# from disk, ahead of its debt fork. So `nextStage` is read back
|
|
732
|
+
# OUT of the command it chose rather than assumed None —
|
|
733
|
+
# reporting a production command with `nextStage: null` would
|
|
734
|
+
# break this payload's own contract (see `nextCommand` above).
|
|
735
|
+
next_command = (
|
|
736
|
+
status["nextCommand"]
|
|
737
|
+
or f"/feature-forge:forge-0-epic {feature}"
|
|
738
|
+
)
|
|
739
|
+
next_stage_id = _production_stage_of(next_command)
|
|
740
|
+
else:
|
|
741
|
+
next_stage_id = member_next
|
|
742
|
+
next_command = f"/feature-forge:{member_next} {resolved_member}"
|
|
743
|
+
else:
|
|
744
|
+
next_stage_id = None
|
|
745
|
+
next_command = f"/feature-forge:forge-0-epic {feature}"
|
|
746
|
+
except UsageError:
|
|
747
|
+
next_stage_id = None
|
|
748
|
+
next_command = f"/feature-forge:forge-0-epic {feature}"
|
|
749
|
+
else:
|
|
750
|
+
next_arg = next_feature or feature
|
|
751
|
+
next_command = (
|
|
752
|
+
f"/feature-forge:{next_stage_id} {next_arg}" if next_stage_id else None
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
# ---- Epic edit-mode live member routing (issue #175) -------------------- #
|
|
756
|
+
# The fixed `forge-0-epic -> forge-1-prd` successor above is a CREATION-mode
|
|
757
|
+
# answer: a member that has just been decomposed has no completed production
|
|
758
|
+
# stage, so PRD is right. In edit mode the selected member may be anywhere in
|
|
759
|
+
# the pipeline, and sending it back to PRD would ask for work already done.
|
|
760
|
+
# The live position comes from the member's own state via `next_stage`, never
|
|
761
|
+
# from the epic's state, the successor table, or conversational context.
|
|
762
|
+
epic_member_warning: str | None = None
|
|
763
|
+
if next_feature is not None:
|
|
764
|
+
if member_reason is not None:
|
|
765
|
+
# Degrade DOWN, never up: an unreadable member cannot be assumed to
|
|
766
|
+
# have progressed, and inferring a later stage would fabricate the
|
|
767
|
+
# very progress this exit failed to read (REQ-PROD-06).
|
|
768
|
+
epic_member_warning = EPIC_MEMBER_FALLBACK_WARNING.format(
|
|
769
|
+
member=next_feature, epic=feature, reason=member_reason
|
|
770
|
+
)
|
|
771
|
+
next_stage_id = "forge-1-prd"
|
|
772
|
+
next_command = f"/feature-forge:forge-1-prd {next_feature}"
|
|
773
|
+
else:
|
|
774
|
+
member_next = next_stage(member_state)
|
|
775
|
+
if member_next is None:
|
|
776
|
+
# Every production stage is complete. There is no stage 7 to
|
|
777
|
+
# fabricate, so the handoff is the epic dashboard itself — unless the
|
|
778
|
+
# EPIC is finished too, in which case that hand-back is the #248
|
|
779
|
+
# self-loop and the exit goes terminal instead. Only this path can
|
|
780
|
+
# reach the dashboard, so it is the only one that pays for the extra
|
|
781
|
+
# render-status read; an unreadable graph degrades to the hand-back
|
|
782
|
+
# rather than converting a stage closing into a failure.
|
|
783
|
+
next_stage_id = None
|
|
784
|
+
next_command = f"/feature-forge:forge-0-epic {feature}"
|
|
785
|
+
try:
|
|
786
|
+
epic_terminal = _epic_terminal_state(
|
|
787
|
+
_render_status(specs_dir, feature), feature
|
|
788
|
+
)
|
|
789
|
+
except UsageError:
|
|
790
|
+
epic_terminal = None
|
|
791
|
+
else:
|
|
792
|
+
next_stage_id = member_next
|
|
793
|
+
next_command = f"/feature-forge:{member_next} {next_feature}"
|
|
794
|
+
|
|
795
|
+
if loop_incomplete:
|
|
796
|
+
# The pipeline has no next production stage from here, exactly as it has
|
|
797
|
+
# none after `forge-6-docs`. Cleared BEFORE the epic-backflow block below,
|
|
798
|
+
# so a blocking reconcile's `deferred` line cannot re-introduce
|
|
799
|
+
# `/feature-forge:forge-6-docs` as text the loop resume did not earn.
|
|
800
|
+
next_stage_id = None
|
|
801
|
+
next_command = None
|
|
802
|
+
|
|
803
|
+
# Epic backflow routing: an exiting member may carry epic-level change requests
|
|
804
|
+
# (recorded by forge-1-prd/forge-2-tech). A `blocksCurrent: true` request means
|
|
805
|
+
# the current feature's next stage would build on a soon-to-change decomposition,
|
|
806
|
+
# so the exit interposes a reconcile-first step; only-`false` requests append a
|
|
807
|
+
# non-blocking reminder. Read-only; the common path (no open requests) is a no-op.
|
|
808
|
+
# The epic name comes from the `--epic` arg or the state's `epic` back-pointer.
|
|
809
|
+
epic_reconcile: dict | None = None
|
|
810
|
+
epic_name = epic or state.get("epic")
|
|
811
|
+
# The epic a documentation or completed-loop exit routes against:
|
|
812
|
+
# the explicit `--epic`, else the state's back-pointer. A back-pointer is
|
|
813
|
+
# untrusted on-disk data, so it is name-checked here rather than reaching the
|
|
814
|
+
# helper's argv (REQ-SEC-01); an unusable value degrades to the standalone route
|
|
815
|
+
# rather than crashing a stage closing. `--epic` itself was already validated in
|
|
816
|
+
# step 1.
|
|
817
|
+
route_epic = (
|
|
818
|
+
epic_name if isinstance(epic_name, str) and SAFE_NAME_RE.match(epic_name) else None
|
|
819
|
+
)
|
|
820
|
+
open_requests = [
|
|
821
|
+
r
|
|
822
|
+
for r in state.get("epicChangeRequests", [])
|
|
823
|
+
if isinstance(r, dict) and r.get("status") == "open"
|
|
824
|
+
]
|
|
825
|
+
if open_requests and epic_name:
|
|
826
|
+
reconcile_command = f"/feature-forge:forge-0-epic {epic_name}"
|
|
827
|
+
blocking = [r for r in open_requests if r.get("blocksCurrent") is True]
|
|
828
|
+
if blocking:
|
|
829
|
+
epic_reconcile = {
|
|
830
|
+
"required": True,
|
|
831
|
+
"command": reconcile_command,
|
|
832
|
+
"count": len(blocking),
|
|
833
|
+
"deferred": next_command,
|
|
834
|
+
}
|
|
835
|
+
else:
|
|
836
|
+
epic_reconcile = {
|
|
837
|
+
"required": False,
|
|
838
|
+
"reminder": True,
|
|
839
|
+
"command": reconcile_command,
|
|
840
|
+
"count": len(open_requests),
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
# ---- Verify-first primary routing ------------------------------------- #
|
|
844
|
+
# While verification is unresolved the verify command is THE action — except
|
|
845
|
+
# under a live findings report, whose one action is the forge-fix that applies
|
|
846
|
+
# it. Either way the production successor is demoted to unfenced conditional
|
|
847
|
+
# prose. No path may fence or recommend the deferred production command first
|
|
848
|
+
# (REQ-EXIT-06).
|
|
849
|
+
verify_canonical = f"/feature-forge:forge-verify {feature}"
|
|
850
|
+
# The one action a live findings report promotes, on every route that can
|
|
851
|
+
# reach it: findings already exist at this exact revision, so re-dispatching
|
|
852
|
+
# verify would only restate them. The served stage is carried so the fix
|
|
853
|
+
# rejoins this production thread.
|
|
854
|
+
fix_canonical = f"/feature-forge:forge-fix {feature} --served-stage {route_stage}"
|
|
855
|
+
verify_command = _host_command(verify_canonical, host)
|
|
856
|
+
blocking_reconcile = bool(epic_reconcile and epic_reconcile.get("required"))
|
|
857
|
+
primary_canonical: str | None
|
|
858
|
+
deferred_canonical: str | None
|
|
859
|
+
outcome_text: str | None = None
|
|
860
|
+
if stage in _BRANCH_STAGES:
|
|
861
|
+
# The outcome table alone decides a branch terminus — verify-first
|
|
862
|
+
# ordering does not apply, because the branch IS the verification work.
|
|
863
|
+
primary_canonical, deferred_canonical, outcome_text, advancing = _branch_route(
|
|
864
|
+
stage,
|
|
865
|
+
outcome,
|
|
866
|
+
feature,
|
|
867
|
+
resolved_served,
|
|
868
|
+
next_command,
|
|
869
|
+
resolved,
|
|
870
|
+
)
|
|
871
|
+
if advancing and blocking_reconcile:
|
|
872
|
+
# An advancing rejoin is subject to the same reconcile-first rule as a
|
|
873
|
+
# production exit; a non-advancing one already outranks the reconcile.
|
|
874
|
+
primary_canonical = epic_reconcile["command"]
|
|
875
|
+
deferred_canonical = None
|
|
876
|
+
elif stage == "forge-5-loop":
|
|
877
|
+
# Every loop result gets a deterministic resume or recovery action.
|
|
878
|
+
# `complete` keeps verify-first ordering (the table applies it to its own
|
|
879
|
+
# handoff); the other four never reach a production stage at all.
|
|
880
|
+
primary_canonical, deferred_canonical, outcome_text, advancing = _loop_route(
|
|
881
|
+
outcome,
|
|
882
|
+
feature,
|
|
883
|
+
route_epic,
|
|
884
|
+
specs_dir,
|
|
885
|
+
next_command,
|
|
886
|
+
resolved,
|
|
887
|
+
verify_canonical,
|
|
888
|
+
fix_canonical if live_findings_report else None,
|
|
889
|
+
cause,
|
|
890
|
+
)
|
|
891
|
+
if blocking_reconcile:
|
|
892
|
+
# Same reconcile-first rule as every other advancing route — but the
|
|
893
|
+
# continuation carried forward is the LOOP's, not the successor table's
|
|
894
|
+
# documentation stage, which this route deliberately did not choose.
|
|
895
|
+
primary_canonical, deferred_canonical, outcome_text = _promote_reconcile(
|
|
896
|
+
stage,
|
|
897
|
+
epic_reconcile,
|
|
898
|
+
feature,
|
|
899
|
+
epic_name,
|
|
900
|
+
primary_canonical,
|
|
901
|
+
deferred_canonical,
|
|
902
|
+
outcome_text,
|
|
903
|
+
advancing,
|
|
904
|
+
)
|
|
905
|
+
elif stage == "forge-6-docs":
|
|
906
|
+
# The documentation terminus is decided by LIVE epic state, not by
|
|
907
|
+
# the successor table — the pipeline ends here, so there is no next stage
|
|
908
|
+
# to fence and no verification to put first (docs is tokenless).
|
|
909
|
+
primary_canonical, deferred_canonical, outcome_text, advancing = _docs_route(
|
|
910
|
+
feature,
|
|
911
|
+
route_epic,
|
|
912
|
+
specs_dir,
|
|
913
|
+
outcome,
|
|
914
|
+
host,
|
|
915
|
+
)
|
|
916
|
+
if blocking_reconcile:
|
|
917
|
+
# Same reconcile-first rule as an advancing branch rejoin: handing off to
|
|
918
|
+
# the next member would build it on a decomposition that is about to
|
|
919
|
+
# change. A non-advancing docs route already lands on the epic itself. The
|
|
920
|
+
# successor table has no entry for this stage, so its seeded continuation
|
|
921
|
+
# is None — the live route's own primary is what must be carried forward.
|
|
922
|
+
primary_canonical, deferred_canonical, outcome_text = _promote_reconcile(
|
|
923
|
+
stage,
|
|
924
|
+
epic_reconcile,
|
|
925
|
+
feature,
|
|
926
|
+
epic_name,
|
|
927
|
+
primary_canonical,
|
|
928
|
+
deferred_canonical,
|
|
929
|
+
outcome_text,
|
|
930
|
+
advancing,
|
|
931
|
+
)
|
|
932
|
+
elif live_findings_report:
|
|
933
|
+
primary_canonical = fix_canonical
|
|
934
|
+
deferred_canonical = next_command
|
|
935
|
+
elif not resolved:
|
|
936
|
+
primary_canonical = verify_canonical
|
|
937
|
+
deferred_canonical = next_command
|
|
938
|
+
elif blocking_reconcile:
|
|
939
|
+
# Verification is settled, so the blocking reconcile is the primary
|
|
940
|
+
# action and `epicReconcile["deferred"]` carries the demoted successor.
|
|
941
|
+
primary_canonical = epic_reconcile["command"]
|
|
942
|
+
deferred_canonical = None
|
|
943
|
+
else:
|
|
944
|
+
primary_canonical = next_command or "/feature-forge:forge"
|
|
945
|
+
deferred_canonical = None
|
|
946
|
+
|
|
947
|
+
# ---- Terminal epic exit (issue #248) ---------------------------------- #
|
|
948
|
+
# A finished epic gets a genuinely terminal block: no fenced command at all,
|
|
949
|
+
# because every command this exit could fence is the dashboard it was just run
|
|
950
|
+
# from. The equality guard below is what makes this safe to apply so late — it
|
|
951
|
+
# fires ONLY where the routing above already landed on the epic dashboard, so
|
|
952
|
+
# verify-first ordering (primary is the verify command), a live findings report
|
|
953
|
+
# (primary is the fix), and a `blocked` docs recovery all keep their precedence
|
|
954
|
+
# untouched. `not blocking_reconcile` is a SEPARATE, load-bearing guard rather than
|
|
955
|
+
# a restatement of anything above: the reconcile command is the same STRING as the
|
|
956
|
+
# dashboard when the exiting feature IS the epic, so without it a blocking reconcile
|
|
957
|
+
# would be silently swallowed by the equality test.
|
|
958
|
+
#
|
|
959
|
+
# A NON-blocking reminder does not block the terminal answer, and must not: fencing
|
|
960
|
+
# the dashboard for an optional reconcile reinstates the #248 loop (decline it,
|
|
961
|
+
# re-run, get the same block) while promoting a change the exit itself calls
|
|
962
|
+
# non-blocking to the one required action. The reminder survives as the inline line
|
|
963
|
+
# `_next_steps_block` already renders for it — an offer beside "nothing is
|
|
964
|
+
# required", which is exactly what a non-blocking request is.
|
|
965
|
+
epic_dashboard = f"/feature-forge:forge-0-epic {feature}"
|
|
966
|
+
if (
|
|
967
|
+
epic_terminal is not None
|
|
968
|
+
and not blocking_reconcile
|
|
969
|
+
and primary_canonical == epic_dashboard
|
|
970
|
+
):
|
|
971
|
+
terminal_kind, terminal_fields = epic_terminal
|
|
972
|
+
terminal_text = _EPIC_TERMINAL_TEXT[terminal_kind].format(
|
|
973
|
+
**terminal_fields,
|
|
974
|
+
new_feature=_host_command("/feature-forge:forge-1-prd <new-feature>", host),
|
|
975
|
+
new_epic=_host_command("/feature-forge:forge-0-epic <new-epic>", host),
|
|
976
|
+
dashboard=_host_command(epic_dashboard, host),
|
|
977
|
+
)
|
|
978
|
+
# A branch exit arrives with its own outcome sentence ("verification passed…"),
|
|
979
|
+
# which is still true and still worth saying; the terminal sentence follows it.
|
|
980
|
+
outcome_text = f"{outcome_text} {terminal_text}" if outcome_text else terminal_text
|
|
981
|
+
primary_canonical = None
|
|
982
|
+
deferred_canonical = None
|
|
983
|
+
# Downstream signals are suppressed for the same reason the fence is: there is
|
|
984
|
+
# no next stage, so `nextCommand` must not offer one for a consumer to promote.
|
|
985
|
+
next_command = None
|
|
986
|
+
next_stage_id = None
|
|
987
|
+
|
|
988
|
+
# Fixed order: entry 1 is the epic-member unreadable-state fallback,
|
|
989
|
+
# then the debt-metadata and revision-mismatch entries.
|
|
990
|
+
warnings: list[str] = []
|
|
991
|
+
if epic_member_warning is not None:
|
|
992
|
+
warnings.append(epic_member_warning)
|
|
993
|
+
warnings.extend(
|
|
994
|
+
_debt_metadata_warnings(
|
|
995
|
+
verify_entry, verify_key, route_stage, feature, verify_command, verify_current
|
|
996
|
+
)
|
|
997
|
+
)
|
|
998
|
+
|
|
999
|
+
# `owner == "nested"` means an outer authoring stage prints the terminal block.
|
|
1000
|
+
# The routing directives survive; the human-facing block does not exist at all,
|
|
1001
|
+
# so a nested chain can never emit a second sentinel (REQ-EXIT-03/04).
|
|
1002
|
+
nested = owner == "nested"
|
|
1003
|
+
|
|
1004
|
+
directives = {
|
|
1005
|
+
"stage": stage,
|
|
1006
|
+
"stageNoun": STAGE_NOUN.get(stage, stage),
|
|
1007
|
+
"servedStage": resolved_served,
|
|
1008
|
+
"verifyMode": _STAGE_TO_VERIFY_MODE.get(resolved_served or ""),
|
|
1009
|
+
"outcome": outcome,
|
|
1010
|
+
"owner": owner,
|
|
1011
|
+
"terminalOwnedBy": "outer" if nested else "self",
|
|
1012
|
+
"feature": feature,
|
|
1013
|
+
"runInStageVerify": run_in_stage,
|
|
1014
|
+
"verifyGate": verify_gate,
|
|
1015
|
+
"verifyCapability": verify_capability,
|
|
1016
|
+
"autoFixEligible": auto_fix_eligible,
|
|
1017
|
+
"verifyState": verify_label,
|
|
1018
|
+
"verifyStage": pending_verify(state),
|
|
1019
|
+
"verifyCommand": verify_command,
|
|
1020
|
+
"autoVerifyEffective": effective_auto_verify,
|
|
1021
|
+
"autoVerifyDebtRecorded": auto_verify_debt_recorded,
|
|
1022
|
+
"nextStage": next_stage_id,
|
|
1023
|
+
"nextCommand": _host_command(next_command, host) if next_command else next_command,
|
|
1024
|
+
"primaryCommand": (
|
|
1025
|
+
_host_command(primary_canonical, host) if primary_canonical else None
|
|
1026
|
+
),
|
|
1027
|
+
"deferredCommand": (
|
|
1028
|
+
_host_command(deferred_canonical, host) if deferred_canonical else None
|
|
1029
|
+
),
|
|
1030
|
+
"invalidAutoVerifyKeys": invalid_keys,
|
|
1031
|
+
"warnings": warnings,
|
|
1032
|
+
"gitRepo": git_repo,
|
|
1033
|
+
"cleanTree": clean_tree,
|
|
1034
|
+
"host": host,
|
|
1035
|
+
}
|
|
1036
|
+
if epic_reconcile is not None:
|
|
1037
|
+
directives["epicReconcile"] = epic_reconcile
|
|
1038
|
+
if nested:
|
|
1039
|
+
return {"directives": directives, "nextSteps": None, "sentinel": None}
|
|
1040
|
+
return {
|
|
1041
|
+
"directives": directives,
|
|
1042
|
+
"nextSteps": _next_steps_block(
|
|
1043
|
+
primary_canonical,
|
|
1044
|
+
host,
|
|
1045
|
+
epic_reconcile,
|
|
1046
|
+
deferred_command=deferred_canonical,
|
|
1047
|
+
outcome_text=outcome_text,
|
|
1048
|
+
),
|
|
1049
|
+
"sentinel": NEXT_STEPS_SENTINEL,
|
|
1050
|
+
}
|
|
1051
|
+
def _print_stage_exit(payload: dict) -> None:
|
|
1052
|
+
"""Print DIRECTIVES then the NEXT-STEPS block (the skill-facing form).
|
|
1053
|
+
|
|
1054
|
+
A NESTED branch payload carries ``nextSteps is None``: an outer authoring stage
|
|
1055
|
+
owns the terminal block, so this printer emits the directives and stops. Printing
|
|
1056
|
+
a terminal section here — even an empty one — is the ownership leak REQ-EXIT-04
|
|
1057
|
+
forbids.
|
|
1058
|
+
"""
|
|
1059
|
+
print("DIRECTIVES:")
|
|
1060
|
+
print(json.dumps(payload["directives"], indent=2, ensure_ascii=False))
|
|
1061
|
+
if payload.get("nextSteps") is None:
|
|
1062
|
+
return
|
|
1063
|
+
print(
|
|
1064
|
+
"NEXT-STEPS (print this block verbatim as your absolute last output — "
|
|
1065
|
+
"nothing after the sentinel):"
|
|
1066
|
+
)
|
|
1067
|
+
print(payload["nextSteps"])
|
|
1068
|
+
|
|
1069
|
+
__all__ = [
|
|
1070
|
+
"_EXIT_PRODUCTION_STAGES",
|
|
1071
|
+
"_BRANCH_STAGES",
|
|
1072
|
+
"_STAGE_TO_VERIFY_MODE",
|
|
1073
|
+
"_EXIT_NEXT_STAGE",
|
|
1074
|
+
"_epic_verify_context",
|
|
1075
|
+
"_same_named_candidates",
|
|
1076
|
+
"_epic_member_state",
|
|
1077
|
+
"resolve_served_stage",
|
|
1078
|
+
"_EPIC_TERMINAL_TEXT",
|
|
1079
|
+
"stage_exit",
|
|
1080
|
+
"_print_stage_exit",
|
|
1081
|
+
]
|