@paths.design/caws-cli 2.0.1 → 3.0.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 (50) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +101 -96
  3. package/package.json +3 -2
  4. package/templates/agents.md +820 -0
  5. package/templates/apps/tools/caws/COMPLETION_REPORT.md +331 -0
  6. package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +360 -0
  7. package/templates/apps/tools/caws/README.md +463 -0
  8. package/templates/apps/tools/caws/TEST_STATUS.md +365 -0
  9. package/templates/apps/tools/caws/attest.js +357 -0
  10. package/templates/apps/tools/caws/ci-optimizer.js +642 -0
  11. package/templates/apps/tools/caws/config.ts +245 -0
  12. package/templates/apps/tools/caws/cross-functional.js +876 -0
  13. package/templates/apps/tools/caws/dashboard.js +1112 -0
  14. package/templates/apps/tools/caws/flake-detector.ts +362 -0
  15. package/templates/apps/tools/caws/gates.js +198 -0
  16. package/templates/apps/tools/caws/gates.ts +237 -0
  17. package/templates/apps/tools/caws/language-adapters.ts +381 -0
  18. package/templates/apps/tools/caws/language-support.d.ts +367 -0
  19. package/templates/apps/tools/caws/language-support.d.ts.map +1 -0
  20. package/templates/apps/tools/caws/language-support.js +585 -0
  21. package/templates/apps/tools/caws/legacy-assessment.ts +408 -0
  22. package/templates/apps/tools/caws/legacy-assessor.js +764 -0
  23. package/templates/apps/tools/caws/mutant-analyzer.js +734 -0
  24. package/templates/apps/tools/caws/perf-budgets.ts +349 -0
  25. package/templates/apps/tools/caws/property-testing.js +707 -0
  26. package/templates/apps/tools/caws/provenance.d.ts +14 -0
  27. package/templates/apps/tools/caws/provenance.d.ts.map +1 -0
  28. package/templates/apps/tools/caws/provenance.js +132 -0
  29. package/templates/apps/tools/caws/provenance.ts +211 -0
  30. package/templates/apps/tools/caws/schemas/waivers.schema.json +30 -0
  31. package/templates/apps/tools/caws/schemas/working-spec.schema.json +115 -0
  32. package/templates/apps/tools/caws/scope-guard.js +208 -0
  33. package/templates/apps/tools/caws/security-provenance.ts +483 -0
  34. package/templates/apps/tools/caws/shared/base-tool.ts +281 -0
  35. package/templates/apps/tools/caws/shared/config-manager.ts +366 -0
  36. package/templates/apps/tools/caws/shared/gate-checker.ts +597 -0
  37. package/templates/apps/tools/caws/shared/types.ts +444 -0
  38. package/templates/apps/tools/caws/shared/validator.ts +305 -0
  39. package/templates/apps/tools/caws/shared/waivers-manager.ts +174 -0
  40. package/templates/apps/tools/caws/spec-test-mapper.ts +391 -0
  41. package/templates/apps/tools/caws/templates/working-spec.template.yml +60 -0
  42. package/templates/apps/tools/caws/test-quality.js +578 -0
  43. package/templates/apps/tools/caws/tools-allow.json +331 -0
  44. package/templates/apps/tools/caws/validate.js +76 -0
  45. package/templates/apps/tools/caws/validate.ts +228 -0
  46. package/templates/apps/tools/caws/waivers.js +344 -0
  47. package/templates/apps/tools/caws/waivers.yml +19 -0
  48. package/templates/codemod/README.md +1 -0
  49. package/templates/codemod/test.js +1 -0
  50. package/templates/docs/README.md +150 -0
@@ -0,0 +1,408 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ /**
4
+ * CAWS Legacy Assessment Tool
5
+ * Assesses legacy code for CAWS migration and generates migration plans
6
+ *
7
+ * @author @darianrosebrook
8
+ */
9
+
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { CawsBaseTool } from './shared/base-tool.js';
13
+
14
+ interface LegacyAssessment {
15
+ module: string;
16
+ complexity: number;
17
+ coverage: number;
18
+ changeFrequency: number;
19
+ dependencies: number;
20
+ recommendedTier: number;
21
+ migrationPriority: 'high' | 'medium' | 'low';
22
+ quickWins: string[];
23
+ estimatedEffort: 'small' | 'medium' | 'large';
24
+ }
25
+
26
+ interface MigrationPlan {
27
+ phase: number;
28
+ modules: string[];
29
+ estimatedDays: number;
30
+ dependencies: string[];
31
+ risks: string[];
32
+ }
33
+
34
+ export class LegacyAssessmentTool extends CawsBaseTool {
35
+ /**
36
+ * Assess a legacy module for CAWS migration
37
+ */
38
+ async assessModule(modulePath: string): Promise<LegacyAssessment> {
39
+ const complexity = await this.calculateComplexity(modulePath);
40
+ const coverage = await this.getCurrentCoverage(modulePath);
41
+ const changeFrequency = await this.analyzeChangeFrequency(modulePath);
42
+ const dependencies = await this.analyzeDependencies(modulePath);
43
+
44
+ const recommendedTier = this.inferTier(coverage, dependencies, changeFrequency);
45
+
46
+ const migrationPriority = this.calculatePriority(changeFrequency, coverage, complexity);
47
+
48
+ const quickWins = this.identifyQuickWins(modulePath, coverage);
49
+
50
+ const estimatedEffort = this.estimateEffort(complexity, coverage);
51
+
52
+ return {
53
+ module: modulePath,
54
+ complexity,
55
+ coverage,
56
+ changeFrequency,
57
+ dependencies,
58
+ recommendedTier,
59
+ migrationPriority,
60
+ quickWins,
61
+ estimatedEffort,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Generate migration plan for legacy codebase
67
+ */
68
+ async generateMigrationPlan(projectDir: string): Promise<{
69
+ phases: MigrationPlan[];
70
+ totalEstimatedDays: number;
71
+ criticalPath: string[];
72
+ }> {
73
+ const modules = await this.findModules(projectDir);
74
+ const assessments = await Promise.all(modules.map((m) => this.assessModule(m)));
75
+
76
+ // Sort by priority and dependencies
77
+ const sortedModules = assessments.sort((a, b) => {
78
+ const priorityScore = { high: 3, medium: 2, low: 1 };
79
+ return priorityScore[b.migrationPriority] - priorityScore[a.migrationPriority];
80
+ });
81
+
82
+ const phases: MigrationPlan[] = [];
83
+ let currentPhase: string[] = [];
84
+ let phaseNumber = 1;
85
+ let totalDays = 0;
86
+
87
+ const effortDays = { small: 2, medium: 5, large: 10 };
88
+
89
+ for (const assessment of sortedModules) {
90
+ if (currentPhase.length >= 3) {
91
+ // Max 3 modules per phase
92
+ const phaseDays = currentPhase.reduce(
93
+ (sum, mod) =>
94
+ sum +
95
+ effortDays[sortedModules.find((a) => a.module === mod)?.estimatedEffort || 'medium'],
96
+ 0
97
+ );
98
+
99
+ phases.push({
100
+ phase: phaseNumber++,
101
+ modules: [...currentPhase],
102
+ estimatedDays: phaseDays,
103
+ dependencies: [],
104
+ risks: ['Dependencies may require coordination'],
105
+ });
106
+
107
+ totalDays += phaseDays;
108
+ currentPhase = [];
109
+ }
110
+
111
+ currentPhase.push(assessment.module);
112
+ }
113
+
114
+ // Add final phase
115
+ if (currentPhase.length > 0) {
116
+ const phaseDays = currentPhase.reduce(
117
+ (sum, mod) =>
118
+ sum +
119
+ effortDays[sortedModules.find((a) => a.module === mod)?.estimatedEffort || 'medium'],
120
+ 0
121
+ );
122
+
123
+ phases.push({
124
+ phase: phaseNumber,
125
+ modules: currentPhase,
126
+ estimatedDays: phaseDays,
127
+ dependencies: [],
128
+ risks: [],
129
+ });
130
+
131
+ totalDays += phaseDays;
132
+ }
133
+
134
+ const criticalPath = sortedModules
135
+ .filter((a) => a.migrationPriority === 'high')
136
+ .map((a) => a.module);
137
+
138
+ return { phases, totalEstimatedDays: totalDays, criticalPath };
139
+ }
140
+
141
+ private async findModules(projectDir: string): Promise<string[]> {
142
+ const modules: string[] = [];
143
+ const srcDir = path.join(projectDir, 'src');
144
+
145
+ if (!fs.existsSync(srcDir)) {
146
+ return modules;
147
+ }
148
+
149
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
150
+ for (const entry of entries) {
151
+ if (entry.isDirectory()) {
152
+ const modulePath = path.join('src', entry.name);
153
+ modules.push(modulePath);
154
+ }
155
+ }
156
+
157
+ return modules;
158
+ }
159
+
160
+ private async calculateComplexity(modulePath: string): Promise<number> {
161
+ // Simplified complexity calculation
162
+ try {
163
+ const files = this.findFilesRecursive(modulePath);
164
+ let totalLines = 0;
165
+ let cyclomaticSum = 0;
166
+
167
+ for (const file of files) {
168
+ const content = fs.readFileSync(file, 'utf-8');
169
+ const lines = content.split('\n').length;
170
+ totalLines += lines;
171
+
172
+ // Count control flow statements as proxy for cyclomatic complexity
173
+ const controlFlow = (
174
+ content.match(/\b(if|else|for|while|switch|case|catch|&&|\|\|)\b/g) || []
175
+ ).length;
176
+ cyclomaticSum += controlFlow;
177
+ }
178
+
179
+ return files.length > 0 ? cyclomaticSum / files.length : 0;
180
+ } catch {
181
+ return 0;
182
+ }
183
+ }
184
+
185
+ private async getCurrentCoverage(modulePath: string): Promise<number> {
186
+ try {
187
+ const coveragePath = path.join(process.cwd(), 'coverage', 'coverage-final.json');
188
+ if (!fs.existsSync(coveragePath)) {
189
+ return 0;
190
+ }
191
+
192
+ const coverageData = JSON.parse(fs.readFileSync(coveragePath, 'utf-8'));
193
+ let totalStatements = 0;
194
+ let coveredStatements = 0;
195
+
196
+ for (const [file, data] of Object.entries(coverageData)) {
197
+ if (file.includes(modulePath)) {
198
+ const fileData = data as any;
199
+ if (fileData.s) {
200
+ totalStatements += Object.keys(fileData.s).length;
201
+ coveredStatements += Object.values(fileData.s).filter((s: any) => s > 0).length;
202
+ }
203
+ }
204
+ }
205
+
206
+ return totalStatements > 0 ? coveredStatements / totalStatements : 0;
207
+ } catch {
208
+ return 0;
209
+ }
210
+ }
211
+
212
+ private async analyzeChangeFrequency(modulePath: string): Promise<number> {
213
+ // Simplified - in real implementation, use git log
214
+ try {
215
+ const files = this.findFilesRecursive(modulePath);
216
+ // Placeholder: return based on number of files as proxy
217
+ return Math.min(files.length / 10, 1);
218
+ } catch {
219
+ return 0;
220
+ }
221
+ }
222
+
223
+ private async analyzeDependencies(modulePath: string): Promise<number> {
224
+ try {
225
+ const files = this.findFilesRecursive(modulePath);
226
+ let importCount = 0;
227
+
228
+ for (const file of files) {
229
+ const content = fs.readFileSync(file, 'utf-8');
230
+ const imports = content.match(/^import .* from/gm) || [];
231
+ importCount += imports.length;
232
+ }
233
+
234
+ return files.length > 0 ? importCount / files.length : 0;
235
+ } catch {
236
+ return 0;
237
+ }
238
+ }
239
+
240
+ private inferTier(coverage: number, dependencies: number, changeFrequency: number): number {
241
+ // High change frequency + low coverage = critical (Tier 1)
242
+ if (changeFrequency > 0.7 && coverage < 0.5) {
243
+ return 1;
244
+ }
245
+
246
+ // Medium activity
247
+ if (changeFrequency > 0.4 || dependencies > 5) {
248
+ return 2;
249
+ }
250
+
251
+ // Low activity, isolated
252
+ return 3;
253
+ }
254
+
255
+ private calculatePriority(
256
+ changeFrequency: number,
257
+ coverage: number,
258
+ complexity: number
259
+ ): 'high' | 'medium' | 'low' {
260
+ const score = changeFrequency * 0.4 + (1 - coverage) * 0.4 + complexity * 0.2;
261
+
262
+ if (score > 0.7) return 'high';
263
+ if (score > 0.4) return 'medium';
264
+ return 'low';
265
+ }
266
+
267
+ private identifyQuickWins(modulePath: string, coverage: number): string[] {
268
+ const wins: string[] = [];
269
+
270
+ if (coverage === 0) {
271
+ wins.push('Add basic smoke tests for main functions');
272
+ } else if (coverage < 0.3) {
273
+ wins.push('Increase coverage by testing happy paths');
274
+ }
275
+
276
+ const files = this.findFilesRecursive(modulePath);
277
+ if (files.some((f) => !f.includes('.test.') && !f.includes('.spec.'))) {
278
+ wins.push('Add test files for untested modules');
279
+ }
280
+
281
+ wins.push('Extract pure functions for easier testing');
282
+ wins.push('Add type definitions if missing');
283
+
284
+ return wins;
285
+ }
286
+
287
+ private estimateEffort(complexity: number, coverage: number): 'small' | 'medium' | 'large' {
288
+ const effortScore = complexity * 0.6 + (1 - coverage) * 0.4;
289
+
290
+ if (effortScore > 0.7) return 'large';
291
+ if (effortScore > 0.4) return 'medium';
292
+ return 'small';
293
+ }
294
+
295
+ private findFilesRecursive(dir: string, files: string[] = []): string[] {
296
+ try {
297
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
298
+
299
+ for (const entry of entries) {
300
+ const fullPath = path.join(dir, entry.name);
301
+ if (entry.isDirectory() && !entry.name.includes('node_modules')) {
302
+ this.findFilesRecursive(fullPath, files);
303
+ } else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.js'))) {
304
+ files.push(fullPath);
305
+ }
306
+ }
307
+ } catch {
308
+ // Directory doesn't exist
309
+ }
310
+
311
+ return files;
312
+ }
313
+ }
314
+
315
+ // CLI interface
316
+ if (import.meta.url === `file://${process.argv[1]}`) {
317
+ (async () => {
318
+ const command = process.argv[2];
319
+ const tool = new LegacyAssessmentTool();
320
+
321
+ switch (command) {
322
+ case 'assess': {
323
+ const modulePath = process.argv[3];
324
+ if (!modulePath) {
325
+ console.error('Usage: legacy-assessment assess <module-path>');
326
+ process.exit(1);
327
+ }
328
+
329
+ try {
330
+ const assessment = await tool.assessModule(modulePath);
331
+
332
+ console.log('\nšŸ“Š Legacy Module Assessment');
333
+ console.log('='.repeat(50));
334
+ console.log(`Module: ${assessment.module}`);
335
+ console.log(`Complexity: ${assessment.complexity.toFixed(2)} (avg cyclomatic per file)`);
336
+ console.log(`Coverage: ${(assessment.coverage * 100).toFixed(1)}%`);
337
+ console.log(`Change Frequency: ${(assessment.changeFrequency * 100).toFixed(1)}%`);
338
+ console.log(`Avg Dependencies: ${assessment.dependencies.toFixed(1)}`);
339
+ console.log(`\nRecommended Tier: ${assessment.recommendedTier}`);
340
+ console.log(`Migration Priority: ${assessment.migrationPriority}`);
341
+ console.log(`Estimated Effort: ${assessment.estimatedEffort}`);
342
+
343
+ if (assessment.quickWins.length > 0) {
344
+ console.log(`\nšŸŽÆ Quick Wins:`);
345
+ assessment.quickWins.forEach((win) => {
346
+ console.log(` - ${win}`);
347
+ });
348
+ }
349
+ } catch (error) {
350
+ console.error(`āŒ Assessment failed: ${error}`);
351
+ process.exit(1);
352
+ }
353
+ break;
354
+ }
355
+
356
+ case 'plan': {
357
+ const projectDir = process.argv[3] || process.cwd();
358
+
359
+ try {
360
+ const plan = await tool.generateMigrationPlan(projectDir);
361
+
362
+ console.log('\nšŸ—“ļø Legacy Migration Plan');
363
+ console.log('='.repeat(50));
364
+ console.log(`Total Estimated Days: ${plan.totalEstimatedDays}`);
365
+ console.log(`Number of Phases: ${plan.phases.length}`);
366
+
367
+ if (plan.criticalPath.length > 0) {
368
+ console.log(`\nšŸ”“ Critical Path Modules:`);
369
+ plan.criticalPath.forEach((mod) => {
370
+ console.log(` - ${mod}`);
371
+ });
372
+ }
373
+
374
+ console.log(`\nšŸ“‹ Migration Phases:`);
375
+ plan.phases.forEach((phase) => {
376
+ console.log(`\nPhase ${phase.phase} (${phase.estimatedDays} days):`);
377
+ console.log(` Modules:`);
378
+ phase.modules.forEach((mod) => {
379
+ console.log(` - ${mod}`);
380
+ });
381
+ if (phase.risks.length > 0) {
382
+ console.log(` Risks:`);
383
+ phase.risks.forEach((risk) => {
384
+ console.log(` āš ļø ${risk}`);
385
+ });
386
+ }
387
+ });
388
+ } catch (error) {
389
+ console.error(`āŒ Plan generation failed: ${error}`);
390
+ process.exit(1);
391
+ }
392
+ break;
393
+ }
394
+
395
+ default:
396
+ console.log('CAWS Legacy Assessment Tool');
397
+ console.log('');
398
+ console.log('Usage:');
399
+ console.log(' legacy-assessment assess <module-path> - Assess legacy module');
400
+ console.log(' legacy-assessment plan [project-dir] - Generate migration plan');
401
+ console.log('');
402
+ console.log('Examples:');
403
+ console.log(' legacy-assessment assess src/auth');
404
+ console.log(' legacy-assessment plan .');
405
+ break;
406
+ }
407
+ })();
408
+ }