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,339 @@
1
+ /**
2
+ * Artifact Manager — manages immutable versioned artifacts under /docs/.
3
+ * Supports both Markdown and JSON content types (P0-C).
4
+ * Implements version chains via group_id + previous_id (P1-2).
5
+ */
6
+
7
+ import { randomUUID } from 'node:crypto';
8
+ import { createHash } from 'node:crypto';
9
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'node:fs';
10
+ import { join, relative } from 'node:path';
11
+
12
+ import type {
13
+ ArtifactType,
14
+ ArtifactEntry,
15
+ ArtifactRef,
16
+ ContentType,
17
+ PipelinePhase,
18
+ } from './types.js';
19
+
20
+ // ─── Constants ───────────────────────────────────────────
21
+
22
+ /** Directory mappings: artifact type -> subdirectory under /docs/ */
23
+ const ARTIFACT_DIRS: Record<string, string> = {
24
+ master_plan: 'master-plan',
25
+ architecture: 'architecture',
26
+ role_plan: 'role-plans',
27
+ consensus: 'consensus',
28
+ arbitration: 'arbitration',
29
+ audit_report: 'audit',
30
+ rca_report: 'incidents',
31
+ production_readiness: 'production',
32
+ release_notes: 'release',
33
+ deployment: 'release',
34
+ rollback: 'release',
35
+ repo_snapshot: 'snapshots',
36
+ build_check: 'checks',
37
+ test_check: 'checks',
38
+ lint_check: 'checks',
39
+ typecheck_check: 'checks',
40
+ placeholder_scan: 'checks',
41
+ qa_validation: 'role-plans',
42
+ review_decision: 'consensus',
43
+ stuck_report: 'incidents',
44
+ journalist_trace: 'journal',
45
+ resolved_commands: 'checks',
46
+ constitution: 'governance',
47
+ change_request: 'governance',
48
+ };
49
+
50
+ /** All required subdirectories under /docs/ */
51
+ const DOCS_SUBDIRS = [
52
+ 'master-plan',
53
+ 'architecture',
54
+ 'role-plans',
55
+ 'consensus',
56
+ 'arbitration',
57
+ 'audit',
58
+ 'incidents',
59
+ 'production',
60
+ 'release',
61
+ 'snapshots',
62
+ 'checks',
63
+ 'journal',
64
+ 'governance',
65
+ ];
66
+
67
+ // ─── Helper Functions ────────────────────────────────────
68
+
69
+ function computeSha256(content: string): string {
70
+ return createHash('sha256').update(content, 'utf-8').digest('hex');
71
+ }
72
+
73
+ function shortId(): string {
74
+ return randomUUID().split('-')[0];
75
+ }
76
+
77
+ function formatDate(): string {
78
+ return new Date().toISOString().split('T')[0];
79
+ }
80
+
81
+ function getExtension(contentType: ContentType): string {
82
+ return contentType === 'json' ? '.json' : '.md';
83
+ }
84
+
85
+ // ─── Artifact Manager ────────────────────────────────────
86
+
87
+ export interface ArtifactManagerOptions {
88
+ projectDir: string;
89
+ }
90
+
91
+ export class ArtifactManager {
92
+ private readonly projectDir: string;
93
+ private readonly docsDir: string;
94
+
95
+ constructor(options: ArtifactManagerOptions) {
96
+ this.projectDir = options.projectDir;
97
+ this.docsDir = join(options.projectDir, 'docs');
98
+ }
99
+
100
+ /** Ensure all /docs/ subdirectories exist */
101
+ ensureDocsStructure(): void {
102
+ if (!existsSync(this.docsDir)) {
103
+ mkdirSync(this.docsDir, { recursive: true });
104
+ }
105
+ for (const subdir of DOCS_SUBDIRS) {
106
+ const dirPath = join(this.docsDir, subdir);
107
+ if (!existsSync(dirPath)) {
108
+ mkdirSync(dirPath, { recursive: true });
109
+ }
110
+ }
111
+ }
112
+
113
+ /** Create an artifact from Markdown content */
114
+ createArtifactText(
115
+ type: ArtifactType,
116
+ markdown: string,
117
+ phase: PipelinePhase,
118
+ groupId?: string,
119
+ ): ArtifactEntry {
120
+ return this.createArtifact(type, markdown, phase, 'markdown', groupId);
121
+ }
122
+
123
+ /** Create an artifact from a JSON-serializable object */
124
+ createArtifactJson(
125
+ type: ArtifactType,
126
+ jsonObject: unknown,
127
+ phase: PipelinePhase,
128
+ groupId?: string,
129
+ ): ArtifactEntry {
130
+ const content = JSON.stringify(jsonObject, null, 2);
131
+ return this.createArtifact(type, content, phase, 'json', groupId);
132
+ }
133
+
134
+ /** Core artifact creation logic */
135
+ private createArtifact(
136
+ type: ArtifactType,
137
+ content: string,
138
+ phase: PipelinePhase,
139
+ contentType: ContentType,
140
+ groupId?: string,
141
+ ): ArtifactEntry {
142
+ this.ensureDocsStructure();
143
+
144
+ const resolvedGroupId = groupId ?? randomUUID();
145
+ const existingArtifacts = this.listArtifacts(type);
146
+ const version = this.getNextVersion(resolvedGroupId, existingArtifacts);
147
+ const previousEntry = this.getLatestInGroup(resolvedGroupId, existingArtifacts);
148
+
149
+ const id = randomUUID();
150
+ const date = formatDate();
151
+ const sid = shortId();
152
+ const ext = getExtension(contentType);
153
+ const filename = `${type}_${sid}_v${version}_${date}${ext}`;
154
+
155
+ const subdir = ARTIFACT_DIRS[type] ?? 'misc';
156
+ const dirPath = join(this.docsDir, subdir);
157
+ if (!existsSync(dirPath)) {
158
+ mkdirSync(dirPath, { recursive: true });
159
+ }
160
+
161
+ const filePath = join(dirPath, filename);
162
+ const sha256 = computeSha256(content);
163
+
164
+ writeFileSync(filePath, content, 'utf-8');
165
+
166
+ const relativePath = relative(this.projectDir, filePath);
167
+
168
+ const entry: ArtifactEntry = {
169
+ id,
170
+ type,
171
+ phase,
172
+ version,
173
+ path: relativePath,
174
+ sha256,
175
+ timestamp: new Date().toISOString(),
176
+ immutable: true,
177
+ content_type: contentType,
178
+ group_id: resolvedGroupId,
179
+ previous_id: previousEntry?.id,
180
+ };
181
+
182
+ return entry;
183
+ }
184
+
185
+ /** Get the file path for a given artifact type and naming components */
186
+ getArtifactPath(
187
+ type: ArtifactType,
188
+ sid: string,
189
+ version: number,
190
+ date: string,
191
+ contentType: ContentType,
192
+ ): string {
193
+ const ext = getExtension(contentType);
194
+ const subdir = ARTIFACT_DIRS[type] ?? 'misc';
195
+ return join(this.docsDir, subdir, `${type}_${sid}_v${version}_${date}${ext}`);
196
+ }
197
+
198
+ /** List all artifacts, optionally filtered by type */
199
+ listArtifacts(type?: ArtifactType): ArtifactEntry[] {
200
+ // Scan for artifact JSON metadata files in a .artifacts/ dir
201
+ const metaDir = join(this.docsDir, '.artifacts');
202
+ if (!existsSync(metaDir)) {
203
+ return [];
204
+ }
205
+
206
+ const entries: ArtifactEntry[] = [];
207
+ const files = readdirSync(metaDir).filter((f) => f.endsWith('.json'));
208
+
209
+ for (const file of files) {
210
+ try {
211
+ const raw = readFileSync(join(metaDir, file), 'utf-8');
212
+ const parsed = JSON.parse(raw) as ArtifactEntry;
213
+ if (!type || parsed.type === type) {
214
+ entries.push(parsed);
215
+ }
216
+ } catch {
217
+ // Skip malformed metadata files
218
+ }
219
+ }
220
+
221
+ return entries.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
222
+ }
223
+
224
+ /** Verify an artifact's SHA-256 matches its stored content */
225
+ verifyArtifact(entry: ArtifactEntry): boolean {
226
+ const fullPath = join(this.projectDir, entry.path);
227
+ if (!existsSync(fullPath)) {
228
+ return false;
229
+ }
230
+
231
+ const content = readFileSync(fullPath, 'utf-8');
232
+ const currentHash = computeSha256(content);
233
+ return currentHash === entry.sha256;
234
+ }
235
+
236
+ /** Get the latest artifact of a given type */
237
+ getLatestArtifact(type: ArtifactType): ArtifactEntry | null {
238
+ const all = this.listArtifacts(type);
239
+ if (all.length === 0) return null;
240
+ return all[all.length - 1];
241
+ }
242
+
243
+ /** Get next version number for a group across existing artifacts */
244
+ getNextVersion(groupId: string, existingArtifacts: ArtifactEntry[]): number {
245
+ const groupArtifacts = existingArtifacts.filter((a) => a.group_id === groupId);
246
+ if (groupArtifacts.length === 0) return 1;
247
+ const maxVersion = Math.max(...groupArtifacts.map((a) => a.version));
248
+ return maxVersion + 1;
249
+ }
250
+
251
+ /** Convert an ArtifactEntry to an ArtifactRef */
252
+ toArtifactRef(entry: ArtifactEntry): ArtifactRef {
253
+ return {
254
+ artifact_id: entry.id,
255
+ path: entry.path,
256
+ sha256: entry.sha256,
257
+ version: entry.version,
258
+ type: entry.type,
259
+ };
260
+ }
261
+
262
+ /** Store artifact metadata for later retrieval */
263
+ storeArtifactMetadata(entry: ArtifactEntry): void {
264
+ const metaDir = join(this.docsDir, '.artifacts');
265
+ if (!existsSync(metaDir)) {
266
+ mkdirSync(metaDir, { recursive: true });
267
+ }
268
+
269
+ const metaPath = join(metaDir, `${entry.id}.json`);
270
+ writeFileSync(metaPath, JSON.stringify(entry, null, 2), 'utf-8');
271
+ }
272
+
273
+ /** Create an artifact and store its metadata in one step */
274
+ createAndStoreText(
275
+ type: ArtifactType,
276
+ markdown: string,
277
+ phase: PipelinePhase,
278
+ groupId?: string,
279
+ ): ArtifactEntry {
280
+ const entry = this.createArtifactText(type, markdown, phase, groupId);
281
+ this.storeArtifactMetadata(entry);
282
+ return entry;
283
+ }
284
+
285
+ /** Create a JSON artifact and store its metadata in one step */
286
+ createAndStoreJson(
287
+ type: ArtifactType,
288
+ jsonObject: unknown,
289
+ phase: PipelinePhase,
290
+ groupId?: string,
291
+ ): ArtifactEntry {
292
+ const entry = this.createArtifactJson(type, jsonObject, phase, groupId);
293
+ this.storeArtifactMetadata(entry);
294
+ return entry;
295
+ }
296
+
297
+ /** Update /docs/INDEX.md with current artifact listing */
298
+ updateIndex(artifacts: ArtifactEntry[]): void {
299
+ this.ensureDocsStructure();
300
+
301
+ const lines: string[] = [
302
+ '# Documentation Index',
303
+ '',
304
+ `> Auto-generated by Popeye Pipeline — ${new Date().toISOString()}`,
305
+ '',
306
+ '## Artifacts',
307
+ '',
308
+ '| Type | Version | Path | Phase | Timestamp |',
309
+ '|------|---------|------|-------|-----------|',
310
+ ];
311
+
312
+ const sorted = [...artifacts].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
313
+
314
+ for (const a of sorted) {
315
+ lines.push(`| ${a.type} | v${a.version} | ${a.path} | ${a.phase} | ${a.timestamp} |`);
316
+ }
317
+
318
+ lines.push('');
319
+ const indexPath = join(this.docsDir, 'INDEX.md');
320
+ writeFileSync(indexPath, lines.join('\n'), 'utf-8');
321
+ }
322
+
323
+ /** Get the latest artifact entry in a specific group */
324
+ private getLatestInGroup(
325
+ groupId: string,
326
+ existingArtifacts: ArtifactEntry[],
327
+ ): ArtifactEntry | null {
328
+ const groupArtifacts = existingArtifacts
329
+ .filter((a) => a.group_id === groupId)
330
+ .sort((a, b) => a.version - b.version);
331
+ if (groupArtifacts.length === 0) return null;
332
+ return groupArtifacts[groupArtifacts.length - 1];
333
+ }
334
+ }
335
+
336
+ /** Factory function */
337
+ export function createArtifactManager(projectDir: string): ArtifactManager {
338
+ return new ArtifactManager({ projectDir });
339
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Artifact Completeness Validators — deterministic structural checks.
3
+ * Runs BEFORE LLM review in consensus phases to catch obvious issues.
4
+ * Each validator checks for required sections, minimum content length,
5
+ * and structural integrity specific to its artifact type.
6
+ */
7
+
8
+ import type { ArtifactType } from './types.js';
9
+
10
+ // ─── Types ───────────────────────────────────────────────
11
+
12
+ export interface ValidationResult {
13
+ valid: boolean;
14
+ errors: string[];
15
+ warnings: string[];
16
+ }
17
+
18
+ // ─── Section Patterns ────────────────────────────────────
19
+
20
+ /** Regex patterns for detecting markdown sections (case-insensitive) */
21
+ function hasSection(content: string, patterns: RegExp[]): boolean {
22
+ return patterns.some((p) => p.test(content));
23
+ }
24
+
25
+ function findMissingSections(
26
+ content: string,
27
+ required: { name: string; patterns: RegExp[] }[],
28
+ ): string[] {
29
+ const missing: string[] = [];
30
+ for (const { name, patterns } of required) {
31
+ if (!hasSection(content, patterns)) {
32
+ missing.push(name);
33
+ }
34
+ }
35
+ return missing;
36
+ }
37
+
38
+ // ─── Per-Type Validators ─────────────────────────────────
39
+
40
+ function validateMasterPlan(content: string): ValidationResult {
41
+ const errors: string[] = [];
42
+ const warnings: string[] = [];
43
+
44
+ if (content.length < 200) {
45
+ errors.push('Master plan is too short (min 200 characters)');
46
+ }
47
+
48
+ const missing = findMissingSections(content, [
49
+ { name: 'Goals/Objectives', patterns: [/#+\s*(goals?|objectives?)/i, /\bgoals?\b.*:/i] },
50
+ { name: 'Milestones', patterns: [/#+\s*milestones?/i, /\bmilestone\s+\d/i] },
51
+ { name: 'Success Criteria', patterns: [/#+\s*success\s+criteria/i, /\bsuccess\s+criteria\b/i, /#+\s*acceptance\s+criteria/i] },
52
+ ]);
53
+
54
+ for (const section of missing) {
55
+ errors.push(`Missing required section: ${section}`);
56
+ }
57
+
58
+ // Check for empty sections (heading followed by another heading or end)
59
+ const emptyHeadings = content.match(/^(#+\s+.+)\n(?=#+\s+|\s*$)/gm);
60
+ if (emptyHeadings && emptyHeadings.length > 2) {
61
+ warnings.push(`${emptyHeadings.length} potentially empty sections detected`);
62
+ }
63
+
64
+ return { valid: errors.length === 0, errors, warnings };
65
+ }
66
+
67
+ function validateArchitecture(content: string): ValidationResult {
68
+ const errors: string[] = [];
69
+ const warnings: string[] = [];
70
+
71
+ if (content.length < 200) {
72
+ errors.push('Architecture document is too short (min 200 characters)');
73
+ }
74
+
75
+ const missing = findMissingSections(content, [
76
+ { name: 'Components/Modules', patterns: [/#+\s*(components?|modules?|services?)/i, /\bcomponent\b/i] },
77
+ { name: 'Data Flow/Contracts', patterns: [/#+\s*(data\s+flow|contracts?|api|interfaces?)/i, /\bcontract\b/i, /\bdata\s+flow\b/i] },
78
+ { name: 'Tech Stack', patterns: [/#+\s*(tech\s+stack|technology|stack)/i, /\btech\s+stack\b/i] },
79
+ ]);
80
+
81
+ for (const section of missing) {
82
+ errors.push(`Missing required section: ${section}`);
83
+ }
84
+
85
+ // Must reference at least one file path
86
+ const hasFilePath = /(?:src\/|app\/|pages\/|lib\/|\.ts|\.js|\.py|\.go)/.test(content);
87
+ if (!hasFilePath) {
88
+ warnings.push('Architecture should reference at least one file path');
89
+ }
90
+
91
+ return { valid: errors.length === 0, errors, warnings };
92
+ }
93
+
94
+ function validateRolePlan(content: string): ValidationResult {
95
+ const errors: string[] = [];
96
+ const warnings: string[] = [];
97
+
98
+ if (content.length < 100) {
99
+ errors.push('Role plan is too short (min 100 characters)');
100
+ }
101
+
102
+ const missing = findMissingSections(content, [
103
+ { name: 'Tasks/Responsibilities', patterns: [/#+\s*(tasks?|responsibilities?|work\s+items?)/i, /\btask\b/i] },
104
+ { name: 'Dependencies', patterns: [/#+\s*(dependenc|prerequisites?|requires?)/i, /\bdepend/i] },
105
+ { name: 'Acceptance Criteria', patterns: [/#+\s*(acceptance|done\s+when|completion)/i, /\bacceptance\b/i, /\bdone\s+when\b/i] },
106
+ ]);
107
+
108
+ for (const section of missing) {
109
+ errors.push(`Missing required section: ${section}`);
110
+ }
111
+
112
+ // Should reference a role name
113
+ const rolePatterns = /\b(DISPATCHER|ARCHITECT|DB_EXPERT|BACKEND|FRONTEND|WEBSITE|QA_TESTER|REVIEWER|AUDITOR|JOURNALIST)\b/i;
114
+ if (!rolePatterns.test(content)) {
115
+ warnings.push('Role plan should reference the role name');
116
+ }
117
+
118
+ return { valid: errors.length === 0, errors, warnings };
119
+ }
120
+
121
+ function validateQaValidation(content: string): ValidationResult {
122
+ const errors: string[] = [];
123
+ const warnings: string[] = [];
124
+
125
+ const missing = findMissingSections(content, [
126
+ { name: 'Test Results', patterns: [/#+\s*(test\s+results?|results?)/i, /\btest\s+results?\b/i, /\bpass(?:ed|ing)?\b/i] },
127
+ { name: 'Coverage', patterns: [/#+\s*coverage/i, /\bcoverage\b/i, /\d+\s*%/] },
128
+ ]);
129
+
130
+ for (const section of missing) {
131
+ errors.push(`Missing required section: ${section}`);
132
+ }
133
+
134
+ // Should contain pass/fail counts
135
+ const hasPassFail = /\b\d+\s*(pass|fail|error|skip)/i.test(content);
136
+ if (!hasPassFail) {
137
+ warnings.push('QA validation should include pass/fail counts');
138
+ }
139
+
140
+ return { valid: errors.length === 0, errors, warnings };
141
+ }
142
+
143
+ function validateAuditReport(content: string): ValidationResult {
144
+ const errors: string[] = [];
145
+ const warnings: string[] = [];
146
+
147
+ // Try JSON parsing
148
+ try {
149
+ const parsed = JSON.parse(content);
150
+ if (!Array.isArray(parsed.findings)) {
151
+ errors.push('Audit report must have a "findings" array');
152
+ }
153
+ if (typeof parsed.overall_status !== 'string') {
154
+ errors.push('Audit report must have "overall_status"');
155
+ }
156
+ if (typeof parsed.system_risk_score !== 'number') {
157
+ errors.push('Audit report must have "system_risk_score"');
158
+ }
159
+ } catch {
160
+ // Not JSON — check for markdown-style audit
161
+ if (!content.includes('findings') && !content.includes('finding')) {
162
+ errors.push('Audit report must contain findings');
163
+ }
164
+ if (!content.includes('status') && !content.includes('PASS') && !content.includes('FAIL')) {
165
+ errors.push('Audit report must contain overall status');
166
+ }
167
+ if (!content.includes('risk') && !content.includes('score')) {
168
+ warnings.push('Audit report should include risk score');
169
+ }
170
+ }
171
+
172
+ return { valid: errors.length === 0, errors, warnings };
173
+ }
174
+
175
+ // ─── Validator Registry ──────────────────────────────────
176
+
177
+ const VALIDATORS: Partial<Record<ArtifactType, (content: string) => ValidationResult>> = {
178
+ master_plan: validateMasterPlan,
179
+ architecture: validateArchitecture,
180
+ role_plan: validateRolePlan,
181
+ qa_validation: validateQaValidation,
182
+ audit_report: validateAuditReport,
183
+ };
184
+
185
+ // ─── Public API ──────────────────────────────────────────
186
+
187
+ /**
188
+ * Validate artifact content completeness based on type-specific rules.
189
+ * Returns a ValidationResult with errors (blocking) and warnings (non-blocking).
190
+ *
191
+ * Args:
192
+ * type: The artifact type to validate against.
193
+ * content: The artifact content string.
194
+ *
195
+ * Returns:
196
+ * ValidationResult with valid flag, errors, and warnings.
197
+ */
198
+ export function validateArtifactCompleteness(
199
+ type: ArtifactType,
200
+ content: string,
201
+ ): ValidationResult {
202
+ const validator = VALIDATORS[type];
203
+ if (!validator) {
204
+ // No validator for this type — pass by default
205
+ return { valid: true, errors: [], warnings: [] };
206
+ }
207
+
208
+ if (!content || content.trim().length === 0) {
209
+ return {
210
+ valid: false,
211
+ errors: [`${type} artifact has empty content`],
212
+ warnings: [],
213
+ };
214
+ }
215
+
216
+ return validator(content);
217
+ }
218
+
219
+ /**
220
+ * Get all artifact types that have validators.
221
+ */
222
+ export function getValidatableArtifactTypes(): ArtifactType[] {
223
+ return Object.keys(VALIDATORS) as ArtifactType[];
224
+ }