@paths.design/caws-cli 7.0.2 ā 7.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/budget-derivation.js +5 -4
- package/dist/commands/diagnose.js +24 -19
- package/dist/commands/init.js +51 -4
- package/dist/commands/specs.js +40 -1
- package/dist/commands/status.js +2 -2
- package/dist/commands/tool.js +2 -3
- package/dist/config/index.js +17 -8
- package/dist/generators/working-spec.js +19 -6
- package/dist/scaffold/git-hooks.js +127 -29
- package/dist/scaffold/index.js +53 -7
- package/dist/templates/.caws/tools/README.md +20 -0
- package/dist/templates/.cursor/README.md +311 -0
- package/dist/templates/.cursor/hooks/audit.sh +55 -0
- package/dist/templates/.cursor/hooks/block-dangerous.sh +83 -0
- package/dist/templates/.cursor/hooks/caws-quality-check.sh +52 -0
- package/dist/templates/.cursor/hooks/caws-scope-guard.sh +130 -0
- package/dist/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
- package/dist/templates/.cursor/hooks/format.sh +38 -0
- package/dist/templates/.cursor/hooks/naming-check.sh +64 -0
- package/dist/templates/.cursor/hooks/scan-secrets.sh +46 -0
- package/dist/templates/.cursor/hooks/scope-guard.sh +52 -0
- package/dist/templates/.cursor/hooks/validate-spec.sh +83 -0
- package/dist/templates/.cursor/hooks.json +59 -0
- package/dist/templates/.cursor/rules/00-claims-verification.mdc +144 -0
- package/dist/templates/.cursor/rules/01-working-style.mdc +50 -0
- package/dist/templates/.cursor/rules/02-quality-gates.mdc +370 -0
- package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +33 -0
- package/dist/templates/.cursor/rules/04-logging-language-style.mdc +23 -0
- package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +23 -0
- package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +36 -0
- package/dist/templates/.cursor/rules/07-process-ops.mdc +20 -0
- package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +16 -0
- package/dist/templates/.cursor/rules/09-docstrings.mdc +89 -0
- package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +390 -0
- package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +385 -0
- package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +516 -0
- package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +588 -0
- package/dist/templates/.cursor/rules/README.md +148 -0
- package/dist/templates/.github/copilot/instructions.md +311 -0
- package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
- package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
- package/dist/templates/.vscode/launch.json +56 -0
- package/dist/templates/.vscode/settings.json +93 -0
- package/dist/templates/.windsurf/workflows/caws-guided-development.md +92 -0
- package/dist/templates/COMMIT_CONVENTIONS.md +86 -0
- package/dist/templates/OIDC_SETUP.md +300 -0
- package/dist/templates/agents.md +1047 -0
- package/dist/templates/codemod/README.md +1 -0
- package/dist/templates/codemod/test.js +93 -0
- package/dist/templates/docs/README.md +150 -0
- package/dist/templates/scripts/quality-gates/check-god-objects.js +146 -0
- package/dist/templates/scripts/quality-gates/run-quality-gates.js +50 -0
- package/dist/templates/scripts/v3/analysis/todo_analyzer.py +1997 -0
- package/dist/tool-loader.js +6 -1
- package/dist/tool-validator.js +8 -2
- package/dist/utils/detection.js +4 -3
- package/dist/utils/git-lock.js +118 -0
- package/dist/utils/gitignore-updater.js +148 -0
- package/dist/utils/quality-gates.js +47 -7
- package/dist/utils/spec-resolver.js +23 -3
- package/dist/utils/yaml-validation.js +155 -0
- package/dist/validation/spec-validation.js +81 -2
- package/package.json +2 -2
- package/templates/.caws/schemas/waivers.schema.json +30 -0
- package/templates/.caws/schemas/working-spec.schema.json +133 -0
- package/templates/.caws/templates/working-spec.template.yml +74 -0
- package/templates/.caws/tools/README.md +20 -0
- package/templates/.caws/tools/scope-guard.js +208 -0
- package/templates/.caws/tools-allow.json +331 -0
- package/templates/.caws/waivers.yml +19 -0
- package/templates/.cursor/hooks/scope-guard.sh +2 -2
- package/templates/.cursor/hooks/validate-spec.sh +42 -7
- package/templates/apps/tools/caws/COMPLETION_REPORT.md +0 -331
- package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +0 -360
- package/templates/apps/tools/caws/README.md +0 -463
- package/templates/apps/tools/caws/TEST_STATUS.md +0 -365
- package/templates/apps/tools/caws/attest.js +0 -357
- package/templates/apps/tools/caws/ci-optimizer.js +0 -642
- package/templates/apps/tools/caws/config.ts +0 -245
- package/templates/apps/tools/caws/cross-functional.js +0 -876
- package/templates/apps/tools/caws/dashboard.js +0 -1112
- package/templates/apps/tools/caws/flake-detector.ts +0 -362
- package/templates/apps/tools/caws/gates.js +0 -198
- package/templates/apps/tools/caws/gates.ts +0 -271
- package/templates/apps/tools/caws/language-adapters.ts +0 -381
- package/templates/apps/tools/caws/language-support.d.ts +0 -367
- package/templates/apps/tools/caws/language-support.d.ts.map +0 -1
- package/templates/apps/tools/caws/language-support.js +0 -585
- package/templates/apps/tools/caws/legacy-assessment.ts +0 -408
- package/templates/apps/tools/caws/legacy-assessor.js +0 -764
- package/templates/apps/tools/caws/mutant-analyzer.js +0 -734
- package/templates/apps/tools/caws/perf-budgets.ts +0 -349
- package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
- package/templates/apps/tools/caws/property-testing.js +0 -707
- package/templates/apps/tools/caws/provenance.d.ts +0 -14
- package/templates/apps/tools/caws/provenance.d.ts.map +0 -1
- package/templates/apps/tools/caws/provenance.js +0 -132
- package/templates/apps/tools/caws/provenance.js.backup +0 -73
- package/templates/apps/tools/caws/provenance.ts +0 -211
- package/templates/apps/tools/caws/security-provenance.ts +0 -483
- package/templates/apps/tools/caws/shared/base-tool.ts +0 -281
- package/templates/apps/tools/caws/shared/config-manager.ts +0 -366
- package/templates/apps/tools/caws/shared/gate-checker.ts +0 -849
- package/templates/apps/tools/caws/shared/types.ts +0 -444
- package/templates/apps/tools/caws/shared/validator.ts +0 -305
- package/templates/apps/tools/caws/shared/waivers-manager.ts +0 -174
- package/templates/apps/tools/caws/spec-test-mapper.ts +0 -391
- package/templates/apps/tools/caws/test-quality.js +0 -578
- package/templates/apps/tools/caws/validate.js +0 -76
- package/templates/apps/tools/caws/validate.ts +0 -228
- package/templates/apps/tools/caws/waivers.js +0 -344
- /package/{templates/apps/tools/caws ā dist/templates/.caws}/schemas/waivers.schema.json +0 -0
- /package/{templates/apps/tools/caws ā dist/templates/.caws}/schemas/working-spec.schema.json +0 -0
- /package/{templates/apps/tools/caws ā dist/templates/.caws}/templates/working-spec.template.yml +0 -0
- /package/{templates/apps/tools/caws ā dist/templates/.caws/tools}/scope-guard.js +0 -0
- /package/{templates/apps/tools/caws ā dist/templates/.caws}/tools-allow.json +0 -0
- /package/{templates/apps/tools/caws ā dist/templates/.caws}/waivers.yml +0 -0
|
@@ -1,764 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @fileoverview CAWS Legacy Codebase Assessment Tool
|
|
5
|
-
* Evaluates existing projects and provides incremental adoption roadmap for CAWS
|
|
6
|
-
* @author @darianrosebrook
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Assessment categories and their scoring criteria
|
|
14
|
-
*/
|
|
15
|
-
const ASSESSMENT_CATEGORIES = {
|
|
16
|
-
TESTING: {
|
|
17
|
-
name: 'Testing Infrastructure',
|
|
18
|
-
weight: 0.25,
|
|
19
|
-
indicators: {
|
|
20
|
-
testFiles: { weight: 0.3, description: 'Presence of test files' },
|
|
21
|
-
testCoverage: { weight: 0.25, description: 'Code coverage metrics' },
|
|
22
|
-
testTypes: { weight: 0.2, description: 'Unit, integration, and E2E tests' },
|
|
23
|
-
testQuality: { weight: 0.15, description: 'Test structure and assertions' },
|
|
24
|
-
testAutomation: { weight: 0.1, description: 'CI/CD integration' },
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
DOCUMENTATION: {
|
|
28
|
-
name: 'Documentation Quality',
|
|
29
|
-
weight: 0.2,
|
|
30
|
-
indicators: {
|
|
31
|
-
readmeQuality: { weight: 0.3, description: 'README completeness' },
|
|
32
|
-
codeComments: { weight: 0.25, description: 'Inline documentation' },
|
|
33
|
-
apiDocs: { weight: 0.2, description: 'API documentation' },
|
|
34
|
-
architectureDocs: { weight: 0.15, description: 'Architecture documentation' },
|
|
35
|
-
changelog: { weight: 0.1, description: 'Change tracking' },
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
CODE_QUALITY: {
|
|
39
|
-
name: 'Code Quality',
|
|
40
|
-
weight: 0.2,
|
|
41
|
-
indicators: {
|
|
42
|
-
linting: { weight: 0.25, description: 'Linting configuration' },
|
|
43
|
-
formatting: { weight: 0.2, description: 'Code formatting standards' },
|
|
44
|
-
complexity: { weight: 0.2, description: 'Cyclomatic complexity' },
|
|
45
|
-
dependencies: { weight: 0.2, description: 'Dependency management' },
|
|
46
|
-
security: { weight: 0.15, description: 'Security scanning' },
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
PROJECT_STRUCTURE: {
|
|
50
|
-
name: 'Project Structure',
|
|
51
|
-
weight: 0.15,
|
|
52
|
-
indicators: {
|
|
53
|
-
organization: { weight: 0.3, description: 'Logical file organization' },
|
|
54
|
-
buildConfig: { weight: 0.25, description: 'Build configuration' },
|
|
55
|
-
packageManagement: { weight: 0.2, description: 'Package management' },
|
|
56
|
-
ciCd: { weight: 0.15, description: 'CI/CD configuration' },
|
|
57
|
-
versionControl: { weight: 0.1, description: 'Version control setup' },
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
PROCESS_MATURITY: {
|
|
61
|
-
name: 'Process Maturity',
|
|
62
|
-
weight: 0.2,
|
|
63
|
-
indicators: {
|
|
64
|
-
branchingStrategy: { weight: 0.25, description: 'Branching and merging strategy' },
|
|
65
|
-
codeReview: { weight: 0.25, description: 'Code review process' },
|
|
66
|
-
releaseProcess: { weight: 0.2, description: 'Release management' },
|
|
67
|
-
issueTracking: { weight: 0.15, description: 'Issue tracking' },
|
|
68
|
-
teamCollaboration: { weight: 0.15, description: 'Team collaboration tools' },
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Assess a project directory for CAWS readiness
|
|
75
|
-
* @param {string} projectDir - Project directory path
|
|
76
|
-
* @returns {Object} Assessment results
|
|
77
|
-
*/
|
|
78
|
-
function assessProject(projectDir = process.cwd()) {
|
|
79
|
-
console.log(`š Assessing project for CAWS adoption: ${projectDir}`);
|
|
80
|
-
|
|
81
|
-
const assessment = {
|
|
82
|
-
projectInfo: getProjectInfo(projectDir),
|
|
83
|
-
scores: {},
|
|
84
|
-
recommendations: [],
|
|
85
|
-
adoptionRoadmap: [],
|
|
86
|
-
riskProfile: {},
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// Calculate scores for each category
|
|
90
|
-
Object.keys(ASSESSMENT_CATEGORIES).forEach((category) => {
|
|
91
|
-
assessment.scores[category] = calculateCategoryScore(projectDir, category);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Calculate overall readiness score
|
|
95
|
-
assessment.overallScore = calculateOverallScore(assessment.scores);
|
|
96
|
-
|
|
97
|
-
// Generate recommendations
|
|
98
|
-
assessment.recommendations = generateRecommendations(assessment.scores);
|
|
99
|
-
|
|
100
|
-
// Generate adoption roadmap
|
|
101
|
-
assessment.adoptionRoadmap = generateAdoptionRoadmap(assessment.scores);
|
|
102
|
-
|
|
103
|
-
// Assess risk profile
|
|
104
|
-
assessment.riskProfile = assessRiskProfile(projectDir, assessment.scores);
|
|
105
|
-
|
|
106
|
-
return assessment;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get basic project information
|
|
111
|
-
*/
|
|
112
|
-
function getProjectInfo(projectDir) {
|
|
113
|
-
const info = {
|
|
114
|
-
name: path.basename(projectDir),
|
|
115
|
-
path: projectDir,
|
|
116
|
-
languages: [],
|
|
117
|
-
packageManager: null,
|
|
118
|
-
frameworks: [],
|
|
119
|
-
size: { files: 0, lines: 0, directories: 0 },
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// Detect languages and frameworks
|
|
123
|
-
try {
|
|
124
|
-
const files = fs.readdirSync(projectDir, { recursive: true });
|
|
125
|
-
|
|
126
|
-
files.forEach((file) => {
|
|
127
|
-
const filePath = path.join(projectDir, file);
|
|
128
|
-
const stat = fs.statSync(filePath);
|
|
129
|
-
|
|
130
|
-
if (stat.isFile()) {
|
|
131
|
-
info.size.files++;
|
|
132
|
-
if (file.endsWith('.js') || file.endsWith('.ts')) {
|
|
133
|
-
info.languages.push('javascript');
|
|
134
|
-
if (file.includes('react') || file.includes('vue') || file.includes('angular')) {
|
|
135
|
-
info.frameworks.push('frontend');
|
|
136
|
-
}
|
|
137
|
-
} else if (file.endsWith('.py')) {
|
|
138
|
-
info.languages.push('python');
|
|
139
|
-
} else if (file.endsWith('.java')) {
|
|
140
|
-
info.languages.push('java');
|
|
141
|
-
} else if (file.endsWith('.go')) {
|
|
142
|
-
info.languages.push('go');
|
|
143
|
-
} else if (file.endsWith('.rs')) {
|
|
144
|
-
info.languages.push('rust');
|
|
145
|
-
}
|
|
146
|
-
} else if (stat.isDirectory()) {
|
|
147
|
-
info.size.directories++;
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Remove duplicates
|
|
152
|
-
info.languages = [...new Set(info.languages)];
|
|
153
|
-
info.frameworks = [...new Set(info.frameworks)];
|
|
154
|
-
|
|
155
|
-
// Detect package manager
|
|
156
|
-
if (fs.existsSync(path.join(projectDir, 'package.json'))) {
|
|
157
|
-
info.packageManager = 'npm';
|
|
158
|
-
} else if (fs.existsSync(path.join(projectDir, 'requirements.txt'))) {
|
|
159
|
-
info.packageManager = 'pip';
|
|
160
|
-
} else if (fs.existsSync(path.join(projectDir, 'pom.xml'))) {
|
|
161
|
-
info.packageManager = 'maven';
|
|
162
|
-
} else if (fs.existsSync(path.join(projectDir, 'go.mod'))) {
|
|
163
|
-
info.packageManager = 'go modules';
|
|
164
|
-
} else if (fs.existsSync(path.join(projectDir, 'Cargo.toml'))) {
|
|
165
|
-
info.packageManager = 'cargo';
|
|
166
|
-
}
|
|
167
|
-
} catch (error) {
|
|
168
|
-
console.warn('ā ļø Error gathering project info:', error.message);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return info;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Calculate score for a specific category
|
|
176
|
-
*/
|
|
177
|
-
function calculateCategoryScore(projectDir, category) {
|
|
178
|
-
const categoryInfo = ASSESSMENT_CATEGORIES[category];
|
|
179
|
-
let totalScore = 0;
|
|
180
|
-
|
|
181
|
-
Object.keys(categoryInfo.indicators).forEach((indicator) => {
|
|
182
|
-
const indicatorInfo = categoryInfo.indicators[indicator];
|
|
183
|
-
const score = calculateIndicatorScore(projectDir, category, indicator);
|
|
184
|
-
const weightedScore = score * indicatorInfo.weight;
|
|
185
|
-
totalScore += weightedScore;
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
return Math.round(totalScore * 100);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Calculate score for a specific indicator
|
|
193
|
-
*/
|
|
194
|
-
function calculateIndicatorScore(projectDir, category, indicator) {
|
|
195
|
-
switch (category) {
|
|
196
|
-
case 'TESTING':
|
|
197
|
-
return calculateTestingScore(projectDir, indicator);
|
|
198
|
-
case 'DOCUMENTATION':
|
|
199
|
-
return calculateDocumentationScore(projectDir, indicator);
|
|
200
|
-
case 'CODE_QUALITY':
|
|
201
|
-
return calculateCodeQualityScore(projectDir, indicator);
|
|
202
|
-
case 'PROJECT_STRUCTURE':
|
|
203
|
-
return calculateProjectStructureScore(projectDir, indicator);
|
|
204
|
-
case 'PROCESS_MATURITY':
|
|
205
|
-
return calculateProcessMaturityScore(projectDir, indicator);
|
|
206
|
-
default:
|
|
207
|
-
return 0.5; // Neutral score
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Calculate testing-related scores
|
|
213
|
-
*/
|
|
214
|
-
function calculateTestingScore(projectDir, indicator) {
|
|
215
|
-
const testDir = path.join(projectDir, 'tests') || path.join(projectDir, 'test');
|
|
216
|
-
|
|
217
|
-
switch (indicator) {
|
|
218
|
-
case 'testFiles':
|
|
219
|
-
if (fs.existsSync(testDir)) {
|
|
220
|
-
const testFiles = fs
|
|
221
|
-
.readdirSync(testDir)
|
|
222
|
-
.filter((f) => f.includes('test') || f.includes('spec'));
|
|
223
|
-
return Math.min(testFiles.length / 10, 1); // Scale based on number of test files
|
|
224
|
-
}
|
|
225
|
-
return 0;
|
|
226
|
-
|
|
227
|
-
case 'testCoverage':
|
|
228
|
-
// Check for coverage configuration
|
|
229
|
-
const coverageFiles = [
|
|
230
|
-
'.nycrc',
|
|
231
|
-
'jest.config.js',
|
|
232
|
-
'coverage.json',
|
|
233
|
-
'sonar-project.properties',
|
|
234
|
-
];
|
|
235
|
-
const hasCoverage = coverageFiles.some((file) => fs.existsSync(path.join(projectDir, file)));
|
|
236
|
-
return hasCoverage ? 1 : 0;
|
|
237
|
-
|
|
238
|
-
case 'testTypes':
|
|
239
|
-
if (fs.existsSync(testDir)) {
|
|
240
|
-
const files = fs.readdirSync(testDir);
|
|
241
|
-
let types = 0;
|
|
242
|
-
if (files.some((f) => f.includes('unit'))) types += 0.3;
|
|
243
|
-
if (files.some((f) => f.includes('integration'))) types += 0.3;
|
|
244
|
-
if (files.some((f) => f.includes('e2e'))) types += 0.4;
|
|
245
|
-
return Math.min(types, 1);
|
|
246
|
-
}
|
|
247
|
-
return 0;
|
|
248
|
-
|
|
249
|
-
case 'testQuality':
|
|
250
|
-
// Basic check for test structure
|
|
251
|
-
if (fs.existsSync(testDir)) {
|
|
252
|
-
const testContent = fs
|
|
253
|
-
.readdirSync(testDir)
|
|
254
|
-
.filter((f) => f.endsWith('.js') || f.endsWith('.ts'))
|
|
255
|
-
.map((f) => fs.readFileSync(path.join(testDir, f), 'utf8'))
|
|
256
|
-
.join('');
|
|
257
|
-
|
|
258
|
-
const hasAssertions = /\b(expect|assert|should)\b/.test(testContent);
|
|
259
|
-
const hasDescribe = /\bdescribe\b/.test(testContent);
|
|
260
|
-
return hasAssertions && hasDescribe ? 0.8 : 0.3;
|
|
261
|
-
}
|
|
262
|
-
return 0;
|
|
263
|
-
|
|
264
|
-
case 'testAutomation':
|
|
265
|
-
const hasCI =
|
|
266
|
-
fs.existsSync(path.join(projectDir, '.github/workflows')) ||
|
|
267
|
-
fs.existsSync(path.join(projectDir, '.gitlab-ci.yml')) ||
|
|
268
|
-
fs.existsSync(path.join(projectDir, 'Jenkinsfile'));
|
|
269
|
-
return hasCI ? 1 : 0;
|
|
270
|
-
|
|
271
|
-
default:
|
|
272
|
-
return 0.5;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Calculate documentation-related scores
|
|
278
|
-
*/
|
|
279
|
-
function calculateDocumentationScore(projectDir, indicator) {
|
|
280
|
-
switch (indicator) {
|
|
281
|
-
case 'readmeQuality':
|
|
282
|
-
const readmePath = path.join(projectDir, 'README.md');
|
|
283
|
-
if (fs.existsSync(readmePath)) {
|
|
284
|
-
const readme = fs.readFileSync(readmePath, 'utf8');
|
|
285
|
-
const score =
|
|
286
|
-
(readme.length > 500 ? 0.4 : 0) +
|
|
287
|
-
(readme.includes('#') ? 0.2 : 0) +
|
|
288
|
-
(readme.includes('install') || readme.includes('setup') ? 0.2 : 0) +
|
|
289
|
-
(readme.includes('usage') || readme.includes('example') ? 0.2 : 0);
|
|
290
|
-
return Math.min(score, 1);
|
|
291
|
-
}
|
|
292
|
-
return 0;
|
|
293
|
-
|
|
294
|
-
case 'codeComments':
|
|
295
|
-
// Basic check for comment density
|
|
296
|
-
const sourceFiles = getSourceFiles(projectDir);
|
|
297
|
-
if (sourceFiles.length === 0) return 0.5;
|
|
298
|
-
|
|
299
|
-
const totalLines = sourceFiles.reduce((sum, file) => {
|
|
300
|
-
return sum + fs.readFileSync(file, 'utf8').split('\n').length;
|
|
301
|
-
}, 0);
|
|
302
|
-
|
|
303
|
-
const commentLines = sourceFiles.reduce((sum, file) => {
|
|
304
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
305
|
-
return (
|
|
306
|
-
sum +
|
|
307
|
-
(content.match(/\/\//g) || []).length +
|
|
308
|
-
(content.match(/\/\*/g) || []).length +
|
|
309
|
-
(content.match(/#/g) || []).length
|
|
310
|
-
);
|
|
311
|
-
}, 0);
|
|
312
|
-
|
|
313
|
-
const commentRatio = commentLines / totalLines;
|
|
314
|
-
return Math.min(commentRatio * 5, 1); // Scale comment ratio
|
|
315
|
-
|
|
316
|
-
case 'apiDocs':
|
|
317
|
-
const hasApiDocs =
|
|
318
|
-
fs.existsSync(path.join(projectDir, 'docs/api')) ||
|
|
319
|
-
fs.existsSync(path.join(projectDir, 'docs/API.md'));
|
|
320
|
-
return hasApiDocs ? 1 : 0;
|
|
321
|
-
|
|
322
|
-
case 'architectureDocs':
|
|
323
|
-
const hasArchDocs =
|
|
324
|
-
fs.existsSync(path.join(projectDir, 'docs/architecture')) ||
|
|
325
|
-
fs.existsSync(path.join(projectDir, 'ARCHITECTURE.md'));
|
|
326
|
-
return hasArchDocs ? 1 : 0;
|
|
327
|
-
|
|
328
|
-
case 'changelog':
|
|
329
|
-
const hasChangelog =
|
|
330
|
-
fs.existsSync(path.join(projectDir, 'CHANGELOG.md')) ||
|
|
331
|
-
fs.existsSync(path.join(projectDir, 'HISTORY.md'));
|
|
332
|
-
return hasChangelog ? 1 : 0;
|
|
333
|
-
|
|
334
|
-
default:
|
|
335
|
-
return 0.5;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Calculate code quality scores
|
|
341
|
-
*/
|
|
342
|
-
function calculateCodeQualityScore(projectDir, indicator) {
|
|
343
|
-
switch (indicator) {
|
|
344
|
-
case 'linting':
|
|
345
|
-
const lintingFiles = ['.eslintrc', '.prettierrc', 'tsconfig.json', '.editorconfig'];
|
|
346
|
-
const hasLinting = lintingFiles.some((file) => fs.existsSync(path.join(projectDir, file)));
|
|
347
|
-
return hasLinting ? 1 : 0;
|
|
348
|
-
|
|
349
|
-
case 'formatting':
|
|
350
|
-
// Check if formatting tools are likely configured
|
|
351
|
-
return 0.7; // Assume some formatting exists
|
|
352
|
-
|
|
353
|
-
case 'complexity':
|
|
354
|
-
// Basic complexity check (could be enhanced with actual analysis)
|
|
355
|
-
return 0.6; // Neutral score
|
|
356
|
-
|
|
357
|
-
case 'dependencies':
|
|
358
|
-
const hasPackageFile =
|
|
359
|
-
fs.existsSync(path.join(projectDir, 'package.json')) ||
|
|
360
|
-
fs.existsSync(path.join(projectDir, 'requirements.txt')) ||
|
|
361
|
-
fs.existsSync(path.join(projectDir, 'pom.xml'));
|
|
362
|
-
return hasPackageFile ? 1 : 0;
|
|
363
|
-
|
|
364
|
-
case 'security':
|
|
365
|
-
const hasSecurity =
|
|
366
|
-
fs.existsSync(path.join(projectDir, '.github/workflows')) &&
|
|
367
|
-
fs
|
|
368
|
-
.readdirSync(path.join(projectDir, '.github/workflows'))
|
|
369
|
-
.some((f) => f.includes('security') || f.includes('audit'));
|
|
370
|
-
return hasSecurity ? 1 : 0;
|
|
371
|
-
|
|
372
|
-
default:
|
|
373
|
-
return 0.5;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Calculate project structure scores
|
|
379
|
-
*/
|
|
380
|
-
function calculateProjectStructureScore(projectDir, indicator) {
|
|
381
|
-
switch (indicator) {
|
|
382
|
-
case 'organization':
|
|
383
|
-
const srcDir =
|
|
384
|
-
fs.existsSync(path.join(projectDir, 'src')) || fs.existsSync(path.join(projectDir, 'lib'));
|
|
385
|
-
return srcDir ? 1 : 0.5;
|
|
386
|
-
|
|
387
|
-
case 'buildConfig':
|
|
388
|
-
const buildFiles = ['package.json', 'Makefile', 'CMakeLists.txt', 'build.gradle'];
|
|
389
|
-
const hasBuild = buildFiles.some((file) => fs.existsSync(path.join(projectDir, file)));
|
|
390
|
-
return hasBuild ? 1 : 0;
|
|
391
|
-
|
|
392
|
-
case 'packageManagement':
|
|
393
|
-
const packageFiles = ['package.json', 'requirements.txt', 'pom.xml', 'go.mod', 'Cargo.toml'];
|
|
394
|
-
const hasPackage = packageFiles.some((file) => fs.existsSync(path.join(projectDir, file)));
|
|
395
|
-
return hasPackage ? 1 : 0;
|
|
396
|
-
|
|
397
|
-
case 'ciCd':
|
|
398
|
-
const hasCI =
|
|
399
|
-
fs.existsSync(path.join(projectDir, '.github')) ||
|
|
400
|
-
fs.existsSync(path.join(projectDir, '.gitlab-ci.yml'));
|
|
401
|
-
return hasCI ? 1 : 0;
|
|
402
|
-
|
|
403
|
-
case 'versionControl':
|
|
404
|
-
// Check for .git directory (basic check)
|
|
405
|
-
return fs.existsSync(path.join(projectDir, '.git')) ? 1 : 0;
|
|
406
|
-
|
|
407
|
-
default:
|
|
408
|
-
return 0.5;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Calculate process maturity scores
|
|
414
|
-
*/
|
|
415
|
-
function calculateProcessMaturityScore(projectDir, indicator) {
|
|
416
|
-
switch (indicator) {
|
|
417
|
-
case 'branchingStrategy':
|
|
418
|
-
// Check for branch protection or common branching files
|
|
419
|
-
const hasBranches =
|
|
420
|
-
fs.existsSync(path.join(projectDir, '.github/workflows')) &&
|
|
421
|
-
fs
|
|
422
|
-
.readdirSync(path.join(projectDir, '.github/workflows'))
|
|
423
|
-
.some((f) => f.includes('branch') || f.includes('merge'));
|
|
424
|
-
return hasBranches ? 0.8 : 0.3;
|
|
425
|
-
|
|
426
|
-
case 'codeReview':
|
|
427
|
-
const hasPRTemplate = fs.existsSync(
|
|
428
|
-
path.join(projectDir, '.github/PULL_REQUEST_TEMPLATE.md')
|
|
429
|
-
);
|
|
430
|
-
return hasPRTemplate ? 0.9 : 0.4;
|
|
431
|
-
|
|
432
|
-
case 'releaseProcess':
|
|
433
|
-
const hasReleaseWorkflow =
|
|
434
|
-
fs.existsSync(path.join(projectDir, '.github/workflows')) &&
|
|
435
|
-
fs
|
|
436
|
-
.readdirSync(path.join(projectDir, '.github/workflows'))
|
|
437
|
-
.some((f) => f.includes('release') || f.includes('publish'));
|
|
438
|
-
return hasReleaseWorkflow ? 1 : 0;
|
|
439
|
-
|
|
440
|
-
case 'issueTracking':
|
|
441
|
-
const hasIssueTemplate = fs.existsSync(path.join(projectDir, '.github/ISSUE_TEMPLATE'));
|
|
442
|
-
return hasIssueTemplate ? 0.8 : 0.2;
|
|
443
|
-
|
|
444
|
-
case 'teamCollaboration':
|
|
445
|
-
const hasContributing = fs.existsSync(path.join(projectDir, 'CONTRIBUTING.md'));
|
|
446
|
-
return hasContributing ? 0.7 : 0.3;
|
|
447
|
-
|
|
448
|
-
default:
|
|
449
|
-
return 0.5;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Get source files for analysis
|
|
455
|
-
*/
|
|
456
|
-
function getSourceFiles(projectDir) {
|
|
457
|
-
const sourceFiles = [];
|
|
458
|
-
|
|
459
|
-
function scanDirectory(dir) {
|
|
460
|
-
try {
|
|
461
|
-
const files = fs.readdirSync(dir);
|
|
462
|
-
|
|
463
|
-
files.forEach((file) => {
|
|
464
|
-
const filePath = path.join(dir, file);
|
|
465
|
-
const stat = fs.statSync(filePath);
|
|
466
|
-
|
|
467
|
-
if (
|
|
468
|
-
stat.isDirectory() &&
|
|
469
|
-
!file.startsWith('.') &&
|
|
470
|
-
file !== 'node_modules' &&
|
|
471
|
-
file !== 'target'
|
|
472
|
-
) {
|
|
473
|
-
scanDirectory(filePath);
|
|
474
|
-
} else if (stat.isFile() && /\.(js|ts|py|java|go|rs)$/.test(file)) {
|
|
475
|
-
sourceFiles.push(filePath);
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
} catch (error) {
|
|
479
|
-
// Skip directories we can't read
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
scanDirectory(projectDir);
|
|
484
|
-
return sourceFiles;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Calculate overall readiness score
|
|
489
|
-
*/
|
|
490
|
-
function calculateOverallScore(categoryScores) {
|
|
491
|
-
let totalScore = 0;
|
|
492
|
-
|
|
493
|
-
Object.keys(categoryScores).forEach((category) => {
|
|
494
|
-
totalScore += categoryScores[category] * ASSESSMENT_CATEGORIES[category].weight;
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
return Math.round(totalScore);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* Generate recommendations based on scores
|
|
502
|
-
*/
|
|
503
|
-
function generateRecommendations(scores) {
|
|
504
|
-
const recommendations = [];
|
|
505
|
-
|
|
506
|
-
Object.keys(scores).forEach((category) => {
|
|
507
|
-
const score = scores[category];
|
|
508
|
-
const categoryInfo = ASSESSMENT_CATEGORIES[category];
|
|
509
|
-
|
|
510
|
-
if (score < 40) {
|
|
511
|
-
recommendations.push({
|
|
512
|
-
category: categoryInfo.name,
|
|
513
|
-
priority: 'high',
|
|
514
|
-
message: `${categoryInfo.name} needs significant improvement (Score: ${score}/100)`,
|
|
515
|
-
suggestions: getCategorySuggestions(category, 'high'),
|
|
516
|
-
});
|
|
517
|
-
} else if (score < 70) {
|
|
518
|
-
recommendations.push({
|
|
519
|
-
category: categoryInfo.name,
|
|
520
|
-
priority: 'medium',
|
|
521
|
-
message: `${categoryInfo.name} has room for improvement (Score: ${score}/100)`,
|
|
522
|
-
suggestions: getCategorySuggestions(category, 'medium'),
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
return recommendations;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Get category-specific suggestions
|
|
532
|
-
*/
|
|
533
|
-
function getCategorySuggestions(category, priority) {
|
|
534
|
-
const suggestions = {
|
|
535
|
-
TESTING: {
|
|
536
|
-
high: [
|
|
537
|
-
'Add comprehensive unit test suite',
|
|
538
|
-
'Implement integration tests for critical paths',
|
|
539
|
-
'Set up test automation in CI/CD',
|
|
540
|
-
'Add test coverage reporting',
|
|
541
|
-
],
|
|
542
|
-
medium: [
|
|
543
|
-
'Improve test coverage for existing code',
|
|
544
|
-
'Add more edge case testing',
|
|
545
|
-
'Implement automated test execution',
|
|
546
|
-
],
|
|
547
|
-
},
|
|
548
|
-
DOCUMENTATION: {
|
|
549
|
-
high: [
|
|
550
|
-
'Create comprehensive README with setup instructions',
|
|
551
|
-
'Add inline code documentation',
|
|
552
|
-
'Document API endpoints and usage',
|
|
553
|
-
'Create architecture documentation',
|
|
554
|
-
],
|
|
555
|
-
medium: ['Enhance existing documentation', 'Add code examples', 'Document recent changes'],
|
|
556
|
-
},
|
|
557
|
-
CODE_QUALITY: {
|
|
558
|
-
high: [
|
|
559
|
-
'Set up linting and code formatting',
|
|
560
|
-
'Implement code quality gates',
|
|
561
|
-
'Add security scanning',
|
|
562
|
-
'Manage technical debt',
|
|
563
|
-
],
|
|
564
|
-
medium: [
|
|
565
|
-
'Improve code formatting consistency',
|
|
566
|
-
'Add static code analysis',
|
|
567
|
-
'Update dependencies regularly',
|
|
568
|
-
],
|
|
569
|
-
},
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
return suggestions[category]?.[priority] || ['Improve this area'];
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* Generate phased adoption roadmap
|
|
577
|
-
*/
|
|
578
|
-
function generateAdoptionRoadmap(scores) {
|
|
579
|
-
const phases = [
|
|
580
|
-
{
|
|
581
|
-
name: 'Foundation Setup',
|
|
582
|
-
duration: '2-4 weeks',
|
|
583
|
-
prerequisites: ['Basic project structure', 'Development environment'],
|
|
584
|
-
tasks: [
|
|
585
|
-
'Set up CAWS CLI and basic configuration',
|
|
586
|
-
'Create initial working spec template',
|
|
587
|
-
'Establish basic linting and formatting',
|
|
588
|
-
'Set up version control and CI basics',
|
|
589
|
-
],
|
|
590
|
-
success_criteria: 'CAWS CLI operational, basic quality gates in place',
|
|
591
|
-
},
|
|
592
|
-
{
|
|
593
|
-
name: 'Testing Infrastructure',
|
|
594
|
-
duration: '4-6 weeks',
|
|
595
|
-
prerequisites: ['Foundation setup complete'],
|
|
596
|
-
tasks: [
|
|
597
|
-
'Implement comprehensive test suite',
|
|
598
|
-
'Add test coverage reporting',
|
|
599
|
-
'Set up mutation testing',
|
|
600
|
-
'Integrate tests into CI/CD',
|
|
601
|
-
],
|
|
602
|
-
success_criteria: 'Test coverage >70%, automated testing operational',
|
|
603
|
-
},
|
|
604
|
-
{
|
|
605
|
-
name: 'Documentation & Quality',
|
|
606
|
-
duration: '3-5 weeks',
|
|
607
|
-
prerequisites: ['Testing infrastructure complete'],
|
|
608
|
-
tasks: [
|
|
609
|
-
'Complete project documentation',
|
|
610
|
-
'Implement advanced quality gates',
|
|
611
|
-
'Add contract testing',
|
|
612
|
-
'Set up monitoring and observability',
|
|
613
|
-
],
|
|
614
|
-
success_criteria: 'Full CAWS compliance, comprehensive documentation',
|
|
615
|
-
},
|
|
616
|
-
];
|
|
617
|
-
|
|
618
|
-
// Adjust phases based on current scores
|
|
619
|
-
return phases.map((phase, index) => ({
|
|
620
|
-
...phase,
|
|
621
|
-
priority: index + 1,
|
|
622
|
-
estimated_effort: scores[Object.keys(scores)[index]] < 50 ? 'high' : 'medium',
|
|
623
|
-
}));
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* Assess risk profile of the project
|
|
628
|
-
*/
|
|
629
|
-
function assessRiskProfile(projectDir, scores) {
|
|
630
|
-
const profile = {
|
|
631
|
-
overall_risk: 'medium',
|
|
632
|
-
risk_factors: [],
|
|
633
|
-
recommended_tier: 2,
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
// Determine risk based on scores
|
|
637
|
-
const avgScore = Object.values(scores).reduce((a, b) => a + b, 0) / Object.keys(scores).length;
|
|
638
|
-
|
|
639
|
-
if (avgScore < 40) {
|
|
640
|
-
profile.overall_risk = 'high';
|
|
641
|
-
profile.risk_factors.push('Low test coverage and documentation');
|
|
642
|
-
profile.risk_factors.push('Missing quality gates');
|
|
643
|
-
profile.recommended_tier = 1;
|
|
644
|
-
} else if (avgScore < 70) {
|
|
645
|
-
profile.overall_risk = 'medium';
|
|
646
|
-
profile.risk_factors.push('Inconsistent testing practices');
|
|
647
|
-
profile.risk_factors.push('Documentation gaps');
|
|
648
|
-
profile.recommended_tier = 2;
|
|
649
|
-
} else {
|
|
650
|
-
profile.overall_risk = 'low';
|
|
651
|
-
profile.risk_factors.push('Good foundation for quality practices');
|
|
652
|
-
profile.recommended_tier = 3;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Add specific risk factors
|
|
656
|
-
if (scores.TESTING < 30) {
|
|
657
|
-
profile.risk_factors.push('Critical: Minimal testing infrastructure');
|
|
658
|
-
}
|
|
659
|
-
if (scores.DOCUMENTATION < 30) {
|
|
660
|
-
profile.risk_factors.push('High: Poor documentation quality');
|
|
661
|
-
}
|
|
662
|
-
if (scores.CODE_QUALITY < 40) {
|
|
663
|
-
profile.risk_factors.push('Medium: Code quality needs improvement');
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
return profile;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// CLI interface
|
|
670
|
-
if (require.main === module) {
|
|
671
|
-
const command = process.argv[2];
|
|
672
|
-
const projectDir = process.argv[3] || process.cwd();
|
|
673
|
-
|
|
674
|
-
switch (command) {
|
|
675
|
-
case 'assess':
|
|
676
|
-
console.log(`š Assessing CAWS readiness for project: ${projectDir}`);
|
|
677
|
-
|
|
678
|
-
const assessment = assessProject(projectDir);
|
|
679
|
-
|
|
680
|
-
console.log('\nš CAWS Readiness Assessment Results:');
|
|
681
|
-
console.log(`š Overall Score: ${assessment.overallScore}/100`);
|
|
682
|
-
|
|
683
|
-
console.log('\nš Category Scores:');
|
|
684
|
-
Object.keys(assessment.scores).forEach((category) => {
|
|
685
|
-
const score = assessment.scores[category];
|
|
686
|
-
const categoryInfo = ASSESSMENT_CATEGORIES[category];
|
|
687
|
-
const status =
|
|
688
|
-
score >= 80
|
|
689
|
-
? 'ā
Excellent'
|
|
690
|
-
: score >= 60
|
|
691
|
-
? 'š¢ Good'
|
|
692
|
-
: score >= 40
|
|
693
|
-
? 'š” Fair'
|
|
694
|
-
: 'š“ Poor';
|
|
695
|
-
console.log(`${status} ${categoryInfo.name}: ${score}/100`);
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
console.log('\nš Project Information:');
|
|
699
|
-
console.log(` Languages: ${assessment.projectInfo.languages.join(', ')}`);
|
|
700
|
-
console.log(` Package Manager: ${assessment.projectInfo.packageManager || 'Not detected'}`);
|
|
701
|
-
console.log(
|
|
702
|
-
` Frameworks: ${assessment.projectInfo.frameworks.join(', ') || 'None detected'}`
|
|
703
|
-
);
|
|
704
|
-
console.log(
|
|
705
|
-
` Project Size: ${assessment.projectInfo.size.files} files, ${assessment.projectInfo.size.directories} directories`
|
|
706
|
-
);
|
|
707
|
-
|
|
708
|
-
if (assessment.recommendations.length > 0) {
|
|
709
|
-
console.log('\nš” Priority Recommendations:');
|
|
710
|
-
assessment.recommendations.forEach((rec, index) => {
|
|
711
|
-
console.log(`\n${index + 1}. [${rec.priority.toUpperCase()}] ${rec.category}`);
|
|
712
|
-
console.log(` ${rec.message}`);
|
|
713
|
-
rec.suggestions.forEach((suggestion) => {
|
|
714
|
-
console.log(` ⢠${suggestion}`);
|
|
715
|
-
});
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
console.log('\nšļø Adoption Roadmap:');
|
|
720
|
-
assessment.adoptionRoadmap.forEach((phase, index) => {
|
|
721
|
-
console.log(`\n${index + 1}. ${phase.name} (${phase.duration})`);
|
|
722
|
-
console.log(` Priority: ${phase.priority} | Effort: ${phase.estimated_effort}`);
|
|
723
|
-
console.log(` Tasks: ${phase.tasks.join(', ')}`);
|
|
724
|
-
console.log(` Success: ${phase.success_criteria}`);
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
console.log('\nā ļø Risk Profile:');
|
|
728
|
-
console.log(` Overall Risk: ${assessment.riskProfile.overall_risk}`);
|
|
729
|
-
console.log(` Recommended Tier: ${assessment.riskProfile.recommended_tier}`);
|
|
730
|
-
assessment.riskProfile.risk_factors.forEach((factor) => {
|
|
731
|
-
console.log(` ⢠${factor}`);
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
console.log('\nšÆ Next Steps:');
|
|
735
|
-
console.log(' 1. Start with Foundation setup tasks');
|
|
736
|
-
console.log(' 2. Address high-priority recommendations first');
|
|
737
|
-
console.log(' 3. Set up CAWS CLI and create initial working spec');
|
|
738
|
-
console.log(' 4. Gradually implement testing and quality improvements');
|
|
739
|
-
|
|
740
|
-
break;
|
|
741
|
-
|
|
742
|
-
default:
|
|
743
|
-
console.log('CAWS Legacy Codebase Assessment Tool');
|
|
744
|
-
console.log('Usage:');
|
|
745
|
-
console.log(' node legacy-assessor.js assess [project-directory]');
|
|
746
|
-
console.log('');
|
|
747
|
-
console.log('Assessment Categories:');
|
|
748
|
-
Object.values(ASSESSMENT_CATEGORIES).forEach((category) => {
|
|
749
|
-
console.log(` - ${category.name} (${Math.round(category.weight * 100)}% weight)`);
|
|
750
|
-
});
|
|
751
|
-
console.log('');
|
|
752
|
-
console.log('Examples:');
|
|
753
|
-
console.log(' node legacy-assessor.js assess .');
|
|
754
|
-
console.log(' node legacy-assessor.js assess /path/to/project');
|
|
755
|
-
process.exit(1);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
module.exports = {
|
|
760
|
-
assessProject,
|
|
761
|
-
ASSESSMENT_CATEGORIES,
|
|
762
|
-
generateRecommendations,
|
|
763
|
-
generateAdoptionRoadmap,
|
|
764
|
-
};
|