edsger 0.42.0 → 0.43.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/dist/api/web-deploy.d.ts +8 -1
- package/dist/api/web-deploy.js +2 -1
- package/dist/commands/workflow/phase-orchestrator.js +3 -1
- 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 +45 -12
- package/dist/phases/pr-resolve/prompts.js +2 -1
- package/dist/phases/pr-resolve/workspace.d.ts +18 -2
- package/dist/phases/pr-resolve/workspace.js +43 -14
- 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/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/package.json +1 -1
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
|
}
|
|
@@ -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}`);
|
|
@@ -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}`);
|
|
@@ -110,26 +110,26 @@ function makeDeps(aiResult, contextOverride) {
|
|
|
110
110
|
saveSnapCalls,
|
|
111
111
|
saveRptCalls,
|
|
112
112
|
updateRptCalls,
|
|
113
|
-
prepareContext:
|
|
113
|
+
prepareContext: () => Promise.resolve({
|
|
114
114
|
context: contextOverride || makeContext(),
|
|
115
115
|
analysisPrompt: 'fake prompt',
|
|
116
116
|
}),
|
|
117
|
-
executeQuery:
|
|
118
|
-
batchCreate:
|
|
117
|
+
executeQuery: () => Promise.resolve(aiResult),
|
|
118
|
+
batchCreate: (_productId, competitors) => {
|
|
119
119
|
batchCreateCalls.push([_productId, competitors]);
|
|
120
|
-
return competitors.map((c) => makeCompetitor(c.name, `discovered-${batchCreateCounter++}`, 'suggested'));
|
|
120
|
+
return Promise.resolve(competitors.map((c) => makeCompetitor(c.name, `discovered-${batchCreateCounter++}`, 'suggested')));
|
|
121
121
|
},
|
|
122
|
-
saveSnap:
|
|
122
|
+
saveSnap: (snap) => {
|
|
123
123
|
saveSnapCalls.push([snap]);
|
|
124
|
-
return makeSnapshot(snap.competitor_id, `snap-${saveSnapCalls.length}`);
|
|
124
|
+
return Promise.resolve(makeSnapshot(snap.competitor_id, `snap-${saveSnapCalls.length}`));
|
|
125
125
|
},
|
|
126
|
-
saveRpt:
|
|
126
|
+
saveRpt: (report) => {
|
|
127
127
|
saveRptCalls.push([report]);
|
|
128
|
-
return makeReport(`rpt-${saveRptCalls.length}`);
|
|
128
|
+
return Promise.resolve(makeReport(`rpt-${saveRptCalls.length}`));
|
|
129
129
|
},
|
|
130
|
-
updateRpt:
|
|
130
|
+
updateRpt: (_id, updates) => {
|
|
131
131
|
updateRptCalls.push([_id, updates]);
|
|
132
|
-
return makeReport(_id);
|
|
132
|
+
return Promise.resolve(makeReport(_id));
|
|
133
133
|
},
|
|
134
134
|
};
|
|
135
135
|
}
|
|
@@ -368,8 +368,8 @@ void describe('analyseIntelligence — guidance', () => {
|
|
|
368
368
|
void describe('analyseIntelligence — exception in deps', () => {
|
|
369
369
|
void it('should return error when context fetch throws', async () => {
|
|
370
370
|
const deps = makeDeps(null);
|
|
371
|
-
deps.prepareContext =
|
|
372
|
-
|
|
371
|
+
deps.prepareContext = () => {
|
|
372
|
+
return Promise.reject(new Error('Network error'));
|
|
373
373
|
};
|
|
374
374
|
const result = await analyseIntelligence({ productId: 'prod-001' }, fakeConfig, deps);
|
|
375
375
|
assert.strictEqual(result.status, 'error');
|
|
@@ -261,12 +261,14 @@ function parseIntelligenceResult(responseText) {
|
|
|
261
261
|
error: 'JSON parsing failed: could not extract structured data from response',
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
264
265
|
async function* prompt(analysisPrompt) {
|
|
265
266
|
yield {
|
|
266
267
|
type: 'user',
|
|
267
268
|
message: { role: 'user', content: analysisPrompt },
|
|
268
269
|
};
|
|
269
270
|
}
|
|
271
|
+
// eslint-disable-next-line complexity
|
|
270
272
|
export async function executeIntelligenceQuery(currentPrompt, systemPrompt, _config, verbose, cwd) {
|
|
271
273
|
let lastAssistantResponse = '';
|
|
272
274
|
let structuredResult = null;
|
|
@@ -74,6 +74,7 @@ const defaultDeps = {
|
|
|
74
74
|
* 2. AI agent researches competitors and market via web search
|
|
75
75
|
* 3. Save discovered competitors, snapshots, and report
|
|
76
76
|
*/
|
|
77
|
+
// eslint-disable-next-line complexity
|
|
77
78
|
export async function analyseIntelligence(options, config, deps = defaultDeps) {
|
|
78
79
|
const { productId, reportType = 'competitive', verbose, guidance, reportId, } = options;
|
|
79
80
|
try {
|
|
@@ -29,9 +29,19 @@ export const createIntelligencePromptWithContext = (productId, contextInfo, repo
|
|
|
29
29
|
**Competitor Discovery**: While analyzing confirmed competitors, also look for new entrants or products that should be tracked. Include any new finds in the "discovered_competitors" array.`;
|
|
30
30
|
}
|
|
31
31
|
else {
|
|
32
|
+
let focusLabel;
|
|
33
|
+
if (reportType === 'research') {
|
|
34
|
+
focusLabel = 'papers and reports';
|
|
35
|
+
}
|
|
36
|
+
else if (reportType === 'market') {
|
|
37
|
+
focusLabel = 'market signals and trends';
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
focusLabel = 'product-market fit metrics';
|
|
41
|
+
}
|
|
32
42
|
discoveryInstructions = `
|
|
33
43
|
|
|
34
|
-
**Note**: This is a ${reportType} analysis — focus on ${
|
|
44
|
+
**Note**: This is a ${reportType} analysis — focus on ${focusLabel}. Do NOT discover new competitors. Set "discovered_competitors" to an empty array.`;
|
|
35
45
|
}
|
|
36
46
|
const researchInstructions = reportType === 'research'
|
|
37
47
|
? `
|
|
@@ -20,8 +20,8 @@ function makePR(overrides) {
|
|
|
20
20
|
...overrides,
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
function cf(path,
|
|
24
|
-
return { path, change_type };
|
|
23
|
+
function cf(path, changeType = 'modified') {
|
|
24
|
+
return { path, change_type: changeType };
|
|
25
25
|
}
|
|
26
26
|
// ============================================================
|
|
27
27
|
// findUnassignedFiles
|
|
@@ -133,8 +133,9 @@ void describe('applyAssignments', () => {
|
|
|
133
133
|
];
|
|
134
134
|
const unassigned = [cf('b.ts', 'added')];
|
|
135
135
|
const updates = [];
|
|
136
|
-
const mockUpdater =
|
|
136
|
+
const mockUpdater = (prId, u) => {
|
|
137
137
|
updates.push({ prId, files: u.files });
|
|
138
|
+
return Promise.resolve();
|
|
138
139
|
};
|
|
139
140
|
const count = await applyAssignments([{ file: 'b.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
|
|
140
141
|
assert.strictEqual(count, 1);
|
|
@@ -155,8 +156,9 @@ void describe('applyAssignments', () => {
|
|
|
155
156
|
];
|
|
156
157
|
const unassigned = [cf('a.ts')];
|
|
157
158
|
const updates = [];
|
|
158
|
-
const mockUpdater =
|
|
159
|
+
const mockUpdater = (_prId, u) => {
|
|
159
160
|
updates.push({ files: u.files });
|
|
161
|
+
return Promise.resolve();
|
|
160
162
|
};
|
|
161
163
|
await applyAssignments([{ file: 'a.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
|
|
162
164
|
// File list should not have duplicates
|
|
@@ -168,8 +170,9 @@ void describe('applyAssignments', () => {
|
|
|
168
170
|
const prs = [makePR({ id: 'pr-1', sequence: 1, files: [] })];
|
|
169
171
|
const unassigned = [cf('x.ts', 'added')];
|
|
170
172
|
let called = false;
|
|
171
|
-
const mockUpdater =
|
|
173
|
+
const mockUpdater = () => {
|
|
172
174
|
called = true;
|
|
175
|
+
return Promise.resolve();
|
|
173
176
|
};
|
|
174
177
|
const count = await applyAssignments([{ file: 'x.ts', pr_id: 'unknown-id' }], unassigned, prs, mockUpdater);
|
|
175
178
|
assert.strictEqual(count, 0);
|
|
@@ -179,8 +182,9 @@ void describe('applyAssignments', () => {
|
|
|
179
182
|
const prs = [makePR({ id: 'pr-1', sequence: 1, files: [] })];
|
|
180
183
|
const unassigned = [cf('new.ts', 'added')];
|
|
181
184
|
const updates = [];
|
|
182
|
-
const mockUpdater =
|
|
185
|
+
const mockUpdater = (_prId, u) => {
|
|
183
186
|
updates.push({ files: u.files });
|
|
187
|
+
return Promise.resolve();
|
|
184
188
|
};
|
|
185
189
|
await applyAssignments([{ file: 'new.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
|
|
186
190
|
assert.strictEqual(updates[0].files[0].change_type, 'added');
|
|
@@ -195,8 +199,9 @@ void describe('applyAssignments', () => {
|
|
|
195
199
|
cf('b.ts', 'modified'),
|
|
196
200
|
];
|
|
197
201
|
const updates = [];
|
|
198
|
-
const mockUpdater =
|
|
202
|
+
const mockUpdater = (prId) => {
|
|
199
203
|
updates.push({ prId });
|
|
204
|
+
return Promise.resolve();
|
|
200
205
|
};
|
|
201
206
|
const count = await applyAssignments([
|
|
202
207
|
{ file: 'a.ts', pr_id: 'pr-1' },
|
|
@@ -208,8 +213,8 @@ void describe('applyAssignments', () => {
|
|
|
208
213
|
void it('handles updater failure gracefully', async () => {
|
|
209
214
|
const prs = [makePR({ id: 'pr-1', sequence: 1, files: [] })];
|
|
210
215
|
const unassigned = [cf('a.ts')];
|
|
211
|
-
const mockUpdater =
|
|
212
|
-
|
|
216
|
+
const mockUpdater = () => {
|
|
217
|
+
return Promise.reject(new Error('DB error'));
|
|
213
218
|
};
|
|
214
219
|
const count = await applyAssignments([{ file: 'a.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
|
|
215
220
|
assert.strictEqual(count, 0);
|
|
@@ -235,8 +240,9 @@ void describe('removeDeletedFilesFromPRs', () => {
|
|
|
235
240
|
cf('gone.ts', 'deleted'),
|
|
236
241
|
];
|
|
237
242
|
const updates = [];
|
|
238
|
-
const mockUpdater =
|
|
243
|
+
const mockUpdater = (prId, u) => {
|
|
239
244
|
updates.push({ prId, files: u.files });
|
|
245
|
+
return Promise.resolve();
|
|
240
246
|
};
|
|
241
247
|
const count = await removeDeletedFilesFromPRs(changedFiles, prs, mockUpdater);
|
|
242
248
|
assert.strictEqual(count, 1);
|
|
@@ -253,8 +259,9 @@ void describe('removeDeletedFilesFromPRs', () => {
|
|
|
253
259
|
}),
|
|
254
260
|
];
|
|
255
261
|
let called = false;
|
|
256
|
-
const mockUpdater =
|
|
262
|
+
const mockUpdater = () => {
|
|
257
263
|
called = true;
|
|
264
|
+
return Promise.resolve();
|
|
258
265
|
};
|
|
259
266
|
const count = await removeDeletedFilesFromPRs([cf('a.ts')], prs, mockUpdater);
|
|
260
267
|
assert.strictEqual(count, 0);
|
|
@@ -274,8 +281,9 @@ void describe('removeDeletedFilesFromPRs', () => {
|
|
|
274
281
|
}),
|
|
275
282
|
];
|
|
276
283
|
const updates = [];
|
|
277
|
-
const mockUpdater =
|
|
284
|
+
const mockUpdater = (prId) => {
|
|
278
285
|
updates.push(prId);
|
|
286
|
+
return Promise.resolve();
|
|
279
287
|
};
|
|
280
288
|
await removeDeletedFilesFromPRs([cf('b.ts', 'deleted')], prs, mockUpdater);
|
|
281
289
|
// Only pr-2 should be updated
|
|
@@ -284,8 +292,9 @@ void describe('removeDeletedFilesFromPRs', () => {
|
|
|
284
292
|
void it('handles PRs with null files', async () => {
|
|
285
293
|
const prs = [makePR({ id: 'pr-1', sequence: 1, files: null })];
|
|
286
294
|
let called = false;
|
|
287
|
-
const mockUpdater =
|
|
295
|
+
const mockUpdater = () => {
|
|
288
296
|
called = true;
|
|
297
|
+
return Promise.resolve();
|
|
289
298
|
};
|
|
290
299
|
const count = await removeDeletedFilesFromPRs([cf('a.ts', 'deleted')], prs, mockUpdater);
|
|
291
300
|
assert.strictEqual(count, 0);
|
|
@@ -193,7 +193,9 @@ export async function fetchPRExecutionContext(featureId, verbose) {
|
|
|
193
193
|
// If the branch was rebased, lastSyncedCommit is no longer in the history.
|
|
194
194
|
// Treat this as a full re-execution so downstream logic uses the correct
|
|
195
195
|
// prompt and skips incremental file-assignment against an invalid base.
|
|
196
|
-
if (isIncrementalSync &&
|
|
196
|
+
if (isIncrementalSync &&
|
|
197
|
+
lastSyncedCommit &&
|
|
198
|
+
!isAncestor(lastSyncedCommit, devRef)) {
|
|
197
199
|
if (verbose) {
|
|
198
200
|
logInfo(`⚠️ Last synced commit ${lastSyncedCommit} is no longer in branch history (likely rebased). Treating as full re-execution.`);
|
|
199
201
|
}
|
|
@@ -201,7 +203,7 @@ export async function fetchPRExecutionContext(featureId, verbose) {
|
|
|
201
203
|
isIncrementalSync = false;
|
|
202
204
|
}
|
|
203
205
|
// Get diff info: for incremental, diff from last sync; for first run, diff from main
|
|
204
|
-
const diffBase = isIncrementalSync ? lastSyncedCommit : 'main';
|
|
206
|
+
const diffBase = isIncrementalSync && lastSyncedCommit ? lastSyncedCommit : 'main';
|
|
205
207
|
const diffStat = getDiffStat(diffBase, devRef);
|
|
206
208
|
const changedFiles = getChangedFiles(diffBase, devRef);
|
|
207
209
|
if (verbose) {
|
|
@@ -97,6 +97,7 @@ async function callLLMForAssignment(prompt, verbose) {
|
|
|
97
97
|
})) {
|
|
98
98
|
if (message.type === 'assistant' && message.message?.content) {
|
|
99
99
|
for (const item of message.message.content) {
|
|
100
|
+
// eslint-disable-next-line max-depth
|
|
100
101
|
if (item.type === 'text') {
|
|
101
102
|
responseText += `${item.text}\n`;
|
|
102
103
|
}
|