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,204 +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
|
-
description: 'Execute test command',
|
|
9
|
-
},
|
|
10
|
-
content: '# Command: Test\n\nExecute test command.',
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const mockContext = {
|
|
14
|
-
projectId: 'test-project-123',
|
|
15
|
-
projectPath: '/test/path',
|
|
16
|
-
globalPath: '/global/path',
|
|
17
|
-
timestamp: '2025-10-04T12:00:00Z',
|
|
18
|
-
date: '2025-10-04',
|
|
19
|
-
params: {},
|
|
20
|
-
paths: {
|
|
21
|
-
now: '/global/path/core/now.md',
|
|
22
|
-
next: '/global/path/core/next.md',
|
|
23
|
-
},
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const mockState = {
|
|
27
|
-
now: '# Current Task\n\nTest task in progress',
|
|
28
|
-
next: '# Priority Queue\n\nTask 1, Task 2',
|
|
29
|
-
context: null, // Simulate non-existent file
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe('build()', () => {
|
|
33
|
-
it('should build a complete prompt', () => {
|
|
34
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
35
|
-
|
|
36
|
-
expect(prompt).toBeDefined()
|
|
37
|
-
expect(typeof prompt).toBe('string')
|
|
38
|
-
expect(prompt.length).toBeGreaterThan(0)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('should include command instructions', () => {
|
|
42
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
43
|
-
|
|
44
|
-
expect(prompt).toContain('TASK: Execute test command')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('should include allowed tools', () => {
|
|
48
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
49
|
-
|
|
50
|
-
expect(prompt).toContain('TOOLS: Read, Write, Bash')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('should include project context', () => {
|
|
54
|
-
// Context is now handled differently in the new prompt builder (filtered context)
|
|
55
|
-
// The prompt builder doesn't explicitly list project ID/Timestamp anymore in the main prompt
|
|
56
|
-
// It focuses on the task and tools
|
|
57
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
58
|
-
expect(prompt).toBeDefined()
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('should include current state', () => {
|
|
62
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
63
|
-
|
|
64
|
-
expect(prompt).toContain('STATE:')
|
|
65
|
-
expect(prompt).toContain('now: # Current Task')
|
|
66
|
-
expect(prompt).toContain('Test task in progress')
|
|
67
|
-
expect(prompt).toContain('next: # Priority Queue')
|
|
68
|
-
expect(prompt).toContain('Task 1, Task 2')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('should exclude null or empty state values', () => {
|
|
72
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
73
|
-
|
|
74
|
-
// Should not include 'context' since it's null
|
|
75
|
-
expect(prompt).not.toContain('context:')
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('should include parameters when present', () => {
|
|
79
|
-
const contextWithParams = {
|
|
80
|
-
...mockContext,
|
|
81
|
-
params: {
|
|
82
|
-
task: 'Test Task',
|
|
83
|
-
description: 'Test Feature',
|
|
84
|
-
},
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const prompt = promptBuilder.build(mockTemplate, contextWithParams, mockState)
|
|
88
|
-
|
|
89
|
-
expect(prompt).toContain('INPUT: Test Task')
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('should exclude parameters section when empty', () => {
|
|
93
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
94
|
-
|
|
95
|
-
// Should not include INPUT section since params is empty
|
|
96
|
-
const hasInputSection = prompt.includes('INPUT:')
|
|
97
|
-
expect(hasInputSection).toBe(false)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('should include final execution instructions', () => {
|
|
101
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
102
|
-
|
|
103
|
-
expect(prompt).toContain('## PROCESS ENFORCEMENT')
|
|
104
|
-
expect(prompt).toContain('FOLLOW the Flow strictly')
|
|
105
|
-
expect(prompt).toContain('EXECUTE: Follow flow. Use tools. Decide.')
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('should handle template without allowed-tools', () => {
|
|
109
|
-
const templateNoTools = {
|
|
110
|
-
frontmatter: {},
|
|
111
|
-
content: 'Simple command',
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const prompt = promptBuilder.build(templateNoTools, mockContext, mockState)
|
|
115
|
-
|
|
116
|
-
expect(prompt).toBeDefined()
|
|
117
|
-
expect(prompt).toContain('Simple command')
|
|
118
|
-
// Should not have TOOLS section
|
|
119
|
-
expect(prompt).not.toContain('TOOLS:')
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('should handle empty state', () => {
|
|
123
|
-
const emptyState = {}
|
|
124
|
-
|
|
125
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, emptyState)
|
|
126
|
-
|
|
127
|
-
expect(prompt).toBeDefined()
|
|
128
|
-
expect(prompt).not.toContain('STATE:')
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('should format state content', () => {
|
|
132
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
133
|
-
|
|
134
|
-
expect(prompt).toContain('STATE:')
|
|
135
|
-
expect(prompt).toContain('now: # Current Task')
|
|
136
|
-
})
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
describe('buildAnalysis()', () => {
|
|
140
|
-
it('should build analysis prompt', () => {
|
|
141
|
-
const prompt = promptBuilder.buildAnalysis('repository', mockContext)
|
|
142
|
-
|
|
143
|
-
expect(prompt).toBeDefined()
|
|
144
|
-
expect(typeof prompt).toBe('string')
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('should include analysis type', () => {
|
|
148
|
-
const prompt = promptBuilder.buildAnalysis('repository', mockContext)
|
|
149
|
-
|
|
150
|
-
expect(prompt).toContain('# Analyze: repository')
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('should include project context', () => {
|
|
154
|
-
const prompt = promptBuilder.buildAnalysis('repository', mockContext)
|
|
155
|
-
|
|
156
|
-
expect(prompt).toContain('## Project Context')
|
|
157
|
-
expect(prompt).toContain('Path: /test/path')
|
|
158
|
-
expect(prompt).toContain('ID: test-project-123')
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('should include analysis instructions', () => {
|
|
162
|
-
const prompt = promptBuilder.buildAnalysis('repository', mockContext)
|
|
163
|
-
|
|
164
|
-
expect(prompt).toContain('Read the project context')
|
|
165
|
-
expect(prompt).toContain('provide your analysis')
|
|
166
|
-
expect(prompt).toContain('No predetermined patterns')
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
it('should work with different analysis types', () => {
|
|
170
|
-
const types = ['repository', 'feature', 'bug', 'performance']
|
|
171
|
-
|
|
172
|
-
types.forEach((type) => {
|
|
173
|
-
const prompt = promptBuilder.buildAnalysis(type, mockContext)
|
|
174
|
-
expect(prompt).toContain(`# Analyze: ${type}`)
|
|
175
|
-
})
|
|
176
|
-
})
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
describe('Prompt Structure', () => {
|
|
180
|
-
it('should have clear sections in order', () => {
|
|
181
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
182
|
-
|
|
183
|
-
const taskIndex = prompt.indexOf('TASK:')
|
|
184
|
-
const toolsIndex = prompt.indexOf('TOOLS:')
|
|
185
|
-
const stateIndex = prompt.indexOf('STATE:')
|
|
186
|
-
const enforcementIndex = prompt.indexOf('## PROCESS ENFORCEMENT')
|
|
187
|
-
const executeIndex = prompt.indexOf('EXECUTE:')
|
|
188
|
-
|
|
189
|
-
expect(taskIndex).toBeGreaterThan(-1)
|
|
190
|
-
expect(toolsIndex).toBeGreaterThan(taskIndex)
|
|
191
|
-
expect(stateIndex).toBeGreaterThan(toolsIndex)
|
|
192
|
-
expect(enforcementIndex).toBeGreaterThan(stateIndex)
|
|
193
|
-
expect(executeIndex).toBeGreaterThan(enforcementIndex)
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('should use proper formatting', () => {
|
|
197
|
-
const prompt = promptBuilder.build(mockTemplate, mockContext, mockState)
|
|
198
|
-
|
|
199
|
-
// Should have proper headings
|
|
200
|
-
expect(prompt).toContain('TASK:')
|
|
201
|
-
expect(prompt).toContain('TOOLS:')
|
|
202
|
-
})
|
|
203
|
-
})
|
|
204
|
-
})
|
|
@@ -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
|
-
})
|