popeye-cli 2.0.0 → 2.2.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/CHANGELOG.md +55 -0
- package/CONTRIBUTING.md +23 -2
- package/README.md +47 -18
- package/dist/adapters/gemini.js +3 -3
- package/dist/adapters/openai.js +2 -2
- package/dist/adapters/openai.js.map +1 -1
- package/dist/auth/gemini.js +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +11 -5
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/resume.d.ts.map +1 -1
- package/dist/cli/commands/resume.js +9 -1
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +33 -4
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +7 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +1 -7
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/popeye-md.d.ts +32 -0
- package/dist/config/popeye-md.d.ts.map +1 -0
- package/dist/config/popeye-md.js +111 -0
- package/dist/config/popeye-md.js.map +1 -0
- package/dist/config/schema.d.ts +3 -21
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +21 -8
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +23 -1
- package/dist/generators/all.js.map +1 -1
- package/dist/pipeline/artifact-manager.d.ts.map +1 -1
- package/dist/pipeline/artifact-manager.js +3 -0
- package/dist/pipeline/artifact-manager.js.map +1 -1
- package/dist/pipeline/bridges/review-bridge.d.ts +70 -0
- package/dist/pipeline/bridges/review-bridge.d.ts.map +1 -0
- package/dist/pipeline/bridges/review-bridge.js +266 -0
- package/dist/pipeline/bridges/review-bridge.js.map +1 -0
- package/dist/pipeline/consensus/consensus-runner.js +3 -3
- package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
- package/dist/pipeline/gate-engine.js +1 -1
- package/dist/pipeline/gate-engine.js.map +1 -1
- package/dist/pipeline/migration.d.ts.map +1 -1
- package/dist/pipeline/migration.js +3 -26
- package/dist/pipeline/migration.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts +2 -0
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +10 -1
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/phases/implementation.d.ts.map +1 -1
- package/dist/pipeline/phases/implementation.js +5 -2
- package/dist/pipeline/phases/implementation.js.map +1 -1
- package/dist/pipeline/phases/intake.d.ts +1 -0
- package/dist/pipeline/phases/intake.d.ts.map +1 -1
- package/dist/pipeline/phases/intake.js +56 -8
- package/dist/pipeline/phases/intake.js.map +1 -1
- package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
- package/dist/pipeline/phases/recovery-loop.js +2 -0
- package/dist/pipeline/phases/recovery-loop.js.map +1 -1
- package/dist/pipeline/phases/role-planning.d.ts.map +1 -1
- package/dist/pipeline/phases/role-planning.js +2 -3
- package/dist/pipeline/phases/role-planning.js.map +1 -1
- package/dist/pipeline/skills/constitution-generator.d.ts +51 -0
- package/dist/pipeline/skills/constitution-generator.d.ts.map +1 -0
- package/dist/pipeline/skills/constitution-generator.js +210 -0
- package/dist/pipeline/skills/constitution-generator.js.map +1 -0
- package/dist/pipeline/skills/generator.d.ts +65 -0
- package/dist/pipeline/skills/generator.d.ts.map +1 -0
- package/dist/pipeline/skills/generator.js +221 -0
- package/dist/pipeline/skills/generator.js.map +1 -0
- package/dist/pipeline/skills/role-map.d.ts +38 -0
- package/dist/pipeline/skills/role-map.d.ts.map +1 -0
- package/dist/pipeline/skills/role-map.js +234 -0
- package/dist/pipeline/skills/role-map.js.map +1 -0
- package/dist/pipeline/skills/types.d.ts +47 -0
- package/dist/pipeline/skills/types.d.ts.map +1 -0
- package/dist/pipeline/skills/types.js +5 -0
- package/dist/pipeline/skills/types.js.map +1 -0
- package/dist/pipeline/type-defs/artifacts.d.ts +10 -0
- package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
- package/dist/pipeline/type-defs/artifacts.js +2 -0
- package/dist/pipeline/type-defs/artifacts.js.map +1 -1
- package/dist/pipeline/type-defs/audit.d.ts +6 -0
- package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
- package/dist/pipeline/type-defs/checks.d.ts +2 -0
- package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
- package/dist/pipeline/type-defs/packets.d.ts +30 -0
- package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
- package/dist/pipeline/type-defs/state.d.ts +11 -0
- package/dist/pipeline/type-defs/state.d.ts.map +1 -1
- package/dist/pipeline/type-defs/state.js +2 -0
- package/dist/pipeline/type-defs/state.js.map +1 -1
- package/dist/types/consensus.d.ts +5 -1
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +15 -4
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/project.d.ts +1 -1
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +39 -10
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +1 -7
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +1 -1
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/handlers.js +5 -5
- package/dist/upgrade/handlers.js.map +1 -1
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +18 -14
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/website-strategy.js +1 -1
- package/dist/workflow/website-strategy.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/gemini.ts +3 -3
- package/src/adapters/openai.ts +2 -2
- package/src/auth/gemini.ts +1 -1
- package/src/cli/commands/create.ts +12 -6
- package/src/cli/commands/resume.ts +9 -1
- package/src/cli/interactive.ts +36 -4
- package/src/config/defaults.ts +7 -2
- package/src/config/popeye-md.ts +139 -0
- package/src/config/schema.ts +21 -8
- package/src/generators/all.ts +23 -1
- package/src/pipeline/artifact-manager.ts +3 -0
- package/src/pipeline/bridges/review-bridge.ts +371 -0
- package/src/pipeline/consensus/consensus-runner.ts +3 -3
- package/src/pipeline/gate-engine.ts +1 -1
- package/src/pipeline/migration.ts +5 -30
- package/src/pipeline/orchestrator.ts +14 -0
- package/src/pipeline/phases/implementation.ts +6 -2
- package/src/pipeline/phases/intake.ts +73 -10
- package/src/pipeline/phases/recovery-loop.ts +2 -0
- package/src/pipeline/phases/role-planning.ts +2 -3
- package/src/pipeline/skills/constitution-generator.ts +236 -0
- package/src/pipeline/skills/generator.ts +287 -0
- package/src/pipeline/skills/role-map.ts +248 -0
- package/src/pipeline/skills/types.ts +53 -0
- package/src/pipeline/type-defs/artifacts.ts +2 -0
- package/src/pipeline/type-defs/state.ts +2 -0
- package/src/types/consensus.ts +16 -4
- package/src/types/index.ts +1 -0
- package/src/types/project.ts +39 -10
- package/src/types/workflow.ts +1 -1
- package/src/upgrade/handlers.ts +5 -5
- package/src/workflow/index.ts +18 -14
- package/src/workflow/website-strategy.ts +1 -1
- package/tests/cli/model-command.test.ts +19 -9
- package/tests/config/config.test.ts +3 -3
- package/tests/config/popeye-md.test.ts +168 -0
- package/tests/pipeline/bridges/review-bridge.test.ts +243 -0
- package/tests/pipeline/migration.test.ts +4 -3
- package/tests/pipeline/session-guidance.test.ts +205 -0
- package/tests/pipeline/skills/constitution-generator.test.ts +201 -0
- package/tests/pipeline/skills/generator.test.ts +213 -0
- package/tests/pipeline/skills/role-map.test.ts +198 -0
- package/tests/types/consensus.test.ts +1 -1
- package/tests/workflow/pipeline-bootstrap.test.ts +162 -0
package/src/types/project.ts
CHANGED
|
@@ -242,7 +242,12 @@ export interface WebsiteSpec {
|
|
|
242
242
|
/**
|
|
243
243
|
* Known OpenAI models (used for suggestions and display, not strict validation)
|
|
244
244
|
*/
|
|
245
|
-
export const KNOWN_OPENAI_MODELS = [
|
|
245
|
+
export const KNOWN_OPENAI_MODELS = [
|
|
246
|
+
'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano',
|
|
247
|
+
'o3', 'o3-mini', 'o4-mini',
|
|
248
|
+
'gpt-4o', 'gpt-4o-mini',
|
|
249
|
+
'gpt-4-turbo', 'o1-preview', 'o1-mini',
|
|
250
|
+
] as const;
|
|
246
251
|
|
|
247
252
|
/**
|
|
248
253
|
* OpenAI model schema - accepts any non-empty string to support new models
|
|
@@ -289,24 +294,48 @@ export interface GenerationOptions {
|
|
|
289
294
|
* Available OpenAI models with descriptions
|
|
290
295
|
*/
|
|
291
296
|
export const OPENAI_MODELS: Record<OpenAIModel, { description: string; recommended: string }> = {
|
|
292
|
-
'gpt-
|
|
293
|
-
description: '
|
|
297
|
+
'gpt-4.1': {
|
|
298
|
+
description: 'Smartest non-reasoning model, 1M context',
|
|
294
299
|
recommended: 'Complex projects',
|
|
295
300
|
},
|
|
301
|
+
'gpt-4.1-mini': {
|
|
302
|
+
description: 'Fast, strong instruction following',
|
|
303
|
+
recommended: 'Medium complexity',
|
|
304
|
+
},
|
|
305
|
+
'gpt-4.1-nano': {
|
|
306
|
+
description: 'Fastest, most cost-efficient',
|
|
307
|
+
recommended: 'Simple projects',
|
|
308
|
+
},
|
|
309
|
+
'o3': {
|
|
310
|
+
description: 'Strongest reasoning model',
|
|
311
|
+
recommended: 'Architectural decisions',
|
|
312
|
+
},
|
|
313
|
+
'o3-mini': {
|
|
314
|
+
description: 'Efficient reasoning',
|
|
315
|
+
recommended: 'Code review',
|
|
316
|
+
},
|
|
317
|
+
'o4-mini': {
|
|
318
|
+
description: 'Fast reasoning, best on STEM',
|
|
319
|
+
recommended: 'Technical analysis',
|
|
320
|
+
},
|
|
321
|
+
'gpt-4o': {
|
|
322
|
+
description: 'Multimodal, strong all-rounder',
|
|
323
|
+
recommended: 'General purpose',
|
|
324
|
+
},
|
|
296
325
|
'gpt-4o-mini': {
|
|
297
326
|
description: 'Fast, cost-effective',
|
|
298
|
-
recommended: 'Simple
|
|
327
|
+
recommended: 'Simple tasks',
|
|
299
328
|
},
|
|
300
329
|
'gpt-4-turbo': {
|
|
301
|
-
description: 'High capability,
|
|
302
|
-
recommended: '
|
|
330
|
+
description: 'High capability, legacy',
|
|
331
|
+
recommended: 'Backward compatibility',
|
|
303
332
|
},
|
|
304
333
|
'o1-preview': {
|
|
305
|
-
description: 'Advanced reasoning',
|
|
306
|
-
recommended: '
|
|
334
|
+
description: 'Advanced reasoning (legacy)',
|
|
335
|
+
recommended: 'Legacy reasoning tasks',
|
|
307
336
|
},
|
|
308
337
|
'o1-mini': {
|
|
309
|
-
description: 'Efficient reasoning',
|
|
310
|
-
recommended: '
|
|
338
|
+
description: 'Efficient reasoning (legacy)',
|
|
339
|
+
recommended: 'Legacy reasoning tasks',
|
|
311
340
|
},
|
|
312
341
|
};
|
package/src/types/workflow.ts
CHANGED
|
@@ -254,7 +254,7 @@ export const ProjectStateSchema = z.object({
|
|
|
254
254
|
name: z.string(),
|
|
255
255
|
idea: z.string(),
|
|
256
256
|
language: OutputLanguageSchema,
|
|
257
|
-
openaiModel: z.
|
|
257
|
+
openaiModel: z.string().min(1),
|
|
258
258
|
phase: WorkflowPhaseSchema,
|
|
259
259
|
status: ProjectStatusSchema,
|
|
260
260
|
specification: z.string().optional(),
|
package/src/upgrade/handlers.ts
CHANGED
|
@@ -349,7 +349,7 @@ export async function upgradeFullstackToAll(
|
|
|
349
349
|
idea: 'Marketing website',
|
|
350
350
|
name: projectName,
|
|
351
351
|
language: 'all',
|
|
352
|
-
openaiModel: 'gpt-
|
|
352
|
+
openaiModel: 'gpt-4.1',
|
|
353
353
|
};
|
|
354
354
|
|
|
355
355
|
// Build content context from user docs, brand assets, and strategy
|
|
@@ -421,7 +421,7 @@ export async function upgradeSingleToFullstack(
|
|
|
421
421
|
if (!(await pathExists(frontendDir))) {
|
|
422
422
|
const spec: ProjectSpec = {
|
|
423
423
|
idea: 'Frontend application', name: projectName,
|
|
424
|
-
language: 'fullstack', openaiModel: 'gpt-
|
|
424
|
+
language: 'fullstack', openaiModel: 'gpt-4.1',
|
|
425
425
|
};
|
|
426
426
|
const result = await generateTypeScriptProject(spec, path.join(projectDir, 'apps'), {
|
|
427
427
|
baseDir: frontendDir,
|
|
@@ -439,7 +439,7 @@ export async function upgradeSingleToFullstack(
|
|
|
439
439
|
if (!(await pathExists(backendDir))) {
|
|
440
440
|
const spec: ProjectSpec = {
|
|
441
441
|
idea: 'Backend API', name: projectName,
|
|
442
|
-
language: 'fullstack', openaiModel: 'gpt-
|
|
442
|
+
language: 'fullstack', openaiModel: 'gpt-4.1',
|
|
443
443
|
};
|
|
444
444
|
const result = await generatePythonProject(spec, path.join(projectDir, 'apps'), {
|
|
445
445
|
baseDir: backendDir,
|
|
@@ -508,7 +508,7 @@ export async function upgradeWebsiteToAll(
|
|
|
508
508
|
if (!(await pathExists(frontendDir))) {
|
|
509
509
|
const spec: ProjectSpec = {
|
|
510
510
|
idea: 'Frontend application', name: projectName,
|
|
511
|
-
language: 'all', openaiModel: 'gpt-
|
|
511
|
+
language: 'all', openaiModel: 'gpt-4.1',
|
|
512
512
|
};
|
|
513
513
|
const result = await generateTypeScriptProject(spec, path.join(projectDir, 'apps'), {
|
|
514
514
|
baseDir: frontendDir,
|
|
@@ -520,7 +520,7 @@ export async function upgradeWebsiteToAll(
|
|
|
520
520
|
if (!(await pathExists(backendDir))) {
|
|
521
521
|
const spec: ProjectSpec = {
|
|
522
522
|
idea: 'Backend API', name: projectName,
|
|
523
|
-
language: 'all', openaiModel: 'gpt-
|
|
523
|
+
language: 'all', openaiModel: 'gpt-4.1',
|
|
524
524
|
};
|
|
525
525
|
const result = await generatePythonProject(spec, path.join(projectDir, 'apps'), {
|
|
526
526
|
baseDir: backendDir,
|
package/src/workflow/index.ts
CHANGED
|
@@ -104,21 +104,24 @@ export async function runWorkflow(
|
|
|
104
104
|
const useLegacy = process.env.POPEYE_LEGACY_WORKFLOW === '1' || process.env.POPEYE_LEGACY_WORKFLOW === 'true';
|
|
105
105
|
if (!useLegacy) {
|
|
106
106
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
consensusConfig,
|
|
113
|
-
onPhaseStart: (phase) => onProgress?.('pipeline', `Starting phase: ${phase}`),
|
|
114
|
-
onProgress: (msg) => onProgress?.('pipeline', msg),
|
|
115
|
-
});
|
|
116
|
-
return {
|
|
117
|
-
success: result.success,
|
|
118
|
-
state: await loadProject(projectDir).catch(() => ({} as ProjectState)),
|
|
119
|
-
error: result.error,
|
|
120
|
-
};
|
|
107
|
+
// Bootstrap state if it doesn't exist yet (new projects)
|
|
108
|
+
let state = await loadProject(projectDir).catch(() => null);
|
|
109
|
+
if (!state) {
|
|
110
|
+
const { createProject } = await import('../state/index.js');
|
|
111
|
+
state = await createProject(spec, projectDir);
|
|
121
112
|
}
|
|
113
|
+
const result = await runPipeline({
|
|
114
|
+
projectDir,
|
|
115
|
+
state,
|
|
116
|
+
consensusConfig,
|
|
117
|
+
onPhaseStart: (phase) => onProgress?.('pipeline', `Starting phase: ${phase}`),
|
|
118
|
+
onProgress: (msg) => onProgress?.('pipeline', msg),
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
success: result.success,
|
|
122
|
+
state: await loadProject(projectDir).catch(() => ({} as ProjectState)),
|
|
123
|
+
error: result.error,
|
|
124
|
+
};
|
|
122
125
|
} catch {
|
|
123
126
|
// Fall through to legacy workflow on pipeline error
|
|
124
127
|
onProgress?.('workflow', 'Pipeline mode failed, falling back to legacy workflow...');
|
|
@@ -241,6 +244,7 @@ export async function resumeWorkflow(
|
|
|
241
244
|
projectDir,
|
|
242
245
|
state,
|
|
243
246
|
consensusConfig,
|
|
247
|
+
additionalContext,
|
|
244
248
|
onPhaseStart: (phase) => onProgress?.('pipeline', `Resuming phase: ${phase}`),
|
|
245
249
|
onProgress: (msg) => onProgress?.('pipeline', msg),
|
|
246
250
|
});
|
|
@@ -142,7 +142,7 @@ Respond with ONLY valid JSON, no markdown code fences or explanation.`;
|
|
|
142
142
|
onProgress?.('Generating website strategy via AI...');
|
|
143
143
|
|
|
144
144
|
const completion = await client.chat.completions.create({
|
|
145
|
-
model: 'gpt-
|
|
145
|
+
model: 'gpt-4.1',
|
|
146
146
|
messages: [{ role: 'user', content: prompt }],
|
|
147
147
|
temperature: 0.4,
|
|
148
148
|
max_tokens: 4096,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, it, expect } from 'vitest';
|
|
7
7
|
import { OpenAIModelSchema, KNOWN_OPENAI_MODELS } from '../../src/types/project.js';
|
|
8
|
-
import { GeminiModelSchema, GrokModelSchema, KNOWN_GEMINI_MODELS } from '../../src/types/consensus.js';
|
|
8
|
+
import { GeminiModelSchema, GrokModelSchema, KNOWN_GEMINI_MODELS, KNOWN_GROK_MODELS } from '../../src/types/consensus.js';
|
|
9
9
|
|
|
10
10
|
describe('OpenAI model validation', () => {
|
|
11
11
|
it('should accept known OpenAI models', () => {
|
|
@@ -17,7 +17,7 @@ describe('OpenAI model validation', () => {
|
|
|
17
17
|
it('should accept unknown/new OpenAI models (flexible)', () => {
|
|
18
18
|
expect(OpenAIModelSchema.safeParse('gpt-5').success).toBe(true);
|
|
19
19
|
expect(OpenAIModelSchema.safeParse('gpt-5.2-turbo').success).toBe(true);
|
|
20
|
-
expect(OpenAIModelSchema.safeParse('
|
|
20
|
+
expect(OpenAIModelSchema.safeParse('some-future-model').success).toBe(true);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it('should reject empty string', () => {
|
|
@@ -33,8 +33,8 @@ describe('Gemini model validation', () => {
|
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
it('should accept unknown/new Gemini models (flexible)', () => {
|
|
36
|
-
expect(GeminiModelSchema.safeParse('gemini-2.5-pro').success).toBe(true);
|
|
37
36
|
expect(GeminiModelSchema.safeParse('gemini-3.0-ultra').success).toBe(true);
|
|
37
|
+
expect(GeminiModelSchema.safeParse('gemini-4.0-flash').success).toBe(true);
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
it('should reject empty string', () => {
|
|
@@ -44,9 +44,9 @@ describe('Gemini model validation', () => {
|
|
|
44
44
|
|
|
45
45
|
describe('Grok model validation', () => {
|
|
46
46
|
it('should accept any non-empty string as Grok model', () => {
|
|
47
|
+
expect(GrokModelSchema.safeParse('grok-4-0709').success).toBe(true);
|
|
47
48
|
expect(GrokModelSchema.safeParse('grok-3').success).toBe(true);
|
|
48
49
|
expect(GrokModelSchema.safeParse('grok-3-mini').success).toBe(true);
|
|
49
|
-
expect(GrokModelSchema.safeParse('grok-2').success).toBe(true);
|
|
50
50
|
expect(GrokModelSchema.safeParse('some-future-model').success).toBe(true);
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -62,15 +62,25 @@ describe('Grok model validation', () => {
|
|
|
62
62
|
|
|
63
63
|
describe('known models lists', () => {
|
|
64
64
|
it('should have known OpenAI models', () => {
|
|
65
|
+
expect(KNOWN_OPENAI_MODELS).toContain('gpt-4.1');
|
|
65
66
|
expect(KNOWN_OPENAI_MODELS).toContain('gpt-4o');
|
|
66
|
-
expect(KNOWN_OPENAI_MODELS).toContain('
|
|
67
|
-
expect(KNOWN_OPENAI_MODELS
|
|
67
|
+
expect(KNOWN_OPENAI_MODELS).toContain('o3');
|
|
68
|
+
expect(KNOWN_OPENAI_MODELS).toContain('o4-mini');
|
|
69
|
+
expect(KNOWN_OPENAI_MODELS.length).toBeGreaterThanOrEqual(8);
|
|
68
70
|
});
|
|
69
71
|
|
|
70
72
|
it('should have known Gemini models', () => {
|
|
73
|
+
expect(KNOWN_GEMINI_MODELS).toContain('gemini-2.5-flash');
|
|
74
|
+
expect(KNOWN_GEMINI_MODELS).toContain('gemini-2.5-pro');
|
|
71
75
|
expect(KNOWN_GEMINI_MODELS).toContain('gemini-2.0-flash');
|
|
72
|
-
expect(KNOWN_GEMINI_MODELS).
|
|
73
|
-
|
|
76
|
+
expect(KNOWN_GEMINI_MODELS.length).toBeGreaterThanOrEqual(5);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should have known Grok models', () => {
|
|
80
|
+
expect(KNOWN_GROK_MODELS).toContain('grok-4-0709');
|
|
81
|
+
expect(KNOWN_GROK_MODELS).toContain('grok-3');
|
|
82
|
+
expect(KNOWN_GROK_MODELS).toContain('grok-3-mini');
|
|
83
|
+
expect(KNOWN_GROK_MODELS.length).toBeGreaterThanOrEqual(4);
|
|
74
84
|
});
|
|
75
85
|
});
|
|
76
86
|
|
|
@@ -84,7 +94,7 @@ describe('backward compatibility', () => {
|
|
|
84
94
|
});
|
|
85
95
|
|
|
86
96
|
it('should not auto-detect non-OpenAI models as known OpenAI', () => {
|
|
87
|
-
const nonOpenAI = ['gemini-2.
|
|
97
|
+
const nonOpenAI = ['gemini-2.5-flash', 'grok-3', 'grok-4-0709'];
|
|
88
98
|
for (const model of nonOpenAI) {
|
|
89
99
|
const isKnown = (KNOWN_OPENAI_MODELS as readonly string[]).includes(model);
|
|
90
100
|
expect(isKnown).toBe(false);
|
|
@@ -23,7 +23,7 @@ describe('DEFAULT_CONFIG', () => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('should have valid API defaults', () => {
|
|
26
|
-
expect(DEFAULT_CONFIG.apis.openai.model).toBe('gpt-
|
|
26
|
+
expect(DEFAULT_CONFIG.apis.openai.model).toBe('gpt-4.1');
|
|
27
27
|
expect(DEFAULT_CONFIG.apis.openai.temperature).toBe(0.3);
|
|
28
28
|
expect(DEFAULT_CONFIG.apis.openai.max_tokens).toBe(4096);
|
|
29
29
|
});
|
|
@@ -122,11 +122,11 @@ describe('ConfigSchema', () => {
|
|
|
122
122
|
expect(result.success).toBe(false);
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
it('should reject
|
|
125
|
+
it('should reject empty model string', () => {
|
|
126
126
|
const config = {
|
|
127
127
|
apis: {
|
|
128
128
|
openai: {
|
|
129
|
-
model: '
|
|
129
|
+
model: '',
|
|
130
130
|
},
|
|
131
131
|
},
|
|
132
132
|
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fix C tests — readPopeyeMdConfig shared config reader.
|
|
3
|
+
* Verifies popeye.md parsing for CLI commands.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import { promises as fs } from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import { readPopeyeMdConfig } from '../../src/config/popeye-md.js';
|
|
11
|
+
|
|
12
|
+
describe('Fix C: readPopeyeMdConfig', () => {
|
|
13
|
+
let tmpDir: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'popeye-md-test-'));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return null when popeye.md does not exist', async () => {
|
|
24
|
+
const config = await readPopeyeMdConfig(tmpDir);
|
|
25
|
+
expect(config).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should parse basic config with reviewer and language', async () => {
|
|
29
|
+
await fs.writeFile(
|
|
30
|
+
path.join(tmpDir, 'popeye.md'),
|
|
31
|
+
[
|
|
32
|
+
'---',
|
|
33
|
+
'language: python',
|
|
34
|
+
'reviewer: openai',
|
|
35
|
+
'arbitrator: gemini',
|
|
36
|
+
'---',
|
|
37
|
+
'',
|
|
38
|
+
'# Project Config',
|
|
39
|
+
].join('\n'),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const config = await readPopeyeMdConfig(tmpDir);
|
|
43
|
+
expect(config).not.toBeNull();
|
|
44
|
+
expect(config!.language).toBe('python');
|
|
45
|
+
expect(config!.reviewer).toBe('openai');
|
|
46
|
+
expect(config!.arbitrator).toBe('gemini');
|
|
47
|
+
expect(config!.enableArbitration).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should parse model fields from popeye.md', async () => {
|
|
51
|
+
await fs.writeFile(
|
|
52
|
+
path.join(tmpDir, 'popeye.md'),
|
|
53
|
+
[
|
|
54
|
+
'---',
|
|
55
|
+
'language: typescript',
|
|
56
|
+
'reviewer: gemini',
|
|
57
|
+
'arbitrator: grok',
|
|
58
|
+
'openaiModel: gpt-4o-mini',
|
|
59
|
+
'geminiModel: gemini-2.0-flash',
|
|
60
|
+
'grokModel: grok-3',
|
|
61
|
+
'---',
|
|
62
|
+
].join('\n'),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const config = await readPopeyeMdConfig(tmpDir);
|
|
66
|
+
expect(config).not.toBeNull();
|
|
67
|
+
expect(config!.openaiModel).toBe('gpt-4o-mini');
|
|
68
|
+
expect(config!.geminiModel).toBe('gemini-2.0-flash');
|
|
69
|
+
expect(config!.grokModel).toBe('grok-3');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle arbitrator: off', async () => {
|
|
73
|
+
await fs.writeFile(
|
|
74
|
+
path.join(tmpDir, 'popeye.md'),
|
|
75
|
+
[
|
|
76
|
+
'---',
|
|
77
|
+
'language: fullstack',
|
|
78
|
+
'reviewer: openai',
|
|
79
|
+
'arbitrator: off',
|
|
80
|
+
'---',
|
|
81
|
+
].join('\n'),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const config = await readPopeyeMdConfig(tmpDir);
|
|
85
|
+
expect(config).not.toBeNull();
|
|
86
|
+
expect(config!.enableArbitration).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should return null when frontmatter is missing', async () => {
|
|
90
|
+
await fs.writeFile(
|
|
91
|
+
path.join(tmpDir, 'popeye.md'),
|
|
92
|
+
'# Just a markdown file\n\nNo frontmatter here.',
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const config = await readPopeyeMdConfig(tmpDir);
|
|
96
|
+
expect(config).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should return null when essential fields are missing', async () => {
|
|
100
|
+
await fs.writeFile(
|
|
101
|
+
path.join(tmpDir, 'popeye.md'),
|
|
102
|
+
[
|
|
103
|
+
'---',
|
|
104
|
+
'projectName: my-app',
|
|
105
|
+
'created: 2024-01-01',
|
|
106
|
+
'---',
|
|
107
|
+
].join('\n'),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const config = await readPopeyeMdConfig(tmpDir);
|
|
111
|
+
// Missing language and reviewer -> null
|
|
112
|
+
expect(config).toBeNull();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should extract notes section', async () => {
|
|
116
|
+
await fs.writeFile(
|
|
117
|
+
path.join(tmpDir, 'popeye.md'),
|
|
118
|
+
[
|
|
119
|
+
'---',
|
|
120
|
+
'language: website',
|
|
121
|
+
'reviewer: openai',
|
|
122
|
+
'---',
|
|
123
|
+
'',
|
|
124
|
+
'## Notes',
|
|
125
|
+
'This project uses Tailwind CSS.',
|
|
126
|
+
'Deploy to Vercel.',
|
|
127
|
+
].join('\n'),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const config = await readPopeyeMdConfig(tmpDir);
|
|
131
|
+
expect(config).not.toBeNull();
|
|
132
|
+
expect(config!.notes).toContain('Tailwind CSS');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should reject invalid language values', async () => {
|
|
136
|
+
await fs.writeFile(
|
|
137
|
+
path.join(tmpDir, 'popeye.md'),
|
|
138
|
+
[
|
|
139
|
+
'---',
|
|
140
|
+
'language: rust',
|
|
141
|
+
'reviewer: openai',
|
|
142
|
+
'---',
|
|
143
|
+
].join('\n'),
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const config = await readPopeyeMdConfig(tmpDir);
|
|
147
|
+
// Invalid language means essential field is missing
|
|
148
|
+
expect(config).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should return model fields as undefined when not specified', async () => {
|
|
152
|
+
await fs.writeFile(
|
|
153
|
+
path.join(tmpDir, 'popeye.md'),
|
|
154
|
+
[
|
|
155
|
+
'---',
|
|
156
|
+
'language: python',
|
|
157
|
+
'reviewer: openai',
|
|
158
|
+
'---',
|
|
159
|
+
].join('\n'),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const config = await readPopeyeMdConfig(tmpDir);
|
|
163
|
+
expect(config).not.toBeNull();
|
|
164
|
+
expect(config!.openaiModel).toBeUndefined();
|
|
165
|
+
expect(config!.geminiModel).toBeUndefined();
|
|
166
|
+
expect(config!.grokModel).toBeUndefined();
|
|
167
|
+
});
|
|
168
|
+
});
|