@oalacea/daemon 0.5.0 → 0.5.1

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +46 -38
  2. package/LICENSE +23 -23
  3. package/README.md +147 -141
  4. package/agents/deps-analyzer.js +366 -366
  5. package/agents/detector.js +570 -570
  6. package/agents/fix-engine.js +305 -305
  7. package/agents/lighthouse-scanner.js +405 -405
  8. package/agents/perf-analyzer.js +294 -294
  9. package/agents/perf-front-analyzer.js +229 -229
  10. package/agents/test-generator.js +387 -387
  11. package/agents/test-runner.js +318 -318
  12. package/bin/Dockerfile +75 -74
  13. package/bin/cli.js +449 -449
  14. package/lib/config.js +250 -250
  15. package/lib/docker.js +207 -207
  16. package/lib/reporter.js +297 -297
  17. package/package.json +34 -34
  18. package/prompts/DEPS_EFFICIENCY.md +558 -558
  19. package/prompts/E2E.md +491 -491
  20. package/prompts/EXECUTE.md +1060 -1060
  21. package/prompts/INTEGRATION_API.md +484 -484
  22. package/prompts/INTEGRATION_DB.md +425 -425
  23. package/prompts/PERF_API.md +433 -433
  24. package/prompts/PERF_DB.md +430 -430
  25. package/prompts/PERF_FRONT.md +357 -357
  26. package/prompts/REMEDIATION.md +482 -482
  27. package/prompts/UNIT.md +260 -260
  28. package/scripts/dev.js +106 -106
  29. package/templates/README.md +38 -38
  30. package/templates/k6/load-test.js +54 -54
  31. package/templates/playwright/e2e.spec.ts +61 -61
  32. package/templates/vitest/angular-component.test.ts +38 -38
  33. package/templates/vitest/api.test.ts +51 -51
  34. package/templates/vitest/component.test.ts +27 -27
  35. package/templates/vitest/hook.test.ts +36 -36
  36. package/templates/vitest/solid-component.test.ts +34 -34
  37. package/templates/vitest/svelte-component.test.ts +33 -33
  38. package/templates/vitest/vue-component.test.ts +39 -39
package/bin/cli.js CHANGED
@@ -1,449 +1,449 @@
1
- #!/usr/bin/env node
2
-
3
- const { execSync } = require('child_process');
4
- const readline = require('readline');
5
- const fs = require('fs');
6
- const path = require('path');
7
-
8
- // --- Config ---
9
- const CONFIG = {
10
- IMAGE: 'daemon-tools',
11
- CONTAINER: 'daemon-tools',
12
- PROMPT_SRC: path.join(__dirname, '..', 'prompts', 'EXECUTE.md'),
13
- // Output in current working directory
14
- PROMPT_DEST: path.join(process.cwd(), '.daemon', 'EXECUTE.md'),
15
- PROJECT_DIR: process.cwd(),
16
- DOCKERFILE: path.join(__dirname, 'Dockerfile')
17
- };
18
-
19
- // --- Colors ---
20
- const green = (s) => `\x1b[32m${s}\x1b[0m`;
21
- const red = (s) => `\x1b[31m${s}\x1b[0m`;
22
- const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
23
- const bold = (s) => `\x1b[1m${s}\x1b[0m`;
24
- const dim = (s) => `\x1b[2m${s}\x1b[0m`;
25
- const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
26
- const blue = (s) => `\x1b[34m${s}\x1b[0m`;
27
- const magenta = (s) => `\x1b[35m${s}\x1b[0m`;
28
-
29
- // --- Utilities ---
30
- function run(cmd, { silent = false, timeout = 60000 } = {}) {
31
- try {
32
- return execSync(cmd, { encoding: 'utf-8', stdio: 'pipe', timeout }).trim();
33
- } catch (error) {
34
- if (!silent && error.status !== null) {
35
- console.error(dim(` [debug] Command exited with code ${error.status}`));
36
- }
37
- return null;
38
- }
39
- }
40
-
41
- function fail(msg) {
42
- console.error(`\n ${red('✗')} ${msg}\n`);
43
- process.exit(1);
44
- }
45
-
46
- function ask(question) {
47
- return new Promise((resolve) => {
48
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
49
- rl.question(question, (answer) => {
50
- rl.close();
51
- resolve(answer.trim().toLowerCase());
52
- });
53
- });
54
- }
55
-
56
- // --- Project Markers ---
57
- const PROJECT_MARKERS = [
58
- 'package.json',
59
- 'requirements.txt',
60
- 'pyproject.toml',
61
- 'Pipfile',
62
- 'go.mod',
63
- 'pom.xml',
64
- 'build.gradle',
65
- 'Gemfile',
66
- 'composer.json',
67
- 'Cargo.toml'
68
- ];
69
-
70
- function hasProjectFiles() {
71
- const cwd = path.resolve(process.cwd());
72
- try {
73
- const realCwd = fs.realpathSync(cwd);
74
- if (realCwd !== cwd) {
75
- console.warn(yellow(' ⚠ Symbolic link detected in path, using real path'));
76
- }
77
- } catch {
78
- // realpathSync failed, continue with cwd
79
- }
80
- return PROJECT_MARKERS.some((f) => fs.existsSync(path.join(cwd, f)));
81
- }
82
-
83
- // --- Detection Import ---
84
- function runDetection(projectDir) {
85
- const detectorPath = path.join(__dirname, '..', 'agents', 'detector.js');
86
-
87
- if (!fs.existsSync(detectorPath)) {
88
- // Fallback if detector doesn't exist yet
89
- return {
90
- framework: 'Unknown',
91
- language: 'JavaScript/TypeScript',
92
- testRunner: 'Vitest',
93
- database: null,
94
- existingTests: 0,
95
- coverage: null,
96
- dependencies: [],
97
- target: 'http://localhost:3000'
98
- };
99
- }
100
-
101
- try {
102
- // Execute detector as a module
103
- const detector = require(detectorPath);
104
- return detector.analyze(projectDir);
105
- } catch (e) {
106
- console.log(dim(` [debug] Detection error: ${e.message}`));
107
- return {
108
- framework: 'Unknown',
109
- language: 'JavaScript/TypeScript',
110
- testRunner: 'Vitest',
111
- database: null,
112
- existingTests: 0,
113
- coverage: null,
114
- dependencies: [],
115
- target: 'http://localhost:3000'
116
- };
117
- }
118
- }
119
-
120
- // --- Prompt Generation ---
121
- function generatePrompt(context) {
122
- const promptSrc = CONFIG.PROMPT_SRC;
123
-
124
- if (!fs.existsSync(promptSrc)) {
125
- return generateFallbackPrompt(context);
126
- }
127
-
128
- const basePrompt = fs.readFileSync(promptSrc, 'utf-8');
129
-
130
- // Build context block
131
- const contextBlock = buildContextBlock(context);
132
-
133
- return contextBlock + '\n' + basePrompt;
134
- }
135
-
136
- function buildContextBlock(context) {
137
- const lines = [
138
- '> **DETECTED CONTEXT**',
139
- ];
140
-
141
- if (context.framework) {
142
- lines.push(`> Framework: ${context.framework}`);
143
- }
144
- if (context.language) {
145
- lines.push(`> Language: ${context.language}`);
146
- }
147
- if (context.testRunner) {
148
- lines.push(`> Test Runner: ${context.testRunner}`);
149
- }
150
- if (context.database) {
151
- lines.push(`> Database: ${context.database.type || 'detected'}`);
152
- lines.push(`> DB Connection: ${context.database.connection || 'DATABASE_URL'}`);
153
- lines.push(`> Test Strategy: Transaction rollback (do not modify real data)`);
154
- } else {
155
- lines.push(`> Database: none detected`);
156
- }
157
- lines.push(`> Existing Tests: ${context.existingTests || 0} found`);
158
- if (context.coverage) {
159
- lines.push(`> Current Coverage: ${context.coverage}`);
160
- }
161
- if (context.dependencies && context.dependencies.length > 0) {
162
- lines.push(`> Key Dependencies: ${context.dependencies.join(', ')}`);
163
- }
164
- lines.push(`> Target: ${context.target || 'http://localhost:3000'}`);
165
-
166
- lines.push('');
167
- lines.push('> **IMPORTANT**:');
168
- lines.push('> - Use this detected context. Do not re-detect.');
169
- lines.push('> - Always read source code before generating tests.');
170
- lines.push('> - Run tests to verify they work before declaring success.');
171
- if (context.database) {
172
- lines.push('> - Use transaction rollback for DB tests - never modify real data.');
173
- }
174
- lines.push('');
175
- lines.push('> **WORKFLOW**:');
176
- lines.push('> 1. Read ./.daemon/EXECUTE.md for full instructions');
177
- lines.push('> 2. Generate tests following the detected patterns');
178
- lines.push('> 3. Run tests via Docker container');
179
- lines.push('> 4. Fix failures iteratively until all pass');
180
- lines.push('> 5. Generate final report');
181
- lines.push('');
182
-
183
- return lines.join('\n');
184
- }
185
-
186
- function generateFallbackPrompt(context) {
187
- return `# Daemon — Automated Testing Process
188
-
189
- > **DETECTED CONTEXT**
190
- > Framework: ${context.framework || 'Unknown'}
191
- > Language: ${context.language || 'JavaScript/TypeScript'}
192
- > Test Runner: ${context.testRunner || 'Vitest'}
193
- > Database: ${context.database?.type || 'none'}
194
- > Target: ${context.target || 'http://localhost:3000'}
195
-
196
- ## Instructions
197
-
198
- This is the automated testing agent. Follow these steps:
199
-
200
- 1. **Read the project structure** - Understand the framework and patterns
201
- 2. **Generate unit tests** - For components, hooks, and utilities
202
- 3. **Generate integration tests** - For API routes and database operations
203
- 4. **Generate E2E tests** - For critical user flows
204
- 5. **Run performance tests** - API load testing and DB query analysis
205
- 6. **Analyze dependencies** - Check for efficiency patterns
206
-
207
- Always run tests to verify they work. When a test fails, analyze and fix before proceeding.
208
-
209
- ## Tool Execution
210
-
211
- All test tools run inside the Daemon Docker container:
212
-
213
- \`\`\`bash
214
- docker exec daemon-tools <command>
215
- \`\`\`
216
-
217
- ### Available Commands
218
-
219
- | Task | Command |
220
- |------|---------|
221
- | Unit tests | \`docker exec daemon-tools npm test\` |
222
- | E2E tests | \`docker exec daemon-tools npx playwright test\` |
223
- | Performance | \`docker exec daemon-tools k6 run tests/performance/load.js\` |
224
-
225
- ## Phase 1 - Unit Tests
226
-
227
- Generate tests for:
228
- - Components (render, props, events, edge cases)
229
- - Hooks (state updates, return values, cleanup)
230
- - Utils (pure functions, validators)
231
-
232
- Template:
233
- \`\`\`typescript
234
- import { render, screen } from '@testing-library/react';
235
- import { describe, it, expect, vi } from 'vitest';
236
- import { Component } from '@/components/Component';
237
-
238
- describe('Component', () => {
239
- it('should render', () => {
240
- render(<Component />);
241
- expect(screen.getByRole('button')).toBeInTheDocument();
242
- });
243
- });
244
- \`\`\`
245
-
246
- ## Phase 2 - Integration Tests
247
-
248
- For API routes and database operations:
249
- \`\`\`typescript
250
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
251
- import { db } from '@test/db';
252
-
253
- describe('API Integration', () => {
254
- beforeEach(async () => {
255
- await db.begin();
256
- });
257
-
258
- afterEach(async () => {
259
- await db.rollback();
260
- });
261
-
262
- it('should create resource', async () => {
263
- const result = await db.resource.create({ data: { name: 'test' } });
264
- expect(result).toHaveProperty('id');
265
- });
266
- });
267
- \`\`\`
268
-
269
- ## Phase 3 - E2E Tests
270
-
271
- Use Playwright for user flows:
272
- \`\`\`typescript
273
- import { test, expect } from '@playwright/test';
274
-
275
- test('user journey', async ({ page }) => {
276
- await page.goto('/login');
277
- await page.fill('input[name="email"]', 'test@example.com');
278
- await page.click('button[type="submit"]');
279
- await expect(page).toHaveURL('/dashboard');
280
- });
281
- \`\`\`
282
-
283
- ## Fix Loop
284
-
285
- When tests fail:
286
- 1. Analyze the error
287
- 2. Determine if it's a test issue or code bug
288
- 3. Apply fix
289
- 4. Re-test
290
-
291
- ## Completion
292
-
293
- Report:
294
- \`\`\`
295
- ✓ Unit Tests: X created, Y passing
296
- ✓ Integration: X created, Y passing
297
- ✓ E2E: X created, Y passing
298
- ✓ Performance: API p95 = Xms
299
- \`\`\`
300
- `;
301
- }
302
-
303
- // --- Main Function ---
304
- async function main() {
305
- console.log('');
306
- console.log(bold(magenta(' ╔═══════════════════════════════════════╗')));
307
- console.log(bold(magenta(' ║ Daemon ║')));
308
- console.log(bold(magenta(' ║ AI-Powered Test Generation ║')));
309
- console.log(bold(magenta(' ╚═══════════════════════════════════════╝')));
310
- console.log('');
311
- console.log(dim(' Automated testing toolkit for web applications'));
312
- console.log('');
313
-
314
- // Check if we're in a project directory
315
- if (!hasProjectFiles()) {
316
- console.log(yellow(' ⚠ No project markers found.'));
317
- console.log(yellow(' Please run from a project directory with package.json or equivalent.'));
318
- console.log('');
319
- const answer = await ask(' Continue anyway? ' + dim('(Y/n)') + ' ');
320
- if (answer === 'n' || answer === 'no') {
321
- process.exit(0);
322
- }
323
- }
324
-
325
- // --- Step 1: Check Docker ---
326
- console.log(` ${dim('→')} Checking Docker...`);
327
- if (run('docker info', { silent: true }) === null) {
328
- fail(`Docker is not running.
329
-
330
- Start Docker Desktop (or the Docker daemon) and try again.
331
-
332
- Install Docker: ${cyan('https://docs.docker.com/get-docker/')}`);
333
- }
334
- console.log(` ${green('✓')} Docker is running`);
335
-
336
- // --- Step 2: Build image if missing ---
337
- const imageExists = run(`docker images -q ${CONFIG.IMAGE}`);
338
-
339
- if (!imageExists) {
340
- console.log('');
341
- console.log(` ${yellow('◆')} The testing toolkit needs to be installed (~500 MB Docker image).`);
342
- console.log(` ${dim('This only happens once.')}`);
343
- console.log('');
344
- const answer = await ask(` Install it now? ${dim('(Y/n)')} `);
345
- if (answer === 'n' || answer === 'no') {
346
- console.log('');
347
- console.log(dim(' No problem. Run npx --yes @oalacea/daemon@latest again when you\'re ready.'));
348
- console.log('');
349
- process.exit(0);
350
- }
351
- console.log('');
352
- console.log(` ${yellow('→')} Building testing toolkit...`);
353
- console.log(dim(' This may take 2-3 minutes on first run...'));
354
- console.log('');
355
- try {
356
- execSync(
357
- `docker build -t ${CONFIG.IMAGE} -f "${CONFIG.DOCKERFILE}" "${path.dirname(CONFIG.DOCKERFILE)}"`,
358
- { stdio: 'inherit', timeout: 600000 }
359
- );
360
- } catch {
361
- fail(`Failed to build the testing toolkit image.
362
-
363
- Try manually:
364
- ${cyan(`docker build -t ${CONFIG.IMAGE} -f "${CONFIG.DOCKERFILE}" "${path.dirname(CONFIG.DOCKERFILE)}"`)}`);
365
- }
366
- console.log('');
367
- console.log(` ${green('✓')} Testing toolkit installed`);
368
- } else {
369
- console.log(` ${green('✓')} Testing toolkit ready`);
370
- }
371
-
372
- // --- Step 3: Start container if not running ---
373
- const containerRunning = run(
374
- `docker ps --filter "name=^${CONFIG.CONTAINER}$" --format "{{.Names}}"`
375
- );
376
-
377
- if (containerRunning === CONFIG.CONTAINER) {
378
- console.log(` ${green('✓')} Toolkit container running (${bold(CONFIG.CONTAINER)})`);
379
- } else {
380
- const containerExists = run(
381
- `docker ps -a --filter "name=^${CONFIG.CONTAINER}$" --format "{{.Names}}"`
382
- );
383
-
384
- if (containerExists === CONFIG.CONTAINER) {
385
- process.stdout.write(` ${yellow('→')} Starting toolkit container...`);
386
- if (run(`docker start ${CONFIG.CONTAINER}`, { timeout: 30000 }) === null) {
387
- console.log('');
388
- fail(`Failed to start container.
389
-
390
- Try manually:
391
- ${cyan(`docker start ${CONFIG.CONTAINER}`)}`);
392
- }
393
- console.log(` ${green('done')}`);
394
- } else {
395
- const isLinux = process.platform === 'linux';
396
- const networkFlag = isLinux ? '--network=host' : '';
397
-
398
- process.stdout.write(` ${yellow('→')} Creating toolkit container (${CONFIG.CONTAINER})...`);
399
- const runCmd = `docker run -d --name ${CONFIG.CONTAINER} ${networkFlag} ${CONFIG.IMAGE}`.replace(/\s+/g, ' ');
400
- if (run(runCmd, { timeout: 30000 }) === null) {
401
- console.log('');
402
- fail(`Failed to create container.
403
-
404
- Try manually:
405
- ${cyan(runCmd)}`);
406
- }
407
- console.log(` ${green('done')}`);
408
- }
409
- console.log(` ${green('✓')} Toolkit container running (${bold(CONFIG.CONTAINER)})`);
410
- }
411
-
412
- // --- Step 4: Run detection ---
413
- console.log(` ${dim('→')} Analyzing project...`);
414
- const context = runDetection(CONFIG.PROJECT_DIR);
415
-
416
- // --- Step 5: Create daemon directory and prompt ---
417
- const daemonDir = path.dirname(CONFIG.PROMPT_DEST);
418
- if (!fs.existsSync(daemonDir)) {
419
- fs.mkdirSync(daemonDir, { recursive: true });
420
- }
421
-
422
- const prompt = generatePrompt(context);
423
- fs.writeFileSync(CONFIG.PROMPT_DEST, prompt);
424
- console.log(` ${green('✓')} Prompt installed to ${bold('./.daemon/EXECUTE.md')}`);
425
-
426
- // --- Step 6: Print summary ---
427
- console.log('');
428
- console.log(bold(' Detected Configuration:'));
429
- console.log(` Framework: ${cyan(context.framework || 'Unknown')}`);
430
- console.log(` Language: ${cyan(context.language || 'JavaScript/TypeScript')}`);
431
- console.log(` Test Runner: ${cyan(context.testRunner || 'Vitest')}`);
432
- if (context.database) {
433
- console.log(` Database: ${cyan(context.database.type)}`);
434
- console.log(` Connection: ${cyan(context.database.connection)}`);
435
- }
436
- console.log(` Existing: ${cyan((context.existingTests || 0) + ' tests')}`);
437
- console.log(` Target: ${cyan(context.target || 'http://localhost:3000')}`);
438
- console.log('');
439
-
440
- // --- Step 7: Print instructions ---
441
- console.log(bold(' Ready!') + ' Open your AI agent from your project directory and paste:');
442
- console.log('');
443
- console.log(` ${cyan(`Read ./.daemon/EXECUTE.md and start the testing process`)}`);
444
- console.log('');
445
- console.log(dim(' Works with Claude Code, Cursor, Windsurf, Aider, Codex...'));
446
- console.log('');
447
- }
448
-
449
- main();
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require('child_process');
4
+ const readline = require('readline');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ // --- Config ---
9
+ const CONFIG = {
10
+ IMAGE: 'daemon-tools',
11
+ CONTAINER: 'daemon-tools',
12
+ PROMPT_SRC: path.join(__dirname, '..', 'prompts', 'EXECUTE.md'),
13
+ // Output in current working directory
14
+ PROMPT_DEST: path.join(process.cwd(), '.daemon', 'EXECUTE.md'),
15
+ PROJECT_DIR: process.cwd(),
16
+ DOCKERFILE: path.join(__dirname, 'Dockerfile')
17
+ };
18
+
19
+ // --- Colors ---
20
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
21
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
22
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
23
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
24
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
25
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
26
+ const blue = (s) => `\x1b[34m${s}\x1b[0m`;
27
+ const magenta = (s) => `\x1b[35m${s}\x1b[0m`;
28
+
29
+ // --- Utilities ---
30
+ function run(cmd, { silent = false, timeout = 60000 } = {}) {
31
+ try {
32
+ return execSync(cmd, { encoding: 'utf-8', stdio: 'pipe', timeout }).trim();
33
+ } catch (error) {
34
+ if (!silent && error.status !== null) {
35
+ console.error(dim(` [debug] Command exited with code ${error.status}`));
36
+ }
37
+ return null;
38
+ }
39
+ }
40
+
41
+ function fail(msg) {
42
+ console.error(`\n ${red('✗')} ${msg}\n`);
43
+ process.exit(1);
44
+ }
45
+
46
+ function ask(question) {
47
+ return new Promise((resolve) => {
48
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
49
+ rl.question(question, (answer) => {
50
+ rl.close();
51
+ resolve(answer.trim().toLowerCase());
52
+ });
53
+ });
54
+ }
55
+
56
+ // --- Project Markers ---
57
+ const PROJECT_MARKERS = [
58
+ 'package.json',
59
+ 'requirements.txt',
60
+ 'pyproject.toml',
61
+ 'Pipfile',
62
+ 'go.mod',
63
+ 'pom.xml',
64
+ 'build.gradle',
65
+ 'Gemfile',
66
+ 'composer.json',
67
+ 'Cargo.toml'
68
+ ];
69
+
70
+ function hasProjectFiles() {
71
+ const cwd = path.resolve(process.cwd());
72
+ try {
73
+ const realCwd = fs.realpathSync(cwd);
74
+ if (realCwd !== cwd) {
75
+ console.warn(yellow(' ⚠ Symbolic link detected in path, using real path'));
76
+ }
77
+ } catch {
78
+ // realpathSync failed, continue with cwd
79
+ }
80
+ return PROJECT_MARKERS.some((f) => fs.existsSync(path.join(cwd, f)));
81
+ }
82
+
83
+ // --- Detection Import ---
84
+ function runDetection(projectDir) {
85
+ const detectorPath = path.join(__dirname, '..', 'agents', 'detector.js');
86
+
87
+ if (!fs.existsSync(detectorPath)) {
88
+ // Fallback if detector doesn't exist yet
89
+ return {
90
+ framework: 'Unknown',
91
+ language: 'JavaScript/TypeScript',
92
+ testRunner: 'Vitest',
93
+ database: null,
94
+ existingTests: 0,
95
+ coverage: null,
96
+ dependencies: [],
97
+ target: 'http://localhost:3000'
98
+ };
99
+ }
100
+
101
+ try {
102
+ // Execute detector as a module
103
+ const detector = require(detectorPath);
104
+ return detector.analyze(projectDir);
105
+ } catch (e) {
106
+ console.log(dim(` [debug] Detection error: ${e.message}`));
107
+ return {
108
+ framework: 'Unknown',
109
+ language: 'JavaScript/TypeScript',
110
+ testRunner: 'Vitest',
111
+ database: null,
112
+ existingTests: 0,
113
+ coverage: null,
114
+ dependencies: [],
115
+ target: 'http://localhost:3000'
116
+ };
117
+ }
118
+ }
119
+
120
+ // --- Prompt Generation ---
121
+ function generatePrompt(context) {
122
+ const promptSrc = CONFIG.PROMPT_SRC;
123
+
124
+ if (!fs.existsSync(promptSrc)) {
125
+ return generateFallbackPrompt(context);
126
+ }
127
+
128
+ const basePrompt = fs.readFileSync(promptSrc, 'utf-8');
129
+
130
+ // Build context block
131
+ const contextBlock = buildContextBlock(context);
132
+
133
+ return contextBlock + '\n' + basePrompt;
134
+ }
135
+
136
+ function buildContextBlock(context) {
137
+ const lines = [
138
+ '> **DETECTED CONTEXT**',
139
+ ];
140
+
141
+ if (context.framework) {
142
+ lines.push(`> Framework: ${context.framework}`);
143
+ }
144
+ if (context.language) {
145
+ lines.push(`> Language: ${context.language}`);
146
+ }
147
+ if (context.testRunner) {
148
+ lines.push(`> Test Runner: ${context.testRunner}`);
149
+ }
150
+ if (context.database) {
151
+ lines.push(`> Database: ${context.database.type || 'detected'}`);
152
+ lines.push(`> DB Connection: ${context.database.connection || 'DATABASE_URL'}`);
153
+ lines.push(`> Test Strategy: Transaction rollback (do not modify real data)`);
154
+ } else {
155
+ lines.push(`> Database: none detected`);
156
+ }
157
+ lines.push(`> Existing Tests: ${context.existingTests || 0} found`);
158
+ if (context.coverage) {
159
+ lines.push(`> Current Coverage: ${context.coverage}`);
160
+ }
161
+ if (context.dependencies && context.dependencies.length > 0) {
162
+ lines.push(`> Key Dependencies: ${context.dependencies.join(', ')}`);
163
+ }
164
+ lines.push(`> Target: ${context.target || 'http://localhost:3000'}`);
165
+
166
+ lines.push('');
167
+ lines.push('> **IMPORTANT**:');
168
+ lines.push('> - Use this detected context. Do not re-detect.');
169
+ lines.push('> - Always read source code before generating tests.');
170
+ lines.push('> - Run tests to verify they work before declaring success.');
171
+ if (context.database) {
172
+ lines.push('> - Use transaction rollback for DB tests - never modify real data.');
173
+ }
174
+ lines.push('');
175
+ lines.push('> **WORKFLOW**:');
176
+ lines.push('> 1. Read ./.daemon/EXECUTE.md for full instructions');
177
+ lines.push('> 2. Generate tests following the detected patterns');
178
+ lines.push('> 3. Run tests via Docker container');
179
+ lines.push('> 4. Fix failures iteratively until all pass');
180
+ lines.push('> 5. Generate final report');
181
+ lines.push('');
182
+
183
+ return lines.join('\n');
184
+ }
185
+
186
+ function generateFallbackPrompt(context) {
187
+ return `# Daemon — Automated Testing Process
188
+
189
+ > **DETECTED CONTEXT**
190
+ > Framework: ${context.framework || 'Unknown'}
191
+ > Language: ${context.language || 'JavaScript/TypeScript'}
192
+ > Test Runner: ${context.testRunner || 'Vitest'}
193
+ > Database: ${context.database?.type || 'none'}
194
+ > Target: ${context.target || 'http://localhost:3000'}
195
+
196
+ ## Instructions
197
+
198
+ This is the automated testing agent. Follow these steps:
199
+
200
+ 1. **Read the project structure** - Understand the framework and patterns
201
+ 2. **Generate unit tests** - For components, hooks, and utilities
202
+ 3. **Generate integration tests** - For API routes and database operations
203
+ 4. **Generate E2E tests** - For critical user flows
204
+ 5. **Run performance tests** - API load testing and DB query analysis
205
+ 6. **Analyze dependencies** - Check for efficiency patterns
206
+
207
+ Always run tests to verify they work. When a test fails, analyze and fix before proceeding.
208
+
209
+ ## Tool Execution
210
+
211
+ All test tools run inside the Daemon Docker container:
212
+
213
+ \`\`\`bash
214
+ docker exec daemon-tools <command>
215
+ \`\`\`
216
+
217
+ ### Available Commands
218
+
219
+ | Task | Command |
220
+ |------|---------|
221
+ | Unit tests | \`docker exec daemon-tools npm test\` |
222
+ | E2E tests | \`docker exec daemon-tools npx playwright test\` |
223
+ | Performance | \`docker exec daemon-tools k6 run tests/performance/load.js\` |
224
+
225
+ ## Phase 1 - Unit Tests
226
+
227
+ Generate tests for:
228
+ - Components (render, props, events, edge cases)
229
+ - Hooks (state updates, return values, cleanup)
230
+ - Utils (pure functions, validators)
231
+
232
+ Template:
233
+ \`\`\`typescript
234
+ import { render, screen } from '@testing-library/react';
235
+ import { describe, it, expect, vi } from 'vitest';
236
+ import { Component } from '@/components/Component';
237
+
238
+ describe('Component', () => {
239
+ it('should render', () => {
240
+ render(<Component />);
241
+ expect(screen.getByRole('button')).toBeInTheDocument();
242
+ });
243
+ });
244
+ \`\`\`
245
+
246
+ ## Phase 2 - Integration Tests
247
+
248
+ For API routes and database operations:
249
+ \`\`\`typescript
250
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
251
+ import { db } from '@test/db';
252
+
253
+ describe('API Integration', () => {
254
+ beforeEach(async () => {
255
+ await db.begin();
256
+ });
257
+
258
+ afterEach(async () => {
259
+ await db.rollback();
260
+ });
261
+
262
+ it('should create resource', async () => {
263
+ const result = await db.resource.create({ data: { name: 'test' } });
264
+ expect(result).toHaveProperty('id');
265
+ });
266
+ });
267
+ \`\`\`
268
+
269
+ ## Phase 3 - E2E Tests
270
+
271
+ Use Playwright for user flows:
272
+ \`\`\`typescript
273
+ import { test, expect } from '@playwright/test';
274
+
275
+ test('user journey', async ({ page }) => {
276
+ await page.goto('/login');
277
+ await page.fill('input[name="email"]', 'test@example.com');
278
+ await page.click('button[type="submit"]');
279
+ await expect(page).toHaveURL('/dashboard');
280
+ });
281
+ \`\`\`
282
+
283
+ ## Fix Loop
284
+
285
+ When tests fail:
286
+ 1. Analyze the error
287
+ 2. Determine if it's a test issue or code bug
288
+ 3. Apply fix
289
+ 4. Re-test
290
+
291
+ ## Completion
292
+
293
+ Report:
294
+ \`\`\`
295
+ ✓ Unit Tests: X created, Y passing
296
+ ✓ Integration: X created, Y passing
297
+ ✓ E2E: X created, Y passing
298
+ ✓ Performance: API p95 = Xms
299
+ \`\`\`
300
+ `;
301
+ }
302
+
303
+ // --- Main Function ---
304
+ async function main() {
305
+ console.log('');
306
+ console.log(bold(magenta(' ╔═══════════════════════════════════════╗')));
307
+ console.log(bold(magenta(' ║ Daemon ║')));
308
+ console.log(bold(magenta(' ║ AI-Powered Test Generation ║')));
309
+ console.log(bold(magenta(' ╚═══════════════════════════════════════╝')));
310
+ console.log('');
311
+ console.log(dim(' Automated testing toolkit for web applications'));
312
+ console.log('');
313
+
314
+ // Check if we're in a project directory
315
+ if (!hasProjectFiles()) {
316
+ console.log(yellow(' ⚠ No project markers found.'));
317
+ console.log(yellow(' Please run from a project directory with package.json or equivalent.'));
318
+ console.log('');
319
+ const answer = await ask(' Continue anyway? ' + dim('(Y/n)') + ' ');
320
+ if (answer === 'n' || answer === 'no') {
321
+ process.exit(0);
322
+ }
323
+ }
324
+
325
+ // --- Step 1: Check Docker ---
326
+ console.log(` ${dim('→')} Checking Docker...`);
327
+ if (run('docker info', { silent: true }) === null) {
328
+ fail(`Docker is not running.
329
+
330
+ Start Docker Desktop (or the Docker daemon) and try again.
331
+
332
+ Install Docker: ${cyan('https://docs.docker.com/get-docker/')}`);
333
+ }
334
+ console.log(` ${green('✓')} Docker is running`);
335
+
336
+ // --- Step 2: Build image if missing ---
337
+ const imageExists = run(`docker images -q ${CONFIG.IMAGE}`);
338
+
339
+ if (!imageExists) {
340
+ console.log('');
341
+ console.log(` ${yellow('◆')} The testing toolkit needs to be installed (~500 MB Docker image).`);
342
+ console.log(` ${dim('This only happens once.')}`);
343
+ console.log('');
344
+ const answer = await ask(` Install it now? ${dim('(Y/n)')} `);
345
+ if (answer === 'n' || answer === 'no') {
346
+ console.log('');
347
+ console.log(dim(' No problem. Run npx --yes @oalacea/daemon@latest again when you\'re ready.'));
348
+ console.log('');
349
+ process.exit(0);
350
+ }
351
+ console.log('');
352
+ console.log(` ${yellow('→')} Building testing toolkit...`);
353
+ console.log(dim(' This may take 2-3 minutes on first run...'));
354
+ console.log('');
355
+ try {
356
+ execSync(
357
+ `docker build -t ${CONFIG.IMAGE} -f "${CONFIG.DOCKERFILE}" "${path.dirname(CONFIG.DOCKERFILE)}"`,
358
+ { stdio: 'inherit', timeout: 600000 }
359
+ );
360
+ } catch {
361
+ fail(`Failed to build the testing toolkit image.
362
+
363
+ Try manually:
364
+ ${cyan(`docker build -t ${CONFIG.IMAGE} -f "${CONFIG.DOCKERFILE}" "${path.dirname(CONFIG.DOCKERFILE)}"`)}`);
365
+ }
366
+ console.log('');
367
+ console.log(` ${green('✓')} Testing toolkit installed`);
368
+ } else {
369
+ console.log(` ${green('✓')} Testing toolkit ready`);
370
+ }
371
+
372
+ // --- Step 3: Start container if not running ---
373
+ const containerRunning = run(
374
+ `docker ps --filter "name=^${CONFIG.CONTAINER}$" --format "{{.Names}}"`
375
+ );
376
+
377
+ if (containerRunning === CONFIG.CONTAINER) {
378
+ console.log(` ${green('✓')} Toolkit container running (${bold(CONFIG.CONTAINER)})`);
379
+ } else {
380
+ const containerExists = run(
381
+ `docker ps -a --filter "name=^${CONFIG.CONTAINER}$" --format "{{.Names}}"`
382
+ );
383
+
384
+ if (containerExists === CONFIG.CONTAINER) {
385
+ process.stdout.write(` ${yellow('→')} Starting toolkit container...`);
386
+ if (run(`docker start ${CONFIG.CONTAINER}`, { timeout: 30000 }) === null) {
387
+ console.log('');
388
+ fail(`Failed to start container.
389
+
390
+ Try manually:
391
+ ${cyan(`docker start ${CONFIG.CONTAINER}`)}`);
392
+ }
393
+ console.log(` ${green('done')}`);
394
+ } else {
395
+ const isLinux = process.platform === 'linux';
396
+ const networkFlag = isLinux ? '--network=host' : '';
397
+
398
+ process.stdout.write(` ${yellow('→')} Creating toolkit container (${CONFIG.CONTAINER})...`);
399
+ const runCmd = `docker run -d --name ${CONFIG.CONTAINER} ${networkFlag} ${CONFIG.IMAGE}`.replace(/\s+/g, ' ');
400
+ if (run(runCmd, { timeout: 30000 }) === null) {
401
+ console.log('');
402
+ fail(`Failed to create container.
403
+
404
+ Try manually:
405
+ ${cyan(runCmd)}`);
406
+ }
407
+ console.log(` ${green('done')}`);
408
+ }
409
+ console.log(` ${green('✓')} Toolkit container running (${bold(CONFIG.CONTAINER)})`);
410
+ }
411
+
412
+ // --- Step 4: Run detection ---
413
+ console.log(` ${dim('→')} Analyzing project...`);
414
+ const context = runDetection(CONFIG.PROJECT_DIR);
415
+
416
+ // --- Step 5: Create daemon directory and prompt ---
417
+ const daemonDir = path.dirname(CONFIG.PROMPT_DEST);
418
+ if (!fs.existsSync(daemonDir)) {
419
+ fs.mkdirSync(daemonDir, { recursive: true });
420
+ }
421
+
422
+ const prompt = generatePrompt(context);
423
+ fs.writeFileSync(CONFIG.PROMPT_DEST, prompt);
424
+ console.log(` ${green('✓')} Prompt installed to ${bold('./.daemon/EXECUTE.md')}`);
425
+
426
+ // --- Step 6: Print summary ---
427
+ console.log('');
428
+ console.log(bold(' Detected Configuration:'));
429
+ console.log(` Framework: ${cyan(context.framework || 'Unknown')}`);
430
+ console.log(` Language: ${cyan(context.language || 'JavaScript/TypeScript')}`);
431
+ console.log(` Test Runner: ${cyan(context.testRunner || 'Vitest')}`);
432
+ if (context.database) {
433
+ console.log(` Database: ${cyan(context.database.type)}`);
434
+ console.log(` Connection: ${cyan(context.database.connection)}`);
435
+ }
436
+ console.log(` Existing: ${cyan((context.existingTests || 0) + ' tests')}`);
437
+ console.log(` Target: ${cyan(context.target || 'http://localhost:3000')}`);
438
+ console.log('');
439
+
440
+ // --- Step 7: Print instructions ---
441
+ console.log(bold(' Ready!') + ' Open your AI agent from your project directory and paste:');
442
+ console.log('');
443
+ console.log(` ${cyan(`Read ./.daemon/EXECUTE.md and start the testing process`)}`);
444
+ console.log('');
445
+ console.log(dim(' Works with Claude Code, Cursor, Windsurf, Aider, Codex...'));
446
+ console.log('');
447
+ }
448
+
449
+ main();