@paths.design/caws-cli 5.0.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -330,6 +330,31 @@ async function scaffoldProject(options) {
330
330
  required: false,
331
331
  });
332
332
 
333
+ // Install quality gates package if requested
334
+ if (options.withQualityGates) {
335
+ console.log(chalk.blue('\n📦 Installing quality gates package...'));
336
+ try {
337
+ const { execSync } = require('child_process');
338
+ const npmCommand = fs.existsSync(path.join(currentDir, 'package.json'))
339
+ ? 'npm install --save-dev @paths.design/quality-gates'
340
+ : 'npm install -g @paths.design/quality-gates';
341
+
342
+ console.log(chalk.gray(` Running: ${npmCommand}`));
343
+ execSync(npmCommand, {
344
+ cwd: currentDir,
345
+ stdio: 'inherit',
346
+ });
347
+ console.log(chalk.green('✅ Quality gates package installed'));
348
+ console.log(chalk.blue('💡 You can now use: caws quality-gates'));
349
+ } catch (error) {
350
+ console.log(chalk.yellow(`⚠️ Failed to install quality gates package: ${error.message}`));
351
+ console.log(
352
+ chalk.gray(' You can install manually: npm install -g @paths.design/quality-gates')
353
+ );
354
+ console.log(chalk.gray(' Or use Python scripts: python3 scripts/simple_gates.py'));
355
+ }
356
+ }
357
+
333
358
  // Add commit conventions for setups that don't have them
334
359
  if (!setup.hasTemplates || !fs.existsSync(path.join(currentDir, 'COMMIT_CONVENTIONS.md'))) {
335
360
  enhancements.push({
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @fileoverview Async Operation Utilities
3
+ * Provides consistent patterns for async operations, parallel execution, and resource cleanup
4
+ * @author @darianrosebrook
5
+ */
6
+
7
+ /**
8
+ * Execute multiple async operations in parallel
9
+ * @param {Array<Promise>} promises - Array of promises to execute
10
+ * @param {Object} options - Options
11
+ * @param {boolean} [options.failFast=true] - Stop on first error
12
+ * @returns {Promise<Array>} Array of results
13
+ */
14
+ async function parallel(promises, options = {}) {
15
+ const { failFast = true } = options;
16
+
17
+ if (failFast) {
18
+ return Promise.all(promises);
19
+ } else {
20
+ // Wait for all promises, collecting both successes and failures
21
+ return Promise.allSettled(promises).then((results) => {
22
+ return results.map((result) => {
23
+ if (result.status === 'fulfilled') {
24
+ return { success: true, value: result.value };
25
+ } else {
26
+ return { success: false, error: result.reason };
27
+ }
28
+ });
29
+ });
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Execute async operations sequentially
35
+ * @param {Array<Function>} operations - Array of async functions to execute
36
+ * @param {Object} options - Options
37
+ * @param {boolean} [options.stopOnError=true] - Stop on first error
38
+ * @returns {Promise<Array>} Array of results
39
+ */
40
+ async function sequential(operations, options = {}) {
41
+ const { stopOnError = true } = options;
42
+ const results = [];
43
+
44
+ for (const operation of operations) {
45
+ try {
46
+ const result = await operation();
47
+ results.push({ success: true, value: result });
48
+ } catch (error) {
49
+ if (stopOnError) {
50
+ throw error;
51
+ }
52
+ results.push({ success: false, error });
53
+ }
54
+ }
55
+
56
+ return results;
57
+ }
58
+
59
+ /**
60
+ * Retry an async operation with exponential backoff
61
+ * @param {Function} operation - Async function to retry
62
+ * @param {Object} options - Retry options
63
+ * @param {number} [options.maxRetries=3] - Maximum number of retries
64
+ * @param {number} [options.initialDelay=1000] - Initial delay in ms
65
+ * @param {number} [options.maxDelay=10000] - Maximum delay in ms
66
+ * @param {Function} [options.shouldRetry] - Function to determine if error should be retried
67
+ * @returns {Promise<any>} Operation result
68
+ */
69
+ async function retry(operation, options = {}) {
70
+ const {
71
+ maxRetries = 3,
72
+ initialDelay = 1000,
73
+ maxDelay = 10000,
74
+ shouldRetry = () => true,
75
+ } = options;
76
+
77
+ let lastError;
78
+ let delay = initialDelay;
79
+
80
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
81
+ try {
82
+ return await operation();
83
+ } catch (error) {
84
+ lastError = error;
85
+
86
+ if (attempt === maxRetries || !shouldRetry(error)) {
87
+ throw error;
88
+ }
89
+
90
+ // Wait before retrying with exponential backoff
91
+ // eslint-disable-next-line no-undef
92
+ await new Promise((resolve) => setTimeout(resolve, delay));
93
+ delay = Math.min(delay * 2, maxDelay);
94
+ }
95
+ }
96
+
97
+ throw lastError;
98
+ }
99
+
100
+ /**
101
+ * Execute operation with timeout
102
+ * @param {Promise} promise - Promise to execute
103
+ * @param {number} timeoutMs - Timeout in milliseconds
104
+ * @param {string} [errorMessage] - Custom error message
105
+ * @returns {Promise<any>} Operation result
106
+ */
107
+ async function withTimeout(promise, timeoutMs, errorMessage = 'Operation timed out') {
108
+ const timeoutPromise = new Promise((_, reject) => {
109
+ // eslint-disable-next-line no-undef
110
+ setTimeout(() => {
111
+ reject(new Error(`${errorMessage} (${timeoutMs}ms)`));
112
+ }, timeoutMs);
113
+ });
114
+
115
+ return Promise.race([promise, timeoutPromise]);
116
+ }
117
+
118
+ /**
119
+ * Execute operation with resource cleanup
120
+ * @param {Function} operation - Async operation to execute
121
+ * @param {Function} cleanup - Cleanup function (called in finally)
122
+ * @returns {Promise<any>} Operation result
123
+ */
124
+ async function withCleanup(operation, cleanup) {
125
+ try {
126
+ return await operation();
127
+ } finally {
128
+ await cleanup();
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Execute multiple operations and collect all errors
134
+ * @param {Array<Function>} operations - Array of async functions
135
+ * @returns {Promise<{successes: Array, errors: Array}>} Results and errors
136
+ */
137
+ async function collectResults(operations) {
138
+ const results = await Promise.allSettled(
139
+ operations.map((op) => op())
140
+ );
141
+
142
+ const successes = [];
143
+ const errors = [];
144
+
145
+ results.forEach((result, index) => {
146
+ if (result.status === 'fulfilled') {
147
+ successes.push({ index, value: result.value });
148
+ } else {
149
+ errors.push({ index, error: result.reason });
150
+ }
151
+ });
152
+
153
+ return { successes, errors };
154
+ }
155
+
156
+ /**
157
+ * Execute operation with cancellation support
158
+ * @param {Function} operation - Async operation to execute
159
+ * @param {AbortSignal} signal - Abort signal for cancellation
160
+ * @returns {Promise<any>} Operation result
161
+ */
162
+ async function withCancellation(operation, signal) {
163
+ if (signal.aborted) {
164
+ throw new Error('Operation cancelled');
165
+ }
166
+
167
+ return new Promise((resolve, reject) => {
168
+ signal.addEventListener('abort', () => {
169
+ reject(new Error('Operation cancelled'));
170
+ });
171
+
172
+ operation()
173
+ .then(resolve)
174
+ .catch(reject);
175
+ });
176
+ }
177
+
178
+ module.exports = {
179
+ parallel,
180
+ sequential,
181
+ retry,
182
+ withTimeout,
183
+ withCleanup,
184
+ collectResults,
185
+ withCancellation,
186
+ };
187
+
188
+
@@ -0,0 +1,200 @@
1
+ /**
2
+ * @fileoverview Unified Command Wrapper
3
+ * Provides consistent error handling and output formatting for all CLI commands
4
+ * @author @darianrosebrook
5
+ */
6
+
7
+ const { safeAsync, handleCliError, outputResult, isJsonOutput } = require('../error-handler');
8
+ const chalk = require('chalk');
9
+
10
+ /**
11
+ * Unified command wrapper that provides:
12
+ * - Consistent error handling
13
+ * - Standardized output formatting
14
+ * - Execution timing
15
+ * - JSON output support
16
+ *
17
+ * @param {Function} commandFn - Async command function to execute
18
+ * @param {Object} options - Command options
19
+ * @param {string} options.commandName - Name of the command (for error context)
20
+ * @param {boolean} [options.includeTiming=true] - Include execution timing
21
+ * @param {boolean} [options.exitOnError=true] - Exit process on error
22
+ * @param {Object} [options.context={}] - Additional context for error handling
23
+ * @returns {Promise<any>} Command result
24
+ */
25
+ async function commandWrapper(commandFn, options = {}) {
26
+ const {
27
+ commandName = 'command',
28
+ includeTiming = true,
29
+ exitOnError = true,
30
+ context = {},
31
+ } = options;
32
+
33
+ return safeAsync(
34
+ async () => {
35
+ try {
36
+ const result = await commandFn();
37
+ return result;
38
+ } catch (error) {
39
+ // Enhance error with command context
40
+ error.commandName = commandName;
41
+ error.context = { ...context, ...error.context };
42
+
43
+ // Handle error with unified handler
44
+ handleCliError(
45
+ error,
46
+ {
47
+ command: commandName,
48
+ ...context,
49
+ },
50
+ exitOnError
51
+ );
52
+
53
+ // If exitOnError is false, rethrow for caller to handle
54
+ if (!exitOnError) {
55
+ throw error;
56
+ }
57
+ }
58
+ },
59
+ commandName,
60
+ includeTiming
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Unified output utilities for consistent formatting
66
+ */
67
+ const Output = {
68
+ /**
69
+ * Output success message
70
+ * @param {string} message - Success message
71
+ * @param {Object} [data] - Additional data to output
72
+ */
73
+ success(message, data = {}) {
74
+ if (isJsonOutput()) {
75
+ outputResult(
76
+ {
77
+ success: true,
78
+ message,
79
+ ...data,
80
+ },
81
+ true
82
+ );
83
+ } else {
84
+ console.log(chalk.green(`✅ ${message}`));
85
+ if (Object.keys(data).length > 0 && !isJsonOutput()) {
86
+ console.log(chalk.gray(JSON.stringify(data, null, 2)));
87
+ }
88
+ }
89
+ },
90
+
91
+ /**
92
+ * Output error message
93
+ * @param {string} message - Error message
94
+ * @param {string[]} [suggestions] - Recovery suggestions
95
+ */
96
+ error(message, suggestions = []) {
97
+ if (isJsonOutput()) {
98
+ outputResult(
99
+ {
100
+ success: false,
101
+ error: {
102
+ message,
103
+ suggestions,
104
+ },
105
+ },
106
+ false
107
+ );
108
+ } else {
109
+ console.error(chalk.red(`❌ ${message}`));
110
+ if (suggestions.length > 0) {
111
+ console.error(chalk.yellow('\n💡 Suggestions:'));
112
+ suggestions.forEach((suggestion) => {
113
+ console.error(chalk.yellow(` ${suggestion}`));
114
+ });
115
+ }
116
+ }
117
+ },
118
+
119
+ /**
120
+ * Output warning message
121
+ * @param {string} message - Warning message
122
+ * @param {string} [suggestion] - Optional suggestion
123
+ */
124
+ warning(message, suggestion = null) {
125
+ if (isJsonOutput()) {
126
+ outputResult(
127
+ {
128
+ warning: true,
129
+ message,
130
+ suggestion,
131
+ },
132
+ true
133
+ );
134
+ } else {
135
+ console.warn(chalk.yellow(`⚠️ ${message}`));
136
+ if (suggestion) {
137
+ console.warn(chalk.blue(` 💡 ${suggestion}`));
138
+ }
139
+ }
140
+ },
141
+
142
+ /**
143
+ * Output info message
144
+ * @param {string} message - Info message
145
+ * @param {Object} [data] - Additional data
146
+ */
147
+ info(message, data = {}) {
148
+ if (isJsonOutput()) {
149
+ outputResult(
150
+ {
151
+ info: true,
152
+ message,
153
+ ...data,
154
+ },
155
+ true
156
+ );
157
+ } else {
158
+ console.log(chalk.blue(`ℹ️ ${message}`));
159
+ if (Object.keys(data).length > 0) {
160
+ console.log(chalk.gray(JSON.stringify(data, null, 2)));
161
+ }
162
+ }
163
+ },
164
+
165
+ /**
166
+ * Output data in JSON format
167
+ * @param {Object} data - Data to output
168
+ * @param {boolean} [success=true] - Whether operation was successful
169
+ */
170
+ json(data, success = true) {
171
+ outputResult(data, success);
172
+ },
173
+
174
+ /**
175
+ * Output progress message
176
+ * @param {string} message - Progress message
177
+ */
178
+ progress(message) {
179
+ if (!isJsonOutput()) {
180
+ console.log(chalk.blue(`🔄 ${message}`));
181
+ }
182
+ },
183
+
184
+ /**
185
+ * Output section header
186
+ * @param {string} title - Section title
187
+ */
188
+ section(title) {
189
+ if (!isJsonOutput()) {
190
+ console.log(chalk.bold(`\n${title}`));
191
+ console.log('─'.repeat(Math.min(title.length, 60)));
192
+ }
193
+ },
194
+ };
195
+
196
+ module.exports = {
197
+ commandWrapper,
198
+ Output,
199
+ isJsonOutput,
200
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @fileoverview Promise Utilities
3
+ * Utilities for converting callback-based APIs to promises
4
+ * @author @darianrosebrook
5
+ */
6
+
7
+ /**
8
+ * Convert readline question to promise
9
+ * @param {readline.Interface} rl - Readline interface
10
+ * @param {string} question - Question to ask
11
+ * @returns {Promise<string>} User's answer
12
+ */
13
+ function question(rl, questionText) {
14
+ return new Promise((resolve) => {
15
+ rl.question(questionText, (answer) => {
16
+ resolve(answer);
17
+ });
18
+ });
19
+ }
20
+
21
+ /**
22
+ * Close readline interface and return promise
23
+ * @param {readline.Interface} rl - Readline interface
24
+ * @returns {Promise<void>}
25
+ */
26
+ function closeReadline(rl) {
27
+ return new Promise((resolve) => {
28
+ rl.once('close', resolve);
29
+ rl.close();
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Create a promise that resolves when event fires
35
+ * @param {EventEmitter} emitter - Event emitter
36
+ * @param {string} event - Event name
37
+ * @param {Object} options - Options
38
+ * @param {number} [options.timeout] - Timeout in ms
39
+ * @returns {Promise<any>} Event data
40
+ */
41
+ function once(emitter, event, options = {}) {
42
+ return new Promise((resolve, reject) => {
43
+ const { timeout } = options;
44
+
45
+ const timeoutId = timeout
46
+ ? // eslint-disable-next-line no-undef
47
+ setTimeout(() => {
48
+ emitter.removeListener(event, handler);
49
+ reject(new Error(`Event '${event}' timed out after ${timeout}ms`));
50
+ }, timeout)
51
+ : null;
52
+
53
+ const handler = (...args) => {
54
+ if (timeoutId) {
55
+ // eslint-disable-next-line no-undef
56
+ clearTimeout(timeoutId);
57
+ }
58
+ emitter.removeListener(event, handler);
59
+ resolve(args.length === 1 ? args[0] : args);
60
+ };
61
+
62
+ emitter.once(event, handler);
63
+ });
64
+ }
65
+
66
+ module.exports = {
67
+ question,
68
+ closeReadline,
69
+ once,
70
+ };
71
+
72
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paths.design/caws-cli",
3
- "version": "5.0.1",
3
+ "version": "6.0.0",
4
4
  "description": "CAWS CLI - Coding Agent Workflow System command line tools",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -16,12 +16,19 @@ COMMAND=$(echo "$INPUT" | jq -r '.command // ""')
16
16
  CWD=$(echo "$INPUT" | jq -r '.cwd // ""')
17
17
 
18
18
  # Hard blocks - never allow these
19
+ # CRITICAL: These commands can cause catastrophic data loss
20
+ # git init, git reset --hard, and git push --force were added after an incident
21
+ # where an agent panicked at quality gates and wiped thousands of lines of work
19
22
  HARD_BLOCKS=(
20
23
  "rm -rf /"
21
24
  "rm -rf /*"
22
25
  "rm -rf ~"
23
26
  "rm -rf $HOME"
24
27
  "> /dev/sda"
28
+ "git init" # Can wipe entire git history and stashed changes
29
+ "git commit --amend --no-edit" # Can rewrite commit history destructively
30
+ "git reset --hard" # Can lose uncommitted work and stashed changes
31
+ "git push --force" # Can overwrite remote repository history
25
32
  "dd if="
26
33
  "mkfs"
27
34
  "format c:"
@@ -38,10 +45,9 @@ for blocked in "${HARD_BLOCKS[@]}"; do
38
45
  done
39
46
 
40
47
  # Ask permission for risky operations
48
+ # Note: git commands moved to HARD_BLOCKS after catastrophic data loss incident
41
49
  ASK_PERMISSION=(
42
50
  "rm -rf"
43
- "git push --force"
44
- "git reset --hard"
45
51
  "npm publish"
46
52
  "docker rmi"
47
53
  "docker system prune"
@@ -17,13 +17,11 @@ This directory contains modular rule files that Cursor uses to guide development
17
17
  - `06-typescript-conventions.mdc` - TypeScript/JS conventions and best practices
18
18
  - `07-process-ops.mdc` - Process discipline and server management
19
19
  - `08-solid-and-architecture.mdc` - SOLID principles and architectural patterns
20
- - `09-docstrings.mdc` - Language-specific docstring formats and standards
21
- - `10-authorship-and-attribution.mdc` - File headers and authorship standards
22
- - `16-documentation-quality-standards.mdc` - Engineering-grade documentation standards
23
- - `17-scope-management-waivers.mdc` - Scope management, change budgets, and emergency waiver procedures
24
- - `18-implementation-completeness.mdc` - Anti-fake implementation guardrails and completeness verification
25
- - `19-language-agnostic-standards.mdc` - Universal engineering standards across all programming languages
26
- - `20-sophisticated-todo-detection.mdc` - Advanced TODO detection patterns and hidden implementation analysis
20
+ - `09-docstrings.mdc` - Language-specific docstring formats, standards, and file headers
21
+ - `10-documentation-quality-standards.mdc` - Engineering-grade documentation standards
22
+ - `11-scope-management-waivers.mdc` - Scope management, change budgets, and emergency waiver procedures
23
+ - `12-implementation-completeness.mdc` - Anti-fake implementation guardrails with sophisticated TODO detection
24
+ - `13-language-agnostic-standards.mdc` - Universal engineering standards (conditional - applies to code files only)
27
25
 
28
26
  ## How MDC Works
29
27
 
@@ -453,6 +453,29 @@ class HiddenTodoAnalyzer:
453
453
  # Console and logging
454
454
  r'console\.(log|warn|error|info)',
455
455
  r'\blogging\s+implementation\b',
456
+
457
+ # TODO system documentation (false positives when documenting TODO system itself)
458
+ r'\btodo\s+template\s+system\b',
459
+ r'\btodo\s+template\b',
460
+ r'\btodo\s+instance\b',
461
+ r'\btodo\s+step\b',
462
+ r'\btodo\s+integration\b',
463
+ r'\btodo\s+system\b',
464
+ r'\btodotemplate\b',
465
+ r'\btodoinstance\b',
466
+ r'\btodostep\b',
467
+ r'\btodointegration\b',
468
+ r'\btodotemplatesystem\b',
469
+ r'\btodoprogress\b',
470
+ r'\btododependency\b',
471
+ r'\btodoqualityenforcer\b',
472
+ r'\btodoworkflowhooks\b',
473
+ r'\btodostatus\b',
474
+ r'\btodopriority\b',
475
+ r'\btodosteptype\b',
476
+ # Rust doc comment patterns when mentioning TODO system types
477
+ r'^\s*//[!]/.*\btodo\b.*(template|instance|step|integration|system)\b',
478
+ r'^\s*///.*\btodo\b.*(template|instance|step|integration|system)\b',
456
479
  ]
457
480
 
458
481
  # Engineering-grade TODO template patterns (for suggestions)
@@ -612,6 +635,30 @@ class HiddenTodoAnalyzer:
612
635
  for indicator in self.documentation_indicators:
613
636
  if re.search(indicator, comment, re.IGNORECASE):
614
637
  return True
638
+
639
+ # Check for Rust doc comments (//! and ///) that mention TODO system types
640
+ todo_system_types = [
641
+ r'todo\s+template',
642
+ r'todo\s+instance',
643
+ r'todo\s+step',
644
+ r'todo\s+integration',
645
+ r'todo\s+system',
646
+ r'todotemplate',
647
+ r'todoinstance',
648
+ r'todostep',
649
+ r'todointegration',
650
+ r'todotemplatesystem',
651
+ r'todoprogress',
652
+ r'tododependency',
653
+ ]
654
+
655
+ # If comment mentions TODO system types and appears to be documentation, exclude it
656
+ if any(re.search(pattern, comment, re.IGNORECASE) for pattern in todo_system_types):
657
+ # Check if it's describing the system rather than a TODO item
658
+ # If it contains "TODO:" followed by a colon, it's likely a real TODO
659
+ if not re.search(r'\bTODO\s*:\s*', comment, re.IGNORECASE):
660
+ return True
661
+
615
662
  return False
616
663
 
617
664
  def has_todo_indicators(self, comment: str) -> bool:
@@ -1522,7 +1569,7 @@ class HiddenTodoAnalyzer:
1522
1569
  'blocking_analysis': {}
1523
1570
  }
1524
1571
 
1525
- print(f"📁 Found {len(staged_files)} staged files to analyze")
1572
+ print(f"Found {len(staged_files)} staged files to analyze")
1526
1573
 
1527
1574
  # Analyze staged files
1528
1575
  analysis_results = self.analyze_files(staged_files, min_confidence)
@@ -1,15 +0,0 @@
1
- ---
2
- description: File headers and authorship
3
- globs:
4
- alwaysApply: false
5
- ---
6
-
7
- # File Header & Attribution
8
-
9
- For top-of-file documentation, include author signature:
10
-
11
- ```
12
- Author: @darianrosebrook
13
- ```
14
-
15
- Only in source files with public entrypoints or non-trivial modules.