@garygentry/feature-forge 0.2.12 → 0.2.14
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/GENERATION-REPORT.md +1 -0
- package/adapters/claude/.feature-forge-bundle.json +1 -1
- package/adapters/claude/references/epic-manifest-schema.json +5 -0
- package/adapters/claude/references/forge-config-schema.json +5 -0
- package/adapters/claude/references/shared-conventions.md +12 -0
- package/adapters/claude/references/stacks/_generic.md +21 -0
- package/adapters/claude/references/stacks/go.md +19 -0
- package/adapters/claude/references/stacks/python.md +21 -0
- package/adapters/claude/references/stacks/rust.md +19 -0
- package/adapters/claude/references/stacks/typescript.md +23 -0
- package/adapters/claude/scripts/epic-manifest.py +284 -5
- package/adapters/claude/scripts/forge-bootstrap.py +1 -0
- package/adapters/claude/scripts/forge-init.sh +2 -0
- package/adapters/claude/scripts/forge-root.sh +55 -11
- package/adapters/claude/scripts/forge-session.py +38 -1
- package/adapters/claude/skills/forge/SKILL.md +9 -1
- package/adapters/claude/skills/forge/references/pipeline-state-schema.json +191 -0
- package/adapters/claude/skills/forge/references/process-overview.md +143 -0
- package/adapters/claude/skills/forge/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge/references/stage-exit-protocol.md +258 -0
- package/adapters/claude/skills/forge-0-epic/references/epic-manifest-subcommands.md +11 -0
- package/adapters/claude/skills/forge-0-epic/references/pipeline-state-schema.json +191 -0
- package/adapters/claude/skills/forge-0-epic/references/portable-root.md +71 -0
- package/adapters/claude/skills/forge-0-epic/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge-0-epic/references/stage-exit-protocol.md +258 -0
- package/adapters/claude/skills/forge-1-prd/SKILL.md +2 -0
- package/adapters/claude/skills/forge-1-prd/references/pipeline-state-schema.json +191 -0
- package/adapters/claude/skills/forge-1-prd/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge-1-prd/references/stage-exit-protocol.md +258 -0
- package/adapters/claude/skills/forge-2-tech/SKILL.md +3 -1
- package/adapters/claude/skills/forge-2-tech/references/pipeline-state-schema.json +191 -0
- package/adapters/claude/skills/forge-2-tech/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge-2-tech/references/stack-resolution.md +54 -0
- package/adapters/claude/skills/forge-2-tech/references/stacks/_generic.md +111 -0
- package/adapters/claude/skills/forge-2-tech/references/stacks/go.md +157 -0
- package/adapters/claude/skills/forge-2-tech/references/stacks/python.md +184 -0
- package/adapters/claude/skills/forge-2-tech/references/stacks/rust.md +170 -0
- package/adapters/claude/skills/forge-2-tech/references/stacks/typescript.md +134 -0
- package/adapters/claude/skills/forge-2-tech/references/stage-exit-protocol.md +258 -0
- package/adapters/claude/skills/forge-3-specs/SKILL.md +2 -0
- package/adapters/claude/skills/forge-3-specs/references/pipeline-state-schema.json +191 -0
- package/adapters/claude/skills/forge-3-specs/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge-3-specs/references/stacks/_generic.md +111 -0
- package/adapters/claude/skills/forge-3-specs/references/stacks/go.md +157 -0
- package/adapters/claude/skills/forge-3-specs/references/stacks/python.md +184 -0
- package/adapters/claude/skills/forge-3-specs/references/stacks/rust.md +170 -0
- package/adapters/claude/skills/forge-3-specs/references/stacks/typescript.md +134 -0
- package/adapters/claude/skills/forge-3-specs/references/stage-exit-protocol.md +258 -0
- package/adapters/claude/skills/forge-4-backlog/SKILL.md +5 -0
- package/adapters/claude/skills/forge-4-backlog/references/forge-config-schema.json +236 -0
- package/adapters/claude/skills/forge-4-backlog/references/pipeline-state-schema.json +191 -0
- package/adapters/claude/skills/forge-4-backlog/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge-4-backlog/references/stage-exit-protocol.md +258 -0
- package/adapters/claude/skills/forge-5-loop/SKILL.md +7 -9
- package/adapters/claude/skills/forge-5-loop/references/forge-config-schema.json +236 -0
- package/adapters/claude/skills/forge-5-loop/references/ralph-loop-contract.md +221 -0
- package/adapters/claude/skills/forge-5-loop/references/runner-contract.md +41 -0
- package/adapters/claude/skills/forge-5-loop/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge-5-loop/references/stage-exit-protocol.md +258 -0
- package/adapters/claude/skills/forge-6-docs/SKILL.md +4 -1
- package/adapters/claude/skills/forge-6-docs/references/pipeline-state-schema.json +191 -0
- package/adapters/claude/skills/forge-6-docs/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge-fix/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge-fix/references/stage-exit-protocol.md +258 -0
- package/adapters/claude/skills/forge-guide/references/forge-config-schema.json +236 -0
- package/adapters/claude/skills/forge-guide/references/process-overview.md +143 -0
- package/adapters/claude/skills/forge-guide/references/ralph-loop-contract.md +221 -0
- package/adapters/claude/skills/forge-guide/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge-guide/references/stack-resolution.md +54 -0
- package/adapters/claude/skills/forge-guide/references/stacks/_generic.md +111 -0
- package/adapters/claude/skills/forge-guide/references/stacks/go.md +157 -0
- package/adapters/claude/skills/forge-guide/references/stacks/python.md +184 -0
- package/adapters/claude/skills/forge-guide/references/stacks/rust.md +170 -0
- package/adapters/claude/skills/forge-guide/references/stacks/typescript.md +134 -0
- package/adapters/claude/skills/forge-init/SKILL.md +1 -0
- package/adapters/claude/skills/forge-verify/SKILL.md +5 -4
- package/adapters/claude/skills/forge-verify/references/pipeline-state-schema.json +191 -0
- package/adapters/claude/skills/forge-verify/references/shared-conventions.md +295 -0
- package/adapters/claude/skills/forge-verify/references/verification-checklists.md +88 -2
- package/adapters/codex/.feature-forge-bundle.json +1 -1
- package/adapters/codex/references/epic-manifest-schema.json +5 -0
- package/adapters/codex/references/forge-config-schema.json +5 -0
- package/adapters/codex/references/shared-conventions.md +12 -0
- package/adapters/codex/references/stacks/_generic.md +21 -0
- package/adapters/codex/references/stacks/go.md +19 -0
- package/adapters/codex/references/stacks/python.md +21 -0
- package/adapters/codex/references/stacks/rust.md +19 -0
- package/adapters/codex/references/stacks/typescript.md +23 -0
- package/adapters/codex/scripts/epic-manifest.py +284 -5
- package/adapters/codex/scripts/forge-bootstrap.py +1 -0
- package/adapters/codex/scripts/forge-init.sh +2 -0
- package/adapters/codex/scripts/forge-root.sh +55 -11
- package/adapters/codex/scripts/forge-session.py +38 -1
- package/adapters/codex/skills/forge/SKILL.md +9 -1
- package/adapters/codex/skills/forge/references/pipeline-state-schema.json +191 -0
- package/adapters/codex/skills/forge/references/process-overview.md +143 -0
- package/adapters/codex/skills/forge/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge/references/stage-exit-protocol.md +258 -0
- package/adapters/codex/skills/forge-0-epic/references/epic-manifest-subcommands.md +11 -0
- package/adapters/codex/skills/forge-0-epic/references/pipeline-state-schema.json +191 -0
- package/adapters/codex/skills/forge-0-epic/references/portable-root.md +71 -0
- package/adapters/codex/skills/forge-0-epic/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge-0-epic/references/stage-exit-protocol.md +258 -0
- package/adapters/codex/skills/forge-1-prd/SKILL.md +2 -0
- package/adapters/codex/skills/forge-1-prd/references/pipeline-state-schema.json +191 -0
- package/adapters/codex/skills/forge-1-prd/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge-1-prd/references/stage-exit-protocol.md +258 -0
- package/adapters/codex/skills/forge-2-tech/SKILL.md +3 -1
- package/adapters/codex/skills/forge-2-tech/references/pipeline-state-schema.json +191 -0
- package/adapters/codex/skills/forge-2-tech/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge-2-tech/references/stack-resolution.md +54 -0
- package/adapters/codex/skills/forge-2-tech/references/stacks/_generic.md +111 -0
- package/adapters/codex/skills/forge-2-tech/references/stacks/go.md +157 -0
- package/adapters/codex/skills/forge-2-tech/references/stacks/python.md +184 -0
- package/adapters/codex/skills/forge-2-tech/references/stacks/rust.md +170 -0
- package/adapters/codex/skills/forge-2-tech/references/stacks/typescript.md +134 -0
- package/adapters/codex/skills/forge-2-tech/references/stage-exit-protocol.md +258 -0
- package/adapters/codex/skills/forge-3-specs/SKILL.md +2 -0
- package/adapters/codex/skills/forge-3-specs/references/pipeline-state-schema.json +191 -0
- package/adapters/codex/skills/forge-3-specs/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge-3-specs/references/stacks/_generic.md +111 -0
- package/adapters/codex/skills/forge-3-specs/references/stacks/go.md +157 -0
- package/adapters/codex/skills/forge-3-specs/references/stacks/python.md +184 -0
- package/adapters/codex/skills/forge-3-specs/references/stacks/rust.md +170 -0
- package/adapters/codex/skills/forge-3-specs/references/stacks/typescript.md +134 -0
- package/adapters/codex/skills/forge-3-specs/references/stage-exit-protocol.md +258 -0
- package/adapters/codex/skills/forge-4-backlog/SKILL.md +5 -0
- package/adapters/codex/skills/forge-4-backlog/references/forge-config-schema.json +236 -0
- package/adapters/codex/skills/forge-4-backlog/references/pipeline-state-schema.json +191 -0
- package/adapters/codex/skills/forge-4-backlog/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge-4-backlog/references/stage-exit-protocol.md +258 -0
- package/adapters/codex/skills/forge-5-loop/SKILL.md +7 -9
- package/adapters/codex/skills/forge-5-loop/references/forge-config-schema.json +236 -0
- package/adapters/codex/skills/forge-5-loop/references/ralph-loop-contract.md +221 -0
- package/adapters/codex/skills/forge-5-loop/references/runner-contract.md +41 -0
- package/adapters/codex/skills/forge-5-loop/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge-5-loop/references/stage-exit-protocol.md +258 -0
- package/adapters/codex/skills/forge-6-docs/SKILL.md +4 -1
- package/adapters/codex/skills/forge-6-docs/references/pipeline-state-schema.json +191 -0
- package/adapters/codex/skills/forge-6-docs/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge-fix/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge-fix/references/stage-exit-protocol.md +258 -0
- package/adapters/codex/skills/forge-guide/references/forge-config-schema.json +236 -0
- package/adapters/codex/skills/forge-guide/references/process-overview.md +143 -0
- package/adapters/codex/skills/forge-guide/references/ralph-loop-contract.md +221 -0
- package/adapters/codex/skills/forge-guide/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge-guide/references/stack-resolution.md +54 -0
- package/adapters/codex/skills/forge-guide/references/stacks/_generic.md +111 -0
- package/adapters/codex/skills/forge-guide/references/stacks/go.md +157 -0
- package/adapters/codex/skills/forge-guide/references/stacks/python.md +184 -0
- package/adapters/codex/skills/forge-guide/references/stacks/rust.md +170 -0
- package/adapters/codex/skills/forge-guide/references/stacks/typescript.md +134 -0
- package/adapters/codex/skills/forge-init/SKILL.md +1 -0
- package/adapters/codex/skills/forge-verify/SKILL.md +5 -4
- package/adapters/codex/skills/forge-verify/references/pipeline-state-schema.json +191 -0
- package/adapters/codex/skills/forge-verify/references/shared-conventions.md +295 -0
- package/adapters/codex/skills/forge-verify/references/verification-checklists.md +88 -2
- package/adapters/copilot/.feature-forge-bundle.json +1 -1
- package/adapters/copilot/references/epic-manifest-schema.json +5 -0
- package/adapters/copilot/references/forge-config-schema.json +5 -0
- package/adapters/copilot/references/shared-conventions.md +12 -0
- package/adapters/copilot/references/stacks/_generic.md +21 -0
- package/adapters/copilot/references/stacks/go.md +19 -0
- package/adapters/copilot/references/stacks/python.md +21 -0
- package/adapters/copilot/references/stacks/rust.md +19 -0
- package/adapters/copilot/references/stacks/typescript.md +23 -0
- package/adapters/copilot/scripts/epic-manifest.py +284 -5
- package/adapters/copilot/scripts/forge-bootstrap.py +1 -0
- package/adapters/copilot/scripts/forge-init.sh +2 -0
- package/adapters/copilot/scripts/forge-root.sh +55 -11
- package/adapters/copilot/scripts/forge-session.py +38 -1
- package/adapters/copilot/skills/forge/forge.md +9 -1
- package/adapters/copilot/skills/forge/references/pipeline-state-schema.json +191 -0
- package/adapters/copilot/skills/forge/references/process-overview.md +143 -0
- package/adapters/copilot/skills/forge/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge/references/stage-exit-protocol.md +258 -0
- package/adapters/copilot/skills/forge-0-epic/references/epic-manifest-subcommands.md +11 -0
- package/adapters/copilot/skills/forge-0-epic/references/pipeline-state-schema.json +191 -0
- package/adapters/copilot/skills/forge-0-epic/references/portable-root.md +71 -0
- package/adapters/copilot/skills/forge-0-epic/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge-0-epic/references/stage-exit-protocol.md +258 -0
- package/adapters/copilot/skills/forge-1-prd/forge-1-prd.md +2 -0
- package/adapters/copilot/skills/forge-1-prd/references/pipeline-state-schema.json +191 -0
- package/adapters/copilot/skills/forge-1-prd/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge-1-prd/references/stage-exit-protocol.md +258 -0
- package/adapters/copilot/skills/forge-2-tech/forge-2-tech.md +3 -1
- package/adapters/copilot/skills/forge-2-tech/references/pipeline-state-schema.json +191 -0
- package/adapters/copilot/skills/forge-2-tech/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge-2-tech/references/stack-resolution.md +54 -0
- package/adapters/copilot/skills/forge-2-tech/references/stacks/_generic.md +111 -0
- package/adapters/copilot/skills/forge-2-tech/references/stacks/go.md +157 -0
- package/adapters/copilot/skills/forge-2-tech/references/stacks/python.md +184 -0
- package/adapters/copilot/skills/forge-2-tech/references/stacks/rust.md +170 -0
- package/adapters/copilot/skills/forge-2-tech/references/stacks/typescript.md +134 -0
- package/adapters/copilot/skills/forge-2-tech/references/stage-exit-protocol.md +258 -0
- package/adapters/copilot/skills/forge-3-specs/forge-3-specs.md +2 -0
- package/adapters/copilot/skills/forge-3-specs/references/pipeline-state-schema.json +191 -0
- package/adapters/copilot/skills/forge-3-specs/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge-3-specs/references/stacks/_generic.md +111 -0
- package/adapters/copilot/skills/forge-3-specs/references/stacks/go.md +157 -0
- package/adapters/copilot/skills/forge-3-specs/references/stacks/python.md +184 -0
- package/adapters/copilot/skills/forge-3-specs/references/stacks/rust.md +170 -0
- package/adapters/copilot/skills/forge-3-specs/references/stacks/typescript.md +134 -0
- package/adapters/copilot/skills/forge-3-specs/references/stage-exit-protocol.md +258 -0
- package/adapters/copilot/skills/forge-4-backlog/forge-4-backlog.md +5 -0
- package/adapters/copilot/skills/forge-4-backlog/references/forge-config-schema.json +236 -0
- package/adapters/copilot/skills/forge-4-backlog/references/pipeline-state-schema.json +191 -0
- package/adapters/copilot/skills/forge-4-backlog/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge-4-backlog/references/stage-exit-protocol.md +258 -0
- package/adapters/copilot/skills/forge-5-loop/forge-5-loop.md +7 -9
- package/adapters/copilot/skills/forge-5-loop/references/forge-config-schema.json +236 -0
- package/adapters/copilot/skills/forge-5-loop/references/ralph-loop-contract.md +221 -0
- package/adapters/copilot/skills/forge-5-loop/references/runner-contract.md +41 -0
- package/adapters/copilot/skills/forge-5-loop/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge-5-loop/references/stage-exit-protocol.md +258 -0
- package/adapters/copilot/skills/forge-6-docs/forge-6-docs.md +4 -1
- package/adapters/copilot/skills/forge-6-docs/references/pipeline-state-schema.json +191 -0
- package/adapters/copilot/skills/forge-6-docs/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge-fix/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge-fix/references/stage-exit-protocol.md +258 -0
- package/adapters/copilot/skills/forge-guide/references/forge-config-schema.json +236 -0
- package/adapters/copilot/skills/forge-guide/references/process-overview.md +143 -0
- package/adapters/copilot/skills/forge-guide/references/ralph-loop-contract.md +221 -0
- package/adapters/copilot/skills/forge-guide/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge-guide/references/stack-resolution.md +54 -0
- package/adapters/copilot/skills/forge-guide/references/stacks/_generic.md +111 -0
- package/adapters/copilot/skills/forge-guide/references/stacks/go.md +157 -0
- package/adapters/copilot/skills/forge-guide/references/stacks/python.md +184 -0
- package/adapters/copilot/skills/forge-guide/references/stacks/rust.md +170 -0
- package/adapters/copilot/skills/forge-guide/references/stacks/typescript.md +134 -0
- package/adapters/copilot/skills/forge-init/forge-init.md +1 -0
- package/adapters/copilot/skills/forge-verify/forge-verify.md +5 -4
- package/adapters/copilot/skills/forge-verify/references/pipeline-state-schema.json +191 -0
- package/adapters/copilot/skills/forge-verify/references/shared-conventions.md +295 -0
- package/adapters/copilot/skills/forge-verify/references/verification-checklists.md +88 -2
- package/adapters/cursor/.feature-forge-bundle.json +1 -1
- package/adapters/cursor/references/epic-manifest-schema.json +5 -0
- package/adapters/cursor/references/forge-config-schema.json +5 -0
- package/adapters/cursor/references/shared-conventions.md +12 -0
- package/adapters/cursor/references/stacks/_generic.md +21 -0
- package/adapters/cursor/references/stacks/go.md +19 -0
- package/adapters/cursor/references/stacks/python.md +21 -0
- package/adapters/cursor/references/stacks/rust.md +19 -0
- package/adapters/cursor/references/stacks/typescript.md +23 -0
- package/adapters/cursor/scripts/epic-manifest.py +284 -5
- package/adapters/cursor/scripts/forge-bootstrap.py +1 -0
- package/adapters/cursor/scripts/forge-init.sh +2 -0
- package/adapters/cursor/scripts/forge-root.sh +55 -11
- package/adapters/cursor/scripts/forge-session.py +38 -1
- package/adapters/cursor/skills/forge/forge.mdc +9 -1
- package/adapters/cursor/skills/forge/references/pipeline-state-schema.json +191 -0
- package/adapters/cursor/skills/forge/references/process-overview.md +143 -0
- package/adapters/cursor/skills/forge/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge/references/stage-exit-protocol.md +258 -0
- package/adapters/cursor/skills/forge-0-epic/references/epic-manifest-subcommands.md +11 -0
- package/adapters/cursor/skills/forge-0-epic/references/pipeline-state-schema.json +191 -0
- package/adapters/cursor/skills/forge-0-epic/references/portable-root.md +71 -0
- package/adapters/cursor/skills/forge-0-epic/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge-0-epic/references/stage-exit-protocol.md +258 -0
- package/adapters/cursor/skills/forge-1-prd/forge-1-prd.mdc +2 -0
- package/adapters/cursor/skills/forge-1-prd/references/pipeline-state-schema.json +191 -0
- package/adapters/cursor/skills/forge-1-prd/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge-1-prd/references/stage-exit-protocol.md +258 -0
- package/adapters/cursor/skills/forge-2-tech/forge-2-tech.mdc +3 -1
- package/adapters/cursor/skills/forge-2-tech/references/pipeline-state-schema.json +191 -0
- package/adapters/cursor/skills/forge-2-tech/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge-2-tech/references/stack-resolution.md +54 -0
- package/adapters/cursor/skills/forge-2-tech/references/stacks/_generic.md +111 -0
- package/adapters/cursor/skills/forge-2-tech/references/stacks/go.md +157 -0
- package/adapters/cursor/skills/forge-2-tech/references/stacks/python.md +184 -0
- package/adapters/cursor/skills/forge-2-tech/references/stacks/rust.md +170 -0
- package/adapters/cursor/skills/forge-2-tech/references/stacks/typescript.md +134 -0
- package/adapters/cursor/skills/forge-2-tech/references/stage-exit-protocol.md +258 -0
- package/adapters/cursor/skills/forge-3-specs/forge-3-specs.mdc +2 -0
- package/adapters/cursor/skills/forge-3-specs/references/pipeline-state-schema.json +191 -0
- package/adapters/cursor/skills/forge-3-specs/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge-3-specs/references/stacks/_generic.md +111 -0
- package/adapters/cursor/skills/forge-3-specs/references/stacks/go.md +157 -0
- package/adapters/cursor/skills/forge-3-specs/references/stacks/python.md +184 -0
- package/adapters/cursor/skills/forge-3-specs/references/stacks/rust.md +170 -0
- package/adapters/cursor/skills/forge-3-specs/references/stacks/typescript.md +134 -0
- package/adapters/cursor/skills/forge-3-specs/references/stage-exit-protocol.md +258 -0
- package/adapters/cursor/skills/forge-4-backlog/forge-4-backlog.mdc +5 -0
- package/adapters/cursor/skills/forge-4-backlog/references/forge-config-schema.json +236 -0
- package/adapters/cursor/skills/forge-4-backlog/references/pipeline-state-schema.json +191 -0
- package/adapters/cursor/skills/forge-4-backlog/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge-4-backlog/references/stage-exit-protocol.md +258 -0
- package/adapters/cursor/skills/forge-5-loop/forge-5-loop.mdc +7 -9
- package/adapters/cursor/skills/forge-5-loop/references/forge-config-schema.json +236 -0
- package/adapters/cursor/skills/forge-5-loop/references/ralph-loop-contract.md +221 -0
- package/adapters/cursor/skills/forge-5-loop/references/runner-contract.md +41 -0
- package/adapters/cursor/skills/forge-5-loop/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge-5-loop/references/stage-exit-protocol.md +258 -0
- package/adapters/cursor/skills/forge-6-docs/forge-6-docs.mdc +4 -1
- package/adapters/cursor/skills/forge-6-docs/references/pipeline-state-schema.json +191 -0
- package/adapters/cursor/skills/forge-6-docs/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge-fix/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge-fix/references/stage-exit-protocol.md +258 -0
- package/adapters/cursor/skills/forge-guide/references/forge-config-schema.json +236 -0
- package/adapters/cursor/skills/forge-guide/references/process-overview.md +143 -0
- package/adapters/cursor/skills/forge-guide/references/ralph-loop-contract.md +221 -0
- package/adapters/cursor/skills/forge-guide/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge-guide/references/stack-resolution.md +54 -0
- package/adapters/cursor/skills/forge-guide/references/stacks/_generic.md +111 -0
- package/adapters/cursor/skills/forge-guide/references/stacks/go.md +157 -0
- package/adapters/cursor/skills/forge-guide/references/stacks/python.md +184 -0
- package/adapters/cursor/skills/forge-guide/references/stacks/rust.md +170 -0
- package/adapters/cursor/skills/forge-guide/references/stacks/typescript.md +134 -0
- package/adapters/cursor/skills/forge-init/forge-init.mdc +1 -0
- package/adapters/cursor/skills/forge-verify/forge-verify.mdc +5 -4
- package/adapters/cursor/skills/forge-verify/references/pipeline-state-schema.json +191 -0
- package/adapters/cursor/skills/forge-verify/references/shared-conventions.md +295 -0
- package/adapters/cursor/skills/forge-verify/references/verification-checklists.md +88 -2
- package/adapters/gemini/.feature-forge-bundle.json +1 -1
- package/adapters/gemini/gemini-extension.json +1 -1
- package/adapters/gemini/references/epic-manifest-schema.json +5 -0
- package/adapters/gemini/references/forge-config-schema.json +5 -0
- package/adapters/gemini/references/shared-conventions.md +12 -0
- package/adapters/gemini/references/stacks/_generic.md +21 -0
- package/adapters/gemini/references/stacks/go.md +19 -0
- package/adapters/gemini/references/stacks/python.md +21 -0
- package/adapters/gemini/references/stacks/rust.md +19 -0
- package/adapters/gemini/references/stacks/typescript.md +23 -0
- package/adapters/gemini/scripts/epic-manifest.py +284 -5
- package/adapters/gemini/scripts/forge-bootstrap.py +1 -0
- package/adapters/gemini/scripts/forge-init.sh +2 -0
- package/adapters/gemini/scripts/forge-root.sh +55 -11
- package/adapters/gemini/scripts/forge-session.py +38 -1
- package/adapters/gemini/skills/forge/forge.md +9 -1
- package/adapters/gemini/skills/forge/references/pipeline-state-schema.json +191 -0
- package/adapters/gemini/skills/forge/references/process-overview.md +143 -0
- package/adapters/gemini/skills/forge/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge/references/stage-exit-protocol.md +258 -0
- package/adapters/gemini/skills/forge-0-epic/references/epic-manifest-subcommands.md +11 -0
- package/adapters/gemini/skills/forge-0-epic/references/pipeline-state-schema.json +191 -0
- package/adapters/gemini/skills/forge-0-epic/references/portable-root.md +71 -0
- package/adapters/gemini/skills/forge-0-epic/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge-0-epic/references/stage-exit-protocol.md +258 -0
- package/adapters/gemini/skills/forge-1-prd/forge-1-prd.md +2 -0
- package/adapters/gemini/skills/forge-1-prd/references/pipeline-state-schema.json +191 -0
- package/adapters/gemini/skills/forge-1-prd/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge-1-prd/references/stage-exit-protocol.md +258 -0
- package/adapters/gemini/skills/forge-2-tech/forge-2-tech.md +3 -1
- package/adapters/gemini/skills/forge-2-tech/references/pipeline-state-schema.json +191 -0
- package/adapters/gemini/skills/forge-2-tech/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge-2-tech/references/stack-resolution.md +54 -0
- package/adapters/gemini/skills/forge-2-tech/references/stacks/_generic.md +111 -0
- package/adapters/gemini/skills/forge-2-tech/references/stacks/go.md +157 -0
- package/adapters/gemini/skills/forge-2-tech/references/stacks/python.md +184 -0
- package/adapters/gemini/skills/forge-2-tech/references/stacks/rust.md +170 -0
- package/adapters/gemini/skills/forge-2-tech/references/stacks/typescript.md +134 -0
- package/adapters/gemini/skills/forge-2-tech/references/stage-exit-protocol.md +258 -0
- package/adapters/gemini/skills/forge-3-specs/forge-3-specs.md +2 -0
- package/adapters/gemini/skills/forge-3-specs/references/pipeline-state-schema.json +191 -0
- package/adapters/gemini/skills/forge-3-specs/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge-3-specs/references/stacks/_generic.md +111 -0
- package/adapters/gemini/skills/forge-3-specs/references/stacks/go.md +157 -0
- package/adapters/gemini/skills/forge-3-specs/references/stacks/python.md +184 -0
- package/adapters/gemini/skills/forge-3-specs/references/stacks/rust.md +170 -0
- package/adapters/gemini/skills/forge-3-specs/references/stacks/typescript.md +134 -0
- package/adapters/gemini/skills/forge-3-specs/references/stage-exit-protocol.md +258 -0
- package/adapters/gemini/skills/forge-4-backlog/forge-4-backlog.md +5 -0
- package/adapters/gemini/skills/forge-4-backlog/references/forge-config-schema.json +236 -0
- package/adapters/gemini/skills/forge-4-backlog/references/pipeline-state-schema.json +191 -0
- package/adapters/gemini/skills/forge-4-backlog/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge-4-backlog/references/stage-exit-protocol.md +258 -0
- package/adapters/gemini/skills/forge-5-loop/forge-5-loop.md +7 -9
- package/adapters/gemini/skills/forge-5-loop/references/forge-config-schema.json +236 -0
- package/adapters/gemini/skills/forge-5-loop/references/ralph-loop-contract.md +221 -0
- package/adapters/gemini/skills/forge-5-loop/references/runner-contract.md +41 -0
- package/adapters/gemini/skills/forge-5-loop/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge-5-loop/references/stage-exit-protocol.md +258 -0
- package/adapters/gemini/skills/forge-6-docs/forge-6-docs.md +4 -1
- package/adapters/gemini/skills/forge-6-docs/references/pipeline-state-schema.json +191 -0
- package/adapters/gemini/skills/forge-6-docs/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge-fix/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge-fix/references/stage-exit-protocol.md +258 -0
- package/adapters/gemini/skills/forge-guide/references/forge-config-schema.json +236 -0
- package/adapters/gemini/skills/forge-guide/references/process-overview.md +143 -0
- package/adapters/gemini/skills/forge-guide/references/ralph-loop-contract.md +221 -0
- package/adapters/gemini/skills/forge-guide/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge-guide/references/stack-resolution.md +54 -0
- package/adapters/gemini/skills/forge-guide/references/stacks/_generic.md +111 -0
- package/adapters/gemini/skills/forge-guide/references/stacks/go.md +157 -0
- package/adapters/gemini/skills/forge-guide/references/stacks/python.md +184 -0
- package/adapters/gemini/skills/forge-guide/references/stacks/rust.md +170 -0
- package/adapters/gemini/skills/forge-guide/references/stacks/typescript.md +134 -0
- package/adapters/gemini/skills/forge-init/forge-init.md +1 -0
- package/adapters/gemini/skills/forge-verify/forge-verify.md +5 -4
- package/adapters/gemini/skills/forge-verify/references/pipeline-state-schema.json +191 -0
- package/adapters/gemini/skills/forge-verify/references/shared-conventions.md +295 -0
- package/adapters/gemini/skills/forge-verify/references/verification-checklists.md +88 -2
- package/package.json +1 -1
|
@@ -27,6 +27,7 @@ import argparse
|
|
|
27
27
|
import json
|
|
28
28
|
import os
|
|
29
29
|
import re
|
|
30
|
+
import shutil
|
|
30
31
|
import sys
|
|
31
32
|
import tempfile
|
|
32
33
|
from datetime import datetime, timezone
|
|
@@ -46,6 +47,21 @@ PIPELINE_STATE_FILENAME: Final = ".pipeline-state.json"
|
|
|
46
47
|
MANIFEST_FILENAME: Final = "epic-manifest.json"
|
|
47
48
|
NARRATIVE_FILENAME: Final = "EPIC.md"
|
|
48
49
|
|
|
50
|
+
#: The authoritative forge-verify status vocabulary. SOURCE OF TRUTH:
|
|
51
|
+
#: references/pipeline-state-schema.json (definitions.verifyEntry.properties.status.enum).
|
|
52
|
+
#: A member state carrying a forge-verify-*.status outside this set is treated as
|
|
53
|
+
#: incomplete-for-orchestration but SURFACED (never guessed silently) by render_status
|
|
54
|
+
#: — a single typo'd status otherwise poisons the epic rollup + dependency gates (#148).
|
|
55
|
+
#: NOTE: forge-session.py keeps a byte-identical copy of this constant — flat, self-
|
|
56
|
+
#: contained scripts have no shared import module (each is copied verbatim into adapters).
|
|
57
|
+
KNOWN_VERIFY_STATUSES: Final = frozenset(
|
|
58
|
+
{"pending", "passed", "findings-reported", "findings-applied", "skipped"}
|
|
59
|
+
)
|
|
60
|
+
#: The subset of KNOWN_VERIFY_STATUSES that makes a member's forge-verify-impl count as
|
|
61
|
+
#: complete-for-orchestration (00 §7). A STRICT subset — 'findings-reported' (unfixed),
|
|
62
|
+
#: 'skipped', and 'pending' do NOT unblock dependents. Not collapsible into the set above.
|
|
63
|
+
_VERIFY_ORCH_COMPLETE: Final = frozenset({"passed", "findings-applied"})
|
|
64
|
+
|
|
49
65
|
|
|
50
66
|
# --------------------------------------------------------------------------- #
|
|
51
67
|
# Type Definitions (00-core-definitions.md §4, §5; 02 §8.4)
|
|
@@ -138,6 +154,10 @@ class RenderStatus(TypedDict):
|
|
|
138
154
|
rollup: Aggregate {complete, total} counts.
|
|
139
155
|
nextCommand: Recommended next command for the first actionable feature, or
|
|
140
156
|
None when nothing is actionable (all complete, empty epic, or paused).
|
|
157
|
+
warnings: Human-readable diagnostics that do NOT invalidate the graph but
|
|
158
|
+
need surfacing — currently, members carrying a ``forge-verify-*.status``
|
|
159
|
+
outside ``KNOWN_VERIFY_STATUSES`` (treated as incomplete, but silently so
|
|
160
|
+
would poison the rollup + dependency gates, #148). Empty in the common case.
|
|
141
161
|
"""
|
|
142
162
|
|
|
143
163
|
epic: str
|
|
@@ -147,6 +167,7 @@ class RenderStatus(TypedDict):
|
|
|
147
167
|
parallelEligible: list[str]
|
|
148
168
|
rollup: Rollup
|
|
149
169
|
nextCommand: str | None
|
|
170
|
+
warnings: list[str]
|
|
150
171
|
|
|
151
172
|
|
|
152
173
|
# --------------------------------------------------------------------------- #
|
|
@@ -541,6 +562,11 @@ _TOP_REQUIRED: Final = (
|
|
|
541
562
|
)
|
|
542
563
|
#: Required keys on each Feature object (00 §2.2).
|
|
543
564
|
_FEATURE_REQUIRED: Final = ("name", "charter", "dependsOn", "exposes", "consumes")
|
|
565
|
+
#: Optional keys on each Feature object. `mutatesShared` is the #144 precision
|
|
566
|
+
#: hint for cross-member coupling (array of project-root-relative path strings);
|
|
567
|
+
#: schema-legal when present, ignored when absent (mirrors
|
|
568
|
+
#: epic-manifest-schema.json definitions.feature.properties.mutatesShared).
|
|
569
|
+
_FEATURE_OPTIONAL: Final = ("mutatesShared",)
|
|
544
570
|
#: Required keys on each Contract (exposes[]) object (00 §2.3).
|
|
545
571
|
_CONTRACT_REQUIRED: Final = ("name", "kind", "summary")
|
|
546
572
|
#: Required keys on each ConsumedContract (consumes[]) object (00 §2.4).
|
|
@@ -616,11 +642,16 @@ def _schema_findings(manifest: dict) -> list[Finding]:
|
|
|
616
642
|
findings.append(_schema(f"feature {label!r} missing required key {key!r}", fname))
|
|
617
643
|
for key in feat:
|
|
618
644
|
# 'status' is rejected separately above via the dedicated 'cached-status' code.
|
|
619
|
-
if key not in _FEATURE_REQUIRED and key != "status":
|
|
645
|
+
if key not in _FEATURE_REQUIRED and key not in _FEATURE_OPTIONAL and key != "status":
|
|
620
646
|
findings.append(_schema(f"feature {label!r} has unknown key {key!r}", fname))
|
|
621
647
|
for key in ("name", "charter"):
|
|
622
648
|
if key in feat and not isinstance(feat[key], str):
|
|
623
649
|
findings.append(_schema(f"feature {label!r} {key} must be a string", fname))
|
|
650
|
+
if "mutatesShared" in feat and (
|
|
651
|
+
not isinstance(feat["mutatesShared"], list)
|
|
652
|
+
or not all(isinstance(p, str) for p in feat["mutatesShared"])
|
|
653
|
+
):
|
|
654
|
+
findings.append(_schema(f"feature {label!r} mutatesShared must be an array of strings", fname)) # noqa: E501
|
|
624
655
|
if "dependsOn" in feat:
|
|
625
656
|
if not isinstance(feat["dependsOn"], list) or not all(isinstance(d, str) for d in feat["dependsOn"]): # noqa: E501
|
|
626
657
|
findings.append(_schema(f"feature {label!r} dependsOn must be an array of strings", fname)) # noqa: E501
|
|
@@ -791,7 +822,41 @@ def is_complete_for_orchestration(state: dict) -> bool:
|
|
|
791
822
|
return True
|
|
792
823
|
if not isinstance(impl, dict):
|
|
793
824
|
return False
|
|
794
|
-
return impl.get("status") in
|
|
825
|
+
return impl.get("status") in _VERIFY_ORCH_COMPLETE
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
def _verify_status_warnings(name: str, state: dict) -> list[str]:
|
|
829
|
+
"""Flag any ``forge-verify-*.status`` outside the known vocabulary (#148).
|
|
830
|
+
|
|
831
|
+
An unrecognized status (e.g. the eye-slip ``findings-resolved``, conflated with the
|
|
832
|
+
adjacent ``findingsResolved`` count) is correctly treated as incomplete by
|
|
833
|
+
``is_complete_for_orchestration`` — but *silently*, so one typo on one member
|
|
834
|
+
under-reports the whole epic rollup and fabricates phantom ``unmetDeps`` on
|
|
835
|
+
dependents. Surfacing it turns that non-local corruption into a visible diagnostic.
|
|
836
|
+
|
|
837
|
+
Non-string / malformed status values (a list, an int) are also flagged, and the
|
|
838
|
+
membership test is guarded so an unhashable value never raises.
|
|
839
|
+
"""
|
|
840
|
+
stages = state.get("stages", {})
|
|
841
|
+
if not isinstance(stages, dict):
|
|
842
|
+
return []
|
|
843
|
+
warnings: list[str] = []
|
|
844
|
+
known = ", ".join(sorted(KNOWN_VERIFY_STATUSES))
|
|
845
|
+
for stage_name, entry in stages.items():
|
|
846
|
+
if not (isinstance(stage_name, str) and stage_name.startswith("forge-verify-")):
|
|
847
|
+
continue
|
|
848
|
+
if not isinstance(entry, dict):
|
|
849
|
+
continue
|
|
850
|
+
status = entry.get("status")
|
|
851
|
+
if status is None:
|
|
852
|
+
continue
|
|
853
|
+
if isinstance(status, str) and status in KNOWN_VERIFY_STATUSES:
|
|
854
|
+
continue
|
|
855
|
+
warnings.append(
|
|
856
|
+
f"{name}: unknown {stage_name} status {status!r} "
|
|
857
|
+
f"(treated as incomplete; expected one of {known})"
|
|
858
|
+
)
|
|
859
|
+
return warnings
|
|
795
860
|
|
|
796
861
|
|
|
797
862
|
def _read_state_safely(state_path: Path) -> dict:
|
|
@@ -926,14 +991,15 @@ def render_status(epic_dir: Path, specs_dir: Path) -> RenderStatus:
|
|
|
926
991
|
rows: list[FeatureStatus] = []
|
|
927
992
|
feature_dir_by_name: dict[str, Path] = {}
|
|
928
993
|
complete: dict[str, bool] = {}
|
|
994
|
+
warnings: list[str] = []
|
|
929
995
|
for feat in features:
|
|
930
996
|
name = feat["name"]
|
|
931
997
|
member_dir = contained_path(epic_dir, name)
|
|
932
998
|
feature_dir_by_name[name] = member_dir
|
|
933
999
|
rows.append(derive_status(member_dir))
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
)
|
|
1000
|
+
member_state = _read_state_safely(member_dir / PIPELINE_STATE_FILENAME)
|
|
1001
|
+
complete[name] = is_complete_for_orchestration(member_state)
|
|
1002
|
+
warnings.extend(_verify_status_warnings(name, member_state))
|
|
937
1003
|
|
|
938
1004
|
# (4) per-feature unmetDeps + blocked. A feature that is itself complete is
|
|
939
1005
|
# never "blocked" — unmet deps only matter for work not yet finished.
|
|
@@ -988,6 +1054,7 @@ def render_status(epic_dir: Path, specs_dir: Path) -> RenderStatus:
|
|
|
988
1054
|
"parallelEligible": parallel_eligible,
|
|
989
1055
|
"rollup": rollup,
|
|
990
1056
|
"nextCommand": next_command,
|
|
1057
|
+
"warnings": warnings,
|
|
991
1058
|
}
|
|
992
1059
|
|
|
993
1060
|
|
|
@@ -1195,6 +1262,180 @@ def set_status(epic_dir: Path, specs_dir: Path, status: str) -> list[Finding]:
|
|
|
1195
1262
|
return _bump_and_write(epic_dir, specs_dir, manifest)
|
|
1196
1263
|
|
|
1197
1264
|
|
|
1265
|
+
def _load_state_strict(state_path: Path) -> dict:
|
|
1266
|
+
"""Read a .pipeline-state.json, raising UsageError on a missing/corrupt file.
|
|
1267
|
+
|
|
1268
|
+
Unlike ``_read_state_safely`` (which downgrades a torn read to ``{}`` for the
|
|
1269
|
+
read-only dashboard), an adopt mutation must NOT silently discard the
|
|
1270
|
+
standalone's stage history — a corrupt source is a hard stop (exit 2) so the
|
|
1271
|
+
human fixes it before the irreversible move.
|
|
1272
|
+
"""
|
|
1273
|
+
try:
|
|
1274
|
+
parsed = json.loads(state_path.read_text(encoding="utf-8"))
|
|
1275
|
+
except OSError as exc:
|
|
1276
|
+
raise UsageError(f"cannot read {state_path}: {exc}")
|
|
1277
|
+
except json.JSONDecodeError as exc:
|
|
1278
|
+
raise UsageError(f"{state_path} is not valid JSON: {exc}")
|
|
1279
|
+
if not isinstance(parsed, dict):
|
|
1280
|
+
raise UsageError(f"{state_path} is not a JSON object")
|
|
1281
|
+
return parsed
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
def _merge_member_state(stub: dict, standalone: dict, epic_name: str) -> dict:
|
|
1285
|
+
"""Merge a detached standalone's state onto an epic member stub (Issue #126).
|
|
1286
|
+
|
|
1287
|
+
The **stub** is the base — it carries the correct ``epic`` and ``branch``
|
|
1288
|
+
back-pointers that the epic minted. The **standalone** holds the real work, so
|
|
1289
|
+
its stage history / artifacts / currentStage overlay the stub. The ``epic``
|
|
1290
|
+
back-pointer is forced to the target epic; the stub's ``branch`` is preserved
|
|
1291
|
+
(only falling back to the standalone's when the stub has none).
|
|
1292
|
+
"""
|
|
1293
|
+
merged = dict(stub)
|
|
1294
|
+
for key in ("currentStage", "artifacts", "notes",
|
|
1295
|
+
"deferredDecisions", "epicChangeRequests"):
|
|
1296
|
+
if key in standalone:
|
|
1297
|
+
merged[key] = standalone[key]
|
|
1298
|
+
stages = dict(stub.get("stages") or {})
|
|
1299
|
+
stages.update(standalone.get("stages") or {})
|
|
1300
|
+
merged["stages"] = stages
|
|
1301
|
+
merged["epic"] = epic_name
|
|
1302
|
+
if not (isinstance(stub.get("branch"), str) and stub["branch"]) and \
|
|
1303
|
+
isinstance(standalone.get("branch"), str) and standalone["branch"]:
|
|
1304
|
+
merged["branch"] = standalone["branch"]
|
|
1305
|
+
return merged
|
|
1306
|
+
|
|
1307
|
+
|
|
1308
|
+
def adopt_feature(
|
|
1309
|
+
epic_dir: Path,
|
|
1310
|
+
specs_dir: Path,
|
|
1311
|
+
feature: str,
|
|
1312
|
+
charter: str | None,
|
|
1313
|
+
deps: list[str],
|
|
1314
|
+
) -> dict:
|
|
1315
|
+
"""Reconcile a detached standalone feature into an epic member (Issue #126).
|
|
1316
|
+
|
|
1317
|
+
The scripted recovery for a **split-brain epic** (#125): a feature that should
|
|
1318
|
+
be an epic member was forged as a flat standalone at ``{specsDir}/{feature}/``.
|
|
1319
|
+
This relocates it into the member slot ``{epicDir}/{feature}/``, merging state
|
|
1320
|
+
so the stub's ``epic``/``branch`` back-pointers survive, removes the flat dir
|
|
1321
|
+
(no residual), and adds the feature to the manifest if absent.
|
|
1322
|
+
|
|
1323
|
+
Operates on the CURRENT tree/branch — both the flat standalone and the epic
|
|
1324
|
+
manifest must already be present (the human brings a cross-branch standalone
|
|
1325
|
+
onto the epic's home branch first, per ``docs/recovery-detached-epic-member.md``;
|
|
1326
|
+
EPIC.md prose is regenerated separately via forge-0-epic). Deliberately ordered
|
|
1327
|
+
**relocate-then-manifest**: after the flat dir is gone the feature name maps to
|
|
1328
|
+
exactly one dir, so ``add_feature``'s global-uniqueness re-validation stays
|
|
1329
|
+
clean. Re-entrant — a half-finished run (files moved, manifest not yet updated)
|
|
1330
|
+
completes on re-run.
|
|
1331
|
+
|
|
1332
|
+
Args:
|
|
1333
|
+
epic_dir: The target epic subtree directory (must hold epic-manifest.json).
|
|
1334
|
+
specs_dir: The configured specs directory.
|
|
1335
|
+
feature: The detached standalone feature name to adopt.
|
|
1336
|
+
charter: Charter for the manifest entry when the feature is not yet a
|
|
1337
|
+
member; a default is used when omitted.
|
|
1338
|
+
deps: dependsOn for the new manifest entry (ignored if already a member).
|
|
1339
|
+
|
|
1340
|
+
Returns:
|
|
1341
|
+
A summary dict (``adopted``/``relocated``/``manifestUpdated``/…) on success.
|
|
1342
|
+
|
|
1343
|
+
Raises:
|
|
1344
|
+
UsageError: Unsafe name, missing manifest, nothing to adopt, or an I/O
|
|
1345
|
+
failure (exit 2).
|
|
1346
|
+
FindingsError: A blocking manifest finding from ``add_feature`` (e.g. an
|
|
1347
|
+
unknown dependency) after relocation — exit 1; re-run after fixing.
|
|
1348
|
+
"""
|
|
1349
|
+
assert_safe_name(feature)
|
|
1350
|
+
for dep in deps:
|
|
1351
|
+
assert_safe_name(dep)
|
|
1352
|
+
|
|
1353
|
+
# The epic manifest must exist on this tree (exit 2 if not — nothing to adopt into).
|
|
1354
|
+
load_manifest(epic_dir)
|
|
1355
|
+
|
|
1356
|
+
flat_dir = contained_path(specs_dir, feature)
|
|
1357
|
+
member_dir = contained_path(epic_dir, feature)
|
|
1358
|
+
if flat_dir == member_dir:
|
|
1359
|
+
raise UsageError(f"feature {feature!r} resolves to the epic dir itself")
|
|
1360
|
+
|
|
1361
|
+
flat_state = flat_dir / PIPELINE_STATE_FILENAME
|
|
1362
|
+
member_state = member_dir / PIPELINE_STATE_FILENAME
|
|
1363
|
+
flat_exists = flat_state.is_file()
|
|
1364
|
+
member_exists = member_state.is_file()
|
|
1365
|
+
|
|
1366
|
+
if not flat_exists and not member_exists:
|
|
1367
|
+
raise UsageError(
|
|
1368
|
+
f"nothing to adopt: neither a standalone {flat_dir} nor a member "
|
|
1369
|
+
f"{member_dir} has a {PIPELINE_STATE_FILENAME}"
|
|
1370
|
+
)
|
|
1371
|
+
|
|
1372
|
+
relocated = False
|
|
1373
|
+
if flat_exists:
|
|
1374
|
+
standalone = _load_state_strict(flat_state)
|
|
1375
|
+
if member_exists:
|
|
1376
|
+
merged = _merge_member_state(
|
|
1377
|
+
_read_state_safely(member_state), standalone, epic_dir.name
|
|
1378
|
+
)
|
|
1379
|
+
else:
|
|
1380
|
+
merged = dict(standalone)
|
|
1381
|
+
merged["epic"] = epic_dir.name
|
|
1382
|
+
# Move every artifact except the state file (merged separately) into the
|
|
1383
|
+
# member slot; the standalone (real work) wins on any name collision.
|
|
1384
|
+
member_dir.mkdir(parents=True, exist_ok=True)
|
|
1385
|
+
for child in sorted(flat_dir.iterdir()):
|
|
1386
|
+
if child.name == PIPELINE_STATE_FILENAME:
|
|
1387
|
+
continue
|
|
1388
|
+
dest = member_dir / child.name
|
|
1389
|
+
if dest.is_dir():
|
|
1390
|
+
shutil.rmtree(dest)
|
|
1391
|
+
elif dest.exists():
|
|
1392
|
+
dest.unlink()
|
|
1393
|
+
shutil.move(str(child), str(dest))
|
|
1394
|
+
atomic_write(member_state, merged)
|
|
1395
|
+
shutil.rmtree(flat_dir)
|
|
1396
|
+
relocated = True
|
|
1397
|
+
else:
|
|
1398
|
+
# Already nested (a prior run moved the files) — only ensure the back-pointer.
|
|
1399
|
+
stub = _read_state_safely(member_state)
|
|
1400
|
+
if stub.get("epic") != epic_dir.name:
|
|
1401
|
+
stub["epic"] = epic_dir.name
|
|
1402
|
+
atomic_write(member_state, stub)
|
|
1403
|
+
|
|
1404
|
+
# Ensure the feature is a manifest member (idempotent — the flat dir is gone now,
|
|
1405
|
+
# so add_feature's tree-uniqueness re-check sees exactly one dir for the name).
|
|
1406
|
+
manifest = load_manifest(epic_dir)
|
|
1407
|
+
already_member = any(
|
|
1408
|
+
isinstance(f, dict) and f.get("name") == feature
|
|
1409
|
+
for f in manifest.get("features", [])
|
|
1410
|
+
)
|
|
1411
|
+
manifest_updated = False
|
|
1412
|
+
if not already_member:
|
|
1413
|
+
findings = add_feature(
|
|
1414
|
+
epic_dir, specs_dir, feature,
|
|
1415
|
+
charter or f"Adopted into the {epic_dir.name} epic from a detached standalone.",
|
|
1416
|
+
deps,
|
|
1417
|
+
)
|
|
1418
|
+
if findings:
|
|
1419
|
+
raise FindingsError(findings)
|
|
1420
|
+
manifest_updated = True
|
|
1421
|
+
|
|
1422
|
+
return {
|
|
1423
|
+
"adopted": True,
|
|
1424
|
+
"epic": epic_dir.name,
|
|
1425
|
+
"feature": feature,
|
|
1426
|
+
"memberDir": str(member_dir),
|
|
1427
|
+
"relocated": relocated,
|
|
1428
|
+
"manifestUpdated": manifest_updated,
|
|
1429
|
+
"wasAlreadyMember": already_member,
|
|
1430
|
+
"nextSteps": [
|
|
1431
|
+
f"Regenerate EPIC.md prose via /feature-forge:forge-0-epic {epic_dir.name}",
|
|
1432
|
+
f"Confirm the dashboard via /feature-forge:forge {epic_dir.name}",
|
|
1433
|
+
f"Verify the member via /feature-forge:forge-verify {feature}",
|
|
1434
|
+
"Commit the surgery (stage the epic subtree and the removed flat dir).",
|
|
1435
|
+
],
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
|
|
1198
1439
|
# --------------------------------------------------------------------------- #
|
|
1199
1440
|
# CLI Dispatch (02 §9)
|
|
1200
1441
|
# --------------------------------------------------------------------------- #
|
|
@@ -1239,6 +1480,10 @@ def _print_status_table(status: RenderStatus) -> None:
|
|
|
1239
1480
|
marker = "⚠️ BLOCKING" if row["blockingEpicChangeRequests"] else "⚠️"
|
|
1240
1481
|
line += f" — {marker} {row['openEpicChangeRequests']} pending epic change(s)"
|
|
1241
1482
|
print(line)
|
|
1483
|
+
if status["warnings"]:
|
|
1484
|
+
print("Warnings:")
|
|
1485
|
+
for warning in status["warnings"]:
|
|
1486
|
+
print(f" ⚠️ {warning}")
|
|
1242
1487
|
if status["actionable"]:
|
|
1243
1488
|
print(f"Actionable: {', '.join(status['actionable'])}")
|
|
1244
1489
|
if status["parallelEligible"]:
|
|
@@ -1285,6 +1530,25 @@ def _dispatch(args: argparse.Namespace, specs_dir: Path) -> int:
|
|
|
1285
1530
|
_print_status_table(status)
|
|
1286
1531
|
return 0
|
|
1287
1532
|
|
|
1533
|
+
if cmd == "adopt-feature":
|
|
1534
|
+
epic_dir = contained_path(specs_dir, args.epic)
|
|
1535
|
+
summary = adopt_feature(
|
|
1536
|
+
epic_dir, specs_dir, args.name, args.charter,
|
|
1537
|
+
_split_list(args.depends_on),
|
|
1538
|
+
)
|
|
1539
|
+
if args.json_output:
|
|
1540
|
+
print(json.dumps(summary, indent=2, ensure_ascii=False))
|
|
1541
|
+
else:
|
|
1542
|
+
print(f"Adopted {summary['feature']!r} into epic {summary['epic']!r}.")
|
|
1543
|
+
print(f" member dir: {summary['memberDir']}")
|
|
1544
|
+
print(f" relocated files: {summary['relocated']}")
|
|
1545
|
+
print(f" manifest added: {summary['manifestUpdated']}"
|
|
1546
|
+
f"{' (already a member)' if summary['wasAlreadyMember'] else ''}")
|
|
1547
|
+
print("Next steps:")
|
|
1548
|
+
for step in summary["nextSteps"]:
|
|
1549
|
+
print(f" - {step}")
|
|
1550
|
+
return 0
|
|
1551
|
+
|
|
1288
1552
|
# Mutators ---------------------------------------------------------------
|
|
1289
1553
|
if cmd in {"add-feature", "remove-feature", "reorder", "set-dep", "set-status"}:
|
|
1290
1554
|
epic_dir = contained_path(specs_dir, args.epic)
|
|
@@ -1358,6 +1622,21 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1358
1622
|
add_specs_dir(p_add)
|
|
1359
1623
|
add_json(p_add)
|
|
1360
1624
|
|
|
1625
|
+
# adopt-feature --------------------------------------------------------- #
|
|
1626
|
+
p_adopt = sub.add_parser(
|
|
1627
|
+
"adopt-feature",
|
|
1628
|
+
help="Reconcile a detached standalone feature into an epic member (#126)",
|
|
1629
|
+
)
|
|
1630
|
+
p_adopt.add_argument("epic")
|
|
1631
|
+
p_adopt.add_argument("name")
|
|
1632
|
+
p_adopt.add_argument(
|
|
1633
|
+
"--charter", default=None,
|
|
1634
|
+
help="Charter for the manifest entry when the feature is not yet a member",
|
|
1635
|
+
)
|
|
1636
|
+
p_adopt.add_argument("--depends-on", dest="depends_on", default="", help="Comma list")
|
|
1637
|
+
add_specs_dir(p_adopt)
|
|
1638
|
+
add_json(p_adopt)
|
|
1639
|
+
|
|
1361
1640
|
# remove-feature -------------------------------------------------------- #
|
|
1362
1641
|
p_remove = sub.add_parser("remove-feature", help="Remove a member feature")
|
|
1363
1642
|
p_remove.add_argument("epic")
|
|
@@ -568,6 +568,7 @@ def write_config(answers: Answers, target: Path, sentinel: Sentinel) -> None:
|
|
|
568
568
|
"stack": None,
|
|
569
569
|
"typeCheckCommand": None,
|
|
570
570
|
"testCommand": None,
|
|
571
|
+
"smokeCommand": None,
|
|
571
572
|
"loopIterationMultiplier": 1.5,
|
|
572
573
|
# Navigator keys — kept in lockstep with forge-init.sh's emitted field set
|
|
573
574
|
# (00 §7, REQ-CFG-02) so a bootstrapped config == forge-init's + loopRunner.
|
|
@@ -21,6 +21,7 @@ cat > "$CONFIG_FILE" << 'EOF'
|
|
|
21
21
|
"stack": null,
|
|
22
22
|
"typeCheckCommand": null,
|
|
23
23
|
"testCommand": null,
|
|
24
|
+
"smokeCommand": null,
|
|
24
25
|
"loopIterationMultiplier": 1.5,
|
|
25
26
|
"autoInvokeNextStage": true,
|
|
26
27
|
"contextWindowTokens": null,
|
|
@@ -42,6 +43,7 @@ echo " commitPrefix: forge"
|
|
|
42
43
|
echo " stack: null (auto-detected during forge-2-tech)"
|
|
43
44
|
echo " typeCheckCommand: null (auto-detected during forge-2-tech)"
|
|
44
45
|
echo " testCommand: null (auto-detected during forge-2-tech)"
|
|
46
|
+
echo " smokeCommand: null (optional end-to-end smoke; enables impl-verify runnability check)"
|
|
45
47
|
echo " loopIterationMultiplier: 1.5 (multiplier for loop iterations)"
|
|
46
48
|
echo " autoInvokeNextStage: true (navigator auto-starts the next stage after you confirm)"
|
|
47
49
|
echo " contextWindowTokens: null (infer; set to 1000000 on a 1M-context model)"
|
|
@@ -21,14 +21,56 @@ is_root() { # $1 = candidate dir
|
|
|
21
21
|
[ -f "$1/.feature-forge-bundle.json" ] || [ -f "$1/.claude-plugin/plugin.json" ]
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
# Completeness gate (#152). A dir can carry the sentinel yet be a partial/stale install —
|
|
25
|
+
# a skill-only extraction, or an install predating the shared-reference fan-out — where the
|
|
26
|
+
# scripts/references the skills load at every stage are absent. Such a root looks usable but
|
|
27
|
+
# runs DEGRADED silently (hand-improvised state schema, skipped Mint Guard + scripted exit).
|
|
28
|
+
# These are the assets a complete install MUST carry beyond the sentinel; a resolved root
|
|
29
|
+
# missing any of them is reported as degraded rather than handed back as if it were whole.
|
|
30
|
+
CORE_ASSETS=(
|
|
31
|
+
"scripts/forge-session.py"
|
|
32
|
+
"references/pipeline-state-schema.json"
|
|
33
|
+
"references/stage-exit-protocol.md"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Echo the first missing core asset (relative path) for $1, or nothing when complete.
|
|
37
|
+
first_missing_asset() { # $1 = candidate dir
|
|
38
|
+
local a
|
|
39
|
+
for a in "${CORE_ASSETS[@]}"; do
|
|
40
|
+
[ -f "$1/$a" ] || { printf '%s' "$a"; return 0; }
|
|
41
|
+
done
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# A sentinel-bearing candidate that lacks a core asset is remembered here (first one wins)
|
|
45
|
+
# so a COMPLETE root found later in the probe order still takes precedence; only if the probe
|
|
46
|
+
# ends with no complete root do we report this degraded install (step 4).
|
|
47
|
+
partial_root=""
|
|
48
|
+
partial_missing=""
|
|
49
|
+
|
|
50
|
+
# Given a candidate that already passed is_root: print + exit 0 if it is complete; otherwise
|
|
51
|
+
# record it as the degraded fallback and return so probing continues. Always returns 0 on the
|
|
52
|
+
# fall-through path so `set -e` never aborts the resolver on a partial candidate.
|
|
53
|
+
accept_root() { # $1 = candidate dir (already is_root)
|
|
54
|
+
local miss
|
|
55
|
+
miss="$(first_missing_asset "$1")"
|
|
56
|
+
if [ -z "$miss" ]; then
|
|
57
|
+
printf '%s\n' "$1"
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
if [ -z "$partial_root" ]; then
|
|
61
|
+
partial_root="$1"
|
|
62
|
+
partial_missing="$miss"
|
|
63
|
+
fi
|
|
64
|
+
return 0
|
|
65
|
+
}
|
|
66
|
+
|
|
24
67
|
# ── Step 1: self-location — parent of this script's dir is the install root. ─────────────
|
|
25
68
|
# This is the PRIMARY path: a bundle ships its own scripts/forge-root.sh, so the parent of
|
|
26
69
|
# this script's dir is that bundle's root under any agent's layout.
|
|
27
70
|
self_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
28
71
|
root="$(cd -- "$self_dir/.." && pwd -P)"
|
|
29
72
|
if is_root "$root"; then
|
|
30
|
-
|
|
31
|
-
exit 0
|
|
73
|
+
accept_root "$root"
|
|
32
74
|
fi
|
|
33
75
|
|
|
34
76
|
# ── Step 2a: Claude marketplace-cache installs — ~/.claude/plugins/cache/<mp>/<plugin>/<ver>/.
|
|
@@ -42,8 +84,7 @@ fi
|
|
|
42
84
|
while IFS= read -r manifest; do
|
|
43
85
|
candidate="${manifest%/.claude-plugin/plugin.json}"
|
|
44
86
|
if is_root "$candidate"; then
|
|
45
|
-
|
|
46
|
-
exit 0
|
|
87
|
+
accept_root "$candidate"
|
|
47
88
|
fi
|
|
48
89
|
done < <(ls -t "$HOME"/.claude/plugins/cache/*/feature-forge/*/.claude-plugin/plugin.json 2>/dev/null || true)
|
|
49
90
|
|
|
@@ -69,8 +110,7 @@ for candidate in \
|
|
|
69
110
|
"$PWD/.gemini/extensions/feature-forge" \
|
|
70
111
|
; do
|
|
71
112
|
if is_root "$candidate"; then
|
|
72
|
-
|
|
73
|
-
exit 0
|
|
113
|
+
accept_root "$candidate"
|
|
74
114
|
fi
|
|
75
115
|
done
|
|
76
116
|
|
|
@@ -78,14 +118,18 @@ done
|
|
|
78
118
|
# The neutral override works for every agent; CLAUDE_PLUGIN_ROOT is kept only for backwards
|
|
79
119
|
# compatibility with existing Claude installs (C-4).
|
|
80
120
|
if [ -n "${FEATURE_FORGE_ROOT:-}" ] && is_root "$FEATURE_FORGE_ROOT"; then
|
|
81
|
-
|
|
82
|
-
exit 0
|
|
121
|
+
accept_root "$FEATURE_FORGE_ROOT"
|
|
83
122
|
fi
|
|
84
123
|
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && is_root "$CLAUDE_PLUGIN_ROOT"; then
|
|
85
|
-
|
|
86
|
-
exit 0
|
|
124
|
+
accept_root "$CLAUDE_PLUGIN_ROOT"
|
|
87
125
|
fi
|
|
88
126
|
|
|
89
|
-
# ── Step 4: failure
|
|
127
|
+
# ── Step 4: failure. A sentinel-bearing but asset-incomplete root found along the way is a
|
|
128
|
+
# DEGRADED install (#152) — report it distinctly and actionably rather than the generic
|
|
129
|
+
# cannot-locate message, so the operator knows to reinstall/update rather than reconfigure. ─
|
|
130
|
+
if [ -n "$partial_root" ]; then
|
|
131
|
+
echo "feature-forge: install incomplete/degraded at $partial_root (missing $partial_missing) — reinstall with 'npx @garygentry/feature-forge' (or 'feature-forge update' for a stale install)." >&2
|
|
132
|
+
exit 1
|
|
133
|
+
fi
|
|
90
134
|
echo "feature-forge: cannot locate install root. Set FEATURE_FORGE_ROOT to the bundle dir, or run from an installed skill dir." >&2
|
|
91
135
|
exit 1
|
|
@@ -117,8 +117,20 @@ VERIFY_TOKEN_BY_STAGE: Final[dict[str, str]] = {
|
|
|
117
117
|
|
|
118
118
|
#: A production stage status that counts as "done" for next-stage selection.
|
|
119
119
|
_DONE_STATUS: Final = "complete"
|
|
120
|
-
#:
|
|
120
|
+
#: The authoritative forge-verify status vocabulary. SOURCE OF TRUTH:
|
|
121
|
+
#: references/pipeline-state-schema.json (definitions.verifyEntry.properties.status.enum).
|
|
122
|
+
#: A status outside this set is unrecognized and must not be silently interpreted (#148).
|
|
123
|
+
#: NOTE: epic-manifest.py keeps a byte-identical copy — flat, self-contained scripts have
|
|
124
|
+
#: no shared import module (each is copied verbatim into per-agent adapter bundles).
|
|
125
|
+
KNOWN_VERIFY_STATUSES: Final = frozenset(
|
|
126
|
+
{"pending", "passed", "findings-reported", "findings-applied", "skipped"}
|
|
127
|
+
)
|
|
128
|
+
#: Verify statuses that count as "resolved" (no outstanding verify needed). A STRICT
|
|
129
|
+
#: subset of KNOWN_VERIFY_STATUSES — not collapsible into it (different meaning).
|
|
121
130
|
_VERIFY_RESOLVED: Final = frozenset({"passed", "findings-applied", "skipped"})
|
|
131
|
+
#: Per-process dedupe for the unknown-verify-status diagnostic (#148) so a single
|
|
132
|
+
#: bogus status is flagged once, not once per verify_state() call in a command.
|
|
133
|
+
_UNKNOWN_VERIFY_WARNED: set[str] = set()
|
|
122
134
|
|
|
123
135
|
#: Default context window when the model can't be inferred and config is silent.
|
|
124
136
|
_DEFAULT_WINDOW: Final = 200_000
|
|
@@ -257,6 +269,25 @@ def _verify_entry(state: dict, verify_key: str) -> dict:
|
|
|
257
269
|
return entry if isinstance(entry, dict) else {}
|
|
258
270
|
|
|
259
271
|
|
|
272
|
+
def _warn_unknown_verify_status(stage_name: str, status: object) -> None:
|
|
273
|
+
"""Emit a one-time stderr diagnostic for an out-of-vocabulary verify status (#148).
|
|
274
|
+
|
|
275
|
+
The freshness classifier maps an unrecognized status to "never verified" — correct,
|
|
276
|
+
but silent, so a typo poisons the downstream gate (e.g. forge-5-loop's dependency
|
|
277
|
+
check) with no clue. Flagging it here makes the bad value visible where it is read.
|
|
278
|
+
"""
|
|
279
|
+
key = f"{stage_name}={status!r}"
|
|
280
|
+
if key in _UNKNOWN_VERIFY_WARNED:
|
|
281
|
+
return
|
|
282
|
+
_UNKNOWN_VERIFY_WARNED.add(key)
|
|
283
|
+
known = ", ".join(sorted(KNOWN_VERIFY_STATUSES))
|
|
284
|
+
print(
|
|
285
|
+
f"feature-forge: unknown {stage_name} status {status!r} "
|
|
286
|
+
f"(treated as unverified; expected one of {known})",
|
|
287
|
+
file=sys.stderr,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
260
291
|
def verify_state(state: dict) -> tuple[str | None, str]:
|
|
261
292
|
"""Classify verify freshness for the most-recently-completed stage.
|
|
262
293
|
|
|
@@ -299,6 +330,12 @@ def verify_state(state: dict) -> tuple[str | None, str]:
|
|
|
299
330
|
if status not in _VERIFY_RESOLVED:
|
|
300
331
|
if status == "findings-reported":
|
|
301
332
|
return stage, "failing"
|
|
333
|
+
# An unrecognized status (outside KNOWN_VERIFY_STATUSES) is treated as
|
|
334
|
+
# "never verified" — defensible, but flag it once so a typo (e.g. the
|
|
335
|
+
# eye-slip 'findings-resolved') doesn't silently poison the gate that
|
|
336
|
+
# reads this label (#148). ``pending``/``None`` are known/absent → quiet.
|
|
337
|
+
if status is not None and status not in KNOWN_VERIFY_STATUSES:
|
|
338
|
+
_warn_unknown_verify_status(f"forge-verify-{token}", status)
|
|
302
339
|
return stage, "never"
|
|
303
340
|
verified_version = entry.get("verifiedStageVersion")
|
|
304
341
|
stage_version = _stage_version(state, stage)
|
|
@@ -88,10 +88,18 @@ Use these status indicators:
|
|
|
88
88
|
|
|
89
89
|
After rendering a **per-feature** dashboard for an **active** pipeline (skip this for paused/abandoned pipelines and for the Epic Dashboard), don't stop at a text suggestion — actively offer to start the next stage. This removes the copy-paste-after-clear your session / start a fresh session chore that makes long, multi-stage runs painful (especially on mobile).
|
|
90
90
|
|
|
91
|
-
**1. Read the next step.** From the `rank-features --json` output (above), find this feature's row and read its `nextStage`, `nextCommand`, `verifyPending`, `verifyCommand`, `verifyStage`, `verifyState`, `autoVerify`, `autoFix`, and `verifyGate` (the resolved gate: `none` → no verify action; `auto` → the §2b catch-up; `standard` → the §3 gate). If the feature is not in the `active` list (paused/abandoned), or `nextStage` is `null` (every production stage complete), skip the drive prompt — instead congratulate the user and, if `forge-6-docs` has not run, offer it; otherwise
|
|
91
|
+
**1. Read the next step.** From the `rank-features --json` output (above), find this feature's row and read its `nextStage`, `nextCommand`, `verifyPending`, `verifyCommand`, `verifyStage`, `verifyState`, `autoVerify`, `autoFix`, and `verifyGate` (the resolved gate: `none` → no verify action; `auto` → the §2b catch-up; `standard` → the §3 gate). If the feature is not in the `active` list (paused/abandoned), or `nextStage` is `null` (every production stage complete), skip the drive prompt — instead congratulate the user and, if `forge-6-docs` has not run, offer it; otherwise the whole pipeline is done, so **route into the Completion hand-off block below instead of dead-ending** (steps 2/2b/3 do not apply — there is no next stage to drive). If the payload has a non-empty `invalidAutoVerifyKeys`, print a one-line warning first (e.g. "⚠️ forge.config.json `autoVerifyStages` has unknown keys: … — they are ignored; fix the typo").
|
|
92
92
|
|
|
93
93
|
**Detached-epic hint (completion of a *standalone* feature, Issue #125).** When `nextStage` is `null` for a feature whose row has no `epic` (a standalone completion), run `discover-feature "{feature}" --json` (via the standard prelude) *before* congratulating. If any candidate has `isEpicMember: true` on another branch, add an **additive, non-blocking** hint — it does not change the completion, only flags a likely split-brain: "⚠️ Heads-up: `{feature}` matches a member of epic `{epic}` on branch `{stateBranch}`. This standalone pipeline may have been forged detached from that epic. To reconcile, see `docs/recovery-detached-epic-member.md` (or continue — this is only a heads-up)." Skip the hint for epic-member rows (their `epic` is non-null) and when discovery surfaces no epic-member candidate.
|
|
94
94
|
|
|
95
|
+
**Completion hand-off (fires when `nextStage` is `null` AND `forge-6-docs` has run — the pipeline is fully complete, Issue #124).** Do not dead-end on "pipeline is complete" — after congratulating, point the user at the next unit of work. This is a **terminal branch**: the drive steps (2/2b/3) do not run.
|
|
96
|
+
|
|
97
|
+
- **Epic member** (this feature's `rank-features` row has a non-null `epic`, i.e. its `.pipeline-state.json` carries an `epic` back-pointer): run `epic-manifest.py render-status "{epic}" --specs-dir "{specsDir}" --json` (standard prelude; you likely already have this from the Tier-1 epic rollup — reuse it rather than re-running). Then:
|
|
98
|
+
- If `actionable` is non-empty, offer the next member via the host's question mechanism: *"Epic '{epic}' has {total−complete} feature(s) left — next up: **{actionable[0].name}** (`{actionable[0].nextCommand}`). Start it now?"* On accept, drive it exactly like a normal next stage (honor `autoInvokeNextStage` + `Skill`-tool availability per step 3's Continue-in-this-session rule; else print `{nextCommand}`). List any remaining `actionable` members as also-available.
|
|
99
|
+
- If `actionable` is empty but the epic is **not** fully complete (members blocked on unmet `dependsOn`), say so and point at the blockers rather than offering a start.
|
|
100
|
+
- If the epic is **fully complete** (`rollup.complete == rollup.total`, `total > 0`), congratulate on the whole epic and offer the epic-level architecture doc (`/feature-forge:forge-6-docs` already surfaces this — mention it) and/or that the epic can be marked complete (`/feature-forge:forge {epic}` renders the finished dashboard).
|
|
101
|
+
- **Standalone** (row's `epic` is null): offer to start a fresh feature — *"Start a new feature: `/feature-forge:forge-1-prd <feature-name>` (or group several with `/feature-forge:forge-0-epic <epic-name>`)."* Then, from the `rank-features` `active` list, surface any **other** active pipelines (exclude this just-completed one) as a one-line list with their `nextCommand`s so the user can jump straight back into in-flight work. If the **detached-epic hint above fired** for this feature, lead with that recovery path (`docs/recovery-detached-epic-member.md`) — the split-brain reconcile is the more likely next action than starting something new.
|
|
102
|
+
|
|
95
103
|
**2. Check the context window.** Run the context-usage helper so you can advise whether to continue here or start the next stage in a fresh session:
|
|
96
104
|
```bash
|
|
97
105
|
R="$(bash -c 'for d in "${FEATURE_FORGE_ROOT:-}" "$HOME"/.claude/skills/feature-forge "$HOME"/.claude/plugins/cache/*/feature-forge/* "$HOME"/.claude/plugins/*/feature-forge "$HOME"/.agents/skills/feature-forge ./.agents/skills/feature-forge; do [ -x "$d/scripts/forge-root.sh" ] && exec "$d/scripts/forge-root.sh"; done')"
|