@garygentry/feature-forge 0.3.0 → 0.3.2
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/adapters/claude/.feature-forge-bundle.json +1 -1
- package/adapters/claude/agents/forge-verifier.md +1 -1
- package/adapters/claude/references/epic-manifest-schema.json +6 -1
- package/adapters/claude/references/forge-config-schema.json +1 -1
- package/adapters/claude/references/pipeline-state-schema.json +5 -3
- package/adapters/claude/references/shared-conventions.md +125 -12
- package/adapters/claude/references/stage-exit-protocol.md +358 -144
- package/adapters/claude/references/vendor-construct-inventory.md +1 -1
- package/adapters/claude/scripts/epic-manifest.py +459 -90
- package/adapters/claude/scripts/forge-bootstrap.py +57 -5
- package/adapters/claude/scripts/forge-session.py +4346 -201
- package/adapters/claude/scripts/validate-traceability.py +92 -6
- package/adapters/claude/skills/forge/SKILL.md +18 -11
- package/adapters/claude/skills/forge/references/pipeline-state-schema.json +5 -3
- package/adapters/claude/skills/forge/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge/references/stage-exit-protocol.md +358 -144
- package/adapters/claude/skills/forge-0-epic/SKILL.md +8 -3
- package/adapters/claude/skills/forge-0-epic/references/edit-mode.md +24 -24
- package/adapters/claude/skills/forge-0-epic/references/pipeline-state-schema.json +5 -3
- package/adapters/claude/skills/forge-0-epic/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge-0-epic/references/stage-exit-protocol.md +358 -144
- package/adapters/claude/skills/forge-1-prd/SKILL.md +39 -11
- package/adapters/claude/skills/forge-1-prd/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge-1-prd/references/stage-exit-protocol.md +358 -144
- package/adapters/claude/skills/forge-2-tech/SKILL.md +38 -10
- package/adapters/claude/skills/forge-2-tech/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge-2-tech/references/stage-exit-protocol.md +358 -144
- package/adapters/claude/skills/forge-3-specs/SKILL.md +28 -9
- package/adapters/claude/skills/forge-3-specs/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge-3-specs/references/spec-archetypes.md +9 -0
- package/adapters/claude/skills/forge-3-specs/references/stage-exit-protocol.md +358 -144
- package/adapters/claude/skills/forge-4-backlog/SKILL.md +39 -10
- package/adapters/claude/skills/forge-4-backlog/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge-4-backlog/references/stage-exit-protocol.md +358 -144
- package/adapters/claude/skills/forge-5-loop/SKILL.md +65 -67
- package/adapters/claude/skills/forge-5-loop/references/agent-selection.md +116 -0
- package/adapters/claude/skills/forge-5-loop/references/result-reporting.md +70 -31
- package/adapters/claude/skills/forge-5-loop/references/runner-contract.md +18 -109
- package/adapters/claude/skills/forge-5-loop/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge-5-loop/references/stage-exit-protocol.md +358 -144
- package/adapters/claude/skills/forge-6-docs/SKILL.md +59 -9
- package/adapters/claude/skills/forge-6-docs/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge-6-docs/references/stage-exit-protocol.md +472 -0
- package/adapters/claude/skills/forge-fix/SKILL.md +85 -33
- package/adapters/claude/skills/forge-fix/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge-fix/references/stage-exit-protocol.md +358 -144
- package/adapters/claude/skills/forge-guide/references/forge-config-schema.json +1 -1
- package/adapters/claude/skills/forge-guide/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge-verify/SKILL.md +86 -44
- package/adapters/claude/skills/forge-verify/references/findings-template.md +147 -0
- package/adapters/claude/skills/forge-verify/references/shared-conventions.md +125 -12
- package/adapters/claude/skills/forge-verify/references/stage-exit-protocol.md +472 -0
- package/adapters/claude/skills/forge-verify/references/verification-checklists/backlog.md +97 -0
- package/adapters/claude/skills/forge-verify/references/verification-checklists/epic.md +80 -0
- package/adapters/claude/skills/forge-verify/references/verification-checklists/impl.md +48 -0
- package/adapters/claude/skills/forge-verify/references/verification-checklists/prd.md +31 -0
- package/adapters/claude/skills/forge-verify/references/verification-checklists/specs.md +66 -0
- package/adapters/claude/skills/forge-verify/references/verification-checklists/tech.md +35 -0
- package/adapters/codex/.feature-forge-bundle.json +1 -1
- package/adapters/codex/agents/forge-verifier.toml +1 -1
- package/adapters/codex/references/epic-manifest-schema.json +6 -1
- package/adapters/codex/references/forge-config-schema.json +1 -1
- package/adapters/codex/references/pipeline-state-schema.json +5 -3
- package/adapters/codex/references/shared-conventions.md +125 -12
- package/adapters/codex/references/stage-exit-protocol.md +358 -144
- package/adapters/codex/references/vendor-construct-inventory.md +1 -1
- package/adapters/codex/scripts/epic-manifest.py +459 -90
- package/adapters/codex/scripts/forge-bootstrap.py +57 -5
- package/adapters/codex/scripts/forge-session.py +4346 -201
- package/adapters/codex/scripts/validate-traceability.py +92 -6
- package/adapters/codex/skills/forge/SKILL.md +18 -11
- package/adapters/codex/skills/forge/references/pipeline-state-schema.json +5 -3
- package/adapters/codex/skills/forge/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge/references/stage-exit-protocol.md +358 -144
- package/adapters/codex/skills/forge-0-epic/SKILL.md +8 -3
- package/adapters/codex/skills/forge-0-epic/references/edit-mode.md +24 -24
- package/adapters/codex/skills/forge-0-epic/references/pipeline-state-schema.json +5 -3
- package/adapters/codex/skills/forge-0-epic/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge-0-epic/references/stage-exit-protocol.md +358 -144
- package/adapters/codex/skills/forge-1-prd/SKILL.md +39 -11
- package/adapters/codex/skills/forge-1-prd/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge-1-prd/references/stage-exit-protocol.md +358 -144
- package/adapters/codex/skills/forge-2-tech/SKILL.md +38 -10
- package/adapters/codex/skills/forge-2-tech/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge-2-tech/references/stage-exit-protocol.md +358 -144
- package/adapters/codex/skills/forge-3-specs/SKILL.md +28 -9
- package/adapters/codex/skills/forge-3-specs/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge-3-specs/references/spec-archetypes.md +9 -0
- package/adapters/codex/skills/forge-3-specs/references/stage-exit-protocol.md +358 -144
- package/adapters/codex/skills/forge-4-backlog/SKILL.md +39 -10
- package/adapters/codex/skills/forge-4-backlog/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge-4-backlog/references/stage-exit-protocol.md +358 -144
- package/adapters/codex/skills/forge-5-loop/SKILL.md +65 -67
- package/adapters/codex/skills/forge-5-loop/references/agent-selection.md +116 -0
- package/adapters/codex/skills/forge-5-loop/references/result-reporting.md +70 -31
- package/adapters/codex/skills/forge-5-loop/references/runner-contract.md +18 -109
- package/adapters/codex/skills/forge-5-loop/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge-5-loop/references/stage-exit-protocol.md +358 -144
- package/adapters/codex/skills/forge-6-docs/SKILL.md +59 -9
- package/adapters/codex/skills/forge-6-docs/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge-6-docs/references/stage-exit-protocol.md +472 -0
- package/adapters/codex/skills/forge-fix/SKILL.md +84 -32
- package/adapters/codex/skills/forge-fix/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge-fix/references/stage-exit-protocol.md +358 -144
- package/adapters/codex/skills/forge-guide/references/forge-config-schema.json +1 -1
- package/adapters/codex/skills/forge-guide/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge-verify/SKILL.md +85 -43
- package/adapters/codex/skills/forge-verify/references/findings-template.md +147 -0
- package/adapters/codex/skills/forge-verify/references/shared-conventions.md +125 -12
- package/adapters/codex/skills/forge-verify/references/stage-exit-protocol.md +472 -0
- package/adapters/codex/skills/forge-verify/references/verification-checklists/backlog.md +97 -0
- package/adapters/codex/skills/forge-verify/references/verification-checklists/epic.md +80 -0
- package/adapters/codex/skills/forge-verify/references/verification-checklists/impl.md +48 -0
- package/adapters/codex/skills/forge-verify/references/verification-checklists/prd.md +31 -0
- package/adapters/codex/skills/forge-verify/references/verification-checklists/specs.md +66 -0
- package/adapters/codex/skills/forge-verify/references/verification-checklists/tech.md +35 -0
- package/adapters/copilot/.feature-forge-bundle.json +1 -1
- package/adapters/copilot/agents/forge-verifier.md +1 -1
- package/adapters/copilot/references/epic-manifest-schema.json +6 -1
- package/adapters/copilot/references/forge-config-schema.json +1 -1
- package/adapters/copilot/references/pipeline-state-schema.json +5 -3
- package/adapters/copilot/references/shared-conventions.md +125 -12
- package/adapters/copilot/references/stage-exit-protocol.md +358 -144
- package/adapters/copilot/references/vendor-construct-inventory.md +1 -1
- package/adapters/copilot/scripts/epic-manifest.py +459 -90
- package/adapters/copilot/scripts/forge-bootstrap.py +57 -5
- package/adapters/copilot/scripts/forge-session.py +4346 -201
- package/adapters/copilot/scripts/validate-traceability.py +92 -6
- package/adapters/copilot/skills/forge/forge.md +18 -11
- package/adapters/copilot/skills/forge/references/pipeline-state-schema.json +5 -3
- package/adapters/copilot/skills/forge/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge/references/stage-exit-protocol.md +358 -144
- package/adapters/copilot/skills/forge-0-epic/forge-0-epic.md +8 -3
- package/adapters/copilot/skills/forge-0-epic/references/edit-mode.md +24 -24
- package/adapters/copilot/skills/forge-0-epic/references/pipeline-state-schema.json +5 -3
- package/adapters/copilot/skills/forge-0-epic/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge-0-epic/references/stage-exit-protocol.md +358 -144
- package/adapters/copilot/skills/forge-1-prd/forge-1-prd.md +39 -11
- package/adapters/copilot/skills/forge-1-prd/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge-1-prd/references/stage-exit-protocol.md +358 -144
- package/adapters/copilot/skills/forge-2-tech/forge-2-tech.md +38 -10
- package/adapters/copilot/skills/forge-2-tech/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge-2-tech/references/stage-exit-protocol.md +358 -144
- package/adapters/copilot/skills/forge-3-specs/forge-3-specs.md +28 -9
- package/adapters/copilot/skills/forge-3-specs/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge-3-specs/references/spec-archetypes.md +9 -0
- package/adapters/copilot/skills/forge-3-specs/references/stage-exit-protocol.md +358 -144
- package/adapters/copilot/skills/forge-4-backlog/forge-4-backlog.md +39 -10
- package/adapters/copilot/skills/forge-4-backlog/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge-4-backlog/references/stage-exit-protocol.md +358 -144
- package/adapters/copilot/skills/forge-5-loop/forge-5-loop.md +65 -67
- package/adapters/copilot/skills/forge-5-loop/references/agent-selection.md +116 -0
- package/adapters/copilot/skills/forge-5-loop/references/result-reporting.md +70 -31
- package/adapters/copilot/skills/forge-5-loop/references/runner-contract.md +18 -109
- package/adapters/copilot/skills/forge-5-loop/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge-5-loop/references/stage-exit-protocol.md +358 -144
- package/adapters/copilot/skills/forge-6-docs/forge-6-docs.md +59 -9
- package/adapters/copilot/skills/forge-6-docs/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge-6-docs/references/stage-exit-protocol.md +472 -0
- package/adapters/copilot/skills/forge-fix/forge-fix.md +84 -32
- package/adapters/copilot/skills/forge-fix/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge-fix/references/stage-exit-protocol.md +358 -144
- package/adapters/copilot/skills/forge-guide/references/forge-config-schema.json +1 -1
- package/adapters/copilot/skills/forge-guide/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge-verify/forge-verify.md +85 -43
- package/adapters/copilot/skills/forge-verify/references/findings-template.md +147 -0
- package/adapters/copilot/skills/forge-verify/references/shared-conventions.md +125 -12
- package/adapters/copilot/skills/forge-verify/references/stage-exit-protocol.md +472 -0
- package/adapters/copilot/skills/forge-verify/references/verification-checklists/backlog.md +97 -0
- package/adapters/copilot/skills/forge-verify/references/verification-checklists/epic.md +80 -0
- package/adapters/copilot/skills/forge-verify/references/verification-checklists/impl.md +48 -0
- package/adapters/copilot/skills/forge-verify/references/verification-checklists/prd.md +31 -0
- package/adapters/copilot/skills/forge-verify/references/verification-checklists/specs.md +66 -0
- package/adapters/copilot/skills/forge-verify/references/verification-checklists/tech.md +35 -0
- package/adapters/cursor/.feature-forge-bundle.json +1 -1
- package/adapters/cursor/agents/forge-verifier.mdc +1 -1
- package/adapters/cursor/references/epic-manifest-schema.json +6 -1
- package/adapters/cursor/references/forge-config-schema.json +1 -1
- package/adapters/cursor/references/pipeline-state-schema.json +5 -3
- package/adapters/cursor/references/shared-conventions.md +125 -12
- package/adapters/cursor/references/stage-exit-protocol.md +358 -144
- package/adapters/cursor/references/vendor-construct-inventory.md +1 -1
- package/adapters/cursor/scripts/epic-manifest.py +459 -90
- package/adapters/cursor/scripts/forge-bootstrap.py +57 -5
- package/adapters/cursor/scripts/forge-session.py +4346 -201
- package/adapters/cursor/scripts/validate-traceability.py +92 -6
- package/adapters/cursor/skills/forge/forge.mdc +18 -11
- package/adapters/cursor/skills/forge/references/pipeline-state-schema.json +5 -3
- package/adapters/cursor/skills/forge/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge/references/stage-exit-protocol.md +358 -144
- package/adapters/cursor/skills/forge-0-epic/forge-0-epic.mdc +8 -3
- package/adapters/cursor/skills/forge-0-epic/references/edit-mode.md +24 -24
- package/adapters/cursor/skills/forge-0-epic/references/pipeline-state-schema.json +5 -3
- package/adapters/cursor/skills/forge-0-epic/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge-0-epic/references/stage-exit-protocol.md +358 -144
- package/adapters/cursor/skills/forge-1-prd/forge-1-prd.mdc +39 -11
- package/adapters/cursor/skills/forge-1-prd/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge-1-prd/references/stage-exit-protocol.md +358 -144
- package/adapters/cursor/skills/forge-2-tech/forge-2-tech.mdc +38 -10
- package/adapters/cursor/skills/forge-2-tech/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge-2-tech/references/stage-exit-protocol.md +358 -144
- package/adapters/cursor/skills/forge-3-specs/forge-3-specs.mdc +28 -9
- package/adapters/cursor/skills/forge-3-specs/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge-3-specs/references/spec-archetypes.md +9 -0
- package/adapters/cursor/skills/forge-3-specs/references/stage-exit-protocol.md +358 -144
- package/adapters/cursor/skills/forge-4-backlog/forge-4-backlog.mdc +39 -10
- package/adapters/cursor/skills/forge-4-backlog/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge-4-backlog/references/stage-exit-protocol.md +358 -144
- package/adapters/cursor/skills/forge-5-loop/forge-5-loop.mdc +65 -67
- package/adapters/cursor/skills/forge-5-loop/references/agent-selection.md +116 -0
- package/adapters/cursor/skills/forge-5-loop/references/result-reporting.md +70 -31
- package/adapters/cursor/skills/forge-5-loop/references/runner-contract.md +18 -109
- package/adapters/cursor/skills/forge-5-loop/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge-5-loop/references/stage-exit-protocol.md +358 -144
- package/adapters/cursor/skills/forge-6-docs/forge-6-docs.mdc +59 -9
- package/adapters/cursor/skills/forge-6-docs/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge-6-docs/references/stage-exit-protocol.md +472 -0
- package/adapters/cursor/skills/forge-fix/forge-fix.mdc +84 -32
- package/adapters/cursor/skills/forge-fix/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge-fix/references/stage-exit-protocol.md +358 -144
- package/adapters/cursor/skills/forge-guide/references/forge-config-schema.json +1 -1
- package/adapters/cursor/skills/forge-guide/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge-verify/forge-verify.mdc +85 -43
- package/adapters/cursor/skills/forge-verify/references/findings-template.md +147 -0
- package/adapters/cursor/skills/forge-verify/references/shared-conventions.md +125 -12
- package/adapters/cursor/skills/forge-verify/references/stage-exit-protocol.md +472 -0
- package/adapters/cursor/skills/forge-verify/references/verification-checklists/backlog.md +97 -0
- package/adapters/cursor/skills/forge-verify/references/verification-checklists/epic.md +80 -0
- package/adapters/cursor/skills/forge-verify/references/verification-checklists/impl.md +48 -0
- package/adapters/cursor/skills/forge-verify/references/verification-checklists/prd.md +31 -0
- package/adapters/cursor/skills/forge-verify/references/verification-checklists/specs.md +66 -0
- package/adapters/cursor/skills/forge-verify/references/verification-checklists/tech.md +35 -0
- package/adapters/gemini/.feature-forge-bundle.json +1 -1
- package/adapters/gemini/agents/forge-verifier.md +1 -1
- package/adapters/gemini/gemini-extension.json +1 -1
- package/adapters/gemini/references/epic-manifest-schema.json +6 -1
- package/adapters/gemini/references/forge-config-schema.json +1 -1
- package/adapters/gemini/references/pipeline-state-schema.json +5 -3
- package/adapters/gemini/references/shared-conventions.md +125 -12
- package/adapters/gemini/references/stage-exit-protocol.md +358 -144
- package/adapters/gemini/references/vendor-construct-inventory.md +1 -1
- package/adapters/gemini/scripts/epic-manifest.py +459 -90
- package/adapters/gemini/scripts/forge-bootstrap.py +57 -5
- package/adapters/gemini/scripts/forge-session.py +4346 -201
- package/adapters/gemini/scripts/validate-traceability.py +92 -6
- package/adapters/gemini/skills/forge/forge.md +18 -11
- package/adapters/gemini/skills/forge/references/pipeline-state-schema.json +5 -3
- package/adapters/gemini/skills/forge/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge/references/stage-exit-protocol.md +358 -144
- package/adapters/gemini/skills/forge-0-epic/forge-0-epic.md +8 -3
- package/adapters/gemini/skills/forge-0-epic/references/edit-mode.md +24 -24
- package/adapters/gemini/skills/forge-0-epic/references/pipeline-state-schema.json +5 -3
- package/adapters/gemini/skills/forge-0-epic/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge-0-epic/references/stage-exit-protocol.md +358 -144
- package/adapters/gemini/skills/forge-1-prd/forge-1-prd.md +39 -11
- package/adapters/gemini/skills/forge-1-prd/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge-1-prd/references/stage-exit-protocol.md +358 -144
- package/adapters/gemini/skills/forge-2-tech/forge-2-tech.md +38 -10
- package/adapters/gemini/skills/forge-2-tech/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge-2-tech/references/stage-exit-protocol.md +358 -144
- package/adapters/gemini/skills/forge-3-specs/forge-3-specs.md +28 -9
- package/adapters/gemini/skills/forge-3-specs/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge-3-specs/references/spec-archetypes.md +9 -0
- package/adapters/gemini/skills/forge-3-specs/references/stage-exit-protocol.md +358 -144
- package/adapters/gemini/skills/forge-4-backlog/forge-4-backlog.md +39 -10
- package/adapters/gemini/skills/forge-4-backlog/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge-4-backlog/references/stage-exit-protocol.md +358 -144
- package/adapters/gemini/skills/forge-5-loop/forge-5-loop.md +65 -67
- package/adapters/gemini/skills/forge-5-loop/references/agent-selection.md +116 -0
- package/adapters/gemini/skills/forge-5-loop/references/result-reporting.md +70 -31
- package/adapters/gemini/skills/forge-5-loop/references/runner-contract.md +18 -109
- package/adapters/gemini/skills/forge-5-loop/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge-5-loop/references/stage-exit-protocol.md +358 -144
- package/adapters/gemini/skills/forge-6-docs/forge-6-docs.md +59 -9
- package/adapters/gemini/skills/forge-6-docs/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge-6-docs/references/stage-exit-protocol.md +472 -0
- package/adapters/gemini/skills/forge-fix/forge-fix.md +84 -32
- package/adapters/gemini/skills/forge-fix/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge-fix/references/stage-exit-protocol.md +358 -144
- package/adapters/gemini/skills/forge-guide/references/forge-config-schema.json +1 -1
- package/adapters/gemini/skills/forge-guide/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge-verify/forge-verify.md +85 -43
- package/adapters/gemini/skills/forge-verify/references/findings-template.md +147 -0
- package/adapters/gemini/skills/forge-verify/references/shared-conventions.md +125 -12
- package/adapters/gemini/skills/forge-verify/references/stage-exit-protocol.md +472 -0
- package/adapters/gemini/skills/forge-verify/references/verification-checklists/backlog.md +97 -0
- package/adapters/gemini/skills/forge-verify/references/verification-checklists/epic.md +80 -0
- package/adapters/gemini/skills/forge-verify/references/verification-checklists/impl.md +48 -0
- package/adapters/gemini/skills/forge-verify/references/verification-checklists/prd.md +31 -0
- package/adapters/gemini/skills/forge-verify/references/verification-checklists/specs.md +66 -0
- package/adapters/gemini/skills/forge-verify/references/verification-checklists/tech.md +35 -0
- package/adapters/pi/.feature-forge-bundle.json +1 -1
- package/adapters/pi/agents/forge-verifier.md +1 -1
- package/adapters/pi/references/epic-manifest-schema.json +6 -1
- package/adapters/pi/references/forge-config-schema.json +1 -1
- package/adapters/pi/references/pipeline-state-schema.json +5 -3
- package/adapters/pi/references/shared-conventions.md +125 -12
- package/adapters/pi/references/stage-exit-protocol.md +358 -144
- package/adapters/pi/references/vendor-construct-inventory.md +1 -1
- package/adapters/pi/scripts/epic-manifest.py +459 -90
- package/adapters/pi/scripts/forge-bootstrap.py +57 -5
- package/adapters/pi/scripts/forge-session.py +4346 -201
- package/adapters/pi/scripts/validate-traceability.py +92 -6
- package/adapters/pi/skills/forge/SKILL.md +18 -11
- package/adapters/pi/skills/forge/references/pipeline-state-schema.json +5 -3
- package/adapters/pi/skills/forge/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge/references/stage-exit-protocol.md +358 -144
- package/adapters/pi/skills/forge-0-epic/SKILL.md +8 -3
- package/adapters/pi/skills/forge-0-epic/references/edit-mode.md +24 -24
- package/adapters/pi/skills/forge-0-epic/references/pipeline-state-schema.json +5 -3
- package/adapters/pi/skills/forge-0-epic/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge-0-epic/references/stage-exit-protocol.md +358 -144
- package/adapters/pi/skills/forge-1-prd/SKILL.md +39 -11
- package/adapters/pi/skills/forge-1-prd/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge-1-prd/references/stage-exit-protocol.md +358 -144
- package/adapters/pi/skills/forge-2-tech/SKILL.md +38 -10
- package/adapters/pi/skills/forge-2-tech/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge-2-tech/references/stage-exit-protocol.md +358 -144
- package/adapters/pi/skills/forge-3-specs/SKILL.md +28 -9
- package/adapters/pi/skills/forge-3-specs/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge-3-specs/references/spec-archetypes.md +9 -0
- package/adapters/pi/skills/forge-3-specs/references/stage-exit-protocol.md +358 -144
- package/adapters/pi/skills/forge-4-backlog/SKILL.md +39 -10
- package/adapters/pi/skills/forge-4-backlog/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge-4-backlog/references/stage-exit-protocol.md +358 -144
- package/adapters/pi/skills/forge-5-loop/SKILL.md +65 -67
- package/adapters/pi/skills/forge-5-loop/references/agent-selection.md +116 -0
- package/adapters/pi/skills/forge-5-loop/references/result-reporting.md +70 -31
- package/adapters/pi/skills/forge-5-loop/references/runner-contract.md +18 -109
- package/adapters/pi/skills/forge-5-loop/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge-5-loop/references/stage-exit-protocol.md +358 -144
- package/adapters/pi/skills/forge-6-docs/SKILL.md +59 -9
- package/adapters/pi/skills/forge-6-docs/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge-6-docs/references/stage-exit-protocol.md +472 -0
- package/adapters/pi/skills/forge-fix/SKILL.md +84 -32
- package/adapters/pi/skills/forge-fix/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge-fix/references/stage-exit-protocol.md +358 -144
- package/adapters/pi/skills/forge-guide/references/forge-config-schema.json +1 -1
- package/adapters/pi/skills/forge-guide/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge-verify/SKILL.md +85 -43
- package/adapters/pi/skills/forge-verify/references/findings-template.md +147 -0
- package/adapters/pi/skills/forge-verify/references/shared-conventions.md +125 -12
- package/adapters/pi/skills/forge-verify/references/stage-exit-protocol.md +472 -0
- package/adapters/pi/skills/forge-verify/references/verification-checklists/backlog.md +97 -0
- package/adapters/pi/skills/forge-verify/references/verification-checklists/epic.md +80 -0
- package/adapters/pi/skills/forge-verify/references/verification-checklists/impl.md +48 -0
- package/adapters/pi/skills/forge-verify/references/verification-checklists/prd.md +31 -0
- package/adapters/pi/skills/forge-verify/references/verification-checklists/specs.md +66 -0
- package/adapters/pi/skills/forge-verify/references/verification-checklists/tech.md +35 -0
- package/package.json +1 -1
- package/adapters/claude/skills/forge-1-prd/references/pipeline-state-schema.json +0 -191
- package/adapters/claude/skills/forge-2-tech/references/pipeline-state-schema.json +0 -191
- package/adapters/claude/skills/forge-3-specs/references/pipeline-state-schema.json +0 -191
- package/adapters/claude/skills/forge-4-backlog/references/forge-config-schema.json +0 -236
- package/adapters/claude/skills/forge-4-backlog/references/pipeline-state-schema.json +0 -191
- package/adapters/claude/skills/forge-5-loop/references/forge-config-schema.json +0 -236
- package/adapters/claude/skills/forge-6-docs/references/pipeline-state-schema.json +0 -191
- package/adapters/claude/skills/forge-verify/references/pipeline-state-schema.json +0 -191
- package/adapters/claude/skills/forge-verify/references/verification-checklists.md +0 -477
- package/adapters/codex/skills/forge-1-prd/references/pipeline-state-schema.json +0 -191
- package/adapters/codex/skills/forge-2-tech/references/pipeline-state-schema.json +0 -191
- package/adapters/codex/skills/forge-3-specs/references/pipeline-state-schema.json +0 -191
- package/adapters/codex/skills/forge-4-backlog/references/forge-config-schema.json +0 -236
- package/adapters/codex/skills/forge-4-backlog/references/pipeline-state-schema.json +0 -191
- package/adapters/codex/skills/forge-5-loop/references/forge-config-schema.json +0 -236
- package/adapters/codex/skills/forge-6-docs/references/pipeline-state-schema.json +0 -191
- package/adapters/codex/skills/forge-verify/references/pipeline-state-schema.json +0 -191
- package/adapters/codex/skills/forge-verify/references/verification-checklists.md +0 -477
- package/adapters/copilot/skills/forge-1-prd/references/pipeline-state-schema.json +0 -191
- package/adapters/copilot/skills/forge-2-tech/references/pipeline-state-schema.json +0 -191
- package/adapters/copilot/skills/forge-3-specs/references/pipeline-state-schema.json +0 -191
- package/adapters/copilot/skills/forge-4-backlog/references/forge-config-schema.json +0 -236
- package/adapters/copilot/skills/forge-4-backlog/references/pipeline-state-schema.json +0 -191
- package/adapters/copilot/skills/forge-5-loop/references/forge-config-schema.json +0 -236
- package/adapters/copilot/skills/forge-6-docs/references/pipeline-state-schema.json +0 -191
- package/adapters/copilot/skills/forge-verify/references/pipeline-state-schema.json +0 -191
- package/adapters/copilot/skills/forge-verify/references/verification-checklists.md +0 -477
- package/adapters/cursor/skills/forge-1-prd/references/pipeline-state-schema.json +0 -191
- package/adapters/cursor/skills/forge-2-tech/references/pipeline-state-schema.json +0 -191
- package/adapters/cursor/skills/forge-3-specs/references/pipeline-state-schema.json +0 -191
- package/adapters/cursor/skills/forge-4-backlog/references/forge-config-schema.json +0 -236
- package/adapters/cursor/skills/forge-4-backlog/references/pipeline-state-schema.json +0 -191
- package/adapters/cursor/skills/forge-5-loop/references/forge-config-schema.json +0 -236
- package/adapters/cursor/skills/forge-6-docs/references/pipeline-state-schema.json +0 -191
- package/adapters/cursor/skills/forge-verify/references/pipeline-state-schema.json +0 -191
- package/adapters/cursor/skills/forge-verify/references/verification-checklists.md +0 -477
- package/adapters/gemini/skills/forge-1-prd/references/pipeline-state-schema.json +0 -191
- package/adapters/gemini/skills/forge-2-tech/references/pipeline-state-schema.json +0 -191
- package/adapters/gemini/skills/forge-3-specs/references/pipeline-state-schema.json +0 -191
- package/adapters/gemini/skills/forge-4-backlog/references/forge-config-schema.json +0 -236
- package/adapters/gemini/skills/forge-4-backlog/references/pipeline-state-schema.json +0 -191
- package/adapters/gemini/skills/forge-5-loop/references/forge-config-schema.json +0 -236
- package/adapters/gemini/skills/forge-6-docs/references/pipeline-state-schema.json +0 -191
- package/adapters/gemini/skills/forge-verify/references/pipeline-state-schema.json +0 -191
- package/adapters/gemini/skills/forge-verify/references/verification-checklists.md +0 -477
- package/adapters/pi/skills/forge-1-prd/references/pipeline-state-schema.json +0 -191
- package/adapters/pi/skills/forge-2-tech/references/pipeline-state-schema.json +0 -191
- package/adapters/pi/skills/forge-3-specs/references/pipeline-state-schema.json +0 -191
- package/adapters/pi/skills/forge-4-backlog/references/forge-config-schema.json +0 -236
- package/adapters/pi/skills/forge-4-backlog/references/pipeline-state-schema.json +0 -191
- package/adapters/pi/skills/forge-5-loop/references/forge-config-schema.json +0 -236
- package/adapters/pi/skills/forge-6-docs/references/pipeline-state-schema.json +0 -191
- package/adapters/pi/skills/forge-verify/references/pipeline-state-schema.json +0 -191
- package/adapters/pi/skills/forge-verify/references/verification-checklists.md +0 -477
|
@@ -36,10 +36,10 @@ from typing import Final, Literal, TypedDict
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
# --------------------------------------------------------------------------- #
|
|
39
|
-
# Constants
|
|
39
|
+
# Constants
|
|
40
40
|
# --------------------------------------------------------------------------- #
|
|
41
41
|
|
|
42
|
-
#: A safe feature/epic name: one kebab-case token
|
|
42
|
+
#: A safe feature/epic name: one kebab-case token.
|
|
43
43
|
SAFE_NAME_RE: Final = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
|
44
44
|
#: A directory is "feature-shaped" iff it directly contains this file.
|
|
45
45
|
PIPELINE_STATE_FILENAME: Final = ".pipeline-state.json"
|
|
@@ -55,16 +55,73 @@ NARRATIVE_FILENAME: Final = "EPIC.md"
|
|
|
55
55
|
#: NOTE: forge-session.py keeps a byte-identical copy of this constant — flat, self-
|
|
56
56
|
#: contained scripts have no shared import module (each is copied verbatim into adapters).
|
|
57
57
|
KNOWN_VERIFY_STATUSES: Final = frozenset(
|
|
58
|
-
{
|
|
58
|
+
{
|
|
59
|
+
"pending",
|
|
60
|
+
"auto-verify-pending",
|
|
61
|
+
"passed",
|
|
62
|
+
"findings-reported",
|
|
63
|
+
"findings-applied",
|
|
64
|
+
"skipped",
|
|
65
|
+
}
|
|
59
66
|
)
|
|
60
67
|
#: The subset of KNOWN_VERIFY_STATUSES that makes a member's forge-verify-impl count as
|
|
61
|
-
#: complete-for-orchestration
|
|
68
|
+
#: complete-for-orchestration. A STRICT subset — 'findings-reported' (unfixed),
|
|
62
69
|
#: 'skipped', and 'pending' do NOT unblock dependents. Not collapsible into the set above.
|
|
63
70
|
_VERIFY_ORCH_COMPLETE: Final = frozenset({"passed", "findings-applied"})
|
|
64
71
|
|
|
72
|
+
#: The six production stages in pipeline order — mirrors ``PRODUCTION_STAGES`` in
|
|
73
|
+
#: forge-session.py. Used ONLY to DERIVE "what runs next" from ``stages[].status``
|
|
74
|
+
#: (see ``_next_production_stage``), never to read it out of ``currentStage``:
|
|
75
|
+
#: the schema defines ``currentStage`` as "where the pipeline IS" (the most
|
|
76
|
+
#: recently *started* stage), which is one stage behind the next un-run stage for
|
|
77
|
+
#: the whole window between a stage completing and its successor being entered.
|
|
78
|
+
_PRODUCTION_STAGES: Final = (
|
|
79
|
+
"forge-1-prd",
|
|
80
|
+
"forge-2-tech",
|
|
81
|
+
"forge-3-specs",
|
|
82
|
+
"forge-4-backlog",
|
|
83
|
+
"forge-5-loop",
|
|
84
|
+
"forge-6-docs",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
#: The served production stage behind each ``forge-verify-{token}`` member entry, in
|
|
88
|
+
#: pipeline order — the inverse of ``VERIFY_TOKEN_BY_STAGE`` in forge-session.py. Used
|
|
89
|
+
#: to NAME the stage owed automatic verification, and iterated (rather than
|
|
90
|
+
#: iterating the state file's own key order) so the warning order is deterministic
|
|
91
|
+
#: regardless of how a member's state document happens to be serialized.
|
|
92
|
+
_VERIFY_STAGE_BY_TOKEN: Final[dict[str, str]] = {
|
|
93
|
+
"prd": "forge-1-prd",
|
|
94
|
+
"tech": "forge-2-tech",
|
|
95
|
+
"specs": "forge-3-specs",
|
|
96
|
+
"backlog": "forge-4-backlog",
|
|
97
|
+
"impl": "forge-5-loop",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#: Epic-scoped verification state lives beside the manifest, NEVER in a member's
|
|
101
|
+
#: .pipeline-state.json (REQ-SEC-01). Mirrors ``EPIC_STATE_FILENAME`` in
|
|
102
|
+
#: forge-session.py.
|
|
103
|
+
EPIC_STATE_FILENAME: Final = ".epic-state.json"
|
|
104
|
+
#: The single verify entry an epic root carries.
|
|
105
|
+
EPIC_VERIFY_KEY: Final = "forge-verify-epic"
|
|
106
|
+
#: The stage identifier epic verification is served for — used in diagnostics only.
|
|
107
|
+
EPIC_VERIFY_STAGE: Final = "forge-0-epic"
|
|
108
|
+
|
|
109
|
+
#: The statuses that count as a RESOLVED verification for freshness classification.
|
|
110
|
+
#: Mirrors ``_VERIFY_RESOLVED`` in forge-session.py. ``auto-verify-pending`` is
|
|
111
|
+
#: deliberately absent: recorded debt is owed, not resolved (REQ-DEBT-02).
|
|
112
|
+
_VERIFY_RESOLVED: Final = frozenset({"passed", "findings-applied", "skipped"})
|
|
113
|
+
|
|
114
|
+
#: The obligation sentence. Mirrors ``AUTO_PENDING_DIAGNOSTIC`` in
|
|
115
|
+
#: forge-session.py so the navigator ledger and the epic dashboard say the same thing
|
|
116
|
+
#: about the same debt; the two scripts share no import module.
|
|
117
|
+
AUTO_PENDING_DIAGNOSTIC: Final = (
|
|
118
|
+
"{subject}: automatic verification is still pending for {stage}; "
|
|
119
|
+
"run {command} to resolve it."
|
|
120
|
+
)
|
|
121
|
+
|
|
65
122
|
|
|
66
123
|
# --------------------------------------------------------------------------- #
|
|
67
|
-
# Type Definitions
|
|
124
|
+
# Type Definitions
|
|
68
125
|
# --------------------------------------------------------------------------- #
|
|
69
126
|
|
|
70
127
|
FindingCode = Literal[
|
|
@@ -99,12 +156,12 @@ class Finding(TypedDict):
|
|
|
99
156
|
DerivedStatus = Literal[
|
|
100
157
|
"not-started", # no .pipeline-state.json, or all stages pending
|
|
101
158
|
"in-progress", # at least one stage started, loop not complete-for-orchestration
|
|
102
|
-
"complete", # complete-for-orchestration
|
|
159
|
+
"complete", # complete-for-orchestration
|
|
103
160
|
]
|
|
104
161
|
|
|
105
162
|
|
|
106
163
|
class FeatureStatus(TypedDict):
|
|
107
|
-
"""Live per-feature status derived from its own pipeline state
|
|
164
|
+
"""Live per-feature status derived from its own pipeline state.
|
|
108
165
|
|
|
109
166
|
Attributes:
|
|
110
167
|
name: Feature name.
|
|
@@ -114,7 +171,7 @@ class FeatureStatus(TypedDict):
|
|
|
114
171
|
navigator status semantics for display.
|
|
115
172
|
blocked: True if any entry in unmetDeps is non-empty.
|
|
116
173
|
unmetDeps: Names of this feature's direct dependencies that are not yet
|
|
117
|
-
complete-for-orchestration
|
|
174
|
+
complete-for-orchestration. Empty when actionable or complete.
|
|
118
175
|
openEpicChangeRequests: Count of this member's ``epicChangeRequests``
|
|
119
176
|
entries with ``status == "open"`` — epic-level change requests raised
|
|
120
177
|
by a member stage that forge-0-epic edit mode has not yet reconciled.
|
|
@@ -134,30 +191,35 @@ class FeatureStatus(TypedDict):
|
|
|
134
191
|
|
|
135
192
|
|
|
136
193
|
class Rollup(TypedDict):
|
|
137
|
-
"""Aggregate completion counts for the epic dashboard
|
|
194
|
+
"""Aggregate completion counts for the epic dashboard."""
|
|
138
195
|
|
|
139
|
-
complete: int #: Number of member features complete-for-orchestration
|
|
196
|
+
complete: int #: Number of member features complete-for-orchestration.
|
|
140
197
|
total: int #: Total member features in the manifest (0 for an empty epic).
|
|
141
198
|
|
|
142
199
|
|
|
143
200
|
class RenderStatus(TypedDict):
|
|
144
|
-
"""The full live dashboard payload returned by render_status
|
|
201
|
+
"""The full live dashboard payload returned by render_status.
|
|
145
202
|
|
|
146
203
|
Attributes:
|
|
147
204
|
epic: The epic name (manifest `epic`).
|
|
148
|
-
status: The epic lifecycle status
|
|
205
|
+
status: The epic lifecycle status.
|
|
149
206
|
features: Per-member status rows, one per manifest feature (may be empty).
|
|
150
207
|
actionable: Names of features whose dependsOn are all complete and that
|
|
151
|
-
are not themselves complete
|
|
208
|
+
are not themselves complete.
|
|
152
209
|
parallelEligible: Subset of `actionable` with no mutual (transitive)
|
|
153
|
-
dependency — surfaced for future parallel execution
|
|
210
|
+
dependency — surfaced for future parallel execution.
|
|
154
211
|
rollup: Aggregate {complete, total} counts.
|
|
155
212
|
nextCommand: Recommended next command for the first actionable feature, or
|
|
156
213
|
None when nothing is actionable (all complete, empty epic, or paused).
|
|
157
214
|
warnings: Human-readable diagnostics that do NOT invalidate the graph but
|
|
158
|
-
need surfacing
|
|
159
|
-
outside ``KNOWN_VERIFY_STATUSES`` (treated as
|
|
160
|
-
would poison the rollup + dependency gates,
|
|
215
|
+
need surfacing. Two kinds, in this order: members carrying a
|
|
216
|
+
``forge-verify-*.status`` outside ``KNOWN_VERIFY_STATUSES`` (treated as
|
|
217
|
+
incomplete, but silently so would poison the rollup + dependency gates,
|
|
218
|
+
#148), then OBLIGATION warnings for owed automatic verification — first
|
|
219
|
+
per member in pipeline-stage order, then the epic root.
|
|
220
|
+
The two are deliberately distinct: an unknown status is a corrupt value,
|
|
221
|
+
owed debt is a valid value naming work that has not happened. Empty in
|
|
222
|
+
the common case.
|
|
161
223
|
"""
|
|
162
224
|
|
|
163
225
|
epic: str
|
|
@@ -171,7 +233,7 @@ class RenderStatus(TypedDict):
|
|
|
171
233
|
|
|
172
234
|
|
|
173
235
|
# --------------------------------------------------------------------------- #
|
|
174
|
-
# Internal Exceptions
|
|
236
|
+
# Internal Exceptions
|
|
175
237
|
# --------------------------------------------------------------------------- #
|
|
176
238
|
|
|
177
239
|
|
|
@@ -194,7 +256,7 @@ class UsageError(Exception):
|
|
|
194
256
|
class FindingsError(Exception):
|
|
195
257
|
"""A non-fatal validation outcome that must exit 1.
|
|
196
258
|
|
|
197
|
-
Raised when an operation produces one or more Findings
|
|
259
|
+
Raised when an operation produces one or more Findings that block a
|
|
198
260
|
gating operation: a cycle, a dangling ref, an ambiguous/not-found name, etc.
|
|
199
261
|
Maps to exit code 1. Carries the structured findings so the dispatch layer
|
|
200
262
|
can emit them as JSON or human lines.
|
|
@@ -209,7 +271,7 @@ class FindingsError(Exception):
|
|
|
209
271
|
|
|
210
272
|
|
|
211
273
|
# --------------------------------------------------------------------------- #
|
|
212
|
-
# Safety & I/O Layer
|
|
274
|
+
# Safety & I/O Layer
|
|
213
275
|
# --------------------------------------------------------------------------- #
|
|
214
276
|
|
|
215
277
|
|
|
@@ -228,7 +290,7 @@ def assert_safe_name(name: str) -> None:
|
|
|
228
290
|
equals '..', or fails SAFE_NAME_RE. The message embeds the
|
|
229
291
|
offending name (e.g. ``unsafe name '../escape'``) so the caller can
|
|
230
292
|
surface it verbatim. Corresponds to the 'unsafe-name' Finding code
|
|
231
|
-
|
|
293
|
+
but is raised as a usage error because it is detected before
|
|
232
294
|
any manifest is read.
|
|
233
295
|
"""
|
|
234
296
|
if (
|
|
@@ -263,7 +325,7 @@ def contained_path(base: Path, *parts: str) -> Path:
|
|
|
263
325
|
UsageError: If the resolved path escapes ``base`` (message:
|
|
264
326
|
``resolved path escapes specs dir: …``). Containment violations
|
|
265
327
|
surface only as exit-2 usage errors per the error model in
|
|
266
|
-
|
|
328
|
+
the manifest contract (there is no dedicated Finding code for them).
|
|
267
329
|
"""
|
|
268
330
|
base_real = base.resolve()
|
|
269
331
|
target = (base_real / Path(*parts)).resolve()
|
|
@@ -281,6 +343,12 @@ def load_manifest(epic_dir: Path) -> dict:
|
|
|
281
343
|
epic_dir: The epic subtree directory (must already be contained within
|
|
282
344
|
{specsDir} via contained_path).
|
|
283
345
|
|
|
346
|
+
A legacy manifest written before the canonical ``revision`` field existed is
|
|
347
|
+
presented as logical ``revision: 1`` in the returned dict WITHOUT rewriting the
|
|
348
|
+
file. Legacy validation and rendering therefore keep working, and the
|
|
349
|
+
file's bytes only change on its first genuine semantic mutation — which writes
|
|
350
|
+
``revision: 2`` (REQ-DEBT-06, REQ-COMPAT-02).
|
|
351
|
+
|
|
284
352
|
Returns:
|
|
285
353
|
The parsed manifest as a plain dict. Structural validation (schema,
|
|
286
354
|
cycles, dangling refs) is performed separately by ``validate`` — this
|
|
@@ -289,7 +357,7 @@ def load_manifest(epic_dir: Path) -> dict:
|
|
|
289
357
|
Raises:
|
|
290
358
|
UsageError: If the manifest file is missing or unreadable (exit 2).
|
|
291
359
|
FindingsError: If the file exists but is not parseable JSON — emits a
|
|
292
|
-
single 'corrupt-json' Finding
|
|
360
|
+
single 'corrupt-json' Finding with the JSON error position,
|
|
293
361
|
so a hand-corrupted manifest fails with an actionable message rather
|
|
294
362
|
than a traceback (REQ-ROBUST-02). Exit 1.
|
|
295
363
|
"""
|
|
@@ -301,7 +369,7 @@ def load_manifest(epic_dir: Path) -> dict:
|
|
|
301
369
|
except OSError as exc:
|
|
302
370
|
raise UsageError(f"cannot read manifest {path}: {exc}")
|
|
303
371
|
try:
|
|
304
|
-
|
|
372
|
+
parsed = json.loads(text)
|
|
305
373
|
except json.JSONDecodeError as exc:
|
|
306
374
|
raise FindingsError([
|
|
307
375
|
{
|
|
@@ -310,6 +378,9 @@ def load_manifest(epic_dir: Path) -> dict:
|
|
|
310
378
|
"feature": None,
|
|
311
379
|
}
|
|
312
380
|
])
|
|
381
|
+
if isinstance(parsed, dict) and "revision" not in parsed:
|
|
382
|
+
parsed["revision"] = 1 # synthesized only — never written back here.
|
|
383
|
+
return parsed
|
|
313
384
|
|
|
314
385
|
|
|
315
386
|
def atomic_write(path: Path, data: dict) -> None:
|
|
@@ -358,12 +429,12 @@ def atomic_write(path: Path, data: dict) -> None:
|
|
|
358
429
|
|
|
359
430
|
|
|
360
431
|
# --------------------------------------------------------------------------- #
|
|
361
|
-
# Graph Algorithms
|
|
432
|
+
# Graph Algorithms — implemented in item 004
|
|
362
433
|
# --------------------------------------------------------------------------- #
|
|
363
434
|
|
|
364
435
|
|
|
365
436
|
def find_cycle(features: list[dict]) -> list[str] | None:
|
|
366
|
-
"""Return a cycle in the dependsOn graph, or None if acyclic
|
|
437
|
+
"""Return a cycle in the dependsOn graph, or None if acyclic.
|
|
367
438
|
|
|
368
439
|
Iterative DFS over the directed graph whose edges are ``feature -> dep``.
|
|
369
440
|
On the first back-edge into a GRAY node, reconstructs and returns the cycle
|
|
@@ -414,7 +485,7 @@ def find_cycle(features: list[dict]) -> list[str] | None:
|
|
|
414
485
|
def unmet_deps(
|
|
415
486
|
name: str, features: list[dict], complete: dict[str, bool]
|
|
416
487
|
) -> list[str]:
|
|
417
|
-
"""Return a feature's direct dependencies that are not complete
|
|
488
|
+
"""Return a feature's direct dependencies that are not complete.
|
|
418
489
|
|
|
419
490
|
Names of this feature's direct ``dependsOn`` entries whose value in
|
|
420
491
|
``complete`` is False, preserving manifest order. Empty when the feature is
|
|
@@ -426,15 +497,15 @@ def unmet_deps(
|
|
|
426
497
|
|
|
427
498
|
|
|
428
499
|
# --------------------------------------------------------------------------- #
|
|
429
|
-
# Resolution & Uniqueness
|
|
500
|
+
# Resolution & Uniqueness — implemented in item 005
|
|
430
501
|
# --------------------------------------------------------------------------- #
|
|
431
502
|
|
|
432
503
|
|
|
433
504
|
def feature_dirs(specs_dir: Path) -> dict[str, list[Path]]:
|
|
434
|
-
"""Map every feature name in the specs tree to the dirs that bear it
|
|
505
|
+
"""Map every feature name in the specs tree to the dirs that bear it.
|
|
435
506
|
|
|
436
507
|
Scans both layouts to a fixed depth, treating a directory as a feature iff
|
|
437
|
-
it directly contains a ``.pipeline-state.json`` (
|
|
508
|
+
it directly contains a ``.pipeline-state.json`` (REQ-DIR-03):
|
|
438
509
|
* flat: {specsDir}/{name}/.pipeline-state.json
|
|
439
510
|
* nested: {specsDir}/{epic}/{name}/.pipeline-state.json
|
|
440
511
|
|
|
@@ -442,7 +513,7 @@ def feature_dirs(specs_dir: Path) -> dict[str, list[Path]]:
|
|
|
442
513
|
entry is a uniqueness violation (REQ-DIR-04) surfaced as 'ambiguous' or
|
|
443
514
|
'duplicate-name' by the caller. Epic directories themselves (which hold
|
|
444
515
|
``epic-manifest.json`` but no ``.pipeline-state.json``) are skipped, so an
|
|
445
|
-
epic name never collides with a feature name
|
|
516
|
+
epic name never collides with a feature name.
|
|
446
517
|
|
|
447
518
|
Args:
|
|
448
519
|
specs_dir: The configured specs directory (already verified to exist).
|
|
@@ -465,9 +536,9 @@ def feature_dirs(specs_dir: Path) -> dict[str, list[Path]]:
|
|
|
465
536
|
|
|
466
537
|
|
|
467
538
|
def resolve(name: str, specs_dir: Path) -> Path:
|
|
468
|
-
"""Resolve a bare feature/epic name to its absolute directory
|
|
539
|
+
"""Resolve a bare feature/epic name to its absolute directory.
|
|
469
540
|
|
|
470
|
-
Implements the 5-step algorithm
|
|
541
|
+
Implements the 5-step algorithm:
|
|
471
542
|
1. reject unsafe names (assert_safe_name) — exit 2 before any FS access;
|
|
472
543
|
2. flat match: {specsDir}/{name}/.pipeline-state.json wins outright;
|
|
473
544
|
3. exactly one nested match resolves cleanly;
|
|
@@ -487,7 +558,7 @@ def resolve(name: str, specs_dir: Path) -> Path:
|
|
|
487
558
|
Raises:
|
|
488
559
|
UsageError: Unsafe name or missing specs dir (exit 2).
|
|
489
560
|
FindingsError: 'ambiguous' (lists every matching path) or 'not-found'
|
|
490
|
-
(exit 1).
|
|
561
|
+
(exit 1).
|
|
491
562
|
"""
|
|
492
563
|
assert_safe_name(name)
|
|
493
564
|
if not specs_dir.is_dir():
|
|
@@ -515,10 +586,10 @@ def resolve(name: str, specs_dir: Path) -> Path:
|
|
|
515
586
|
|
|
516
587
|
|
|
517
588
|
def check_name(name: str, specs_dir: Path) -> list[Finding]:
|
|
518
|
-
"""Return a duplicate-name finding if the name is already taken
|
|
589
|
+
"""Return a duplicate-name finding if the name is already taken.
|
|
519
590
|
|
|
520
591
|
Used by forge-0-epic before creating a new member feature so no NEW global
|
|
521
|
-
name collision can be introduced (REQ-DIR-04
|
|
592
|
+
name collision can be introduced (REQ-DIR-04). Any single
|
|
522
593
|
existing occurrence is enough to reject — unlike ``resolve``, which tolerates
|
|
523
594
|
a uniquely-matching name and only errors on genuine multi-match.
|
|
524
595
|
|
|
@@ -551,43 +622,44 @@ def check_name(name: str, specs_dir: Path) -> list[Finding]:
|
|
|
551
622
|
|
|
552
623
|
|
|
553
624
|
# --------------------------------------------------------------------------- #
|
|
554
|
-
# Validation
|
|
625
|
+
# Validation — implemented in item 006
|
|
555
626
|
# --------------------------------------------------------------------------- #
|
|
556
627
|
|
|
557
628
|
|
|
558
|
-
#: Top-level required keys (
|
|
629
|
+
#: Top-level required keys (mirrors epic-manifest-schema.json). Doubles as
|
|
630
|
+
#: the allow-list for the unknown-top-level-key check below.
|
|
559
631
|
_TOP_REQUIRED: Final = (
|
|
560
|
-
"schemaVersion", "epic", "description", "status",
|
|
632
|
+
"schemaVersion", "revision", "epic", "description", "status",
|
|
561
633
|
"narrativeDoc", "createdAt", "updatedAt", "features",
|
|
562
634
|
)
|
|
563
|
-
#: Required keys on each Feature object
|
|
635
|
+
#: Required keys on each Feature object.
|
|
564
636
|
_FEATURE_REQUIRED: Final = ("name", "charter", "dependsOn", "exposes", "consumes")
|
|
565
637
|
#: Optional keys on each Feature object. `mutatesShared` is the #144 precision
|
|
566
638
|
#: hint for cross-member coupling (array of project-root-relative path strings);
|
|
567
639
|
#: schema-legal when present, ignored when absent (mirrors
|
|
568
640
|
#: epic-manifest-schema.json definitions.feature.properties.mutatesShared).
|
|
569
641
|
_FEATURE_OPTIONAL: Final = ("mutatesShared",)
|
|
570
|
-
#: Required keys on each Contract (exposes[]) object
|
|
642
|
+
#: Required keys on each Contract (exposes[]) object.
|
|
571
643
|
_CONTRACT_REQUIRED: Final = ("name", "kind", "summary")
|
|
572
|
-
#: Required keys on each ConsumedContract (consumes[]) object
|
|
644
|
+
#: Required keys on each ConsumedContract (consumes[]) object.
|
|
573
645
|
_CONSUMED_REQUIRED: Final = ("from", "name", "summary")
|
|
574
|
-
#: Allowed epic lifecycle states
|
|
646
|
+
#: Allowed epic lifecycle states.
|
|
575
647
|
_EPIC_STATUSES: Final = ("active", "paused", "abandoned", "complete")
|
|
576
|
-
#: Allowed Contract kinds
|
|
648
|
+
#: Allowed Contract kinds.
|
|
577
649
|
_CONTRACT_KINDS: Final = ("function", "type", "endpoint", "module", "event")
|
|
578
650
|
|
|
579
651
|
|
|
580
652
|
def _schema(message: str, feature: str | None = None) -> Finding:
|
|
581
|
-
"""Construct a 'schema' Finding
|
|
653
|
+
"""Construct a 'schema' Finding."""
|
|
582
654
|
return {"code": "schema", "message": message, "feature": feature}
|
|
583
655
|
|
|
584
656
|
|
|
585
657
|
def _schema_findings(manifest: dict) -> list[Finding]:
|
|
586
|
-
"""Hand-rolled stdlib schema checker over the manifest
|
|
658
|
+
"""Hand-rolled stdlib schema checker over the manifest.
|
|
587
659
|
|
|
588
|
-
Asserts required keys/types/enums/consts
|
|
660
|
+
Asserts the required keys/types/enums/consts and explicitly rejects
|
|
589
661
|
any ``features[].status`` key (REQ-STATE-02 -> 'cached-status'). No
|
|
590
|
-
third-party ``jsonschema
|
|
662
|
+
third-party ``jsonschema``. Returns 'schema' findings plus, for a
|
|
591
663
|
per-feature status key, a 'cached-status' finding.
|
|
592
664
|
"""
|
|
593
665
|
findings: list[Finding] = []
|
|
@@ -600,6 +672,12 @@ def _schema_findings(manifest: dict) -> list[Finding]:
|
|
|
600
672
|
|
|
601
673
|
if "schemaVersion" in manifest and manifest["schemaVersion"] != 1:
|
|
602
674
|
findings.append(_schema(f"schemaVersion must be 1, got {manifest['schemaVersion']!r}"))
|
|
675
|
+
if "revision" in manifest:
|
|
676
|
+
revision = manifest["revision"]
|
|
677
|
+
# `bool` is a subclass of `int`, so `True` would otherwise pass as revision 1
|
|
678
|
+
# and then silently arithmetic-increment to 2.
|
|
679
|
+
if isinstance(revision, bool) or not isinstance(revision, int) or revision < 1:
|
|
680
|
+
findings.append(_schema(f"revision must be an integer >= 1, got {revision!r}"))
|
|
603
681
|
if "narrativeDoc" in manifest and manifest["narrativeDoc"] != NARRATIVE_FILENAME:
|
|
604
682
|
findings.append(_schema(f"narrativeDoc must be {NARRATIVE_FILENAME!r}, got {manifest['narrativeDoc']!r}")) # noqa: E501
|
|
605
683
|
for key in ("epic", "description", "createdAt", "updatedAt"):
|
|
@@ -683,9 +761,9 @@ def _schema_findings(manifest: dict) -> list[Finding]:
|
|
|
683
761
|
def _validate_dict(
|
|
684
762
|
manifest: dict, epic_dir: Path, specs_dir: Path
|
|
685
763
|
) -> list[Finding]:
|
|
686
|
-
"""Validate an already-parsed manifest dict, returning findings
|
|
764
|
+
"""Validate an already-parsed manifest dict, returning findings.
|
|
687
765
|
|
|
688
|
-
Runs the invariant checks
|
|
766
|
+
Runs the invariant checks in order, short-circuiting only where a
|
|
689
767
|
later check cannot run. Reused by the item-008 mutators on the EDITED dict
|
|
690
768
|
before writing. Does not parse JSON (that is ``validate``'s job) — operates
|
|
691
769
|
purely in memory.
|
|
@@ -773,7 +851,7 @@ def _validate_dict(
|
|
|
773
851
|
|
|
774
852
|
|
|
775
853
|
def validate(epic_dir: Path, specs_dir: Path) -> list[Finding]:
|
|
776
|
-
"""Validate a single epic manifest, returning all findings
|
|
854
|
+
"""Validate a single epic manifest, returning all findings.
|
|
777
855
|
|
|
778
856
|
Parses the manifest (folding any corrupt-json finding from load_manifest
|
|
779
857
|
into the returned list) then delegates to ``_validate_dict``. Raises
|
|
@@ -787,12 +865,12 @@ def validate(epic_dir: Path, specs_dir: Path) -> list[Finding]:
|
|
|
787
865
|
|
|
788
866
|
|
|
789
867
|
# --------------------------------------------------------------------------- #
|
|
790
|
-
# Live Status Derivation
|
|
868
|
+
# Live Status Derivation — implemented in item 007
|
|
791
869
|
# --------------------------------------------------------------------------- #
|
|
792
870
|
|
|
793
871
|
|
|
794
872
|
def is_complete_for_orchestration(state: dict) -> bool:
|
|
795
|
-
"""Apply the completion-for-orchestration predicate
|
|
873
|
+
"""Apply the completion-for-orchestration predicate.
|
|
796
874
|
|
|
797
875
|
A feature is complete-for-orchestration iff::
|
|
798
876
|
|
|
@@ -802,8 +880,7 @@ def is_complete_for_orchestration(state: dict) -> bool:
|
|
|
802
880
|
|
|
803
881
|
A feature whose forge-verify-impl is 'findings-reported' (unfixed) is NOT
|
|
804
882
|
complete and does NOT unblock dependents (REQ-ORCH-01). This is the single
|
|
805
|
-
implementation of the predicate, reused by the dependency gate and handoff
|
|
806
|
-
(04-pipeline-integration.md).
|
|
883
|
+
implementation of the predicate, reused by the dependency gate and handoff.
|
|
807
884
|
|
|
808
885
|
Args:
|
|
809
886
|
state: A parsed .pipeline-state.json dict (or {} if the member has none).
|
|
@@ -836,6 +913,11 @@ def _verify_status_warnings(name: str, state: dict) -> list[str]:
|
|
|
836
913
|
|
|
837
914
|
Non-string / malformed status values (a list, an int) are also flagged, and the
|
|
838
915
|
membership test is guarded so an unhashable value never raises.
|
|
916
|
+
|
|
917
|
+
``auto-verify-pending`` is a KNOWN status and is therefore silent here.
|
|
918
|
+
Owed automatic verification is an OBLIGATION, not a corrupt value, so it is
|
|
919
|
+
surfaced separately by ``_auto_verify_debt_warnings`` with actionable wording —
|
|
920
|
+
reporting it as an unknown status would be actively misleading.
|
|
839
921
|
"""
|
|
840
922
|
stages = state.get("stages", {})
|
|
841
923
|
if not isinstance(stages, dict):
|
|
@@ -859,11 +941,209 @@ def _verify_status_warnings(name: str, state: dict) -> list[str]:
|
|
|
859
941
|
return warnings
|
|
860
942
|
|
|
861
943
|
|
|
944
|
+
def _positive_int(value: object) -> int | None:
|
|
945
|
+
"""Return ``value`` when it is a usable artifact revision, else None.
|
|
946
|
+
|
|
947
|
+
A bool is rejected before the int test on purpose: ``True`` is an ``int`` and
|
|
948
|
+
would otherwise compare equal to revision 1 (the same trap the manifest
|
|
949
|
+
``revision`` validator guards). Absent, non-integer, and sub-1 values are
|
|
950
|
+
unusable — the caller keeps the debt owed rather than guessing.
|
|
951
|
+
"""
|
|
952
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
|
|
953
|
+
return None
|
|
954
|
+
return value
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def _auto_pending_message(
|
|
958
|
+
subject: str,
|
|
959
|
+
stage: str,
|
|
960
|
+
command: str,
|
|
961
|
+
scheduled_version: int | None = None,
|
|
962
|
+
current_version: int | None = None,
|
|
963
|
+
) -> str:
|
|
964
|
+
"""Render the obligation sentence for owed automatic verification.
|
|
965
|
+
|
|
966
|
+
Mirrors ``auto_pending_message`` in forge-session.py — one sentence naming the
|
|
967
|
+
subject, the served stage, and the retry command, with both revision numbers
|
|
968
|
+
appended when the recorded schedule predates the current artifact. Never a
|
|
969
|
+
state-file dump (REQ-OBS-02).
|
|
970
|
+
"""
|
|
971
|
+
message = AUTO_PENDING_DIAGNOSTIC.format(
|
|
972
|
+
subject=subject, stage=stage, command=command
|
|
973
|
+
)
|
|
974
|
+
if (
|
|
975
|
+
scheduled_version is not None
|
|
976
|
+
and current_version is not None
|
|
977
|
+
and scheduled_version != current_version
|
|
978
|
+
):
|
|
979
|
+
message += (
|
|
980
|
+
f" The artifact has advanced since it was scheduled "
|
|
981
|
+
f"(scheduled at revision {scheduled_version}, now at revision "
|
|
982
|
+
f"{current_version})."
|
|
983
|
+
)
|
|
984
|
+
return message
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def _auto_verify_debt_warnings(name: str, state: dict) -> list[str]:
|
|
988
|
+
"""Surface every ``auto-verify-pending`` member entry as an obligation.
|
|
989
|
+
|
|
990
|
+
A member carrying recorded-but-undischarged automatic verification is neither
|
|
991
|
+
"never verified" nor done: the directive that should have run it was dropped,
|
|
992
|
+
crashed, or was interrupted, and the epic dashboard is where an operator notices
|
|
993
|
+
(#163, REQ-DEBT-02/05). Emitted in pipeline-stage order — not the state file's key
|
|
994
|
+
order — so repeated renders are byte-identical.
|
|
995
|
+
|
|
996
|
+
Args:
|
|
997
|
+
name: The member feature name.
|
|
998
|
+
state: The member's parsed .pipeline-state.json (or {}).
|
|
999
|
+
|
|
1000
|
+
Returns:
|
|
1001
|
+
Zero or more sentences naming the member, the served production
|
|
1002
|
+
stage, and the ``forge-verify`` retry command.
|
|
1003
|
+
"""
|
|
1004
|
+
stages = state.get("stages", {})
|
|
1005
|
+
if not isinstance(stages, dict):
|
|
1006
|
+
return []
|
|
1007
|
+
warnings: list[str] = []
|
|
1008
|
+
for token, served_stage in _VERIFY_STAGE_BY_TOKEN.items():
|
|
1009
|
+
entry = stages.get(f"forge-verify-{token}")
|
|
1010
|
+
if not isinstance(entry, dict) or entry.get("status") != "auto-verify-pending":
|
|
1011
|
+
continue
|
|
1012
|
+
production = stages.get(served_stage)
|
|
1013
|
+
warnings.append(_auto_pending_message(
|
|
1014
|
+
name,
|
|
1015
|
+
served_stage,
|
|
1016
|
+
f"/feature-forge:forge-verify {name}",
|
|
1017
|
+
_positive_int(entry.get("scheduledStageVersion")),
|
|
1018
|
+
_positive_int(production.get("version")) if isinstance(production, dict) else None,
|
|
1019
|
+
))
|
|
1020
|
+
return warnings
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
def _read_epic_state_safely(epic_dir: Path) -> dict:
|
|
1024
|
+
"""Read an epic's own ``.epic-state.json``, tolerating absence and corruption.
|
|
1025
|
+
|
|
1026
|
+
Epic verification state is epic-scoped: this reads the sibling of the manifest
|
|
1027
|
+
and NEVER a member's ``.pipeline-state.json`` (REQ-SEC-01). A missing, unreadable,
|
|
1028
|
+
unparseable, or non-object file downgrades to ``{}``, which classifies as
|
|
1029
|
+
``never`` — the dashboard must not crash on one torn file.
|
|
1030
|
+
"""
|
|
1031
|
+
path = epic_dir / EPIC_STATE_FILENAME
|
|
1032
|
+
if not path.is_file():
|
|
1033
|
+
return {}
|
|
1034
|
+
try:
|
|
1035
|
+
parsed = json.loads(path.read_text(encoding="utf-8"))
|
|
1036
|
+
except (OSError, json.JSONDecodeError):
|
|
1037
|
+
return {}
|
|
1038
|
+
return parsed if isinstance(parsed, dict) else {}
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def epic_verify_state(epic_dir: Path, revision: int | None) -> str:
|
|
1042
|
+
"""Classify epic-root verification freshness against the manifest revision.
|
|
1043
|
+
|
|
1044
|
+
The epic mirror of ``verify_state`` in forge-session.py, with the manifest
|
|
1045
|
+
``revision`` standing in for a production stage's ``version``. It reads
|
|
1046
|
+
``.epic-state.json`` and the supplied revision ONLY — no member state participates
|
|
1047
|
+
in epic verification (REQ-SEC-01).
|
|
1048
|
+
|
|
1049
|
+
Args:
|
|
1050
|
+
epic_dir: The epic subtree directory.
|
|
1051
|
+
revision: The manifest's canonical revision, or None when unusable.
|
|
1052
|
+
|
|
1053
|
+
Returns:
|
|
1054
|
+
One of:
|
|
1055
|
+
|
|
1056
|
+
- ``never`` — no epic state, no ``forge-verify-epic`` entry, or a
|
|
1057
|
+
status outside the known vocabulary. Verification was never scheduled.
|
|
1058
|
+
- ``auto-pending``— ``auto-verify-pending``: scheduled and owed. Classified
|
|
1059
|
+
ahead of every other rule and never downgraded to ``never``, even when its
|
|
1060
|
+
``scheduledStageVersion`` is missing or names an older revision — a later
|
|
1061
|
+
manifest edit does not erase owed work (REQ-DEBT-02).
|
|
1062
|
+
- ``failing`` — ``findings-reported``: verification ran and its findings
|
|
1063
|
+
are not yet applied.
|
|
1064
|
+
- ``skipped`` — an explicit human decision, resolved under the existing
|
|
1065
|
+
compatibility rule: it records no version and so never goes stale.
|
|
1066
|
+
- ``fresh`` — ``passed`` whose ``verifiedStageVersion`` equals the
|
|
1067
|
+
current manifest revision.
|
|
1068
|
+
- ``stale`` — a resolved terminal entry whose recorded revision is
|
|
1069
|
+
absent or does not match. ``findings-applied`` is classified here
|
|
1070
|
+
UNCONDITIONALLY, not merely because the writer deletes
|
|
1071
|
+
``verifiedStageVersion``: applying fixes is not verifying them, and legacy
|
|
1072
|
+
state loaded without migration (REQ-DEBT-06) may still carry the key.
|
|
1073
|
+
"""
|
|
1074
|
+
return _classify_epic_verify_entry(_read_epic_verify_entry(epic_dir), revision)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def _read_epic_verify_entry(epic_dir: Path) -> dict:
|
|
1078
|
+
"""The ``forge-verify-epic`` entry out of ``.epic-state.json``, or ``{}``.
|
|
1079
|
+
|
|
1080
|
+
The single read both the classifier and the warning renderer share: a caller
|
|
1081
|
+
that needs the label AND the entry's metadata reads once and passes the same
|
|
1082
|
+
dict to both, so a file rewritten between two reads can never classify against
|
|
1083
|
+
one snapshot and index into another.
|
|
1084
|
+
"""
|
|
1085
|
+
stages = _read_epic_state_safely(epic_dir).get("stages", {})
|
|
1086
|
+
entry = stages.get(EPIC_VERIFY_KEY) if isinstance(stages, dict) else None
|
|
1087
|
+
return entry if isinstance(entry, dict) else {}
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def _classify_epic_verify_entry(entry: dict, revision: int | None) -> str:
|
|
1091
|
+
"""The classification half of ``epic_verify_state``, over an already-read entry."""
|
|
1092
|
+
if not entry:
|
|
1093
|
+
return "never"
|
|
1094
|
+
status = entry.get("status")
|
|
1095
|
+
if not isinstance(status, str):
|
|
1096
|
+
# A torn or hand-edited entry can carry any JSON type here; an unhashable
|
|
1097
|
+
# one would raise TypeError at the frozenset membership below, crashing
|
|
1098
|
+
# the whole dashboard on one bad file.
|
|
1099
|
+
return "never"
|
|
1100
|
+
if status == "auto-verify-pending":
|
|
1101
|
+
return "auto-pending"
|
|
1102
|
+
if status == "findings-reported":
|
|
1103
|
+
return "failing"
|
|
1104
|
+
if status == "skipped":
|
|
1105
|
+
return "skipped"
|
|
1106
|
+
if status not in _VERIFY_RESOLVED:
|
|
1107
|
+
return "never"
|
|
1108
|
+
if status == "findings-applied":
|
|
1109
|
+
# §4.2 step 4: applying fixes CLEARS freshness; only a later `passed` restores
|
|
1110
|
+
# it. Mirrors the identical guards in forge-session.py's `verify_state` and
|
|
1111
|
+
# `_classify_verify_entry` — §5.1 requires identical labels across all three,
|
|
1112
|
+
# and §5.2 requires manifest parity, so a partial fix is itself the drift
|
|
1113
|
+
# `test_stage_constants_parity.py` exists to catch.
|
|
1114
|
+
return "stale"
|
|
1115
|
+
verified = _positive_int(entry.get("verifiedStageVersion"))
|
|
1116
|
+
if verified is not None and revision is not None and verified == revision:
|
|
1117
|
+
return "fresh"
|
|
1118
|
+
return "stale"
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
def _epic_verify_warnings(epic: str, epic_dir: Path, revision: int | None) -> list[str]:
|
|
1122
|
+
"""Surface owed epic-root automatic verification as an obligation.
|
|
1123
|
+
|
|
1124
|
+
Only ``auto-pending`` warrants a dashboard warning: ``never`` is the ordinary
|
|
1125
|
+
state of an epic nobody has verified, and ``stale``/``failing`` are already
|
|
1126
|
+
reachable through the epic's own verify run. Owed-and-dropped debt is the case
|
|
1127
|
+
that is otherwise invisible.
|
|
1128
|
+
"""
|
|
1129
|
+
entry = _read_epic_verify_entry(epic_dir)
|
|
1130
|
+
if _classify_epic_verify_entry(entry, revision) != "auto-pending":
|
|
1131
|
+
return []
|
|
1132
|
+
scheduled = _positive_int(entry.get("scheduledStageVersion"))
|
|
1133
|
+
return [_auto_pending_message(
|
|
1134
|
+
epic,
|
|
1135
|
+
EPIC_VERIFY_STAGE,
|
|
1136
|
+
f"/feature-forge:forge-verify {epic}",
|
|
1137
|
+
scheduled,
|
|
1138
|
+
revision,
|
|
1139
|
+
)]
|
|
1140
|
+
|
|
1141
|
+
|
|
862
1142
|
def _read_state_safely(state_path: Path) -> dict:
|
|
863
1143
|
"""Read and parse a member's .pipeline-state.json, tolerating corruption.
|
|
864
1144
|
|
|
865
1145
|
A missing, unreadable, unparseable, or torn (partially-written) member state
|
|
866
|
-
downgrades to ``{}`` rather than crashing the dashboard
|
|
1146
|
+
downgrades to ``{}`` rather than crashing the dashboard. Member
|
|
867
1147
|
state writes are made by forge-1..5 skills outside the helper's atomicity
|
|
868
1148
|
scope, so a torn read is expected and simply renders that one feature as
|
|
869
1149
|
``not-started``.
|
|
@@ -878,7 +1158,7 @@ def _read_state_safely(state_path: Path) -> dict:
|
|
|
878
1158
|
|
|
879
1159
|
|
|
880
1160
|
def derive_status(feature_dir: Path) -> FeatureStatus:
|
|
881
|
-
"""Derive a feature's live status from its own pipeline state
|
|
1161
|
+
"""Derive a feature's live status from its own pipeline state.
|
|
882
1162
|
|
|
883
1163
|
Reads ``{feature_dir}/.pipeline-state.json`` and maps it to a FeatureStatus:
|
|
884
1164
|
missing/unparseable/all-pending -> ``not-started``; complete-for-
|
|
@@ -891,7 +1171,7 @@ def derive_status(feature_dir: Path) -> FeatureStatus:
|
|
|
891
1171
|
feature_dir: The member feature's directory.
|
|
892
1172
|
|
|
893
1173
|
Returns:
|
|
894
|
-
A FeatureStatus
|
|
1174
|
+
A FeatureStatus with name, stage, coarse status, and placeholder
|
|
895
1175
|
blocked/unmetDeps.
|
|
896
1176
|
"""
|
|
897
1177
|
name = feature_dir.name
|
|
@@ -934,7 +1214,7 @@ def derive_status(feature_dir: Path) -> FeatureStatus:
|
|
|
934
1214
|
|
|
935
1215
|
|
|
936
1216
|
def _transitive_deps(name: str, adjacency: dict[str, list[str]]) -> set[str]:
|
|
937
|
-
"""Return all features reachable from ``name`` via dependsOn edges
|
|
1217
|
+
"""Return all features reachable from ``name`` via dependsOn edges."""
|
|
938
1218
|
seen: set[str] = set()
|
|
939
1219
|
stack = list(adjacency.get(name, []))
|
|
940
1220
|
while stack:
|
|
@@ -946,23 +1226,61 @@ def _transitive_deps(name: str, adjacency: dict[str, list[str]]) -> set[str]:
|
|
|
946
1226
|
return seen
|
|
947
1227
|
|
|
948
1228
|
|
|
949
|
-
def
|
|
950
|
-
"""
|
|
1229
|
+
def _next_production_stage(state: dict) -> str | None:
|
|
1230
|
+
"""Return the first production stage that is not yet complete, else None.
|
|
951
1231
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1232
|
+
The epic-side mirror of ``next_stage()`` in forge-session.py, and for the same
|
|
1233
|
+
reason its docstring gives: "what runs next" is DERIVED from ``stages[].status``,
|
|
1234
|
+
never read from the stored ``currentStage``. A missing, pending, in-progress or
|
|
1235
|
+
stale stage all count as "not done".
|
|
1236
|
+
"""
|
|
1237
|
+
stages = state.get("stages")
|
|
1238
|
+
if not isinstance(stages, dict):
|
|
1239
|
+
return _PRODUCTION_STAGES[0]
|
|
1240
|
+
for stage in _PRODUCTION_STAGES:
|
|
1241
|
+
entry = stages.get(stage)
|
|
1242
|
+
if not isinstance(entry, dict) or entry.get("status") != "complete":
|
|
1243
|
+
return stage
|
|
1244
|
+
return None
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
def _next_command(feature_dir: Path, status_row: FeatureStatus) -> str:
|
|
1248
|
+
"""Recommend the next forge command for an actionable feature.
|
|
1249
|
+
|
|
1250
|
+
``/feature-forge:forge-1-prd <name>`` when the feature's PRD is absent (or the
|
|
1251
|
+
member has not progressed past epic creation), else the command for its next
|
|
1252
|
+
un-run production stage — DERIVED from ``stages[].status``, not read from
|
|
1253
|
+
``currentStage`` (item 020: ``currentStage`` is "where the pipeline IS", so
|
|
1254
|
+
reading it here recommended re-running the stage the member had just finished
|
|
1255
|
+
for the whole window before the next stage was entered).
|
|
1256
|
+
|
|
1257
|
+
When every production stage is complete but the member is still actionable, its
|
|
1258
|
+
``forge-verify-impl`` entry is what holds it back, and the two cases need
|
|
1259
|
+
different commands: ``findings-reported`` means a report exists and is
|
|
1260
|
+
unapplied, so ``forge-fix`` is accurate; anything else outstanding — notably
|
|
1261
|
+
``auto-verify-pending``, where the scheduled run never happened — has nothing to
|
|
1262
|
+
fix and needs the verification itself, so ``forge-verify`` is. Recommending
|
|
1263
|
+
``forge-fix`` for owed automatic debt sends the operator looking for a findings
|
|
1264
|
+
document that was never written (REQ-DEBT-02/05).
|
|
955
1265
|
"""
|
|
956
1266
|
name = status_row["name"]
|
|
957
|
-
|
|
1267
|
+
state = _read_state_safely(feature_dir / PIPELINE_STATE_FILENAME)
|
|
1268
|
+
nxt = _next_production_stage(state)
|
|
958
1269
|
prd_present = (feature_dir / "PRD.md").is_file()
|
|
959
|
-
if not prd_present or
|
|
1270
|
+
if not prd_present or nxt == "forge-1-prd":
|
|
960
1271
|
return f"/feature-forge:forge-1-prd {name}"
|
|
961
|
-
|
|
1272
|
+
if nxt is None:
|
|
1273
|
+
stages = state.get("stages")
|
|
1274
|
+
impl = stages.get("forge-verify-impl") if isinstance(stages, dict) else None
|
|
1275
|
+
status = impl.get("status") if isinstance(impl, dict) else None
|
|
1276
|
+
if status == "findings-reported":
|
|
1277
|
+
return f"/feature-forge:forge-fix {name}"
|
|
1278
|
+
return f"/feature-forge:forge-verify {name}"
|
|
1279
|
+
return f"/feature-forge:{nxt} {name}"
|
|
962
1280
|
|
|
963
1281
|
|
|
964
1282
|
def render_status(epic_dir: Path, specs_dir: Path) -> RenderStatus:
|
|
965
|
-
"""Build the full live dashboard payload for an epic
|
|
1283
|
+
"""Build the full live dashboard payload for an epic.
|
|
966
1284
|
|
|
967
1285
|
Validates first (refusing to render over an invalid graph), then derives each
|
|
968
1286
|
member's live status from its own state file, computes blocked/unmetDeps,
|
|
@@ -973,7 +1291,7 @@ def render_status(epic_dir: Path, specs_dir: Path) -> RenderStatus:
|
|
|
973
1291
|
specs_dir: The configured specs directory.
|
|
974
1292
|
|
|
975
1293
|
Returns:
|
|
976
|
-
The RenderStatus dict
|
|
1294
|
+
The RenderStatus dict.
|
|
977
1295
|
|
|
978
1296
|
Raises:
|
|
979
1297
|
UsageError: Missing/unreadable manifest (exit 2).
|
|
@@ -1000,6 +1318,15 @@ def render_status(epic_dir: Path, specs_dir: Path) -> RenderStatus:
|
|
|
1000
1318
|
member_state = _read_state_safely(member_dir / PIPELINE_STATE_FILENAME)
|
|
1001
1319
|
complete[name] = is_complete_for_orchestration(member_state)
|
|
1002
1320
|
warnings.extend(_verify_status_warnings(name, member_state))
|
|
1321
|
+
warnings.extend(_auto_verify_debt_warnings(name, member_state))
|
|
1322
|
+
|
|
1323
|
+
# (3b) epic-root verification debt, classified against the manifest revision and
|
|
1324
|
+
# read from .epic-state.json alone — no member state participates.
|
|
1325
|
+
warnings.extend(_epic_verify_warnings(
|
|
1326
|
+
manifest.get("epic", epic_dir.name),
|
|
1327
|
+
epic_dir,
|
|
1328
|
+
_positive_int(manifest.get("revision")),
|
|
1329
|
+
))
|
|
1003
1330
|
|
|
1004
1331
|
# (4) per-feature unmetDeps + blocked. A feature that is itself complete is
|
|
1005
1332
|
# never "blocked" — unmet deps only matter for work not yet finished.
|
|
@@ -1059,21 +1386,47 @@ def render_status(epic_dir: Path, specs_dir: Path) -> RenderStatus:
|
|
|
1059
1386
|
|
|
1060
1387
|
|
|
1061
1388
|
# --------------------------------------------------------------------------- #
|
|
1062
|
-
# Mutators
|
|
1389
|
+
# Mutators — implemented in item 008
|
|
1063
1390
|
# --------------------------------------------------------------------------- #
|
|
1064
1391
|
|
|
1065
1392
|
|
|
1393
|
+
def _semantic_manifest(manifest: dict) -> dict:
|
|
1394
|
+
"""Return a manifest copy carrying only its semantic fields.
|
|
1395
|
+
|
|
1396
|
+
Drops exactly the two bookkeeping fields a mutation is allowed to change on its
|
|
1397
|
+
own: ``updatedAt`` (a timestamp) and ``revision`` (the counter this function's
|
|
1398
|
+
caller maintains, and which ``load_manifest`` synthesizes for legacy files). What
|
|
1399
|
+
remains is the content an epic's verification freshness is actually about.
|
|
1400
|
+
"""
|
|
1401
|
+
return {k: v for k, v in manifest.items() if k not in ("updatedAt", "revision")}
|
|
1402
|
+
|
|
1403
|
+
|
|
1066
1404
|
def _bump_and_write(
|
|
1067
1405
|
epic_dir: Path, specs_dir: Path, manifest: dict
|
|
1068
1406
|
) -> list[Finding]:
|
|
1069
|
-
"""Re-validate, bump updatedAt, and atomically persist
|
|
1070
|
-
|
|
1071
|
-
The shared tail of every mutator (REQ-ROBUST-03, REQ-OBS-01, REQ-EPIC-05
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1407
|
+
"""Re-validate, bump revision + updatedAt, and atomically persist.
|
|
1408
|
+
|
|
1409
|
+
The shared tail of every mutator (REQ-ROBUST-03, REQ-OBS-01, REQ-EPIC-05,
|
|
1410
|
+
REQ-REL-01) and the SINGLE place ``revision`` is incremented — no mutator bumps
|
|
1411
|
+
it itself, so every successful mutation advances it exactly once.
|
|
1412
|
+
|
|
1413
|
+
Order of operations:
|
|
1414
|
+
|
|
1415
|
+
1. Re-run ``_validate_dict`` on the EDITED manifest; if any blocking finding is
|
|
1416
|
+
present (cycle, dangling-ref, duplicate-name, schema, ...), the on-disk file
|
|
1417
|
+
is left byte-identical and the findings are returned so the caller exits 1.
|
|
1418
|
+
This runs FIRST, ahead of the no-op comparison, so that EVERY mutator
|
|
1419
|
+
re-validates (REQ-ROBUST-03): a semantically idempotent edit against a
|
|
1420
|
+
manifest that is already invalid on disk must report the same blocking
|
|
1421
|
+
findings a non-idempotent edit would, instead of exiting 0 in silence and
|
|
1422
|
+
letting a caller read that as "the epic is well-formed".
|
|
1423
|
+
2. Compare the proposed manifest with the on-disk one, ignoring only
|
|
1424
|
+
``updatedAt`` and the (possibly synthesized) ``revision``. If every semantic
|
|
1425
|
+
field matches this is a no-op: return ``[]`` WITHOUT writing, so an edit that
|
|
1426
|
+
changes nothing leaves the file byte-identical — including ``updatedAt``.
|
|
1427
|
+
3. Set ``revision`` to ``current + 1`` and ``updatedAt`` to now (UTC, ISO-8601),
|
|
1428
|
+
then write once via ``atomic_write``. A failed write raises, so a torn
|
|
1429
|
+
mutation leaves the previous revision and bytes intact.
|
|
1077
1430
|
|
|
1078
1431
|
Args:
|
|
1079
1432
|
epic_dir: The epic subtree directory.
|
|
@@ -1081,17 +1434,33 @@ def _bump_and_write(
|
|
|
1081
1434
|
manifest: The already-edited in-memory manifest dict.
|
|
1082
1435
|
|
|
1083
1436
|
Returns:
|
|
1084
|
-
An empty list on success (write performed)
|
|
1085
|
-
refusal (no write performed).
|
|
1437
|
+
An empty list on success (write performed) OR on a semantic no-op (no write);
|
|
1438
|
+
the blocking findings on refusal (no write performed).
|
|
1086
1439
|
|
|
1087
1440
|
Raises:
|
|
1088
1441
|
UsageError: If the atomic write itself fails (exit 2).
|
|
1089
1442
|
"""
|
|
1443
|
+
path = epic_dir / MANIFEST_FILENAME
|
|
1444
|
+
try:
|
|
1445
|
+
on_disk: dict | None = load_manifest(epic_dir)
|
|
1446
|
+
except (UsageError, FindingsError):
|
|
1447
|
+
# No readable predecessor (first write, or a corrupt file the caller is
|
|
1448
|
+
# replacing wholesale) — treat every field as changed.
|
|
1449
|
+
on_disk = None
|
|
1450
|
+
|
|
1090
1451
|
findings = _validate_dict(manifest, epic_dir, specs_dir)
|
|
1091
1452
|
if findings:
|
|
1092
1453
|
return findings
|
|
1454
|
+
|
|
1455
|
+
if on_disk is not None and _semantic_manifest(on_disk) == _semantic_manifest(manifest):
|
|
1456
|
+
return []
|
|
1457
|
+
|
|
1458
|
+
current = on_disk.get("revision") if isinstance(on_disk, dict) else None
|
|
1459
|
+
if isinstance(current, bool) or not isinstance(current, int) or current < 1:
|
|
1460
|
+
current = 1 # legacy / malformed predecessor: logical revision 1.
|
|
1461
|
+
manifest["revision"] = current + 1
|
|
1093
1462
|
manifest["updatedAt"] = datetime.now(timezone.utc).isoformat()
|
|
1094
|
-
atomic_write(
|
|
1463
|
+
atomic_write(path, manifest)
|
|
1095
1464
|
return []
|
|
1096
1465
|
|
|
1097
1466
|
|
|
@@ -1102,7 +1471,7 @@ def add_feature(
|
|
|
1102
1471
|
charter: str,
|
|
1103
1472
|
deps: list[str],
|
|
1104
1473
|
) -> list[Finding]:
|
|
1105
|
-
"""Append a new member feature to the manifest
|
|
1474
|
+
"""Append a new member feature to the manifest.
|
|
1106
1475
|
|
|
1107
1476
|
Appends a ``Feature`` with the given name/charter/dependsOn and EMPTY
|
|
1108
1477
|
exposes/consumes. Re-validation surfaces a duplicate name (within the
|
|
@@ -1138,7 +1507,7 @@ def add_feature(
|
|
|
1138
1507
|
|
|
1139
1508
|
|
|
1140
1509
|
def remove_feature(epic_dir: Path, specs_dir: Path, name: str) -> list[Finding]:
|
|
1141
|
-
"""Remove a member feature from the manifest
|
|
1510
|
+
"""Remove a member feature from the manifest.
|
|
1142
1511
|
|
|
1143
1512
|
Drops the named feature from ``features[]``. After removal, re-validation
|
|
1144
1513
|
surfaces any now-dangling ``dependsOn`` / ``consumes.from`` that pointed at
|
|
@@ -1170,10 +1539,10 @@ def remove_feature(epic_dir: Path, specs_dir: Path, name: str) -> list[Finding]:
|
|
|
1170
1539
|
|
|
1171
1540
|
|
|
1172
1541
|
def reorder(epic_dir: Path, specs_dir: Path, order: list[str]) -> list[Finding]:
|
|
1173
|
-
"""Reorder the manifest features[] to a given permutation
|
|
1542
|
+
"""Reorder the manifest features[] to a given permutation.
|
|
1174
1543
|
|
|
1175
1544
|
``order`` must be an exact permutation of the current member names (purely a
|
|
1176
|
-
display sequence, not a dependency ordering
|
|
1545
|
+
display sequence, not a dependency ordering). If it is not, a
|
|
1177
1546
|
``schema`` finding is returned and the manifest is left unchanged.
|
|
1178
1547
|
|
|
1179
1548
|
Args:
|
|
@@ -1203,7 +1572,7 @@ def reorder(epic_dir: Path, specs_dir: Path, order: list[str]) -> list[Finding]:
|
|
|
1203
1572
|
def set_dep(
|
|
1204
1573
|
epic_dir: Path, specs_dir: Path, name: str, deps: list[str]
|
|
1205
1574
|
) -> list[Finding]:
|
|
1206
|
-
"""Replace a member feature's dependsOn list
|
|
1575
|
+
"""Replace a member feature's dependsOn list.
|
|
1207
1576
|
|
|
1208
1577
|
Re-validation enforces every new dependency exists (``dangling-ref``) and the
|
|
1209
1578
|
resulting graph is acyclic (``cycle``). An empty ``deps`` clears the
|
|
@@ -1239,7 +1608,7 @@ def set_dep(
|
|
|
1239
1608
|
|
|
1240
1609
|
|
|
1241
1610
|
def set_status(epic_dir: Path, specs_dir: Path, status: str) -> list[Finding]:
|
|
1242
|
-
"""Set the epic-level lifecycle status
|
|
1611
|
+
"""Set the epic-level lifecycle status.
|
|
1243
1612
|
|
|
1244
1613
|
Sets the epic-level ``status`` (the value is constrained to the allowed
|
|
1245
1614
|
lifecycle states by ``argparse`` ``choices`` before reaching here). Never
|
|
@@ -1437,7 +1806,7 @@ def adopt_feature(
|
|
|
1437
1806
|
|
|
1438
1807
|
|
|
1439
1808
|
# --------------------------------------------------------------------------- #
|
|
1440
|
-
# CLI Dispatch
|
|
1809
|
+
# CLI Dispatch
|
|
1441
1810
|
# --------------------------------------------------------------------------- #
|
|
1442
1811
|
|
|
1443
1812
|
|
|
@@ -1466,7 +1835,7 @@ def _emit_findings(findings: list[Finding], as_json: bool) -> None:
|
|
|
1466
1835
|
|
|
1467
1836
|
|
|
1468
1837
|
def _print_status_table(status: RenderStatus) -> None:
|
|
1469
|
-
"""Print a readable epic dashboard plus the recommended next command
|
|
1838
|
+
"""Print a readable epic dashboard plus the recommended next command."""
|
|
1470
1839
|
rollup = status["rollup"]
|
|
1471
1840
|
print(f"Epic: {status['epic']} [{status['status']}]")
|
|
1472
1841
|
print(f"Progress: {rollup['complete']}/{rollup['total']} complete")
|
|
@@ -1575,7 +1944,7 @@ def _dispatch(args: argparse.Namespace, specs_dir: Path) -> int:
|
|
|
1575
1944
|
|
|
1576
1945
|
|
|
1577
1946
|
def _build_parser() -> argparse.ArgumentParser:
|
|
1578
|
-
"""Build the argparse parser with one subparser per subcommand
|
|
1947
|
+
"""Build the argparse parser with one subparser per subcommand."""
|
|
1579
1948
|
parser = argparse.ArgumentParser(prog="epic-manifest.py", description=__doc__)
|
|
1580
1949
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
1581
1950
|
|