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,376 @@
1
+ /**
2
+ * Tests for the debug command
3
+ * Tests extractPathsFromError, detectTechFromError, selectRelevantFiles,
4
+ * buildDebugPrompt, and gatherDebugContext.
5
+ */
6
+
7
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
8
+ import {
9
+ extractPathsFromError,
10
+ detectTechFromError,
11
+ selectRelevantFiles,
12
+ isConfigFile,
13
+ extractImagePaths,
14
+ } from '../../../src/cli/commands/debug-context.js';
15
+ import type { FileIndexEntry } from '../../../src/cli/commands/debug-context.js';
16
+ import {
17
+ getDebugSystemPrompt,
18
+ formatConversationHistory,
19
+ } from '../../../src/cli/commands/debug-prompts.js';
20
+ import type { DebugMessage } from '../../../src/cli/commands/debug-prompts.js';
21
+ import { buildDebugPrompt } from '../../../src/cli/commands/debug.js';
22
+ import type { DebugContext } from '../../../src/cli/commands/debug.js';
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // extractPathsFromError
26
+ // ---------------------------------------------------------------------------
27
+ describe('extractPathsFromError', () => {
28
+ it('should extract paths from Python tracebacks', () => {
29
+ const error = `Traceback (most recent call last):
30
+ File "/app/src/module/service.py", line 42, in process
31
+ result = await db.execute(query)
32
+ File "/app/src/database/connection.py", line 15, in execute
33
+ raise ConnectionError("Connection refused")`;
34
+
35
+ const paths = extractPathsFromError(error);
36
+ expect(paths).toContain('src/module/service.py');
37
+ expect(paths).toContain('src/database/connection.py');
38
+ });
39
+
40
+ it('should extract paths from TypeScript errors', () => {
41
+ const error = `src/components/App.tsx(15,3): error TS2339: Property 'foo' does not exist.
42
+ src/utils/helpers.ts:42:10 - error TS7006: Parameter implicitly has an 'any' type.`;
43
+
44
+ const paths = extractPathsFromError(error);
45
+ expect(paths).toContain('src/components/App.tsx');
46
+ expect(paths).toContain('src/utils/helpers.ts');
47
+ });
48
+
49
+ it('should return empty array when no paths found', () => {
50
+ const error = 'Error: Something went wrong with no file references';
51
+ const paths = extractPathsFromError(error);
52
+ expect(paths).toEqual([]);
53
+ });
54
+
55
+ it('should deduplicate paths', () => {
56
+ const error = `Error in src/main.ts:10:5
57
+ Another error in src/main.ts:20:3`;
58
+
59
+ const paths = extractPathsFromError(error);
60
+ const mainCount = paths.filter((p) => p === 'src/main.ts').length;
61
+ expect(mainCount).toBe(1);
62
+ });
63
+
64
+ it('should extract module-not-found paths', () => {
65
+ const error = `Cannot find module './services/auth' from 'src/routes/login.ts'`;
66
+ const paths = extractPathsFromError(error);
67
+ expect(paths).toContain('services/auth');
68
+ });
69
+
70
+ it('should extract Python module-not-found paths', () => {
71
+ const error = `ModuleNotFoundError: No module named 'src.services.auth'`;
72
+ const paths = extractPathsFromError(error);
73
+ expect(paths).toContain('src/services/auth');
74
+ });
75
+
76
+ it('should strip /app/ prefix from Docker container paths', () => {
77
+ const error = `File "/app/src/main.py", line 1`;
78
+ const paths = extractPathsFromError(error);
79
+ expect(paths).toContain('src/main.py');
80
+ expect(paths).not.toContain('/app/src/main.py');
81
+ });
82
+ });
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // detectTechFromError
86
+ // ---------------------------------------------------------------------------
87
+ describe('detectTechFromError', () => {
88
+ it('should detect alembic-related tags', () => {
89
+ const error = `alembic.util.exc.CommandError: Can't locate revision identified by '12345'`;
90
+ const { tags } = detectTechFromError(error);
91
+ expect(tags).toContain('alembic');
92
+ expect(tags).toContain('database');
93
+ expect(tags).toContain('migration');
94
+ });
95
+
96
+ it('should detect vite/bundler tags', () => {
97
+ const error = `[vite] Internal server error: Module not found: @/components/Button`;
98
+ const { tags } = detectTechFromError(error);
99
+ expect(tags).toContain('vite');
100
+ expect(tags).toContain('bundler');
101
+ expect(tags).toContain('frontend');
102
+ });
103
+
104
+ it('should return empty tags for generic errors', () => {
105
+ const error = `TypeError: Cannot read properties of undefined`;
106
+ const { tags } = detectTechFromError(error);
107
+ expect(tags).toEqual([]);
108
+ });
109
+
110
+ it('should detect multiple technologies', () => {
111
+ const error = `docker-compose error: postgres service unhealthy, fastapi startup failed`;
112
+ const { tags } = detectTechFromError(error);
113
+ expect(tags).toContain('docker');
114
+ expect(tags).toContain('compose');
115
+ expect(tags).toContain('postgres');
116
+ expect(tags).toContain('fastapi');
117
+ expect(tags).toContain('python');
118
+ });
119
+
120
+ it('should detect redis tags', () => {
121
+ const error = `redis.exceptions.ConnectionError: Error 111 connecting to redis:6379`;
122
+ const { tags } = detectTechFromError(error);
123
+ expect(tags).toContain('redis');
124
+ expect(tags).toContain('cache');
125
+ });
126
+ });
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // selectRelevantFiles
130
+ // ---------------------------------------------------------------------------
131
+ describe('selectRelevantFiles', () => {
132
+ const baseIndex: FileIndexEntry[] = [
133
+ { relativePath: 'src/main.py', size: 500, mtime: 1000, isConfig: false },
134
+ { relativePath: 'src/routes/auth.py', size: 300, mtime: 1000, isConfig: false },
135
+ { relativePath: 'src/routes/users.py', size: 400, mtime: 1000, isConfig: false },
136
+ { relativePath: 'src/database/models.py', size: 600, mtime: 1000, isConfig: false },
137
+ { relativePath: 'alembic/versions/001_init.py', size: 200, mtime: 1000, isConfig: false },
138
+ { relativePath: 'docker-compose.yml', size: 150, mtime: 1000, isConfig: true },
139
+ { relativePath: 'package.json', size: 100, mtime: 1000, isConfig: true },
140
+ { relativePath: 'requirements.txt', size: 80, mtime: 1000, isConfig: true },
141
+ ];
142
+
143
+ it('should select files mentioned in the error', () => {
144
+ const errorPaths = ['src/routes/auth.py'];
145
+ const result = selectRelevantFiles(baseIndex, errorPaths, []);
146
+ expect(result).toContain('src/routes/auth.py');
147
+ });
148
+
149
+ it('should include sibling files from the same directory', () => {
150
+ const errorPaths = ['src/routes/auth.py'];
151
+ const result = selectRelevantFiles(baseIndex, errorPaths, []);
152
+ expect(result).toContain('src/routes/auth.py');
153
+ expect(result).toContain('src/routes/users.py');
154
+ });
155
+
156
+ it('should include migration files when database tags detected', () => {
157
+ const result = selectRelevantFiles(baseIndex, [], ['database', 'migration']);
158
+ expect(result).toContain('alembic/versions/001_init.py');
159
+ });
160
+
161
+ it('should include docker files when docker tags detected', () => {
162
+ const result = selectRelevantFiles(baseIndex, [], ['docker', 'compose']);
163
+ expect(result).toContain('docker-compose.yml');
164
+ });
165
+
166
+ it('should fall back to config files when few matches', () => {
167
+ const result = selectRelevantFiles(baseIndex, ['nonexistent.py'], []);
168
+ const hasConfig = result.some((p) =>
169
+ p === 'package.json' || p === 'docker-compose.yml' || p === 'requirements.txt'
170
+ );
171
+ expect(hasConfig).toBe(true);
172
+ });
173
+
174
+ it('should return empty array for empty file index', () => {
175
+ const result = selectRelevantFiles([], ['src/main.py'], ['python']);
176
+ expect(result).toEqual([]);
177
+ });
178
+
179
+ it('should limit results to MAX_FILES', () => {
180
+ // Create large index
181
+ const largeIndex: FileIndexEntry[] = Array.from({ length: 100 }, (_, i) => ({
182
+ relativePath: `src/file${i}.py`,
183
+ size: 100,
184
+ mtime: 1000,
185
+ isConfig: false,
186
+ }));
187
+ // All files match by being in src/
188
+ const errorPaths = Array.from({ length: 50 }, (_, i) => `src/file${i}.py`);
189
+ const result = selectRelevantFiles(largeIndex, errorPaths, []);
190
+ expect(result.length).toBeLessThanOrEqual(15);
191
+ });
192
+ });
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // isConfigFile
196
+ // ---------------------------------------------------------------------------
197
+ describe('isConfigFile', () => {
198
+ it('should identify package.json as config', () => {
199
+ expect(isConfigFile('package.json')).toBe(true);
200
+ expect(isConfigFile('apps/backend/package.json')).toBe(true);
201
+ });
202
+
203
+ it('should identify docker-compose.yml as config', () => {
204
+ expect(isConfigFile('docker-compose.yml')).toBe(true);
205
+ });
206
+
207
+ it('should not identify source files as config', () => {
208
+ expect(isConfigFile('src/main.ts')).toBe(false);
209
+ expect(isConfigFile('src/utils/helpers.py')).toBe(false);
210
+ });
211
+ });
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // extractImagePaths
215
+ // ---------------------------------------------------------------------------
216
+ describe('extractImagePaths', () => {
217
+ it('should extract quoted screenshot paths', () => {
218
+ const input = `'/var/folders/abc/Screenshot 2026-02-19.png' the logo is missing`;
219
+ const paths = extractImagePaths(input);
220
+ expect(paths).toContain('/var/folders/abc/Screenshot 2026-02-19.png');
221
+ });
222
+
223
+ it('should extract double-quoted image paths', () => {
224
+ const input = `Look at "/tmp/error.jpg" for the bug`;
225
+ const paths = extractImagePaths(input);
226
+ expect(paths).toContain('/tmp/error.jpg');
227
+ });
228
+
229
+ it('should extract unquoted absolute paths', () => {
230
+ const input = `See /Users/dev/screenshots/bug.png for details`;
231
+ const paths = extractImagePaths(input);
232
+ expect(paths).toContain('/Users/dev/screenshots/bug.png');
233
+ });
234
+
235
+ it('should return empty array when no images found', () => {
236
+ const input = `TypeError: Cannot read properties of undefined`;
237
+ const paths = extractImagePaths(input);
238
+ expect(paths).toEqual([]);
239
+ });
240
+
241
+ it('should handle multiple image paths', () => {
242
+ const input = `'/tmp/before.png' vs '/tmp/after.jpg'`;
243
+ const paths = extractImagePaths(input);
244
+ expect(paths.length).toBe(2);
245
+ expect(paths).toContain('/tmp/before.png');
246
+ expect(paths).toContain('/tmp/after.jpg');
247
+ });
248
+
249
+ it('should support webp and gif extensions', () => {
250
+ const input = `'/tmp/screen.webp' and '/tmp/anim.gif'`;
251
+ const paths = extractImagePaths(input);
252
+ expect(paths).toContain('/tmp/screen.webp');
253
+ expect(paths).toContain('/tmp/anim.gif');
254
+ });
255
+ });
256
+
257
+ // ---------------------------------------------------------------------------
258
+ // buildDebugPrompt
259
+ // ---------------------------------------------------------------------------
260
+ describe('buildDebugPrompt', () => {
261
+ const baseContext: DebugContext = {
262
+ projectDir: '/test/project',
263
+ structureSummary: 'src/\n main.ts\n utils/',
264
+ purpose: 'A test REST API',
265
+ claudeMd: '# Project Rules\nUse TypeScript',
266
+ readme: '# Test Project',
267
+ anchorFiles: { 'package.json': '{"name": "test"}' },
268
+ fileIndex: [],
269
+ language: 'backend',
270
+ };
271
+
272
+ it('should include project context, relevant files, history, and current message', () => {
273
+ const history: DebugMessage[] = [
274
+ { role: 'user', content: 'prev error' },
275
+ { role: 'assistant', content: 'prev fix' },
276
+ ];
277
+ const relevantContents = { 'src/main.ts': 'console.log("hello")' };
278
+
279
+ const prompt = buildDebugPrompt(baseContext, history, 'new error here', relevantContents);
280
+
281
+ expect(prompt).toContain('A test REST API');
282
+ expect(prompt).toContain('backend');
283
+ expect(prompt).toContain('package.json');
284
+ expect(prompt).toContain('console.log("hello")');
285
+ expect(prompt).toContain('prev error');
286
+ expect(prompt).toContain('prev fix');
287
+ expect(prompt).toContain('new error here');
288
+ });
289
+
290
+ it('should omit history section when history is empty', () => {
291
+ const prompt = buildDebugPrompt(baseContext, [], 'some error', {});
292
+ expect(prompt).not.toContain('Previous Conversation');
293
+ });
294
+
295
+ it('should include CLAUDE.md when present', () => {
296
+ const prompt = buildDebugPrompt(baseContext, [], 'error', {});
297
+ expect(prompt).toContain('Use TypeScript');
298
+ });
299
+
300
+ it('should handle missing CLAUDE.md gracefully', () => {
301
+ const ctx = { ...baseContext, claudeMd: undefined };
302
+ const prompt = buildDebugPrompt(ctx, [], 'error', {});
303
+ expect(prompt).toContain('error');
304
+ expect(prompt).not.toContain('CLAUDE.md');
305
+ });
306
+
307
+ it('should handle empty relevant files', () => {
308
+ const prompt = buildDebugPrompt(baseContext, [], 'error', {});
309
+ expect(prompt).not.toContain('Relevant Source Files');
310
+ });
311
+
312
+ it('should include screenshot instructions when image paths provided', () => {
313
+ const prompt = buildDebugPrompt(baseContext, [], 'logo is missing', {}, ['/tmp/screenshot.png']);
314
+ expect(prompt).toContain('Screenshots Attached');
315
+ expect(prompt).toContain('/tmp/screenshot.png');
316
+ expect(prompt).toContain('Read tool');
317
+ });
318
+
319
+ it('should omit screenshot section when no images', () => {
320
+ const prompt = buildDebugPrompt(baseContext, [], 'error', {}, []);
321
+ expect(prompt).not.toContain('Screenshots Attached');
322
+ });
323
+ });
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // getDebugSystemPrompt
327
+ // ---------------------------------------------------------------------------
328
+ describe('getDebugSystemPrompt', () => {
329
+ it('should return a non-empty system prompt', () => {
330
+ const prompt = getDebugSystemPrompt();
331
+ expect(prompt.length).toBeGreaterThan(100);
332
+ });
333
+
334
+ it('should include required response sections', () => {
335
+ const prompt = getDebugSystemPrompt();
336
+ expect(prompt).toContain('Diagnosis');
337
+ expect(prompt).toContain('Evidence');
338
+ expect(prompt).toContain('Proposed Fix');
339
+ expect(prompt).toContain('Commands to Verify');
340
+ expect(prompt).toContain('Ready to Apply');
341
+ });
342
+
343
+ it('should include screenshot/image instructions', () => {
344
+ const prompt = getDebugSystemPrompt();
345
+ expect(prompt).toContain('Screenshots');
346
+ expect(prompt).toContain('Read');
347
+ });
348
+ });
349
+
350
+ // ---------------------------------------------------------------------------
351
+ // formatConversationHistory
352
+ // ---------------------------------------------------------------------------
353
+ describe('formatConversationHistory', () => {
354
+ it('should format messages with role labels', () => {
355
+ const history: DebugMessage[] = [
356
+ { role: 'user', content: 'my error' },
357
+ { role: 'assistant', content: 'here is the fix' },
358
+ ];
359
+ const formatted = formatConversationHistory(history);
360
+ expect(formatted).toContain('**User:**');
361
+ expect(formatted).toContain('**Assistant:**');
362
+ expect(formatted).toContain('my error');
363
+ expect(formatted).toContain('here is the fix');
364
+ });
365
+
366
+ it('should return empty string for empty history', () => {
367
+ const formatted = formatConversationHistory([]);
368
+ expect(formatted).toBe('');
369
+ });
370
+
371
+ it('should include Previous Conversation header', () => {
372
+ const history: DebugMessage[] = [{ role: 'user', content: 'test' }];
373
+ const formatted = formatConversationHistory(history);
374
+ expect(formatted).toContain('## Previous Conversation');
375
+ });
376
+ });
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Artifact Manager tests — create, version, verify, index.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { createArtifactManager } from '../../src/pipeline/artifact-manager.js';
9
+
10
+ const TEST_DIR = join(process.cwd(), '.test-artifact-manager');
11
+
12
+ describe('ArtifactManager', () => {
13
+ beforeEach(() => {
14
+ if (existsSync(TEST_DIR)) {
15
+ rmSync(TEST_DIR, { recursive: true });
16
+ }
17
+ mkdirSync(TEST_DIR, { recursive: true });
18
+ });
19
+
20
+ afterEach(() => {
21
+ if (existsSync(TEST_DIR)) {
22
+ rmSync(TEST_DIR, { recursive: true });
23
+ }
24
+ });
25
+
26
+ describe('ensureDocsStructure', () => {
27
+ it('should create all required subdirectories', () => {
28
+ const manager = createArtifactManager(TEST_DIR);
29
+ manager.ensureDocsStructure();
30
+
31
+ expect(existsSync(join(TEST_DIR, 'docs'))).toBe(true);
32
+ expect(existsSync(join(TEST_DIR, 'docs', 'master-plan'))).toBe(true);
33
+ expect(existsSync(join(TEST_DIR, 'docs', 'architecture'))).toBe(true);
34
+ expect(existsSync(join(TEST_DIR, 'docs', 'consensus'))).toBe(true);
35
+ expect(existsSync(join(TEST_DIR, 'docs', 'audit'))).toBe(true);
36
+ expect(existsSync(join(TEST_DIR, 'docs', 'release'))).toBe(true);
37
+ });
38
+ });
39
+
40
+ describe('createArtifactText', () => {
41
+ it('should create a markdown artifact file', () => {
42
+ const manager = createArtifactManager(TEST_DIR);
43
+ const entry = manager.createArtifactText(
44
+ 'master_plan',
45
+ '# Master Plan\n\nThe plan.',
46
+ 'INTAKE',
47
+ );
48
+
49
+ expect(entry.type).toBe('master_plan');
50
+ expect(entry.phase).toBe('INTAKE');
51
+ expect(entry.version).toBe(1);
52
+ expect(entry.content_type).toBe('markdown');
53
+ expect(entry.immutable).toBe(true);
54
+ expect(entry.path).toContain('master-plan');
55
+ expect(entry.path).toContain('.md');
56
+
57
+ // File should exist on disk
58
+ const fullPath = join(TEST_DIR, entry.path);
59
+ expect(existsSync(fullPath)).toBe(true);
60
+ expect(readFileSync(fullPath, 'utf-8')).toBe('# Master Plan\n\nThe plan.');
61
+ });
62
+ });
63
+
64
+ describe('createArtifactJson', () => {
65
+ it('should create a JSON artifact file', () => {
66
+ const manager = createArtifactManager(TEST_DIR);
67
+ const data = { key: 'value', count: 42 };
68
+ const entry = manager.createArtifactJson(
69
+ 'repo_snapshot',
70
+ data,
71
+ 'INTAKE',
72
+ );
73
+
74
+ expect(entry.content_type).toBe('json');
75
+ expect(entry.path).toContain('.json');
76
+
77
+ const fullPath = join(TEST_DIR, entry.path);
78
+ expect(existsSync(fullPath)).toBe(true);
79
+
80
+ const parsed = JSON.parse(readFileSync(fullPath, 'utf-8'));
81
+ expect(parsed.key).toBe('value');
82
+ expect(parsed.count).toBe(42);
83
+ });
84
+ });
85
+
86
+ describe('verifyArtifact', () => {
87
+ it('should verify intact artifacts', () => {
88
+ const manager = createArtifactManager(TEST_DIR);
89
+ const entry = manager.createArtifactText(
90
+ 'master_plan',
91
+ 'Content here',
92
+ 'INTAKE',
93
+ );
94
+
95
+ expect(manager.verifyArtifact(entry)).toBe(true);
96
+ });
97
+
98
+ it('should detect missing artifacts', () => {
99
+ const manager = createArtifactManager(TEST_DIR);
100
+ const entry = manager.createArtifactText(
101
+ 'master_plan',
102
+ 'Content',
103
+ 'INTAKE',
104
+ );
105
+
106
+ // Remove the file
107
+ rmSync(join(TEST_DIR, entry.path));
108
+ expect(manager.verifyArtifact(entry)).toBe(false);
109
+ });
110
+ });
111
+
112
+ describe('version chains', () => {
113
+ it('should auto-increment versions within a group', () => {
114
+ const manager = createArtifactManager(TEST_DIR);
115
+ const groupId = 'test-group';
116
+
117
+ const v1 = manager.createAndStoreText('master_plan', 'v1 content', 'INTAKE', groupId);
118
+ const v2 = manager.createAndStoreText('master_plan', 'v2 content', 'INTAKE', groupId);
119
+ const v3 = manager.createAndStoreText('master_plan', 'v3 content', 'INTAKE', groupId);
120
+
121
+ expect(v1.version).toBe(1);
122
+ expect(v2.version).toBe(2);
123
+ expect(v3.version).toBe(3);
124
+
125
+ expect(v1.group_id).toBe(groupId);
126
+ expect(v2.group_id).toBe(groupId);
127
+ expect(v3.group_id).toBe(groupId);
128
+
129
+ expect(v2.previous_id).toBe(v1.id);
130
+ expect(v3.previous_id).toBe(v2.id);
131
+ });
132
+
133
+ it('should start at v1 for a new group', () => {
134
+ const manager = createArtifactManager(TEST_DIR);
135
+ const entry = manager.createAndStoreText('architecture', 'content', 'ARCHITECTURE');
136
+ expect(entry.version).toBe(1);
137
+ expect(entry.previous_id).toBeUndefined();
138
+ });
139
+ });
140
+
141
+ describe('toArtifactRef', () => {
142
+ it('should convert entry to ref', () => {
143
+ const manager = createArtifactManager(TEST_DIR);
144
+ const entry = manager.createArtifactText('master_plan', 'content', 'INTAKE');
145
+ const ref = manager.toArtifactRef(entry);
146
+
147
+ expect(ref.artifact_id).toBe(entry.id);
148
+ expect(ref.path).toBe(entry.path);
149
+ expect(ref.sha256).toBe(entry.sha256);
150
+ expect(ref.version).toBe(entry.version);
151
+ expect(ref.type).toBe(entry.type);
152
+ });
153
+ });
154
+
155
+ describe('updateIndex', () => {
156
+ it('should create INDEX.md with artifact listing', () => {
157
+ const manager = createArtifactManager(TEST_DIR);
158
+ const entry = manager.createArtifactText('master_plan', 'content', 'INTAKE');
159
+
160
+ manager.updateIndex([entry]);
161
+
162
+ const indexPath = join(TEST_DIR, 'docs', 'INDEX.md');
163
+ expect(existsSync(indexPath)).toBe(true);
164
+
165
+ const indexContent = readFileSync(indexPath, 'utf-8');
166
+ expect(indexContent).toContain('Documentation Index');
167
+ expect(indexContent).toContain('master_plan');
168
+ expect(indexContent).toContain('INTAKE');
169
+ });
170
+ });
171
+
172
+ describe('storeArtifactMetadata', () => {
173
+ it('should store and retrieve metadata', () => {
174
+ const manager = createArtifactManager(TEST_DIR);
175
+ const entry = manager.createArtifactText('master_plan', 'content', 'INTAKE');
176
+ manager.storeArtifactMetadata(entry);
177
+
178
+ const listed = manager.listArtifacts('master_plan');
179
+ expect(listed).toHaveLength(1);
180
+ expect(listed[0].id).toBe(entry.id);
181
+ });
182
+ });
183
+ });