prjct-cli 0.9.2 → 0.10.2

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 (53) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/core/__tests__/agentic/memory-system.test.js +263 -0
  3. package/core/__tests__/agentic/plan-mode.test.js +336 -0
  4. package/core/agentic/agent-router.js +253 -186
  5. package/core/agentic/chain-of-thought.js +578 -0
  6. package/core/agentic/command-executor.js +299 -17
  7. package/core/agentic/context-builder.js +208 -8
  8. package/core/agentic/context-filter.js +83 -83
  9. package/core/agentic/ground-truth.js +591 -0
  10. package/core/agentic/loop-detector.js +406 -0
  11. package/core/agentic/memory-system.js +850 -0
  12. package/core/agentic/parallel-tools.js +366 -0
  13. package/core/agentic/plan-mode.js +572 -0
  14. package/core/agentic/prompt-builder.js +127 -2
  15. package/core/agentic/response-templates.js +290 -0
  16. package/core/agentic/semantic-compression.js +517 -0
  17. package/core/agentic/think-blocks.js +657 -0
  18. package/core/agentic/tool-registry.js +32 -0
  19. package/core/agentic/validation-rules.js +380 -0
  20. package/core/command-registry.js +48 -0
  21. package/core/commands.js +128 -60
  22. package/core/context-sync.js +183 -0
  23. package/core/domain/agent-generator.js +77 -46
  24. package/core/domain/agent-loader.js +183 -0
  25. package/core/domain/agent-matcher.js +217 -0
  26. package/core/domain/agent-validator.js +217 -0
  27. package/core/domain/context-estimator.js +175 -0
  28. package/core/domain/product-standards.js +92 -0
  29. package/core/domain/smart-cache.js +157 -0
  30. package/core/domain/task-analyzer.js +353 -0
  31. package/core/domain/tech-detector.js +365 -0
  32. package/package.json +8 -16
  33. package/templates/commands/done.md +7 -0
  34. package/templates/commands/feature.md +8 -0
  35. package/templates/commands/ship.md +8 -0
  36. package/templates/commands/spec.md +128 -0
  37. package/templates/global/CLAUDE.md +17 -0
  38. package/core/__tests__/agentic/agent-router.test.js +0 -398
  39. package/core/__tests__/agentic/command-executor.test.js +0 -223
  40. package/core/__tests__/agentic/context-builder.test.js +0 -160
  41. package/core/__tests__/agentic/context-filter.test.js +0 -494
  42. package/core/__tests__/agentic/prompt-builder.test.js +0 -212
  43. package/core/__tests__/agentic/template-loader.test.js +0 -164
  44. package/core/__tests__/agentic/tool-registry.test.js +0 -243
  45. package/core/__tests__/domain/agent-generator.test.js +0 -296
  46. package/core/__tests__/domain/analyzer.test.js +0 -324
  47. package/core/__tests__/infrastructure/author-detector.test.js +0 -103
  48. package/core/__tests__/infrastructure/config-manager.test.js +0 -454
  49. package/core/__tests__/infrastructure/path-manager.test.js +0 -412
  50. package/core/__tests__/setup.test.js +0 -15
  51. package/core/__tests__/utils/date-helper.test.js +0 -169
  52. package/core/__tests__/utils/file-helper.test.js +0 -258
  53. package/core/__tests__/utils/jsonl-helper.test.js +0 -387
@@ -1,494 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import { createRequire } from 'module'
3
- import path from 'path'
4
- import fs from 'fs/promises'
5
- import os from 'os'
6
-
7
- // Use createRequire to import CommonJS module
8
- const require = createRequire(import.meta.url)
9
-
10
- describe('Context Filter', () => {
11
- let ContextFilter
12
- let testProjectPath
13
- let tempDir
14
-
15
- beforeEach(async () => {
16
- // Test that module can be imported (detects missing dependency)
17
- try {
18
- ContextFilter = require('../../agentic/context-filter.js')
19
- } catch (error) {
20
- throw new Error(`Failed to import context-filter: ${error.message}. This indicates a missing dependency.`)
21
- }
22
-
23
- // Create temporary test directory
24
- tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'prjct-test-'))
25
- testProjectPath = tempDir
26
- })
27
-
28
- afterEach(async () => {
29
- // Cleanup temp directory
30
- if (tempDir) {
31
- try {
32
- await fs.rm(tempDir, { recursive: true, force: true })
33
- } catch (error) {
34
- // Ignore cleanup errors
35
- }
36
- }
37
- })
38
-
39
- describe('Module Import', () => {
40
- it('should import without errors (detects missing glob dependency)', () => {
41
- expect(ContextFilter).toBeDefined()
42
- expect(typeof ContextFilter).toBe('function')
43
- })
44
-
45
- it('should be instantiable', () => {
46
- const filter = new ContextFilter()
47
- expect(filter).toBeDefined()
48
- expect(filter).toBeInstanceOf(ContextFilter)
49
- })
50
- })
51
-
52
- describe('Glob API Compatibility', () => {
53
- it('should use modern glob API (detects promisify issue)', async () => {
54
- const filter = new ContextFilter()
55
-
56
- // Create test files
57
- const srcDir = path.join(testProjectPath, 'src')
58
- await fs.mkdir(srcDir, { recursive: true })
59
- await fs.writeFile(path.join(srcDir, 'test.js'), '// test')
60
- await fs.writeFile(path.join(srcDir, 'test2.js'), '// test2')
61
-
62
- // Test that glob works with modern API
63
- // This will fail if using old promisify pattern
64
- const patterns = {
65
- include: ['src'],
66
- exclude: ['node_modules'],
67
- extensions: ['.js'],
68
- specific: ['src/**/*.js'] // Use specific pattern to ensure files are found
69
- }
70
-
71
- const files = await filter.loadRelevantFiles(testProjectPath, patterns)
72
-
73
- // Should find test files (or at least return array without errors)
74
- expect(Array.isArray(files)).toBe(true)
75
- // Files may be relative or absolute paths, so check more flexibly
76
- if (files.length > 0) {
77
- expect(files.some(f => f.includes('test') && f.includes('.js'))).toBe(true)
78
- }
79
- })
80
-
81
- it('should handle glob errors gracefully', async () => {
82
- const filter = new ContextFilter()
83
-
84
- // Invalid pattern should not throw
85
- const patterns = {
86
- include: [],
87
- exclude: [],
88
- extensions: [],
89
- specific: ['**/nonexistent/**/*.xyz']
90
- }
91
-
92
- const files = await filter.loadRelevantFiles('/nonexistent/path', patterns)
93
-
94
- // Should return empty array, not throw
95
- expect(Array.isArray(files)).toBe(true)
96
- expect(files.length).toBe(0)
97
- })
98
- })
99
-
100
- describe('Initialization', () => {
101
- it('should initialize tech patterns', () => {
102
- const filter = new ContextFilter()
103
- const stats = filter.getStatistics()
104
-
105
- expect(stats.supportedTechnologies).toBeGreaterThan(0)
106
- expect(stats.taskTypes).toBeGreaterThan(0)
107
- })
108
-
109
- it('should have technology patterns', () => {
110
- const filter = new ContextFilter()
111
- // Access private property through method
112
- const stats = filter.getStatistics()
113
-
114
- expect(stats.supportedTechnologies).toBeGreaterThan(10) // Should have many tech patterns
115
- })
116
-
117
- it('should have task patterns', () => {
118
- const filter = new ContextFilter()
119
- const stats = filter.getStatistics()
120
-
121
- expect(stats.taskTypes).toBeGreaterThan(5) // Should have multiple task types
122
- })
123
- })
124
-
125
- describe('Technology Detection', () => {
126
- it('should detect JavaScript project', async () => {
127
- const filter = new ContextFilter()
128
-
129
- // Create package.json
130
- const packageJson = {
131
- name: 'test-project',
132
- dependencies: {}
133
- }
134
- await fs.writeFile(
135
- path.join(testProjectPath, 'package.json'),
136
- JSON.stringify(packageJson, null, 2)
137
- )
138
-
139
- const techs = await filter.detectProjectTechnologies(testProjectPath)
140
-
141
- expect(Array.isArray(techs)).toBe(true)
142
- expect(techs).toContain('javascript')
143
- })
144
-
145
- it('should detect TypeScript project', async () => {
146
- const filter = new ContextFilter()
147
-
148
- const packageJson = {
149
- name: 'test-project',
150
- dependencies: {
151
- typescript: '^5.0.0'
152
- }
153
- }
154
- await fs.writeFile(
155
- path.join(testProjectPath, 'package.json'),
156
- JSON.stringify(packageJson, null, 2)
157
- )
158
-
159
- const techs = await filter.detectProjectTechnologies(testProjectPath)
160
-
161
- expect(techs).toContain('typescript')
162
- expect(techs).not.toContain('javascript')
163
- })
164
-
165
- it('should detect React project', async () => {
166
- const filter = new ContextFilter()
167
-
168
- const packageJson = {
169
- name: 'test-project',
170
- dependencies: {
171
- react: '^18.0.0',
172
- typescript: '^5.0.0'
173
- }
174
- }
175
- await fs.writeFile(
176
- path.join(testProjectPath, 'package.json'),
177
- JSON.stringify(packageJson, null, 2)
178
- )
179
-
180
- const techs = await filter.detectProjectTechnologies(testProjectPath)
181
-
182
- expect(techs).toContain('react')
183
- expect(techs).toContain('typescript')
184
- })
185
-
186
- it('should detect Express project', async () => {
187
- const filter = new ContextFilter()
188
-
189
- const packageJson = {
190
- name: 'test-project',
191
- dependencies: {
192
- express: '^4.18.0'
193
- }
194
- }
195
- await fs.writeFile(
196
- path.join(testProjectPath, 'package.json'),
197
- JSON.stringify(packageJson, null, 2)
198
- )
199
-
200
- const techs = await filter.detectProjectTechnologies(testProjectPath)
201
-
202
- expect(techs).toContain('express')
203
- })
204
-
205
- it('should handle missing package.json gracefully', async () => {
206
- const filter = new ContextFilter()
207
-
208
- const techs = await filter.detectProjectTechnologies(testProjectPath)
209
-
210
- expect(Array.isArray(techs)).toBe(true)
211
- expect(techs.length).toBe(0)
212
- })
213
- })
214
-
215
- describe('Task Type Detection', () => {
216
- it('should detect API task', () => {
217
- const filter = new ContextFilter()
218
-
219
- const taskType = filter.detectTaskType({ description: 'create API endpoint' })
220
-
221
- expect(taskType).toBe('api')
222
- })
223
-
224
- it('should detect UI task', () => {
225
- const filter = new ContextFilter()
226
-
227
- const taskType = filter.detectTaskType({ description: 'build login component' })
228
-
229
- expect(taskType).toBe('ui')
230
- })
231
-
232
- it('should detect database task', () => {
233
- const filter = new ContextFilter()
234
-
235
- const taskType = filter.detectTaskType({ description: 'create migration schema' })
236
-
237
- expect(taskType).toBe('database')
238
- })
239
-
240
- it('should detect testing task', () => {
241
- const filter = new ContextFilter()
242
-
243
- const taskType = filter.detectTaskType({ description: 'write unit tests' })
244
-
245
- expect(taskType).toBe('testing')
246
- })
247
-
248
- it('should return general for unknown task', () => {
249
- const filter = new ContextFilter()
250
-
251
- const taskType = filter.detectTaskType({ description: 'random task' })
252
-
253
- expect(taskType).toBe('general')
254
- })
255
- })
256
-
257
- describe('Agent-Specific Patterns', () => {
258
- it('should return frontend patterns for frontend agent', () => {
259
- const filter = new ContextFilter()
260
-
261
- const patterns = filter.getAgentSpecificPatterns({ type: 'frontend' })
262
-
263
- expect(patterns.include).toContain('components')
264
- expect(patterns.exclude).toContain('backend')
265
- })
266
-
267
- it('should return backend patterns for backend agent', () => {
268
- const filter = new ContextFilter()
269
-
270
- const patterns = filter.getAgentSpecificPatterns({ type: 'backend' })
271
-
272
- expect(patterns.include).toContain('api')
273
- expect(patterns.exclude).toContain('components')
274
- })
275
-
276
- it('should return default patterns for unknown agent', () => {
277
- const filter = new ContextFilter()
278
-
279
- const patterns = filter.getAgentSpecificPatterns({ type: 'unknown' })
280
-
281
- expect(patterns.exclude).toContain('node_modules')
282
- })
283
- })
284
-
285
- describe('File Filtering', () => {
286
- it('should filter files by extension', async () => {
287
- const filter = new ContextFilter()
288
-
289
- // Create test structure
290
- const srcDir = path.join(testProjectPath, 'src')
291
- await fs.mkdir(srcDir, { recursive: true })
292
- await fs.writeFile(path.join(srcDir, 'app.js'), '// app')
293
- await fs.writeFile(path.join(srcDir, 'app.ts'), '// app ts')
294
- await fs.writeFile(path.join(srcDir, 'readme.md'), '# readme')
295
-
296
- const patterns = {
297
- include: ['src'],
298
- exclude: [],
299
- extensions: ['.js'],
300
- specific: ['src/**/*.js'] // Use specific pattern
301
- }
302
-
303
- const files = await filter.loadRelevantFiles(testProjectPath, patterns)
304
-
305
- // Should find .js files (may be relative paths)
306
- const hasJsFile = files.some(f => (f.includes('app') || f.includes('src')) && f.includes('.js'))
307
- expect(hasJsFile || files.length === 0).toBe(true) // Either finds files or returns empty
308
- })
309
-
310
- it('should exclude node_modules', async () => {
311
- const filter = new ContextFilter()
312
-
313
- // Create test structure
314
- const srcDir = path.join(testProjectPath, 'src')
315
- const nodeModulesDir = path.join(testProjectPath, 'node_modules')
316
- await fs.mkdir(srcDir, { recursive: true })
317
- await fs.mkdir(nodeModulesDir, { recursive: true })
318
- await fs.writeFile(path.join(srcDir, 'app.js'), '// app')
319
- await fs.writeFile(path.join(nodeModulesDir, 'lib.js'), '// lib')
320
-
321
- const patterns = {
322
- include: ['src'],
323
- exclude: ['node_modules'],
324
- extensions: ['.js'],
325
- specific: ['src/**/*.js'] // Focus on src directory
326
- }
327
-
328
- const files = await filter.loadRelevantFiles(testProjectPath, patterns)
329
-
330
- // Should not include node_modules files
331
- const hasNodeModules = files.some(f => f.includes('node_modules'))
332
- expect(hasNodeModules).toBe(false)
333
- })
334
-
335
- it('should limit files to maxFiles', async () => {
336
- const filter = new ContextFilter()
337
-
338
- // Create many test files
339
- const srcDir = path.join(testProjectPath, 'src')
340
- await fs.mkdir(srcDir, { recursive: true })
341
-
342
- for (let i = 0; i < 150; i++) {
343
- await fs.writeFile(path.join(srcDir, `file${i}.js`), `// file ${i}`)
344
- }
345
-
346
- const patterns = {
347
- include: ['src'],
348
- exclude: [],
349
- extensions: ['.js'],
350
- specific: []
351
- }
352
-
353
- const files = await filter.loadRelevantFiles(testProjectPath, patterns)
354
-
355
- // Should be limited to 100 files
356
- expect(files.length).toBeLessThanOrEqual(100)
357
- })
358
- })
359
-
360
- describe('Pattern Building', () => {
361
- it('should build glob patterns from configuration', () => {
362
- const filter = new ContextFilter()
363
-
364
- const patterns = {
365
- include: ['src', 'lib'],
366
- exclude: [],
367
- extensions: ['.js', '.ts'],
368
- specific: ['**/components/**']
369
- }
370
-
371
- const globPatterns = filter.buildGlobPatterns(patterns)
372
-
373
- expect(globPatterns).toContain('**/components/**')
374
- expect(globPatterns.some(p => p.includes('src'))).toBe(true)
375
- expect(globPatterns.some(p => p.includes('.js'))).toBe(true)
376
- })
377
-
378
- it('should use default pattern when none specified', () => {
379
- const filter = new ContextFilter()
380
-
381
- const patterns = {
382
- include: [],
383
- exclude: [],
384
- extensions: [],
385
- specific: []
386
- }
387
-
388
- const globPatterns = filter.buildGlobPatterns(patterns)
389
-
390
- expect(globPatterns.length).toBeGreaterThan(0)
391
- expect(globPatterns[0]).toContain('**/*.')
392
- })
393
- })
394
-
395
- describe('Metrics Calculation', () => {
396
- it('should calculate reduction metrics', () => {
397
- const filter = new ContextFilter()
398
- const startTime = Date.now()
399
-
400
- const metrics = filter.calculateMetrics(1000, 200, startTime)
401
-
402
- expect(metrics.originalFiles).toBe(1000)
403
- expect(metrics.filteredFiles).toBe(200)
404
- expect(metrics.reductionPercent).toBe(80)
405
- expect(metrics.effectiveness).toBe('high')
406
- })
407
-
408
- it('should classify effectiveness correctly', () => {
409
- const filter = new ContextFilter()
410
- const startTime = Date.now()
411
-
412
- const high = filter.calculateMetrics(1000, 200, startTime)
413
- const medium = filter.calculateMetrics(1000, 500, startTime)
414
- const low = filter.calculateMetrics(1000, 800, startTime)
415
-
416
- expect(high.effectiveness).toBe('high')
417
- expect(medium.effectiveness).toBe('medium')
418
- expect(low.effectiveness).toBe('low')
419
- })
420
- })
421
-
422
- describe('File Existence Check', () => {
423
- it('should return true for existing file', async () => {
424
- const filter = new ContextFilter()
425
-
426
- const testFile = path.join(testProjectPath, 'test.txt')
427
- await fs.writeFile(testFile, 'test')
428
-
429
- const exists = await filter.fileExists(testFile)
430
-
431
- expect(exists).toBe(true)
432
- })
433
-
434
- it('should return false for non-existent file', async () => {
435
- const filter = new ContextFilter()
436
-
437
- const exists = await filter.fileExists('/nonexistent/path/file.txt')
438
-
439
- expect(exists).toBe(false)
440
- })
441
- })
442
-
443
- describe('Full Filter Workflow', () => {
444
- it('should filter context for frontend agent', async () => {
445
- const filter = new ContextFilter()
446
-
447
- // Create test project structure
448
- const srcDir = path.join(testProjectPath, 'src')
449
- const componentsDir = path.join(srcDir, 'components')
450
- await fs.mkdir(componentsDir, { recursive: true })
451
-
452
- await fs.writeFile(path.join(componentsDir, 'Button.jsx'), '// Button')
453
- await fs.writeFile(path.join(srcDir, 'api.js'), '// API')
454
-
455
- // Create package.json
456
- const packageJson = {
457
- name: 'test-project',
458
- dependencies: {
459
- react: '^18.0.0'
460
- }
461
- }
462
- await fs.writeFile(
463
- path.join(testProjectPath, 'package.json'),
464
- JSON.stringify(packageJson, null, 2)
465
- )
466
-
467
- const agent = { name: 'frontend', type: 'frontend' }
468
- const task = { description: 'build UI component' }
469
-
470
- const result = await filter.filterForAgent(agent, task, testProjectPath, { fileCount: 100 })
471
-
472
- expect(result).toBeDefined()
473
- expect(result.agent).toBe('frontend')
474
- expect(result.filtered).toBe(true)
475
- expect(result.files).toBeDefined()
476
- expect(result.metrics).toBeDefined()
477
- expect(result.metrics.reductionPercent).toBeGreaterThanOrEqual(0)
478
- })
479
-
480
- it('should handle empty project gracefully', async () => {
481
- const filter = new ContextFilter()
482
-
483
- const agent = { name: 'backend', type: 'backend' }
484
- const task = { description: 'do something' }
485
-
486
- const result = await filter.filterForAgent(agent, task, testProjectPath, { fileCount: 0 })
487
-
488
- expect(result).toBeDefined()
489
- expect(result.files).toBeDefined()
490
- expect(Array.isArray(result.files)).toBe(true)
491
- })
492
- })
493
- })
494
-
@@ -1,212 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import promptBuilder from '../../agentic/prompt-builder.js'
3
-
4
- describe('Prompt Builder', () => {
5
- const mockTemplate = {
6
- frontmatter: {
7
- 'allowed-tools': ['Read', 'Write', 'Bash'],
8
- },
9
- content: '# Command: Test\n\nExecute test command.',
10
- }
11
-
12
- const mockContext = {
13
- projectId: 'test-project-123',
14
- projectPath: '/test/path',
15
- globalPath: '/global/path',
16
- timestamp: '2025-10-04T12:00:00Z',
17
- date: '2025-10-04',
18
- params: {},
19
- paths: {
20
- now: '/global/path/core/now.md',
21
- next: '/global/path/core/next.md',
22
- },
23
- }
24
-
25
- const mockState = {
26
- now: '# Current Task\n\nTest task in progress',
27
- next: '# Priority Queue\n\nTask 1, Task 2',
28
- context: null, // Simulate non-existent file
29
- }
30
-
31
- describe('build()', () => {
32
- it('should build a complete prompt', () => {
33
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
34
-
35
- expect(prompt).toBeDefined()
36
- expect(typeof prompt).toBe('string')
37
- expect(prompt.length).toBeGreaterThan(0)
38
- })
39
-
40
- it('should include command instructions', () => {
41
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
42
-
43
- expect(prompt).toContain('# Command Instructions')
44
- expect(prompt).toContain('Execute test command')
45
- })
46
-
47
- it('should include allowed tools', () => {
48
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
49
-
50
- expect(prompt).toContain('## Allowed Tools')
51
- expect(prompt).toContain('Read, Write, Bash')
52
- })
53
-
54
- it('should include project context', () => {
55
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
56
-
57
- expect(prompt).toContain('## Project Context')
58
- expect(prompt).toContain('Project ID: test-project-123')
59
- expect(prompt).toContain('Timestamp: 2025-10-04T12:00:00Z')
60
- })
61
-
62
- it('should include current state', () => {
63
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
64
-
65
- expect(prompt).toContain('## Current State')
66
- expect(prompt).toContain('### now')
67
- expect(prompt).toContain('Test task in progress')
68
- expect(prompt).toContain('### next')
69
- expect(prompt).toContain('Task 1, Task 2')
70
- })
71
-
72
- it('should exclude null or empty state values', () => {
73
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
74
-
75
- // Should not include 'context' since it's null
76
- expect(prompt).not.toContain('### context')
77
- })
78
-
79
- it('should include parameters when present', () => {
80
- const contextWithParams = {
81
- ...mockContext,
82
- params: {
83
- taskName: 'Test Task',
84
- feature: 'Test Feature',
85
- },
86
- }
87
-
88
- const prompt = promptBuilder.build(mockTemplate, contextWithParams, mockState)
89
-
90
- expect(prompt).toContain('## Parameters')
91
- expect(prompt).toContain('taskName: Test Task')
92
- expect(prompt).toContain('feature: Test Feature')
93
- })
94
-
95
- it('should exclude parameters section when empty', () => {
96
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
97
-
98
- // Should not include Parameters section since params is empty
99
- const hasParametersSection = prompt.includes('## Parameters')
100
- expect(hasParametersSection).toBe(false)
101
- })
102
-
103
- it('should include final execution instructions', () => {
104
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
105
-
106
- expect(prompt).toContain('## Execute')
107
- expect(prompt).toContain('execute the command')
108
- expect(prompt).toContain('Use ONLY the allowed tools')
109
- expect(prompt).toContain('do not follow rigid if/else rules')
110
- })
111
-
112
- it('should handle template without allowed-tools', () => {
113
- const templateNoTools = {
114
- frontmatter: {},
115
- content: 'Simple command',
116
- }
117
-
118
- const prompt = promptBuilder.build(templateNoTools, mockContext, mockState)
119
-
120
- expect(prompt).toBeDefined()
121
- expect(prompt).toContain('Simple command')
122
- // Should not have Allowed Tools section
123
- expect(prompt).not.toContain('## Allowed Tools')
124
- })
125
-
126
- it('should handle empty state', () => {
127
- const emptyState = {}
128
-
129
- const prompt = promptBuilder.build(mockTemplate, mockContext, emptyState)
130
-
131
- expect(prompt).toBeDefined()
132
- expect(prompt).toContain('## Current State')
133
- // But no actual state entries
134
- })
135
-
136
- it('should format state content in code blocks', () => {
137
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
138
-
139
- expect(prompt).toContain('```')
140
- expect(prompt).toMatch(/```\n# Current Task/)
141
- })
142
- })
143
-
144
- describe('buildAnalysis()', () => {
145
- it('should build analysis prompt', () => {
146
- const prompt = promptBuilder.buildAnalysis('repository', mockContext)
147
-
148
- expect(prompt).toBeDefined()
149
- expect(typeof prompt).toBe('string')
150
- })
151
-
152
- it('should include analysis type', () => {
153
- const prompt = promptBuilder.buildAnalysis('repository', mockContext)
154
-
155
- expect(prompt).toContain('# Analyze: repository')
156
- })
157
-
158
- it('should include project context', () => {
159
- const prompt = promptBuilder.buildAnalysis('repository', mockContext)
160
-
161
- expect(prompt).toContain('## Project Context')
162
- expect(prompt).toContain('Path: /test/path')
163
- expect(prompt).toContain('ID: test-project-123')
164
- })
165
-
166
- it('should include analysis instructions', () => {
167
- const prompt = promptBuilder.buildAnalysis('repository', mockContext)
168
-
169
- expect(prompt).toContain('Read the project context')
170
- expect(prompt).toContain('provide your analysis')
171
- expect(prompt).toContain('No predetermined patterns')
172
- })
173
-
174
- it('should work with different analysis types', () => {
175
- const types = ['repository', 'feature', 'bug', 'performance']
176
-
177
- types.forEach((type) => {
178
- const prompt = promptBuilder.buildAnalysis(type, mockContext)
179
- expect(prompt).toContain(`# Analyze: ${type}`)
180
- })
181
- })
182
- })
183
-
184
- describe('Prompt Structure', () => {
185
- it('should have clear sections in order', () => {
186
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
187
-
188
- const instructionsIndex = prompt.indexOf('# Command Instructions')
189
- const toolsIndex = prompt.indexOf('## Allowed Tools')
190
- const contextIndex = prompt.indexOf('## Project Context')
191
- const stateIndex = prompt.indexOf('## Current State')
192
- const executeIndex = prompt.indexOf('## Execute')
193
-
194
- expect(instructionsIndex).toBeGreaterThan(-1)
195
- expect(toolsIndex).toBeGreaterThan(instructionsIndex)
196
- expect(contextIndex).toBeGreaterThan(toolsIndex)
197
- expect(stateIndex).toBeGreaterThan(contextIndex)
198
- expect(executeIndex).toBeGreaterThan(stateIndex)
199
- })
200
-
201
- it('should use proper markdown formatting', () => {
202
- const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
203
-
204
- // Should have proper headings
205
- expect(prompt).toMatch(/^# /m)
206
- expect(prompt).toMatch(/^## /m)
207
-
208
- // Should have code blocks
209
- expect(prompt).toContain('```')
210
- })
211
- })
212
- })