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.
- package/CHANGELOG.md +142 -0
- package/core/__tests__/agentic/memory-system.test.js +263 -0
- package/core/__tests__/agentic/plan-mode.test.js +336 -0
- package/core/agentic/agent-router.js +253 -186
- package/core/agentic/chain-of-thought.js +578 -0
- package/core/agentic/command-executor.js +299 -17
- package/core/agentic/context-builder.js +208 -8
- package/core/agentic/context-filter.js +83 -83
- 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 +127 -2
- 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 +128 -60
- package/core/context-sync.js +183 -0
- package/core/domain/agent-generator.js +77 -46
- package/core/domain/agent-loader.js +183 -0
- package/core/domain/agent-matcher.js +217 -0
- package/core/domain/agent-validator.js +217 -0
- package/core/domain/context-estimator.js +175 -0
- package/core/domain/product-standards.js +92 -0
- package/core/domain/smart-cache.js +157 -0
- package/core/domain/task-analyzer.js +353 -0
- package/core/domain/tech-detector.js +365 -0
- package/package.json +8 -16
- 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 -212
- 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 -296
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prjct-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Built for Claude - Ship fast, track progress, stay focused. Developer momentum tool for indie hackers.",
|
|
5
5
|
"main": "core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,18 +15,14 @@
|
|
|
15
15
|
"update-commands": "node -e \"const installer = require('./core/infrastructure/command-installer'); installer.syncCommands().then(r => console.log('Commands updated:', r)).catch(e => console.error('Error:', e.message))\"",
|
|
16
16
|
"install-global": "./scripts/install.sh",
|
|
17
17
|
"update": "./scripts/update.sh",
|
|
18
|
-
"test": "vitest run
|
|
19
|
-
"test:watch": "vitest
|
|
20
|
-
"test:coverage": "vitest run --coverage
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"test:coverage": "vitest run --coverage",
|
|
21
21
|
"validate": "node scripts/validate-commands.js",
|
|
22
|
-
"lint": "eslint
|
|
23
|
-
"lint:fix": "eslint
|
|
22
|
+
"lint": "eslint . --ignore-pattern node_modules",
|
|
23
|
+
"lint:fix": "eslint . --fix --ignore-pattern node_modules",
|
|
24
24
|
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\" --config .config/.prettierrc --ignore-path .config/.prettierignore",
|
|
25
|
-
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,md}\" --config .config/.prettierrc --ignore-path .config/.prettierignore"
|
|
26
|
-
"website:dev": "cd website && npm run dev",
|
|
27
|
-
"website:build": "cd website && npm run build",
|
|
28
|
-
"website:preview": "cd website && npm run preview",
|
|
29
|
-
"website:install": "cd website && npm install"
|
|
25
|
+
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,md}\" --config .config/.prettierrc --ignore-path .config/.prettierignore"
|
|
30
26
|
},
|
|
31
27
|
"keywords": [
|
|
32
28
|
"claude-code",
|
|
@@ -48,7 +44,6 @@
|
|
|
48
44
|
"prompts": "^2.4.2"
|
|
49
45
|
},
|
|
50
46
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^20.0.0",
|
|
52
47
|
"@vitest/coverage-v8": "^3.2.4",
|
|
53
48
|
"eslint": "^8.57.1",
|
|
54
49
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -56,10 +51,7 @@
|
|
|
56
51
|
"eslint-plugin-import": "^2.32.0",
|
|
57
52
|
"eslint-plugin-n": "^16.6.2",
|
|
58
53
|
"eslint-plugin-promise": "^6.6.0",
|
|
59
|
-
"jsdom": "^27.0.0",
|
|
60
54
|
"prettier": "^3.6.2",
|
|
61
|
-
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
62
|
-
"typescript": "^5.0.0",
|
|
63
55
|
"vitest": "^3.2.4"
|
|
64
56
|
},
|
|
65
57
|
"repository": {
|
|
@@ -88,4 +80,4 @@
|
|
|
88
80
|
"chalk",
|
|
89
81
|
"prompts"
|
|
90
82
|
]
|
|
91
|
-
}
|
|
83
|
+
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
allowed-tools: [Read, Write]
|
|
3
3
|
description: 'Complete task'
|
|
4
|
+
think-triggers: [report_complete]
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# /p:done
|
|
7
8
|
|
|
9
|
+
## Think First
|
|
10
|
+
Before marking complete, verify:
|
|
11
|
+
1. Is the task actually finished?
|
|
12
|
+
2. Were all acceptance criteria met?
|
|
13
|
+
3. Should this trigger a /p:ship?
|
|
14
|
+
|
|
8
15
|
## Check
|
|
9
16
|
Requires: `core/now.md` has content
|
|
10
17
|
|
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
allowed-tools: [Read, Write, Bash, GetTimestamp, GetDate]
|
|
3
3
|
description: 'Value analysis + roadmap + task breakdown + auto-start'
|
|
4
4
|
timestamp-rule: 'GetTimestamp() and GetDate() for ALL timestamps'
|
|
5
|
+
think-triggers: [explore_to_edit, complex_analysis]
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# /p:feature
|
|
8
9
|
|
|
10
|
+
## Think First
|
|
11
|
+
Before creating feature, analyze:
|
|
12
|
+
1. Is this a simple or complex feature?
|
|
13
|
+
2. What files/components will be affected?
|
|
14
|
+
3. Are there dependencies or blockers?
|
|
15
|
+
4. Should I create a /p:spec first?
|
|
16
|
+
|
|
9
17
|
## Flow
|
|
10
18
|
|
|
11
19
|
1. **Value**: Impact/effort/timing → do_now/defer/blocked
|
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
allowed-tools: [Read, Write, Bash, GetTimestamp, GetDate]
|
|
3
3
|
description: 'Ship feature workflow'
|
|
4
4
|
timestamp-rule: 'GetTimestamp() and GetDate() for timestamps'
|
|
5
|
+
think-triggers: [git_decision, report_complete]
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# /p:ship
|
|
8
9
|
|
|
10
|
+
## Think First
|
|
11
|
+
Before shipping, verify:
|
|
12
|
+
1. Are there uncommitted changes to include?
|
|
13
|
+
2. What version bump is needed (patch/minor/major)?
|
|
14
|
+
3. Is the feature actually complete?
|
|
15
|
+
4. Should tests run first?
|
|
16
|
+
|
|
9
17
|
## Workflow (non-blocking)
|
|
10
18
|
1. Lint → Tests → Docs update
|
|
11
19
|
2. Version bump → CHANGELOG
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
allowed-tools: [Read, Write, Glob, GetTimestamp, GetDate]
|
|
3
|
+
description: 'Spec-driven development for complex features'
|
|
4
|
+
timestamp-rule: 'GetTimestamp() and GetDate() for ALL timestamps'
|
|
5
|
+
think-triggers: [explore_to_edit, complex_analysis]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /p:spec
|
|
9
|
+
|
|
10
|
+
Spec-Driven Development. Creates detailed specifications for complex features before implementation.
|
|
11
|
+
|
|
12
|
+
## Think First
|
|
13
|
+
Before creating spec, analyze:
|
|
14
|
+
1. Is this feature complex enough for a spec? (auth, payments, migrations = yes)
|
|
15
|
+
2. What are the key architectural decisions to make?
|
|
16
|
+
3. Are there multiple valid approaches? Document tradeoffs.
|
|
17
|
+
4. What questions should I ask the user before proceeding?
|
|
18
|
+
|
|
19
|
+
## Purpose
|
|
20
|
+
|
|
21
|
+
For features that require:
|
|
22
|
+
- Clear requirements before coding
|
|
23
|
+
- Design decisions documented
|
|
24
|
+
- Tasks broken into 20-30 min chunks
|
|
25
|
+
- User approval before starting
|
|
26
|
+
|
|
27
|
+
## Flow
|
|
28
|
+
|
|
29
|
+
### No params: Show template
|
|
30
|
+
```
|
|
31
|
+
→ Interactive spec template
|
|
32
|
+
→ Ask for feature name
|
|
33
|
+
→ Guide through requirements
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### With feature name: Create spec
|
|
37
|
+
```
|
|
38
|
+
/p:spec "Dark Mode"
|
|
39
|
+
1. Analyze: Context, patterns, dependencies
|
|
40
|
+
2. Propose: Requirements + Design + Tasks
|
|
41
|
+
3. Write: `planning/specs/{slug}.md`
|
|
42
|
+
4. Ask: User approval
|
|
43
|
+
5. On approve: Add tasks to queue, start first
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Spec Structure
|
|
47
|
+
|
|
48
|
+
```markdown
|
|
49
|
+
# Feature Spec: {name}
|
|
50
|
+
|
|
51
|
+
**Created**: {GetDate()}
|
|
52
|
+
**Status**: PENDING_APPROVAL | APPROVED | IN_PROGRESS | COMPLETED
|
|
53
|
+
|
|
54
|
+
## Requirements (User approves)
|
|
55
|
+
- [ ] Requirement 1
|
|
56
|
+
- [ ] Requirement 2
|
|
57
|
+
- [ ] Requirement 3
|
|
58
|
+
|
|
59
|
+
## Design (Claude proposes)
|
|
60
|
+
- **Approach**: {architecture decision}
|
|
61
|
+
- **Key decisions**: {list}
|
|
62
|
+
- **Dependencies**: {existing code/libs}
|
|
63
|
+
|
|
64
|
+
## Tasks (20-30min each)
|
|
65
|
+
1. [ ] Task 1 (20m) - {description}
|
|
66
|
+
2. [ ] Task 2 (25m) - {description}
|
|
67
|
+
3. [ ] Task 3 (30m) - {description}
|
|
68
|
+
|
|
69
|
+
**Total**: {n} tasks, ~{Xh}
|
|
70
|
+
|
|
71
|
+
## Notes
|
|
72
|
+
- {implementation notes}
|
|
73
|
+
- {edge cases to consider}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Validation
|
|
77
|
+
|
|
78
|
+
- Feature name required for creation
|
|
79
|
+
- Spec must have at least 1 requirement
|
|
80
|
+
- Each task should be 20-30 minutes
|
|
81
|
+
- Check for existing spec with same name
|
|
82
|
+
|
|
83
|
+
## Data
|
|
84
|
+
|
|
85
|
+
Session: `{"ts":"{GetTimestamp()}","type":"spec_create","name":"{feature}","requirements":{n},"tasks":{n},"effort":"{Xh}"}`
|
|
86
|
+
Spec file: `planning/specs/{feature-slug}.md`
|
|
87
|
+
|
|
88
|
+
## Response
|
|
89
|
+
|
|
90
|
+
### On creation:
|
|
91
|
+
```
|
|
92
|
+
📋 Spec: {feature}
|
|
93
|
+
|
|
94
|
+
Requirements ({n}):
|
|
95
|
+
{numbered_list}
|
|
96
|
+
|
|
97
|
+
Design:
|
|
98
|
+
→ {approach}
|
|
99
|
+
→ {key_decision}
|
|
100
|
+
|
|
101
|
+
Tasks ({n}, ~{total_time}):
|
|
102
|
+
{numbered_list}
|
|
103
|
+
|
|
104
|
+
APPROVE? (y/n/edit)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### On approval:
|
|
108
|
+
```
|
|
109
|
+
✅ Spec approved: {feature}
|
|
110
|
+
→ {n} tasks added to queue
|
|
111
|
+
→ Starting: {task_1}
|
|
112
|
+
|
|
113
|
+
Use /p:done when complete
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Examples
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
/p:spec "User Authentication"
|
|
120
|
+
→ Creates spec with OAuth/JWT decisions
|
|
121
|
+
→ Breaks into: setup, login, logout, session, tests
|
|
122
|
+
→ Estimates ~4h total
|
|
123
|
+
|
|
124
|
+
/p:spec "Dark Mode"
|
|
125
|
+
→ Creates spec with theme approach
|
|
126
|
+
→ Breaks into: toggle, state, styles, persist, test
|
|
127
|
+
→ Estimates ~3h total
|
|
128
|
+
```
|
|
@@ -5,6 +5,23 @@ This section provides global context for all `/p:*` commands across any prjct pr
|
|
|
5
5
|
|
|
6
6
|
**Auto-managed by prjct-cli** - This section is automatically updated when you install or update prjct.
|
|
7
7
|
|
|
8
|
+
## 🤖 Project Context (OBLIGATORIO)
|
|
9
|
+
|
|
10
|
+
**ANTES de trabajar en un proyecto prjct, LEE el contexto del proyecto:**
|
|
11
|
+
|
|
12
|
+
1. Lee `.prjct/prjct.config.json` → obtén `projectId`
|
|
13
|
+
2. Lee `~/.prjct-cli/projects/{projectId}/CLAUDE.md` → contexto dinámico del proyecto
|
|
14
|
+
3. Para detalles de implementación, lee los archivos en `agents/`
|
|
15
|
+
|
|
16
|
+
El archivo `CLAUDE.md` del proyecto contiene:
|
|
17
|
+
- Stack detectado del proyecto
|
|
18
|
+
- Agentes disponibles (varían por proyecto)
|
|
19
|
+
- Tarea actual
|
|
20
|
+
- Cola de prioridades
|
|
21
|
+
- Rutas a documentación detallada
|
|
22
|
+
|
|
23
|
+
**Si no existe CLAUDE.md**: Sugiere ejecutar `/p:sync` para generarlo.
|
|
24
|
+
|
|
8
25
|
## 🎯 Path Resolution for ALL /p:* Commands
|
|
9
26
|
|
|
10
27
|
**CRITICAL**: Every `/p:*` command operates on **global storage**, NOT local files.
|
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } 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
|
-
const require = createRequire(import.meta.url)
|
|
8
|
-
|
|
9
|
-
describe('Mandatory Agent Router', () => {
|
|
10
|
-
let MandatoryAgentRouter
|
|
11
|
-
let AgentGenerator
|
|
12
|
-
let router
|
|
13
|
-
let mockAgentGenerator
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
MandatoryAgentRouter = require('../../agentic/agent-router.js')
|
|
17
|
-
AgentGenerator = require('../../domain/agent-generator.js')
|
|
18
|
-
|
|
19
|
-
// Mock agent generator
|
|
20
|
-
mockAgentGenerator = {
|
|
21
|
-
generateDynamicAgent: vi.fn().mockResolvedValue({
|
|
22
|
-
name: 'test-agent',
|
|
23
|
-
type: 'frontend-specialist',
|
|
24
|
-
domain: 'frontend',
|
|
25
|
-
confidence: 0.9
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
router = new MandatoryAgentRouter()
|
|
30
|
-
router.agentGenerator = mockAgentGenerator
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
describe('Constructor', () => {
|
|
34
|
-
it('should initialize with agent generator', () => {
|
|
35
|
-
expect(router.agentGenerator).toBeDefined()
|
|
36
|
-
expect(router.agentCache).toBeInstanceOf(Map)
|
|
37
|
-
expect(Array.isArray(router.usageLog)).toBe(true)
|
|
38
|
-
})
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
describe('executeTask()', () => {
|
|
42
|
-
it('should assign agent to task', async () => {
|
|
43
|
-
const task = { description: 'create login component', type: 'ui' }
|
|
44
|
-
const context = { projectPath: '/test', files: [] }
|
|
45
|
-
const projectPath = '/test'
|
|
46
|
-
|
|
47
|
-
const result = await router.executeTask(task, context, projectPath)
|
|
48
|
-
|
|
49
|
-
expect(result).toBeDefined()
|
|
50
|
-
expect(result.agent).toBeDefined()
|
|
51
|
-
expect(result.agent.name).toBe('test-agent')
|
|
52
|
-
expect(result.context).toBeDefined()
|
|
53
|
-
expect(result.taskAnalysis).toBeDefined()
|
|
54
|
-
expect(result.routing).toBeDefined()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('should throw error if no agent can be assigned', async () => {
|
|
58
|
-
router.agentGenerator.generateDynamicAgent = vi.fn().mockResolvedValue(null)
|
|
59
|
-
|
|
60
|
-
const task = { description: 'unknown task' }
|
|
61
|
-
const context = { projectPath: '/test' }
|
|
62
|
-
const projectPath = '/test'
|
|
63
|
-
|
|
64
|
-
await expect(router.executeTask(task, context, projectPath)).rejects.toThrow('CRITICAL: No agent assigned')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('should cache agents for reuse', async () => {
|
|
68
|
-
const task = { description: 'create component', type: 'ui' }
|
|
69
|
-
const context = { projectPath: '/test' }
|
|
70
|
-
const projectPath = '/test'
|
|
71
|
-
|
|
72
|
-
await router.executeTask(task, context, projectPath)
|
|
73
|
-
await router.executeTask(task, context, projectPath)
|
|
74
|
-
|
|
75
|
-
// Should only generate once, second call uses cache
|
|
76
|
-
expect(mockAgentGenerator.generateDynamicAgent).toHaveBeenCalledTimes(1)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('should log agent usage', async () => {
|
|
80
|
-
const task = { description: 'test task' }
|
|
81
|
-
const context = { projectPath: '/test', files: ['file1.js', 'file2.js'] }
|
|
82
|
-
const projectPath = '/test'
|
|
83
|
-
|
|
84
|
-
await router.executeTask(task, context, projectPath)
|
|
85
|
-
|
|
86
|
-
expect(router.usageLog.length).toBeGreaterThan(0)
|
|
87
|
-
expect(router.usageLog[0].task).toBe('test task')
|
|
88
|
-
expect(router.usageLog[0].agent).toBe('test-agent')
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
describe('analyzeTask()', () => {
|
|
93
|
-
it('should detect frontend tasks', () => {
|
|
94
|
-
const task = { description: 'create react component with styles' }
|
|
95
|
-
const analysis = router.analyzeTask(task)
|
|
96
|
-
|
|
97
|
-
expect(analysis.domain).toBe('frontend')
|
|
98
|
-
expect(analysis.confidence).toBeGreaterThan(0)
|
|
99
|
-
expect(analysis.matchedKeywords.length).toBeGreaterThan(0)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('should detect backend tasks', () => {
|
|
103
|
-
const task = { description: 'create API endpoint with authentication' }
|
|
104
|
-
const analysis = router.analyzeTask(task)
|
|
105
|
-
|
|
106
|
-
expect(analysis.domain).toBe('backend')
|
|
107
|
-
expect(analysis.matchedKeywords).toContain('api')
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('should detect database tasks', () => {
|
|
111
|
-
const task = { description: 'create migration schema for postgres' }
|
|
112
|
-
const analysis = router.analyzeTask(task)
|
|
113
|
-
|
|
114
|
-
expect(analysis.domain).toBe('database')
|
|
115
|
-
expect(analysis.matchedKeywords).toContain('migration')
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('should detect devops tasks', () => {
|
|
119
|
-
const task = { description: 'deploy to docker kubernetes' }
|
|
120
|
-
const analysis = router.analyzeTask(task)
|
|
121
|
-
|
|
122
|
-
expect(analysis.domain).toBe('devops')
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('should detect qa tasks', () => {
|
|
126
|
-
const task = { description: 'write unit tests and fix bugs' }
|
|
127
|
-
const analysis = router.analyzeTask(task)
|
|
128
|
-
|
|
129
|
-
expect(analysis.domain).toBe('qa')
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('should detect architecture tasks', () => {
|
|
133
|
-
const task = { description: 'design architecture pattern and refactor' }
|
|
134
|
-
const analysis = router.analyzeTask(task)
|
|
135
|
-
|
|
136
|
-
expect(analysis.domain).toBe('architecture')
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('should return generalist for unknown tasks', () => {
|
|
140
|
-
const task = { description: 'random task description' }
|
|
141
|
-
const analysis = router.analyzeTask(task)
|
|
142
|
-
|
|
143
|
-
expect(analysis.domain).toBe('generalist')
|
|
144
|
-
expect(analysis.confidence).toBeLessThan(1)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('should include technology stack in analysis', () => {
|
|
148
|
-
const task = { description: 'create react component with typescript' }
|
|
149
|
-
const analysis = router.analyzeTask(task)
|
|
150
|
-
|
|
151
|
-
expect(analysis.techStack).toBeDefined()
|
|
152
|
-
expect(analysis.techStack.languages).toContain('typescript')
|
|
153
|
-
expect(analysis.techStack.frameworks).toContain('react')
|
|
154
|
-
})
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
describe('detectTechnology()', () => {
|
|
158
|
-
it('should detect languages', () => {
|
|
159
|
-
const task = { description: 'write python script' }
|
|
160
|
-
const tech = router.detectTechnology(task, task.description)
|
|
161
|
-
|
|
162
|
-
expect(tech.languages).toContain('python')
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('should detect frameworks', () => {
|
|
166
|
-
const task = { description: 'build express api' }
|
|
167
|
-
const tech = router.detectTechnology(task, task.description)
|
|
168
|
-
|
|
169
|
-
expect(tech.frameworks).toContain('express')
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('should detect databases', () => {
|
|
173
|
-
const task = { description: 'query mongodb database' }
|
|
174
|
-
const tech = router.detectTechnology(task, task.description)
|
|
175
|
-
|
|
176
|
-
expect(tech.databases).toContain('mongodb')
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('should return empty arrays for unknown tech', () => {
|
|
180
|
-
const task = { description: 'random task' }
|
|
181
|
-
const tech = router.detectTechnology(task, task.description)
|
|
182
|
-
|
|
183
|
-
expect(tech.languages).toEqual([])
|
|
184
|
-
expect(tech.frameworks).toEqual([])
|
|
185
|
-
expect(tech.databases).toEqual([])
|
|
186
|
-
})
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
describe('assignAgent()', () => {
|
|
190
|
-
it('should generate agent for task', async () => {
|
|
191
|
-
const taskAnalysis = {
|
|
192
|
-
domain: 'frontend',
|
|
193
|
-
techStack: { languages: ['typescript'], frameworks: ['react'] }
|
|
194
|
-
}
|
|
195
|
-
const context = { projectPath: '/test' }
|
|
196
|
-
|
|
197
|
-
const agent = await router.assignAgent(taskAnalysis, context)
|
|
198
|
-
|
|
199
|
-
expect(agent).toBeDefined()
|
|
200
|
-
expect(mockAgentGenerator.generateDynamicAgent).toHaveBeenCalled()
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
it('should use cached agent if available', async () => {
|
|
204
|
-
const taskAnalysis = {
|
|
205
|
-
domain: 'frontend',
|
|
206
|
-
techStack: { languages: ['typescript'] }
|
|
207
|
-
}
|
|
208
|
-
const context = { projectPath: '/test' }
|
|
209
|
-
|
|
210
|
-
const agent1 = await router.assignAgent(taskAnalysis, context)
|
|
211
|
-
const agent2 = await router.assignAgent(taskAnalysis, context)
|
|
212
|
-
|
|
213
|
-
expect(agent1).toBe(agent2) // Same instance from cache
|
|
214
|
-
expect(mockAgentGenerator.generateDynamicAgent).toHaveBeenCalledTimes(1)
|
|
215
|
-
})
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
describe('filterContextForAgent()', () => {
|
|
219
|
-
it('should filter context for frontend agent', async () => {
|
|
220
|
-
const agent = { name: 'frontend-agent', domain: 'frontend' }
|
|
221
|
-
const fullContext = {
|
|
222
|
-
files: [
|
|
223
|
-
'src/components/Button.jsx',
|
|
224
|
-
'src/api/users.js',
|
|
225
|
-
'src/styles/main.css',
|
|
226
|
-
'migrations/001_users.sql'
|
|
227
|
-
]
|
|
228
|
-
}
|
|
229
|
-
const taskAnalysis = { domain: 'frontend' }
|
|
230
|
-
|
|
231
|
-
const filtered = await router.filterContextForAgent(agent, fullContext, taskAnalysis)
|
|
232
|
-
|
|
233
|
-
expect(filtered.files).toContain('src/components/Button.jsx')
|
|
234
|
-
expect(filtered.files).toContain('src/styles/main.css')
|
|
235
|
-
expect(filtered.files).not.toContain('migrations/001_users.sql')
|
|
236
|
-
expect(filtered.relevantOnly).toBe(true)
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it('should filter context for backend agent', async () => {
|
|
240
|
-
const agent = { name: 'backend-agent', domain: 'backend' }
|
|
241
|
-
const fullContext = {
|
|
242
|
-
files: [
|
|
243
|
-
'src/components/Button.jsx',
|
|
244
|
-
'src/api/users.js',
|
|
245
|
-
'src/routes/auth.js',
|
|
246
|
-
'src/styles/main.css'
|
|
247
|
-
]
|
|
248
|
-
}
|
|
249
|
-
const taskAnalysis = { domain: 'backend' }
|
|
250
|
-
|
|
251
|
-
const filtered = await router.filterContextForAgent(agent, fullContext, taskAnalysis)
|
|
252
|
-
|
|
253
|
-
expect(filtered.files).toContain('src/api/users.js')
|
|
254
|
-
expect(filtered.files).toContain('src/routes/auth.js')
|
|
255
|
-
expect(filtered.files).not.toContain('src/styles/main.css')
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
it('should filter context for database agent', async () => {
|
|
259
|
-
const agent = { name: 'database-agent', domain: 'database' }
|
|
260
|
-
const fullContext = {
|
|
261
|
-
files: [
|
|
262
|
-
'src/models/User.js',
|
|
263
|
-
'migrations/001_users.sql',
|
|
264
|
-
'src/components/Button.jsx'
|
|
265
|
-
]
|
|
266
|
-
}
|
|
267
|
-
const taskAnalysis = { domain: 'database' }
|
|
268
|
-
|
|
269
|
-
const filtered = await router.filterContextForAgent(agent, fullContext, taskAnalysis)
|
|
270
|
-
|
|
271
|
-
expect(filtered.files).toContain('src/models/User.js')
|
|
272
|
-
expect(filtered.files).toContain('migrations/001_users.sql')
|
|
273
|
-
expect(filtered.files).not.toContain('src/components/Button.jsx')
|
|
274
|
-
})
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
describe('filterFiles()', () => {
|
|
278
|
-
it('should exclude files matching exclude patterns', () => {
|
|
279
|
-
const files = ['src/app.js', 'node_modules/lib.js', 'dist/bundle.js']
|
|
280
|
-
const pattern = {
|
|
281
|
-
include: [],
|
|
282
|
-
exclude: ['node_modules', 'dist'],
|
|
283
|
-
extensions: []
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const filtered = router.filterFiles(files, pattern)
|
|
287
|
-
|
|
288
|
-
expect(filtered).not.toContain('node_modules/lib.js')
|
|
289
|
-
expect(filtered).not.toContain('dist/bundle.js')
|
|
290
|
-
expect(filtered).toContain('src/app.js')
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
it('should include only files matching include patterns', () => {
|
|
294
|
-
const files = ['src/components/Button.jsx', 'src/api/users.js', 'tests/test.js']
|
|
295
|
-
const pattern = {
|
|
296
|
-
include: ['components'],
|
|
297
|
-
exclude: [],
|
|
298
|
-
extensions: []
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const filtered = router.filterFiles(files, pattern)
|
|
302
|
-
|
|
303
|
-
expect(filtered).toContain('src/components/Button.jsx')
|
|
304
|
-
expect(filtered).not.toContain('src/api/users.js')
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
it('should filter by extensions', () => {
|
|
308
|
-
const files = ['src/app.js', 'src/app.ts', 'src/app.py']
|
|
309
|
-
const pattern = {
|
|
310
|
-
include: [],
|
|
311
|
-
exclude: [],
|
|
312
|
-
extensions: ['.js', '.ts']
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const filtered = router.filterFiles(files, pattern)
|
|
316
|
-
|
|
317
|
-
expect(filtered).toContain('src/app.js')
|
|
318
|
-
expect(filtered).toContain('src/app.ts')
|
|
319
|
-
expect(filtered).not.toContain('src/app.py')
|
|
320
|
-
})
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
describe('getBestPractices()', () => {
|
|
324
|
-
it('should return domain-specific practices', async () => {
|
|
325
|
-
const practices = await router.getBestPractices('frontend', { languages: [], frameworks: [] })
|
|
326
|
-
|
|
327
|
-
expect(practices.length).toBeGreaterThan(0)
|
|
328
|
-
expect(practices).toContain('Component composition over inheritance')
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
it('should include tech-specific practices', async () => {
|
|
332
|
-
const practices = await router.getBestPractices('frontend', {
|
|
333
|
-
languages: ['typescript'],
|
|
334
|
-
frameworks: ['react']
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
expect(practices.some(p => p.includes('Hooks') || p.includes('TypeScript'))).toBe(true)
|
|
338
|
-
})
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
describe('getSimilarDomains()', () => {
|
|
342
|
-
it('should return similar domains for frontend', () => {
|
|
343
|
-
const similar = router.getSimilarDomains('frontend')
|
|
344
|
-
expect(similar).toContain('fullstack')
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
it('should return similar domains for backend', () => {
|
|
348
|
-
const similar = router.getSimilarDomains('backend')
|
|
349
|
-
expect(similar).toContain('fullstack')
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
it('should return default for unknown domain', () => {
|
|
353
|
-
const similar = router.getSimilarDomains('unknown')
|
|
354
|
-
expect(similar).toEqual(['generalist'])
|
|
355
|
-
})
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
describe('getUsageStats()', () => {
|
|
359
|
-
it('should return usage statistics', async () => {
|
|
360
|
-
const task = { description: 'test task' }
|
|
361
|
-
const context = { projectPath: '/test' }
|
|
362
|
-
const projectPath = '/test'
|
|
363
|
-
|
|
364
|
-
await router.executeTask(task, context, projectPath)
|
|
365
|
-
await router.executeTask(task, context, projectPath)
|
|
366
|
-
|
|
367
|
-
const stats = router.getUsageStats()
|
|
368
|
-
|
|
369
|
-
expect(stats.totalTasks).toBe(2)
|
|
370
|
-
expect(stats.byAgent).toBeDefined()
|
|
371
|
-
expect(stats.mostUsedAgent).toBe('test-agent')
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
it('should handle empty usage log', () => {
|
|
375
|
-
const stats = router.getUsageStats()
|
|
376
|
-
|
|
377
|
-
expect(stats.totalTasks).toBe(0)
|
|
378
|
-
expect(stats.mostUsedAgent).toBeNull()
|
|
379
|
-
})
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
describe('calculateContextReduction()', () => {
|
|
383
|
-
it('should calculate reduction for filtered context', () => {
|
|
384
|
-
const filteredContext = { relevantOnly: true }
|
|
385
|
-
const reduction = router.calculateContextReduction(filteredContext)
|
|
386
|
-
|
|
387
|
-
expect(reduction).toBe('70-90%')
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
it('should return 0% for unfiltered context', () => {
|
|
391
|
-
const context = { relevantOnly: false }
|
|
392
|
-
const reduction = router.calculateContextReduction(context)
|
|
393
|
-
|
|
394
|
-
expect(reduction).toBe('0%')
|
|
395
|
-
})
|
|
396
|
-
})
|
|
397
|
-
})
|
|
398
|
-
|