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