@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.
- package/LICENSE +21 -0
- package/README.md +88 -37
- package/bin/claude-commands +307 -65
- package/commands/active/xarchitecture.md +393 -0
- package/commands/active/xconfig.md +127 -0
- package/commands/active/xcontinue.md +92 -0
- package/commands/active/xdebug.md +130 -0
- package/commands/active/xdocs.md +178 -0
- package/commands/active/xexplore.md +94 -0
- package/commands/active/xgit.md +149 -0
- package/commands/active/xpipeline.md +152 -0
- package/commands/active/xquality.md +96 -0
- package/commands/active/xrefactor.md +198 -0
- package/commands/active/xrelease.md +142 -0
- package/commands/active/xsecurity.md +92 -0
- package/commands/active/xspec.md +174 -0
- package/commands/active/xtdd.md +151 -0
- package/commands/active/xtest.md +89 -0
- package/commands/active/xverify.md +80 -0
- package/commands/experiments/xact.md +742 -0
- package/commands/experiments/xanalytics.md +113 -0
- package/commands/experiments/xanalyze.md +70 -0
- package/commands/experiments/xapi.md +161 -0
- package/commands/experiments/xatomic.md +112 -0
- package/commands/experiments/xaws.md +85 -0
- package/commands/experiments/xcicd.md +337 -0
- package/commands/experiments/xcommit.md +122 -0
- package/commands/experiments/xcompliance.md +182 -0
- package/commands/experiments/xconstraints.md +89 -0
- package/commands/experiments/xcoverage.md +90 -0
- package/commands/experiments/xdb.md +102 -0
- package/commands/experiments/xdesign.md +121 -0
- package/commands/experiments/xdevcontainer.md +238 -0
- package/commands/experiments/xevaluate.md +111 -0
- package/commands/experiments/xfootnote.md +12 -0
- package/commands/experiments/xgenerate.md +117 -0
- package/commands/experiments/xgovernance.md +149 -0
- package/commands/experiments/xgreen.md +66 -0
- package/commands/experiments/xiac.md +118 -0
- package/commands/experiments/xincident.md +137 -0
- package/commands/experiments/xinfra.md +115 -0
- package/commands/experiments/xknowledge.md +115 -0
- package/commands/experiments/xmaturity.md +120 -0
- package/commands/experiments/xmetrics.md +118 -0
- package/commands/experiments/xmonitoring.md +128 -0
- package/commands/experiments/xnew.md +903 -0
- package/commands/experiments/xobservable.md +114 -0
- package/commands/experiments/xoidc.md +165 -0
- package/commands/experiments/xoptimize.md +115 -0
- package/commands/experiments/xperformance.md +112 -0
- package/commands/experiments/xplanning.md +131 -0
- package/commands/experiments/xpolicy.md +115 -0
- package/commands/experiments/xproduct.md +98 -0
- package/commands/experiments/xreadiness.md +75 -0
- package/commands/experiments/xred.md +55 -0
- package/commands/experiments/xrisk.md +128 -0
- package/commands/experiments/xrules.md +124 -0
- package/commands/experiments/xsandbox.md +120 -0
- package/commands/experiments/xscan.md +102 -0
- package/commands/experiments/xsetup.md +123 -0
- package/commands/experiments/xtemplate.md +116 -0
- package/commands/experiments/xtrace.md +212 -0
- package/commands/experiments/xux.md +171 -0
- package/commands/experiments/xvalidate.md +104 -0
- package/commands/experiments/xworkflow.md +113 -0
- package/hooks/.smellrc.example.json +19 -0
- package/hooks/README.md +263 -0
- package/hooks/check-commit-signing.py +127 -0
- package/hooks/check-complexity.py +38 -0
- package/hooks/check-security.py +37 -0
- package/hooks/claude-wrapper.sh +29 -0
- package/hooks/config.py +110 -0
- package/hooks/file-logger.sh +100 -0
- package/hooks/lib/argument-parser.sh +427 -0
- package/hooks/lib/config-constants.sh +230 -0
- package/hooks/lib/context-manager.sh +560 -0
- package/hooks/lib/error-handler.sh +423 -0
- package/hooks/lib/execution-engine.sh +444 -0
- package/hooks/lib/execution-results.sh +113 -0
- package/hooks/lib/execution-simulation.sh +114 -0
- package/hooks/lib/field-validators.sh +104 -0
- package/hooks/lib/file-utils.sh +398 -0
- package/hooks/lib/subagent-discovery.sh +468 -0
- package/hooks/lib/subagent-validator.sh +407 -0
- package/hooks/lib/validation-reporter.sh +134 -0
- package/hooks/on-error-debug.sh +226 -0
- package/hooks/pre-commit-quality.sh +204 -0
- package/hooks/pre-commit-test-runner.sh +132 -0
- package/hooks/pre-write-security.sh +115 -0
- package/hooks/prevent-credential-exposure.sh +279 -0
- package/hooks/security_bandit.py +177 -0
- package/hooks/security_checks.py +97 -0
- package/hooks/security_secrets.py +81 -0
- package/hooks/security_trojan.py +61 -0
- package/hooks/settings.example.json +52 -0
- package/hooks/smell_checks.py +238 -0
- package/hooks/smell_javascript.py +231 -0
- package/hooks/smell_python.py +110 -0
- package/hooks/smell_ruff.py +70 -0
- package/hooks/smell_types.py +72 -0
- package/hooks/subagent-trigger-simple.sh +202 -0
- package/hooks/subagent-trigger.sh +253 -0
- package/hooks/suppression.py +82 -0
- package/hooks/tab-color.sh +70 -0
- package/hooks/verify-before-edit.sh +135 -0
- package/lib/backup-restore-command.js +140 -0
- package/lib/base/base-command.js +252 -0
- package/lib/base/command-result.js +184 -0
- package/lib/config/constants.js +255 -0
- package/lib/config.js +48 -6
- package/lib/configure-command.js +428 -0
- package/lib/dependency-validator.js +64 -5
- package/lib/hook-installer-core.js +2 -2
- package/lib/installation-instruction-generator.js +213 -495
- package/lib/installer.js +134 -56
- package/lib/oidc-command.js +740 -0
- package/lib/services/backup-list-service.js +226 -0
- package/lib/services/backup-service.js +230 -0
- package/lib/services/command-installer-service.js +217 -0
- package/lib/services/logger-service.js +201 -0
- package/lib/services/package-manager-service.js +319 -0
- package/lib/services/platform-instruction-service.js +294 -0
- package/lib/services/recovery-instruction-service.js +348 -0
- package/lib/services/restore-service.js +221 -0
- package/lib/setup-command.js +359 -0
- package/lib/setup-wizard.js +155 -262
- package/lib/uninstall-command.js +100 -0
- package/lib/utils/claude-path-config.js +184 -0
- package/lib/utils/file-system-utils.js +152 -0
- package/lib/utils.js +8 -4
- package/lib/verify-command.js +430 -0
- package/package.json +7 -3
- package/scripts/postinstall.js +172 -157
- package/subagents/debug-specialist.md +7 -0
- package/templates/README.md +115 -0
- package/templates/basic-settings.json +30 -0
- package/templates/comprehensive-settings.json +57 -0
- package/templates/global-claude.md +344 -0
- package/templates/hybrid-hook-config.yaml +132 -0
- package/templates/security-focused-settings.json +62 -0
- package/templates/subagent-hooks.yaml +188 -0
- package/lib/package-manager-service.js +0 -270
- 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;
|