prjct-cli 1.8.0 → 1.9.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 +102 -0
- package/core/__tests__/agentic/domain-classifier.test.ts +330 -0
- package/core/__tests__/agentic/response-validator.test.ts +263 -0
- package/core/__tests__/agentic/smart-context.test.ts +3 -3
- package/core/__tests__/schemas/model.test.ts +272 -0
- package/core/agentic/domain-classifier.ts +525 -0
- package/core/agentic/index.ts +1 -0
- package/core/agentic/orchestrator-executor.ts +43 -199
- package/core/agentic/prompt-builder.ts +22 -0
- package/core/agentic/response-validator.ts +98 -0
- package/core/agentic/smart-context.ts +60 -144
- package/core/infrastructure/ai-provider.ts +35 -0
- package/core/schemas/analysis.ts +4 -0
- package/core/schemas/classification.ts +91 -0
- package/core/schemas/index.ts +6 -0
- package/core/schemas/llm-output.ts +170 -0
- package/core/schemas/model.ts +153 -0
- package/core/schemas/state.ts +3 -0
- package/core/types/config.ts +2 -0
- package/core/types/provider.ts +12 -0
- package/dist/bin/prjct.mjs +1753 -1201
- package/dist/core/infrastructure/command-installer.js +78 -7
- package/dist/core/infrastructure/setup.js +78 -7
- package/package.json +1 -1
|
@@ -32,149 +32,11 @@ import type {
|
|
|
32
32
|
RealCodebaseContext,
|
|
33
33
|
} from '../types'
|
|
34
34
|
import { getErrorMessage, isNotFoundError } from '../types/fs'
|
|
35
|
+
import domainClassifier, { type ProjectContext } from './domain-classifier'
|
|
35
36
|
import { parseFrontmatter } from './template-loader'
|
|
36
37
|
|
|
37
38
|
const execAsync = promisify(execCallback)
|
|
38
39
|
|
|
39
|
-
// =============================================================================
|
|
40
|
-
// Domain Detection Keywords
|
|
41
|
-
// =============================================================================
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Keywords that indicate a domain is involved in the task
|
|
45
|
-
* These are hints, not absolute rules - context matters
|
|
46
|
-
*/
|
|
47
|
-
const DOMAIN_KEYWORDS: Record<string, string[]> = {
|
|
48
|
-
database: [
|
|
49
|
-
'database',
|
|
50
|
-
'db',
|
|
51
|
-
'sql',
|
|
52
|
-
'query',
|
|
53
|
-
'table',
|
|
54
|
-
'schema',
|
|
55
|
-
'migration',
|
|
56
|
-
'postgres',
|
|
57
|
-
'mysql',
|
|
58
|
-
'sqlite',
|
|
59
|
-
'mongo',
|
|
60
|
-
'redis',
|
|
61
|
-
'prisma',
|
|
62
|
-
'drizzle',
|
|
63
|
-
'orm',
|
|
64
|
-
'model',
|
|
65
|
-
'entity',
|
|
66
|
-
'repository',
|
|
67
|
-
'data layer',
|
|
68
|
-
'persist',
|
|
69
|
-
],
|
|
70
|
-
backend: [
|
|
71
|
-
'api',
|
|
72
|
-
'endpoint',
|
|
73
|
-
'route',
|
|
74
|
-
'server',
|
|
75
|
-
'controller',
|
|
76
|
-
'service',
|
|
77
|
-
'middleware',
|
|
78
|
-
'auth',
|
|
79
|
-
'authentication',
|
|
80
|
-
'authorization',
|
|
81
|
-
'jwt',
|
|
82
|
-
'oauth',
|
|
83
|
-
'rest',
|
|
84
|
-
'graphql',
|
|
85
|
-
'trpc',
|
|
86
|
-
'express',
|
|
87
|
-
'fastify',
|
|
88
|
-
'hono',
|
|
89
|
-
'nest',
|
|
90
|
-
'validation',
|
|
91
|
-
'business logic',
|
|
92
|
-
],
|
|
93
|
-
frontend: [
|
|
94
|
-
'ui',
|
|
95
|
-
'component',
|
|
96
|
-
'page',
|
|
97
|
-
'form',
|
|
98
|
-
'button',
|
|
99
|
-
'input',
|
|
100
|
-
'modal',
|
|
101
|
-
'dialog',
|
|
102
|
-
'react',
|
|
103
|
-
'vue',
|
|
104
|
-
'svelte',
|
|
105
|
-
'angular',
|
|
106
|
-
'next',
|
|
107
|
-
'nuxt',
|
|
108
|
-
'solid',
|
|
109
|
-
'css',
|
|
110
|
-
'style',
|
|
111
|
-
'tailwind',
|
|
112
|
-
'layout',
|
|
113
|
-
'responsive',
|
|
114
|
-
'animation',
|
|
115
|
-
'hook',
|
|
116
|
-
'state',
|
|
117
|
-
'context',
|
|
118
|
-
'redux',
|
|
119
|
-
'zustand',
|
|
120
|
-
'jotai',
|
|
121
|
-
],
|
|
122
|
-
testing: [
|
|
123
|
-
'test',
|
|
124
|
-
'spec',
|
|
125
|
-
'unit',
|
|
126
|
-
'integration',
|
|
127
|
-
'e2e',
|
|
128
|
-
'jest',
|
|
129
|
-
'vitest',
|
|
130
|
-
'playwright',
|
|
131
|
-
'cypress',
|
|
132
|
-
'mocha',
|
|
133
|
-
'chai',
|
|
134
|
-
'mock',
|
|
135
|
-
'stub',
|
|
136
|
-
'fixture',
|
|
137
|
-
'coverage',
|
|
138
|
-
'assertion',
|
|
139
|
-
],
|
|
140
|
-
devops: [
|
|
141
|
-
'docker',
|
|
142
|
-
'kubernetes',
|
|
143
|
-
'k8s',
|
|
144
|
-
'ci',
|
|
145
|
-
'cd',
|
|
146
|
-
'pipeline',
|
|
147
|
-
'deploy',
|
|
148
|
-
'github actions',
|
|
149
|
-
'vercel',
|
|
150
|
-
'aws',
|
|
151
|
-
'gcp',
|
|
152
|
-
'azure',
|
|
153
|
-
'terraform',
|
|
154
|
-
'nginx',
|
|
155
|
-
'caddy',
|
|
156
|
-
'env',
|
|
157
|
-
'environment',
|
|
158
|
-
'config',
|
|
159
|
-
'secret',
|
|
160
|
-
],
|
|
161
|
-
uxui: [
|
|
162
|
-
'design',
|
|
163
|
-
'ux',
|
|
164
|
-
'user experience',
|
|
165
|
-
'accessibility',
|
|
166
|
-
'a11y',
|
|
167
|
-
'color',
|
|
168
|
-
'typography',
|
|
169
|
-
'spacing',
|
|
170
|
-
'prototype',
|
|
171
|
-
'wireframe',
|
|
172
|
-
'figma',
|
|
173
|
-
'user flow',
|
|
174
|
-
'interaction',
|
|
175
|
-
],
|
|
176
|
-
}
|
|
177
|
-
|
|
178
40
|
/**
|
|
179
41
|
* Domain dependency order - earlier domains should complete first
|
|
180
42
|
*/
|
|
@@ -353,84 +215,66 @@ export class OrchestratorExecutor {
|
|
|
353
215
|
}
|
|
354
216
|
|
|
355
217
|
/**
|
|
356
|
-
* Detect which domains are relevant for this task
|
|
218
|
+
* Detect which domains are relevant for this task.
|
|
357
219
|
*
|
|
358
|
-
* Uses
|
|
359
|
-
*
|
|
360
|
-
* - Task description keywords
|
|
361
|
-
* - Project technology stack
|
|
362
|
-
* - Available agents
|
|
220
|
+
* Uses LLM-based classification with fallback chain (PRJ-299):
|
|
221
|
+
* cache → confirmed patterns → LLM → heuristic
|
|
363
222
|
*/
|
|
364
223
|
async detectDomains(
|
|
365
224
|
taskDescription: string,
|
|
366
225
|
projectId: string,
|
|
367
226
|
repoAnalysis: { ecosystem: string; technologies?: string[] } | null
|
|
368
227
|
): Promise<{ domains: string[]; primary: string }> {
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
//
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
228
|
+
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
229
|
+
const availableAgents = await this.getAvailableAgentNames(globalPath)
|
|
230
|
+
|
|
231
|
+
// Load state.json for project domain info
|
|
232
|
+
let projectDomains = {
|
|
233
|
+
hasFrontend: false,
|
|
234
|
+
hasBackend: true,
|
|
235
|
+
hasDatabase: false,
|
|
236
|
+
hasTesting: false,
|
|
237
|
+
hasDocker: false,
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const statePath = path.join(globalPath, 'storage', 'state.json')
|
|
241
|
+
const stateContent = await fs.readFile(statePath, 'utf-8')
|
|
242
|
+
const state = JSON.parse(stateContent)
|
|
243
|
+
if (state.domains) {
|
|
244
|
+
projectDomains = state.domains
|
|
383
245
|
}
|
|
246
|
+
} catch {
|
|
247
|
+
// Use defaults
|
|
384
248
|
}
|
|
385
249
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (/react|vue|svelte|angular|next|nuxt/.test(techStr)) {
|
|
392
|
-
const current = detectedDomains.get('frontend') || 0
|
|
393
|
-
if (current > 0) detectedDomains.set('frontend', current + 2)
|
|
394
|
-
}
|
|
250
|
+
const context: ProjectContext = {
|
|
251
|
+
domains: projectDomains,
|
|
252
|
+
agents: availableAgents,
|
|
253
|
+
stack: repoAnalysis ? { language: repoAnalysis.ecosystem } : undefined,
|
|
254
|
+
}
|
|
395
255
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
256
|
+
const { classification } = await domainClassifier.classify(
|
|
257
|
+
taskDescription,
|
|
258
|
+
projectId,
|
|
259
|
+
globalPath,
|
|
260
|
+
context
|
|
261
|
+
)
|
|
401
262
|
|
|
402
|
-
|
|
403
|
-
if (/prisma|drizzle|mongoose|typeorm|sequelize/.test(techStr)) {
|
|
404
|
-
const current = detectedDomains.get('database') || 0
|
|
405
|
-
if (current > 0) detectedDomains.set('database', current + 2)
|
|
406
|
-
}
|
|
407
|
-
}
|
|
263
|
+
const domains = [classification.primaryDomain, ...classification.secondaryDomains]
|
|
408
264
|
|
|
409
|
-
//
|
|
410
|
-
const
|
|
411
|
-
|
|
265
|
+
// Filter to domains that have corresponding agents
|
|
266
|
+
const validDomains = domains.filter((domain) =>
|
|
267
|
+
availableAgents.some(
|
|
268
|
+
(agent) =>
|
|
269
|
+
agent === domain || agent.includes(domain) || domain.includes(agent.replace('.md', ''))
|
|
270
|
+
)
|
|
271
|
+
)
|
|
412
272
|
|
|
413
|
-
// Only include domains that have corresponding agents
|
|
414
|
-
const validDomains = Array.from(detectedDomains.entries())
|
|
415
|
-
.filter(([domain]) => {
|
|
416
|
-
// Check if agent exists for this domain
|
|
417
|
-
return availableAgents.some(
|
|
418
|
-
(agent) =>
|
|
419
|
-
agent === domain || agent.includes(domain) || domain.includes(agent.replace('.md', ''))
|
|
420
|
-
)
|
|
421
|
-
})
|
|
422
|
-
.sort((a, b) => b[1] - a[1]) // Sort by score descending
|
|
423
|
-
.map(([domain]) => domain)
|
|
424
|
-
|
|
425
|
-
// If no domains detected, default to 'general'
|
|
426
273
|
if (validDomains.length === 0) {
|
|
427
274
|
return { domains: ['general'], primary: 'general' }
|
|
428
275
|
}
|
|
429
276
|
|
|
430
|
-
|
|
431
|
-
const primary = validDomains[0]
|
|
432
|
-
|
|
433
|
-
return { domains: validDomains, primary }
|
|
277
|
+
return { domains: validDomains, primary: validDomains[0] }
|
|
434
278
|
}
|
|
435
279
|
|
|
436
280
|
/**
|
|
@@ -698,6 +698,16 @@ class PromptBuilder {
|
|
|
698
698
|
}
|
|
699
699
|
}
|
|
700
700
|
|
|
701
|
+
// PRJ-264: Output schema injection for structured responses
|
|
702
|
+
const schemaType = this.getSchemaTypeForCommand(commandName)
|
|
703
|
+
if (schemaType) {
|
|
704
|
+
const { renderSchemaForPrompt } = await import('../schemas/llm-output')
|
|
705
|
+
const schemaBlock = renderSchemaForPrompt(schemaType)
|
|
706
|
+
if (schemaBlock) {
|
|
707
|
+
parts.push(`\n${schemaBlock}\n`)
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
701
711
|
// Simple execution directive
|
|
702
712
|
parts.push('\nEXECUTE: Follow flow. Use tools. Decide.\n')
|
|
703
713
|
|
|
@@ -775,6 +785,18 @@ class PromptBuilder {
|
|
|
775
785
|
return result || null
|
|
776
786
|
}
|
|
777
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Map command names to their expected output schema type.
|
|
790
|
+
* Returns null for commands that don't need structured output.
|
|
791
|
+
*/
|
|
792
|
+
private getSchemaTypeForCommand(commandName: string): string | null {
|
|
793
|
+
const schemaMap: Record<string, string> = {
|
|
794
|
+
task: 'subtaskBreakdown',
|
|
795
|
+
bug: 'classification',
|
|
796
|
+
}
|
|
797
|
+
return schemaMap[commandName] ?? null
|
|
798
|
+
}
|
|
799
|
+
|
|
778
800
|
/**
|
|
779
801
|
* Build critical anti-hallucination rules section
|
|
780
802
|
*/
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates LLM responses against Zod schemas.
|
|
5
|
+
* Provides structured error handling with re-prompt support.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Parse raw text as JSON
|
|
9
|
+
* 2. Validate against Zod schema
|
|
10
|
+
* 3. On success: return typed data
|
|
11
|
+
* 4. On failure: return validation errors for re-prompt or fallback
|
|
12
|
+
*
|
|
13
|
+
* @see PRJ-264
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { z } from 'zod'
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
export interface ValidationSuccess<T> {
|
|
23
|
+
success: true
|
|
24
|
+
data: T
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ValidationFailure {
|
|
28
|
+
success: false
|
|
29
|
+
error: string
|
|
30
|
+
/** Raw parsed JSON (may be partial) */
|
|
31
|
+
rawParsed: unknown
|
|
32
|
+
/** Zod validation issues */
|
|
33
|
+
issues: string[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type ValidationResult<T> = ValidationSuccess<T> | ValidationFailure
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Core Validation
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate a raw LLM response string against a Zod schema.
|
|
44
|
+
*
|
|
45
|
+
* Handles:
|
|
46
|
+
* - JSON parse errors (LLM returned non-JSON)
|
|
47
|
+
* - Markdown-wrapped JSON (```json ... ```)
|
|
48
|
+
* - Schema validation errors (wrong fields, types)
|
|
49
|
+
*/
|
|
50
|
+
export function validateLLMResponse<T>(raw: string, schema: z.ZodType<T>): ValidationResult<T> {
|
|
51
|
+
// Strip markdown code fences if present
|
|
52
|
+
let jsonText = raw.trim()
|
|
53
|
+
const fenceMatch = jsonText.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?\s*```$/)
|
|
54
|
+
if (fenceMatch) {
|
|
55
|
+
jsonText = fenceMatch[1].trim()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Attempt JSON parse
|
|
59
|
+
let parsed: unknown
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(jsonText)
|
|
62
|
+
} catch {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: 'Response is not valid JSON',
|
|
66
|
+
rawParsed: null,
|
|
67
|
+
issues: [`JSON parse error: expected JSON, got: ${jsonText.slice(0, 100)}...`],
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Validate against schema
|
|
72
|
+
const result = schema.safeParse(parsed)
|
|
73
|
+
if (result.success) {
|
|
74
|
+
return { success: true, data: result.data }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Extract readable error messages
|
|
78
|
+
const issues = result.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: `Schema validation failed: ${issues.join('; ')}`,
|
|
83
|
+
rawParsed: parsed,
|
|
84
|
+
issues,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Build a re-prompt message when validation fails.
|
|
90
|
+
* Includes the original error so the LLM can fix its response.
|
|
91
|
+
*/
|
|
92
|
+
export function buildReprompt(failure: ValidationFailure, schemaExample: string): string {
|
|
93
|
+
return `Your previous response was not valid. Errors:
|
|
94
|
+
${failure.issues.map((i) => `- ${i}`).join('\n')}
|
|
95
|
+
|
|
96
|
+
Return ONLY valid JSON matching this exact format (no markdown, no explanation):
|
|
97
|
+
${schemaExample}`
|
|
98
|
+
}
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* Intelligently filters context based on task type.
|
|
5
5
|
* Reduces prompt size by 40-70% while maintaining relevance.
|
|
6
6
|
*
|
|
7
|
+
* Uses LLM-based domain classification (PRJ-299) instead of keyword matching.
|
|
8
|
+
*
|
|
7
9
|
* @module agentic/smart-context
|
|
8
|
-
* @version
|
|
10
|
+
* @version 2.0
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import { agentPerformanceTracker } from '../agents'
|
|
@@ -19,6 +21,7 @@ import type {
|
|
|
19
21
|
StackInfo,
|
|
20
22
|
TaskType,
|
|
21
23
|
} from '../types'
|
|
24
|
+
import domainClassifier, { classifyWithHeuristic, type ProjectContext } from './domain-classifier'
|
|
22
25
|
|
|
23
26
|
// Re-export types for convenience
|
|
24
27
|
export type {
|
|
@@ -35,163 +38,76 @@ export type {
|
|
|
35
38
|
// Type alias exported for backward compatibility (used by external consumers)
|
|
36
39
|
export type ProjectState = SmartContextProjectState
|
|
37
40
|
|
|
41
|
+
// Map ClassificationDomain → ContextDomain
|
|
42
|
+
function toContextDomain(domain: string): ContextDomain {
|
|
43
|
+
const mapping: Record<string, ContextDomain> = {
|
|
44
|
+
frontend: 'frontend',
|
|
45
|
+
backend: 'backend',
|
|
46
|
+
database: 'backend', // database maps to backend context domain
|
|
47
|
+
devops: 'devops',
|
|
48
|
+
testing: 'testing',
|
|
49
|
+
docs: 'docs',
|
|
50
|
+
uxui: 'frontend', // uxui maps to frontend context domain
|
|
51
|
+
general: 'general',
|
|
52
|
+
}
|
|
53
|
+
return mapping[domain] || 'general'
|
|
54
|
+
}
|
|
55
|
+
|
|
38
56
|
/**
|
|
39
57
|
* SmartContext - Intelligent context filtering.
|
|
40
58
|
*/
|
|
41
59
|
class SmartContext {
|
|
42
60
|
/**
|
|
43
61
|
* Detect the domain of a task from its description.
|
|
62
|
+
*
|
|
63
|
+
* Synchronous version using the improved heuristic (word-boundary matching).
|
|
64
|
+
* For full LLM-based classification, use classifyDomain().
|
|
44
65
|
*/
|
|
45
66
|
detectDomain(taskDescription: string): DomainAnalysis {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
'style',
|
|
57
|
-
'button',
|
|
58
|
-
'form',
|
|
59
|
-
'modal',
|
|
60
|
-
'layout',
|
|
61
|
-
'responsive',
|
|
62
|
-
'animation',
|
|
63
|
-
'dom',
|
|
64
|
-
'html',
|
|
65
|
-
'frontend',
|
|
66
|
-
'fe',
|
|
67
|
-
'client',
|
|
68
|
-
'browser',
|
|
69
|
-
'jsx',
|
|
70
|
-
'tsx',
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
// Backend indicators
|
|
74
|
-
const backendKeywords = [
|
|
75
|
-
'api',
|
|
76
|
-
'server',
|
|
77
|
-
'database',
|
|
78
|
-
'db',
|
|
79
|
-
'endpoint',
|
|
80
|
-
'route',
|
|
81
|
-
'handler',
|
|
82
|
-
'controller',
|
|
83
|
-
'service',
|
|
84
|
-
'repository',
|
|
85
|
-
'model',
|
|
86
|
-
'query',
|
|
87
|
-
'backend',
|
|
88
|
-
'be',
|
|
89
|
-
'rest',
|
|
90
|
-
'graphql',
|
|
91
|
-
'prisma',
|
|
92
|
-
'sql',
|
|
93
|
-
'redis',
|
|
94
|
-
'auth',
|
|
95
|
-
]
|
|
96
|
-
|
|
97
|
-
// DevOps indicators
|
|
98
|
-
const devopsKeywords = [
|
|
99
|
-
'deploy',
|
|
100
|
-
'docker',
|
|
101
|
-
'kubernetes',
|
|
102
|
-
'k8s',
|
|
103
|
-
'ci',
|
|
104
|
-
'cd',
|
|
105
|
-
'pipeline',
|
|
106
|
-
'terraform',
|
|
107
|
-
'ansible',
|
|
108
|
-
'aws',
|
|
109
|
-
'gcp',
|
|
110
|
-
'azure',
|
|
111
|
-
'config',
|
|
112
|
-
'nginx',
|
|
113
|
-
'devops',
|
|
114
|
-
'infrastructure',
|
|
115
|
-
'monitoring',
|
|
116
|
-
'logging',
|
|
117
|
-
'build',
|
|
118
|
-
]
|
|
119
|
-
|
|
120
|
-
// Docs indicators
|
|
121
|
-
const docsKeywords = [
|
|
122
|
-
'document',
|
|
123
|
-
'docs',
|
|
124
|
-
'readme',
|
|
125
|
-
'changelog',
|
|
126
|
-
'comment',
|
|
127
|
-
'jsdoc',
|
|
128
|
-
'tutorial',
|
|
129
|
-
'guide',
|
|
130
|
-
'explain',
|
|
131
|
-
'describe',
|
|
132
|
-
'markdown',
|
|
133
|
-
]
|
|
134
|
-
|
|
135
|
-
// Testing indicators
|
|
136
|
-
const testingKeywords = [
|
|
137
|
-
'test',
|
|
138
|
-
'spec',
|
|
139
|
-
// JS/TS
|
|
140
|
-
'bun',
|
|
141
|
-
'bun test',
|
|
142
|
-
'jest',
|
|
143
|
-
'mocha',
|
|
144
|
-
'cypress',
|
|
145
|
-
'playwright',
|
|
146
|
-
// Python
|
|
147
|
-
'pytest',
|
|
148
|
-
'unittest',
|
|
149
|
-
// Go
|
|
150
|
-
'go test',
|
|
151
|
-
// Rust
|
|
152
|
-
'cargo test',
|
|
153
|
-
// .NET
|
|
154
|
-
'dotnet test',
|
|
155
|
-
// Java
|
|
156
|
-
'mvn test',
|
|
157
|
-
'gradle test',
|
|
158
|
-
'gradlew test',
|
|
159
|
-
'e2e',
|
|
160
|
-
'unit',
|
|
161
|
-
'integration',
|
|
162
|
-
'coverage',
|
|
163
|
-
'mock',
|
|
164
|
-
'fixture',
|
|
165
|
-
]
|
|
166
|
-
|
|
167
|
-
// Count matches
|
|
168
|
-
const scores: Record<ContextDomain, number> = {
|
|
169
|
-
frontend: frontendKeywords.filter((k) => lower.includes(k)).length,
|
|
170
|
-
backend: backendKeywords.filter((k) => lower.includes(k)).length,
|
|
171
|
-
devops: devopsKeywords.filter((k) => lower.includes(k)).length,
|
|
172
|
-
docs: docsKeywords.filter((k) => lower.includes(k)).length,
|
|
173
|
-
testing: testingKeywords.filter((k) => lower.includes(k)).length,
|
|
174
|
-
general: 0,
|
|
67
|
+
// Default context when no project info is available
|
|
68
|
+
const defaultContext: ProjectContext = {
|
|
69
|
+
domains: {
|
|
70
|
+
hasFrontend: true,
|
|
71
|
+
hasBackend: true,
|
|
72
|
+
hasDatabase: true,
|
|
73
|
+
hasTesting: true,
|
|
74
|
+
hasDocker: true,
|
|
75
|
+
},
|
|
76
|
+
agents: [],
|
|
175
77
|
}
|
|
176
78
|
|
|
177
|
-
|
|
178
|
-
const sorted = Object.entries(scores)
|
|
179
|
-
.filter(([_, score]) => score > 0)
|
|
180
|
-
.sort((a, b) => b[1] - a[1])
|
|
79
|
+
const result = classifyWithHeuristic(taskDescription, defaultContext)
|
|
181
80
|
|
|
182
|
-
|
|
183
|
-
|
|
81
|
+
return {
|
|
82
|
+
primary: toContextDomain(result.primaryDomain),
|
|
83
|
+
secondary: result.secondaryDomains.map(toContextDomain),
|
|
84
|
+
confidence: result.confidence,
|
|
184
85
|
}
|
|
86
|
+
}
|
|
185
87
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Classify domain using the full fallback chain (cache → history → LLM → heuristic).
|
|
90
|
+
* Async version that leverages project context and LLM classification.
|
|
91
|
+
*/
|
|
92
|
+
async classifyDomain(
|
|
93
|
+
taskDescription: string,
|
|
94
|
+
projectId: string,
|
|
95
|
+
globalPath: string,
|
|
96
|
+
context: ProjectContext
|
|
97
|
+
): Promise<DomainAnalysis & { source: string }> {
|
|
98
|
+
const { classification, source } = await domainClassifier.classify(
|
|
99
|
+
taskDescription,
|
|
100
|
+
projectId,
|
|
101
|
+
globalPath,
|
|
102
|
+
context
|
|
103
|
+
)
|
|
193
104
|
|
|
194
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
primary: toContextDomain(classification.primaryDomain),
|
|
107
|
+
secondary: classification.secondaryDomains.map(toContextDomain),
|
|
108
|
+
confidence: classification.confidence,
|
|
109
|
+
source,
|
|
110
|
+
}
|
|
195
111
|
}
|
|
196
112
|
|
|
197
113
|
/**
|