@paths.design/caws-cli 8.0.1 → 8.2.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 (112) hide show
  1. package/README.md +5 -6
  2. package/dist/commands/archive.d.ts +1 -0
  3. package/dist/commands/archive.d.ts.map +1 -1
  4. package/dist/commands/archive.js +114 -6
  5. package/dist/commands/burnup.d.ts.map +1 -1
  6. package/dist/commands/burnup.js +109 -10
  7. package/dist/commands/diagnose.js +1 -1
  8. package/dist/commands/init.d.ts.map +1 -1
  9. package/dist/commands/init.js +185 -39
  10. package/dist/commands/mode.d.ts +2 -1
  11. package/dist/commands/mode.d.ts.map +1 -1
  12. package/dist/commands/mode.js +24 -14
  13. package/dist/commands/provenance.d.ts.map +1 -1
  14. package/dist/commands/provenance.js +216 -93
  15. package/dist/commands/quality-gates.d.ts.map +1 -1
  16. package/dist/commands/quality-gates.js +3 -1
  17. package/dist/commands/specs.d.ts.map +1 -1
  18. package/dist/commands/specs.js +184 -6
  19. package/dist/commands/status.d.ts.map +1 -1
  20. package/dist/commands/status.js +134 -10
  21. package/dist/commands/templates.js +2 -2
  22. package/dist/commands/worktree.d.ts +7 -0
  23. package/dist/commands/worktree.d.ts.map +1 -0
  24. package/dist/commands/worktree.js +136 -0
  25. package/dist/config/lite-scope.d.ts +33 -0
  26. package/dist/config/lite-scope.d.ts.map +1 -0
  27. package/dist/config/lite-scope.js +158 -0
  28. package/dist/config/modes.d.ts +90 -51
  29. package/dist/config/modes.d.ts.map +1 -1
  30. package/dist/config/modes.js +26 -0
  31. package/dist/error-handler.d.ts +3 -16
  32. package/dist/error-handler.d.ts.map +1 -1
  33. package/dist/error-handler.js +6 -98
  34. package/dist/generators/jest-config-generator.d.ts +32 -0
  35. package/dist/generators/jest-config-generator.d.ts.map +1 -0
  36. package/dist/generators/jest-config-generator.js +242 -0
  37. package/dist/index.js +40 -7
  38. package/dist/minimal-cli.js +3 -1
  39. package/dist/scaffold/claude-hooks.d.ts +28 -0
  40. package/dist/scaffold/claude-hooks.d.ts.map +1 -0
  41. package/dist/scaffold/claude-hooks.js +344 -0
  42. package/dist/scaffold/index.d.ts +2 -0
  43. package/dist/scaffold/index.d.ts.map +1 -1
  44. package/dist/scaffold/index.js +96 -76
  45. package/dist/templates/.caws/schemas/scope.schema.json +52 -0
  46. package/dist/templates/.caws/schemas/working-spec.schema.json +1 -1
  47. package/dist/templates/.caws/schemas/worktrees.schema.json +36 -0
  48. package/dist/templates/.claude/README.md +190 -0
  49. package/dist/templates/.claude/hooks/audit.sh +96 -0
  50. package/dist/templates/.claude/hooks/block-dangerous.sh +123 -0
  51. package/dist/templates/.claude/hooks/lite-sprawl-check.sh +117 -0
  52. package/dist/templates/.claude/hooks/naming-check.sh +97 -0
  53. package/dist/templates/.claude/hooks/quality-check.sh +68 -0
  54. package/dist/templates/.claude/hooks/scan-secrets.sh +85 -0
  55. package/dist/templates/.claude/hooks/scope-guard.sh +192 -0
  56. package/dist/templates/.claude/hooks/simplification-guard.sh +92 -0
  57. package/dist/templates/.claude/hooks/validate-spec.sh +76 -0
  58. package/dist/templates/.claude/settings.json +95 -0
  59. package/dist/templates/.cursor/README.md +0 -3
  60. package/dist/templates/.github/copilot-instructions.md +82 -0
  61. package/dist/templates/.junie/guidelines.md +73 -0
  62. package/dist/templates/.vscode/launch.json +0 -27
  63. package/dist/templates/.windsurf/rules/caws-quality-standards.md +54 -0
  64. package/dist/templates/CLAUDE.md +101 -0
  65. package/dist/templates/agents.md +73 -1016
  66. package/dist/templates/docs/README.md +5 -5
  67. package/dist/test-analysis.d.ts +50 -1
  68. package/dist/test-analysis.d.ts.map +1 -1
  69. package/dist/test-analysis.js +203 -10
  70. package/dist/utils/error-categories.d.ts +52 -0
  71. package/dist/utils/error-categories.d.ts.map +1 -0
  72. package/dist/utils/error-categories.js +210 -0
  73. package/dist/utils/gitignore-updater.d.ts +1 -1
  74. package/dist/utils/gitignore-updater.d.ts.map +1 -1
  75. package/dist/utils/gitignore-updater.js +4 -0
  76. package/dist/utils/ide-detection.js +133 -0
  77. package/dist/utils/quality-gates-utils.d.ts +49 -0
  78. package/dist/utils/quality-gates-utils.d.ts.map +1 -0
  79. package/dist/utils/quality-gates-utils.js +402 -0
  80. package/dist/utils/typescript-detector.d.ts +8 -5
  81. package/dist/utils/typescript-detector.d.ts.map +1 -1
  82. package/dist/utils/typescript-detector.js +36 -90
  83. package/dist/validation/spec-validation.d.ts.map +1 -1
  84. package/dist/validation/spec-validation.js +59 -6
  85. package/dist/worktree/worktree-manager.d.ts +54 -0
  86. package/dist/worktree/worktree-manager.d.ts.map +1 -0
  87. package/dist/worktree/worktree-manager.js +378 -0
  88. package/package.json +9 -3
  89. package/templates/.caws/schemas/scope.schema.json +52 -0
  90. package/templates/.caws/schemas/working-spec.schema.json +1 -1
  91. package/templates/.caws/schemas/worktrees.schema.json +36 -0
  92. package/templates/.claude/README.md +190 -0
  93. package/templates/.claude/hooks/audit.sh +96 -0
  94. package/templates/.claude/hooks/block-dangerous.sh +123 -0
  95. package/templates/.claude/hooks/lite-sprawl-check.sh +117 -0
  96. package/templates/.claude/hooks/naming-check.sh +97 -0
  97. package/templates/.claude/hooks/quality-check.sh +68 -0
  98. package/templates/.claude/hooks/scan-secrets.sh +85 -0
  99. package/templates/.claude/hooks/scope-guard.sh +192 -0
  100. package/templates/.claude/hooks/simplification-guard.sh +92 -0
  101. package/templates/.claude/hooks/validate-spec.sh +76 -0
  102. package/templates/.claude/settings.json +95 -0
  103. package/templates/.cursor/README.md +0 -3
  104. package/templates/.github/copilot-instructions.md +82 -0
  105. package/templates/.junie/guidelines.md +73 -0
  106. package/templates/.vscode/launch.json +0 -27
  107. package/templates/.windsurf/rules/caws-quality-standards.md +54 -0
  108. package/templates/AGENTS.md +104 -0
  109. package/templates/CLAUDE.md +101 -0
  110. package/templates/docs/README.md +5 -5
  111. package/templates/.github/copilot/instructions.md +0 -311
  112. package/templates/agents.md +0 -1047
@@ -0,0 +1,97 @@
1
+ #!/bin/bash
2
+ # CAWS Naming Convention Check Hook for Claude Code
3
+ # Validates file naming against CAWS conventions
4
+ # @author @darianrosebrook
5
+
6
+ set -euo pipefail
7
+
8
+ # Read JSON input from Claude Code
9
+ INPUT=$(cat)
10
+
11
+ # Extract file path from PostToolUse input
12
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
13
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
14
+
15
+ # Only check Write tool (new files)
16
+ if [[ "$TOOL_NAME" != "Write" ]]; then
17
+ exit 0
18
+ fi
19
+
20
+ if [[ -z "$FILE_PATH" ]]; then
21
+ exit 0
22
+ fi
23
+
24
+ # Get filename
25
+ FILENAME=$(basename "$FILE_PATH")
26
+
27
+ # Banned modifiers that indicate incomplete/temporary naming
28
+ BANNED_MODIFIERS=(
29
+ "enhanced"
30
+ "unified"
31
+ "simplified"
32
+ "better"
33
+ "new"
34
+ "next"
35
+ "final"
36
+ "copy"
37
+ "revamp"
38
+ "improved"
39
+ "alt"
40
+ "tmp"
41
+ "scratch"
42
+ "wip"
43
+ "test-"
44
+ "-test"
45
+ "_test"
46
+ "temp"
47
+ "old"
48
+ "backup"
49
+ )
50
+
51
+ # Convert filename to lowercase for checking
52
+ FILENAME_LOWER=$(echo "$FILENAME" | tr '[:upper:]' '[:lower:]')
53
+
54
+ # Check for banned modifiers
55
+ for modifier in "${BANNED_MODIFIERS[@]}"; do
56
+ if [[ "$FILENAME_LOWER" == *"$modifier"* ]]; then
57
+ # Special case: allow test files that follow conventions
58
+ if [[ "$modifier" == "test-" ]] || [[ "$modifier" == "-test" ]] || [[ "$modifier" == "_test" ]]; then
59
+ if [[ "$FILENAME_LOWER" =~ \.(test|spec)\.(js|ts|jsx|tsx|py|go|rs)$ ]]; then
60
+ continue
61
+ fi
62
+ fi
63
+
64
+ echo '{
65
+ "hookSpecificOutput": {
66
+ "hookEventName": "PostToolUse",
67
+ "additionalContext": "Warning: The filename '\'''"$FILENAME"''\'' contains the modifier '\'''"$modifier"''\'' which may indicate temporary or non-canonical naming. Consider using a more descriptive, permanent name. See CAWS naming conventions in .caws/canonical-map.yaml or run '\''caws naming check'\''."
68
+ }
69
+ }'
70
+ exit 0
71
+ fi
72
+ done
73
+
74
+ # Check for version suffixes (e.g., file-v2.js, file_v3.ts)
75
+ if [[ "$FILENAME_LOWER" =~ [-_]v[0-9]+\. ]]; then
76
+ echo '{
77
+ "hookSpecificOutput": {
78
+ "hookEventName": "PostToolUse",
79
+ "additionalContext": "Warning: The filename '\'''"$FILENAME"''\'' contains a version suffix. Version control should be handled by git, not file names. Consider removing the version suffix."
80
+ }
81
+ }'
82
+ exit 0
83
+ fi
84
+
85
+ # Check for date stamps (e.g., file-2024-01-15.js)
86
+ if [[ "$FILENAME_LOWER" =~ [0-9]{4}[-_][0-9]{2}[-_][0-9]{2} ]]; then
87
+ echo '{
88
+ "hookSpecificOutput": {
89
+ "hookEventName": "PostToolUse",
90
+ "additionalContext": "Warning: The filename '\'''"$FILENAME"''\'' contains a date stamp. Version control should be handled by git, not file names. Consider removing the date."
91
+ }
92
+ }'
93
+ exit 0
94
+ fi
95
+
96
+ # File naming is OK
97
+ exit 0
@@ -0,0 +1,68 @@
1
+ #!/bin/bash
2
+ # CAWS Quality Check Hook for Claude Code
3
+ # Runs CAWS quality validation after file edits
4
+ # @author @darianrosebrook
5
+
6
+ set -euo pipefail
7
+
8
+ # Read JSON input from Claude Code
9
+ INPUT=$(cat)
10
+
11
+ # Extract file info from PostToolUse input
12
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
13
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
14
+
15
+ # Only run on Write/Edit of source files
16
+ if [[ "$TOOL_NAME" != "Write" ]] && [[ "$TOOL_NAME" != "Edit" ]]; then
17
+ exit 0
18
+ fi
19
+
20
+ # Skip non-source files and node_modules/dist
21
+ if [[ ! "$FILE_PATH" =~ \.(js|ts|jsx|tsx|py|go|rs|java|mjs|cjs)$ ]] || \
22
+ [[ "$FILE_PATH" =~ node_modules ]] || \
23
+ [[ "$FILE_PATH" =~ dist/ ]] || \
24
+ [[ "$FILE_PATH" =~ build/ ]]; then
25
+ exit 0
26
+ fi
27
+
28
+ # Determine project directory
29
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
30
+
31
+ # Check if we're in a CAWS project
32
+ if [[ ! -f "$PROJECT_DIR/.caws/working-spec.yaml" ]]; then
33
+ exit 0
34
+ fi
35
+
36
+ # Check if CAWS CLI is available
37
+ if ! command -v caws &> /dev/null; then
38
+ # Suggest installing CAWS
39
+ echo '{
40
+ "hookSpecificOutput": {
41
+ "hookEventName": "PostToolUse",
42
+ "additionalContext": "CAWS CLI not available. Consider installing with: npm install -g @caws/cli"
43
+ }
44
+ }'
45
+ exit 0
46
+ fi
47
+
48
+ # Run CAWS quality gates in quiet mode for quick feedback
49
+ if caws quality-gates --context=commit --quiet 2>/dev/null; then
50
+ # Quality check passed - provide positive feedback
51
+ echo '{
52
+ "hookSpecificOutput": {
53
+ "hookEventName": "PostToolUse",
54
+ "additionalContext": "Quality gates passed for this change."
55
+ }
56
+ }'
57
+ else
58
+ # Quality check failed - provide feedback to Claude
59
+ # Run again to get violations summary
60
+ VIOLATIONS=$(caws quality-gates --context=commit --json 2>/dev/null | jq -r '.violations[:3] | .[] | "- \(.gate): \(.message)"' 2>/dev/null || echo "Run 'caws quality-gates' for details")
61
+
62
+ echo '{
63
+ "decision": "block",
64
+ "reason": "Quality gate violations detected. Please address the following issues before continuing:\n'"$VIOLATIONS"'\n\nRun '\''caws quality-gates'\'' for full details."
65
+ }'
66
+ fi
67
+
68
+ exit 0
@@ -0,0 +1,85 @@
1
+ #!/bin/bash
2
+ # CAWS Secret Scanner for Claude Code
3
+ # Warns when reading files that may contain secrets
4
+ # @author @darianrosebrook
5
+
6
+ set -euo pipefail
7
+
8
+ # Read JSON input from Claude Code
9
+ INPUT=$(cat)
10
+
11
+ # Extract file path
12
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
13
+
14
+ if [[ -z "$FILE_PATH" ]]; then
15
+ exit 0
16
+ fi
17
+
18
+ # Get just the filename for pattern matching
19
+ FILENAME=$(basename "$FILE_PATH")
20
+
21
+ # Files that commonly contain secrets
22
+ SECRET_FILE_PATTERNS=(
23
+ '.env'
24
+ '.env.local'
25
+ '.env.production'
26
+ '.env.development'
27
+ '.env.*'
28
+ 'credentials.json'
29
+ 'service-account.json'
30
+ 'secrets.yaml'
31
+ 'secrets.yml'
32
+ 'secrets.json'
33
+ '.netrc'
34
+ '.npmrc'
35
+ '.pypirc'
36
+ 'id_rsa'
37
+ 'id_ed25519'
38
+ 'id_ecdsa'
39
+ '*.pem'
40
+ '*.key'
41
+ '*.p12'
42
+ '*.pfx'
43
+ 'htpasswd'
44
+ 'shadow'
45
+ )
46
+
47
+ # Directories that commonly contain secrets
48
+ SECRET_DIRS=(
49
+ '.ssh'
50
+ '.aws'
51
+ '.azure'
52
+ '.gcloud'
53
+ '.kube'
54
+ '.gnupg'
55
+ )
56
+
57
+ # Check if file matches secret patterns
58
+ for pattern in "${SECRET_FILE_PATTERNS[@]}"; do
59
+ if [[ "$FILENAME" == $pattern ]]; then
60
+ # Output JSON with warning for Claude
61
+ echo '{
62
+ "hookSpecificOutput": {
63
+ "hookEventName": "PreToolUse",
64
+ "additionalContext": "WARNING: This file may contain secrets. Do not include sensitive values in your response. If you need to reference credentials, use placeholders like <API_KEY> instead of actual values."
65
+ }
66
+ }'
67
+ exit 0
68
+ fi
69
+ done
70
+
71
+ # Check if file is in a sensitive directory
72
+ for dir in "${SECRET_DIRS[@]}"; do
73
+ if [[ "$FILE_PATH" == *"/$dir/"* ]] || [[ "$FILE_PATH" == *"/$dir" ]]; then
74
+ echo '{
75
+ "hookSpecificOutput": {
76
+ "hookEventName": "PreToolUse",
77
+ "additionalContext": "WARNING: This file is in a sensitive directory that may contain secrets. Do not include any sensitive values in your response."
78
+ }
79
+ }'
80
+ exit 0
81
+ fi
82
+ done
83
+
84
+ # Allow the read
85
+ exit 0
@@ -0,0 +1,192 @@
1
+ #!/bin/bash
2
+ # CAWS Scope Guard Hook for Claude Code
3
+ # Validates file edits against the working spec's scope boundaries
4
+ # @author @darianrosebrook
5
+
6
+ set -euo pipefail
7
+
8
+ # Read JSON input from Claude Code
9
+ INPUT=$(cat)
10
+
11
+ # Extract file path from PreToolUse input
12
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
13
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
14
+
15
+ # Only check Write/Edit operations
16
+ if [[ "$TOOL_NAME" != "Write" ]] && [[ "$TOOL_NAME" != "Edit" ]]; then
17
+ exit 0
18
+ fi
19
+
20
+ if [[ -z "$FILE_PATH" ]]; then
21
+ exit 0
22
+ fi
23
+
24
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
25
+ SPEC_FILE="$PROJECT_DIR/.caws/working-spec.yaml"
26
+ SCOPE_FILE="$PROJECT_DIR/.caws/scope.json"
27
+
28
+ # Check if spec file or scope.json exists
29
+ if [[ ! -f "$SPEC_FILE" ]] && [[ ! -f "$SCOPE_FILE" ]]; then
30
+ exit 0
31
+ fi
32
+
33
+ # Get relative path from project root (portable — macOS realpath lacks --relative-to)
34
+ if [[ "$FILE_PATH" == "$PROJECT_DIR"/* ]]; then
35
+ REL_PATH="${FILE_PATH#$PROJECT_DIR/}"
36
+ else
37
+ REL_PATH="$FILE_PATH"
38
+ fi
39
+
40
+ # Lite mode: check scope.json if no working-spec.yaml
41
+ if [[ ! -f "$SPEC_FILE" ]] && [[ -f "$SCOPE_FILE" ]]; then
42
+ if command -v node >/dev/null 2>&1; then
43
+ LITE_CHECK=$(node -e "
44
+ const fs = require('fs');
45
+ const path = require('path');
46
+ try {
47
+ const scope = JSON.parse(fs.readFileSync('$SCOPE_FILE', 'utf8'));
48
+ const filePath = '$REL_PATH';
49
+ const dirs = scope.allowedDirectories || [];
50
+ const banned = scope.bannedPatterns || {};
51
+
52
+ // Check banned file patterns
53
+ const basename = path.basename(filePath);
54
+ const bannedFiles = banned.files || [];
55
+ for (const pattern of bannedFiles) {
56
+ const regex = new RegExp(pattern.replace(/\\*/g, '.*').replace(/\\?/g, '.'));
57
+ if (regex.test(basename)) {
58
+ console.log('banned:' + pattern);
59
+ process.exit(0);
60
+ }
61
+ }
62
+
63
+ // Check banned doc patterns
64
+ const bannedDocs = banned.docs || [];
65
+ for (const pattern of bannedDocs) {
66
+ const regex = new RegExp(pattern.replace(/\\*/g, '.*').replace(/\\?/g, '.'));
67
+ if (regex.test(basename)) {
68
+ console.log('banned:' + pattern);
69
+ process.exit(0);
70
+ }
71
+ }
72
+
73
+ // Check allowed directories
74
+ if (dirs.length > 0) {
75
+ const normalized = filePath.replace(/\\\\\\\\/g, '/');
76
+ let found = false;
77
+ for (const dir of dirs) {
78
+ const d = dir.replace(/\\/$/, '');
79
+ if (normalized.startsWith(d + '/') || normalized === d) { found = true; break; }
80
+ }
81
+ // Allow root-level files and .caws/ directory
82
+ if (!normalized.includes('/') || normalized.startsWith('.caws/')) found = true;
83
+ if (!found) {
84
+ console.log('not_allowed');
85
+ process.exit(0);
86
+ }
87
+ }
88
+ console.log('allowed');
89
+ } catch (error) {
90
+ console.log('error:' + error.message);
91
+ }
92
+ " 2>&1)
93
+
94
+ if [[ "$LITE_CHECK" == banned:* ]]; then
95
+ PATTERN="${LITE_CHECK#banned:}"
96
+ echo '{
97
+ "hookSpecificOutput": {
98
+ "hookEventName": "PreToolUse",
99
+ "permissionDecision": "ask",
100
+ "permissionDecisionReason": "This file ('"$REL_PATH"') matches a banned pattern ('"$PATTERN"') in .caws/scope.json. Creating files with this pattern is blocked to prevent file sprawl."
101
+ }
102
+ }'
103
+ exit 0
104
+ fi
105
+
106
+ if [[ "$LITE_CHECK" == "not_allowed" ]]; then
107
+ echo '{
108
+ "hookSpecificOutput": {
109
+ "hookEventName": "PreToolUse",
110
+ "permissionDecision": "ask",
111
+ "permissionDecisionReason": "This file ('"$REL_PATH"') is outside the allowed directories in .caws/scope.json. Please confirm this edit is intentional."
112
+ }
113
+ }'
114
+ exit 0
115
+ fi
116
+
117
+ # File is allowed - exit normally
118
+ exit 0
119
+ fi
120
+ fi
121
+
122
+ # Use Node.js to parse YAML and check scope
123
+ if command -v node >/dev/null 2>&1; then
124
+ SCOPE_CHECK=$(node -e "
125
+ const yaml = require('js-yaml');
126
+ const fs = require('fs');
127
+ const path = require('path');
128
+
129
+ try {
130
+ const spec = yaml.load(fs.readFileSync('$SPEC_FILE', 'utf8'));
131
+ const filePath = '$REL_PATH';
132
+
133
+ // Check if file is explicitly out of scope
134
+ const outOfScope = spec.scope?.out || [];
135
+ for (const pattern of outOfScope) {
136
+ // Simple glob-like matching
137
+ const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\?/g, '.'));
138
+ if (regex.test(filePath)) {
139
+ console.log('out_of_scope:' + pattern);
140
+ process.exit(0);
141
+ }
142
+ }
143
+
144
+ // Check if file is in scope (if scope is explicitly defined)
145
+ const inScope = spec.scope?.in || [];
146
+ if (inScope.length > 0) {
147
+ let found = false;
148
+ for (const pattern of inScope) {
149
+ const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\?/g, '.'));
150
+ if (regex.test(filePath)) {
151
+ found = true;
152
+ break;
153
+ }
154
+ }
155
+ if (!found) {
156
+ console.log('not_in_scope');
157
+ process.exit(0);
158
+ }
159
+ }
160
+
161
+ console.log('in_scope');
162
+ } catch (error) {
163
+ console.log('error:' + error.message);
164
+ }
165
+ " 2>&1)
166
+
167
+ if [[ "$SCOPE_CHECK" == out_of_scope:* ]]; then
168
+ PATTERN="${SCOPE_CHECK#out_of_scope:}"
169
+ echo '{
170
+ "hookSpecificOutput": {
171
+ "hookEventName": "PreToolUse",
172
+ "permissionDecision": "ask",
173
+ "permissionDecisionReason": "This file ('"$REL_PATH"') is marked as out-of-scope in the working spec (pattern: '"$PATTERN"'). Editing it may cause scope creep. Please confirm this edit is intentional."
174
+ }
175
+ }'
176
+ exit 0
177
+ fi
178
+
179
+ if [[ "$SCOPE_CHECK" == "not_in_scope" ]]; then
180
+ echo '{
181
+ "hookSpecificOutput": {
182
+ "hookEventName": "PreToolUse",
183
+ "permissionDecision": "ask",
184
+ "permissionDecisionReason": "This file ('"$REL_PATH"') is not in the defined scope of the working spec. Editing it may cause scope creep. Please confirm this edit is intentional."
185
+ }
186
+ }'
187
+ exit 0
188
+ fi
189
+ fi
190
+
191
+ # File is in scope or scope couldn't be checked - allow
192
+ exit 0
@@ -0,0 +1,92 @@
1
+ #!/bin/bash
2
+ # CAWS Simplification Guard Hook
3
+ # Detects when files are being stubbed out (large deletions + stub content)
4
+ # @author @darianrosebrook
5
+
6
+ set -euo pipefail
7
+
8
+ # Read JSON input from Claude Code
9
+ INPUT=$(cat)
10
+
11
+ # Extract tool info
12
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
13
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
14
+
15
+ # Only check Edit operations (modifications to existing files)
16
+ if [[ "$TOOL_NAME" != "Edit" ]]; then
17
+ exit 0
18
+ fi
19
+
20
+ if [[ -z "$FILE_PATH" ]]; then
21
+ exit 0
22
+ fi
23
+
24
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
25
+
26
+ # Get relative path (portable — macOS realpath lacks --relative-to)
27
+ if [[ "$FILE_PATH" == "$PROJECT_DIR"/* ]]; then
28
+ REL_PATH="${FILE_PATH#$PROJECT_DIR/}"
29
+ else
30
+ REL_PATH="$FILE_PATH"
31
+ fi
32
+
33
+ # Only check code files
34
+ case "$REL_PATH" in
35
+ *.js|*.jsx|*.ts|*.tsx|*.mjs|*.cjs|*.py|*.rs|*.go|*.java|*.kt|*.swift|*.rb|*.php|*.c|*.cpp|*.h)
36
+ ;;
37
+ *)
38
+ exit 0
39
+ ;;
40
+ esac
41
+
42
+ # Check if the file exists in git (skip new files)
43
+ if ! git show "HEAD:$REL_PATH" >/dev/null 2>&1; then
44
+ exit 0
45
+ fi
46
+
47
+ # Compare staged version with HEAD
48
+ if command -v node >/dev/null 2>&1; then
49
+ SIMP_CHECK=$(node -e "
50
+ const { execFileSync } = require('child_process');
51
+ try {
52
+ const headContent = execFileSync('git', ['show', 'HEAD:$REL_PATH'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
53
+ const currentContent = require('fs').readFileSync('$FILE_PATH', 'utf8');
54
+
55
+ const countLOC = (c) => c.split('\\n').filter(l => l.trim() && !l.trim().startsWith('//') && !l.trim().startsWith('#')).length;
56
+ const headLOC = countLOC(headContent);
57
+ const currentLOC = countLOC(currentContent);
58
+
59
+ if (headLOC < 10) { console.log('ok'); process.exit(0); }
60
+
61
+ const decrease = (headLOC - currentLOC) / headLOC;
62
+
63
+ // Check for stub patterns in new content
64
+ const stubPatterns = [/^\\s*pass\\s*$/m, /^\\s*\\.\\.\\.\\s*$/m, /raise\\s+NotImplementedError/,
65
+ /throw\\s+new\\s+Error.*not implemented/i, /\\/\\/\\s*TODO\\s*$/m, /#\\s*TODO\\s*$/m];
66
+ const hasStubs = stubPatterns.some(p => p.test(currentContent));
67
+
68
+ if (decrease >= 0.3 && hasStubs) {
69
+ console.log('simplified:' + Math.round(decrease * 100) + ':' + headLOC + ':' + currentLOC);
70
+ } else {
71
+ console.log('ok');
72
+ }
73
+ } catch (error) {
74
+ console.log('ok');
75
+ }
76
+ " 2>&1)
77
+
78
+ if [[ "$SIMP_CHECK" == simplified:* ]]; then
79
+ IFS=':' read -r _ PERCENT OLD_LOC NEW_LOC <<< "$SIMP_CHECK"
80
+ echo '{
81
+ "hookSpecificOutput": {
82
+ "hookEventName": "PreToolUse",
83
+ "permissionDecision": "ask",
84
+ "permissionDecisionReason": "WARNING: This edit would reduce '"$REL_PATH"' by '"$PERCENT"'% ('"$OLD_LOC"' → '"$NEW_LOC"' LOC) and introduces stub patterns (pass, TODO, NotImplementedError). This looks like a simplification — implementations should be modified, not replaced with stubs. Please confirm this is intentional."
85
+ }
86
+ }'
87
+ exit 0
88
+ fi
89
+ fi
90
+
91
+ # Allow the edit
92
+ exit 0
@@ -0,0 +1,76 @@
1
+ #!/bin/bash
2
+ # CAWS Spec Validation Hook for Claude Code
3
+ # Validates working-spec.yaml when it's edited
4
+ # @author @darianrosebrook
5
+
6
+ set -euo pipefail
7
+
8
+ # Read JSON input from Claude Code
9
+ INPUT=$(cat)
10
+
11
+ # Extract file path from PostToolUse input
12
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
13
+
14
+ # Only validate CAWS YAML files
15
+ if [[ "$FILE_PATH" != *".caws/"* ]] || ([[ "$FILE_PATH" != *.yaml ]] && [[ "$FILE_PATH" != *.yml ]]); then
16
+ exit 0
17
+ fi
18
+
19
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
20
+
21
+ # First, validate YAML syntax using Node.js if available
22
+ if command -v node >/dev/null 2>&1; then
23
+ YAML_CHECK=$(node -e "
24
+ try {
25
+ const yaml = require('js-yaml');
26
+ const fs = require('fs');
27
+ const content = fs.readFileSync('$FILE_PATH', 'utf8');
28
+ yaml.load(content);
29
+ console.log('valid');
30
+ } catch (error) {
31
+ console.error(error.message);
32
+ if (error.mark) {
33
+ console.error('Line: ' + (error.mark.line + 1) + ', Column: ' + (error.mark.column + 1));
34
+ }
35
+ process.exit(1);
36
+ }
37
+ " 2>&1)
38
+
39
+ if [ $? -ne 0 ]; then
40
+ echo '{
41
+ "decision": "block",
42
+ "reason": "YAML syntax error in spec file:\n'"$YAML_CHECK"'\n\nPlease fix the syntax before continuing. Common issues:\n- Check indentation (YAML uses 2 spaces)\n- Ensure arrays use consistent format\n- Remove duplicate keys"
43
+ }'
44
+ exit 0
45
+ fi
46
+ fi
47
+
48
+ # Run CAWS CLI validation if available
49
+ if command -v caws &> /dev/null; then
50
+ if VALIDATION=$(caws validate "$FILE_PATH" --quiet 2>&1); then
51
+ echo '{
52
+ "hookSpecificOutput": {
53
+ "hookEventName": "PostToolUse",
54
+ "additionalContext": "Spec validation passed. The specification is valid and complete."
55
+ }
56
+ }'
57
+ else
58
+ # Get suggestions
59
+ SUGGESTIONS=$(caws validate "$FILE_PATH" --suggestions 2>/dev/null | head -5 | tr '\n' ' ' || echo "Run 'caws validate --suggestions' for details")
60
+
61
+ echo '{
62
+ "decision": "block",
63
+ "reason": "Spec validation failed:\n'"$VALIDATION"'\n\nSuggestions:\n'"$SUGGESTIONS"'"
64
+ }'
65
+ fi
66
+ else
67
+ # Basic validation without CAWS CLI
68
+ echo '{
69
+ "hookSpecificOutput": {
70
+ "hookEventName": "PostToolUse",
71
+ "additionalContext": "CAWS CLI not available for full spec validation. Install with: npm install -g @caws/cli"
72
+ }
73
+ }'
74
+ fi
75
+
76
+ exit 0