@paths.design/caws-cli 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +295 -150
  2. package/dist/budget-derivation.d.ts +35 -0
  3. package/dist/budget-derivation.d.ts.map +1 -0
  4. package/dist/budget-derivation.js +204 -0
  5. package/dist/cicd-optimizer.d.ts +142 -0
  6. package/dist/cicd-optimizer.d.ts.map +1 -0
  7. package/dist/cicd-optimizer.js +504 -0
  8. package/dist/commands/burnup.d.ts +6 -0
  9. package/dist/commands/burnup.d.ts.map +1 -0
  10. package/dist/commands/burnup.js +90 -0
  11. package/dist/commands/init.d.ts +5 -0
  12. package/dist/commands/init.d.ts.map +1 -0
  13. package/dist/commands/init.js +514 -0
  14. package/dist/commands/provenance.d.ts +32 -0
  15. package/dist/commands/provenance.d.ts.map +1 -0
  16. package/dist/commands/provenance.js +979 -0
  17. package/dist/commands/tool.d.ts +13 -0
  18. package/dist/commands/tool.d.ts.map +1 -0
  19. package/dist/commands/tool.js +138 -0
  20. package/dist/commands/validate.d.ts +7 -0
  21. package/dist/commands/validate.d.ts.map +1 -0
  22. package/dist/commands/validate.js +80 -0
  23. package/dist/config/index.d.ts +29 -0
  24. package/dist/config/index.d.ts.map +1 -0
  25. package/dist/config/index.js +132 -0
  26. package/dist/error-handler.d.ts +50 -0
  27. package/dist/error-handler.d.ts.map +1 -0
  28. package/dist/error-handler.js +253 -0
  29. package/dist/generators/working-spec.d.ts +13 -0
  30. package/dist/generators/working-spec.d.ts.map +1 -0
  31. package/dist/generators/working-spec.js +204 -0
  32. package/dist/index.d.ts +3 -12
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +193 -2983
  35. package/dist/scaffold/cursor-hooks.d.ts +7 -0
  36. package/dist/scaffold/cursor-hooks.d.ts.map +1 -0
  37. package/dist/scaffold/cursor-hooks.js +152 -0
  38. package/dist/scaffold/git-hooks.d.ts +20 -0
  39. package/dist/scaffold/git-hooks.d.ts.map +1 -0
  40. package/dist/scaffold/git-hooks.js +417 -0
  41. package/dist/scaffold/index.d.ts +20 -0
  42. package/dist/scaffold/index.d.ts.map +1 -0
  43. package/dist/scaffold/index.js +486 -0
  44. package/dist/test-analysis.d.ts +182 -0
  45. package/dist/test-analysis.d.ts.map +1 -0
  46. package/dist/test-analysis.js +580 -0
  47. package/dist/tool-interface.d.ts +236 -0
  48. package/dist/tool-interface.d.ts.map +1 -0
  49. package/dist/tool-interface.js +314 -0
  50. package/dist/tool-loader.d.ts +77 -0
  51. package/dist/tool-loader.d.ts.map +1 -0
  52. package/dist/tool-loader.js +298 -0
  53. package/dist/tool-validator.d.ts +72 -0
  54. package/dist/tool-validator.d.ts.map +1 -0
  55. package/dist/tool-validator.js +387 -0
  56. package/dist/utils/detection.d.ts +7 -0
  57. package/dist/utils/detection.d.ts.map +1 -0
  58. package/dist/utils/detection.js +174 -0
  59. package/dist/utils/finalization.d.ts +17 -0
  60. package/dist/utils/finalization.d.ts.map +1 -0
  61. package/dist/utils/finalization.js +229 -0
  62. package/dist/utils/project-analysis.d.ts +14 -0
  63. package/dist/utils/project-analysis.d.ts.map +1 -0
  64. package/dist/utils/project-analysis.js +105 -0
  65. package/dist/validation/spec-validation.d.ts +29 -0
  66. package/dist/validation/spec-validation.d.ts.map +1 -0
  67. package/dist/validation/spec-validation.js +376 -0
  68. package/dist/waivers-manager.d.ts +167 -0
  69. package/dist/waivers-manager.d.ts.map +1 -0
  70. package/dist/waivers-manager.js +549 -0
  71. package/package.json +10 -12
  72. package/templates/.cursor/README.md +311 -0
  73. package/templates/.cursor/hooks/audit.sh +55 -0
  74. package/templates/.cursor/hooks/block-dangerous.sh +77 -0
  75. package/templates/.cursor/hooks/caws-quality-check.sh +52 -0
  76. package/templates/.cursor/hooks/caws-scope-guard.sh +74 -0
  77. package/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
  78. package/templates/.cursor/hooks/format.sh +38 -0
  79. package/templates/.cursor/hooks/naming-check.sh +64 -0
  80. package/templates/.cursor/hooks/scan-secrets.sh +46 -0
  81. package/templates/.cursor/hooks/scope-guard.sh +52 -0
  82. package/templates/.cursor/hooks/validate-spec.sh +38 -0
  83. package/templates/.cursor/hooks.json +59 -0
  84. package/templates/.github/copilot/instructions.md +311 -0
  85. package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
  86. package/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
  87. package/templates/.vscode/launch.json +56 -0
  88. package/templates/.vscode/settings.json +93 -0
  89. package/templates/.windsurf/workflows/caws-guided-development.md +92 -0
  90. package/templates/apps/tools/caws/README.md +1 -1
  91. package/templates/apps/tools/caws/schemas/working-spec.schema.json +21 -3
  92. package/templates/codemod/test.js +93 -1
  93. package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
  94. package/templates/apps/tools/caws/provenance.js.backup +0 -73
@@ -0,0 +1,580 @@
1
+ /**
2
+ * @fileoverview Test Analysis Module - v0.1 Statistical Learning
3
+ * Learns from waivers and historical data to improve budget allocation and test selection
4
+ * @author @darianrosebrook
5
+ */
6
+
7
+ const fs = require('fs-extra');
8
+ const path = require('path');
9
+ const yaml = require('js-yaml');
10
+
11
+ /**
12
+ * Waiver Pattern Learning Engine
13
+ * Analyzes waiver history to find systematic patterns in budget overruns
14
+ */
15
+ class WaiverPatternLearner {
16
+ constructor(projectRoot = process.cwd()) {
17
+ this.projectRoot = projectRoot;
18
+ }
19
+
20
+ /**
21
+ * Analyze waiver patterns from historical data
22
+ */
23
+ analyzePatterns() {
24
+ try {
25
+ const waivers = this.loadWaivers();
26
+ const specs = this.loadHistoricalSpecs();
27
+
28
+ if (waivers.length === 0) {
29
+ return {
30
+ status: 'insufficient_data',
31
+ message: 'No waiver data available for analysis',
32
+ patterns: {},
33
+ };
34
+ }
35
+
36
+ const patterns = {
37
+ total_waivers: waivers.length,
38
+ budget_overruns: this.analyzeBudgetOverruns(waivers, specs),
39
+ common_reasons: this.analyzeCommonReasons(waivers),
40
+ risk_factors: this.identifyRiskFactors(waivers, specs),
41
+ generated_at: new Date().toISOString(),
42
+ };
43
+
44
+ return {
45
+ status: 'success',
46
+ patterns,
47
+ };
48
+ } catch (error) {
49
+ return {
50
+ status: 'error',
51
+ message: error.message,
52
+ patterns: {},
53
+ };
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Load all waiver files from .caws/waivers/
59
+ */
60
+ loadWaivers() {
61
+ const waiversDir = path.join(this.projectRoot, '.caws', 'waivers');
62
+ if (!fs.existsSync(waiversDir)) {
63
+ return [];
64
+ }
65
+
66
+ const waiverFiles = fs
67
+ .readdirSync(waiversDir)
68
+ .filter((file) => file.endsWith('.yaml'))
69
+ .map((file) => {
70
+ try {
71
+ const waiverPath = path.join(waiversDir, file);
72
+ const waiver = yaml.load(fs.readFileSync(waiverPath, 'utf8'));
73
+ return { ...waiver, file: file };
74
+ } catch (error) {
75
+ console.warn(`Failed to load waiver ${file}: ${error.message}`);
76
+ return null;
77
+ }
78
+ })
79
+ .filter((waiver) => waiver !== null);
80
+
81
+ return waiverFiles;
82
+ }
83
+
84
+ /**
85
+ * Load historical working specs (mock implementation)
86
+ */
87
+ loadHistoricalSpecs() {
88
+ // In a real implementation, this would load from git history or a local cache
89
+ // For v0.1, we'll use mock data based on waivers
90
+ return [];
91
+ }
92
+
93
+ /**
94
+ * Analyze budget overrun patterns
95
+ */
96
+ analyzeBudgetOverruns(waivers, specs) {
97
+ const budgetWaivers = waivers.filter((w) => w.gates?.includes('budget_limit'));
98
+
99
+ if (budgetWaivers.length === 0) {
100
+ return {
101
+ average_overrun_files: 0,
102
+ average_overrun_loc: 0,
103
+ common_patterns: [],
104
+ };
105
+ }
106
+
107
+ const overruns = budgetWaivers
108
+ .filter((w) => w.delta)
109
+ .map((w) => ({
110
+ files: w.delta.max_files || 0,
111
+ loc: w.delta.max_loc || 0,
112
+ reason: w.reason_code,
113
+ applies_to: w.applies_to,
114
+ }));
115
+
116
+ const avgFiles = overruns.reduce((sum, o) => sum + o.files, 0) / overruns.length;
117
+ const avgLoc = overruns.reduce((sum, o) => sum + o.loc, 0) / overruns.length;
118
+
119
+ // Group by reason
120
+ const byReason = overruns.reduce((acc, overrun) => {
121
+ acc[overrun.reason] = acc[overrun.reason] || [];
122
+ acc[overrun.reason].push(overrun);
123
+ return acc;
124
+ }, {});
125
+
126
+ const commonPatterns = Object.entries(byReason)
127
+ .map(([reason, overruns]) => ({
128
+ reason,
129
+ frequency: overruns.length / budgetWaivers.length,
130
+ avg_overrun_files: overruns.reduce((sum, o) => sum + o.files, 0) / overruns.length,
131
+ avg_overrun_loc: overruns.reduce((sum, o) => sum + o.loc, 0) / overruns.length,
132
+ }))
133
+ .sort((a, b) => b.frequency - a.frequency);
134
+
135
+ return {
136
+ total_budget_waivers: budgetWaivers.length,
137
+ average_overrun_files: Math.round(avgFiles),
138
+ average_overrun_loc: Math.round(avgLoc),
139
+ common_patterns: commonPatterns.slice(0, 5), // Top 5 patterns
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Analyze most common waiver reasons
145
+ */
146
+ analyzeCommonReasons(waivers) {
147
+ const reasons = waivers.reduce((acc, waiver) => {
148
+ acc[waiver.reason_code] = (acc[waiver.reason_code] || 0) + 1;
149
+ return acc;
150
+ }, {});
151
+
152
+ return Object.entries(reasons)
153
+ .map(([reason, count]) => ({
154
+ reason,
155
+ count,
156
+ frequency: count / waivers.length,
157
+ }))
158
+ .sort((a, b) => b.count - a.count);
159
+ }
160
+
161
+ /**
162
+ * Identify risk factors from waiver patterns
163
+ */
164
+ identifyRiskFactors(waivers, specs) {
165
+ // Simple risk factor identification based on waiver frequency
166
+ const riskFactors = [];
167
+
168
+ const reasons = this.analyzeCommonReasons(waivers);
169
+ if (reasons.length > 0) {
170
+ riskFactors.push({
171
+ factor: 'common_waiver_reasons',
172
+ description: `${reasons[0].reason} waivers occur in ${Math.round(reasons[0].frequency * 100)}% of cases`,
173
+ risk_level:
174
+ reasons[0].frequency > 0.5 ? 'high' : reasons[0].frequency > 0.3 ? 'medium' : 'low',
175
+ });
176
+ }
177
+
178
+ return riskFactors;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Project Similarity Matcher
184
+ * Finds historical projects similar to current work
185
+ */
186
+ class ProjectSimilarityMatcher {
187
+ constructor(projectRoot = process.cwd()) {
188
+ this.projectRoot = projectRoot;
189
+ }
190
+
191
+ /**
192
+ * Find projects similar to the current spec
193
+ */
194
+ findSimilarProjects(currentSpec) {
195
+ // For v0.1, we'll use mock historical data based on waiver patterns
196
+ // In a real implementation, this would load from git history or local cache
197
+
198
+ const mockHistoricalProjects = [
199
+ {
200
+ id: 'PROJ-0123',
201
+ title: 'API Enhancement',
202
+ risk_tier: 2,
203
+ mode: 'feature',
204
+ tech_stack: 'node',
205
+ feature_type: 'api',
206
+ actual_budget: { files: 85, loc: 8500 },
207
+ allocated_budget: { files: 70, loc: 7000 },
208
+ waivers: ['WV-0001'],
209
+ },
210
+ {
211
+ id: 'FEAT-0456',
212
+ title: 'UI Component Library',
213
+ risk_tier: 2,
214
+ mode: 'feature',
215
+ tech_stack: 'react',
216
+ feature_type: 'ui',
217
+ actual_budget: { files: 45, loc: 4200 },
218
+ allocated_budget: { files: 50, loc: 5000 },
219
+ waivers: [],
220
+ },
221
+ {
222
+ id: 'FIX-0789',
223
+ title: 'Data Migration',
224
+ risk_tier: 1,
225
+ mode: 'feature',
226
+ tech_stack: 'node',
227
+ feature_type: 'data',
228
+ actual_budget: { files: 25, loc: 2800 },
229
+ allocated_budget: { files: 20, loc: 2000 },
230
+ waivers: ['WV-0002'],
231
+ },
232
+ ];
233
+
234
+ // Add a mock project similar to ARCH-0001 for demonstration
235
+ if (currentSpec.id === 'ARCH-0001') {
236
+ mockHistoricalProjects.push({
237
+ id: 'ARCH-0002',
238
+ title: 'Policy System Refactor',
239
+ risk_tier: 1,
240
+ mode: 'feature',
241
+ tech_stack: 'node',
242
+ feature_type: 'architecture',
243
+ actual_budget: { files: 120, loc: 12000 },
244
+ allocated_budget: { files: 100, loc: 10000 },
245
+ waivers: ['WV-0002'],
246
+ });
247
+ }
248
+
249
+ return mockHistoricalProjects
250
+ .map((project) => ({
251
+ project: project.id,
252
+ similarity_score: this.calculateSimilarity(currentSpec, project),
253
+ budget_accuracy: project.actual_budget.files / project.allocated_budget.files,
254
+ waiver_count: project.waivers.length,
255
+ details: project,
256
+ }))
257
+ .filter((p) => p.similarity_score > 0.3) // Lower threshold for demonstration
258
+ .sort((a, b) => b.similarity_score - a.similarity_score)
259
+ .slice(0, 5); // Top 5 matches
260
+ }
261
+
262
+ /**
263
+ * Calculate similarity score between two specs/projects
264
+ */
265
+ calculateSimilarity(spec1, spec2) {
266
+ let score = 0;
267
+ let factors = 0;
268
+
269
+ // Risk tier match
270
+ if (spec1.risk_tier === spec2.risk_tier) {
271
+ score += 0.3;
272
+ }
273
+ factors += 0.3;
274
+
275
+ // Mode match
276
+ if (spec1.mode === spec2.mode) {
277
+ score += 0.2;
278
+ }
279
+ factors += 0.2;
280
+
281
+ // Tech stack match (if available)
282
+ if (spec1.tech_stack && spec2.tech_stack && spec1.tech_stack === spec2.tech_stack) {
283
+ score += 0.2;
284
+ }
285
+ factors += 0.2;
286
+
287
+ // Feature type match (if available)
288
+ if (spec1.feature_type && spec2.feature_type && spec1.feature_type === spec2.feature_type) {
289
+ score += 0.3;
290
+ }
291
+ factors += 0.3;
292
+
293
+ return factors > 0 ? score / factors : 0;
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Budget Predictor using statistical analysis
299
+ */
300
+ class BudgetPredictor {
301
+ constructor(projectRoot = process.cwd()) {
302
+ this.projectRoot = projectRoot;
303
+ this.patternLearner = new WaiverPatternLearner(projectRoot);
304
+ this.similarityMatcher = new ProjectSimilarityMatcher(projectRoot);
305
+ }
306
+
307
+ /**
308
+ * Assess budget for a working spec
309
+ */
310
+ assessBudget(spec) {
311
+ try {
312
+ const patterns = this.patternLearner.analyzePatterns();
313
+ const similarProjects = this.similarityMatcher.findSimilarProjects(spec);
314
+
315
+ if (patterns.status !== 'success' || similarProjects.length === 0) {
316
+ return {
317
+ status: 'insufficient_data',
318
+ message: 'Not enough historical data for accurate prediction',
319
+ recommendation: {
320
+ use_default_tier: true,
321
+ confidence: 0.0,
322
+ },
323
+ };
324
+ }
325
+
326
+ // Calculate recommended budget based on similar projects
327
+ const similarBudgets = similarProjects.map((p) => p.details.actual_budget);
328
+ const avgFiles = similarBudgets.reduce((sum, b) => sum + b.files, 0) / similarBudgets.length;
329
+ const avgLoc = similarBudgets.reduce((sum, b) => sum + b.loc, 0) / similarBudgets.length;
330
+
331
+ // Apply buffer based on waiver patterns
332
+ const fileBuffer = patterns.patterns.budget_overruns?.average_overrun_files || 0;
333
+ const locBuffer = patterns.patterns.budget_overruns?.average_overrun_loc || 0;
334
+
335
+ const recommendedFiles = Math.round(avgFiles * (1 + fileBuffer / 100));
336
+ const recommendedLoc = Math.round(avgLoc * (1 + locBuffer / 100));
337
+
338
+ // Calculate confidence based on sample size and variance
339
+ const confidence = Math.min(0.9, similarProjects.length / 10); // Max 90% confidence
340
+
341
+ return {
342
+ status: 'success',
343
+ assessment: {
344
+ similar_projects_analyzed: similarProjects.length,
345
+ recommended_budget: {
346
+ files: recommendedFiles,
347
+ loc: recommendedLoc,
348
+ },
349
+ baseline_budget: {
350
+ files: Math.round(avgFiles),
351
+ loc: Math.round(avgLoc),
352
+ },
353
+ buffer_applied: {
354
+ files_percent: Math.round((fileBuffer / avgFiles) * 100),
355
+ loc_percent: Math.round((locBuffer / avgLoc) * 100),
356
+ },
357
+ rationale: this.generateRationale(spec, similarProjects, patterns),
358
+ risk_factors: patterns.patterns.risk_factors || [],
359
+ confidence: Math.round(confidence * 100) / 100,
360
+ },
361
+ };
362
+ } catch (error) {
363
+ return {
364
+ status: 'error',
365
+ message: error.message,
366
+ recommendation: {
367
+ use_default_tier: true,
368
+ confidence: 0.0,
369
+ },
370
+ };
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Generate human-readable rationale for the recommendation
376
+ */
377
+ generateRationale(spec, similarProjects, patterns) {
378
+ const reasons = [];
379
+
380
+ if (similarProjects.length > 0) {
381
+ const topMatch = similarProjects[0];
382
+ reasons.push(
383
+ `Similar to ${topMatch.project} (${Math.round(topMatch.similarity_score * 100)}% match)`
384
+ );
385
+ }
386
+
387
+ if (patterns.patterns.budget_overruns?.common_patterns?.length > 0) {
388
+ const topPattern = patterns.patterns.budget_overruns.common_patterns[0];
389
+ reasons.push(
390
+ `Historical ${topPattern.reason} overruns add ${topPattern.avg_overrun_files} files on average`
391
+ );
392
+ }
393
+
394
+ if (spec.mode === 'feature') {
395
+ reasons.push('Feature development typically needs 15-25% budget buffer');
396
+ }
397
+
398
+ return reasons;
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Main Test Analysis CLI handler
404
+ */
405
+ async function testAnalysisCommand(subcommand, options = []) {
406
+ const chalk = (await import('chalk')).default;
407
+
408
+ try {
409
+ switch (subcommand) {
410
+ case 'assess-budget':
411
+ return await handleAssessBudget(options);
412
+ case 'analyze-patterns':
413
+ return await handleAnalyzePatterns(options);
414
+ case 'find-similar':
415
+ return await handleFindSimilar(options);
416
+ default:
417
+ console.log(chalk.red('āŒ Unknown test-analysis subcommand'));
418
+ console.log('Available commands:');
419
+ console.log(' assess-budget - Analyze budget needs for current spec');
420
+ console.log(' analyze-patterns - Show waiver pattern analysis');
421
+ console.log(' find-similar - Find similar historical projects');
422
+ return;
423
+ }
424
+ } catch (error) {
425
+ console.error(chalk.red('āŒ Test analysis failed:'), error.message);
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Handle budget assessment command
431
+ */
432
+ async function handleAssessBudget(options) {
433
+ const chalk = (await import('chalk')).default;
434
+ const predictor = new BudgetPredictor();
435
+
436
+ // Load current spec
437
+ let specPath = '.caws/working-spec.yaml';
438
+ if (options.includes('--spec')) {
439
+ const specIndex = options.indexOf('--spec');
440
+ if (specIndex + 1 < options.length) {
441
+ specPath = options[specIndex + 1];
442
+ }
443
+ }
444
+
445
+ try {
446
+ const specContent = fs.readFileSync(specPath, 'utf8');
447
+ const spec = yaml.load(specContent);
448
+
449
+ console.log(chalk.cyan(`šŸ“Š Budget Assessment for ${spec.id}`));
450
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
451
+
452
+ const result = predictor.assessBudget(spec);
453
+
454
+ if (result.status === 'success') {
455
+ const assessment = result.assessment;
456
+ console.log(
457
+ `Historical Analysis: ${assessment.similar_projects_analyzed} similar projects analyzed`
458
+ );
459
+ console.log(
460
+ `šŸŽÆ Recommended Budget: ${assessment.recommended_budget.files} files, ${assessment.recommended_budget.loc} LOC (+${assessment.buffer_applied.files_percent}% buffer)`
461
+ );
462
+ console.log(`šŸ’” Rationale: ${assessment.rationale.join('; ')}`);
463
+
464
+ if (assessment.risk_factors.length > 0) {
465
+ console.log(
466
+ chalk.yellow(
467
+ `āš ļø Risk Factors: ${assessment.risk_factors.map((f) => f.description).join('; ')}`
468
+ )
469
+ );
470
+ }
471
+
472
+ const confidenceLevel =
473
+ assessment.confidence > 0.8 ? 'High' : assessment.confidence > 0.6 ? 'Medium' : 'Low';
474
+ console.log(
475
+ chalk.green(
476
+ `āœ… Confidence: ${confidenceLevel} (${Math.round(assessment.confidence * 100)}%)`
477
+ )
478
+ );
479
+ } else {
480
+ console.log(chalk.yellow(`āš ļø ${result.message}`));
481
+ console.log('šŸ’” Consider using default tier-based budgeting for now');
482
+ }
483
+ } catch (error) {
484
+ console.error(chalk.red('āŒ Failed to load spec:'), error.message);
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Handle pattern analysis command
490
+ */
491
+ async function handleAnalyzePatterns(options) {
492
+ const chalk = (await import('chalk')).default;
493
+ const learner = new WaiverPatternLearner();
494
+
495
+ console.log(chalk.cyan('šŸ” Analyzing Waiver Patterns'));
496
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
497
+
498
+ const result = learner.analyzePatterns();
499
+
500
+ if (result.status === 'success') {
501
+ const patterns = result.patterns;
502
+
503
+ console.log(`Total waivers analyzed: ${patterns.total_waivers}`);
504
+
505
+ if (patterns.budget_overruns) {
506
+ console.log('\nšŸ’° Budget Overrun Patterns:');
507
+ console.log(
508
+ ` Average overrun: ${patterns.budget_overruns.average_overrun_files} files, ${patterns.budget_overruns.average_overrun_loc} LOC`
509
+ );
510
+
511
+ if (patterns.budget_overruns.common_patterns.length > 0) {
512
+ console.log(' Common patterns:');
513
+ patterns.budget_overruns.common_patterns.forEach((pattern) => {
514
+ console.log(
515
+ ` ${pattern.reason}: ${Math.round(pattern.frequency * 100)}% frequency (+${pattern.avg_overrun_files} files avg)`
516
+ );
517
+ });
518
+ }
519
+ }
520
+
521
+ if (patterns.common_reasons.length > 0) {
522
+ console.log('\nšŸ“‹ Most Common Waiver Reasons:');
523
+ patterns.common_reasons.slice(0, 5).forEach((reason) => {
524
+ console.log(
525
+ ` ${reason.reason}: ${reason.count} times (${Math.round(reason.frequency * 100)}%)`
526
+ );
527
+ });
528
+ }
529
+ } else {
530
+ console.log(chalk.yellow(`āš ļø ${result.message}`));
531
+ }
532
+ }
533
+
534
+ /**
535
+ * Handle find similar projects command
536
+ */
537
+ async function handleFindSimilar(options) {
538
+ const chalk = (await import('chalk')).default;
539
+ const matcher = new ProjectSimilarityMatcher();
540
+
541
+ // Load current spec
542
+ let specPath = '.caws/working-spec.yaml';
543
+ if (options.includes('--spec')) {
544
+ const specIndex = options.indexOf('--spec');
545
+ if (specIndex + 1 < options.length) {
546
+ specPath = options[specIndex + 1];
547
+ }
548
+ }
549
+
550
+ try {
551
+ const specContent = fs.readFileSync(specPath, 'utf8');
552
+ const spec = yaml.load(specContent);
553
+
554
+ console.log(chalk.cyan(`šŸ” Finding projects similar to ${spec.id}`));
555
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
556
+
557
+ const similar = matcher.findSimilarProjects(spec);
558
+
559
+ if (similar.length > 0) {
560
+ similar.forEach((project) => {
561
+ const similarityPercent = Math.round(project.similarity_score * 100);
562
+ const accuracyPercent = Math.round(project.budget_accuracy * 100);
563
+ console.log(
564
+ `${project.project}: ${similarityPercent}% similar, ${accuracyPercent}% budget accuracy, ${project.waiver_count} waivers`
565
+ );
566
+ });
567
+ } else {
568
+ console.log(chalk.yellow('āš ļø No similar projects found'));
569
+ }
570
+ } catch (error) {
571
+ console.error(chalk.red('āŒ Failed to load spec:'), error.message);
572
+ }
573
+ }
574
+
575
+ module.exports = {
576
+ testAnalysisCommand,
577
+ WaiverPatternLearner,
578
+ ProjectSimilarityMatcher,
579
+ BudgetPredictor,
580
+ };