@paulduvall/claude-dev-toolkit 0.0.1-alpha.2 → 0.0.1-alpha.21

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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -37
  3. package/bin/claude-commands +307 -65
  4. package/commands/active/xarchitecture.md +393 -0
  5. package/commands/active/xconfig.md +127 -0
  6. package/commands/active/xcontinue.md +92 -0
  7. package/commands/active/xdebug.md +130 -0
  8. package/commands/active/xdocs.md +178 -0
  9. package/commands/active/xexplore.md +94 -0
  10. package/commands/active/xgit.md +149 -0
  11. package/commands/active/xpipeline.md +152 -0
  12. package/commands/active/xquality.md +96 -0
  13. package/commands/active/xrefactor.md +198 -0
  14. package/commands/active/xrelease.md +142 -0
  15. package/commands/active/xsecurity.md +92 -0
  16. package/commands/active/xspec.md +174 -0
  17. package/commands/active/xtdd.md +151 -0
  18. package/commands/active/xtest.md +89 -0
  19. package/commands/active/xverify.md +80 -0
  20. package/commands/experiments/xact.md +742 -0
  21. package/commands/experiments/xanalytics.md +113 -0
  22. package/commands/experiments/xanalyze.md +70 -0
  23. package/commands/experiments/xapi.md +161 -0
  24. package/commands/experiments/xatomic.md +112 -0
  25. package/commands/experiments/xaws.md +85 -0
  26. package/commands/experiments/xcicd.md +337 -0
  27. package/commands/experiments/xcommit.md +122 -0
  28. package/commands/experiments/xcompliance.md +182 -0
  29. package/commands/experiments/xconstraints.md +89 -0
  30. package/commands/experiments/xcoverage.md +90 -0
  31. package/commands/experiments/xdb.md +102 -0
  32. package/commands/experiments/xdesign.md +121 -0
  33. package/commands/experiments/xdevcontainer.md +238 -0
  34. package/commands/experiments/xevaluate.md +111 -0
  35. package/commands/experiments/xfootnote.md +12 -0
  36. package/commands/experiments/xgenerate.md +117 -0
  37. package/commands/experiments/xgovernance.md +149 -0
  38. package/commands/experiments/xgreen.md +66 -0
  39. package/commands/experiments/xiac.md +118 -0
  40. package/commands/experiments/xincident.md +137 -0
  41. package/commands/experiments/xinfra.md +115 -0
  42. package/commands/experiments/xknowledge.md +115 -0
  43. package/commands/experiments/xmaturity.md +120 -0
  44. package/commands/experiments/xmetrics.md +118 -0
  45. package/commands/experiments/xmonitoring.md +128 -0
  46. package/commands/experiments/xnew.md +903 -0
  47. package/commands/experiments/xobservable.md +114 -0
  48. package/commands/experiments/xoidc.md +165 -0
  49. package/commands/experiments/xoptimize.md +115 -0
  50. package/commands/experiments/xperformance.md +112 -0
  51. package/commands/experiments/xplanning.md +131 -0
  52. package/commands/experiments/xpolicy.md +115 -0
  53. package/commands/experiments/xproduct.md +98 -0
  54. package/commands/experiments/xreadiness.md +75 -0
  55. package/commands/experiments/xred.md +55 -0
  56. package/commands/experiments/xrisk.md +128 -0
  57. package/commands/experiments/xrules.md +124 -0
  58. package/commands/experiments/xsandbox.md +120 -0
  59. package/commands/experiments/xscan.md +102 -0
  60. package/commands/experiments/xsetup.md +123 -0
  61. package/commands/experiments/xtemplate.md +116 -0
  62. package/commands/experiments/xtrace.md +212 -0
  63. package/commands/experiments/xux.md +171 -0
  64. package/commands/experiments/xvalidate.md +104 -0
  65. package/commands/experiments/xworkflow.md +113 -0
  66. package/hooks/.smellrc.example.json +19 -0
  67. package/hooks/README.md +263 -0
  68. package/hooks/check-commit-signing.py +127 -0
  69. package/hooks/check-complexity.py +38 -0
  70. package/hooks/check-security.py +37 -0
  71. package/hooks/claude-wrapper.sh +29 -0
  72. package/hooks/config.py +110 -0
  73. package/hooks/file-logger.sh +100 -0
  74. package/hooks/lib/argument-parser.sh +427 -0
  75. package/hooks/lib/config-constants.sh +230 -0
  76. package/hooks/lib/context-manager.sh +560 -0
  77. package/hooks/lib/error-handler.sh +423 -0
  78. package/hooks/lib/execution-engine.sh +444 -0
  79. package/hooks/lib/execution-results.sh +113 -0
  80. package/hooks/lib/execution-simulation.sh +114 -0
  81. package/hooks/lib/field-validators.sh +104 -0
  82. package/hooks/lib/file-utils.sh +398 -0
  83. package/hooks/lib/subagent-discovery.sh +468 -0
  84. package/hooks/lib/subagent-validator.sh +407 -0
  85. package/hooks/lib/validation-reporter.sh +134 -0
  86. package/hooks/on-error-debug.sh +226 -0
  87. package/hooks/pre-commit-quality.sh +204 -0
  88. package/hooks/pre-commit-test-runner.sh +132 -0
  89. package/hooks/pre-write-security.sh +115 -0
  90. package/hooks/prevent-credential-exposure.sh +279 -0
  91. package/hooks/security_bandit.py +177 -0
  92. package/hooks/security_checks.py +97 -0
  93. package/hooks/security_secrets.py +81 -0
  94. package/hooks/security_trojan.py +61 -0
  95. package/hooks/settings.example.json +52 -0
  96. package/hooks/smell_checks.py +238 -0
  97. package/hooks/smell_javascript.py +231 -0
  98. package/hooks/smell_python.py +110 -0
  99. package/hooks/smell_ruff.py +70 -0
  100. package/hooks/smell_types.py +72 -0
  101. package/hooks/subagent-trigger-simple.sh +202 -0
  102. package/hooks/subagent-trigger.sh +253 -0
  103. package/hooks/suppression.py +82 -0
  104. package/hooks/tab-color.sh +70 -0
  105. package/hooks/verify-before-edit.sh +135 -0
  106. package/lib/backup-restore-command.js +140 -0
  107. package/lib/base/base-command.js +252 -0
  108. package/lib/base/command-result.js +184 -0
  109. package/lib/config/constants.js +255 -0
  110. package/lib/config.js +48 -6
  111. package/lib/configure-command.js +428 -0
  112. package/lib/dependency-validator.js +64 -5
  113. package/lib/hook-installer-core.js +2 -2
  114. package/lib/installation-instruction-generator.js +213 -495
  115. package/lib/installer.js +134 -56
  116. package/lib/oidc-command.js +740 -0
  117. package/lib/services/backup-list-service.js +226 -0
  118. package/lib/services/backup-service.js +230 -0
  119. package/lib/services/command-installer-service.js +217 -0
  120. package/lib/services/logger-service.js +201 -0
  121. package/lib/services/package-manager-service.js +319 -0
  122. package/lib/services/platform-instruction-service.js +294 -0
  123. package/lib/services/recovery-instruction-service.js +348 -0
  124. package/lib/services/restore-service.js +221 -0
  125. package/lib/setup-command.js +359 -0
  126. package/lib/setup-wizard.js +155 -262
  127. package/lib/uninstall-command.js +100 -0
  128. package/lib/utils/claude-path-config.js +184 -0
  129. package/lib/utils/file-system-utils.js +152 -0
  130. package/lib/utils.js +8 -4
  131. package/lib/verify-command.js +430 -0
  132. package/package.json +7 -3
  133. package/scripts/postinstall.js +172 -157
  134. package/subagents/debug-specialist.md +7 -0
  135. package/templates/README.md +115 -0
  136. package/templates/basic-settings.json +30 -0
  137. package/templates/comprehensive-settings.json +57 -0
  138. package/templates/global-claude.md +344 -0
  139. package/templates/hybrid-hook-config.yaml +132 -0
  140. package/templates/security-focused-settings.json +62 -0
  141. package/templates/subagent-hooks.yaml +188 -0
  142. package/lib/package-manager-service.js +0 -270
  143. package/subagents/debug-context.md +0 -197
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Claude Code Hook: Verify Before Edit
5
+ #
6
+ # Description: Warns about potentially fabricated references in file edits
7
+ # Purpose: Prevent use of fabricated URLs, account IDs, or asset paths by validating references
8
+ # Trigger: PreToolUse
9
+ # Blocking: No
10
+ # Tools: Edit, Write, MultiEdit
11
+ # Author: Claude Dev Toolkit
12
+ # Version: 1.0.0
13
+ # Category: security
14
+ #
15
+ # This hook checks for placeholder or fabricated identifiers to prevent
16
+ # accidental use of invalid references in code and configuration files.
17
+
18
+ ##################################
19
+ # Configuration
20
+ ##################################
21
+ HOOK_NAME="verify-before-edit"
22
+ LOG_FILE="$HOME/.claude/logs/verify-before-edit.log"
23
+
24
+ mkdir -p "$(dirname "$LOG_FILE")"
25
+ chmod 700 "$(dirname "$LOG_FILE")"
26
+ touch "$LOG_FILE"
27
+ chmod 600 "$LOG_FILE"
28
+
29
+ ##################################
30
+ # Logging
31
+ ##################################
32
+ log() {
33
+ echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$HOOK_NAME] $*" >> "$LOG_FILE"
34
+ }
35
+
36
+ warn() {
37
+ echo "[WARN] $*" >&2
38
+ log "WARNING: $*"
39
+ }
40
+
41
+ ##################################
42
+ # Skip Checks
43
+ ##################################
44
+ should_skip_file() {
45
+ local file="$1"
46
+ case "$file" in
47
+ *test*|*spec*|*example*|*fixture*|*mock*|*stub*)
48
+ return 0 ;;
49
+ *)
50
+ return 1 ;;
51
+ esac
52
+ }
53
+
54
+ ##################################
55
+ # Pattern Scanning
56
+ ##################################
57
+ scan_for_placeholders() {
58
+ local content="$1"
59
+
60
+ local patterns=(
61
+ 'your-api-key-here'
62
+ 'REPLACE_ME'
63
+ '<INSERT>'
64
+ 'your-.*-here'
65
+ 'xxx-.*-xxx'
66
+ 'TODO:.*http'
67
+ 'placeholder'
68
+ )
69
+
70
+ for pattern in "${patterns[@]}"; do
71
+ if echo "$content" | grep -qiE "$pattern"; then
72
+ warn "Suspicious placeholder detected: $pattern"
73
+ fi
74
+ done
75
+ }
76
+
77
+ scan_for_fabricated_ids() {
78
+ local content="$1"
79
+
80
+ if echo "$content" | grep -qE '000000|123456|111111'; then
81
+ warn "Sequential/zero ID detected — check if valid"
82
+ fi
83
+
84
+ if echo "$content" | grep -qE 'G-[A-Z0-9]{10}|UA-[0-9]{9}' 2>/dev/null; then
85
+ warn "Analytics ID found — validate against project config"
86
+ fi
87
+ }
88
+
89
+ ##################################
90
+ # Main Hook Logic
91
+ ##################################
92
+ main() {
93
+ local tool_name="${CLAUDE_TOOL:-unknown}"
94
+ local file_path="${CLAUDE_FILE:-}"
95
+ local content="${CLAUDE_CONTENT:-}"
96
+
97
+ log "Hook triggered for tool: $tool_name, file: $file_path"
98
+
99
+ case "$tool_name" in
100
+ "Edit"|"Write"|"MultiEdit") ;;
101
+ *)
102
+ log "Skipping non-edit tool: $tool_name"
103
+ exit 0 ;;
104
+ esac
105
+
106
+ if should_skip_file "$file_path"; then
107
+ log "Skipping check for test/example file: $file_path"
108
+ exit 0
109
+ fi
110
+
111
+ if [[ -z "$content" ]] && [[ -n "$file_path" ]] && [[ -f "$file_path" ]]; then
112
+ content=$(cat "$file_path" 2>/dev/null || echo "")
113
+ fi
114
+
115
+ if [[ -z "$content" ]]; then
116
+ log "No content to validate"
117
+ exit 0
118
+ fi
119
+
120
+ scan_for_placeholders "$content"
121
+ scan_for_fabricated_ids "$content"
122
+
123
+ log "Security validation complete for $file_path"
124
+ exit 0
125
+ }
126
+
127
+ ##################################
128
+ # Error Handling
129
+ ##################################
130
+ trap 'log "Hook failed with error on line $LINENO"' ERR
131
+
132
+ ##################################
133
+ # Execute
134
+ ##################################
135
+ main "$@"
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Backup and Restore Commands Implementation - Refactored
3
+ * Orchestrator for backup and restore operations using focused services
4
+ */
5
+
6
+ const BackupService = require('./services/backup-service');
7
+ const RestoreService = require('./services/restore-service');
8
+ const BackupListService = require('./services/backup-list-service');
9
+ const BaseCommand = require('./base/base-command');
10
+ const FileSystemUtils = require('./utils/file-system-utils');
11
+ const { execSync } = require('child_process');
12
+
13
+ class BackupRestoreCommand extends BaseCommand {
14
+ constructor(config = null) {
15
+ super(config);
16
+ this.backupService = new BackupService(this.config);
17
+ this.restoreService = new RestoreService(this.config);
18
+ this.listService = new BackupListService(this.config);
19
+ }
20
+
21
+ /**
22
+ * Create a backup of the entire .claude directory
23
+ */
24
+ async backup(name = null) {
25
+ this.logger.step('Creating backup of Claude Code configuration', { backupName: name });
26
+
27
+ try {
28
+ const result = await this.backupService.create(name);
29
+
30
+ // Try to compress the backup
31
+ const compressed = await this.compressBackup(result.path);
32
+
33
+ this.logger.complete(`Backup '${result.name}' created successfully`, {
34
+ files: result.totalFiles,
35
+ size: FileSystemUtils.formatSize(result.totalSize),
36
+ compressed: !!compressed
37
+ });
38
+
39
+ if (compressed) {
40
+ this.logger.info(`Backup compressed and stored at: ${compressed.path}`, {
41
+ compressionRatio: compressed.size / result.totalSize
42
+ });
43
+ } else {
44
+ this.logger.info(`Backup stored at: ${result.path}`);
45
+ }
46
+
47
+ return {
48
+ success: true,
49
+ name: result.name,
50
+ path: compressed ? compressed.path : result.path,
51
+ metadata: result.metadata
52
+ };
53
+
54
+ } catch (error) {
55
+ return this.handleError(error);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Try to compress a backup using tar
61
+ */
62
+ async compressBackup(backupPath) {
63
+ try {
64
+ const backupName = require('path').basename(backupPath);
65
+ const tarPath = `${backupPath}.tar.gz`;
66
+
67
+ execSync(`tar -czf "${tarPath}" -C "${this.config.backupsDir}" "${backupName}"`, {
68
+ encoding: 'utf8',
69
+ stdio: 'pipe'
70
+ });
71
+
72
+ // Remove uncompressed backup
73
+ FileSystemUtils.remove(backupPath);
74
+
75
+ const compressedSize = FileSystemUtils.getStats(tarPath).size;
76
+ return {
77
+ path: tarPath,
78
+ size: compressedSize
79
+ };
80
+ } catch (error) {
81
+ // Compression failed, keep uncompressed backup
82
+ return null;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Restore from a backup
88
+ */
89
+ async restore(backupName) {
90
+ this.logger.step(`Restoring from backup: ${backupName}`, { backupName });
91
+
92
+ try {
93
+ // Create undo backup first
94
+ this.logger.info('Creating undo backup for safety');
95
+ const undoBackup = await this.backup('undo-before-restore');
96
+
97
+ if (!undoBackup.success) {
98
+ this.logger.warn('Could not create undo backup, continuing anyway', {
99
+ risk: 'restore cannot be undone'
100
+ });
101
+ }
102
+
103
+ // Perform restore using service
104
+ const result = await this.restoreService.restore(backupName);
105
+
106
+ this.logger.complete(`Restore completed successfully`, {
107
+ restoredCount: result.restoredCount,
108
+ hasUndo: undoBackup.success
109
+ });
110
+
111
+ if (undoBackup.success) {
112
+ this.logger.info(`To undo this restore, run: claude-commands restore ${undoBackup.name}`, {
113
+ undoCommand: `claude-commands restore ${undoBackup.name}`
114
+ });
115
+ }
116
+
117
+ return {
118
+ success: true,
119
+ restoredCount: result.restoredCount,
120
+ undoBackup: undoBackup.success ? undoBackup.name : null
121
+ };
122
+
123
+ } catch (error) {
124
+ return this.handleError(error);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * List available backups
130
+ */
131
+ async listBackups() {
132
+ try {
133
+ return await this.listService.display();
134
+ } catch (error) {
135
+ return this.handleError(error);
136
+ }
137
+ }
138
+ }
139
+
140
+ module.exports = BackupRestoreCommand;
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Base Command Pattern
3
+ * Abstract base class for all CLI commands with standardized error handling
4
+ */
5
+
6
+ const ClaudePathConfig = require('../utils/claude-path-config');
7
+ const FileSystemUtils = require('../utils/file-system-utils');
8
+ const LoggerService = require('../services/logger-service');
9
+
10
+ class BaseCommand {
11
+ constructor(config = null, logger = null) {
12
+ this.config = config || new ClaudePathConfig();
13
+ this.logger = logger || new LoggerService();
14
+ this.startTime = null;
15
+ this.metrics = {
16
+ filesProcessed: 0,
17
+ operationsPerformed: 0,
18
+ errorsEncountered: 0
19
+ };
20
+
21
+ // Set logger context for this command
22
+ this.logger.setContext({
23
+ command: this.constructor.name,
24
+ timestamp: new Date().toISOString()
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Main execution method with standardized error handling
30
+ */
31
+ async execute(options = {}) {
32
+ this.startTime = Date.now();
33
+
34
+ try {
35
+ // Pre-execution validation
36
+ const preValidation = await this.preValidate(options);
37
+ if (!preValidation.success) {
38
+ return this.createFailureResult(preValidation.error, preValidation);
39
+ }
40
+
41
+ // Execute the main command logic
42
+ const result = await this.run(options);
43
+
44
+ // Post-execution cleanup
45
+ await this.postExecute(result, options);
46
+
47
+ return this.createSuccessResult(result);
48
+
49
+ } catch (error) {
50
+ this.metrics.errorsEncountered++;
51
+ return this.handleError(error, options);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Abstract method - must be implemented by subclasses
57
+ */
58
+ async run(options) {
59
+ throw new Error('Subclasses must implement the run() method');
60
+ }
61
+
62
+ /**
63
+ * Pre-execution validation hook
64
+ */
65
+ async preValidate(options) {
66
+ // Default validation - can be overridden
67
+ return { success: true };
68
+ }
69
+
70
+ /**
71
+ * Post-execution cleanup hook
72
+ */
73
+ async postExecute(result, options) {
74
+ // Default post-execution - can be overridden
75
+ const duration = this.getDuration();
76
+
77
+ if (process.env.NODE_ENV === 'development' || options.verbose) {
78
+ console.log(`\n📊 Command completed in ${duration}s`);
79
+ if (this.metrics.filesProcessed > 0) {
80
+ console.log(` Files processed: ${this.metrics.filesProcessed}`);
81
+ }
82
+ if (this.metrics.operationsPerformed > 0) {
83
+ console.log(` Operations: ${this.metrics.operationsPerformed}`);
84
+ }
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Standardized error handling
90
+ */
91
+ handleError(error, options = {}) {
92
+ const errorDetails = this.analyzeError(error);
93
+
94
+ // Log error appropriately
95
+ if (options.verbose || process.env.NODE_ENV === 'development') {
96
+ console.error(`💥 ${this.constructor.name} Error Details:`, error);
97
+ } else {
98
+ console.error(`❌ ${errorDetails.message}`);
99
+ }
100
+
101
+ // Provide helpful guidance
102
+ if (errorDetails.suggestions.length > 0) {
103
+ console.log('\n💡 Suggestions:');
104
+ errorDetails.suggestions.forEach(suggestion => {
105
+ console.log(` • ${suggestion}`);
106
+ });
107
+ }
108
+
109
+ return this.createFailureResult(errorDetails.message, {
110
+ type: errorDetails.type,
111
+ suggestions: errorDetails.suggestions,
112
+ originalError: options.verbose ? error : undefined
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Analyze error type and generate helpful suggestions
118
+ */
119
+ analyzeError(error) {
120
+ const message = error.message || 'Unknown error occurred';
121
+ const suggestions = [];
122
+ let type = 'general';
123
+
124
+ // File system errors
125
+ if (message.includes('ENOENT') || message.includes('no such file')) {
126
+ type = 'file_not_found';
127
+ suggestions.push('Check that the file or directory exists');
128
+ suggestions.push('Verify you have the correct path');
129
+ } else if (message.includes('EACCES') || message.includes('permission denied')) {
130
+ type = 'permission_error';
131
+ suggestions.push('Check file/directory permissions');
132
+ suggestions.push('Try running with appropriate permissions');
133
+ } else if (message.includes('ENOTDIR') || message.includes('not a directory')) {
134
+ type = 'path_error';
135
+ suggestions.push('Verify the path is correct');
136
+ suggestions.push('Check that parent directories exist');
137
+ }
138
+ // Network errors
139
+ else if (message.includes('ECONNREFUSED') || message.includes('network')) {
140
+ type = 'network_error';
141
+ suggestions.push('Check your internet connection');
142
+ suggestions.push('Verify proxy settings if applicable');
143
+ }
144
+ // Git errors
145
+ else if (message.includes('git') || message.includes('Not a git repository')) {
146
+ type = 'git_error';
147
+ suggestions.push('Ensure you are in a git repository');
148
+ suggestions.push('Run "git init" if needed');
149
+ }
150
+ // Claude Code errors
151
+ else if (message.includes('claude') || message.includes('settings')) {
152
+ type = 'claude_error';
153
+ suggestions.push('Run "claude-commands verify" to check installation');
154
+ suggestions.push('Try "claude-commands setup" to reconfigure');
155
+ }
156
+
157
+ return {
158
+ message: this.sanitizeErrorMessage(message),
159
+ type,
160
+ suggestions
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Sanitize error messages for user consumption
166
+ */
167
+ sanitizeErrorMessage(message) {
168
+ // Remove internal paths and stack traces
169
+ return message
170
+ .replace(/\/Users\/[^\/]+\/[^\s]+/g, '~/<path>')
171
+ .replace(/\s+at\s+.*/g, '')
172
+ .replace(/Error:\s*/g, '')
173
+ .trim();
174
+ }
175
+
176
+ /**
177
+ * Create standardized success result
178
+ */
179
+ createSuccessResult(data = {}) {
180
+ return {
181
+ success: true,
182
+ duration: this.getDuration(),
183
+ metrics: { ...this.metrics },
184
+ timestamp: new Date().toISOString(),
185
+ ...data
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Create standardized failure result
191
+ */
192
+ createFailureResult(message, data = {}) {
193
+ return {
194
+ success: false,
195
+ error: message,
196
+ duration: this.getDuration(),
197
+ metrics: { ...this.metrics },
198
+ timestamp: new Date().toISOString(),
199
+ ...data
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Get execution duration in seconds
205
+ */
206
+ getDuration() {
207
+ if (!this.startTime) return 0;
208
+ return ((Date.now() - this.startTime) / 1000).toFixed(2);
209
+ }
210
+
211
+ /**
212
+ * Helper method to ensure directories exist
213
+ */
214
+ ensureDirectoryExists(dirPath) {
215
+ FileSystemUtils.ensureDirectory(dirPath);
216
+ this.metrics.operationsPerformed++;
217
+ }
218
+
219
+ /**
220
+ * Helper method to safely read files
221
+ */
222
+ safeReadFile(filePath) {
223
+ const content = FileSystemUtils.readFile(filePath);
224
+ if (content !== null) {
225
+ this.metrics.filesProcessed++;
226
+ }
227
+ return content;
228
+ }
229
+
230
+ /**
231
+ * Helper method to safely write files
232
+ */
233
+ safeWriteFile(filePath, content, mode = 0o644) {
234
+ const success = FileSystemUtils.writeFile(filePath, content, mode);
235
+ if (success) {
236
+ this.metrics.filesProcessed++;
237
+ this.metrics.operationsPerformed++;
238
+ }
239
+ return success;
240
+ }
241
+
242
+ /**
243
+ * Display progress if verbose mode enabled
244
+ */
245
+ showProgress(message, options = {}) {
246
+ if (options.verbose || process.env.NODE_ENV === 'development') {
247
+ console.log(`🔄 ${message}`);
248
+ }
249
+ }
250
+ }
251
+
252
+ module.exports = BaseCommand;
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Command Result Value Object
3
+ * Standardized result structure for all command operations
4
+ */
5
+
6
+ class CommandResult {
7
+ constructor(success, data = {}) {
8
+ this.success = success;
9
+ this.timestamp = new Date().toISOString();
10
+
11
+ // Merge provided data
12
+ Object.assign(this, data);
13
+ }
14
+
15
+ /**
16
+ * Create successful result
17
+ */
18
+ static success(data = {}) {
19
+ return new CommandResult(true, data);
20
+ }
21
+
22
+ /**
23
+ * Create failure result
24
+ */
25
+ static failure(error, data = {}) {
26
+ return new CommandResult(false, {
27
+ error: typeof error === 'string' ? error : error.message,
28
+ originalError: typeof error === 'object' ? error : undefined,
29
+ ...data
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Create result from boolean
35
+ */
36
+ static fromBoolean(isSuccess, successData = {}, failureData = {}) {
37
+ return isSuccess
38
+ ? CommandResult.success(successData)
39
+ : CommandResult.failure('Operation failed', failureData);
40
+ }
41
+
42
+ /**
43
+ * Check if result indicates success
44
+ */
45
+ isSuccess() {
46
+ return this.success === true;
47
+ }
48
+
49
+ /**
50
+ * Check if result indicates failure
51
+ */
52
+ isFailure() {
53
+ return this.success === false;
54
+ }
55
+
56
+ /**
57
+ * Get error message if failure
58
+ */
59
+ getError() {
60
+ return this.success ? null : (this.error || 'Unknown error');
61
+ }
62
+
63
+ /**
64
+ * Get data payload
65
+ */
66
+ getData(key = null) {
67
+ if (key) {
68
+ return this[key];
69
+ }
70
+
71
+ // Return all data except control properties
72
+ const { success, timestamp, error, originalError, ...data } = this;
73
+ return data;
74
+ }
75
+
76
+ /**
77
+ * Transform result with a function
78
+ */
79
+ map(transform) {
80
+ if (this.isFailure()) {
81
+ return this; // Return unchanged failure
82
+ }
83
+
84
+ try {
85
+ const transformedData = transform(this.getData());
86
+ return CommandResult.success(transformedData);
87
+ } catch (error) {
88
+ return CommandResult.failure(error);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Chain operations that might fail
94
+ */
95
+ flatMap(operation) {
96
+ if (this.isFailure()) {
97
+ return this; // Return unchanged failure
98
+ }
99
+
100
+ try {
101
+ const result = operation(this.getData());
102
+ return result instanceof CommandResult ? result : CommandResult.success(result);
103
+ } catch (error) {
104
+ return CommandResult.failure(error);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Provide fallback value for failures
110
+ */
111
+ orElse(fallback) {
112
+ return this.isSuccess() ? this.getData() : fallback;
113
+ }
114
+
115
+ /**
116
+ * Convert to JSON for serialization
117
+ */
118
+ toJSON() {
119
+ return {
120
+ success: this.success,
121
+ timestamp: this.timestamp,
122
+ ...(this.success ? this.getData() : { error: this.getError() })
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Convert to human-readable string
128
+ */
129
+ toString() {
130
+ if (this.success) {
131
+ const data = this.getData();
132
+ const keys = Object.keys(data);
133
+
134
+ if (keys.length === 0) {
135
+ return '✅ Success';
136
+ } else if (keys.length === 1) {
137
+ return `✅ Success: ${keys[0]} = ${data[keys[0]]}`;
138
+ } else {
139
+ return `✅ Success (${keys.length} properties)`;
140
+ }
141
+ } else {
142
+ return `❌ Failure: ${this.getError()}`;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Merge with another result (for combining operations)
148
+ */
149
+ merge(otherResult) {
150
+ if (this.isFailure()) return this;
151
+ if (otherResult.isFailure()) return otherResult;
152
+
153
+ return CommandResult.success({
154
+ ...this.getData(),
155
+ ...otherResult.getData()
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Add metrics to the result
161
+ */
162
+ withMetrics(metrics) {
163
+ this.metrics = metrics;
164
+ return this;
165
+ }
166
+
167
+ /**
168
+ * Add duration to the result
169
+ */
170
+ withDuration(duration) {
171
+ this.duration = typeof duration === 'number' ? `${duration.toFixed(2)}s` : duration;
172
+ return this;
173
+ }
174
+
175
+ /**
176
+ * Add context information
177
+ */
178
+ withContext(context) {
179
+ this.context = context;
180
+ return this;
181
+ }
182
+ }
183
+
184
+ module.exports = CommandResult;