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,164 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest'
2
- import templateLoader from '../../agentic/template-loader.js'
3
-
4
- describe('Template Loader', () => {
5
- beforeEach(() => {
6
- templateLoader.clearCache()
7
- })
8
-
9
- describe('load()', () => {
10
- it('should load a template successfully', async () => {
11
- const template = await templateLoader.load('now')
12
-
13
- expect(template).toBeDefined()
14
- expect(template).toHaveProperty('content')
15
- expect(template).toHaveProperty('frontmatter')
16
- })
17
-
18
- it('should load multiple templates', async () => {
19
- const now = await templateLoader.load('now')
20
- const done = await templateLoader.load('done')
21
- const next = await templateLoader.load('next')
22
-
23
- expect(now).toBeDefined()
24
- expect(done).toBeDefined()
25
- expect(next).toBeDefined()
26
- })
27
-
28
- it('should throw error for non-existent template', async () => {
29
- await expect(templateLoader.load('nonexistent')).rejects.toThrow('Template not found: nonexistent.md')
30
- })
31
-
32
- it('should cache templates', async () => {
33
- const first = await templateLoader.load('now')
34
- const second = await templateLoader.load('now')
35
-
36
- // Should return the same cached object
37
- expect(first).toBe(second)
38
- })
39
- })
40
-
41
- describe('parseFrontmatter()', () => {
42
- it('should parse frontmatter correctly', () => {
43
- const content = `---
44
- allowed-tools: [Read, Write, Bash]
45
- category: core
46
- ---
47
- # Template Content
48
-
49
- This is the main content.`
50
-
51
- const parsed = templateLoader.parseFrontmatter(content)
52
-
53
- expect(parsed.frontmatter).toHaveProperty('allowed-tools')
54
- expect(parsed.frontmatter['allowed-tools']).toEqual(['Read', 'Write', 'Bash'])
55
- expect(parsed.frontmatter.category).toBe('core')
56
- expect(parsed.content).toContain('# Template Content')
57
- })
58
-
59
- it('should handle templates without frontmatter', () => {
60
- const content = `# Simple Template
61
-
62
- Just content, no frontmatter.`
63
-
64
- const parsed = templateLoader.parseFrontmatter(content)
65
-
66
- expect(parsed.frontmatter).toEqual({})
67
- expect(parsed.content).toContain('# Simple Template')
68
- })
69
-
70
- it('should parse string values', () => {
71
- const content = `---
72
- title: Test Command
73
- description: A test command
74
- ---
75
- Content`
76
-
77
- const parsed = templateLoader.parseFrontmatter(content)
78
-
79
- expect(parsed.frontmatter.title).toBe('Test Command')
80
- expect(parsed.frontmatter.description).toBe('A test command')
81
- })
82
-
83
- it('should handle quoted strings', () => {
84
- const content = `---
85
- title: "Quoted Title"
86
- description: 'Single Quoted'
87
- ---
88
- Content`
89
-
90
- const parsed = templateLoader.parseFrontmatter(content)
91
-
92
- expect(parsed.frontmatter.title).toBe('Quoted Title')
93
- expect(parsed.frontmatter.description).toBe('Single Quoted')
94
- })
95
-
96
- it('should parse array values', () => {
97
- const content = `---
98
- tools: [Read, Write, Exec]
99
- tags: [core, important]
100
- ---
101
- Content`
102
-
103
- const parsed = templateLoader.parseFrontmatter(content)
104
-
105
- expect(Array.isArray(parsed.frontmatter.tools)).toBe(true)
106
- expect(parsed.frontmatter.tools).toEqual(['Read', 'Write', 'Exec'])
107
- expect(parsed.frontmatter.tags).toEqual(['core', 'important'])
108
- })
109
- })
110
-
111
- describe('getAllowedTools()', () => {
112
- it('should return allowed tools from template', async () => {
113
- const tools = await templateLoader.getAllowedTools('now')
114
-
115
- expect(Array.isArray(tools)).toBe(true)
116
- })
117
-
118
- it('should return empty array if no allowed-tools defined', async () => {
119
- // Create a mock template without allowed-tools
120
- const content = `# Simple Template
121
- No tools defined`
122
-
123
- const parsed = templateLoader.parseFrontmatter(content)
124
- expect(parsed.frontmatter['allowed-tools'] || []).toEqual([])
125
- })
126
- })
127
-
128
- describe('clearCache()', () => {
129
- it('should clear the cache', async () => {
130
- // Load and cache
131
- await templateLoader.load('now')
132
-
133
- // Clear cache
134
- templateLoader.clearCache()
135
-
136
- // Load again - should read from file, not cache
137
- const template = await templateLoader.load('now')
138
- expect(template).toBeDefined()
139
- })
140
- })
141
-
142
- describe('Real Templates', () => {
143
- it('should load "now" template', async () => {
144
- const template = await templateLoader.load('now')
145
-
146
- expect(template.content).toBeTruthy()
147
- expect(template.content.length).toBeGreaterThan(0)
148
- })
149
-
150
- it('should load "done" template', async () => {
151
- const template = await templateLoader.load('done')
152
-
153
- expect(template.content).toBeTruthy()
154
- expect(template.content.length).toBeGreaterThan(0)
155
- })
156
-
157
- it('should load "ship" template', async () => {
158
- const template = await templateLoader.load('ship')
159
-
160
- expect(template.content).toBeTruthy()
161
- expect(template.content.length).toBeGreaterThan(0)
162
- })
163
- })
164
- })
@@ -1,243 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import toolRegistry from '../../agentic/tool-registry.js'
3
- import fs from 'fs/promises'
4
- import path from 'path'
5
- import os from 'os'
6
-
7
- describe('Tool Registry', () => {
8
- const testDir = path.join(os.tmpdir(), 'prjct-test-' + Date.now())
9
- const testFile = path.join(testDir, 'test.txt')
10
-
11
- beforeEach(async () => {
12
- await fs.mkdir(testDir, { recursive: true })
13
- })
14
-
15
- afterEach(async () => {
16
- try {
17
- await fs.rm(testDir, { recursive: true, force: true })
18
- } catch (error) {
19
- // Ignore cleanup errors
20
- }
21
- })
22
-
23
- describe('get()', () => {
24
- it('should get Read tool', () => {
25
- const tool = toolRegistry.get('Read')
26
-
27
- expect(tool).toBeDefined()
28
- expect(typeof tool).toBe('function')
29
- })
30
-
31
- it('should get Write tool', () => {
32
- const tool = toolRegistry.get('Write')
33
-
34
- expect(tool).toBeDefined()
35
- expect(typeof tool).toBe('function')
36
- })
37
-
38
- it('should get Bash tool', () => {
39
- const tool = toolRegistry.get('Bash')
40
-
41
- expect(tool).toBeDefined()
42
- expect(typeof tool).toBe('function')
43
- })
44
-
45
- it('should get Exec tool (alias for Bash)', () => {
46
- const tool = toolRegistry.get('Exec')
47
-
48
- expect(tool).toBeDefined()
49
- expect(typeof tool).toBe('function')
50
- })
51
-
52
- it('should throw error for unknown tool', () => {
53
- expect(() => toolRegistry.get('UnknownTool')).toThrow('Unknown tool: UnknownTool')
54
- })
55
- })
56
-
57
- describe('isAllowed()', () => {
58
- it('should return true for allowed tool', () => {
59
- const allowed = toolRegistry.isAllowed('Read', ['Read', 'Write', 'Bash'])
60
-
61
- expect(allowed).toBe(true)
62
- })
63
-
64
- it('should return false for disallowed tool', () => {
65
- const allowed = toolRegistry.isAllowed('Exec', ['Read', 'Write'])
66
-
67
- expect(allowed).toBe(false)
68
- })
69
-
70
- it('should handle empty allowed list', () => {
71
- const allowed = toolRegistry.isAllowed('Read', [])
72
-
73
- expect(allowed).toBe(false)
74
- })
75
- })
76
-
77
- describe('read()', () => {
78
- it('should read file content', async () => {
79
- const content = 'Test content for reading'
80
- await fs.writeFile(testFile, content)
81
-
82
- const result = await toolRegistry.read(testFile)
83
-
84
- expect(result).toBe(content)
85
- })
86
-
87
- it('should return null for non-existent file', async () => {
88
- const result = await toolRegistry.read('/nonexistent/file.txt')
89
-
90
- expect(result).toBeNull()
91
- })
92
-
93
- it('should read UTF-8 encoded content', async () => {
94
- const content = 'UTF-8: ñ é ü 中文 日本語'
95
- await fs.writeFile(testFile, content)
96
-
97
- const result = await toolRegistry.read(testFile)
98
-
99
- expect(result).toBe(content)
100
- })
101
- })
102
-
103
- describe('write()', () => {
104
- it('should write file content', async () => {
105
- const content = 'Test content for writing'
106
-
107
- await toolRegistry.write(testFile, content)
108
-
109
- const result = await fs.readFile(testFile, 'utf-8')
110
- expect(result).toBe(content)
111
- })
112
-
113
- it('should create directory if not exists', async () => {
114
- const nestedFile = path.join(testDir, 'nested', 'dir', 'file.txt')
115
- const content = 'Nested content'
116
-
117
- await toolRegistry.write(nestedFile, content)
118
-
119
- const result = await fs.readFile(nestedFile, 'utf-8')
120
- expect(result).toBe(content)
121
- })
122
-
123
- it('should overwrite existing file', async () => {
124
- await toolRegistry.write(testFile, 'First content')
125
- await toolRegistry.write(testFile, 'Second content')
126
-
127
- const result = await fs.readFile(testFile, 'utf-8')
128
- expect(result).toBe('Second content')
129
- })
130
- })
131
-
132
- describe('bash()', () => {
133
- it('should execute simple command', async () => {
134
- const result = await toolRegistry.bash('echo "hello"')
135
-
136
- expect(result.stdout).toContain('hello')
137
- expect(result.stderr).toBe('')
138
- })
139
-
140
- it('should return stdout and stderr', async () => {
141
- const result = await toolRegistry.bash('echo "output"')
142
-
143
- expect(result).toHaveProperty('stdout')
144
- expect(result).toHaveProperty('stderr')
145
- })
146
-
147
- it('should handle command errors', async () => {
148
- const result = await toolRegistry.bash('invalid-command-xyz-123')
149
-
150
- expect(result.error).toBe(true)
151
- expect(result.stderr).toBeTruthy()
152
- })
153
-
154
- it('should execute pwd command', async () => {
155
- const result = await toolRegistry.bash('pwd')
156
-
157
- expect(result.stdout).toBeTruthy()
158
- expect(result.stderr).toBe('')
159
- })
160
- })
161
-
162
- describe('exists()', () => {
163
- it('should return true for existing file', async () => {
164
- await fs.writeFile(testFile, 'content')
165
-
166
- const exists = await toolRegistry.exists(testFile)
167
-
168
- expect(exists).toBe(true)
169
- })
170
-
171
- it('should return false for non-existent file', async () => {
172
- const exists = await toolRegistry.exists('/nonexistent/file.txt')
173
-
174
- expect(exists).toBe(false)
175
- })
176
-
177
- it('should work with directories', async () => {
178
- const exists = await toolRegistry.exists(testDir)
179
-
180
- expect(exists).toBe(true)
181
- })
182
- })
183
-
184
- describe('list()', () => {
185
- it('should list directory contents', async () => {
186
- await fs.writeFile(path.join(testDir, 'file1.txt'), 'content1')
187
- await fs.writeFile(path.join(testDir, 'file2.txt'), 'content2')
188
-
189
- const files = await toolRegistry.list(testDir)
190
-
191
- expect(Array.isArray(files)).toBe(true)
192
- expect(files.length).toBeGreaterThanOrEqual(2)
193
- expect(files).toContain('file1.txt')
194
- expect(files).toContain('file2.txt')
195
- })
196
-
197
- it('should return empty array for non-existent directory', async () => {
198
- const files = await toolRegistry.list('/nonexistent/directory')
199
-
200
- expect(files).toEqual([])
201
- })
202
-
203
- it('should list empty directory', async () => {
204
- const emptyDir = path.join(testDir, 'empty')
205
- await fs.mkdir(emptyDir)
206
-
207
- const files = await toolRegistry.list(emptyDir)
208
-
209
- expect(Array.isArray(files)).toBe(true)
210
- expect(files.length).toBe(0)
211
- })
212
- })
213
-
214
- describe('Integration', () => {
215
- it('should write and then read file', async () => {
216
- const content = 'Integration test content'
217
-
218
- await toolRegistry.write(testFile, content)
219
- const result = await toolRegistry.read(testFile)
220
-
221
- expect(result).toBe(content)
222
- })
223
-
224
- it('should check existence, write, and list', async () => {
225
- const newFile = path.join(testDir, 'integration.txt')
226
-
227
- // Check doesn't exist
228
- const existsBefore = await toolRegistry.exists(newFile)
229
- expect(existsBefore).toBe(false)
230
-
231
- // Write
232
- await toolRegistry.write(newFile, 'content')
233
-
234
- // Check exists
235
- const existsAfter = await toolRegistry.exists(newFile)
236
- expect(existsAfter).toBe(true)
237
-
238
- // List directory
239
- const files = await toolRegistry.list(testDir)
240
- expect(files).toContain('integration.txt')
241
- })
242
- })
243
- })
@@ -1,296 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import AgentGenerator from '../../domain/agent-generator.js'
3
- import fs from 'fs/promises'
4
- import os from 'os'
5
- import path from 'path'
6
-
7
- describe('Agent Generator', () => {
8
- const testProjectId = 'test-agent-gen-' + Date.now()
9
- let generator
10
- let agentsDir
11
-
12
- beforeEach(() => {
13
- generator = new AgentGenerator(testProjectId)
14
- agentsDir = path.join(os.homedir(), '.prjct-cli', 'projects', testProjectId, 'agents')
15
- })
16
-
17
- afterEach(async () => {
18
- // Cleanup test files
19
- try {
20
- await fs.rm(agentsDir, { recursive: true, force: true })
21
- } catch (error) {
22
- // Ignore cleanup errors
23
- }
24
- })
25
-
26
- describe('Constructor', () => {
27
- it('should create generator with project ID', () => {
28
- expect(generator.projectId).toBe(testProjectId)
29
- expect(generator.outputDir).toContain(testProjectId)
30
- })
31
-
32
- it('should use fallback directory without project ID', () => {
33
- const fallbackGenerator = new AgentGenerator()
34
- expect(fallbackGenerator.outputDir).toContain('.prjct-cli/agents')
35
- expect(fallbackGenerator.outputDir).not.toContain('projects')
36
- })
37
-
38
- it('should construct correct output path', () => {
39
- expect(generator.outputDir).toBe(agentsDir)
40
- })
41
- })
42
-
43
- describe('generateDynamicAgent()', () => {
44
- it('should generate agent file', async () => {
45
- await generator.generateDynamicAgent('test-agent', {
46
- role: 'Test Agent Role',
47
- expertise: 'Test Technologies',
48
- responsibilities: 'Test Responsibilities',
49
- })
50
-
51
- const agentFile = path.join(agentsDir, 'test-agent.md')
52
- const exists = await fs
53
- .access(agentFile)
54
- .then(() => true)
55
- .catch(() => false)
56
-
57
- expect(exists).toBe(true)
58
- })
59
-
60
- it('should create agent with correct content', async () => {
61
- await generator.generateDynamicAgent('backend-agent', {
62
- role: 'Backend Developer',
63
- expertise: 'Node.js, Express, PostgreSQL',
64
- responsibilities: 'API development and database management',
65
- })
66
-
67
- const content = await fs.readFile(path.join(agentsDir, 'backend-agent.md'), 'utf-8')
68
-
69
- expect(content).toContain('# Backend Developer')
70
- expect(content).toContain('## Role')
71
- expect(content).toContain('Backend Developer')
72
- expect(content).toContain('## Expertise')
73
- expect(content).toContain('Node.js, Express, PostgreSQL')
74
- expect(content).toContain('## Responsibilities')
75
- expect(content).toContain('API development and database management')
76
- })
77
-
78
- it('should include project context in agent file', async () => {
79
- await generator.generateDynamicAgent('context-agent', {
80
- role: 'Agent with Context',
81
- expertise: 'Testing',
82
- responsibilities: 'Test things',
83
- projectContext: {
84
- framework: 'React',
85
- version: '18.0',
86
- },
87
- })
88
-
89
- const content = await fs.readFile(path.join(agentsDir, 'context-agent.md'), 'utf-8')
90
-
91
- expect(content).toContain('## Project Context')
92
- expect(content).toContain('framework')
93
- expect(content).toContain('React')
94
- expect(content).toContain('version')
95
- expect(content).toContain('18.0')
96
- })
97
-
98
- it('should handle missing optional fields', async () => {
99
- await generator.generateDynamicAgent('minimal-agent', {
100
- role: 'Minimal Role',
101
- })
102
-
103
- const content = await fs.readFile(path.join(agentsDir, 'minimal-agent.md'), 'utf-8')
104
-
105
- expect(content).toContain('# Minimal Role')
106
- expect(content).toContain('Technologies used in this project')
107
- expect(content).toContain('Handle specific aspects of development')
108
- expect(content).toContain('No additional context')
109
- })
110
-
111
- it('should create output directory if not exists', async () => {
112
- const newProjectId = 'new-project-' + Date.now()
113
- const newGenerator = new AgentGenerator(newProjectId)
114
- const newAgentsDir = path.join(os.homedir(), '.prjct-cli', 'projects', newProjectId, 'agents')
115
-
116
- await newGenerator.generateDynamicAgent('auto-create', {
117
- role: 'Auto Created Agent',
118
- })
119
-
120
- const exists = await fs
121
- .access(newAgentsDir)
122
- .then(() => true)
123
- .catch(() => false)
124
-
125
- expect(exists).toBe(true)
126
-
127
- // Cleanup
128
- await fs.rm(path.join(os.homedir(), '.prjct-cli', 'projects', newProjectId), {
129
- recursive: true,
130
- force: true,
131
- })
132
- })
133
-
134
- it('should create multiple agents', async () => {
135
- await generator.generateDynamicAgent('agent-1', { role: 'Agent One' })
136
- await generator.generateDynamicAgent('agent-2', { role: 'Agent Two' })
137
- await generator.generateDynamicAgent('agent-3', { role: 'Agent Three' })
138
-
139
- const agents = await generator.listAgents()
140
-
141
- expect(agents).toHaveLength(3)
142
- expect(agents).toContain('agent-1')
143
- expect(agents).toContain('agent-2')
144
- expect(agents).toContain('agent-3')
145
- })
146
-
147
- it('should use agent name as fallback for role', async () => {
148
- await generator.generateDynamicAgent('fallback-agent', {})
149
-
150
- const content = await fs.readFile(path.join(agentsDir, 'fallback-agent.md'), 'utf-8')
151
-
152
- expect(content).toContain('# fallback-agent')
153
- })
154
- })
155
-
156
- describe('cleanupObsoleteAgents()', () => {
157
- beforeEach(async () => {
158
- // Create some test agents
159
- await generator.generateDynamicAgent('agent-1', { role: 'Agent 1' })
160
- await generator.generateDynamicAgent('agent-2', { role: 'Agent 2' })
161
- await generator.generateDynamicAgent('agent-3', { role: 'Agent 3' })
162
- })
163
-
164
- it('should remove obsolete agents', async () => {
165
- const removed = await generator.cleanupObsoleteAgents(['agent-1', 'agent-2'])
166
-
167
- expect(removed).toContain('agent-3')
168
- expect(removed).toHaveLength(1)
169
- })
170
-
171
- it('should keep required agents', async () => {
172
- await generator.cleanupObsoleteAgents(['agent-1', 'agent-2'])
173
-
174
- const agents = await generator.listAgents()
175
-
176
- expect(agents).toContain('agent-1')
177
- expect(agents).toContain('agent-2')
178
- expect(agents).not.toContain('agent-3')
179
- })
180
-
181
- it('should remove multiple obsolete agents', async () => {
182
- await generator.generateDynamicAgent('agent-4', { role: 'Agent 4' })
183
-
184
- const removed = await generator.cleanupObsoleteAgents(['agent-1'])
185
-
186
- expect(removed).toHaveLength(3)
187
- expect(removed).toContain('agent-2')
188
- expect(removed).toContain('agent-3')
189
- expect(removed).toContain('agent-4')
190
- })
191
-
192
- it('should return empty array if all agents are required', async () => {
193
- const removed = await generator.cleanupObsoleteAgents(['agent-1', 'agent-2', 'agent-3'])
194
-
195
- expect(removed).toEqual([])
196
- })
197
-
198
- it('should handle non-existent directory gracefully', async () => {
199
- const emptyGenerator = new AgentGenerator('empty-' + Date.now())
200
-
201
- const removed = await emptyGenerator.cleanupObsoleteAgents(['agent-1'])
202
-
203
- expect(Array.isArray(removed)).toBe(true)
204
- })
205
- })
206
-
207
- describe('listAgents()', () => {
208
- it('should list all agents', async () => {
209
- await generator.generateDynamicAgent('frontend', { role: 'Frontend' })
210
- await generator.generateDynamicAgent('backend', { role: 'Backend' })
211
-
212
- const agents = await generator.listAgents()
213
-
214
- expect(agents).toHaveLength(2)
215
- expect(agents).toContain('frontend')
216
- expect(agents).toContain('backend')
217
- })
218
-
219
- it('should return empty array for no agents', async () => {
220
- const agents = await generator.listAgents()
221
-
222
- expect(agents).toEqual([])
223
- })
224
-
225
- it('should ignore non-.md files', async () => {
226
- await generator.generateDynamicAgent('valid-agent', { role: 'Valid' })
227
- await fs.writeFile(path.join(agentsDir, 'not-agent.txt'), 'text file')
228
- await fs.writeFile(path.join(agentsDir, 'config.json'), '{}')
229
-
230
- const agents = await generator.listAgents()
231
-
232
- expect(agents).toHaveLength(1)
233
- expect(agents).toContain('valid-agent')
234
- })
235
-
236
- it('should ignore hidden files', async () => {
237
- await generator.generateDynamicAgent('visible', { role: 'Visible' })
238
- await fs.writeFile(path.join(agentsDir, '.hidden.md'), 'hidden')
239
-
240
- const agents = await generator.listAgents()
241
-
242
- expect(agents).toHaveLength(1)
243
- expect(agents).toContain('visible')
244
- })
245
- })
246
-
247
- describe('Integration', () => {
248
- it('should create, list, and cleanup agents', async () => {
249
- // Create agents
250
- await generator.generateDynamicAgent('keep-me', { role: 'Keep' })
251
- await generator.generateDynamicAgent('remove-me', { role: 'Remove' })
252
-
253
- // Verify they exist
254
- let agents = await generator.listAgents()
255
- expect(agents).toHaveLength(2)
256
-
257
- // Cleanup obsolete
258
- const removed = await generator.cleanupObsoleteAgents(['keep-me'])
259
- expect(removed).toContain('remove-me')
260
-
261
- // Verify cleanup
262
- agents = await generator.listAgents()
263
- expect(agents).toHaveLength(1)
264
- expect(agents).toContain('keep-me')
265
- })
266
-
267
- it('should handle agent file content correctly', async () => {
268
- await generator.generateDynamicAgent('full-agent', {
269
- role: 'Full Stack Developer',
270
- expertise: 'React, Node.js, PostgreSQL, Docker',
271
- responsibilities: 'Build and deploy full stack applications',
272
- projectContext: {
273
- stack: 'MERN',
274
- deployment: 'AWS',
275
- },
276
- })
277
-
278
- const content = await fs.readFile(path.join(agentsDir, 'full-agent.md'), 'utf-8')
279
-
280
- // Should have all sections
281
- expect(content).toContain('# Full Stack Developer')
282
- expect(content).toContain('## Role')
283
- expect(content).toContain('## Expertise')
284
- expect(content).toContain('## Responsibilities')
285
- expect(content).toContain('## Project Context')
286
- expect(content).toContain('## Guidelines')
287
-
288
- // Should have all content
289
- expect(content).toContain('Full Stack Developer')
290
- expect(content).toContain('React, Node.js, PostgreSQL, Docker')
291
- expect(content).toContain('Build and deploy full stack applications')
292
- expect(content).toContain('MERN')
293
- expect(content).toContain('AWS')
294
- })
295
- })
296
- })