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.
Files changed (42) hide show
  1. package/CHANGELOG.md +205 -1
  2. package/bin/prjct.ts +14 -0
  3. package/core/__tests__/agentic/command-context.test.ts +281 -0
  4. package/core/__tests__/agentic/domain-classifier.test.ts +330 -0
  5. package/core/__tests__/agentic/response-validator.test.ts +263 -0
  6. package/core/__tests__/agentic/smart-context.test.ts +3 -3
  7. package/core/__tests__/domain/fibonacci.test.ts +113 -0
  8. package/core/__tests__/infrastructure/performance-tracker.test.ts +328 -0
  9. package/core/__tests__/schemas/model.test.ts +272 -0
  10. package/core/agentic/command-classifier.ts +141 -0
  11. package/core/agentic/command-context.ts +168 -0
  12. package/core/agentic/domain-classifier.ts +525 -0
  13. package/core/agentic/index.ts +1 -0
  14. package/core/agentic/orchestrator-executor.ts +43 -199
  15. package/core/agentic/prompt-builder.ts +50 -55
  16. package/core/agentic/response-validator.ts +98 -0
  17. package/core/agentic/smart-context.ts +60 -144
  18. package/core/commands/command-data.ts +17 -0
  19. package/core/commands/commands.ts +9 -0
  20. package/core/commands/performance.ts +114 -0
  21. package/core/commands/register.ts +6 -0
  22. package/core/commands/workflow.ts +87 -4
  23. package/core/config/command-context.config.json +66 -0
  24. package/core/domain/fibonacci.ts +128 -0
  25. package/core/index.ts +25 -1
  26. package/core/infrastructure/ai-provider.ts +35 -0
  27. package/core/infrastructure/performance-tracker.ts +326 -0
  28. package/core/schemas/analysis.ts +4 -0
  29. package/core/schemas/classification.ts +91 -0
  30. package/core/schemas/command-context.ts +29 -0
  31. package/core/schemas/index.ts +6 -0
  32. package/core/schemas/llm-output.ts +170 -0
  33. package/core/schemas/model.ts +153 -0
  34. package/core/schemas/performance.ts +128 -0
  35. package/core/schemas/state.ts +9 -0
  36. package/core/storage/state-storage.ts +21 -0
  37. package/core/types/config.ts +2 -0
  38. package/core/types/provider.ts +12 -0
  39. package/dist/bin/prjct.mjs +3184 -1945
  40. package/dist/core/infrastructure/command-installer.js +78 -7
  41. package/dist/core/infrastructure/setup.js +78 -7
  42. 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>
@@ -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>
@@ -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
  */
@@ -78,6 +78,8 @@ export interface ProjectSettings {
78
78
  autoCommit?: boolean
79
79
  commitFooter?: string
80
80
  branchNaming?: string
81
+ /** Preferred AI model for this project (e.g., 'opus', 'sonnet', '2.5-pro') */
82
+ preferredModel?: string
81
83
  }
82
84
 
83
85
  /**
@@ -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
  /**