edsger 0.45.0 → 0.46.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/commands/workflow/executors/phase-executor.js +3 -1
- package/dist/commands/workflow/phase-orchestrator.js +1 -2
- package/dist/phases/app-store-generation/index.js +1 -2
- package/dist/phases/branch-planning/index.js +1 -2
- package/dist/phases/bug-fixing/analyzer.js +1 -2
- package/dist/phases/code-implementation/index.js +1 -2
- package/dist/phases/code-refine/index.js +1 -2
- package/dist/phases/code-review/index.js +1 -2
- package/dist/phases/code-testing/analyzer.js +1 -2
- package/dist/phases/feature-analysis/index.js +1 -2
- package/dist/phases/functional-testing/analyzer.js +1 -2
- package/dist/phases/growth-analysis/index.js +1 -2
- package/dist/phases/pr-execution/index.js +1 -0
- package/dist/phases/pr-splitting/index.js +1 -2
- package/dist/phases/run-sheet/index.js +7 -7
- package/dist/phases/run-sheet/render.js +3 -1
- package/dist/phases/smoke-test/agent.js +2 -4
- package/dist/phases/smoke-test/index.js +11 -6
- package/dist/phases/technical-design/index.js +1 -2
- package/dist/phases/test-cases-analysis/index.js +1 -2
- package/dist/phases/user-stories-analysis/index.js +1 -2
- package/package.json +3 -3
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +3 -9
- package/dist/api/__tests__/app-store.test.d.ts +0 -7
- package/dist/api/__tests__/app-store.test.js +0 -60
- package/dist/api/__tests__/intelligence.test.d.ts +0 -11
- package/dist/api/__tests__/intelligence.test.js +0 -315
- package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
- package/dist/api/features/__tests__/feature-utils.test.js +0 -370
- package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
- package/dist/api/features/__tests__/status-updater.test.js +0 -88
- package/dist/commands/build/__tests__/build.test.d.ts +0 -5
- package/dist/commands/build/__tests__/build.test.js +0 -206
- package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
- package/dist/commands/build/__tests__/detect-project.test.js +0 -160
- package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
- package/dist/commands/build/__tests__/run-build.test.js +0 -433
- package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
- package/dist/commands/intelligence/__tests__/command.test.js +0 -48
- package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
- package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
- package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
- package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
- package/dist/config/__tests__/config.test.d.ts +0 -4
- package/dist/config/__tests__/config.test.js +0 -286
- package/dist/config/__tests__/feature-status.test.d.ts +0 -4
- package/dist/config/__tests__/feature-status.test.js +0 -111
- package/dist/errors/__tests__/index.test.d.ts +0 -4
- package/dist/errors/__tests__/index.test.js +0 -349
- package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
- package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
- package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
- package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
- package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
- package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
- package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
- package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
- package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
- package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
- package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
- package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
- package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
- package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
- package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
- package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
- package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
- package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
- package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
- package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
- package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
- package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
- package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
- package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
- package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
- package/dist/phases/release-sync/__tests__/github.test.d.ts +0 -9
- package/dist/phases/release-sync/__tests__/github.test.js +0 -123
- package/dist/phases/release-sync/__tests__/snapshot.test.d.ts +0 -8
- package/dist/phases/release-sync/__tests__/snapshot.test.js +0 -93
- package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
- package/dist/phases/smoke-test/__tests__/agent.test.js +0 -85
- package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
- package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
- package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
- package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
- package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
- package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
- package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
- package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
- package/dist/workspace/__tests__/workspace-manager.test.js +0 -52
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for feature status configuration
|
|
3
|
-
*/
|
|
4
|
-
import assert from 'node:assert';
|
|
5
|
-
import { describe, it } from 'node:test';
|
|
6
|
-
import { PHASE_STATUS_MAP, STATUS_PROGRESSION_ORDER, } from '../feature-status.js';
|
|
7
|
-
void describe('Feature Status Configuration', () => {
|
|
8
|
-
void describe('STATUS_PROGRESSION_ORDER', () => {
|
|
9
|
-
void it('should start with backlog and end with archived', () => {
|
|
10
|
-
assert.strictEqual(STATUS_PROGRESSION_ORDER[0], 'backlog', 'First status should be backlog');
|
|
11
|
-
assert.strictEqual(STATUS_PROGRESSION_ORDER[STATUS_PROGRESSION_ORDER.length - 1], 'archived', 'Last status should be archived');
|
|
12
|
-
});
|
|
13
|
-
void it('should be readonly', () => {
|
|
14
|
-
// This test ensures the configuration is properly typed as readonly
|
|
15
|
-
// TypeScript will catch attempts to modify the array at compile time
|
|
16
|
-
assert.ok(Array.isArray(STATUS_PROGRESSION_ORDER));
|
|
17
|
-
assert.ok(STATUS_PROGRESSION_ORDER.length > 0);
|
|
18
|
-
});
|
|
19
|
-
void it('should maintain logical workflow order', () => {
|
|
20
|
-
const criticalOrder = [
|
|
21
|
-
'backlog',
|
|
22
|
-
'ready_for_ai',
|
|
23
|
-
'feature_analysis',
|
|
24
|
-
'technical_design',
|
|
25
|
-
'code_implementation',
|
|
26
|
-
'shipped',
|
|
27
|
-
];
|
|
28
|
-
let lastIndex = -1;
|
|
29
|
-
for (const status of criticalOrder) {
|
|
30
|
-
const currentIndex = STATUS_PROGRESSION_ORDER.indexOf(status);
|
|
31
|
-
assert.ok(currentIndex > lastIndex, `${status} should come after the previous critical status`);
|
|
32
|
-
lastIndex = currentIndex;
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
void describe('PHASE_STATUS_MAP', () => {
|
|
37
|
-
void it('should map all expected phases', () => {
|
|
38
|
-
const expectedPhases = [
|
|
39
|
-
'feature-analysis',
|
|
40
|
-
'feature-analysis-verification',
|
|
41
|
-
'technical-design',
|
|
42
|
-
'technical-design-verification',
|
|
43
|
-
'code-implementation',
|
|
44
|
-
'code-implementation-verification',
|
|
45
|
-
'code-refine',
|
|
46
|
-
'code-refine-verification',
|
|
47
|
-
'bug-fixing',
|
|
48
|
-
'code-review',
|
|
49
|
-
'pull-request',
|
|
50
|
-
'functional-testing',
|
|
51
|
-
'testing-in-progress',
|
|
52
|
-
'testing-passed',
|
|
53
|
-
'testing-failed',
|
|
54
|
-
];
|
|
55
|
-
for (const phase of expectedPhases) {
|
|
56
|
-
assert.ok(phase in PHASE_STATUS_MAP, `Should have mapping for phase: ${phase}`);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
void it('should map phases to valid statuses', () => {
|
|
60
|
-
for (const [phase, status] of Object.entries(PHASE_STATUS_MAP)) {
|
|
61
|
-
assert.ok(STATUS_PROGRESSION_ORDER.includes(status), `Phase ${phase} maps to valid status ${status}`);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
void it('should use kebab-case for multi-word phase names and snake_case for multi-word status names', () => {
|
|
65
|
-
for (const [phase, status] of Object.entries(PHASE_STATUS_MAP)) {
|
|
66
|
-
// Phase names should not contain underscores
|
|
67
|
-
assert.ok(!phase.includes('_'), `Phase name ${phase} should not contain underscores`);
|
|
68
|
-
// Status names should not contain hyphens
|
|
69
|
-
assert.ok(!status.includes('-'), `Status name ${status} should not contain hyphens`);
|
|
70
|
-
// Multi-word phase names should use hyphens
|
|
71
|
-
if (phase.split('-').length > 1) {
|
|
72
|
-
assert.ok(phase.includes('-'), `Multi-word phase name ${phase} should use kebab-case`);
|
|
73
|
-
}
|
|
74
|
-
// Multi-word status names should use underscores
|
|
75
|
-
if (status.split('_').length > 1) {
|
|
76
|
-
assert.ok(status.includes('_'), `Multi-word status name ${status} should use snake_case`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
void it('should maintain consistency with phase naming conventions', () => {
|
|
81
|
-
// Test some specific mappings to ensure they follow expected patterns
|
|
82
|
-
assert.strictEqual(PHASE_STATUS_MAP['feature-analysis'], 'feature_analysis', 'feature-analysis should map to feature_analysis');
|
|
83
|
-
assert.strictEqual(PHASE_STATUS_MAP['technical-design-verification'], 'technical_design_verification', 'technical-design-verification should map to technical_design_verification');
|
|
84
|
-
assert.strictEqual(PHASE_STATUS_MAP['code-implementation'], 'code_implementation', 'code-implementation should map to code_implementation');
|
|
85
|
-
});
|
|
86
|
-
void it('should be readonly', () => {
|
|
87
|
-
// Ensure the mapping is properly configured as readonly
|
|
88
|
-
assert.ok(typeof PHASE_STATUS_MAP === 'object');
|
|
89
|
-
assert.ok(Object.keys(PHASE_STATUS_MAP).length > 0);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
void describe('Integration Tests', () => {
|
|
93
|
-
void it('should have all mapped statuses in progression order', () => {
|
|
94
|
-
const mappedStatuses = Object.values(PHASE_STATUS_MAP);
|
|
95
|
-
for (const status of mappedStatuses) {
|
|
96
|
-
assert.ok(STATUS_PROGRESSION_ORDER.includes(status), `Mapped status ${status} should be in progression order`);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
void it('should not have gaps in critical workflow phases', () => {
|
|
100
|
-
const workflowPhases = [
|
|
101
|
-
'feature-analysis',
|
|
102
|
-
'technical-design',
|
|
103
|
-
'code-implementation',
|
|
104
|
-
'functional-testing',
|
|
105
|
-
];
|
|
106
|
-
for (const phase of workflowPhases) {
|
|
107
|
-
assert.ok(phase in PHASE_STATUS_MAP, `Critical workflow phase ${phase} should be mapped`);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
});
|
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for Edsger error classification system
|
|
3
|
-
*/
|
|
4
|
-
import assert from 'node:assert';
|
|
5
|
-
import { describe, it } from 'node:test';
|
|
6
|
-
import { ApiError, AuthError, ConfigError, EdsgerError, GitHubError, LLMParseError, PhaseError, ValidationError, WorkerCrashError, WorkerTimeoutError, } from '../index.js';
|
|
7
|
-
// ---- EdsgerError (base) ----
|
|
8
|
-
void describe('EdsgerError', () => {
|
|
9
|
-
void it('should set name to EdsgerError', () => {
|
|
10
|
-
const err = new EdsgerError('API_ERROR', 'test message');
|
|
11
|
-
assert.strictEqual(err.name, 'EdsgerError');
|
|
12
|
-
});
|
|
13
|
-
void it('should store the error code', () => {
|
|
14
|
-
const err = new EdsgerError('AUTH_ERROR', 'unauthorized');
|
|
15
|
-
assert.strictEqual(err.code, 'AUTH_ERROR');
|
|
16
|
-
});
|
|
17
|
-
void it('should store the message', () => {
|
|
18
|
-
const err = new EdsgerError('CONFIG_ERROR', 'missing field');
|
|
19
|
-
assert.strictEqual(err.message, 'missing field');
|
|
20
|
-
});
|
|
21
|
-
void it('should be an instance of Error', () => {
|
|
22
|
-
const err = new EdsgerError('API_ERROR', 'test');
|
|
23
|
-
assert.ok(err instanceof Error);
|
|
24
|
-
});
|
|
25
|
-
void it('should preserve cause when provided via options', () => {
|
|
26
|
-
const cause = new Error('root cause');
|
|
27
|
-
const err = new EdsgerError('API_ERROR', 'wrapped', { cause });
|
|
28
|
-
assert.strictEqual(err.cause, cause);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
// ---- EdsgerError.from ----
|
|
32
|
-
void describe('EdsgerError.from', () => {
|
|
33
|
-
void it('should wrap an Error, preserving its message and cause chain', () => {
|
|
34
|
-
const original = new Error('connection refused');
|
|
35
|
-
const wrapped = EdsgerError.from('API_ERROR', 'fetch failed', original);
|
|
36
|
-
assert.strictEqual(wrapped.code, 'API_ERROR');
|
|
37
|
-
assert.strictEqual(wrapped.message, 'fetch failed: connection refused');
|
|
38
|
-
assert.strictEqual(wrapped.cause, original);
|
|
39
|
-
});
|
|
40
|
-
void it('should wrap a string thrown value', () => {
|
|
41
|
-
const wrapped = EdsgerError.from('CONFIG_ERROR', 'init', 'bad config');
|
|
42
|
-
assert.strictEqual(wrapped.code, 'CONFIG_ERROR');
|
|
43
|
-
assert.strictEqual(wrapped.message, 'init: bad config');
|
|
44
|
-
assert.strictEqual(wrapped.cause, undefined);
|
|
45
|
-
});
|
|
46
|
-
void it('should wrap an unknown thrown value by stringifying it', () => {
|
|
47
|
-
const wrapped = EdsgerError.from('VALIDATION_ERROR', 'parse', 42);
|
|
48
|
-
assert.strictEqual(wrapped.message, 'parse: 42');
|
|
49
|
-
assert.strictEqual(wrapped.cause, undefined);
|
|
50
|
-
});
|
|
51
|
-
void it('should wrap null thrown value', () => {
|
|
52
|
-
const wrapped = EdsgerError.from('API_ERROR', 'unexpected', null);
|
|
53
|
-
assert.strictEqual(wrapped.message, 'unexpected: null');
|
|
54
|
-
assert.strictEqual(wrapped.cause, undefined);
|
|
55
|
-
});
|
|
56
|
-
void it('should wrap undefined thrown value', () => {
|
|
57
|
-
const wrapped = EdsgerError.from('API_ERROR', 'unexpected', undefined);
|
|
58
|
-
assert.strictEqual(wrapped.message, 'unexpected: undefined');
|
|
59
|
-
assert.strictEqual(wrapped.cause, undefined);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
// ---- ApiError ----
|
|
63
|
-
void describe('ApiError', () => {
|
|
64
|
-
void it('should set name to ApiError', () => {
|
|
65
|
-
const err = new ApiError('/api/features', 'not found', 404);
|
|
66
|
-
assert.strictEqual(err.name, 'ApiError');
|
|
67
|
-
});
|
|
68
|
-
void it('should have code API_ERROR', () => {
|
|
69
|
-
const err = new ApiError('/api/features', 'error');
|
|
70
|
-
assert.strictEqual(err.code, 'API_ERROR');
|
|
71
|
-
});
|
|
72
|
-
void it('should store endpoint and statusCode', () => {
|
|
73
|
-
const err = new ApiError('/api/products', 'server error', 500);
|
|
74
|
-
assert.strictEqual(err.endpoint, '/api/products');
|
|
75
|
-
assert.strictEqual(err.statusCode, 500);
|
|
76
|
-
});
|
|
77
|
-
void it('should allow statusCode to be undefined', () => {
|
|
78
|
-
const err = new ApiError('/api/test', 'network error');
|
|
79
|
-
assert.strictEqual(err.statusCode, undefined);
|
|
80
|
-
});
|
|
81
|
-
void it('should be an instance of EdsgerError', () => {
|
|
82
|
-
const err = new ApiError('/api/test', 'error');
|
|
83
|
-
assert.ok(err instanceof EdsgerError);
|
|
84
|
-
});
|
|
85
|
-
void it('should be an instance of Error', () => {
|
|
86
|
-
const err = new ApiError('/api/test', 'error');
|
|
87
|
-
assert.ok(err instanceof Error);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
// ---- GitHubError ----
|
|
91
|
-
void describe('GitHubError', () => {
|
|
92
|
-
void it('should set name to GitHubError', () => {
|
|
93
|
-
const err = new GitHubError('rate limited');
|
|
94
|
-
assert.strictEqual(err.name, 'GitHubError');
|
|
95
|
-
});
|
|
96
|
-
void it('should have code GITHUB_ERROR', () => {
|
|
97
|
-
const err = new GitHubError('error');
|
|
98
|
-
assert.strictEqual(err.code, 'GITHUB_ERROR');
|
|
99
|
-
});
|
|
100
|
-
void it('should store owner, repo, and statusCode from details', () => {
|
|
101
|
-
const err = new GitHubError('forbidden', {
|
|
102
|
-
owner: 'acme',
|
|
103
|
-
repo: 'widget',
|
|
104
|
-
statusCode: 403,
|
|
105
|
-
});
|
|
106
|
-
assert.strictEqual(err.owner, 'acme');
|
|
107
|
-
assert.strictEqual(err.repo, 'widget');
|
|
108
|
-
assert.strictEqual(err.statusCode, 403);
|
|
109
|
-
});
|
|
110
|
-
void it('should default detail fields to undefined', () => {
|
|
111
|
-
const err = new GitHubError('error');
|
|
112
|
-
assert.strictEqual(err.owner, undefined);
|
|
113
|
-
assert.strictEqual(err.repo, undefined);
|
|
114
|
-
assert.strictEqual(err.statusCode, undefined);
|
|
115
|
-
});
|
|
116
|
-
void it('should be an instance of EdsgerError', () => {
|
|
117
|
-
const err = new GitHubError('error');
|
|
118
|
-
assert.ok(err instanceof EdsgerError);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
// ---- AuthError ----
|
|
122
|
-
void describe('AuthError', () => {
|
|
123
|
-
void it('should set name to AuthError', () => {
|
|
124
|
-
const err = new AuthError('token expired');
|
|
125
|
-
assert.strictEqual(err.name, 'AuthError');
|
|
126
|
-
});
|
|
127
|
-
void it('should have code AUTH_ERROR', () => {
|
|
128
|
-
const err = new AuthError('invalid');
|
|
129
|
-
assert.strictEqual(err.code, 'AUTH_ERROR');
|
|
130
|
-
});
|
|
131
|
-
void it('should be an instance of EdsgerError', () => {
|
|
132
|
-
const err = new AuthError('test');
|
|
133
|
-
assert.ok(err instanceof EdsgerError);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
// ---- ConfigError ----
|
|
137
|
-
void describe('ConfigError', () => {
|
|
138
|
-
void it('should set name to ConfigError', () => {
|
|
139
|
-
const err = new ConfigError('missing value');
|
|
140
|
-
assert.strictEqual(err.name, 'ConfigError');
|
|
141
|
-
});
|
|
142
|
-
void it('should have code CONFIG_ERROR', () => {
|
|
143
|
-
const err = new ConfigError('bad config');
|
|
144
|
-
assert.strictEqual(err.code, 'CONFIG_ERROR');
|
|
145
|
-
});
|
|
146
|
-
void it('should store the field name', () => {
|
|
147
|
-
const err = new ConfigError('missing API key', 'apiKey');
|
|
148
|
-
assert.strictEqual(err.field, 'apiKey');
|
|
149
|
-
});
|
|
150
|
-
void it('should default field to undefined', () => {
|
|
151
|
-
const err = new ConfigError('error');
|
|
152
|
-
assert.strictEqual(err.field, undefined);
|
|
153
|
-
});
|
|
154
|
-
void it('should be an instance of EdsgerError', () => {
|
|
155
|
-
const err = new ConfigError('test');
|
|
156
|
-
assert.ok(err instanceof EdsgerError);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
// ---- PhaseError ----
|
|
160
|
-
void describe('PhaseError', () => {
|
|
161
|
-
void it('should set name to PhaseError', () => {
|
|
162
|
-
const err = new PhaseError('code_implementation', 'feat-1', 'compile error');
|
|
163
|
-
assert.strictEqual(err.name, 'PhaseError');
|
|
164
|
-
});
|
|
165
|
-
void it('should have code PHASE_ERROR', () => {
|
|
166
|
-
const err = new PhaseError('testing', 'feat-1', 'test failed');
|
|
167
|
-
assert.strictEqual(err.code, 'PHASE_ERROR');
|
|
168
|
-
});
|
|
169
|
-
void it('should store phase, featureId, and iteration', () => {
|
|
170
|
-
const err = new PhaseError('analysis', 'feat-42', 'timeout', 3);
|
|
171
|
-
assert.strictEqual(err.phase, 'analysis');
|
|
172
|
-
assert.strictEqual(err.featureId, 'feat-42');
|
|
173
|
-
assert.strictEqual(err.iteration, 3);
|
|
174
|
-
});
|
|
175
|
-
void it('should default iteration to undefined', () => {
|
|
176
|
-
const err = new PhaseError('design', 'feat-1', 'error');
|
|
177
|
-
assert.strictEqual(err.iteration, undefined);
|
|
178
|
-
});
|
|
179
|
-
void it('should be an instance of EdsgerError', () => {
|
|
180
|
-
const err = new PhaseError('design', 'feat-1', 'error');
|
|
181
|
-
assert.ok(err instanceof EdsgerError);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
// ---- LLMParseError ----
|
|
185
|
-
void describe('LLMParseError', () => {
|
|
186
|
-
void it('should set name to LLMParseError', () => {
|
|
187
|
-
const err = new LLMParseError('invalid JSON');
|
|
188
|
-
assert.strictEqual(err.name, 'LLMParseError');
|
|
189
|
-
});
|
|
190
|
-
void it('should have code LLM_PARSE_ERROR', () => {
|
|
191
|
-
const err = new LLMParseError('parse error');
|
|
192
|
-
assert.strictEqual(err.code, 'LLM_PARSE_ERROR');
|
|
193
|
-
});
|
|
194
|
-
void it('should store rawResponse when provided', () => {
|
|
195
|
-
const err = new LLMParseError('bad json', '{"incomplete');
|
|
196
|
-
assert.strictEqual(err.rawResponse, '{"incomplete');
|
|
197
|
-
});
|
|
198
|
-
void it('should default rawResponse to undefined', () => {
|
|
199
|
-
const err = new LLMParseError('error');
|
|
200
|
-
assert.strictEqual(err.rawResponse, undefined);
|
|
201
|
-
});
|
|
202
|
-
void it('should truncate rawResponse to 2000 characters', () => {
|
|
203
|
-
const longResponse = 'x'.repeat(5000);
|
|
204
|
-
const err = new LLMParseError('too long', longResponse);
|
|
205
|
-
assert.strictEqual(err.rawResponse?.length, 2000);
|
|
206
|
-
});
|
|
207
|
-
void it('should not truncate rawResponse that is exactly 2000 characters', () => {
|
|
208
|
-
const response = 'y'.repeat(2000);
|
|
209
|
-
const err = new LLMParseError('exact', response);
|
|
210
|
-
assert.strictEqual(err.rawResponse?.length, 2000);
|
|
211
|
-
assert.strictEqual(err.rawResponse, response);
|
|
212
|
-
});
|
|
213
|
-
void it('should not truncate rawResponse shorter than 2000 characters', () => {
|
|
214
|
-
const response = 'short response';
|
|
215
|
-
const err = new LLMParseError('ok', response);
|
|
216
|
-
assert.strictEqual(err.rawResponse, response);
|
|
217
|
-
});
|
|
218
|
-
void it('should be an instance of EdsgerError', () => {
|
|
219
|
-
const err = new LLMParseError('test');
|
|
220
|
-
assert.ok(err instanceof EdsgerError);
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
// ---- WorkerTimeoutError ----
|
|
224
|
-
void describe('WorkerTimeoutError', () => {
|
|
225
|
-
void it('should set name to WorkerTimeoutError', () => {
|
|
226
|
-
const err = new WorkerTimeoutError('feat-1', 30000);
|
|
227
|
-
assert.strictEqual(err.name, 'WorkerTimeoutError');
|
|
228
|
-
});
|
|
229
|
-
void it('should have code WORKER_TIMEOUT', () => {
|
|
230
|
-
const err = new WorkerTimeoutError('feat-1', 60000);
|
|
231
|
-
assert.strictEqual(err.code, 'WORKER_TIMEOUT');
|
|
232
|
-
});
|
|
233
|
-
void it('should store featureId and timeoutMs', () => {
|
|
234
|
-
const err = new WorkerTimeoutError('feat-abc', 120000);
|
|
235
|
-
assert.strictEqual(err.featureId, 'feat-abc');
|
|
236
|
-
assert.strictEqual(err.timeoutMs, 120000);
|
|
237
|
-
});
|
|
238
|
-
void it('should format message with seconds', () => {
|
|
239
|
-
const err = new WorkerTimeoutError('feat-1', 90000);
|
|
240
|
-
assert.strictEqual(err.message, 'Worker for feature feat-1 timed out after 90s');
|
|
241
|
-
});
|
|
242
|
-
void it('should round fractional seconds', () => {
|
|
243
|
-
const err = new WorkerTimeoutError('feat-1', 90500);
|
|
244
|
-
assert.strictEqual(err.message, 'Worker for feature feat-1 timed out after 91s');
|
|
245
|
-
});
|
|
246
|
-
void it('should be an instance of EdsgerError', () => {
|
|
247
|
-
const err = new WorkerTimeoutError('feat-1', 1000);
|
|
248
|
-
assert.ok(err instanceof EdsgerError);
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
// ---- WorkerCrashError ----
|
|
252
|
-
void describe('WorkerCrashError', () => {
|
|
253
|
-
void it('should set name to WorkerCrashError', () => {
|
|
254
|
-
const err = new WorkerCrashError('feat-1', 1);
|
|
255
|
-
assert.strictEqual(err.name, 'WorkerCrashError');
|
|
256
|
-
});
|
|
257
|
-
void it('should have code WORKER_CRASH', () => {
|
|
258
|
-
const err = new WorkerCrashError('feat-1', 1);
|
|
259
|
-
assert.strictEqual(err.code, 'WORKER_CRASH');
|
|
260
|
-
});
|
|
261
|
-
void it('should store featureId and exitCode', () => {
|
|
262
|
-
const err = new WorkerCrashError('feat-99', 137);
|
|
263
|
-
assert.strictEqual(err.featureId, 'feat-99');
|
|
264
|
-
assert.strictEqual(err.exitCode, 137);
|
|
265
|
-
});
|
|
266
|
-
void it('should format message with exit code', () => {
|
|
267
|
-
const err = new WorkerCrashError('feat-1', 1);
|
|
268
|
-
assert.strictEqual(err.message, 'Worker for feature feat-1 crashed with exit code 1');
|
|
269
|
-
});
|
|
270
|
-
void it('should handle null exit code', () => {
|
|
271
|
-
const err = new WorkerCrashError('feat-1', null);
|
|
272
|
-
assert.strictEqual(err.exitCode, null);
|
|
273
|
-
assert.strictEqual(err.message, 'Worker for feature feat-1 crashed with exit code null');
|
|
274
|
-
});
|
|
275
|
-
void it('should be an instance of EdsgerError', () => {
|
|
276
|
-
const err = new WorkerCrashError('feat-1', 1);
|
|
277
|
-
assert.ok(err instanceof EdsgerError);
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
// ---- ValidationError ----
|
|
281
|
-
void describe('ValidationError', () => {
|
|
282
|
-
void it('should set name to ValidationError', () => {
|
|
283
|
-
const err = new ValidationError('invalid input', ['name', 'email']);
|
|
284
|
-
assert.strictEqual(err.name, 'ValidationError');
|
|
285
|
-
});
|
|
286
|
-
void it('should have code VALIDATION_ERROR', () => {
|
|
287
|
-
const err = new ValidationError('bad data');
|
|
288
|
-
assert.strictEqual(err.code, 'VALIDATION_ERROR');
|
|
289
|
-
});
|
|
290
|
-
void it('should store the fields array', () => {
|
|
291
|
-
const err = new ValidationError('missing fields', ['name', 'email']);
|
|
292
|
-
assert.deepStrictEqual(err.fields, ['name', 'email']);
|
|
293
|
-
});
|
|
294
|
-
void it('should default fields to empty array', () => {
|
|
295
|
-
const err = new ValidationError('error');
|
|
296
|
-
assert.deepStrictEqual(err.fields, []);
|
|
297
|
-
});
|
|
298
|
-
void it('should be an instance of EdsgerError', () => {
|
|
299
|
-
const err = new ValidationError('test');
|
|
300
|
-
assert.ok(err instanceof EdsgerError);
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
// ---- Cross-cutting: instanceof chain ----
|
|
304
|
-
void describe('instanceof chain', () => {
|
|
305
|
-
void it('ApiError should be instanceof EdsgerError and Error', () => {
|
|
306
|
-
const err = new ApiError('/test', 'err');
|
|
307
|
-
assert.ok(err instanceof ApiError);
|
|
308
|
-
assert.ok(err instanceof EdsgerError);
|
|
309
|
-
assert.ok(err instanceof Error);
|
|
310
|
-
});
|
|
311
|
-
void it('GitHubError should be instanceof EdsgerError and Error', () => {
|
|
312
|
-
const err = new GitHubError('err');
|
|
313
|
-
assert.ok(err instanceof GitHubError);
|
|
314
|
-
assert.ok(err instanceof EdsgerError);
|
|
315
|
-
assert.ok(err instanceof Error);
|
|
316
|
-
});
|
|
317
|
-
void it('PhaseError should be instanceof EdsgerError and Error', () => {
|
|
318
|
-
const err = new PhaseError('analysis', 'f-1', 'err');
|
|
319
|
-
assert.ok(err instanceof PhaseError);
|
|
320
|
-
assert.ok(err instanceof EdsgerError);
|
|
321
|
-
assert.ok(err instanceof Error);
|
|
322
|
-
});
|
|
323
|
-
void it('WorkerTimeoutError should be instanceof EdsgerError and Error', () => {
|
|
324
|
-
const err = new WorkerTimeoutError('f-1', 1000);
|
|
325
|
-
assert.ok(err instanceof WorkerTimeoutError);
|
|
326
|
-
assert.ok(err instanceof EdsgerError);
|
|
327
|
-
assert.ok(err instanceof Error);
|
|
328
|
-
});
|
|
329
|
-
void it('ApiError should NOT be instanceof GitHubError', () => {
|
|
330
|
-
const err = new ApiError('/test', 'err');
|
|
331
|
-
assert.ok(!(err instanceof GitHubError));
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
// ---- Error cause chain preservation ----
|
|
335
|
-
void describe('Error cause chain preservation', () => {
|
|
336
|
-
void it('should preserve a multi-level cause chain', () => {
|
|
337
|
-
const root = new Error('disk full');
|
|
338
|
-
const mid = new EdsgerError('API_ERROR', 'write failed', { cause: root });
|
|
339
|
-
const top = new ApiError('/api/save', 'save failed', 500, { cause: mid });
|
|
340
|
-
assert.strictEqual(top.cause, mid);
|
|
341
|
-
assert.strictEqual(top.cause.cause, root);
|
|
342
|
-
});
|
|
343
|
-
void it('EdsgerError.from should preserve cause from original error', () => {
|
|
344
|
-
const root = new Error('ECONNREFUSED');
|
|
345
|
-
const wrapped = EdsgerError.from('API_ERROR', 'network', root);
|
|
346
|
-
assert.strictEqual(wrapped.cause, root);
|
|
347
|
-
assert.strictEqual(wrapped.cause.message, 'ECONNREFUSED');
|
|
348
|
-
});
|
|
349
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for app store agent result parsing.
|
|
3
|
-
* Re-implements parseAppStoreResult inline (not exported from source).
|
|
4
|
-
*/
|
|
5
|
-
import assert from 'node:assert';
|
|
6
|
-
import { describe, it } from 'node:test';
|
|
7
|
-
function parseAppStoreResult(responseText) {
|
|
8
|
-
try {
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
-
let jsonResult = null;
|
|
11
|
-
const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
|
|
12
|
-
if (jsonBlockMatch) {
|
|
13
|
-
jsonResult = JSON.parse(jsonBlockMatch[1]);
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
jsonResult = JSON.parse(responseText);
|
|
17
|
-
}
|
|
18
|
-
if (jsonResult && jsonResult.app_store) {
|
|
19
|
-
return { appStore: jsonResult.app_store };
|
|
20
|
-
}
|
|
21
|
-
return { error: 'Invalid JSON structure: missing app_store key' };
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
return {
|
|
25
|
-
error: `JSON parsing failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
void describe('parseAppStoreResult', () => {
|
|
30
|
-
void it('should parse valid JSON with app_store key', () => {
|
|
31
|
-
const input = JSON.stringify({
|
|
32
|
-
app_store: {
|
|
33
|
-
listings: { 'en-US': { app_name: 'Test App' } },
|
|
34
|
-
screenshots: [{ headline: 'Feature 1' }],
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
const result = parseAppStoreResult(input);
|
|
38
|
-
assert.ok(!result.error, 'should not have error');
|
|
39
|
-
assert.ok(result.appStore, 'should have appStore');
|
|
40
|
-
assert.strictEqual(result.appStore.listings['en-US'].app_name, 'Test App');
|
|
41
|
-
assert.strictEqual(result.appStore.screenshots.length, 1);
|
|
42
|
-
});
|
|
43
|
-
void it('should extract JSON from markdown code block', () => {
|
|
44
|
-
const input = `Here is the result:
|
|
45
|
-
|
|
46
|
-
\`\`\`json
|
|
47
|
-
{
|
|
48
|
-
"app_store": {
|
|
49
|
-
"listings": { "en-US": { "app_name": "My App" } },
|
|
50
|
-
"screenshots": []
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
\`\`\`
|
|
54
|
-
|
|
55
|
-
That's the output.`;
|
|
56
|
-
const result = parseAppStoreResult(input);
|
|
57
|
-
assert.ok(!result.error, 'should not have error');
|
|
58
|
-
assert.strictEqual(result.appStore.listings['en-US'].app_name, 'My App');
|
|
59
|
-
});
|
|
60
|
-
void it('should return error for JSON without app_store key', () => {
|
|
61
|
-
const input = JSON.stringify({ listings: {}, screenshots: [] });
|
|
62
|
-
const result = parseAppStoreResult(input);
|
|
63
|
-
assert.ok(result.error, 'should have error');
|
|
64
|
-
assert.ok(result.error.includes('missing app_store key'), 'should mention missing key');
|
|
65
|
-
});
|
|
66
|
-
void it('should return error for invalid JSON', () => {
|
|
67
|
-
const result = parseAppStoreResult('not json at all');
|
|
68
|
-
assert.ok(result.error, 'should have error');
|
|
69
|
-
assert.ok(result.error.includes('JSON parsing failed'), 'should mention parsing failure');
|
|
70
|
-
});
|
|
71
|
-
void it('should return error for empty string', () => {
|
|
72
|
-
const result = parseAppStoreResult('');
|
|
73
|
-
assert.ok(result.error, 'should have error');
|
|
74
|
-
});
|
|
75
|
-
void it('should handle JSON with extra whitespace', () => {
|
|
76
|
-
const input = ` \n { "app_store": { "listings": {} } } \n `;
|
|
77
|
-
const result = parseAppStoreResult(input);
|
|
78
|
-
assert.ok(!result.error, 'should not have error');
|
|
79
|
-
assert.ok(result.appStore, 'should have appStore');
|
|
80
|
-
});
|
|
81
|
-
void it('should parse nested app_store structure fully', () => {
|
|
82
|
-
const input = JSON.stringify({
|
|
83
|
-
app_store: {
|
|
84
|
-
listings: {
|
|
85
|
-
'en-US': {
|
|
86
|
-
app_name: 'Test',
|
|
87
|
-
subtitle: 'Subtitle',
|
|
88
|
-
description: 'Full desc',
|
|
89
|
-
keywords: 'a,b,c',
|
|
90
|
-
whats_new: 'New stuff',
|
|
91
|
-
},
|
|
92
|
-
'zh-CN': {
|
|
93
|
-
app_name: '测试',
|
|
94
|
-
description: '完整描述',
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
screenshots: [
|
|
98
|
-
{
|
|
99
|
-
feature_highlight: 'Chat',
|
|
100
|
-
headline: 'Real-time Chat',
|
|
101
|
-
subheadline: 'Message anyone',
|
|
102
|
-
background_gradient: 'linear-gradient(135deg, #667eea, #764ba2)',
|
|
103
|
-
text_color: '#ffffff',
|
|
104
|
-
device_frame: 'iphone',
|
|
105
|
-
html_template: '<html><body>Mock</body></html>',
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
feature_highlight: 'Dashboard',
|
|
109
|
-
headline: 'Your Dashboard',
|
|
110
|
-
device_frame: 'ipad',
|
|
111
|
-
html_template: '<html><body>Dashboard</body></html>',
|
|
112
|
-
},
|
|
113
|
-
],
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
const result = parseAppStoreResult(input);
|
|
117
|
-
assert.ok(!result.error, 'should not have error');
|
|
118
|
-
assert.strictEqual(Object.keys(result.appStore.listings).length, 2, 'should have 2 locales');
|
|
119
|
-
assert.strictEqual(result.appStore.screenshots.length, 2, 'should have 2 screenshots');
|
|
120
|
-
assert.strictEqual(result.appStore.listings['zh-CN'].app_name, '测试');
|
|
121
|
-
});
|
|
122
|
-
void it('should handle code block with indented closing', () => {
|
|
123
|
-
const input = `\`\`\`json
|
|
124
|
-
{ "app_store": { "listings": { "en-US": { "app_name": "Indented" } } } }
|
|
125
|
-
\`\`\``;
|
|
126
|
-
const result = parseAppStoreResult(input);
|
|
127
|
-
assert.ok(!result.error, 'should not have error');
|
|
128
|
-
assert.strictEqual(result.appStore.listings['en-US'].app_name, 'Indented');
|
|
129
|
-
});
|
|
130
|
-
void it('should prefer code block over surrounding text', () => {
|
|
131
|
-
const input = `Some preamble text
|
|
132
|
-
|
|
133
|
-
\`\`\`json
|
|
134
|
-
{ "app_store": { "listings": {} } }
|
|
135
|
-
\`\`\`
|
|
136
|
-
|
|
137
|
-
Some trailing text`;
|
|
138
|
-
const result = parseAppStoreResult(input);
|
|
139
|
-
assert.ok(!result.error, 'should extract from code block');
|
|
140
|
-
assert.ok(result.appStore, 'should have appStore from code block');
|
|
141
|
-
});
|
|
142
|
-
});
|