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.
- package/.claude/settings.local.json +23 -3
- package/.env.local +12 -0
- package/dist/api/features/__tests__/regression-prevention.test.d.ts +5 -0
- package/dist/api/features/__tests__/regression-prevention.test.js +338 -0
- package/dist/api/features/__tests__/status-updater.integration.test.d.ts +5 -0
- package/dist/api/features/__tests__/status-updater.integration.test.js +497 -0
- package/dist/api/growth.d.ts +23 -1
- package/dist/api/growth.js +25 -0
- package/dist/commands/app-store/index.js +2 -6
- package/dist/commands/code-review/index.js +3 -3
- package/dist/commands/growth-analysis/index.js +2 -0
- package/dist/commands/init/index.js +3 -3
- package/dist/commands/workflow/pipeline-runner.d.ts +17 -0
- package/dist/commands/workflow/pipeline-runner.js +393 -0
- package/dist/commands/workflow/runner.d.ts +26 -0
- package/dist/commands/workflow/runner.js +119 -0
- package/dist/commands/workflow/workflow-runner.d.ts +26 -0
- package/dist/commands/workflow/workflow-runner.js +119 -0
- package/dist/index.js +4 -0
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +452 -32
- package/dist/phases/app-store-generation/assets/inter-latin-ext.woff2 +0 -0
- package/dist/phases/app-store-generation/assets/inter-latin.woff2 +0 -0
- package/dist/phases/app-store-generation/inter-font.d.ts +20 -0
- package/dist/phases/app-store-generation/inter-font.js +49 -0
- package/dist/phases/app-store-generation/screenshot-composer.js +183 -19
- package/dist/phases/code-implementation/analyzer-helpers.d.ts +28 -0
- package/dist/phases/code-implementation/analyzer-helpers.js +177 -0
- package/dist/phases/code-implementation/analyzer.d.ts +32 -0
- package/dist/phases/code-implementation/analyzer.js +629 -0
- package/dist/phases/code-implementation/context-fetcher.d.ts +17 -0
- package/dist/phases/code-implementation/context-fetcher.js +86 -0
- package/dist/phases/code-implementation/mcp-server.d.ts +1 -0
- package/dist/phases/code-implementation/mcp-server.js +93 -0
- package/dist/phases/code-implementation/prompts-improvement.d.ts +5 -0
- package/dist/phases/code-implementation/prompts-improvement.js +108 -0
- package/dist/phases/code-implementation-verification/verifier.d.ts +31 -0
- package/dist/phases/code-implementation-verification/verifier.js +196 -0
- package/dist/phases/code-refine/analyzer.d.ts +41 -0
- package/dist/phases/code-refine/analyzer.js +561 -0
- package/dist/phases/code-refine/context-fetcher.d.ts +94 -0
- package/dist/phases/code-refine/context-fetcher.js +423 -0
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +22 -0
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +134 -0
- package/dist/phases/code-refine-verification/verifier.d.ts +47 -0
- package/dist/phases/code-refine-verification/verifier.js +597 -0
- package/dist/phases/code-review/analyzer.d.ts +29 -0
- package/dist/phases/code-review/analyzer.js +363 -0
- package/dist/phases/code-review/context-fetcher.d.ts +92 -0
- package/dist/phases/code-review/context-fetcher.js +296 -0
- package/dist/phases/feature-analysis/analyzer-helpers.d.ts +10 -0
- package/dist/phases/feature-analysis/analyzer-helpers.js +47 -0
- package/dist/phases/feature-analysis/analyzer.d.ts +11 -0
- package/dist/phases/feature-analysis/analyzer.js +208 -0
- package/dist/phases/feature-analysis/context-fetcher.d.ts +26 -0
- package/dist/phases/feature-analysis/context-fetcher.js +134 -0
- package/dist/phases/feature-analysis/http-fallback.d.ts +20 -0
- package/dist/phases/feature-analysis/http-fallback.js +95 -0
- package/dist/phases/feature-analysis/mcp-server.d.ts +1 -0
- package/dist/phases/feature-analysis/mcp-server.js +144 -0
- package/dist/phases/feature-analysis/prompts-improvement.d.ts +8 -0
- package/dist/phases/feature-analysis/prompts-improvement.js +109 -0
- package/dist/phases/feature-analysis-verification/verifier.d.ts +37 -0
- package/dist/phases/feature-analysis-verification/verifier.js +147 -0
- package/dist/phases/growth-analysis/context.d.ts +2 -2
- package/dist/phases/growth-analysis/context.js +18 -4
- package/dist/phases/growth-analysis/index.d.ts +2 -0
- package/dist/phases/growth-analysis/index.js +21 -13
- package/dist/phases/technical-design/analyzer-helpers.d.ts +25 -0
- package/dist/phases/technical-design/analyzer-helpers.js +39 -0
- package/dist/phases/technical-design/analyzer.d.ts +21 -0
- package/dist/phases/technical-design/analyzer.js +461 -0
- package/dist/phases/technical-design/context-fetcher.d.ts +12 -0
- package/dist/phases/technical-design/context-fetcher.js +39 -0
- package/dist/phases/technical-design/http-fallback.d.ts +17 -0
- package/dist/phases/technical-design/http-fallback.js +151 -0
- package/dist/phases/technical-design/mcp-server.d.ts +1 -0
- package/dist/phases/technical-design/mcp-server.js +157 -0
- package/dist/phases/technical-design/prompts-improvement.d.ts +5 -0
- package/dist/phases/technical-design/prompts-improvement.js +93 -0
- package/dist/phases/technical-design-verification/verifier.d.ts +53 -0
- package/dist/phases/technical-design-verification/verifier.js +170 -0
- package/dist/services/feature-branches.d.ts +77 -0
- package/dist/services/feature-branches.js +205 -0
- package/dist/services/video/device-frames.d.ts +1 -1
- package/dist/services/video/device-frames.js +81 -3
- package/dist/services/video/index.d.ts +1 -1
- package/dist/services/video/index.js +12 -6
- package/dist/services/video/screenshot-generator.js +5 -8
- package/dist/services/video/video-assembler.js +2 -6
- package/dist/types/index.d.ts +2 -0
- package/dist/utils/validation.d.ts +11 -2
- package/dist/utils/validation.js +93 -6
- package/dist/workflow-runner/config/phase-configs.d.ts +5 -0
- package/dist/workflow-runner/config/phase-configs.js +120 -0
- package/dist/workflow-runner/core/feature-filter.d.ts +16 -0
- package/dist/workflow-runner/core/feature-filter.js +46 -0
- package/dist/workflow-runner/core/index.d.ts +8 -0
- package/dist/workflow-runner/core/index.js +12 -0
- package/dist/workflow-runner/core/pipeline-evaluator.d.ts +24 -0
- package/dist/workflow-runner/core/pipeline-evaluator.js +32 -0
- package/dist/workflow-runner/core/state-manager.d.ts +24 -0
- package/dist/workflow-runner/core/state-manager.js +42 -0
- package/dist/workflow-runner/core/workflow-logger.d.ts +20 -0
- package/dist/workflow-runner/core/workflow-logger.js +65 -0
- package/dist/workflow-runner/executors/phase-executor.d.ts +8 -0
- package/dist/workflow-runner/executors/phase-executor.js +248 -0
- package/dist/workflow-runner/feature-workflow-runner.d.ts +26 -0
- package/dist/workflow-runner/feature-workflow-runner.js +119 -0
- package/dist/workflow-runner/index.d.ts +2 -0
- package/dist/workflow-runner/index.js +2 -0
- package/dist/workflow-runner/pipeline-runner.d.ts +17 -0
- package/dist/workflow-runner/pipeline-runner.js +393 -0
- package/dist/workflow-runner/workflow-processor.d.ts +54 -0
- package/dist/workflow-runner/workflow-processor.js +170 -0
- package/package.json +2 -2
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
- package/dist/services/lifecycle-agent/index.d.ts +0 -24
- package/dist/services/lifecycle-agent/index.js +0 -25
- package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
- package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
- package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
- package/dist/services/lifecycle-agent/transition-rules.js +0 -184
- package/dist/services/lifecycle-agent/types.d.ts +0 -190
- package/dist/services/lifecycle-agent/types.js +0 -12
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inter font (variable weight) embedded as base64 WOFF2.
|
|
3
|
+
* Latin + Latin-ext subsets cover Western European languages.
|
|
4
|
+
* Font files are loaded from assets/ at runtime and cached in memory.
|
|
5
|
+
*
|
|
6
|
+
* Source: Google Fonts Inter v20, weights 100-900 (variable)
|
|
7
|
+
* Total size: ~130KB raw WOFF2 (Latin 47KB + Latin-ext 83KB)
|
|
8
|
+
*
|
|
9
|
+
* CJK limitation: Inter does not include Chinese, Japanese, or Korean
|
|
10
|
+
* glyphs. Text in these scripts falls back to the system font stack
|
|
11
|
+
* (-apple-system, BlinkMacSystemFont, etc.). To add CJK coverage,
|
|
12
|
+
* embed Noto Sans CJK subsets (~300-500KB per script), which is not
|
|
13
|
+
* included here to keep the composition payload reasonable.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync } from 'fs';
|
|
16
|
+
import { join } from 'path';
|
|
17
|
+
const ASSETS_DIR = join(import.meta.dirname, 'assets');
|
|
18
|
+
let cachedStyle = null;
|
|
19
|
+
/**
|
|
20
|
+
* Returns a <style> block with @font-face declarations for Inter.
|
|
21
|
+
* Embed this in <head> to load Inter without any network requests.
|
|
22
|
+
* Font data is read from disk on first call and cached thereafter.
|
|
23
|
+
*/
|
|
24
|
+
export function getInterFontStyle() {
|
|
25
|
+
if (cachedStyle) {
|
|
26
|
+
return cachedStyle;
|
|
27
|
+
}
|
|
28
|
+
const latinB64 = readFileSync(join(ASSETS_DIR, 'inter-latin.woff2')).toString('base64');
|
|
29
|
+
const latinExtB64 = readFileSync(join(ASSETS_DIR, 'inter-latin-ext.woff2')).toString('base64');
|
|
30
|
+
cachedStyle = `<style>
|
|
31
|
+
@font-face {
|
|
32
|
+
font-family: 'Inter';
|
|
33
|
+
font-style: normal;
|
|
34
|
+
font-weight: 100 900;
|
|
35
|
+
font-display: swap;
|
|
36
|
+
src: url(data:font/woff2;base64,${latinB64}) format('woff2');
|
|
37
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
38
|
+
}
|
|
39
|
+
@font-face {
|
|
40
|
+
font-family: 'Inter';
|
|
41
|
+
font-style: normal;
|
|
42
|
+
font-weight: 100 900;
|
|
43
|
+
font-display: swap;
|
|
44
|
+
src: url(data:font/woff2;base64,${latinExtB64}) format('woff2');
|
|
45
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
46
|
+
}
|
|
47
|
+
</style>`;
|
|
48
|
+
return cachedStyle;
|
|
49
|
+
}
|
|
@@ -11,6 +11,8 @@ import { tmpdir } from 'os';
|
|
|
11
11
|
import { join } from 'path';
|
|
12
12
|
import { generateDeviceFrameHtml, } from '../../services/video/device-frames.js';
|
|
13
13
|
import { logError, logInfo, logWarning } from '../../utils/logger.js';
|
|
14
|
+
import { ensurePlaywright } from '../../utils/validation.js';
|
|
15
|
+
import { getInterFontStyle } from './inter-font.js';
|
|
14
16
|
import { STORE_SCREENSHOT_SIZES } from './prompts.js';
|
|
15
17
|
/**
|
|
16
18
|
* Compose a single store screenshot for a specific device type
|
|
@@ -60,14 +62,18 @@ async function loadChromium() {
|
|
|
60
62
|
return null;
|
|
61
63
|
}
|
|
62
64
|
}
|
|
65
|
+
async function isPlaywrightAvailable() {
|
|
66
|
+
return (await loadChromium()) !== null;
|
|
67
|
+
}
|
|
63
68
|
/**
|
|
64
69
|
* Map AI device_frame string to our DeviceFrameType
|
|
65
70
|
*/
|
|
66
71
|
function mapDeviceFrame(frame) {
|
|
67
72
|
switch (frame.toLowerCase()) {
|
|
68
73
|
case 'iphone':
|
|
69
|
-
case 'ipad':
|
|
70
74
|
return 'iphone';
|
|
75
|
+
case 'ipad':
|
|
76
|
+
return 'ipad';
|
|
71
77
|
case 'macbook':
|
|
72
78
|
return 'macbook';
|
|
73
79
|
case 'browser':
|
|
@@ -76,17 +82,119 @@ function mapDeviceFrame(frame) {
|
|
|
76
82
|
return 'iphone';
|
|
77
83
|
}
|
|
78
84
|
}
|
|
85
|
+
/** Common named CSS colors likely in AI output. */
|
|
86
|
+
const NAMED_COLORS = {
|
|
87
|
+
white: [255, 255, 255],
|
|
88
|
+
black: [0, 0, 0],
|
|
89
|
+
red: [255, 0, 0],
|
|
90
|
+
green: [0, 128, 0],
|
|
91
|
+
blue: [0, 0, 255],
|
|
92
|
+
yellow: [255, 255, 0],
|
|
93
|
+
cyan: [0, 255, 255],
|
|
94
|
+
magenta: [255, 0, 255],
|
|
95
|
+
gray: [128, 128, 128],
|
|
96
|
+
grey: [128, 128, 128],
|
|
97
|
+
orange: [255, 165, 0],
|
|
98
|
+
purple: [128, 0, 128],
|
|
99
|
+
pink: [255, 192, 203],
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Parse a CSS color string to [R, G, B] (0-255 each).
|
|
103
|
+
* Supports hex (#rgb, #rrggbb, #rrggbbaa), rgb(), rgba(), and named colors.
|
|
104
|
+
*/
|
|
105
|
+
function parseColor(color) {
|
|
106
|
+
const s = color.trim().toLowerCase();
|
|
107
|
+
if (NAMED_COLORS[s]) {
|
|
108
|
+
return NAMED_COLORS[s];
|
|
109
|
+
}
|
|
110
|
+
const rgbMatch = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/.exec(s);
|
|
111
|
+
if (rgbMatch) {
|
|
112
|
+
return [parseInt(rgbMatch[1]), parseInt(rgbMatch[2]), parseInt(rgbMatch[3])];
|
|
113
|
+
}
|
|
114
|
+
const hexMatch = /^#?([0-9a-f]{3,8})$/i.exec(s);
|
|
115
|
+
if (!hexMatch) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const h = hexMatch[1];
|
|
119
|
+
if (h.length === 3) {
|
|
120
|
+
return [
|
|
121
|
+
parseInt(h[0] + h[0], 16),
|
|
122
|
+
parseInt(h[1] + h[1], 16),
|
|
123
|
+
parseInt(h[2] + h[2], 16),
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
if (h.length >= 6) {
|
|
127
|
+
return [
|
|
128
|
+
parseInt(h.slice(0, 2), 16),
|
|
129
|
+
parseInt(h.slice(2, 4), 16),
|
|
130
|
+
parseInt(h.slice(4, 6), 16),
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Relative luminance (0-1) per WCAG 2.x sRGB formula.
|
|
137
|
+
* Returns 1.0 for unparseable input ā safe default that produces dark overlay.
|
|
138
|
+
*/
|
|
139
|
+
function colorLuminance(color) {
|
|
140
|
+
const rgb = parseColor(color);
|
|
141
|
+
if (!rgb) {
|
|
142
|
+
return 1.0;
|
|
143
|
+
}
|
|
144
|
+
const toLinear = (c) => {
|
|
145
|
+
const s = c / 255;
|
|
146
|
+
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
147
|
+
};
|
|
148
|
+
return (0.2126 * toLinear(rgb[0]) +
|
|
149
|
+
0.7152 * toLinear(rgb[1]) +
|
|
150
|
+
0.0722 * toLinear(rgb[2]));
|
|
151
|
+
}
|
|
152
|
+
/** Hermite interpolation for smooth 0ā1 transition. */
|
|
153
|
+
function smoothstep(edge0, edge1, x) {
|
|
154
|
+
const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0)));
|
|
155
|
+
return t * t * (3 - 2 * t);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Returns overlay/shadow rgba() values that contrast with the given text color.
|
|
159
|
+
* Uses smooth interpolation ā strong at extremes, reduced for mid-range text
|
|
160
|
+
* to avoid hurting contrast in ambiguous cases.
|
|
161
|
+
*/
|
|
162
|
+
function contrastStyles(textColor) {
|
|
163
|
+
const lum = colorLuminance(textColor);
|
|
164
|
+
// Smooth 0ā1: 0 = dark text, 1 = light text
|
|
165
|
+
const lightness = smoothstep(0.15, 0.65, lum);
|
|
166
|
+
// Overlay channel: black (0) for light text, white (255) for dark text
|
|
167
|
+
const ch = Math.round(255 * (1 - lightness));
|
|
168
|
+
// Certainty: 1 at extremes, 0 at the ambiguous middle
|
|
169
|
+
const certainty = Math.abs(lightness - 0.5) * 2;
|
|
170
|
+
// Base alphas ā blend between dark-overlay and light-overlay presets
|
|
171
|
+
const baseOverlay = 0.25 * lightness + 0.2 * (1 - lightness);
|
|
172
|
+
const baseShadow = 0.35 * lightness + 0.4 * (1 - lightness);
|
|
173
|
+
const baseShadowLight = 0.25 * lightness + 0.3 * (1 - lightness);
|
|
174
|
+
// Scale by certainty ā 50% minimum even at peak ambiguity
|
|
175
|
+
const scale = 0.5 + 0.5 * certainty;
|
|
176
|
+
const fmt = (alpha) => (alpha * scale).toFixed(2);
|
|
177
|
+
return {
|
|
178
|
+
overlayColor: `rgba(${ch},${ch},${ch},${fmt(baseOverlay)})`,
|
|
179
|
+
shadowColor: `rgba(${ch},${ch},${ch},${fmt(baseShadow)})`,
|
|
180
|
+
shadowColorLight: `rgba(${ch},${ch},${ch},${fmt(baseShadowLight)})`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
79
183
|
/**
|
|
80
184
|
* Create composition HTML: gradient background + text overlay + device-framed screenshot
|
|
81
185
|
*/
|
|
82
186
|
function createCompositionHtml(framedScreenshotDataUrl, spec, targetWidth, targetHeight) {
|
|
83
187
|
const isPortrait = targetHeight > targetWidth;
|
|
84
188
|
const textColor = spec.text_color || '#ffffff';
|
|
189
|
+
const fontStyle = getInterFontStyle();
|
|
190
|
+
const fontFamily = `'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', sans-serif`;
|
|
191
|
+
const { overlayColor, shadowColor, shadowColorLight } = contrastStyles(textColor);
|
|
85
192
|
if (isPortrait) {
|
|
86
193
|
// Portrait: text at top, device below
|
|
87
194
|
return `<!DOCTYPE html>
|
|
88
195
|
<html>
|
|
89
196
|
<head>
|
|
197
|
+
${fontStyle}
|
|
90
198
|
<style>
|
|
91
199
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
92
200
|
body {
|
|
@@ -97,35 +205,60 @@ function createCompositionHtml(framedScreenshotDataUrl, spec, targetWidth, targe
|
|
|
97
205
|
flex-direction: column;
|
|
98
206
|
align-items: center;
|
|
99
207
|
justify-content: flex-start;
|
|
100
|
-
font-family:
|
|
208
|
+
font-family: ${fontFamily};
|
|
101
209
|
overflow: hidden;
|
|
102
210
|
}
|
|
103
211
|
.text-area {
|
|
104
|
-
padding: ${Math.round(targetHeight * 0.
|
|
212
|
+
padding: ${Math.round(targetHeight * 0.05)}px ${Math.round(targetWidth * 0.08)}px;
|
|
105
213
|
text-align: center;
|
|
106
214
|
flex-shrink: 0;
|
|
215
|
+
max-height: 28%;
|
|
216
|
+
position: relative;
|
|
217
|
+
}
|
|
218
|
+
.text-area::after {
|
|
219
|
+
content: '';
|
|
220
|
+
position: absolute;
|
|
221
|
+
inset: 0;
|
|
222
|
+
background: linear-gradient(to bottom, ${overlayColor} 0%, transparent 100%);
|
|
223
|
+
border-radius: inherit;
|
|
224
|
+
pointer-events: none;
|
|
107
225
|
}
|
|
108
226
|
.headline {
|
|
109
|
-
font-size: ${Math.round(targetWidth * 0.
|
|
227
|
+
font-size: ${Math.round(targetWidth * 0.1)}px;
|
|
110
228
|
font-weight: 800;
|
|
111
229
|
color: ${textColor};
|
|
112
230
|
line-height: 1.15;
|
|
113
231
|
letter-spacing: -0.02em;
|
|
114
232
|
margin-bottom: ${Math.round(targetHeight * 0.01)}px;
|
|
233
|
+
text-shadow: 0 2px 12px ${shadowColor};
|
|
234
|
+
position: relative;
|
|
235
|
+
z-index: 1;
|
|
236
|
+
display: -webkit-box;
|
|
237
|
+
-webkit-line-clamp: 3;
|
|
238
|
+
-webkit-box-orient: vertical;
|
|
239
|
+
overflow: hidden;
|
|
115
240
|
}
|
|
116
241
|
.subheadline {
|
|
117
|
-
font-size: ${Math.round(targetWidth * 0.
|
|
242
|
+
font-size: ${Math.round(targetWidth * 0.05)}px;
|
|
118
243
|
font-weight: 400;
|
|
119
244
|
color: ${textColor};
|
|
120
245
|
opacity: 0.85;
|
|
121
246
|
line-height: 1.3;
|
|
247
|
+
text-shadow: 0 1px 8px ${shadowColorLight};
|
|
248
|
+
position: relative;
|
|
249
|
+
z-index: 1;
|
|
250
|
+
display: -webkit-box;
|
|
251
|
+
-webkit-line-clamp: 2;
|
|
252
|
+
-webkit-box-orient: vertical;
|
|
253
|
+
overflow: hidden;
|
|
122
254
|
}
|
|
123
255
|
.device-area {
|
|
124
256
|
flex: 1;
|
|
125
257
|
display: flex;
|
|
126
|
-
align-items:
|
|
258
|
+
align-items: center;
|
|
127
259
|
justify-content: center;
|
|
128
260
|
overflow: hidden;
|
|
261
|
+
min-height: 0;
|
|
129
262
|
padding: 0 ${Math.round(targetWidth * 0.02)}px;
|
|
130
263
|
}
|
|
131
264
|
.device-area img {
|
|
@@ -148,6 +281,7 @@ function createCompositionHtml(framedScreenshotDataUrl, spec, targetWidth, targe
|
|
|
148
281
|
return `<!DOCTYPE html>
|
|
149
282
|
<html>
|
|
150
283
|
<head>
|
|
284
|
+
${fontStyle}
|
|
151
285
|
<style>
|
|
152
286
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
153
287
|
body {
|
|
@@ -156,31 +290,60 @@ function createCompositionHtml(framedScreenshotDataUrl, spec, targetWidth, targe
|
|
|
156
290
|
background: ${spec.background_gradient};
|
|
157
291
|
display: flex;
|
|
158
292
|
align-items: center;
|
|
159
|
-
font-family:
|
|
293
|
+
font-family: ${fontFamily};
|
|
160
294
|
overflow: hidden;
|
|
161
295
|
}
|
|
162
296
|
.text-area {
|
|
163
|
-
|
|
297
|
+
flex: 0 1 auto;
|
|
298
|
+
min-width: 28%;
|
|
299
|
+
max-width: 44%;
|
|
164
300
|
padding: 0 ${Math.round(targetWidth * 0.04)}px;
|
|
301
|
+
display: flex;
|
|
302
|
+
flex-direction: column;
|
|
303
|
+
justify-content: center;
|
|
165
304
|
text-align: left;
|
|
305
|
+
position: relative;
|
|
306
|
+
}
|
|
307
|
+
.text-area::after {
|
|
308
|
+
content: '';
|
|
309
|
+
position: absolute;
|
|
310
|
+
inset: 0;
|
|
311
|
+
background: linear-gradient(to right, ${overlayColor} 0%, transparent 100%);
|
|
312
|
+
border-radius: inherit;
|
|
313
|
+
pointer-events: none;
|
|
166
314
|
}
|
|
167
315
|
.headline {
|
|
168
|
-
font-size: ${Math.round(targetWidth * 0.
|
|
316
|
+
font-size: ${Math.round(targetWidth * 0.055)}px;
|
|
169
317
|
font-weight: 800;
|
|
170
318
|
color: ${textColor};
|
|
171
319
|
line-height: 1.15;
|
|
172
320
|
letter-spacing: -0.02em;
|
|
173
321
|
margin-bottom: ${Math.round(targetHeight * 0.02)}px;
|
|
322
|
+
text-shadow: 0 2px 12px ${shadowColor};
|
|
323
|
+
position: relative;
|
|
324
|
+
z-index: 1;
|
|
325
|
+
display: -webkit-box;
|
|
326
|
+
-webkit-line-clamp: 3;
|
|
327
|
+
-webkit-box-orient: vertical;
|
|
328
|
+
overflow: hidden;
|
|
174
329
|
}
|
|
175
330
|
.subheadline {
|
|
176
|
-
font-size: ${Math.round(targetWidth * 0.
|
|
331
|
+
font-size: ${Math.round(targetWidth * 0.028)}px;
|
|
177
332
|
font-weight: 400;
|
|
178
333
|
color: ${textColor};
|
|
179
334
|
opacity: 0.85;
|
|
180
335
|
line-height: 1.4;
|
|
336
|
+
text-shadow: 0 1px 8px ${shadowColorLight};
|
|
337
|
+
position: relative;
|
|
338
|
+
z-index: 1;
|
|
339
|
+
display: -webkit-box;
|
|
340
|
+
-webkit-line-clamp: 2;
|
|
341
|
+
-webkit-box-orient: vertical;
|
|
342
|
+
overflow: hidden;
|
|
181
343
|
}
|
|
182
344
|
.device-area {
|
|
183
|
-
|
|
345
|
+
flex: 1 1 0;
|
|
346
|
+
min-width: 0;
|
|
184
347
|
height: 100%;
|
|
185
348
|
display: flex;
|
|
186
349
|
align-items: center;
|
|
@@ -222,10 +385,8 @@ function escapeHtml(str) {
|
|
|
222
385
|
* 3. Compose framed PNG + text overlay + gradient background at exact store dimensions ā final PNG
|
|
223
386
|
*/
|
|
224
387
|
export async function generateStoreScreenshots(specs, options) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
throw new Error('Playwright is not installed. Install it with: npm install playwright && npx playwright install chromium');
|
|
228
|
-
}
|
|
388
|
+
await ensurePlaywright(isPlaywrightAvailable);
|
|
389
|
+
const chromium = (await loadChromium());
|
|
229
390
|
const { productId, targetStore, locale: _locale, verbose } = options;
|
|
230
391
|
const outputDir = join(tmpdir(), 'edsger-app-store-assets', productId);
|
|
231
392
|
await mkdir(outputDir, { recursive: true });
|
|
@@ -242,10 +403,13 @@ export async function generateStoreScreenshots(specs, options) {
|
|
|
242
403
|
logInfo(`Generating screenshot ${specIdx + 1}/${specs.length}: "${spec.headline}"`);
|
|
243
404
|
// Step 1: Render the AI HTML template at a base viewport to get raw app mock
|
|
244
405
|
const deviceFrame = mapDeviceFrame(spec.device_frame);
|
|
245
|
-
const isPhone = deviceFrame === 'iphone'
|
|
406
|
+
const isPhone = deviceFrame === 'iphone';
|
|
407
|
+
const isTablet = deviceFrame === 'ipad';
|
|
246
408
|
const baseViewport = isPhone
|
|
247
409
|
? { width: 390, height: 844 }
|
|
248
|
-
:
|
|
410
|
+
: isTablet
|
|
411
|
+
? { width: 1024, height: 1366 }
|
|
412
|
+
: { width: 1280, height: 720 };
|
|
249
413
|
let rawScreenshotBuffer;
|
|
250
414
|
try {
|
|
251
415
|
const rawContext = await browser.newContext({
|
|
@@ -271,8 +435,8 @@ export async function generateStoreScreenshots(specs, options) {
|
|
|
271
435
|
// Use tight canvas dimensions so the device fills most of the frame image.
|
|
272
436
|
// This prevents excess padding that would make the device appear small in composition.
|
|
273
437
|
const rawDataUrl = `data:image/png;base64,${rawScreenshotBuffer.toString('base64')}`;
|
|
274
|
-
const frameWidth = deviceFrame === 'iphone' ? 500 : 1600;
|
|
275
|
-
const frameHeight = deviceFrame === 'iphone' ? 1050 : 1000;
|
|
438
|
+
const frameWidth = deviceFrame === 'iphone' ? 500 : deviceFrame === 'ipad' ? 900 : 1600;
|
|
439
|
+
const frameHeight = deviceFrame === 'iphone' ? 1050 : deviceFrame === 'ipad' ? 1200 : 1000;
|
|
276
440
|
const frameHtml = generateDeviceFrameHtml(rawDataUrl, deviceFrame, {
|
|
277
441
|
background: 'transparent',
|
|
278
442
|
shadow: true,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { EdsgerConfig } from '../../types/index.js';
|
|
2
|
+
import { ChecklistPhaseContext } from '../../services/checklist.js';
|
|
3
|
+
import { ChecklistVerificationResult } from '../code-implementation-verification/agent.js';
|
|
4
|
+
import { CodeImplementationResult } from './index.js';
|
|
5
|
+
export interface VerificationCycleResult {
|
|
6
|
+
passed: boolean;
|
|
7
|
+
verificationResult: ChecklistVerificationResult | null;
|
|
8
|
+
nextPrompt: string | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Perform a complete verification cycle:
|
|
12
|
+
* 1. Verify code implementation against checklist
|
|
13
|
+
* 2. Log results to audit logs
|
|
14
|
+
* 3. Create improvement prompt if verification failed
|
|
15
|
+
*/
|
|
16
|
+
export declare function performVerificationCycle(branchName: string, baseBranch: string, checklistContext: ChecklistPhaseContext | null, featureId: string, featureName: string, featureDescription: string | undefined, config: EdsgerConfig, currentIteration: number, maxIterations: number, verbose?: boolean): Promise<VerificationCycleResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Build successful implementation result
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildImplementationResult(featureId: string, branchName: string, implementationSummary: string, filesModified: string[], commitHash: string, iterations: number, checklistResults?: any[], checklistItemResults?: any[]): CodeImplementationResult;
|
|
21
|
+
/**
|
|
22
|
+
* Build verification failure result
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildVerificationFailureResult(featureId: string, branchName: string, implementationSummary: string, filesModified: string[], commitHash: string, verificationResult: ChecklistVerificationResult, iterations: number): CodeImplementationResult;
|
|
25
|
+
/**
|
|
26
|
+
* Build error result for no implementation
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildNoResultsError(featureId: string, branchName: string): CodeImplementationResult;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { logInfo, logError } from '../../utils/logger.js';
|
|
2
|
+
import { logFeatureVerificationEvent } from '../../services/audit-logs.js';
|
|
3
|
+
import { formatChecklistsForContext, processChecklistItemResultsFromResponse, } from '../../services/checklist.js';
|
|
4
|
+
import { verifyCodeImplementationCompliance, } from '../code-implementation-verification/agent.js';
|
|
5
|
+
import { createCodeImplementationImprovementPrompt } from './prompts.js';
|
|
6
|
+
/**
|
|
7
|
+
* Perform a complete verification cycle:
|
|
8
|
+
* 1. Verify code implementation against checklist
|
|
9
|
+
* 2. Log results to audit logs
|
|
10
|
+
* 3. Create improvement prompt if verification failed
|
|
11
|
+
*/
|
|
12
|
+
export async function performVerificationCycle(branchName, baseBranch, checklistContext, featureId, featureName, featureDescription, config, currentIteration, maxIterations, verbose) {
|
|
13
|
+
// If no checklist, verification passes automatically
|
|
14
|
+
if (!checklistContext || checklistContext.checklists.length === 0) {
|
|
15
|
+
if (verbose) {
|
|
16
|
+
logInfo('ā¹ļø No checklists to verify - skipping verification');
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
passed: true,
|
|
20
|
+
verificationResult: null,
|
|
21
|
+
nextPrompt: null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (verbose) {
|
|
25
|
+
logInfo(`\nš Verifying code implementation (iteration ${currentIteration}/${maxIterations})...`);
|
|
26
|
+
}
|
|
27
|
+
// Format checklist context for verification
|
|
28
|
+
const checklistInfo = formatChecklistsForContext(checklistContext);
|
|
29
|
+
// Perform verification
|
|
30
|
+
const verificationResult = await verifyCodeImplementationCompliance({
|
|
31
|
+
featureId,
|
|
32
|
+
branchName,
|
|
33
|
+
baseBranch,
|
|
34
|
+
featureName,
|
|
35
|
+
featureDescription,
|
|
36
|
+
checklistContext: checklistInfo,
|
|
37
|
+
verbose,
|
|
38
|
+
}, config);
|
|
39
|
+
// Determine if verification passed
|
|
40
|
+
const verificationPassed = verificationResult.rejected_count === 0;
|
|
41
|
+
// Log verification result
|
|
42
|
+
if (verificationPassed) {
|
|
43
|
+
if (verbose) {
|
|
44
|
+
logInfo(`ā
Code verification passed: ${verificationResult.confirmed_count} confirmed, ${verificationResult.uncertain_count} uncertain`);
|
|
45
|
+
}
|
|
46
|
+
await logFeatureVerificationEvent({
|
|
47
|
+
featureId,
|
|
48
|
+
phase: 'code_implementation',
|
|
49
|
+
iteration: currentIteration,
|
|
50
|
+
result: 'success',
|
|
51
|
+
verificationData: {
|
|
52
|
+
confirmed_count: verificationResult.confirmed_count,
|
|
53
|
+
rejected_count: verificationResult.rejected_count,
|
|
54
|
+
uncertain_count: verificationResult.uncertain_count,
|
|
55
|
+
summary: verificationResult.summary,
|
|
56
|
+
},
|
|
57
|
+
}, verbose);
|
|
58
|
+
// Create checklist results based on verification result
|
|
59
|
+
const checklistItemResults = verificationResult.item_verifications.map((item) => ({
|
|
60
|
+
checklist_item_id: item.checklist_item_id,
|
|
61
|
+
is_passed: item.is_satisfied && item.verification_status === 'confirmed',
|
|
62
|
+
value: item.verification_status,
|
|
63
|
+
notes: item.verification_reason || undefined,
|
|
64
|
+
}));
|
|
65
|
+
await processChecklistItemResultsFromResponse({ featureId }, 'code_implementation', checklistItemResults, verbose);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
logError(`ā Iteration ${currentIteration}: Code verification FAILED - ${verificationResult.rejected_count} items rejected, ${verificationResult.uncertain_count} uncertain`);
|
|
69
|
+
await logFeatureVerificationEvent({
|
|
70
|
+
featureId,
|
|
71
|
+
phase: 'code_implementation',
|
|
72
|
+
iteration: currentIteration,
|
|
73
|
+
result: 'error',
|
|
74
|
+
verificationData: {
|
|
75
|
+
confirmed_count: verificationResult.confirmed_count,
|
|
76
|
+
rejected_count: verificationResult.rejected_count,
|
|
77
|
+
uncertain_count: verificationResult.uncertain_count,
|
|
78
|
+
rejected_items: verificationResult.item_verifications
|
|
79
|
+
.filter((item) => item.verification_status === 'rejected')
|
|
80
|
+
.map((item) => ({
|
|
81
|
+
checklist_item_id: item.checklist_item_id,
|
|
82
|
+
reason: item.verification_reason || 'No reason provided',
|
|
83
|
+
concerns: item.concerns || [],
|
|
84
|
+
improvement_suggestions: item.improvement_suggestions || [],
|
|
85
|
+
})),
|
|
86
|
+
uncertain_items: verificationResult.item_verifications
|
|
87
|
+
.filter((item) => item.verification_status === 'uncertain')
|
|
88
|
+
.map((item) => ({
|
|
89
|
+
checklist_item_id: item.checklist_item_id,
|
|
90
|
+
reason: item.verification_reason || 'No reason provided',
|
|
91
|
+
concerns: item.concerns || [],
|
|
92
|
+
improvement_suggestions: item.improvement_suggestions || [],
|
|
93
|
+
})),
|
|
94
|
+
summary: verificationResult.summary,
|
|
95
|
+
overall_suggestions: verificationResult.overall_suggestions || [],
|
|
96
|
+
},
|
|
97
|
+
}, verbose);
|
|
98
|
+
}
|
|
99
|
+
// If verification failed and we have iterations left, create improvement prompt
|
|
100
|
+
let nextPrompt = null;
|
|
101
|
+
if (!verificationPassed && currentIteration < maxIterations) {
|
|
102
|
+
nextPrompt = createCodeImplementationImprovementPrompt(verificationResult, branchName, baseBranch);
|
|
103
|
+
if (verbose) {
|
|
104
|
+
logInfo(`\nš Created improvement prompt for next iteration (${maxIterations - currentIteration} attempts remaining)`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (!verificationPassed) {
|
|
108
|
+
// Max iterations reached - create checklist results based on final verification
|
|
109
|
+
if (verbose) {
|
|
110
|
+
logInfo(`\nš Creating checklist results for final iteration (max iterations reached)`);
|
|
111
|
+
}
|
|
112
|
+
// Create checklist results for all items (both passed and failed)
|
|
113
|
+
const checklistItemResults = verificationResult.item_verifications.map((item) => ({
|
|
114
|
+
checklist_item_id: item.checklist_item_id,
|
|
115
|
+
is_passed: item.is_satisfied && item.verification_status === 'confirmed',
|
|
116
|
+
value: item.verification_status,
|
|
117
|
+
notes: item.verification_reason || undefined,
|
|
118
|
+
}));
|
|
119
|
+
await processChecklistItemResultsFromResponse({ featureId }, 'code_implementation', checklistItemResults, verbose);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
passed: verificationPassed,
|
|
123
|
+
verificationResult,
|
|
124
|
+
nextPrompt,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Build successful implementation result
|
|
129
|
+
*/
|
|
130
|
+
export function buildImplementationResult(featureId, branchName, implementationSummary, filesModified, commitHash, iterations, checklistResults, checklistItemResults) {
|
|
131
|
+
return {
|
|
132
|
+
featureId,
|
|
133
|
+
branchName,
|
|
134
|
+
implementationSummary,
|
|
135
|
+
status: 'success',
|
|
136
|
+
message: 'Code implementation completed successfully',
|
|
137
|
+
filesModified,
|
|
138
|
+
commitHash,
|
|
139
|
+
data: {
|
|
140
|
+
checklist_results: checklistResults,
|
|
141
|
+
checklist_item_results: checklistItemResults,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Build verification failure result
|
|
147
|
+
*/
|
|
148
|
+
export function buildVerificationFailureResult(featureId, branchName, implementationSummary, filesModified, commitHash, verificationResult, iterations) {
|
|
149
|
+
return {
|
|
150
|
+
featureId,
|
|
151
|
+
branchName,
|
|
152
|
+
implementationSummary,
|
|
153
|
+
status: 'error',
|
|
154
|
+
message: `Code implementation verification failed after ${iterations} iterations. Code committed for manual review.`,
|
|
155
|
+
filesModified,
|
|
156
|
+
commitHash,
|
|
157
|
+
data: {
|
|
158
|
+
verification_failed: true,
|
|
159
|
+
verification_summary: verificationResult.summary,
|
|
160
|
+
rejected_count: verificationResult.rejected_count,
|
|
161
|
+
uncertain_count: verificationResult.uncertain_count,
|
|
162
|
+
improvement_suggestions: verificationResult.overall_suggestions || [],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Build error result for no implementation
|
|
168
|
+
*/
|
|
169
|
+
export function buildNoResultsError(featureId, branchName) {
|
|
170
|
+
return {
|
|
171
|
+
featureId,
|
|
172
|
+
branchName,
|
|
173
|
+
implementationSummary: null,
|
|
174
|
+
status: 'error',
|
|
175
|
+
message: 'Code implementation failed - no results produced',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { EdsgerConfig } from '../../types/index.js';
|
|
2
|
+
import { ChecklistPhaseContext } from '../../services/checklist.js';
|
|
3
|
+
export interface CodeImplementationOptions {
|
|
4
|
+
featureId: string;
|
|
5
|
+
verbose?: boolean;
|
|
6
|
+
baseBranch?: string;
|
|
7
|
+
maxVerificationIterations?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface CodeImplementationResult {
|
|
10
|
+
featureId: string;
|
|
11
|
+
branchName: string;
|
|
12
|
+
implementationSummary: string | null;
|
|
13
|
+
status: 'success' | 'error';
|
|
14
|
+
message: string;
|
|
15
|
+
filesModified?: string[];
|
|
16
|
+
commitHash?: string;
|
|
17
|
+
data?: {
|
|
18
|
+
checklist_results?: Array<{
|
|
19
|
+
checklist_id: string;
|
|
20
|
+
satisfied: boolean;
|
|
21
|
+
notes?: string;
|
|
22
|
+
}>;
|
|
23
|
+
checklist_item_results?: Array<{
|
|
24
|
+
checklist_item_id: string;
|
|
25
|
+
is_passed: boolean;
|
|
26
|
+
value?: any;
|
|
27
|
+
notes?: string;
|
|
28
|
+
}>;
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export declare const implementFeatureCode: (options: CodeImplementationOptions, config: EdsgerConfig, checklistContext?: ChecklistPhaseContext | null) => Promise<CodeImplementationResult>;
|