popeye-cli 2.2.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/dist/adapters/gemini.d.ts +14 -0
  2. package/dist/adapters/gemini.d.ts.map +1 -1
  3. package/dist/adapters/gemini.js +41 -6
  4. package/dist/adapters/gemini.js.map +1 -1
  5. package/dist/adapters/grok.d.ts +14 -0
  6. package/dist/adapters/grok.d.ts.map +1 -1
  7. package/dist/adapters/grok.js +42 -6
  8. package/dist/adapters/grok.js.map +1 -1
  9. package/dist/adapters/openai.d.ts +10 -0
  10. package/dist/adapters/openai.d.ts.map +1 -1
  11. package/dist/adapters/openai.js +44 -5
  12. package/dist/adapters/openai.js.map +1 -1
  13. package/dist/cli/commands/create.js +1 -1
  14. package/dist/cli/commands/create.js.map +1 -1
  15. package/dist/cli/interactive.d.ts.map +1 -1
  16. package/dist/cli/interactive.js +324 -20
  17. package/dist/cli/interactive.js.map +1 -1
  18. package/dist/generators/all.d.ts.map +1 -1
  19. package/dist/generators/all.js +3 -2
  20. package/dist/generators/all.js.map +1 -1
  21. package/dist/generators/doc-parser.d.ts +21 -6
  22. package/dist/generators/doc-parser.d.ts.map +1 -1
  23. package/dist/generators/doc-parser.js +55 -4
  24. package/dist/generators/doc-parser.js.map +1 -1
  25. package/dist/generators/templates/fullstack.js +1 -1
  26. package/dist/generators/templates/website-components.js +1 -1
  27. package/dist/generators/templates/website-components.js.map +1 -1
  28. package/dist/generators/templates/website-config.d.ts +4 -1
  29. package/dist/generators/templates/website-config.d.ts.map +1 -1
  30. package/dist/generators/templates/website-config.js +17 -11
  31. package/dist/generators/templates/website-config.js.map +1 -1
  32. package/dist/generators/templates/website-conversion.js +1 -1
  33. package/dist/generators/templates/website-conversion.js.map +1 -1
  34. package/dist/generators/templates/website-landing.js +1 -1
  35. package/dist/generators/templates/website-landing.js.map +1 -1
  36. package/dist/generators/templates/website-layout.d.ts +36 -4
  37. package/dist/generators/templates/website-layout.d.ts.map +1 -1
  38. package/dist/generators/templates/website-layout.js +466 -23
  39. package/dist/generators/templates/website-layout.js.map +1 -1
  40. package/dist/generators/templates/website-pricing.js +1 -1
  41. package/dist/generators/templates/website-pricing.js.map +1 -1
  42. package/dist/generators/templates/website-sections.js +1 -1
  43. package/dist/generators/templates/website-sections.js.map +1 -1
  44. package/dist/generators/templates/website-seo.d.ts.map +1 -1
  45. package/dist/generators/templates/website-seo.js +4 -1
  46. package/dist/generators/templates/website-seo.js.map +1 -1
  47. package/dist/generators/templates/website.d.ts +1 -1
  48. package/dist/generators/templates/website.d.ts.map +1 -1
  49. package/dist/generators/templates/website.js +1 -1
  50. package/dist/generators/templates/website.js.map +1 -1
  51. package/dist/generators/website-content-ai.d.ts +52 -0
  52. package/dist/generators/website-content-ai.d.ts.map +1 -0
  53. package/dist/generators/website-content-ai.js +141 -0
  54. package/dist/generators/website-content-ai.js.map +1 -0
  55. package/dist/generators/website-content-scanner.d.ts +1 -1
  56. package/dist/generators/website-content-scanner.d.ts.map +1 -1
  57. package/dist/generators/website-content-scanner.js +98 -1
  58. package/dist/generators/website-content-scanner.js.map +1 -1
  59. package/dist/generators/website-context.d.ts +34 -1
  60. package/dist/generators/website-context.d.ts.map +1 -1
  61. package/dist/generators/website-context.js +131 -9
  62. package/dist/generators/website-context.js.map +1 -1
  63. package/dist/generators/website-debug.d.ts +12 -0
  64. package/dist/generators/website-debug.d.ts.map +1 -1
  65. package/dist/generators/website-debug.js +16 -0
  66. package/dist/generators/website-debug.js.map +1 -1
  67. package/dist/generators/website.d.ts.map +1 -1
  68. package/dist/generators/website.js +26 -4
  69. package/dist/generators/website.js.map +1 -1
  70. package/dist/pipeline/auto-recovery.d.ts +56 -0
  71. package/dist/pipeline/auto-recovery.d.ts.map +1 -0
  72. package/dist/pipeline/auto-recovery.js +185 -0
  73. package/dist/pipeline/auto-recovery.js.map +1 -0
  74. package/dist/pipeline/change-request.d.ts +39 -0
  75. package/dist/pipeline/change-request.d.ts.map +1 -1
  76. package/dist/pipeline/change-request.js +40 -1
  77. package/dist/pipeline/change-request.js.map +1 -1
  78. package/dist/pipeline/check-runner.d.ts +30 -1
  79. package/dist/pipeline/check-runner.d.ts.map +1 -1
  80. package/dist/pipeline/check-runner.js +122 -1
  81. package/dist/pipeline/check-runner.js.map +1 -1
  82. package/dist/pipeline/command-resolver.d.ts.map +1 -1
  83. package/dist/pipeline/command-resolver.js +33 -2
  84. package/dist/pipeline/command-resolver.js.map +1 -1
  85. package/dist/pipeline/consensus/arbitrator-query.d.ts +22 -0
  86. package/dist/pipeline/consensus/arbitrator-query.d.ts.map +1 -0
  87. package/dist/pipeline/consensus/arbitrator-query.js +70 -0
  88. package/dist/pipeline/consensus/arbitrator-query.js.map +1 -0
  89. package/dist/pipeline/consensus/consensus-runner.d.ts +131 -7
  90. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -1
  91. package/dist/pipeline/consensus/consensus-runner.js +809 -35
  92. package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
  93. package/dist/pipeline/cr-lifecycle.d.ts +42 -0
  94. package/dist/pipeline/cr-lifecycle.d.ts.map +1 -0
  95. package/dist/pipeline/cr-lifecycle.js +89 -0
  96. package/dist/pipeline/cr-lifecycle.js.map +1 -0
  97. package/dist/pipeline/gate-engine.d.ts +1 -0
  98. package/dist/pipeline/gate-engine.d.ts.map +1 -1
  99. package/dist/pipeline/gate-engine.js +26 -7
  100. package/dist/pipeline/gate-engine.js.map +1 -1
  101. package/dist/pipeline/orchestrator.d.ts +1 -1
  102. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  103. package/dist/pipeline/orchestrator.js +306 -16
  104. package/dist/pipeline/orchestrator.js.map +1 -1
  105. package/dist/pipeline/packets/consensus-packet-builder.d.ts +15 -4
  106. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -1
  107. package/dist/pipeline/packets/consensus-packet-builder.js +29 -17
  108. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -1
  109. package/dist/pipeline/phases/architecture.d.ts.map +1 -1
  110. package/dist/pipeline/phases/architecture.js +5 -3
  111. package/dist/pipeline/phases/architecture.js.map +1 -1
  112. package/dist/pipeline/phases/audit.d.ts.map +1 -1
  113. package/dist/pipeline/phases/audit.js +5 -3
  114. package/dist/pipeline/phases/audit.js.map +1 -1
  115. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -1
  116. package/dist/pipeline/phases/consensus-architecture.js +10 -1
  117. package/dist/pipeline/phases/consensus-architecture.js.map +1 -1
  118. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -1
  119. package/dist/pipeline/phases/consensus-master-plan.js +10 -3
  120. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -1
  121. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -1
  122. package/dist/pipeline/phases/consensus-role-plans.js +10 -1
  123. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -1
  124. package/dist/pipeline/phases/done.d.ts.map +1 -1
  125. package/dist/pipeline/phases/done.js +9 -4
  126. package/dist/pipeline/phases/done.js.map +1 -1
  127. package/dist/pipeline/phases/intake.d.ts.map +1 -1
  128. package/dist/pipeline/phases/intake.js +7 -3
  129. package/dist/pipeline/phases/intake.js.map +1 -1
  130. package/dist/pipeline/phases/phase-context.d.ts +2 -0
  131. package/dist/pipeline/phases/phase-context.d.ts.map +1 -1
  132. package/dist/pipeline/phases/phase-context.js +3 -1
  133. package/dist/pipeline/phases/phase-context.js.map +1 -1
  134. package/dist/pipeline/phases/production-gate.d.ts.map +1 -1
  135. package/dist/pipeline/phases/production-gate.js +28 -3
  136. package/dist/pipeline/phases/production-gate.js.map +1 -1
  137. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -1
  138. package/dist/pipeline/phases/qa-validation.js +38 -5
  139. package/dist/pipeline/phases/qa-validation.js.map +1 -1
  140. package/dist/pipeline/phases/recovery-loop.d.ts +2 -0
  141. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
  142. package/dist/pipeline/phases/recovery-loop.js +200 -6
  143. package/dist/pipeline/phases/recovery-loop.js.map +1 -1
  144. package/dist/pipeline/phases/review.d.ts.map +1 -1
  145. package/dist/pipeline/phases/review.js +58 -28
  146. package/dist/pipeline/phases/review.js.map +1 -1
  147. package/dist/pipeline/phases/role-planning.d.ts.map +1 -1
  148. package/dist/pipeline/phases/role-planning.js +18 -2
  149. package/dist/pipeline/phases/role-planning.js.map +1 -1
  150. package/dist/pipeline/phases/stuck.d.ts.map +1 -1
  151. package/dist/pipeline/phases/stuck.js +10 -0
  152. package/dist/pipeline/phases/stuck.js.map +1 -1
  153. package/dist/pipeline/repo-snapshot.d.ts.map +1 -1
  154. package/dist/pipeline/repo-snapshot.js +3 -0
  155. package/dist/pipeline/repo-snapshot.js.map +1 -1
  156. package/dist/pipeline/role-execution-adapter.d.ts +2 -1
  157. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -1
  158. package/dist/pipeline/role-execution-adapter.js +22 -7
  159. package/dist/pipeline/role-execution-adapter.js.map +1 -1
  160. package/dist/pipeline/skill-loader.d.ts +19 -0
  161. package/dist/pipeline/skill-loader.d.ts.map +1 -1
  162. package/dist/pipeline/skill-loader.js +22 -0
  163. package/dist/pipeline/skill-loader.js.map +1 -1
  164. package/dist/pipeline/skills/coverage-gate.d.ts +44 -0
  165. package/dist/pipeline/skills/coverage-gate.d.ts.map +1 -0
  166. package/dist/pipeline/skills/coverage-gate.js +143 -0
  167. package/dist/pipeline/skills/coverage-gate.js.map +1 -0
  168. package/dist/pipeline/skills/usage-registry.d.ts +48 -0
  169. package/dist/pipeline/skills/usage-registry.d.ts.map +1 -0
  170. package/dist/pipeline/skills/usage-registry.js +55 -0
  171. package/dist/pipeline/skills/usage-registry.js.map +1 -0
  172. package/dist/pipeline/strategy-context.d.ts +20 -0
  173. package/dist/pipeline/strategy-context.d.ts.map +1 -0
  174. package/dist/pipeline/strategy-context.js +55 -0
  175. package/dist/pipeline/strategy-context.js.map +1 -0
  176. package/dist/pipeline/type-defs/artifacts.d.ts +25 -5
  177. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
  178. package/dist/pipeline/type-defs/artifacts.js +4 -0
  179. package/dist/pipeline/type-defs/artifacts.js.map +1 -1
  180. package/dist/pipeline/type-defs/audit.d.ts +25 -13
  181. package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
  182. package/dist/pipeline/type-defs/checks.d.ts +18 -8
  183. package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
  184. package/dist/pipeline/type-defs/checks.js +4 -0
  185. package/dist/pipeline/type-defs/checks.js.map +1 -1
  186. package/dist/pipeline/type-defs/packets.d.ts +104 -18
  187. package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
  188. package/dist/pipeline/type-defs/packets.js +17 -1
  189. package/dist/pipeline/type-defs/packets.js.map +1 -1
  190. package/dist/pipeline/type-defs/state.d.ts +160 -16
  191. package/dist/pipeline/type-defs/state.d.ts.map +1 -1
  192. package/dist/pipeline/type-defs/state.js +26 -1
  193. package/dist/pipeline/type-defs/state.js.map +1 -1
  194. package/dist/shared/text-utils.d.ts +23 -0
  195. package/dist/shared/text-utils.d.ts.map +1 -0
  196. package/dist/shared/text-utils.js +66 -0
  197. package/dist/shared/text-utils.js.map +1 -0
  198. package/dist/shared/website-strategy-format.d.ts +18 -0
  199. package/dist/shared/website-strategy-format.d.ts.map +1 -0
  200. package/dist/shared/website-strategy-format.js +47 -0
  201. package/dist/shared/website-strategy-format.js.map +1 -0
  202. package/dist/state/index.d.ts +2 -0
  203. package/dist/state/index.d.ts.map +1 -1
  204. package/dist/state/index.js +57 -8
  205. package/dist/state/index.js.map +1 -1
  206. package/dist/types/consensus.d.ts +1 -0
  207. package/dist/types/consensus.d.ts.map +1 -1
  208. package/dist/types/consensus.js.map +1 -1
  209. package/dist/types/website-strategy.d.ts +1 -1
  210. package/dist/types/workflow.d.ts +447 -0
  211. package/dist/types/workflow.d.ts.map +1 -1
  212. package/dist/types/workflow.js +3 -0
  213. package/dist/types/workflow.js.map +1 -1
  214. package/dist/upgrade/handlers.d.ts.map +1 -1
  215. package/dist/upgrade/handlers.js +6 -3
  216. package/dist/upgrade/handlers.js.map +1 -1
  217. package/dist/workflow/consensus.d.ts.map +1 -1
  218. package/dist/workflow/consensus.js +1 -0
  219. package/dist/workflow/consensus.js.map +1 -1
  220. package/dist/workflow/website-strategy.d.ts.map +1 -1
  221. package/dist/workflow/website-strategy.js +2 -29
  222. package/dist/workflow/website-strategy.js.map +1 -1
  223. package/dist/workflow/website-updater.d.ts.map +1 -1
  224. package/dist/workflow/website-updater.js +3 -2
  225. package/dist/workflow/website-updater.js.map +1 -1
  226. package/package.json +1 -1
  227. package/src/adapters/gemini.ts +51 -6
  228. package/src/adapters/grok.ts +51 -6
  229. package/src/adapters/openai.ts +53 -5
  230. package/src/cli/commands/create.ts +1 -1
  231. package/src/cli/interactive.ts +333 -19
  232. package/src/generators/all.ts +3 -2
  233. package/src/generators/doc-parser.ts +75 -15
  234. package/src/generators/templates/fullstack.ts +1 -1
  235. package/src/generators/templates/website-components.ts +1 -1
  236. package/src/generators/templates/website-config.ts +23 -11
  237. package/src/generators/templates/website-conversion.ts +1 -1
  238. package/src/generators/templates/website-landing.ts +1 -1
  239. package/src/generators/templates/website-layout.ts +491 -23
  240. package/src/generators/templates/website-pricing.ts +1 -1
  241. package/src/generators/templates/website-sections.ts +1 -1
  242. package/src/generators/templates/website-seo.ts +4 -1
  243. package/src/generators/templates/website.ts +3 -0
  244. package/src/generators/website-content-ai.ts +186 -0
  245. package/src/generators/website-content-scanner.ts +113 -1
  246. package/src/generators/website-context.ts +151 -12
  247. package/src/generators/website-debug.ts +26 -0
  248. package/src/generators/website.ts +28 -3
  249. package/src/pipeline/auto-recovery.ts +283 -0
  250. package/src/pipeline/change-request.ts +63 -1
  251. package/src/pipeline/check-runner.ts +141 -2
  252. package/src/pipeline/command-resolver.ts +34 -2
  253. package/src/pipeline/consensus/arbitrator-query.ts +101 -0
  254. package/src/pipeline/consensus/consensus-runner.ts +1099 -42
  255. package/src/pipeline/cr-lifecycle.ts +103 -0
  256. package/src/pipeline/gate-engine.ts +35 -7
  257. package/src/pipeline/orchestrator.ts +361 -16
  258. package/src/pipeline/packets/consensus-packet-builder.ts +44 -18
  259. package/src/pipeline/phases/architecture.ts +6 -3
  260. package/src/pipeline/phases/audit.ts +6 -3
  261. package/src/pipeline/phases/consensus-architecture.ts +10 -1
  262. package/src/pipeline/phases/consensus-master-plan.ts +10 -3
  263. package/src/pipeline/phases/consensus-role-plans.ts +10 -1
  264. package/src/pipeline/phases/done.ts +15 -4
  265. package/src/pipeline/phases/intake.ts +7 -3
  266. package/src/pipeline/phases/phase-context.ts +6 -1
  267. package/src/pipeline/phases/production-gate.ts +41 -3
  268. package/src/pipeline/phases/qa-validation.ts +51 -5
  269. package/src/pipeline/phases/recovery-loop.ts +229 -7
  270. package/src/pipeline/phases/review.ts +73 -30
  271. package/src/pipeline/phases/role-planning.ts +21 -2
  272. package/src/pipeline/phases/stuck.ts +10 -0
  273. package/src/pipeline/repo-snapshot.ts +3 -0
  274. package/src/pipeline/role-execution-adapter.ts +30 -4
  275. package/src/pipeline/skill-loader.ts +33 -0
  276. package/src/pipeline/skills/coverage-gate.ts +199 -0
  277. package/src/pipeline/skills/usage-registry.ts +87 -0
  278. package/src/pipeline/strategy-context.ts +60 -0
  279. package/src/pipeline/type-defs/artifacts.ts +4 -0
  280. package/src/pipeline/type-defs/checks.ts +4 -0
  281. package/src/pipeline/type-defs/packets.ts +18 -1
  282. package/src/pipeline/type-defs/state.ts +26 -1
  283. package/src/shared/text-utils.ts +70 -0
  284. package/src/shared/website-strategy-format.ts +56 -0
  285. package/src/state/index.ts +60 -8
  286. package/src/types/consensus.ts +1 -0
  287. package/src/types/workflow.ts +6 -0
  288. package/src/upgrade/handlers.ts +9 -3
  289. package/src/workflow/consensus.ts +1 -0
  290. package/src/workflow/website-strategy.ts +2 -36
  291. package/src/workflow/website-updater.ts +4 -2
  292. package/tests/adapters/gemini.test.ts +165 -0
  293. package/tests/adapters/grok.test.ts +137 -0
  294. package/tests/adapters/openai.test.ts +128 -0
  295. package/tests/generators/doc-parser.test.ts +88 -9
  296. package/tests/generators/quality-gate.test.ts +19 -3
  297. package/tests/generators/website-components.test.ts +34 -0
  298. package/tests/generators/website-content-ai.test.ts +308 -0
  299. package/tests/generators/website-content-scanner.test.ts +86 -0
  300. package/tests/generators/website-context.test.ts +3 -2
  301. package/tests/integration/smokestack-scaffold.test.ts +385 -0
  302. package/tests/pipeline/auto-recovery.test.ts +337 -0
  303. package/tests/pipeline/change-request.test.ts +70 -0
  304. package/tests/pipeline/command-resolver.test.ts +42 -0
  305. package/tests/pipeline/consensus/arbitrator-query.test.ts +107 -0
  306. package/tests/pipeline/consensus-runner.test.ts +1333 -10
  307. package/tests/pipeline/consensus-scoring.test.ts +602 -18
  308. package/tests/pipeline/gate-engine.test.ts +34 -0
  309. package/tests/pipeline/install-check.test.ts +261 -0
  310. package/tests/pipeline/orchestrator.test.ts +1506 -15
  311. package/tests/pipeline/packets/builders.test.ts +29 -6
  312. package/tests/pipeline/phases/role-planning.strategy.test.ts +204 -0
  313. package/tests/pipeline/pipeline-persistence.test.ts +230 -0
  314. package/tests/pipeline/recovery-loop-guidance.test.ts +280 -0
  315. package/tests/pipeline/role-execution-adapter.test.ts +88 -0
  316. package/tests/pipeline/skills/coverage-gate.test.ts +370 -0
  317. package/tests/pipeline/skills/usage-registry.test.ts +114 -0
  318. package/tests/pipeline/strategy-context.test.ts +148 -0
  319. package/tests/shared/text-utils.test.ts +155 -0
  320. package/tests/state/progress-analysis.test.ts +375 -0
  321. package/tests/upgrade/handlers.test.ts +33 -2
  322. package/tests/workflow/consensus.test.ts +6 -0
  323. package/tsconfig.json +1 -1
@@ -192,4 +192,38 @@ describe('GateEngine', () => {
192
192
  expect(engine.getPhaseIndex('RECOVERY_LOOP')).toBe(-1); // Not in linear sequence
193
193
  });
194
194
  });
195
+
196
+ // ─── v2.4.6: findLast for stale check results ─────────
197
+
198
+ describe('v2.4.6: uses latest check result (findLast)', () => {
199
+ it('should use the latest check result when multiple exist for same check_type', () => {
200
+ const pipeline = createDefaultPipelineState();
201
+ pipeline.artifacts.push(makeArtifact('qa_validation', 'QA_VALIDATION'));
202
+
203
+ // First check failed, second check passed (accumulated on retry)
204
+ pipeline.gateChecks['QA_VALIDATION'] = [
205
+ {
206
+ check_type: 'test',
207
+ status: 'fail',
208
+ command: 'npm test',
209
+ exit_code: 1,
210
+ duration_ms: 500,
211
+ stderr_summary: 'Error: test failed',
212
+ timestamp: '2024-01-01T00:00:00Z',
213
+ },
214
+ {
215
+ check_type: 'test',
216
+ status: 'pass',
217
+ command: 'npm test',
218
+ exit_code: 0,
219
+ duration_ms: 800,
220
+ timestamp: '2024-01-01T00:01:00Z',
221
+ },
222
+ ];
223
+
224
+ const result = engine.evaluateGate('QA_VALIDATION', pipeline);
225
+ expect(result.pass).toBe(true);
226
+ expect(result.failedChecks).toHaveLength(0);
227
+ });
228
+ });
195
229
  });
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Install check tests — install resolution, skip-heuristic, phase ordering, marker invalidation.
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
6
+ import { resolveCommands } from '../../src/pipeline/command-resolver.js';
7
+ import {
8
+ shouldSkipInstall,
9
+ writeInstallMarker,
10
+ invalidateInstallMarker,
11
+ } from '../../src/pipeline/check-runner.js';
12
+ import type { RepoSnapshot, ConfigFileEntry } from '../../src/pipeline/types.js';
13
+
14
+ import * as fs from 'node:fs';
15
+ import * as path from 'node:path';
16
+ import { tmpdir } from 'node:os';
17
+
18
+ function makeSnapshot(overrides: Partial<RepoSnapshot> = {}): RepoSnapshot {
19
+ return {
20
+ snapshot_id: 'test-snap',
21
+ timestamp: new Date().toISOString(),
22
+ tree_summary: '',
23
+ config_files: [],
24
+ languages_detected: [],
25
+ package_manager: undefined,
26
+ scripts: {},
27
+ test_framework: undefined,
28
+ build_tool: undefined,
29
+ env_files: [],
30
+ migrations_present: false,
31
+ ports_entrypoints: [],
32
+ total_files: 0,
33
+ total_lines: 0,
34
+ ...overrides,
35
+ };
36
+ }
37
+
38
+ function makeConfig(type: string, overrides: Partial<ConfigFileEntry> = {}): ConfigFileEntry {
39
+ return { path: type, type, content_hash: 'abc123', key_fields: {}, ...overrides };
40
+ }
41
+
42
+ // ─── Install Resolution Tests ─────────────────────────────
43
+
44
+ describe('Install Resolution', () => {
45
+ it('should resolve install for npm project', () => {
46
+ const snap = makeSnapshot({
47
+ config_files: [makeConfig('package.json')],
48
+ package_manager: 'npm',
49
+ scripts: { build: 'tsc' },
50
+ });
51
+
52
+ const cmds = resolveCommands(snap);
53
+ expect(cmds.install).toBe('npm install');
54
+ });
55
+
56
+ it('should resolve install for yarn project', () => {
57
+ const snap = makeSnapshot({
58
+ config_files: [makeConfig('package.json')],
59
+ package_manager: 'yarn',
60
+ scripts: { build: 'tsc' },
61
+ });
62
+
63
+ const cmds = resolveCommands(snap);
64
+ expect(cmds.install).toBe('yarn install');
65
+ });
66
+
67
+ it('should resolve install for pnpm project', () => {
68
+ const snap = makeSnapshot({
69
+ config_files: [makeConfig('package.json')],
70
+ package_manager: 'pnpm',
71
+ scripts: { build: 'tsc' },
72
+ });
73
+
74
+ const cmds = resolveCommands(snap);
75
+ expect(cmds.install).toBe('pnpm install');
76
+ });
77
+
78
+ it('should resolve install for python project with requirements.txt', () => {
79
+ const snap = makeSnapshot({
80
+ config_files: [makeConfig('requirements.txt')],
81
+ languages_detected: ['python'],
82
+ });
83
+
84
+ const cmds = resolveCommands(snap);
85
+ expect(cmds.install).toBe('pip install -r requirements.txt');
86
+ });
87
+
88
+ it('should resolve install for python project with poetry.lock', () => {
89
+ const snap = makeSnapshot({
90
+ config_files: [makeConfig('pyproject.toml'), makeConfig('poetry.lock')],
91
+ languages_detected: ['python'],
92
+ });
93
+
94
+ const cmds = resolveCommands(snap);
95
+ expect(cmds.install).toBe('poetry install');
96
+ });
97
+
98
+ it('should return NO install for pyproject.toml-only python project', () => {
99
+ const snap = makeSnapshot({
100
+ config_files: [makeConfig('pyproject.toml')],
101
+ languages_detected: ['python'],
102
+ });
103
+
104
+ const cmds = resolveCommands(snap);
105
+ expect(cmds.install).toBeUndefined();
106
+ });
107
+
108
+ it('should return chained install for mixed project', () => {
109
+ const snap = makeSnapshot({
110
+ config_files: [
111
+ makeConfig('package.json'),
112
+ makeConfig('requirements.txt'),
113
+ ],
114
+ package_manager: 'npm',
115
+ scripts: {},
116
+ languages_detected: ['typescript', 'python'],
117
+ });
118
+
119
+ const cmds = resolveCommands(snap);
120
+ expect(cmds.install).toBe('npm install && pip install -r requirements.txt');
121
+ });
122
+
123
+ it('should set install_cwd for workspace project with workspaces field', () => {
124
+ const snap = makeSnapshot({
125
+ config_files: [
126
+ makeConfig('package.json', { key_fields: { workspaces: ['apps/*', 'packages/*'] } }),
127
+ ],
128
+ package_manager: 'npm',
129
+ scripts: {},
130
+ });
131
+
132
+ const cmds = resolveCommands(snap);
133
+ expect(cmds.install_cwd).toBe('.');
134
+ });
135
+
136
+ it('should set install_cwd for pnpm workspace project', () => {
137
+ const snap = makeSnapshot({
138
+ config_files: [
139
+ makeConfig('package.json'),
140
+ makeConfig('pnpm-workspace.yaml'),
141
+ ],
142
+ package_manager: 'pnpm',
143
+ scripts: {},
144
+ });
145
+
146
+ const cmds = resolveCommands(snap);
147
+ expect(cmds.install_cwd).toBe('.');
148
+ });
149
+
150
+ it('should NOT set install_cwd for non-workspace project', () => {
151
+ const snap = makeSnapshot({
152
+ config_files: [makeConfig('package.json')],
153
+ package_manager: 'npm',
154
+ scripts: {},
155
+ });
156
+
157
+ const cmds = resolveCommands(snap);
158
+ expect(cmds.install_cwd).toBeUndefined();
159
+ });
160
+ });
161
+
162
+ // ─── Skip-Heuristic Tests ─────────────────────────────────
163
+
164
+ describe('Install Skip Heuristic', () => {
165
+ let testDir: string;
166
+
167
+ beforeEach(() => {
168
+ testDir = fs.mkdtempSync(path.join(tmpdir(), 'install-skip-'));
169
+ });
170
+
171
+ afterEach(() => {
172
+ fs.rmSync(testDir, { recursive: true, force: true });
173
+ });
174
+
175
+ it('should return false when no marker exists', () => {
176
+ const snap = makeSnapshot({
177
+ config_files: [makeConfig('package-lock.json', { content_hash: 'hash1' })],
178
+ package_manager: 'npm',
179
+ });
180
+
181
+ expect(shouldSkipInstall(testDir, snap)).toBe(false);
182
+ });
183
+
184
+ it('should return true when marker matches lockfile hash and node_modules exists', () => {
185
+ const snap = makeSnapshot({
186
+ config_files: [makeConfig('package-lock.json', { content_hash: 'hash1' })],
187
+ package_manager: 'npm',
188
+ });
189
+
190
+ // Create node_modules directory
191
+ fs.mkdirSync(path.join(testDir, 'node_modules'));
192
+
193
+ // Write marker
194
+ writeInstallMarker(testDir, snap);
195
+
196
+ expect(shouldSkipInstall(testDir, snap)).toBe(true);
197
+ });
198
+
199
+ it('should return false when lockfile hash differs', () => {
200
+ const snap1 = makeSnapshot({
201
+ config_files: [makeConfig('package-lock.json', { content_hash: 'hash1' })],
202
+ package_manager: 'npm',
203
+ });
204
+ const snap2 = makeSnapshot({
205
+ config_files: [makeConfig('package-lock.json', { content_hash: 'hash2' })],
206
+ package_manager: 'npm',
207
+ });
208
+
209
+ // Create node_modules directory
210
+ fs.mkdirSync(path.join(testDir, 'node_modules'));
211
+
212
+ // Write marker with old hash
213
+ writeInstallMarker(testDir, snap1);
214
+
215
+ // Check with new hash
216
+ expect(shouldSkipInstall(testDir, snap2)).toBe(false);
217
+ });
218
+
219
+ it('should return false when node_modules is missing even if marker matches', () => {
220
+ const snap = makeSnapshot({
221
+ config_files: [makeConfig('package-lock.json', { content_hash: 'hash1' })],
222
+ package_manager: 'npm',
223
+ });
224
+
225
+ // Write marker but do NOT create node_modules
226
+ writeInstallMarker(testDir, snap);
227
+
228
+ expect(shouldSkipInstall(testDir, snap)).toBe(false);
229
+ });
230
+
231
+ it('should invalidate marker when called', () => {
232
+ const snap = makeSnapshot({
233
+ config_files: [makeConfig('package-lock.json', { content_hash: 'hash1' })],
234
+ package_manager: 'npm',
235
+ });
236
+
237
+ // Create node_modules and write marker
238
+ fs.mkdirSync(path.join(testDir, 'node_modules'));
239
+ writeInstallMarker(testDir, snap);
240
+ expect(shouldSkipInstall(testDir, snap)).toBe(true);
241
+
242
+ // Invalidate
243
+ invalidateInstallMarker(testDir);
244
+ expect(shouldSkipInstall(testDir, snap)).toBe(false);
245
+ });
246
+
247
+ it('should work with python .venv directory for poetry projects', () => {
248
+ const snap = makeSnapshot({
249
+ config_files: [makeConfig('poetry.lock', { content_hash: 'pyhash1' })],
250
+ package_manager: 'poetry',
251
+ });
252
+
253
+ // Create .venv directory
254
+ fs.mkdirSync(path.join(testDir, '.venv'));
255
+
256
+ // Write marker
257
+ writeInstallMarker(testDir, snap);
258
+
259
+ expect(shouldSkipInstall(testDir, snap)).toBe(true);
260
+ });
261
+ });