prjct-cli 0.15.1 → 0.18.0
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 +35 -0
- package/bin/dev.js +0 -1
- package/bin/serve.js +19 -20
- package/core/__tests__/agentic/memory-system.test.ts +2 -1
- package/core/__tests__/agentic/plan-mode.test.ts +2 -1
- package/core/agentic/agent-router.ts +79 -14
- package/core/agentic/command-executor/command-executor.ts +2 -74
- package/core/agentic/services.ts +0 -48
- package/core/agentic/template-loader.ts +35 -1
- package/core/command-registry/setup-commands.ts +15 -0
- package/core/commands/base.ts +96 -77
- package/core/commands/planning.ts +13 -2
- package/core/commands/setup.ts +3 -85
- package/core/domain/agent-generator.ts +9 -17
- package/core/errors.ts +209 -0
- package/core/infrastructure/config-manager.ts +22 -5
- package/core/infrastructure/path-manager.ts +23 -1
- package/core/infrastructure/setup.ts +5 -50
- package/core/storage/ideas-storage.ts +4 -0
- package/core/storage/queue-storage.ts +4 -0
- package/core/storage/shipped-storage.ts +4 -0
- package/core/storage/state-storage.ts +4 -0
- package/core/storage/storage-manager.ts +52 -13
- package/core/sync/auth-config.ts +145 -0
- package/core/sync/index.ts +30 -0
- package/core/sync/oauth-handler.ts +148 -0
- package/core/sync/sync-client.ts +252 -0
- package/core/sync/sync-manager.ts +358 -0
- package/core/utils/logger.ts +19 -12
- package/package.json +2 -4
- package/templates/agentic/subagent-generation.md +109 -0
- package/templates/commands/auth.md +234 -0
- package/templates/commands/sync.md +129 -13
- package/templates/subagents/domain/backend.md +105 -0
- package/templates/subagents/domain/database.md +118 -0
- package/templates/subagents/domain/devops.md +148 -0
- package/templates/subagents/domain/frontend.md +99 -0
- package/templates/subagents/domain/testing.md +169 -0
- package/templates/subagents/workflow/prjct-planner.md +158 -0
- package/templates/subagents/workflow/prjct-shipper.md +179 -0
- package/templates/subagents/workflow/prjct-workflow.md +98 -0
- package/bin/generate-views.js +0 -209
- package/bin/migrate-to-json.js +0 -742
- package/core/agentic/context-filter.ts +0 -365
- package/core/agentic/parallel-tools.ts +0 -165
- package/core/agentic/response-templates.ts +0 -164
- package/core/agentic/semantic-compression.ts +0 -273
- package/core/agentic/think-blocks.ts +0 -202
- package/core/agentic/validation-rules.ts +0 -313
- package/core/domain/agent-matcher.ts +0 -130
- package/core/domain/agent-validator.ts +0 -250
- package/core/domain/architect-session.ts +0 -315
- package/core/domain/product-standards.ts +0 -106
- package/core/domain/smart-cache.ts +0 -167
- package/core/domain/task-analyzer.ts +0 -296
- package/core/infrastructure/legacy-installer-detector/cleanup.ts +0 -216
- package/core/infrastructure/legacy-installer-detector/detection.ts +0 -95
- package/core/infrastructure/legacy-installer-detector/index.ts +0 -171
- package/core/infrastructure/legacy-installer-detector/migration.ts +0 -87
- package/core/infrastructure/legacy-installer-detector/types.ts +0 -42
- package/core/infrastructure/legacy-installer-detector.ts +0 -7
- package/core/infrastructure/migrator/file-operations.ts +0 -125
- package/core/infrastructure/migrator/index.ts +0 -288
- package/core/infrastructure/migrator/project-scanner.ts +0 -90
- package/core/infrastructure/migrator/reports.ts +0 -117
- package/core/infrastructure/migrator/types.ts +0 -124
- package/core/infrastructure/migrator/validation.ts +0 -94
- package/core/infrastructure/migrator/version-migration.ts +0 -117
- package/core/infrastructure/migrator.ts +0 -10
- package/core/infrastructure/uuid-migration.ts +0 -750
- package/templates/commands/migrate-all.md +0 -96
- package/templates/commands/migrate.md +0 -140
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validation Rules
|
|
3
|
-
* Pre-flight validation for commands before execution
|
|
4
|
-
*
|
|
5
|
-
* @module agentic/validation-rules
|
|
6
|
-
* @version 1.0.0
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import fs from 'fs/promises'
|
|
10
|
-
import path from 'path'
|
|
11
|
-
import os from 'os'
|
|
12
|
-
|
|
13
|
-
interface Context {
|
|
14
|
-
projectPath: string
|
|
15
|
-
projectId?: string | null
|
|
16
|
-
paths: Record<string, string>
|
|
17
|
-
params: Record<string, unknown>
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface ValidationResult {
|
|
21
|
-
valid: boolean
|
|
22
|
-
errors: string[]
|
|
23
|
-
warnings: string[]
|
|
24
|
-
suggestions: string[]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
type Validator = (context: Context) => Promise<ValidationResult>
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Command-specific validators
|
|
31
|
-
*/
|
|
32
|
-
const validators: Record<string, Validator> = {
|
|
33
|
-
/**
|
|
34
|
-
* Validate /p:done can execute
|
|
35
|
-
*/
|
|
36
|
-
async done(context): Promise<ValidationResult> {
|
|
37
|
-
const errors: string[] = []
|
|
38
|
-
const warnings: string[] = []
|
|
39
|
-
const suggestions: string[] = []
|
|
40
|
-
|
|
41
|
-
// Check if now.md exists and has content
|
|
42
|
-
try {
|
|
43
|
-
const nowContent = await fs.readFile(context.paths.now, 'utf-8')
|
|
44
|
-
if (!nowContent.trim() || nowContent.includes('No current task')) {
|
|
45
|
-
errors.push('No active task to complete')
|
|
46
|
-
suggestions.push('Start a task with /p:now "task description"')
|
|
47
|
-
}
|
|
48
|
-
} catch {
|
|
49
|
-
errors.push('now.md does not exist')
|
|
50
|
-
suggestions.push('Initialize project with /p:init')
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
valid: errors.length === 0,
|
|
55
|
-
errors,
|
|
56
|
-
warnings,
|
|
57
|
-
suggestions,
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Validate /p:ship can execute
|
|
63
|
-
*/
|
|
64
|
-
async ship(context): Promise<ValidationResult> {
|
|
65
|
-
const errors: string[] = []
|
|
66
|
-
const warnings: string[] = []
|
|
67
|
-
const suggestions: string[] = []
|
|
68
|
-
|
|
69
|
-
// Check if now.md has content to ship
|
|
70
|
-
try {
|
|
71
|
-
const nowContent = await fs.readFile(context.paths.now, 'utf-8')
|
|
72
|
-
if (!nowContent.trim() || nowContent.includes('No current task')) {
|
|
73
|
-
warnings.push('No active task to ship')
|
|
74
|
-
}
|
|
75
|
-
} catch {
|
|
76
|
-
warnings.push('now.md does not exist')
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Check if shipped.md exists
|
|
80
|
-
try {
|
|
81
|
-
await fs.access(path.dirname(context.paths.shipped))
|
|
82
|
-
} catch {
|
|
83
|
-
warnings.push('shipped.md directory does not exist')
|
|
84
|
-
suggestions.push('Run /p:init to initialize project structure')
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
valid: errors.length === 0,
|
|
89
|
-
errors,
|
|
90
|
-
warnings,
|
|
91
|
-
suggestions,
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Validate /p:now can execute
|
|
97
|
-
*/
|
|
98
|
-
async now(context): Promise<ValidationResult> {
|
|
99
|
-
const errors: string[] = []
|
|
100
|
-
const warnings: string[] = []
|
|
101
|
-
const suggestions: string[] = []
|
|
102
|
-
|
|
103
|
-
// Check if project is initialized
|
|
104
|
-
const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
|
|
105
|
-
try {
|
|
106
|
-
await fs.access(configPath)
|
|
107
|
-
} catch {
|
|
108
|
-
errors.push('Project not initialized')
|
|
109
|
-
suggestions.push('Run /p:init to initialize')
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Check if already has a task
|
|
113
|
-
try {
|
|
114
|
-
const nowContent = await fs.readFile(context.paths.now, 'utf-8')
|
|
115
|
-
if (nowContent.trim() && !nowContent.includes('No current task')) {
|
|
116
|
-
warnings.push('Already has an active task')
|
|
117
|
-
suggestions.push('Complete it first with /p:done')
|
|
118
|
-
}
|
|
119
|
-
} catch {
|
|
120
|
-
// now.md doesn't exist - that's ok for /p:now
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
valid: errors.length === 0,
|
|
125
|
-
errors,
|
|
126
|
-
warnings,
|
|
127
|
-
suggestions,
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Validate /p:init can execute
|
|
133
|
-
*/
|
|
134
|
-
async init(context): Promise<ValidationResult> {
|
|
135
|
-
const errors: string[] = []
|
|
136
|
-
const warnings: string[] = []
|
|
137
|
-
const suggestions: string[] = []
|
|
138
|
-
|
|
139
|
-
// Check if already initialized
|
|
140
|
-
const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
|
|
141
|
-
try {
|
|
142
|
-
await fs.access(configPath)
|
|
143
|
-
warnings.push('Project already initialized')
|
|
144
|
-
suggestions.push('Use /p:sync to refresh or delete .prjct/ to reinitialize')
|
|
145
|
-
} catch {
|
|
146
|
-
// Not initialized - good
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check if global storage is writable
|
|
150
|
-
const globalPath = path.join(os.homedir(), '.prjct-cli')
|
|
151
|
-
try {
|
|
152
|
-
await fs.mkdir(globalPath, { recursive: true })
|
|
153
|
-
} catch {
|
|
154
|
-
errors.push('Cannot create ~/.prjct-cli directory')
|
|
155
|
-
suggestions.push('Check filesystem permissions')
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
valid: errors.length === 0,
|
|
160
|
-
errors,
|
|
161
|
-
warnings,
|
|
162
|
-
suggestions,
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Validate /p:sync can execute
|
|
168
|
-
*/
|
|
169
|
-
async sync(context): Promise<ValidationResult> {
|
|
170
|
-
const errors: string[] = []
|
|
171
|
-
const warnings: string[] = []
|
|
172
|
-
const suggestions: string[] = []
|
|
173
|
-
|
|
174
|
-
// Check if project is initialized
|
|
175
|
-
const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
|
|
176
|
-
try {
|
|
177
|
-
await fs.access(configPath)
|
|
178
|
-
} catch {
|
|
179
|
-
errors.push('Project not initialized')
|
|
180
|
-
suggestions.push('Run /p:init first')
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
valid: errors.length === 0,
|
|
185
|
-
errors,
|
|
186
|
-
warnings,
|
|
187
|
-
suggestions,
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Validate /p:feature can execute
|
|
193
|
-
*/
|
|
194
|
-
async feature(context): Promise<ValidationResult> {
|
|
195
|
-
const errors: string[] = []
|
|
196
|
-
const warnings: string[] = []
|
|
197
|
-
const suggestions: string[] = []
|
|
198
|
-
|
|
199
|
-
// Check if project is initialized
|
|
200
|
-
const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
|
|
201
|
-
try {
|
|
202
|
-
await fs.access(configPath)
|
|
203
|
-
} catch {
|
|
204
|
-
errors.push('Project not initialized')
|
|
205
|
-
suggestions.push('Run /p:init first')
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Check if description provided
|
|
209
|
-
if (!context.params.description && !context.params.feature) {
|
|
210
|
-
warnings.push('No feature description provided')
|
|
211
|
-
suggestions.push('Provide a feature description')
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
valid: errors.length === 0,
|
|
216
|
-
errors,
|
|
217
|
-
warnings,
|
|
218
|
-
suggestions,
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Validate /p:idea can execute
|
|
224
|
-
*/
|
|
225
|
-
async idea(context): Promise<ValidationResult> {
|
|
226
|
-
const errors: string[] = []
|
|
227
|
-
const warnings: string[] = []
|
|
228
|
-
const suggestions: string[] = []
|
|
229
|
-
|
|
230
|
-
// Check if project is initialized
|
|
231
|
-
const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
|
|
232
|
-
try {
|
|
233
|
-
await fs.access(configPath)
|
|
234
|
-
} catch {
|
|
235
|
-
errors.push('Project not initialized')
|
|
236
|
-
suggestions.push('Run /p:init first')
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Check if idea text provided
|
|
240
|
-
if (!context.params.idea && !context.params.text) {
|
|
241
|
-
warnings.push('No idea text provided')
|
|
242
|
-
suggestions.push('Provide idea text')
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
valid: errors.length === 0,
|
|
247
|
-
errors,
|
|
248
|
-
warnings,
|
|
249
|
-
suggestions,
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Validate a command before execution
|
|
256
|
-
*/
|
|
257
|
-
async function validate(commandName: string, context: Context): Promise<ValidationResult> {
|
|
258
|
-
const validator = validators[commandName]
|
|
259
|
-
|
|
260
|
-
if (!validator) {
|
|
261
|
-
// No specific validation needed
|
|
262
|
-
return {
|
|
263
|
-
valid: true,
|
|
264
|
-
errors: [],
|
|
265
|
-
warnings: [],
|
|
266
|
-
suggestions: [],
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
return await validator(context)
|
|
272
|
-
} catch (error) {
|
|
273
|
-
return {
|
|
274
|
-
valid: false,
|
|
275
|
-
errors: [`Validation error: ${(error as Error).message}`],
|
|
276
|
-
warnings: [],
|
|
277
|
-
suggestions: ['Check file permissions and project configuration'],
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Format validation errors for display
|
|
284
|
-
*/
|
|
285
|
-
function formatError(result: ValidationResult): string {
|
|
286
|
-
let output = ''
|
|
287
|
-
|
|
288
|
-
if (result.errors.length > 0) {
|
|
289
|
-
output += '❌ Validation Failed:\n'
|
|
290
|
-
result.errors.forEach((e) => {
|
|
291
|
-
output += ` • ${e}\n`
|
|
292
|
-
})
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (result.warnings.length > 0) {
|
|
296
|
-
output += '\n⚠️ Warnings:\n'
|
|
297
|
-
result.warnings.forEach((w) => {
|
|
298
|
-
output += ` • ${w}\n`
|
|
299
|
-
})
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (result.suggestions.length > 0) {
|
|
303
|
-
output += '\n💡 Suggestions:\n'
|
|
304
|
-
result.suggestions.forEach((s) => {
|
|
305
|
-
output += ` → ${s}\n`
|
|
306
|
-
})
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return output
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export { validate, formatError, validators }
|
|
313
|
-
export default { validate, formatError, validators }
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentMatcher - Orchestration Only
|
|
3
|
-
*
|
|
4
|
-
* AGENTIC: All matching decisions made by Claude via templates/agent-assignment.md
|
|
5
|
-
* JS only orchestrates: format data, pass to Claude, return result
|
|
6
|
-
*
|
|
7
|
-
* NO scoring logic, NO algorithms, NO hardcoded weights
|
|
8
|
-
*
|
|
9
|
-
* @version 2.0.0
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import fs from 'fs/promises'
|
|
13
|
-
import path from 'path'
|
|
14
|
-
|
|
15
|
-
interface Agent {
|
|
16
|
-
name: string
|
|
17
|
-
domain?: string
|
|
18
|
-
content?: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface Task {
|
|
22
|
-
description?: string
|
|
23
|
-
type?: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface HistoryEntry {
|
|
27
|
-
timestamp: string
|
|
28
|
-
agent: string
|
|
29
|
-
task: string
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface FormattedAgent {
|
|
33
|
-
name: string
|
|
34
|
-
domain: string
|
|
35
|
-
hasContent: boolean
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface FormattedTask {
|
|
39
|
-
description: string
|
|
40
|
-
type: string
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
class AgentMatcher {
|
|
44
|
-
historyPath: string | null = null
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Set history path for logging
|
|
48
|
-
* ORCHESTRATION: Path setup only
|
|
49
|
-
*/
|
|
50
|
-
setHistoryPath(projectId: string): void {
|
|
51
|
-
this.historyPath = path.join(
|
|
52
|
-
process.env.HOME || '',
|
|
53
|
-
'.prjct-cli',
|
|
54
|
-
'projects',
|
|
55
|
-
projectId,
|
|
56
|
-
'agent-history.jsonl'
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Format agents for Claude
|
|
62
|
-
* ORCHESTRATION: Data formatting only
|
|
63
|
-
*/
|
|
64
|
-
formatAgentsForTemplate(agents: Agent[]): FormattedAgent[] {
|
|
65
|
-
return agents.map((a) => ({
|
|
66
|
-
name: a.name,
|
|
67
|
-
domain: a.domain || 'general',
|
|
68
|
-
hasContent: !!a.content,
|
|
69
|
-
}))
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Format task for Claude
|
|
74
|
-
* ORCHESTRATION: Data formatting only
|
|
75
|
-
*/
|
|
76
|
-
formatTaskForTemplate(task: string | Task): FormattedTask {
|
|
77
|
-
return {
|
|
78
|
-
description: typeof task === 'string' ? task : task.description || '',
|
|
79
|
-
type: typeof task === 'string' ? 'unknown' : task.type || 'unknown',
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Record agent usage
|
|
85
|
-
* ORCHESTRATION: File I/O only
|
|
86
|
-
*/
|
|
87
|
-
async recordUsage(agent: string | Agent, task: string | Task): Promise<void> {
|
|
88
|
-
if (!this.historyPath) return
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
const entry =
|
|
92
|
-
JSON.stringify({
|
|
93
|
-
timestamp: new Date().toISOString(),
|
|
94
|
-
agent: typeof agent === 'string' ? agent : agent.name,
|
|
95
|
-
task: typeof task === 'string' ? task : task.description,
|
|
96
|
-
}) + '\n'
|
|
97
|
-
|
|
98
|
-
await fs.appendFile(this.historyPath, entry)
|
|
99
|
-
} catch {
|
|
100
|
-
// Silent fail
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Load usage history
|
|
106
|
-
* ORCHESTRATION: File I/O only
|
|
107
|
-
*/
|
|
108
|
-
async loadHistory(): Promise<HistoryEntry[]> {
|
|
109
|
-
if (!this.historyPath) return []
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
const content = await fs.readFile(this.historyPath, 'utf-8')
|
|
113
|
-
return content
|
|
114
|
-
.split('\n')
|
|
115
|
-
.filter(Boolean)
|
|
116
|
-
.map((line) => {
|
|
117
|
-
try {
|
|
118
|
-
return JSON.parse(line) as HistoryEntry
|
|
119
|
-
} catch {
|
|
120
|
-
return null
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
.filter((entry): entry is HistoryEntry => entry !== null)
|
|
124
|
-
} catch {
|
|
125
|
-
return []
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export default AgentMatcher
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentValidator - Validates agents before and after generation
|
|
3
|
-
*
|
|
4
|
-
* Ensures agents are useful and not generic
|
|
5
|
-
* Compares with existing agents before generating
|
|
6
|
-
*
|
|
7
|
-
* @version 1.0.0
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
interface AgentConfig {
|
|
11
|
-
expertise?: string
|
|
12
|
-
domain?: string
|
|
13
|
-
projectContext?: Record<string, unknown>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface Agent {
|
|
17
|
-
name: string
|
|
18
|
-
content?: string
|
|
19
|
-
skills?: string[]
|
|
20
|
-
domain?: string
|
|
21
|
-
role?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface ValidationResult {
|
|
25
|
-
valid: boolean
|
|
26
|
-
issues: string[]
|
|
27
|
-
warnings: string[]
|
|
28
|
-
similarAgent?: Agent | null
|
|
29
|
-
usefulness?: number
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
class AgentValidator {
|
|
33
|
-
/**
|
|
34
|
-
* Validate if agent should be generated
|
|
35
|
-
*/
|
|
36
|
-
validateBeforeGeneration(
|
|
37
|
-
agentName: string,
|
|
38
|
-
config: AgentConfig,
|
|
39
|
-
existingAgents: Agent[] = []
|
|
40
|
-
): ValidationResult {
|
|
41
|
-
const issues: string[] = []
|
|
42
|
-
const warnings: string[] = []
|
|
43
|
-
|
|
44
|
-
// Check if similar agent exists
|
|
45
|
-
const similar = this.findSimilarAgent(agentName, config, existingAgents)
|
|
46
|
-
if (similar) {
|
|
47
|
-
warnings.push(`Similar agent exists: ${similar.name}`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check if agent has specific skills
|
|
51
|
-
if (!config.expertise || config.expertise.length < 10) {
|
|
52
|
-
issues.push('Agent expertise is too generic')
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Check if agent has project context
|
|
56
|
-
if (!config.projectContext || Object.keys(config.projectContext).length === 0) {
|
|
57
|
-
warnings.push('Agent has no project-specific context')
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Check if agent name is descriptive
|
|
61
|
-
if (agentName.includes('specialist') && !config.expertise) {
|
|
62
|
-
issues.push('Agent name suggests specialization but has no expertise defined')
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
valid: issues.length === 0,
|
|
67
|
-
issues,
|
|
68
|
-
warnings,
|
|
69
|
-
similarAgent: similar,
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Validate agent after generation
|
|
75
|
-
*/
|
|
76
|
-
validateAfterGeneration(agent: Agent): ValidationResult {
|
|
77
|
-
const issues: string[] = []
|
|
78
|
-
const warnings: string[] = []
|
|
79
|
-
|
|
80
|
-
// Check if agent has content
|
|
81
|
-
if (!agent.content || agent.content.length < 100) {
|
|
82
|
-
issues.push('Agent content is too short or missing')
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Check if agent has skills extracted
|
|
86
|
-
if (!agent.skills || agent.skills.length === 0) {
|
|
87
|
-
warnings.push('Agent has no skills detected')
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Check if agent has domain
|
|
91
|
-
if (!agent.domain || agent.domain === 'general') {
|
|
92
|
-
warnings.push('Agent domain is generic')
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Check if agent is useful (not too generic)
|
|
96
|
-
const usefulness = this.calculateUsefulness(agent)
|
|
97
|
-
if (usefulness < 0.5) {
|
|
98
|
-
issues.push('Agent is too generic to be useful')
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
valid: issues.length === 0,
|
|
103
|
-
issues,
|
|
104
|
-
warnings,
|
|
105
|
-
usefulness,
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Find similar existing agent
|
|
111
|
-
*/
|
|
112
|
-
findSimilarAgent(agentName: string, config: AgentConfig, existingAgents: Agent[]): Agent | null {
|
|
113
|
-
for (const existing of existingAgents) {
|
|
114
|
-
// Check name similarity
|
|
115
|
-
if (this.namesSimilar(agentName, existing.name)) {
|
|
116
|
-
return existing
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Check domain similarity
|
|
120
|
-
if (config.domain && existing.domain && config.domain === existing.domain) {
|
|
121
|
-
// Check if skills overlap significantly
|
|
122
|
-
const skillOverlap = this.calculateSkillOverlap(config, existing)
|
|
123
|
-
if (skillOverlap > 0.7) {
|
|
124
|
-
return existing
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return null
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Check if agent names are similar
|
|
134
|
-
*/
|
|
135
|
-
namesSimilar(name1: string, name2: string): boolean {
|
|
136
|
-
const n1 = name1.toLowerCase()
|
|
137
|
-
const n2 = name2.toLowerCase()
|
|
138
|
-
|
|
139
|
-
// Exact match
|
|
140
|
-
if (n1 === n2) return true
|
|
141
|
-
|
|
142
|
-
// One contains the other
|
|
143
|
-
if (n1.includes(n2) || n2.includes(n1)) return true
|
|
144
|
-
|
|
145
|
-
// Check word overlap
|
|
146
|
-
const words1 = new Set(n1.split('-'))
|
|
147
|
-
const words2 = new Set(n2.split('-'))
|
|
148
|
-
const intersection = new Set([...words1].filter((w) => words2.has(w)))
|
|
149
|
-
const union = new Set([...words1, ...words2])
|
|
150
|
-
|
|
151
|
-
return intersection.size / union.size > 0.5
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Calculate skill overlap between config and existing agent
|
|
156
|
-
*/
|
|
157
|
-
calculateSkillOverlap(config: AgentConfig, existingAgent: Agent): number {
|
|
158
|
-
if (!existingAgent.skills || existingAgent.skills.length === 0) {
|
|
159
|
-
return 0
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Extract skills from config expertise
|
|
163
|
-
const configSkills = this.extractSkillsFromText(config.expertise || '')
|
|
164
|
-
if (configSkills.length === 0) {
|
|
165
|
-
return 0
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Calculate overlap
|
|
169
|
-
const existingSet = new Set(existingAgent.skills.map((s) => s.toLowerCase()))
|
|
170
|
-
const configSet = new Set(configSkills.map((s) => s.toLowerCase()))
|
|
171
|
-
|
|
172
|
-
const intersection = new Set([...existingSet].filter((s) => configSet.has(s)))
|
|
173
|
-
const union = new Set([...existingSet, ...configSet])
|
|
174
|
-
|
|
175
|
-
return intersection.size / union.size
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Extract skills from text
|
|
180
|
-
*/
|
|
181
|
-
extractSkillsFromText(text: string): string[] {
|
|
182
|
-
// Common technology keywords
|
|
183
|
-
const techKeywords = [
|
|
184
|
-
'React',
|
|
185
|
-
'Vue',
|
|
186
|
-
'Angular',
|
|
187
|
-
'Svelte',
|
|
188
|
-
'Next.js',
|
|
189
|
-
'Nuxt',
|
|
190
|
-
'SvelteKit',
|
|
191
|
-
'TypeScript',
|
|
192
|
-
'JavaScript',
|
|
193
|
-
'Node.js',
|
|
194
|
-
'Express',
|
|
195
|
-
'Fastify',
|
|
196
|
-
'Python',
|
|
197
|
-
'Django',
|
|
198
|
-
'Flask',
|
|
199
|
-
'FastAPI',
|
|
200
|
-
'Go',
|
|
201
|
-
'Rust',
|
|
202
|
-
'Ruby',
|
|
203
|
-
'Rails',
|
|
204
|
-
'PostgreSQL',
|
|
205
|
-
'MySQL',
|
|
206
|
-
'MongoDB',
|
|
207
|
-
]
|
|
208
|
-
|
|
209
|
-
return techKeywords.filter((tech) => text.toLowerCase().includes(tech.toLowerCase()))
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Calculate agent usefulness score
|
|
214
|
-
*/
|
|
215
|
-
calculateUsefulness(agent: Agent): number {
|
|
216
|
-
let score = 0
|
|
217
|
-
|
|
218
|
-
// Has skills
|
|
219
|
-
if (agent.skills && agent.skills.length > 0) {
|
|
220
|
-
score += 0.3
|
|
221
|
-
if (agent.skills.length > 3) {
|
|
222
|
-
score += 0.1 // Bonus for multiple skills
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Has specific domain
|
|
227
|
-
if (agent.domain && agent.domain !== 'general') {
|
|
228
|
-
score += 0.2
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Has content
|
|
232
|
-
if (agent.content && agent.content.length > 200) {
|
|
233
|
-
score += 0.2
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Has role
|
|
237
|
-
if (agent.role && agent.role.length > 10) {
|
|
238
|
-
score += 0.1
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Not generic name
|
|
242
|
-
if (agent.name && !agent.name.includes('generalist')) {
|
|
243
|
-
score += 0.1
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return Math.min(score, 1.0)
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export default AgentValidator
|