edsger 0.45.1 → 0.47.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 +3 -23
- package/dist/api/__tests__/app-store.test.d.ts +7 -0
- package/dist/api/__tests__/app-store.test.js +60 -0
- package/dist/api/__tests__/intelligence.test.d.ts +11 -0
- package/dist/api/__tests__/intelligence.test.js +315 -0
- package/dist/api/features/__tests__/feature-utils.test.d.ts +4 -0
- package/dist/api/features/__tests__/feature-utils.test.js +370 -0
- package/dist/api/features/__tests__/status-updater.test.d.ts +4 -0
- package/dist/api/features/__tests__/status-updater.test.js +88 -0
- package/dist/commands/build/__tests__/build.test.d.ts +5 -0
- package/dist/commands/build/__tests__/build.test.js +206 -0
- package/dist/commands/build/__tests__/detect-project.test.d.ts +6 -0
- package/dist/commands/build/__tests__/detect-project.test.js +160 -0
- package/dist/commands/build/__tests__/run-build.test.d.ts +6 -0
- package/dist/commands/build/__tests__/run-build.test.js +433 -0
- package/dist/commands/intelligence/__tests__/command.test.d.ts +4 -0
- package/dist/commands/intelligence/__tests__/command.test.js +48 -0
- package/dist/commands/run-sheet/index.js +6 -0
- package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +5 -0
- package/dist/commands/workflow/core/__tests__/feature-filter.test.js +316 -0
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +4 -0
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +397 -0
- package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +4 -0
- package/dist/commands/workflow/core/__tests__/state-manager.test.js +384 -0
- package/dist/commands/workflow/executors/phase-executor.js +3 -1
- package/dist/commands/workflow/phase-orchestrator.js +1 -2
- package/dist/config/__tests__/config.test.d.ts +4 -0
- package/dist/config/__tests__/config.test.js +286 -0
- package/dist/config/__tests__/feature-status.test.d.ts +4 -0
- package/dist/config/__tests__/feature-status.test.js +111 -0
- package/dist/errors/__tests__/index.test.d.ts +4 -0
- package/dist/errors/__tests__/index.test.js +349 -0
- package/dist/index.js +0 -0
- package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +5 -0
- package/dist/phases/app-store-generation/__tests__/agent.test.js +142 -0
- package/dist/phases/app-store-generation/__tests__/context.test.d.ts +4 -0
- package/dist/phases/app-store-generation/__tests__/context.test.js +284 -0
- package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +4 -0
- package/dist/phases/app-store-generation/__tests__/prompts.test.js +122 -0
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +5 -0
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +826 -0
- 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/__tests__/diff-utils.test.d.ts +1 -0
- package/dist/phases/code-review/__tests__/diff-utils.test.js +101 -0
- 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/intelligence-analysis/__tests__/context.test.d.ts +4 -0
- package/dist/phases/intelligence-analysis/__tests__/context.test.js +192 -0
- package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +13 -0
- package/dist/phases/intelligence-analysis/__tests__/matching.test.js +154 -0
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +5 -0
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +378 -0
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +4 -0
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +33 -0
- package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +1 -0
- package/dist/phases/pr-execution/__tests__/file-assigner.test.js +303 -0
- package/dist/phases/pr-execution/index.js +1 -0
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +1 -0
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +157 -0
- package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +1 -0
- package/dist/phases/pr-resolve/__tests__/prompts.test.js +116 -0
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +1 -0
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +138 -0
- package/dist/phases/pr-resolve/__tests__/types.test.d.ts +1 -0
- package/dist/phases/pr-resolve/__tests__/types.test.js +43 -0
- package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +1 -0
- package/dist/phases/pr-resolve/__tests__/workspace.test.js +111 -0
- package/dist/phases/pr-review/__tests__/prompts.test.d.ts +1 -0
- package/dist/phases/pr-review/__tests__/prompts.test.js +49 -0
- package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +1 -0
- package/dist/phases/pr-review/__tests__/review-comments.test.js +110 -0
- package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +1 -0
- package/dist/phases/pr-shared/__tests__/agent-utils.test.js +91 -0
- package/dist/phases/pr-shared/__tests__/context.test.d.ts +1 -0
- package/dist/phases/pr-shared/__tests__/context.test.js +94 -0
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +1 -0
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +331 -0
- package/dist/phases/pr-splitting/index.js +1 -2
- package/dist/phases/release-sync/github.d.ts +12 -0
- package/dist/phases/release-sync/github.js +39 -0
- package/dist/phases/release-sync/snapshot.js +0 -1
- package/dist/phases/run-sheet/index.d.ts +15 -0
- package/dist/phases/run-sheet/index.js +161 -29
- package/dist/phases/run-sheet/render.d.ts +23 -5
- package/dist/phases/run-sheet/render.js +195 -31
- package/dist/phases/smoke-test/__tests__/agent.test.d.ts +4 -0
- package/dist/phases/smoke-test/__tests__/agent.test.js +84 -0
- package/dist/phases/smoke-test/__tests__/github.test.d.ts +9 -0
- package/dist/phases/smoke-test/__tests__/github.test.js +120 -0
- package/dist/phases/smoke-test/__tests__/snapshot.test.d.ts +8 -0
- package/dist/phases/smoke-test/__tests__/snapshot.test.js +93 -0
- package/dist/phases/smoke-test/agent.js +2 -4
- package/dist/phases/smoke-test/github.d.ts +54 -0
- package/dist/phases/smoke-test/github.js +101 -0
- package/dist/phases/smoke-test/index.js +11 -6
- package/dist/phases/smoke-test/snapshot.d.ts +27 -0
- package/dist/phases/smoke-test/snapshot.js +157 -0
- 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/dist/services/coaching/__tests__/coaching-agent.test.d.ts +1 -0
- package/dist/services/coaching/__tests__/coaching-agent.test.js +74 -0
- package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +1 -0
- package/dist/services/coaching/__tests__/coaching-loop.test.js +59 -0
- package/dist/services/coaching/__tests__/self-rating.test.d.ts +1 -0
- package/dist/services/coaching/__tests__/self-rating.test.js +188 -0
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
- package/dist/services/lifecycle-agent/index.d.ts +24 -0
- package/dist/services/lifecycle-agent/index.js +25 -0
- package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
- package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
- package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
- package/dist/services/lifecycle-agent/transition-rules.js +184 -0
- package/dist/services/lifecycle-agent/types.d.ts +190 -0
- package/dist/services/lifecycle-agent/types.js +12 -0
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +1 -0
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +122 -0
- package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +1 -0
- package/dist/services/phase-hooks/__tests__/hook-executor.test.js +321 -0
- package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +1 -0
- package/dist/services/phase-hooks/__tests__/hook-runner.test.js +261 -0
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +1 -0
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +158 -0
- package/dist/services/video/__tests__/video-pipeline.test.d.ts +6 -0
- package/dist/services/video/__tests__/video-pipeline.test.js +249 -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.js +17 -4
- package/package.json +1 -1
- package/.env.local +0 -12
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the config module (loadConfig and validateConfig)
|
|
3
|
+
*/
|
|
4
|
+
import assert from 'node:assert';
|
|
5
|
+
import { mkdtempSync } from 'node:fs';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { describe, it } from 'node:test';
|
|
9
|
+
import { loadConfig, validateConfig } from '../../config.js';
|
|
10
|
+
/**
|
|
11
|
+
* Helper to create a valid default-shaped config for validateConfig tests.
|
|
12
|
+
* Individual tests override specific fields to trigger validation errors.
|
|
13
|
+
*/
|
|
14
|
+
function makeValidConfig(overrides = {}) {
|
|
15
|
+
return {
|
|
16
|
+
patterns: ['**/*.ts'],
|
|
17
|
+
exclude: ['**/node_modules/**'],
|
|
18
|
+
severity: 'error',
|
|
19
|
+
maxFiles: 50,
|
|
20
|
+
claude: { timeout: 120000 },
|
|
21
|
+
workflow: {
|
|
22
|
+
maxConcurrency: 3,
|
|
23
|
+
pollInterval: 30000,
|
|
24
|
+
maxRetries: 3,
|
|
25
|
+
retryCooldown: 300000,
|
|
26
|
+
workerTimeout: 1800000,
|
|
27
|
+
maxVerificationIterations: 10,
|
|
28
|
+
},
|
|
29
|
+
...overrides,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
void describe('Config Module', () => {
|
|
33
|
+
// ── loadConfig ──────────────────────────────────────────────────────
|
|
34
|
+
void describe('loadConfig', () => {
|
|
35
|
+
void it('should return defaults when no config file exists', () => {
|
|
36
|
+
// Use a fresh temp directory that has no config files
|
|
37
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
38
|
+
const originalCwd = process.cwd();
|
|
39
|
+
try {
|
|
40
|
+
process.chdir(tempDir);
|
|
41
|
+
const config = loadConfig();
|
|
42
|
+
assert.ok(Array.isArray(config.patterns), 'patterns should be an array');
|
|
43
|
+
assert.ok(config.patterns.length > 0, 'patterns should not be empty');
|
|
44
|
+
assert.ok(Array.isArray(config.exclude), 'exclude should be an array');
|
|
45
|
+
assert.ok(config.exclude.length > 0, 'exclude should not be empty');
|
|
46
|
+
assert.strictEqual(config.severity, 'error');
|
|
47
|
+
assert.strictEqual(config.maxFiles, 50);
|
|
48
|
+
assert.strictEqual(config.claude.timeout, 120000);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
process.chdir(originalCwd);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
void it('should include all default patterns for common languages', () => {
|
|
55
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
56
|
+
const originalCwd = process.cwd();
|
|
57
|
+
try {
|
|
58
|
+
process.chdir(tempDir);
|
|
59
|
+
const config = loadConfig();
|
|
60
|
+
const expectedPatterns = [
|
|
61
|
+
'**/*.js',
|
|
62
|
+
'**/*.ts',
|
|
63
|
+
'**/*.tsx',
|
|
64
|
+
'**/*.py',
|
|
65
|
+
'**/*.go',
|
|
66
|
+
'**/*.rs',
|
|
67
|
+
'**/*.java',
|
|
68
|
+
];
|
|
69
|
+
for (const pattern of expectedPatterns) {
|
|
70
|
+
assert.ok(config.patterns.includes(pattern), `Default patterns should include ${pattern}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
process.chdir(originalCwd);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
void it('should include sensible default excludes', () => {
|
|
78
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
79
|
+
const originalCwd = process.cwd();
|
|
80
|
+
try {
|
|
81
|
+
process.chdir(tempDir);
|
|
82
|
+
const config = loadConfig();
|
|
83
|
+
assert.ok(config.exclude.includes('**/node_modules/**'), 'Should exclude node_modules');
|
|
84
|
+
assert.ok(config.exclude.includes('**/dist/**'), 'Should exclude dist');
|
|
85
|
+
assert.ok(config.exclude.includes('**/__tests__/**'), 'Should exclude __tests__');
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
process.chdir(originalCwd);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
void it('should return correct default workflow values', () => {
|
|
92
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
93
|
+
const originalCwd = process.cwd();
|
|
94
|
+
try {
|
|
95
|
+
process.chdir(tempDir);
|
|
96
|
+
const config = loadConfig();
|
|
97
|
+
assert.strictEqual(config.workflow.maxConcurrency, 3);
|
|
98
|
+
assert.strictEqual(config.workflow.pollInterval, 30000);
|
|
99
|
+
assert.strictEqual(config.workflow.maxRetries, 3);
|
|
100
|
+
assert.strictEqual(config.workflow.retryCooldown, 300000);
|
|
101
|
+
assert.strictEqual(config.workflow.workerTimeout, 1800000);
|
|
102
|
+
assert.strictEqual(config.workflow.maxVerificationIterations, 10);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
process.chdir(originalCwd);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
void it('should return a claude object with default timeout', () => {
|
|
109
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
110
|
+
const originalCwd = process.cwd();
|
|
111
|
+
try {
|
|
112
|
+
process.chdir(tempDir);
|
|
113
|
+
const config = loadConfig();
|
|
114
|
+
assert.ok(typeof config.claude === 'object' && config.claude !== null, 'claude should be an object');
|
|
115
|
+
assert.strictEqual(config.claude.timeout, 120000);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
process.chdir(originalCwd);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
// ── validateConfig: valid configs ───────────────────────────────────
|
|
123
|
+
void describe('validateConfig - valid configs', () => {
|
|
124
|
+
void it('should return empty errors for a valid config', () => {
|
|
125
|
+
const config = makeValidConfig();
|
|
126
|
+
const errors = validateConfig(config);
|
|
127
|
+
assert.deepStrictEqual(errors, []);
|
|
128
|
+
});
|
|
129
|
+
void it('should accept severity "warning"', () => {
|
|
130
|
+
const config = makeValidConfig({ severity: 'warning' });
|
|
131
|
+
const errors = validateConfig(config);
|
|
132
|
+
assert.deepStrictEqual(errors, []);
|
|
133
|
+
});
|
|
134
|
+
void it('should accept config without claude.timeout (undefined)', () => {
|
|
135
|
+
const config = makeValidConfig({ claude: {} });
|
|
136
|
+
const errors = validateConfig(config);
|
|
137
|
+
assert.deepStrictEqual(errors, []);
|
|
138
|
+
});
|
|
139
|
+
void it('should accept maxFiles of 1', () => {
|
|
140
|
+
const config = makeValidConfig({ maxFiles: 1 });
|
|
141
|
+
const errors = validateConfig(config);
|
|
142
|
+
assert.deepStrictEqual(errors, []);
|
|
143
|
+
});
|
|
144
|
+
void it('should accept a large maxFiles value', () => {
|
|
145
|
+
const config = makeValidConfig({ maxFiles: 10000 });
|
|
146
|
+
const errors = validateConfig(config);
|
|
147
|
+
assert.deepStrictEqual(errors, []);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
// ── validateConfig: patterns ────────────────────────────────────────
|
|
151
|
+
void describe('validateConfig - patterns validation', () => {
|
|
152
|
+
void it('should return error for empty patterns array', () => {
|
|
153
|
+
const config = makeValidConfig({ patterns: [] });
|
|
154
|
+
const errors = validateConfig(config);
|
|
155
|
+
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
156
|
+
assert.ok(errors.some((e) => e.includes('patterns')), 'Error should mention patterns');
|
|
157
|
+
});
|
|
158
|
+
void it('should return error for non-array patterns', () => {
|
|
159
|
+
const config = makeValidConfig({
|
|
160
|
+
patterns: 'not-an-array',
|
|
161
|
+
});
|
|
162
|
+
const errors = validateConfig(config);
|
|
163
|
+
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
164
|
+
assert.ok(errors.some((e) => e.includes('patterns')), 'Error should mention patterns');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
// ── validateConfig: exclude ─────────────────────────────────────────
|
|
168
|
+
void describe('validateConfig - exclude validation', () => {
|
|
169
|
+
void it('should return error for non-array exclude', () => {
|
|
170
|
+
const config = makeValidConfig({
|
|
171
|
+
exclude: 'not-an-array',
|
|
172
|
+
});
|
|
173
|
+
const errors = validateConfig(config);
|
|
174
|
+
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
175
|
+
assert.ok(errors.some((e) => e.includes('exclude')), 'Error should mention exclude');
|
|
176
|
+
});
|
|
177
|
+
void it('should accept empty exclude array', () => {
|
|
178
|
+
const config = makeValidConfig({ exclude: [] });
|
|
179
|
+
const errors = validateConfig(config);
|
|
180
|
+
assert.deepStrictEqual(errors, []);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
// ── validateConfig: severity ────────────────────────────────────────
|
|
184
|
+
void describe('validateConfig - severity validation', () => {
|
|
185
|
+
void it('should return error for invalid severity', () => {
|
|
186
|
+
const config = makeValidConfig({
|
|
187
|
+
severity: 'info',
|
|
188
|
+
});
|
|
189
|
+
const errors = validateConfig(config);
|
|
190
|
+
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
191
|
+
assert.ok(errors.some((e) => e.includes('severity')), 'Error should mention severity');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
// ── validateConfig: maxFiles ────────────────────────────────────────
|
|
195
|
+
void describe('validateConfig - maxFiles validation', () => {
|
|
196
|
+
void it('should return error for zero maxFiles', () => {
|
|
197
|
+
const config = makeValidConfig({ maxFiles: 0 });
|
|
198
|
+
const errors = validateConfig(config);
|
|
199
|
+
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
200
|
+
assert.ok(errors.some((e) => e.includes('maxFiles')), 'Error should mention maxFiles');
|
|
201
|
+
});
|
|
202
|
+
void it('should return error for negative maxFiles', () => {
|
|
203
|
+
const config = makeValidConfig({ maxFiles: -5 });
|
|
204
|
+
const errors = validateConfig(config);
|
|
205
|
+
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
206
|
+
assert.ok(errors.some((e) => e.includes('maxFiles')), 'Error should mention maxFiles');
|
|
207
|
+
});
|
|
208
|
+
void it('should return error for non-number maxFiles', () => {
|
|
209
|
+
const config = makeValidConfig({
|
|
210
|
+
maxFiles: 'fifty',
|
|
211
|
+
});
|
|
212
|
+
const errors = validateConfig(config);
|
|
213
|
+
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
214
|
+
assert.ok(errors.some((e) => e.includes('maxFiles')), 'Error should mention maxFiles');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
// ── validateConfig: claude.timeout ──────────────────────────────────
|
|
218
|
+
void describe('validateConfig - claude.timeout validation', () => {
|
|
219
|
+
void it('should return error for negative timeout', () => {
|
|
220
|
+
const config = makeValidConfig({ claude: { timeout: -1000 } });
|
|
221
|
+
const errors = validateConfig(config);
|
|
222
|
+
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
223
|
+
assert.ok(errors.some((e) => e.includes('timeout')), 'Error should mention timeout');
|
|
224
|
+
});
|
|
225
|
+
void it('should return error for zero timeout', () => {
|
|
226
|
+
// timeout of 0 is falsy, so the validation guard skips it
|
|
227
|
+
// This documents the current behavior
|
|
228
|
+
const config = makeValidConfig({ claude: { timeout: 0 } });
|
|
229
|
+
const errors = validateConfig(config);
|
|
230
|
+
// 0 is falsy, so the `config.claude.timeout &&` guard means
|
|
231
|
+
// zero timeout does NOT trigger the validation error
|
|
232
|
+
assert.deepStrictEqual(errors, []);
|
|
233
|
+
});
|
|
234
|
+
void it('should return error for non-number timeout', () => {
|
|
235
|
+
const config = makeValidConfig({
|
|
236
|
+
claude: { timeout: 'slow' },
|
|
237
|
+
});
|
|
238
|
+
const errors = validateConfig(config);
|
|
239
|
+
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
240
|
+
assert.ok(errors.some((e) => e.includes('timeout')), 'Error should mention timeout');
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
// ── validateConfig: multiple errors ─────────────────────────────────
|
|
244
|
+
void describe('validateConfig - multiple errors', () => {
|
|
245
|
+
void it('should collect all validation errors at once', () => {
|
|
246
|
+
const config = {
|
|
247
|
+
patterns: [],
|
|
248
|
+
exclude: 'not-array',
|
|
249
|
+
severity: 'info',
|
|
250
|
+
maxFiles: -1,
|
|
251
|
+
claude: { timeout: -500 },
|
|
252
|
+
workflow: {
|
|
253
|
+
maxConcurrency: 3,
|
|
254
|
+
pollInterval: 30000,
|
|
255
|
+
maxRetries: 3,
|
|
256
|
+
retryCooldown: 300000,
|
|
257
|
+
workerTimeout: 1800000,
|
|
258
|
+
maxVerificationIterations: 10,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
const errors = validateConfig(config);
|
|
262
|
+
assert.strictEqual(errors.length, 5, 'Should report all five errors');
|
|
263
|
+
assert.ok(errors.some((e) => e.includes('patterns')));
|
|
264
|
+
assert.ok(errors.some((e) => e.includes('exclude')));
|
|
265
|
+
assert.ok(errors.some((e) => e.includes('severity')));
|
|
266
|
+
assert.ok(errors.some((e) => e.includes('maxFiles')));
|
|
267
|
+
assert.ok(errors.some((e) => e.includes('timeout')));
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
// ── Default config passes validation ────────────────────────────────
|
|
271
|
+
void describe('loadConfig + validateConfig integration', () => {
|
|
272
|
+
void it('should produce a config from loadConfig that passes validation', () => {
|
|
273
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
274
|
+
const originalCwd = process.cwd();
|
|
275
|
+
try {
|
|
276
|
+
process.chdir(tempDir);
|
|
277
|
+
const config = loadConfig();
|
|
278
|
+
const errors = validateConfig(config);
|
|
279
|
+
assert.deepStrictEqual(errors, [], 'Default config from loadConfig should pass validation');
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
process.chdir(originalCwd);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
});
|