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.
- package/.claude/settings.local.json +23 -3
- package/.env.local +12 -0
- package/dist/api/release-test-cases.d.ts +7 -0
- package/dist/api/release-test-cases.js +21 -0
- package/dist/api/releases.d.ts +41 -0
- package/dist/api/releases.js +31 -0
- package/dist/api/web-deploy.d.ts +8 -1
- package/dist/api/web-deploy.js +2 -1
- package/dist/commands/release-sync/index.d.ts +5 -0
- package/dist/commands/release-sync/index.js +38 -0
- package/dist/commands/smoke-test/index.d.ts +5 -0
- package/dist/commands/smoke-test/index.js +40 -0
- package/dist/commands/workflow/phase-orchestrator.js +3 -1
- package/dist/index.js +40 -0
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +1 -0
- package/dist/phases/app-store-generation/index.js +3 -1
- package/dist/phases/app-store-generation/screenshot-composer.js +34 -10
- package/dist/phases/branch-planning/index.js +3 -1
- package/dist/phases/bug-fixing/analyzer.js +3 -1
- package/dist/phases/code-implementation/index.js +3 -1
- package/dist/phases/code-refine/index.js +3 -1
- package/dist/phases/code-review/__tests__/diff-utils.test.js +11 -11
- package/dist/phases/code-review/index.js +3 -1
- package/dist/phases/code-testing/analyzer.js +3 -1
- package/dist/phases/feature-analysis/index.js +3 -1
- package/dist/phases/functional-testing/analyzer.js +3 -1
- package/dist/phases/growth-analysis/index.js +3 -1
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +12 -12
- package/dist/phases/intelligence-analysis/agent.js +2 -0
- package/dist/phases/intelligence-analysis/index.js +1 -0
- package/dist/phases/intelligence-analysis/prompts.js +11 -1
- package/dist/phases/output-contracts.js +1 -0
- package/dist/phases/pr-execution/__tests__/file-assigner.test.js +22 -13
- package/dist/phases/pr-execution/context.js +4 -2
- package/dist/phases/pr-execution/file-assigner.js +1 -0
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +11 -11
- package/dist/phases/pr-resolve/__tests__/prompts.test.js +12 -12
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +6 -6
- package/dist/phases/pr-resolve/__tests__/types.test.js +11 -11
- package/dist/phases/pr-resolve/__tests__/workspace.test.js +13 -13
- package/dist/phases/pr-resolve/checklist-learner.js +34 -9
- package/dist/phases/pr-resolve/index.js +29 -13
- package/dist/phases/pr-resolve/prompts.js +2 -1
- package/dist/phases/pr-resolve/workspace.d.ts +12 -2
- package/dist/phases/pr-resolve/workspace.js +6 -4
- package/dist/phases/pr-review/__tests__/prompts.test.js +9 -9
- package/dist/phases/pr-review/__tests__/review-comments.test.js +6 -6
- package/dist/phases/pr-review/index.js +1 -0
- package/dist/phases/pr-shared/__tests__/agent-utils.test.js +17 -17
- package/dist/phases/pr-shared/__tests__/context.test.js +12 -12
- package/dist/phases/pr-splitting/import-dep-validator.js +14 -6
- package/dist/phases/pr-splitting/index.js +3 -1
- package/dist/phases/release-sync/__tests__/github.test.d.ts +9 -0
- package/dist/phases/release-sync/__tests__/github.test.js +123 -0
- package/dist/phases/release-sync/__tests__/snapshot.test.d.ts +8 -0
- package/dist/phases/release-sync/__tests__/snapshot.test.js +93 -0
- package/dist/phases/release-sync/github.d.ts +54 -0
- package/dist/phases/release-sync/github.js +101 -0
- package/dist/phases/release-sync/index.d.ts +24 -0
- package/dist/phases/release-sync/index.js +147 -0
- package/dist/phases/release-sync/snapshot.d.ts +27 -0
- package/dist/phases/release-sync/snapshot.js +159 -0
- package/dist/phases/smoke-test/__tests__/agent.test.d.ts +4 -0
- package/dist/phases/smoke-test/__tests__/agent.test.js +85 -0
- package/dist/phases/smoke-test/agent.d.ts +12 -0
- package/dist/phases/smoke-test/agent.js +94 -0
- package/dist/phases/smoke-test/index.d.ts +22 -0
- package/dist/phases/smoke-test/index.js +233 -0
- package/dist/phases/smoke-test/prompts.d.ts +15 -0
- package/dist/phases/smoke-test/prompts.js +35 -0
- package/dist/phases/technical-design/index.js +3 -1
- package/dist/phases/test-cases-analysis/index.js +3 -1
- package/dist/phases/user-stories-analysis/index.js +3 -1
- package/dist/services/phase-hooks/__tests__/hook-executor.test.js +7 -4
- package/dist/services/phase-hooks/__tests__/hook-runner.test.js +22 -21
- package/dist/services/phase-hooks/hook-executor.js +1 -0
- package/dist/services/phase-hooks/plugin-loader.js +3 -0
- package/dist/services/video/screenshot-generator.js +8 -2
- package/dist/skills/phase/smoke-test/SKILL.md +80 -0
- package/dist/utils/json-extract.d.ts +6 -0
- package/dist/utils/json-extract.js +44 -0
- package/dist/workspace/__tests__/workspace-manager.test.d.ts +7 -0
- package/dist/workspace/__tests__/workspace-manager.test.js +52 -0
- package/dist/workspace/workspace-manager.d.ts +31 -0
- package/dist/workspace/workspace-manager.js +96 -10
- package/package.json +1 -1
- 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
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"
|
|
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,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
|
+
}
|
package/dist/api/web-deploy.d.ts
CHANGED
|
@@ -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(
|
|
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[]>;
|
package/dist/api/web-deploy.js
CHANGED
|
@@ -93,7 +93,8 @@ export async function listVercelProjects(configId, verbose) {
|
|
|
93
93
|
return [];
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
export async function createVercelProject(
|
|
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,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,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
|
|
@@ -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 =
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
439
|
-
|
|
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}`);
|