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
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Skill Coverage Gate tests — assertSkillCoverage with conditional roles.
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import {
7
+ assertSkillCoverage,
8
+ ROLE_REQUIRED_USAGE,
9
+ PHASE_ORDER,
10
+ } from '../../../src/pipeline/skills/coverage-gate.js';
11
+ import type { SkillUsageEvent } from '../../../src/pipeline/skills/usage-registry.js';
12
+ import type { PipelineState, PipelineRole, PipelinePhase } from '../../../src/pipeline/types.js';
13
+ import { createDefaultPipelineState } from '../../../src/pipeline/types.js';
14
+
15
+ /** Helper to create a usage event */
16
+ function makeEvent(
17
+ role: PipelineRole,
18
+ phase: string = 'ROLE_PLANNING',
19
+ ): SkillUsageEvent {
20
+ return {
21
+ role,
22
+ phase: phase as SkillUsageEvent['phase'],
23
+ used_as: 'system_prompt',
24
+ skill_source: 'defaults',
25
+ timestamp: new Date().toISOString(),
26
+ };
27
+ }
28
+
29
+ /** Helper to create pipeline state with overrides */
30
+ function makePipeline(overrides: Partial<PipelineState> = {}): PipelineState {
31
+ return {
32
+ ...createDefaultPipelineState(),
33
+ ...overrides,
34
+ };
35
+ }
36
+
37
+ describe('assertSkillCoverage', () => {
38
+ describe('expected use — all active roles have usage', () => {
39
+ it('should pass when all active roles have recorded usage', () => {
40
+ const activeRoles: PipelineRole[] = [
41
+ 'DISPATCHER', 'ARCHITECT', 'DB_EXPERT', 'BACKEND_PROGRAMMER',
42
+ ];
43
+ const events: SkillUsageEvent[] = [
44
+ makeEvent('ARCHITECT', 'ARCHITECTURE'),
45
+ makeEvent('DB_EXPERT', 'ROLE_PLANNING'),
46
+ makeEvent('BACKEND_PROGRAMMER', 'ROLE_PLANNING'),
47
+ ];
48
+ const pipeline = makePipeline({ activeRoles });
49
+
50
+ const result = assertSkillCoverage(activeRoles, events, pipeline);
51
+
52
+ expect(result.pass).toBe(true);
53
+ expect(result.missing).toHaveLength(0);
54
+ expect(result.covered).toContain('DISPATCHER');
55
+ expect(result.covered).toContain('ARCHITECT');
56
+ });
57
+ });
58
+
59
+ describe('fail case — active role missing usage', () => {
60
+ it('should fail when active UI_UX_SPECIALIST has no usage events', () => {
61
+ const activeRoles: PipelineRole[] = [
62
+ 'DISPATCHER', 'ARCHITECT', 'UI_UX_SPECIALIST',
63
+ ];
64
+ const events: SkillUsageEvent[] = [
65
+ makeEvent('ARCHITECT', 'ARCHITECTURE'),
66
+ // UI_UX_SPECIALIST has no events
67
+ ];
68
+ const pipeline = makePipeline({ activeRoles });
69
+
70
+ const result = assertSkillCoverage(activeRoles, events, pipeline);
71
+
72
+ expect(result.pass).toBe(false);
73
+ expect(result.missing).toHaveLength(1);
74
+ expect(result.missing[0].role).toBe('UI_UX_SPECIALIST');
75
+ expect(result.missing[0].reason).toContain('No skill usage recorded');
76
+ });
77
+ });
78
+
79
+ describe('DISPATCHER — meta-only role always passes', () => {
80
+ it('should mark DISPATCHER as covered without any events', () => {
81
+ const result = assertSkillCoverage(
82
+ ['DISPATCHER'],
83
+ [],
84
+ makePipeline({ activeRoles: ['DISPATCHER'] }),
85
+ );
86
+
87
+ expect(result.pass).toBe(true);
88
+ expect(result.covered).toContain('DISPATCHER');
89
+ });
90
+ });
91
+
92
+ describe('DEBUGGER — conditional on recovery', () => {
93
+ it('should pass when recoveryCount=0 (not required)', () => {
94
+ const result = assertSkillCoverage(
95
+ ['DISPATCHER', 'DEBUGGER'],
96
+ [],
97
+ makePipeline({
98
+ activeRoles: ['DISPATCHER', 'DEBUGGER'],
99
+ recoveryCount: 0,
100
+ }),
101
+ );
102
+
103
+ expect(result.pass).toBe(true);
104
+ expect(result.covered).toContain('DEBUGGER');
105
+ });
106
+
107
+ it('should fail when recoveryCount>0 and no usage events', () => {
108
+ const result = assertSkillCoverage(
109
+ ['DISPATCHER', 'DEBUGGER'],
110
+ [],
111
+ makePipeline({
112
+ activeRoles: ['DISPATCHER', 'DEBUGGER'],
113
+ recoveryCount: 1,
114
+ }),
115
+ );
116
+
117
+ expect(result.pass).toBe(false);
118
+ expect(result.missing).toHaveLength(1);
119
+ expect(result.missing[0].role).toBe('DEBUGGER');
120
+ });
121
+
122
+ it('should pass when recoveryCount>0 and usage recorded', () => {
123
+ const result = assertSkillCoverage(
124
+ ['DISPATCHER', 'DEBUGGER'],
125
+ [makeEvent('DEBUGGER', 'RECOVERY_LOOP')],
126
+ makePipeline({
127
+ activeRoles: ['DISPATCHER', 'DEBUGGER'],
128
+ recoveryCount: 2,
129
+ }),
130
+ );
131
+
132
+ expect(result.pass).toBe(true);
133
+ expect(result.covered).toContain('DEBUGGER');
134
+ });
135
+ });
136
+
137
+ describe('ARBITRATOR — conditional on arbitration', () => {
138
+ it('should pass when no arbitration artifacts exist', () => {
139
+ const result = assertSkillCoverage(
140
+ ['DISPATCHER', 'ARBITRATOR'],
141
+ [],
142
+ makePipeline({
143
+ activeRoles: ['DISPATCHER', 'ARBITRATOR'],
144
+ artifacts: [],
145
+ }),
146
+ );
147
+
148
+ expect(result.pass).toBe(true);
149
+ expect(result.covered).toContain('ARBITRATOR');
150
+ });
151
+
152
+ it('should fail when arbitration artifacts exist but no usage', () => {
153
+ const result = assertSkillCoverage(
154
+ ['DISPATCHER', 'ARBITRATOR'],
155
+ [],
156
+ makePipeline({
157
+ activeRoles: ['DISPATCHER', 'ARBITRATOR'],
158
+ artifacts: [{
159
+ id: 'arb-1',
160
+ type: 'arbitration',
161
+ phase: 'CONSENSUS_MASTER_PLAN',
162
+ version: 1,
163
+ path: 'docs/arbitration.json',
164
+ sha256: 'abc',
165
+ timestamp: new Date().toISOString(),
166
+ immutable: true,
167
+ content_type: 'json',
168
+ group_id: 'g1',
169
+ }],
170
+ }),
171
+ );
172
+
173
+ expect(result.pass).toBe(false);
174
+ expect(result.missing[0].role).toBe('ARBITRATOR');
175
+ });
176
+ });
177
+
178
+ describe('JOURNALIST — conditional on journal triggers', () => {
179
+ it('should pass when no journal-triggering phases completed', () => {
180
+ const result = assertSkillCoverage(
181
+ ['DISPATCHER', 'JOURNALIST'],
182
+ [],
183
+ makePipeline({
184
+ activeRoles: ['DISPATCHER', 'JOURNALIST'],
185
+ gateResults: {},
186
+ }),
187
+ );
188
+
189
+ expect(result.pass).toBe(true);
190
+ });
191
+
192
+ it('should fail when journal phase completed but no usage', () => {
193
+ const result = assertSkillCoverage(
194
+ ['DISPATCHER', 'JOURNALIST'],
195
+ [],
196
+ makePipeline({
197
+ activeRoles: ['DISPATCHER', 'JOURNALIST'],
198
+ gateResults: {
199
+ CONSENSUS_MASTER_PLAN: {
200
+ phase: 'CONSENSUS_MASTER_PLAN',
201
+ pass: true,
202
+ blockers: [],
203
+ missingArtifacts: [],
204
+ failedChecks: [],
205
+ timestamp: new Date().toISOString(),
206
+ },
207
+ },
208
+ }),
209
+ );
210
+
211
+ expect(result.pass).toBe(false);
212
+ expect(result.missing[0].role).toBe('JOURNALIST');
213
+ });
214
+ });
215
+
216
+ describe('ROLE_REQUIRED_USAGE configuration', () => {
217
+ it('should have entries for all 16 pipeline roles', () => {
218
+ const expectedRoles: PipelineRole[] = [
219
+ 'DISPATCHER', 'ARCHITECT', 'DB_EXPERT', 'BACKEND_PROGRAMMER',
220
+ 'FRONTEND_PROGRAMMER', 'WEBSITE_PROGRAMMER', 'UI_UX_SPECIALIST',
221
+ 'MARKETING_EXPERT', 'SOCIAL_EXPERT', 'QA_TESTER', 'REVIEWER',
222
+ 'ARBITRATOR', 'DEBUGGER', 'AUDITOR', 'JOURNALIST', 'RELEASE_MANAGER',
223
+ ];
224
+
225
+ for (const role of expectedRoles) {
226
+ expect(ROLE_REQUIRED_USAGE[role]).toBeDefined();
227
+ }
228
+ });
229
+ });
230
+
231
+ // ─── v2.4.5: Phase-aware deferral ────────────────────────
232
+
233
+ describe('phase-aware deferral (v2.4.5)', () => {
234
+ it('should defer AUDITOR at CONSENSUS_ROLE_PLANS', () => {
235
+ // AUDITOR requires AUDIT phase, which comes after CONSENSUS_ROLE_PLANS
236
+ const activeRoles: PipelineRole[] = ['DISPATCHER', 'AUDITOR'];
237
+ const pipeline = makePipeline({ activeRoles });
238
+
239
+ const result = assertSkillCoverage(activeRoles, [], pipeline, 'CONSENSUS_ROLE_PLANS');
240
+
241
+ expect(result.pass).toBe(true);
242
+ expect(result.deferred).toContain('AUDITOR');
243
+ expect(result.missing).toHaveLength(0);
244
+ });
245
+
246
+ it('should check AUDITOR at PRODUCTION_GATE (AUDIT <= PRODUCTION_GATE)', () => {
247
+ // AUDIT comes before PRODUCTION_GATE in PHASE_ORDER, so AUDITOR is checked
248
+ const activeRoles: PipelineRole[] = ['DISPATCHER', 'AUDITOR'];
249
+ const pipeline = makePipeline({ activeRoles });
250
+
251
+ const result = assertSkillCoverage(activeRoles, [], pipeline, 'PRODUCTION_GATE');
252
+
253
+ expect(result.pass).toBe(false);
254
+ expect(result.missing).toHaveLength(1);
255
+ expect(result.missing[0].role).toBe('AUDITOR');
256
+ expect(result.deferred).not.toContain('AUDITOR');
257
+ });
258
+
259
+ it('should pass AUDITOR at PRODUCTION_GATE with events', () => {
260
+ const activeRoles: PipelineRole[] = ['DISPATCHER', 'AUDITOR'];
261
+ const events = [makeEvent('AUDITOR', 'AUDIT')];
262
+ const pipeline = makePipeline({ activeRoles });
263
+
264
+ const result = assertSkillCoverage(activeRoles, events, pipeline, 'PRODUCTION_GATE');
265
+
266
+ expect(result.pass).toBe(true);
267
+ expect(result.covered).toContain('AUDITOR');
268
+ expect(result.deferred).toHaveLength(0);
269
+ });
270
+
271
+ it('should defer RELEASE_MANAGER at PRODUCTION_GATE (DONE > PRODUCTION_GATE)', () => {
272
+ const activeRoles: PipelineRole[] = ['DISPATCHER', 'RELEASE_MANAGER'];
273
+ const pipeline = makePipeline({ activeRoles });
274
+
275
+ const result = assertSkillCoverage(activeRoles, [], pipeline, 'PRODUCTION_GATE');
276
+
277
+ expect(result.pass).toBe(true);
278
+ expect(result.deferred).toContain('RELEASE_MANAGER');
279
+ });
280
+
281
+ it('should check QA_TESTER at CONSENSUS_ROLE_PLANS (ROLE_PLANNING <= CONSENSUS_ROLE_PLANS)', () => {
282
+ // QA_TESTER has phases [ROLE_PLANNING, QA_VALIDATION].
283
+ // ROLE_PLANNING comes before CONSENSUS_ROLE_PLANS, so QA_TESTER is checked.
284
+ const activeRoles: PipelineRole[] = ['DISPATCHER', 'QA_TESTER'];
285
+ const events = [makeEvent('QA_TESTER', 'ROLE_PLANNING')];
286
+ const pipeline = makePipeline({ activeRoles });
287
+
288
+ const result = assertSkillCoverage(activeRoles, events, pipeline, 'CONSENSUS_ROLE_PLANS');
289
+
290
+ expect(result.pass).toBe(true);
291
+ expect(result.covered).toContain('QA_TESTER');
292
+ expect(result.deferred).not.toContain('QA_TESTER');
293
+ });
294
+
295
+ it('should pass full production scenario at CONSENSUS_ROLE_PLANS', () => {
296
+ // Simulate real pipeline: all SUPPORT_ROLES + website impl roles active.
297
+ // Provide events only for roles through ROLE_PLANNING phase.
298
+ const activeRoles: PipelineRole[] = [
299
+ 'DISPATCHER', 'ARCHITECT', 'QA_TESTER', 'REVIEWER', 'ARBITRATOR',
300
+ 'DEBUGGER', 'AUDITOR', 'JOURNALIST', 'RELEASE_MANAGER',
301
+ 'DB_EXPERT', 'BACKEND_PROGRAMMER', 'WEBSITE_PROGRAMMER',
302
+ ];
303
+ const events: SkillUsageEvent[] = [
304
+ makeEvent('ARCHITECT', 'ARCHITECTURE'),
305
+ makeEvent('DB_EXPERT', 'ROLE_PLANNING'),
306
+ makeEvent('BACKEND_PROGRAMMER', 'ROLE_PLANNING'),
307
+ makeEvent('WEBSITE_PROGRAMMER', 'ROLE_PLANNING'),
308
+ makeEvent('QA_TESTER', 'ROLE_PLANNING'),
309
+ ];
310
+ const pipeline = makePipeline({
311
+ activeRoles,
312
+ recoveryCount: 0,
313
+ artifacts: [],
314
+ gateResults: {},
315
+ });
316
+
317
+ const result = assertSkillCoverage(activeRoles, events, pipeline, 'CONSENSUS_ROLE_PLANS');
318
+
319
+ expect(result.pass).toBe(true);
320
+ // AUDITOR and RELEASE_MANAGER should be deferred
321
+ expect(result.deferred).toContain('AUDITOR');
322
+ expect(result.deferred).toContain('RELEASE_MANAGER');
323
+ // REVIEWER should also be deferred (REVIEW phase > CONSENSUS_ROLE_PLANS)
324
+ expect(result.deferred).toContain('REVIEWER');
325
+ // Conditional roles with no trigger are covered (not deferred)
326
+ expect(result.covered).toContain('ARBITRATOR');
327
+ expect(result.covered).toContain('DEBUGGER');
328
+ expect(result.covered).toContain('JOURNALIST');
329
+ });
330
+
331
+ it('should fall back to strict mode with unknown currentPhase', () => {
332
+ // An unknown phase not in PHASE_ORDER gets indexOf = -1, same as strict mode
333
+ const activeRoles: PipelineRole[] = ['DISPATCHER', 'AUDITOR'];
334
+ const pipeline = makePipeline({ activeRoles });
335
+
336
+ const result = assertSkillCoverage(
337
+ activeRoles, [], pipeline, 'SOME_NEW_PHASE' as PipelinePhase,
338
+ );
339
+
340
+ // Strict mode: AUDITOR is checked (not deferred) and fails
341
+ expect(result.pass).toBe(false);
342
+ expect(result.missing).toHaveLength(1);
343
+ expect(result.missing[0].role).toBe('AUDITOR');
344
+ expect(result.deferred).toHaveLength(0);
345
+ });
346
+
347
+ it('should use strict mode when currentPhase is omitted (backward compat)', () => {
348
+ const activeRoles: PipelineRole[] = ['DISPATCHER', 'AUDITOR'];
349
+ const pipeline = makePipeline({ activeRoles });
350
+
351
+ // No currentPhase param — same as original behavior
352
+ const result = assertSkillCoverage(activeRoles, [], pipeline);
353
+
354
+ expect(result.pass).toBe(false);
355
+ expect(result.missing).toHaveLength(1);
356
+ expect(result.missing[0].role).toBe('AUDITOR');
357
+ expect(result.deferred).toHaveLength(0);
358
+ });
359
+
360
+ it('PHASE_ORDER should include all key phases', () => {
361
+ const keyPhases: PipelinePhase[] = [
362
+ 'INTAKE', 'CONSENSUS_ROLE_PLANS', 'PRODUCTION_GATE', 'DONE', 'STUCK',
363
+ 'AUDIT', 'REVIEW', 'ARCHITECTURE', 'ROLE_PLANNING',
364
+ ];
365
+ for (const phase of keyPhases) {
366
+ expect(PHASE_ORDER).toContain(phase);
367
+ }
368
+ });
369
+ });
370
+ });
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Skill generator tests — prompt building, parsing, rendering, skip logic.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import {
9
+ shouldGenerateSkill,
10
+ buildSkillGenPrompt,
11
+ parseSkillPrompts,
12
+ renderSkillMarkdown,
13
+ writeGenerationMarker,
14
+ } from '../../../src/pipeline/skills/generator.js';
15
+ import type { SkillGenerationContext } from '../../../src/pipeline/skills/types.js';
16
+ import type { RepoSnapshot } from '../../../src/pipeline/types.js';
17
+
18
+ const TEST_DIR = join(process.cwd(), '.test-skill-generator');
19
+ const SKILLS_DIR = join(TEST_DIR, 'skills');
20
+
21
+ function makeSnapshot(): RepoSnapshot {
22
+ return {
23
+ snapshot_id: 'test-snap',
24
+ timestamp: new Date().toISOString(),
25
+ tree_summary: '',
26
+ config_files: [],
27
+ languages_detected: [],
28
+ scripts: {},
29
+ env_files: [],
30
+ migrations_present: false,
31
+ ports_entrypoints: [],
32
+ total_files: 0,
33
+ total_lines: 0,
34
+ };
35
+ }
36
+
37
+ function makeContext(overrides: Partial<SkillGenerationContext> = {}): SkillGenerationContext {
38
+ return {
39
+ language: 'python',
40
+ expandedSpec: 'Build a REST API for task management',
41
+ snapshot: makeSnapshot(),
42
+ activeRoles: ['DISPATCHER', 'ARCHITECT', 'BACKEND_PROGRAMMER', 'DB_EXPERT'],
43
+ skillsDir: SKILLS_DIR,
44
+ projectName: 'TestProject',
45
+ ...overrides,
46
+ };
47
+ }
48
+
49
+ describe('generator', () => {
50
+ beforeEach(() => {
51
+ if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
52
+ mkdirSync(SKILLS_DIR, { recursive: true });
53
+ });
54
+
55
+ afterEach(() => {
56
+ if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
57
+ });
58
+
59
+ describe('shouldGenerateSkill', () => {
60
+ it('should return true when no .md file exists', () => {
61
+ expect(shouldGenerateSkill(SKILLS_DIR, 'BACKEND_PROGRAMMER')).toBe(true);
62
+ });
63
+
64
+ it('should return false when .md file already exists', () => {
65
+ writeFileSync(join(SKILLS_DIR, 'BACKEND_PROGRAMMER.md'), 'existing content');
66
+ expect(shouldGenerateSkill(SKILLS_DIR, 'BACKEND_PROGRAMMER')).toBe(false);
67
+ });
68
+
69
+ it('should check per-role independently', () => {
70
+ writeFileSync(join(SKILLS_DIR, 'ARCHITECT.md'), 'existing');
71
+ expect(shouldGenerateSkill(SKILLS_DIR, 'ARCHITECT')).toBe(false);
72
+ expect(shouldGenerateSkill(SKILLS_DIR, 'BACKEND_PROGRAMMER')).toBe(true);
73
+ });
74
+ });
75
+
76
+ describe('buildSkillGenPrompt', () => {
77
+ it('should include project name and tech stack', () => {
78
+ const prompt = buildSkillGenPrompt(
79
+ makeContext(),
80
+ ['BACKEND_PROGRAMMER'],
81
+ { backend: 'FastAPI', language: 'Python 3.11+' },
82
+ );
83
+ expect(prompt).toContain('TestProject');
84
+ expect(prompt).toContain('FastAPI');
85
+ expect(prompt).toContain('Python 3.11+');
86
+ });
87
+
88
+ it('should include role descriptions', () => {
89
+ const prompt = buildSkillGenPrompt(
90
+ makeContext(),
91
+ ['BACKEND_PROGRAMMER', 'DB_EXPERT'],
92
+ { backend: 'FastAPI' },
93
+ );
94
+ expect(prompt).toContain('BACKEND_PROGRAMMER');
95
+ expect(prompt).toContain('DB_EXPERT');
96
+ });
97
+
98
+ it('should include session guidance when present', () => {
99
+ const prompt = buildSkillGenPrompt(
100
+ makeContext({ sessionGuidance: 'Focus on security' }),
101
+ ['BACKEND_PROGRAMMER'],
102
+ { backend: 'FastAPI' },
103
+ );
104
+ expect(prompt).toContain('Focus on security');
105
+ });
106
+
107
+ it('should include expanded spec', () => {
108
+ const prompt = buildSkillGenPrompt(
109
+ makeContext({ expandedSpec: 'Build a REST API with auth' }),
110
+ ['BACKEND_PROGRAMMER'],
111
+ { backend: 'FastAPI' },
112
+ );
113
+ expect(prompt).toContain('Build a REST API with auth');
114
+ });
115
+ });
116
+
117
+ describe('parseSkillPrompts', () => {
118
+ it('should parse valid JSON response', () => {
119
+ const response = JSON.stringify({
120
+ BACKEND_PROGRAMMER: 'You are the Backend Programmer for MyProject.',
121
+ DB_EXPERT: 'You are the DB Expert for MyProject.',
122
+ });
123
+ const result = parseSkillPrompts(response, ['BACKEND_PROGRAMMER', 'DB_EXPERT']);
124
+ expect(result.BACKEND_PROGRAMMER).toContain('Backend Programmer');
125
+ expect(result.DB_EXPERT).toContain('DB Expert');
126
+ });
127
+
128
+ it('should extract JSON from markdown code fences', () => {
129
+ const response = '```json\n{"BACKEND_PROGRAMMER": "You are the Backend Programmer."}\n```';
130
+ const result = parseSkillPrompts(response, ['BACKEND_PROGRAMMER']);
131
+ expect(result.BACKEND_PROGRAMMER).toContain('Backend Programmer');
132
+ });
133
+
134
+ it('should return empty for malformed JSON', () => {
135
+ const result = parseSkillPrompts('not json at all', ['BACKEND_PROGRAMMER']);
136
+ expect(Object.keys(result)).toHaveLength(0);
137
+ });
138
+
139
+ it('should skip roles not in expectedRoles', () => {
140
+ const response = JSON.stringify({
141
+ BACKEND_PROGRAMMER: 'Valid prompt for backend.',
142
+ FRONTEND_PROGRAMMER: 'Should be ignored.',
143
+ });
144
+ const result = parseSkillPrompts(response, ['BACKEND_PROGRAMMER']);
145
+ expect(result.BACKEND_PROGRAMMER).toBeDefined();
146
+ expect(result.FRONTEND_PROGRAMMER).toBeUndefined();
147
+ });
148
+
149
+ it('should skip prompts shorter than 10 chars', () => {
150
+ const response = JSON.stringify({
151
+ BACKEND_PROGRAMMER: 'Short',
152
+ });
153
+ const result = parseSkillPrompts(response, ['BACKEND_PROGRAMMER']);
154
+ expect(result.BACKEND_PROGRAMMER).toBeUndefined();
155
+ });
156
+ });
157
+
158
+ describe('renderSkillMarkdown', () => {
159
+ it('should produce valid YAML frontmatter format', () => {
160
+ const md = renderSkillMarkdown(
161
+ 'BACKEND_PROGRAMMER',
162
+ 'You are the Backend Programmer.',
163
+ ['follow_architecture', 'must_follow_master_plan'],
164
+ ['endpoints', 'services'],
165
+ ['ARCHITECT'],
166
+ );
167
+
168
+ expect(md).toMatch(/^---\n/);
169
+ expect(md).toContain('role: BACKEND_PROGRAMMER');
170
+ expect(md).toContain('version: 1.0-project');
171
+ expect(md).toContain(' - endpoints');
172
+ expect(md).toContain(' - services');
173
+ expect(md).toContain(' - follow_architecture');
174
+ expect(md).toContain(' - must_follow_master_plan');
175
+ expect(md).toContain('depends_on:');
176
+ expect(md).toContain(' - ARCHITECT');
177
+ expect(md).toContain('You are the Backend Programmer.');
178
+ });
179
+
180
+ it('should omit depends_on when empty', () => {
181
+ const md = renderSkillMarkdown(
182
+ 'DISPATCHER',
183
+ 'You are the Dispatcher.',
184
+ ['governance'],
185
+ ['phase_transition'],
186
+ [],
187
+ );
188
+ expect(md).not.toContain('depends_on:');
189
+ });
190
+ });
191
+
192
+ describe('writeGenerationMarker', () => {
193
+ it('should write valid JSON marker file', () => {
194
+ const marker = {
195
+ timestamp: '2026-02-22T14:30:00Z',
196
+ pipelineVersion: '1.0',
197
+ activeRoles: ['DISPATCHER', 'BACKEND_PROGRAMMER'],
198
+ techStack: { backend: 'FastAPI' },
199
+ aiGenerated: true,
200
+ };
201
+ writeGenerationMarker(SKILLS_DIR, marker);
202
+
203
+ const markerPath = join(SKILLS_DIR, '.popeye-skills-generated.json');
204
+ expect(existsSync(markerPath)).toBe(true);
205
+
206
+ const content = JSON.parse(readFileSync(markerPath, 'utf-8'));
207
+ expect(content.pipelineVersion).toBe('1.0');
208
+ expect(content.aiGenerated).toBe(true);
209
+ expect(content.activeRoles).toContain('BACKEND_PROGRAMMER');
210
+ expect(content.techStack.backend).toBe('FastAPI');
211
+ });
212
+ });
213
+ });