prjct-cli 0.44.1 → 0.45.3

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 (207) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/bin/prjct.ts +131 -10
  3. package/core/__tests__/agentic/memory-system.test.ts +39 -26
  4. package/core/__tests__/agentic/plan-mode.test.ts +64 -46
  5. package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
  6. package/core/__tests__/services/project-index.test.ts +353 -0
  7. package/core/__tests__/types/fs.test.ts +3 -3
  8. package/core/__tests__/utils/date-helper.test.ts +10 -10
  9. package/core/__tests__/utils/output.test.ts +9 -6
  10. package/core/__tests__/utils/project-commands.test.ts +5 -6
  11. package/core/agentic/agent-router.ts +9 -10
  12. package/core/agentic/chain-of-thought.ts +16 -4
  13. package/core/agentic/command-executor.ts +66 -40
  14. package/core/agentic/context-builder.ts +8 -5
  15. package/core/agentic/ground-truth.ts +15 -9
  16. package/core/agentic/index.ts +145 -152
  17. package/core/agentic/loop-detector.ts +40 -11
  18. package/core/agentic/memory-system.ts +98 -35
  19. package/core/agentic/orchestrator-executor.ts +135 -71
  20. package/core/agentic/plan-mode.ts +46 -16
  21. package/core/agentic/prompt-builder.ts +108 -42
  22. package/core/agentic/services.ts +10 -9
  23. package/core/agentic/skill-loader.ts +9 -15
  24. package/core/agentic/smart-context.ts +129 -79
  25. package/core/agentic/template-executor.ts +13 -12
  26. package/core/agentic/template-loader.ts +7 -4
  27. package/core/agentic/tool-registry.ts +16 -13
  28. package/core/agents/index.ts +1 -1
  29. package/core/agents/performance.ts +10 -27
  30. package/core/ai-tools/formatters.ts +8 -6
  31. package/core/ai-tools/generator.ts +4 -4
  32. package/core/ai-tools/index.ts +1 -1
  33. package/core/ai-tools/registry.ts +21 -11
  34. package/core/bus/bus.ts +23 -16
  35. package/core/bus/index.ts +2 -2
  36. package/core/cli/linear.ts +3 -5
  37. package/core/cli/start.ts +28 -25
  38. package/core/commands/analysis.ts +287 -29
  39. package/core/commands/analytics.ts +52 -44
  40. package/core/commands/base.ts +15 -13
  41. package/core/commands/cleanup.ts +6 -13
  42. package/core/commands/command-data.ts +49 -8
  43. package/core/commands/commands.ts +60 -23
  44. package/core/commands/context.ts +4 -4
  45. package/core/commands/design.ts +3 -10
  46. package/core/commands/index.ts +5 -8
  47. package/core/commands/maintenance.ts +7 -4
  48. package/core/commands/planning.ts +179 -56
  49. package/core/commands/register.ts +14 -9
  50. package/core/commands/registry.ts +15 -14
  51. package/core/commands/setup.ts +26 -14
  52. package/core/commands/shipping.ts +11 -16
  53. package/core/commands/snapshots.ts +16 -32
  54. package/core/commands/uninstall.ts +541 -0
  55. package/core/commands/workflow.ts +24 -28
  56. package/core/constants/index.ts +10 -22
  57. package/core/context/generator.ts +82 -33
  58. package/core/context-tools/files-tool.ts +583 -0
  59. package/core/context-tools/imports-tool.ts +403 -0
  60. package/core/context-tools/index.ts +433 -0
  61. package/core/context-tools/recent-tool.ts +307 -0
  62. package/core/context-tools/signatures-tool.ts +501 -0
  63. package/core/context-tools/summary-tool.ts +307 -0
  64. package/core/context-tools/token-counter.ts +284 -0
  65. package/core/context-tools/types.ts +253 -0
  66. package/core/domain/agent-generator.ts +7 -5
  67. package/core/domain/agent-loader.ts +2 -2
  68. package/core/domain/analyzer.ts +19 -16
  69. package/core/domain/architecture-generator.ts +6 -3
  70. package/core/domain/context-estimator.ts +3 -4
  71. package/core/domain/snapshot-manager.ts +25 -22
  72. package/core/domain/task-stack.ts +24 -14
  73. package/core/errors.ts +1 -1
  74. package/core/events/events.ts +2 -4
  75. package/core/events/index.ts +1 -2
  76. package/core/index.ts +28 -12
  77. package/core/infrastructure/agent-detector.ts +3 -3
  78. package/core/infrastructure/ai-provider.ts +23 -20
  79. package/core/infrastructure/author-detector.ts +16 -10
  80. package/core/infrastructure/capability-installer.ts +2 -2
  81. package/core/infrastructure/claude-agent.ts +6 -6
  82. package/core/infrastructure/command-installer.ts +22 -17
  83. package/core/infrastructure/config-manager.ts +18 -14
  84. package/core/infrastructure/editors-config.ts +8 -4
  85. package/core/infrastructure/path-manager.ts +8 -6
  86. package/core/infrastructure/permission-manager.ts +20 -17
  87. package/core/infrastructure/setup.ts +42 -38
  88. package/core/infrastructure/update-checker.ts +5 -5
  89. package/core/integrations/issue-tracker/enricher.ts +8 -19
  90. package/core/integrations/issue-tracker/index.ts +2 -2
  91. package/core/integrations/issue-tracker/manager.ts +15 -15
  92. package/core/integrations/issue-tracker/types.ts +5 -22
  93. package/core/integrations/jira/client.ts +67 -59
  94. package/core/integrations/jira/index.ts +11 -14
  95. package/core/integrations/jira/mcp-adapter.ts +5 -10
  96. package/core/integrations/jira/service.ts +10 -10
  97. package/core/integrations/linear/client.ts +27 -18
  98. package/core/integrations/linear/index.ts +9 -12
  99. package/core/integrations/linear/service.ts +11 -11
  100. package/core/integrations/linear/sync.ts +8 -8
  101. package/core/outcomes/analyzer.ts +5 -18
  102. package/core/outcomes/index.ts +2 -2
  103. package/core/outcomes/recorder.ts +3 -3
  104. package/core/plugin/builtin/webhook.ts +19 -15
  105. package/core/plugin/hooks.ts +29 -21
  106. package/core/plugin/index.ts +7 -7
  107. package/core/plugin/loader.ts +19 -19
  108. package/core/plugin/registry.ts +12 -23
  109. package/core/schemas/agents.ts +1 -1
  110. package/core/schemas/analysis.ts +1 -1
  111. package/core/schemas/enriched-task.ts +62 -49
  112. package/core/schemas/ideas.ts +13 -13
  113. package/core/schemas/index.ts +17 -27
  114. package/core/schemas/issues.ts +40 -25
  115. package/core/schemas/metrics.ts +143 -0
  116. package/core/schemas/outcomes.ts +70 -62
  117. package/core/schemas/permissions.ts +15 -12
  118. package/core/schemas/prd.ts +27 -14
  119. package/core/schemas/project.ts +3 -3
  120. package/core/schemas/roadmap.ts +47 -34
  121. package/core/schemas/schemas.ts +3 -4
  122. package/core/schemas/shipped.ts +3 -3
  123. package/core/schemas/state.ts +43 -29
  124. package/core/server/index.ts +5 -6
  125. package/core/server/routes-extended.ts +68 -72
  126. package/core/server/routes.ts +3 -3
  127. package/core/server/server.ts +31 -26
  128. package/core/services/agent-generator.ts +237 -0
  129. package/core/services/agent-service.ts +2 -2
  130. package/core/services/breakdown-service.ts +2 -4
  131. package/core/services/context-generator.ts +299 -0
  132. package/core/services/context-selector.ts +420 -0
  133. package/core/services/doctor-service.ts +426 -0
  134. package/core/services/file-categorizer.ts +448 -0
  135. package/core/services/file-scorer.ts +270 -0
  136. package/core/services/git-analyzer.ts +267 -0
  137. package/core/services/index.ts +27 -10
  138. package/core/services/memory-service.ts +3 -4
  139. package/core/services/project-index.ts +911 -0
  140. package/core/services/project-service.ts +4 -4
  141. package/core/services/skill-installer.ts +14 -17
  142. package/core/services/skill-lock.ts +3 -3
  143. package/core/services/skill-service.ts +12 -6
  144. package/core/services/stack-detector.ts +245 -0
  145. package/core/services/sync-service.ts +170 -329
  146. package/core/services/watch-service.ts +294 -0
  147. package/core/session/compaction.ts +23 -31
  148. package/core/session/index.ts +11 -5
  149. package/core/session/log-migration.ts +3 -3
  150. package/core/session/metrics.ts +19 -14
  151. package/core/session/session-log-manager.ts +12 -17
  152. package/core/session/task-session-manager.ts +25 -25
  153. package/core/session/utils.ts +1 -1
  154. package/core/storage/ideas-storage.ts +41 -57
  155. package/core/storage/index-storage.ts +514 -0
  156. package/core/storage/index.ts +41 -13
  157. package/core/storage/metrics-storage.ts +320 -0
  158. package/core/storage/queue-storage.ts +35 -45
  159. package/core/storage/shipped-storage.ts +17 -20
  160. package/core/storage/state-storage.ts +50 -30
  161. package/core/storage/storage-manager.ts +6 -6
  162. package/core/storage/storage.ts +18 -15
  163. package/core/sync/auth-config.ts +3 -3
  164. package/core/sync/index.ts +13 -19
  165. package/core/sync/oauth-handler.ts +3 -3
  166. package/core/sync/sync-client.ts +4 -9
  167. package/core/sync/sync-manager.ts +12 -14
  168. package/core/types/commands.ts +42 -7
  169. package/core/types/index.ts +284 -302
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +49 -0
  172. package/core/types/utils.ts +3 -3
  173. package/core/utils/agent-stream.ts +3 -1
  174. package/core/utils/animations.ts +14 -11
  175. package/core/utils/branding.ts +7 -7
  176. package/core/utils/cache.ts +1 -3
  177. package/core/utils/collection-filters.ts +3 -15
  178. package/core/utils/date-helper.ts +2 -7
  179. package/core/utils/file-helper.ts +13 -8
  180. package/core/utils/jsonl-helper.ts +13 -10
  181. package/core/utils/keychain.ts +4 -8
  182. package/core/utils/logger.ts +1 -1
  183. package/core/utils/next-steps.ts +3 -3
  184. package/core/utils/output.ts +58 -11
  185. package/core/utils/project-commands.ts +6 -6
  186. package/core/utils/project-credentials.ts +5 -12
  187. package/core/utils/runtime.ts +2 -2
  188. package/core/utils/session-helper.ts +3 -4
  189. package/core/utils/version.ts +3 -3
  190. package/core/wizard/index.ts +13 -0
  191. package/core/wizard/onboarding.ts +633 -0
  192. package/core/workflow/state-machine.ts +7 -7
  193. package/dist/bin/prjct.mjs +18907 -13189
  194. package/dist/core/infrastructure/command-installer.js +96 -111
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +256 -257
  197. package/dist/core/utils/version.js +9 -9
  198. package/package.json +11 -12
  199. package/scripts/build.js +3 -3
  200. package/scripts/postinstall.js +2 -2
  201. package/templates/mcp-config.json +6 -1
  202. package/templates/permissions/permissive.jsonc +1 -1
  203. package/templates/permissions/strict.jsonc +5 -9
  204. package/templates/global/docs/agents.md +0 -88
  205. package/templates/global/docs/architecture.md +0 -103
  206. package/templates/global/docs/commands.md +0 -96
  207. package/templates/global/docs/validation.md +0 -95
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Metrics Schema
3
+ *
4
+ * Defines the structure for metrics.json - value dashboard metrics.
5
+ * Tracks token savings, sync performance, and usage trends.
6
+ *
7
+ * Uses Zod for runtime validation and TypeScript type inference.
8
+ * @version 1.0.0
9
+ */
10
+
11
+ import { z } from 'zod'
12
+
13
+ // =============================================================================
14
+ // Zod Schemas - Source of Truth
15
+ // =============================================================================
16
+
17
+ /**
18
+ * Daily stats for trend analysis
19
+ */
20
+ export const DailyStatsSchema = z.object({
21
+ date: z.string(), // YYYY-MM-DD
22
+ tokensSaved: z.number(), // Tokens saved that day
23
+ syncs: z.number(), // Number of syncs
24
+ avgCompressionRate: z.number(), // Average compression rate (0-1)
25
+ totalDuration: z.number(), // Total sync time in ms
26
+ })
27
+
28
+ /**
29
+ * Agent usage tracking
30
+ */
31
+ export const AgentUsageSchema = z.object({
32
+ agentName: z.string(), // e.g., "backend", "frontend"
33
+ usageCount: z.number(), // Times invoked
34
+ tokensSaved: z.number(), // Tokens saved by this agent
35
+ })
36
+
37
+ /**
38
+ * Main metrics JSON structure
39
+ */
40
+ export const MetricsJsonSchema = z.object({
41
+ // Token metrics
42
+ totalTokensSaved: z.number(),
43
+ avgCompressionRate: z.number(), // 0-1 (e.g., 0.63 = 63% reduction)
44
+
45
+ // Sync metrics
46
+ syncCount: z.number(),
47
+ watchTriggers: z.number(), // Auto-syncs from watch mode
48
+ avgSyncDuration: z.number(), // Average in ms
49
+ totalSyncDuration: z.number(), // Total in ms
50
+
51
+ // Agent usage
52
+ agentUsage: z.array(AgentUsageSchema),
53
+
54
+ // Time series for trends
55
+ dailyStats: z.array(DailyStatsSchema),
56
+
57
+ // Metadata
58
+ firstSync: z.string(), // ISO8601 - when tracking started
59
+ lastUpdated: z.string(), // ISO8601
60
+ })
61
+
62
+ // =============================================================================
63
+ // Inferred Types
64
+ // =============================================================================
65
+
66
+ export type DailyStats = z.infer<typeof DailyStatsSchema>
67
+ export type AgentUsage = z.infer<typeof AgentUsageSchema>
68
+ export type MetricsJson = z.infer<typeof MetricsJsonSchema>
69
+
70
+ // =============================================================================
71
+ // Validation Helpers
72
+ // =============================================================================
73
+
74
+ /** Parse and validate metrics.json content */
75
+ export const parseMetrics = (data: unknown): MetricsJson => MetricsJsonSchema.parse(data)
76
+
77
+ /** Safe parse with error result */
78
+ export const safeParseMetrics = (data: unknown) => MetricsJsonSchema.safeParse(data)
79
+
80
+ // =============================================================================
81
+ // Defaults
82
+ // =============================================================================
83
+
84
+ export const DEFAULT_METRICS: MetricsJson = {
85
+ totalTokensSaved: 0,
86
+ avgCompressionRate: 0,
87
+ syncCount: 0,
88
+ watchTriggers: 0,
89
+ avgSyncDuration: 0,
90
+ totalSyncDuration: 0,
91
+ agentUsage: [],
92
+ dailyStats: [],
93
+ firstSync: '',
94
+ lastUpdated: '',
95
+ }
96
+
97
+ // =============================================================================
98
+ // Cost Calculation Constants (January 2026 Pricing)
99
+ // =============================================================================
100
+
101
+ /**
102
+ * Token costs per 1000 tokens (INPUT pricing)
103
+ * Source: https://docs.anthropic.com/en/docs/about-claude/models
104
+ *
105
+ * Used for estimating cost savings from context compression
106
+ */
107
+ export const TOKEN_COSTS = {
108
+ // Current models (2026)
109
+ 'claude-opus-4.5': 0.005, // $5/M input - flagship
110
+ 'claude-sonnet-4.5': 0.003, // $3/M input - balanced
111
+ 'claude-haiku-4.5': 0.001, // $1/M input - fastest
112
+ // Legacy models
113
+ 'claude-opus-4': 0.015, // $15/M input
114
+ 'claude-sonnet-4': 0.003, // $3/M input
115
+ 'claude-3-opus': 0.015, // $15/M input (deprecated)
116
+ 'claude-3-sonnet': 0.003, // $3/M input (deprecated)
117
+ // Other providers
118
+ 'gpt-4o': 0.0025, // $2.50/M input
119
+ 'gpt-4': 0.01, // $10/M input (legacy)
120
+ 'gemini-pro': 0.00125, // $1.25/M input
121
+ // Default: Claude Sonnet (most common for Claude Code)
122
+ default: 0.003, // $3/M input
123
+ } as const
124
+
125
+ export type ModelName = keyof typeof TOKEN_COSTS
126
+
127
+ /**
128
+ * Calculate estimated cost saved based on tokens
129
+ */
130
+ export function estimateCostSaved(tokens: number, model: ModelName = 'default'): number {
131
+ const costPer1k = TOKEN_COSTS[model] || TOKEN_COSTS.default
132
+ return (tokens / 1000) * costPer1k
133
+ }
134
+
135
+ /**
136
+ * Format cost as currency string
137
+ */
138
+ export function formatCost(cost: number): string {
139
+ if (cost < 0.01) {
140
+ return `$${(cost * 100).toFixed(2)}¢`
141
+ }
142
+ return `$${cost.toFixed(2)}`
143
+ }
@@ -32,7 +32,7 @@ export const VarianceReasonSchema = z.enum([
32
32
  'requirements_changed',
33
33
  'optimistic_estimate',
34
34
  'team_changes',
35
- 'other'
35
+ 'other',
36
36
  ])
37
37
 
38
38
  // -----------------------------------------------------------------------------
@@ -50,11 +50,11 @@ export const EffortComparisonSchema = z.object({
50
50
  commits: z.number().optional(),
51
51
  linesAdded: z.number().optional(),
52
52
  linesRemoved: z.number().optional(),
53
- sessions: z.number().optional(), // Number of work sessions
53
+ sessions: z.number().optional(), // Number of work sessions
54
54
  }),
55
55
  variance: z.object({
56
- hours: z.number(), // actual - estimated
57
- percentage: z.number(), // ((actual - estimated) / estimated) * 100
56
+ hours: z.number(), // actual - estimated
57
+ percentage: z.number(), // ((actual - estimated) / estimated) * 100
58
58
  reason: VarianceReasonSchema.optional(),
59
59
  explanation: z.string().optional(),
60
60
  }),
@@ -70,8 +70,8 @@ export const MetricResultSchema = z.object({
70
70
  target: z.number(),
71
71
  actual: z.number(),
72
72
  unit: z.string(),
73
- achieved: z.boolean(), // actual >= target (or <= for decrease metrics)
74
- percentOfTarget: z.number(), // (actual / target) * 100
73
+ achieved: z.boolean(), // actual >= target (or <= for decrease metrics)
74
+ percentOfTarget: z.number(), // (actual / target) * 100
75
75
  })
76
76
 
77
77
  export const AcceptanceCriteriaResultSchema = z.object({
@@ -84,7 +84,7 @@ export const SuccessTrackingSchema = z.object({
84
84
  metrics: z.array(MetricResultSchema),
85
85
  acceptanceCriteria: z.array(AcceptanceCriteriaResultSchema),
86
86
  overallSuccess: SuccessLevelSchema,
87
- successScore: z.number().min(0).max(100), // Percentage of metrics/criteria met
87
+ successScore: z.number().min(0).max(100), // Percentage of metrics/criteria met
88
88
  })
89
89
 
90
90
  // -----------------------------------------------------------------------------
@@ -100,11 +100,11 @@ export const LearningSchema = z.object({
100
100
  'tooling',
101
101
  'architecture',
102
102
  'testing',
103
- 'other'
103
+ 'other',
104
104
  ]),
105
105
  insight: z.string(),
106
106
  actionable: z.boolean(),
107
- action: z.string().optional(), // What to do differently next time
107
+ action: z.string().optional(), // What to do differently next time
108
108
  })
109
109
 
110
110
  export const LearningsSchema = z.object({
@@ -119,7 +119,7 @@ export const LearningsSchema = z.object({
119
119
  // -----------------------------------------------------------------------------
120
120
 
121
121
  export const ROIAssessmentSchema = z.object({
122
- valueDelivered: z.number().min(1).max(10), // Subjective 1-10 score
122
+ valueDelivered: z.number().min(1).max(10), // Subjective 1-10 score
123
123
  userImpact: z.enum(['none', 'low', 'medium', 'high', 'critical']),
124
124
  businessImpact: z.enum(['none', 'low', 'medium', 'high', 'critical']),
125
125
 
@@ -140,7 +140,7 @@ export const ROIAssessmentSchema = z.object({
140
140
  // -----------------------------------------------------------------------------
141
141
 
142
142
  export const TaskOutcomeSchema = z.object({
143
- id: z.string(), // out_task_xxxxxxxx
143
+ id: z.string(), // out_task_xxxxxxxx
144
144
  taskId: z.string(),
145
145
  description: z.string(),
146
146
 
@@ -167,12 +167,12 @@ export const TaskOutcomeSchema = z.object({
167
167
  // -----------------------------------------------------------------------------
168
168
 
169
169
  export const FeatureOutcomeSchema = z.object({
170
- id: z.string(), // out_feat_xxxxxxxx
170
+ id: z.string(), // out_feat_xxxxxxxx
171
171
 
172
172
  // Links
173
173
  featureId: z.string(),
174
174
  featureName: z.string(),
175
- prdId: z.string().nullable(), // null for legacy features
175
+ prdId: z.string().nullable(), // null for legacy features
176
176
 
177
177
  // Version info
178
178
  version: z.string().optional(),
@@ -200,11 +200,11 @@ export const FeatureOutcomeSchema = z.object({
200
200
  // Timestamps
201
201
  startedAt: z.string(),
202
202
  shippedAt: z.string(),
203
- reviewedAt: z.string().optional(), // When impact was captured
203
+ reviewedAt: z.string().optional(), // When impact was captured
204
204
 
205
205
  // Metadata
206
- reviewedBy: z.string().optional(), // Who filled out the impact review
207
- legacy: z.boolean().optional(), // Legacy feature (no PRD)
206
+ reviewedBy: z.string().optional(), // Who filled out the impact review
207
+ legacy: z.boolean().optional(), // Legacy feature (no PRD)
208
208
  })
209
209
 
210
210
  // -----------------------------------------------------------------------------
@@ -213,8 +213,8 @@ export const FeatureOutcomeSchema = z.object({
213
213
 
214
214
  export const AggregateMetricsSchema = z.object({
215
215
  totalFeatures: z.number(),
216
- averageEstimationAccuracy: z.number(), // Percentage
217
- averageSuccessRate: z.number(), // Percentage
216
+ averageEstimationAccuracy: z.number(), // Percentage
217
+ averageSuccessRate: z.number(), // Percentage
218
218
  averageROI: z.number(),
219
219
 
220
220
  // By category
@@ -226,17 +226,21 @@ export const AggregateMetricsSchema = z.object({
226
226
  }),
227
227
 
228
228
  // Variance patterns
229
- variancePatterns: z.array(z.object({
230
- reason: VarianceReasonSchema,
231
- count: z.number(),
232
- averageVariance: z.number(),
233
- })),
229
+ variancePatterns: z.array(
230
+ z.object({
231
+ reason: VarianceReasonSchema,
232
+ count: z.number(),
233
+ averageVariance: z.number(),
234
+ })
235
+ ),
234
236
 
235
237
  // Top learnings (aggregated)
236
- topLearnings: z.array(z.object({
237
- insight: z.string(),
238
- frequency: z.number(),
239
- })),
238
+ topLearnings: z.array(
239
+ z.object({
240
+ insight: z.string(),
241
+ frequency: z.number(),
242
+ })
243
+ ),
240
244
  })
241
245
 
242
246
  // -----------------------------------------------------------------------------
@@ -245,7 +249,7 @@ export const AggregateMetricsSchema = z.object({
245
249
 
246
250
  export const OutcomesJsonSchema = z.object({
247
251
  outcomes: z.array(FeatureOutcomeSchema),
248
- taskOutcomes: z.array(TaskOutcomeSchema).optional(), // Standalone task outcomes
252
+ taskOutcomes: z.array(TaskOutcomeSchema).optional(), // Standalone task outcomes
249
253
  aggregates: AggregateMetricsSchema.optional(),
250
254
  lastUpdated: z.string(),
251
255
  lastAggregated: z.string().optional(),
@@ -286,7 +290,8 @@ export const parseOutcomes = (data: unknown): OutcomesJson => OutcomesJsonSchema
286
290
  export const safeParseOutcomes = (data: unknown) => OutcomesJsonSchema.safeParse(data)
287
291
 
288
292
  /** Parse a single feature outcome */
289
- export const parseFeatureOutcome = (data: unknown): FeatureOutcome => FeatureOutcomeSchema.parse(data)
293
+ export const parseFeatureOutcome = (data: unknown): FeatureOutcome =>
294
+ FeatureOutcomeSchema.parse(data)
290
295
  export const safeParseFeatureOutcome = (data: unknown) => FeatureOutcomeSchema.safeParse(data)
291
296
 
292
297
  // =============================================================================
@@ -335,7 +340,7 @@ export const calculateVariance = (
335
340
  */
336
341
  export const calculateROIScore = (valueDelivered: number, actualHours: number): number => {
337
342
  if (actualHours <= 0) return valueDelivered * 10
338
- return Math.round((valueDelivered * 10 / actualHours) * 100) / 100
343
+ return Math.round(((valueDelivered * 10) / actualHours) * 100) / 100
339
344
  }
340
345
 
341
346
  /**
@@ -348,8 +353,8 @@ export const calculateSuccessScore = (
348
353
  const totalItems = metrics.length + acceptanceCriteria.length
349
354
  if (totalItems === 0) return 100
350
355
 
351
- const metMetrics = metrics.filter(m => m.achieved).length
352
- const metCriteria = acceptanceCriteria.filter(ac => ac.met).length
356
+ const metMetrics = metrics.filter((m) => m.achieved).length
357
+ const metCriteria = acceptanceCriteria.filter((ac) => ac.met).length
353
358
 
354
359
  return Math.round(((metMetrics + metCriteria) / totalItems) * 100)
355
360
  }
@@ -389,34 +394,33 @@ export const aggregateOutcomes = (outcomes: FeatureOutcome[]): AggregateMetrics
389
394
  }
390
395
 
391
396
  // Calculate averages
392
- const accuracies = outcomes.map(o =>
393
- calculateEstimationAccuracy(o.effort.variance.percentage)
394
- )
395
- const successRates = outcomes
396
- .filter(o => o.success)
397
- .map(o => o.success!.successScore)
398
- const rois = outcomes.map(o => o.roi.roiScore)
397
+ const accuracies = outcomes.map((o) => calculateEstimationAccuracy(o.effort.variance.percentage))
398
+ const successRates = outcomes.filter((o) => o.success).map((o) => o.success!.successScore)
399
+ const rois = outcomes.map((o) => o.roi.roiScore)
399
400
 
400
401
  // Count by success level
401
402
  const bySuccessLevel = {
402
- exceeded: outcomes.filter(o => o.success?.overallSuccess === 'exceeded').length,
403
- met: outcomes.filter(o => o.success?.overallSuccess === 'met').length,
404
- partial: outcomes.filter(o => o.success?.overallSuccess === 'partial').length,
405
- failed: outcomes.filter(o => o.success?.overallSuccess === 'failed').length,
403
+ exceeded: outcomes.filter((o) => o.success?.overallSuccess === 'exceeded').length,
404
+ met: outcomes.filter((o) => o.success?.overallSuccess === 'met').length,
405
+ partial: outcomes.filter((o) => o.success?.overallSuccess === 'partial').length,
406
+ failed: outcomes.filter((o) => o.success?.overallSuccess === 'failed').length,
406
407
  }
407
408
 
408
409
  // Variance patterns
409
410
  const varianceReasons = outcomes
410
- .filter(o => o.effort.variance.reason)
411
- .reduce((acc, o) => {
412
- const reason = o.effort.variance.reason!
413
- if (!acc[reason]) {
414
- acc[reason] = { count: 0, totalVariance: 0 }
415
- }
416
- acc[reason].count++
417
- acc[reason].totalVariance += o.effort.variance.percentage
418
- return acc
419
- }, {} as Record<VarianceReason, { count: number; totalVariance: number }>)
411
+ .filter((o) => o.effort.variance.reason)
412
+ .reduce(
413
+ (acc, o) => {
414
+ const reason = o.effort.variance.reason!
415
+ if (!acc[reason]) {
416
+ acc[reason] = { count: 0, totalVariance: 0 }
417
+ }
418
+ acc[reason].count++
419
+ acc[reason].totalVariance += o.effort.variance.percentage
420
+ return acc
421
+ },
422
+ {} as Record<VarianceReason, { count: number; totalVariance: number }>
423
+ )
420
424
 
421
425
  const variancePatterns = Object.entries(varianceReasons).map(([reason, data]) => ({
422
426
  reason: reason as VarianceReason,
@@ -425,14 +429,17 @@ export const aggregateOutcomes = (outcomes: FeatureOutcome[]): AggregateMetrics
425
429
  }))
426
430
 
427
431
  // Top learnings
428
- const allLearnings = outcomes.flatMap(o => [
432
+ const allLearnings = outcomes.flatMap((o) => [
429
433
  ...o.learnings.whatWorked,
430
434
  ...o.learnings.whatDidnt,
431
435
  ])
432
- const learningCounts = allLearnings.reduce((acc, learning) => {
433
- acc[learning] = (acc[learning] || 0) + 1
434
- return acc
435
- }, {} as Record<string, number>)
436
+ const learningCounts = allLearnings.reduce(
437
+ (acc, learning) => {
438
+ acc[learning] = (acc[learning] || 0) + 1
439
+ return acc
440
+ },
441
+ {} as Record<string, number>
442
+ )
436
443
 
437
444
  const topLearnings = Object.entries(learningCounts)
438
445
  .sort((a, b) => b[1] - a[1])
@@ -444,10 +451,11 @@ export const aggregateOutcomes = (outcomes: FeatureOutcome[]): AggregateMetrics
444
451
  averageEstimationAccuracy: Math.round(
445
452
  accuracies.reduce((a, b) => a + b, 0) / accuracies.length
446
453
  ),
447
- averageSuccessRate: successRates.length > 0
448
- ? Math.round(successRates.reduce((a, b) => a + b, 0) / successRates.length)
449
- : 0,
450
- averageROI: Math.round(rois.reduce((a, b) => a + b, 0) / rois.length * 100) / 100,
454
+ averageSuccessRate:
455
+ successRates.length > 0
456
+ ? Math.round(successRates.reduce((a, b) => a + b, 0) / successRates.length)
457
+ : 0,
458
+ averageROI: Math.round((rois.reduce((a, b) => a + b, 0) / rois.length) * 100) / 100,
451
459
  bySuccessLevel,
452
460
  variancePatterns,
453
461
  topLearnings,
@@ -38,11 +38,13 @@ export const PermissionsConfigSchema = z.object({
38
38
  bash: BashPermissionSchema.optional(),
39
39
 
40
40
  /** File operation permissions - glob patterns */
41
- files: z.object({
42
- read: FilePermissionSchema.optional(),
43
- write: FilePermissionSchema.optional(),
44
- delete: FilePermissionSchema.optional(),
45
- }).optional(),
41
+ files: z
42
+ .object({
43
+ read: FilePermissionSchema.optional(),
44
+ write: FilePermissionSchema.optional(),
45
+ delete: FilePermissionSchema.optional(),
46
+ })
47
+ .optional(),
46
48
 
47
49
  /** Web fetch permissions */
48
50
  web: WebPermissionSchema.optional(),
@@ -51,10 +53,12 @@ export const PermissionsConfigSchema = z.object({
51
53
  skills: z.record(z.string(), PermissionLevelSchema).optional(),
52
54
 
53
55
  /** Doom loop protection - prevent infinite retries */
54
- doomLoop: z.object({
55
- enabled: z.boolean().default(true),
56
- maxRetries: z.number().default(3),
57
- }).optional(),
56
+ doomLoop: z
57
+ .object({
58
+ enabled: z.boolean().default(true),
59
+ maxRetries: z.number().default(3),
60
+ })
61
+ .optional(),
58
62
 
59
63
  /** External directory access */
60
64
  externalDirectories: PermissionLevelSchema.default('ask'),
@@ -117,7 +121,7 @@ export const DEFAULT_BASH_ASK: string[] = [
117
121
  export const DEFAULT_BASH_DENY: string[] = [
118
122
  'rm -rf /*',
119
123
  'rm -rf ~/*',
120
- ':(){ :|:& };:*', // Fork bomb
124
+ ':(){ :|:& };:*', // Fork bomb
121
125
  'mkfs*',
122
126
  'dd if=*of=/dev/*',
123
127
  ]
@@ -167,8 +171,7 @@ export function buildDefaultPermissions(): PermissionsConfig {
167
171
  export const parsePermissions = (data: unknown): PermissionsConfig =>
168
172
  PermissionsConfigSchema.parse(data)
169
173
 
170
- export const safeParsePermissions = (data: unknown) =>
171
- PermissionsConfigSchema.safeParse(data)
174
+ export const safeParsePermissions = (data: unknown) => PermissionsConfigSchema.safeParse(data)
172
175
 
173
176
  // =============================================================================
174
177
  // Defaults
@@ -16,7 +16,13 @@ import { z } from 'zod'
16
16
  // Zod Schemas - Source of Truth
17
17
  // =============================================================================
18
18
 
19
- export const PRDStatusSchema = z.enum(['draft', 'approved', 'in_progress', 'completed', 'cancelled'])
19
+ export const PRDStatusSchema = z.enum([
20
+ 'draft',
21
+ 'approved',
22
+ 'in_progress',
23
+ 'completed',
24
+ 'cancelled',
25
+ ])
20
26
  export const PRDSizeSchema = z.enum(['XS', 'S', 'M', 'L', 'XL'])
21
27
  export const ImpactLevelSchema = z.enum(['critical', 'high', 'medium', 'low'])
22
28
  export const ConfidenceLevelSchema = z.enum(['low', 'medium', 'high'])
@@ -87,10 +93,14 @@ export const APIEndpointSchema = z.object({
87
93
  auth: z.enum(['required', 'optional', 'none']),
88
94
  input: z.record(z.string()).optional(),
89
95
  output: z.record(z.string()).optional(),
90
- errors: z.array(z.object({
91
- code: z.number(),
92
- description: z.string(),
93
- })).optional(),
96
+ errors: z
97
+ .array(
98
+ z.object({
99
+ code: z.number(),
100
+ description: z.string(),
101
+ })
102
+ )
103
+ .optional(),
94
104
  })
95
105
 
96
106
  export const APIContractsSchema = z.object({
@@ -180,10 +190,10 @@ export const ImplementationPhaseSchema = z.object({
180
190
  })
181
191
 
182
192
  export const MVPScopeSchema = z.object({
183
- p0: z.array(z.string()), // Must-have
184
- p1: z.array(z.string()), // Should-have
185
- p2: z.array(z.string()), // Nice-to-have
186
- p3: z.array(z.string()), // Future
193
+ p0: z.array(z.string()), // Must-have
194
+ p1: z.array(z.string()), // Should-have
195
+ p2: z.array(z.string()), // Nice-to-have
196
+ p3: z.array(z.string()), // Future
187
197
  })
188
198
 
189
199
  export const ImplementationRoadmapSchema = z.object({
@@ -265,7 +275,7 @@ export const PRDOutcomesSchema = z.object({
265
275
  // -----------------------------------------------------------------------------
266
276
 
267
277
  export const PRDItemSchema = z.object({
268
- id: z.string(), // prd_xxxxxxxx
278
+ id: z.string(), // prd_xxxxxxxx
269
279
  title: z.string(),
270
280
  status: PRDStatusSchema,
271
281
  size: PRDSizeSchema,
@@ -286,9 +296,9 @@ export const PRDItemSchema = z.object({
286
296
  value: ValueAssessmentSchema.optional(),
287
297
 
288
298
  // Links (for integration)
289
- featureId: z.string().nullable(), // Link to roadmap feature
290
- phase: z.string().nullable(), // P0, P1, etc.
291
- quarter: z.string().nullable(), // Q1-2026, etc.
299
+ featureId: z.string().nullable(), // Link to roadmap feature
300
+ phase: z.string().nullable(), // P0, P1, etc.
301
+ quarter: z.string().nullable(), // Q1-2026, etc.
292
302
 
293
303
  // Metadata
294
304
  createdAt: z.string(),
@@ -358,7 +368,10 @@ export const safeParsePRD = (data: unknown) => PRDItemSchema.safeParse(data)
358
368
  // Defaults
359
369
  // =============================================================================
360
370
 
361
- export const DEFAULT_PRD: Omit<PRDItem, 'id' | 'title' | 'problem' | 'roadmap' | 'estimation' | 'successCriteria'> = {
371
+ export const DEFAULT_PRD: Omit<
372
+ PRDItem,
373
+ 'id' | 'title' | 'problem' | 'roadmap' | 'estimation' | 'successCriteria'
374
+ > = {
362
375
  status: 'draft',
363
376
  size: 'M',
364
377
  featureId: null,
@@ -23,8 +23,8 @@ export const ProjectItemSchema = z.object({
23
23
  techStack: z.array(z.string()),
24
24
  fileCount: z.number(),
25
25
  commitCount: z.number(),
26
- createdAt: z.string(), // ISO8601
27
- lastSync: z.string(), // ISO8601
26
+ createdAt: z.string(), // ISO8601
27
+ lastSync: z.string(), // ISO8601
28
28
  })
29
29
 
30
30
  // =============================================================================
@@ -50,5 +50,5 @@ export const DEFAULT_PROJECT: Omit<ProjectSchema, 'projectId' | 'name' | 'repoPa
50
50
  fileCount: 0,
51
51
  commitCount: 0,
52
52
  createdAt: new Date().toISOString(),
53
- lastSync: new Date().toISOString()
53
+ lastSync: new Date().toISOString(),
54
54
  }