@garygentry/feature-forge 0.2.14 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/README.md +6 -3
  2. package/adapters/GENERATION-REPORT.md +20 -0
  3. package/adapters/claude/.feature-forge-bundle.json +1 -1
  4. package/adapters/claude/references/forge-config-schema.json +2 -2
  5. package/adapters/claude/scripts/forge-root.sh +47 -3
  6. package/adapters/claude/scripts/forge-session.py +30 -8
  7. package/adapters/claude/skills/forge-4-backlog/references/forge-config-schema.json +2 -2
  8. package/adapters/claude/skills/forge-5-loop/references/forge-config-schema.json +2 -2
  9. package/adapters/claude/skills/forge-guide/references/forge-config-schema.json +2 -2
  10. package/adapters/codex/.feature-forge-bundle.json +1 -1
  11. package/adapters/codex/references/forge-config-schema.json +2 -2
  12. package/adapters/codex/scripts/forge-root.sh +47 -3
  13. package/adapters/codex/scripts/forge-session.py +30 -8
  14. package/adapters/codex/skills/forge-4-backlog/references/forge-config-schema.json +2 -2
  15. package/adapters/codex/skills/forge-5-loop/references/forge-config-schema.json +2 -2
  16. package/adapters/codex/skills/forge-guide/references/forge-config-schema.json +2 -2
  17. package/adapters/copilot/.feature-forge-bundle.json +1 -1
  18. package/adapters/copilot/references/forge-config-schema.json +2 -2
  19. package/adapters/copilot/scripts/forge-root.sh +47 -3
  20. package/adapters/copilot/scripts/forge-session.py +30 -8
  21. package/adapters/copilot/skills/forge-4-backlog/references/forge-config-schema.json +2 -2
  22. package/adapters/copilot/skills/forge-5-loop/references/forge-config-schema.json +2 -2
  23. package/adapters/copilot/skills/forge-guide/references/forge-config-schema.json +2 -2
  24. package/adapters/cursor/.feature-forge-bundle.json +1 -1
  25. package/adapters/cursor/references/forge-config-schema.json +2 -2
  26. package/adapters/cursor/scripts/forge-root.sh +47 -3
  27. package/adapters/cursor/scripts/forge-session.py +30 -8
  28. package/adapters/cursor/skills/forge-4-backlog/references/forge-config-schema.json +2 -2
  29. package/adapters/cursor/skills/forge-5-loop/references/forge-config-schema.json +2 -2
  30. package/adapters/cursor/skills/forge-guide/references/forge-config-schema.json +2 -2
  31. package/adapters/gemini/.feature-forge-bundle.json +1 -1
  32. package/adapters/gemini/gemini-extension.json +1 -1
  33. package/adapters/gemini/references/forge-config-schema.json +2 -2
  34. package/adapters/gemini/scripts/forge-root.sh +47 -3
  35. package/adapters/gemini/scripts/forge-session.py +30 -8
  36. package/adapters/gemini/skills/forge-4-backlog/references/forge-config-schema.json +2 -2
  37. package/adapters/gemini/skills/forge-5-loop/references/forge-config-schema.json +2 -2
  38. package/adapters/gemini/skills/forge-guide/references/forge-config-schema.json +2 -2
  39. package/adapters/pi/.feature-forge-bundle.json +6 -0
  40. package/adapters/pi/agents/forge-researcher.md +139 -0
  41. package/adapters/pi/agents/forge-spec-writer.md +116 -0
  42. package/adapters/pi/agents/forge-verifier.md +126 -0
  43. package/adapters/pi/extensions/ask-user-question/LICENSE +21 -0
  44. package/adapters/pi/extensions/ask-user-question/README.md +91 -0
  45. package/adapters/pi/extensions/ask-user-question/ask-user-question.ts +298 -0
  46. package/adapters/pi/extensions/ask-user-question/config.ts +78 -0
  47. package/adapters/pi/extensions/ask-user-question/events.ts +57 -0
  48. package/adapters/pi/extensions/ask-user-question/index.ts +61 -0
  49. package/adapters/pi/extensions/ask-user-question/locales/de.json +27 -0
  50. package/adapters/pi/extensions/ask-user-question/locales/en.json +27 -0
  51. package/adapters/pi/extensions/ask-user-question/locales/es.json +27 -0
  52. package/adapters/pi/extensions/ask-user-question/locales/fr.json +27 -0
  53. package/adapters/pi/extensions/ask-user-question/locales/pt-BR.json +27 -0
  54. package/adapters/pi/extensions/ask-user-question/locales/pt.json +27 -0
  55. package/adapters/pi/extensions/ask-user-question/locales/ru.json +27 -0
  56. package/adapters/pi/extensions/ask-user-question/locales/uk.json +27 -0
  57. package/adapters/pi/extensions/ask-user-question/locales/zh.json +29 -0
  58. package/adapters/pi/extensions/ask-user-question/reconcile.ts +49 -0
  59. package/adapters/pi/extensions/ask-user-question/rpc-fallback.ts +168 -0
  60. package/adapters/pi/extensions/ask-user-question/state/build-questionnaire.ts +302 -0
  61. package/adapters/pi/extensions/ask-user-question/state/i18n-bridge.ts +53 -0
  62. package/adapters/pi/extensions/ask-user-question/state/key-router.ts +277 -0
  63. package/adapters/pi/extensions/ask-user-question/state/questionnaire-session.ts +234 -0
  64. package/adapters/pi/extensions/ask-user-question/state/row-intent.ts +145 -0
  65. package/adapters/pi/extensions/ask-user-question/state/selectors/contract.ts +26 -0
  66. package/adapters/pi/extensions/ask-user-question/state/selectors/derivations.ts +42 -0
  67. package/adapters/pi/extensions/ask-user-question/state/selectors/focus.ts +19 -0
  68. package/adapters/pi/extensions/ask-user-question/state/selectors/projections.ts +101 -0
  69. package/adapters/pi/extensions/ask-user-question/state/state-reducer.ts +292 -0
  70. package/adapters/pi/extensions/ask-user-question/state/state.ts +55 -0
  71. package/adapters/pi/extensions/ask-user-question/tool/format-answer.ts +31 -0
  72. package/adapters/pi/extensions/ask-user-question/tool/response-envelope.ts +49 -0
  73. package/adapters/pi/extensions/ask-user-question/tool/types.ts +147 -0
  74. package/adapters/pi/extensions/ask-user-question/tool/validate-questionnaire.ts +58 -0
  75. package/adapters/pi/extensions/ask-user-question/vendor-config-shim.ts +65 -0
  76. package/adapters/pi/extensions/ask-user-question/view/component-binding.ts +47 -0
  77. package/adapters/pi/extensions/ask-user-question/view/components/inline-input.ts +98 -0
  78. package/adapters/pi/extensions/ask-user-question/view/components/multi-select-view.ts +193 -0
  79. package/adapters/pi/extensions/ask-user-question/view/components/option-list-view.ts +70 -0
  80. package/adapters/pi/extensions/ask-user-question/view/components/preview/markdown-content-cache.ts +79 -0
  81. package/adapters/pi/extensions/ask-user-question/view/components/preview/preview-block-renderer.ts +111 -0
  82. package/adapters/pi/extensions/ask-user-question/view/components/preview/preview-box-renderer.ts +88 -0
  83. package/adapters/pi/extensions/ask-user-question/view/components/preview/preview-layout-decider.ts +202 -0
  84. package/adapters/pi/extensions/ask-user-question/view/components/preview/preview-pane.ts +228 -0
  85. package/adapters/pi/extensions/ask-user-question/view/components/submit-picker.ts +67 -0
  86. package/adapters/pi/extensions/ask-user-question/view/components/tab-bar.ts +59 -0
  87. package/adapters/pi/extensions/ask-user-question/view/components/wrapping-select.ts +293 -0
  88. package/adapters/pi/extensions/ask-user-question/view/dialog-builder.ts +224 -0
  89. package/adapters/pi/extensions/ask-user-question/view/props-adapter.ts +125 -0
  90. package/adapters/pi/extensions/ask-user-question/view/stateful-view.ts +26 -0
  91. package/adapters/pi/extensions/ask-user-question/view/tab-components.ts +18 -0
  92. package/adapters/pi/extensions/ask-user-question/view/tab-content-strategy.ts +252 -0
  93. package/adapters/pi/package.json +26 -0
  94. package/adapters/pi/references/epic-manifest-schema.json +125 -0
  95. package/adapters/pi/references/forge-config-schema.json +236 -0
  96. package/adapters/pi/references/pipeline-state-schema.json +191 -0
  97. package/adapters/pi/references/portable-root.md +71 -0
  98. package/adapters/pi/references/process-overview.md +143 -0
  99. package/adapters/pi/references/ralph-loop-contract.md +221 -0
  100. package/adapters/pi/references/shared-conventions.md +295 -0
  101. package/adapters/pi/references/skill-frontmatter.schema.json +17 -0
  102. package/adapters/pi/references/stack-resolution.md +54 -0
  103. package/adapters/pi/references/stacks/_generic.md +111 -0
  104. package/adapters/pi/references/stacks/go.md +157 -0
  105. package/adapters/pi/references/stacks/python.md +184 -0
  106. package/adapters/pi/references/stacks/rust.md +170 -0
  107. package/adapters/pi/references/stacks/typescript.md +134 -0
  108. package/adapters/pi/references/stage-exit-protocol.md +258 -0
  109. package/adapters/pi/references/templates/specs-hygiene/AGENTS.md +32 -0
  110. package/adapters/pi/references/templates/specs-hygiene/CLAUDE.md +31 -0
  111. package/adapters/pi/references/vendor-construct-inventory.md +50 -0
  112. package/adapters/pi/scripts/epic-manifest.py +1694 -0
  113. package/adapters/pi/scripts/forge-bootstrap.py +1070 -0
  114. package/adapters/pi/scripts/forge-init.sh +58 -0
  115. package/adapters/pi/scripts/forge-root.sh +179 -0
  116. package/adapters/pi/scripts/forge-session.py +1888 -0
  117. package/adapters/pi/scripts/validate-traceability.py +150 -0
  118. package/adapters/pi/skills/forge/SKILL.md +243 -0
  119. package/adapters/pi/skills/forge/references/pipeline-state-schema.json +191 -0
  120. package/adapters/pi/skills/forge/references/process-overview.md +143 -0
  121. package/adapters/pi/skills/forge/references/shared-conventions.md +295 -0
  122. package/adapters/pi/skills/forge/references/stage-exit-protocol.md +258 -0
  123. package/adapters/pi/skills/forge-0-epic/SKILL.md +308 -0
  124. package/adapters/pi/skills/forge-0-epic/references/edit-mode.md +266 -0
  125. package/adapters/pi/skills/forge-0-epic/references/epic-manifest-subcommands.md +75 -0
  126. package/adapters/pi/skills/forge-0-epic/references/pipeline-state-schema.json +191 -0
  127. package/adapters/pi/skills/forge-0-epic/references/portable-root.md +71 -0
  128. package/adapters/pi/skills/forge-0-epic/references/shared-conventions.md +295 -0
  129. package/adapters/pi/skills/forge-0-epic/references/stage-exit-protocol.md +258 -0
  130. package/adapters/pi/skills/forge-1-prd/SKILL.md +164 -0
  131. package/adapters/pi/skills/forge-1-prd/references/pipeline-state-schema.json +191 -0
  132. package/adapters/pi/skills/forge-1-prd/references/prd-template.md +106 -0
  133. package/adapters/pi/skills/forge-1-prd/references/shared-conventions.md +295 -0
  134. package/adapters/pi/skills/forge-1-prd/references/stage-exit-protocol.md +258 -0
  135. package/adapters/pi/skills/forge-2-tech/SKILL.md +225 -0
  136. package/adapters/pi/skills/forge-2-tech/references/pipeline-state-schema.json +191 -0
  137. package/adapters/pi/skills/forge-2-tech/references/shared-conventions.md +295 -0
  138. package/adapters/pi/skills/forge-2-tech/references/stack-discovery-checklist.md +95 -0
  139. package/adapters/pi/skills/forge-2-tech/references/stack-resolution.md +54 -0
  140. package/adapters/pi/skills/forge-2-tech/references/stacks/_generic.md +111 -0
  141. package/adapters/pi/skills/forge-2-tech/references/stacks/go.md +157 -0
  142. package/adapters/pi/skills/forge-2-tech/references/stacks/python.md +184 -0
  143. package/adapters/pi/skills/forge-2-tech/references/stacks/rust.md +170 -0
  144. package/adapters/pi/skills/forge-2-tech/references/stacks/typescript.md +134 -0
  145. package/adapters/pi/skills/forge-2-tech/references/stage-exit-protocol.md +258 -0
  146. package/adapters/pi/skills/forge-3-specs/SKILL.md +178 -0
  147. package/adapters/pi/skills/forge-3-specs/references/pipeline-state-schema.json +191 -0
  148. package/adapters/pi/skills/forge-3-specs/references/shared-conventions.md +295 -0
  149. package/adapters/pi/skills/forge-3-specs/references/spec-archetypes.md +106 -0
  150. package/adapters/pi/skills/forge-3-specs/references/spec-examples.md +71 -0
  151. package/adapters/pi/skills/forge-3-specs/references/stacks/_generic.md +111 -0
  152. package/adapters/pi/skills/forge-3-specs/references/stacks/go.md +157 -0
  153. package/adapters/pi/skills/forge-3-specs/references/stacks/python.md +184 -0
  154. package/adapters/pi/skills/forge-3-specs/references/stacks/rust.md +170 -0
  155. package/adapters/pi/skills/forge-3-specs/references/stacks/typescript.md +134 -0
  156. package/adapters/pi/skills/forge-3-specs/references/stage-exit-protocol.md +258 -0
  157. package/adapters/pi/skills/forge-4-backlog/SKILL.md +175 -0
  158. package/adapters/pi/skills/forge-4-backlog/references/forge-config-schema.json +236 -0
  159. package/adapters/pi/skills/forge-4-backlog/references/pipeline-state-schema.json +191 -0
  160. package/adapters/pi/skills/forge-4-backlog/references/shared-conventions.md +295 -0
  161. package/adapters/pi/skills/forge-4-backlog/references/stage-exit-protocol.md +258 -0
  162. package/adapters/pi/skills/forge-5-loop/SKILL.md +314 -0
  163. package/adapters/pi/skills/forge-5-loop/references/forge-config-schema.json +236 -0
  164. package/adapters/pi/skills/forge-5-loop/references/ralph-loop-contract.md +221 -0
  165. package/adapters/pi/skills/forge-5-loop/references/result-reporting.md +85 -0
  166. package/adapters/pi/skills/forge-5-loop/references/runner-contract.md +341 -0
  167. package/adapters/pi/skills/forge-5-loop/references/shared-conventions.md +295 -0
  168. package/adapters/pi/skills/forge-5-loop/references/stage-exit-protocol.md +258 -0
  169. package/adapters/pi/skills/forge-6-docs/SKILL.md +202 -0
  170. package/adapters/pi/skills/forge-6-docs/references/doc-conventions.md +126 -0
  171. package/adapters/pi/skills/forge-6-docs/references/pipeline-state-schema.json +191 -0
  172. package/adapters/pi/skills/forge-6-docs/references/shared-conventions.md +295 -0
  173. package/adapters/pi/skills/forge-bootstrap/SKILL.md +250 -0
  174. package/adapters/pi/skills/forge-bootstrap/references/templates/ci/github-actions.yml +12 -0
  175. package/adapters/pi/skills/forge-bootstrap/references/templates/generic/run.sh +3 -0
  176. package/adapters/pi/skills/forge-bootstrap/references/templates/generic/test.sh +13 -0
  177. package/adapters/pi/skills/forge-bootstrap/references/templates/go/go.mod +3 -0
  178. package/adapters/pi/skills/forge-bootstrap/references/templates/go/main.go +12 -0
  179. package/adapters/pi/skills/forge-bootstrap/references/templates/go/main_test.go +11 -0
  180. package/adapters/pi/skills/forge-bootstrap/references/templates/hygiene/AGENTS.md +35 -0
  181. package/adapters/pi/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +36 -0
  182. package/adapters/pi/skills/forge-bootstrap/references/templates/hygiene/README.md +11 -0
  183. package/adapters/pi/skills/forge-bootstrap/references/templates/licenses/Apache-2.0/LICENSE +198 -0
  184. package/adapters/pi/skills/forge-bootstrap/references/templates/licenses/MIT/LICENSE +21 -0
  185. package/adapters/pi/skills/forge-bootstrap/references/templates/python/pyproject.toml +24 -0
  186. package/adapters/pi/skills/forge-bootstrap/references/templates/python/src/{{PKG}}/__init__.py +5 -0
  187. package/adapters/pi/skills/forge-bootstrap/references/templates/python/src/{{PKG}}/main.py +13 -0
  188. package/adapters/pi/skills/forge-bootstrap/references/templates/python/tests/test_smoke.py +8 -0
  189. package/adapters/pi/skills/forge-bootstrap/references/templates/rust/Cargo.toml +15 -0
  190. package/adapters/pi/skills/forge-bootstrap/references/templates/rust/src/lib.rs +7 -0
  191. package/adapters/pi/skills/forge-bootstrap/references/templates/rust/src/main.rs +5 -0
  192. package/adapters/pi/skills/forge-bootstrap/references/templates/rust/tests/smoke.rs +6 -0
  193. package/adapters/pi/skills/forge-bootstrap/references/templates/typescript/package.json +15 -0
  194. package/adapters/pi/skills/forge-bootstrap/references/templates/typescript/src/index.ts +4 -0
  195. package/adapters/pi/skills/forge-bootstrap/references/templates/typescript/test/smoke.test.ts +6 -0
  196. package/adapters/pi/skills/forge-bootstrap/references/templates/typescript/tsconfig.json +14 -0
  197. package/adapters/pi/skills/forge-fix/SKILL.md +98 -0
  198. package/adapters/pi/skills/forge-fix/references/shared-conventions.md +295 -0
  199. package/adapters/pi/skills/forge-fix/references/stage-exit-protocol.md +258 -0
  200. package/adapters/pi/skills/forge-guide/SKILL.md +192 -0
  201. package/adapters/pi/skills/forge-guide/references/forge-config-schema.json +236 -0
  202. package/adapters/pi/skills/forge-guide/references/process-overview.md +143 -0
  203. package/adapters/pi/skills/forge-guide/references/ralph-loop-contract.md +221 -0
  204. package/adapters/pi/skills/forge-guide/references/shared-conventions.md +295 -0
  205. package/adapters/pi/skills/forge-guide/references/stack-resolution.md +54 -0
  206. package/adapters/pi/skills/forge-guide/references/stacks/_generic.md +111 -0
  207. package/adapters/pi/skills/forge-guide/references/stacks/go.md +157 -0
  208. package/adapters/pi/skills/forge-guide/references/stacks/python.md +184 -0
  209. package/adapters/pi/skills/forge-guide/references/stacks/rust.md +170 -0
  210. package/adapters/pi/skills/forge-guide/references/stacks/typescript.md +134 -0
  211. package/adapters/pi/skills/forge-init/SKILL.md +72 -0
  212. package/adapters/pi/skills/forge-verify/SKILL.md +273 -0
  213. package/adapters/pi/skills/forge-verify/references/pipeline-state-schema.json +191 -0
  214. package/adapters/pi/skills/forge-verify/references/shared-conventions.md +295 -0
  215. package/adapters/pi/skills/forge-verify/references/verification-checklists.md +477 -0
  216. package/dist/agent-targets.d.ts +1 -1
  217. package/dist/agent-targets.js +23 -3
  218. package/dist/detect.d.ts +1 -1
  219. package/dist/detect.js +2 -1
  220. package/dist/manifest.d.ts +1 -1
  221. package/dist/manifest.js +2 -2
  222. package/dist/placements.js +5 -1
  223. package/dist/rauf.d.ts +4 -4
  224. package/dist/rauf.js +3 -3
  225. package/dist/types.d.ts +31 -6
  226. package/dist/types.js +6 -3
  227. package/package.json +14 -3
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ """Validate requirement traceability between PRD and implementation specs.
3
+
4
+ Extracts REQ-XXX-NN identifiers from a PRD file and checks that every
5
+ requirement is referenced in at least one implementation spec document.
6
+ Also reports orphaned references (IDs found in specs but not in PRD).
7
+
8
+ Usage:
9
+ python validate-traceability.py <prd-path> <specs-dir> [--json]
10
+
11
+ Exit codes:
12
+ 0 = all requirements covered, no orphans
13
+ 1 = gaps or orphans found
14
+ 2 = file not found or read error
15
+ """
16
+
17
+ import argparse
18
+ import json
19
+ import re
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ REQ_PATTERN = re.compile(r"REQ-[A-Z]+-\d+")
24
+
25
+
26
+ def extract_req_ids(text: str) -> set[str]:
27
+ """Extract all unique REQ-XXX-NN identifiers from text."""
28
+ return set(REQ_PATTERN.findall(text))
29
+
30
+
31
+ def main() -> int:
32
+ parser = argparse.ArgumentParser(
33
+ description="Validate requirement traceability between PRD and specs"
34
+ )
35
+ parser.add_argument("prd_path", help="Path to PRD.md file")
36
+ parser.add_argument("specs_dir", help="Directory containing ##-*.md spec files")
37
+ parser.add_argument(
38
+ "--json", action="store_true", dest="json_output", help="Output as JSON"
39
+ )
40
+ args = parser.parse_args()
41
+
42
+ prd_path = Path(args.prd_path)
43
+ specs_dir = Path(args.specs_dir)
44
+
45
+ # Read PRD
46
+ if not prd_path.exists():
47
+ print(f"Error: PRD file not found: {prd_path}", file=sys.stderr)
48
+ return 2
49
+
50
+ try:
51
+ prd_text = prd_path.read_text()
52
+ except OSError as e:
53
+ print(f"Error reading PRD: {e}", file=sys.stderr)
54
+ return 2
55
+
56
+ prd_reqs = extract_req_ids(prd_text)
57
+
58
+ if not prd_reqs:
59
+ print(f"Warning: No REQ-XXX-NN identifiers found in {prd_path}", file=sys.stderr)
60
+
61
+ # Read spec files (##-*.md pattern)
62
+ if not specs_dir.exists():
63
+ print(f"Error: Specs directory not found: {specs_dir}", file=sys.stderr)
64
+ return 2
65
+
66
+ spec_files = sorted(specs_dir.glob("[0-9][0-9]-*.md"))
67
+ if not spec_files:
68
+ print(f"Warning: No spec files matching ##-*.md found in {specs_dir}", file=sys.stderr)
69
+
70
+ # Track which specs cover which requirements
71
+ spec_reqs: dict[str, set[str]] = {}
72
+ all_spec_reqs: set[str] = set()
73
+
74
+ for spec_file in spec_files:
75
+ try:
76
+ text = spec_file.read_text()
77
+ reqs = extract_req_ids(text)
78
+ spec_reqs[spec_file.name] = reqs
79
+ all_spec_reqs |= reqs
80
+ except OSError as e:
81
+ print(f"Warning: Could not read {spec_file}: {e}", file=sys.stderr)
82
+
83
+ # Also check TRACEABILITY.md if it exists
84
+ traceability_file = specs_dir / "TRACEABILITY.md"
85
+ if traceability_file.exists():
86
+ try:
87
+ text = traceability_file.read_text()
88
+ trace_reqs = extract_req_ids(text)
89
+ spec_reqs["TRACEABILITY.md"] = trace_reqs
90
+ all_spec_reqs |= trace_reqs
91
+ except OSError:
92
+ pass
93
+
94
+ # Analysis
95
+ uncovered = sorted(prd_reqs - all_spec_reqs)
96
+ orphaned = sorted(all_spec_reqs - prd_reqs)
97
+
98
+ # Per-requirement coverage map
99
+ coverage: dict[str, list[str]] = {}
100
+ for req_id in sorted(prd_reqs):
101
+ covering_specs = [
102
+ name for name, reqs in spec_reqs.items() if req_id in reqs
103
+ ]
104
+ coverage[req_id] = covering_specs
105
+
106
+ has_issues = bool(uncovered or orphaned)
107
+
108
+ if args.json_output:
109
+ result = {
110
+ "prd_file": str(prd_path),
111
+ "specs_dir": str(specs_dir),
112
+ "total_requirements": len(prd_reqs),
113
+ "total_spec_files": len(spec_files),
114
+ "uncovered_requirements": uncovered,
115
+ "orphaned_references": orphaned,
116
+ "coverage": coverage,
117
+ "valid": not has_issues,
118
+ }
119
+ print(json.dumps(result, indent=2))
120
+ else:
121
+ print(f"PRD: {prd_path} ({len(prd_reqs)} requirements)")
122
+ print(f"Specs: {specs_dir} ({len(spec_files)} spec files)")
123
+ print()
124
+
125
+ if uncovered:
126
+ print(f"UNCOVERED REQUIREMENTS ({len(uncovered)}):")
127
+ for req_id in uncovered:
128
+ print(f" - {req_id}: not found in any spec file")
129
+ print()
130
+
131
+ if orphaned:
132
+ print(f"ORPHANED REFERENCES ({len(orphaned)}):")
133
+ for req_id in orphaned:
134
+ sources = [
135
+ name for name, reqs in spec_reqs.items() if req_id in reqs
136
+ ]
137
+ print(f" - {req_id}: found in {', '.join(sources)} but not in PRD")
138
+ print()
139
+
140
+ if not has_issues:
141
+ print("All requirements covered. No orphaned references.")
142
+ else:
143
+ total_issues = len(uncovered) + len(orphaned)
144
+ print(f"Found {total_issues} issue(s).")
145
+
146
+ return 1 if has_issues else 0
147
+
148
+
149
+ if __name__ == "__main__":
150
+ sys.exit(main())
@@ -0,0 +1,243 @@
1
+ ---
2
+ # GENERATED — DO NOT EDIT. Source: skills/forge/SKILL.md. Regenerate: python3 scripts/build-adapters.py
3
+ name: forge
4
+ description: Feature-forge pipeline navigator and status dashboard. Use when the user references the forge pipeline, asks about forge status or progress, types /skill:forge, or wants to check what stage a feature is at in the forge pipeline. Do NOT use for general feature requests, project status, or tasks unrelated to the forge development pipeline.
5
+ ---
6
+
7
+ # Feature Forge — Pipeline Navigator
8
+
9
+ You are the navigator for the feature-forge development pipeline. Your job is to orient the user: show where they are, what's been done, and what's next.
10
+
11
+ ## Behavior
12
+
13
+ ### 1. Read Configuration
14
+
15
+ Read and follow `references/shared-conventions.md` for configuration reading (feature name validation, config defaults, force mode).
16
+
17
+ For pipeline architecture details, read `references/process-overview.md`.
18
+
19
+ ### 2. Determine Context
20
+
21
+ **If a feature name is provided** (e.g., `/skill:forge auth`):
22
+ - **First test whether the name is an epic:** if `{specsDir}/{name}/epic-manifest.json` exists, render the **Epic Dashboard** (see format below) and stop — do not treat it as a feature.
23
+ - Otherwise, resolve the name via the **Feature Directory Resolution** block in `references/shared-conventions.md` (so a nested epic-member name finds its dashboard too). On a resolution failure (`not-found` / `ambiguous` at exit 1; `unsafe-name` or a path-containment escape at exit 2), surface it verbatim.
24
+ - On `not-found`, first run the cross-branch discovery step from that same Feature Directory Resolution block (`forge-session.py discover-feature` — the state may live on a topic branch or an unfetched remote branch). Candidates → offer switch / fetch+switch per that block (explicit accept + clean tree only), then re-resolve and render the dashboard. Only when discovery also returns nothing, ask: "No pipeline exists for '{feature}' on any branch. Want to start one? Run `/skill:forge-1-prd {feature}` to begin." Never render a dashboard from memory of earlier sessions (anti-fabrication guard).
25
+ - If resolution succeeds, display the per-feature pipeline status dashboard (see format below) from `{resolvedFeatureDir}/`.
26
+
27
+ **If no feature name is provided:** list in two tiers.
28
+
29
+ 1. **Epics first.** Identify epic directories as any `{specsDir}/*/` that directly contains an `epic-manifest.json` **and no `.pipeline-state.json` of its own** (an epic root is never itself a feature). For each epic, run:
30
+ ```bash
31
+ 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')"
32
+ [ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
33
+ python3 "$R/scripts/epic-manifest.py" render-status "{epic}" --specs-dir "{specsDir}" --json
34
+ ```
35
+ and show one rollup line: `{epic} — {complete}/{total} complete, next: {nextCommand}`.
36
+ 2. **Standalone features below.** Scan the remaining `{specsDir}/*/` that directly contain a `.pipeline-state.json` **without** an `epic` back-pointer. A nested member's `.pipeline-state.json` is **attributed to its epic (Tier 1), never listed as a standalone feature**.
37
+ - **Rank by recency.** Run the recency ranker so the most-recently-touched active feature is the default — the user rarely has to type a name (especially on mobile after a `/new`):
38
+ ```bash
39
+ 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')"
40
+ [ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
41
+ python3 "$R/scripts/forge-session.py" rank-features --specs-dir "{specsDir}" --json
42
+ ```
43
+ This returns `{active: [...], counts: {...}}` with active features sorted by `updatedAt` **descending** (row 0 is the most recent). Each row carries `currentStage`, `nextStage`, `nextCommand`, `verifyPending`, `verifyCommand`, `verifyStage`, `verifyState` (`fresh`/`stale`/`failing`/`never`/`none`), `autoVerify` (the effective per-stage setting), `autoFix` (the single source of stage order), and `verifyGate` (`none`/`auto`/`standard` — the single resolved verify-gate, computed once by the ranker; read it rather than re-deriving from `verifyPending`+`autoVerify`). A top-level `invalidAutoVerifyKeys` array appears when `forge.config.json` has `autoVerifyStages` keys outside the five verify-capable stages — surface it as a one-line warning. The `active` list excludes nested epic members surfaced in Tier 1 — but the ranker scans them too, so ignore rows whose `epic` is non-null here (they belong to the epic rollup).
44
+ - **Pick the feature:** if exactly one active standalone pipeline exists, show its dashboard. If multiple exist, use `AskUserQuestion` — **list the most-recently-updated first, labeled `(recommended)`**, each option's description showing its `currentStage` and a relative age ("updated 2h ago"). Always include a free-form escape ("A different feature / something else") so the user is never boxed in. Then render the chosen feature's dashboard.
45
+
46
+ If no epics and no standalone features exist **on the current branch**, do not conclude the pipeline is empty yet — the state may live on topic branches (a fresh clone / default-branch session sees nothing on disk). Run `discover-feature --all` (via the standard prelude: `python3 "$R/scripts/forge-session.py" discover-feature --all --specs-dir "{specsDir}" --json`). If it surfaces features, list them with their branches and offer to switch (explicit accept + clean tree only, per the Feature Directory Resolution block), then render that feature's dashboard. Only when `--all` also returns nothing, say: "No active feature pipelines found. Start one with `/skill:forge-1-prd <feature-name>` or group several with `/skill:forge-0-epic <epic-name>`." Never fabricate a dashboard from memory of earlier sessions.
47
+
48
+ The feature name must be a single kebab-case token. If the user provides multiple words (e.g., "user auth flow"), convert to kebab-case: `user-auth-flow`.
49
+
50
+ ### 3. Pipeline Status Dashboard
51
+
52
+ Write pipeline state conforming to `references/pipeline-state-schema.json`.
53
+
54
+ Display a clear, scannable status for the feature:
55
+
56
+ ```
57
+ Feature: {feature} [active]
58
+ Stage: {currentStage} ({status}, started {relative time})
59
+
60
+ ✅ forge-1-prd → PRD.md (v{n})
61
+ ⬜ forge-verify (prd) ← show only if forge-1-prd is complete
62
+ ✅ forge-2-tech → tech-spec.md (v{n}, ⚠️ not yet verified)
63
+ ⬜ forge-verify (tech) ← show only if forge-2-tech is complete
64
+ 🔄 forge-3-specs → in progress
65
+ ⬜ forge-verify (specs) ← show only if forge-3-specs is complete
66
+ ⬜ forge-4-backlog
67
+ ⬜ forge-verify (backlog) ← show only if forge-4-backlog is complete
68
+ ⬜ forge-5-loop
69
+ ⬜ forge-6-docs
70
+
71
+ Next: Continue with /skill:forge-3-specs {feature}
72
+ Or verify tech-spec with /skill:forge-verify {feature}
73
+
74
+ Notes: "{any persisted notes}"
75
+ ```
76
+
77
+ Use these status indicators:
78
+ - ✅ = complete
79
+ - ✅⚠️ = complete but not yet verified
80
+ - 🔄 = in progress
81
+ - ⬜ = pending
82
+ - ❌ = verification found issues (not yet fixed)
83
+ - ✅🔍 = verified and fixes applied
84
+ - ⏭️ = verification skipped (user chose to proceed without verifying)
85
+ - ⚠️ = stale (built against an older version of an upstream artifact)
86
+
87
+ ### 3b. Drive to the Next Stage
88
+
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-`/new` chore that makes long, multi-stage runs painful (especially on mobile).
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 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
+
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
+
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 `AskUserQuestion`: *"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 (`/skill:forge-6-docs` already surfaces this — mention it) and/or that the epic can be marked complete (`/skill:forge {epic}` renders the finished dashboard).
101
+ - **Standalone** (row's `epic` is null): offer to start a fresh feature — *"Start a new feature: `/skill:forge-1-prd <feature-name>` (or group several with `/skill: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
+
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:
104
+ ```bash
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')"
106
+ [ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
107
+ python3 "$R/scripts/forge-session.py" context-usage --json
108
+ ```
109
+ - `{"available": true, ...}` → note `pct` (e.g. "context ~68% full") and `overThreshold`. Window/threshold come from `contextWindowTokens` / `contextWarnThreshold` in `forge.config.json` (the helper defaults to a 200k window and 0.7 threshold, and auto-bumps the assumed window to 1M once observed usage exceeds 200k; **on a 1M-context model set `contextWindowTokens: 1000000` so the percentage is accurate below 200k too** — 1M can't be detected from the transcript until usage crosses 200k).
110
+ - `{"available": false, ...}` → omit context advice silently (non-Claude host, or a fresh session with no transcript). Never treat this as an error.
111
+
112
+ **2b. Auto-verify catch-up (when `verifyPending` is true).** Under this behavior the just-completed authoring stage runs auto-verify **in-stage** (`references/stage-exit-protocol.md`, in-stage verify block), which clears `verifyPending` — so on the normal path this branch does **nothing**. It fires only as a **catch-up**: `verifyPending` is still true because the producing stage could not dispatch a clean-room subagent (non-Claude host), or ran before this behavior landed. When it does fire, decide whether verify runs automatically (identical logic to the in-stage run, so a stage that already verified is never double-verified):
113
+
114
+ - **`autoVerify` is true for the just-completed `verifyStage`** → **skip the verify question entirely** and run verify now, *provided it can run clean-room*. Auto-verify is safe to run unattended only because verify executes in a fresh `forge-verifier` subagent that inherits none of this session's context (so no `/new` is needed and only a compact digest returns). Guard the clean-room assumption: proceed unattended **only when the host's subagent mechanism + `forge-verifier` subagent are available**. Invoke `feature-forge:forge-verify` via the host's skill-invocation mechanism in **require-clean (`auto`) mode** — in that mode forge-verify refuses to run inline and returns a sentinel if the subagent is not dispatchable (see `skills/forge-verify/SKILL.md`). Then:
115
+ - **Sentinel returned (clean-room unavailable)** → do **not** run verify inline. Degrade to the manual gate: fall through to step 3 with the **"Verify `{stage}` first (manual)"** option included, and if `overThreshold`, recommend "/new, then verify in a clean session." Verify state stays outstanding; the stage is never advanced on false assurance.
116
+ - **Verify passed / no findings** → proceed to the normal advance gate (step 3), no verify option.
117
+ - **Verify found findings** →
118
+ - **`autoFix` is true AND preconditions hold** — the findings document has **zero unresolved decision points** and the **working tree is clean** → invoke `feature-forge:forge-fix` via the host's skill-invocation mechanism, then run a **mandatory re-verify** (require-clean mode). Advance only if the re-verify passes. On **any** precondition miss (decisions required, dirty tree), a forge-fix early stop, or a red re-verify → fall back to the digest + prompt below (never a silent partial mutation).
119
+ - **`autoFix` is false (default), or preconditions failed** → present a **compact findings digest** as text, then `AskUserQuestion`: *Apply fixes now (forge-fix)* / *Review findings* / *Skip & advance*.
120
+ - **`autoVerify` is false/unconfigured** → do not auto-run; use the advance gate in step 3, which gains the folded opt-in verify options ("Verify `{stage}` now" and "Verify `{stage}` now + enable auto-verify going forward").
121
+
122
+ **3. Offer the next step via `AskUserQuestion`.** (Reached when auto-verify did not fully resolve the step, or `verifyPending` is false.) Output the dashboard + a one-line context note as text, then ask (per the Decision Support protocol in `references/shared-conventions.md`). **Present this gate exactly once and act only on the option the user chooses — never also narrate the not-taken branch.** Concretely: do not print the "start in a clean session" recommendation as prose and *then* auto-invoke the next stage in the same session (or the reverse) — the `AskUserQuestion` answer is the single decision, and only the chosen path's action follows. This is the same **Stage Exit Protocol** the stage skills stamp (`references/stage-exit-protocol.md`) — a clean session at the boundary is recommended on its own merits, and `overThreshold` only modulates *how emphatically*, never *whether*. Options, in this order:
123
+ - **Start `{nextStage}` in a clean session** — **recommended, unconditionally**, at every boundary. A clean session is the right default for the next stage on its own merits, independent of window size. The work survives a clear because all state is on disk: instruct the user to `/new`, then re-run `/skill:forge {feature}` (or run `{nextCommand}` directly) in the fresh session. Note plainly that you cannot `/new` for them. **When `overThreshold` is true**, strengthen this wording (the window is also genuinely full) and add mid-stage compaction advice; **when the window is healthy**, still recommend the clean session — `overThreshold` is a *secondary, additive* modifier here, not the clear on/off switch.
124
+ - **Continue in this session** — the always-available alternative, reasonable when the window is nearly empty and the user would rather not clear. Not the default.
125
+ - **Verify `{stage}` now** — include **only when `verifyPending` is true and `autoVerify` is false**, and the clean-room path is available. **Recommended**, since verify is clean-room and rarely worth skipping. Runs verify now (require-clean mode); **does not** change config.
126
+ - **Verify `{stage}` now + enable auto-verify going forward** — same trigger as the option above; runs verify now **and** patches `autoVerify: true` into `forge.config.json` in place (preserve formatting and other keys) so every future stage verifies automatically without prompting. This is the **folded** config-enable — it replaces the old post-hoc "make auto-verify the default?" follow-up. No silent config writes: the config changes **only** when the user picks this option.
127
+ - **Verify `{stage}` first (manual)** — include **only when `verifyPending` is true** and the clean-room path is unavailable (the Tier-2 degradation above), or the host lacks the host's skill-invocation and subagent mechanisms; selecting it runs `{verifyCommand}` (inline / manual). Offer the "enable auto-verify" choice as text only if a config write is possible.
128
+ - **Pick a different stage** — free-form escape to any stage or other action.
129
+
130
+ **4. Act on the choice.**
131
+ - **Clean session** (the default) → give the exact next command and the `/new`-then-re-run instruction; do not invoke anything (you cannot `/new` for the user).
132
+ - **Continue in this session** → if `autoInvokeNextStage` is true (default) **and** the host's skill-invocation mechanism is available, invoke the chosen stage **via the host's skill-invocation mechanism** in this same session (e.g. `skill: "feature-forge:forge-3-specs"`, `args: "{feature}"`) — no retyping, no paste. If `autoInvokeNextStage` is false, or the host's skill-invocation mechanism is unavailable (a non-Claude host), fall back to printing `{nextCommand}` prominently for the user to run.
133
+ - **Verify now / Verify now + enable auto-verify** → invoke `feature-forge:forge-verify` via the host's skill-invocation mechanism (require-clean mode). For the **enable-auto-verify** variant, additionally patch `autoVerify: true` into `forge.config.json` in place (preserve formatting and other keys) — never a silent write. On a non-Claude host or when degrading to the manual/inline gate, print `{verifyCommand}` instead.
134
+ - **Different stage** → honor the free-form request.
135
+
136
+ **Host fallback.** On a non-Claude host or when the host's skill-invocation and subagent mechanisms are unavailable, auto-verify never runs unattended — fall back to printing `{verifyCommand}` exactly as today, mirroring `autoInvokeNextStage`.
137
+
138
+ This applies whether the feature was named explicitly (`/skill:forge {feature}`) or resolved from the recency default.
139
+
140
+ ### Epic Dashboard
141
+
142
+ When the named argument is an epic (`{specsDir}/{name}/epic-manifest.json` exists), render the epic dashboard instead of a per-feature one. Run:
143
+
144
+ ```bash
145
+ 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')"
146
+ [ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
147
+ python3 "$R/scripts/epic-manifest.py" render-status "{epic}" --specs-dir "{specsDir}" --json
148
+ ```
149
+
150
+ and render from its output:
151
+
152
+ - **Epic header:** name + `status` (active | paused | abandoned | complete).
153
+ - **Dependency graph:** each feature with its `dependsOn`, as an arrow list or indented tree (the helper guarantees the graph is acyclic).
154
+ - **Per-feature rows:** reuse the **existing status indicators** below (✅/✅⚠️/🔄/⬜/❌/✅🔍/⏭️/⚠️), driven by each feature's derived `stage`/`status`. Mark `blocked` features and list their `unmetDeps`.
155
+ - **Pending epic changes:** for any feature with `openEpicChangeRequests > 0`, append a ⚠️ marker and a hint: *"N pending epic change(s) — run `/skill:forge-0-epic {epic}` to reconcile."* If `blockingEpicChangeRequests > 0`, use a stronger marker (⚠️ **blocking**) and word it *"reconcile the epic **before** writing specs"* — this mirrors the pause-now vs finish-then split that stage-exit already routes on. Take these counts **only** from `render-status --json` (`features[].openEpicChangeRequests` / `.blockingEpicChangeRequests`); do not read member `.pipeline-state.json` directly for them.
156
+ - **Actionable vs blocked:** list the `actionable` set and the recommended `nextCommand`.
157
+ - **Rollup:** `{complete}/{total} features complete`.
158
+
159
+ Example:
160
+
161
+ ```
162
+ Epic: auth-overhaul [active] — 2/4 features complete
163
+
164
+ Dependency graph:
165
+ config-store (no deps)
166
+ token-service → config-store
167
+ api-gateway → token-service
168
+ audit-log (no deps)
169
+
170
+ ✅ config-store complete
171
+ 🔄 token-service forge-3-specs (in progress) — ⚠️ 1 pending epic change
172
+ ⬜ api-gateway blocked — waiting on token-service
173
+ ✅ audit-log complete
174
+
175
+ Actionable now: token-service
176
+ Next: /skill:forge-3-specs token-service
177
+ ⚠️ Pending epic changes: token-service (1). Run /skill:forge-0-epic auth-overhaul to reconcile.
178
+ ```
179
+
180
+ All of this is reconstructed **purely from disk** — the manifest plus each member's `.pipeline-state.json`, with no in-memory state — so a fresh session renders the same dashboard. If `render-status` fails, do not render a partial dashboard; surface per the exit-1/exit-2 split in the **Feature Directory Resolution** block of `references/shared-conventions.md` (exit 1 → parse `{findings[]}` from stdout; exit 2 → surface the plain `Error:` stderr line verbatim).
181
+
182
+ ### 4. Notes Management
183
+
184
+ If the user says something like "note: switching to jose for JWT" or "remember: we decided X", update the `notes` field in `.pipeline-state.json`. This helps preserve context across session clears.
185
+
186
+ ### 5. Available Commands Reference
187
+
188
+ When showing the dashboard, include a compact reference:
189
+
190
+ ```
191
+ Commands:
192
+ /skill:forge-1-prd <feature> Create requirements document
193
+ /skill:forge-2-tech <feature> Create technical spec
194
+ /skill:forge-3-specs <feature> Create implementation specs
195
+ /skill:forge-4-backlog <feature> Generate rauf backlog
196
+ /skill:forge-5-loop <feature> Run rauf autonomous loop
197
+ /skill:forge-6-docs <feature> Generate architecture docs
198
+ /skill:forge-verify <feature> Run verification on current stage
199
+ ```
200
+
201
+ ### 6. Pipeline Lifecycle Commands
202
+
203
+ Support these sub-commands for pipeline lifecycle management:
204
+ - `/skill:forge pause {feature}` — Set `pipelineStatus` to `"paused"`. Do NOT modify `currentStage` or any stage statuses. The pipeline freezes exactly as-is. Show a confirmation.
205
+ - `/skill:forge resume {feature}` — Set `pipelineStatus` back to `"active"`. Calculate how long the feature was paused (from `updatedAt` to now). If paused for more than 24 hours, show a hint: "This feature was paused for {duration}. Session context may have been lost — consider re-running `/skill:forge-{currentStage} {feature}` to rebuild context."
206
+ - `/skill:forge abandon {feature}` — Set `pipelineStatus` to `"abandoned"`. Use `AskUserQuestion` to confirm first, and state what's reversible: abandoning does not delete artifacts and can be undone with `/skill:forge resume {feature}`, so the cost is low — but if the user really means "stop and discard," point out that `pause` is the better choice when they're only setting it aside. Offer **Abandon** · **Pause instead** · **Cancel**.
207
+ - `/skill:forge run [{feature}]` — **Opt-in auto-advance.** Drive the feature through consecutive stages in one session instead of confirming each boundary. This is a convenience wrapper over **3b. Drive to the Next Stage** — same stage order, same context gate — just looped:
208
+ 1. Resolve the feature (if omitted, use the recency default from `rank-features`; if multiple are equally plausible, ask once via `AskUserQuestion`).
209
+ 2. **Before each stage,** run `forge-session.py context-usage`. If `overThreshold` is true, **stop** and recommend a clean session (give the exact `{nextCommand}` and the `/new`-then-re-run instruction) — never auto-`/new`.
210
+ 3. Otherwise invoke the next stage's skill via the host's skill-invocation mechanism, let it run to its natural stopping point, then re-read state and continue from step 2.
211
+ 4. **Stop conditions:** the next stage is an interview/decision point that calls `AskUserQuestion` (PRD and tech inherently pause for input — let them); `nextStage` is `null` (pipeline complete); context over threshold; or a stage signals it needs human input / is blocked. Always report where the loop stopped and why.
212
+ Per-stage confirmation (3b) remains the default — `run` is only used when the user explicitly asks to "run" / "drive" / "auto-advance" the pipeline. On a non-Claude host where the host's skill-invocation mechanism is unavailable, fall back to printing the ordered list of commands to run.
213
+
214
+ **Epic lifecycle.** When the argument names an **epic** (`{specsDir}/{name}/epic-manifest.json` exists), `pause` / `resume` / `abandon` operate on the epic manifest, not a `.pipeline-state.json`:
215
+
216
+ - Set the manifest's top-level `status` (`paused` / `active` / `abandoned`) via the helper's `set-status` mutator — an atomic write that also bumps `updatedAt`:
217
+ ```bash
218
+ 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')"
219
+ [ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
220
+ python3 "$R/scripts/epic-manifest.py" set-status "{epic}" --status paused --specs-dir "{specsDir}"
221
+ ```
222
+ For `complete`, do **not** set the status directly — completion is *derived* from member states (the rollup), so the manifest `status` is a lifecycle flag, never a completion signal.
223
+ - **Member feature states are NOT silently mutated.** Pausing/abandoning the epic changes only the manifest `status`. Before doing so, use `AskUserQuestion` to make the relationship explicit and frame the trade-off: "Pausing the epic does not pause its in-flight member features. {N} members are active. Pause the epic only, or also pause each member?" Recommend **epic only** when members are mid-stage and the user just wants to stop *new* orchestration; recommend **also pause members** when the intent is a hard freeze of all in-flight work. If the user opts to pause members too, update each member's own `pipelineStatus` **individually and visibly** (one explicit action per member), never as a hidden side-effect.
224
+ - Commit the change via the Git Commit Protocol, staging `{specsDir}/{epic}/`.
225
+
226
+ When listing features, show active pipelines by default. Include a count of paused/abandoned: "3 active pipelines (1 paused, 1 abandoned — use `/skill:forge list all` to see them)."
227
+
228
+ ## Gotchas
229
+
230
+ - Never modify any spec files, backlog files, or pipeline state beyond the `notes`, `updatedAt`, and `pipelineStatus` fields. The navigator is read-only except for notes and lifecycle commands.
231
+ - If a user asks to "continue" or "pick up where I left off" without naming a feature, check for active pipelines before asking. Only ask if ambiguous.
232
+ - The pipeline state file is the source of truth. Don't infer stage from the existence of files alone — a file might exist from a previous incomplete run.
233
+
234
+ ---
235
+
236
+ ## Host execution notes (Pi)
237
+
238
+ This Pi bundle preserves Claude's `AskUserQuestion` references because it ships a Pi compatibility extension registering an `AskUserQuestion` tool. On Pi:
239
+
240
+ - **User input:** use `AskUserQuestion` for genuine user decisions. It supports multiple questions, option descriptions, recommended ordering, multi-select, previews, and free-form Other/custom answers.
241
+ - **Skill dispatch:** Pi uses `/skill:<name>` commands. If you cannot invoke a skill directly, print the exact `/skill:<name> ...` command for the user to run.
242
+ - **Subagents:** this bundle declares its custom agents (`forge-researcher`, `forge-spec-writer`, `forge-verifier`) as package agents. If a `subagent` tool is registered, dispatch one with `{ agent: "forge-verifier", task: "..." }`, or fan several out concurrently with `{ tasks: [{ agent: "forge-spec-writer", task: "..." }, ...] }`. If no `subagent` tool is available, run that step inline yourself.
243
+ - **Background / monitoring:** run long-lived commands in the foreground and report progress as it arrives.
@@ -0,0 +1,191 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Feature Forge Pipeline State",
4
+ "description": "Tracks pipeline progress for a single feature across sessions. Lives at {specsDir}/{feature}/.pipeline-state.json",
5
+ "type": "object",
6
+ "required": ["feature", "createdAt", "updatedAt", "currentStage", "stages", "pipelineStatus"],
7
+ "properties": {
8
+ "pipelineStatus": {
9
+ "type": "string",
10
+ "enum": ["active", "paused", "abandoned"],
11
+ "default": "active"
12
+ },
13
+ "feature": {
14
+ "type": "string",
15
+ "description": "Feature name (matches directory name under specsDir)"
16
+ },
17
+ "epic": {
18
+ "type": "string",
19
+ "description": "Back-pointer to the owning epic's name. Absent for standalone features. The epic-manifest.json is canonical on conflict (REQ-STATE-01)."
20
+ },
21
+ "branch": {
22
+ "type": "string",
23
+ "description": "Git branch this feature's pipeline work is intended to land on, recorded by the Branch Setup block (shared-conventions.md) at the entry stage. Absent when the project isn't a git repo or branchPerFeature is false. Downstream stages and forge-5-loop compare the current branch against this to detect drift back onto the default branch before committing."
24
+ },
25
+ "createdAt": {
26
+ "type": "string",
27
+ "format": "date-time"
28
+ },
29
+ "updatedAt": {
30
+ "type": "string",
31
+ "format": "date-time"
32
+ },
33
+ "currentStage": {
34
+ "type": "string",
35
+ "enum": ["forge-1-prd", "forge-2-tech", "forge-3-specs", "forge-4-backlog", "forge-5-loop", "forge-6-docs", "complete", "forge-verify-prd", "forge-verify-tech", "forge-verify-specs", "forge-verify-backlog", "forge-verify-impl", "forge-0-epic", "forge-verify-epic"],
36
+ "description": "Where the pipeline IS: the most recently started stage — its `stages[<currentStage>].status` is `in-progress` while that stage is being authored, then `complete` once its artifacts are committed. A stage skill sets this to its own id when it starts. This is deliberately NOT 'the next stage to run': the next stage is DERIVED, never stored — it is the first production stage whose `stages[].status` is not `complete` (see `next_stage()` in forge-session.py, surfaced as the navigator/doctor `nextStage`). Consumers that need 'what runs next' compute it from `stages[].status`, not from this field. `complete` here means the whole pipeline is done. (Legacy/absent value: tools fall back to the derived next stage for display only — `build_rows` in forge-session.py.)"
37
+ },
38
+ "notes": {
39
+ "type": "string",
40
+ "description": "Free-form notes persisted between sessions (user can add context before stepping away)"
41
+ },
42
+ "epicChangeRequests": {
43
+ "type": "array",
44
+ "description": "Epic-level change requests raised by a member stage (forge-1-prd/forge-2-tech) when the epic DECOMPOSITION itself must change — distinct from the same-feature `notes` parking lot. Read by forge-0-epic edit mode (which applies them) and by forge-session stage-exit (which routes the exit on `blocksCurrent`). Absent/empty for standalone features. Additive/optional: legacy states without it validate unchanged.",
45
+ "items": {
46
+ "type": "object",
47
+ "required": ["kind", "target", "rationale", "blocksCurrent", "raisedBy", "raisedAt", "status"],
48
+ "additionalProperties": false,
49
+ "properties": {
50
+ "kind": {
51
+ "type": "string",
52
+ "enum": ["add-feature", "redep", "move-boundary", "split"],
53
+ "description": "The decomposition change. add-feature/redep map 1:1 onto edit-mode mutators; move-boundary/split are composite (applied guided-manual in v1)."
54
+ },
55
+ "target": {
56
+ "type": "string",
57
+ "description": "The sibling feature to add, or the feature/boundary affected."
58
+ },
59
+ "rationale": {
60
+ "type": "string",
61
+ "description": "Why the epic must change; seeds the edit-mode charter/prompt when applied."
62
+ },
63
+ "blocksCurrent": {
64
+ "type": "boolean",
65
+ "description": "true → the current feature's next stage would build on a soon-to-change decomposition (pause-now: reconcile before proceeding). false → a peer/downstream change (finish-then-edit). Drives stage-exit routing."
66
+ },
67
+ "raisedBy": {
68
+ "type": "string",
69
+ "enum": ["forge-1-prd", "forge-2-tech"],
70
+ "description": "The stage that detected the epic-level concern."
71
+ },
72
+ "raisedAt": { "type": "string", "format": "date-time" },
73
+ "status": {
74
+ "type": "string",
75
+ "enum": ["open", "applied", "dismissed"],
76
+ "default": "open",
77
+ "description": "Lifecycle: open → applied|dismissed. Only forge-0-epic edit mode flips open→applied/dismissed; recording stages only create open entries; navigator/verify only read."
78
+ }
79
+ }
80
+ }
81
+ },
82
+ "deferredDecisions": {
83
+ "type": "array",
84
+ "description": "Same-feature decisions deliberately postponed to a LATER stage of THIS feature — a structured alternative to burying them in the free-text `notes` string. Distinct from `notes` (unstructured scratch) and from `epicChangeRequests[]` (the epic DECOMPOSITION must change). Use this when a stage would otherwise be tempted to solicit a decision that properly belongs to a downstream stage (e.g. forge-1-prd deferring the concrete cache backend to forge-2-tech): record it here instead of asking now (see the deferred-decisions rule in `references/stage-exit-protocol.md`), and the target stage addresses it. Additive/optional: legacy states without it validate unchanged.",
85
+ "items": {
86
+ "type": "object",
87
+ "required": ["question", "raisedBy", "raisedAt", "status"],
88
+ "additionalProperties": false,
89
+ "properties": {
90
+ "question": {
91
+ "type": "string",
92
+ "description": "The decision being deferred, phrased as a concrete question for the target stage to answer."
93
+ },
94
+ "rationale": {
95
+ "type": "string",
96
+ "description": "Why it is deferred rather than decided now (e.g. depends on information the target stage produces)."
97
+ },
98
+ "targetStage": {
99
+ "type": "string",
100
+ "enum": ["forge-1-prd", "forge-2-tech", "forge-3-specs", "forge-4-backlog", "forge-5-loop", "forge-6-docs"],
101
+ "description": "The stage that should resolve this decision. Omit when unknown/any-later-stage."
102
+ },
103
+ "raisedBy": {
104
+ "type": "string",
105
+ "enum": ["forge-1-prd", "forge-2-tech", "forge-3-specs", "forge-4-backlog"],
106
+ "description": "The stage that deferred the decision."
107
+ },
108
+ "raisedAt": { "type": "string", "format": "date-time" },
109
+ "status": {
110
+ "type": "string",
111
+ "enum": ["open", "addressed", "dismissed"],
112
+ "default": "open",
113
+ "description": "Lifecycle: open → addressed|dismissed. The target stage flips open→addressed when it resolves the decision (or dismissed if it no longer applies); recording stages only create open entries."
114
+ }
115
+ }
116
+ }
117
+ },
118
+ "stages": {
119
+ "type": "object",
120
+ "properties": {
121
+ "forge-0-epic": { "$ref": "#/definitions/stageEntry" },
122
+ "forge-verify-epic": { "$ref": "#/definitions/verifyEntry" },
123
+ "forge-1-prd": { "$ref": "#/definitions/stageEntry" },
124
+ "forge-2-tech": { "$ref": "#/definitions/stageEntry" },
125
+ "forge-3-specs": { "$ref": "#/definitions/stageEntry" },
126
+ "forge-verify-specs": { "$ref": "#/definitions/verifyEntry" },
127
+ "forge-4-backlog": { "$ref": "#/definitions/stageEntry" },
128
+ "forge-verify-backlog": { "$ref": "#/definitions/verifyEntry" },
129
+ "forge-5-loop": { "$ref": "#/definitions/stageEntry" },
130
+ "forge-6-docs": { "$ref": "#/definitions/stageEntry" },
131
+ "forge-verify-impl": { "$ref": "#/definitions/verifyEntry" },
132
+ "forge-verify-prd": { "$ref": "#/definitions/verifyEntry" },
133
+ "forge-verify-tech": { "$ref": "#/definitions/verifyEntry" }
134
+ }
135
+ }
136
+ },
137
+ "definitions": {
138
+ "stageEntry": {
139
+ "type": "object",
140
+ "required": ["status"],
141
+ "properties": {
142
+ "status": {
143
+ "type": "string",
144
+ "enum": ["pending", "in-progress", "complete", "stale"]
145
+ },
146
+ "version": {
147
+ "type": "integer",
148
+ "description": "Incremented each time this stage's artifacts are revised"
149
+ },
150
+ "artifacts": {
151
+ "type": "array",
152
+ "items": { "type": "string" },
153
+ "description": "Relative paths to artifacts produced by this stage"
154
+ },
155
+ "startedAt": { "type": ["string", "null"], "format": "date-time" },
156
+ "completedAt": { "type": ["string", "null"], "format": "date-time" },
157
+ "commitHash": {
158
+ "type": ["string", "null"],
159
+ "description": "Git commit SHA of this stage's artifact commit (Commit 1 of the two-commit Git Commit Protocol in shared-conventions.md). Written by a small follow-up commit so it points at the artifact commit itself, never at an orphaned amend or the hash-recording commit. null until recorded (or when there was no new artifact commit)."
160
+ },
161
+ "basedOnVersions": {
162
+ "type": "object",
163
+ "description": "Upstream stage versions this artifact was built against",
164
+ "additionalProperties": { "type": "integer" }
165
+ }
166
+ }
167
+ },
168
+ "verifyEntry": {
169
+ "type": "object",
170
+ "required": ["status"],
171
+ "properties": {
172
+ "status": {
173
+ "type": "string",
174
+ "enum": ["pending", "passed", "findings-reported", "findings-applied", "skipped"]
175
+ },
176
+ "findingsFile": {
177
+ "type": ["string", "null"],
178
+ "description": "Path to the verification findings document"
179
+ },
180
+ "findingsCount": {
181
+ "type": ["integer", "null"],
182
+ "description": "Number of findings reported"
183
+ },
184
+ "verifiedAt": { "type": ["string", "null"], "format": "date-time" },
185
+ "fixedAt": { "type": ["string", "null"], "format": "date-time" },
186
+ "commitHash": { "type": ["string", "null"], "description": "Git commit SHA of the verify/fix artifact commit. Recorded via the two-commit Git Commit Protocol (shared-conventions.md) so it points at the artifact commit, never an orphaned amend." },
187
+ "verifiedStageVersion": { "type": ["integer", "null"], "description": "The production stage's `version` at the moment this verify was resolved. The navigator's freshness ledger compares it to the stage's current `version`: equal means the verify is fresh; a mismatch (artifact revised since) or an absent field (legacy state) means stale, so auto-verify re-fires. Recorded by forge-verify/forge-fix when writing a passed/findings-applied status." }
188
+ }
189
+ }
190
+ }
191
+ }