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
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Skill Coverage Gate — deterministic assertion that every active role
3
+ * has recorded usage (or is explicitly exempt).
4
+ *
5
+ * Called at CONSENSUS_ROLE_PLANS and PRODUCTION_GATE gates.
6
+ */
7
+
8
+ import type { PipelineRole, PipelinePhase, PipelineState } from '../types.js';
9
+ import type { SkillUsageEvent } from './usage-registry.js';
10
+
11
+ // ─── Phase Order (must match orchestrator/gate phase sequence) ────
12
+
13
+ /** Canonical phase order — used for phase-aware deferral in coverage checks. */
14
+ export const PHASE_ORDER: PipelinePhase[] = [
15
+ 'INTAKE', 'CONSENSUS_MASTER_PLAN', 'ARCHITECTURE', 'CONSENSUS_ARCHITECTURE',
16
+ 'ROLE_PLANNING', 'CONSENSUS_ROLE_PLANS', 'IMPLEMENTATION', 'QA_VALIDATION',
17
+ 'REVIEW', 'AUDIT', 'PRODUCTION_GATE', 'RECOVERY_LOOP', 'DONE', 'STUCK',
18
+ ];
19
+
20
+ // ─── Required Usage Configuration ────────────────────────
21
+
22
+ export interface RoleUsageRequirement {
23
+ phases: PipelinePhase[];
24
+ minEvents: number;
25
+ conditional?: 'always' | 'if_recovery' | 'if_arbitrated' | 'if_journal_triggered';
26
+ }
27
+
28
+ export const ROLE_REQUIRED_USAGE: Record<PipelineRole, RoleUsageRequirement> = {
29
+ DISPATCHER: { phases: [], minEvents: 0 },
30
+ ARCHITECT: { phases: ['ARCHITECTURE'], minEvents: 1 },
31
+ DB_EXPERT: { phases: ['ROLE_PLANNING'], minEvents: 1 },
32
+ BACKEND_PROGRAMMER: { phases: ['ROLE_PLANNING'], minEvents: 1 },
33
+ FRONTEND_PROGRAMMER: { phases: ['ROLE_PLANNING'], minEvents: 1 },
34
+ WEBSITE_PROGRAMMER: { phases: ['ROLE_PLANNING'], minEvents: 1 },
35
+ UI_UX_SPECIALIST: { phases: ['ROLE_PLANNING'], minEvents: 1 },
36
+ MARKETING_EXPERT: { phases: ['ROLE_PLANNING'], minEvents: 1 },
37
+ SOCIAL_EXPERT: { phases: ['ROLE_PLANNING'], minEvents: 1 },
38
+ QA_TESTER: { phases: ['ROLE_PLANNING', 'QA_VALIDATION'], minEvents: 1 },
39
+ REVIEWER: { phases: ['REVIEW'], minEvents: 1 },
40
+ ARBITRATOR: { phases: [], minEvents: 0, conditional: 'if_arbitrated' },
41
+ DEBUGGER: { phases: ['RECOVERY_LOOP'], minEvents: 0, conditional: 'if_recovery' },
42
+ AUDITOR: { phases: ['AUDIT'], minEvents: 1 },
43
+ JOURNALIST: {
44
+ phases: [
45
+ 'CONSENSUS_MASTER_PLAN', 'CONSENSUS_ARCHITECTURE', 'CONSENSUS_ROLE_PLANS',
46
+ 'AUDIT', 'PRODUCTION_GATE', 'RECOVERY_LOOP', 'DONE',
47
+ ],
48
+ minEvents: 1,
49
+ conditional: 'if_journal_triggered',
50
+ },
51
+ RELEASE_MANAGER: { phases: ['DONE'], minEvents: 1 },
52
+ };
53
+
54
+ // ─── Coverage Result ─────────────────────────────────────
55
+
56
+ export interface CoverageMissing {
57
+ role: PipelineRole;
58
+ expectedPhases: PipelinePhase[];
59
+ reason: string;
60
+ }
61
+
62
+ export interface CoverageResult {
63
+ pass: boolean;
64
+ missing: CoverageMissing[];
65
+ covered: PipelineRole[];
66
+ /** Roles skipped because their required phases haven't been reached yet (v2.4.5) */
67
+ deferred: PipelineRole[];
68
+ }
69
+
70
+ // ─── Assertion Logic ─────────────────────────────────────
71
+
72
+ /**
73
+ * Assert that all active roles have recorded skill usage
74
+ * according to their requirements.
75
+ *
76
+ * Args:
77
+ * activeRoles: Roles currently active in the pipeline.
78
+ * events: All recorded skill usage events.
79
+ * pipeline: Full pipeline state for conditional checks.
80
+ * currentPhase: Current pipeline phase for phase-aware deferral (v2.4.5).
81
+ * Omit for strict mode (checks all roles regardless of phase).
82
+ *
83
+ * Returns:
84
+ * CoverageResult with pass/fail and details.
85
+ */
86
+ export function assertSkillCoverage(
87
+ activeRoles: PipelineRole[],
88
+ events: SkillUsageEvent[],
89
+ pipeline: PipelineState,
90
+ currentPhase?: PipelinePhase,
91
+ ): CoverageResult {
92
+ const missing: CoverageMissing[] = [];
93
+ const covered: PipelineRole[] = [];
94
+ const deferred: PipelineRole[] = [];
95
+
96
+ // Resolve phase index; -1 means unknown/omitted -> strict mode (check all)
97
+ const currentIdx = currentPhase ? PHASE_ORDER.indexOf(currentPhase) : -1;
98
+
99
+ for (const role of activeRoles) {
100
+ const requirement = ROLE_REQUIRED_USAGE[role];
101
+ if (!requirement) {
102
+ // Reason: Unknown role — skip rather than crash
103
+ continue;
104
+ }
105
+
106
+ // Meta-only roles (DISPATCHER) with minEvents 0 and no conditional
107
+ if (requirement.minEvents === 0 && !requirement.conditional) {
108
+ covered.push(role);
109
+ continue;
110
+ }
111
+
112
+ // Conditional roles — check if their condition is met
113
+ if (requirement.conditional) {
114
+ const isRequired = isConditionalRequired(requirement.conditional, pipeline);
115
+ if (!isRequired) {
116
+ // Condition not met — not required, count as covered
117
+ covered.push(role);
118
+ continue;
119
+ }
120
+ }
121
+
122
+ // v2.4.5: Phase-aware deferral — skip roles whose phases are all after currentPhase.
123
+ // Only applies when currentIdx >= 0 (known phase). Unknown phase = strict mode.
124
+ if (currentIdx >= 0 && requirement.phases.length > 0) {
125
+ const anyPhaseReached = requirement.phases.some(
126
+ (p) => PHASE_ORDER.indexOf(p) <= currentIdx,
127
+ );
128
+ if (!anyPhaseReached) {
129
+ // Reason: Role's required phases are all after currentPhase — defer check
130
+ deferred.push(role);
131
+ continue;
132
+ }
133
+ }
134
+
135
+ // Check if role has at least minEvents usage events
136
+ const roleEvents = events.filter((e) => e.role === role);
137
+ const effectiveMin = Math.max(requirement.minEvents, 1);
138
+
139
+ if (roleEvents.length >= effectiveMin) {
140
+ covered.push(role);
141
+ } else {
142
+ missing.push({
143
+ role,
144
+ expectedPhases: requirement.phases,
145
+ reason: roleEvents.length === 0
146
+ ? `No skill usage recorded for ${role}`
147
+ : `Only ${roleEvents.length}/${effectiveMin} usage events for ${role}`,
148
+ });
149
+ }
150
+ }
151
+
152
+ return {
153
+ pass: missing.length === 0,
154
+ missing,
155
+ covered,
156
+ deferred,
157
+ };
158
+ }
159
+
160
+ // ─── Conditional Helpers ─────────────────────────────────
161
+
162
+ function isConditionalRequired(
163
+ conditional: NonNullable<RoleUsageRequirement['conditional']>,
164
+ pipeline: PipelineState,
165
+ ): boolean {
166
+ switch (conditional) {
167
+ case 'always':
168
+ return true;
169
+
170
+ case 'if_recovery':
171
+ return pipeline.recoveryCount > 0;
172
+
173
+ case 'if_arbitrated':
174
+ return hasArbitratedConsensus(pipeline);
175
+
176
+ case 'if_journal_triggered':
177
+ return hasJournalTriggered(pipeline);
178
+
179
+ default:
180
+ return false;
181
+ }
182
+ }
183
+
184
+ /** Check if any arbitration occurred during consensus phases. */
185
+ function hasArbitratedConsensus(pipeline: PipelineState): boolean {
186
+ // Reason: Arbitration artifacts are created when consensus requires arbitrator intervention
187
+ const arbitrationArtifacts = pipeline.artifacts.filter((a) => a.type === 'arbitration');
188
+ return arbitrationArtifacts.length > 0;
189
+ }
190
+
191
+ /** Check if any journalist-triggering phases have completed. */
192
+ function hasJournalTriggered(pipeline: PipelineState): boolean {
193
+ const journalPhases: PipelinePhase[] = [
194
+ 'CONSENSUS_MASTER_PLAN', 'CONSENSUS_ARCHITECTURE', 'CONSENSUS_ROLE_PLANS',
195
+ 'AUDIT', 'PRODUCTION_GATE', 'RECOVERY_LOOP', 'DONE',
196
+ ];
197
+ const completedPhases = new Set(Object.keys(pipeline.gateResults));
198
+ return journalPhases.some((phase) => completedPhases.has(phase));
199
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Skill Usage Registry — in-memory helper wrapping the persistent
3
+ * SkillUsageEvent[] array from PipelineState.
4
+ *
5
+ * Records when a skill is actually injected into an LLM prompt
6
+ * or execution context (not on mere load).
7
+ */
8
+
9
+ import type { PipelineRole, PipelinePhase } from '../types.js';
10
+
11
+ // ─── Types ───────────────────────────────────────────────
12
+
13
+ export type SkillUsedAs =
14
+ | 'system_prompt'
15
+ | 'review_prompt'
16
+ | 'arbitration_prompt'
17
+ | 'role_context'
18
+ | 'planning_prompt'
19
+ | 'strategy_context'
20
+ | 'other';
21
+
22
+ export type SkillSource = 'project_override' | 'defaults' | 'disk';
23
+
24
+ export interface SkillUsageEvent {
25
+ role: PipelineRole;
26
+ phase: PipelinePhase;
27
+ used_as: SkillUsedAs;
28
+ skill_source: SkillSource;
29
+ skill_version?: string;
30
+ timestamp: string;
31
+ }
32
+
33
+ // ─── Registry ────────────────────────────────────────────
34
+
35
+ /**
36
+ * Wraps the persistent events array from PipelineState.
37
+ * Pushes directly into the shared array reference so state
38
+ * serialization captures all recorded events.
39
+ */
40
+ export class SkillUsageRegistry {
41
+ constructor(private readonly events: SkillUsageEvent[]) {}
42
+
43
+ /**
44
+ * Record a usage event.
45
+ *
46
+ * Call ONLY when skill is injected into an LLM prompt
47
+ * or execution context, not on mere load.
48
+ *
49
+ * Args:
50
+ * role: The pipeline role whose skill was used.
51
+ * phase: The phase during which usage occurred.
52
+ * usedAs: How the skill was consumed.
53
+ * skillSource: Whether from project override or defaults.
54
+ * version: Optional skill version string.
55
+ */
56
+ record(
57
+ role: PipelineRole,
58
+ phase: PipelinePhase,
59
+ usedAs: SkillUsedAs,
60
+ skillSource: SkillSource,
61
+ version?: string,
62
+ ): void {
63
+ this.events.push({
64
+ role,
65
+ phase,
66
+ used_as: usedAs,
67
+ skill_source: skillSource,
68
+ skill_version: version,
69
+ timestamp: new Date().toISOString(),
70
+ });
71
+ }
72
+
73
+ /** Get a copy of all recorded events. */
74
+ getEvents(): SkillUsageEvent[] {
75
+ return [...this.events];
76
+ }
77
+
78
+ /** Get events for a specific role. */
79
+ getEventsForRole(role: PipelineRole): SkillUsageEvent[] {
80
+ return this.events.filter((e) => e.role === role);
81
+ }
82
+
83
+ /** Check if any usage has been recorded for a role. */
84
+ hasUsage(role: PipelineRole): boolean {
85
+ return this.events.some((e) => e.role === role);
86
+ }
87
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Strategy context loader for pipeline roles.
3
+ * Loads website strategy from disk and formats it for injection
4
+ * into role planning and execution prompts.
5
+ */
6
+
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+
10
+ import type { PipelineRole } from './types.js';
11
+ import { WebsiteStrategySchema } from '../types/website-strategy.js';
12
+ import { formatWebsiteStrategy } from '../shared/website-strategy-format.js';
13
+
14
+ /** Roles that should receive website strategy context */
15
+ export const STRATEGY_ROLES: readonly PipelineRole[] = [
16
+ 'WEBSITE_PROGRAMMER',
17
+ 'MARKETING_EXPERT',
18
+ 'SOCIAL_EXPERT',
19
+ ];
20
+
21
+ /** Known strategy file locations, checked in order */
22
+ const STRATEGY_PATHS = [
23
+ '.popeye/website-strategy.json',
24
+ '.popeye/website-strategy.md',
25
+ ] as const;
26
+
27
+ /**
28
+ * Load website strategy from disk and format for prompt injection.
29
+ * Checks known paths in order. Returns undefined if no valid strategy found.
30
+ *
31
+ * Args:
32
+ * projectDir: Root project directory containing .popeye/.
33
+ *
34
+ * Returns:
35
+ * string | undefined: Formatted strategy text, or undefined if not found/invalid.
36
+ */
37
+ export function loadStrategyForRole(projectDir: string): string | undefined {
38
+ for (const relPath of STRATEGY_PATHS) {
39
+ const fullPath = join(projectDir, relPath);
40
+ if (!existsSync(fullPath)) continue;
41
+
42
+ try {
43
+ const raw = readFileSync(fullPath, 'utf-8');
44
+
45
+ // .md files: return raw content directly
46
+ if (relPath.endsWith('.md')) return raw;
47
+
48
+ // .json files: parse, validate, format
49
+ const parsed = JSON.parse(raw);
50
+ const strategyData = parsed.strategy ?? parsed;
51
+ const result = WebsiteStrategySchema.safeParse(strategyData);
52
+ if (!result.success) continue;
53
+
54
+ return formatWebsiteStrategy(result.data);
55
+ } catch {
56
+ continue; // Malformed file, try next path
57
+ }
58
+ }
59
+ return undefined;
60
+ }
@@ -32,8 +32,12 @@ export const ArtifactTypeSchema = z.enum([
32
32
  'resolved_commands',
33
33
  'constitution',
34
34
  'change_request',
35
+ 'recovery_fix_plan',
36
+ 'auto_recovery_guidance',
35
37
  'additional_context',
36
38
  'skill_generation_log',
39
+ 'skill_usage_log',
40
+ 'install_check',
37
41
  ]);
38
42
  export type ArtifactType = z.infer<typeof ArtifactTypeSchema>;
39
43
 
@@ -16,6 +16,8 @@ export const GateCheckTypeSchema = z.enum([
16
16
  'placeholder_scan',
17
17
  'start',
18
18
  'env_check',
19
+ 'skill_coverage',
20
+ 'install',
19
21
  ]);
20
22
  export type GateCheckType = z.infer<typeof GateCheckTypeSchema>;
21
23
 
@@ -42,6 +44,8 @@ export const ResolvedCommandsSchema = z.object({
42
44
  typecheck: z.string().optional(),
43
45
  migrations: z.string().optional(),
44
46
  start: z.string().optional(),
47
+ install: z.string().optional(),
48
+ install_cwd: z.string().optional(),
45
49
  resolved_from: z.string(),
46
50
  });
47
51
  export type ResolvedCommands = z.infer<typeof ResolvedCommandsSchema>;
@@ -51,8 +51,10 @@ export const ReviewerVoteSchema = z.object({
51
51
  vote: z.enum(['APPROVE', 'REJECT', 'CONDITIONAL']),
52
52
  confidence: z.number().min(0).max(1),
53
53
  blocking_issues: z.array(z.string()),
54
+ required_changes: z.array(z.string()).optional(),
54
55
  suggestions: z.array(z.string()),
55
56
  evidence_refs: z.array(ArtifactRefSchema),
57
+ reviewer_inconsistency: z.boolean().optional(),
56
58
  });
57
59
  export type ReviewerVote = z.infer<typeof ReviewerVoteSchema>;
58
60
 
@@ -69,6 +71,7 @@ export const ConsensusResultSchema = z.object({
69
71
  score: z.number().min(0).max(1),
70
72
  weighted_score: z.number().min(0).max(1),
71
73
  participating_reviewers: z.number().int(),
74
+ has_true_blockers: z.boolean().default(false),
72
75
  });
73
76
 
74
77
  export const ArbitratorResultSchema = z.object({
@@ -77,6 +80,13 @@ export const ArbitratorResultSchema = z.object({
77
80
  artifact_ref: ArtifactRefSchema.optional(),
78
81
  });
79
82
 
83
+ export const NormalizationMovesSchema = z.object({
84
+ tagged_blockers_demoted_to_suggestions: z.number(),
85
+ tagged_blockers_demoted_to_required: z.number(),
86
+ untagged_from_blocking_routed_to_required: z.number(),
87
+ forced_rejects: z.number(),
88
+ }).optional();
89
+
80
90
  export const ConsensusPacketSchema = z.object({
81
91
  metadata: z.object({
82
92
  packet_id: z.string(),
@@ -89,6 +99,7 @@ export const ConsensusPacketSchema = z.object({
89
99
  consensus_result: ConsensusResultSchema,
90
100
  arbitrator_result: ArbitratorResultSchema.optional(),
91
101
  final_status: z.enum(['APPROVED', 'REJECTED', 'ARBITRATED']),
102
+ normalization_moves: NormalizationMovesSchema,
92
103
  });
93
104
  export type ConsensusPacket = z.infer<typeof ConsensusPacketSchema>;
94
105
 
@@ -125,7 +136,13 @@ export const ChangeRequestSchema = z.object({
125
136
  affected_phases: z.array(PipelinePhaseSchema),
126
137
  risk_level: z.enum(['low', 'medium', 'high']),
127
138
  }),
128
- status: z.enum(['proposed', 'approved', 'rejected']),
139
+ status: z.enum(['proposed', 'approved', 'rejected', 'resolved']),
129
140
  approval_artifact: ArtifactRefSchema.optional(),
141
+ /** Deterministic drift fingerprint for CR deduplication (v2.4.9) */
142
+ drift_key: z.string().optional(),
143
+ /** ISO timestamp when the CR was resolved (v2.4.9) */
144
+ resolved_at: z.string().optional(),
145
+ /** How the CR was resolved (v2.4.9) */
146
+ resolution: z.enum(['accepted_baseline', 'reverted', 'manual_override']).optional(),
130
147
  });
131
148
  export type ChangeRequest = z.infer<typeof ChangeRequestSchema>;
@@ -9,6 +9,7 @@ import { GateCheckTypeSchema, GateCheckResultSchema, ResolvedCommandsSchema, typ
9
9
  import type { ArtifactType } from './artifacts.js';
10
10
  import type { ConsensusPacket } from './packets.js';
11
11
  import type { RCAPacket } from './packets.js';
12
+ // Reason: SkillUsageEvent type is defined inline in the Zod schema to keep state serialization self-contained
12
13
 
13
14
  // ─── Gate Definition ─────────────────────────────────────
14
15
 
@@ -88,6 +89,7 @@ export const PipelineStateSchema = z.object({
88
89
  missingArtifacts: z.array(ArtifactTypeSchema),
89
90
  failedChecks: z.array(GateCheckTypeSchema),
90
91
  consensusScore: z.number().optional(),
92
+ finalStatus: z.string().optional(), // v2.4.3: 'APPROVED' | 'REJECTED' | 'ARBITRATED'
91
93
  timestamp: z.string(),
92
94
  })),
93
95
  gateChecks: z.record(z.string(), z.array(GateCheckResultSchema)),
@@ -97,6 +99,12 @@ export const PipelineStateSchema = z.object({
97
99
  resolvedCommands: ResolvedCommandsSchema.optional(),
98
100
  /** Tracks which phase failed, for recovery routing */
99
101
  failedPhase: PipelinePhaseSchema.optional(),
102
+ /** Last rewind target from recovery — detects repeated same-target rewinds (v2.4.6) */
103
+ lastRewindTarget: PipelinePhaseSchema.optional(),
104
+ /** v2.6.0: Auto-recovery result — tracks whether arbitrator guidance was attempted before STUCK */
105
+ autoRecoveryResult: z.enum(['success', 'timeout', 'invalid', 'error']).optional(),
106
+ /** v2.7.0: Baseline failure count before recovery — for regression detection */
107
+ recoveryBaselineFailedCheckCount: z.number().int().min(0).optional(),
100
108
  /** Session guidance: user steering, upgrade context, or resume instructions */
101
109
  sessionGuidance: z.string().optional(),
102
110
  /** Pending change requests that force re-routing to consensus phases (v1.1) */
@@ -104,7 +112,23 @@ export const PipelineStateSchema = z.object({
104
112
  cr_id: z.string(),
105
113
  change_type: z.enum(['scope', 'architecture', 'dependency', 'config', 'requirement']),
106
114
  target_phase: PipelinePhaseSchema,
107
- status: z.enum(['proposed', 'approved', 'rejected']),
115
+ status: z.enum(['proposed', 'approved', 'rejected', 'resolved']),
116
+ drift_key: z.string().optional(),
117
+ })).optional(),
118
+ /** ID of the CR currently being processed by the routed phase (v2.4.9) */
119
+ activeChangeRequestId: z.string().optional(),
120
+ /** Snapshot override: after config CR resolved, REVIEW uses this instead of CONSENSUS_ROLE_PLANS baseline (v2.4.9) */
121
+ baselineSnapshotOverride: ArtifactRefSchema.optional(),
122
+ /** Rolling loop signatures for stagnation detection (v2.4.9) */
123
+ lastSignatures: z.array(z.string()).optional(),
124
+ /** Skill usage events for coverage enforcement (v2.2.1) */
125
+ skillUsageEvents: z.array(z.object({
126
+ role: PipelineRoleSchema,
127
+ phase: PipelinePhaseSchema,
128
+ used_as: z.enum(['system_prompt', 'review_prompt', 'arbitration_prompt', 'role_context', 'planning_prompt', 'other']),
129
+ skill_source: z.enum(['project_override', 'defaults']),
130
+ skill_version: z.string().optional(),
131
+ timestamp: z.string(),
108
132
  })).optional(),
109
133
  });
110
134
  export type PipelineState = z.infer<typeof PipelineStateSchema>;
@@ -163,5 +187,6 @@ export function createDefaultPipelineState(): PipelineState {
163
187
  gateChecks: {},
164
188
  activeRoles: [],
165
189
  constitutionHash: '',
190
+ skillUsageEvents: [],
166
191
  };
167
192
  }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Text utilities for normalizing LLM-generated issue lists.
3
+ * Detects false-positive "no issues" responses that LLMs produce in
4
+ * BLOCKING_ISSUES fields (e.g. "No blocking issues found", "N/A", "None identified").
5
+ */
6
+
7
+ /** Exact-match tokens that mean "nothing" after trim+lowercase */
8
+ const NONE_EXACT = new Set(['none', 'n/a', 'na', 'nil', 'nothing']);
9
+
10
+ /**
11
+ * Anchored phrase patterns that indicate "no issues".
12
+ * Tested against the cleaned text (bullet-stripped, trimmed, lowercased,
13
+ * trailing punctuation removed).
14
+ */
15
+ const NONE_PHRASES: RegExp[] = [
16
+ /^no\s+(?:(?:blocking|critical|significant|major)\s+)*(?:issues?|concerns?|problems?|blockers?|showstoppers?)\b/,
17
+ /^none\s+(?:identified|found|detected|noted|observed|reported|applicable|at this time)\b/,
18
+ /^there\s+are\s+no\s+(?:(?:significant|major|critical|blocking)\s+)*(?:issues|concerns|problems|blockers)\b/,
19
+ ];
20
+
21
+ /**
22
+ * Determine whether a text string is a "none-variant" — an LLM's way of
23
+ * saying "no blocking issues" rather than an actual issue description.
24
+ *
25
+ * @param text - Raw text from a blocking_issues list item
26
+ * @returns true if the text is a none-variant (should be filtered out)
27
+ */
28
+ export function isNoneVariant(text: string): boolean {
29
+ // Empty / whitespace-only
30
+ const trimmed = text.trim();
31
+ if (trimmed.length === 0) return true;
32
+
33
+ // Strip leading bullet prefixes: "- ", "* ", "+ ", "1) ", "1. ", etc.
34
+ const stripped = trimmed.replace(/^[-*+\d.)\s]+/, '').trim();
35
+ if (stripped.length === 0) return true;
36
+
37
+ // Strip trailing punctuation
38
+ const cleaned = stripped.replace(/[.,;!]+$/, '').trim();
39
+ if (cleaned.length === 0) return true;
40
+
41
+ const lower = cleaned.toLowerCase();
42
+
43
+ // Exact match check
44
+ if (NONE_EXACT.has(lower)) return true;
45
+
46
+ // Anchored phrase check
47
+ for (const pattern of NONE_PHRASES) {
48
+ if (pattern.test(lower)) return true;
49
+ }
50
+
51
+ // Length guard: >80 chars and no phrase match means it is likely a real issue
52
+ if (cleaned.length > 80) return false;
53
+
54
+ return false;
55
+ }
56
+
57
+ /**
58
+ * Normalize an issue list by filtering out empty strings and none-variants.
59
+ * Intended as a drop-in replacement for the weak `.filter(i => i.toLowerCase() !== 'none')`
60
+ * used across adapters.
61
+ *
62
+ * @param items - Raw list items from parseList()
63
+ * @returns Filtered list with only genuine issues
64
+ */
65
+ export function normalizeIssueList(items: string[]): string[] {
66
+ return items
67
+ .map((i) => i.trim())
68
+ .filter((i) => i.length > 0)
69
+ .filter((i) => !isNoneVariant(i));
70
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Shared website strategy formatter.
3
+ * Formats a WebsiteStrategyDocument as structured text for prompt injection.
4
+ * Used by both workflow (plan-mode consensus) and pipeline (role planning/execution).
5
+ */
6
+
7
+ import type { WebsiteStrategyDocument } from '../types/website-strategy.js';
8
+
9
+ /**
10
+ * Format a WebsiteStrategyDocument as structured text for prompt injection.
11
+ *
12
+ * Args:
13
+ * strategy: The validated strategy document to format.
14
+ *
15
+ * Returns:
16
+ * string: Multi-line formatted text with ICP, Positioning, Messaging,
17
+ * SEO Keywords, Site Architecture, and Conversion Strategy sections.
18
+ */
19
+ export function formatWebsiteStrategy(strategy: WebsiteStrategyDocument): string {
20
+ const lines: string[] = [];
21
+
22
+ lines.push(`### Target Customer`);
23
+ lines.push(`- Persona: ${strategy.icp.primaryPersona}`);
24
+ lines.push(`- Pain points: ${strategy.icp.painPoints.join(', ')}`);
25
+ lines.push('');
26
+
27
+ lines.push(`### Positioning`);
28
+ lines.push(`- Category: ${strategy.positioning.category}`);
29
+ lines.push(`- Value proposition: ${strategy.positioning.valueProposition}`);
30
+ lines.push(`- Differentiators: ${strategy.positioning.differentiators.join(', ')}`);
31
+ lines.push('');
32
+
33
+ lines.push(`### Messaging`);
34
+ lines.push(`- Headline: ${strategy.messaging.headline}`);
35
+ lines.push(`- Subheadline: ${strategy.messaging.subheadline}`);
36
+ lines.push('');
37
+
38
+ lines.push(`### SEO Keywords`);
39
+ lines.push(`- Primary: ${strategy.seoStrategy.primaryKeywords.join(', ')}`);
40
+ lines.push(`- Secondary: ${strategy.seoStrategy.secondaryKeywords.join(', ')}`);
41
+ lines.push('');
42
+
43
+ lines.push(`### Site Architecture`);
44
+ for (const page of strategy.siteArchitecture.pages) {
45
+ lines.push(`- ${page.path} (${page.pageType}): ${page.purpose}`);
46
+ }
47
+ lines.push('');
48
+
49
+ lines.push(`### Conversion Strategy`);
50
+ lines.push(`- Primary CTA: "${strategy.conversionStrategy.primaryCta.text}" -> ${strategy.conversionStrategy.primaryCta.href}`);
51
+ lines.push(`- Secondary CTA: "${strategy.conversionStrategy.secondaryCta.text}" -> ${strategy.conversionStrategy.secondaryCta.href}`);
52
+ lines.push(`- Trust signals: ${strategy.conversionStrategy.trustSignals.join(', ')}`);
53
+ lines.push(`- Lead capture: ${strategy.conversionStrategy.leadCapture}`);
54
+
55
+ return lines.join('\n');
56
+ }