@contractspec/lib.example-shared-ui 6.0.6 → 6.0.7

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 (81) hide show
  1. package/.turbo/turbo-build.log +90 -84
  2. package/AGENTS.md +43 -25
  3. package/README.md +63 -35
  4. package/dist/EvolutionDashboard.js +9 -9
  5. package/dist/EvolutionSidebar.js +15 -15
  6. package/dist/LocalDataIndicator.js +3 -3
  7. package/dist/MarkdownView.d.ts +0 -7
  8. package/dist/MarkdownView.js +76 -172
  9. package/dist/PersonalizationInsights.js +12 -12
  10. package/dist/SaveToStudioButton.js +2 -2
  11. package/dist/SpecDrivenTemplateShell.d.ts +1 -1
  12. package/dist/SpecDrivenTemplateShell.js +10 -10
  13. package/dist/SpecEditorPanel.js +3 -3
  14. package/dist/TemplateShell.js +10 -10
  15. package/dist/browser/EvolutionDashboard.js +9 -9
  16. package/dist/browser/EvolutionSidebar.js +15 -15
  17. package/dist/browser/LocalDataIndicator.js +3 -3
  18. package/dist/browser/MarkdownView.js +76 -172
  19. package/dist/browser/PersonalizationInsights.js +12 -12
  20. package/dist/browser/SaveToStudioButton.js +2 -2
  21. package/dist/browser/SpecDrivenTemplateShell.js +10 -10
  22. package/dist/browser/SpecEditorPanel.js +3 -3
  23. package/dist/browser/TemplateShell.js +10 -10
  24. package/dist/browser/hooks/index.js +29 -29
  25. package/dist/browser/index.js +193 -286
  26. package/dist/browser/lib/component-registry.js +1 -1
  27. package/dist/browser/markdown/formatPresentationName.js +9 -0
  28. package/dist/browser/markdown/useMarkdownPresentation.js +65 -0
  29. package/dist/hooks/index.d.ts +3 -3
  30. package/dist/hooks/index.js +29 -29
  31. package/dist/index.d.ts +12 -11
  32. package/dist/index.js +193 -286
  33. package/dist/lib/component-registry.js +1 -1
  34. package/dist/markdown/formatPresentationName.d.ts +1 -0
  35. package/dist/markdown/formatPresentationName.js +10 -0
  36. package/dist/markdown/useMarkdownPresentation.d.ts +21 -0
  37. package/dist/markdown/useMarkdownPresentation.js +66 -0
  38. package/dist/node/EvolutionDashboard.js +9 -9
  39. package/dist/node/EvolutionSidebar.js +15 -15
  40. package/dist/node/LocalDataIndicator.js +3 -3
  41. package/dist/node/MarkdownView.js +76 -172
  42. package/dist/node/PersonalizationInsights.js +12 -12
  43. package/dist/node/SaveToStudioButton.js +2 -2
  44. package/dist/node/SpecDrivenTemplateShell.js +10 -10
  45. package/dist/node/SpecEditorPanel.js +3 -3
  46. package/dist/node/TemplateShell.js +10 -10
  47. package/dist/node/hooks/index.js +29 -29
  48. package/dist/node/index.js +193 -286
  49. package/dist/node/lib/component-registry.js +1 -1
  50. package/dist/node/markdown/formatPresentationName.js +9 -0
  51. package/dist/node/markdown/useMarkdownPresentation.js +65 -0
  52. package/dist/utils/index.d.ts +1 -1
  53. package/package.json +38 -11
  54. package/src/EvolutionDashboard.tsx +415 -415
  55. package/src/EvolutionSidebar.tsx +245 -245
  56. package/src/LocalDataIndicator.tsx +28 -28
  57. package/src/MarkdownView.tsx +119 -372
  58. package/src/OverlayContextProvider.tsx +272 -272
  59. package/src/PersonalizationInsights.tsx +232 -232
  60. package/src/SaveToStudioButton.tsx +51 -51
  61. package/src/SpecDrivenTemplateShell.tsx +59 -59
  62. package/src/SpecEditorPanel.tsx +138 -138
  63. package/src/TemplateShell.tsx +50 -50
  64. package/src/bundles/ExampleTemplateBundle.ts +78 -78
  65. package/src/hooks/index.ts +3 -3
  66. package/src/hooks/useBehaviorTracking.ts +252 -252
  67. package/src/hooks/useEvolution.ts +437 -437
  68. package/src/hooks/useRegistryTemplates.ts +42 -42
  69. package/src/hooks/useSpecContent.ts +214 -214
  70. package/src/hooks/useWorkflowComposer.ts +567 -567
  71. package/src/index.ts +12 -11
  72. package/src/lib/component-registry.tsx +40 -40
  73. package/src/lib/runtime-context.tsx +31 -31
  74. package/src/lib/types.ts +57 -57
  75. package/src/markdown/formatPresentationName.ts +9 -0
  76. package/src/markdown/useMarkdownPresentation.ts +107 -0
  77. package/src/overlay-types.ts +15 -15
  78. package/src/utils/fetchPresentationData.ts +13 -13
  79. package/src/utils/generateSpecFromTemplate.ts +29 -29
  80. package/src/utils/index.ts +1 -1
  81. package/tsconfig.json +8 -8
@@ -10,118 +10,118 @@ export type AnomalySeverity = 'low' | 'medium' | 'high';
10
10
  export type SuggestionStatus = 'pending' | 'approved' | 'rejected';
11
11
 
12
12
  export interface OperationCoordinate {
13
- name: string;
14
- version: string;
15
- tenantId?: string;
13
+ name: string;
14
+ version: string;
15
+ tenantId?: string;
16
16
  }
17
17
 
18
18
  export interface OperationMetricSample {
19
- operation: OperationCoordinate;
20
- durationMs: number;
21
- success: boolean;
22
- timestamp: Date;
23
- payloadSizeBytes?: number;
24
- errorCode?: string;
25
- errorMessage?: string;
19
+ operation: OperationCoordinate;
20
+ durationMs: number;
21
+ success: boolean;
22
+ timestamp: Date;
23
+ payloadSizeBytes?: number;
24
+ errorCode?: string;
25
+ errorMessage?: string;
26
26
  }
27
27
 
28
28
  export interface SpecUsageStats {
29
- operation: OperationCoordinate;
30
- totalCalls: number;
31
- successRate: number;
32
- errorRate: number;
33
- averageLatencyMs: number;
34
- p95LatencyMs: number;
35
- p99LatencyMs: number;
36
- maxLatencyMs: number;
37
- lastSeenAt: Date;
38
- windowStart: Date;
39
- windowEnd: Date;
40
- topErrors: Record<string, number>;
29
+ operation: OperationCoordinate;
30
+ totalCalls: number;
31
+ successRate: number;
32
+ errorRate: number;
33
+ averageLatencyMs: number;
34
+ p95LatencyMs: number;
35
+ p99LatencyMs: number;
36
+ maxLatencyMs: number;
37
+ lastSeenAt: Date;
38
+ windowStart: Date;
39
+ windowEnd: Date;
40
+ topErrors: Record<string, number>;
41
41
  }
42
42
 
43
43
  export interface SpecAnomaly {
44
- operation: OperationCoordinate;
45
- severity: AnomalySeverity;
46
- metric: 'latency' | 'error-rate' | 'throughput' | 'policy' | 'schema';
47
- description: string;
48
- detectedAt: Date;
49
- threshold?: number;
50
- observedValue?: number;
44
+ operation: OperationCoordinate;
45
+ severity: AnomalySeverity;
46
+ metric: 'latency' | 'error-rate' | 'throughput' | 'policy' | 'schema';
47
+ description: string;
48
+ detectedAt: Date;
49
+ threshold?: number;
50
+ observedValue?: number;
51
51
  }
52
52
 
53
53
  export interface IntentPattern {
54
- id: string;
55
- type:
56
- | 'latency-regression'
57
- | 'error-spike'
58
- | 'missing-operation'
59
- | 'chained-intent'
60
- | 'throughput-drop'
61
- | 'schema-mismatch';
62
- description: string;
63
- operation?: OperationCoordinate;
64
- confidence: { score: number; sampleSize: number };
54
+ id: string;
55
+ type:
56
+ | 'latency-regression'
57
+ | 'error-spike'
58
+ | 'missing-operation'
59
+ | 'chained-intent'
60
+ | 'throughput-drop'
61
+ | 'schema-mismatch';
62
+ description: string;
63
+ operation?: OperationCoordinate;
64
+ confidence: { score: number; sampleSize: number };
65
65
  }
66
66
 
67
67
  export interface SpecSuggestion {
68
- id: string;
69
- intent: IntentPattern;
70
- target?: OperationCoordinate;
71
- proposal: {
72
- summary: string;
73
- rationale: string;
74
- changeType: 'new-spec' | 'revision' | 'policy-update' | 'schema-update';
75
- recommendedActions?: string[];
76
- };
77
- confidence: number;
78
- createdAt: Date;
79
- createdBy: string;
80
- status: SuggestionStatus;
81
- priority: 'low' | 'medium' | 'high';
68
+ id: string;
69
+ intent: IntentPattern;
70
+ target?: OperationCoordinate;
71
+ proposal: {
72
+ summary: string;
73
+ rationale: string;
74
+ changeType: 'new-spec' | 'revision' | 'policy-update' | 'schema-update';
75
+ recommendedActions?: string[];
76
+ };
77
+ confidence: number;
78
+ createdAt: Date;
79
+ createdBy: string;
80
+ status: SuggestionStatus;
81
+ priority: 'low' | 'medium' | 'high';
82
82
  }
83
83
 
84
84
  export interface OptimizationHint {
85
- operation: OperationCoordinate;
86
- category: 'schema' | 'policy' | 'performance' | 'error-handling';
87
- summary: string;
88
- justification: string;
89
- recommendedActions: string[];
85
+ operation: OperationCoordinate;
86
+ category: 'schema' | 'policy' | 'performance' | 'error-handling';
87
+ summary: string;
88
+ justification: string;
89
+ recommendedActions: string[];
90
90
  }
91
91
 
92
92
  /**
93
93
  * Hook return type
94
94
  */
95
95
  export interface UseEvolutionReturn {
96
- /** Usage statistics from analyzed metrics */
97
- usageStats: SpecUsageStats[];
98
- /** Detected anomalies */
99
- anomalies: SpecAnomaly[];
100
- /** AI-generated suggestions */
101
- suggestions: SpecSuggestion[];
102
- /** Optimization hints */
103
- hints: OptimizationHint[];
104
- /** Whether data is loading */
105
- loading: boolean;
106
- /** Track a new operation metric */
107
- trackOperation: (
108
- operationName: string,
109
- durationMs: number,
110
- success: boolean,
111
- errorCode?: string
112
- ) => void;
113
- /** Analyze tracked operations */
114
- analyzeUsage: () => void;
115
- /** Generate suggestions from anomalies */
116
- generateSuggestions: () => Promise<void>;
117
- /** Approve a suggestion */
118
- approveSuggestion: (id: string, notes?: string) => void;
119
- /** Reject a suggestion */
120
- rejectSuggestion: (id: string, notes?: string) => void;
121
- /** Clear all data */
122
- clear: () => void;
123
- /** Total tracked operations count */
124
- operationCount: number;
96
+ /** Usage statistics from analyzed metrics */
97
+ usageStats: SpecUsageStats[];
98
+ /** Detected anomalies */
99
+ anomalies: SpecAnomaly[];
100
+ /** AI-generated suggestions */
101
+ suggestions: SpecSuggestion[];
102
+ /** Optimization hints */
103
+ hints: OptimizationHint[];
104
+ /** Whether data is loading */
105
+ loading: boolean;
106
+ /** Track a new operation metric */
107
+ trackOperation: (
108
+ operationName: string,
109
+ durationMs: number,
110
+ success: boolean,
111
+ errorCode?: string
112
+ ) => void;
113
+ /** Analyze tracked operations */
114
+ analyzeUsage: () => void;
115
+ /** Generate suggestions from anomalies */
116
+ generateSuggestions: () => Promise<void>;
117
+ /** Approve a suggestion */
118
+ approveSuggestion: (id: string, notes?: string) => void;
119
+ /** Reject a suggestion */
120
+ rejectSuggestion: (id: string, notes?: string) => void;
121
+ /** Clear all data */
122
+ clear: () => void;
123
+ /** Total tracked operations count */
124
+ operationCount: number;
125
125
  }
126
126
 
127
127
  /**
@@ -134,368 +134,368 @@ const EVOLUTION_STORAGE_KEY = 'contractspec-evolution-data';
134
134
  * Tracks sandbox operations, detects anomalies, and generates improvement suggestions.
135
135
  */
136
136
  export function useEvolution(templateId: TemplateId): UseEvolutionReturn {
137
- const [usageStats, setUsageStats] = useState<SpecUsageStats[]>([]);
138
- const [anomalies, setAnomalies] = useState<SpecAnomaly[]>([]);
139
- const [suggestions, setSuggestions] = useState<SpecSuggestion[]>([]);
140
- const [hints, setHints] = useState<OptimizationHint[]>([]);
141
- const [loading, setLoading] = useState(false);
142
-
143
- // Use ref to avoid re-renders on every track
144
- const metricsRef = useRef<OperationMetricSample[]>([]);
145
- const [operationCount, setOperationCount] = useState(0);
146
-
147
- // Load persisted data on mount
148
- useEffect(() => {
149
- try {
150
- const stored = localStorage.getItem(
151
- `${EVOLUTION_STORAGE_KEY}-${templateId}`
152
- );
153
- if (stored) {
154
- const data = JSON.parse(stored) as {
155
- suggestions: (Omit<SpecSuggestion, 'createdAt'> & {
156
- createdAt: string;
157
- })[];
158
- };
159
- setSuggestions(
160
- data.suggestions.map((s) => ({
161
- ...s,
162
- createdAt: new Date(s.createdAt),
163
- }))
164
- );
165
- }
166
- } catch {
167
- // Ignore storage errors
168
- }
169
- }, [templateId]);
170
-
171
- // Persist suggestions when they change
172
- useEffect(() => {
173
- try {
174
- localStorage.setItem(
175
- `${EVOLUTION_STORAGE_KEY}-${templateId}`,
176
- JSON.stringify({ suggestions })
177
- );
178
- } catch {
179
- // Ignore storage errors
180
- }
181
- }, [suggestions, templateId]);
182
-
183
- /**
184
- * Track a new operation metric
185
- */
186
- const trackOperation = useCallback(
187
- (
188
- operationName: string,
189
- durationMs: number,
190
- success: boolean,
191
- errorCode?: string
192
- ) => {
193
- const sample: OperationMetricSample = {
194
- operation: {
195
- name: operationName,
196
- version: '1.0.0',
197
- tenantId: 'sandbox',
198
- },
199
- durationMs,
200
- success,
201
- timestamp: new Date(),
202
- errorCode,
203
- };
204
- metricsRef.current.push(sample);
205
- setOperationCount((prev) => prev + 1);
206
- },
207
- []
208
- );
209
-
210
- /**
211
- * Analyze tracked operations to generate usage stats and anomalies
212
- */
213
- const analyzeUsage = useCallback(() => {
214
- const samples = metricsRef.current;
215
- if (samples.length < 5) return;
216
-
217
- // Group samples by operation
218
- const groups = new Map<string, OperationMetricSample[]>();
219
- for (const sample of samples) {
220
- const key = `${sample.operation.name}.v${sample.operation.version}`;
221
- const arr = groups.get(key) ?? [];
222
- arr.push(sample);
223
- groups.set(key, arr);
224
- }
225
-
226
- // Calculate stats for each operation
227
- const stats: SpecUsageStats[] = [];
228
- const detectedAnomalies: SpecAnomaly[] = [];
229
-
230
- groups.forEach((opSamples) => {
231
- if (opSamples.length < 3) return;
232
-
233
- const durations = opSamples
234
- .map((s) => s.durationMs)
235
- .sort((a, b) => a - b);
236
- const errors = opSamples.filter((s) => !s.success);
237
- const totalCalls = opSamples.length;
238
- const errorRate = errors.length / totalCalls;
239
- const averageLatencyMs =
240
- durations.reduce((sum, value) => sum + value, 0) / totalCalls;
241
-
242
- const timestamps = opSamples.map((s) => s.timestamp.getTime());
243
- const firstSample = opSamples[0];
244
- if (!firstSample) return;
245
-
246
- const stat: SpecUsageStats = {
247
- operation: firstSample.operation,
248
- totalCalls,
249
- successRate: 1 - errorRate,
250
- errorRate,
251
- averageLatencyMs,
252
- p95LatencyMs: percentile(durations, 0.95),
253
- p99LatencyMs: percentile(durations, 0.99),
254
- maxLatencyMs: Math.max(...durations),
255
- lastSeenAt: new Date(Math.max(...timestamps)),
256
- windowStart: new Date(Math.min(...timestamps)),
257
- windowEnd: new Date(Math.max(...timestamps)),
258
- topErrors: errors.reduce<Record<string, number>>((acc, s) => {
259
- if (s.errorCode) {
260
- acc[s.errorCode] = (acc[s.errorCode] ?? 0) + 1;
261
- }
262
- return acc;
263
- }, {}),
264
- };
265
- stats.push(stat);
266
-
267
- // Detect anomalies
268
- if (errorRate > 0.1) {
269
- detectedAnomalies.push({
270
- operation: stat.operation,
271
- severity:
272
- errorRate > 0.3 ? 'high' : errorRate > 0.2 ? 'medium' : 'low',
273
- metric: 'error-rate',
274
- description: `Error rate ${(errorRate * 100).toFixed(1)}% exceeds threshold`,
275
- detectedAt: new Date(),
276
- threshold: 0.1,
277
- observedValue: errorRate,
278
- });
279
- }
280
-
281
- if (stat.p99LatencyMs > 500) {
282
- detectedAnomalies.push({
283
- operation: stat.operation,
284
- severity:
285
- stat.p99LatencyMs > 1000
286
- ? 'high'
287
- : stat.p99LatencyMs > 750
288
- ? 'medium'
289
- : 'low',
290
- metric: 'latency',
291
- description: `P99 latency ${stat.p99LatencyMs.toFixed(0)}ms exceeds threshold`,
292
- detectedAt: new Date(),
293
- threshold: 500,
294
- observedValue: stat.p99LatencyMs,
295
- });
296
- }
297
- });
298
-
299
- setUsageStats(stats);
300
- setAnomalies(detectedAnomalies);
301
-
302
- // Generate hints
303
- const newHints: OptimizationHint[] = detectedAnomalies.map((anomaly) => ({
304
- operation: anomaly.operation,
305
- category: anomaly.metric === 'latency' ? 'performance' : 'error-handling',
306
- summary:
307
- anomaly.metric === 'latency'
308
- ? 'Latency regression detected'
309
- : 'Error spike detected',
310
- justification: anomaly.description,
311
- recommendedActions:
312
- anomaly.metric === 'latency'
313
- ? [
314
- 'Add caching layer',
315
- 'Optimize database queries',
316
- 'Consider pagination',
317
- ]
318
- : [
319
- 'Add retry logic',
320
- 'Improve error handling',
321
- 'Add circuit breaker',
322
- ],
323
- }));
324
- setHints(newHints);
325
- }, []);
326
-
327
- /**
328
- * Generate AI suggestions from anomalies (mock for sandbox)
329
- */
330
- const generateSuggestions = useCallback(async () => {
331
- if (anomalies.length === 0) return;
332
-
333
- setLoading(true);
334
- // Simulate AI generation delay
335
- await new Promise((resolve) => setTimeout(resolve, 800));
336
-
337
- const newSuggestions: SpecSuggestion[] = anomalies.map((anomaly) => ({
338
- id: `suggestion-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
339
- intent: {
340
- id: `intent-${anomaly.operation.name}`,
341
- type:
342
- anomaly.metric === 'latency'
343
- ? 'latency-regression'
344
- : anomaly.metric === 'error-rate'
345
- ? 'error-spike'
346
- : 'throughput-drop',
347
- description: anomaly.description,
348
- operation: anomaly.operation,
349
- confidence: {
350
- score:
351
- anomaly.severity === 'high'
352
- ? 0.9
353
- : anomaly.severity === 'medium'
354
- ? 0.7
355
- : 0.5,
356
- sampleSize:
357
- usageStats.find((s) => s.operation.name === anomaly.operation.name)
358
- ?.totalCalls ?? 0,
359
- },
360
- },
361
- target: anomaly.operation,
362
- proposal: {
363
- summary: generateSuggestionSummary(anomaly),
364
- rationale: generateSuggestionRationale(anomaly),
365
- changeType:
366
- anomaly.metric === 'error-rate' ? 'policy-update' : 'revision',
367
- recommendedActions: generateRecommendedActions(anomaly),
368
- },
369
- confidence:
370
- anomaly.severity === 'high'
371
- ? 0.85
372
- : anomaly.severity === 'medium'
373
- ? 0.7
374
- : 0.55,
375
- createdAt: new Date(),
376
- createdBy: 'ai-evolution-agent',
377
- status: 'pending',
378
- priority: anomaly.severity,
379
- }));
380
-
381
- setSuggestions((prev) => [...prev, ...newSuggestions]);
382
- setLoading(false);
383
- }, [anomalies, usageStats]);
384
-
385
- /**
386
- * Approve a suggestion
387
- */
388
- const approveSuggestion = useCallback((id: string, _notes?: string) => {
389
- setSuggestions((prev) =>
390
- prev.map((s) => (s.id === id ? { ...s, status: 'approved' as const } : s))
391
- );
392
- }, []);
393
-
394
- /**
395
- * Reject a suggestion
396
- */
397
- const rejectSuggestion = useCallback((id: string, _notes?: string) => {
398
- setSuggestions((prev) =>
399
- prev.map((s) => (s.id === id ? { ...s, status: 'rejected' as const } : s))
400
- );
401
- }, []);
402
-
403
- /**
404
- * Clear all evolution data
405
- */
406
- const clear = useCallback(() => {
407
- metricsRef.current = [];
408
- setOperationCount(0);
409
- setUsageStats([]);
410
- setAnomalies([]);
411
- setSuggestions([]);
412
- setHints([]);
413
- localStorage.removeItem(`${EVOLUTION_STORAGE_KEY}-${templateId}`);
414
- }, [templateId]);
415
-
416
- return useMemo(
417
- () => ({
418
- usageStats,
419
- anomalies,
420
- suggestions,
421
- hints,
422
- loading,
423
- trackOperation,
424
- analyzeUsage,
425
- generateSuggestions,
426
- approveSuggestion,
427
- rejectSuggestion,
428
- clear,
429
- operationCount,
430
- }),
431
- [
432
- usageStats,
433
- anomalies,
434
- suggestions,
435
- hints,
436
- loading,
437
- trackOperation,
438
- analyzeUsage,
439
- generateSuggestions,
440
- approveSuggestion,
441
- rejectSuggestion,
442
- clear,
443
- operationCount,
444
- ]
445
- );
137
+ const [usageStats, setUsageStats] = useState<SpecUsageStats[]>([]);
138
+ const [anomalies, setAnomalies] = useState<SpecAnomaly[]>([]);
139
+ const [suggestions, setSuggestions] = useState<SpecSuggestion[]>([]);
140
+ const [hints, setHints] = useState<OptimizationHint[]>([]);
141
+ const [loading, setLoading] = useState(false);
142
+
143
+ // Use ref to avoid re-renders on every track
144
+ const metricsRef = useRef<OperationMetricSample[]>([]);
145
+ const [operationCount, setOperationCount] = useState(0);
146
+
147
+ // Load persisted data on mount
148
+ useEffect(() => {
149
+ try {
150
+ const stored = localStorage.getItem(
151
+ `${EVOLUTION_STORAGE_KEY}-${templateId}`
152
+ );
153
+ if (stored) {
154
+ const data = JSON.parse(stored) as {
155
+ suggestions: (Omit<SpecSuggestion, 'createdAt'> & {
156
+ createdAt: string;
157
+ })[];
158
+ };
159
+ setSuggestions(
160
+ data.suggestions.map((s) => ({
161
+ ...s,
162
+ createdAt: new Date(s.createdAt),
163
+ }))
164
+ );
165
+ }
166
+ } catch {
167
+ // Ignore storage errors
168
+ }
169
+ }, [templateId]);
170
+
171
+ // Persist suggestions when they change
172
+ useEffect(() => {
173
+ try {
174
+ localStorage.setItem(
175
+ `${EVOLUTION_STORAGE_KEY}-${templateId}`,
176
+ JSON.stringify({ suggestions })
177
+ );
178
+ } catch {
179
+ // Ignore storage errors
180
+ }
181
+ }, [suggestions, templateId]);
182
+
183
+ /**
184
+ * Track a new operation metric
185
+ */
186
+ const trackOperation = useCallback(
187
+ (
188
+ operationName: string,
189
+ durationMs: number,
190
+ success: boolean,
191
+ errorCode?: string
192
+ ) => {
193
+ const sample: OperationMetricSample = {
194
+ operation: {
195
+ name: operationName,
196
+ version: '1.0.0',
197
+ tenantId: 'sandbox',
198
+ },
199
+ durationMs,
200
+ success,
201
+ timestamp: new Date(),
202
+ errorCode,
203
+ };
204
+ metricsRef.current.push(sample);
205
+ setOperationCount((prev) => prev + 1);
206
+ },
207
+ []
208
+ );
209
+
210
+ /**
211
+ * Analyze tracked operations to generate usage stats and anomalies
212
+ */
213
+ const analyzeUsage = useCallback(() => {
214
+ const samples = metricsRef.current;
215
+ if (samples.length < 5) return;
216
+
217
+ // Group samples by operation
218
+ const groups = new Map<string, OperationMetricSample[]>();
219
+ for (const sample of samples) {
220
+ const key = `${sample.operation.name}.v${sample.operation.version}`;
221
+ const arr = groups.get(key) ?? [];
222
+ arr.push(sample);
223
+ groups.set(key, arr);
224
+ }
225
+
226
+ // Calculate stats for each operation
227
+ const stats: SpecUsageStats[] = [];
228
+ const detectedAnomalies: SpecAnomaly[] = [];
229
+
230
+ groups.forEach((opSamples) => {
231
+ if (opSamples.length < 3) return;
232
+
233
+ const durations = opSamples
234
+ .map((s) => s.durationMs)
235
+ .sort((a, b) => a - b);
236
+ const errors = opSamples.filter((s) => !s.success);
237
+ const totalCalls = opSamples.length;
238
+ const errorRate = errors.length / totalCalls;
239
+ const averageLatencyMs =
240
+ durations.reduce((sum, value) => sum + value, 0) / totalCalls;
241
+
242
+ const timestamps = opSamples.map((s) => s.timestamp.getTime());
243
+ const firstSample = opSamples[0];
244
+ if (!firstSample) return;
245
+
246
+ const stat: SpecUsageStats = {
247
+ operation: firstSample.operation,
248
+ totalCalls,
249
+ successRate: 1 - errorRate,
250
+ errorRate,
251
+ averageLatencyMs,
252
+ p95LatencyMs: percentile(durations, 0.95),
253
+ p99LatencyMs: percentile(durations, 0.99),
254
+ maxLatencyMs: Math.max(...durations),
255
+ lastSeenAt: new Date(Math.max(...timestamps)),
256
+ windowStart: new Date(Math.min(...timestamps)),
257
+ windowEnd: new Date(Math.max(...timestamps)),
258
+ topErrors: errors.reduce<Record<string, number>>((acc, s) => {
259
+ if (s.errorCode) {
260
+ acc[s.errorCode] = (acc[s.errorCode] ?? 0) + 1;
261
+ }
262
+ return acc;
263
+ }, {}),
264
+ };
265
+ stats.push(stat);
266
+
267
+ // Detect anomalies
268
+ if (errorRate > 0.1) {
269
+ detectedAnomalies.push({
270
+ operation: stat.operation,
271
+ severity:
272
+ errorRate > 0.3 ? 'high' : errorRate > 0.2 ? 'medium' : 'low',
273
+ metric: 'error-rate',
274
+ description: `Error rate ${(errorRate * 100).toFixed(1)}% exceeds threshold`,
275
+ detectedAt: new Date(),
276
+ threshold: 0.1,
277
+ observedValue: errorRate,
278
+ });
279
+ }
280
+
281
+ if (stat.p99LatencyMs > 500) {
282
+ detectedAnomalies.push({
283
+ operation: stat.operation,
284
+ severity:
285
+ stat.p99LatencyMs > 1000
286
+ ? 'high'
287
+ : stat.p99LatencyMs > 750
288
+ ? 'medium'
289
+ : 'low',
290
+ metric: 'latency',
291
+ description: `P99 latency ${stat.p99LatencyMs.toFixed(0)}ms exceeds threshold`,
292
+ detectedAt: new Date(),
293
+ threshold: 500,
294
+ observedValue: stat.p99LatencyMs,
295
+ });
296
+ }
297
+ });
298
+
299
+ setUsageStats(stats);
300
+ setAnomalies(detectedAnomalies);
301
+
302
+ // Generate hints
303
+ const newHints: OptimizationHint[] = detectedAnomalies.map((anomaly) => ({
304
+ operation: anomaly.operation,
305
+ category: anomaly.metric === 'latency' ? 'performance' : 'error-handling',
306
+ summary:
307
+ anomaly.metric === 'latency'
308
+ ? 'Latency regression detected'
309
+ : 'Error spike detected',
310
+ justification: anomaly.description,
311
+ recommendedActions:
312
+ anomaly.metric === 'latency'
313
+ ? [
314
+ 'Add caching layer',
315
+ 'Optimize database queries',
316
+ 'Consider pagination',
317
+ ]
318
+ : [
319
+ 'Add retry logic',
320
+ 'Improve error handling',
321
+ 'Add circuit breaker',
322
+ ],
323
+ }));
324
+ setHints(newHints);
325
+ }, []);
326
+
327
+ /**
328
+ * Generate AI suggestions from anomalies (mock for sandbox)
329
+ */
330
+ const generateSuggestions = useCallback(async () => {
331
+ if (anomalies.length === 0) return;
332
+
333
+ setLoading(true);
334
+ // Simulate AI generation delay
335
+ await new Promise((resolve) => setTimeout(resolve, 800));
336
+
337
+ const newSuggestions: SpecSuggestion[] = anomalies.map((anomaly) => ({
338
+ id: `suggestion-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
339
+ intent: {
340
+ id: `intent-${anomaly.operation.name}`,
341
+ type:
342
+ anomaly.metric === 'latency'
343
+ ? 'latency-regression'
344
+ : anomaly.metric === 'error-rate'
345
+ ? 'error-spike'
346
+ : 'throughput-drop',
347
+ description: anomaly.description,
348
+ operation: anomaly.operation,
349
+ confidence: {
350
+ score:
351
+ anomaly.severity === 'high'
352
+ ? 0.9
353
+ : anomaly.severity === 'medium'
354
+ ? 0.7
355
+ : 0.5,
356
+ sampleSize:
357
+ usageStats.find((s) => s.operation.name === anomaly.operation.name)
358
+ ?.totalCalls ?? 0,
359
+ },
360
+ },
361
+ target: anomaly.operation,
362
+ proposal: {
363
+ summary: generateSuggestionSummary(anomaly),
364
+ rationale: generateSuggestionRationale(anomaly),
365
+ changeType:
366
+ anomaly.metric === 'error-rate' ? 'policy-update' : 'revision',
367
+ recommendedActions: generateRecommendedActions(anomaly),
368
+ },
369
+ confidence:
370
+ anomaly.severity === 'high'
371
+ ? 0.85
372
+ : anomaly.severity === 'medium'
373
+ ? 0.7
374
+ : 0.55,
375
+ createdAt: new Date(),
376
+ createdBy: 'ai-evolution-agent',
377
+ status: 'pending',
378
+ priority: anomaly.severity,
379
+ }));
380
+
381
+ setSuggestions((prev) => [...prev, ...newSuggestions]);
382
+ setLoading(false);
383
+ }, [anomalies, usageStats]);
384
+
385
+ /**
386
+ * Approve a suggestion
387
+ */
388
+ const approveSuggestion = useCallback((id: string, _notes?: string) => {
389
+ setSuggestions((prev) =>
390
+ prev.map((s) => (s.id === id ? { ...s, status: 'approved' as const } : s))
391
+ );
392
+ }, []);
393
+
394
+ /**
395
+ * Reject a suggestion
396
+ */
397
+ const rejectSuggestion = useCallback((id: string, _notes?: string) => {
398
+ setSuggestions((prev) =>
399
+ prev.map((s) => (s.id === id ? { ...s, status: 'rejected' as const } : s))
400
+ );
401
+ }, []);
402
+
403
+ /**
404
+ * Clear all evolution data
405
+ */
406
+ const clear = useCallback(() => {
407
+ metricsRef.current = [];
408
+ setOperationCount(0);
409
+ setUsageStats([]);
410
+ setAnomalies([]);
411
+ setSuggestions([]);
412
+ setHints([]);
413
+ localStorage.removeItem(`${EVOLUTION_STORAGE_KEY}-${templateId}`);
414
+ }, [templateId]);
415
+
416
+ return useMemo(
417
+ () => ({
418
+ usageStats,
419
+ anomalies,
420
+ suggestions,
421
+ hints,
422
+ loading,
423
+ trackOperation,
424
+ analyzeUsage,
425
+ generateSuggestions,
426
+ approveSuggestion,
427
+ rejectSuggestion,
428
+ clear,
429
+ operationCount,
430
+ }),
431
+ [
432
+ usageStats,
433
+ anomalies,
434
+ suggestions,
435
+ hints,
436
+ loading,
437
+ trackOperation,
438
+ analyzeUsage,
439
+ generateSuggestions,
440
+ approveSuggestion,
441
+ rejectSuggestion,
442
+ clear,
443
+ operationCount,
444
+ ]
445
+ );
446
446
  }
447
447
 
448
448
  /**
449
449
  * Utility functions for generating mock AI content
450
450
  */
451
451
  function percentile(values: number[], p: number): number {
452
- if (!values.length) return 0;
453
- if (values.length === 1) return values[0] ?? 0;
454
- const idx = Math.min(values.length - 1, Math.floor(p * values.length));
455
- return values[idx] ?? 0;
452
+ if (!values.length) return 0;
453
+ if (values.length === 1) return values[0] ?? 0;
454
+ const idx = Math.min(values.length - 1, Math.floor(p * values.length));
455
+ return values[idx] ?? 0;
456
456
  }
457
457
 
458
458
  function generateSuggestionSummary(anomaly: SpecAnomaly): string {
459
- if (anomaly.metric === 'latency') {
460
- return `Add caching and pagination to ${anomaly.operation.name} to reduce latency`;
461
- }
462
- if (anomaly.metric === 'error-rate') {
463
- return `Add retry policy and circuit breaker to ${anomaly.operation.name}`;
464
- }
465
- return `Optimize ${anomaly.operation.name} for improved throughput`;
459
+ if (anomaly.metric === 'latency') {
460
+ return `Add caching and pagination to ${anomaly.operation.name} to reduce latency`;
461
+ }
462
+ if (anomaly.metric === 'error-rate') {
463
+ return `Add retry policy and circuit breaker to ${anomaly.operation.name}`;
464
+ }
465
+ return `Optimize ${anomaly.operation.name} for improved throughput`;
466
466
  }
467
467
 
468
468
  function generateSuggestionRationale(anomaly: SpecAnomaly): string {
469
- if (anomaly.metric === 'latency') {
470
- return `The operation ${anomaly.operation.name} is experiencing P99 latency of ${anomaly.observedValue?.toFixed(0)}ms, which is above the recommended threshold of ${anomaly.threshold}ms. This can impact user experience and downstream operations.`;
471
- }
472
- if (anomaly.metric === 'error-rate') {
473
- return `The error rate for ${anomaly.operation.name} is ${((anomaly.observedValue ?? 0) * 100).toFixed(1)}%, indicating potential issues with input validation, external dependencies, or resource limits.`;
474
- }
475
- return `Throughput for ${anomaly.operation.name} has dropped significantly, suggesting potential bottlenecks or reduced demand that should be investigated.`;
469
+ if (anomaly.metric === 'latency') {
470
+ return `The operation ${anomaly.operation.name} is experiencing P99 latency of ${anomaly.observedValue?.toFixed(0)}ms, which is above the recommended threshold of ${anomaly.threshold}ms. This can impact user experience and downstream operations.`;
471
+ }
472
+ if (anomaly.metric === 'error-rate') {
473
+ return `The error rate for ${anomaly.operation.name} is ${((anomaly.observedValue ?? 0) * 100).toFixed(1)}%, indicating potential issues with input validation, external dependencies, or resource limits.`;
474
+ }
475
+ return `Throughput for ${anomaly.operation.name} has dropped significantly, suggesting potential bottlenecks or reduced demand that should be investigated.`;
476
476
  }
477
477
 
478
478
  function generateRecommendedActions(anomaly: SpecAnomaly): string[] {
479
- if (anomaly.metric === 'latency') {
480
- return [
481
- 'Add response caching for frequently accessed data',
482
- 'Implement pagination for large result sets',
483
- 'Optimize database queries with proper indexing',
484
- 'Consider adding a GraphQL DataLoader for batching',
485
- ];
486
- }
487
- if (anomaly.metric === 'error-rate') {
488
- return [
489
- 'Add input validation at the contract level',
490
- 'Implement retry policy with exponential backoff',
491
- 'Add circuit breaker for external dependencies',
492
- 'Enhance error logging for better debugging',
493
- ];
494
- }
495
- return [
496
- 'Review resource allocation and scaling policies',
497
- 'Check for upstream routing or load balancer issues',
498
- 'Validate feature flag configurations',
499
- 'Monitor dependency health metrics',
500
- ];
479
+ if (anomaly.metric === 'latency') {
480
+ return [
481
+ 'Add response caching for frequently accessed data',
482
+ 'Implement pagination for large result sets',
483
+ 'Optimize database queries with proper indexing',
484
+ 'Consider adding a GraphQL DataLoader for batching',
485
+ ];
486
+ }
487
+ if (anomaly.metric === 'error-rate') {
488
+ return [
489
+ 'Add input validation at the contract level',
490
+ 'Implement retry policy with exponential backoff',
491
+ 'Add circuit breaker for external dependencies',
492
+ 'Enhance error logging for better debugging',
493
+ ];
494
+ }
495
+ return [
496
+ 'Review resource allocation and scaling policies',
497
+ 'Check for upstream routing or load balancer issues',
498
+ 'Validate feature flag configurations',
499
+ 'Monitor dependency health metrics',
500
+ ];
501
501
  }