edsger 0.36.2 → 0.37.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 (127) hide show
  1. package/.claude/settings.local.json +23 -3
  2. package/.env.local +12 -0
  3. package/dist/api/features/__tests__/regression-prevention.test.d.ts +5 -0
  4. package/dist/api/features/__tests__/regression-prevention.test.js +338 -0
  5. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +5 -0
  6. package/dist/api/features/__tests__/status-updater.integration.test.js +497 -0
  7. package/dist/api/growth.d.ts +23 -1
  8. package/dist/api/growth.js +25 -0
  9. package/dist/commands/app-store/index.js +2 -6
  10. package/dist/commands/code-review/index.js +3 -3
  11. package/dist/commands/growth-analysis/index.js +2 -0
  12. package/dist/commands/init/index.js +3 -3
  13. package/dist/commands/workflow/pipeline-runner.d.ts +17 -0
  14. package/dist/commands/workflow/pipeline-runner.js +393 -0
  15. package/dist/commands/workflow/runner.d.ts +26 -0
  16. package/dist/commands/workflow/runner.js +119 -0
  17. package/dist/commands/workflow/workflow-runner.d.ts +26 -0
  18. package/dist/commands/workflow/workflow-runner.js +119 -0
  19. package/dist/index.js +4 -0
  20. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +452 -32
  21. package/dist/phases/app-store-generation/assets/inter-latin-ext.woff2 +0 -0
  22. package/dist/phases/app-store-generation/assets/inter-latin.woff2 +0 -0
  23. package/dist/phases/app-store-generation/inter-font.d.ts +20 -0
  24. package/dist/phases/app-store-generation/inter-font.js +49 -0
  25. package/dist/phases/app-store-generation/screenshot-composer.js +183 -19
  26. package/dist/phases/code-implementation/analyzer-helpers.d.ts +28 -0
  27. package/dist/phases/code-implementation/analyzer-helpers.js +177 -0
  28. package/dist/phases/code-implementation/analyzer.d.ts +32 -0
  29. package/dist/phases/code-implementation/analyzer.js +629 -0
  30. package/dist/phases/code-implementation/context-fetcher.d.ts +17 -0
  31. package/dist/phases/code-implementation/context-fetcher.js +86 -0
  32. package/dist/phases/code-implementation/mcp-server.d.ts +1 -0
  33. package/dist/phases/code-implementation/mcp-server.js +93 -0
  34. package/dist/phases/code-implementation/prompts-improvement.d.ts +5 -0
  35. package/dist/phases/code-implementation/prompts-improvement.js +108 -0
  36. package/dist/phases/code-implementation-verification/verifier.d.ts +31 -0
  37. package/dist/phases/code-implementation-verification/verifier.js +196 -0
  38. package/dist/phases/code-refine/analyzer.d.ts +41 -0
  39. package/dist/phases/code-refine/analyzer.js +561 -0
  40. package/dist/phases/code-refine/context-fetcher.d.ts +94 -0
  41. package/dist/phases/code-refine/context-fetcher.js +423 -0
  42. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +22 -0
  43. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +134 -0
  44. package/dist/phases/code-refine-verification/verifier.d.ts +47 -0
  45. package/dist/phases/code-refine-verification/verifier.js +597 -0
  46. package/dist/phases/code-review/analyzer.d.ts +29 -0
  47. package/dist/phases/code-review/analyzer.js +363 -0
  48. package/dist/phases/code-review/context-fetcher.d.ts +92 -0
  49. package/dist/phases/code-review/context-fetcher.js +296 -0
  50. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +10 -0
  51. package/dist/phases/feature-analysis/analyzer-helpers.js +47 -0
  52. package/dist/phases/feature-analysis/analyzer.d.ts +11 -0
  53. package/dist/phases/feature-analysis/analyzer.js +208 -0
  54. package/dist/phases/feature-analysis/context-fetcher.d.ts +26 -0
  55. package/dist/phases/feature-analysis/context-fetcher.js +134 -0
  56. package/dist/phases/feature-analysis/http-fallback.d.ts +20 -0
  57. package/dist/phases/feature-analysis/http-fallback.js +95 -0
  58. package/dist/phases/feature-analysis/mcp-server.d.ts +1 -0
  59. package/dist/phases/feature-analysis/mcp-server.js +144 -0
  60. package/dist/phases/feature-analysis/prompts-improvement.d.ts +8 -0
  61. package/dist/phases/feature-analysis/prompts-improvement.js +109 -0
  62. package/dist/phases/feature-analysis-verification/verifier.d.ts +37 -0
  63. package/dist/phases/feature-analysis-verification/verifier.js +147 -0
  64. package/dist/phases/growth-analysis/context.d.ts +2 -2
  65. package/dist/phases/growth-analysis/context.js +18 -4
  66. package/dist/phases/growth-analysis/index.d.ts +2 -0
  67. package/dist/phases/growth-analysis/index.js +21 -13
  68. package/dist/phases/technical-design/analyzer-helpers.d.ts +25 -0
  69. package/dist/phases/technical-design/analyzer-helpers.js +39 -0
  70. package/dist/phases/technical-design/analyzer.d.ts +21 -0
  71. package/dist/phases/technical-design/analyzer.js +461 -0
  72. package/dist/phases/technical-design/context-fetcher.d.ts +12 -0
  73. package/dist/phases/technical-design/context-fetcher.js +39 -0
  74. package/dist/phases/technical-design/http-fallback.d.ts +17 -0
  75. package/dist/phases/technical-design/http-fallback.js +151 -0
  76. package/dist/phases/technical-design/mcp-server.d.ts +1 -0
  77. package/dist/phases/technical-design/mcp-server.js +157 -0
  78. package/dist/phases/technical-design/prompts-improvement.d.ts +5 -0
  79. package/dist/phases/technical-design/prompts-improvement.js +93 -0
  80. package/dist/phases/technical-design-verification/verifier.d.ts +53 -0
  81. package/dist/phases/technical-design-verification/verifier.js +170 -0
  82. package/dist/services/feature-branches.d.ts +77 -0
  83. package/dist/services/feature-branches.js +205 -0
  84. package/dist/services/video/device-frames.d.ts +1 -1
  85. package/dist/services/video/device-frames.js +81 -3
  86. package/dist/services/video/index.d.ts +1 -1
  87. package/dist/services/video/index.js +12 -6
  88. package/dist/services/video/screenshot-generator.js +5 -8
  89. package/dist/services/video/video-assembler.js +2 -6
  90. package/dist/types/index.d.ts +2 -0
  91. package/dist/utils/validation.d.ts +11 -2
  92. package/dist/utils/validation.js +93 -6
  93. package/dist/workflow-runner/config/phase-configs.d.ts +5 -0
  94. package/dist/workflow-runner/config/phase-configs.js +120 -0
  95. package/dist/workflow-runner/core/feature-filter.d.ts +16 -0
  96. package/dist/workflow-runner/core/feature-filter.js +46 -0
  97. package/dist/workflow-runner/core/index.d.ts +8 -0
  98. package/dist/workflow-runner/core/index.js +12 -0
  99. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +24 -0
  100. package/dist/workflow-runner/core/pipeline-evaluator.js +32 -0
  101. package/dist/workflow-runner/core/state-manager.d.ts +24 -0
  102. package/dist/workflow-runner/core/state-manager.js +42 -0
  103. package/dist/workflow-runner/core/workflow-logger.d.ts +20 -0
  104. package/dist/workflow-runner/core/workflow-logger.js +65 -0
  105. package/dist/workflow-runner/executors/phase-executor.d.ts +8 -0
  106. package/dist/workflow-runner/executors/phase-executor.js +248 -0
  107. package/dist/workflow-runner/feature-workflow-runner.d.ts +26 -0
  108. package/dist/workflow-runner/feature-workflow-runner.js +119 -0
  109. package/dist/workflow-runner/index.d.ts +2 -0
  110. package/dist/workflow-runner/index.js +2 -0
  111. package/dist/workflow-runner/pipeline-runner.d.ts +17 -0
  112. package/dist/workflow-runner/pipeline-runner.js +393 -0
  113. package/dist/workflow-runner/workflow-processor.d.ts +54 -0
  114. package/dist/workflow-runner/workflow-processor.js +170 -0
  115. package/package.json +2 -2
  116. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
  117. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
  118. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
  119. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
  120. package/dist/services/lifecycle-agent/index.d.ts +0 -24
  121. package/dist/services/lifecycle-agent/index.js +0 -25
  122. package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
  123. package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
  124. package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
  125. package/dist/services/lifecycle-agent/transition-rules.js +0 -184
  126. package/dist/services/lifecycle-agent/types.d.ts +0 -190
  127. package/dist/services/lifecycle-agent/types.js +0 -12
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Feature Branches service for pipeline integration
3
+ * Allows phases to manage feature branches via MCP
4
+ */
5
+ import { callMcpEndpoint } from '../api/mcp-client.js';
6
+ /**
7
+ * List all branches for a feature
8
+ */
9
+ export async function getFeatureBranches(options) {
10
+ const { featureId, verbose } = options;
11
+ if (verbose) {
12
+ console.log(`📋 Fetching feature branches for feature: ${featureId}`);
13
+ }
14
+ const result = (await callMcpEndpoint('feature_branches/list', {
15
+ feature_id: featureId,
16
+ }));
17
+ const branches = result?.feature_branches || [];
18
+ if (verbose) {
19
+ console.log(`✅ Found ${branches.length} feature branches`);
20
+ }
21
+ return branches;
22
+ }
23
+ /**
24
+ * Get the current active branch for a feature
25
+ */
26
+ export async function getCurrentBranch(options) {
27
+ const { featureId, verbose } = options;
28
+ if (verbose) {
29
+ console.log(`📋 Getting current branch for feature: ${featureId}`);
30
+ }
31
+ const result = (await callMcpEndpoint('feature_branches/current', {
32
+ feature_id: featureId,
33
+ }));
34
+ return result?.current_branch || null;
35
+ }
36
+ /**
37
+ * Create feature branches
38
+ */
39
+ export async function createFeatureBranches(options, branches) {
40
+ const { featureId, verbose } = options;
41
+ if (verbose) {
42
+ console.log(`📋 Creating ${branches.length} feature branches for feature: ${featureId}`);
43
+ }
44
+ const result = (await callMcpEndpoint('feature_branches/create', {
45
+ feature_id: featureId,
46
+ branches: branches,
47
+ }));
48
+ const createdBranches = result?.created_branches || [];
49
+ if (verbose) {
50
+ console.log(`✅ Created ${createdBranches.length} feature branches`);
51
+ createdBranches.forEach((b, idx) => {
52
+ console.log(` ${idx + 1}. ${b.name} (status: ${b.status})`);
53
+ });
54
+ }
55
+ return createdBranches;
56
+ }
57
+ /**
58
+ * Update a feature branch
59
+ */
60
+ export async function updateFeatureBranch(branchId, updates, verbose) {
61
+ if (verbose) {
62
+ console.log(`📋 Updating feature branch: ${branchId}`);
63
+ }
64
+ const result = (await callMcpEndpoint('feature_branches/update', {
65
+ branch_id: branchId,
66
+ ...updates,
67
+ }));
68
+ if (verbose) {
69
+ console.log(`✅ Feature branch updated successfully`);
70
+ }
71
+ return result?.feature_branch;
72
+ }
73
+ /**
74
+ * Clear all branches for a feature (used before re-planning)
75
+ */
76
+ export async function clearFeatureBranches(options, force = false) {
77
+ const { featureId, verbose } = options;
78
+ if (verbose) {
79
+ console.log(`📋 Clearing feature branches for feature: ${featureId}`);
80
+ }
81
+ const result = (await callMcpEndpoint('feature_branches/clear', {
82
+ feature_id: featureId,
83
+ force: force,
84
+ }));
85
+ const deletedCount = result?.deleted_count || 0;
86
+ if (verbose) {
87
+ console.log(`✅ Cleared ${deletedCount} feature branches`);
88
+ }
89
+ return deletedCount;
90
+ }
91
+ /**
92
+ * Format feature branches for context (to include in prompts)
93
+ */
94
+ export function formatBranchesForContext(branches) {
95
+ if (!branches || branches.length === 0) {
96
+ return 'No feature branches defined yet.';
97
+ }
98
+ const branchList = branches
99
+ .map((b, idx) => {
100
+ const statusEmoji = b.status === 'merged'
101
+ ? '✅'
102
+ : b.status === 'in_progress'
103
+ ? '🔄'
104
+ : b.status === 'ready_for_review'
105
+ ? '👀'
106
+ : b.status === 'closed'
107
+ ? '❌'
108
+ : '⏳';
109
+ return `${idx + 1}. **${b.name}** ${statusEmoji}
110
+ - Status: ${b.status}
111
+ - Branch: ${b.branch_name || 'Not created'}
112
+ - PR: ${b.pull_request_url || 'Not created'}
113
+ - Description: ${b.description || 'No description'}`;
114
+ })
115
+ .join('\n\n');
116
+ return `# Feature Branches
117
+
118
+ ${branchList}
119
+
120
+ Total: ${branches.length} branches
121
+ Merged: ${branches.filter((b) => b.status === 'merged').length}
122
+ In Progress: ${branches.filter((b) => b.status === 'in_progress').length}
123
+ Pending: ${branches.filter((b) => b.status === 'pending').length}`;
124
+ }
125
+ /**
126
+ * Check if feature has multiple branches planned
127
+ */
128
+ export async function hasMultipleBranches(options) {
129
+ const branches = await getFeatureBranches(options);
130
+ return branches.length > 1;
131
+ }
132
+ /**
133
+ * Get next pending branch to work on
134
+ */
135
+ export async function getNextPendingBranch(options) {
136
+ const branches = await getFeatureBranches(options);
137
+ return branches.find((b) => b.status === 'pending') || null;
138
+ }
139
+ /**
140
+ * Check if all branches are completed
141
+ */
142
+ export async function allBranchesCompleted(options) {
143
+ const branches = await getFeatureBranches(options);
144
+ if (branches.length === 0)
145
+ return true;
146
+ return branches.every((b) => b.status === 'merged' || b.status === 'closed');
147
+ }
148
+ /**
149
+ * Get the base branch information for a feature branch.
150
+ * Returns:
151
+ * - If base_branch_id is set and that branch is merged: use main
152
+ * - If base_branch_id is set and that branch is not merged: use that branch's branch_name
153
+ * - If base_branch_id is null: use main
154
+ */
155
+ export async function getBaseBranchInfo(branch, allBranches, mainBranch = 'main') {
156
+ // No base branch - start from main
157
+ if (!branch.base_branch_id) {
158
+ return {
159
+ baseBranch: mainBranch,
160
+ needsRebase: false,
161
+ baseBranchMerged: true,
162
+ };
163
+ }
164
+ // Find the base branch
165
+ const baseBranch = allBranches.find((b) => b.id === branch.base_branch_id);
166
+ if (!baseBranch) {
167
+ // Base branch not found - fall back to main
168
+ return {
169
+ baseBranch: mainBranch,
170
+ needsRebase: false,
171
+ baseBranchMerged: true,
172
+ };
173
+ }
174
+ // Check if base branch is merged
175
+ if (baseBranch.status === 'merged') {
176
+ // Base branch is merged to main - we should base on main and rebase if needed
177
+ return { baseBranch: mainBranch, needsRebase: true, baseBranchMerged: true };
178
+ }
179
+ // Base branch is not merged - we should base on that branch
180
+ if (!baseBranch.branch_name) {
181
+ // Base branch doesn't have a git branch yet - fall back to main
182
+ return {
183
+ baseBranch: mainBranch,
184
+ needsRebase: false,
185
+ baseBranchMerged: false,
186
+ };
187
+ }
188
+ return {
189
+ baseBranch: baseBranch.branch_name,
190
+ needsRebase: false,
191
+ baseBranchMerged: false,
192
+ };
193
+ }
194
+ /**
195
+ * Get branch by ID
196
+ */
197
+ export async function getBranchById(branchId, verbose) {
198
+ if (verbose) {
199
+ console.log(`📋 Fetching feature branch: ${branchId}`);
200
+ }
201
+ const result = (await callMcpEndpoint('feature_branches/get', {
202
+ branch_id: branchId,
203
+ }));
204
+ return result?.feature_branch || null;
205
+ }
@@ -3,7 +3,7 @@
3
3
  * Wraps raw screenshots in realistic device mockups (MacBook, iPhone, browser window).
4
4
  */
5
5
  /** Supported device frame types */
6
- export type DeviceFrameType = 'macbook' | 'iphone' | 'browser' | 'none';
6
+ export type DeviceFrameType = 'macbook' | 'iphone' | 'ipad' | 'browser' | 'none';
7
7
  /** Device presets with recommended viewport sizes */
8
8
  export declare const DEVICE_PRESETS: Record<Exclude<DeviceFrameType, 'none'>, {
9
9
  viewportWidth: number;
@@ -6,6 +6,7 @@
6
6
  export const DEVICE_PRESETS = {
7
7
  macbook: { viewportWidth: 1280, viewportHeight: 800, label: 'MacBook Pro' },
8
8
  iphone: { viewportWidth: 390, viewportHeight: 844, label: 'iPhone 15 Pro' },
9
+ ipad: { viewportWidth: 1024, viewportHeight: 1366, label: 'iPad Pro 12.9"' },
9
10
  browser: {
10
11
  viewportWidth: 1280,
11
12
  viewportHeight: 720,
@@ -27,6 +28,8 @@ export function generateDeviceFrameHtml(screenshotDataUrl, deviceType, options)
27
28
  return generateMacBookFrame(screenshotDataUrl, background, shadow, canvasWidth ?? 1600, canvasHeight ?? 1000);
28
29
  case 'iphone':
29
30
  return generateIPhoneFrame(screenshotDataUrl, background, shadow, canvasWidth ?? 800, canvasHeight ?? 1200);
31
+ case 'ipad':
32
+ return generateIPadFrame(screenshotDataUrl, background, shadow, canvasWidth ?? 900, canvasHeight ?? 1200);
30
33
  case 'browser':
31
34
  return generateBrowserFrame({
32
35
  screenshotDataUrl,
@@ -59,7 +62,7 @@ function generateMacBookFrame(screenshotDataUrl, background, shadow, canvasWidth
59
62
  }
60
63
  .macbook {
61
64
  position: relative;
62
- width: 1340px;
65
+ width: 84%;
63
66
  }
64
67
  .screen {
65
68
  background: #1a1a1a;
@@ -148,7 +151,7 @@ function generateIPhoneFrame(screenshotDataUrl, background, shadow, canvasWidth,
148
151
  }
149
152
  .iphone {
150
153
  position: relative;
151
- width: 430px;
154
+ width: 86%;
152
155
  background: #1a1a1a;
153
156
  border-radius: 54px;
154
157
  padding: 16px;
@@ -246,6 +249,81 @@ function generateIPhoneFrame(screenshotDataUrl, background, shadow, canvasWidth,
246
249
  </body>
247
250
  </html>`;
248
251
  }
252
+ function generateIPadFrame(screenshotDataUrl, background, shadow, canvasWidth, canvasHeight) {
253
+ return `<!DOCTYPE html>
254
+ <html>
255
+ <head>
256
+ <style>
257
+ * { margin: 0; padding: 0; box-sizing: border-box; }
258
+ body {
259
+ width: ${canvasWidth}px;
260
+ height: ${canvasHeight}px;
261
+ display: flex;
262
+ align-items: center;
263
+ justify-content: center;
264
+ background: ${background};
265
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
266
+ overflow: hidden;
267
+ }
268
+ .ipad {
269
+ position: relative;
270
+ width: 78%;
271
+ background: #1a1a1a;
272
+ border-radius: 28px;
273
+ padding: 14px;
274
+ ${shadow ? 'box-shadow: 0 30px 80px rgba(0,0,0,0.5), inset 0 0 0 2px #333;' : ''}
275
+ }
276
+ .ipad-inner {
277
+ background: #000;
278
+ border-radius: 18px;
279
+ overflow: hidden;
280
+ position: relative;
281
+ }
282
+ .front-camera {
283
+ position: absolute;
284
+ top: 8px;
285
+ left: 50%;
286
+ transform: translateX(-50%);
287
+ width: 8px;
288
+ height: 8px;
289
+ background: #1a1a2a;
290
+ border-radius: 50%;
291
+ z-index: 10;
292
+ }
293
+ .screen-content {
294
+ position: relative;
295
+ }
296
+ .screen-content img {
297
+ width: 100%;
298
+ display: block;
299
+ border-radius: 18px;
300
+ }
301
+ .home-indicator {
302
+ position: absolute;
303
+ bottom: 6px;
304
+ left: 50%;
305
+ transform: translateX(-50%);
306
+ width: 160px;
307
+ height: 5px;
308
+ background: rgba(255,255,255,0.3);
309
+ border-radius: 3px;
310
+ z-index: 10;
311
+ }
312
+ </style>
313
+ </head>
314
+ <body>
315
+ <div class="ipad">
316
+ <div class="ipad-inner">
317
+ <div class="front-camera"></div>
318
+ <div class="screen-content">
319
+ <img src="${screenshotDataUrl}" alt="screenshot" />
320
+ </div>
321
+ <div class="home-indicator"></div>
322
+ </div>
323
+ </div>
324
+ </body>
325
+ </html>`;
326
+ }
249
327
  function generateBrowserFrame(opts) {
250
328
  const { screenshotDataUrl, background, shadow, title, url, canvasWidth, canvasHeight, } = opts;
251
329
  return `<!DOCTYPE html>
@@ -264,7 +342,7 @@ function generateBrowserFrame(opts) {
264
342
  overflow: hidden;
265
343
  }
266
344
  .browser {
267
- width: 1400px;
345
+ width: 88%;
268
346
  background: #fff;
269
347
  border-radius: 12px;
270
348
  overflow: hidden;
@@ -46,7 +46,7 @@ export interface VideoGenerationOptions {
46
46
  }
47
47
  /**
48
48
  * Check system dependencies for video generation.
49
- * Returns a list of missing dependencies.
49
+ * Auto-installs Playwright if missing. Returns a list of still-missing dependencies.
50
50
  */
51
51
  export declare function checkVideoDependencies(): Promise<string[]>;
52
52
  /**
@@ -5,22 +5,28 @@
5
5
  import { mkdir, rm } from 'fs/promises';
6
6
  import { join } from 'path';
7
7
  import { logError, logInfo, logSuccess, logWarning, } from '../../utils/logger.js';
8
+ import { ensureFFmpeg, ensurePlaywright } from '../../utils/validation.js';
8
9
  import { captureStaticScreenshots, getVideoTempDir, isPlaywrightAvailable, } from './screenshot-generator.js';
9
10
  import { generateNarration } from './tts-generator.js';
10
11
  import { assembleVideo, isFfmpegAvailable } from './video-assembler.js';
11
12
  import { saveVideoMetadata, uploadGrowthVideo } from './video-uploader.js';
12
13
  /**
13
14
  * Check system dependencies for video generation.
14
- * Returns a list of missing dependencies.
15
+ * Auto-installs Playwright if missing. Returns a list of still-missing dependencies.
15
16
  */
16
17
  export async function checkVideoDependencies() {
17
18
  const missing = [];
18
- if (!isFfmpegAvailable()) {
19
- missing.push('ffmpeg (install: brew install ffmpeg / apt install ffmpeg)');
19
+ try {
20
+ ensureFFmpeg(isFfmpegAvailable);
21
+ }
22
+ catch {
23
+ missing.push('ffmpeg (auto-install failed — install manually: brew install ffmpeg / apt install ffmpeg)');
24
+ }
25
+ try {
26
+ await ensurePlaywright(isPlaywrightAvailable);
20
27
  }
21
- const playwrightReady = await isPlaywrightAvailable();
22
- if (!playwrightReady) {
23
- missing.push('playwright (install: npm install playwright && npx playwright install chromium)');
28
+ catch {
29
+ missing.push('playwright (auto-install failed — run: npm install playwright && npx playwright install chromium)');
24
30
  }
25
31
  return missing;
26
32
  }
@@ -7,6 +7,7 @@ import { mkdir, readFile } from 'fs/promises';
7
7
  import { tmpdir } from 'os';
8
8
  import { join } from 'path';
9
9
  import { logError, logInfo, logWarning } from '../../utils/logger.js';
10
+ import { ensurePlaywright } from '../../utils/validation.js';
10
11
  import { generateDeviceFrameHtml, } from './device-frames.js';
11
12
  /**
12
13
  * Dynamically load Playwright's chromium launcher.
@@ -101,10 +102,8 @@ async function applyDeviceFrame(screenshotPath, scene, browser, verbose) {
101
102
  * Requires Playwright to be installed: npm install playwright
102
103
  */
103
104
  export async function captureScreenshots(scenes, options, verbose) {
104
- const chromium = await loadChromium();
105
- if (!chromium) {
106
- throw new Error('Playwright is not installed. Install it with: npm install playwright && npx playwright install chromium');
107
- }
105
+ await ensurePlaywright(isPlaywrightAvailable);
106
+ const chromium = (await loadChromium());
108
107
  const { baseUrl, outputDir, viewportWidth = 1280, viewportHeight = 720, fullPage = false, } = options;
109
108
  // Ensure output directory exists
110
109
  await mkdir(outputDir, { recursive: true });
@@ -194,10 +193,8 @@ export async function captureScreenshots(scenes, options, verbose) {
194
193
  * Creates screenshots from pre-built HTML templates with mock data.
195
194
  */
196
195
  export async function captureStaticScreenshots(scenes, outputDir, htmlTemplates, verbose) {
197
- const chromium = await loadChromium();
198
- if (!chromium) {
199
- throw new Error('Playwright is not installed. Install it with: npm install playwright && npx playwright install chromium');
200
- }
196
+ await ensurePlaywright(isPlaywrightAvailable);
197
+ const chromium = (await loadChromium());
201
198
  await mkdir(outputDir, { recursive: true });
202
199
  if (verbose) {
203
200
  logInfo(`Rendering ${scenes.length} static HTML scenes`);
@@ -6,6 +6,7 @@ import { execFileSync, execSync } from 'child_process';
6
6
  import { mkdir, writeFile } from 'fs/promises';
7
7
  import { join } from 'path';
8
8
  import { logError, logInfo, logWarning } from '../../utils/logger.js';
9
+ import { ensureFFmpeg } from '../../utils/validation.js';
9
10
  import { getAudioDuration } from './tts-generator.js';
10
11
  /**
11
12
  * Check if ffmpeg is available on the system
@@ -146,12 +147,7 @@ async function simpleConcatenate(segmentPaths, outputPath, tempDir, verbose) {
146
147
  */
147
148
  // eslint-disable-next-line complexity -- sequential video assembly with scene processing and ffmpeg orchestration
148
149
  export async function assembleVideo(scenes, audioFiles, options, verbose) {
149
- if (!isFfmpegAvailable()) {
150
- throw new Error('ffmpeg is not installed. Install it:\n' +
151
- ' macOS: brew install ffmpeg\n' +
152
- ' Ubuntu: sudo apt install ffmpeg\n' +
153
- ' Windows: choco install ffmpeg');
154
- }
150
+ ensureFFmpeg(isFfmpegAvailable);
155
151
  const { outputPath, width: optWidth, height: optHeight, fps = 30, codec = 'libx264', preset = 'medium', crossfadeDuration = 0.5, } = options;
156
152
  // Ensure output directory exists
157
153
  const outputDir = join(outputPath, '..');
@@ -41,6 +41,8 @@ export interface CliOptions {
41
41
  refactor?: boolean;
42
42
  init?: boolean;
43
43
  growthAnalysis?: string;
44
+ growthGuidance?: string;
45
+ growthAnalysisId?: string;
44
46
  productLevel?: boolean;
45
47
  concurrency?: number;
46
48
  watchTasks?: string;
@@ -21,6 +21,15 @@ export declare const validateMCPEnvironment: () => {
21
21
  */
22
22
  export declare const validateCommandEnvironment: (options: CliOptions) => ValidationResult;
23
23
  /**
24
- * Check if required dependencies are installed
24
+ * Ensure Playwright is installed, auto-installing if necessary.
25
+ * Installs the npm package and Chromium browser when missing.
25
26
  */
26
- export declare const validateRequirements: (checkFunction: () => Promise<boolean>, errorMessage: string) => Promise<void>;
27
+ export declare const ensurePlaywright: (checkFunction: () => Promise<boolean>) => Promise<void>;
28
+ /**
29
+ * Ensure an npm package is installed, auto-installing if necessary.
30
+ */
31
+ export declare const ensureNpmPackage: (checkFunction: () => Promise<boolean>, packageName: string) => Promise<void>;
32
+ /**
33
+ * Ensure ffmpeg is installed, auto-installing via Homebrew on macOS if possible.
34
+ */
35
+ export declare const ensureFFmpeg: (checkFunction: () => boolean) => void;
@@ -1,6 +1,7 @@
1
+ import { execSync } from 'child_process';
1
2
  import { getMcpServerUrl, getMcpToken } from '../auth/auth-store.js';
2
3
  import { loadConfig, validateConfig } from '../config.js';
3
- import { logInfo } from './logger.js';
4
+ import { logInfo, logWarning } from './logger.js';
4
5
  /**
5
6
  * Common configuration validation for all CLI commands
6
7
  */
@@ -49,11 +50,97 @@ export const validateCommandEnvironment = (options) => {
49
50
  };
50
51
  };
51
52
  /**
52
- * Check if required dependencies are installed
53
+ * Ensure Playwright is installed, auto-installing if necessary.
54
+ * Installs the npm package and Chromium browser when missing.
53
55
  */
54
- export const validateRequirements = async (checkFunction, errorMessage) => {
55
- const hasRequirements = await checkFunction();
56
- if (!hasRequirements) {
57
- throw new Error(errorMessage);
56
+ export const ensurePlaywright = async (checkFunction) => {
57
+ if (await checkFunction()) {
58
+ return;
58
59
  }
60
+ logWarning('Playwright is not installed. Installing automatically (this may take a minute)...');
61
+ try {
62
+ logInfo(' Installing playwright package...');
63
+ execSync('npm install playwright', { stdio: 'inherit' });
64
+ logInfo(' Installing Chromium browser...');
65
+ execSync('npx playwright install chromium', { stdio: 'inherit' });
66
+ }
67
+ catch {
68
+ throw new Error('Failed to auto-install Playwright.\n\n' +
69
+ ' Please install it manually:\n\n' +
70
+ ' npm install playwright\n' +
71
+ ' npx playwright install chromium\n\n' +
72
+ ' Then re-run this command. Use --listings-only to skip screenshots.');
73
+ }
74
+ // Verify installation succeeded
75
+ if (!(await checkFunction())) {
76
+ throw new Error('Playwright was installed but is still not available.\n\n' +
77
+ ' Try installing manually:\n\n' +
78
+ ' npm install playwright\n' +
79
+ ' npx playwright install chromium\n\n' +
80
+ ' Then re-run this command. Use --listings-only to skip screenshots.');
81
+ }
82
+ logInfo(' Playwright installed successfully!');
83
+ };
84
+ /**
85
+ * Ensure an npm package is installed, auto-installing if necessary.
86
+ */
87
+ export const ensureNpmPackage = async (checkFunction, packageName) => {
88
+ if (await checkFunction()) {
89
+ return;
90
+ }
91
+ logWarning(`${packageName} is not installed. Installing automatically...`);
92
+ try {
93
+ execSync(`npm install ${packageName}`, { stdio: 'inherit' });
94
+ }
95
+ catch {
96
+ throw new Error(`Failed to auto-install ${packageName}.\n\n` +
97
+ ` Please install it manually:\n\n` +
98
+ ` npm install ${packageName}`);
99
+ }
100
+ if (!(await checkFunction())) {
101
+ throw new Error(`${packageName} was installed but is still not available.\n\n` +
102
+ ` Try installing manually:\n\n` +
103
+ ` npm install ${packageName}`);
104
+ }
105
+ logInfo(` ${packageName} installed successfully!`);
106
+ };
107
+ /**
108
+ * Ensure ffmpeg is installed, auto-installing via Homebrew on macOS if possible.
109
+ */
110
+ export const ensureFFmpeg = (checkFunction) => {
111
+ if (checkFunction()) {
112
+ return;
113
+ }
114
+ // Attempt auto-install on macOS via Homebrew
115
+ if (process.platform === 'darwin') {
116
+ try {
117
+ execSync('which brew', { stdio: 'ignore' });
118
+ }
119
+ catch {
120
+ throw new Error('ffmpeg is not installed and Homebrew was not found.\n\n' +
121
+ ' Install Homebrew first (https://brew.sh), then run:\n\n' +
122
+ ' brew install ffmpeg');
123
+ }
124
+ logWarning('ffmpeg is not installed. Installing via Homebrew (this may take a few minutes)...');
125
+ try {
126
+ execSync('brew install ffmpeg', { stdio: 'inherit' });
127
+ }
128
+ catch {
129
+ throw new Error('Failed to auto-install ffmpeg via Homebrew.\n\n' +
130
+ ' Please install it manually:\n\n' +
131
+ ' brew install ffmpeg');
132
+ }
133
+ if (!checkFunction()) {
134
+ throw new Error('ffmpeg was installed but is still not available.\n\n' +
135
+ ' Try installing manually:\n\n' +
136
+ ' brew install ffmpeg');
137
+ }
138
+ logInfo(' ffmpeg installed successfully!');
139
+ return;
140
+ }
141
+ // Non-macOS: provide platform-specific instructions
142
+ throw new Error('ffmpeg is not installed. Install it:\n' +
143
+ ' Ubuntu/Debian: sudo apt install ffmpeg\n' +
144
+ ' Fedora: sudo dnf install ffmpeg\n' +
145
+ ' Windows: choco install ffmpeg');
59
146
  };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Phase configurations for the pipeline runner
3
+ */
4
+ import { PhaseConfig } from '../../types/pipeline.js';
5
+ export declare const phaseConfigs: readonly PhaseConfig[];