prjct-cli 1.7.5 → 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 +205 -1
- package/bin/prjct.ts +14 -0
- package/core/__tests__/agentic/command-context.test.ts +281 -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__/domain/fibonacci.test.ts +113 -0
- package/core/__tests__/infrastructure/performance-tracker.test.ts +328 -0
- package/core/__tests__/schemas/model.test.ts +272 -0
- package/core/agentic/command-classifier.ts +141 -0
- package/core/agentic/command-context.ts +168 -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 +50 -55
- package/core/agentic/response-validator.ts +98 -0
- package/core/agentic/smart-context.ts +60 -144
- package/core/commands/command-data.ts +17 -0
- package/core/commands/commands.ts +9 -0
- package/core/commands/performance.ts +114 -0
- package/core/commands/register.ts +6 -0
- package/core/commands/workflow.ts +87 -4
- package/core/config/command-context.config.json +66 -0
- package/core/domain/fibonacci.ts +128 -0
- package/core/index.ts +25 -1
- package/core/infrastructure/ai-provider.ts +35 -0
- package/core/infrastructure/performance-tracker.ts +326 -0
- package/core/schemas/analysis.ts +4 -0
- package/core/schemas/classification.ts +91 -0
- package/core/schemas/command-context.ts +29 -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/performance.ts +128 -0
- package/core/schemas/state.ts +9 -0
- package/core/storage/state-storage.ts +21 -0
- package/core/types/config.ts +2 -0
- package/core/types/provider.ts +12 -0
- package/dist/bin/prjct.mjs +3184 -1945
- package/dist/core/infrastructure/command-installer.js +78 -7
- package/dist/core/infrastructure/setup.js +78 -7
- package/package.json +1 -1
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Context Schema
|
|
3
|
+
*
|
|
4
|
+
* Zod schemas for command-context.config.json.
|
|
5
|
+
* Controls which context sections (agents, patterns, checklists, modules)
|
|
6
|
+
* get injected into prompts for each command.
|
|
7
|
+
*
|
|
8
|
+
* @see PRJ-298
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { z } from 'zod'
|
|
12
|
+
|
|
13
|
+
export const CommandContextEntrySchema = z.object({
|
|
14
|
+
agents: z.boolean(),
|
|
15
|
+
patterns: z.boolean(),
|
|
16
|
+
checklist: z.boolean(),
|
|
17
|
+
modules: z.array(z.string()),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export const CommandContextConfigSchema = z.object({
|
|
21
|
+
version: z.string(),
|
|
22
|
+
description: z.string().optional(),
|
|
23
|
+
commands: z.record(z.string(), CommandContextEntrySchema).refine((commands) => '*' in commands, {
|
|
24
|
+
message: 'Config must include a "*" wildcard entry for unknown commands',
|
|
25
|
+
}),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export type CommandContextEntry = z.infer<typeof CommandContextEntrySchema>
|
|
29
|
+
export type CommandContextConfig = z.infer<typeof CommandContextConfigSchema>
|
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
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Schema
|
|
3
|
+
*
|
|
4
|
+
* Defines metric types for the PerformanceTracker service.
|
|
5
|
+
* Uses JSONL storage (append-only) with rotation at 5MB.
|
|
6
|
+
*
|
|
7
|
+
* @see PRJ-297
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { z } from 'zod'
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Metric Schemas
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
export const MetricNameSchema = z.enum([
|
|
17
|
+
'startup_time',
|
|
18
|
+
'heap_used',
|
|
19
|
+
'heap_total',
|
|
20
|
+
'rss',
|
|
21
|
+
'external_memory',
|
|
22
|
+
'context_correctness',
|
|
23
|
+
'subtask_handoff',
|
|
24
|
+
'analysis_state',
|
|
25
|
+
'token_usage',
|
|
26
|
+
'command_duration',
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
export const MemorySnapshotSchema = z.object({
|
|
30
|
+
heapUsed: z.number(),
|
|
31
|
+
heapTotal: z.number(),
|
|
32
|
+
rss: z.number(),
|
|
33
|
+
external: z.number(),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export const PerformanceMetricSchema = z.object({
|
|
37
|
+
timestamp: z.string(),
|
|
38
|
+
metric: MetricNameSchema,
|
|
39
|
+
value: z.number(),
|
|
40
|
+
unit: z.string(),
|
|
41
|
+
context: z.record(z.string(), z.unknown()).optional(),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export const ContextCorrectnessSchema = z.object({
|
|
45
|
+
timestamp: z.string(),
|
|
46
|
+
metric: z.literal('context_correctness'),
|
|
47
|
+
taskId: z.string(),
|
|
48
|
+
receivedSync: z.boolean(),
|
|
49
|
+
syncFieldsInjected: z.array(z.string()).optional(),
|
|
50
|
+
tokensBudgetUsed: z.number().optional(),
|
|
51
|
+
tokensBudgetTotal: z.number().optional(),
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
export const SubtaskHandoffSchema = z.object({
|
|
55
|
+
timestamp: z.string(),
|
|
56
|
+
metric: z.literal('subtask_handoff'),
|
|
57
|
+
taskId: z.string(),
|
|
58
|
+
subtaskId: z.string(),
|
|
59
|
+
outputPopulated: z.boolean(),
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
export const AnalysisStateSchema = z.object({
|
|
63
|
+
timestamp: z.string(),
|
|
64
|
+
metric: z.literal('analysis_state'),
|
|
65
|
+
state: z.enum(['draft', 'verified', 'sealed']),
|
|
66
|
+
commitHash: z.string(),
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
/** Union of all metric entry types stored in performance.jsonl */
|
|
70
|
+
export const PerformanceEntrySchema = z.union([
|
|
71
|
+
PerformanceMetricSchema,
|
|
72
|
+
ContextCorrectnessSchema,
|
|
73
|
+
SubtaskHandoffSchema,
|
|
74
|
+
AnalysisStateSchema,
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
// =============================================================================
|
|
78
|
+
// Report Schemas
|
|
79
|
+
// =============================================================================
|
|
80
|
+
|
|
81
|
+
export const MetricSummarySchema = z.object({
|
|
82
|
+
avg: z.number(),
|
|
83
|
+
min: z.number(),
|
|
84
|
+
max: z.number(),
|
|
85
|
+
count: z.number(),
|
|
86
|
+
unit: z.string(),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
export const PerformanceReportSchema = z.object({
|
|
90
|
+
period: z.string(),
|
|
91
|
+
startup: MetricSummarySchema.optional(),
|
|
92
|
+
memory: z
|
|
93
|
+
.object({
|
|
94
|
+
avgHeapMB: z.number(),
|
|
95
|
+
peakHeapMB: z.number(),
|
|
96
|
+
avgRssMB: z.number(),
|
|
97
|
+
})
|
|
98
|
+
.optional(),
|
|
99
|
+
contextCorrectness: z
|
|
100
|
+
.object({
|
|
101
|
+
total: z.number(),
|
|
102
|
+
receivedSync: z.number(),
|
|
103
|
+
rate: z.number(),
|
|
104
|
+
})
|
|
105
|
+
.optional(),
|
|
106
|
+
subtaskHandoff: z
|
|
107
|
+
.object({
|
|
108
|
+
total: z.number(),
|
|
109
|
+
outputPopulated: z.number(),
|
|
110
|
+
rate: z.number(),
|
|
111
|
+
})
|
|
112
|
+
.optional(),
|
|
113
|
+
commandDurations: z.record(z.string(), MetricSummarySchema).optional(),
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// =============================================================================
|
|
117
|
+
// Inferred Types
|
|
118
|
+
// =============================================================================
|
|
119
|
+
|
|
120
|
+
export type MetricName = z.infer<typeof MetricNameSchema>
|
|
121
|
+
export type MemorySnapshot = z.infer<typeof MemorySnapshotSchema>
|
|
122
|
+
export type PerformanceMetric = z.infer<typeof PerformanceMetricSchema>
|
|
123
|
+
export type ContextCorrectness = z.infer<typeof ContextCorrectnessSchema>
|
|
124
|
+
export type SubtaskHandoff = z.infer<typeof SubtaskHandoffSchema>
|
|
125
|
+
export type AnalysisState = z.infer<typeof AnalysisStateSchema>
|
|
126
|
+
export type PerformanceEntry = z.infer<typeof PerformanceEntrySchema>
|
|
127
|
+
export type MetricSummary = z.infer<typeof MetricSummarySchema>
|
|
128
|
+
export type PerformanceReport = z.infer<typeof PerformanceReportSchema>
|
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
|
|
@@ -62,6 +63,9 @@ export const SubtaskSchema = z.object({
|
|
|
62
63
|
summary: SubtaskSummarySchema.optional(), // Full summary for context handoff
|
|
63
64
|
skipReason: z.string().optional(), // Why this subtask was skipped
|
|
64
65
|
blockReason: z.string().optional(), // What is blocking this subtask
|
|
66
|
+
// Fibonacci estimation
|
|
67
|
+
estimatedPoints: z.number().optional(), // Fibonacci: 1,2,3,5,8,13,21
|
|
68
|
+
estimatedMinutes: z.number().optional(), // Derived from points
|
|
65
69
|
})
|
|
66
70
|
|
|
67
71
|
// Subtask progress tracking
|
|
@@ -84,6 +88,11 @@ export const CurrentTaskSchema = z.object({
|
|
|
84
88
|
// Linear integration - bidirectional sync
|
|
85
89
|
linearId: z.string().optional(), // "PRJ-123" - Linear identifier
|
|
86
90
|
linearUuid: z.string().optional(), // Linear internal UUID for API calls
|
|
91
|
+
// Fibonacci estimation
|
|
92
|
+
estimatedPoints: z.number().optional(), // Fibonacci: 1,2,3,5,8,13,21
|
|
93
|
+
estimatedMinutes: z.number().optional(), // Derived from points
|
|
94
|
+
// Model specification - which AI model was used (PRJ-265)
|
|
95
|
+
modelMetadata: ModelMetadataSchema.optional(),
|
|
87
96
|
})
|
|
88
97
|
|
|
89
98
|
export const PreviousTaskSchema = z.object({
|
|
@@ -196,6 +196,27 @@ class StateStorage extends StorageManager<StateJson> {
|
|
|
196
196
|
return currentTask
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Update fields on the current task (partial update)
|
|
201
|
+
*/
|
|
202
|
+
async updateCurrentTask(
|
|
203
|
+
projectId: string,
|
|
204
|
+
fields: Partial<CurrentTask>
|
|
205
|
+
): Promise<CurrentTask | null> {
|
|
206
|
+
const state = await this.read(projectId)
|
|
207
|
+
if (!state.currentTask) return null
|
|
208
|
+
|
|
209
|
+
const updated: CurrentTask = { ...state.currentTask, ...fields }
|
|
210
|
+
|
|
211
|
+
await this.update(projectId, (s) => ({
|
|
212
|
+
...s,
|
|
213
|
+
currentTask: updated,
|
|
214
|
+
lastUpdated: getTimestamp(),
|
|
215
|
+
}))
|
|
216
|
+
|
|
217
|
+
return updated
|
|
218
|
+
}
|
|
219
|
+
|
|
199
220
|
/**
|
|
200
221
|
* Complete current task
|
|
201
222
|
*/
|
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
|
/**
|