@paths.design/caws-cli 3.0.0 ā 3.1.1
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/README.md +295 -150
- package/dist/budget-derivation.d.ts +35 -0
- package/dist/budget-derivation.d.ts.map +1 -0
- package/dist/budget-derivation.js +204 -0
- package/dist/cicd-optimizer.d.ts +142 -0
- package/dist/cicd-optimizer.d.ts.map +1 -0
- package/dist/cicd-optimizer.js +504 -0
- package/dist/commands/burnup.d.ts +6 -0
- package/dist/commands/burnup.d.ts.map +1 -0
- package/dist/commands/burnup.js +90 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +514 -0
- package/dist/commands/provenance.d.ts +22 -0
- package/dist/commands/provenance.d.ts.map +1 -0
- package/dist/commands/provenance.js +594 -0
- package/dist/commands/tool.d.ts +13 -0
- package/dist/commands/tool.d.ts.map +1 -0
- package/dist/commands/tool.js +138 -0
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +80 -0
- package/dist/config/index.d.ts +29 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +132 -0
- package/dist/error-handler.d.ts +50 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +253 -0
- package/dist/generators/working-spec.d.ts +13 -0
- package/dist/generators/working-spec.d.ts.map +1 -0
- package/dist/generators/working-spec.js +204 -0
- package/dist/index-new.d.ts +5 -0
- package/dist/index-new.d.ts.map +1 -0
- package/dist/index-new.js +317 -0
- package/dist/index.d.ts +3 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +100 -1659
- package/dist/index.js.backup +4711 -0
- package/dist/scaffold/cursor-hooks.d.ts +7 -0
- package/dist/scaffold/cursor-hooks.d.ts.map +1 -0
- package/dist/scaffold/cursor-hooks.js +152 -0
- package/dist/scaffold/index.d.ts +20 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +486 -0
- package/dist/test-analysis.d.ts +182 -0
- package/dist/test-analysis.d.ts.map +1 -0
- package/dist/test-analysis.js +580 -0
- package/dist/tool-interface.d.ts +236 -0
- package/dist/tool-interface.d.ts.map +1 -0
- package/dist/tool-interface.js +314 -0
- package/dist/tool-loader.d.ts +77 -0
- package/dist/tool-loader.d.ts.map +1 -0
- package/dist/tool-loader.js +298 -0
- package/dist/tool-validator.d.ts +72 -0
- package/dist/tool-validator.d.ts.map +1 -0
- package/dist/tool-validator.js +387 -0
- package/dist/utils/detection.d.ts +7 -0
- package/dist/utils/detection.d.ts.map +1 -0
- package/dist/utils/detection.js +174 -0
- package/dist/utils/finalization.d.ts +17 -0
- package/dist/utils/finalization.d.ts.map +1 -0
- package/dist/utils/finalization.js +229 -0
- package/dist/utils/project-analysis.d.ts +14 -0
- package/dist/utils/project-analysis.d.ts.map +1 -0
- package/dist/utils/project-analysis.js +105 -0
- package/dist/validation/spec-validation.d.ts +29 -0
- package/dist/validation/spec-validation.d.ts.map +1 -0
- package/dist/validation/spec-validation.js +376 -0
- package/dist/waivers-manager.d.ts +167 -0
- package/dist/waivers-manager.d.ts.map +1 -0
- package/dist/waivers-manager.js +549 -0
- package/package.json +10 -12
- package/templates/.cursor/README.md +311 -0
- package/templates/.cursor/hooks/audit.sh +55 -0
- package/templates/.cursor/hooks/block-dangerous.sh +77 -0
- package/templates/.cursor/hooks/caws-quality-check.sh +52 -0
- package/templates/.cursor/hooks/caws-scope-guard.sh +74 -0
- package/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
- package/templates/.cursor/hooks/format.sh +38 -0
- package/templates/.cursor/hooks/naming-check.sh +64 -0
- package/templates/.cursor/hooks/scan-secrets.sh +46 -0
- package/templates/.cursor/hooks/scope-guard.sh +52 -0
- package/templates/.cursor/hooks/validate-spec.sh +38 -0
- package/templates/.cursor/hooks.json +59 -0
- package/templates/.github/copilot/instructions.md +311 -0
- package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
- package/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
- package/templates/.vscode/launch.json +56 -0
- package/templates/.vscode/settings.json +93 -0
- package/templates/.windsurf/workflows/caws-guided-development.md +92 -0
- package/templates/apps/tools/caws/README.md +1 -1
- package/templates/apps/tools/caws/prompt-lint.js.backup +274 -0
- package/templates/apps/tools/caws/provenance.js.backup +73 -0
- package/templates/apps/tools/caws/schemas/working-spec.schema.json +21 -3
- package/templates/codemod/test.js +93 -1
|
@@ -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
|
+
};
|