popeye-cli 1.10.0 → 2.1.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 (326) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/CONTRIBUTING.md +38 -3
  3. package/README.md +104 -18
  4. package/dist/adapters/gemini.js +3 -3
  5. package/dist/adapters/openai.js +2 -2
  6. package/dist/adapters/openai.js.map +1 -1
  7. package/dist/auth/gemini.js +1 -1
  8. package/dist/cli/commands/create.d.ts.map +1 -1
  9. package/dist/cli/commands/create.js +11 -5
  10. package/dist/cli/commands/create.js.map +1 -1
  11. package/dist/cli/commands/resume.d.ts.map +1 -1
  12. package/dist/cli/commands/resume.js +9 -1
  13. package/dist/cli/commands/resume.js.map +1 -1
  14. package/dist/cli/interactive.d.ts.map +1 -1
  15. package/dist/cli/interactive.js +29 -3
  16. package/dist/cli/interactive.js.map +1 -1
  17. package/dist/config/defaults.d.ts.map +1 -1
  18. package/dist/config/defaults.js +7 -2
  19. package/dist/config/defaults.js.map +1 -1
  20. package/dist/config/index.d.ts +1 -7
  21. package/dist/config/index.d.ts.map +1 -1
  22. package/dist/config/popeye-md.d.ts +32 -0
  23. package/dist/config/popeye-md.d.ts.map +1 -0
  24. package/dist/config/popeye-md.js +111 -0
  25. package/dist/config/popeye-md.js.map +1 -0
  26. package/dist/config/schema.d.ts +3 -21
  27. package/dist/config/schema.d.ts.map +1 -1
  28. package/dist/config/schema.js +21 -8
  29. package/dist/config/schema.js.map +1 -1
  30. package/dist/pipeline/artifact-manager.d.ts +47 -0
  31. package/dist/pipeline/artifact-manager.d.ts.map +1 -0
  32. package/dist/pipeline/artifact-manager.js +251 -0
  33. package/dist/pipeline/artifact-manager.js.map +1 -0
  34. package/dist/pipeline/artifact-validators.d.ts +29 -0
  35. package/dist/pipeline/artifact-validators.d.ts.map +1 -0
  36. package/dist/pipeline/artifact-validators.js +173 -0
  37. package/dist/pipeline/artifact-validators.js.map +1 -0
  38. package/dist/pipeline/bridges/review-bridge.d.ts +70 -0
  39. package/dist/pipeline/bridges/review-bridge.d.ts.map +1 -0
  40. package/dist/pipeline/bridges/review-bridge.js +266 -0
  41. package/dist/pipeline/bridges/review-bridge.js.map +1 -0
  42. package/dist/pipeline/change-request.d.ts +47 -0
  43. package/dist/pipeline/change-request.d.ts.map +1 -0
  44. package/dist/pipeline/change-request.js +91 -0
  45. package/dist/pipeline/change-request.js.map +1 -0
  46. package/dist/pipeline/check-runner.d.ts +47 -0
  47. package/dist/pipeline/check-runner.d.ts.map +1 -0
  48. package/dist/pipeline/check-runner.js +417 -0
  49. package/dist/pipeline/check-runner.js.map +1 -0
  50. package/dist/pipeline/command-resolver.d.ts +9 -0
  51. package/dist/pipeline/command-resolver.d.ts.map +1 -0
  52. package/dist/pipeline/command-resolver.js +140 -0
  53. package/dist/pipeline/command-resolver.js.map +1 -0
  54. package/dist/pipeline/consensus/consensus-runner.d.ts +44 -0
  55. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -0
  56. package/dist/pipeline/consensus/consensus-runner.js +212 -0
  57. package/dist/pipeline/consensus/consensus-runner.js.map +1 -0
  58. package/dist/pipeline/constitution.d.ts +45 -0
  59. package/dist/pipeline/constitution.d.ts.map +1 -0
  60. package/dist/pipeline/constitution.js +82 -0
  61. package/dist/pipeline/constitution.js.map +1 -0
  62. package/dist/pipeline/gate-engine.d.ts +55 -0
  63. package/dist/pipeline/gate-engine.d.ts.map +1 -0
  64. package/dist/pipeline/gate-engine.js +270 -0
  65. package/dist/pipeline/gate-engine.js.map +1 -0
  66. package/dist/pipeline/index.d.ts +26 -0
  67. package/dist/pipeline/index.d.ts.map +1 -0
  68. package/dist/pipeline/index.js +35 -0
  69. package/dist/pipeline/index.js.map +1 -0
  70. package/dist/pipeline/migration.d.ts +15 -0
  71. package/dist/pipeline/migration.d.ts.map +1 -0
  72. package/dist/pipeline/migration.js +76 -0
  73. package/dist/pipeline/migration.js.map +1 -0
  74. package/dist/pipeline/orchestrator.d.ts +30 -0
  75. package/dist/pipeline/orchestrator.d.ts.map +1 -0
  76. package/dist/pipeline/orchestrator.js +242 -0
  77. package/dist/pipeline/orchestrator.js.map +1 -0
  78. package/dist/pipeline/packets/audit-report-builder.d.ts +11 -0
  79. package/dist/pipeline/packets/audit-report-builder.d.ts.map +1 -0
  80. package/dist/pipeline/packets/audit-report-builder.js +32 -0
  81. package/dist/pipeline/packets/audit-report-builder.js.map +1 -0
  82. package/dist/pipeline/packets/consensus-packet-builder.d.ts +35 -0
  83. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -0
  84. package/dist/pipeline/packets/consensus-packet-builder.js +80 -0
  85. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -0
  86. package/dist/pipeline/packets/index.d.ts +12 -0
  87. package/dist/pipeline/packets/index.d.ts.map +1 -0
  88. package/dist/pipeline/packets/index.js +8 -0
  89. package/dist/pipeline/packets/index.js.map +1 -0
  90. package/dist/pipeline/packets/plan-packet-builder.d.ts +21 -0
  91. package/dist/pipeline/packets/plan-packet-builder.d.ts.map +1 -0
  92. package/dist/pipeline/packets/plan-packet-builder.js +27 -0
  93. package/dist/pipeline/packets/plan-packet-builder.js.map +1 -0
  94. package/dist/pipeline/packets/rca-packet-builder.d.ts +19 -0
  95. package/dist/pipeline/packets/rca-packet-builder.d.ts.map +1 -0
  96. package/dist/pipeline/packets/rca-packet-builder.js +22 -0
  97. package/dist/pipeline/packets/rca-packet-builder.js.map +1 -0
  98. package/dist/pipeline/phases/architecture.d.ts +7 -0
  99. package/dist/pipeline/phases/architecture.d.ts.map +1 -0
  100. package/dist/pipeline/phases/architecture.js +60 -0
  101. package/dist/pipeline/phases/architecture.js.map +1 -0
  102. package/dist/pipeline/phases/audit.d.ts +8 -0
  103. package/dist/pipeline/phases/audit.d.ts.map +1 -0
  104. package/dist/pipeline/phases/audit.js +144 -0
  105. package/dist/pipeline/phases/audit.js.map +1 -0
  106. package/dist/pipeline/phases/consensus-architecture.d.ts +7 -0
  107. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -0
  108. package/dist/pipeline/phases/consensus-architecture.js +84 -0
  109. package/dist/pipeline/phases/consensus-architecture.js.map +1 -0
  110. package/dist/pipeline/phases/consensus-master-plan.d.ts +7 -0
  111. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -0
  112. package/dist/pipeline/phases/consensus-master-plan.js +81 -0
  113. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -0
  114. package/dist/pipeline/phases/consensus-role-plans.d.ts +7 -0
  115. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -0
  116. package/dist/pipeline/phases/consensus-role-plans.js +85 -0
  117. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -0
  118. package/dist/pipeline/phases/done.d.ts +7 -0
  119. package/dist/pipeline/phases/done.d.ts.map +1 -0
  120. package/dist/pipeline/phases/done.js +45 -0
  121. package/dist/pipeline/phases/done.js.map +1 -0
  122. package/dist/pipeline/phases/implementation.d.ts +8 -0
  123. package/dist/pipeline/phases/implementation.d.ts.map +1 -0
  124. package/dist/pipeline/phases/implementation.js +45 -0
  125. package/dist/pipeline/phases/implementation.js.map +1 -0
  126. package/dist/pipeline/phases/index.d.ts +20 -0
  127. package/dist/pipeline/phases/index.d.ts.map +1 -0
  128. package/dist/pipeline/phases/index.js +19 -0
  129. package/dist/pipeline/phases/index.js.map +1 -0
  130. package/dist/pipeline/phases/intake.d.ts +8 -0
  131. package/dist/pipeline/phases/intake.d.ts.map +1 -0
  132. package/dist/pipeline/phases/intake.js +49 -0
  133. package/dist/pipeline/phases/intake.js.map +1 -0
  134. package/dist/pipeline/phases/phase-context.d.ts +30 -0
  135. package/dist/pipeline/phases/phase-context.d.ts.map +1 -0
  136. package/dist/pipeline/phases/phase-context.js +33 -0
  137. package/dist/pipeline/phases/phase-context.js.map +1 -0
  138. package/dist/pipeline/phases/production-gate.d.ts +8 -0
  139. package/dist/pipeline/phases/production-gate.d.ts.map +1 -0
  140. package/dist/pipeline/phases/production-gate.js +84 -0
  141. package/dist/pipeline/phases/production-gate.js.map +1 -0
  142. package/dist/pipeline/phases/qa-validation.d.ts +7 -0
  143. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -0
  144. package/dist/pipeline/phases/qa-validation.js +50 -0
  145. package/dist/pipeline/phases/qa-validation.js.map +1 -0
  146. package/dist/pipeline/phases/recovery-loop.d.ts +7 -0
  147. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -0
  148. package/dist/pipeline/phases/recovery-loop.js +93 -0
  149. package/dist/pipeline/phases/recovery-loop.js.map +1 -0
  150. package/dist/pipeline/phases/review.d.ts +8 -0
  151. package/dist/pipeline/phases/review.d.ts.map +1 -0
  152. package/dist/pipeline/phases/review.js +127 -0
  153. package/dist/pipeline/phases/review.js.map +1 -0
  154. package/dist/pipeline/phases/role-planning.d.ts +7 -0
  155. package/dist/pipeline/phases/role-planning.d.ts.map +1 -0
  156. package/dist/pipeline/phases/role-planning.js +75 -0
  157. package/dist/pipeline/phases/role-planning.js.map +1 -0
  158. package/dist/pipeline/phases/stuck.d.ts +7 -0
  159. package/dist/pipeline/phases/stuck.d.ts.map +1 -0
  160. package/dist/pipeline/phases/stuck.js +51 -0
  161. package/dist/pipeline/phases/stuck.js.map +1 -0
  162. package/dist/pipeline/repo-snapshot.d.ts +24 -0
  163. package/dist/pipeline/repo-snapshot.d.ts.map +1 -0
  164. package/dist/pipeline/repo-snapshot.js +343 -0
  165. package/dist/pipeline/repo-snapshot.js.map +1 -0
  166. package/dist/pipeline/role-execution-adapter.d.ts +59 -0
  167. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -0
  168. package/dist/pipeline/role-execution-adapter.js +159 -0
  169. package/dist/pipeline/role-execution-adapter.js.map +1 -0
  170. package/dist/pipeline/skill-loader.d.ts +34 -0
  171. package/dist/pipeline/skill-loader.d.ts.map +1 -0
  172. package/dist/pipeline/skill-loader.js +156 -0
  173. package/dist/pipeline/skill-loader.js.map +1 -0
  174. package/dist/pipeline/skills/defaults.d.ts +16 -0
  175. package/dist/pipeline/skills/defaults.d.ts.map +1 -0
  176. package/dist/pipeline/skills/defaults.js +189 -0
  177. package/dist/pipeline/skills/defaults.js.map +1 -0
  178. package/dist/pipeline/type-defs/artifacts.d.ts +207 -0
  179. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -0
  180. package/dist/pipeline/type-defs/artifacts.js +67 -0
  181. package/dist/pipeline/type-defs/artifacts.js.map +1 -0
  182. package/dist/pipeline/type-defs/audit.d.ts +259 -0
  183. package/dist/pipeline/type-defs/audit.d.ts.map +1 -0
  184. package/dist/pipeline/type-defs/audit.js +54 -0
  185. package/dist/pipeline/type-defs/audit.js.map +1 -0
  186. package/dist/pipeline/type-defs/checks.d.ts +82 -0
  187. package/dist/pipeline/type-defs/checks.d.ts.map +1 -0
  188. package/dist/pipeline/type-defs/checks.js +38 -0
  189. package/dist/pipeline/type-defs/checks.js.map +1 -0
  190. package/dist/pipeline/type-defs/enums.d.ts +43 -0
  191. package/dist/pipeline/type-defs/enums.d.ts.map +1 -0
  192. package/dist/pipeline/type-defs/enums.js +55 -0
  193. package/dist/pipeline/type-defs/enums.js.map +1 -0
  194. package/dist/pipeline/type-defs/index.d.ts +12 -0
  195. package/dist/pipeline/type-defs/index.d.ts.map +1 -0
  196. package/dist/pipeline/type-defs/index.js +12 -0
  197. package/dist/pipeline/type-defs/index.js.map +1 -0
  198. package/dist/pipeline/type-defs/packets.d.ts +821 -0
  199. package/dist/pipeline/type-defs/packets.d.ts.map +1 -0
  200. package/dist/pipeline/type-defs/packets.js +109 -0
  201. package/dist/pipeline/type-defs/packets.js.map +1 -0
  202. package/dist/pipeline/type-defs/snapshot.d.ts +52 -0
  203. package/dist/pipeline/type-defs/snapshot.d.ts.map +1 -0
  204. package/dist/pipeline/type-defs/snapshot.js +35 -0
  205. package/dist/pipeline/type-defs/snapshot.js.map +1 -0
  206. package/dist/pipeline/type-defs/state.d.ts +455 -0
  207. package/dist/pipeline/type-defs/state.d.ts.map +1 -0
  208. package/dist/pipeline/type-defs/state.js +90 -0
  209. package/dist/pipeline/type-defs/state.js.map +1 -0
  210. package/dist/pipeline/types.d.ts +16 -0
  211. package/dist/pipeline/types.d.ts.map +1 -0
  212. package/dist/pipeline/types.js +16 -0
  213. package/dist/pipeline/types.js.map +1 -0
  214. package/dist/types/audit.d.ts +6 -6
  215. package/dist/types/consensus.d.ts +5 -1
  216. package/dist/types/consensus.d.ts.map +1 -1
  217. package/dist/types/consensus.js +15 -4
  218. package/dist/types/consensus.js.map +1 -1
  219. package/dist/types/index.d.ts +1 -1
  220. package/dist/types/index.d.ts.map +1 -1
  221. package/dist/types/index.js +1 -1
  222. package/dist/types/index.js.map +1 -1
  223. package/dist/types/project.d.ts +1 -1
  224. package/dist/types/project.d.ts.map +1 -1
  225. package/dist/types/project.js +39 -10
  226. package/dist/types/project.js.map +1 -1
  227. package/dist/types/workflow.d.ts +1 -7
  228. package/dist/types/workflow.d.ts.map +1 -1
  229. package/dist/types/workflow.js +1 -1
  230. package/dist/types/workflow.js.map +1 -1
  231. package/dist/upgrade/handlers.js +5 -5
  232. package/dist/upgrade/handlers.js.map +1 -1
  233. package/dist/workflow/index.d.ts.map +1 -1
  234. package/dist/workflow/index.js +52 -0
  235. package/dist/workflow/index.js.map +1 -1
  236. package/dist/workflow/website-strategy.js +1 -1
  237. package/dist/workflow/website-strategy.js.map +1 -1
  238. package/package.json +1 -1
  239. package/skills/PHASE_GATE_ENGINE_SPEC.md +113 -20
  240. package/skills/POPEYE_FULL_AUTONOMY_PIPELINE.md +66 -13
  241. package/src/adapters/gemini.ts +3 -3
  242. package/src/adapters/openai.ts +2 -2
  243. package/src/auth/gemini.ts +1 -1
  244. package/src/cli/commands/create.ts +12 -6
  245. package/src/cli/commands/resume.ts +9 -1
  246. package/src/cli/interactive.ts +32 -3
  247. package/src/config/defaults.ts +7 -2
  248. package/src/config/popeye-md.ts +139 -0
  249. package/src/config/schema.ts +21 -8
  250. package/src/pipeline/artifact-manager.ts +339 -0
  251. package/src/pipeline/artifact-validators.ts +224 -0
  252. package/src/pipeline/bridges/review-bridge.ts +371 -0
  253. package/src/pipeline/change-request.ts +119 -0
  254. package/src/pipeline/check-runner.ts +504 -0
  255. package/src/pipeline/command-resolver.ts +168 -0
  256. package/src/pipeline/consensus/consensus-runner.ts +317 -0
  257. package/src/pipeline/constitution.ts +109 -0
  258. package/src/pipeline/gate-engine.ts +347 -0
  259. package/src/pipeline/index.ts +82 -0
  260. package/src/pipeline/migration.ts +91 -0
  261. package/src/pipeline/orchestrator.ts +322 -0
  262. package/src/pipeline/packets/audit-report-builder.ts +47 -0
  263. package/src/pipeline/packets/consensus-packet-builder.ts +112 -0
  264. package/src/pipeline/packets/index.ts +15 -0
  265. package/src/pipeline/packets/plan-packet-builder.ts +52 -0
  266. package/src/pipeline/packets/rca-packet-builder.ts +38 -0
  267. package/src/pipeline/phases/architecture.ts +73 -0
  268. package/src/pipeline/phases/audit.ts +193 -0
  269. package/src/pipeline/phases/consensus-architecture.ts +104 -0
  270. package/src/pipeline/phases/consensus-master-plan.ts +100 -0
  271. package/src/pipeline/phases/consensus-role-plans.ts +105 -0
  272. package/src/pipeline/phases/done.ts +68 -0
  273. package/src/pipeline/phases/implementation.ts +52 -0
  274. package/src/pipeline/phases/index.ts +21 -0
  275. package/src/pipeline/phases/intake.ts +68 -0
  276. package/src/pipeline/phases/phase-context.ts +86 -0
  277. package/src/pipeline/phases/production-gate.ts +113 -0
  278. package/src/pipeline/phases/qa-validation.ts +63 -0
  279. package/src/pipeline/phases/recovery-loop.ts +120 -0
  280. package/src/pipeline/phases/review.ts +149 -0
  281. package/src/pipeline/phases/role-planning.ts +92 -0
  282. package/src/pipeline/phases/stuck.ts +62 -0
  283. package/src/pipeline/repo-snapshot.ts +395 -0
  284. package/src/pipeline/role-execution-adapter.ts +238 -0
  285. package/src/pipeline/skill-loader.ts +192 -0
  286. package/src/pipeline/skills/defaults.ts +215 -0
  287. package/src/pipeline/type-defs/artifacts.ts +82 -0
  288. package/src/pipeline/type-defs/audit.ts +67 -0
  289. package/src/pipeline/type-defs/checks.ts +47 -0
  290. package/src/pipeline/type-defs/enums.ts +62 -0
  291. package/src/pipeline/type-defs/index.ts +12 -0
  292. package/src/pipeline/type-defs/packets.ts +131 -0
  293. package/src/pipeline/type-defs/snapshot.ts +55 -0
  294. package/src/pipeline/type-defs/state.ts +167 -0
  295. package/src/pipeline/types.ts +16 -0
  296. package/src/types/consensus.ts +16 -4
  297. package/src/types/index.ts +1 -0
  298. package/src/types/project.ts +39 -10
  299. package/src/types/workflow.ts +1 -1
  300. package/src/upgrade/handlers.ts +5 -5
  301. package/src/workflow/index.ts +52 -0
  302. package/src/workflow/website-strategy.ts +1 -1
  303. package/tests/cli/model-command.test.ts +19 -9
  304. package/tests/config/config.test.ts +3 -3
  305. package/tests/config/popeye-md.test.ts +168 -0
  306. package/tests/pipeline/artifact-manager.test.ts +183 -0
  307. package/tests/pipeline/artifact-validators.test.ts +207 -0
  308. package/tests/pipeline/bridges/review-bridge.test.ts +243 -0
  309. package/tests/pipeline/change-request.test.ts +180 -0
  310. package/tests/pipeline/check-runner.test.ts +157 -0
  311. package/tests/pipeline/command-resolver.test.ts +159 -0
  312. package/tests/pipeline/consensus-runner.test.ts +206 -0
  313. package/tests/pipeline/consensus-scoring.test.ts +163 -0
  314. package/tests/pipeline/constitution.test.ts +122 -0
  315. package/tests/pipeline/gate-engine.test.ts +195 -0
  316. package/tests/pipeline/migration.test.ts +133 -0
  317. package/tests/pipeline/orchestrator.test.ts +614 -0
  318. package/tests/pipeline/packets/builders.test.ts +347 -0
  319. package/tests/pipeline/repo-snapshot.test.ts +189 -0
  320. package/tests/pipeline/role-execution-adapter.test.ts +299 -0
  321. package/tests/pipeline/session-guidance.test.ts +205 -0
  322. package/tests/pipeline/skill-loader.test.ts +186 -0
  323. package/tests/pipeline/start-env-checks.test.ts +123 -0
  324. package/tests/pipeline/types.test.ts +156 -0
  325. package/tests/types/consensus.test.ts +1 -1
  326. package/tests/workflow/pipeline-bootstrap.test.ts +162 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * ROLE_PLANNING phase — produce deterministic implementation plans by role.
3
+ * Runs per-role: DB, BE, FE, Website, QA. Creates role_plan artifacts.
4
+ */
5
+
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+
9
+ import type { PipelineRole } from '../types.js';
10
+ import type { PhaseContext, PhaseResult } from './phase-context.js';
11
+ import { successResult, failureResult } from './phase-context.js';
12
+
13
+ /** Roles that produce implementation plans */
14
+ const PLANNING_ROLES: PipelineRole[] = [
15
+ 'DB_EXPERT',
16
+ 'BACKEND_PROGRAMMER',
17
+ 'FRONTEND_PROGRAMMER',
18
+ 'WEBSITE_PROGRAMMER',
19
+ 'QA_TESTER',
20
+ ];
21
+
22
+ export async function runRolePlanning(context: PhaseContext): Promise<PhaseResult> {
23
+ const { pipeline, artifactManager, skillLoader, projectDir } = context;
24
+ const artifacts = [];
25
+
26
+ try {
27
+ // Read architecture doc for context
28
+ const archArtifact = pipeline.artifacts.find((a) => a.type === 'architecture');
29
+ let architectureContent = '';
30
+ if (archArtifact) {
31
+ const fullPath = join(projectDir, archArtifact.path);
32
+ if (existsSync(fullPath)) {
33
+ architectureContent = readFileSync(fullPath, 'utf-8');
34
+ }
35
+ }
36
+
37
+ // Read master plan for context
38
+ const masterPlanArtifact = pipeline.artifacts.find((a) => a.type === 'master_plan');
39
+ let masterPlanContent = '';
40
+ if (masterPlanArtifact) {
41
+ const fullPath = join(projectDir, masterPlanArtifact.path);
42
+ if (existsSync(fullPath)) {
43
+ masterPlanContent = readFileSync(fullPath, 'utf-8');
44
+ }
45
+ }
46
+
47
+ const { executePrompt } = await import('../../adapters/claude.js');
48
+
49
+ // Generate plan for each role
50
+ for (const role of PLANNING_ROLES) {
51
+ // Skip website if not in active roles
52
+ if (role === 'WEBSITE_PROGRAMMER' && !pipeline.activeRoles.includes('WEBSITE_PROGRAMMER')) {
53
+ continue;
54
+ }
55
+
56
+ const skill = skillLoader.loadSkill(role);
57
+
58
+ const planPrompt = [
59
+ skill.systemPrompt,
60
+ '',
61
+ '## Master Plan',
62
+ masterPlanContent.slice(0, 5000),
63
+ '',
64
+ '## Architecture',
65
+ architectureContent.slice(0, 5000),
66
+ '',
67
+ '## Instructions',
68
+ `Create your ${role} implementation plan. Include:`,
69
+ '- Deterministic file-level outputs',
70
+ '- Specific tasks with acceptance criteria',
71
+ '- Dependencies on other roles',
72
+ '- Test requirements',
73
+ ].join('\n');
74
+
75
+ const planResult = await executePrompt(planPrompt);
76
+ const plan = planResult.response;
77
+
78
+ const entry = artifactManager.createAndStoreText(
79
+ 'role_plan',
80
+ `# ${role} Plan\n\n${plan}`,
81
+ 'ROLE_PLANNING',
82
+ );
83
+ artifacts.push(entry);
84
+ }
85
+
86
+ pipeline.artifacts.push(...artifacts);
87
+ return successResult('ROLE_PLANNING', artifacts, `Created ${artifacts.length} role plans`);
88
+ } catch (err) {
89
+ const message = err instanceof Error ? err.message : 'Unknown error';
90
+ return failureResult('ROLE_PLANNING', 'Role planning failed', message);
91
+ }
92
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * STUCK phase — safety valve when max recovery iterations exceeded.
3
+ * Produces stuck report with last RCA, suspected paths, required human input.
4
+ */
5
+
6
+ import type { PhaseContext, PhaseResult } from './phase-context.js';
7
+ import { successResult, failureResult } from './phase-context.js';
8
+
9
+ export async function runStuck(context: PhaseContext): Promise<PhaseResult> {
10
+ const { pipeline, artifactManager } = context;
11
+ const artifacts = [];
12
+
13
+ try {
14
+ // Find last RCA
15
+ const lastRca = pipeline.artifacts
16
+ .filter((a) => a.type === 'rca_report')
17
+ .sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
18
+
19
+ const stuckReport = [
20
+ '# Stuck Report',
21
+ '',
22
+ `**Timestamp:** ${new Date().toISOString()}`,
23
+ `**Recovery Iterations:** ${pipeline.recoveryCount} / ${pipeline.maxRecoveryIterations}`,
24
+ `**Failed Phase:** ${pipeline.failedPhase ?? 'unknown'}`,
25
+ '',
26
+ '## Last RCA',
27
+ lastRca ? `See: ${lastRca.path}` : 'No RCA available',
28
+ '',
29
+ '## Suspected Resolution Paths',
30
+ '1. Review the last RCA report for root cause details',
31
+ '2. Check the failing gate conditions and resolve blockers manually',
32
+ '3. Consider reverting to a known good state and re-running',
33
+ '',
34
+ '## Required Human Input',
35
+ '- Review failing gate conditions',
36
+ '- Determine if scope changes are needed',
37
+ '- Decide whether to restart pipeline from a specific phase',
38
+ '',
39
+ '## Artifacts That May Need Update',
40
+ ...pipeline.artifacts
41
+ .filter((a) => a.phase === pipeline.failedPhase)
42
+ .map((a) => `- ${a.type}: ${a.path}`),
43
+ ].join('\n');
44
+
45
+ const stuckEntry = artifactManager.createAndStoreText(
46
+ 'stuck_report',
47
+ stuckReport,
48
+ 'STUCK',
49
+ );
50
+ artifacts.push(stuckEntry);
51
+
52
+ pipeline.artifacts.push(...artifacts);
53
+
54
+ // Update INDEX
55
+ artifactManager.updateIndex(pipeline.artifacts);
56
+
57
+ return successResult('STUCK', artifacts, 'Pipeline STUCK. Human intervention required.');
58
+ } catch (err) {
59
+ const message = err instanceof Error ? err.message : 'Unknown error';
60
+ return failureResult('STUCK', 'Stuck report generation failed', message);
61
+ }
62
+ }
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Repo Snapshot Generator — deterministic project state capture.
3
+ * Generated before every consensus gate for anti-hallucination.
4
+ * Supports caching via mtime for performance (P2-H).
5
+ */
6
+
7
+ import { createHash } from 'node:crypto';
8
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
9
+ import { join, extname } from 'node:path';
10
+
11
+ import type {
12
+ RepoSnapshot,
13
+ ConfigFileEntry,
14
+ PortEntry,
15
+ } from './types.js';
16
+ import type { ArtifactEntry } from './types.js';
17
+ import { ArtifactManager } from './artifact-manager.js';
18
+ import type { PipelinePhase } from './types.js';
19
+
20
+ // ─── Constants ───────────────────────────────────────────
21
+
22
+ const EXCLUDE_DIRS = new Set([
23
+ 'node_modules', '.git', 'dist', '__pycache__', '.next',
24
+ '.nuxt', 'build', 'coverage', '.turbo', '.cache',
25
+ '.venv', 'venv', 'env',
26
+ ]);
27
+
28
+ const CONFIG_FILES = new Set([
29
+ 'package.json', 'pyproject.toml', 'docker-compose.yml',
30
+ 'docker-compose.yaml', 'Dockerfile', 'tsconfig.json',
31
+ 'vite.config.ts', 'vite.config.js', 'next.config.js',
32
+ 'next.config.mjs', 'next.config.ts', 'webpack.config.js',
33
+ 'tailwind.config.ts', 'tailwind.config.js',
34
+ 'jest.config.ts', 'jest.config.js', 'vitest.config.ts',
35
+ 'vitest.config.js', '.eslintrc.js', '.eslintrc.json',
36
+ 'eslint.config.js', 'eslint.config.mjs',
37
+ 'prisma/schema.prisma', 'alembic.ini',
38
+ 'requirements.txt', 'setup.py', 'setup.cfg',
39
+ 'Makefile', 'Procfile',
40
+ ]);
41
+
42
+ const CODE_EXTENSIONS = new Set([
43
+ '.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs',
44
+ '.java', '.rb', '.php', '.vue', '.svelte', '.astro',
45
+ '.css', '.scss', '.html', '.sql', '.prisma',
46
+ ]);
47
+
48
+ // ─── Line Count Cache ────────────────────────────────────
49
+
50
+ interface LineCacheEntry {
51
+ mtime: number;
52
+ lines: number;
53
+ }
54
+
55
+ const lineCache = new Map<string, LineCacheEntry>();
56
+
57
+ function countLinesWithCache(filePath: string): number {
58
+ try {
59
+ const stat = statSync(filePath);
60
+ const cached = lineCache.get(filePath);
61
+ if (cached && cached.mtime === stat.mtimeMs) {
62
+ return cached.lines;
63
+ }
64
+ const content = readFileSync(filePath, 'utf-8');
65
+ const lines = content.split('\n').length;
66
+ lineCache.set(filePath, { mtime: stat.mtimeMs, lines });
67
+ return lines;
68
+ } catch {
69
+ return 0;
70
+ }
71
+ }
72
+
73
+ // ─── Snapshot Generation ─────────────────────────────────
74
+
75
+ /** Generate a deterministic snapshot of the project */
76
+ export async function generateRepoSnapshot(projectDir: string): Promise<RepoSnapshot> {
77
+ const snapshotId = createHash('sha256')
78
+ .update(`${projectDir}-${Date.now()}`)
79
+ .digest('hex')
80
+ .slice(0, 16);
81
+
82
+ const treeSummary = buildTreeSummary(projectDir, '', 3);
83
+ const configFiles = scanConfigFiles(projectDir);
84
+ const languagesDetected = detectLanguages(projectDir);
85
+ const envFiles = findEnvFiles(projectDir);
86
+ const migrationsPresent = detectMigrations(projectDir);
87
+ const portsEntrypoints = detectPorts(configFiles);
88
+ const { totalFiles, totalLines } = countFilesAndLines(projectDir);
89
+
90
+ // Extract key info from config files
91
+ const packageManager = detectPackageManager(projectDir, configFiles);
92
+ const scripts = extractScripts(configFiles);
93
+ const testFramework = detectTestFramework(configFiles, scripts);
94
+ const buildTool = detectBuildTool(configFiles, scripts);
95
+
96
+ return {
97
+ snapshot_id: snapshotId,
98
+ timestamp: new Date().toISOString(),
99
+ tree_summary: treeSummary,
100
+ config_files: configFiles,
101
+ languages_detected: languagesDetected,
102
+ package_manager: packageManager,
103
+ scripts,
104
+ test_framework: testFramework,
105
+ build_tool: buildTool,
106
+ env_files: envFiles,
107
+ migrations_present: migrationsPresent,
108
+ ports_entrypoints: portsEntrypoints,
109
+ total_files: totalFiles,
110
+ total_lines: totalLines,
111
+ };
112
+ }
113
+
114
+ /** Store snapshot as an artifact */
115
+ export function createSnapshotArtifact(
116
+ snapshot: RepoSnapshot,
117
+ artifactManager: ArtifactManager,
118
+ phase: PipelinePhase,
119
+ ): ArtifactEntry {
120
+ return artifactManager.createAndStoreJson('repo_snapshot', snapshot, phase);
121
+ }
122
+
123
+ /** Compare two snapshots to detect drift */
124
+ export function diffSnapshots(
125
+ before: RepoSnapshot,
126
+ after: RepoSnapshot,
127
+ ): SnapshotDiff {
128
+ const addedConfigs = after.config_files
129
+ .filter((c) => !before.config_files.some((b) => b.path === c.path));
130
+ const removedConfigs = before.config_files
131
+ .filter((c) => !after.config_files.some((a) => a.path === c.path));
132
+ const changedConfigs = after.config_files.filter((ac) => {
133
+ const bc = before.config_files.find((b) => b.path === ac.path);
134
+ return bc && bc.content_hash !== ac.content_hash;
135
+ });
136
+
137
+ const filesChanged = after.total_files !== before.total_files;
138
+ const linesChanged = after.total_lines !== before.total_lines;
139
+
140
+ return {
141
+ added_configs: addedConfigs.map((c) => c.path),
142
+ removed_configs: removedConfigs.map((c) => c.path),
143
+ changed_configs: changedConfigs.map((c) => c.path),
144
+ files_delta: after.total_files - before.total_files,
145
+ lines_delta: after.total_lines - before.total_lines,
146
+ has_changes: filesChanged || linesChanged || changedConfigs.length > 0
147
+ || addedConfigs.length > 0 || removedConfigs.length > 0,
148
+ };
149
+ }
150
+
151
+ export interface SnapshotDiff {
152
+ added_configs: string[];
153
+ removed_configs: string[];
154
+ changed_configs: string[];
155
+ files_delta: number;
156
+ lines_delta: number;
157
+ has_changes: boolean;
158
+ }
159
+
160
+ // ─── Internal Helpers ────────────────────────────────────
161
+
162
+ function buildTreeSummary(dir: string, prefix: string, maxDepth: number): string {
163
+ if (maxDepth <= 0) return '';
164
+
165
+ const lines: string[] = [];
166
+ try {
167
+ const entries = readdirSync(dir, { withFileTypes: true })
168
+ .filter((e) => !EXCLUDE_DIRS.has(e.name) && !e.name.startsWith('.'));
169
+
170
+ for (const entry of entries.slice(0, 50)) {
171
+ const marker = entry.isDirectory() ? '/' : '';
172
+ lines.push(`${prefix}${entry.name}${marker}`);
173
+
174
+ if (entry.isDirectory() && maxDepth > 1) {
175
+ const sub = buildTreeSummary(join(dir, entry.name), prefix + ' ', maxDepth - 1);
176
+ if (sub) lines.push(sub);
177
+ }
178
+ }
179
+
180
+ if (entries.length > 50) {
181
+ lines.push(`${prefix}... (+${entries.length - 50} more)`);
182
+ }
183
+ } catch {
184
+ // Directory unreadable
185
+ }
186
+
187
+ return lines.join('\n');
188
+ }
189
+
190
+ function scanConfigFiles(projectDir: string): ConfigFileEntry[] {
191
+ const configs: ConfigFileEntry[] = [];
192
+
193
+ for (const configName of CONFIG_FILES) {
194
+ const fullPath = join(projectDir, configName);
195
+ if (!existsSync(fullPath)) continue;
196
+
197
+ try {
198
+ const content = readFileSync(fullPath, 'utf-8');
199
+ const contentHash = createHash('sha256').update(content).digest('hex').slice(0, 16);
200
+ const keyFields = extractKeyFields(configName, content);
201
+
202
+ configs.push({
203
+ path: configName,
204
+ type: configName,
205
+ content_hash: contentHash,
206
+ key_fields: keyFields,
207
+ });
208
+ } catch {
209
+ // Skip unreadable config files
210
+ }
211
+ }
212
+
213
+ return configs;
214
+ }
215
+
216
+ function extractKeyFields(configName: string, content: string): Record<string, unknown> {
217
+ try {
218
+ if (configName === 'package.json') {
219
+ const pkg = JSON.parse(content);
220
+ return {
221
+ name: pkg.name,
222
+ version: pkg.version,
223
+ scripts: pkg.scripts,
224
+ dependencies: pkg.dependencies ? Object.keys(pkg.dependencies) : [],
225
+ devDependencies: pkg.devDependencies ? Object.keys(pkg.devDependencies) : [],
226
+ };
227
+ }
228
+
229
+ if (configName === 'tsconfig.json') {
230
+ const ts = JSON.parse(content);
231
+ return {
232
+ target: ts.compilerOptions?.target,
233
+ module: ts.compilerOptions?.module,
234
+ outDir: ts.compilerOptions?.outDir,
235
+ };
236
+ }
237
+ } catch {
238
+ // Not JSON parseable, return empty
239
+ }
240
+
241
+ return {};
242
+ }
243
+
244
+ function detectLanguages(projectDir: string): string[] {
245
+ const languages = new Set<string>();
246
+ const extensionMap: Record<string, string> = {
247
+ '.ts': 'typescript', '.tsx': 'typescript',
248
+ '.js': 'javascript', '.jsx': 'javascript',
249
+ '.py': 'python', '.go': 'go', '.rs': 'rust',
250
+ '.java': 'java', '.rb': 'ruby', '.php': 'php',
251
+ };
252
+
253
+ function scan(dir: string, depth: number): void {
254
+ if (depth > 3) return;
255
+ try {
256
+ const entries = readdirSync(dir, { withFileTypes: true });
257
+ for (const entry of entries) {
258
+ if (EXCLUDE_DIRS.has(entry.name)) continue;
259
+ if (entry.isDirectory()) {
260
+ scan(join(dir, entry.name), depth + 1);
261
+ } else {
262
+ const ext = extname(entry.name);
263
+ if (extensionMap[ext]) languages.add(extensionMap[ext]);
264
+ }
265
+ }
266
+ } catch {
267
+ // Skip unreadable
268
+ }
269
+ }
270
+
271
+ scan(projectDir, 0);
272
+ return Array.from(languages).sort();
273
+ }
274
+
275
+ function findEnvFiles(projectDir: string): string[] {
276
+ try {
277
+ return readdirSync(projectDir)
278
+ .filter((f) => f.startsWith('.env'))
279
+ .sort();
280
+ } catch {
281
+ return [];
282
+ }
283
+ }
284
+
285
+ function detectMigrations(projectDir: string): boolean {
286
+ const migrationDirs = ['migrations', 'prisma/migrations', 'alembic/versions', 'db/migrate'];
287
+ return migrationDirs.some((d) => existsSync(join(projectDir, d)));
288
+ }
289
+
290
+ function detectPorts(configFiles: ConfigFileEntry[]): PortEntry[] {
291
+ const ports: PortEntry[] = [];
292
+
293
+ for (const config of configFiles) {
294
+ if (config.type === 'docker-compose.yml' || config.type === 'docker-compose.yaml') {
295
+ // Basic port detection from docker-compose key_fields
296
+ // Full parsing would need YAML, keeping it simple
297
+ ports.push({ port: 3000, service: 'app', source: config.path });
298
+ }
299
+ }
300
+
301
+ // Check package.json scripts for port hints
302
+ const pkg = configFiles.find((c) => c.type === 'package.json');
303
+ if (pkg) {
304
+ const scripts = (pkg.key_fields as Record<string, unknown>).scripts;
305
+ if (scripts && typeof scripts === 'object') {
306
+ const startScript = (scripts as Record<string, string>).start ?? '';
307
+ const portMatch = startScript.match(/(?:PORT|port)[=: ]?(\d+)/);
308
+ if (portMatch) {
309
+ ports.push({ port: parseInt(portMatch[1], 10), service: 'start', source: 'package.json' });
310
+ }
311
+ }
312
+ }
313
+
314
+ return ports;
315
+ }
316
+
317
+ function detectPackageManager(
318
+ projectDir: string,
319
+ _configFiles: ConfigFileEntry[],
320
+ ): string | undefined {
321
+ if (existsSync(join(projectDir, 'pnpm-lock.yaml'))) return 'pnpm';
322
+ if (existsSync(join(projectDir, 'yarn.lock'))) return 'yarn';
323
+ if (existsSync(join(projectDir, 'bun.lockb'))) return 'bun';
324
+ if (existsSync(join(projectDir, 'package-lock.json'))) return 'npm';
325
+ if (existsSync(join(projectDir, 'requirements.txt'))) return 'pip';
326
+ if (existsSync(join(projectDir, 'pyproject.toml'))) return 'poetry';
327
+ return undefined;
328
+ }
329
+
330
+ function extractScripts(configFiles: ConfigFileEntry[]): Record<string, string> {
331
+ const pkg = configFiles.find((c) => c.type === 'package.json');
332
+ if (!pkg) return {};
333
+ const scripts = (pkg.key_fields as Record<string, unknown>).scripts;
334
+ if (!scripts || typeof scripts !== 'object') return {};
335
+ return scripts as Record<string, string>;
336
+ }
337
+
338
+ function detectTestFramework(
339
+ configFiles: ConfigFileEntry[],
340
+ scripts: Record<string, string>,
341
+ ): string | undefined {
342
+ const testScript = scripts.test ?? '';
343
+ if (testScript.includes('vitest')) return 'vitest';
344
+ if (testScript.includes('jest')) return 'jest';
345
+ if (testScript.includes('pytest')) return 'pytest';
346
+ if (testScript.includes('mocha')) return 'mocha';
347
+
348
+ if (configFiles.some((c) => c.type.startsWith('vitest.config'))) return 'vitest';
349
+ if (configFiles.some((c) => c.type.startsWith('jest.config'))) return 'jest';
350
+ return undefined;
351
+ }
352
+
353
+ function detectBuildTool(
354
+ configFiles: ConfigFileEntry[],
355
+ scripts: Record<string, string>,
356
+ ): string | undefined {
357
+ const buildScript = scripts.build ?? '';
358
+ if (buildScript.includes('tsc')) return 'tsc';
359
+ if (buildScript.includes('vite')) return 'vite';
360
+ if (buildScript.includes('webpack')) return 'webpack';
361
+ if (buildScript.includes('next')) return 'next';
362
+ if (buildScript.includes('turbo')) return 'turbo';
363
+
364
+ if (configFiles.some((c) => c.type.startsWith('vite.config'))) return 'vite';
365
+ if (configFiles.some((c) => c.type.startsWith('next.config'))) return 'next';
366
+ return undefined;
367
+ }
368
+
369
+ function countFilesAndLines(projectDir: string): { totalFiles: number; totalLines: number } {
370
+ let totalFiles = 0;
371
+ let totalLines = 0;
372
+
373
+ function walk(dir: string, depth: number): void {
374
+ if (depth > 8) return;
375
+ try {
376
+ const entries = readdirSync(dir, { withFileTypes: true });
377
+ for (const entry of entries) {
378
+ if (EXCLUDE_DIRS.has(entry.name)) continue;
379
+ const fullPath = join(dir, entry.name);
380
+
381
+ if (entry.isDirectory()) {
382
+ walk(fullPath, depth + 1);
383
+ } else if (CODE_EXTENSIONS.has(extname(entry.name))) {
384
+ totalFiles++;
385
+ totalLines += countLinesWithCache(fullPath);
386
+ }
387
+ }
388
+ } catch {
389
+ // Skip unreadable
390
+ }
391
+ }
392
+
393
+ walk(projectDir, 0);
394
+ return { totalFiles, totalLines };
395
+ }