@garygentry/feature-forge 0.2.13 → 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 (437) 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/epic-manifest-schema.json +5 -0
  5. package/adapters/claude/references/forge-config-schema.json +2 -2
  6. package/adapters/claude/references/shared-conventions.md +12 -0
  7. package/adapters/claude/references/stacks/_generic.md +21 -0
  8. package/adapters/claude/references/stacks/go.md +19 -0
  9. package/adapters/claude/references/stacks/python.md +21 -0
  10. package/adapters/claude/references/stacks/rust.md +19 -0
  11. package/adapters/claude/references/stacks/typescript.md +23 -0
  12. package/adapters/claude/scripts/epic-manifest.py +75 -5
  13. package/adapters/claude/scripts/forge-root.sh +102 -14
  14. package/adapters/claude/scripts/forge-session.py +68 -9
  15. package/adapters/claude/skills/forge/references/shared-conventions.md +12 -0
  16. package/adapters/claude/skills/forge-0-epic/references/shared-conventions.md +12 -0
  17. package/adapters/claude/skills/forge-1-prd/SKILL.md +2 -0
  18. package/adapters/claude/skills/forge-1-prd/references/shared-conventions.md +12 -0
  19. package/adapters/claude/skills/forge-2-tech/SKILL.md +2 -0
  20. package/adapters/claude/skills/forge-2-tech/references/shared-conventions.md +12 -0
  21. package/adapters/claude/skills/forge-2-tech/references/stacks/_generic.md +21 -0
  22. package/adapters/claude/skills/forge-2-tech/references/stacks/go.md +19 -0
  23. package/adapters/claude/skills/forge-2-tech/references/stacks/python.md +21 -0
  24. package/adapters/claude/skills/forge-2-tech/references/stacks/rust.md +19 -0
  25. package/adapters/claude/skills/forge-2-tech/references/stacks/typescript.md +23 -0
  26. package/adapters/claude/skills/forge-3-specs/SKILL.md +2 -0
  27. package/adapters/claude/skills/forge-3-specs/references/shared-conventions.md +12 -0
  28. package/adapters/claude/skills/forge-3-specs/references/stacks/_generic.md +21 -0
  29. package/adapters/claude/skills/forge-3-specs/references/stacks/go.md +19 -0
  30. package/adapters/claude/skills/forge-3-specs/references/stacks/python.md +21 -0
  31. package/adapters/claude/skills/forge-3-specs/references/stacks/rust.md +19 -0
  32. package/adapters/claude/skills/forge-3-specs/references/stacks/typescript.md +23 -0
  33. package/adapters/claude/skills/forge-4-backlog/SKILL.md +5 -0
  34. package/adapters/claude/skills/forge-4-backlog/references/forge-config-schema.json +2 -2
  35. package/adapters/claude/skills/forge-4-backlog/references/shared-conventions.md +12 -0
  36. package/adapters/claude/skills/forge-5-loop/SKILL.md +7 -9
  37. package/adapters/claude/skills/forge-5-loop/references/forge-config-schema.json +2 -2
  38. package/adapters/claude/skills/forge-5-loop/references/runner-contract.md +41 -0
  39. package/adapters/claude/skills/forge-5-loop/references/shared-conventions.md +12 -0
  40. package/adapters/claude/skills/forge-6-docs/references/shared-conventions.md +12 -0
  41. package/adapters/claude/skills/forge-fix/references/shared-conventions.md +12 -0
  42. package/adapters/claude/skills/forge-guide/references/forge-config-schema.json +2 -2
  43. package/adapters/claude/skills/forge-guide/references/shared-conventions.md +12 -0
  44. package/adapters/claude/skills/forge-guide/references/stacks/_generic.md +21 -0
  45. package/adapters/claude/skills/forge-guide/references/stacks/go.md +19 -0
  46. package/adapters/claude/skills/forge-guide/references/stacks/python.md +21 -0
  47. package/adapters/claude/skills/forge-guide/references/stacks/rust.md +19 -0
  48. package/adapters/claude/skills/forge-guide/references/stacks/typescript.md +23 -0
  49. package/adapters/claude/skills/forge-verify/SKILL.md +3 -3
  50. package/adapters/claude/skills/forge-verify/references/shared-conventions.md +12 -0
  51. package/adapters/claude/skills/forge-verify/references/verification-checklists.md +82 -3
  52. package/adapters/codex/.feature-forge-bundle.json +1 -1
  53. package/adapters/codex/references/epic-manifest-schema.json +5 -0
  54. package/adapters/codex/references/forge-config-schema.json +2 -2
  55. package/adapters/codex/references/shared-conventions.md +12 -0
  56. package/adapters/codex/references/stacks/_generic.md +21 -0
  57. package/adapters/codex/references/stacks/go.md +19 -0
  58. package/adapters/codex/references/stacks/python.md +21 -0
  59. package/adapters/codex/references/stacks/rust.md +19 -0
  60. package/adapters/codex/references/stacks/typescript.md +23 -0
  61. package/adapters/codex/scripts/epic-manifest.py +75 -5
  62. package/adapters/codex/scripts/forge-root.sh +102 -14
  63. package/adapters/codex/scripts/forge-session.py +68 -9
  64. package/adapters/codex/skills/forge/references/shared-conventions.md +12 -0
  65. package/adapters/codex/skills/forge-0-epic/references/shared-conventions.md +12 -0
  66. package/adapters/codex/skills/forge-1-prd/SKILL.md +2 -0
  67. package/adapters/codex/skills/forge-1-prd/references/shared-conventions.md +12 -0
  68. package/adapters/codex/skills/forge-2-tech/SKILL.md +2 -0
  69. package/adapters/codex/skills/forge-2-tech/references/shared-conventions.md +12 -0
  70. package/adapters/codex/skills/forge-2-tech/references/stacks/_generic.md +21 -0
  71. package/adapters/codex/skills/forge-2-tech/references/stacks/go.md +19 -0
  72. package/adapters/codex/skills/forge-2-tech/references/stacks/python.md +21 -0
  73. package/adapters/codex/skills/forge-2-tech/references/stacks/rust.md +19 -0
  74. package/adapters/codex/skills/forge-2-tech/references/stacks/typescript.md +23 -0
  75. package/adapters/codex/skills/forge-3-specs/SKILL.md +2 -0
  76. package/adapters/codex/skills/forge-3-specs/references/shared-conventions.md +12 -0
  77. package/adapters/codex/skills/forge-3-specs/references/stacks/_generic.md +21 -0
  78. package/adapters/codex/skills/forge-3-specs/references/stacks/go.md +19 -0
  79. package/adapters/codex/skills/forge-3-specs/references/stacks/python.md +21 -0
  80. package/adapters/codex/skills/forge-3-specs/references/stacks/rust.md +19 -0
  81. package/adapters/codex/skills/forge-3-specs/references/stacks/typescript.md +23 -0
  82. package/adapters/codex/skills/forge-4-backlog/SKILL.md +5 -0
  83. package/adapters/codex/skills/forge-4-backlog/references/forge-config-schema.json +2 -2
  84. package/adapters/codex/skills/forge-4-backlog/references/shared-conventions.md +12 -0
  85. package/adapters/codex/skills/forge-5-loop/SKILL.md +7 -9
  86. package/adapters/codex/skills/forge-5-loop/references/forge-config-schema.json +2 -2
  87. package/adapters/codex/skills/forge-5-loop/references/runner-contract.md +41 -0
  88. package/adapters/codex/skills/forge-5-loop/references/shared-conventions.md +12 -0
  89. package/adapters/codex/skills/forge-6-docs/references/shared-conventions.md +12 -0
  90. package/adapters/codex/skills/forge-fix/references/shared-conventions.md +12 -0
  91. package/adapters/codex/skills/forge-guide/references/forge-config-schema.json +2 -2
  92. package/adapters/codex/skills/forge-guide/references/shared-conventions.md +12 -0
  93. package/adapters/codex/skills/forge-guide/references/stacks/_generic.md +21 -0
  94. package/adapters/codex/skills/forge-guide/references/stacks/go.md +19 -0
  95. package/adapters/codex/skills/forge-guide/references/stacks/python.md +21 -0
  96. package/adapters/codex/skills/forge-guide/references/stacks/rust.md +19 -0
  97. package/adapters/codex/skills/forge-guide/references/stacks/typescript.md +23 -0
  98. package/adapters/codex/skills/forge-verify/SKILL.md +3 -3
  99. package/adapters/codex/skills/forge-verify/references/shared-conventions.md +12 -0
  100. package/adapters/codex/skills/forge-verify/references/verification-checklists.md +82 -3
  101. package/adapters/copilot/.feature-forge-bundle.json +1 -1
  102. package/adapters/copilot/references/epic-manifest-schema.json +5 -0
  103. package/adapters/copilot/references/forge-config-schema.json +2 -2
  104. package/adapters/copilot/references/shared-conventions.md +12 -0
  105. package/adapters/copilot/references/stacks/_generic.md +21 -0
  106. package/adapters/copilot/references/stacks/go.md +19 -0
  107. package/adapters/copilot/references/stacks/python.md +21 -0
  108. package/adapters/copilot/references/stacks/rust.md +19 -0
  109. package/adapters/copilot/references/stacks/typescript.md +23 -0
  110. package/adapters/copilot/scripts/epic-manifest.py +75 -5
  111. package/adapters/copilot/scripts/forge-root.sh +102 -14
  112. package/adapters/copilot/scripts/forge-session.py +68 -9
  113. package/adapters/copilot/skills/forge/references/shared-conventions.md +12 -0
  114. package/adapters/copilot/skills/forge-0-epic/references/shared-conventions.md +12 -0
  115. package/adapters/copilot/skills/forge-1-prd/forge-1-prd.md +2 -0
  116. package/adapters/copilot/skills/forge-1-prd/references/shared-conventions.md +12 -0
  117. package/adapters/copilot/skills/forge-2-tech/forge-2-tech.md +2 -0
  118. package/adapters/copilot/skills/forge-2-tech/references/shared-conventions.md +12 -0
  119. package/adapters/copilot/skills/forge-2-tech/references/stacks/_generic.md +21 -0
  120. package/adapters/copilot/skills/forge-2-tech/references/stacks/go.md +19 -0
  121. package/adapters/copilot/skills/forge-2-tech/references/stacks/python.md +21 -0
  122. package/adapters/copilot/skills/forge-2-tech/references/stacks/rust.md +19 -0
  123. package/adapters/copilot/skills/forge-2-tech/references/stacks/typescript.md +23 -0
  124. package/adapters/copilot/skills/forge-3-specs/forge-3-specs.md +2 -0
  125. package/adapters/copilot/skills/forge-3-specs/references/shared-conventions.md +12 -0
  126. package/adapters/copilot/skills/forge-3-specs/references/stacks/_generic.md +21 -0
  127. package/adapters/copilot/skills/forge-3-specs/references/stacks/go.md +19 -0
  128. package/adapters/copilot/skills/forge-3-specs/references/stacks/python.md +21 -0
  129. package/adapters/copilot/skills/forge-3-specs/references/stacks/rust.md +19 -0
  130. package/adapters/copilot/skills/forge-3-specs/references/stacks/typescript.md +23 -0
  131. package/adapters/copilot/skills/forge-4-backlog/forge-4-backlog.md +5 -0
  132. package/adapters/copilot/skills/forge-4-backlog/references/forge-config-schema.json +2 -2
  133. package/adapters/copilot/skills/forge-4-backlog/references/shared-conventions.md +12 -0
  134. package/adapters/copilot/skills/forge-5-loop/forge-5-loop.md +7 -9
  135. package/adapters/copilot/skills/forge-5-loop/references/forge-config-schema.json +2 -2
  136. package/adapters/copilot/skills/forge-5-loop/references/runner-contract.md +41 -0
  137. package/adapters/copilot/skills/forge-5-loop/references/shared-conventions.md +12 -0
  138. package/adapters/copilot/skills/forge-6-docs/references/shared-conventions.md +12 -0
  139. package/adapters/copilot/skills/forge-fix/references/shared-conventions.md +12 -0
  140. package/adapters/copilot/skills/forge-guide/references/forge-config-schema.json +2 -2
  141. package/adapters/copilot/skills/forge-guide/references/shared-conventions.md +12 -0
  142. package/adapters/copilot/skills/forge-guide/references/stacks/_generic.md +21 -0
  143. package/adapters/copilot/skills/forge-guide/references/stacks/go.md +19 -0
  144. package/adapters/copilot/skills/forge-guide/references/stacks/python.md +21 -0
  145. package/adapters/copilot/skills/forge-guide/references/stacks/rust.md +19 -0
  146. package/adapters/copilot/skills/forge-guide/references/stacks/typescript.md +23 -0
  147. package/adapters/copilot/skills/forge-verify/forge-verify.md +3 -3
  148. package/adapters/copilot/skills/forge-verify/references/shared-conventions.md +12 -0
  149. package/adapters/copilot/skills/forge-verify/references/verification-checklists.md +82 -3
  150. package/adapters/cursor/.feature-forge-bundle.json +1 -1
  151. package/adapters/cursor/references/epic-manifest-schema.json +5 -0
  152. package/adapters/cursor/references/forge-config-schema.json +2 -2
  153. package/adapters/cursor/references/shared-conventions.md +12 -0
  154. package/adapters/cursor/references/stacks/_generic.md +21 -0
  155. package/adapters/cursor/references/stacks/go.md +19 -0
  156. package/adapters/cursor/references/stacks/python.md +21 -0
  157. package/adapters/cursor/references/stacks/rust.md +19 -0
  158. package/adapters/cursor/references/stacks/typescript.md +23 -0
  159. package/adapters/cursor/scripts/epic-manifest.py +75 -5
  160. package/adapters/cursor/scripts/forge-root.sh +102 -14
  161. package/adapters/cursor/scripts/forge-session.py +68 -9
  162. package/adapters/cursor/skills/forge/references/shared-conventions.md +12 -0
  163. package/adapters/cursor/skills/forge-0-epic/references/shared-conventions.md +12 -0
  164. package/adapters/cursor/skills/forge-1-prd/forge-1-prd.mdc +2 -0
  165. package/adapters/cursor/skills/forge-1-prd/references/shared-conventions.md +12 -0
  166. package/adapters/cursor/skills/forge-2-tech/forge-2-tech.mdc +2 -0
  167. package/adapters/cursor/skills/forge-2-tech/references/shared-conventions.md +12 -0
  168. package/adapters/cursor/skills/forge-2-tech/references/stacks/_generic.md +21 -0
  169. package/adapters/cursor/skills/forge-2-tech/references/stacks/go.md +19 -0
  170. package/adapters/cursor/skills/forge-2-tech/references/stacks/python.md +21 -0
  171. package/adapters/cursor/skills/forge-2-tech/references/stacks/rust.md +19 -0
  172. package/adapters/cursor/skills/forge-2-tech/references/stacks/typescript.md +23 -0
  173. package/adapters/cursor/skills/forge-3-specs/forge-3-specs.mdc +2 -0
  174. package/adapters/cursor/skills/forge-3-specs/references/shared-conventions.md +12 -0
  175. package/adapters/cursor/skills/forge-3-specs/references/stacks/_generic.md +21 -0
  176. package/adapters/cursor/skills/forge-3-specs/references/stacks/go.md +19 -0
  177. package/adapters/cursor/skills/forge-3-specs/references/stacks/python.md +21 -0
  178. package/adapters/cursor/skills/forge-3-specs/references/stacks/rust.md +19 -0
  179. package/adapters/cursor/skills/forge-3-specs/references/stacks/typescript.md +23 -0
  180. package/adapters/cursor/skills/forge-4-backlog/forge-4-backlog.mdc +5 -0
  181. package/adapters/cursor/skills/forge-4-backlog/references/forge-config-schema.json +2 -2
  182. package/adapters/cursor/skills/forge-4-backlog/references/shared-conventions.md +12 -0
  183. package/adapters/cursor/skills/forge-5-loop/forge-5-loop.mdc +7 -9
  184. package/adapters/cursor/skills/forge-5-loop/references/forge-config-schema.json +2 -2
  185. package/adapters/cursor/skills/forge-5-loop/references/runner-contract.md +41 -0
  186. package/adapters/cursor/skills/forge-5-loop/references/shared-conventions.md +12 -0
  187. package/adapters/cursor/skills/forge-6-docs/references/shared-conventions.md +12 -0
  188. package/adapters/cursor/skills/forge-fix/references/shared-conventions.md +12 -0
  189. package/adapters/cursor/skills/forge-guide/references/forge-config-schema.json +2 -2
  190. package/adapters/cursor/skills/forge-guide/references/shared-conventions.md +12 -0
  191. package/adapters/cursor/skills/forge-guide/references/stacks/_generic.md +21 -0
  192. package/adapters/cursor/skills/forge-guide/references/stacks/go.md +19 -0
  193. package/adapters/cursor/skills/forge-guide/references/stacks/python.md +21 -0
  194. package/adapters/cursor/skills/forge-guide/references/stacks/rust.md +19 -0
  195. package/adapters/cursor/skills/forge-guide/references/stacks/typescript.md +23 -0
  196. package/adapters/cursor/skills/forge-verify/forge-verify.mdc +3 -3
  197. package/adapters/cursor/skills/forge-verify/references/shared-conventions.md +12 -0
  198. package/adapters/cursor/skills/forge-verify/references/verification-checklists.md +82 -3
  199. package/adapters/gemini/.feature-forge-bundle.json +1 -1
  200. package/adapters/gemini/gemini-extension.json +1 -1
  201. package/adapters/gemini/references/epic-manifest-schema.json +5 -0
  202. package/adapters/gemini/references/forge-config-schema.json +2 -2
  203. package/adapters/gemini/references/shared-conventions.md +12 -0
  204. package/adapters/gemini/references/stacks/_generic.md +21 -0
  205. package/adapters/gemini/references/stacks/go.md +19 -0
  206. package/adapters/gemini/references/stacks/python.md +21 -0
  207. package/adapters/gemini/references/stacks/rust.md +19 -0
  208. package/adapters/gemini/references/stacks/typescript.md +23 -0
  209. package/adapters/gemini/scripts/epic-manifest.py +75 -5
  210. package/adapters/gemini/scripts/forge-root.sh +102 -14
  211. package/adapters/gemini/scripts/forge-session.py +68 -9
  212. package/adapters/gemini/skills/forge/references/shared-conventions.md +12 -0
  213. package/adapters/gemini/skills/forge-0-epic/references/shared-conventions.md +12 -0
  214. package/adapters/gemini/skills/forge-1-prd/forge-1-prd.md +2 -0
  215. package/adapters/gemini/skills/forge-1-prd/references/shared-conventions.md +12 -0
  216. package/adapters/gemini/skills/forge-2-tech/forge-2-tech.md +2 -0
  217. package/adapters/gemini/skills/forge-2-tech/references/shared-conventions.md +12 -0
  218. package/adapters/gemini/skills/forge-2-tech/references/stacks/_generic.md +21 -0
  219. package/adapters/gemini/skills/forge-2-tech/references/stacks/go.md +19 -0
  220. package/adapters/gemini/skills/forge-2-tech/references/stacks/python.md +21 -0
  221. package/adapters/gemini/skills/forge-2-tech/references/stacks/rust.md +19 -0
  222. package/adapters/gemini/skills/forge-2-tech/references/stacks/typescript.md +23 -0
  223. package/adapters/gemini/skills/forge-3-specs/forge-3-specs.md +2 -0
  224. package/adapters/gemini/skills/forge-3-specs/references/shared-conventions.md +12 -0
  225. package/adapters/gemini/skills/forge-3-specs/references/stacks/_generic.md +21 -0
  226. package/adapters/gemini/skills/forge-3-specs/references/stacks/go.md +19 -0
  227. package/adapters/gemini/skills/forge-3-specs/references/stacks/python.md +21 -0
  228. package/adapters/gemini/skills/forge-3-specs/references/stacks/rust.md +19 -0
  229. package/adapters/gemini/skills/forge-3-specs/references/stacks/typescript.md +23 -0
  230. package/adapters/gemini/skills/forge-4-backlog/forge-4-backlog.md +5 -0
  231. package/adapters/gemini/skills/forge-4-backlog/references/forge-config-schema.json +2 -2
  232. package/adapters/gemini/skills/forge-4-backlog/references/shared-conventions.md +12 -0
  233. package/adapters/gemini/skills/forge-5-loop/forge-5-loop.md +7 -9
  234. package/adapters/gemini/skills/forge-5-loop/references/forge-config-schema.json +2 -2
  235. package/adapters/gemini/skills/forge-5-loop/references/runner-contract.md +41 -0
  236. package/adapters/gemini/skills/forge-5-loop/references/shared-conventions.md +12 -0
  237. package/adapters/gemini/skills/forge-6-docs/references/shared-conventions.md +12 -0
  238. package/adapters/gemini/skills/forge-fix/references/shared-conventions.md +12 -0
  239. package/adapters/gemini/skills/forge-guide/references/forge-config-schema.json +2 -2
  240. package/adapters/gemini/skills/forge-guide/references/shared-conventions.md +12 -0
  241. package/adapters/gemini/skills/forge-guide/references/stacks/_generic.md +21 -0
  242. package/adapters/gemini/skills/forge-guide/references/stacks/go.md +19 -0
  243. package/adapters/gemini/skills/forge-guide/references/stacks/python.md +21 -0
  244. package/adapters/gemini/skills/forge-guide/references/stacks/rust.md +19 -0
  245. package/adapters/gemini/skills/forge-guide/references/stacks/typescript.md +23 -0
  246. package/adapters/gemini/skills/forge-verify/forge-verify.md +3 -3
  247. package/adapters/gemini/skills/forge-verify/references/shared-conventions.md +12 -0
  248. package/adapters/gemini/skills/forge-verify/references/verification-checklists.md +82 -3
  249. package/adapters/pi/.feature-forge-bundle.json +6 -0
  250. package/adapters/pi/agents/forge-researcher.md +139 -0
  251. package/adapters/pi/agents/forge-spec-writer.md +116 -0
  252. package/adapters/pi/agents/forge-verifier.md +126 -0
  253. package/adapters/pi/extensions/ask-user-question/LICENSE +21 -0
  254. package/adapters/pi/extensions/ask-user-question/README.md +91 -0
  255. package/adapters/pi/extensions/ask-user-question/ask-user-question.ts +298 -0
  256. package/adapters/pi/extensions/ask-user-question/config.ts +78 -0
  257. package/adapters/pi/extensions/ask-user-question/events.ts +57 -0
  258. package/adapters/pi/extensions/ask-user-question/index.ts +61 -0
  259. package/adapters/pi/extensions/ask-user-question/locales/de.json +27 -0
  260. package/adapters/pi/extensions/ask-user-question/locales/en.json +27 -0
  261. package/adapters/pi/extensions/ask-user-question/locales/es.json +27 -0
  262. package/adapters/pi/extensions/ask-user-question/locales/fr.json +27 -0
  263. package/adapters/pi/extensions/ask-user-question/locales/pt-BR.json +27 -0
  264. package/adapters/pi/extensions/ask-user-question/locales/pt.json +27 -0
  265. package/adapters/pi/extensions/ask-user-question/locales/ru.json +27 -0
  266. package/adapters/pi/extensions/ask-user-question/locales/uk.json +27 -0
  267. package/adapters/pi/extensions/ask-user-question/locales/zh.json +29 -0
  268. package/adapters/pi/extensions/ask-user-question/reconcile.ts +49 -0
  269. package/adapters/pi/extensions/ask-user-question/rpc-fallback.ts +168 -0
  270. package/adapters/pi/extensions/ask-user-question/state/build-questionnaire.ts +302 -0
  271. package/adapters/pi/extensions/ask-user-question/state/i18n-bridge.ts +53 -0
  272. package/adapters/pi/extensions/ask-user-question/state/key-router.ts +277 -0
  273. package/adapters/pi/extensions/ask-user-question/state/questionnaire-session.ts +234 -0
  274. package/adapters/pi/extensions/ask-user-question/state/row-intent.ts +145 -0
  275. package/adapters/pi/extensions/ask-user-question/state/selectors/contract.ts +26 -0
  276. package/adapters/pi/extensions/ask-user-question/state/selectors/derivations.ts +42 -0
  277. package/adapters/pi/extensions/ask-user-question/state/selectors/focus.ts +19 -0
  278. package/adapters/pi/extensions/ask-user-question/state/selectors/projections.ts +101 -0
  279. package/adapters/pi/extensions/ask-user-question/state/state-reducer.ts +292 -0
  280. package/adapters/pi/extensions/ask-user-question/state/state.ts +55 -0
  281. package/adapters/pi/extensions/ask-user-question/tool/format-answer.ts +31 -0
  282. package/adapters/pi/extensions/ask-user-question/tool/response-envelope.ts +49 -0
  283. package/adapters/pi/extensions/ask-user-question/tool/types.ts +147 -0
  284. package/adapters/pi/extensions/ask-user-question/tool/validate-questionnaire.ts +58 -0
  285. package/adapters/pi/extensions/ask-user-question/vendor-config-shim.ts +65 -0
  286. package/adapters/pi/extensions/ask-user-question/view/component-binding.ts +47 -0
  287. package/adapters/pi/extensions/ask-user-question/view/components/inline-input.ts +98 -0
  288. package/adapters/pi/extensions/ask-user-question/view/components/multi-select-view.ts +193 -0
  289. package/adapters/pi/extensions/ask-user-question/view/components/option-list-view.ts +70 -0
  290. package/adapters/pi/extensions/ask-user-question/view/components/preview/markdown-content-cache.ts +79 -0
  291. package/adapters/pi/extensions/ask-user-question/view/components/preview/preview-block-renderer.ts +111 -0
  292. package/adapters/pi/extensions/ask-user-question/view/components/preview/preview-box-renderer.ts +88 -0
  293. package/adapters/pi/extensions/ask-user-question/view/components/preview/preview-layout-decider.ts +202 -0
  294. package/adapters/pi/extensions/ask-user-question/view/components/preview/preview-pane.ts +228 -0
  295. package/adapters/pi/extensions/ask-user-question/view/components/submit-picker.ts +67 -0
  296. package/adapters/pi/extensions/ask-user-question/view/components/tab-bar.ts +59 -0
  297. package/adapters/pi/extensions/ask-user-question/view/components/wrapping-select.ts +293 -0
  298. package/adapters/pi/extensions/ask-user-question/view/dialog-builder.ts +224 -0
  299. package/adapters/pi/extensions/ask-user-question/view/props-adapter.ts +125 -0
  300. package/adapters/pi/extensions/ask-user-question/view/stateful-view.ts +26 -0
  301. package/adapters/pi/extensions/ask-user-question/view/tab-components.ts +18 -0
  302. package/adapters/pi/extensions/ask-user-question/view/tab-content-strategy.ts +252 -0
  303. package/adapters/pi/package.json +26 -0
  304. package/adapters/pi/references/epic-manifest-schema.json +125 -0
  305. package/adapters/pi/references/forge-config-schema.json +236 -0
  306. package/adapters/pi/references/pipeline-state-schema.json +191 -0
  307. package/adapters/pi/references/portable-root.md +71 -0
  308. package/adapters/pi/references/process-overview.md +143 -0
  309. package/adapters/pi/references/ralph-loop-contract.md +221 -0
  310. package/adapters/pi/references/shared-conventions.md +295 -0
  311. package/adapters/pi/references/skill-frontmatter.schema.json +17 -0
  312. package/adapters/pi/references/stack-resolution.md +54 -0
  313. package/adapters/pi/references/stacks/_generic.md +111 -0
  314. package/adapters/pi/references/stacks/go.md +157 -0
  315. package/adapters/pi/references/stacks/python.md +184 -0
  316. package/adapters/pi/references/stacks/rust.md +170 -0
  317. package/adapters/pi/references/stacks/typescript.md +134 -0
  318. package/adapters/pi/references/stage-exit-protocol.md +258 -0
  319. package/adapters/pi/references/templates/specs-hygiene/AGENTS.md +32 -0
  320. package/adapters/pi/references/templates/specs-hygiene/CLAUDE.md +31 -0
  321. package/adapters/pi/references/vendor-construct-inventory.md +50 -0
  322. package/adapters/pi/scripts/epic-manifest.py +1694 -0
  323. package/adapters/pi/scripts/forge-bootstrap.py +1070 -0
  324. package/adapters/pi/scripts/forge-init.sh +58 -0
  325. package/adapters/pi/scripts/forge-root.sh +179 -0
  326. package/adapters/pi/scripts/forge-session.py +1888 -0
  327. package/adapters/pi/scripts/validate-traceability.py +150 -0
  328. package/adapters/pi/skills/forge/SKILL.md +243 -0
  329. package/adapters/pi/skills/forge/references/pipeline-state-schema.json +191 -0
  330. package/adapters/pi/skills/forge/references/process-overview.md +143 -0
  331. package/adapters/pi/skills/forge/references/shared-conventions.md +295 -0
  332. package/adapters/pi/skills/forge/references/stage-exit-protocol.md +258 -0
  333. package/adapters/pi/skills/forge-0-epic/SKILL.md +308 -0
  334. package/adapters/pi/skills/forge-0-epic/references/edit-mode.md +266 -0
  335. package/adapters/pi/skills/forge-0-epic/references/epic-manifest-subcommands.md +75 -0
  336. package/adapters/pi/skills/forge-0-epic/references/pipeline-state-schema.json +191 -0
  337. package/adapters/pi/skills/forge-0-epic/references/portable-root.md +71 -0
  338. package/adapters/pi/skills/forge-0-epic/references/shared-conventions.md +295 -0
  339. package/adapters/pi/skills/forge-0-epic/references/stage-exit-protocol.md +258 -0
  340. package/adapters/pi/skills/forge-1-prd/SKILL.md +164 -0
  341. package/adapters/pi/skills/forge-1-prd/references/pipeline-state-schema.json +191 -0
  342. package/adapters/pi/skills/forge-1-prd/references/prd-template.md +106 -0
  343. package/adapters/pi/skills/forge-1-prd/references/shared-conventions.md +295 -0
  344. package/adapters/pi/skills/forge-1-prd/references/stage-exit-protocol.md +258 -0
  345. package/adapters/pi/skills/forge-2-tech/SKILL.md +225 -0
  346. package/adapters/pi/skills/forge-2-tech/references/pipeline-state-schema.json +191 -0
  347. package/adapters/pi/skills/forge-2-tech/references/shared-conventions.md +295 -0
  348. package/adapters/pi/skills/forge-2-tech/references/stack-discovery-checklist.md +95 -0
  349. package/adapters/pi/skills/forge-2-tech/references/stack-resolution.md +54 -0
  350. package/adapters/pi/skills/forge-2-tech/references/stacks/_generic.md +111 -0
  351. package/adapters/pi/skills/forge-2-tech/references/stacks/go.md +157 -0
  352. package/adapters/pi/skills/forge-2-tech/references/stacks/python.md +184 -0
  353. package/adapters/pi/skills/forge-2-tech/references/stacks/rust.md +170 -0
  354. package/adapters/pi/skills/forge-2-tech/references/stacks/typescript.md +134 -0
  355. package/adapters/pi/skills/forge-2-tech/references/stage-exit-protocol.md +258 -0
  356. package/adapters/pi/skills/forge-3-specs/SKILL.md +178 -0
  357. package/adapters/pi/skills/forge-3-specs/references/pipeline-state-schema.json +191 -0
  358. package/adapters/pi/skills/forge-3-specs/references/shared-conventions.md +295 -0
  359. package/adapters/pi/skills/forge-3-specs/references/spec-archetypes.md +106 -0
  360. package/adapters/pi/skills/forge-3-specs/references/spec-examples.md +71 -0
  361. package/adapters/pi/skills/forge-3-specs/references/stacks/_generic.md +111 -0
  362. package/adapters/pi/skills/forge-3-specs/references/stacks/go.md +157 -0
  363. package/adapters/pi/skills/forge-3-specs/references/stacks/python.md +184 -0
  364. package/adapters/pi/skills/forge-3-specs/references/stacks/rust.md +170 -0
  365. package/adapters/pi/skills/forge-3-specs/references/stacks/typescript.md +134 -0
  366. package/adapters/pi/skills/forge-3-specs/references/stage-exit-protocol.md +258 -0
  367. package/adapters/pi/skills/forge-4-backlog/SKILL.md +175 -0
  368. package/adapters/pi/skills/forge-4-backlog/references/forge-config-schema.json +236 -0
  369. package/adapters/pi/skills/forge-4-backlog/references/pipeline-state-schema.json +191 -0
  370. package/adapters/pi/skills/forge-4-backlog/references/shared-conventions.md +295 -0
  371. package/adapters/pi/skills/forge-4-backlog/references/stage-exit-protocol.md +258 -0
  372. package/adapters/pi/skills/forge-5-loop/SKILL.md +314 -0
  373. package/adapters/pi/skills/forge-5-loop/references/forge-config-schema.json +236 -0
  374. package/adapters/pi/skills/forge-5-loop/references/ralph-loop-contract.md +221 -0
  375. package/adapters/pi/skills/forge-5-loop/references/result-reporting.md +85 -0
  376. package/adapters/pi/skills/forge-5-loop/references/runner-contract.md +341 -0
  377. package/adapters/pi/skills/forge-5-loop/references/shared-conventions.md +295 -0
  378. package/adapters/pi/skills/forge-5-loop/references/stage-exit-protocol.md +258 -0
  379. package/adapters/pi/skills/forge-6-docs/SKILL.md +202 -0
  380. package/adapters/pi/skills/forge-6-docs/references/doc-conventions.md +126 -0
  381. package/adapters/pi/skills/forge-6-docs/references/pipeline-state-schema.json +191 -0
  382. package/adapters/pi/skills/forge-6-docs/references/shared-conventions.md +295 -0
  383. package/adapters/pi/skills/forge-bootstrap/SKILL.md +250 -0
  384. package/adapters/pi/skills/forge-bootstrap/references/templates/ci/github-actions.yml +12 -0
  385. package/adapters/pi/skills/forge-bootstrap/references/templates/generic/run.sh +3 -0
  386. package/adapters/pi/skills/forge-bootstrap/references/templates/generic/test.sh +13 -0
  387. package/adapters/pi/skills/forge-bootstrap/references/templates/go/go.mod +3 -0
  388. package/adapters/pi/skills/forge-bootstrap/references/templates/go/main.go +12 -0
  389. package/adapters/pi/skills/forge-bootstrap/references/templates/go/main_test.go +11 -0
  390. package/adapters/pi/skills/forge-bootstrap/references/templates/hygiene/AGENTS.md +35 -0
  391. package/adapters/pi/skills/forge-bootstrap/references/templates/hygiene/CLAUDE.md +36 -0
  392. package/adapters/pi/skills/forge-bootstrap/references/templates/hygiene/README.md +11 -0
  393. package/adapters/pi/skills/forge-bootstrap/references/templates/licenses/Apache-2.0/LICENSE +198 -0
  394. package/adapters/pi/skills/forge-bootstrap/references/templates/licenses/MIT/LICENSE +21 -0
  395. package/adapters/pi/skills/forge-bootstrap/references/templates/python/pyproject.toml +24 -0
  396. package/adapters/pi/skills/forge-bootstrap/references/templates/python/src/{{PKG}}/__init__.py +5 -0
  397. package/adapters/pi/skills/forge-bootstrap/references/templates/python/src/{{PKG}}/main.py +13 -0
  398. package/adapters/pi/skills/forge-bootstrap/references/templates/python/tests/test_smoke.py +8 -0
  399. package/adapters/pi/skills/forge-bootstrap/references/templates/rust/Cargo.toml +15 -0
  400. package/adapters/pi/skills/forge-bootstrap/references/templates/rust/src/lib.rs +7 -0
  401. package/adapters/pi/skills/forge-bootstrap/references/templates/rust/src/main.rs +5 -0
  402. package/adapters/pi/skills/forge-bootstrap/references/templates/rust/tests/smoke.rs +6 -0
  403. package/adapters/pi/skills/forge-bootstrap/references/templates/typescript/package.json +15 -0
  404. package/adapters/pi/skills/forge-bootstrap/references/templates/typescript/src/index.ts +4 -0
  405. package/adapters/pi/skills/forge-bootstrap/references/templates/typescript/test/smoke.test.ts +6 -0
  406. package/adapters/pi/skills/forge-bootstrap/references/templates/typescript/tsconfig.json +14 -0
  407. package/adapters/pi/skills/forge-fix/SKILL.md +98 -0
  408. package/adapters/pi/skills/forge-fix/references/shared-conventions.md +295 -0
  409. package/adapters/pi/skills/forge-fix/references/stage-exit-protocol.md +258 -0
  410. package/adapters/pi/skills/forge-guide/SKILL.md +192 -0
  411. package/adapters/pi/skills/forge-guide/references/forge-config-schema.json +236 -0
  412. package/adapters/pi/skills/forge-guide/references/process-overview.md +143 -0
  413. package/adapters/pi/skills/forge-guide/references/ralph-loop-contract.md +221 -0
  414. package/adapters/pi/skills/forge-guide/references/shared-conventions.md +295 -0
  415. package/adapters/pi/skills/forge-guide/references/stack-resolution.md +54 -0
  416. package/adapters/pi/skills/forge-guide/references/stacks/_generic.md +111 -0
  417. package/adapters/pi/skills/forge-guide/references/stacks/go.md +157 -0
  418. package/adapters/pi/skills/forge-guide/references/stacks/python.md +184 -0
  419. package/adapters/pi/skills/forge-guide/references/stacks/rust.md +170 -0
  420. package/adapters/pi/skills/forge-guide/references/stacks/typescript.md +134 -0
  421. package/adapters/pi/skills/forge-init/SKILL.md +72 -0
  422. package/adapters/pi/skills/forge-verify/SKILL.md +273 -0
  423. package/adapters/pi/skills/forge-verify/references/pipeline-state-schema.json +191 -0
  424. package/adapters/pi/skills/forge-verify/references/shared-conventions.md +295 -0
  425. package/adapters/pi/skills/forge-verify/references/verification-checklists.md +477 -0
  426. package/dist/agent-targets.d.ts +1 -1
  427. package/dist/agent-targets.js +23 -3
  428. package/dist/detect.d.ts +1 -1
  429. package/dist/detect.js +2 -1
  430. package/dist/manifest.d.ts +1 -1
  431. package/dist/manifest.js +2 -2
  432. package/dist/placements.js +5 -1
  433. package/dist/rauf.d.ts +4 -4
  434. package/dist/rauf.js +3 -3
  435. package/dist/types.d.ts +31 -6
  436. package/dist/types.js +6 -3
  437. package/package.json +14 -3
@@ -0,0 +1,277 @@
1
+ // GENERATED — DO NOT EDIT. Source: adapter-src/pi/extensions/ask-user-question/state/key-router.ts
2
+ // Regenerate with: python3 scripts/build-adapters.py
3
+ import { Key, matchesKey } from "@earendil-works/pi-tui";
4
+ import type { QuestionAnswer } from "../tool/types.js";
5
+ import { ROW_INTENT_META } from "./row-intent.js";
6
+ import type { QuestionnaireRuntime, QuestionnaireState } from "./state.js";
7
+
8
+ const KEYBIND_UP = "tui.select.up";
9
+ const KEYBIND_DOWN = "tui.select.down";
10
+ const KEYBIND_CONFIRM = "tui.select.confirm";
11
+ const KEYBIND_CANCEL = "tui.select.cancel";
12
+
13
+ const NOTES_ACTIVATE_KEY = "n";
14
+ const SPACE_KEY = " ";
15
+
16
+ export type QuestionnaireAction =
17
+ | { kind: "nav"; nextIndex: number }
18
+ | { kind: "tab_switch"; nextTab: number }
19
+ | { kind: "confirm"; answer: QuestionAnswer; autoAdvanceTab?: number }
20
+ | { kind: "toggle"; index: number }
21
+ | { kind: "multi_confirm"; selected: string[]; autoAdvanceTab?: number }
22
+ | { kind: "cancel" }
23
+ | { kind: "notes_enter" }
24
+ | { kind: "notes_exit" }
25
+ | { kind: "submit" }
26
+ | { kind: "submit_nav"; nextIndex: 0 | 1 }
27
+ | { kind: "notes_forward"; data: string }
28
+ /** Flip `state.collapsed`. Always available, regardless of inner mode (see top intercept in `routeKey`). */
29
+ | { kind: "toggle_collapsed" }
30
+ | { kind: "ignore" };
31
+
32
+ export interface QuestionnaireKeybindings {
33
+ matches(data: string, name: string): boolean;
34
+ }
35
+
36
+ export function wrapTab(index: number, total: number): number {
37
+ if (total <= 0) return 0;
38
+ return ((index % total) + total) % total;
39
+ }
40
+
41
+ export function allAnswered(state: QuestionnaireState, runtime: QuestionnaireRuntime): boolean {
42
+ if (runtime.questions.length === 0) return false;
43
+ for (let i = 0; i < runtime.questions.length; i++) {
44
+ if (!state.answers.has(i)) return false;
45
+ }
46
+ return true;
47
+ }
48
+
49
+ function totalTabs(runtime: QuestionnaireRuntime): number {
50
+ return runtime.isMulti ? runtime.questions.length + 1 : 1;
51
+ }
52
+
53
+ function computeAutoAdvanceTab(state: QuestionnaireState, runtime: QuestionnaireRuntime): number | undefined {
54
+ if (!runtime.isMulti) return undefined;
55
+ if (state.currentTab < runtime.questions.length - 1) return state.currentTab + 1;
56
+ return runtime.questions.length;
57
+ }
58
+
59
+ function buildSingleSelectAnswer(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionAnswer | null {
60
+ const q = runtime.questions[state.currentTab];
61
+ if (!q) return null;
62
+
63
+ const item = runtime.currentItem;
64
+
65
+ if (state.inputMode) {
66
+ const label = runtime.inputBuffer;
67
+ return {
68
+ questionIndex: state.currentTab,
69
+ question: q.question,
70
+ kind: "custom",
71
+ answer: label.length > 0 ? label : null,
72
+ };
73
+ }
74
+ if (!item) return null;
75
+ if (item.kind === "other") {
76
+ return null;
77
+ }
78
+ if (item.kind === "next") {
79
+ return null;
80
+ }
81
+ return {
82
+ questionIndex: state.currentTab,
83
+ question: q.question,
84
+ kind: "option",
85
+ answer: item.label,
86
+ };
87
+ }
88
+
89
+ function buildMultiSelected(state: QuestionnaireState, runtime: QuestionnaireRuntime): string[] {
90
+ const q = runtime.questions[state.currentTab];
91
+ if (!q) return [];
92
+ const out: string[] = [];
93
+ for (let i = 0; i < q.options.length; i++) {
94
+ if (state.multiSelectChecked.has(i)) {
95
+ const label = q.options[i]?.label;
96
+ if (typeof label === "string") out.push(label);
97
+ }
98
+ }
99
+ return out;
100
+ }
101
+
102
+ function tabSwitchAction(
103
+ data: string,
104
+ state: QuestionnaireState,
105
+ runtime: QuestionnaireRuntime,
106
+ ): QuestionnaireAction | null {
107
+ if (!runtime.isMulti) return null;
108
+ const total = totalTabs(runtime);
109
+ if (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {
110
+ return { kind: "tab_switch", nextTab: wrapTab(state.currentTab + 1, total) };
111
+ }
112
+ if (matchesKey(data, Key.shift("tab")) || matchesKey(data, Key.left)) {
113
+ return { kind: "tab_switch", nextTab: wrapTab(state.currentTab - 1, total) };
114
+ }
115
+ return null;
116
+ }
117
+
118
+ // DOWN at the last item wraps to the first (cycle through [option0, …, optionLast]).
119
+ function nextNavOnDown(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {
120
+ return { kind: "nav", nextIndex: wrapTab(state.optionIndex + 1, Math.max(1, runtime.items.length)) };
121
+ }
122
+
123
+ // UP at the first item wraps to the last (symmetric with nextNavOnDown).
124
+ function prevNavOnUp(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {
125
+ return { kind: "nav", nextIndex: wrapTab(state.optionIndex - 1, Math.max(1, runtime.items.length)) };
126
+ }
127
+
128
+ export function routeKey(data: string, state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {
129
+ const kb = runtime.keybindings;
130
+
131
+ // Collapse/expand toggle is a UI-level affordance — intercepted at the top so it
132
+ // works from every inner state (notes, inputMode, submit tab, multi-select)
133
+ // without reaching any branch that would otherwise consume the keystroke. The
134
+ // questionnaire overlay is fully hidden via `OverlayHandle.setHidden(true)` while
135
+ // collapsed; pi-tui's overlay stack updates accordingly, so overlay-aware consumers
136
+ // (e.g. `pi-station`) see no visible modal and chat scroll resumes. The toggle key is
137
+ // also captured at the raw terminal level via `ctx.ui.onTerminalInput` so it still
138
+ // routes here when the overlay is hidden (pi-tui does not deliver input to a hidden
139
+ // overlay's `component.handleInput`).
140
+ //
141
+ // The default `ctrl+]` is free in every mainstream macOS terminal (Terminal.app,
142
+ // iTerm2, Warp), every multiplexer (tmux, zellij, screen — none use it as a prefix),
143
+ // and the legacy telnet/ssh escape role doesn't apply because our overlay runs
144
+ // in-process. It is, however, awkward on keyboard layouts where `]` is on the
145
+ // shifted layer (Latin American `es-AR`/`es-MX` require `Ctrl+Shift+}` for `Ctrl+]`)
146
+ // — use the `collapseKey` config field to override.
147
+ // Treat a missing/non-string key as disabled. This can occur at runtime when
148
+ // a long-lived Pi process retains an older outer module while a package update
149
+ // replaces the lazily imported QuestionnaireSession graph on disk. Passing
150
+ // undefined into matchesKey reaches parseKeyId().toLowerCase() and crashes the
151
+ // entire host process, so keep the runtime boundary defensive even though the
152
+ // TypeScript contract requires a string.
153
+ if (
154
+ typeof runtime.collapseKey === "string" &&
155
+ runtime.collapseKey !== "off" &&
156
+ matchesKey(data, runtime.collapseKey as Parameters<typeof matchesKey>[1])
157
+ ) {
158
+ return { kind: "toggle_collapsed" };
159
+ }
160
+
161
+ // Collapsed-mode lockout: while collapsed, swallow every keystroke except cancel so
162
+ // the user can read the now-uncovered transcript without accidentally mutating
163
+ // answers or notes. The collapse toggle itself is already handled above.
164
+ if (state.collapsed) {
165
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
166
+ return { kind: "ignore" };
167
+ }
168
+
169
+ if (state.notesVisible) {
170
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "notes_exit" };
171
+ if (kb.matches(data, KEYBIND_CONFIRM)) return { kind: "notes_exit" };
172
+ return { kind: "notes_forward", data };
173
+ }
174
+
175
+ if (state.inputMode) {
176
+ if (kb.matches(data, KEYBIND_CONFIRM)) {
177
+ const answer = buildSingleSelectAnswer(state, runtime);
178
+ if (!answer) return { kind: "ignore" };
179
+ return { kind: "confirm", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };
180
+ }
181
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
182
+ if (kb.matches(data, KEYBIND_UP)) {
183
+ return prevNavOnUp(state, runtime);
184
+ }
185
+ if (kb.matches(data, KEYBIND_DOWN)) {
186
+ return nextNavOnDown(state, runtime);
187
+ }
188
+ return { kind: "ignore" };
189
+ }
190
+
191
+ if (runtime.isMulti && state.currentTab === runtime.questions.length) {
192
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
193
+ const tab = tabSwitchAction(data, state, runtime);
194
+ if (tab) return tab;
195
+ if (kb.matches(data, KEYBIND_UP) || kb.matches(data, KEYBIND_DOWN)) {
196
+ const delta = kb.matches(data, KEYBIND_DOWN) ? 1 : -1;
197
+ const next = wrapTab(state.submitChoiceIndex + delta, 2);
198
+ return { kind: "submit_nav", nextIndex: (next === 1 ? 1 : 0) as 0 | 1 };
199
+ }
200
+ if (kb.matches(data, KEYBIND_CONFIRM)) {
201
+ // D1 (revised): Submit always submits; Cancel always cancels. The warning header
202
+ // is informational only — `allAnswered(state)` no longer gates submission. Partial
203
+ // answers flow through `orderedAnswers()` in the host.
204
+ return state.submitChoiceIndex === 1 ? { kind: "cancel" } : { kind: "submit" };
205
+ }
206
+ return { kind: "ignore" };
207
+ }
208
+
209
+ const tab = tabSwitchAction(data, state, runtime);
210
+ if (tab) return tab;
211
+
212
+ const q = runtime.questions[state.currentTab];
213
+ if (!q) return { kind: "ignore" };
214
+
215
+ // Universal `n` activation (FR-1): the notes editor opens on every question tab
216
+ // (single- or multi-select, preview or no-preview). The blocks above already
217
+ // swallow `n` when it should NOT reach here — notesVisible forwards to the
218
+ // notes Input, inputMode forwards to the inline Input, the submit-tab block
219
+ // ignores it, and tabSwitchAction ignores it — so by the time we reach this
220
+ // gate, `n` is unambiguously a notes-enter request. Row intent (Next sentinel,
221
+ // "Type something.") is irrelevant: the gate sits ABOVE the multi-select
222
+ // toggle block and the Next sentinel never activates inputMode, so `n` is
223
+ // neither swallowed earlier nor blocked by `blocksMultiToggle`.
224
+ if (data === NOTES_ACTIVATE_KEY) {
225
+ return { kind: "notes_enter" };
226
+ }
227
+
228
+ if (kb.matches(data, KEYBIND_UP)) {
229
+ return prevNavOnUp(state, runtime);
230
+ }
231
+ if (kb.matches(data, KEYBIND_DOWN)) {
232
+ return nextNavOnDown(state, runtime);
233
+ }
234
+
235
+ if (q.multiSelect) {
236
+ const focusedKind = runtime.currentItem?.kind;
237
+ const focusedMeta = focusedKind ? ROW_INTENT_META[focusedKind] : undefined;
238
+ // Space toggles the focused row's checkbox. Suppressed on rows whose META declares
239
+ // `blocksMultiToggle` (the Next sentinel) or `activatesInputMode` (the "Type
240
+ // something." row — it is an inline input, not a checkable option).
241
+ if (data === SPACE_KEY) {
242
+ if (focusedMeta?.blocksMultiToggle) return { kind: "ignore" };
243
+ if (focusedMeta?.activatesInputMode) return { kind: "ignore" };
244
+ return { kind: "toggle", index: state.optionIndex };
245
+ }
246
+ if (kb.matches(data, KEYBIND_CONFIRM)) {
247
+ // Enter on the "Type something." row is handled by the inputMode block above
248
+ // (→ confirm kind:"custom"). Defensive: never enter the toggle/multi_confirm
249
+ // path for an inputMode-activating row.
250
+ if (focusedMeta?.activatesInputMode) return { kind: "ignore" };
251
+ // Enter on a regular row toggles (matching Space) — committing the question is now
252
+ // gated behind explicit focus on a row whose META declares `autoSubmitsInMulti`
253
+ // (the Next sentinel), so Enter on options is a no-cost way to flip checkboxes
254
+ // without leaving the keyboard home row.
255
+ if (!focusedMeta?.autoSubmitsInMulti) return { kind: "toggle", index: state.optionIndex };
256
+ // Enter on Next: carry autoAdvanceTab so the host can advance to the next tab in
257
+ // multi-question mode, OR submit the dialog in single-question mode
258
+ // (autoAdvanceTab === undefined when !isMulti). Without this, a single multi-select
259
+ // question would have no way to commit at all.
260
+ return {
261
+ kind: "multi_confirm",
262
+ selected: buildMultiSelected(state, runtime),
263
+ autoAdvanceTab: computeAutoAdvanceTab(state, runtime),
264
+ };
265
+ }
266
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
267
+ return { kind: "ignore" };
268
+ }
269
+
270
+ if (kb.matches(data, KEYBIND_CONFIRM)) {
271
+ const answer = buildSingleSelectAnswer(state, runtime);
272
+ if (!answer) return { kind: "ignore" };
273
+ return { kind: "confirm", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };
274
+ }
275
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
276
+ return { kind: "ignore" };
277
+ }
@@ -0,0 +1,234 @@
1
+ // GENERATED — DO NOT EDIT. Source: adapter-src/pi/extensions/ask-user-question/state/questionnaire-session.ts
2
+ // Regenerate with: python3 scripts/build-adapters.py
3
+ import type { Theme } from "@earendil-works/pi-coding-agent";
4
+ import { getKeybindings, type Input, type OverlayHandle } from "@earendil-works/pi-tui";
5
+ import type { QuestionData, QuestionnaireResult, QuestionParams } from "../tool/types.js";
6
+ import type { WrappingSelectItem } from "../view/components/wrapping-select.js";
7
+ import { COLLAPSED_HINT } from "../view/dialog-builder.js";
8
+ import type { QuestionnairePropsAdapter } from "../view/props-adapter.js";
9
+ import { buildQuestionnaire } from "./build-questionnaire.js";
10
+ import { t } from "./i18n-bridge.js";
11
+ import { type QuestionnaireAction, routeKey } from "./key-router.js";
12
+ import type { QuestionnaireRuntime, QuestionnaireState } from "./state.js";
13
+ import { type ApplyContext, type Effect, reduce } from "./state-reducer.js";
14
+
15
+ // Module-level constant; reused for cursor-end mutations after setValue rehydration.
16
+ // Ctrl-E → tui.editor.cursorLineEnd (public path; pi-tui keybindings.js:25-28).
17
+ const CURSOR_END = "\x05";
18
+
19
+ export interface QuestionnaireSessionConfig {
20
+ tui: { terminal: { columns: number; rows: number }; requestRender(): void };
21
+ theme: Theme;
22
+ params: QuestionParams;
23
+ itemsByTab: WrappingSelectItem[][];
24
+ done: (result: QuestionnaireResult) => void;
25
+ /** Key spec for the collapse/expand shortcut, e.g. `"ctrl+]"` or `"alt+o"`. */
26
+ collapseKey: string;
27
+ }
28
+
29
+ export interface QuestionnaireSessionComponent {
30
+ render(width: number): string[];
31
+ invalidate(): void;
32
+ handleInput(data: string): void;
33
+ }
34
+
35
+ function initialState(): QuestionnaireState {
36
+ return {
37
+ currentTab: 0,
38
+ optionIndex: 0,
39
+ inputMode: false,
40
+ notesVisible: false,
41
+ answers: new Map(),
42
+ multiSelectChecked: new Set(),
43
+ notesByTab: new Map(),
44
+ submitChoiceIndex: 0,
45
+ notesDraft: "",
46
+ collapsed: false,
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Slim runtime: owns the canonical state cell, the input-buffer cell, the
52
+ * two-pass `notesVisible` dispatch loop, and the effect runner. State
53
+ * transitions go through the pure `reduce` reducer; UI fan-out goes through
54
+ * the `QuestionnairePropsAdapter` produced by `buildQuestionnaire`.
55
+ */
56
+ export class QuestionnaireSession {
57
+ private state: QuestionnaireState = initialState();
58
+
59
+ private readonly questions: readonly QuestionData[];
60
+ private readonly isMulti: boolean;
61
+ private readonly itemsByTab: WrappingSelectItem[][];
62
+
63
+ private readonly notesInput: Input;
64
+ private readonly inlineInput: Input;
65
+ private readonly viewAdapter: QuestionnairePropsAdapter;
66
+ private readonly collapseKey: string;
67
+
68
+ /**
69
+ * Overlay handle captured by `ctx.ui.custom`'s `onHandle` callback. Lets the session
70
+ * call `setHidden(true/false)` so pi-tui's overlay stack reflects the collapsed state
71
+ * and overlay-aware consumers (e.g. `pi-station`) can resume normal behaviour.
72
+ */
73
+ private overlayHandle: OverlayHandle | undefined;
74
+
75
+ private readonly tui: QuestionnaireSessionConfig["tui"];
76
+ private readonly done: QuestionnaireSessionConfig["done"];
77
+ readonly component: QuestionnaireSessionComponent;
78
+
79
+ constructor(config: QuestionnaireSessionConfig) {
80
+ this.tui = config.tui;
81
+ this.done = config.done;
82
+ this.questions = config.params.questions;
83
+ this.isMulti = this.questions.length > 1;
84
+ this.itemsByTab = config.itemsByTab;
85
+ this.collapseKey = config.collapseKey;
86
+
87
+ const built = buildQuestionnaire({
88
+ tui: this.tui,
89
+ theme: config.theme,
90
+ questions: this.questions,
91
+ itemsByTab: this.itemsByTab,
92
+ isMulti: this.isMulti,
93
+ initialState: this.state,
94
+ getCurrentTab: () => this.state.currentTab,
95
+ });
96
+
97
+ this.notesInput = built.notesInput;
98
+ this.inlineInput = built.inlineInput;
99
+ this.viewAdapter = built.adapter;
100
+
101
+ const theme = config.theme;
102
+ // Collapsed render: a single dim row at the bottom of the overlay. pi-tui sizes
103
+ // the overlay to `min(lines.length, maxHeight)`, so returning one line shrinks
104
+ // the bottom-anchored overlay from full-height to one row and the transcript
105
+ // behind it becomes readable (#47). The overlay stays focused and in the
106
+ // stack, so Ctrl+] still routes here to expand.
107
+ const collapsedRender = (_width: number): string[] => [
108
+ theme.fg("dim", ` ${t("hint.expand_line", COLLAPSED_HINT)} `),
109
+ ];
110
+
111
+ this.component = {
112
+ render: (width) => (this.state.collapsed ? collapsedRender(width) : built.render(width)),
113
+ invalidate: built.invalidate,
114
+ handleInput: (data) => this.dispatch(data),
115
+ };
116
+
117
+ this.viewAdapter.apply(this.state);
118
+ }
119
+
120
+ dispatch(data: string): void {
121
+ const action = routeKey(data, this.state, this.runtime());
122
+ if (action.kind === "ignore") {
123
+ this.handleIgnoreInline(data);
124
+ return;
125
+ }
126
+ this.commit(action);
127
+ }
128
+
129
+ private commit(action: QuestionnaireAction): void {
130
+ const result = reduce(this.state, action, this.applyContext());
131
+ this.state = result.state;
132
+ for (const effect of result.effects) this.runEffect(effect);
133
+ this.state = this.mirrorNotesDraft(this.state);
134
+ this.viewAdapter.apply(this.state);
135
+ }
136
+
137
+ private mirrorNotesDraft(s: QuestionnaireState): QuestionnaireState {
138
+ const draft = this.notesInput.getValue();
139
+ return s.notesDraft === draft ? s : { ...s, notesDraft: draft };
140
+ }
141
+
142
+ private runEffect(effect: Effect): void {
143
+ switch (effect.kind) {
144
+ case "set_input_buffer":
145
+ this.inlineInput.setValue(effect.value);
146
+ this.inlineInput.handleInput(CURSOR_END);
147
+ return;
148
+ case "clear_input_buffer":
149
+ this.inlineInput.setValue("");
150
+ return;
151
+ case "set_notes_value":
152
+ this.notesInput.setValue(effect.value);
153
+ return;
154
+ case "set_notes_focused":
155
+ this.notesInput.focused = effect.focused;
156
+ return;
157
+ case "forward_notes_keystroke":
158
+ this.notesInput.handleInput(effect.data);
159
+ return;
160
+ case "set_overlay_hidden":
161
+ // No-op until `setOverlayHandle` has been called (the handle arrives via
162
+ // `ctx.ui.custom`'s `onHandle` right after the overlay is shown).
163
+ this.overlayHandle?.setHidden(effect.hidden);
164
+ return;
165
+ case "done":
166
+ this.done(effect.result);
167
+ return;
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Per-keystroke `ignore` fast path: delegates to the headless `inlineInput`
173
+ * Input so bracketed-paste accumulator (`input.js:33-63`) and Kitty CSI-u
174
+ * decode (`input.js:155-163`) take effect. Cursor is NOT force-reset here —
175
+ * doing so would corrupt split-chunk pastes (a `\x05` byte mid-paste lands
176
+ * verbatim in `pasteBuffer` and survives `handlePaste`'s narrow strip).
177
+ * Cursor advances naturally via `insertCharacter` on typing/paste; cursor-
178
+ * movement keys (Left/Right/Home/End/word-jumps) are now functional, with
179
+ * the always-end visual cursor marker drawn independently by
180
+ * `WrappingSelect.renderInlineInputRow`. `viewAdapter.apply` is called
181
+ * directly without a reducer round-trip — preserves the D3 fast-path
182
+ * latency profile from Phase 11.
183
+ */
184
+ private handleIgnoreInline(data: string): void {
185
+ if (!this.state.inputMode) return;
186
+ this.inlineInput.handleInput(data);
187
+ this.viewAdapter.apply(this.state);
188
+ }
189
+
190
+ private runtime(): QuestionnaireRuntime {
191
+ return {
192
+ keybindings: getKeybindings(),
193
+ inputBuffer: this.inlineInput.getValue(),
194
+ questions: this.questions,
195
+ isMulti: this.isMulti,
196
+ currentItem: this.currentItem(),
197
+ items: this.itemsByTab[this.state.currentTab] ?? [],
198
+ collapseKey: this.collapseKey,
199
+ };
200
+ }
201
+
202
+ private applyContext(): ApplyContext {
203
+ return {
204
+ questions: this.questions,
205
+ itemsByTab: this.itemsByTab,
206
+ };
207
+ }
208
+
209
+ private currentItem(): WrappingSelectItem | undefined {
210
+ const arr = this.itemsByTab[this.state.currentTab] ?? [];
211
+ return this.state.optionIndex < arr.length ? arr[this.state.optionIndex] : undefined;
212
+ }
213
+
214
+ /**
215
+ * Setter for the overlay handle, called by `ctx.ui.custom`'s `onHandle` callback once
216
+ * the TUI has created the overlay. Until this is called, `set_overlay_hidden` effects
217
+ * are no-ops — the session still tracks `state.collapsed` for the view layer.
218
+ */
219
+ setOverlayHandle(handle: OverlayHandle): void {
220
+ this.overlayHandle = handle;
221
+ }
222
+
223
+ /**
224
+ * Public toggle used by the raw terminal input listener registered in `execute()`.
225
+ * pi-tui does not route input to a hidden overlay's `component.handleInput`, so the
226
+ * raw listener (which fires for terminal data regardless of overlay visibility)
227
+ * reaches the session through this method instead of the dispatch path. Routed
228
+ * through `commit` so the transition stays in the reducer and the overlay hide
229
+ * happens via the `set_overlay_hidden` effect like every other side effect.
230
+ */
231
+ toggleCollapsedExternal(): void {
232
+ this.commit({ kind: "toggle_collapsed" });
233
+ }
234
+ }
@@ -0,0 +1,145 @@
1
+ // GENERATED — DO NOT EDIT. Source: adapter-src/pi/extensions/ask-user-question/state/row-intent.ts
2
+ // Regenerate with: python3 scripts/build-adapters.py
3
+ import type { QuestionData } from "../tool/types.js";
4
+ import type { WrappingSelectItem } from "../view/components/wrapping-select.js";
5
+
6
+ /**
7
+ * Row kind discriminator. Single source of truth — derived from the runtime
8
+ * `WrappingSelectItem` union (`wrapping-select.ts:18-22`) so adding a new
9
+ * variant there forces a `ROW_INTENT_META` entry here (compile-time
10
+ * exhaustiveness via `Record<RowKind, …>`).
11
+ */
12
+ export type RowKind = WrappingSelectItem["kind"];
13
+
14
+ /**
15
+ * Sentinel kinds — the subset of `RowKind` representing protocol-driven rows
16
+ * (vs author-defined `option` rows). The auto-append walker, the reserved-label
17
+ * derivation, and `LABELS_BY_KIND` consumers iterate this list.
18
+ */
19
+ export type SentinelKind = Exclude<RowKind, "option">;
20
+ export const SENTINEL_KINDS: readonly SentinelKind[] = ["other", "next"];
21
+
22
+ /**
23
+ * Per-kind static metadata. Pure data — no closures, no per-kind handler
24
+ * functions. Behavior-bearing branches (answer construction in
25
+ * `key-router.ts:69-99`, the Next-row render block in `multi-select-view.ts`,
26
+ * and the inline-Input render branch in `wrapping-select.ts`) keep their
27
+ * exhaustive switches and READ flags from this table.
28
+ *
29
+ * Adding a new sentinel:
30
+ * 1. Add the variant to `WrappingSelectItem` (`wrapping-select.ts:18-22`).
31
+ * 2. Add an entry here. Compile fails until both edits are present.
32
+ * 3. (If user-facing) author wherever the row is synthesized — typically
33
+ * `buildItemsForQuestion` for main-list residents.
34
+ *
35
+ * Field semantics:
36
+ * - `label` — user-facing string. For `option` it's an empty placeholder
37
+ * (per-instance labels come from `QuestionData.options[i].label`); every
38
+ * sentinel uses its META entry as the single source of truth.
39
+ * - `reserved` — author-facing labels matching this string trigger
40
+ * `reserved_label` at validation time. `RESERVED_LABEL_SET` is derived.
41
+ * - `livesInMainList` — true iff the row appears in `itemsByTab[i]`. Every
42
+ * current sentinel lives in the main list (the `next` row is synthesized by
43
+ * `sentinelsToAppend`).
44
+ * - `numbered` — true iff the row contributes to the main-list numbering.
45
+ * Multi-select Next is the only `numbered=false` row that lives in the list.
46
+ * - `activatesInputMode` — true iff focusing the row should toggle
47
+ * `state.inputMode = true`. Read by `state-reducer.ts` `nav` case.
48
+ * - `blocksMultiToggle` — in multiSelect mode, Space (and Enter-as-toggle)
49
+ * on this row is suppressed. The Next sentinel is the only true.
50
+ * - `autoSubmitsInMulti` — in multiSelect mode, Enter on this row commits
51
+ * the question (emits `multi_confirm`). The Next sentinel is the only true.
52
+ * - `autoAppendOnSingleSelect` — `buildItemsForQuestion` appends
53
+ * this row when the question is single-select, regardless of whether any
54
+ * option carries a `preview`. The "other" sentinel is the only true.
55
+ * - `autoAppendOnMultiSelect` — `buildItemsForQuestion` appends this row
56
+ * when the question is multi-select. The `other` and `next` sentinels are both true.
57
+ */
58
+ export interface RowIntentMeta {
59
+ label: string;
60
+ reserved: boolean;
61
+ livesInMainList: boolean;
62
+ numbered: boolean;
63
+ activatesInputMode: boolean;
64
+ blocksMultiToggle: boolean;
65
+ autoSubmitsInMulti: boolean;
66
+ autoAppendOnSingleSelect: boolean;
67
+ autoAppendOnMultiSelect: boolean;
68
+ }
69
+
70
+ export const ROW_INTENT_META: Record<RowKind, RowIntentMeta> = {
71
+ option: {
72
+ label: "",
73
+ reserved: false,
74
+ livesInMainList: true,
75
+ numbered: true,
76
+ activatesInputMode: false,
77
+ blocksMultiToggle: false,
78
+ autoSubmitsInMulti: false,
79
+ autoAppendOnSingleSelect: false,
80
+ autoAppendOnMultiSelect: false,
81
+ },
82
+ other: {
83
+ label: "Type something.",
84
+ reserved: true,
85
+ livesInMainList: true,
86
+ numbered: true,
87
+ activatesInputMode: true,
88
+ blocksMultiToggle: false,
89
+ autoSubmitsInMulti: false,
90
+ autoAppendOnSingleSelect: true,
91
+ autoAppendOnMultiSelect: true,
92
+ },
93
+ next: {
94
+ label: "Next",
95
+ reserved: true,
96
+ livesInMainList: true,
97
+ numbered: false,
98
+ activatesInputMode: false,
99
+ blocksMultiToggle: true,
100
+ autoSubmitsInMulti: true,
101
+ autoAppendOnSingleSelect: false,
102
+ autoAppendOnMultiSelect: true,
103
+ },
104
+ };
105
+
106
+ /**
107
+ * Kind-keyed label view. `option` is excluded — its label is per-instance,
108
+ * not per-kind. `types.ts#SENTINEL_LABELS` re-sources from here.
109
+ */
110
+ export const LABELS_BY_KIND: { readonly [K in SentinelKind]: string } = {
111
+ other: ROW_INTENT_META.other.label,
112
+ next: ROW_INTENT_META.next.label,
113
+ };
114
+
115
+ /**
116
+ * Reserved-label set for runtime validation. Includes "Other" (a model-conditioned
117
+ * label that has no runtime kind) plus every sentinel with `reserved: true`.
118
+ */
119
+ export const RESERVED_LABEL_SET: ReadonlySet<string> = new Set<string>([
120
+ "Other",
121
+ ...SENTINEL_KINDS.filter((k) => ROW_INTENT_META[k].reserved).map((k) => ROW_INTENT_META[k].label),
122
+ ]);
123
+
124
+ /**
125
+ * Walk the META table to synthesize sentinel rows for one question. The two
126
+ * append predicates are mutually exclusive in practice (`multiSelect` vs
127
+ * single-select) but the walker doesn't enforce that — adding a third bucket
128
+ * only requires a new META flag.
129
+ *
130
+ * Returns sentinel descriptors in declaration order of `SENTINEL_KINDS`. The
131
+ * caller wraps each with the `WrappingSelectItem` shape (kind + label).
132
+ */
133
+ export function sentinelsToAppend(question: QuestionData): SentinelKind[] {
134
+ const out: SentinelKind[] = [];
135
+ for (const k of SENTINEL_KINDS) {
136
+ const meta = ROW_INTENT_META[k];
137
+ if (!meta.livesInMainList) continue;
138
+ if (question.multiSelect === true) {
139
+ if (meta.autoAppendOnMultiSelect) out.push(k);
140
+ } else {
141
+ if (meta.autoAppendOnSingleSelect) out.push(k);
142
+ }
143
+ }
144
+ return out;
145
+ }
@@ -0,0 +1,26 @@
1
+ // GENERATED — DO NOT EDIT. Source: adapter-src/pi/extensions/ask-user-question/state/selectors/contract.ts
2
+ // Regenerate with: python3 scripts/build-adapters.py
3
+ import type { QuestionData } from "../../tool/types.js";
4
+ import type { PreviewPaneProps } from "../../view/components/preview/preview-pane.js";
5
+ import type { WrappingSelectItem } from "../../view/components/wrapping-select.js";
6
+ import type { ActiveView, StatefulView } from "../../view/stateful-view.js";
7
+ import type { TabComponents } from "../../view/tab-components.js";
8
+ import type { QuestionnaireState } from "../state.js";
9
+
10
+ export interface BindingContext {
11
+ readonly questions: readonly QuestionData[];
12
+ readonly itemsByTab: ReadonlyArray<readonly WrappingSelectItem[]>;
13
+ readonly totalQuestions: number;
14
+ readonly activeView: ActiveView;
15
+ readonly inputBuffer: string;
16
+ readonly inputCursorOffset: number | undefined;
17
+ readonly activePreviewPane: StatefulView<PreviewPaneProps>;
18
+ }
19
+
20
+ export interface PerTabBindingContext extends BindingContext {
21
+ readonly tab: TabComponents;
22
+ readonly i: number;
23
+ }
24
+
25
+ export type GlobalSelector<P> = (state: QuestionnaireState, ctx: BindingContext) => P;
26
+ export type PerTabSelector<P> = (state: QuestionnaireState, ctx: PerTabBindingContext) => P;