@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,58 @@
1
+ #!/usr/bin/env bash
2
+ # Initialize feature-forge configuration in the current project.
3
+ # Creates forge.config.json with sensible defaults.
4
+
5
+ set -euo pipefail
6
+
7
+ CONFIG_FILE="forge.config.json"
8
+
9
+ if [ -f "$CONFIG_FILE" ]; then
10
+ echo "⚠️ $CONFIG_FILE already exists. Skipping."
11
+ exit 0
12
+ fi
13
+
14
+ cat > "$CONFIG_FILE" << 'EOF'
15
+ {
16
+ "specsDir": "./specs",
17
+ "docsDir": "./docs/architecture",
18
+ "backlogDir": null,
19
+ "gitCommitAfterStage": true,
20
+ "commitPrefix": "forge",
21
+ "stack": null,
22
+ "typeCheckCommand": null,
23
+ "testCommand": null,
24
+ "smokeCommand": null,
25
+ "loopIterationMultiplier": 1.5,
26
+ "autoInvokeNextStage": true,
27
+ "contextWindowTokens": null,
28
+ "contextWarnThreshold": 0.7,
29
+ "autoVerify": false,
30
+ "autoVerifyStages": {},
31
+ "autoFix": false
32
+ }
33
+ EOF
34
+
35
+ echo "✅ Created $CONFIG_FILE with defaults."
36
+ echo ""
37
+ echo "Defaults:"
38
+ echo " specsDir: ./specs"
39
+ echo " docsDir: ./docs/architecture"
40
+ echo " backlogDir: null (defaults to {specsDir}/{feature}/backlog.json)"
41
+ echo " gitCommitAfterStage: true"
42
+ echo " commitPrefix: forge"
43
+ echo " stack: null (auto-detected during forge-2-tech)"
44
+ echo " typeCheckCommand: null (auto-detected during forge-2-tech)"
45
+ echo " testCommand: null (auto-detected during forge-2-tech)"
46
+ echo " smokeCommand: null (optional end-to-end smoke; enables impl-verify runnability check)"
47
+ echo " loopIterationMultiplier: 1.5 (multiplier for loop iterations)"
48
+ echo " autoInvokeNextStage: true (navigator auto-starts the next stage after you confirm)"
49
+ echo " contextWindowTokens: null (infer; set to 1000000 on a 1M-context model)"
50
+ echo " contextWarnThreshold: 0.7 (suggest a clean session past this fraction of the window)"
51
+ echo " autoVerify: false (set true to run forge-verify automatically after each stage)"
52
+ echo " autoVerifyStages: {} (per-stage overrides for autoVerify)"
53
+ echo " autoFix: false (set true to chain forge-fix after an auto-verify finds issues)"
54
+ echo ""
55
+ echo "The loop runner defaults to rauf. To target a different ralph-style runner,"
56
+ echo "add a \"loopRunner\" block (see references/forge-config-schema.json)."
57
+ echo ""
58
+ echo "Edit $CONFIG_FILE to customize paths and stack settings for your project."
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/forge-root.sh — the portable skill/plugin-root resolver
3
+ # (exposed contract: portable-skill-root-resolver; REQ-RES-01..05, REQ-SEC-01).
4
+ #
5
+ # Prints the absolute feature-forge plugin root to stdout and exits 0, or writes an
6
+ # actionable error to stderr and exits 1. Takes no arguments. Idempotent and
7
+ # side-effect-free: it NEVER sources or executes a discovered path — it only prints a
8
+ # directory (REQ-SEC-01). Resolution is bounded to the candidate roots below plus this
9
+ # script's own on-disk location.
10
+ #
11
+ # This file is copied VERBATIM into per-agent script mirrors by the downstream adapter
12
+ # generator (REQ-RES-05); keep it dependency-free beyond POSIX/Bash + the sentinel files.
13
+ set -euo pipefail
14
+
15
+ # Sentinel predicate (00-core-definitions.md §2 / SENTINEL_FILES). A directory is a valid
16
+ # feature-forge root iff it carries the neutral bundle sentinel `.feature-forge-bundle.json`
17
+ # (emitted into EVERY per-agent adapter bundle) OR the legacy Claude plugin manifest
18
+ # `.claude-plugin/plugin.json` (the canon repo root + the Claude bundle). Content-based, so it
19
+ # identifies a feature-forge install under ANY agent's directory layout — not just Claude's.
20
+ is_root() { # $1 = candidate dir
21
+ [ -f "$1/.feature-forge-bundle.json" ] || [ -f "$1/.claude-plugin/plugin.json" ]
22
+ }
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
+
67
+ # ── Step 1: self-location — parent of this script's dir is the install root. ─────────────
68
+ # This is the PRIMARY path: a bundle ships its own scripts/forge-root.sh, so the parent of
69
+ # this script's dir is that bundle's root under any agent's layout.
70
+ self_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
71
+ root="$(cd -- "$self_dir/.." && pwd -P)"
72
+ if is_root "$root"; then
73
+ accept_root "$root"
74
+ fi
75
+
76
+ # ── Step 2a: Claude marketplace-cache installs — ~/.claude/plugins/cache/<mp>/<plugin>/<ver>/.
77
+ # This is where Claude Code actually installs marketplace plugins (three segments below
78
+ # plugins/, so the single-star plugins/*/feature-forge glob below can never match it). Version
79
+ # dirs can coexist after upgrades, so probe newest plugin.json first — a stale version must
80
+ # never shadow the current install. Deliberately ordered BEFORE the plugins/* glob: that glob
81
+ # can match the marketplace *clone* (~/.claude/plugins/marketplaces/<mp>/ when the marketplace
82
+ # repo root is itself a plugin root), which may sit at a different commit than the installed
83
+ # skills — the versioned cache install must always win to prevent that version skew.
84
+ while IFS= read -r manifest; do
85
+ candidate="${manifest%/.claude-plugin/plugin.json}"
86
+ if is_root "$candidate"; then
87
+ accept_root "$candidate"
88
+ fi
89
+ done < <(ls -t "$HOME"/.claude/plugins/cache/*/feature-forge/*/.claude-plugin/plugin.json 2>/dev/null || true)
90
+
91
+ # ── Step 2: candidate-root probe (authoritative multi-agent root list; extend here first). ─
92
+ # Globs that match nothing expand to themselves; the is_root test rejects such literals. Covers
93
+ # every supported agent's install destination under BOTH global ($HOME) and project ($PWD) scope,
94
+ # matching the installer's per-agent layout: claude .claude/skills, codex .agents/skills, copilot
95
+ # .github/feature-forge, cursor .cursor/rules, gemini .gemini/extensions, pi .pi/skills or
96
+ # $PI_CODING_AGENT_DIR/skills. The cache glob repeats step 2a's path for a cache install that
97
+ # carries only the neutral bundle sentinel (no plugin.json for ls -t to key on).
98
+ if [ -n "${PI_CODING_AGENT_DIR:-}" ]; then
99
+ for candidate in \
100
+ "$PI_CODING_AGENT_DIR/skills/feature-forge" \
101
+ "$PI_CODING_AGENT_DIR"/git/*/feature-forge/adapters/pi \
102
+ "$PI_CODING_AGENT_DIR"/git/*/*/feature-forge/adapters/pi \
103
+ "$PI_CODING_AGENT_DIR"/packages/*/feature-forge/adapters/pi \
104
+ "$PI_CODING_AGENT_DIR"/npm/*/@garygentry/feature-forge/adapters/pi \
105
+ "$PI_CODING_AGENT_DIR"/node_modules/@garygentry/feature-forge/adapters/pi \
106
+ ; do
107
+ if is_root "$candidate"; then
108
+ accept_root "$candidate"
109
+ fi
110
+ done
111
+ fi
112
+
113
+ for candidate in \
114
+ "$HOME/.claude/skills/feature-forge" \
115
+ "$PWD/.claude/skills/feature-forge" \
116
+ "$HOME"/.claude/plugins/cache/*/feature-forge/* \
117
+ "$HOME"/.claude/plugins/*/feature-forge \
118
+ "$HOME/.agents/skills/feature-forge" \
119
+ "$PWD/.agents/skills/feature-forge" \
120
+ "$HOME/.github/feature-forge" \
121
+ "$PWD/.github/feature-forge" \
122
+ "$HOME/.cursor/rules/feature-forge" \
123
+ "$PWD/.cursor/rules/feature-forge" \
124
+ "$HOME/.gemini/extensions/feature-forge" \
125
+ "$PWD/.gemini/extensions/feature-forge" \
126
+ "$HOME/.pi/agent/skills/feature-forge" \
127
+ "$PWD/.pi/skills/feature-forge" \
128
+ "$HOME"/.pi/agent/git/*/feature-forge/adapters/pi \
129
+ "$HOME"/.pi/agent/git/*/*/feature-forge/adapters/pi \
130
+ "$HOME"/.pi/agent/packages/*/feature-forge/adapters/pi \
131
+ "$HOME"/.pi/agent/npm/*/@garygentry/feature-forge/adapters/pi \
132
+ "$HOME"/.pi/agent/node_modules/@garygentry/feature-forge/adapters/pi \
133
+ "$PWD"/.pi/git/*/feature-forge/adapters/pi \
134
+ "$PWD"/.pi/git/*/*/feature-forge/adapters/pi \
135
+ "$PWD"/.pi/packages/*/feature-forge/adapters/pi \
136
+ "$PWD"/.pi/npm/*/@garygentry/feature-forge/adapters/pi \
137
+ "$PWD"/.pi/node_modules/@garygentry/feature-forge/adapters/pi \
138
+ ; do
139
+ if is_root "$candidate"; then
140
+ accept_root "$candidate"
141
+ fi
142
+ done
143
+
144
+ # Project-scoped Pi installs may be discovered from a subdirectory of the repository. Probe
145
+ # ancestor .pi roots up to the filesystem root; this is bounded by path depth and avoids
146
+ # recursive globs. Scoped to .pi ONLY — every other agent's project layout is probed at $PWD
147
+ # in the fixed list above, and widening those to ancestors here would silently change
148
+ # established discovery behaviour for agents this bundle did not introduce.
149
+ probe_dir="$PWD"
150
+ while :; do
151
+ candidate="$probe_dir/.pi/skills/feature-forge"
152
+ if is_root "$candidate"; then
153
+ accept_root "$candidate"
154
+ fi
155
+ [ "$probe_dir" = "/" ] && break
156
+ next_probe_dir="$(dirname -- "$probe_dir")"
157
+ [ "$next_probe_dir" = "$probe_dir" ] && break
158
+ probe_dir="$next_probe_dir"
159
+ done
160
+
161
+ # ── Step 3: env fallback — neutral FEATURE_FORGE_ROOT, then the legacy CLAUDE_PLUGIN_ROOT. ─
162
+ # The neutral override works for every agent; CLAUDE_PLUGIN_ROOT is kept only for backwards
163
+ # compatibility with existing Claude installs (C-4).
164
+ if [ -n "${FEATURE_FORGE_ROOT:-}" ] && is_root "$FEATURE_FORGE_ROOT"; then
165
+ accept_root "$FEATURE_FORGE_ROOT"
166
+ fi
167
+ if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && is_root "$CLAUDE_PLUGIN_ROOT"; then
168
+ accept_root "$CLAUDE_PLUGIN_ROOT"
169
+ fi
170
+
171
+ # ── Step 4: failure. A sentinel-bearing but asset-incomplete root found along the way is a
172
+ # DEGRADED install (#152) — report it distinctly and actionably rather than the generic
173
+ # cannot-locate message, so the operator knows to reinstall/update rather than reconfigure. ─
174
+ if [ -n "$partial_root" ]; then
175
+ 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
176
+ exit 1
177
+ fi
178
+ echo "feature-forge: cannot locate install root. Set FEATURE_FORGE_ROOT to the bundle dir, or run from an installed skill dir." >&2
179
+ exit 1