specweave 1.0.31 ā 1.0.33
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/.claude-plugin/marketplace.json +1 -1
- package/CLAUDE.md +205 -148
- package/README.md +0 -2
- package/bin/specweave.js +11 -0
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/update-instructions.d.ts +16 -0
- package/dist/src/cli/commands/update-instructions.d.ts.map +1 -0
- package/dist/src/cli/commands/update-instructions.js +134 -0
- package/dist/src/cli/commands/update-instructions.js.map +1 -0
- package/dist/src/cli/helpers/init/directory-structure.d.ts +28 -1
- package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/directory-structure.js +163 -33
- package/dist/src/cli/helpers/init/directory-structure.js.map +1 -1
- package/dist/src/cli/helpers/init/index.d.ts +2 -1
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +3 -1
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/helpers/init/instruction-file-merger.d.ts +23 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js +243 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -0
- package/dist/src/cli/helpers/init/plugin-installer.js +49 -0
- package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
- package/dist/src/config/types.d.ts +2 -2
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +26 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -1
- package/dist/src/core/living-docs/external-sync-orchestrator.js +61 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -1
- package/dist/src/core/living-docs/scaffolding/index.d.ts +12 -0
- package/dist/src/core/living-docs/scaffolding/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/index.js +15 -0
- package/dist/src/core/living-docs/scaffolding/index.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/merger.d.ts +183 -0
- package/dist/src/core/living-docs/scaffolding/merger.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/merger.js +523 -0
- package/dist/src/core/living-docs/scaffolding/merger.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.d.ts +102 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.js +346 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.d.ts +108 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.js +204 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.js.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts +38 -2
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/generators.js +65 -10
- package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
- package/dist/src/core/tools/index.d.ts +11 -0
- package/dist/src/core/tools/index.d.ts.map +1 -0
- package/dist/src/core/tools/index.js +10 -0
- package/dist/src/core/tools/index.js.map +1 -0
- package/dist/src/core/tools/tool-event-bus.d.ts +33 -0
- package/dist/src/core/tools/tool-event-bus.d.ts.map +1 -0
- package/dist/src/core/tools/tool-event-bus.js +84 -0
- package/dist/src/core/tools/tool-event-bus.js.map +1 -0
- package/dist/src/core/tools/tool-index-builder.d.ts +27 -0
- package/dist/src/core/tools/tool-index-builder.d.ts.map +1 -0
- package/dist/src/core/tools/tool-index-builder.js +289 -0
- package/dist/src/core/tools/tool-index-builder.js.map +1 -0
- package/dist/src/core/tools/tool-registry.d.ts +51 -0
- package/dist/src/core/tools/tool-registry.d.ts.map +1 -0
- package/dist/src/core/tools/tool-registry.js +224 -0
- package/dist/src/core/tools/tool-registry.js.map +1 -0
- package/dist/src/core/tools/tool-search-engine.d.ts +22 -0
- package/dist/src/core/tools/tool-search-engine.d.ts.map +1 -0
- package/dist/src/core/tools/tool-search-engine.js +174 -0
- package/dist/src/core/tools/tool-search-engine.js.map +1 -0
- package/dist/src/core/tools/types/tool-registry-types.d.ts +112 -0
- package/dist/src/core/tools/types/tool-registry-types.d.ts.map +1 -0
- package/dist/src/core/tools/types/tool-registry-types.js +7 -0
- package/dist/src/core/tools/types/tool-registry-types.js.map +1 -0
- package/dist/src/init/compliance/types.d.ts +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +3 -13
- package/plugins/specweave/hooks/lib/common-setup.sh +47 -321
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh +5 -5
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +5 -5
- package/plugins/specweave/hooks/universal/dispatcher.mjs +4 -5
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +43 -296
- package/plugins/specweave/hooks/universal/hook-wrapper.sh +3 -1
- package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +2 -2
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -10
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +12 -29
- package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +27 -29
- package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +10 -4
- package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +139 -0
- package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +4 -2
- package/plugins/specweave/hooks/v2/session-end.sh +3 -1
- package/plugins/specweave/hooks/v2/session-start.sh +3 -1
- package/plugins/specweave/skills/increment-planner/templates/plan.md +14 -0
- package/plugins/specweave/skills/update-instructions/SKILL.md +80 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh +1 -1
- package/plugins/specweave-mobile/README.md +55 -35
- package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +805 -329
- package/plugins/specweave-mobile/skills/expo-workflow/SKILL.md +226 -9
- package/plugins/specweave-mobile/skills/native-modules/SKILL.md +221 -20
- package/plugins/specweave-mobile/skills/performance-optimization/SKILL.md +186 -14
- package/plugins/specweave-mobile/skills/react-native-setup/SKILL.md +151 -54
- package/plugins/specweave-release/commands/npm.md +61 -17
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -3
- package/src/templates/AGENTS.md.template +34 -0
- package/src/templates/CLAUDE.md.template +121 -155
- package/plugins/specweave/hooks/config-env-separator.sh +0 -99
- package/plugins/specweave/hooks/github-metadata-guard.sh +0 -73
- package/plugins/specweave/hooks/lib/circuit-breaker.sh +0 -381
- package/plugins/specweave/hooks/lib/crash-prevention.sh +0 -336
- package/plugins/specweave/hooks/lib/logging.sh +0 -231
- package/plugins/specweave/hooks/lib/metrics.sh +0 -347
- package/plugins/specweave/hooks/lib/semaphore.sh +0 -216
- package/plugins/specweave/hooks/project-folder-guard.sh +0 -274
- package/plugins/specweave/hooks/spec-project-validator.sh +0 -210
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +0 -212
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +0 -163
- package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +0 -51
- package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +0 -63
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +0 -335
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.test.sh +0 -406
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# bash-file-guard.sh - CRITICAL: Block Bash file creation patterns that cause infinite hangs
|
|
3
|
-
#
|
|
4
|
-
# ROOT CAUSE: When Claude uses heredoc (cat << EOF ... EOF) to create files and the
|
|
5
|
-
# content gets truncated due to token limits, the shell waits FOREVER for the EOF
|
|
6
|
-
# terminator that will never come. This causes Claude Code to hang indefinitely
|
|
7
|
-
# (2+ hours observed in production crashes).
|
|
8
|
-
#
|
|
9
|
-
# SOLUTION: Block ALL bash file creation patterns and force use of Write/Edit tools.
|
|
10
|
-
# Write/Edit tools are:
|
|
11
|
-
# - Atomic (no intermediate states)
|
|
12
|
-
# - Token-safe (handled by Claude Code, not shell)
|
|
13
|
-
# - Recoverable (no zombie processes)
|
|
14
|
-
#
|
|
15
|
-
# PreToolUse hook for Bash commands - exit 0 allows, exit 2 blocks
|
|
16
|
-
#
|
|
17
|
-
# v0.32.1 - Initial implementation based on crash analysis from 2025-12-06
|
|
18
|
-
|
|
19
|
-
set +e
|
|
20
|
-
|
|
21
|
-
[[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
|
|
22
|
-
|
|
23
|
-
# Read stdin for tool input
|
|
24
|
-
INPUT=$(cat)
|
|
25
|
-
|
|
26
|
-
# Extract the command being executed
|
|
27
|
-
# The Bash tool has a "command" parameter
|
|
28
|
-
# Use jq for proper JSON parsing (handles escaped quotes correctly)
|
|
29
|
-
if command -v jq &> /dev/null; then
|
|
30
|
-
COMMAND=$(echo "$INPUT" | jq -r '.command // empty' 2>/dev/null)
|
|
31
|
-
else
|
|
32
|
-
# Fallback: simple regex (may fail on complex JSON with escaped quotes)
|
|
33
|
-
COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)"/\1/')
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
# If no command found or empty, allow (safety)
|
|
37
|
-
if [[ -z "$COMMAND" ]]; then
|
|
38
|
-
exit 0
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
# === PATTERN DETECTION ===
|
|
42
|
-
# These patterns WILL cause infinite hangs or truncation issues
|
|
43
|
-
|
|
44
|
-
BLOCKED=0
|
|
45
|
-
PATTERN_NAME=""
|
|
46
|
-
|
|
47
|
-
# === HEREDOC PATTERNS (MOST DANGEROUS - infinite hang!) ===
|
|
48
|
-
|
|
49
|
-
# Pattern 1a: Standard heredoc with any delimiter
|
|
50
|
-
# Matches: cat << EOF, cat << 'EOF', cat << "EOF", cat <<EOF (no space)
|
|
51
|
-
if echo "$COMMAND" | grep -qE "(cat|tee).*<<-?[[:space:]]*['\"]?[A-Za-z]+" 2>/dev/null; then
|
|
52
|
-
BLOCKED=1
|
|
53
|
-
PATTERN_NAME="heredoc (cat << DELIMITER)"
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# Pattern 1b: Heredoc with escaped quotes in JSON (\"EOF\")
|
|
57
|
-
# JSON escapes double quotes, so << "EOF" becomes << \"EOF\"
|
|
58
|
-
if echo "$COMMAND" | grep -qE "(cat|tee).*<<-?[[:space:]]*\\\\\"[A-Za-z]+" 2>/dev/null; then
|
|
59
|
-
BLOCKED=1
|
|
60
|
-
PATTERN_NAME="heredoc with quoted delimiter"
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
# Pattern 1c: SUPER AGGRESSIVE - block ANY heredoc-like pattern
|
|
64
|
-
# Catches edge cases where content has << followed by word boundary
|
|
65
|
-
# This is the NUCLEAR option - heredocs are NEVER safe in Claude context
|
|
66
|
-
if echo "$COMMAND" | grep -qE "<<[[:space:]]*['\"]?[A-Za-z_]+" 2>/dev/null; then
|
|
67
|
-
BLOCKED=1
|
|
68
|
-
PATTERN_NAME="heredoc pattern (any form)"
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
# Pattern 1d: Multiline content in the command itself
|
|
72
|
-
# If the command contains literal newlines, it might be a heredoc in disguise
|
|
73
|
-
if echo "$COMMAND" | grep -qE $'\n' 2>/dev/null; then
|
|
74
|
-
# Commands with newlines are suspicious - check for file redirects
|
|
75
|
-
if echo "$COMMAND" | grep -qE '>' 2>/dev/null; then
|
|
76
|
-
BLOCKED=1
|
|
77
|
-
PATTERN_NAME="multiline command with redirect (heredoc variant)"
|
|
78
|
-
fi
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
# === CAT STDIN REDIRECT (VERY DANGEROUS!) ===
|
|
82
|
-
# Pattern 2: Plain cat with output redirect but no input
|
|
83
|
-
# Matches: cat > file.txt (waits forever for stdin!)
|
|
84
|
-
# This is just as dangerous as heredoc - shell waits for input
|
|
85
|
-
if echo "$COMMAND" | grep -qE "^[[:space:]]*cat[[:space:]]+>[[:space:]]*[^>]" 2>/dev/null; then
|
|
86
|
-
BLOCKED=1
|
|
87
|
-
PATTERN_NAME="cat > file (stdin redirect - waits forever!)"
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
# === DD COMMAND (EQUALLY DANGEROUS!) ===
|
|
91
|
-
# Pattern 2b: dd with stdin or output file
|
|
92
|
-
# Matches: dd if=/dev/stdin of=file, dd of=file bs=1024
|
|
93
|
-
# dd waits for input EXACTLY like cat > file
|
|
94
|
-
if echo "$COMMAND" | grep -qE "^[[:space:]]*dd[[:space:]]" 2>/dev/null; then
|
|
95
|
-
if echo "$COMMAND" | grep -qE "of=" 2>/dev/null; then
|
|
96
|
-
BLOCKED=1
|
|
97
|
-
PATTERN_NAME="dd command (waits for stdin like cat)"
|
|
98
|
-
fi
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
# === READ COMMAND (WAITS FOR INPUT) ===
|
|
102
|
-
# Pattern 2c: read with redirect
|
|
103
|
-
# Matches: read VAR > file (waits for input from user/stdin)
|
|
104
|
-
if echo "$COMMAND" | grep -qE "^[[:space:]]*read[[:space:]].*>" 2>/dev/null; then
|
|
105
|
-
BLOCKED=1
|
|
106
|
-
PATTERN_NAME="read command with redirect (waits for input)"
|
|
107
|
-
fi
|
|
108
|
-
|
|
109
|
-
# === STREAM REDIRECT EXCEPTION ===
|
|
110
|
-
# Allow redirects to /dev/stdout, /dev/stderr, /dev/null (safe stream operations)
|
|
111
|
-
if echo "$COMMAND" | grep -qE ">[[:space:]]*/dev/(stdout|stderr|null)" 2>/dev/null; then
|
|
112
|
-
exit 0 # Safe stream redirect, not file creation
|
|
113
|
-
fi
|
|
114
|
-
|
|
115
|
-
# === ECHO/PRINTF PATTERNS ===
|
|
116
|
-
# These can cause truncation issues and are bad practice
|
|
117
|
-
# NOTE: Match any echo/printf that writes to file (single > not >>)
|
|
118
|
-
|
|
119
|
-
# Pattern 3: Echo with redirect to file
|
|
120
|
-
# Matches: echo anything > file (but not >>)
|
|
121
|
-
# Use negative lookahead simulation: check for > but then verify not >>
|
|
122
|
-
if echo "$COMMAND" | grep -qE '^[[:space:]]*(echo)[[:space:]]' 2>/dev/null; then
|
|
123
|
-
if echo "$COMMAND" | grep -qE '>[[:space:]]*[^>]' 2>/dev/null; then
|
|
124
|
-
# Check it's not append (>>)
|
|
125
|
-
if ! echo "$COMMAND" | grep -qE '>>' 2>/dev/null; then
|
|
126
|
-
BLOCKED=1
|
|
127
|
-
PATTERN_NAME="echo > file"
|
|
128
|
-
fi
|
|
129
|
-
fi
|
|
130
|
-
fi
|
|
131
|
-
|
|
132
|
-
# Pattern 4: Printf with redirect to file
|
|
133
|
-
if echo "$COMMAND" | grep -qE '^[[:space:]]*(printf)[[:space:]]' 2>/dev/null; then
|
|
134
|
-
if echo "$COMMAND" | grep -qE '>[[:space:]]*[^>]' 2>/dev/null; then
|
|
135
|
-
# Check it's not append (>>)
|
|
136
|
-
if ! echo "$COMMAND" | grep -qE '>>' 2>/dev/null; then
|
|
137
|
-
BLOCKED=1
|
|
138
|
-
PATTERN_NAME="printf > file"
|
|
139
|
-
fi
|
|
140
|
-
fi
|
|
141
|
-
fi
|
|
142
|
-
|
|
143
|
-
# Pattern 5: Multi-line string with cat
|
|
144
|
-
# Matches: cat > file -e "multi\nline", etc.
|
|
145
|
-
if echo "$COMMAND" | grep -qE "cat[[:space:]]+-[a-z]*[[:space:]]+['\"].*\\\\n" 2>/dev/null; then
|
|
146
|
-
BLOCKED=1
|
|
147
|
-
PATTERN_NAME="cat with multi-line string"
|
|
148
|
-
fi
|
|
149
|
-
|
|
150
|
-
# === SAFE EXCEPTIONS ===
|
|
151
|
-
# Append operations (>>) are safe - echo/printf complete immediately
|
|
152
|
-
if echo "$COMMAND" | grep -qE "(echo|printf)[[:space:]].*>>[[:space:]]*" 2>/dev/null; then
|
|
153
|
-
BLOCKED=0
|
|
154
|
-
PATTERN_NAME=""
|
|
155
|
-
fi
|
|
156
|
-
|
|
157
|
-
# === EXCEPTION: Allow safe bash operations ===
|
|
158
|
-
# These are safe because they don't involve file content creation
|
|
159
|
-
|
|
160
|
-
# Exception 1: Simple redirects from commands (not content creation)
|
|
161
|
-
# Safe: curl ... > file, wget ... > file (command output, not content creation)
|
|
162
|
-
# These won't hang because the content comes from the command, not heredoc
|
|
163
|
-
if echo "$COMMAND" | grep -qE "^(curl|wget|git|npm|node|python|pip)[[:space:]].*>" 2>/dev/null; then
|
|
164
|
-
# These are downloading/generating content from commands, not creating from heredoc
|
|
165
|
-
# Only block if also contains heredoc/echo patterns
|
|
166
|
-
if [[ "$PATTERN_NAME" != "heredoc"* ]] && [[ "$PATTERN_NAME" != "echo"* ]] && [[ "$PATTERN_NAME" != "printf"* ]]; then
|
|
167
|
-
exit 0 # Allow
|
|
168
|
-
fi
|
|
169
|
-
fi
|
|
170
|
-
|
|
171
|
-
# === BLOCK DANGEROUS PATTERNS ===
|
|
172
|
-
if [[ $BLOCKED -eq 1 ]]; then
|
|
173
|
-
echo ""
|
|
174
|
-
echo "=============================================================================="
|
|
175
|
-
echo " š BLOCKED: Bash file creation detected (Infinite Hang Prevention)"
|
|
176
|
-
echo "=============================================================================="
|
|
177
|
-
echo ""
|
|
178
|
-
echo "Pattern detected: $PATTERN_NAME"
|
|
179
|
-
echo ""
|
|
180
|
-
echo "WHY THIS IS BLOCKED:"
|
|
181
|
-
echo " When using heredoc (cat << EOF) or echo/printf redirects, if the content"
|
|
182
|
-
echo " is truncated due to token limits, the shell waits FOREVER for input that"
|
|
183
|
-
echo " will never come. This causes Claude Code to hang for hours with no recovery."
|
|
184
|
-
echo ""
|
|
185
|
-
echo " Observed crash: 2+ hour session hang from truncated heredoc (2025-12-06)"
|
|
186
|
-
echo ""
|
|
187
|
-
echo "SAFE ALTERNATIVES:"
|
|
188
|
-
echo " āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
189
|
-
echo " ā Instead of: Use: ā"
|
|
190
|
-
echo " āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤"
|
|
191
|
-
echo " ā cat > file << EOF Write tool: Write(file_path, content) ā"
|
|
192
|
-
echo " ā echo \"...\" > file Write tool: Write(file_path, content) ā"
|
|
193
|
-
echo " ā printf ... > file Write tool: Write(file_path, content) ā"
|
|
194
|
-
echo " ā Modify existing Edit tool: Edit(file_path, old, new) ā"
|
|
195
|
-
echo " ā Append to file Read + Write tools ā"
|
|
196
|
-
echo " āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
197
|
-
echo ""
|
|
198
|
-
echo "WHAT IS ALLOWED:"
|
|
199
|
-
echo " - mkdir -p /path/to/dir (directory creation)"
|
|
200
|
-
echo " - git commit, git push (version control)"
|
|
201
|
-
echo " - npm install, npm run (package management)"
|
|
202
|
-
echo " - curl URL > file (downloading, not content creation)"
|
|
203
|
-
echo " - rm, mv, cp (file operations)"
|
|
204
|
-
echo ""
|
|
205
|
-
echo "=============================================================================="
|
|
206
|
-
|
|
207
|
-
exit 2 # Block the tool call
|
|
208
|
-
fi
|
|
209
|
-
|
|
210
|
-
# All other bash commands are allowed
|
|
211
|
-
echo '{"decision":"allow"}'
|
|
212
|
-
exit 0
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Comprehensive test suite for bash-file-guard.sh
|
|
3
|
-
# Tests ALL dangerous patterns that could cause infinite hangs
|
|
4
|
-
#
|
|
5
|
-
# Usage: bash bash-file-guard.test.sh
|
|
6
|
-
|
|
7
|
-
set -e
|
|
8
|
-
|
|
9
|
-
GUARD="$(dirname "$0")/bash-file-guard.sh"
|
|
10
|
-
PASS=0
|
|
11
|
-
FAIL=0
|
|
12
|
-
TOTAL=0
|
|
13
|
-
|
|
14
|
-
# Colors for output
|
|
15
|
-
RED='\033[0;31m'
|
|
16
|
-
GREEN='\033[0;32m'
|
|
17
|
-
YELLOW='\033[1;33m'
|
|
18
|
-
NC='\033[0m' # No Color
|
|
19
|
-
|
|
20
|
-
test_should_block() {
|
|
21
|
-
local name="$1"
|
|
22
|
-
local command="$2"
|
|
23
|
-
TOTAL=$((TOTAL + 1))
|
|
24
|
-
|
|
25
|
-
# Properly escape the command for JSON: " becomes \"
|
|
26
|
-
local escaped_command=$(echo "$command" | sed 's/"/\\"/g')
|
|
27
|
-
result=$(echo "{\"command\": \"$escaped_command\"}" | bash "$GUARD" 2>&1; echo "EXIT:$?")
|
|
28
|
-
exit_code=$(echo "$result" | grep -o 'EXIT:[0-9]*' | cut -d: -f2)
|
|
29
|
-
|
|
30
|
-
if [[ "$exit_code" == "2" ]]; then
|
|
31
|
-
echo -e "${GREEN}ā BLOCKED${NC}: $name"
|
|
32
|
-
PASS=$((PASS + 1))
|
|
33
|
-
else
|
|
34
|
-
echo -e "${RED}ā NOT BLOCKED${NC}: $name"
|
|
35
|
-
echo " Command: $command"
|
|
36
|
-
echo " Exit code: $exit_code (expected 2)"
|
|
37
|
-
FAIL=$((FAIL + 1))
|
|
38
|
-
fi
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
test_should_allow() {
|
|
42
|
-
local name="$1"
|
|
43
|
-
local command="$2"
|
|
44
|
-
TOTAL=$((TOTAL + 1))
|
|
45
|
-
|
|
46
|
-
# Properly escape the command for JSON: " becomes \"
|
|
47
|
-
local escaped_command=$(echo "$command" | sed 's/"/\\"/g')
|
|
48
|
-
result=$(echo "{\"command\": \"$escaped_command\"}" | bash "$GUARD" 2>&1; echo "EXIT:$?")
|
|
49
|
-
exit_code=$(echo "$result" | grep -o 'EXIT:[0-9]*' | cut -d: -f2)
|
|
50
|
-
|
|
51
|
-
if [[ "$exit_code" == "0" ]]; then
|
|
52
|
-
echo -e "${GREEN}ā ALLOWED${NC}: $name"
|
|
53
|
-
PASS=$((PASS + 1))
|
|
54
|
-
else
|
|
55
|
-
echo -e "${RED}ā WRONGLY BLOCKED${NC}: $name"
|
|
56
|
-
echo " Command: $command"
|
|
57
|
-
echo " Exit code: $exit_code (expected 0)"
|
|
58
|
-
FAIL=$((FAIL + 1))
|
|
59
|
-
fi
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
echo "========================================"
|
|
63
|
-
echo " BASH FILE GUARD - COMPREHENSIVE TESTS"
|
|
64
|
-
echo "========================================"
|
|
65
|
-
echo ""
|
|
66
|
-
|
|
67
|
-
echo -e "${YELLOW}=== HEREDOC PATTERNS (MOST DANGEROUS) ===${NC}"
|
|
68
|
-
test_should_block "Basic heredoc" "cat > file.txt << EOF"
|
|
69
|
-
test_should_block "Heredoc with quotes" "cat > file.txt << 'EOF'"
|
|
70
|
-
test_should_block "Heredoc double quotes" 'cat > file.txt << "EOF"'
|
|
71
|
-
test_should_block "Heredoc reversed order" "cat << EOF > file.txt"
|
|
72
|
-
test_should_block "Heredoc with END delimiter" "cat > file.txt << END"
|
|
73
|
-
test_should_block "Heredoc with CONTENT delimiter" "cat << CONTENT > out.md"
|
|
74
|
-
test_should_block "Heredoc with MARKER" "cat << MARKER"
|
|
75
|
-
test_should_block "Heredoc tab strip" "cat <<- EOF > file.txt"
|
|
76
|
-
test_should_block "Tee with heredoc" "tee file.txt << EOF"
|
|
77
|
-
test_should_block "Tee reversed" "tee << EOF > file.txt"
|
|
78
|
-
test_should_block "Heredoc with path" "cat > /tmp/test.md << EOF"
|
|
79
|
-
test_should_block "Heredoc with variable path" 'cat > $HOME/test.txt << EOF'
|
|
80
|
-
|
|
81
|
-
echo ""
|
|
82
|
-
echo -e "${YELLOW}=== ECHO PATTERNS ===${NC}"
|
|
83
|
-
test_should_block "Echo to file" "echo hello > file.txt"
|
|
84
|
-
test_should_block "Echo quoted string" 'echo "hello world" > file.txt'
|
|
85
|
-
test_should_block "Echo single quoted" "echo 'hello' > file.txt"
|
|
86
|
-
test_should_block "Echo with -e flag" "echo -e 'hello\\nworld' > file.txt"
|
|
87
|
-
test_should_block "Echo with -n flag" "echo -n 'hello' > file.txt"
|
|
88
|
-
test_should_block "Echo variable" 'echo $VAR > file.txt'
|
|
89
|
-
test_should_block "Echo to path" "echo content > /tmp/test.txt"
|
|
90
|
-
|
|
91
|
-
echo ""
|
|
92
|
-
echo -e "${YELLOW}=== PRINTF PATTERNS ===${NC}"
|
|
93
|
-
test_should_block "Printf to file" "printf hello > file.txt"
|
|
94
|
-
test_should_block "Printf format string" "printf '%s' hello > file.txt"
|
|
95
|
-
test_should_block "Printf with newline" "printf '%s\\n' hello > file.txt"
|
|
96
|
-
test_should_block "Printf quoted" 'printf "hello world" > file.txt'
|
|
97
|
-
|
|
98
|
-
echo ""
|
|
99
|
-
echo -e "${YELLOW}=== EDGE CASES - SHOULD BLOCK ===${NC}"
|
|
100
|
-
test_should_block "Cat stdin to file (dangerous!)" "cat > file.txt"
|
|
101
|
-
test_should_block "Bash -c with heredoc" "bash -c 'cat > f << EOF'"
|
|
102
|
-
test_should_block "Double redirect" "echo a > file.txt > backup.txt"
|
|
103
|
-
test_should_block "dd stdin to file" "dd if=/dev/stdin of=output.dat"
|
|
104
|
-
test_should_block "dd output file" "dd of=file.bin bs=1024"
|
|
105
|
-
test_should_block "dd with count" "dd of=output.img bs=512 count=100"
|
|
106
|
-
test_should_block "read to file" "read VAR > input.txt"
|
|
107
|
-
test_should_block "read prompt to file" 'read -p "Enter:" > log.txt'
|
|
108
|
-
|
|
109
|
-
echo ""
|
|
110
|
-
echo -e "${YELLOW}=== SAFE COMMANDS - SHOULD ALLOW ===${NC}"
|
|
111
|
-
test_should_allow "Git status" "git status"
|
|
112
|
-
test_should_allow "Git add" "git add ."
|
|
113
|
-
test_should_allow "Git commit" "git commit -m 'message'"
|
|
114
|
-
test_should_allow "Git push" "git push origin main"
|
|
115
|
-
test_should_allow "Git diff" "git diff HEAD"
|
|
116
|
-
test_should_allow "Npm install" "npm install"
|
|
117
|
-
test_should_allow "Npm run build" "npm run build"
|
|
118
|
-
test_should_allow "Npm test" "npm test"
|
|
119
|
-
test_should_allow "Mkdir" "mkdir -p /tmp/test"
|
|
120
|
-
test_should_allow "Ls" "ls -la"
|
|
121
|
-
test_should_allow "Pwd" "pwd"
|
|
122
|
-
test_should_allow "Cd" "cd /tmp"
|
|
123
|
-
test_should_allow "Rm file" "rm -f /tmp/test.txt"
|
|
124
|
-
test_should_allow "Mv file" "mv old.txt new.txt"
|
|
125
|
-
test_should_allow "Cp file" "cp source.txt dest.txt"
|
|
126
|
-
test_should_allow "Curl download" "curl -o file.txt https://example.com"
|
|
127
|
-
test_should_allow "Wget download" "wget -O file.txt https://example.com"
|
|
128
|
-
test_should_allow "Ps" "ps aux"
|
|
129
|
-
test_should_allow "Kill" "kill -9 1234"
|
|
130
|
-
test_should_allow "Grep" "grep pattern file.txt"
|
|
131
|
-
test_should_allow "Find" "find . -name '*.ts'"
|
|
132
|
-
test_should_allow "Chmod" "chmod +x script.sh"
|
|
133
|
-
test_should_allow "Node run" "node script.js"
|
|
134
|
-
test_should_allow "Python run" "python script.py"
|
|
135
|
-
test_should_allow "Read from file" "cat file.txt"
|
|
136
|
-
test_should_allow "Head" "head -n 10 file.txt"
|
|
137
|
-
test_should_allow "Tail" "tail -f log.txt"
|
|
138
|
-
|
|
139
|
-
echo ""
|
|
140
|
-
echo -e "${YELLOW}=== APPEND OPERATIONS (should allow) ===${NC}"
|
|
141
|
-
test_should_allow "Echo append" "echo hello >> file.txt"
|
|
142
|
-
test_should_allow "Printf append" "printf '%s' data >> file.txt"
|
|
143
|
-
|
|
144
|
-
echo ""
|
|
145
|
-
echo -e "${YELLOW}=== STREAM REDIRECTS (should allow) ===${NC}"
|
|
146
|
-
test_should_allow "Echo to stderr" "echo 'error' > /dev/stderr"
|
|
147
|
-
test_should_allow "Printf to stdout" "printf '%s' output > /dev/stdout"
|
|
148
|
-
test_should_allow "Echo to null" "echo 'discard' > /dev/null"
|
|
149
|
-
|
|
150
|
-
echo ""
|
|
151
|
-
echo "========================================"
|
|
152
|
-
echo " RESULTS"
|
|
153
|
-
echo "========================================"
|
|
154
|
-
echo -e "Total: $TOTAL"
|
|
155
|
-
echo -e "${GREEN}Passed: $PASS${NC}"
|
|
156
|
-
if [[ $FAIL -gt 0 ]]; then
|
|
157
|
-
echo -e "${RED}Failed: $FAIL${NC}"
|
|
158
|
-
exit 1
|
|
159
|
-
else
|
|
160
|
-
echo -e "Failed: 0"
|
|
161
|
-
echo ""
|
|
162
|
-
echo -e "${GREEN}ALL TESTS PASSED!${NC}"
|
|
163
|
-
fi
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# features-folder-guard.sh - Block writes to obsolete _features/ folder
|
|
3
|
-
#
|
|
4
|
-
# ROOT CAUSE: The _features/ folder is OBSOLETE since v5.0.0. Features should
|
|
5
|
-
# live in {project}/FS-XXX/ folders. LLM agents sometimes create files in
|
|
6
|
-
# _features/ due to outdated documentation references.
|
|
7
|
-
#
|
|
8
|
-
# SOLUTION: Block all Write/Edit operations targeting _features/ folder.
|
|
9
|
-
# This forces proper project-based feature folder structure.
|
|
10
|
-
#
|
|
11
|
-
# PreToolUse hook for Write/Edit commands - exit 0 allows, exit 2 blocks
|
|
12
|
-
#
|
|
13
|
-
# v0.33.0 - Initial implementation based on bug analysis from 2025-12-06
|
|
14
|
-
|
|
15
|
-
set +e
|
|
16
|
-
|
|
17
|
-
[[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
|
|
18
|
-
|
|
19
|
-
# Read stdin for tool input
|
|
20
|
-
INPUT=$(cat)
|
|
21
|
-
|
|
22
|
-
# Extract the file_path being written/edited
|
|
23
|
-
# Claude Code passes tool input in .tool_input.file_path format
|
|
24
|
-
if command -v jq &> /dev/null; then
|
|
25
|
-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .file_path // empty' 2>/dev/null)
|
|
26
|
-
else
|
|
27
|
-
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\(.*\)"/\1/')
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
# If no file_path found, allow (safety)
|
|
31
|
-
if [[ -z "$FILE_PATH" ]]; then
|
|
32
|
-
exit 0
|
|
33
|
-
fi
|
|
34
|
-
|
|
35
|
-
# === PATTERN DETECTION ===
|
|
36
|
-
# Block any write to _features/ folder (obsolete since v5.0.0)
|
|
37
|
-
|
|
38
|
-
# Pattern: .specweave/docs/internal/specs/_features/
|
|
39
|
-
if [[ "$FILE_PATH" =~ \.specweave/docs/internal/specs/_features/ ]]; then
|
|
40
|
-
cat << 'EOF'
|
|
41
|
-
{
|
|
42
|
-
"decision": "block",
|
|
43
|
-
"reason": "š« BLOCKED: Writing to _features/ folder (OBSOLETE since v5.0.0)\n\nā ļø The _features/ folder has been deprecated.\nFeatures must now live in project folders: .specweave/docs/internal/specs/{project}/FS-XXX/\n\nš CORRECT structure:\n .specweave/docs/internal/specs/specweave/FS-116/FEATURE.md ā
\n .specweave/docs/internal/specs/backend/FS-117/us-001.md ā
\n\nā OBSOLETE structure:\n .specweave/docs/internal/specs/_features/FS-116/FEATURE.md ā\n\nš§ To fix: Use spec.md `project:` field to determine target folder.\nSee: CLAUDE.md section '2c. spec.md MUST Have project:'"
|
|
44
|
-
}
|
|
45
|
-
EOF
|
|
46
|
-
exit 2
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
# Allow all other writes
|
|
50
|
-
printf '{"decision":"allow"}'
|
|
51
|
-
exit 0
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# increment-root-guard.sh - Block files at increment root (except allowed)
|
|
3
|
-
#
|
|
4
|
-
# ROOT CAUSE: Agents sometimes create files like COMPLETION_REPORT.md,
|
|
5
|
-
# COMPLETION_SUMMARY.md, or other .md files at increment root instead of
|
|
6
|
-
# placing them in appropriate subfolders (reports/, scripts/, logs/, etc.)
|
|
7
|
-
#
|
|
8
|
-
# SOLUTION: Block Write operations to increment root for non-standard files.
|
|
9
|
-
# Only allow: metadata.json, spec.md, plan.md, tasks.md
|
|
10
|
-
# Everything else MUST go in subfolders: reports/, scripts/, logs/, backups/, docs/
|
|
11
|
-
#
|
|
12
|
-
# PreToolUse hook for Write command - exit 0 allows, exit 2 blocks
|
|
13
|
-
#
|
|
14
|
-
# v0.33.0 - Initial implementation based on bug analysis from 2025-12-09
|
|
15
|
-
|
|
16
|
-
set +e
|
|
17
|
-
|
|
18
|
-
[[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
|
|
19
|
-
|
|
20
|
-
# Read stdin for tool input
|
|
21
|
-
INPUT=$(cat)
|
|
22
|
-
|
|
23
|
-
# Extract the file_path being written
|
|
24
|
-
# Claude Code passes tool input in .tool_input.file_path format
|
|
25
|
-
if command -v jq &> /dev/null; then
|
|
26
|
-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .file_path // empty' 2>/dev/null)
|
|
27
|
-
else
|
|
28
|
-
# Fallback: try both patterns
|
|
29
|
-
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\(.*\)"/\1/')
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
# If no file_path found, allow (safety)
|
|
33
|
-
if [[ -z "$FILE_PATH" ]]; then
|
|
34
|
-
exit 0
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
# === PATTERN DETECTION ===
|
|
38
|
-
# Block files at increment root that should be in subfolders
|
|
39
|
-
|
|
40
|
-
# Pattern: .specweave/increments/####-name/FILE.md (at root, not in subfolder)
|
|
41
|
-
# Allowed at root: metadata.json, spec.md, plan.md, tasks.md
|
|
42
|
-
if [[ "$FILE_PATH" =~ \.specweave/increments/[0-9]{3,4}E?-[^/]+/([^/]+)$ ]]; then
|
|
43
|
-
FILENAME="${BASH_REMATCH[1]}"
|
|
44
|
-
|
|
45
|
-
# Allow standard increment files at root
|
|
46
|
-
if [[ "$FILENAME" =~ ^(metadata\.json|spec\.md|plan\.md|tasks\.md)$ ]]; then
|
|
47
|
-
printf '{"decision":"allow"}'
|
|
48
|
-
exit 0
|
|
49
|
-
fi
|
|
50
|
-
|
|
51
|
-
# Block everything else at increment root
|
|
52
|
-
cat << EOF
|
|
53
|
-
{
|
|
54
|
-
"decision": "block",
|
|
55
|
-
"reason": "š« BLOCKED: File '$FILENAME' should be in a subfolder, not at increment root\n\nā ļø CLAUDE.md Folder Structure Rule:\n Inside increment folders - ONLY at root: spec.md, plan.md, tasks.md, metadata.json\n Everything else ā subfolders: reports/, scripts/, logs/, backups/, docs/\n\nš CORRECT structure:\n .specweave/increments/####-name/reports/COMPLETION_REPORT.md ā
\n .specweave/increments/####-name/reports/COMPLETION_SUMMARY.md ā
\n .specweave/increments/####-name/scripts/analyze.sh ā
\n\nā WRONG structure:\n .specweave/increments/####-name/COMPLETION_REPORT.md ā\n .specweave/increments/####-name/COMPLETION_SUMMARY.md ā\n\nš§ To fix: Create file in reports/ subfolder instead.\nUse: Write({ file_path: \".specweave/increments/####-name/reports/$FILENAME\", ... })\n\nSee: CLAUDE.md section 'Folder Structure'"
|
|
56
|
-
}
|
|
57
|
-
EOF
|
|
58
|
-
exit 2
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
# Allow all other writes (files in subfolders, or outside increments)
|
|
62
|
-
printf '{"decision":"allow"}'
|
|
63
|
-
exit 0
|