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.
- package/CHANGELOG.md +114 -0
- package/bin/prjct.ts +131 -10
- package/core/__tests__/agentic/memory-system.test.ts +39 -26
- package/core/__tests__/agentic/plan-mode.test.ts +64 -46
- package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
- package/core/__tests__/services/project-index.test.ts +353 -0
- package/core/__tests__/types/fs.test.ts +3 -3
- package/core/__tests__/utils/date-helper.test.ts +10 -10
- package/core/__tests__/utils/output.test.ts +9 -6
- package/core/__tests__/utils/project-commands.test.ts +5 -6
- package/core/agentic/agent-router.ts +9 -10
- package/core/agentic/chain-of-thought.ts +16 -4
- package/core/agentic/command-executor.ts +66 -40
- package/core/agentic/context-builder.ts +8 -5
- package/core/agentic/ground-truth.ts +15 -9
- package/core/agentic/index.ts +145 -152
- package/core/agentic/loop-detector.ts +40 -11
- package/core/agentic/memory-system.ts +98 -35
- package/core/agentic/orchestrator-executor.ts +135 -71
- package/core/agentic/plan-mode.ts +46 -16
- package/core/agentic/prompt-builder.ts +108 -42
- package/core/agentic/services.ts +10 -9
- package/core/agentic/skill-loader.ts +9 -15
- package/core/agentic/smart-context.ts +129 -79
- package/core/agentic/template-executor.ts +13 -12
- package/core/agentic/template-loader.ts +7 -4
- package/core/agentic/tool-registry.ts +16 -13
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +10 -27
- package/core/ai-tools/formatters.ts +8 -6
- package/core/ai-tools/generator.ts +4 -4
- package/core/ai-tools/index.ts +1 -1
- package/core/ai-tools/registry.ts +21 -11
- package/core/bus/bus.ts +23 -16
- package/core/bus/index.ts +2 -2
- package/core/cli/linear.ts +3 -5
- package/core/cli/start.ts +28 -25
- package/core/commands/analysis.ts +287 -29
- package/core/commands/analytics.ts +52 -44
- package/core/commands/base.ts +15 -13
- package/core/commands/cleanup.ts +6 -13
- package/core/commands/command-data.ts +49 -8
- package/core/commands/commands.ts +60 -23
- package/core/commands/context.ts +4 -4
- package/core/commands/design.ts +3 -10
- package/core/commands/index.ts +5 -8
- package/core/commands/maintenance.ts +7 -4
- package/core/commands/planning.ts +179 -56
- package/core/commands/register.ts +14 -9
- package/core/commands/registry.ts +15 -14
- package/core/commands/setup.ts +26 -14
- package/core/commands/shipping.ts +11 -16
- package/core/commands/snapshots.ts +16 -32
- package/core/commands/uninstall.ts +541 -0
- package/core/commands/workflow.ts +24 -28
- package/core/constants/index.ts +10 -22
- package/core/context/generator.ts +82 -33
- package/core/context-tools/files-tool.ts +583 -0
- package/core/context-tools/imports-tool.ts +403 -0
- package/core/context-tools/index.ts +433 -0
- package/core/context-tools/recent-tool.ts +307 -0
- package/core/context-tools/signatures-tool.ts +501 -0
- package/core/context-tools/summary-tool.ts +307 -0
- package/core/context-tools/token-counter.ts +284 -0
- package/core/context-tools/types.ts +253 -0
- package/core/domain/agent-generator.ts +7 -5
- package/core/domain/agent-loader.ts +2 -2
- package/core/domain/analyzer.ts +19 -16
- package/core/domain/architecture-generator.ts +6 -3
- package/core/domain/context-estimator.ts +3 -4
- package/core/domain/snapshot-manager.ts +25 -22
- package/core/domain/task-stack.ts +24 -14
- package/core/errors.ts +1 -1
- package/core/events/events.ts +2 -4
- package/core/events/index.ts +1 -2
- package/core/index.ts +28 -12
- package/core/infrastructure/agent-detector.ts +3 -3
- package/core/infrastructure/ai-provider.ts +23 -20
- package/core/infrastructure/author-detector.ts +16 -10
- package/core/infrastructure/capability-installer.ts +2 -2
- package/core/infrastructure/claude-agent.ts +6 -6
- package/core/infrastructure/command-installer.ts +22 -17
- package/core/infrastructure/config-manager.ts +18 -14
- package/core/infrastructure/editors-config.ts +8 -4
- package/core/infrastructure/path-manager.ts +8 -6
- package/core/infrastructure/permission-manager.ts +20 -17
- package/core/infrastructure/setup.ts +42 -38
- package/core/infrastructure/update-checker.ts +5 -5
- package/core/integrations/issue-tracker/enricher.ts +8 -19
- package/core/integrations/issue-tracker/index.ts +2 -2
- package/core/integrations/issue-tracker/manager.ts +15 -15
- package/core/integrations/issue-tracker/types.ts +5 -22
- package/core/integrations/jira/client.ts +67 -59
- package/core/integrations/jira/index.ts +11 -14
- package/core/integrations/jira/mcp-adapter.ts +5 -10
- package/core/integrations/jira/service.ts +10 -10
- package/core/integrations/linear/client.ts +27 -18
- package/core/integrations/linear/index.ts +9 -12
- package/core/integrations/linear/service.ts +11 -11
- package/core/integrations/linear/sync.ts +8 -8
- package/core/outcomes/analyzer.ts +5 -18
- package/core/outcomes/index.ts +2 -2
- package/core/outcomes/recorder.ts +3 -3
- package/core/plugin/builtin/webhook.ts +19 -15
- package/core/plugin/hooks.ts +29 -21
- package/core/plugin/index.ts +7 -7
- package/core/plugin/loader.ts +19 -19
- package/core/plugin/registry.ts +12 -23
- package/core/schemas/agents.ts +1 -1
- package/core/schemas/analysis.ts +1 -1
- package/core/schemas/enriched-task.ts +62 -49
- package/core/schemas/ideas.ts +13 -13
- package/core/schemas/index.ts +17 -27
- package/core/schemas/issues.ts +40 -25
- package/core/schemas/metrics.ts +143 -0
- package/core/schemas/outcomes.ts +70 -62
- package/core/schemas/permissions.ts +15 -12
- package/core/schemas/prd.ts +27 -14
- package/core/schemas/project.ts +3 -3
- package/core/schemas/roadmap.ts +47 -34
- package/core/schemas/schemas.ts +3 -4
- package/core/schemas/shipped.ts +3 -3
- package/core/schemas/state.ts +43 -29
- package/core/server/index.ts +5 -6
- package/core/server/routes-extended.ts +68 -72
- package/core/server/routes.ts +3 -3
- package/core/server/server.ts +31 -26
- package/core/services/agent-generator.ts +237 -0
- package/core/services/agent-service.ts +2 -2
- package/core/services/breakdown-service.ts +2 -4
- package/core/services/context-generator.ts +299 -0
- package/core/services/context-selector.ts +420 -0
- package/core/services/doctor-service.ts +426 -0
- package/core/services/file-categorizer.ts +448 -0
- package/core/services/file-scorer.ts +270 -0
- package/core/services/git-analyzer.ts +267 -0
- package/core/services/index.ts +27 -10
- package/core/services/memory-service.ts +3 -4
- package/core/services/project-index.ts +911 -0
- package/core/services/project-service.ts +4 -4
- package/core/services/skill-installer.ts +14 -17
- package/core/services/skill-lock.ts +3 -3
- package/core/services/skill-service.ts +12 -6
- package/core/services/stack-detector.ts +245 -0
- package/core/services/sync-service.ts +170 -329
- package/core/services/watch-service.ts +294 -0
- package/core/session/compaction.ts +23 -31
- package/core/session/index.ts +11 -5
- package/core/session/log-migration.ts +3 -3
- package/core/session/metrics.ts +19 -14
- package/core/session/session-log-manager.ts +12 -17
- package/core/session/task-session-manager.ts +25 -25
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +41 -57
- package/core/storage/index-storage.ts +514 -0
- package/core/storage/index.ts +41 -13
- package/core/storage/metrics-storage.ts +320 -0
- package/core/storage/queue-storage.ts +35 -45
- package/core/storage/shipped-storage.ts +17 -20
- package/core/storage/state-storage.ts +50 -30
- package/core/storage/storage-manager.ts +6 -6
- package/core/storage/storage.ts +18 -15
- package/core/sync/auth-config.ts +3 -3
- package/core/sync/index.ts +13 -19
- package/core/sync/oauth-handler.ts +3 -3
- package/core/sync/sync-client.ts +4 -9
- package/core/sync/sync-manager.ts +12 -14
- package/core/types/commands.ts +42 -7
- package/core/types/index.ts +284 -302
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +49 -0
- package/core/types/utils.ts +3 -3
- package/core/utils/agent-stream.ts +3 -1
- package/core/utils/animations.ts +14 -11
- package/core/utils/branding.ts +7 -7
- package/core/utils/cache.ts +1 -3
- package/core/utils/collection-filters.ts +3 -15
- package/core/utils/date-helper.ts +2 -7
- package/core/utils/file-helper.ts +13 -8
- package/core/utils/jsonl-helper.ts +13 -10
- package/core/utils/keychain.ts +4 -8
- package/core/utils/logger.ts +1 -1
- package/core/utils/next-steps.ts +3 -3
- package/core/utils/output.ts +58 -11
- package/core/utils/project-commands.ts +6 -6
- package/core/utils/project-credentials.ts +5 -12
- package/core/utils/runtime.ts +2 -2
- package/core/utils/session-helper.ts +3 -4
- package/core/utils/version.ts +3 -3
- package/core/wizard/index.ts +13 -0
- package/core/wizard/onboarding.ts +633 -0
- package/core/workflow/state-machine.ts +7 -7
- package/dist/bin/prjct.mjs +18907 -13189
- package/dist/core/infrastructure/command-installer.js +96 -111
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +256 -257
- package/dist/core/utils/version.js +9 -9
- package/package.json +11 -12
- package/scripts/build.js +3 -3
- package/scripts/postinstall.js +2 -2
- package/templates/mcp-config.json +6 -1
- package/templates/permissions/permissive.jsonc +1 -1
- package/templates/permissions/strict.jsonc +5 -9
- package/templates/global/docs/agents.md +0 -88
- package/templates/global/docs/architecture.md +0 -103
- package/templates/global/docs/commands.md +0 -96
- 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
|
+
}
|
package/core/schemas/outcomes.ts
CHANGED
|
@@ -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(),
|
|
53
|
+
sessions: z.number().optional(), // Number of work sessions
|
|
54
54
|
}),
|
|
55
55
|
variance: z.object({
|
|
56
|
-
hours: z.number(),
|
|
57
|
-
percentage: z.number(),
|
|
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(),
|
|
74
|
-
percentOfTarget: z.number(),
|
|
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),
|
|
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(),
|
|
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),
|
|
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(),
|
|
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(),
|
|
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(),
|
|
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(),
|
|
203
|
+
reviewedAt: z.string().optional(), // When impact was captured
|
|
204
204
|
|
|
205
205
|
// Metadata
|
|
206
|
-
reviewedBy: z.string().optional(),
|
|
207
|
-
legacy: z.boolean().optional(),
|
|
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(),
|
|
217
|
-
averageSuccessRate: z.number(),
|
|
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(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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(
|
|
237
|
-
|
|
238
|
-
|
|
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(),
|
|
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 =>
|
|
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
|
-
|
|
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(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
acc[reason]
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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(
|
|
433
|
-
acc
|
|
434
|
-
|
|
435
|
-
|
|
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:
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
':(){ :|:& };:*',
|
|
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
|
package/core/schemas/prd.ts
CHANGED
|
@@ -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([
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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()),
|
|
184
|
-
p1: z.array(z.string()),
|
|
185
|
-
p2: z.array(z.string()),
|
|
186
|
-
p3: z.array(z.string()),
|
|
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(),
|
|
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(),
|
|
290
|
-
phase: z.string().nullable(),
|
|
291
|
-
quarter: z.string().nullable(),
|
|
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<
|
|
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,
|
package/core/schemas/project.ts
CHANGED
|
@@ -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(),
|
|
27
|
-
lastSync: z.string(),
|
|
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
|
}
|