@paths.design/caws-cli 3.3.1 → 3.4.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.
- package/dist/commands/diagnose.d.ts.map +1 -1
- package/dist/commands/diagnose.js +39 -4
- package/dist/commands/evaluate.d.ts +8 -0
- package/dist/commands/evaluate.d.ts.map +1 -0
- package/dist/commands/evaluate.js +288 -0
- package/dist/commands/iterate.d.ts +8 -0
- package/dist/commands/iterate.d.ts.map +1 -0
- package/dist/commands/iterate.js +341 -0
- package/dist/commands/quality-monitor.d.ts +17 -0
- package/dist/commands/quality-monitor.d.ts.map +1 -0
- package/dist/commands/quality-monitor.js +265 -0
- package/dist/commands/status.d.ts +6 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +120 -20
- package/dist/commands/troubleshoot.d.ts +8 -0
- package/dist/commands/troubleshoot.d.ts.map +1 -0
- package/dist/commands/troubleshoot.js +104 -0
- package/dist/commands/waivers.d.ts +8 -0
- package/dist/commands/waivers.d.ts.map +1 -0
- package/dist/commands/waivers.js +293 -0
- package/dist/commands/workflow.d.ts +85 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +243 -0
- package/dist/error-handler.d.ts +91 -2
- package/dist/error-handler.d.ts.map +1 -1
- package/dist/error-handler.js +362 -16
- package/dist/index.js +95 -0
- package/dist/utils/typescript-detector.d.ts +31 -0
- package/dist/utils/typescript-detector.d.ts.map +1 -1
- package/dist/utils/typescript-detector.js +245 -7
- package/package.json +2 -1
- package/templates/apps/tools/caws/gates.ts +34 -0
- package/templates/apps/tools/caws/shared/gate-checker.ts +265 -13
- package/templates/apps/tools/caws/templates/working-spec.template.yml +14 -0
- package/dist/index-new.d.ts +0 -5
- package/dist/index-new.d.ts.map +0 -1
- package/dist/index-new.js +0 -317
- package/dist/index.js.backup +0 -4711
- package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
- package/templates/apps/tools/caws/provenance.js.backup +0 -73
|
@@ -100,24 +100,257 @@ function detectTestFramework(projectDir = process.cwd(), packageJson = null) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Get workspace directories from package.json
|
|
105
|
+
* @param {string} projectDir - Project directory path
|
|
106
|
+
* @returns {string[]} Array of workspace directories
|
|
107
|
+
*/
|
|
108
|
+
/**
|
|
109
|
+
* Get workspace directories from npm/yarn package.json workspaces
|
|
110
|
+
* @param {string} projectDir - Project directory path
|
|
111
|
+
* @returns {string[]} Array of workspace directories
|
|
112
|
+
*/
|
|
113
|
+
function getNpmWorkspaces(projectDir) {
|
|
114
|
+
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
115
|
+
|
|
116
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
122
|
+
const workspaces = packageJson.workspaces || [];
|
|
123
|
+
|
|
124
|
+
// Convert glob patterns to actual directories (simple implementation)
|
|
125
|
+
const workspaceDirs = [];
|
|
126
|
+
for (const ws of workspaces) {
|
|
127
|
+
// Handle simple patterns like "packages/*" or "iterations/*"
|
|
128
|
+
if (ws.includes('*')) {
|
|
129
|
+
const baseDir = ws.split('*')[0];
|
|
130
|
+
const fullBaseDir = path.join(projectDir, baseDir);
|
|
131
|
+
|
|
132
|
+
if (fs.existsSync(fullBaseDir)) {
|
|
133
|
+
const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
const wsPath = path.join(fullBaseDir, entry.name);
|
|
137
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
138
|
+
workspaceDirs.push(wsPath);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
// Direct path
|
|
145
|
+
const wsPath = path.join(projectDir, ws);
|
|
146
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
147
|
+
workspaceDirs.push(wsPath);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return workspaceDirs;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get workspace directories from pnpm-workspace.yaml
|
|
160
|
+
* @param {string} projectDir - Project directory path
|
|
161
|
+
* @returns {string[]} Array of workspace directories
|
|
162
|
+
*/
|
|
163
|
+
function getPnpmWorkspaces(projectDir) {
|
|
164
|
+
const pnpmFile = path.join(projectDir, 'pnpm-workspace.yaml');
|
|
165
|
+
|
|
166
|
+
if (!fs.existsSync(pnpmFile)) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const yaml = require('js-yaml');
|
|
172
|
+
const config = yaml.load(fs.readFileSync(pnpmFile, 'utf8'));
|
|
173
|
+
const workspacePatterns = config.packages || [];
|
|
174
|
+
|
|
175
|
+
// Convert glob patterns to actual directories
|
|
176
|
+
const workspaceDirs = [];
|
|
177
|
+
for (const pattern of workspacePatterns) {
|
|
178
|
+
if (pattern.includes('*')) {
|
|
179
|
+
const baseDir = pattern.split('*')[0];
|
|
180
|
+
const fullBaseDir = path.join(projectDir, baseDir);
|
|
181
|
+
|
|
182
|
+
if (fs.existsSync(fullBaseDir)) {
|
|
183
|
+
const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
if (entry.isDirectory()) {
|
|
186
|
+
const wsPath = path.join(fullBaseDir, entry.name);
|
|
187
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
188
|
+
workspaceDirs.push(wsPath);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
// Direct path
|
|
195
|
+
const wsPath = path.join(projectDir, pattern);
|
|
196
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
197
|
+
workspaceDirs.push(wsPath);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return workspaceDirs;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get workspace directories from lerna.json
|
|
210
|
+
* @param {string} projectDir - Project directory path
|
|
211
|
+
* @returns {string[]} Array of workspace directories
|
|
212
|
+
*/
|
|
213
|
+
function getLernaWorkspaces(projectDir) {
|
|
214
|
+
const lernaFile = path.join(projectDir, 'lerna.json');
|
|
215
|
+
|
|
216
|
+
if (!fs.existsSync(lernaFile)) {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const config = JSON.parse(fs.readFileSync(lernaFile, 'utf8'));
|
|
222
|
+
const workspacePatterns = config.packages || ['packages/*'];
|
|
223
|
+
|
|
224
|
+
// Convert glob patterns to actual directories
|
|
225
|
+
const workspaceDirs = [];
|
|
226
|
+
for (const pattern of workspacePatterns) {
|
|
227
|
+
if (pattern.includes('*')) {
|
|
228
|
+
const baseDir = pattern.split('*')[0];
|
|
229
|
+
const fullBaseDir = path.join(projectDir, baseDir);
|
|
230
|
+
|
|
231
|
+
if (fs.existsSync(fullBaseDir)) {
|
|
232
|
+
const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
|
|
233
|
+
for (const entry of entries) {
|
|
234
|
+
if (entry.isDirectory()) {
|
|
235
|
+
const wsPath = path.join(fullBaseDir, entry.name);
|
|
236
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
237
|
+
workspaceDirs.push(wsPath);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
// Direct path
|
|
244
|
+
const wsPath = path.join(projectDir, pattern);
|
|
245
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
246
|
+
workspaceDirs.push(wsPath);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return workspaceDirs;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Check if a dependency exists in hoisted node_modules
|
|
259
|
+
* @param {string} depName - Dependency name to check
|
|
260
|
+
* @param {string} projectDir - Project directory path
|
|
261
|
+
* @returns {boolean} True if dependency found in hoisted node_modules
|
|
262
|
+
*/
|
|
263
|
+
function checkHoistedDependency(depName, projectDir) {
|
|
264
|
+
const hoistedPath = path.join(projectDir, 'node_modules', depName, 'package.json');
|
|
265
|
+
return fs.existsSync(hoistedPath);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function getWorkspaceDirectories(projectDir = process.cwd()) {
|
|
269
|
+
const workspaceDirs = [
|
|
270
|
+
...getNpmWorkspaces(projectDir),
|
|
271
|
+
...getPnpmWorkspaces(projectDir),
|
|
272
|
+
...getLernaWorkspaces(projectDir),
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
// Remove duplicates
|
|
276
|
+
return [...new Set(workspaceDirs)];
|
|
277
|
+
}
|
|
278
|
+
|
|
103
279
|
/**
|
|
104
280
|
* Check if TypeScript project needs test configuration
|
|
105
281
|
* @param {string} projectDir - Project directory path
|
|
106
282
|
* @returns {Object} Configuration status
|
|
107
283
|
*/
|
|
108
284
|
function checkTypeScriptTestConfig(projectDir = process.cwd()) {
|
|
109
|
-
|
|
110
|
-
const
|
|
285
|
+
// First check root directory
|
|
286
|
+
const rootTsDetection = detectTypeScript(projectDir);
|
|
287
|
+
const rootTestDetection = detectTestFramework(projectDir, rootTsDetection.packageJson);
|
|
288
|
+
|
|
289
|
+
// Get workspace directories and check them too
|
|
290
|
+
const workspaceDirs = getWorkspaceDirectories(projectDir);
|
|
291
|
+
const workspaceResults = [];
|
|
292
|
+
|
|
293
|
+
for (const wsDir of workspaceDirs) {
|
|
294
|
+
const wsTsDetection = detectTypeScript(wsDir);
|
|
295
|
+
const wsTestDetection = detectTestFramework(wsDir, wsTsDetection.packageJson);
|
|
296
|
+
|
|
297
|
+
workspaceResults.push({
|
|
298
|
+
directory: path.relative(projectDir, wsDir),
|
|
299
|
+
tsDetection: wsTsDetection,
|
|
300
|
+
testDetection: wsTestDetection,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Determine overall status - prefer workspace results if they exist
|
|
305
|
+
let primaryTsDetection = rootTsDetection;
|
|
306
|
+
let primaryTestDetection = rootTestDetection;
|
|
307
|
+
let primaryWorkspace = null;
|
|
308
|
+
|
|
309
|
+
// Find the workspace with the most complete TypeScript setup
|
|
310
|
+
for (const wsResult of workspaceResults) {
|
|
311
|
+
if (wsResult.tsDetection.isTypeScript) {
|
|
312
|
+
if (
|
|
313
|
+
!primaryTsDetection.isTypeScript ||
|
|
314
|
+
(wsResult.tsDetection.hasTsConfig && !primaryTsDetection.hasTsConfig) ||
|
|
315
|
+
(wsResult.testDetection.framework !== 'none' && primaryTestDetection.framework === 'none')
|
|
316
|
+
) {
|
|
317
|
+
primaryTsDetection = wsResult.tsDetection;
|
|
318
|
+
primaryTestDetection = wsResult.testDetection;
|
|
319
|
+
primaryWorkspace = wsResult.directory;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check for ts-jest in workspaces and hoisted node_modules
|
|
325
|
+
let hasTsJestAnywhere = primaryTestDetection.hasTsJest;
|
|
326
|
+
|
|
327
|
+
// If not found in primary workspace, check all workspaces
|
|
328
|
+
if (!hasTsJestAnywhere) {
|
|
329
|
+
hasTsJestAnywhere = workspaceResults.some((ws) => ws.testDetection.hasTsJest);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// If still not found, check hoisted node_modules
|
|
333
|
+
if (!hasTsJestAnywhere) {
|
|
334
|
+
hasTsJestAnywhere = checkHoistedDependency('ts-jest', projectDir);
|
|
335
|
+
}
|
|
111
336
|
|
|
112
337
|
const needsConfig =
|
|
113
|
-
|
|
338
|
+
primaryTsDetection.isTypeScript &&
|
|
339
|
+
primaryTestDetection.framework === 'jest' &&
|
|
340
|
+
!hasTsJestAnywhere;
|
|
114
341
|
|
|
115
342
|
return {
|
|
116
|
-
...
|
|
117
|
-
testFramework:
|
|
118
|
-
needsJestConfig:
|
|
343
|
+
...primaryTsDetection,
|
|
344
|
+
testFramework: primaryTestDetection,
|
|
345
|
+
needsJestConfig: primaryTsDetection.isTypeScript && !primaryTestDetection.isConfigured,
|
|
119
346
|
needsTsJest: needsConfig,
|
|
120
|
-
recommendations: generateRecommendations(
|
|
347
|
+
recommendations: generateRecommendations(primaryTsDetection, primaryTestDetection),
|
|
348
|
+
workspaceInfo: {
|
|
349
|
+
hasWorkspaces: workspaceDirs.length > 0,
|
|
350
|
+
workspaceCount: workspaceDirs.length,
|
|
351
|
+
primaryWorkspace,
|
|
352
|
+
allWorkspaces: workspaceResults.map((ws) => ws.directory),
|
|
353
|
+
},
|
|
121
354
|
};
|
|
122
355
|
}
|
|
123
356
|
|
|
@@ -179,6 +412,11 @@ function displayTypeScriptDetection(detection) {
|
|
|
179
412
|
module.exports = {
|
|
180
413
|
detectTypeScript,
|
|
181
414
|
detectTestFramework,
|
|
415
|
+
getWorkspaceDirectories,
|
|
416
|
+
getNpmWorkspaces,
|
|
417
|
+
getPnpmWorkspaces,
|
|
418
|
+
getLernaWorkspaces,
|
|
419
|
+
checkHoistedDependency,
|
|
182
420
|
checkTypeScriptTestConfig,
|
|
183
421
|
generateRecommendations,
|
|
184
422
|
displayTypeScriptDetection,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paths.design/caws-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "CAWS CLI - Coding Agent Workflow System command line tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"@types/inquirer": "^8.2.6",
|
|
69
69
|
"@types/js-yaml": "^4.0.0",
|
|
70
70
|
"@types/node": "^20.0.0",
|
|
71
|
+
"esbuild": "0.25.10",
|
|
71
72
|
"eslint": "^9.0.0",
|
|
72
73
|
"jest": "30.1.3",
|
|
73
74
|
"lint-staged": "15.5.2",
|
|
@@ -87,6 +87,16 @@ class GatesCLI {
|
|
|
87
87
|
if (result.errors && result.errors.length > 0) {
|
|
88
88
|
result.errors.forEach((error) => console.error(` - ${error}`));
|
|
89
89
|
}
|
|
90
|
+
if (result.details?.searched_paths) {
|
|
91
|
+
console.error(` Searched paths: ${result.details.searched_paths.join(', ')}`);
|
|
92
|
+
}
|
|
93
|
+
if (result.details?.run_command) {
|
|
94
|
+
console.error(` Run: ${result.details.run_command}`);
|
|
95
|
+
}
|
|
96
|
+
if (result.details?.waiver_available) {
|
|
97
|
+
console.error(` 💡 ${result.details.waiver_suggestion}`);
|
|
98
|
+
console.error(` ${result.details.waiver_command}`);
|
|
99
|
+
}
|
|
90
100
|
return false;
|
|
91
101
|
}
|
|
92
102
|
} catch (error) {
|
|
@@ -118,6 +128,18 @@ class GatesCLI {
|
|
|
118
128
|
if (result.errors && result.errors.length > 0) {
|
|
119
129
|
result.errors.forEach((error) => console.error(` - ${error}`));
|
|
120
130
|
}
|
|
131
|
+
if (result.details?.searched_paths) {
|
|
132
|
+
console.error(` Searched paths: ${result.details.searched_paths.join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
if (result.details?.run_command) {
|
|
135
|
+
console.error(` Run: ${result.details.run_command}`);
|
|
136
|
+
}
|
|
137
|
+
if (result.details?.waiver_available) {
|
|
138
|
+
console.error(` 💡 ${result.details.waiver_suggestion}`);
|
|
139
|
+
console.error(
|
|
140
|
+
` caws waivers create --title="Mutation waiver" --reason=emergency_hotfix --gates=mutation`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
121
143
|
return false;
|
|
122
144
|
}
|
|
123
145
|
} catch (error) {
|
|
@@ -144,6 +166,18 @@ class GatesCLI {
|
|
|
144
166
|
if (result.errors && result.errors.length > 0) {
|
|
145
167
|
result.errors.forEach((error) => console.error(` - ${error}`));
|
|
146
168
|
}
|
|
169
|
+
if (result.details?.searched_paths) {
|
|
170
|
+
console.error(` Searched paths: ${result.details.searched_paths.join(', ')}`);
|
|
171
|
+
}
|
|
172
|
+
if (result.details?.example_command) {
|
|
173
|
+
console.error(` Example: ${result.details.example_command}`);
|
|
174
|
+
}
|
|
175
|
+
console.error(
|
|
176
|
+
` 💡 If contracts are not required for this tier, consider creating a waiver`
|
|
177
|
+
);
|
|
178
|
+
console.error(
|
|
179
|
+
` caws waivers create --title="Contract waiver" --reason=experimental_feature --gates=contracts`
|
|
180
|
+
);
|
|
147
181
|
return false;
|
|
148
182
|
}
|
|
149
183
|
} catch (error) {
|
|
@@ -60,6 +60,146 @@ export class CawsGateChecker extends CawsBaseTool {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Auto-detect the correct working directory for coverage/mutation reports in monorepos
|
|
65
|
+
*/
|
|
66
|
+
private findReportDirectory(startPath: string = this.getWorkingDirectory()): string {
|
|
67
|
+
// Priority 1: Check if the current directory has the reports or test results
|
|
68
|
+
if (
|
|
69
|
+
this.hasCoverageReports(startPath) ||
|
|
70
|
+
this.hasMutationReports(startPath) ||
|
|
71
|
+
this.hasTestResults(startPath)
|
|
72
|
+
) {
|
|
73
|
+
return startPath;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Priority 2: Check for npm workspaces configuration
|
|
77
|
+
const packageJsonPath = path.join(startPath, 'package.json');
|
|
78
|
+
if (this.pathExists(packageJsonPath)) {
|
|
79
|
+
try {
|
|
80
|
+
const packageJson = this.readJsonFile<any>(packageJsonPath);
|
|
81
|
+
if (packageJson?.workspaces) {
|
|
82
|
+
const workspaces = packageJson.workspaces;
|
|
83
|
+
|
|
84
|
+
// Handle workspace patterns (e.g., ["packages/*", "iterations/*"])
|
|
85
|
+
for (const wsPattern of workspaces) {
|
|
86
|
+
if (wsPattern.includes('*')) {
|
|
87
|
+
const baseDir = wsPattern.split('*')[0];
|
|
88
|
+
const fullBaseDir = path.join(startPath, baseDir);
|
|
89
|
+
|
|
90
|
+
if (this.pathExists(fullBaseDir)) {
|
|
91
|
+
const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
if (entry.isDirectory()) {
|
|
94
|
+
const wsPath = path.join(fullBaseDir, entry.name);
|
|
95
|
+
if (
|
|
96
|
+
this.hasCoverageReports(wsPath) ||
|
|
97
|
+
this.hasMutationReports(wsPath) ||
|
|
98
|
+
this.hasTestResults(wsPath)
|
|
99
|
+
) {
|
|
100
|
+
return wsPath;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// Direct workspace path
|
|
107
|
+
const wsPath = path.join(startPath, wsPattern);
|
|
108
|
+
if (
|
|
109
|
+
this.hasCoverageReports(wsPath) ||
|
|
110
|
+
this.hasMutationReports(wsPath) ||
|
|
111
|
+
this.hasTestResults(wsPath)
|
|
112
|
+
) {
|
|
113
|
+
return wsPath;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Priority 3: If no reports found in workspaces, look for workspaces with test scripts
|
|
120
|
+
if (packageJson?.workspaces) {
|
|
121
|
+
for (const wsPattern of workspaces) {
|
|
122
|
+
if (wsPattern.includes('*')) {
|
|
123
|
+
const baseDir = wsPattern.split('*')[0];
|
|
124
|
+
const fullBaseDir = path.join(startPath, baseDir);
|
|
125
|
+
|
|
126
|
+
if (this.pathExists(fullBaseDir)) {
|
|
127
|
+
const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (entry.isDirectory()) {
|
|
130
|
+
const wsPath = path.join(fullBaseDir, entry.name);
|
|
131
|
+
if (this.hasTestScript(wsPath)) {
|
|
132
|
+
// Found a workspace with tests, prefer this even without reports
|
|
133
|
+
return wsPath;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
const wsPath = path.join(startPath, wsPattern);
|
|
140
|
+
if (this.hasTestScript(wsPath)) {
|
|
141
|
+
return wsPath;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
// Ignore workspace parsing errors
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Fall back to original working directory
|
|
152
|
+
return startPath;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if a directory has coverage reports
|
|
157
|
+
*/
|
|
158
|
+
private hasCoverageReports(dirPath: string): boolean {
|
|
159
|
+
const coveragePath = path.join(dirPath, 'coverage', 'coverage-final.json');
|
|
160
|
+
return this.pathExists(coveragePath);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Check if a directory has mutation reports
|
|
165
|
+
*/
|
|
166
|
+
private hasMutationReports(dirPath: string): boolean {
|
|
167
|
+
const mutationPath = path.join(dirPath, 'reports', 'mutation', 'mutation.json');
|
|
168
|
+
return this.pathExists(mutationPath);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if a directory has test results
|
|
173
|
+
*/
|
|
174
|
+
private hasTestResults(dirPath: string): boolean {
|
|
175
|
+
const testResultsPath = path.join(dirPath, 'test-results');
|
|
176
|
+
if (this.pathExists(testResultsPath)) {
|
|
177
|
+
try {
|
|
178
|
+
const entries = fs.readdirSync(testResultsPath);
|
|
179
|
+
return entries.some((entry) => entry.endsWith('.json') || entry.endsWith('.xml'));
|
|
180
|
+
} catch (error) {
|
|
181
|
+
// Ignore read errors
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check if a directory has a package.json with test scripts
|
|
189
|
+
*/
|
|
190
|
+
private hasTestScript(dirPath: string): boolean {
|
|
191
|
+
const packageJsonPath = path.join(dirPath, 'package.json');
|
|
192
|
+
if (this.pathExists(packageJsonPath)) {
|
|
193
|
+
try {
|
|
194
|
+
const packageJson = this.readJsonFile<any>(packageJsonPath);
|
|
195
|
+
return !!packageJson?.scripts?.test;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
// Ignore parse errors
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
63
203
|
/**
|
|
64
204
|
* Check if a waiver applies to the given gate
|
|
65
205
|
*/
|
|
@@ -223,10 +363,11 @@ export class CawsGateChecker extends CawsBaseTool {
|
|
|
223
363
|
};
|
|
224
364
|
}
|
|
225
365
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
366
|
+
// Auto-detect the correct directory for coverage reports
|
|
367
|
+
const reportDir = this.findReportDirectory(
|
|
368
|
+
options.workingDirectory || this.getWorkingDirectory()
|
|
229
369
|
);
|
|
370
|
+
const coveragePath = path.join(reportDir, 'coverage', 'coverage-final.json');
|
|
230
371
|
|
|
231
372
|
if (!this.pathExists(coveragePath)) {
|
|
232
373
|
return {
|
|
@@ -234,8 +375,55 @@ export class CawsGateChecker extends CawsBaseTool {
|
|
|
234
375
|
score: 0,
|
|
235
376
|
details: {
|
|
236
377
|
error: 'Coverage report not found. Run tests with coverage first.',
|
|
378
|
+
searched_paths: [
|
|
379
|
+
path.join(reportDir, 'coverage', 'coverage-final.json'),
|
|
380
|
+
path.join(this.getWorkingDirectory(), 'coverage', 'coverage-final.json'),
|
|
381
|
+
],
|
|
382
|
+
expected_format: 'Istanbul coverage format (coverage-final.json)',
|
|
383
|
+
expected_schema: {
|
|
384
|
+
description: 'JSON object with coverage data by file',
|
|
385
|
+
example: {
|
|
386
|
+
'/path/to/file.js': {
|
|
387
|
+
statementMap: {
|
|
388
|
+
/* ... */
|
|
389
|
+
},
|
|
390
|
+
fnMap: {
|
|
391
|
+
/* ... */
|
|
392
|
+
},
|
|
393
|
+
branchMap: {
|
|
394
|
+
/* ... */
|
|
395
|
+
},
|
|
396
|
+
s: {
|
|
397
|
+
/* hit counts */
|
|
398
|
+
},
|
|
399
|
+
f: {
|
|
400
|
+
/* function hits */
|
|
401
|
+
},
|
|
402
|
+
b: {
|
|
403
|
+
/* branch hits */
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
run_command: 'npm test -- --coverage --coverageReporters=json',
|
|
409
|
+
alternative_commands: [
|
|
410
|
+
'npm run test:coverage',
|
|
411
|
+
'jest --coverage --coverageReporters=json',
|
|
412
|
+
'vitest run --coverage',
|
|
413
|
+
],
|
|
414
|
+
workspace_hint:
|
|
415
|
+
reportDir !== this.getWorkingDirectory()
|
|
416
|
+
? `Auto-detected workspace: ${path.relative(this.getWorkingDirectory(), reportDir)}`
|
|
417
|
+
: 'Run from workspace directory if using monorepo',
|
|
418
|
+
waiver_available: true,
|
|
419
|
+
waiver_suggestion:
|
|
420
|
+
'If this is an exceptional case, consider creating a coverage waiver',
|
|
421
|
+
waiver_command:
|
|
422
|
+
'caws waivers create --title="Coverage waiver" --reason=emergency_hotfix --gates=coverage',
|
|
237
423
|
},
|
|
238
|
-
errors: [
|
|
424
|
+
errors: [
|
|
425
|
+
`Coverage report not found at ${path.relative(this.getWorkingDirectory(), coveragePath)}`,
|
|
426
|
+
],
|
|
239
427
|
};
|
|
240
428
|
}
|
|
241
429
|
|
|
@@ -356,10 +544,11 @@ export class CawsGateChecker extends CawsBaseTool {
|
|
|
356
544
|
};
|
|
357
545
|
}
|
|
358
546
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
547
|
+
// Auto-detect the correct directory for mutation reports
|
|
548
|
+
const reportDir = this.findReportDirectory(
|
|
549
|
+
options.workingDirectory || this.getWorkingDirectory()
|
|
362
550
|
);
|
|
551
|
+
const mutationPath = path.join(reportDir, 'reports', 'mutation', 'mutation.json');
|
|
363
552
|
|
|
364
553
|
if (!this.pathExists(mutationPath)) {
|
|
365
554
|
return {
|
|
@@ -367,8 +556,49 @@ export class CawsGateChecker extends CawsBaseTool {
|
|
|
367
556
|
score: 0,
|
|
368
557
|
details: {
|
|
369
558
|
error: 'Mutation report not found. Run mutation tests first.',
|
|
559
|
+
searched_paths: [
|
|
560
|
+
path.join(reportDir, 'reports', 'mutation', 'mutation.json'),
|
|
561
|
+
path.join(this.getWorkingDirectory(), 'reports', 'mutation', 'mutation.json'),
|
|
562
|
+
],
|
|
563
|
+
expected_format: 'Stryker mutation testing JSON report',
|
|
564
|
+
expected_schema: {
|
|
565
|
+
description: 'JSON object with mutation testing results',
|
|
566
|
+
example: {
|
|
567
|
+
files: {
|
|
568
|
+
/* file-specific results */
|
|
569
|
+
},
|
|
570
|
+
testFiles: {
|
|
571
|
+
/* test file results */
|
|
572
|
+
},
|
|
573
|
+
mutants: [
|
|
574
|
+
{
|
|
575
|
+
/* mutant details */
|
|
576
|
+
},
|
|
577
|
+
],
|
|
578
|
+
metrics: {
|
|
579
|
+
killed: 85,
|
|
580
|
+
survived: 5,
|
|
581
|
+
timeout: 2,
|
|
582
|
+
totalDetected: 92,
|
|
583
|
+
totalUndetected: 0,
|
|
584
|
+
totalValid: 92,
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
run_command: 'npx stryker run',
|
|
589
|
+
alternative_commands: [
|
|
590
|
+
'npm run test:mutation',
|
|
591
|
+
'npx stryker run --configFile stryker.conf.json',
|
|
592
|
+
'yarn mutation:test',
|
|
593
|
+
],
|
|
594
|
+
workspace_hint:
|
|
595
|
+
reportDir !== this.getWorkingDirectory()
|
|
596
|
+
? `Auto-detected workspace: ${path.relative(this.getWorkingDirectory(), reportDir)}`
|
|
597
|
+
: 'Run from workspace directory if using monorepo',
|
|
370
598
|
},
|
|
371
|
-
errors: [
|
|
599
|
+
errors: [
|
|
600
|
+
`Mutation report not found at ${path.relative(this.getWorkingDirectory(), mutationPath)}`,
|
|
601
|
+
],
|
|
372
602
|
};
|
|
373
603
|
}
|
|
374
604
|
|
|
@@ -439,17 +669,39 @@ export class CawsGateChecker extends CawsBaseTool {
|
|
|
439
669
|
};
|
|
440
670
|
}
|
|
441
671
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
672
|
+
// Auto-detect the correct directory for contract test results
|
|
673
|
+
const reportDir = this.findReportDirectory(
|
|
674
|
+
options.workingDirectory || this.getWorkingDirectory()
|
|
445
675
|
);
|
|
676
|
+
const contractResultsPath = path.join(reportDir, 'test-results', 'contract-results.json');
|
|
446
677
|
|
|
447
678
|
if (!this.pathExists(contractResultsPath)) {
|
|
448
679
|
return {
|
|
449
680
|
passed: false,
|
|
450
681
|
score: 0,
|
|
451
|
-
details: {
|
|
452
|
-
|
|
682
|
+
details: {
|
|
683
|
+
error: 'Contract test results not found',
|
|
684
|
+
searched_paths: [
|
|
685
|
+
path.join(reportDir, 'test-results', 'contract-results.json'),
|
|
686
|
+
path.join(this.getWorkingDirectory(), 'test-results', 'contract-results.json'),
|
|
687
|
+
path.join(reportDir, '.caws', 'contract-results.json'),
|
|
688
|
+
path.join(this.getWorkingDirectory(), '.caws', 'contract-results.json'),
|
|
689
|
+
],
|
|
690
|
+
expected_format:
|
|
691
|
+
'JSON with { tests: [], passed: boolean, numPassed: number, numTotal: number }',
|
|
692
|
+
example_command:
|
|
693
|
+
'npm run test:contract -- --json --outputFile=test-results/contract-results.json',
|
|
694
|
+
},
|
|
695
|
+
errors: [
|
|
696
|
+
`Contract test results not found. Searched in: ${[
|
|
697
|
+
path.relative(
|
|
698
|
+
this.getWorkingDirectory(),
|
|
699
|
+
path.join(reportDir, 'test-results', 'contract-results.json')
|
|
700
|
+
),
|
|
701
|
+
'test-results/contract-results.json',
|
|
702
|
+
'.caws/contract-results.json',
|
|
703
|
+
].join(', ')}`,
|
|
704
|
+
],
|
|
453
705
|
};
|
|
454
706
|
}
|
|
455
707
|
|
|
@@ -15,10 +15,24 @@ acceptance:
|
|
|
15
15
|
given: '{{GIVEN_CONDITION}}'
|
|
16
16
|
when: '{{WHEN_ACTION}}'
|
|
17
17
|
then: '{{THEN_OUTCOME}}'
|
|
18
|
+
status: pending # pending | in_progress | completed
|
|
19
|
+
# Optional: detailed progress tracking
|
|
20
|
+
# tests:
|
|
21
|
+
# written: 0
|
|
22
|
+
# passing: 0
|
|
23
|
+
# coverage: 0.0
|
|
24
|
+
# last_updated: '2025-10-09T14:30:00Z'
|
|
18
25
|
- id: A2
|
|
19
26
|
given: '{{GIVEN_CONDITION_2}}'
|
|
20
27
|
when: '{{WHEN_ACTION_2}}'
|
|
21
28
|
then: '{{THEN_OUTCOME_2}}'
|
|
29
|
+
status: pending # pending | in_progress | completed
|
|
30
|
+
# Optional: detailed progress tracking
|
|
31
|
+
# tests:
|
|
32
|
+
# written: 0
|
|
33
|
+
# passing: 0
|
|
34
|
+
# coverage: 0.0
|
|
35
|
+
# last_updated: '2025-10-09T14:30:00Z'
|
|
22
36
|
non_functional:
|
|
23
37
|
a11y:
|
|
24
38
|
- '{{ACCESSIBILITY_REQUIREMENT}}'
|