prjct-cli 0.10.0 → 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.
- package/CHANGELOG.md +31 -0
- package/core/__tests__/agentic/memory-system.test.js +263 -0
- package/core/__tests__/agentic/plan-mode.test.js +336 -0
- package/core/agentic/chain-of-thought.js +578 -0
- package/core/agentic/command-executor.js +238 -4
- package/core/agentic/context-builder.js +208 -8
- package/core/agentic/ground-truth.js +591 -0
- package/core/agentic/loop-detector.js +406 -0
- package/core/agentic/memory-system.js +850 -0
- package/core/agentic/parallel-tools.js +366 -0
- package/core/agentic/plan-mode.js +572 -0
- package/core/agentic/prompt-builder.js +76 -1
- package/core/agentic/response-templates.js +290 -0
- package/core/agentic/semantic-compression.js +517 -0
- package/core/agentic/think-blocks.js +657 -0
- package/core/agentic/tool-registry.js +32 -0
- package/core/agentic/validation-rules.js +380 -0
- package/core/command-registry.js +48 -0
- package/core/commands.js +43 -1
- package/core/context-sync.js +183 -0
- package/package.json +7 -15
- package/templates/commands/done.md +7 -0
- package/templates/commands/feature.md +8 -0
- package/templates/commands/ship.md +8 -0
- package/templates/commands/spec.md +128 -0
- package/templates/global/CLAUDE.md +17 -0
- package/core/__tests__/agentic/agent-router.test.js +0 -398
- package/core/__tests__/agentic/command-executor.test.js +0 -223
- package/core/__tests__/agentic/context-builder.test.js +0 -160
- package/core/__tests__/agentic/context-filter.test.js +0 -494
- package/core/__tests__/agentic/prompt-builder.test.js +0 -204
- package/core/__tests__/agentic/template-loader.test.js +0 -164
- package/core/__tests__/agentic/tool-registry.test.js +0 -243
- package/core/__tests__/domain/agent-generator.test.js +0 -289
- package/core/__tests__/domain/agent-loader.test.js +0 -179
- package/core/__tests__/domain/analyzer.test.js +0 -324
- package/core/__tests__/infrastructure/author-detector.test.js +0 -103
- package/core/__tests__/infrastructure/config-manager.test.js +0 -454
- package/core/__tests__/infrastructure/path-manager.test.js +0 -412
- package/core/__tests__/setup.test.js +0 -15
- package/core/__tests__/utils/date-helper.test.js +0 -169
- package/core/__tests__/utils/file-helper.test.js +0 -258
- package/core/__tests__/utils/jsonl-helper.test.js +0 -387
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import contextBuilder from '../../agentic/context-builder.js'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
|
|
5
|
-
describe('Context Builder', () => {
|
|
6
|
-
const testProjectPath = process.cwd()
|
|
7
|
-
|
|
8
|
-
describe('build()', () => {
|
|
9
|
-
it('should build context with all required fields', async () => {
|
|
10
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
11
|
-
|
|
12
|
-
expect(context).toBeDefined()
|
|
13
|
-
expect(context).toHaveProperty('projectId')
|
|
14
|
-
expect(context).toHaveProperty('projectPath')
|
|
15
|
-
expect(context).toHaveProperty('globalPath')
|
|
16
|
-
expect(context).toHaveProperty('paths')
|
|
17
|
-
expect(context).toHaveProperty('params')
|
|
18
|
-
expect(context).toHaveProperty('timestamp')
|
|
19
|
-
expect(context).toHaveProperty('date')
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('should include all file paths', async () => {
|
|
23
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
24
|
-
|
|
25
|
-
expect(context.paths).toHaveProperty('now')
|
|
26
|
-
expect(context.paths).toHaveProperty('next')
|
|
27
|
-
expect(context.paths).toHaveProperty('context')
|
|
28
|
-
expect(context.paths).toHaveProperty('shipped')
|
|
29
|
-
expect(context.paths).toHaveProperty('metrics')
|
|
30
|
-
expect(context.paths).toHaveProperty('ideas')
|
|
31
|
-
expect(context.paths).toHaveProperty('roadmap')
|
|
32
|
-
expect(context.paths).toHaveProperty('memory')
|
|
33
|
-
expect(context.paths).toHaveProperty('analysis')
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('should use correct project path', async () => {
|
|
37
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
38
|
-
|
|
39
|
-
expect(context.projectPath).toBe(testProjectPath)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('should include command parameters', async () => {
|
|
43
|
-
const params = { taskName: 'test task', feature: 'test feature' }
|
|
44
|
-
const context = await contextBuilder.build(testProjectPath, params)
|
|
45
|
-
|
|
46
|
-
expect(context.params).toEqual(params)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('should include timestamp', async () => {
|
|
50
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
51
|
-
|
|
52
|
-
expect(context.timestamp).toBeDefined()
|
|
53
|
-
expect(typeof context.timestamp).toBe('string')
|
|
54
|
-
expect(new Date(context.timestamp).toString()).not.toBe('Invalid Date')
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('should include date', async () => {
|
|
58
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
59
|
-
|
|
60
|
-
expect(context.date).toBeDefined()
|
|
61
|
-
expect(typeof context.date).toBe('string')
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('should build global path from project ID', async () => {
|
|
65
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
66
|
-
|
|
67
|
-
expect(context.globalPath).toContain('.prjct-cli')
|
|
68
|
-
expect(context.globalPath).toContain('projects')
|
|
69
|
-
expect(context.globalPath).toContain(context.projectId)
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
describe('loadState()', () => {
|
|
74
|
-
it('should load state from context', async () => {
|
|
75
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
76
|
-
const state = await contextBuilder.loadState(context)
|
|
77
|
-
|
|
78
|
-
expect(state).toBeDefined()
|
|
79
|
-
expect(typeof state).toBe('object')
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('should return null for non-existent files', async () => {
|
|
83
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
84
|
-
const state = await contextBuilder.loadState(context)
|
|
85
|
-
|
|
86
|
-
// Some files might not exist
|
|
87
|
-
Object.values(state).forEach((value) => {
|
|
88
|
-
expect(value === null || typeof value === 'string').toBe(true)
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('should load existing files as strings', async () => {
|
|
93
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
94
|
-
const state = await contextBuilder.loadState(context)
|
|
95
|
-
|
|
96
|
-
// At least some state values should be strings (if files exist)
|
|
97
|
-
const hasStrings = Object.values(state).some((value) => typeof value === 'string')
|
|
98
|
-
expect(typeof hasStrings).toBe('boolean')
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
describe('fileExists()', () => {
|
|
103
|
-
it('should return true for existing file', async () => {
|
|
104
|
-
const exists = await contextBuilder.fileExists(__filename)
|
|
105
|
-
|
|
106
|
-
expect(exists).toBe(true)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('should return false for non-existent file', async () => {
|
|
110
|
-
const exists = await contextBuilder.fileExists('/nonexistent/path/file.txt')
|
|
111
|
-
|
|
112
|
-
expect(exists).toBe(false)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it('should work with package.json', async () => {
|
|
116
|
-
const packagePath = path.join(process.cwd(), 'package.json')
|
|
117
|
-
const exists = await contextBuilder.fileExists(packagePath)
|
|
118
|
-
|
|
119
|
-
expect(exists).toBe(true)
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
describe('Path Construction', () => {
|
|
124
|
-
it('should construct paths with correct structure', async () => {
|
|
125
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
126
|
-
|
|
127
|
-
expect(context.paths.now).toContain('core/now.md')
|
|
128
|
-
expect(context.paths.next).toContain('core/next.md')
|
|
129
|
-
expect(context.paths.context).toContain('core/context.md')
|
|
130
|
-
expect(context.paths.shipped).toContain('progress/shipped.md')
|
|
131
|
-
expect(context.paths.metrics).toContain('progress/metrics.md')
|
|
132
|
-
expect(context.paths.ideas).toContain('planning/ideas.md')
|
|
133
|
-
expect(context.paths.roadmap).toContain('planning/roadmap.md')
|
|
134
|
-
expect(context.paths.memory).toContain('memory/context.jsonl')
|
|
135
|
-
expect(context.paths.analysis).toContain('analysis/repo-summary.md')
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('should use global path for all file paths', async () => {
|
|
139
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
140
|
-
|
|
141
|
-
Object.values(context.paths).forEach((filePath) => {
|
|
142
|
-
expect(filePath).toContain(context.globalPath)
|
|
143
|
-
})
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
describe('Empty Parameters', () => {
|
|
148
|
-
it('should handle empty command params', async () => {
|
|
149
|
-
const context = await contextBuilder.build(testProjectPath, {})
|
|
150
|
-
|
|
151
|
-
expect(context.params).toEqual({})
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('should handle undefined command params', async () => {
|
|
155
|
-
const context = await contextBuilder.build(testProjectPath)
|
|
156
|
-
|
|
157
|
-
expect(context.params).toEqual({})
|
|
158
|
-
})
|
|
159
|
-
})
|
|
160
|
-
})
|
|
@@ -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 300 files
|
|
356
|
-
expect(files.length).toBeLessThanOrEqual(300)
|
|
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
|
-
|