popeye-cli 2.2.0 → 2.7.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 (323) hide show
  1. package/dist/adapters/gemini.d.ts +14 -0
  2. package/dist/adapters/gemini.d.ts.map +1 -1
  3. package/dist/adapters/gemini.js +41 -6
  4. package/dist/adapters/gemini.js.map +1 -1
  5. package/dist/adapters/grok.d.ts +14 -0
  6. package/dist/adapters/grok.d.ts.map +1 -1
  7. package/dist/adapters/grok.js +42 -6
  8. package/dist/adapters/grok.js.map +1 -1
  9. package/dist/adapters/openai.d.ts +10 -0
  10. package/dist/adapters/openai.d.ts.map +1 -1
  11. package/dist/adapters/openai.js +44 -5
  12. package/dist/adapters/openai.js.map +1 -1
  13. package/dist/cli/commands/create.js +1 -1
  14. package/dist/cli/commands/create.js.map +1 -1
  15. package/dist/cli/interactive.d.ts.map +1 -1
  16. package/dist/cli/interactive.js +324 -20
  17. package/dist/cli/interactive.js.map +1 -1
  18. package/dist/generators/all.d.ts.map +1 -1
  19. package/dist/generators/all.js +3 -2
  20. package/dist/generators/all.js.map +1 -1
  21. package/dist/generators/doc-parser.d.ts +21 -6
  22. package/dist/generators/doc-parser.d.ts.map +1 -1
  23. package/dist/generators/doc-parser.js +55 -4
  24. package/dist/generators/doc-parser.js.map +1 -1
  25. package/dist/generators/templates/fullstack.js +1 -1
  26. package/dist/generators/templates/website-components.js +1 -1
  27. package/dist/generators/templates/website-components.js.map +1 -1
  28. package/dist/generators/templates/website-config.d.ts +4 -1
  29. package/dist/generators/templates/website-config.d.ts.map +1 -1
  30. package/dist/generators/templates/website-config.js +17 -11
  31. package/dist/generators/templates/website-config.js.map +1 -1
  32. package/dist/generators/templates/website-conversion.js +1 -1
  33. package/dist/generators/templates/website-conversion.js.map +1 -1
  34. package/dist/generators/templates/website-landing.js +1 -1
  35. package/dist/generators/templates/website-landing.js.map +1 -1
  36. package/dist/generators/templates/website-layout.d.ts +36 -4
  37. package/dist/generators/templates/website-layout.d.ts.map +1 -1
  38. package/dist/generators/templates/website-layout.js +466 -23
  39. package/dist/generators/templates/website-layout.js.map +1 -1
  40. package/dist/generators/templates/website-pricing.js +1 -1
  41. package/dist/generators/templates/website-pricing.js.map +1 -1
  42. package/dist/generators/templates/website-sections.js +1 -1
  43. package/dist/generators/templates/website-sections.js.map +1 -1
  44. package/dist/generators/templates/website-seo.d.ts.map +1 -1
  45. package/dist/generators/templates/website-seo.js +4 -1
  46. package/dist/generators/templates/website-seo.js.map +1 -1
  47. package/dist/generators/templates/website.d.ts +1 -1
  48. package/dist/generators/templates/website.d.ts.map +1 -1
  49. package/dist/generators/templates/website.js +1 -1
  50. package/dist/generators/templates/website.js.map +1 -1
  51. package/dist/generators/website-content-ai.d.ts +52 -0
  52. package/dist/generators/website-content-ai.d.ts.map +1 -0
  53. package/dist/generators/website-content-ai.js +141 -0
  54. package/dist/generators/website-content-ai.js.map +1 -0
  55. package/dist/generators/website-content-scanner.d.ts +1 -1
  56. package/dist/generators/website-content-scanner.d.ts.map +1 -1
  57. package/dist/generators/website-content-scanner.js +98 -1
  58. package/dist/generators/website-content-scanner.js.map +1 -1
  59. package/dist/generators/website-context.d.ts +34 -1
  60. package/dist/generators/website-context.d.ts.map +1 -1
  61. package/dist/generators/website-context.js +131 -9
  62. package/dist/generators/website-context.js.map +1 -1
  63. package/dist/generators/website-debug.d.ts +12 -0
  64. package/dist/generators/website-debug.d.ts.map +1 -1
  65. package/dist/generators/website-debug.js +16 -0
  66. package/dist/generators/website-debug.js.map +1 -1
  67. package/dist/generators/website.d.ts.map +1 -1
  68. package/dist/generators/website.js +26 -4
  69. package/dist/generators/website.js.map +1 -1
  70. package/dist/pipeline/auto-recovery.d.ts +56 -0
  71. package/dist/pipeline/auto-recovery.d.ts.map +1 -0
  72. package/dist/pipeline/auto-recovery.js +185 -0
  73. package/dist/pipeline/auto-recovery.js.map +1 -0
  74. package/dist/pipeline/change-request.d.ts +39 -0
  75. package/dist/pipeline/change-request.d.ts.map +1 -1
  76. package/dist/pipeline/change-request.js +40 -1
  77. package/dist/pipeline/change-request.js.map +1 -1
  78. package/dist/pipeline/check-runner.d.ts +30 -1
  79. package/dist/pipeline/check-runner.d.ts.map +1 -1
  80. package/dist/pipeline/check-runner.js +122 -1
  81. package/dist/pipeline/check-runner.js.map +1 -1
  82. package/dist/pipeline/command-resolver.d.ts.map +1 -1
  83. package/dist/pipeline/command-resolver.js +33 -2
  84. package/dist/pipeline/command-resolver.js.map +1 -1
  85. package/dist/pipeline/consensus/arbitrator-query.d.ts +22 -0
  86. package/dist/pipeline/consensus/arbitrator-query.d.ts.map +1 -0
  87. package/dist/pipeline/consensus/arbitrator-query.js +70 -0
  88. package/dist/pipeline/consensus/arbitrator-query.js.map +1 -0
  89. package/dist/pipeline/consensus/consensus-runner.d.ts +131 -7
  90. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -1
  91. package/dist/pipeline/consensus/consensus-runner.js +809 -35
  92. package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
  93. package/dist/pipeline/cr-lifecycle.d.ts +42 -0
  94. package/dist/pipeline/cr-lifecycle.d.ts.map +1 -0
  95. package/dist/pipeline/cr-lifecycle.js +89 -0
  96. package/dist/pipeline/cr-lifecycle.js.map +1 -0
  97. package/dist/pipeline/gate-engine.d.ts +1 -0
  98. package/dist/pipeline/gate-engine.d.ts.map +1 -1
  99. package/dist/pipeline/gate-engine.js +26 -7
  100. package/dist/pipeline/gate-engine.js.map +1 -1
  101. package/dist/pipeline/orchestrator.d.ts +1 -1
  102. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  103. package/dist/pipeline/orchestrator.js +306 -16
  104. package/dist/pipeline/orchestrator.js.map +1 -1
  105. package/dist/pipeline/packets/consensus-packet-builder.d.ts +15 -4
  106. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -1
  107. package/dist/pipeline/packets/consensus-packet-builder.js +29 -17
  108. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -1
  109. package/dist/pipeline/phases/architecture.d.ts.map +1 -1
  110. package/dist/pipeline/phases/architecture.js +5 -3
  111. package/dist/pipeline/phases/architecture.js.map +1 -1
  112. package/dist/pipeline/phases/audit.d.ts.map +1 -1
  113. package/dist/pipeline/phases/audit.js +5 -3
  114. package/dist/pipeline/phases/audit.js.map +1 -1
  115. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -1
  116. package/dist/pipeline/phases/consensus-architecture.js +10 -1
  117. package/dist/pipeline/phases/consensus-architecture.js.map +1 -1
  118. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -1
  119. package/dist/pipeline/phases/consensus-master-plan.js +10 -3
  120. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -1
  121. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -1
  122. package/dist/pipeline/phases/consensus-role-plans.js +10 -1
  123. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -1
  124. package/dist/pipeline/phases/done.d.ts.map +1 -1
  125. package/dist/pipeline/phases/done.js +9 -4
  126. package/dist/pipeline/phases/done.js.map +1 -1
  127. package/dist/pipeline/phases/intake.d.ts.map +1 -1
  128. package/dist/pipeline/phases/intake.js +7 -3
  129. package/dist/pipeline/phases/intake.js.map +1 -1
  130. package/dist/pipeline/phases/phase-context.d.ts +2 -0
  131. package/dist/pipeline/phases/phase-context.d.ts.map +1 -1
  132. package/dist/pipeline/phases/phase-context.js +3 -1
  133. package/dist/pipeline/phases/phase-context.js.map +1 -1
  134. package/dist/pipeline/phases/production-gate.d.ts.map +1 -1
  135. package/dist/pipeline/phases/production-gate.js +28 -3
  136. package/dist/pipeline/phases/production-gate.js.map +1 -1
  137. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -1
  138. package/dist/pipeline/phases/qa-validation.js +38 -5
  139. package/dist/pipeline/phases/qa-validation.js.map +1 -1
  140. package/dist/pipeline/phases/recovery-loop.d.ts +2 -0
  141. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
  142. package/dist/pipeline/phases/recovery-loop.js +200 -6
  143. package/dist/pipeline/phases/recovery-loop.js.map +1 -1
  144. package/dist/pipeline/phases/review.d.ts.map +1 -1
  145. package/dist/pipeline/phases/review.js +58 -28
  146. package/dist/pipeline/phases/review.js.map +1 -1
  147. package/dist/pipeline/phases/role-planning.d.ts.map +1 -1
  148. package/dist/pipeline/phases/role-planning.js +18 -2
  149. package/dist/pipeline/phases/role-planning.js.map +1 -1
  150. package/dist/pipeline/phases/stuck.d.ts.map +1 -1
  151. package/dist/pipeline/phases/stuck.js +10 -0
  152. package/dist/pipeline/phases/stuck.js.map +1 -1
  153. package/dist/pipeline/repo-snapshot.d.ts.map +1 -1
  154. package/dist/pipeline/repo-snapshot.js +3 -0
  155. package/dist/pipeline/repo-snapshot.js.map +1 -1
  156. package/dist/pipeline/role-execution-adapter.d.ts +2 -1
  157. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -1
  158. package/dist/pipeline/role-execution-adapter.js +22 -7
  159. package/dist/pipeline/role-execution-adapter.js.map +1 -1
  160. package/dist/pipeline/skill-loader.d.ts +19 -0
  161. package/dist/pipeline/skill-loader.d.ts.map +1 -1
  162. package/dist/pipeline/skill-loader.js +22 -0
  163. package/dist/pipeline/skill-loader.js.map +1 -1
  164. package/dist/pipeline/skills/coverage-gate.d.ts +44 -0
  165. package/dist/pipeline/skills/coverage-gate.d.ts.map +1 -0
  166. package/dist/pipeline/skills/coverage-gate.js +143 -0
  167. package/dist/pipeline/skills/coverage-gate.js.map +1 -0
  168. package/dist/pipeline/skills/usage-registry.d.ts +48 -0
  169. package/dist/pipeline/skills/usage-registry.d.ts.map +1 -0
  170. package/dist/pipeline/skills/usage-registry.js +55 -0
  171. package/dist/pipeline/skills/usage-registry.js.map +1 -0
  172. package/dist/pipeline/strategy-context.d.ts +20 -0
  173. package/dist/pipeline/strategy-context.d.ts.map +1 -0
  174. package/dist/pipeline/strategy-context.js +55 -0
  175. package/dist/pipeline/strategy-context.js.map +1 -0
  176. package/dist/pipeline/type-defs/artifacts.d.ts +25 -5
  177. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
  178. package/dist/pipeline/type-defs/artifacts.js +4 -0
  179. package/dist/pipeline/type-defs/artifacts.js.map +1 -1
  180. package/dist/pipeline/type-defs/audit.d.ts +25 -13
  181. package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
  182. package/dist/pipeline/type-defs/checks.d.ts +18 -8
  183. package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
  184. package/dist/pipeline/type-defs/checks.js +4 -0
  185. package/dist/pipeline/type-defs/checks.js.map +1 -1
  186. package/dist/pipeline/type-defs/packets.d.ts +104 -18
  187. package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
  188. package/dist/pipeline/type-defs/packets.js +17 -1
  189. package/dist/pipeline/type-defs/packets.js.map +1 -1
  190. package/dist/pipeline/type-defs/state.d.ts +160 -16
  191. package/dist/pipeline/type-defs/state.d.ts.map +1 -1
  192. package/dist/pipeline/type-defs/state.js +26 -1
  193. package/dist/pipeline/type-defs/state.js.map +1 -1
  194. package/dist/shared/text-utils.d.ts +23 -0
  195. package/dist/shared/text-utils.d.ts.map +1 -0
  196. package/dist/shared/text-utils.js +66 -0
  197. package/dist/shared/text-utils.js.map +1 -0
  198. package/dist/shared/website-strategy-format.d.ts +18 -0
  199. package/dist/shared/website-strategy-format.d.ts.map +1 -0
  200. package/dist/shared/website-strategy-format.js +47 -0
  201. package/dist/shared/website-strategy-format.js.map +1 -0
  202. package/dist/state/index.d.ts +2 -0
  203. package/dist/state/index.d.ts.map +1 -1
  204. package/dist/state/index.js +57 -8
  205. package/dist/state/index.js.map +1 -1
  206. package/dist/types/consensus.d.ts +1 -0
  207. package/dist/types/consensus.d.ts.map +1 -1
  208. package/dist/types/consensus.js.map +1 -1
  209. package/dist/types/website-strategy.d.ts +1 -1
  210. package/dist/types/workflow.d.ts +447 -0
  211. package/dist/types/workflow.d.ts.map +1 -1
  212. package/dist/types/workflow.js +3 -0
  213. package/dist/types/workflow.js.map +1 -1
  214. package/dist/upgrade/handlers.d.ts.map +1 -1
  215. package/dist/upgrade/handlers.js +6 -3
  216. package/dist/upgrade/handlers.js.map +1 -1
  217. package/dist/workflow/consensus.d.ts.map +1 -1
  218. package/dist/workflow/consensus.js +1 -0
  219. package/dist/workflow/consensus.js.map +1 -1
  220. package/dist/workflow/website-strategy.d.ts.map +1 -1
  221. package/dist/workflow/website-strategy.js +2 -29
  222. package/dist/workflow/website-strategy.js.map +1 -1
  223. package/dist/workflow/website-updater.d.ts.map +1 -1
  224. package/dist/workflow/website-updater.js +3 -2
  225. package/dist/workflow/website-updater.js.map +1 -1
  226. package/package.json +1 -1
  227. package/src/adapters/gemini.ts +51 -6
  228. package/src/adapters/grok.ts +51 -6
  229. package/src/adapters/openai.ts +53 -5
  230. package/src/cli/commands/create.ts +1 -1
  231. package/src/cli/interactive.ts +333 -19
  232. package/src/generators/all.ts +3 -2
  233. package/src/generators/doc-parser.ts +75 -15
  234. package/src/generators/templates/fullstack.ts +1 -1
  235. package/src/generators/templates/website-components.ts +1 -1
  236. package/src/generators/templates/website-config.ts +23 -11
  237. package/src/generators/templates/website-conversion.ts +1 -1
  238. package/src/generators/templates/website-landing.ts +1 -1
  239. package/src/generators/templates/website-layout.ts +491 -23
  240. package/src/generators/templates/website-pricing.ts +1 -1
  241. package/src/generators/templates/website-sections.ts +1 -1
  242. package/src/generators/templates/website-seo.ts +4 -1
  243. package/src/generators/templates/website.ts +3 -0
  244. package/src/generators/website-content-ai.ts +186 -0
  245. package/src/generators/website-content-scanner.ts +113 -1
  246. package/src/generators/website-context.ts +151 -12
  247. package/src/generators/website-debug.ts +26 -0
  248. package/src/generators/website.ts +28 -3
  249. package/src/pipeline/auto-recovery.ts +283 -0
  250. package/src/pipeline/change-request.ts +63 -1
  251. package/src/pipeline/check-runner.ts +141 -2
  252. package/src/pipeline/command-resolver.ts +34 -2
  253. package/src/pipeline/consensus/arbitrator-query.ts +101 -0
  254. package/src/pipeline/consensus/consensus-runner.ts +1099 -42
  255. package/src/pipeline/cr-lifecycle.ts +103 -0
  256. package/src/pipeline/gate-engine.ts +35 -7
  257. package/src/pipeline/orchestrator.ts +361 -16
  258. package/src/pipeline/packets/consensus-packet-builder.ts +44 -18
  259. package/src/pipeline/phases/architecture.ts +6 -3
  260. package/src/pipeline/phases/audit.ts +6 -3
  261. package/src/pipeline/phases/consensus-architecture.ts +10 -1
  262. package/src/pipeline/phases/consensus-master-plan.ts +10 -3
  263. package/src/pipeline/phases/consensus-role-plans.ts +10 -1
  264. package/src/pipeline/phases/done.ts +15 -4
  265. package/src/pipeline/phases/intake.ts +7 -3
  266. package/src/pipeline/phases/phase-context.ts +6 -1
  267. package/src/pipeline/phases/production-gate.ts +41 -3
  268. package/src/pipeline/phases/qa-validation.ts +51 -5
  269. package/src/pipeline/phases/recovery-loop.ts +229 -7
  270. package/src/pipeline/phases/review.ts +73 -30
  271. package/src/pipeline/phases/role-planning.ts +21 -2
  272. package/src/pipeline/phases/stuck.ts +10 -0
  273. package/src/pipeline/repo-snapshot.ts +3 -0
  274. package/src/pipeline/role-execution-adapter.ts +30 -4
  275. package/src/pipeline/skill-loader.ts +33 -0
  276. package/src/pipeline/skills/coverage-gate.ts +199 -0
  277. package/src/pipeline/skills/usage-registry.ts +87 -0
  278. package/src/pipeline/strategy-context.ts +60 -0
  279. package/src/pipeline/type-defs/artifacts.ts +4 -0
  280. package/src/pipeline/type-defs/checks.ts +4 -0
  281. package/src/pipeline/type-defs/packets.ts +18 -1
  282. package/src/pipeline/type-defs/state.ts +26 -1
  283. package/src/shared/text-utils.ts +70 -0
  284. package/src/shared/website-strategy-format.ts +56 -0
  285. package/src/state/index.ts +60 -8
  286. package/src/types/consensus.ts +1 -0
  287. package/src/types/workflow.ts +6 -0
  288. package/src/upgrade/handlers.ts +9 -3
  289. package/src/workflow/consensus.ts +1 -0
  290. package/src/workflow/website-strategy.ts +2 -36
  291. package/src/workflow/website-updater.ts +4 -2
  292. package/tests/adapters/gemini.test.ts +165 -0
  293. package/tests/adapters/grok.test.ts +137 -0
  294. package/tests/adapters/openai.test.ts +128 -0
  295. package/tests/generators/doc-parser.test.ts +88 -9
  296. package/tests/generators/quality-gate.test.ts +19 -3
  297. package/tests/generators/website-components.test.ts +34 -0
  298. package/tests/generators/website-content-ai.test.ts +308 -0
  299. package/tests/generators/website-content-scanner.test.ts +86 -0
  300. package/tests/generators/website-context.test.ts +3 -2
  301. package/tests/integration/smokestack-scaffold.test.ts +385 -0
  302. package/tests/pipeline/auto-recovery.test.ts +337 -0
  303. package/tests/pipeline/change-request.test.ts +70 -0
  304. package/tests/pipeline/command-resolver.test.ts +42 -0
  305. package/tests/pipeline/consensus/arbitrator-query.test.ts +107 -0
  306. package/tests/pipeline/consensus-runner.test.ts +1333 -10
  307. package/tests/pipeline/consensus-scoring.test.ts +602 -18
  308. package/tests/pipeline/gate-engine.test.ts +34 -0
  309. package/tests/pipeline/install-check.test.ts +261 -0
  310. package/tests/pipeline/orchestrator.test.ts +1506 -15
  311. package/tests/pipeline/packets/builders.test.ts +29 -6
  312. package/tests/pipeline/phases/role-planning.strategy.test.ts +204 -0
  313. package/tests/pipeline/pipeline-persistence.test.ts +230 -0
  314. package/tests/pipeline/recovery-loop-guidance.test.ts +280 -0
  315. package/tests/pipeline/role-execution-adapter.test.ts +88 -0
  316. package/tests/pipeline/skills/coverage-gate.test.ts +370 -0
  317. package/tests/pipeline/skills/usage-registry.test.ts +114 -0
  318. package/tests/pipeline/strategy-context.test.ts +148 -0
  319. package/tests/shared/text-utils.test.ts +155 -0
  320. package/tests/state/progress-analysis.test.ts +375 -0
  321. package/tests/upgrade/handlers.test.ts +33 -2
  322. package/tests/workflow/consensus.test.ts +6 -0
  323. package/tsconfig.json +1 -1
@@ -10,6 +10,7 @@ import type { ConsensusResult, ArbitrationResult } from '../types/consensus.js';
10
10
  import type { OutputLanguage } from '../types/project.js';
11
11
  import { getGrokToken, GROK_API_URL } from '../auth/grok.js';
12
12
  import { DEFAULT_GROK_MODEL } from '../types/consensus.js';
13
+ import { normalizeIssueList } from '../shared/text-utils.js';
13
14
 
14
15
  /**
15
16
  * Default Grok configuration
@@ -79,6 +80,44 @@ export async function requestConsensus(
79
80
  }
80
81
  }
81
82
 
83
+ /**
84
+ * Send a prompt directly to the Grok API and return the raw response string.
85
+ * No prompt wrapping or response parsing — used by the consensus runner
86
+ * which builds its own prompts and parses responses itself.
87
+ *
88
+ * @param prompt - The complete prompt to send
89
+ * @param config - Configuration for model/temperature/maxTokens
90
+ * @returns Raw LLM response text
91
+ */
92
+ export async function requestRawReview(
93
+ prompt: string,
94
+ config: { model?: string; temperature?: number; maxTokens?: number } = {},
95
+ ): Promise<string> {
96
+ const {
97
+ model = DEFAULT_GROK_CONFIG.model,
98
+ temperature = DEFAULT_GROK_CONFIG.temperature,
99
+ maxTokens = DEFAULT_GROK_CONFIG.maxTokens,
100
+ } = config;
101
+
102
+ const client = await createClient();
103
+
104
+ try {
105
+ const completion = await client.chat.completions.create({
106
+ model,
107
+ messages: [{ role: 'user', content: prompt }],
108
+ temperature,
109
+ max_tokens: maxTokens,
110
+ });
111
+ return completion.choices[0]?.message?.content || '';
112
+ } catch (error) {
113
+ if (error instanceof OpenAI.RateLimitError) {
114
+ await sleep(5000);
115
+ return requestRawReview(prompt, config);
116
+ }
117
+ throw error;
118
+ }
119
+ }
120
+
82
121
  /**
83
122
  * Request arbitration from Grok when consensus is stuck
84
123
  *
@@ -144,10 +183,13 @@ STRENGTHS:
144
183
  - [etc.]
145
184
 
146
185
  CONCERNS:
147
- - [Concern 1]
148
- - [Concern 2]
186
+ - [Non-blocking concern 1]
187
+ - [Non-blocking concern 2]
149
188
  - [etc.]
150
189
 
190
+ BLOCKING_ISSUES:
191
+ - [Critical issue that must be resolved, or "None"]
192
+
151
193
  RECOMMENDATIONS:
152
194
  - [Recommendation 1]
153
195
  - [Recommendation 2]
@@ -253,11 +295,13 @@ export function parseConsensusResponse(response: string): ConsensusResult {
253
295
  const analysis = extractSection(response, ['ANALYSIS', '## Analysis', '### Analysis']);
254
296
  const strengthsText = extractSection(response, ['STRENGTHS', '## Strengths', '### Strengths']);
255
297
  const concernsText = extractSection(response, ['CONCERNS', '## Concerns', '### Concerns']);
298
+ const blockingIssuesText = extractSection(response, ['BLOCKING_ISSUES', '## Blocking Issues', '### Blocking Issues']);
256
299
  const recommendationsText = extractSection(response, ['RECOMMENDATIONS', '## Recommendations', '### Recommendations']);
257
300
 
258
301
  // Parse lists from sections
259
302
  const strengths = parseList(strengthsText);
260
303
  const concerns = parseList(concernsText);
304
+ const blockingIssues = normalizeIssueList(parseList(blockingIssuesText));
261
305
  const recommendations = parseList(recommendationsText);
262
306
 
263
307
  return {
@@ -265,6 +309,7 @@ export function parseConsensusResponse(response: string): ConsensusResult {
265
309
  analysis: analysis.trim(),
266
310
  strengths,
267
311
  concerns,
312
+ blockingIssues,
268
313
  recommendations,
269
314
  approved: score >= 95,
270
315
  rawResponse: response,
@@ -295,11 +340,11 @@ function parseArbitrationResponse(response: string): ArbitrationResult {
295
340
  approved,
296
341
  score,
297
342
  analysis,
298
- criticalConcerns: criticalConcerns.filter(c => c.toLowerCase() !== 'none'),
299
- minorConcerns: minorConcerns.filter(c => c.toLowerCase() !== 'none'),
300
- subjectiveConcerns: subjectiveConcerns.filter(c => c.toLowerCase() !== 'none'),
343
+ criticalConcerns: normalizeIssueList(criticalConcerns),
344
+ minorConcerns: normalizeIssueList(minorConcerns),
345
+ subjectiveConcerns: normalizeIssueList(subjectiveConcerns),
301
346
  reasoning,
302
- suggestedChanges: suggestedChanges.filter(c => !c.toLowerCase().includes('none')),
347
+ suggestedChanges: normalizeIssueList(suggestedChanges),
303
348
  rawResponse: response,
304
349
  };
305
350
  }
@@ -7,6 +7,7 @@ import OpenAI from 'openai';
7
7
  import type { ConsensusResult, ConsensusConfig, OpenAIModel, OutputLanguage } from '../types/index.js';
8
8
  import { getOpenAIToken } from '../auth/index.js';
9
9
  import { DEFAULT_CONSENSUS_CONFIG } from '../types/consensus.js';
10
+ import { normalizeIssueList } from '../shared/text-utils.js';
10
11
 
11
12
  /**
12
13
  * Create an OpenAI client with stored credentials
@@ -87,9 +88,10 @@ ${plan}
87
88
  Please provide:
88
89
  1. ANALYSIS: Detailed review of the plan
89
90
  2. STRENGTHS: What works well
90
- 3. CONCERNS: Issues or gaps identified
91
- 4. RECOMMENDATIONS: Specific improvements
92
- 5. CONSENSUS SCORE: A percentage (0-100%) indicating your agreement
91
+ 3. CONCERNS: Non-blocking issues, minor gaps, or suggestions for improvement
92
+ 4. BLOCKING_ISSUES: Critical issues that MUST be resolved before proceeding (security, data loss, missing requirements, broken architecture). Write "None" if no blocking issues.
93
+ 5. RECOMMENDATIONS: Specific improvements
94
+ 6. CONSENSUS SCORE: A percentage (0-100%) indicating your agreement
93
95
  - 95-100%: Ready for execution
94
96
  - 80-94%: Minor revisions needed
95
97
  - 60-79%: Significant revisions needed
@@ -109,15 +111,22 @@ export function parseConsensusResponse(response: string): ConsensusResult {
109
111
  const scoreMatch = response.match(/CONSENSUS:\s*(\d+)%/i);
110
112
  const score = scoreMatch ? parseInt(scoreMatch[1], 10) : 0;
111
113
 
112
- // Extract sections
114
+ // Extract sections — handle backward compat when BLOCKING_ISSUES is absent
113
115
  const analysis = extractSection(response, 'ANALYSIS', 'STRENGTHS');
114
116
  const strengthsText = extractSection(response, 'STRENGTHS', 'CONCERNS');
115
- const concernsText = extractSection(response, 'CONCERNS', 'RECOMMENDATIONS');
117
+ const hasBlockingIssues = /BLOCKING_ISSUES[:\s]/i.test(response);
118
+ const concernsText = hasBlockingIssues
119
+ ? extractSection(response, 'CONCERNS', 'BLOCKING_ISSUES')
120
+ : extractSection(response, 'CONCERNS', 'RECOMMENDATIONS');
121
+ const blockingIssuesText = hasBlockingIssues
122
+ ? extractSection(response, 'BLOCKING_ISSUES', 'RECOMMENDATIONS')
123
+ : '';
116
124
  const recommendationsText = extractSection(response, 'RECOMMENDATIONS', 'CONSENSUS');
117
125
 
118
126
  // Parse lists from sections
119
127
  const strengths = parseList(strengthsText);
120
128
  const concerns = parseList(concernsText);
129
+ const blockingIssues = normalizeIssueList(parseList(blockingIssuesText));
121
130
  const recommendations = parseList(recommendationsText);
122
131
 
123
132
  return {
@@ -125,6 +134,7 @@ export function parseConsensusResponse(response: string): ConsensusResult {
125
134
  analysis: analysis.trim(),
126
135
  strengths,
127
136
  concerns,
137
+ blockingIssues,
128
138
  recommendations,
129
139
  approved: score >= 95,
130
140
  rawResponse: response,
@@ -217,6 +227,44 @@ function sleep(ms: number): Promise<void> {
217
227
  return new Promise((resolve) => setTimeout(resolve, ms));
218
228
  }
219
229
 
230
+ /**
231
+ * Send a prompt directly to the OpenAI API and return the raw response string.
232
+ * No prompt wrapping or response parsing — used by the consensus runner
233
+ * which builds its own prompts and parses responses itself.
234
+ *
235
+ * @param prompt - The complete prompt to send
236
+ * @param config - Consensus configuration for model/temperature/maxTokens
237
+ * @returns Raw LLM response text
238
+ */
239
+ export async function requestRawReview(
240
+ prompt: string,
241
+ config: Partial<ConsensusConfig> = {},
242
+ ): Promise<string> {
243
+ const {
244
+ openaiModel = DEFAULT_CONSENSUS_CONFIG.openaiModel,
245
+ temperature = DEFAULT_CONSENSUS_CONFIG.temperature,
246
+ maxTokens = DEFAULT_CONSENSUS_CONFIG.maxTokens,
247
+ } = config;
248
+
249
+ const client = await createClient();
250
+
251
+ try {
252
+ const completion = await client.chat.completions.create({
253
+ model: openaiModel,
254
+ messages: [{ role: 'user', content: prompt }],
255
+ temperature,
256
+ max_tokens: maxTokens,
257
+ });
258
+ return completion.choices[0]?.message?.content || '';
259
+ } catch (error) {
260
+ if (error instanceof OpenAI.RateLimitError) {
261
+ await sleep(5000);
262
+ return requestRawReview(prompt, config);
263
+ }
264
+ throw error;
265
+ }
266
+ }
267
+
220
268
  /**
221
269
  * Validate that a model is available
222
270
  */
@@ -241,7 +241,7 @@ export function createCreateCommand(): Command {
241
241
  printConsensusResult(
242
242
  workflowResult.planResult.consensusResult.iterations[
243
243
  workflowResult.planResult.consensusResult.iterations.length - 1
244
- ]?.result || { score: 0, analysis: '', approved: false, rawResponse: '' }
244
+ ]?.result || { score: 0, analysis: '', approved: false, rawResponse: '', strengths: [], concerns: [], blockingIssues: [], recommendations: [] }
245
245
  );
246
246
  }
247
247
 
@@ -293,6 +293,8 @@ interface SessionState {
293
293
  reviewer: AIProvider;
294
294
  arbitrator: AIProvider;
295
295
  enableArbitration: boolean;
296
+ /** v2.5.4: Callback to recreate readline after workflow runs (stdin corruption fix) */
297
+ refreshReadline?: () => void;
296
298
  }
297
299
 
298
300
  /**
@@ -317,6 +319,82 @@ function getBuildLabel(language: string): string {
317
319
  }
318
320
  }
319
321
 
322
+ /**
323
+ * Get language-specific next steps for the project completion summary.
324
+ * Returns an array of instruction strings the user should follow after generation.
325
+ */
326
+ function getNextSteps(language: string, projectDir: string): string[] {
327
+ const steps: string[] = [];
328
+
329
+ switch (language) {
330
+ case 'website':
331
+ steps.push('cd ' + projectDir);
332
+ steps.push('npm install');
333
+ steps.push('npm run dev # Start the development server');
334
+ steps.push('Open http://localhost:3000 in your browser');
335
+ steps.push('npm run build # Create production build');
336
+ break;
337
+
338
+ case 'typescript':
339
+ case 'javascript':
340
+ steps.push('cd ' + projectDir);
341
+ steps.push('npm install');
342
+ steps.push('npm run dev # Start the development server');
343
+ steps.push('npm test # Run tests');
344
+ steps.push('npm run build # Create production build');
345
+ break;
346
+
347
+ case 'python':
348
+ steps.push('cd ' + projectDir);
349
+ steps.push('python -m venv venv && source venv/bin/activate');
350
+ steps.push('pip install -r requirements.txt');
351
+ steps.push('python main.py # Run the application');
352
+ steps.push('pytest # Run tests');
353
+ break;
354
+
355
+ case 'fullstack':
356
+ steps.push('cd ' + projectDir);
357
+ steps.push('npm install # Install workspace dependencies');
358
+ steps.push('# Frontend:');
359
+ steps.push('cd apps/frontend && npm run dev');
360
+ steps.push('# Backend:');
361
+ steps.push('cd apps/backend && pip install -r requirements.txt && python main.py');
362
+ break;
363
+
364
+ case 'all':
365
+ steps.push('cd ' + projectDir);
366
+ steps.push('npm install # Install workspace dependencies');
367
+ steps.push('# Frontend:');
368
+ steps.push('cd apps/frontend && npm run dev');
369
+ steps.push('# Backend:');
370
+ steps.push('cd apps/backend && pip install -r requirements.txt && python main.py');
371
+ steps.push('# Website:');
372
+ steps.push('cd apps/website && npm run dev');
373
+ break;
374
+
375
+ default:
376
+ steps.push('cd ' + projectDir);
377
+ steps.push('Review the README.md for setup instructions');
378
+ break;
379
+ }
380
+
381
+ return steps;
382
+ }
383
+
384
+ /**
385
+ * Print next steps section for project completion summary.
386
+ */
387
+ function printNextSteps(language: string, projectDir: string): void {
388
+ const steps = getNextSteps(language, projectDir);
389
+ console.log();
390
+ console.log(theme.primary.bold(' Next Steps:'));
391
+ for (const step of steps) {
392
+ console.log(` ${theme.dim('$')} ${step}`);
393
+ }
394
+ console.log();
395
+ console.log(` ${theme.dim('Review the generated README.md for full documentation.')}`);
396
+ }
397
+
320
398
  /**
321
399
  * Draw the header box
322
400
  */
@@ -2070,12 +2148,37 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2070
2148
  const progressAnalysis = await analyzeProjectProgress(state.projectDir);
2071
2149
  const verification = await verifyProjectCompletion(state.projectDir);
2072
2150
 
2151
+ // v2.5.3: Compute failing gates once for STUCK/RECOVERY_LOOP display.
2152
+ // failedPhase can be misleading (e.g. QA_VALIDATION when the real blocker is
2153
+ // CONSENSUS_MASTER_PLAN). Scan all gateResults for actual failures.
2154
+ const pp = status.state.pipeline?.pipelinePhase;
2155
+ const pl = status.state.pipeline;
2156
+ const failingGates = pl
2157
+ ? Object.entries(pl.gateResults)
2158
+ .filter(([, gr]) => !gr.pass)
2159
+ .filter(([, gr]) => gr.blockers.length > 0 || gr.missingArtifacts.length > 0 || gr.failedChecks.length > 0)
2160
+ .sort((a, b) => {
2161
+ const aConsensus = a[1].consensusScore !== undefined ? 0 : 1;
2162
+ const bConsensus = b[1].consensusScore !== undefined ? 0 : 1;
2163
+ if (aConsensus !== bConsensus) return aConsensus - bConsensus;
2164
+ return b[1].blockers.length - a[1].blockers.length;
2165
+ })
2166
+ : [];
2167
+ const blockingAt = failingGates[0]?.[0] ?? pl?.failedPhase ?? 'unknown';
2168
+
2073
2169
  console.log();
2074
2170
  console.log(theme.primary.bold(' Project Status:'));
2075
2171
  console.log(` ${theme.dim('Name:')} ${status.state.name}`);
2076
2172
  console.log(` ${theme.dim('Language:')} ${theme.primary(status.state.language)}`);
2077
- console.log(` ${theme.dim('Phase:')} ${theme.primary(status.state.phase)}`);
2078
- console.log(` ${theme.dim('Status:')} ${status.state.status}`);
2173
+ if (pp === 'STUCK' || pp === 'RECOVERY_LOOP') {
2174
+ // Reason: toLegacyPhase('STUCK') returns 'execution' and status may be stale 'complete'
2175
+ // from a prior completeProject() call — override both with accurate info.
2176
+ console.log(` ${theme.dim('Phase:')} ${theme.error(pp)} (blocking at ${blockingAt})`);
2177
+ console.log(` ${theme.dim('Status:')} ${theme.error('requires intervention')}`);
2178
+ } else {
2179
+ console.log(` ${theme.dim('Phase:')} ${theme.primary(status.state.phase)}`);
2180
+ console.log(` ${theme.dim('Status:')} ${status.state.status}`);
2181
+ }
2079
2182
 
2080
2183
  // Show detailed progress comparison
2081
2184
  console.log();
@@ -2140,8 +2243,85 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2140
2243
  console.log(theme.dim(` Plan file: ${progressAnalysis.planParseError}`));
2141
2244
  }
2142
2245
 
2143
- // Check for status mismatch (status says complete but state tasks are incomplete)
2144
- if (progressAnalysis.statusMismatch && !progressAnalysis.planMismatch) {
2246
+ // Pipeline-specific messaging takes priority over legacy mismatch
2247
+ // Reason: pp, pl, failingGates, blockingAt already computed above (Change 1)
2248
+ if (pp) {
2249
+ if (pp === 'STUCK') {
2250
+ console.log();
2251
+ console.log(theme.error.bold(' PIPELINE STUCK:'));
2252
+ console.log(theme.error(` Blocking at: ${blockingAt}`));
2253
+ console.log(theme.error(` Recovery attempts: ${pl!.recoveryCount ?? 0}/${pl!.maxRecoveryIterations ?? 5}`));
2254
+
2255
+ // Top-line summary: if the primary failing gate has a consensus score, show it
2256
+ const primaryGate = failingGates[0];
2257
+ if (primaryGate?.[1].consensusScore !== undefined) {
2258
+ const threshold = 0.95;
2259
+ console.log(theme.error(` Consensus score: ${primaryGate[1].consensusScore.toFixed(2)} < ${threshold}`));
2260
+ }
2261
+
2262
+ // Show all failing gates with details
2263
+ if (failingGates.length > 0) {
2264
+ console.log();
2265
+ console.log(theme.warning(' Gate Blockers:'));
2266
+ for (const [phase, gr] of failingGates) {
2267
+ const scoreStr = gr.consensusScore !== undefined
2268
+ ? ` (score: ${gr.consensusScore.toFixed(2)})`
2269
+ : '';
2270
+ console.log(theme.warning(` ${phase}${scoreStr}:`));
2271
+
2272
+ if (gr.blockers.length > 0) {
2273
+ for (const blocker of gr.blockers.slice(0, 3)) {
2274
+ console.log(` ${theme.dim('-')} ${blocker}`);
2275
+ }
2276
+ } else if (gr.missingArtifacts.length > 0) {
2277
+ console.log(` ${theme.dim('-')} Missing artifacts: ${gr.missingArtifacts.join(', ')}`);
2278
+ } else if (gr.failedChecks.length > 0) {
2279
+ console.log(` ${theme.dim('-')} Failed checks: ${gr.failedChecks.join(', ')}`);
2280
+ } else {
2281
+ console.log(` ${theme.dim('-')} Gate failed (no details reported)`);
2282
+ }
2283
+ }
2284
+ }
2285
+
2286
+ // Surface diagnostic report paths
2287
+ const stuckReport = pl!.artifacts?.find(a => a.type === 'stuck_report');
2288
+ const rcaReport = pl!.artifacts
2289
+ ?.filter(a => a.type === 'rca_report')
2290
+ .sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
2291
+
2292
+ if (stuckReport || rcaReport) {
2293
+ console.log();
2294
+ console.log(theme.secondary(' Diagnostic Reports:'));
2295
+ if (stuckReport) console.log(` ${theme.dim('Stuck report:')} ${stuckReport.path}`);
2296
+ if (rcaReport) console.log(` ${theme.dim('RCA report:')} ${rcaReport.path}`);
2297
+ // v2.6.0: Surface auto-recovery artifact and result
2298
+ const autoRecoveryArtifact = pl!.artifacts?.find(a => a.type === 'auto_recovery_guidance');
2299
+ if (autoRecoveryArtifact) {
2300
+ console.log(` ${theme.dim('Auto-recovery:')} ${autoRecoveryArtifact.path}`);
2301
+ }
2302
+ if (pl!.autoRecoveryResult) {
2303
+ console.log(` ${theme.dim('Auto-recovery result:')} ${pl!.autoRecoveryResult}`);
2304
+ }
2305
+ }
2306
+
2307
+ console.log();
2308
+ console.log(theme.secondary(' Provide guidance when resuming to attempt recovery.'));
2309
+ } else if (pp === 'RECOVERY_LOOP') {
2310
+ const count = pl!.recoveryCount ?? 0;
2311
+ const max = pl!.maxRecoveryIterations ?? 5;
2312
+ console.log();
2313
+ console.log(theme.warning.bold(' RECOVERY IN PROGRESS:'));
2314
+ console.log(theme.warning(` Failed at: ${pl!.failedPhase ?? 'unknown'}`));
2315
+ console.log(theme.warning(` Recovery attempts: ${count}/${max}`));
2316
+ if (count < max) {
2317
+ console.log(theme.secondary(' Resume will auto-retry. Optional: add guidance to steer recovery.'));
2318
+ console.log(theme.secondary(' Once attempts are exhausted, guidance will be required.'));
2319
+ } else {
2320
+ console.log(theme.secondary(' All attempts exhausted. Add guidance when resuming to retry.'));
2321
+ }
2322
+ }
2323
+ } else if (progressAnalysis.statusMismatch && !progressAnalysis.planMismatch) {
2324
+ // Legacy mismatch (non-pipeline projects only)
2145
2325
  console.log();
2146
2326
  console.log(theme.warning.bold(' WARNING: Status Mismatch Detected!'));
2147
2327
  console.log(theme.warning(` Project status says '${status.state.status}' but work is incomplete.`));
@@ -2199,15 +2379,22 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2199
2379
  // successful build verification (sets status='complete', phase='complete').
2200
2380
  // If all tasks are done but status is still 'in-progress', the final
2201
2381
  // verification phase (build, tests, README) never completed successfully.
2202
- if (verification.isComplete && projectExplicitlyCompleted) {
2382
+ const pipelineDone = status.state.pipeline?.pipelinePhase === 'DONE';
2383
+ if ((verification.isComplete && projectExplicitlyCompleted) ||
2384
+ (pipelineDone && progressAnalysis.totalMilestones === 0)) {
2203
2385
  console.log();
2204
2386
  printSuccess('Project is fully complete!');
2205
2387
  console.log();
2206
2388
  console.log(theme.primary.bold(' Project Summary:'));
2207
- console.log(` ${theme.dim('Milestones:')} ${progressAnalysis.totalMilestones}/${progressAnalysis.totalMilestones} complete`);
2208
- console.log(` ${theme.dim('Tasks:')} ${progressAnalysis.totalTasks}/${progressAnalysis.totalTasks} complete (100%)`);
2209
- console.log(` ${theme.dim('Build:')} ${theme.success(`${buildLabel} build passed`)}`);
2389
+ if (pipelineDone && progressAnalysis.totalMilestones === 0) {
2390
+ console.log(` ${theme.dim('Pipeline:')} ${theme.success('completed')}`);
2391
+ } else {
2392
+ console.log(` ${theme.dim('Milestones:')} ${progressAnalysis.totalMilestones}/${progressAnalysis.totalMilestones} complete`);
2393
+ console.log(` ${theme.dim('Tasks:')} ${progressAnalysis.totalTasks}/${progressAnalysis.totalTasks} complete (100%)`);
2394
+ console.log(` ${theme.dim('Build:')} ${theme.success(`${buildLabel} build passed`)}`);
2395
+ }
2210
2396
  console.log(` ${theme.dim('Location:')} ${state.projectDir}`);
2397
+ printNextSteps(status.state.language, state.projectDir);
2211
2398
  return;
2212
2399
  }
2213
2400
 
@@ -2220,9 +2407,51 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2220
2407
 
2221
2408
  // Check if user provided context as argument
2222
2409
  let additionalContext = args.join(' ').trim();
2410
+ const isStuck = pp === 'STUCK';
2223
2411
 
2224
2412
  // If no context provided, ask if they want to add guidance
2225
- if (!additionalContext) {
2413
+ // v2.5.3: STUCK pipelines get context-aware prompt with blocker details and phase-specific hints
2414
+ if (!additionalContext && isStuck) {
2415
+ if (failingGates.length > 0) {
2416
+ console.log();
2417
+ console.log(theme.warning.bold(' Guidance should address:'));
2418
+ for (const [phase, gr] of failingGates) {
2419
+ for (const b of gr.blockers.slice(0, 2)) {
2420
+ console.log(` ${theme.dim('-')} [${phase}] ${b}`);
2421
+ }
2422
+ }
2423
+ }
2424
+
2425
+ // Phase-specific actionable hints
2426
+ let promptHint = 'e.g., "Focus on fixing the specific blocker above"';
2427
+ if (blockingAt.startsWith('CONSENSUS_')) {
2428
+ promptHint = 'e.g., "Reduce scope to core features only", "Split into smaller milestones", "Lower the consensus threshold"';
2429
+ } else if (blockingAt === 'QA_VALIDATION') {
2430
+ promptHint = 'e.g., "Fix failing test X", "Skip flaky tests", "Adjust build config"';
2431
+ } else if (blockingAt === 'PRODUCTION_GATE') {
2432
+ promptHint = 'e.g., "Mark finding as non-blocking", "Implement quick fix for issue X"';
2433
+ }
2434
+
2435
+ console.log();
2436
+ // Default YES — STUCK pipelines strongly need guidance
2437
+ const wantsContext = failingGates.length > 0
2438
+ ? await promptYesNo(theme.primary('Would you like to provide guidance to unblock the pipeline?'), true)
2439
+ : await promptYesNo(theme.primary('Would you like to add guidance before resuming?'), false);
2440
+
2441
+ if (wantsContext) {
2442
+ additionalContext = await promptForContext(
2443
+ `What guidance would you like to give? (${promptHint})`
2444
+ );
2445
+ } else {
2446
+ // v2.6.0: Don't return early — let the orchestrator attempt auto-recovery
2447
+ // via the arbitrator before giving up. If auto-recovery fails,
2448
+ // resumePipeline() returns STUCK and the normal error path handles it.
2449
+ console.log();
2450
+ console.log(theme.secondary(' No guidance provided. Attempting auto-recovery...'));
2451
+ // additionalContext stays empty — resumePipeline will attempt auto-recovery
2452
+ }
2453
+ } else if (!additionalContext) {
2454
+ // Non-STUCK: existing generic guidance prompt
2226
2455
  console.log();
2227
2456
  const wantsContext = await promptYesNo(
2228
2457
  theme.primary('Would you like to add guidance before resuming?'),
@@ -2301,6 +2530,8 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2301
2530
  } else if (testSt === 'no-tests') {
2302
2531
  console.log(` ${theme.dim('Tests:')} ${theme.dim('No tests found')}`);
2303
2532
  }
2533
+
2534
+ printNextSteps(status.state.language, state.projectDir);
2304
2535
  } else if (result.rateLimitPaused) {
2305
2536
  // Rate limit pause - show friendly message, not an error
2306
2537
  console.log();
@@ -2320,8 +2551,27 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2320
2551
  console.log(` ${theme.dim('Build:')} ${theme.error(`${failBuildLabel} build failed`)}`);
2321
2552
  }
2322
2553
 
2554
+ // v2.5.3: For STUCK failures, surface gate blockers so user knows what to fix.
2555
+ // Try refreshed state first (from result), fall back to pre-resume state.
2556
+ const stuckPipeline = result.state?.pipeline ?? status.state.pipeline;
2557
+ if (stuckPipeline?.pipelinePhase === 'STUCK') {
2558
+ const stuckFailingGates = Object.entries(stuckPipeline.gateResults)
2559
+ .filter(([, gr]) => !gr.pass)
2560
+ .filter(([, gr]) => gr.blockers.length > 0 || gr.missingArtifacts.length > 0 || gr.failedChecks.length > 0);
2561
+ if (stuckFailingGates.length > 0) {
2562
+ console.log();
2563
+ console.log(theme.warning(' Blockers:'));
2564
+ for (const [phase, gr] of stuckFailingGates) {
2565
+ const firstIssue = gr.blockers[0] ?? `Missing: ${gr.missingArtifacts[0] ?? gr.failedChecks[0] ?? 'unknown'}`;
2566
+ console.log(` ${theme.dim('-')} [${phase}] ${firstIssue}`);
2567
+ }
2568
+ }
2569
+ }
2570
+
2323
2571
  printInfo('You can run /resume again with additional guidance');
2324
2572
  }
2573
+ // v2.5.4: Recreate readline after workflow run (stdin corruption fix)
2574
+ state.refreshReadline?.();
2325
2575
  return;
2326
2576
  }
2327
2577
 
@@ -2484,10 +2734,16 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2484
2734
 
2485
2735
  printSuccess('Workflow completed!');
2486
2736
  console.log(` ${theme.dim('Location:')} ${state.projectDir}`);
2737
+ if (state.projectDir) {
2738
+ printNextSteps(spec.language, state.projectDir);
2739
+ }
2487
2740
  } else {
2488
2741
  printError(result.error || 'Workflow failed');
2489
2742
  printInfo('You can run /resume again with additional guidance');
2490
2743
  }
2744
+
2745
+ // v2.5.4: Recreate readline after workflow run (stdin corruption fix)
2746
+ state.refreshReadline?.();
2491
2747
  }
2492
2748
 
2493
2749
  /**
@@ -2877,7 +3133,12 @@ async function handleIdea(idea: string, state: SessionState): Promise<void> {
2877
3133
  state.projectDir = projectDir;
2878
3134
  } else {
2879
3135
  printError(workflowResult.error || 'Workflow failed');
3136
+ state.projectDir = projectDir; // v2.5.4: Project exists, track it for /resume
3137
+ printInfo('Run /resume with guidance to continue.');
2880
3138
  }
3139
+
3140
+ // v2.5.4: Recreate readline after workflow run (stdin corruption fix)
3141
+ state.refreshReadline?.();
2881
3142
  }
2882
3143
 
2883
3144
  /**
@@ -2993,7 +3254,12 @@ async function handleNewProject(idea: string, state: SessionState): Promise<void
2993
3254
  state.projectDir = projectDir;
2994
3255
  } else {
2995
3256
  printError(workflowResult.error || 'Workflow failed');
3257
+ state.projectDir = projectDir; // v2.5.4: Project exists, track it for /resume
3258
+ printInfo('Run /resume with guidance to continue.');
2996
3259
  }
3260
+
3261
+ // v2.5.4: Recreate readline after workflow run (stdin corruption fix)
3262
+ state.refreshReadline?.();
2997
3263
  }
2998
3264
 
2999
3265
  /**
@@ -3048,28 +3314,76 @@ export async function startInteractiveMode(): Promise<void> {
3048
3314
 
3049
3315
  console.log();
3050
3316
 
3051
- // Create readline interface
3052
- const rl = readline.createInterface({
3053
- input: process.stdin,
3054
- output: process.stdout,
3055
- });
3317
+ // v2.5.4: Mutable readline — recreated after workflow runs to recover from
3318
+ // stdin corruption caused by the Claude SDK's query() function.
3319
+ // See src/cli/commands/debug.ts:378-379 for documentation of the issue.
3320
+ let intentionalExit = false;
3321
+ let isPrompting = false;
3322
+
3323
+ function createReadlineInterface(): readline.Interface {
3324
+ const iface = readline.createInterface({
3325
+ input: process.stdin,
3326
+ output: process.stdout,
3327
+ });
3328
+ // Last-resort safety net: if stdin closes unexpectedly, exit cleanly or recreate
3329
+ iface.on('close', () => {
3330
+ if (intentionalExit) return;
3331
+ // Only attempt to recreate if stdin is still usable (TTY and not destroyed)
3332
+ if (process.stdin.isTTY && !process.stdin.destroyed && !isPrompting) {
3333
+ isPrompting = false;
3334
+ rl = createReadlineInterface();
3335
+ console.log();
3336
+ printWarning('Input stream interrupted. Restoring prompt...');
3337
+ promptUser();
3338
+ } else {
3339
+ // stdin is gone (piped input finished, non-TTY, etc.) — exit cleanly
3340
+ console.log();
3341
+ printInfo('Input stream closed (stdin EOF). Re-run in an interactive terminal if this was unexpected.');
3342
+ process.exit(1);
3343
+ }
3344
+ });
3345
+ return iface;
3346
+ }
3347
+
3348
+ function refreshReadline(): void {
3349
+ try { rl.close(); } catch { /* already closed */ }
3350
+ rl = createReadlineInterface();
3351
+ }
3352
+
3353
+ let rl = createReadlineInterface();
3354
+
3355
+ // Expose refreshReadline on state so handleIdea/handleResume/handleNewProject can call it
3356
+ state.refreshReadline = refreshReadline;
3056
3357
 
3057
3358
  // Input loop
3058
3359
  const promptUser = (): void => {
3360
+ if (isPrompting) return; // re-entrancy guard
3361
+ isPrompting = true;
3059
3362
  drawInputBoxTop(state);
3060
3363
 
3061
3364
  rl.question(getPrompt(), async (input) => {
3062
3365
  // Draw bottom of input box after user presses enter
3063
3366
  drawInputBoxBottom();
3064
3367
 
3065
- const shouldContinue = await handleInput(input, state);
3368
+ try {
3369
+ const shouldContinue = await handleInput(input, state);
3066
3370
 
3067
- if (shouldContinue) {
3371
+ if (shouldContinue) {
3372
+ isPrompting = false;
3373
+ console.log();
3374
+ promptUser();
3375
+ } else {
3376
+ intentionalExit = true;
3377
+ rl.close();
3378
+ process.exit(0);
3379
+ }
3380
+ } catch (err) {
3381
+ isPrompting = false;
3382
+ printError(`Unexpected error: ${err instanceof Error ? err.message : String(err)}`);
3383
+ printInfo('Returning to prompt. Your progress has been saved.');
3068
3384
  console.log();
3385
+ refreshReadline();
3069
3386
  promptUser();
3070
- } else {
3071
- rl.close();
3072
- process.exit(0);
3073
3387
  }
3074
3388
  });
3075
3389
  };