mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2253d05618
Remove the nested v3/ subdirectory by moving all active source code, tests, configs, and assets directly to the project root. Archive v2/ (dead code, 847MB) to v2-archive.tar.gz. Key changes: - Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/ - Move v3/tsconfig.json, v3/vitest.config.ts to root - Merge v3/package.json into root package.json (ESM, scripts, deps, exports) - Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/ - Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes - Fix all init installer path resolution (3-level-up → 2-level-up) - Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe" - Update settings.json ADR/DDD directories to docs/implementation/ - Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks) - Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces - Update 15 guidance shards: v3/src/ → src/ path references - Fix sync interfaces: remove v3/ and v2/ path references - Fix test path calculations (process.cwd() + '..' no longer needed) - Fix flaky perf test threshold (500ms → 1000ms for CI environments) - Fix complexity-analyzer test to match ADR-051 keyword-based eligibility Build passes, 17,901 tests pass, CLI and MCP bundles verified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
910 lines
30 KiB
TypeScript
910 lines
30 KiB
TypeScript
/**
|
|
* Init Wizard and Orchestrator Tests
|
|
* ADR-025: Enhanced Init with Self-Configuration
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import {
|
|
InitOrchestrator,
|
|
createInitOrchestrator,
|
|
quickInit,
|
|
formatInitResult,
|
|
} from '../../../src/init/init-wizard.js';
|
|
import type { InitResult, PretrainedLibrary } from '../../../src/init/types.js';
|
|
|
|
// Mock fs module for project analyzer and init wizard
|
|
vi.mock('fs', async () => {
|
|
const actual = await vi.importActual('fs');
|
|
return {
|
|
...actual,
|
|
existsSync: vi.fn().mockReturnValue(false),
|
|
readFileSync: vi.fn().mockReturnValue('{}'),
|
|
statSync: vi.fn().mockReturnValue({ isDirectory: () => false, isFile: () => true }),
|
|
readdirSync: vi.fn().mockReturnValue([]),
|
|
mkdirSync: vi.fn(),
|
|
writeFileSync: vi.fn(),
|
|
};
|
|
});
|
|
|
|
// Mock better-sqlite3 for persistence database initialization
|
|
// Now uses ESM dynamic import in production code
|
|
vi.mock('better-sqlite3', () => {
|
|
const mockStatement = {
|
|
get: vi.fn().mockReturnValue({ name: 'kv_store' }),
|
|
run: vi.fn(),
|
|
all: vi.fn().mockReturnValue([]),
|
|
bind: vi.fn().mockReturnThis(),
|
|
};
|
|
const mockDb = {
|
|
pragma: vi.fn(),
|
|
exec: vi.fn(),
|
|
prepare: vi.fn().mockReturnValue(mockStatement),
|
|
close: vi.fn(),
|
|
transaction: vi.fn((fn: () => void) => fn),
|
|
};
|
|
// Use a proper function (not arrow) so it can be used as a constructor with `new`
|
|
function MockDatabase() {
|
|
return mockDb;
|
|
}
|
|
// ESM default export format
|
|
return { default: MockDatabase };
|
|
});
|
|
|
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
|
|
const mockExistsSync = existsSync as ReturnType<typeof vi.fn>;
|
|
const mockReadFileSync = readFileSync as ReturnType<typeof vi.fn>;
|
|
const mockMkdirSync = mkdirSync as ReturnType<typeof vi.fn>;
|
|
const mockWriteFileSync = writeFileSync as ReturnType<typeof vi.fn>;
|
|
|
|
describe('InitOrchestrator', () => {
|
|
const testProjectRoot = '/test/project';
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockExistsSync.mockReturnValue(false);
|
|
mockReadFileSync.mockImplementation((path: string) => {
|
|
if (path.includes('package.json')) {
|
|
return JSON.stringify({ name: 'test-project' });
|
|
}
|
|
return '';
|
|
});
|
|
// Reset write mocks - they should succeed by default
|
|
mockMkdirSync.mockReturnValue(undefined);
|
|
mockWriteFileSync.mockReturnValue(undefined);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('createInitOrchestrator', () => {
|
|
it('should create an init orchestrator', () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
});
|
|
expect(orchestrator).toBeInstanceOf(InitOrchestrator);
|
|
});
|
|
|
|
it('should create orchestrator with auto mode', () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
expect(orchestrator).toBeInstanceOf(InitOrchestrator);
|
|
});
|
|
|
|
it('should create orchestrator with minimal config', () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
minimal: true,
|
|
});
|
|
expect(orchestrator).toBeInstanceOf(InitOrchestrator);
|
|
});
|
|
});
|
|
|
|
describe('getWizardSteps', () => {
|
|
it('should return wizard steps', () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
});
|
|
const steps = orchestrator.getWizardSteps();
|
|
|
|
expect(Array.isArray(steps)).toBe(true);
|
|
expect(steps.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should have welcome step first', () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
});
|
|
const steps = orchestrator.getWizardSteps();
|
|
|
|
expect(steps[0].id).toBe('welcome');
|
|
expect(steps[0].type).toBe('info');
|
|
});
|
|
|
|
it('should include project-type step', () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
});
|
|
const steps = orchestrator.getWizardSteps();
|
|
|
|
const projectTypeStep = steps.find((s) => s.id === 'project-type');
|
|
expect(projectTypeStep).toBeDefined();
|
|
expect(projectTypeStep?.type).toBe('choice');
|
|
expect(projectTypeStep?.options?.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should include learning-mode step', () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
});
|
|
const steps = orchestrator.getWizardSteps();
|
|
|
|
const learningStep = steps.find((s) => s.id === 'learning-mode');
|
|
expect(learningStep).toBeDefined();
|
|
expect(learningStep?.type).toBe('choice');
|
|
});
|
|
|
|
it('should include confirm steps', () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
});
|
|
const steps = orchestrator.getWizardSteps();
|
|
|
|
const confirmSteps = steps.filter((s) => s.type === 'confirm');
|
|
expect(confirmSteps.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('initialize', () => {
|
|
it('should complete initialization in auto mode', async () => {
|
|
mockExistsSync.mockImplementation((path: string) => {
|
|
return path.includes('package.json');
|
|
});
|
|
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.config).toBeDefined();
|
|
expect(result.steps.length).toBeGreaterThan(0);
|
|
expect(result.totalDurationMs).toBeGreaterThanOrEqual(0);
|
|
expect(result.timestamp).toBeInstanceOf(Date);
|
|
});
|
|
|
|
it('should record all step results', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
expect(result.steps.length).toBeGreaterThan(0);
|
|
for (const step of result.steps) {
|
|
expect(step.step).toBeDefined();
|
|
expect(step.status).toMatch(/success|error/);
|
|
expect(step.message).toBeDefined();
|
|
expect(step.durationMs).toBeGreaterThanOrEqual(0);
|
|
}
|
|
});
|
|
|
|
it('should populate summary', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
expect(result.summary.projectAnalyzed).toBe(true);
|
|
expect(result.summary.configGenerated).toBe(true);
|
|
expect(typeof result.summary.patternsLoaded).toBe('number');
|
|
expect(typeof result.summary.skillsInstalled).toBe('number');
|
|
expect(typeof result.summary.agentsInstalled).toBe('number');
|
|
expect(typeof result.summary.hooksConfigured).toBe('boolean');
|
|
expect(typeof result.summary.mcpConfigured).toBe('boolean');
|
|
expect(typeof result.summary.claudeMdGenerated).toBe('boolean');
|
|
expect(typeof result.summary.workersStarted).toBe('number');
|
|
});
|
|
|
|
it('should apply wizard answers', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
wizardAnswers: {
|
|
'project-type': 'monorepo',
|
|
'learning-mode': 'basic',
|
|
'load-patterns': false,
|
|
hooks: false,
|
|
workers: false,
|
|
},
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.config.project.type).toBe('monorepo');
|
|
expect(result.config.learning.embeddingModel).toBe('hash');
|
|
expect(result.config.learning.pretrainedPatterns).toBe(false);
|
|
expect(result.config.hooks.claudeCode).toBe(false);
|
|
expect(result.config.workers.daemonAutoStart).toBe(false);
|
|
});
|
|
|
|
it('should handle disabled learning mode', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
wizardAnswers: {
|
|
'learning-mode': 'disabled',
|
|
},
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.config.learning.enabled).toBe(false);
|
|
});
|
|
|
|
it('should load pretrained patterns when provided', async () => {
|
|
// Create realistic pretrained library with actual patterns grouped by domain
|
|
const testPatterns = Array.from({ length: 50 }, (_, i) => ({
|
|
id: `pattern-${i}`,
|
|
domain: i < 25 ? 'test-generation' : 'test-execution',
|
|
type: 'unit',
|
|
content: 'describe("test", () => { it("should work", () => {}) })',
|
|
metadata: {
|
|
language: 'typescript',
|
|
framework: 'vitest',
|
|
confidence: 0.9,
|
|
usageCount: 10,
|
|
successRate: 0.95,
|
|
},
|
|
}));
|
|
|
|
const pretrainedLibrary: PretrainedLibrary = {
|
|
version: '1.0.0',
|
|
exportedFrom: 'test-suite',
|
|
exportDate: new Date().toISOString(),
|
|
patterns: testPatterns,
|
|
statistics: {
|
|
totalPatterns: 50,
|
|
byDomain: {
|
|
'test-generation': 25,
|
|
'test-execution': 25,
|
|
},
|
|
byLanguage: { typescript: 50 },
|
|
averageConfidence: 0.9,
|
|
},
|
|
};
|
|
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
pretrainedLibrary,
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.summary.patternsLoaded).toBe(50);
|
|
});
|
|
|
|
it('should skip patterns when requested', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
skipPatterns: true,
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.summary.patternsLoaded).toBe(0);
|
|
});
|
|
|
|
it('should start workers based on config', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
expect(result.success).toBe(true);
|
|
// Workers started should match enabled workers count
|
|
expect(result.summary.workersStarted).toBeGreaterThanOrEqual(0);
|
|
});
|
|
});
|
|
|
|
describe('quickInit', () => {
|
|
it('should perform quick initialization', async () => {
|
|
mockExistsSync.mockImplementation((path: string) => {
|
|
return path.includes('package.json');
|
|
});
|
|
|
|
const result = await quickInit(testProjectRoot);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.config).toBeDefined();
|
|
expect(result.steps.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should use auto mode', async () => {
|
|
const result = await quickInit(testProjectRoot);
|
|
|
|
expect(result.success).toBe(true);
|
|
// Auto mode generates config based on analysis
|
|
expect(result.config.project.name).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('formatInitResult', () => {
|
|
it('should format successful result', () => {
|
|
const result: InitResult = {
|
|
success: true,
|
|
config: {
|
|
version: '3.0.0',
|
|
project: {
|
|
name: 'test-project',
|
|
root: '/test/project',
|
|
type: 'single',
|
|
},
|
|
learning: {
|
|
enabled: true,
|
|
embeddingModel: 'transformer',
|
|
hnswConfig: { M: 16, efConstruction: 200, efSearch: 100 },
|
|
qualityThreshold: 0.7,
|
|
promotionThreshold: 3,
|
|
pretrainedPatterns: true,
|
|
},
|
|
routing: {
|
|
mode: 'hybrid',
|
|
confidenceThreshold: 0.7,
|
|
fallbackEnabled: true,
|
|
},
|
|
workers: {
|
|
enabled: ['pattern-consolidator'],
|
|
intervals: {},
|
|
maxConcurrent: 4,
|
|
daemonAutoStart: true,
|
|
},
|
|
hooks: {
|
|
claudeCode: true,
|
|
preCommit: false,
|
|
ciIntegration: false,
|
|
},
|
|
autoTuning: {
|
|
enabled: true,
|
|
parameters: [],
|
|
evaluationPeriodMs: 60000,
|
|
},
|
|
domains: {
|
|
enabled: ['test-generation'],
|
|
disabled: [],
|
|
},
|
|
agents: {
|
|
maxConcurrent: 8,
|
|
defaultTimeout: 60000,
|
|
},
|
|
},
|
|
steps: [
|
|
{ step: 'Project Analysis', status: 'success', message: 'Done', durationMs: 50 },
|
|
{ step: 'Configuration Generation', status: 'success', message: 'Done', durationMs: 10 },
|
|
],
|
|
summary: {
|
|
projectAnalyzed: true,
|
|
configGenerated: true,
|
|
patternsLoaded: 100,
|
|
skillsInstalled: 64,
|
|
agentsInstalled: 59,
|
|
hooksConfigured: true,
|
|
mcpConfigured: true,
|
|
claudeMdGenerated: true,
|
|
workersStarted: 2,
|
|
},
|
|
totalDurationMs: 200,
|
|
timestamp: new Date(),
|
|
};
|
|
|
|
const formatted = formatInitResult(result);
|
|
|
|
expect(formatted).toContain('AQE v3 Initialization');
|
|
expect(formatted).toContain('test-project');
|
|
expect(formatted).toContain('single');
|
|
expect(formatted).toContain('100'); // patterns loaded
|
|
expect(formatted).toContain('59'); // agents installed
|
|
expect(formatted).toContain('MCP Server'); // MCP configured
|
|
expect(formatted).toContain('initialized');
|
|
});
|
|
|
|
it('should format failed result', () => {
|
|
const result: InitResult = {
|
|
success: false,
|
|
config: {
|
|
version: '3.0.0',
|
|
project: {
|
|
name: 'unknown',
|
|
root: '/test/project',
|
|
type: 'single',
|
|
},
|
|
learning: {
|
|
enabled: true,
|
|
embeddingModel: 'hash',
|
|
hnswConfig: { M: 16, efConstruction: 200, efSearch: 100 },
|
|
qualityThreshold: 0.7,
|
|
promotionThreshold: 3,
|
|
pretrainedPatterns: true,
|
|
},
|
|
routing: {
|
|
mode: 'rules',
|
|
confidenceThreshold: 0.7,
|
|
fallbackEnabled: true,
|
|
},
|
|
workers: {
|
|
enabled: [],
|
|
intervals: {},
|
|
maxConcurrent: 4,
|
|
daemonAutoStart: false,
|
|
},
|
|
hooks: {
|
|
claudeCode: false,
|
|
preCommit: false,
|
|
ciIntegration: false,
|
|
},
|
|
autoTuning: {
|
|
enabled: false,
|
|
parameters: [],
|
|
evaluationPeriodMs: 60000,
|
|
},
|
|
domains: {
|
|
enabled: [],
|
|
disabled: [],
|
|
},
|
|
agents: {
|
|
maxConcurrent: 5,
|
|
defaultTimeout: 60000,
|
|
},
|
|
},
|
|
steps: [
|
|
{ step: 'Project Analysis', status: 'error', message: 'Failed to read', durationMs: 10 },
|
|
],
|
|
summary: {
|
|
projectAnalyzed: false,
|
|
configGenerated: false,
|
|
patternsLoaded: 0,
|
|
skillsInstalled: 0,
|
|
agentsInstalled: 0,
|
|
hooksConfigured: false,
|
|
mcpConfigured: false,
|
|
claudeMdGenerated: false,
|
|
workersStarted: 0,
|
|
},
|
|
totalDurationMs: 50,
|
|
timestamp: new Date(),
|
|
};
|
|
|
|
const formatted = formatInitResult(result);
|
|
|
|
expect(formatted).toContain('AQE v3 Initialization');
|
|
expect(formatted).toContain('failed');
|
|
});
|
|
|
|
it('should include step timing', () => {
|
|
const result: InitResult = {
|
|
success: true,
|
|
config: {
|
|
version: '3.0.0',
|
|
project: { name: 'test', root: '/test', type: 'single' },
|
|
learning: {
|
|
enabled: true,
|
|
embeddingModel: 'hash',
|
|
hnswConfig: { M: 16, efConstruction: 200, efSearch: 100 },
|
|
qualityThreshold: 0.7,
|
|
promotionThreshold: 3,
|
|
pretrainedPatterns: true,
|
|
},
|
|
routing: { mode: 'rules', confidenceThreshold: 0.7, fallbackEnabled: true },
|
|
workers: { enabled: [], intervals: {}, maxConcurrent: 4, daemonAutoStart: false },
|
|
hooks: { claudeCode: false, preCommit: false, ciIntegration: false },
|
|
autoTuning: { enabled: false, parameters: [], evaluationPeriodMs: 60000 },
|
|
domains: { enabled: [], disabled: [] },
|
|
agents: { maxConcurrent: 5, defaultTimeout: 60000 },
|
|
},
|
|
steps: [
|
|
{ step: 'Step 1', status: 'success', message: 'Done', durationMs: 123 },
|
|
],
|
|
summary: {
|
|
projectAnalyzed: true,
|
|
configGenerated: true,
|
|
patternsLoaded: 0,
|
|
skillsInstalled: 0,
|
|
agentsInstalled: 0,
|
|
hooksConfigured: false,
|
|
mcpConfigured: false,
|
|
claudeMdGenerated: false,
|
|
workersStarted: 0,
|
|
},
|
|
totalDurationMs: 123,
|
|
timestamp: new Date(),
|
|
};
|
|
|
|
const formatted = formatInitResult(result);
|
|
|
|
expect(formatted).toContain('123ms');
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
it('should handle analysis errors gracefully', async () => {
|
|
mockExistsSync.mockImplementation(() => {
|
|
throw new Error('Permission denied');
|
|
});
|
|
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.steps.some((s) => s.status === 'error')).toBe(true);
|
|
});
|
|
|
|
it('should include error message in failed steps', async () => {
|
|
mockExistsSync.mockImplementation(() => {
|
|
throw new Error('Test error message');
|
|
});
|
|
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
expect(result.success).toBe(false);
|
|
const errorStep = result.steps.find((s) => s.status === 'error');
|
|
expect(errorStep?.message).toContain('Test error message');
|
|
});
|
|
});
|
|
|
|
describe('Real Implementation File Operations', () => {
|
|
it('should create .agentic-qe directory and config.yaml', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
await orchestrator.initialize();
|
|
|
|
// Verify mkdirSync was called for .agentic-qe
|
|
expect(mockMkdirSync).toHaveBeenCalled();
|
|
const mkdirCalls = mockMkdirSync.mock.calls;
|
|
const configDirCall = mkdirCalls.find((call) =>
|
|
(call[0] as string).includes('.agentic-qe')
|
|
);
|
|
expect(configDirCall).toBeDefined();
|
|
|
|
// Verify writeFileSync was called for config.yaml
|
|
expect(mockWriteFileSync).toHaveBeenCalled();
|
|
const writeCalls = mockWriteFileSync.mock.calls;
|
|
const configYamlCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('config.yaml')
|
|
);
|
|
expect(configYamlCall).toBeDefined();
|
|
expect(configYamlCall![1]).toContain('version: "3.0.0"');
|
|
expect(configYamlCall![1]).toContain('project:');
|
|
});
|
|
|
|
it('should create .claude/settings.json with hooks', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
await orchestrator.initialize();
|
|
|
|
// Verify writeFileSync was called for settings.json
|
|
const writeCalls = mockWriteFileSync.mock.calls;
|
|
const settingsCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('settings.json')
|
|
);
|
|
expect(settingsCall).toBeDefined();
|
|
|
|
// Parse and verify the settings content
|
|
const settingsContent = JSON.parse(settingsCall![1] as string);
|
|
expect(settingsContent.hooks).toBeDefined();
|
|
expect(settingsContent.hooks.PreToolUse).toBeDefined();
|
|
expect(settingsContent.hooks.PostToolUse).toBeDefined();
|
|
expect(settingsContent.hooks.SessionStart).toBeDefined();
|
|
expect(settingsContent.hooks.Stop).toBeDefined(); // Claude Code uses 'Stop' for session end
|
|
expect(settingsContent.aqe).toBeDefined();
|
|
expect(settingsContent.aqe.hooksConfigured).toBe(true);
|
|
});
|
|
|
|
it('should create learning system directories and config', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
await orchestrator.initialize();
|
|
|
|
// Verify data and hnsw directories created
|
|
const mkdirCalls = mockMkdirSync.mock.calls;
|
|
const dataDirCall = mkdirCalls.find((call) =>
|
|
(call[0] as string).includes('data')
|
|
);
|
|
expect(dataDirCall).toBeDefined();
|
|
|
|
const hnswDirCall = mkdirCalls.find((call) =>
|
|
(call[0] as string).includes('hnsw')
|
|
);
|
|
expect(hnswDirCall).toBeDefined();
|
|
|
|
// Verify learning-config.json written
|
|
const writeCalls = mockWriteFileSync.mock.calls;
|
|
const learningConfigCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('learning-config.json')
|
|
);
|
|
expect(learningConfigCall).toBeDefined();
|
|
|
|
const learningConfig = JSON.parse(learningConfigCall![1] as string);
|
|
expect(learningConfig.embeddingModel).toBeDefined();
|
|
expect(learningConfig.hnswConfig).toBeDefined();
|
|
expect(learningConfig.databasePath).toContain('memory.db');
|
|
});
|
|
|
|
it('should create worker registry and configs', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
await orchestrator.initialize();
|
|
|
|
// Verify workers directory created
|
|
const mkdirCalls = mockMkdirSync.mock.calls;
|
|
const workersDirCall = mkdirCalls.find((call) =>
|
|
(call[0] as string).includes('workers')
|
|
);
|
|
expect(workersDirCall).toBeDefined();
|
|
|
|
// Verify registry.json written
|
|
const writeCalls = mockWriteFileSync.mock.calls;
|
|
const registryCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('registry.json')
|
|
);
|
|
expect(registryCall).toBeDefined();
|
|
|
|
const registry = JSON.parse(registryCall![1] as string);
|
|
expect(registry.version).toBe('3.0.0');
|
|
expect(registry.workers).toBeDefined();
|
|
expect(registry.maxConcurrent).toBeDefined();
|
|
});
|
|
|
|
it('should write pretrained patterns to domain directories', async () => {
|
|
const testPatterns = [
|
|
{
|
|
id: 'p1',
|
|
domain: 'test-generation',
|
|
type: 'unit',
|
|
content: 'test pattern 1',
|
|
metadata: { confidence: 0.9, usageCount: 5, successRate: 0.95 },
|
|
},
|
|
{
|
|
id: 'p2',
|
|
domain: 'test-generation',
|
|
type: 'integration',
|
|
content: 'test pattern 2',
|
|
metadata: { confidence: 0.85, usageCount: 3, successRate: 0.9 },
|
|
},
|
|
];
|
|
|
|
const pretrainedLibrary: PretrainedLibrary = {
|
|
version: '1.0.0',
|
|
exportedFrom: 'test-suite',
|
|
exportDate: new Date().toISOString(),
|
|
patterns: testPatterns,
|
|
statistics: {
|
|
totalPatterns: 2,
|
|
byDomain: { 'test-generation': 2 },
|
|
byLanguage: {},
|
|
averageConfidence: 0.875,
|
|
},
|
|
};
|
|
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
pretrainedLibrary,
|
|
});
|
|
|
|
await orchestrator.initialize();
|
|
|
|
// Verify pattern directory created for the domain
|
|
const mkdirCalls = mockMkdirSync.mock.calls;
|
|
const patternDirCall = mkdirCalls.find((call) =>
|
|
(call[0] as string).includes('patterns/test-generation')
|
|
);
|
|
expect(patternDirCall).toBeDefined();
|
|
|
|
// Verify patterns.json written
|
|
const writeCalls = mockWriteFileSync.mock.calls;
|
|
const patternsCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('patterns.json')
|
|
);
|
|
expect(patternsCall).toBeDefined();
|
|
|
|
const patterns = JSON.parse(patternsCall![1] as string);
|
|
expect(patterns).toHaveLength(2);
|
|
expect(patterns[0].id).toBe('p1');
|
|
});
|
|
|
|
it('should not write hooks when claudeCode is disabled', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
wizardAnswers: {
|
|
hooks: false,
|
|
},
|
|
});
|
|
|
|
await orchestrator.initialize();
|
|
|
|
// Verify settings.json was NOT written
|
|
const writeCalls = mockWriteFileSync.mock.calls;
|
|
const settingsCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('settings.json')
|
|
);
|
|
expect(settingsCall).toBeUndefined();
|
|
});
|
|
|
|
it('should not write worker configs when daemonAutoStart is disabled', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
wizardAnswers: {
|
|
workers: false,
|
|
},
|
|
});
|
|
|
|
await orchestrator.initialize();
|
|
|
|
// Verify registry.json was NOT written
|
|
const writeCalls = mockWriteFileSync.mock.calls;
|
|
const registryCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('registry.json')
|
|
);
|
|
expect(registryCall).toBeUndefined();
|
|
});
|
|
|
|
it('should create CLAUDE.md with AQE v3 section', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
await orchestrator.initialize();
|
|
|
|
// Verify CLAUDE.md was written
|
|
const writeCalls = mockWriteFileSync.mock.calls;
|
|
const claudeMdCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('CLAUDE.md')
|
|
);
|
|
expect(claudeMdCall).toBeDefined();
|
|
|
|
// Verify content includes expected sections
|
|
const content = claudeMdCall![1] as string;
|
|
expect(content).toContain('Agentic QE v3');
|
|
expect(content).toContain('MCP Server');
|
|
expect(content).toContain('12 DDD Domains');
|
|
expect(content).toContain('V3 QE Agents');
|
|
expect(content).toContain('Data Storage');
|
|
expect(content).toContain('memory.db');
|
|
});
|
|
|
|
it('should append to existing CLAUDE.md with backup', async () => {
|
|
// Simulate existing CLAUDE.md
|
|
mockExistsSync.mockImplementation((path: string) => {
|
|
if (path.includes('CLAUDE.md') && !path.includes('.backup')) return true;
|
|
if (path.includes('package.json')) return true;
|
|
return false;
|
|
});
|
|
mockReadFileSync.mockImplementation((path: string) => {
|
|
if (path.includes('CLAUDE.md')) {
|
|
return '# Existing CLAUDE.md\n\nSome existing content here.';
|
|
}
|
|
if (path.includes('package.json')) {
|
|
return JSON.stringify({ name: 'test-project' });
|
|
}
|
|
return '';
|
|
});
|
|
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
await orchestrator.initialize();
|
|
|
|
const writeCalls = mockWriteFileSync.mock.calls;
|
|
|
|
// Verify backup was created
|
|
const backupCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('CLAUDE.md.backup')
|
|
);
|
|
expect(backupCall).toBeDefined();
|
|
expect(backupCall![1]).toBe('# Existing CLAUDE.md\n\nSome existing content here.');
|
|
|
|
// Verify CLAUDE.md was appended to
|
|
const claudeMdCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('CLAUDE.md') && !(call[0] as string).includes('.backup')
|
|
);
|
|
expect(claudeMdCall).toBeDefined();
|
|
|
|
const content = claudeMdCall![1] as string;
|
|
// Should contain existing content
|
|
expect(content).toContain('# Existing CLAUDE.md');
|
|
expect(content).toContain('Some existing content');
|
|
// Should contain AQE section
|
|
expect(content).toContain('Agentic QE v3');
|
|
});
|
|
|
|
it('should not duplicate AQE section in CLAUDE.md', async () => {
|
|
// Simulate existing CLAUDE.md that already has AQE section
|
|
mockExistsSync.mockImplementation((path: string) => {
|
|
if (path.includes('CLAUDE.md')) return true;
|
|
if (path.includes('package.json')) return true;
|
|
return false;
|
|
});
|
|
mockReadFileSync.mockImplementation((path: string) => {
|
|
if (path.includes('CLAUDE.md')) {
|
|
return '# Existing CLAUDE.md\n\n## Agentic QE v3\n\nAlready has section.';
|
|
}
|
|
if (path.includes('package.json')) {
|
|
return JSON.stringify({ name: 'test-project' });
|
|
}
|
|
return '';
|
|
});
|
|
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
const result = await orchestrator.initialize();
|
|
|
|
// Should still succeed - just won't duplicate
|
|
expect(result.summary.claudeMdGenerated).toBe(true);
|
|
});
|
|
|
|
it('should create .claude/mcp.json with AQE v3 server', async () => {
|
|
const orchestrator = createInitOrchestrator({
|
|
projectRoot: testProjectRoot,
|
|
autoMode: true,
|
|
});
|
|
|
|
await orchestrator.initialize();
|
|
|
|
// Verify mcp.json was written
|
|
const writeCalls = mockWriteFileSync.mock.calls;
|
|
const mcpCall = writeCalls.find((call) =>
|
|
(call[0] as string).includes('mcp.json')
|
|
);
|
|
expect(mcpCall).toBeDefined();
|
|
|
|
// Verify content
|
|
const mcpConfig = JSON.parse(mcpCall![1] as string);
|
|
expect(mcpConfig.mcpServers).toBeDefined();
|
|
expect(mcpConfig.mcpServers['agentic-qe']).toBeDefined();
|
|
expect(mcpConfig.mcpServers['agentic-qe'].command).toBe('npx');
|
|
expect(mcpConfig.mcpServers['agentic-qe'].args).toEqual(['@anthropics/agentic-qe', 'mcp', 'start']);
|
|
});
|
|
});
|
|
});
|