popeye-cli 1.9.5 → 2.0.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 (318) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/CONTRIBUTING.md +15 -1
  3. package/README.md +57 -0
  4. package/cheatsheet.md +65 -0
  5. package/dist/cli/commands/debug-context.d.ts +64 -0
  6. package/dist/cli/commands/debug-context.d.ts.map +1 -0
  7. package/dist/cli/commands/debug-context.js +221 -0
  8. package/dist/cli/commands/debug-context.js.map +1 -0
  9. package/dist/cli/commands/debug-prompts.d.ts +25 -0
  10. package/dist/cli/commands/debug-prompts.d.ts.map +1 -0
  11. package/dist/cli/commands/debug-prompts.js +80 -0
  12. package/dist/cli/commands/debug-prompts.js.map +1 -0
  13. package/dist/cli/commands/debug.d.ts +68 -0
  14. package/dist/cli/commands/debug.d.ts.map +1 -0
  15. package/dist/cli/commands/debug.js +543 -0
  16. package/dist/cli/commands/debug.js.map +1 -0
  17. package/dist/cli/commands/index.d.ts +1 -0
  18. package/dist/cli/commands/index.d.ts.map +1 -1
  19. package/dist/cli/commands/index.js +1 -0
  20. package/dist/cli/commands/index.js.map +1 -1
  21. package/dist/cli/index.d.ts.map +1 -1
  22. package/dist/cli/index.js +2 -1
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/cli/interactive.d.ts.map +1 -1
  25. package/dist/cli/interactive.js +25 -0
  26. package/dist/cli/interactive.js.map +1 -1
  27. package/dist/generators/all.d.ts.map +1 -1
  28. package/dist/generators/all.js +2 -0
  29. package/dist/generators/all.js.map +1 -1
  30. package/dist/generators/templates/database-docker.d.ts.map +1 -1
  31. package/dist/generators/templates/database-docker.js +10 -0
  32. package/dist/generators/templates/database-docker.js.map +1 -1
  33. package/dist/generators/templates/fullstack.d.ts +4 -1
  34. package/dist/generators/templates/fullstack.d.ts.map +1 -1
  35. package/dist/generators/templates/fullstack.js +6 -2
  36. package/dist/generators/templates/fullstack.js.map +1 -1
  37. package/dist/pipeline/artifact-manager.d.ts +47 -0
  38. package/dist/pipeline/artifact-manager.d.ts.map +1 -0
  39. package/dist/pipeline/artifact-manager.js +251 -0
  40. package/dist/pipeline/artifact-manager.js.map +1 -0
  41. package/dist/pipeline/artifact-validators.d.ts +29 -0
  42. package/dist/pipeline/artifact-validators.d.ts.map +1 -0
  43. package/dist/pipeline/artifact-validators.js +173 -0
  44. package/dist/pipeline/artifact-validators.js.map +1 -0
  45. package/dist/pipeline/change-request.d.ts +47 -0
  46. package/dist/pipeline/change-request.d.ts.map +1 -0
  47. package/dist/pipeline/change-request.js +91 -0
  48. package/dist/pipeline/change-request.js.map +1 -0
  49. package/dist/pipeline/check-runner.d.ts +47 -0
  50. package/dist/pipeline/check-runner.d.ts.map +1 -0
  51. package/dist/pipeline/check-runner.js +417 -0
  52. package/dist/pipeline/check-runner.js.map +1 -0
  53. package/dist/pipeline/command-resolver.d.ts +9 -0
  54. package/dist/pipeline/command-resolver.d.ts.map +1 -0
  55. package/dist/pipeline/command-resolver.js +140 -0
  56. package/dist/pipeline/command-resolver.js.map +1 -0
  57. package/dist/pipeline/consensus/consensus-runner.d.ts +44 -0
  58. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -0
  59. package/dist/pipeline/consensus/consensus-runner.js +212 -0
  60. package/dist/pipeline/consensus/consensus-runner.js.map +1 -0
  61. package/dist/pipeline/constitution.d.ts +45 -0
  62. package/dist/pipeline/constitution.d.ts.map +1 -0
  63. package/dist/pipeline/constitution.js +82 -0
  64. package/dist/pipeline/constitution.js.map +1 -0
  65. package/dist/pipeline/gate-engine.d.ts +55 -0
  66. package/dist/pipeline/gate-engine.d.ts.map +1 -0
  67. package/dist/pipeline/gate-engine.js +270 -0
  68. package/dist/pipeline/gate-engine.js.map +1 -0
  69. package/dist/pipeline/index.d.ts +26 -0
  70. package/dist/pipeline/index.d.ts.map +1 -0
  71. package/dist/pipeline/index.js +35 -0
  72. package/dist/pipeline/index.js.map +1 -0
  73. package/dist/pipeline/migration.d.ts +15 -0
  74. package/dist/pipeline/migration.d.ts.map +1 -0
  75. package/dist/pipeline/migration.js +76 -0
  76. package/dist/pipeline/migration.js.map +1 -0
  77. package/dist/pipeline/orchestrator.d.ts +28 -0
  78. package/dist/pipeline/orchestrator.d.ts.map +1 -0
  79. package/dist/pipeline/orchestrator.js +238 -0
  80. package/dist/pipeline/orchestrator.js.map +1 -0
  81. package/dist/pipeline/packets/audit-report-builder.d.ts +11 -0
  82. package/dist/pipeline/packets/audit-report-builder.d.ts.map +1 -0
  83. package/dist/pipeline/packets/audit-report-builder.js +32 -0
  84. package/dist/pipeline/packets/audit-report-builder.js.map +1 -0
  85. package/dist/pipeline/packets/consensus-packet-builder.d.ts +35 -0
  86. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -0
  87. package/dist/pipeline/packets/consensus-packet-builder.js +80 -0
  88. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -0
  89. package/dist/pipeline/packets/index.d.ts +12 -0
  90. package/dist/pipeline/packets/index.d.ts.map +1 -0
  91. package/dist/pipeline/packets/index.js +8 -0
  92. package/dist/pipeline/packets/index.js.map +1 -0
  93. package/dist/pipeline/packets/plan-packet-builder.d.ts +21 -0
  94. package/dist/pipeline/packets/plan-packet-builder.d.ts.map +1 -0
  95. package/dist/pipeline/packets/plan-packet-builder.js +27 -0
  96. package/dist/pipeline/packets/plan-packet-builder.js.map +1 -0
  97. package/dist/pipeline/packets/rca-packet-builder.d.ts +19 -0
  98. package/dist/pipeline/packets/rca-packet-builder.d.ts.map +1 -0
  99. package/dist/pipeline/packets/rca-packet-builder.js +22 -0
  100. package/dist/pipeline/packets/rca-packet-builder.js.map +1 -0
  101. package/dist/pipeline/phases/architecture.d.ts +7 -0
  102. package/dist/pipeline/phases/architecture.d.ts.map +1 -0
  103. package/dist/pipeline/phases/architecture.js +60 -0
  104. package/dist/pipeline/phases/architecture.js.map +1 -0
  105. package/dist/pipeline/phases/audit.d.ts +8 -0
  106. package/dist/pipeline/phases/audit.d.ts.map +1 -0
  107. package/dist/pipeline/phases/audit.js +144 -0
  108. package/dist/pipeline/phases/audit.js.map +1 -0
  109. package/dist/pipeline/phases/consensus-architecture.d.ts +7 -0
  110. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -0
  111. package/dist/pipeline/phases/consensus-architecture.js +84 -0
  112. package/dist/pipeline/phases/consensus-architecture.js.map +1 -0
  113. package/dist/pipeline/phases/consensus-master-plan.d.ts +7 -0
  114. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -0
  115. package/dist/pipeline/phases/consensus-master-plan.js +81 -0
  116. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -0
  117. package/dist/pipeline/phases/consensus-role-plans.d.ts +7 -0
  118. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -0
  119. package/dist/pipeline/phases/consensus-role-plans.js +85 -0
  120. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -0
  121. package/dist/pipeline/phases/done.d.ts +7 -0
  122. package/dist/pipeline/phases/done.d.ts.map +1 -0
  123. package/dist/pipeline/phases/done.js +45 -0
  124. package/dist/pipeline/phases/done.js.map +1 -0
  125. package/dist/pipeline/phases/implementation.d.ts +8 -0
  126. package/dist/pipeline/phases/implementation.d.ts.map +1 -0
  127. package/dist/pipeline/phases/implementation.js +42 -0
  128. package/dist/pipeline/phases/implementation.js.map +1 -0
  129. package/dist/pipeline/phases/index.d.ts +20 -0
  130. package/dist/pipeline/phases/index.d.ts.map +1 -0
  131. package/dist/pipeline/phases/index.js +19 -0
  132. package/dist/pipeline/phases/index.js.map +1 -0
  133. package/dist/pipeline/phases/intake.d.ts +8 -0
  134. package/dist/pipeline/phases/intake.d.ts.map +1 -0
  135. package/dist/pipeline/phases/intake.js +40 -0
  136. package/dist/pipeline/phases/intake.js.map +1 -0
  137. package/dist/pipeline/phases/phase-context.d.ts +30 -0
  138. package/dist/pipeline/phases/phase-context.d.ts.map +1 -0
  139. package/dist/pipeline/phases/phase-context.js +33 -0
  140. package/dist/pipeline/phases/phase-context.js.map +1 -0
  141. package/dist/pipeline/phases/production-gate.d.ts +8 -0
  142. package/dist/pipeline/phases/production-gate.d.ts.map +1 -0
  143. package/dist/pipeline/phases/production-gate.js +84 -0
  144. package/dist/pipeline/phases/production-gate.js.map +1 -0
  145. package/dist/pipeline/phases/qa-validation.d.ts +7 -0
  146. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -0
  147. package/dist/pipeline/phases/qa-validation.js +50 -0
  148. package/dist/pipeline/phases/qa-validation.js.map +1 -0
  149. package/dist/pipeline/phases/recovery-loop.d.ts +7 -0
  150. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -0
  151. package/dist/pipeline/phases/recovery-loop.js +91 -0
  152. package/dist/pipeline/phases/recovery-loop.js.map +1 -0
  153. package/dist/pipeline/phases/review.d.ts +8 -0
  154. package/dist/pipeline/phases/review.d.ts.map +1 -0
  155. package/dist/pipeline/phases/review.js +127 -0
  156. package/dist/pipeline/phases/review.js.map +1 -0
  157. package/dist/pipeline/phases/role-planning.d.ts +7 -0
  158. package/dist/pipeline/phases/role-planning.d.ts.map +1 -0
  159. package/dist/pipeline/phases/role-planning.js +75 -0
  160. package/dist/pipeline/phases/role-planning.js.map +1 -0
  161. package/dist/pipeline/phases/stuck.d.ts +7 -0
  162. package/dist/pipeline/phases/stuck.d.ts.map +1 -0
  163. package/dist/pipeline/phases/stuck.js +51 -0
  164. package/dist/pipeline/phases/stuck.js.map +1 -0
  165. package/dist/pipeline/repo-snapshot.d.ts +24 -0
  166. package/dist/pipeline/repo-snapshot.d.ts.map +1 -0
  167. package/dist/pipeline/repo-snapshot.js +343 -0
  168. package/dist/pipeline/repo-snapshot.js.map +1 -0
  169. package/dist/pipeline/role-execution-adapter.d.ts +59 -0
  170. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -0
  171. package/dist/pipeline/role-execution-adapter.js +159 -0
  172. package/dist/pipeline/role-execution-adapter.js.map +1 -0
  173. package/dist/pipeline/skill-loader.d.ts +34 -0
  174. package/dist/pipeline/skill-loader.d.ts.map +1 -0
  175. package/dist/pipeline/skill-loader.js +156 -0
  176. package/dist/pipeline/skill-loader.js.map +1 -0
  177. package/dist/pipeline/skills/defaults.d.ts +16 -0
  178. package/dist/pipeline/skills/defaults.d.ts.map +1 -0
  179. package/dist/pipeline/skills/defaults.js +189 -0
  180. package/dist/pipeline/skills/defaults.js.map +1 -0
  181. package/dist/pipeline/type-defs/artifacts.d.ts +202 -0
  182. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -0
  183. package/dist/pipeline/type-defs/artifacts.js +66 -0
  184. package/dist/pipeline/type-defs/artifacts.js.map +1 -0
  185. package/dist/pipeline/type-defs/audit.d.ts +256 -0
  186. package/dist/pipeline/type-defs/audit.d.ts.map +1 -0
  187. package/dist/pipeline/type-defs/audit.js +54 -0
  188. package/dist/pipeline/type-defs/audit.js.map +1 -0
  189. package/dist/pipeline/type-defs/checks.d.ts +81 -0
  190. package/dist/pipeline/type-defs/checks.d.ts.map +1 -0
  191. package/dist/pipeline/type-defs/checks.js +38 -0
  192. package/dist/pipeline/type-defs/checks.js.map +1 -0
  193. package/dist/pipeline/type-defs/enums.d.ts +43 -0
  194. package/dist/pipeline/type-defs/enums.d.ts.map +1 -0
  195. package/dist/pipeline/type-defs/enums.js +55 -0
  196. package/dist/pipeline/type-defs/enums.js.map +1 -0
  197. package/dist/pipeline/type-defs/index.d.ts +12 -0
  198. package/dist/pipeline/type-defs/index.d.ts.map +1 -0
  199. package/dist/pipeline/type-defs/index.js +12 -0
  200. package/dist/pipeline/type-defs/index.js.map +1 -0
  201. package/dist/pipeline/type-defs/packets.d.ts +806 -0
  202. package/dist/pipeline/type-defs/packets.d.ts.map +1 -0
  203. package/dist/pipeline/type-defs/packets.js +109 -0
  204. package/dist/pipeline/type-defs/packets.js.map +1 -0
  205. package/dist/pipeline/type-defs/snapshot.d.ts +52 -0
  206. package/dist/pipeline/type-defs/snapshot.d.ts.map +1 -0
  207. package/dist/pipeline/type-defs/snapshot.js +35 -0
  208. package/dist/pipeline/type-defs/snapshot.js.map +1 -0
  209. package/dist/pipeline/type-defs/state.d.ts +449 -0
  210. package/dist/pipeline/type-defs/state.d.ts.map +1 -0
  211. package/dist/pipeline/type-defs/state.js +88 -0
  212. package/dist/pipeline/type-defs/state.js.map +1 -0
  213. package/dist/pipeline/types.d.ts +16 -0
  214. package/dist/pipeline/types.d.ts.map +1 -0
  215. package/dist/pipeline/types.js +16 -0
  216. package/dist/pipeline/types.js.map +1 -0
  217. package/dist/types/audit.d.ts +6 -6
  218. package/dist/workflow/index.d.ts.map +1 -1
  219. package/dist/workflow/index.js +48 -0
  220. package/dist/workflow/index.js.map +1 -1
  221. package/package.json +1 -1
  222. package/skills/ARBITRATOR.md +137 -0
  223. package/skills/ARCHITECT.md +167 -0
  224. package/skills/AUDITOR.md +128 -0
  225. package/skills/AUDIT_REPORT_SCHEMA.md +20 -0
  226. package/skills/BACKEND_PROGRAMMER.md +95 -0
  227. package/skills/CONSENSUS_PACKET_SCHEMA.md +166 -0
  228. package/skills/DB_EXPERT.md +106 -0
  229. package/skills/DEBUGGER.md +286 -0
  230. package/skills/DISPATCHER.md +157 -0
  231. package/skills/FRONTEND_PROGRAMMER.md +84 -0
  232. package/skills/JOURNALIST.md +247 -0
  233. package/skills/MARKETING_EXPERT.md +23 -0
  234. package/skills/PHASE_GATE_ENGINE_SPEC.md +171 -0
  235. package/skills/PLAN_PACKET_SCHEMA.md +222 -0
  236. package/skills/POPEYE_CONSTITUTION.md +177 -0
  237. package/skills/POPEYE_FULL_AUTONOMY_PIPELINE.md +537 -0
  238. package/skills/PRODUCTION_READINESS_SCHEMA.md +19 -0
  239. package/skills/QA_TESTER.md +40 -0
  240. package/skills/RCA_PACKET_SCHEMA.md +22 -0
  241. package/skills/RELEASE_MANAGER.md +60 -0
  242. package/skills/REVIEWER.md +133 -0
  243. package/skills/SOCIAL_EXPERT.md +22 -0
  244. package/skills/UI_UX_SPECIALIST.md +22 -0
  245. package/skills/WEBSITE_PROGRAMMER.md +37 -0
  246. package/src/cli/commands/debug-context.ts +265 -0
  247. package/src/cli/commands/debug-prompts.ts +91 -0
  248. package/src/cli/commands/debug.ts +662 -0
  249. package/src/cli/commands/index.ts +1 -0
  250. package/src/cli/index.ts +2 -0
  251. package/src/cli/interactive.ts +27 -0
  252. package/src/generators/all.ts +2 -0
  253. package/src/generators/templates/database-docker.ts +10 -0
  254. package/src/generators/templates/fullstack.ts +6 -2
  255. package/src/pipeline/artifact-manager.ts +339 -0
  256. package/src/pipeline/artifact-validators.ts +224 -0
  257. package/src/pipeline/change-request.ts +119 -0
  258. package/src/pipeline/check-runner.ts +504 -0
  259. package/src/pipeline/command-resolver.ts +168 -0
  260. package/src/pipeline/consensus/consensus-runner.ts +317 -0
  261. package/src/pipeline/constitution.ts +109 -0
  262. package/src/pipeline/gate-engine.ts +347 -0
  263. package/src/pipeline/index.ts +82 -0
  264. package/src/pipeline/migration.ts +91 -0
  265. package/src/pipeline/orchestrator.ts +314 -0
  266. package/src/pipeline/packets/audit-report-builder.ts +47 -0
  267. package/src/pipeline/packets/consensus-packet-builder.ts +112 -0
  268. package/src/pipeline/packets/index.ts +15 -0
  269. package/src/pipeline/packets/plan-packet-builder.ts +52 -0
  270. package/src/pipeline/packets/rca-packet-builder.ts +38 -0
  271. package/src/pipeline/phases/architecture.ts +73 -0
  272. package/src/pipeline/phases/audit.ts +193 -0
  273. package/src/pipeline/phases/consensus-architecture.ts +104 -0
  274. package/src/pipeline/phases/consensus-master-plan.ts +100 -0
  275. package/src/pipeline/phases/consensus-role-plans.ts +105 -0
  276. package/src/pipeline/phases/done.ts +68 -0
  277. package/src/pipeline/phases/implementation.ts +48 -0
  278. package/src/pipeline/phases/index.ts +21 -0
  279. package/src/pipeline/phases/intake.ts +54 -0
  280. package/src/pipeline/phases/phase-context.ts +86 -0
  281. package/src/pipeline/phases/production-gate.ts +113 -0
  282. package/src/pipeline/phases/qa-validation.ts +63 -0
  283. package/src/pipeline/phases/recovery-loop.ts +118 -0
  284. package/src/pipeline/phases/review.ts +149 -0
  285. package/src/pipeline/phases/role-planning.ts +92 -0
  286. package/src/pipeline/phases/stuck.ts +62 -0
  287. package/src/pipeline/repo-snapshot.ts +395 -0
  288. package/src/pipeline/role-execution-adapter.ts +238 -0
  289. package/src/pipeline/skill-loader.ts +192 -0
  290. package/src/pipeline/skills/defaults.ts +215 -0
  291. package/src/pipeline/type-defs/artifacts.ts +81 -0
  292. package/src/pipeline/type-defs/audit.ts +67 -0
  293. package/src/pipeline/type-defs/checks.ts +47 -0
  294. package/src/pipeline/type-defs/enums.ts +62 -0
  295. package/src/pipeline/type-defs/index.ts +12 -0
  296. package/src/pipeline/type-defs/packets.ts +131 -0
  297. package/src/pipeline/type-defs/snapshot.ts +55 -0
  298. package/src/pipeline/type-defs/state.ts +165 -0
  299. package/src/pipeline/types.ts +16 -0
  300. package/src/workflow/index.ts +48 -0
  301. package/tests/cli/commands/debug.test.ts +376 -0
  302. package/tests/pipeline/artifact-manager.test.ts +183 -0
  303. package/tests/pipeline/artifact-validators.test.ts +207 -0
  304. package/tests/pipeline/change-request.test.ts +180 -0
  305. package/tests/pipeline/check-runner.test.ts +157 -0
  306. package/tests/pipeline/command-resolver.test.ts +159 -0
  307. package/tests/pipeline/consensus-runner.test.ts +206 -0
  308. package/tests/pipeline/consensus-scoring.test.ts +163 -0
  309. package/tests/pipeline/constitution.test.ts +122 -0
  310. package/tests/pipeline/gate-engine.test.ts +195 -0
  311. package/tests/pipeline/migration.test.ts +133 -0
  312. package/tests/pipeline/orchestrator.test.ts +614 -0
  313. package/tests/pipeline/packets/builders.test.ts +347 -0
  314. package/tests/pipeline/repo-snapshot.test.ts +189 -0
  315. package/tests/pipeline/role-execution-adapter.test.ts +299 -0
  316. package/tests/pipeline/skill-loader.test.ts +186 -0
  317. package/tests/pipeline/start-env-checks.test.ts +123 -0
  318. package/tests/pipeline/types.test.ts +156 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Change Request mechanism — structured change tracking for mid-pipeline modifications.
3
+ * CRs are created when drift is detected (REVIEW) or architectural issues found (AUDIT).
4
+ * Each CR routes to the appropriate consensus phase for approval.
5
+ */
6
+
7
+ import { randomUUID } from 'node:crypto';
8
+
9
+ import type {
10
+ PipelinePhase,
11
+ PipelineRole,
12
+ ArtifactRef,
13
+ ChangeRequest,
14
+ } from './types.js';
15
+
16
+ // ─── CR Builder ──────────────────────────────────────────
17
+
18
+ export interface BuildChangeRequestArgs {
19
+ originPhase: PipelinePhase;
20
+ requestedBy: PipelineRole;
21
+ changeType: ChangeRequest['change_type'];
22
+ description: string;
23
+ justification: string;
24
+ affectedArtifacts: ArtifactRef[];
25
+ affectedPhases: PipelinePhase[];
26
+ riskLevel: 'low' | 'medium' | 'high';
27
+ }
28
+
29
+ /**
30
+ * Build a change request with generated ID and timestamp.
31
+ *
32
+ * Args:
33
+ * args: Change request parameters.
34
+ *
35
+ * Returns:
36
+ * A fully formed ChangeRequest in 'proposed' status.
37
+ */
38
+ export function buildChangeRequest(args: BuildChangeRequestArgs): ChangeRequest {
39
+ return {
40
+ cr_id: `CR-${randomUUID().split('-')[0].toUpperCase()}`,
41
+ timestamp: new Date().toISOString(),
42
+ origin_phase: args.originPhase,
43
+ requested_by: args.requestedBy,
44
+ change_type: args.changeType,
45
+ description: args.description,
46
+ justification: args.justification,
47
+ impact_analysis: {
48
+ affected_artifacts: args.affectedArtifacts,
49
+ affected_phases: args.affectedPhases,
50
+ risk_level: args.riskLevel,
51
+ },
52
+ status: 'proposed',
53
+ };
54
+ }
55
+
56
+ // ─── CR Routing ──────────────────────────────────────────
57
+
58
+ /** Maps change types to the consensus phase that must approve them */
59
+ const CHANGE_TYPE_ROUTING: Record<ChangeRequest['change_type'], PipelinePhase> = {
60
+ scope: 'CONSENSUS_MASTER_PLAN',
61
+ architecture: 'CONSENSUS_ARCHITECTURE',
62
+ dependency: 'CONSENSUS_ROLE_PLANS',
63
+ config: 'QA_VALIDATION',
64
+ requirement: 'CONSENSUS_MASTER_PLAN',
65
+ };
66
+
67
+ /**
68
+ * Determine which consensus phase should review a change request.
69
+ *
70
+ * Args:
71
+ * cr: The change request to route.
72
+ *
73
+ * Returns:
74
+ * The pipeline phase that should handle the CR approval.
75
+ */
76
+ export function routeChangeRequest(cr: ChangeRequest): PipelinePhase {
77
+ return CHANGE_TYPE_ROUTING[cr.change_type];
78
+ }
79
+
80
+ // ─── CR Formatting ───────────────────────────────────────
81
+
82
+ /**
83
+ * Format a change request as markdown for inclusion in artifacts.
84
+ *
85
+ * Args:
86
+ * cr: The change request to format.
87
+ *
88
+ * Returns:
89
+ * Markdown-formatted string.
90
+ */
91
+ export function formatChangeRequest(cr: ChangeRequest): string {
92
+ const lines = [
93
+ `# Change Request ${cr.cr_id}`,
94
+ '',
95
+ `**Status:** ${cr.status}`,
96
+ `**Type:** ${cr.change_type}`,
97
+ `**Origin Phase:** ${cr.origin_phase}`,
98
+ `**Requested By:** ${cr.requested_by}`,
99
+ `**Risk Level:** ${cr.impact_analysis.risk_level}`,
100
+ `**Timestamp:** ${cr.timestamp}`,
101
+ '',
102
+ '## Description',
103
+ cr.description,
104
+ '',
105
+ '## Justification',
106
+ cr.justification,
107
+ '',
108
+ '## Impact Analysis',
109
+ `- Affected phases: ${cr.impact_analysis.affected_phases.join(', ')}`,
110
+ `- Affected artifacts: ${cr.impact_analysis.affected_artifacts.length}`,
111
+ `- Risk level: ${cr.impact_analysis.risk_level}`,
112
+ ];
113
+
114
+ if (cr.approval_artifact) {
115
+ lines.push('', `## Approval: ${cr.approval_artifact.artifact_id}`);
116
+ }
117
+
118
+ return lines.join('\n');
119
+ }
@@ -0,0 +1,504 @@
1
+ /**
2
+ * Check Runner — executes build/test/lint/typecheck commands
3
+ * and produces GateCheckResult artifacts.
4
+ *
5
+ * Safety: command sanitization, cwd enforcement, stream caps,
6
+ * configurable timeouts (P2-G).
7
+ */
8
+
9
+ import { exec } from 'node:child_process';
10
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
11
+ import { join, extname } from 'node:path';
12
+
13
+ import type {
14
+ GateCheckResult,
15
+ GateCheckType,
16
+ ResolvedCommands,
17
+ RepoSnapshot,
18
+ ArtifactEntry,
19
+ PipelinePhase,
20
+ } from './types.js';
21
+ import { ArtifactManager } from './artifact-manager.js';
22
+
23
+ // ─── Constants ───────────────────────────────────────────
24
+
25
+ /** Default timeout per check type in milliseconds */
26
+ const DEFAULT_TIMEOUTS: Record<string, number> = {
27
+ build: 20 * 60 * 1000, // 20 minutes
28
+ test: 10 * 60 * 1000, // 10 minutes
29
+ lint: 5 * 60 * 1000, // 5 minutes
30
+ typecheck: 5 * 60 * 1000, // 5 minutes
31
+ migration: 5 * 60 * 1000, // 5 minutes
32
+ };
33
+
34
+ /** Max stdout/stderr capture in bytes */
35
+ const MAX_OUTPUT_SIZE = 1024 * 1024; // 1 MB
36
+
37
+ /** Dangerous command patterns to reject */
38
+ const DANGEROUS_PATTERNS = [
39
+ /rm\s+-rf\s+\//,
40
+ /sudo\s+/,
41
+ />\s*\/dev\//,
42
+ />\s*\/etc\//,
43
+ />\s*\/usr\//,
44
+ /;\s*rm\s/,
45
+ /&&\s*rm\s/,
46
+ /\|\s*sh$/,
47
+ /\|\s*bash$/,
48
+ ];
49
+
50
+ /** Placeholder patterns for scanning */
51
+ const PLACEHOLDER_PATTERNS = [
52
+ /\bTODO\b/i,
53
+ /\bFIXME\b/i,
54
+ /\bHACK\b/i,
55
+ /\bXXX\b/i,
56
+ /placeholder/i,
57
+ /\bmock\b(?!\.)/i, // 'mock' but not 'mock.' (import paths)
58
+ /\btemp\b(?!late)/i, // 'temp' but not 'template'
59
+ /lorem ipsum/i,
60
+ /example\.com/i,
61
+ ];
62
+
63
+ // ─── Command Sanitization ────────────────────────────────
64
+
65
+ function sanitizeCommand(command: string): { safe: boolean; reason?: string } {
66
+ for (const pattern of DANGEROUS_PATTERNS) {
67
+ if (pattern.test(command)) {
68
+ return { safe: false, reason: `Matches dangerous pattern: ${pattern.source}` };
69
+ }
70
+ }
71
+ return { safe: true };
72
+ }
73
+
74
+ // ─── Check Execution ─────────────────────────────────────
75
+
76
+ /** Execute a single check command */
77
+ export async function runCheck(
78
+ checkType: GateCheckType,
79
+ command: string,
80
+ projectDir: string,
81
+ timeoutOverride?: number,
82
+ ): Promise<GateCheckResult> {
83
+ const startTime = Date.now();
84
+
85
+ // Sanitize command
86
+ const { safe, reason } = sanitizeCommand(command);
87
+ if (!safe) {
88
+ return {
89
+ check_type: checkType,
90
+ status: 'fail',
91
+ command,
92
+ exit_code: -1,
93
+ stderr_summary: `Command rejected: ${reason}`,
94
+ duration_ms: 0,
95
+ timestamp: new Date().toISOString(),
96
+ };
97
+ }
98
+
99
+ const timeout = timeoutOverride ?? DEFAULT_TIMEOUTS[checkType] ?? 5 * 60 * 1000;
100
+
101
+ return new Promise<GateCheckResult>((resolve) => {
102
+ const proc = exec(command, {
103
+ cwd: projectDir,
104
+ timeout,
105
+ maxBuffer: MAX_OUTPUT_SIZE,
106
+ env: {
107
+ ...process.env,
108
+ NODE_ENV: 'test',
109
+ CI: 'true',
110
+ },
111
+ }, (error, _stdout, stderr) => {
112
+ const duration = Date.now() - startTime;
113
+ const exitCode = error ? (error as NodeJS.ErrnoException & { code?: number }).code ?? 1 : 0;
114
+
115
+ // Truncate output for summary
116
+ const stderrSummary = stderr
117
+ ? stderr.slice(0, 2000) + (stderr.length > 2000 ? '\n... (truncated)' : '')
118
+ : undefined;
119
+
120
+ resolve({
121
+ check_type: checkType,
122
+ status: exitCode === 0 ? 'pass' : 'fail',
123
+ command,
124
+ exit_code: typeof exitCode === 'number' ? exitCode : 1,
125
+ stdout_artifact: undefined, // Filled by storeCheckResults if needed
126
+ stderr_summary: stderrSummary,
127
+ duration_ms: duration,
128
+ timestamp: new Date().toISOString(),
129
+ });
130
+ });
131
+
132
+ // Safety: kill after timeout (backup for exec timeout)
133
+ setTimeout(() => {
134
+ try { proc.kill('SIGTERM'); } catch { /* already dead */ }
135
+ }, timeout + 5000);
136
+ });
137
+ }
138
+
139
+ /** Run all applicable checks based on resolved commands */
140
+ export async function runAllChecks(
141
+ resolvedCommands: ResolvedCommands,
142
+ projectDir: string,
143
+ ): Promise<GateCheckResult[]> {
144
+ const results: GateCheckResult[] = [];
145
+ const checkMap: [GateCheckType, string | undefined][] = [
146
+ ['build', resolvedCommands.build],
147
+ ['test', resolvedCommands.test],
148
+ ['lint', resolvedCommands.lint],
149
+ ['typecheck', resolvedCommands.typecheck],
150
+ ['migration', resolvedCommands.migrations],
151
+ ];
152
+
153
+ for (const [checkType, command] of checkMap) {
154
+ if (!command) {
155
+ results.push({
156
+ check_type: checkType,
157
+ status: 'skip',
158
+ command: '',
159
+ exit_code: 0,
160
+ duration_ms: 0,
161
+ timestamp: new Date().toISOString(),
162
+ });
163
+ continue;
164
+ }
165
+
166
+ const result = await runCheck(checkType, command, projectDir);
167
+ results.push(result);
168
+ }
169
+
170
+ return results;
171
+ }
172
+
173
+ /** Store check results as artifacts */
174
+ export function storeCheckResults(
175
+ results: GateCheckResult[],
176
+ artifactManager: ArtifactManager,
177
+ phase: PipelinePhase,
178
+ ): ArtifactEntry[] {
179
+ const artifacts: ArtifactEntry[] = [];
180
+
181
+ for (const result of results) {
182
+ if (result.status === 'skip') continue;
183
+
184
+ // Only store meaningful output
185
+ const content = JSON.stringify(result, null, 2);
186
+ if (content.length > 100) {
187
+ const entry = artifactManager.createAndStoreJson(
188
+ mapCheckTypeToArtifactType(result.check_type),
189
+ result,
190
+ phase,
191
+ );
192
+ artifacts.push(entry);
193
+ }
194
+ }
195
+
196
+ return artifacts;
197
+ }
198
+
199
+ function mapCheckTypeToArtifactType(
200
+ checkType: GateCheckType,
201
+ ): 'build_check' | 'test_check' | 'lint_check' | 'typecheck_check' | 'placeholder_scan' {
202
+ switch (checkType) {
203
+ case 'build': return 'build_check';
204
+ case 'test': return 'test_check';
205
+ case 'lint': return 'lint_check';
206
+ case 'typecheck': return 'typecheck_check';
207
+ case 'placeholder_scan': return 'placeholder_scan';
208
+ default: return 'build_check';
209
+ }
210
+ }
211
+
212
+ // ─── Placeholder Scanner (P2-2) ──────────────────────────
213
+
214
+ /** Scan project for placeholder/TODO/mock content */
215
+ export function runPlaceholderScan(
216
+ projectDir: string,
217
+ allowlistPath?: string,
218
+ ): GateCheckResult {
219
+ const startTime = Date.now();
220
+ const findings: string[] = [];
221
+
222
+ // Load allowlist if present
223
+ const allowlist = loadAllowlist(
224
+ allowlistPath ?? join(projectDir, '.popeye-placeholder-allowlist'),
225
+ );
226
+
227
+ // Scan source directories
228
+ const scanDirs = ['src', 'app', 'pages', 'components', 'lib', 'server', 'api'];
229
+
230
+ for (const dir of scanDirs) {
231
+ const fullDir = join(projectDir, dir);
232
+ if (!existsSync(fullDir)) continue;
233
+ scanDirForPlaceholders(fullDir, projectDir, allowlist, findings);
234
+ }
235
+
236
+ const duration = Date.now() - startTime;
237
+
238
+ return {
239
+ check_type: 'placeholder_scan',
240
+ status: findings.length > 0 ? 'fail' : 'pass',
241
+ command: 'placeholder-scan',
242
+ exit_code: findings.length > 0 ? 1 : 0,
243
+ stderr_summary: findings.length > 0
244
+ ? `Found ${findings.length} placeholder(s):\n${findings.slice(0, 20).join('\n')}`
245
+ : undefined,
246
+ duration_ms: duration,
247
+ timestamp: new Date().toISOString(),
248
+ };
249
+ }
250
+
251
+ function scanDirForPlaceholders(
252
+ dir: string,
253
+ projectDir: string,
254
+ allowlist: Set<string>,
255
+ findings: string[],
256
+ ): void {
257
+ const codeExts = new Set(['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs']);
258
+
259
+ try {
260
+ const entries = readdirSync(dir, { withFileTypes: true });
261
+ for (const entry of entries) {
262
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
263
+
264
+ const fullPath = join(dir, entry.name);
265
+
266
+ if (entry.isDirectory()) {
267
+ scanDirForPlaceholders(fullPath, projectDir, allowlist, findings);
268
+ } else if (codeExts.has(extname(entry.name))) {
269
+ const relativePath = fullPath.replace(projectDir + '/', '');
270
+ if (allowlist.has(relativePath)) continue;
271
+
272
+ try {
273
+ const content = readFileSync(fullPath, 'utf-8');
274
+ const lines = content.split('\n');
275
+
276
+ for (let i = 0; i < lines.length; i++) {
277
+ for (const pattern of PLACEHOLDER_PATTERNS) {
278
+ if (pattern.test(lines[i])) {
279
+ findings.push(`${relativePath}:${i + 1}: ${lines[i].trim().slice(0, 80)}`);
280
+ break; // One finding per line
281
+ }
282
+ }
283
+ }
284
+ } catch {
285
+ // Skip unreadable files
286
+ }
287
+ }
288
+ }
289
+ } catch {
290
+ // Skip unreadable directories
291
+ }
292
+ }
293
+
294
+ function loadAllowlist(path: string): Set<string> {
295
+ if (!existsSync(path)) return new Set();
296
+ try {
297
+ const content = readFileSync(path, 'utf-8');
298
+ return new Set(
299
+ content.split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#')),
300
+ );
301
+ } catch {
302
+ return new Set();
303
+ }
304
+ }
305
+
306
+ // ─── Start Check (v1.1 Gap #5) ──────────────────────────
307
+
308
+ /**
309
+ * Attempt to start the application and verify it does not crash immediately.
310
+ * Optionally checks a health endpoint if a port is detected.
311
+ *
312
+ * Args:
313
+ * startCommand: The command to start the app (e.g., "npm run start").
314
+ * projectDir: Project root directory.
315
+ * options: Optional port, health path, and timeout.
316
+ *
317
+ * Returns:
318
+ * GateCheckResult with pass/fail status.
319
+ */
320
+ export async function runStartCheck(
321
+ startCommand: string,
322
+ projectDir: string,
323
+ options?: { port?: number; healthPath?: string; timeoutMs?: number },
324
+ ): Promise<GateCheckResult> {
325
+ const startTime = Date.now();
326
+ const timeout = options?.timeoutMs ?? 15000;
327
+
328
+ // Sanitize command
329
+ const { safe, reason } = sanitizeCommand(startCommand);
330
+ if (!safe) {
331
+ return {
332
+ check_type: 'start',
333
+ status: 'fail',
334
+ command: startCommand,
335
+ exit_code: -1,
336
+ stderr_summary: `Command rejected: ${reason}`,
337
+ duration_ms: 0,
338
+ timestamp: new Date().toISOString(),
339
+ };
340
+ }
341
+
342
+ return new Promise<GateCheckResult>((resolve) => {
343
+ let stderr = '';
344
+ let resolved = false;
345
+
346
+ const proc = exec(startCommand, {
347
+ cwd: projectDir,
348
+ timeout: timeout + 5000,
349
+ maxBuffer: MAX_OUTPUT_SIZE,
350
+ env: { ...process.env, NODE_ENV: 'production' },
351
+ }, (error, _stdout, stderrOutput) => {
352
+ if (resolved) return;
353
+ resolved = true;
354
+
355
+ const duration = Date.now() - startTime;
356
+ stderr = stderrOutput ?? '';
357
+
358
+ // Process exited — if it exited within timeout, it crashed
359
+ resolve({
360
+ check_type: 'start',
361
+ status: 'fail',
362
+ command: startCommand,
363
+ exit_code: error ? (typeof (error as NodeJS.ErrnoException & { code?: number }).code === 'number'
364
+ ? (error as NodeJS.ErrnoException & { code?: number }).code!
365
+ : 1) : 0,
366
+ stderr_summary: stderr ? stderr.slice(0, 2000) : 'Process exited prematurely',
367
+ duration_ms: duration,
368
+ timestamp: new Date().toISOString(),
369
+ });
370
+ });
371
+
372
+ // If process survives for the timeout period, consider it passing
373
+ setTimeout(() => {
374
+ if (resolved) return;
375
+ resolved = true;
376
+
377
+ // Kill the process
378
+ try { proc.kill('SIGTERM'); } catch { /* already dead */ }
379
+
380
+ const duration = Date.now() - startTime;
381
+ resolve({
382
+ check_type: 'start',
383
+ status: 'pass',
384
+ command: startCommand,
385
+ exit_code: 0,
386
+ stderr_summary: stderr ? stderr.slice(0, 500) : undefined,
387
+ duration_ms: duration,
388
+ timestamp: new Date().toISOString(),
389
+ });
390
+ }, timeout);
391
+ });
392
+ }
393
+
394
+ // ─── Env Check (v1.1 Gap #5) ────────────────────────────
395
+
396
+ /**
397
+ * Validate that required environment variables exist.
398
+ * Reads .env.example for required var names and checks .env has them set.
399
+ *
400
+ * Args:
401
+ * projectDir: Project root directory.
402
+ * _snapshot: Repo snapshot (for future use).
403
+ *
404
+ * Returns:
405
+ * GateCheckResult with pass/fail status.
406
+ */
407
+ export function runEnvCheck(
408
+ projectDir: string,
409
+ _snapshot?: RepoSnapshot,
410
+ ): GateCheckResult {
411
+ const startTime = Date.now();
412
+ const examplePath = join(projectDir, '.env.example');
413
+ const envPath = join(projectDir, '.env');
414
+ const missingVars: string[] = [];
415
+ const emptyVars: string[] = [];
416
+
417
+ // If no .env.example, skip check
418
+ if (!existsSync(examplePath)) {
419
+ return {
420
+ check_type: 'env_check',
421
+ status: 'pass',
422
+ command: 'env-check',
423
+ exit_code: 0,
424
+ stderr_summary: 'No .env.example found — skipping env validation',
425
+ duration_ms: Date.now() - startTime,
426
+ timestamp: new Date().toISOString(),
427
+ };
428
+ }
429
+
430
+ // Parse .env.example for required var names
431
+ const exampleContent = readFileSync(examplePath, 'utf-8');
432
+ const requiredVars = parseEnvVarNames(exampleContent);
433
+
434
+ // Check .env exists
435
+ if (!existsSync(envPath)) {
436
+ return {
437
+ check_type: 'env_check',
438
+ status: 'fail',
439
+ command: 'env-check',
440
+ exit_code: 1,
441
+ stderr_summary: `.env file not found. Required vars from .env.example: ${requiredVars.join(', ')}`,
442
+ duration_ms: Date.now() - startTime,
443
+ timestamp: new Date().toISOString(),
444
+ };
445
+ }
446
+
447
+ // Parse .env and check all required vars are present and non-empty
448
+ const envContent = readFileSync(envPath, 'utf-8');
449
+ const envVars = parseEnvVarValues(envContent);
450
+
451
+ for (const varName of requiredVars) {
452
+ if (!(varName in envVars)) {
453
+ missingVars.push(varName);
454
+ } else if (!envVars[varName]) {
455
+ emptyVars.push(varName);
456
+ }
457
+ }
458
+
459
+ const duration = Date.now() - startTime;
460
+ const hasFail = missingVars.length > 0;
461
+ const summaryParts: string[] = [];
462
+
463
+ if (missingVars.length > 0) {
464
+ summaryParts.push(`Missing vars: ${missingVars.join(', ')}`);
465
+ }
466
+ if (emptyVars.length > 0) {
467
+ summaryParts.push(`Empty vars (warning): ${emptyVars.join(', ')}`);
468
+ }
469
+
470
+ return {
471
+ check_type: 'env_check',
472
+ status: hasFail ? 'fail' : 'pass',
473
+ command: 'env-check',
474
+ exit_code: hasFail ? 1 : 0,
475
+ stderr_summary: summaryParts.length > 0 ? summaryParts.join('; ') : undefined,
476
+ duration_ms: duration,
477
+ timestamp: new Date().toISOString(),
478
+ };
479
+ }
480
+
481
+ /** Parse env var names from .env.example (lines like KEY=value or KEY=) */
482
+ function parseEnvVarNames(content: string): string[] {
483
+ return content
484
+ .split('\n')
485
+ .map((line) => line.trim())
486
+ .filter((line) => line && !line.startsWith('#'))
487
+ .map((line) => line.split('=')[0].trim())
488
+ .filter((name) => name.length > 0);
489
+ }
490
+
491
+ /** Parse env vars into key-value map from .env content */
492
+ function parseEnvVarValues(content: string): Record<string, string> {
493
+ const vars: Record<string, string> = {};
494
+ for (const line of content.split('\n')) {
495
+ const trimmed = line.trim();
496
+ if (!trimmed || trimmed.startsWith('#')) continue;
497
+ const eqIndex = trimmed.indexOf('=');
498
+ if (eqIndex === -1) continue;
499
+ const key = trimmed.slice(0, eqIndex).trim();
500
+ const value = trimmed.slice(eqIndex + 1).trim().replace(/^["']|["']$/g, '');
501
+ vars[key] = value;
502
+ }
503
+ return vars;
504
+ }