edsger 0.42.1 → 0.44.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 (98) hide show
  1. package/.claude/settings.local.json +23 -3
  2. package/.env.local +12 -0
  3. package/dist/api/release-test-cases.d.ts +7 -0
  4. package/dist/api/release-test-cases.js +21 -0
  5. package/dist/api/releases.d.ts +41 -0
  6. package/dist/api/releases.js +31 -0
  7. package/dist/api/web-deploy.d.ts +8 -1
  8. package/dist/api/web-deploy.js +2 -1
  9. package/dist/commands/release-sync/index.d.ts +5 -0
  10. package/dist/commands/release-sync/index.js +38 -0
  11. package/dist/commands/smoke-test/index.d.ts +5 -0
  12. package/dist/commands/smoke-test/index.js +40 -0
  13. package/dist/commands/workflow/phase-orchestrator.js +3 -1
  14. package/dist/index.js +40 -0
  15. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +1 -0
  16. package/dist/phases/app-store-generation/index.js +3 -1
  17. package/dist/phases/app-store-generation/screenshot-composer.js +34 -10
  18. package/dist/phases/branch-planning/index.js +3 -1
  19. package/dist/phases/bug-fixing/analyzer.js +3 -1
  20. package/dist/phases/code-implementation/index.js +3 -1
  21. package/dist/phases/code-refine/index.js +3 -1
  22. package/dist/phases/code-review/__tests__/diff-utils.test.js +11 -11
  23. package/dist/phases/code-review/index.js +3 -1
  24. package/dist/phases/code-testing/analyzer.js +3 -1
  25. package/dist/phases/feature-analysis/index.js +3 -1
  26. package/dist/phases/functional-testing/analyzer.js +3 -1
  27. package/dist/phases/growth-analysis/index.js +3 -1
  28. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +12 -12
  29. package/dist/phases/intelligence-analysis/agent.js +2 -0
  30. package/dist/phases/intelligence-analysis/index.js +1 -0
  31. package/dist/phases/intelligence-analysis/prompts.js +11 -1
  32. package/dist/phases/output-contracts.js +1 -0
  33. package/dist/phases/pr-execution/__tests__/file-assigner.test.js +22 -13
  34. package/dist/phases/pr-execution/context.js +4 -2
  35. package/dist/phases/pr-execution/file-assigner.js +1 -0
  36. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +11 -11
  37. package/dist/phases/pr-resolve/__tests__/prompts.test.js +12 -12
  38. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +6 -6
  39. package/dist/phases/pr-resolve/__tests__/types.test.js +11 -11
  40. package/dist/phases/pr-resolve/__tests__/workspace.test.js +13 -13
  41. package/dist/phases/pr-resolve/checklist-learner.js +34 -9
  42. package/dist/phases/pr-resolve/index.js +29 -13
  43. package/dist/phases/pr-resolve/prompts.js +2 -1
  44. package/dist/phases/pr-resolve/workspace.d.ts +12 -2
  45. package/dist/phases/pr-resolve/workspace.js +6 -4
  46. package/dist/phases/pr-review/__tests__/prompts.test.js +9 -9
  47. package/dist/phases/pr-review/__tests__/review-comments.test.js +6 -6
  48. package/dist/phases/pr-review/index.js +1 -0
  49. package/dist/phases/pr-shared/__tests__/agent-utils.test.js +17 -17
  50. package/dist/phases/pr-shared/__tests__/context.test.js +12 -12
  51. package/dist/phases/pr-splitting/import-dep-validator.js +14 -6
  52. package/dist/phases/pr-splitting/index.js +3 -1
  53. package/dist/phases/release-sync/__tests__/github.test.d.ts +9 -0
  54. package/dist/phases/release-sync/__tests__/github.test.js +123 -0
  55. package/dist/phases/release-sync/__tests__/snapshot.test.d.ts +8 -0
  56. package/dist/phases/release-sync/__tests__/snapshot.test.js +93 -0
  57. package/dist/phases/release-sync/github.d.ts +54 -0
  58. package/dist/phases/release-sync/github.js +101 -0
  59. package/dist/phases/release-sync/index.d.ts +24 -0
  60. package/dist/phases/release-sync/index.js +147 -0
  61. package/dist/phases/release-sync/snapshot.d.ts +27 -0
  62. package/dist/phases/release-sync/snapshot.js +159 -0
  63. package/dist/phases/smoke-test/__tests__/agent.test.d.ts +4 -0
  64. package/dist/phases/smoke-test/__tests__/agent.test.js +85 -0
  65. package/dist/phases/smoke-test/agent.d.ts +12 -0
  66. package/dist/phases/smoke-test/agent.js +94 -0
  67. package/dist/phases/smoke-test/index.d.ts +22 -0
  68. package/dist/phases/smoke-test/index.js +233 -0
  69. package/dist/phases/smoke-test/prompts.d.ts +15 -0
  70. package/dist/phases/smoke-test/prompts.js +35 -0
  71. package/dist/phases/technical-design/index.js +3 -1
  72. package/dist/phases/test-cases-analysis/index.js +3 -1
  73. package/dist/phases/user-stories-analysis/index.js +3 -1
  74. package/dist/services/phase-hooks/__tests__/hook-executor.test.js +7 -4
  75. package/dist/services/phase-hooks/__tests__/hook-runner.test.js +22 -21
  76. package/dist/services/phase-hooks/hook-executor.js +1 -0
  77. package/dist/services/phase-hooks/plugin-loader.js +3 -0
  78. package/dist/services/video/screenshot-generator.js +8 -2
  79. package/dist/skills/phase/smoke-test/SKILL.md +80 -0
  80. package/dist/utils/json-extract.d.ts +6 -0
  81. package/dist/utils/json-extract.js +44 -0
  82. package/dist/workspace/__tests__/workspace-manager.test.d.ts +7 -0
  83. package/dist/workspace/__tests__/workspace-manager.test.js +52 -0
  84. package/dist/workspace/workspace-manager.d.ts +31 -0
  85. package/dist/workspace/workspace-manager.js +96 -10
  86. package/package.json +1 -1
  87. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
  88. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
  89. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
  90. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
  91. package/dist/services/lifecycle-agent/index.d.ts +0 -24
  92. package/dist/services/lifecycle-agent/index.js +0 -25
  93. package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
  94. package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
  95. package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
  96. package/dist/services/lifecycle-agent/transition-rules.js +0 -184
  97. package/dist/services/lifecycle-agent/types.d.ts +0 -190
  98. package/dist/services/lifecycle-agent/types.js +0 -12
@@ -1,8 +1,28 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(npx tsc:*)",
5
- "Bash(npm run:*)"
6
- ]
4
+ "Read(//Users/steven/development/edsger/**)",
5
+ "Bash(npm run build)",
6
+ "Bash(node:*)",
7
+ "Bash(git add:*)",
8
+ "Bash(git commit:*)",
9
+ "Bash(ls:*)",
10
+ "Bash(cat:*)",
11
+ "Bash(npm run typecheck:*)",
12
+ "Bash(git diff:*)",
13
+ "WebSearch",
14
+ "WebFetch(domain:supabase.com)",
15
+ "Bash(npm install:*)",
16
+ "Bash(grep:*)",
17
+ "Bash(npx supabase gen types typescript --help:*)",
18
+ "Bash(git -C /Users/steven/development/edsger status)",
19
+ "Bash(git -C /Users/steven/development/edsger diff)",
20
+ "Bash(git -C /Users/steven/development/edsger log --oneline -5)",
21
+ "Bash(git -C /Users/steven/development/edsger add supabase/migrations/20251231000000_drop_unused_views.sql)",
22
+ "Bash(git -C /Users/steven/development/edsger commit -m \"$\\(cat <<''EOF''\nchore: drop unused database views\n\nRemove test_report_summary and user_stories_with_context views that are defined but never used in the application.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
23
+ "Bash(git -C /Users/steven/development/edsger commit -m \"$\\(cat <<''EOF''\nchore: drop unused database views\n\nRemove test_report_summary and user_stories_with_context views\nthat are defined but never used in the application.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
24
+ ],
25
+ "deny": [],
26
+ "ask": []
7
27
  }
8
28
  }
package/.env.local ADDED
@@ -0,0 +1,12 @@
1
+ OPENAI_API_KEY=
2
+ TRIGGER_SECRET_KEY=
3
+ RECALL_AI_API_KEY=
4
+ RECALL_WEBHOOK_SECRET=
5
+ NEXT_PUBLIC_APP_URL=
6
+ RESEND_API_KEY=
7
+ EMAIL_FROM=
8
+ GITHUB_APP_ID=
9
+ GITHUB_APP_PRIVATE_KEY=
10
+ GITHUB_APP_SLUG=
11
+ DEEPGRAM_API_KEY=
12
+ DEEPGRAM_PROJECT_ID=
@@ -0,0 +1,7 @@
1
+ export interface ReleaseTestCaseInput {
2
+ name: string;
3
+ description: string;
4
+ is_critical?: boolean;
5
+ }
6
+ export declare function clearReleaseTestCases(releaseId: string, verbose?: boolean): Promise<void>;
7
+ export declare function createReleaseTestCases(releaseId: string, cases: ReleaseTestCaseInput[], verbose?: boolean): Promise<number>;
@@ -0,0 +1,21 @@
1
+ import { logDebug, logInfo } from '../utils/logger.js';
2
+ import { callMcpEndpoint } from './mcp-client.js';
3
+ export async function clearReleaseTestCases(releaseId, verbose) {
4
+ logDebug(`Clearing draft cases for release ${releaseId}`, verbose);
5
+ await callMcpEndpoint('releases/test_cases/clear', {
6
+ release_id: releaseId,
7
+ });
8
+ }
9
+ export async function createReleaseTestCases(releaseId, cases, verbose) {
10
+ if (cases.length === 0) {
11
+ return 0;
12
+ }
13
+ if (verbose) {
14
+ logInfo(`Inserting ${cases.length} release test cases`);
15
+ }
16
+ const result = (await callMcpEndpoint('releases/test_cases/create', {
17
+ release_id: releaseId,
18
+ test_cases: cases,
19
+ }));
20
+ return result.count;
21
+ }
@@ -0,0 +1,41 @@
1
+ export interface Release {
2
+ id: string;
3
+ product_id: string;
4
+ tag: string;
5
+ name: string | null;
6
+ body: string | null;
7
+ url: string | null;
8
+ published_at: string | null;
9
+ previous_tag: string | null;
10
+ previous_published_at: string | null;
11
+ status: 'pending' | 'generating' | 'ready' | 'failed';
12
+ generation_error: string | null;
13
+ diff_summary: string | null;
14
+ diff_stats: Record<string, unknown>;
15
+ created_at: string;
16
+ updated_at: string;
17
+ }
18
+ export interface UpsertReleaseParams {
19
+ product_id: string;
20
+ tag: string;
21
+ name?: string | null;
22
+ body?: string | null;
23
+ url?: string | null;
24
+ published_at?: string | null;
25
+ previous_tag?: string | null;
26
+ previous_published_at?: string | null;
27
+ status?: 'pending' | 'generating' | 'ready' | 'failed';
28
+ diff_stats?: Record<string, unknown>;
29
+ }
30
+ export interface UpdateReleaseParams {
31
+ release_id: string;
32
+ status?: 'pending' | 'generating' | 'ready' | 'failed';
33
+ generation_error?: string | null;
34
+ diff_summary?: string | null;
35
+ diff_stats?: Record<string, unknown>;
36
+ }
37
+ export declare function upsertRelease(params: UpsertReleaseParams, verbose?: boolean): Promise<Release>;
38
+ export declare function updateRelease(params: UpdateReleaseParams, verbose?: boolean): Promise<Release>;
39
+ export declare function getRelease(releaseId: string, verbose?: boolean): Promise<Release>;
40
+ export declare function getLatestReleaseForProduct(productId: string, verbose?: boolean): Promise<Release | null>;
41
+ export declare function getReleaseByTag(productId: string, tag: string, verbose?: boolean): Promise<Release | null>;
@@ -0,0 +1,31 @@
1
+ import { logDebug } from '../utils/logger.js';
2
+ import { callMcpEndpoint } from './mcp-client.js';
3
+ export async function upsertRelease(params, verbose) {
4
+ logDebug(`Upserting release ${params.product_id}@${params.tag}`, verbose);
5
+ return (await callMcpEndpoint('releases/upsert', params));
6
+ }
7
+ export async function updateRelease(params, verbose) {
8
+ logDebug(`Updating release ${params.release_id}`, verbose);
9
+ return (await callMcpEndpoint('releases/update', params));
10
+ }
11
+ export async function getRelease(releaseId, verbose) {
12
+ logDebug(`Fetching release ${releaseId}`, verbose);
13
+ return (await callMcpEndpoint('releases/get', {
14
+ release_id: releaseId,
15
+ }));
16
+ }
17
+ export async function getLatestReleaseForProduct(productId, verbose) {
18
+ logDebug(`Fetching latest release for product ${productId}`, verbose);
19
+ const result = (await callMcpEndpoint('releases/latest_for_product', {
20
+ product_id: productId,
21
+ }));
22
+ return result.release;
23
+ }
24
+ export async function getReleaseByTag(productId, tag, verbose) {
25
+ logDebug(`Looking up release ${productId}@${tag}`, verbose);
26
+ const result = (await callMcpEndpoint('releases/get_by_tag', {
27
+ product_id: productId,
28
+ tag,
29
+ }));
30
+ return result.release;
31
+ }
@@ -52,7 +52,14 @@ export declare function connectWebDeploy(configId: string, verbose?: boolean): P
52
52
  connected: boolean;
53
53
  } | null>;
54
54
  export declare function listVercelProjects(configId: string, verbose?: boolean): Promise<VercelProject[]>;
55
- export declare function createVercelProject(configId: string, name: string, repo: string, framework?: string, rootDirectory?: string, verbose?: boolean): Promise<VercelProject | null>;
55
+ export declare function createVercelProject(options: {
56
+ configId: string;
57
+ name: string;
58
+ repo: string;
59
+ framework?: string;
60
+ rootDirectory?: string;
61
+ verbose?: boolean;
62
+ }): Promise<VercelProject | null>;
56
63
  export declare function linkVercelProject(configId: string, project: VercelProject, verbose?: boolean): Promise<WebDeployConfig | null>;
57
64
  export declare function listDeployments(configId: string, limit?: number, verbose?: boolean): Promise<VercelDeployment[]>;
58
65
  export declare function listEnvVars(configId: string, verbose?: boolean): Promise<VercelEnvVar[]>;
@@ -93,7 +93,8 @@ export async function listVercelProjects(configId, verbose) {
93
93
  return [];
94
94
  }
95
95
  }
96
- export async function createVercelProject(configId, name, repo, framework, rootDirectory, verbose) {
96
+ export async function createVercelProject(options) {
97
+ const { configId, name, repo, framework, rootDirectory, verbose } = options;
97
98
  if (verbose) {
98
99
  logInfo(`Creating Vercel project: ${name} → ${repo}`);
99
100
  }
@@ -0,0 +1,5 @@
1
+ export interface ReleaseSyncCliOptions {
2
+ productId: string;
3
+ verbose?: boolean;
4
+ }
5
+ export declare const runReleaseSyncCommand: (options: ReleaseSyncCliOptions) => Promise<void>;
@@ -0,0 +1,38 @@
1
+ import { runReleaseSync, } from '../../phases/release-sync/index.js';
2
+ import { deregisterSession, registerSession, } from '../../system/session-manager.js';
3
+ import { logError, logInfo } from '../../utils/logger.js';
4
+ import { validateConfiguration } from '../../utils/validation.js';
5
+ export const runReleaseSyncCommand = async (options) => {
6
+ const { productId } = options;
7
+ if (!productId) {
8
+ throw new Error('Product ID is required for release-sync');
9
+ }
10
+ const config = validateConfiguration({
11
+ verbose: options.verbose,
12
+ });
13
+ await registerSession({ command: 'release-sync', productId });
14
+ logInfo(`Starting release sync for product: ${productId}`);
15
+ try {
16
+ const result = await runReleaseSync({
17
+ productId,
18
+ verbose: options.verbose,
19
+ }, config);
20
+ if (result.status === 'success') {
21
+ logInfo(result.summary);
22
+ for (const r of result.syncedReleases ?? []) {
23
+ logInfo(` - ${r.tag}${r.isSnapshot ? ' (snapshot)' : ''}`);
24
+ }
25
+ }
26
+ else {
27
+ logError(`Release sync failed: ${result.summary}`);
28
+ process.exitCode = 1;
29
+ }
30
+ }
31
+ catch (error) {
32
+ logError(`Release sync failed: ${error instanceof Error ? error.message : String(error)}`);
33
+ process.exitCode = 1;
34
+ }
35
+ finally {
36
+ await deregisterSession();
37
+ }
38
+ };
@@ -0,0 +1,5 @@
1
+ export interface SmokeTestCliOptions {
2
+ releaseId: string;
3
+ verbose?: boolean;
4
+ }
5
+ export declare const runSmokeTestCommand: (options: SmokeTestCliOptions) => Promise<void>;
@@ -0,0 +1,40 @@
1
+ import { runSmokeTest, } from '../../phases/smoke-test/index.js';
2
+ import { deregisterSession, registerSession, } from '../../system/session-manager.js';
3
+ import { logError, logInfo } from '../../utils/logger.js';
4
+ import { validateConfiguration } from '../../utils/validation.js';
5
+ export const runSmokeTestCommand = async (options) => {
6
+ const { releaseId } = options;
7
+ if (!releaseId) {
8
+ throw new Error('Release ID is required for smoke-test');
9
+ }
10
+ const config = validateConfiguration({
11
+ verbose: options.verbose,
12
+ });
13
+ await registerSession({ command: 'smoke-test' });
14
+ logInfo(`Starting smoke-test generation for release: ${releaseId}`);
15
+ try {
16
+ const result = await runSmokeTest({
17
+ releaseId,
18
+ verbose: options.verbose,
19
+ }, config);
20
+ if (result.status === 'success') {
21
+ logInfo(result.isSnapshot
22
+ ? `Release: ${result.releaseTag} (snapshot — not yet published on GitHub)`
23
+ : `Release: ${result.releaseTag}`);
24
+ logInfo(`Previous: ${result.previousReleaseTag ?? '(none)'}`);
25
+ logInfo(`Cases generated: ${result.casesCount}`);
26
+ logInfo('View in the Release detail page of your product dashboard.');
27
+ }
28
+ else {
29
+ logError(`Smoke-test generation failed: ${result.summary}`);
30
+ process.exitCode = 1;
31
+ }
32
+ }
33
+ catch (error) {
34
+ logError(`Smoke-test generation failed: ${error instanceof Error ? error.message : String(error)}`);
35
+ process.exitCode = 1;
36
+ }
37
+ finally {
38
+ await deregisterSession();
39
+ }
40
+ };
@@ -36,7 +36,9 @@ const logAndMarkPhaseCompleted = async (result, verbose) => {
36
36
  * Orchestrate phase execution based on execution mode
37
37
  * Routes to appropriate phase sequence based on mode (only_*, from_*, full_pipeline)
38
38
  */
39
- export const runPipelineByMode = async (options, config, executionMode) => {
39
+ export const runPipelineByMode = async (options, config, executionMode
40
+ // eslint-disable-next-line complexity
41
+ ) => {
40
42
  const { featureId, verbose } = options;
41
43
  if (verbose) {
42
44
  logInfo(`🚀 Starting pipeline with mode: ${executionMode} for feature: ${featureId}`);
package/dist/index.js CHANGED
@@ -19,6 +19,8 @@ import { runIntelligence } from './commands/intelligence/index.js';
19
19
  import { runPRResolve } from './commands/pr-resolve/index.js';
20
20
  import { runPRReview } from './commands/pr-review/index.js';
21
21
  import { runRefactor } from './commands/refactor/refactor.js';
22
+ import { runReleaseSyncCommand } from './commands/release-sync/index.js';
23
+ import { runSmokeTestCommand } from './commands/smoke-test/index.js';
22
24
  import { runTaskWorker } from './commands/task-worker/index.js';
23
25
  import { runWorkflow } from './commands/workflow/index.js';
24
26
  import { logError, logInfo } from './utils/logger.js';
@@ -274,6 +276,44 @@ program
274
276
  }
275
277
  });
276
278
  // ============================================================
279
+ // Subcommand: edsger release-sync <productId>
280
+ // ============================================================
281
+ program
282
+ .command('release-sync <productId>')
283
+ .description('Sync GitHub releases (and any unreleased snapshot version) into the releases table for a product')
284
+ .option('-v, --verbose', 'Verbose output')
285
+ .action(async (productId, opts) => {
286
+ try {
287
+ await runReleaseSyncCommand({
288
+ productId,
289
+ verbose: opts.verbose,
290
+ });
291
+ }
292
+ catch (error) {
293
+ logError(error instanceof Error ? error.message : String(error));
294
+ process.exit(1);
295
+ }
296
+ });
297
+ // ============================================================
298
+ // Subcommand: edsger smoke-test <releaseId>
299
+ // ============================================================
300
+ program
301
+ .command('smoke-test <releaseId>')
302
+ .description('Generate smoke-test cases for a specific release (requires the release to be synced first — see `edsger release-sync`)')
303
+ .option('-v, --verbose', 'Verbose output')
304
+ .action(async (releaseId, opts) => {
305
+ try {
306
+ await runSmokeTestCommand({
307
+ releaseId,
308
+ verbose: opts.verbose,
309
+ });
310
+ }
311
+ catch (error) {
312
+ logError(error instanceof Error ? error.message : String(error));
313
+ process.exit(1);
314
+ }
315
+ });
316
+ // ============================================================
277
317
  // Subcommand: edsger pr-review <productId>
278
318
  // ============================================================
279
319
  program
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  /**
2
3
  * Unit tests for screenshot composer pure functions.
3
4
  * Non-exported functions are re-implemented inline for testing (project pattern).
@@ -13,7 +13,9 @@ function truncateKeywords(keywords) {
13
13
  }
14
14
  return keywords.slice(0, 100).replace(/,[^,]*$/, '');
15
15
  }
16
- export const generateAppStoreAssets = async (options, config) => {
16
+ export const generateAppStoreAssets = async (options, config
17
+ // eslint-disable-next-line complexity
18
+ ) => {
17
19
  const { productId, targetStore, screenshotsOnly, listingsOnly, verbose } = options;
18
20
  if (verbose) {
19
21
  logInfo(`Starting app store generation for product: ${productId}`);
@@ -386,7 +386,10 @@ function escapeHtml(str) {
386
386
  */
387
387
  export async function generateStoreScreenshots(specs, options) {
388
388
  await ensurePlaywright(isPlaywrightAvailable);
389
- const chromium = (await loadChromium());
389
+ const chromium = await loadChromium();
390
+ if (!chromium) {
391
+ throw new Error('Failed to load chromium');
392
+ }
390
393
  const { productId, targetStore, locale: _locale, verbose } = options;
391
394
  const outputDir = join(tmpdir(), 'edsger-app-store-assets', productId);
392
395
  await mkdir(outputDir, { recursive: true });
@@ -403,13 +406,16 @@ export async function generateStoreScreenshots(specs, options) {
403
406
  logInfo(`Generating screenshot ${specIdx + 1}/${specs.length}: "${spec.headline}"`);
404
407
  // Step 1: Render the AI HTML template at a base viewport to get raw app mock
405
408
  const deviceFrame = mapDeviceFrame(spec.device_frame);
406
- const isPhone = deviceFrame === 'iphone';
407
- const isTablet = deviceFrame === 'ipad';
408
- const baseViewport = isPhone
409
- ? { width: 390, height: 844 }
410
- : isTablet
411
- ? { width: 1024, height: 1366 }
412
- : { width: 1280, height: 720 };
409
+ let baseViewport;
410
+ if (deviceFrame === 'iphone') {
411
+ baseViewport = { width: 390, height: 844 };
412
+ }
413
+ else if (deviceFrame === 'ipad') {
414
+ baseViewport = { width: 1024, height: 1366 };
415
+ }
416
+ else {
417
+ baseViewport = { width: 1280, height: 720 };
418
+ }
413
419
  let rawScreenshotBuffer;
414
420
  try {
415
421
  const rawContext = await browser.newContext({
@@ -435,8 +441,26 @@ export async function generateStoreScreenshots(specs, options) {
435
441
  // Use tight canvas dimensions so the device fills most of the frame image.
436
442
  // This prevents excess padding that would make the device appear small in composition.
437
443
  const rawDataUrl = `data:image/png;base64,${rawScreenshotBuffer.toString('base64')}`;
438
- const frameWidth = deviceFrame === 'iphone' ? 500 : deviceFrame === 'ipad' ? 900 : 1600;
439
- const frameHeight = deviceFrame === 'iphone' ? 1050 : deviceFrame === 'ipad' ? 1200 : 1000;
444
+ let frameWidth;
445
+ if (deviceFrame === 'iphone') {
446
+ frameWidth = 500;
447
+ }
448
+ else if (deviceFrame === 'ipad') {
449
+ frameWidth = 900;
450
+ }
451
+ else {
452
+ frameWidth = 1600;
453
+ }
454
+ let frameHeight;
455
+ if (deviceFrame === 'iphone') {
456
+ frameHeight = 1050;
457
+ }
458
+ else if (deviceFrame === 'ipad') {
459
+ frameHeight = 1200;
460
+ }
461
+ else {
462
+ frameHeight = 1000;
463
+ }
440
464
  const frameHtml = generateDeviceFrameHtml(rawDataUrl, deviceFrame, {
441
465
  background: 'transparent',
442
466
  shadow: true,
@@ -116,7 +116,9 @@ async function persistBranches(sortedBranches, featureId, verbose) {
116
116
  }
117
117
  }
118
118
  }
119
- export const planFeatureBranches = async (options, config) => {
119
+ export const planFeatureBranches = async (options, config
120
+ // eslint-disable-next-line complexity
121
+ ) => {
120
122
  const { featureId, verbose, replaceExisting } = options;
121
123
  if (verbose) {
122
124
  logInfo(`Starting branch planning for feature ID: ${featureId}`);
@@ -32,7 +32,9 @@ async function* prompt(bugFixPrompt) {
32
32
  setTimeout(res, 10000);
33
33
  });
34
34
  }
35
- export const fixTestFailures = async (options, config) => {
35
+ export const fixTestFailures = async (options, config
36
+ // eslint-disable-next-line complexity
37
+ ) => {
36
38
  const { featureId, testErrors, attemptNumber = 1, verbose } = options;
37
39
  if (verbose) {
38
40
  logInfo(`Starting bug fixing for feature ID: ${featureId} (Attempt ${attemptNumber})`);
@@ -170,7 +170,9 @@ message, lastAssistantResponse, featureId, verbose
170
170
  }
171
171
  return null;
172
172
  }
173
- export const implementFeatureCode = async (options, config, checklistContext) => {
173
+ export const implementFeatureCode = async (options, config, checklistContext
174
+ // eslint-disable-next-line complexity
175
+ ) => {
174
176
  const { featureId, verbose, baseBranch = 'main' } = options;
175
177
  if (verbose) {
176
178
  logInfo(`Starting code implementation for feature ID: ${featureId}`);
@@ -41,7 +41,9 @@ export const MAX_REFINE_ITERATIONS = 10;
41
41
  * Similar to technical-design, this includes an iterative improvement cycle:
42
42
  * refine → verification → improve → re-refine (if needed)
43
43
  */
44
- export const refineCodeFromPRFeedback = async (options, config, checklistContext) => {
44
+ export const refineCodeFromPRFeedback = async (options, config, checklistContext
45
+ // eslint-disable-next-line complexity
46
+ ) => {
45
47
  const { featureId, githubToken, verbose } = options;
46
48
  if (verbose) {
47
49
  logInfo(`Starting code refine for feature ID: ${featureId}`);
@@ -1,8 +1,8 @@
1
1
  import assert from 'node:assert';
2
2
  import { describe, it } from 'node:test';
3
3
  import { buildLineToPositionMap, findClosestPosition } from '../diff-utils.js';
4
- describe('buildLineToPositionMap', () => {
5
- it('maps simple additions correctly', () => {
4
+ void describe('buildLineToPositionMap', () => {
5
+ void it('maps simple additions correctly', () => {
6
6
  const patch = `@@ -1,3 +1,4 @@
7
7
  line 1
8
8
  +new line
@@ -18,7 +18,7 @@ describe('buildLineToPositionMap', () => {
18
18
  // line 3 is at position 4 (new file line 4)
19
19
  assert.strictEqual(map.get(4), 4);
20
20
  });
21
- it('handles deletions - deleted lines have no new file line number', () => {
21
+ void it('handles deletions - deleted lines have no new file line number', () => {
22
22
  const patch = `@@ -1,3 +1,2 @@
23
23
  line 1
24
24
  -deleted line
@@ -29,7 +29,7 @@ describe('buildLineToPositionMap', () => {
29
29
  // line 3 in old file becomes line 2 in new file, position 3
30
30
  assert.strictEqual(map.get(2), 3);
31
31
  });
32
- it('handles multiple hunks', () => {
32
+ void it('handles multiple hunks', () => {
33
33
  const patch = `@@ -1,2 +1,2 @@
34
34
  line 1
35
35
  +added
@@ -44,13 +44,13 @@ describe('buildLineToPositionMap', () => {
44
44
  assert.strictEqual(map.get(10), 3);
45
45
  assert.strictEqual(map.get(11), 4);
46
46
  });
47
- it('returns empty map for patch with only hunk header', () => {
47
+ void it('returns empty map for patch with only hunk header', () => {
48
48
  const patch = '@@ -0,0 +1 @@';
49
49
  const map = buildLineToPositionMap(patch);
50
50
  // Hunk header only, no content lines
51
51
  assert.strictEqual(map.size, 0);
52
52
  });
53
- it('handles hunk starting at line other than 1', () => {
53
+ void it('handles hunk starting at line other than 1', () => {
54
54
  const patch = `@@ -50,3 +50,3 @@
55
55
  context
56
56
  -old
@@ -62,8 +62,8 @@ describe('buildLineToPositionMap', () => {
62
62
  assert.strictEqual(map.get(51), 3);
63
63
  });
64
64
  });
65
- describe('findClosestPosition', () => {
66
- it('returns exact match when available', () => {
65
+ void describe('findClosestPosition', () => {
66
+ void it('returns exact match when available', () => {
67
67
  const map = new Map([
68
68
  [10, 5],
69
69
  [11, 6],
@@ -72,7 +72,7 @@ describe('findClosestPosition', () => {
72
72
  const result = findClosestPosition(11, map);
73
73
  assert.deepStrictEqual(result, { position: 6, actualLine: 11 });
74
74
  });
75
- it('returns nearby line below first', () => {
75
+ void it('returns nearby line below first', () => {
76
76
  const map = new Map([
77
77
  [10, 5],
78
78
  [15, 8],
@@ -84,7 +84,7 @@ describe('findClosestPosition', () => {
84
84
  const result = findClosestPosition(12, map);
85
85
  assert.deepStrictEqual(result, { position: 5, actualLine: 10 });
86
86
  });
87
- it('returns null when no line within range', () => {
87
+ void it('returns null when no line within range', () => {
88
88
  const map = new Map([
89
89
  [1, 1],
90
90
  [100, 50],
@@ -93,7 +93,7 @@ describe('findClosestPosition', () => {
93
93
  const result = findClosestPosition(50, map);
94
94
  assert.strictEqual(result, null);
95
95
  });
96
- it('returns null for empty map', () => {
96
+ void it('returns null for empty map', () => {
97
97
  const map = new Map();
98
98
  const result = findClosestPosition(5, map);
99
99
  assert.strictEqual(result, null);
@@ -67,7 +67,9 @@ baseBranchInfo, baseBranchForRebase, originalBaseBranchForRebase, verbose) {
67
67
  /**
68
68
  * Main code review function
69
69
  */
70
- export const reviewPullRequest = async (options, config, checklistContext) => {
70
+ export const reviewPullRequest = async (options, config, checklistContext
71
+ // eslint-disable-next-line complexity
72
+ ) => {
71
73
  const { featureId, githubToken, verbose } = options;
72
74
  if (verbose) {
73
75
  logInfo(`Starting code review for feature ID: ${featureId}`);
@@ -31,7 +31,9 @@ async function* prompt(testingPrompt) {
31
31
  setTimeout(res, 10000);
32
32
  });
33
33
  }
34
- export const writeCodeTests = async (options, config) => {
34
+ export const writeCodeTests = async (options, config
35
+ // eslint-disable-next-line complexity
36
+ ) => {
35
37
  const { featureId, verbose } = options;
36
38
  if (verbose) {
37
39
  logInfo(`Starting code testing phase for feature ID: ${featureId}`);
@@ -6,7 +6,9 @@ import { executeAnalysisQuery, parseAnalysisResult } from './agent.js';
6
6
  import { prepareAnalysisContext } from './context.js';
7
7
  import { buildAnalysisResult, deleteArtifacts, deleteSpecificArtifacts, getAllDraftArtifactIds, resetReadyArtifactsToDraft, saveAnalysisArtifactsAsDraft, updateArtifactsToReady, } from './outcome.js';
8
8
  import { createFeatureAnalysisSystemPrompt } from './prompts.js';
9
- export const analyseFeature = async (options, config, checklistContext) => {
9
+ export const analyseFeature = async (options, config, checklistContext
10
+ // eslint-disable-next-line complexity
11
+ ) => {
10
12
  const { featureId, verbose } = options;
11
13
  if (verbose) {
12
14
  logInfo(`Starting feature analysis for feature ID: ${featureId}`);
@@ -326,7 +326,9 @@ async function saveTestResults(featureId, testStatus, structuredTestResult, last
326
326
  }
327
327
  return testReportResult;
328
328
  }
329
- export const runFunctionalTesting = async (options, config, checklistContext) => {
329
+ export const runFunctionalTesting = async (options, config, checklistContext
330
+ // eslint-disable-next-line complexity
331
+ ) => {
330
332
  const { featureId, verbose } = options;
331
333
  if (verbose) {
332
334
  logInfo(`Starting functional testing for feature ID: ${featureId}`);
@@ -49,7 +49,9 @@ contentSuggestions) {
49
49
  }
50
50
  return plans;
51
51
  }
52
- export const analyseGrowth = async (options, config) => {
52
+ export const analyseGrowth = async (options, config
53
+ // eslint-disable-next-line complexity
54
+ ) => {
53
55
  const { productId, verbose, guidance, analysisId } = options;
54
56
  if (verbose) {
55
57
  logInfo(`Starting growth analysis for product ID: ${productId}`);