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
|
@@ -21,6 +21,7 @@ import { exec } from 'node:child_process'
|
|
|
21
21
|
import os from 'node:os'
|
|
22
22
|
import path from 'node:path'
|
|
23
23
|
import { promisify } from 'node:util'
|
|
24
|
+
import { compareSemver } from '../schemas/model'
|
|
24
25
|
import { fileExists } from '../utils/fs-helpers'
|
|
25
26
|
import { readProviderCache, writeProviderCache } from '../utils/provider-cache'
|
|
26
27
|
|
|
@@ -58,6 +59,9 @@ export const ClaudeProvider: AIProviderConfig = {
|
|
|
58
59
|
ignoreFile: '.claudeignore',
|
|
59
60
|
websiteUrl: 'https://www.anthropic.com/claude',
|
|
60
61
|
docsUrl: 'https://docs.anthropic.com/claude-code',
|
|
62
|
+
defaultModel: 'sonnet',
|
|
63
|
+
supportedModels: ['opus', 'sonnet', 'haiku'],
|
|
64
|
+
minCliVersion: '1.0.0',
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
/**
|
|
@@ -77,6 +81,9 @@ export const GeminiProvider: AIProviderConfig = {
|
|
|
77
81
|
ignoreFile: '.geminiignore',
|
|
78
82
|
websiteUrl: 'https://geminicli.com',
|
|
79
83
|
docsUrl: 'https://geminicli.com/docs',
|
|
84
|
+
defaultModel: '2.5-flash',
|
|
85
|
+
supportedModels: ['2.5-pro', '2.5-flash', '2.0-flash'],
|
|
86
|
+
minCliVersion: '1.0.0',
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
/**
|
|
@@ -100,6 +107,9 @@ export const AntigravityProvider: AIProviderConfig = {
|
|
|
100
107
|
ignoreFile: '.agentignore', // Assumed
|
|
101
108
|
websiteUrl: 'https://gemini.google.com/app/antigravity',
|
|
102
109
|
docsUrl: 'https://gemini.google.com/app/antigravity',
|
|
110
|
+
defaultModel: null, // Platform-managed
|
|
111
|
+
supportedModels: [],
|
|
112
|
+
minCliVersion: null,
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
/**
|
|
@@ -129,6 +139,9 @@ export const CursorProvider: AIProviderConfig = {
|
|
|
129
139
|
isProjectLevel: true, // Config is project-level only
|
|
130
140
|
websiteUrl: 'https://cursor.com',
|
|
131
141
|
docsUrl: 'https://cursor.com/docs',
|
|
142
|
+
defaultModel: null, // Multi-model IDE, user selects
|
|
143
|
+
supportedModels: [],
|
|
144
|
+
minCliVersion: null,
|
|
132
145
|
}
|
|
133
146
|
|
|
134
147
|
/**
|
|
@@ -159,6 +172,9 @@ export const WindsurfProvider: AIProviderConfig = {
|
|
|
159
172
|
isProjectLevel: true, // Config is project-level only
|
|
160
173
|
websiteUrl: 'https://windsurf.com',
|
|
161
174
|
docsUrl: 'https://docs.windsurf.com',
|
|
175
|
+
defaultModel: null, // Multi-model IDE, user selects
|
|
176
|
+
supportedModels: [],
|
|
177
|
+
minCliVersion: null,
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
/**
|
|
@@ -221,14 +237,33 @@ export async function detectProvider(provider: AIProviderName): Promise<Provider
|
|
|
221
237
|
}
|
|
222
238
|
|
|
223
239
|
const version = await getCliVersion(config.cliCommand)
|
|
240
|
+
const versionWarning = validateCliVersion(provider, version || undefined)
|
|
224
241
|
|
|
225
242
|
return {
|
|
226
243
|
installed: true,
|
|
227
244
|
version: version || undefined,
|
|
228
245
|
path: cliPath,
|
|
246
|
+
versionWarning: versionWarning || undefined,
|
|
229
247
|
}
|
|
230
248
|
}
|
|
231
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Validate that a detected CLI version meets the provider's minimum requirement.
|
|
252
|
+
* Returns a warning message if the version is below minimum, or null if OK.
|
|
253
|
+
*/
|
|
254
|
+
export function validateCliVersion(
|
|
255
|
+
provider: AIProviderName,
|
|
256
|
+
version: string | undefined
|
|
257
|
+
): string | null {
|
|
258
|
+
const config = Providers[provider]
|
|
259
|
+
if (!config.minCliVersion || !version) return null
|
|
260
|
+
|
|
261
|
+
if (compareSemver(version, config.minCliVersion) < 0) {
|
|
262
|
+
return `⚠️ ${config.displayName} v${version} is below minimum v${config.minCliVersion}. Some features may not work correctly.`
|
|
263
|
+
}
|
|
264
|
+
return null
|
|
265
|
+
}
|
|
266
|
+
|
|
232
267
|
/**
|
|
233
268
|
* Detect all available CLI-based providers
|
|
234
269
|
* Results are cached to disk with a 10-minute TTL to avoid redundant shell spawns.
|
package/core/schemas/analysis.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Defines the structure for analysis.json - repository analysis.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type { ModelMetadata } from './model'
|
|
8
|
+
|
|
7
9
|
export interface CodePattern {
|
|
8
10
|
name: string
|
|
9
11
|
description: string
|
|
@@ -28,6 +30,8 @@ export interface AnalysisSchema {
|
|
|
28
30
|
patterns: CodePattern[]
|
|
29
31
|
antiPatterns: AntiPattern[]
|
|
30
32
|
analyzedAt: string // ISO8601
|
|
33
|
+
/** Which AI model was used for this analysis (PRJ-265) */
|
|
34
|
+
modelMetadata?: ModelMetadata
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
export const DEFAULT_ANALYSIS: Omit<AnalysisSchema, 'projectId'> = {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Classification Schema
|
|
3
|
+
*
|
|
4
|
+
* Defines the structure for LLM-based domain classification results.
|
|
5
|
+
* Replaces hardcoded keyword lists with structured classification output.
|
|
6
|
+
*
|
|
7
|
+
* @see PRJ-299
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { z } from 'zod'
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Classification Schemas
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
export const ClassificationDomainSchema = z.enum([
|
|
17
|
+
'frontend',
|
|
18
|
+
'backend',
|
|
19
|
+
'database',
|
|
20
|
+
'devops',
|
|
21
|
+
'testing',
|
|
22
|
+
'docs',
|
|
23
|
+
'uxui',
|
|
24
|
+
'general',
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
export const TaskClassificationSchema = z.object({
|
|
28
|
+
/** Primary domain for this task */
|
|
29
|
+
primaryDomain: ClassificationDomainSchema,
|
|
30
|
+
/** Secondary domains that are also relevant */
|
|
31
|
+
secondaryDomains: z.array(ClassificationDomainSchema),
|
|
32
|
+
/** Confidence in the classification (0-1) */
|
|
33
|
+
confidence: z.number().min(0).max(1),
|
|
34
|
+
/** Glob patterns for relevant files */
|
|
35
|
+
filePatterns: z.array(z.string()),
|
|
36
|
+
/** Agent names that should handle this task */
|
|
37
|
+
relevantAgents: z.array(z.string()),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
export const ClassificationCacheEntrySchema = z.object({
|
|
41
|
+
/** The classification result */
|
|
42
|
+
classification: TaskClassificationSchema,
|
|
43
|
+
/** When this was classified */
|
|
44
|
+
classifiedAt: z.string(),
|
|
45
|
+
/** How this was classified */
|
|
46
|
+
source: z.enum(['cache', 'history', 'llm', 'heuristic']),
|
|
47
|
+
/** Hash of the task description for cache lookup */
|
|
48
|
+
descriptionHash: z.string(),
|
|
49
|
+
/** Project ID this classification belongs to */
|
|
50
|
+
projectId: z.string(),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
export const ClassificationCacheSchema = z.object({
|
|
54
|
+
/** Cached classifications keyed by descriptionHash */
|
|
55
|
+
entries: z.record(z.string(), ClassificationCacheEntrySchema),
|
|
56
|
+
/** Confirmed patterns from successful task completions */
|
|
57
|
+
confirmedPatterns: z.array(
|
|
58
|
+
z.object({
|
|
59
|
+
descriptionHash: z.string(),
|
|
60
|
+
classification: TaskClassificationSchema,
|
|
61
|
+
confirmedAt: z.string(),
|
|
62
|
+
taskDescription: z.string(),
|
|
63
|
+
})
|
|
64
|
+
),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// =============================================================================
|
|
68
|
+
// Inferred Types
|
|
69
|
+
// =============================================================================
|
|
70
|
+
|
|
71
|
+
export type ClassificationDomain = z.infer<typeof ClassificationDomainSchema>
|
|
72
|
+
export type TaskClassification = z.infer<typeof TaskClassificationSchema>
|
|
73
|
+
export type ClassificationCacheEntry = z.infer<typeof ClassificationCacheEntrySchema>
|
|
74
|
+
export type ClassificationCache = z.infer<typeof ClassificationCacheSchema>
|
|
75
|
+
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// Defaults
|
|
78
|
+
// =============================================================================
|
|
79
|
+
|
|
80
|
+
export const DEFAULT_CLASSIFICATION_CACHE: ClassificationCache = {
|
|
81
|
+
entries: {},
|
|
82
|
+
confirmedPatterns: [],
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const GENERAL_CLASSIFICATION: TaskClassification = {
|
|
86
|
+
primaryDomain: 'general',
|
|
87
|
+
secondaryDomains: [],
|
|
88
|
+
confidence: 0.3,
|
|
89
|
+
filePatterns: ['**/*.ts', '**/*.js'],
|
|
90
|
+
relevantAgents: [],
|
|
91
|
+
}
|
package/core/schemas/index.ts
CHANGED
|
@@ -16,10 +16,16 @@
|
|
|
16
16
|
export * from './agents'
|
|
17
17
|
// Analysis
|
|
18
18
|
export * from './analysis'
|
|
19
|
+
// Classification (LLM-based domain detection)
|
|
20
|
+
export * from './classification'
|
|
19
21
|
// Ideas
|
|
20
22
|
export * from './ideas'
|
|
21
23
|
// Issues (local cache of issue tracker issues)
|
|
22
24
|
export * from './issues'
|
|
25
|
+
// LLM output schemas (structured response validation)
|
|
26
|
+
export * from './llm-output'
|
|
27
|
+
// Model specification (AI provider model tracking)
|
|
28
|
+
export * from './model'
|
|
23
29
|
// Outcomes
|
|
24
30
|
export * from './outcomes'
|
|
25
31
|
// Permissions
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Output Schemas
|
|
3
|
+
*
|
|
4
|
+
* Zod schemas for all LLM prompt response types.
|
|
5
|
+
* These schemas are:
|
|
6
|
+
* 1. Injected into prompts as explicit format instructions
|
|
7
|
+
* 2. Used to validate LLM responses before storage or use
|
|
8
|
+
* 3. Define the contract between prompts and response handling
|
|
9
|
+
*
|
|
10
|
+
* @see PRJ-264
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { z } from 'zod'
|
|
14
|
+
import { ClassificationDomainSchema, TaskClassificationSchema } from './classification'
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Re-export classification schema (it IS an LLM output schema)
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
export { TaskClassificationSchema } from './classification'
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Agent Assignment Schema
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
/** LLM response when selecting which agent should handle a task */
|
|
27
|
+
export const AgentAssignmentSchema = z.object({
|
|
28
|
+
/** Agent file name (e.g., "backend.md", "frontend.md") */
|
|
29
|
+
agentName: z.string(),
|
|
30
|
+
/** Why this agent was selected */
|
|
31
|
+
reasoning: z.string(),
|
|
32
|
+
/** Confidence in the assignment (0-1) */
|
|
33
|
+
confidence: z.number().min(0).max(1),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Subtask Breakdown Schema
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
/** LLM response when breaking a task into subtasks */
|
|
41
|
+
export const SubtaskBreakdownSchema = z.object({
|
|
42
|
+
/** Subtasks in execution order */
|
|
43
|
+
subtasks: z.array(
|
|
44
|
+
z.object({
|
|
45
|
+
/** Short description of the subtask */
|
|
46
|
+
description: z.string(),
|
|
47
|
+
/** Domain this subtask belongs to */
|
|
48
|
+
domain: ClassificationDomainSchema,
|
|
49
|
+
/** Suggested agent for this subtask */
|
|
50
|
+
agent: z.string(),
|
|
51
|
+
/** IDs of subtasks this depends on (by index, 0-based) */
|
|
52
|
+
dependsOn: z.array(z.number()),
|
|
53
|
+
})
|
|
54
|
+
),
|
|
55
|
+
/** Estimated total effort */
|
|
56
|
+
effort: z.enum(['low', 'medium', 'high']),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Schema-to-Prompt Serializer
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Registry of output schemas keyed by prompt type.
|
|
65
|
+
* Used by prompt-builder to inject the correct schema into each prompt.
|
|
66
|
+
*/
|
|
67
|
+
export const OUTPUT_SCHEMAS: Record<string, { schema: z.ZodTypeAny; example: string }> = {
|
|
68
|
+
classification: {
|
|
69
|
+
schema: TaskClassificationSchema,
|
|
70
|
+
example: JSON.stringify(
|
|
71
|
+
{
|
|
72
|
+
primaryDomain: 'backend',
|
|
73
|
+
secondaryDomains: ['database'],
|
|
74
|
+
confidence: 0.9,
|
|
75
|
+
filePatterns: ['src/api/**'],
|
|
76
|
+
relevantAgents: ['backend.md'],
|
|
77
|
+
},
|
|
78
|
+
null,
|
|
79
|
+
2
|
|
80
|
+
),
|
|
81
|
+
},
|
|
82
|
+
agentAssignment: {
|
|
83
|
+
schema: AgentAssignmentSchema,
|
|
84
|
+
example: JSON.stringify(
|
|
85
|
+
{
|
|
86
|
+
agentName: 'backend.md',
|
|
87
|
+
reasoning: 'Task involves API endpoint creation',
|
|
88
|
+
confidence: 0.85,
|
|
89
|
+
},
|
|
90
|
+
null,
|
|
91
|
+
2
|
|
92
|
+
),
|
|
93
|
+
},
|
|
94
|
+
subtaskBreakdown: {
|
|
95
|
+
schema: SubtaskBreakdownSchema,
|
|
96
|
+
example: JSON.stringify(
|
|
97
|
+
{
|
|
98
|
+
subtasks: [
|
|
99
|
+
{
|
|
100
|
+
description: 'Add schema validation',
|
|
101
|
+
domain: 'backend',
|
|
102
|
+
agent: 'backend.md',
|
|
103
|
+
dependsOn: [],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
description: 'Add unit tests',
|
|
107
|
+
domain: 'testing',
|
|
108
|
+
agent: 'testing.md',
|
|
109
|
+
dependsOn: [0],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
effort: 'medium',
|
|
113
|
+
},
|
|
114
|
+
null,
|
|
115
|
+
2
|
|
116
|
+
),
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Render a schema as prompt instructions.
|
|
122
|
+
* Returns a markdown block that tells the LLM exactly what format to use.
|
|
123
|
+
*/
|
|
124
|
+
export function renderSchemaForPrompt(schemaType: string): string | null {
|
|
125
|
+
const entry = OUTPUT_SCHEMAS[schemaType]
|
|
126
|
+
if (!entry) return null
|
|
127
|
+
|
|
128
|
+
return `## OUTPUT FORMAT
|
|
129
|
+
|
|
130
|
+
Return ONLY valid JSON matching this schema (no markdown, no explanation):
|
|
131
|
+
|
|
132
|
+
\`\`\`json
|
|
133
|
+
${entry.example}
|
|
134
|
+
\`\`\`
|
|
135
|
+
|
|
136
|
+
Fields:
|
|
137
|
+
${describeSchema(entry.schema)}`
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Extract field descriptions from a Zod object schema.
|
|
142
|
+
*/
|
|
143
|
+
function describeSchema(schema: z.ZodTypeAny): string {
|
|
144
|
+
if (schema instanceof z.ZodObject) {
|
|
145
|
+
const shape = schema.shape as Record<string, z.ZodTypeAny>
|
|
146
|
+
return Object.entries(shape)
|
|
147
|
+
.map(([key, field]) => `- \`${key}\`: ${describeField(field)}`)
|
|
148
|
+
.join('\n')
|
|
149
|
+
}
|
|
150
|
+
return '(see example above)'
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Describe a single Zod field type for prompt injection.
|
|
155
|
+
*/
|
|
156
|
+
function describeField(field: z.ZodTypeAny): string {
|
|
157
|
+
if (field instanceof z.ZodString) return 'string'
|
|
158
|
+
if (field instanceof z.ZodNumber) return 'number'
|
|
159
|
+
if (field instanceof z.ZodEnum) return `one of: ${(field.options as string[]).join(', ')}`
|
|
160
|
+
if (field instanceof z.ZodArray) return `array of ${describeField(field.element)}`
|
|
161
|
+
if (field instanceof z.ZodObject) return 'object'
|
|
162
|
+
return 'any'
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// =============================================================================
|
|
166
|
+
// Inferred Types
|
|
167
|
+
// =============================================================================
|
|
168
|
+
|
|
169
|
+
export type AgentAssignment = z.infer<typeof AgentAssignmentSchema>
|
|
170
|
+
export type SubtaskBreakdown = z.infer<typeof SubtaskBreakdownSchema>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Schema
|
|
3
|
+
*
|
|
4
|
+
* Defines model specification types for AI providers.
|
|
5
|
+
* Records which model was used for each analysis and task,
|
|
6
|
+
* enabling consistency tracking and mismatch warnings.
|
|
7
|
+
*
|
|
8
|
+
* @see PRJ-265
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { z } from 'zod'
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Provider-Specific Model Identifiers
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/** Claude model identifiers (short names matching agent frontmatter convention) */
|
|
18
|
+
export const ClaudeModelSchema = z.enum(['opus', 'sonnet', 'haiku'])
|
|
19
|
+
|
|
20
|
+
/** Gemini model identifiers */
|
|
21
|
+
export const GeminiModelSchema = z.enum(['2.5-pro', '2.5-flash', '2.0-flash'])
|
|
22
|
+
|
|
23
|
+
/** Generic model identifier - allows any string for future providers */
|
|
24
|
+
export const AIModelSchema = z.string().min(1)
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Supported Models Per Provider
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
export const SUPPORTED_MODELS: Record<string, readonly string[]> = {
|
|
31
|
+
claude: ['opus', 'sonnet', 'haiku'],
|
|
32
|
+
gemini: ['2.5-pro', '2.5-flash', '2.0-flash'],
|
|
33
|
+
cursor: [], // Multi-model IDE, user selects model
|
|
34
|
+
windsurf: [], // Multi-model IDE, user selects model
|
|
35
|
+
antigravity: [], // Platform-managed
|
|
36
|
+
} as const
|
|
37
|
+
|
|
38
|
+
export const DEFAULT_MODELS: Record<string, string> = {
|
|
39
|
+
claude: 'sonnet',
|
|
40
|
+
gemini: '2.5-flash',
|
|
41
|
+
} as const
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Minimum CLI Versions
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
export const MIN_CLI_VERSIONS: Record<string, string> = {
|
|
48
|
+
claude: '1.0.0',
|
|
49
|
+
gemini: '1.0.0',
|
|
50
|
+
} as const
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Model Metadata - Recorded Per Operation
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
/** Model metadata recorded with each analysis or task */
|
|
57
|
+
export const ModelMetadataSchema = z.object({
|
|
58
|
+
/** Provider name (e.g., 'claude', 'gemini') */
|
|
59
|
+
provider: z.string(),
|
|
60
|
+
/** Model identifier (e.g., 'opus', 'sonnet', '2.5-pro') */
|
|
61
|
+
model: z.string(),
|
|
62
|
+
/** CLI version used */
|
|
63
|
+
cliVersion: z.string().optional(),
|
|
64
|
+
/** When this was recorded */
|
|
65
|
+
recordedAt: z.string(),
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// Model Configuration - Per Project
|
|
70
|
+
// =============================================================================
|
|
71
|
+
|
|
72
|
+
/** Per-project model preference */
|
|
73
|
+
export const ModelPreferenceSchema = z.object({
|
|
74
|
+
/** Preferred model for this project */
|
|
75
|
+
preferredModel: z.string().optional(),
|
|
76
|
+
/** Model used for last analysis (for mismatch detection) */
|
|
77
|
+
lastAnalysisModel: ModelMetadataSchema.optional(),
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// Inferred Types
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
export type ClaudeModel = z.infer<typeof ClaudeModelSchema>
|
|
85
|
+
export type GeminiModel = z.infer<typeof GeminiModelSchema>
|
|
86
|
+
export type ModelMetadata = z.infer<typeof ModelMetadataSchema>
|
|
87
|
+
export type ModelPreference = z.infer<typeof ModelPreferenceSchema>
|
|
88
|
+
|
|
89
|
+
// =============================================================================
|
|
90
|
+
// Validation Helpers
|
|
91
|
+
// =============================================================================
|
|
92
|
+
|
|
93
|
+
/** Check if a model is valid for a given provider */
|
|
94
|
+
export function isValidModelForProvider(provider: string, model: string): boolean {
|
|
95
|
+
const supported = SUPPORTED_MODELS[provider]
|
|
96
|
+
if (!supported || supported.length === 0) return true // No restriction for multi-model IDEs
|
|
97
|
+
return supported.includes(model)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Get the default model for a provider */
|
|
101
|
+
export function getDefaultModel(provider: string): string | null {
|
|
102
|
+
return DEFAULT_MODELS[provider] ?? null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Get supported models for a provider */
|
|
106
|
+
export function getSupportedModels(provider: string): readonly string[] {
|
|
107
|
+
return SUPPORTED_MODELS[provider] ?? []
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Get minimum CLI version for a provider */
|
|
111
|
+
export function getMinCliVersion(provider: string): string | null {
|
|
112
|
+
return MIN_CLI_VERSIONS[provider] ?? null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Compare semver versions. Returns:
|
|
117
|
+
* -1 if a < b
|
|
118
|
+
* 0 if a == b
|
|
119
|
+
* 1 if a > b
|
|
120
|
+
*/
|
|
121
|
+
export function compareSemver(a: string, b: string): -1 | 0 | 1 {
|
|
122
|
+
const pa = a.split('.').map(Number)
|
|
123
|
+
const pb = b.split('.').map(Number)
|
|
124
|
+
for (let i = 0; i < 3; i++) {
|
|
125
|
+
const va = pa[i] ?? 0
|
|
126
|
+
const vb = pb[i] ?? 0
|
|
127
|
+
if (va < vb) return -1
|
|
128
|
+
if (va > vb) return 1
|
|
129
|
+
}
|
|
130
|
+
return 0
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Check if a CLI version meets minimum requirements */
|
|
134
|
+
export function meetsMinVersion(provider: string, version: string): boolean {
|
|
135
|
+
const min = MIN_CLI_VERSIONS[provider]
|
|
136
|
+
if (!min) return true // No minimum defined
|
|
137
|
+
return compareSemver(version, min) >= 0
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check for model mismatch between analysis and current task.
|
|
142
|
+
* Returns a warning message if the models differ, or null if they match.
|
|
143
|
+
*/
|
|
144
|
+
export function checkModelMismatch(
|
|
145
|
+
analysisModel: ModelMetadata | undefined,
|
|
146
|
+
taskModel: ModelMetadata | undefined
|
|
147
|
+
): string | null {
|
|
148
|
+
if (!analysisModel || !taskModel) return null
|
|
149
|
+
if (analysisModel.provider !== taskModel.provider || analysisModel.model !== taskModel.model) {
|
|
150
|
+
return `⚠️ Model mismatch: analysis used ${analysisModel.provider}/${analysisModel.model}, but task is using ${taskModel.provider}/${taskModel.model}. Results may differ.`
|
|
151
|
+
}
|
|
152
|
+
return null
|
|
153
|
+
}
|
package/core/schemas/state.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { z } from 'zod'
|
|
12
|
+
import { ModelMetadataSchema } from './model'
|
|
12
13
|
|
|
13
14
|
// =============================================================================
|
|
14
15
|
// Zod Schemas - Source of Truth
|
|
@@ -90,6 +91,8 @@ export const CurrentTaskSchema = z.object({
|
|
|
90
91
|
// Fibonacci estimation
|
|
91
92
|
estimatedPoints: z.number().optional(), // Fibonacci: 1,2,3,5,8,13,21
|
|
92
93
|
estimatedMinutes: z.number().optional(), // Derived from points
|
|
94
|
+
// Model specification - which AI model was used (PRJ-265)
|
|
95
|
+
modelMetadata: ModelMetadataSchema.optional(),
|
|
93
96
|
})
|
|
94
97
|
|
|
95
98
|
export const PreviousTaskSchema = z.object({
|
package/core/types/config.ts
CHANGED
package/core/types/provider.ts
CHANGED
|
@@ -78,6 +78,15 @@ export interface AIProviderConfig {
|
|
|
78
78
|
|
|
79
79
|
/** URL for provider documentation */
|
|
80
80
|
docsUrl: string
|
|
81
|
+
|
|
82
|
+
/** Default model for this provider (e.g., 'sonnet', '2.5-flash'). Null for multi-model IDEs */
|
|
83
|
+
defaultModel: string | null
|
|
84
|
+
|
|
85
|
+
/** Supported model identifiers. Empty array for multi-model IDEs (user selects model) */
|
|
86
|
+
supportedModels: readonly string[]
|
|
87
|
+
|
|
88
|
+
/** Minimum CLI version required. Null for non-CLI providers */
|
|
89
|
+
minCliVersion: string | null
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
/**
|
|
@@ -92,6 +101,9 @@ export interface ProviderDetectionResult {
|
|
|
92
101
|
|
|
93
102
|
/** Path to the CLI executable */
|
|
94
103
|
path?: string
|
|
104
|
+
|
|
105
|
+
/** Warning if CLI version is below minimum requirement */
|
|
106
|
+
versionWarning?: string
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
/**
|