popeye-cli 2.1.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 (356) 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 +328 -21
  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 +25 -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/artifact-manager.d.ts.map +1 -1
  71. package/dist/pipeline/artifact-manager.js +3 -0
  72. package/dist/pipeline/artifact-manager.js.map +1 -1
  73. package/dist/pipeline/auto-recovery.d.ts +56 -0
  74. package/dist/pipeline/auto-recovery.d.ts.map +1 -0
  75. package/dist/pipeline/auto-recovery.js +185 -0
  76. package/dist/pipeline/auto-recovery.js.map +1 -0
  77. package/dist/pipeline/change-request.d.ts +39 -0
  78. package/dist/pipeline/change-request.d.ts.map +1 -1
  79. package/dist/pipeline/change-request.js +40 -1
  80. package/dist/pipeline/change-request.js.map +1 -1
  81. package/dist/pipeline/check-runner.d.ts +30 -1
  82. package/dist/pipeline/check-runner.d.ts.map +1 -1
  83. package/dist/pipeline/check-runner.js +122 -1
  84. package/dist/pipeline/check-runner.js.map +1 -1
  85. package/dist/pipeline/command-resolver.d.ts.map +1 -1
  86. package/dist/pipeline/command-resolver.js +33 -2
  87. package/dist/pipeline/command-resolver.js.map +1 -1
  88. package/dist/pipeline/consensus/arbitrator-query.d.ts +22 -0
  89. package/dist/pipeline/consensus/arbitrator-query.d.ts.map +1 -0
  90. package/dist/pipeline/consensus/arbitrator-query.js +70 -0
  91. package/dist/pipeline/consensus/arbitrator-query.js.map +1 -0
  92. package/dist/pipeline/consensus/consensus-runner.d.ts +131 -7
  93. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -1
  94. package/dist/pipeline/consensus/consensus-runner.js +809 -35
  95. package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
  96. package/dist/pipeline/cr-lifecycle.d.ts +42 -0
  97. package/dist/pipeline/cr-lifecycle.d.ts.map +1 -0
  98. package/dist/pipeline/cr-lifecycle.js +89 -0
  99. package/dist/pipeline/cr-lifecycle.js.map +1 -0
  100. package/dist/pipeline/gate-engine.d.ts +1 -0
  101. package/dist/pipeline/gate-engine.d.ts.map +1 -1
  102. package/dist/pipeline/gate-engine.js +27 -8
  103. package/dist/pipeline/gate-engine.js.map +1 -1
  104. package/dist/pipeline/migration.d.ts.map +1 -1
  105. package/dist/pipeline/migration.js +3 -26
  106. package/dist/pipeline/migration.js.map +1 -1
  107. package/dist/pipeline/orchestrator.d.ts +1 -1
  108. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  109. package/dist/pipeline/orchestrator.js +311 -16
  110. package/dist/pipeline/orchestrator.js.map +1 -1
  111. package/dist/pipeline/packets/consensus-packet-builder.d.ts +15 -4
  112. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -1
  113. package/dist/pipeline/packets/consensus-packet-builder.js +29 -17
  114. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -1
  115. package/dist/pipeline/phases/architecture.d.ts.map +1 -1
  116. package/dist/pipeline/phases/architecture.js +5 -3
  117. package/dist/pipeline/phases/architecture.js.map +1 -1
  118. package/dist/pipeline/phases/audit.d.ts.map +1 -1
  119. package/dist/pipeline/phases/audit.js +5 -3
  120. package/dist/pipeline/phases/audit.js.map +1 -1
  121. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -1
  122. package/dist/pipeline/phases/consensus-architecture.js +10 -1
  123. package/dist/pipeline/phases/consensus-architecture.js.map +1 -1
  124. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -1
  125. package/dist/pipeline/phases/consensus-master-plan.js +10 -3
  126. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -1
  127. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -1
  128. package/dist/pipeline/phases/consensus-role-plans.js +10 -1
  129. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -1
  130. package/dist/pipeline/phases/done.d.ts.map +1 -1
  131. package/dist/pipeline/phases/done.js +9 -4
  132. package/dist/pipeline/phases/done.js.map +1 -1
  133. package/dist/pipeline/phases/intake.d.ts +1 -0
  134. package/dist/pipeline/phases/intake.d.ts.map +1 -1
  135. package/dist/pipeline/phases/intake.js +56 -13
  136. package/dist/pipeline/phases/intake.js.map +1 -1
  137. package/dist/pipeline/phases/phase-context.d.ts +2 -0
  138. package/dist/pipeline/phases/phase-context.d.ts.map +1 -1
  139. package/dist/pipeline/phases/phase-context.js +3 -1
  140. package/dist/pipeline/phases/phase-context.js.map +1 -1
  141. package/dist/pipeline/phases/production-gate.d.ts.map +1 -1
  142. package/dist/pipeline/phases/production-gate.js +28 -3
  143. package/dist/pipeline/phases/production-gate.js.map +1 -1
  144. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -1
  145. package/dist/pipeline/phases/qa-validation.js +38 -5
  146. package/dist/pipeline/phases/qa-validation.js.map +1 -1
  147. package/dist/pipeline/phases/recovery-loop.d.ts +2 -0
  148. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
  149. package/dist/pipeline/phases/recovery-loop.js +200 -6
  150. package/dist/pipeline/phases/recovery-loop.js.map +1 -1
  151. package/dist/pipeline/phases/review.d.ts.map +1 -1
  152. package/dist/pipeline/phases/review.js +58 -28
  153. package/dist/pipeline/phases/review.js.map +1 -1
  154. package/dist/pipeline/phases/role-planning.d.ts.map +1 -1
  155. package/dist/pipeline/phases/role-planning.js +20 -5
  156. package/dist/pipeline/phases/role-planning.js.map +1 -1
  157. package/dist/pipeline/phases/stuck.d.ts.map +1 -1
  158. package/dist/pipeline/phases/stuck.js +10 -0
  159. package/dist/pipeline/phases/stuck.js.map +1 -1
  160. package/dist/pipeline/repo-snapshot.d.ts.map +1 -1
  161. package/dist/pipeline/repo-snapshot.js +3 -0
  162. package/dist/pipeline/repo-snapshot.js.map +1 -1
  163. package/dist/pipeline/role-execution-adapter.d.ts +2 -1
  164. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -1
  165. package/dist/pipeline/role-execution-adapter.js +22 -7
  166. package/dist/pipeline/role-execution-adapter.js.map +1 -1
  167. package/dist/pipeline/skill-loader.d.ts +19 -0
  168. package/dist/pipeline/skill-loader.d.ts.map +1 -1
  169. package/dist/pipeline/skill-loader.js +22 -0
  170. package/dist/pipeline/skill-loader.js.map +1 -1
  171. package/dist/pipeline/skills/constitution-generator.d.ts +51 -0
  172. package/dist/pipeline/skills/constitution-generator.d.ts.map +1 -0
  173. package/dist/pipeline/skills/constitution-generator.js +210 -0
  174. package/dist/pipeline/skills/constitution-generator.js.map +1 -0
  175. package/dist/pipeline/skills/coverage-gate.d.ts +44 -0
  176. package/dist/pipeline/skills/coverage-gate.d.ts.map +1 -0
  177. package/dist/pipeline/skills/coverage-gate.js +143 -0
  178. package/dist/pipeline/skills/coverage-gate.js.map +1 -0
  179. package/dist/pipeline/skills/generator.d.ts +65 -0
  180. package/dist/pipeline/skills/generator.d.ts.map +1 -0
  181. package/dist/pipeline/skills/generator.js +221 -0
  182. package/dist/pipeline/skills/generator.js.map +1 -0
  183. package/dist/pipeline/skills/role-map.d.ts +38 -0
  184. package/dist/pipeline/skills/role-map.d.ts.map +1 -0
  185. package/dist/pipeline/skills/role-map.js +234 -0
  186. package/dist/pipeline/skills/role-map.js.map +1 -0
  187. package/dist/pipeline/skills/types.d.ts +47 -0
  188. package/dist/pipeline/skills/types.d.ts.map +1 -0
  189. package/dist/pipeline/skills/types.js +5 -0
  190. package/dist/pipeline/skills/types.js.map +1 -0
  191. package/dist/pipeline/skills/usage-registry.d.ts +48 -0
  192. package/dist/pipeline/skills/usage-registry.d.ts.map +1 -0
  193. package/dist/pipeline/skills/usage-registry.js +55 -0
  194. package/dist/pipeline/skills/usage-registry.js.map +1 -0
  195. package/dist/pipeline/strategy-context.d.ts +20 -0
  196. package/dist/pipeline/strategy-context.d.ts.map +1 -0
  197. package/dist/pipeline/strategy-context.js +55 -0
  198. package/dist/pipeline/strategy-context.js.map +1 -0
  199. package/dist/pipeline/type-defs/artifacts.d.ts +30 -5
  200. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
  201. package/dist/pipeline/type-defs/artifacts.js +5 -0
  202. package/dist/pipeline/type-defs/artifacts.js.map +1 -1
  203. package/dist/pipeline/type-defs/audit.d.ts +28 -13
  204. package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
  205. package/dist/pipeline/type-defs/checks.d.ts +19 -8
  206. package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
  207. package/dist/pipeline/type-defs/checks.js +4 -0
  208. package/dist/pipeline/type-defs/checks.js.map +1 -1
  209. package/dist/pipeline/type-defs/packets.d.ts +119 -18
  210. package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
  211. package/dist/pipeline/type-defs/packets.js +17 -1
  212. package/dist/pipeline/type-defs/packets.js.map +1 -1
  213. package/dist/pipeline/type-defs/state.d.ts +165 -16
  214. package/dist/pipeline/type-defs/state.d.ts.map +1 -1
  215. package/dist/pipeline/type-defs/state.js +26 -1
  216. package/dist/pipeline/type-defs/state.js.map +1 -1
  217. package/dist/shared/text-utils.d.ts +23 -0
  218. package/dist/shared/text-utils.d.ts.map +1 -0
  219. package/dist/shared/text-utils.js +66 -0
  220. package/dist/shared/text-utils.js.map +1 -0
  221. package/dist/shared/website-strategy-format.d.ts +18 -0
  222. package/dist/shared/website-strategy-format.d.ts.map +1 -0
  223. package/dist/shared/website-strategy-format.js +47 -0
  224. package/dist/shared/website-strategy-format.js.map +1 -0
  225. package/dist/state/index.d.ts +2 -0
  226. package/dist/state/index.d.ts.map +1 -1
  227. package/dist/state/index.js +57 -8
  228. package/dist/state/index.js.map +1 -1
  229. package/dist/types/consensus.d.ts +1 -0
  230. package/dist/types/consensus.d.ts.map +1 -1
  231. package/dist/types/consensus.js.map +1 -1
  232. package/dist/types/website-strategy.d.ts +1 -1
  233. package/dist/types/workflow.d.ts +447 -0
  234. package/dist/types/workflow.d.ts.map +1 -1
  235. package/dist/types/workflow.js +3 -0
  236. package/dist/types/workflow.js.map +1 -1
  237. package/dist/upgrade/handlers.d.ts.map +1 -1
  238. package/dist/upgrade/handlers.js +6 -3
  239. package/dist/upgrade/handlers.js.map +1 -1
  240. package/dist/workflow/consensus.d.ts.map +1 -1
  241. package/dist/workflow/consensus.js +1 -0
  242. package/dist/workflow/consensus.js.map +1 -1
  243. package/dist/workflow/website-strategy.d.ts.map +1 -1
  244. package/dist/workflow/website-strategy.js +2 -29
  245. package/dist/workflow/website-strategy.js.map +1 -1
  246. package/dist/workflow/website-updater.d.ts.map +1 -1
  247. package/dist/workflow/website-updater.js +3 -2
  248. package/dist/workflow/website-updater.js.map +1 -1
  249. package/package.json +1 -1
  250. package/src/adapters/gemini.ts +51 -6
  251. package/src/adapters/grok.ts +51 -6
  252. package/src/adapters/openai.ts +53 -5
  253. package/src/cli/commands/create.ts +1 -1
  254. package/src/cli/interactive.ts +337 -20
  255. package/src/generators/all.ts +25 -2
  256. package/src/generators/doc-parser.ts +75 -15
  257. package/src/generators/templates/fullstack.ts +1 -1
  258. package/src/generators/templates/website-components.ts +1 -1
  259. package/src/generators/templates/website-config.ts +23 -11
  260. package/src/generators/templates/website-conversion.ts +1 -1
  261. package/src/generators/templates/website-landing.ts +1 -1
  262. package/src/generators/templates/website-layout.ts +491 -23
  263. package/src/generators/templates/website-pricing.ts +1 -1
  264. package/src/generators/templates/website-sections.ts +1 -1
  265. package/src/generators/templates/website-seo.ts +4 -1
  266. package/src/generators/templates/website.ts +3 -0
  267. package/src/generators/website-content-ai.ts +186 -0
  268. package/src/generators/website-content-scanner.ts +113 -1
  269. package/src/generators/website-context.ts +151 -12
  270. package/src/generators/website-debug.ts +26 -0
  271. package/src/generators/website.ts +28 -3
  272. package/src/pipeline/artifact-manager.ts +3 -0
  273. package/src/pipeline/auto-recovery.ts +283 -0
  274. package/src/pipeline/change-request.ts +63 -1
  275. package/src/pipeline/check-runner.ts +141 -2
  276. package/src/pipeline/command-resolver.ts +34 -2
  277. package/src/pipeline/consensus/arbitrator-query.ts +101 -0
  278. package/src/pipeline/consensus/consensus-runner.ts +1099 -42
  279. package/src/pipeline/cr-lifecycle.ts +103 -0
  280. package/src/pipeline/gate-engine.ts +36 -8
  281. package/src/pipeline/migration.ts +5 -30
  282. package/src/pipeline/orchestrator.ts +367 -16
  283. package/src/pipeline/packets/consensus-packet-builder.ts +44 -18
  284. package/src/pipeline/phases/architecture.ts +6 -3
  285. package/src/pipeline/phases/audit.ts +6 -3
  286. package/src/pipeline/phases/consensus-architecture.ts +10 -1
  287. package/src/pipeline/phases/consensus-master-plan.ts +10 -3
  288. package/src/pipeline/phases/consensus-role-plans.ts +10 -1
  289. package/src/pipeline/phases/done.ts +15 -4
  290. package/src/pipeline/phases/intake.ts +67 -14
  291. package/src/pipeline/phases/phase-context.ts +6 -1
  292. package/src/pipeline/phases/production-gate.ts +41 -3
  293. package/src/pipeline/phases/qa-validation.ts +51 -5
  294. package/src/pipeline/phases/recovery-loop.ts +229 -7
  295. package/src/pipeline/phases/review.ts +73 -30
  296. package/src/pipeline/phases/role-planning.ts +23 -5
  297. package/src/pipeline/phases/stuck.ts +10 -0
  298. package/src/pipeline/repo-snapshot.ts +3 -0
  299. package/src/pipeline/role-execution-adapter.ts +30 -4
  300. package/src/pipeline/skill-loader.ts +33 -0
  301. package/src/pipeline/skills/constitution-generator.ts +236 -0
  302. package/src/pipeline/skills/coverage-gate.ts +199 -0
  303. package/src/pipeline/skills/generator.ts +287 -0
  304. package/src/pipeline/skills/role-map.ts +248 -0
  305. package/src/pipeline/skills/types.ts +53 -0
  306. package/src/pipeline/skills/usage-registry.ts +87 -0
  307. package/src/pipeline/strategy-context.ts +60 -0
  308. package/src/pipeline/type-defs/artifacts.ts +5 -0
  309. package/src/pipeline/type-defs/checks.ts +4 -0
  310. package/src/pipeline/type-defs/packets.ts +18 -1
  311. package/src/pipeline/type-defs/state.ts +26 -1
  312. package/src/shared/text-utils.ts +70 -0
  313. package/src/shared/website-strategy-format.ts +56 -0
  314. package/src/state/index.ts +60 -8
  315. package/src/types/consensus.ts +1 -0
  316. package/src/types/workflow.ts +6 -0
  317. package/src/upgrade/handlers.ts +9 -3
  318. package/src/workflow/consensus.ts +1 -0
  319. package/src/workflow/website-strategy.ts +2 -36
  320. package/src/workflow/website-updater.ts +4 -2
  321. package/tests/adapters/gemini.test.ts +165 -0
  322. package/tests/adapters/grok.test.ts +137 -0
  323. package/tests/adapters/openai.test.ts +128 -0
  324. package/tests/generators/doc-parser.test.ts +88 -9
  325. package/tests/generators/quality-gate.test.ts +19 -3
  326. package/tests/generators/website-components.test.ts +34 -0
  327. package/tests/generators/website-content-ai.test.ts +308 -0
  328. package/tests/generators/website-content-scanner.test.ts +86 -0
  329. package/tests/generators/website-context.test.ts +3 -2
  330. package/tests/integration/smokestack-scaffold.test.ts +385 -0
  331. package/tests/pipeline/auto-recovery.test.ts +337 -0
  332. package/tests/pipeline/change-request.test.ts +70 -0
  333. package/tests/pipeline/command-resolver.test.ts +42 -0
  334. package/tests/pipeline/consensus/arbitrator-query.test.ts +107 -0
  335. package/tests/pipeline/consensus-runner.test.ts +1333 -10
  336. package/tests/pipeline/consensus-scoring.test.ts +602 -18
  337. package/tests/pipeline/gate-engine.test.ts +34 -0
  338. package/tests/pipeline/install-check.test.ts +261 -0
  339. package/tests/pipeline/migration.test.ts +4 -3
  340. package/tests/pipeline/orchestrator.test.ts +1506 -15
  341. package/tests/pipeline/packets/builders.test.ts +29 -6
  342. package/tests/pipeline/phases/role-planning.strategy.test.ts +204 -0
  343. package/tests/pipeline/pipeline-persistence.test.ts +230 -0
  344. package/tests/pipeline/recovery-loop-guidance.test.ts +280 -0
  345. package/tests/pipeline/role-execution-adapter.test.ts +88 -0
  346. package/tests/pipeline/skills/constitution-generator.test.ts +201 -0
  347. package/tests/pipeline/skills/coverage-gate.test.ts +370 -0
  348. package/tests/pipeline/skills/generator.test.ts +213 -0
  349. package/tests/pipeline/skills/role-map.test.ts +198 -0
  350. package/tests/pipeline/skills/usage-registry.test.ts +114 -0
  351. package/tests/pipeline/strategy-context.test.ts +148 -0
  352. package/tests/shared/text-utils.test.ts +155 -0
  353. package/tests/state/progress-analysis.test.ts +375 -0
  354. package/tests/upgrade/handlers.test.ts +33 -2
  355. package/tests/workflow/consensus.test.ts +6 -0
  356. package/tsconfig.json +1 -1
@@ -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
  */
@@ -1958,7 +2036,10 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
1958
2036
  }
1959
2037
 
1960
2038
  // Only discover projects if no active project with pending work
1961
- if (!state.projectDir || (await getWorkflowStatus(state.projectDir)).state?.phase === 'complete') {
2039
+ // Reason: Also discover when state.projectDir is CWD but has no project state (project may be in subdirectory)
2040
+ const currentStatus = state.projectDir ? await getWorkflowStatus(state.projectDir) : null;
2041
+ const needsDiscovery = !state.projectDir || !currentStatus?.exists || currentStatus?.state?.phase === 'complete';
2042
+ if (needsDiscovery) {
1962
2043
  // Discover all projects (registered + scanned in current directory)
1963
2044
  console.log();
1964
2045
  printInfo('Scanning for projects...');
@@ -2067,12 +2148,37 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2067
2148
  const progressAnalysis = await analyzeProjectProgress(state.projectDir);
2068
2149
  const verification = await verifyProjectCompletion(state.projectDir);
2069
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
+
2070
2169
  console.log();
2071
2170
  console.log(theme.primary.bold(' Project Status:'));
2072
2171
  console.log(` ${theme.dim('Name:')} ${status.state.name}`);
2073
2172
  console.log(` ${theme.dim('Language:')} ${theme.primary(status.state.language)}`);
2074
- console.log(` ${theme.dim('Phase:')} ${theme.primary(status.state.phase)}`);
2075
- 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
+ }
2076
2182
 
2077
2183
  // Show detailed progress comparison
2078
2184
  console.log();
@@ -2137,8 +2243,85 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2137
2243
  console.log(theme.dim(` Plan file: ${progressAnalysis.planParseError}`));
2138
2244
  }
2139
2245
 
2140
- // Check for status mismatch (status says complete but state tasks are incomplete)
2141
- 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)
2142
2325
  console.log();
2143
2326
  console.log(theme.warning.bold(' WARNING: Status Mismatch Detected!'));
2144
2327
  console.log(theme.warning(` Project status says '${status.state.status}' but work is incomplete.`));
@@ -2196,15 +2379,22 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2196
2379
  // successful build verification (sets status='complete', phase='complete').
2197
2380
  // If all tasks are done but status is still 'in-progress', the final
2198
2381
  // verification phase (build, tests, README) never completed successfully.
2199
- if (verification.isComplete && projectExplicitlyCompleted) {
2382
+ const pipelineDone = status.state.pipeline?.pipelinePhase === 'DONE';
2383
+ if ((verification.isComplete && projectExplicitlyCompleted) ||
2384
+ (pipelineDone && progressAnalysis.totalMilestones === 0)) {
2200
2385
  console.log();
2201
2386
  printSuccess('Project is fully complete!');
2202
2387
  console.log();
2203
2388
  console.log(theme.primary.bold(' Project Summary:'));
2204
- console.log(` ${theme.dim('Milestones:')} ${progressAnalysis.totalMilestones}/${progressAnalysis.totalMilestones} complete`);
2205
- console.log(` ${theme.dim('Tasks:')} ${progressAnalysis.totalTasks}/${progressAnalysis.totalTasks} complete (100%)`);
2206
- 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
+ }
2207
2396
  console.log(` ${theme.dim('Location:')} ${state.projectDir}`);
2397
+ printNextSteps(status.state.language, state.projectDir);
2208
2398
  return;
2209
2399
  }
2210
2400
 
@@ -2217,9 +2407,51 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2217
2407
 
2218
2408
  // Check if user provided context as argument
2219
2409
  let additionalContext = args.join(' ').trim();
2410
+ const isStuck = pp === 'STUCK';
2220
2411
 
2221
2412
  // If no context provided, ask if they want to add guidance
2222
- 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
2223
2455
  console.log();
2224
2456
  const wantsContext = await promptYesNo(
2225
2457
  theme.primary('Would you like to add guidance before resuming?'),
@@ -2298,6 +2530,8 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2298
2530
  } else if (testSt === 'no-tests') {
2299
2531
  console.log(` ${theme.dim('Tests:')} ${theme.dim('No tests found')}`);
2300
2532
  }
2533
+
2534
+ printNextSteps(status.state.language, state.projectDir);
2301
2535
  } else if (result.rateLimitPaused) {
2302
2536
  // Rate limit pause - show friendly message, not an error
2303
2537
  console.log();
@@ -2317,8 +2551,27 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2317
2551
  console.log(` ${theme.dim('Build:')} ${theme.error(`${failBuildLabel} build failed`)}`);
2318
2552
  }
2319
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
+
2320
2571
  printInfo('You can run /resume again with additional guidance');
2321
2572
  }
2573
+ // v2.5.4: Recreate readline after workflow run (stdin corruption fix)
2574
+ state.refreshReadline?.();
2322
2575
  return;
2323
2576
  }
2324
2577
 
@@ -2481,10 +2734,16 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
2481
2734
 
2482
2735
  printSuccess('Workflow completed!');
2483
2736
  console.log(` ${theme.dim('Location:')} ${state.projectDir}`);
2737
+ if (state.projectDir) {
2738
+ printNextSteps(spec.language, state.projectDir);
2739
+ }
2484
2740
  } else {
2485
2741
  printError(result.error || 'Workflow failed');
2486
2742
  printInfo('You can run /resume again with additional guidance');
2487
2743
  }
2744
+
2745
+ // v2.5.4: Recreate readline after workflow run (stdin corruption fix)
2746
+ state.refreshReadline?.();
2488
2747
  }
2489
2748
 
2490
2749
  /**
@@ -2874,7 +3133,12 @@ async function handleIdea(idea: string, state: SessionState): Promise<void> {
2874
3133
  state.projectDir = projectDir;
2875
3134
  } else {
2876
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.');
2877
3138
  }
3139
+
3140
+ // v2.5.4: Recreate readline after workflow run (stdin corruption fix)
3141
+ state.refreshReadline?.();
2878
3142
  }
2879
3143
 
2880
3144
  /**
@@ -2990,7 +3254,12 @@ async function handleNewProject(idea: string, state: SessionState): Promise<void
2990
3254
  state.projectDir = projectDir;
2991
3255
  } else {
2992
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.');
2993
3259
  }
3260
+
3261
+ // v2.5.4: Recreate readline after workflow run (stdin corruption fix)
3262
+ state.refreshReadline?.();
2994
3263
  }
2995
3264
 
2996
3265
  /**
@@ -3045,28 +3314,76 @@ export async function startInteractiveMode(): Promise<void> {
3045
3314
 
3046
3315
  console.log();
3047
3316
 
3048
- // Create readline interface
3049
- const rl = readline.createInterface({
3050
- input: process.stdin,
3051
- output: process.stdout,
3052
- });
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;
3053
3357
 
3054
3358
  // Input loop
3055
3359
  const promptUser = (): void => {
3360
+ if (isPrompting) return; // re-entrancy guard
3361
+ isPrompting = true;
3056
3362
  drawInputBoxTop(state);
3057
3363
 
3058
3364
  rl.question(getPrompt(), async (input) => {
3059
3365
  // Draw bottom of input box after user presses enter
3060
3366
  drawInputBoxBottom();
3061
3367
 
3062
- const shouldContinue = await handleInput(input, state);
3368
+ try {
3369
+ const shouldContinue = await handleInput(input, state);
3063
3370
 
3064
- 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.');
3065
3384
  console.log();
3385
+ refreshReadline();
3066
3386
  promptUser();
3067
- } else {
3068
- rl.close();
3069
- process.exit(0);
3070
3387
  }
3071
3388
  });
3072
3389
  };
@@ -10,7 +10,8 @@ import type { GenerationResult } from './python.js';
10
10
  import { generateFullstackProject } from './fullstack.js';
11
11
  import { generateWebsiteProject } from './website.js';
12
12
  import type { WebsiteContentContext } from './website-context.js';
13
- import { buildWebsiteContext, validateWebsiteContext } from './website-context.js';
13
+ import { buildWebsiteContext, validateWebsiteContext, resolveBrandAssets, extractDocPathsFromText } from './website-context.js';
14
+ import { loadWebsiteStrategy } from '../workflow/website-strategy.js';
14
15
  import {
15
16
  generateDesignTokensPackage as generateDesignTokensPackageImpl,
16
17
  generateUiPackage as generateUiPackageImpl,
@@ -392,7 +393,8 @@ export async function generateAllProject(
392
393
  let contextWarning: string | undefined;
393
394
  if (!contentContext) {
394
395
  try {
395
- contentContext = await buildWebsiteContext(projectDir, projectName);
396
+ const extraDocPaths = spec.idea ? extractDocPathsFromText(spec.idea) : [];
397
+ contentContext = await buildWebsiteContext(projectDir, projectName, spec.idea, extraDocPaths);
396
398
  } catch (e) {
397
399
  contextWarning = e instanceof Error ? e.message : 'Unknown error building website context';
398
400
  // Proceed with defaults, but warning is logged below
@@ -403,6 +405,25 @@ export async function generateAllProject(
403
405
  console.warn(`[website-context] Warning: ${contextWarning}`);
404
406
  }
405
407
 
408
+ // Resolve brand assets (logo output path) — must happen after buildWebsiteContext
409
+ if (contentContext?.brand) {
410
+ try {
411
+ contentContext.brandAssets = await resolveBrandAssets(projectDir, contentContext.brand);
412
+ } catch {
413
+ // Non-fatal: brand assets are optional
414
+ }
415
+ }
416
+
417
+ // Load website strategy if previously generated (e.g. re-scaffold)
418
+ try {
419
+ const strategyData = await loadWebsiteStrategy(projectDir);
420
+ if (strategyData && contentContext) {
421
+ contentContext.strategy = strategyData.strategy;
422
+ }
423
+ } catch {
424
+ // Strategy won't exist on first scaffold — generated later by runPlanMode
425
+ }
426
+
406
427
  // Soft validation: log quality issues without blocking monorepo generation
407
428
  if (contentContext) {
408
429
  const validation = validateWebsiteContext(contentContext, projectName);
@@ -419,12 +440,14 @@ export async function generateAllProject(
419
440
  filesCreated.push(...fullstackResult.filesCreated);
420
441
 
421
442
  // Generate website app
443
+ // skipValidation: strategy is generated later by runPlanMode, not at scaffold time
422
444
  const websiteResult = await generateWebsiteProject(spec, projectDir, {
423
445
  baseDir: path.join(projectDir, 'apps', 'website'),
424
446
  workspaceMode: true,
425
447
  skipDocker: true, // Website runs outside Docker (npm run dev / npm start)
426
448
  skipReadme: false,
427
449
  contentContext: contentContext,
450
+ skipValidation: true,
428
451
  });
429
452
  if (!websiteResult.success) {
430
453
  return {
@@ -281,22 +281,33 @@ function extractFeaturesFromSource(
281
281
  return features;
282
282
  }
283
283
 
284
- /**
285
- * Extract pricing tiers from docs
286
- * Parses markdown tables and "Plan Positioning" sections
287
- */
288
- export function extractPricing(
289
- docs: string
290
- ): Array<{
284
+ /** Pricing tier as extracted from docs */
285
+ export interface PricingTier {
291
286
  name: string; price: string; period?: string;
292
287
  description: string; features: string[];
293
288
  cta: string; featured?: boolean;
294
- }> | undefined {
295
- const tiers: Array<{
296
- name: string; price: string; period?: string;
297
- description: string; features: string[];
298
- cta: string; featured?: boolean;
299
- }> = [];
289
+ }
290
+
291
+ /** Result of pricing extraction with provenance tracking */
292
+ export interface PricingExtractionResult {
293
+ tiers: PricingTier[];
294
+ source: 'docs' | 'none';
295
+ evidence?: {
296
+ matchedSection?: string;
297
+ matchedRows: number;
298
+ extractionMethod: 'known_plan_names' | 'table_fallback';
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Extract pricing tiers from docs with provenance tracking
304
+ * Parses markdown tables and "Plan Positioning" sections
305
+ *
306
+ * @param docs - Combined documentation text
307
+ * @returns Extraction result with tiers, source provenance, and evidence
308
+ */
309
+ export function extractPricing(docs: string): PricingExtractionResult {
310
+ const tiers: PricingTier[] = [];
300
311
 
301
312
  // Find the pricing section to avoid matching design token tables
302
313
  // Reason: "Plan-Based Color Usage" matches "Plans?" - require "Pricing" keyword
@@ -307,8 +318,11 @@ export function extractPricing(
307
318
 
308
319
  // Look for pricing overview table rows with plan names and actual prices
309
320
  const priceMap = new Map<string, string>();
321
+ let extractionMethod: 'known_plan_names' | 'table_fallback' | undefined;
322
+
310
323
  const tableRows = searchArea.match(/^\|[^|]*(?:Free|Pro|Enterprise|Starter|Growth|Team|Business)[^|]*\|.+\|$/gm);
311
324
  if (tableRows) {
325
+ extractionMethod = 'known_plan_names';
312
326
  for (const row of tableRows) {
313
327
  const cells = row.split('|').map((c) => c.trim()).filter(Boolean);
314
328
  if (cells.length >= 2) {
@@ -325,7 +339,41 @@ export function extractPricing(
325
339
  }
326
340
  }
327
341
 
328
- if (priceMap.size === 0) return undefined;
342
+ // Fallback: if no known plan names found, scan pricing section for any row with price-like cell
343
+ if (priceMap.size === 0 && pricingSection) {
344
+ const allRows = pricingSection[0].match(/^\|.+\|$/gm);
345
+ if (allRows && allRows.length >= 3) {
346
+ // Detect which column is the price column from the header row
347
+ const headerCells = allRows[0].split('|').map((c) => c.trim()).filter(Boolean);
348
+ let priceColIndex = headerCells.findIndex((h) => /price|cost|monthly|\$/i.test(h));
349
+ if (priceColIndex < 0) priceColIndex = 1; // default: 2nd column
350
+
351
+ for (const row of allRows.slice(2)) {
352
+ const cells = row.split('|').map((c) => c.trim()).filter(Boolean);
353
+ if (cells.length < 2) continue;
354
+ const planName = cells[0].replace(/[🟢🔵🟣⚪🟡🟠🔴]\s*/g, '').replace(/\*\*/g, '').trim();
355
+ // Skip separator rows and header-like rows
356
+ if (/^[-=\s]+$/.test(planName) || /^(Plan|Tier|Name|Feature)/i.test(planName)) continue;
357
+
358
+ // Check price column and adjacent column for price-like content
359
+ for (const idx of [priceColIndex, priceColIndex === 1 ? 2 : 1]) {
360
+ if (idx >= cells.length) continue;
361
+ const price = cells[idx].replace(/<br>/g, ' / ').replace(/\*\*/g, '').trim();
362
+ const looksLikePrice = /^free$/i.test(price) || /^\$/.test(price)
363
+ || /^custom/i.test(price) || /^contact/i.test(price);
364
+ if (looksLikePrice && !priceMap.has(planName)) {
365
+ priceMap.set(planName, price);
366
+ break;
367
+ }
368
+ }
369
+ }
370
+ if (priceMap.size > 0) extractionMethod = 'table_fallback';
371
+ }
372
+ }
373
+
374
+ if (priceMap.size === 0) {
375
+ return { tiers: [], source: 'none' };
376
+ }
329
377
 
330
378
  // Look for plan descriptions
331
379
  const descMap = new Map<string, string>();
@@ -410,7 +458,19 @@ export function extractPricing(
410
458
  tier.features = tier.features.slice(0, 6);
411
459
  }
412
460
 
413
- return tiers.length > 0 ? tiers : undefined;
461
+ if (tiers.length === 0) {
462
+ return { tiers: [], source: 'none' };
463
+ }
464
+
465
+ return {
466
+ tiers,
467
+ source: 'docs',
468
+ evidence: {
469
+ matchedSection: pricingSection ? pricingSection[0].split('\n')[0] : undefined,
470
+ matchedRows: priceMap.size,
471
+ extractionMethod: extractionMethod!,
472
+ },
473
+ };
414
474
  }
415
475
 
416
476
  /**
@@ -765,7 +765,7 @@ export function generateFrontendPackageJson(projectName: string): string {
765
765
  '@vitest/coverage-v8': '^2.0.0',
766
766
  autoprefixer: '^10.4.0',
767
767
  eslint: '^9.0.0',
768
- 'eslint-plugin-react-hooks': '^4.6.0',
768
+ 'eslint-plugin-react-hooks': '^5.0.0',
769
769
  'eslint-plugin-react-refresh': '^0.4.0',
770
770
  postcss: '^8.4.0',
771
771
  tailwindcss: '^3.4.0',