@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.
Files changed (40) hide show
  1. package/dist/commands/diagnose.d.ts.map +1 -1
  2. package/dist/commands/diagnose.js +39 -4
  3. package/dist/commands/evaluate.d.ts +8 -0
  4. package/dist/commands/evaluate.d.ts.map +1 -0
  5. package/dist/commands/evaluate.js +288 -0
  6. package/dist/commands/iterate.d.ts +8 -0
  7. package/dist/commands/iterate.d.ts.map +1 -0
  8. package/dist/commands/iterate.js +341 -0
  9. package/dist/commands/quality-monitor.d.ts +17 -0
  10. package/dist/commands/quality-monitor.d.ts.map +1 -0
  11. package/dist/commands/quality-monitor.js +265 -0
  12. package/dist/commands/status.d.ts +6 -1
  13. package/dist/commands/status.d.ts.map +1 -1
  14. package/dist/commands/status.js +120 -20
  15. package/dist/commands/troubleshoot.d.ts +8 -0
  16. package/dist/commands/troubleshoot.d.ts.map +1 -0
  17. package/dist/commands/troubleshoot.js +104 -0
  18. package/dist/commands/waivers.d.ts +8 -0
  19. package/dist/commands/waivers.d.ts.map +1 -0
  20. package/dist/commands/waivers.js +293 -0
  21. package/dist/commands/workflow.d.ts +85 -0
  22. package/dist/commands/workflow.d.ts.map +1 -0
  23. package/dist/commands/workflow.js +243 -0
  24. package/dist/error-handler.d.ts +91 -2
  25. package/dist/error-handler.d.ts.map +1 -1
  26. package/dist/error-handler.js +362 -16
  27. package/dist/index.js +95 -0
  28. package/dist/utils/typescript-detector.d.ts +31 -0
  29. package/dist/utils/typescript-detector.d.ts.map +1 -1
  30. package/dist/utils/typescript-detector.js +245 -7
  31. package/package.json +2 -1
  32. package/templates/apps/tools/caws/gates.ts +34 -0
  33. package/templates/apps/tools/caws/shared/gate-checker.ts +265 -13
  34. package/templates/apps/tools/caws/templates/working-spec.template.yml +14 -0
  35. package/dist/index-new.d.ts +0 -5
  36. package/dist/index-new.d.ts.map +0 -1
  37. package/dist/index-new.js +0 -317
  38. package/dist/index.js.backup +0 -4711
  39. package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
  40. 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
- const tsDetection = detectTypeScript(projectDir);
110
- const testDetection = detectTestFramework(projectDir, tsDetection.packageJson);
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
- tsDetection.isTypeScript && testDetection.framework === 'jest' && !testDetection.hasTsJest;
338
+ primaryTsDetection.isTypeScript &&
339
+ primaryTestDetection.framework === 'jest' &&
340
+ !hasTsJestAnywhere;
114
341
 
115
342
  return {
116
- ...tsDetection,
117
- testFramework: testDetection,
118
- needsJestConfig: tsDetection.isTypeScript && !testDetection.isConfigured,
343
+ ...primaryTsDetection,
344
+ testFramework: primaryTestDetection,
345
+ needsJestConfig: primaryTsDetection.isTypeScript && !primaryTestDetection.isConfigured,
119
346
  needsTsJest: needsConfig,
120
- recommendations: generateRecommendations(tsDetection, testDetection),
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.1",
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
- const coveragePath = path.join(
227
- options.workingDirectory || this.getWorkingDirectory(),
228
- 'coverage/coverage-final.json'
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: ['Coverage report not found'],
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
- const mutationPath = path.join(
360
- options.workingDirectory || this.getWorkingDirectory(),
361
- 'reports/mutation/mutation.json'
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: ['Mutation report not found'],
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
- const contractResultsPath = path.join(
443
- options.workingDirectory || this.getWorkingDirectory(),
444
- 'test-results/contract-results.json'
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: { error: 'Contract test results not found' },
452
- errors: ['Contract tests not run or results not found'],
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}}'