@paths.design/caws-cli 8.0.1 → 8.1.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 (44) hide show
  1. package/dist/commands/archive.d.ts +2 -1
  2. package/dist/commands/archive.d.ts.map +1 -1
  3. package/dist/commands/archive.js +114 -6
  4. package/dist/commands/burnup.d.ts.map +1 -1
  5. package/dist/commands/burnup.js +109 -10
  6. package/dist/commands/diagnose.js +1 -1
  7. package/dist/commands/mode.js +24 -14
  8. package/dist/commands/provenance.js +216 -93
  9. package/dist/commands/quality-gates.d.ts.map +1 -1
  10. package/dist/commands/quality-gates.js +3 -1
  11. package/dist/commands/specs.js +184 -6
  12. package/dist/commands/status.d.ts.map +1 -1
  13. package/dist/commands/status.js +134 -10
  14. package/dist/commands/templates.js +2 -2
  15. package/dist/error-handler.js +6 -98
  16. package/dist/generators/jest-config-generator.js +242 -0
  17. package/dist/index.js +4 -7
  18. package/dist/minimal-cli.js +3 -1
  19. package/dist/scaffold/claude-hooks.js +316 -0
  20. package/dist/scaffold/index.js +18 -0
  21. package/dist/templates/.claude/README.md +190 -0
  22. package/dist/templates/.claude/hooks/audit.sh +96 -0
  23. package/dist/templates/.claude/hooks/block-dangerous.sh +90 -0
  24. package/dist/templates/.claude/hooks/naming-check.sh +97 -0
  25. package/dist/templates/.claude/hooks/quality-check.sh +68 -0
  26. package/dist/templates/.claude/hooks/scan-secrets.sh +85 -0
  27. package/dist/templates/.claude/hooks/scope-guard.sh +105 -0
  28. package/dist/templates/.claude/hooks/validate-spec.sh +76 -0
  29. package/dist/templates/.claude/settings.json +95 -0
  30. package/dist/test-analysis.js +203 -10
  31. package/dist/utils/error-categories.js +210 -0
  32. package/dist/utils/quality-gates-utils.js +402 -0
  33. package/dist/utils/typescript-detector.js +36 -90
  34. package/dist/validation/spec-validation.js +59 -6
  35. package/package.json +5 -3
  36. package/templates/.claude/README.md +190 -0
  37. package/templates/.claude/hooks/audit.sh +96 -0
  38. package/templates/.claude/hooks/block-dangerous.sh +90 -0
  39. package/templates/.claude/hooks/naming-check.sh +97 -0
  40. package/templates/.claude/hooks/quality-check.sh +68 -0
  41. package/templates/.claude/hooks/scan-secrets.sh +85 -0
  42. package/templates/.claude/hooks/scope-guard.sh +105 -0
  43. package/templates/.claude/hooks/validate-spec.sh +76 -0
  44. package/templates/.claude/settings.json +95 -0
@@ -18,10 +18,11 @@ export function loadChange(changeId: string): Promise<any | null>;
18
18
  export function validateAcceptanceCriteria(workingSpec: any): Promise<any>;
19
19
  /**
20
20
  * Validate change meets quality gates
21
+ * Runs the actual quality gates runner and checks for violations
21
22
  * @param {string} changeId - Change identifier
22
23
  * @returns {Promise<Object>} Quality gate result
23
24
  */
24
- export function validateQualityGates(_changeId: any): Promise<any>;
25
+ export function validateQualityGates(changeId: string): Promise<any>;
25
26
  /**
26
27
  * Generate change summary for archival
27
28
  * @param {Object} change - Change data
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/commands/archive.js"],"names":[],"mappings":"AA8OA;;;;GAIG;AACH,yCAHW,MAAM,+BAqGhB;AAtUD;;;;GAIG;AACH,qCAHW,MAAM,GACJ,OAAO,CAAC,MAAO,IAAI,CAAC,CAgChC;AAED;;;;GAIG;AACH,8DAFa,OAAO,KAAQ,CA8B3B;AAED;;;;GAIG;AACH,sDAFa,OAAO,KAAQ,CAS3B;AAED;;;;GAIG;AACH,oDAFa,OAAO,CAAC,MAAM,CAAC,CAgC3B;AAED;;;;GAIG;AACH,4CAFa,OAAO,CAAC,IAAI,CAAC,CAazB;AAED;;;;GAIG;AACH,+CAFa,OAAO,CAAC,IAAI,CAAC,CAoCzB;AAED;;;;;GAKG;AACH,6FAiCC"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/commands/archive.js"],"names":[],"mappings":"AA0VA;;;;GAIG;AACH,yCAHW,MAAM,+BAqGhB;AAjbD;;;;GAIG;AACH,qCAHW,MAAM,GACJ,OAAO,CAAC,MAAO,IAAI,CAAC,CAgChC;AAED;;;;GAIG;AACH,8DAFa,OAAO,KAAQ,CA8B3B;AAED;;;;;GAKG;AACH,+CAHW,MAAM,GACJ,OAAO,KAAQ,CAmH3B;AAED;;;;GAIG;AACH,oDAFa,OAAO,CAAC,MAAM,CAAC,CAgC3B;AAED;;;;GAIG;AACH,4CAFa,OAAO,CAAC,IAAI,CAAC,CAazB;AAED;;;;GAIG;AACH,+CAFa,OAAO,CAAC,IAAI,CAAC,CAoCzB;AAED;;;;;GAKG;AACH,6FAiCC"}
@@ -8,6 +8,7 @@ const fs = require('fs-extra');
8
8
  const path = require('path');
9
9
  const yaml = require('js-yaml');
10
10
  const chalk = require('chalk');
11
+ const { execSync } = require('child_process');
11
12
  const { safeAsync, outputResult } = require('../error-handler');
12
13
 
13
14
  // Import spec resolution system
@@ -87,16 +88,123 @@ async function validateAcceptanceCriteria(workingSpec) {
87
88
 
88
89
  /**
89
90
  * Validate change meets quality gates
91
+ * Runs the actual quality gates runner and checks for violations
90
92
  * @param {string} changeId - Change identifier
91
93
  * @returns {Promise<Object>} Quality gate result
92
94
  */
93
95
  async function validateQualityGates(_changeId) {
94
- // For now, return success - in full implementation would run actual gate checks
95
- return {
96
- valid: true,
97
- message: 'Quality gates passed (implementation pending)',
98
- gates: ['test-coverage', 'linting', 'type-checking'],
99
- };
96
+ const gates = [];
97
+ const violations = [];
98
+ const warnings = [];
99
+
100
+ try {
101
+ // Try to run the quality gates runner
102
+ const qualityGatesPath = path.join(
103
+ __dirname,
104
+ '..',
105
+ '..',
106
+ '..',
107
+ 'quality-gates',
108
+ 'run-quality-gates.mjs'
109
+ );
110
+
111
+ // Check if quality gates runner exists
112
+ if (await fs.pathExists(qualityGatesPath)) {
113
+ try {
114
+ // Run quality gates in CI mode (checks all files, not just staged)
115
+ const result = execSync(`node "${qualityGatesPath}" --context=ci --json 2>&1`, {
116
+ encoding: 'utf8',
117
+ timeout: 60000, // 60 second timeout
118
+ cwd: process.cwd(),
119
+ });
120
+
121
+ // Try to parse JSON output
122
+ try {
123
+ const jsonMatch = result.match(/\{[\s\S]*\}$/);
124
+ if (jsonMatch) {
125
+ const parsed = JSON.parse(jsonMatch[0]);
126
+ if (parsed.violations) {
127
+ violations.push(...parsed.violations);
128
+ }
129
+ if (parsed.warnings) {
130
+ warnings.push(...parsed.warnings);
131
+ }
132
+ gates.push(...(parsed.gates || []));
133
+ }
134
+ } catch {
135
+ // JSON parsing failed, check for error indicators in output
136
+ if (result.includes('❌') || result.includes('FAIL')) {
137
+ violations.push({ message: 'Quality gates reported failures', output: result });
138
+ }
139
+ }
140
+ } catch (execError) {
141
+ // Command failed - check exit code and output
142
+ if (execError.status !== 0) {
143
+ const output = execError.stdout || execError.message;
144
+ if (output.includes('violations') || output.includes('❌')) {
145
+ violations.push({ message: 'Quality gates failed', output: output.substring(0, 500) });
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ // Also check for active waivers that might cover violations
152
+ const waiversPath = path.join(process.cwd(), '.caws', 'waivers', 'active-waivers.yaml');
153
+ let hasActiveWaivers = false;
154
+ if (await fs.pathExists(waiversPath)) {
155
+ const waiversContent = await fs.readFile(waiversPath, 'utf8');
156
+ const waivers = yaml.load(waiversContent);
157
+ if (waivers && waivers.waivers) {
158
+ const activeWaiverCount = Object.keys(waivers.waivers).length;
159
+ if (activeWaiverCount > 0) {
160
+ hasActiveWaivers = true;
161
+ gates.push(`${activeWaiverCount} active waiver(s)`);
162
+ }
163
+ }
164
+ }
165
+
166
+ // Determine overall validity
167
+ const hasBlockingViolations = violations.some(
168
+ (v) => v.severity === 'block' || v.severity === 'fail'
169
+ );
170
+
171
+ if (violations.length === 0) {
172
+ return {
173
+ valid: true,
174
+ message: 'All quality gates passed',
175
+ gates: gates.length > 0 ? gates : ['naming', 'duplication', 'god-objects', 'hidden-todo'],
176
+ violations: [],
177
+ warnings,
178
+ };
179
+ } else if (hasActiveWaivers && !hasBlockingViolations) {
180
+ return {
181
+ valid: true,
182
+ message: `Quality gates passed with ${violations.length} waived violation(s)`,
183
+ gates,
184
+ violations,
185
+ warnings,
186
+ waived: true,
187
+ };
188
+ } else {
189
+ return {
190
+ valid: false,
191
+ message: `${violations.length} quality gate violation(s) found`,
192
+ gates,
193
+ violations,
194
+ warnings,
195
+ };
196
+ }
197
+ } catch (error) {
198
+ // If quality gates can't be run, warn but don't block
199
+ return {
200
+ valid: true,
201
+ message: `Quality gates check skipped: ${error.message}`,
202
+ gates: [],
203
+ violations: [],
204
+ warnings: [{ message: `Could not run quality gates: ${error.message}` }],
205
+ skipped: true,
206
+ };
207
+ }
100
208
  }
101
209
 
102
210
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"burnup.d.ts","sourceRoot":"","sources":["../../src/commands/burnup.js"],"names":[],"mappings":"AAaA;;;GAGG;AACH,wCAFW,MAAM,iBAsEhB"}
1
+ {"version":3,"file":"burnup.d.ts","sourceRoot":"","sources":["../../src/commands/burnup.js"],"names":[],"mappings":"AAsFA;;;GAGG;AACH,wCAFW,MAAM,iBAgGhB"}
@@ -8,9 +8,82 @@ const fs = require('fs-extra');
8
8
  const path = require('path');
9
9
  const yaml = require('js-yaml');
10
10
  const chalk = require('chalk');
11
+ const { execSync } = require('child_process');
11
12
 
12
13
  const { deriveBudget, generateBurnupReport } = require('../budget-derivation');
13
14
 
15
+ /**
16
+ * Get actual git change statistics from the repository
17
+ * Analyzes changes since the last tag or initial commit
18
+ * @param {string} specDir - Directory containing the spec file
19
+ * @returns {Object} Stats with files_changed, lines_added, lines_removed, lines_changed
20
+ */
21
+ function getGitChangeStats(specDir) {
22
+ try {
23
+ const cwd = specDir || process.cwd();
24
+
25
+ // Find the base reference - prefer last tag, fall back to first commit
26
+ let baseRef;
27
+ try {
28
+ baseRef = execSync('git describe --tags --abbrev=0 2>/dev/null', {
29
+ cwd,
30
+ encoding: 'utf8',
31
+ }).trim();
32
+ } catch {
33
+ // No tags, use first commit
34
+ try {
35
+ baseRef = execSync('git rev-list --max-parents=0 HEAD', {
36
+ cwd,
37
+ encoding: 'utf8',
38
+ }).trim();
39
+ } catch {
40
+ // Not a git repo or no commits
41
+ return null;
42
+ }
43
+ }
44
+
45
+ // Get file change count
46
+ const filesOutput = execSync(`git diff --name-only ${baseRef}..HEAD`, {
47
+ cwd,
48
+ encoding: 'utf8',
49
+ });
50
+ const filesChanged = filesOutput.trim().split('\n').filter(Boolean).length;
51
+
52
+ // Get line statistics using --numstat
53
+ const numstatOutput = execSync(`git diff --numstat ${baseRef}..HEAD`, {
54
+ cwd,
55
+ encoding: 'utf8',
56
+ });
57
+
58
+ let linesAdded = 0;
59
+ let linesRemoved = 0;
60
+
61
+ numstatOutput
62
+ .trim()
63
+ .split('\n')
64
+ .filter(Boolean)
65
+ .forEach((line) => {
66
+ const [added, removed] = line.split('\t');
67
+ // Skip binary files (shown as '-')
68
+ if (added !== '-' && removed !== '-') {
69
+ linesAdded += parseInt(added, 10) || 0;
70
+ linesRemoved += parseInt(removed, 10) || 0;
71
+ }
72
+ });
73
+
74
+ return {
75
+ files_changed: filesChanged,
76
+ lines_added: linesAdded,
77
+ lines_removed: linesRemoved,
78
+ lines_changed: linesAdded + linesRemoved,
79
+ base_ref: baseRef,
80
+ };
81
+ } catch (error) {
82
+ // Return null if git analysis fails
83
+ return null;
84
+ }
85
+ }
86
+
14
87
  /**
15
88
  * Burn-up command handler
16
89
  * @param {string} specFile - Path to spec file
@@ -32,15 +105,34 @@ async function burnupCommand(specFile) {
32
105
  // Derive budget
33
106
  const derivedBudget = deriveBudget(spec, path.dirname(specPath));
34
107
 
35
- // Mock current stats - in real implementation this would analyze actual git changes
36
- const mockStats = {
37
- files_changed: 50, // This would be calculated from actual changes
38
- lines_changed: 5000,
39
- risk_tier: spec.risk_tier,
40
- };
108
+ // Get actual git change statistics
109
+ const gitStats = getGitChangeStats(path.dirname(specPath));
110
+
111
+ let currentStats;
112
+ if (gitStats) {
113
+ currentStats = {
114
+ files_changed: gitStats.files_changed,
115
+ lines_changed: gitStats.lines_changed,
116
+ lines_added: gitStats.lines_added,
117
+ lines_removed: gitStats.lines_removed,
118
+ risk_tier: spec.risk_tier,
119
+ base_ref: gitStats.base_ref,
120
+ };
121
+ console.log(chalk.gray(` Analyzing changes since: ${gitStats.base_ref}`));
122
+ } else {
123
+ // Fallback if git analysis fails (not in a repo or no commits)
124
+ console.log(chalk.yellow(' ⚠️ Could not analyze git history, using zero values'));
125
+ currentStats = {
126
+ files_changed: 0,
127
+ lines_changed: 0,
128
+ lines_added: 0,
129
+ lines_removed: 0,
130
+ risk_tier: spec.risk_tier,
131
+ };
132
+ }
41
133
 
42
134
  // Generate report
43
- const report = generateBurnupReport(derivedBudget, mockStats);
135
+ const report = generateBurnupReport(derivedBudget, currentStats);
44
136
 
45
137
  console.log(report);
46
138
 
@@ -63,15 +155,22 @@ async function burnupCommand(specFile) {
63
155
 
64
156
  console.log(
65
157
  chalk.gray(
66
- ` Current Usage: ${mockStats.files_changed} files, ${mockStats.lines_changed} LOC`
158
+ ` Current Usage: ${currentStats.files_changed} files, ${currentStats.lines_changed} LOC`
67
159
  )
68
160
  );
161
+ if (currentStats.lines_added !== undefined) {
162
+ console.log(
163
+ chalk.gray(
164
+ ` Breakdown: +${currentStats.lines_added} added, -${currentStats.lines_removed} removed`
165
+ )
166
+ );
167
+ }
69
168
 
70
169
  const filePercent = Math.round(
71
- (mockStats.files_changed / derivedBudget.effective.max_files) * 100
170
+ (currentStats.files_changed / derivedBudget.effective.max_files) * 100
72
171
  );
73
172
  const locPercent = Math.round(
74
- (mockStats.lines_changed / derivedBudget.effective.max_loc) * 100
173
+ (currentStats.lines_changed / derivedBudget.effective.max_loc) * 100
75
174
  );
76
175
 
77
176
  if (filePercent > 90 || locPercent > 90) {
@@ -11,7 +11,7 @@ const chalk = require('chalk');
11
11
 
12
12
  // Import utilities
13
13
  const { checkTypeScriptTestConfig } = require('../utils/typescript-detector');
14
- const { configureJestForTypeScript } = require('../generators/jest-config');
14
+ const { configureJestForTypeScript } = require('../generators/jest-config-generator');
15
15
 
16
16
  /**
17
17
  * Health check: Working spec validity
@@ -18,13 +18,32 @@ const {
18
18
 
19
19
  /**
20
20
  * Display current mode status
21
+ * @param {string} currentMode - The current mode to display
21
22
  */
22
- function displayCurrentMode() {
23
+ function displayCurrentMode(currentMode) {
24
+ const tier = getTier(currentMode);
25
+
23
26
  console.log(chalk.bold.cyan('\n🔧 CAWS Current Mode'));
24
27
  console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
25
28
 
26
- // This will be implemented when we load the current mode
27
- console.log(chalk.yellow('Mode display will be implemented...'));
29
+ console.log(chalk.bold(`Active Mode: ${tier.icon} ${tier.color(currentMode)}`));
30
+ console.log(chalk.gray(`Description: ${tier.description}`));
31
+ console.log('');
32
+
33
+ // Quality requirements
34
+ console.log(chalk.bold('Quality Requirements:'));
35
+ console.log(chalk.gray(` Test Coverage: ≥${tier.qualityRequirements.testCoverage}%`));
36
+ console.log(chalk.gray(` Mutation Score: ≥${tier.qualityRequirements.mutationScore}%`));
37
+ console.log(chalk.gray(` Contracts: ${tier.qualityRequirements.contracts}`));
38
+ console.log('');
39
+
40
+ // Risk tiers supported
41
+ console.log(chalk.bold('Supported Risk Tiers:'));
42
+ tier.riskTiers.forEach((riskTier) => {
43
+ const riskColor =
44
+ riskTier === 'T1' ? chalk.red : riskTier === 'T2' ? chalk.yellow : chalk.green;
45
+ console.log(chalk.gray(` ${riskColor(riskTier)}`));
46
+ });
28
47
  console.log('');
29
48
  }
30
49
 
@@ -128,21 +147,12 @@ async function modeCommand(action, options = {}) {
128
147
  switch (action) {
129
148
  case 'current': {
130
149
  const currentMode = await getCurrentMode();
131
- displayCurrentMode();
132
-
133
- const tier = getTier(currentMode);
134
- console.log(chalk.bold(`Current Mode: ${tier.icon} ${tier.color(currentMode)}`));
135
- console.log(chalk.gray(`Description: ${tier.description}`));
136
- console.log(
137
- chalk.gray(
138
- `Quality: ${tier.qualityRequirements.testCoverage}% coverage, ${tier.qualityRequirements.mutationScore}% mutation`
139
- )
140
- );
150
+ displayCurrentMode(currentMode);
141
151
 
142
152
  return outputResult({
143
153
  command: 'mode current',
144
154
  mode: currentMode,
145
- tier: tier,
155
+ tier: getTier(currentMode),
146
156
  });
147
157
  }
148
158