@torka/claude-workflows 0.1.0 → 0.3.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 (36) hide show
  1. package/README.md +34 -105
  2. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/completion-summary-implement-epic-with-subagents.md +103 -0
  3. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/steps/step-01-init.md +228 -0
  4. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/steps/step-01b-continue.md +298 -0
  5. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/steps/step-01c-new.md +434 -0
  6. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/steps/step-02-orchestrate.md +437 -0
  7. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/steps/step-03-complete.md +473 -0
  8. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/templates/epic-completion-report.md +62 -0
  9. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/validation/checklist.md +121 -0
  10. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/workflow-plan-implement-epic-with-subagents.md +758 -0
  11. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/workflow.md +101 -0
  12. package/bmad-workflows/bmm/workflows/4-implementation/implement-epic-with-subagents/workflow.yaml +87 -0
  13. package/examples/settings.local.example.json +0 -39
  14. package/install.js +15 -12
  15. package/package.json +3 -12
  16. package/skills/designer-founder/steps/step-01-context.md +171 -0
  17. package/skills/designer-founder/steps/step-01b-continue.md +75 -0
  18. package/skills/designer-founder/steps/step-02-scope.md +198 -0
  19. package/skills/designer-founder/steps/step-03-design.md +168 -0
  20. package/skills/designer-founder/steps/step-04-artifacts.md +292 -0
  21. package/skills/designer-founder/templates/component-strategy.md +35 -0
  22. package/skills/designer-founder/templates/design-brief.md +26 -0
  23. package/skills/designer-founder/templates/layouts.md +41 -0
  24. package/skills/designer-founder/templates/user-journeys.md +32 -0
  25. package/skills/designer-founder/tools/conversion.md +275 -0
  26. package/skills/designer-founder/tools/direct-mapping.md +222 -0
  27. package/skills/designer-founder/tools/magicpatterns.md +193 -0
  28. package/skills/designer-founder/tools/superdesign-assets/generate-theme.ts +193 -0
  29. package/skills/designer-founder/tools/superdesign-assets/superdesign-agent-instructions.md +375 -0
  30. package/skills/designer-founder/tools/superdesign.md +167 -0
  31. package/skills/designer-founder/tools/wireframe.md +181 -0
  32. package/skills/designer-founder/workflow.md +85 -0
  33. package/uninstall.js +97 -8
  34. package/hooks/auto_approve_safe.py +0 -261
  35. package/hooks/auto_approve_safe.rules.json +0 -134
  36. package/scripts/context-monitor.py +0 -175
@@ -0,0 +1,181 @@
1
+ # Wireframe Tool Execution
2
+
3
+ ## Overview
4
+
5
+ Text-based wireframing for structure-first design. Always available, no external tools required.
6
+
7
+ **Output:** ASCII wireframe or Excalidraw file
8
+ **Output Location:** Inline or `_bmad-output/planning-artifacts/ux-design/`
9
+
10
+ ---
11
+
12
+ ## Execution Flow
13
+
14
+ ### 1. Gather Structure Requirements
15
+
16
+ Ask clarifying questions:
17
+ ```
18
+ WIREFRAME MODE
19
+
20
+ Before I sketch the layout, let me understand the structure:
21
+
22
+ 1. What are the main sections/areas needed?
23
+ 2. What's the primary action users should take?
24
+ 3. Any specific layout preferences? (sidebar, full-width, centered, etc.)
25
+ ```
26
+
27
+ ### 2. Generate ASCII Wireframe
28
+
29
+ Create wireframe based on requirements:
30
+
31
+ ```
32
+ {feature_name} - WIREFRAME
33
+
34
+ ┌─────────────────────────────────────────────────────────────┐
35
+ │ [Logo] [Nav] [Nav] [Nav] [Avatar ▼] │
36
+ ├─────────────────────────────────────────────────────────────┤
37
+ │ │
38
+ │ ┌─────────────────────────────────────────────────┐ │
39
+ │ │ │ │
40
+ │ │ [Main Content Area] │ │
41
+ │ │ │ │
42
+ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
43
+ │ │ │ Card 1 │ │ Card 2 │ │ Card 3 │ │ │
44
+ │ │ │ │ │ │ │ │ │ │
45
+ │ │ └───────────┘ └───────────┘ └───────────┘ │ │
46
+ │ │ │ │
47
+ │ │ [Primary CTA Button] │ │
48
+ │ │ │ │
49
+ │ └─────────────────────────────────────────────────┘ │
50
+ │ │
51
+ ├─────────────────────────────────────────────────────────────┤
52
+ │ [Footer Link] [Footer Link] [Footer Link] © 2024 │
53
+ └─────────────────────────────────────────────────────────────┘
54
+ ```
55
+
56
+ ### 3. Add Component Labels
57
+
58
+ Below the wireframe, add component identification:
59
+
60
+ ```
61
+ COMPONENT MAPPING
62
+
63
+ ┌─ Header ─────────────────────────────────────────────────────┐
64
+ │ Logo: Image/SVG │
65
+ │ Navigation: Horizontal nav links (3-4 items) │
66
+ │ User Menu: Dropdown with avatar trigger │
67
+ └──────────────────────────────────────────────────────────────┘
68
+
69
+ ┌─ Main Content ───────────────────────────────────────────────┐
70
+ │ Section Title: h1 or h2 │
71
+ │ Card Grid: 3-column on desktop, stack on mobile │
72
+ │ Card: Icon + Title + Description + Optional action │
73
+ │ CTA: Primary button, centered │
74
+ └──────────────────────────────────────────────────────────────┘
75
+
76
+ ┌─ Footer ─────────────────────────────────────────────────────┐
77
+ │ Links: Horizontal list │
78
+ │ Copyright: Text, right-aligned │
79
+ └──────────────────────────────────────────────────────────────┘
80
+ ```
81
+
82
+ ### 4. Present Options
83
+
84
+ ```
85
+ WIREFRAME COMPLETE
86
+
87
+ Options:
88
+ [E] Edit - Modify the structure
89
+ [M] Mobile - Show mobile layout variant
90
+ [X] Excalidraw - Generate as Excalidraw file
91
+ [C] Continue - Structure is approved
92
+ ```
93
+
94
+ **If E (Edit):**
95
+ - Ask what changes are needed
96
+ - Regenerate wireframe
97
+ - Return to step 4
98
+
99
+ **If M (Mobile):**
100
+ Generate mobile variant:
101
+ ```
102
+ {feature_name} - MOBILE WIREFRAME
103
+
104
+ ┌─────────────────────┐
105
+ │ [≡] [Logo] [👤] │
106
+ ├─────────────────────┤
107
+ │ │
108
+ │ [Main Content] │
109
+ │ │
110
+ │ ┌───────────────┐ │
111
+ │ │ Card 1 │ │
112
+ │ └───────────────┘ │
113
+ │ │
114
+ │ ┌───────────────┐ │
115
+ │ │ Card 2 │ │
116
+ │ └───────────────┘ │
117
+ │ │
118
+ │ ┌───────────────┐ │
119
+ │ │ Card 3 │ │
120
+ │ └───────────────┘ │
121
+ │ │
122
+ │ [Primary CTA] │
123
+ │ │
124
+ ├─────────────────────┤
125
+ │ [Links] [©] │
126
+ └─────────────────────┘
127
+
128
+ Changes from Desktop:
129
+ - Nav collapsed to hamburger menu
130
+ - Cards stack vertically
131
+ - Full-width buttons
132
+ ```
133
+
134
+ **If X (Excalidraw):**
135
+ - Invoke create-excalidraw-wireframe workflow
136
+ - Or generate Excalidraw JSON directly
137
+ - Save to `_bmad-output/planning-artifacts/ux-design/{feature}-wireframe.excalidraw`
138
+
139
+ **If C (Continue):**
140
+ - Return control to parent step
141
+
142
+ ---
143
+
144
+ ## Output State
145
+
146
+ After completion, set:
147
+
148
+ ```yaml
149
+ design:
150
+ tool_used: wireframe
151
+ output_location: "inline" # or excalidraw file path
152
+ output_format: ascii
153
+ needs_conversion: true
154
+ wireframe_content: "{the ascii wireframe}"
155
+ component_mapping: "{the component labels}"
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Wireframe Symbols Reference
161
+
162
+ ```
163
+ ┌───┐ Box/Container
164
+ │ │
165
+ └───┘
166
+
167
+ [Text] Button or clickable element
168
+ (Text) Rounded/pill element
169
+ <Text> Input field
170
+ {Text} Dynamic content placeholder
171
+
172
+ ────── Horizontal line/divider
173
+ │ Vertical line/divider
174
+
175
+ [≡] Hamburger menu
176
+ [👤] User avatar
177
+ [▼] Dropdown indicator
178
+ [×] Close button
179
+ [+] Add button
180
+ [←] Back navigation
181
+ ```
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: designer-founder
3
+ description: Transform ideas into dev-ready frontend artifacts. Works for greenfield projects and mid-project feature design.
4
+ web_bundle: true
5
+ ---
6
+
7
+ # Designer-Founder Workflow
8
+
9
+ **Goal:** Transform design ideas and concepts into production-ready artifacts for frontend development, optimized for solo developers who prioritize speed and library reuse.
10
+
11
+ ---
12
+
13
+ ## PERSONA
14
+
15
+ You are an expert UI/UX designer and visual design specialist who:
16
+
17
+ - **Advocates strongly** for shadcn/ui + Tailwind CSS
18
+ - **Thinks in components**, not pixels - always map to existing libraries first
19
+ - **Balances speed with maintainability** - just enough design, no over-specification
20
+ - **Draws inspiration** from Apple, Stripe, Airbnb, Linear, Vercel
21
+ - **Focuses on decisions**, not documentation - capture choices, not specs
22
+
23
+ ---
24
+
25
+ ## PHILOSOPHY
26
+
27
+ 1. **Library-first** - Start from "what existing component can I use?" not "let me design from scratch"
28
+ 2. **Decision-focused** - Capture choices, not specifications (the library handles details)
29
+ 3. **Just enough** - Wireframe the layout, pick components, move on
30
+ 4. **Speed over perfection** - A working prototype beats a perfect mockup
31
+
32
+ ---
33
+
34
+ ## WORKFLOW ARCHITECTURE
35
+
36
+ This uses **micro-file architecture** with 4 core steps:
37
+
38
+ | Step | Name | Purpose |
39
+ |------|------|---------|
40
+ | 1 | Context & Mode | Detect project state, select mode (Quick/Production) |
41
+ | 2 | Scope & Inspiration | Define what to design, gather references |
42
+ | 3 | Design | Execute design using selected tool |
43
+ | 4 | Convert & Artifacts | Transform to dev-ready output |
44
+
45
+ **Quick Prototype Mode:** Steps 1 → 3 (skip detailed artifacts)
46
+ **Production Mode:** Steps 1 → 2 → 3 → 4 (full flow)
47
+
48
+ ---
49
+
50
+ ## INITIALIZATION
51
+
52
+ ### Configuration Loading
53
+
54
+ Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
55
+
56
+ - `project_name`, `output_folder`, `planning_artifacts`, `user_name`
57
+ - `communication_language`, `document_output_language`
58
+ - `date` as system-generated current datetime
59
+
60
+ ### Paths
61
+
62
+ - `installed_path` = `{project-root}/_bmad/bmm/workflows/2-plan-workflows/designer-founder`
63
+ - `output_folder` = `{planning_artifacts}/ux-design`
64
+ - `superdesign_folder` = `{project-root}/.superdesign/design_iterations`
65
+
66
+ ### Tool Detection
67
+
68
+ On workflow start, detect available tools:
69
+
70
+ ```
71
+ MagicPatterns MCP: [check if mcp__magicpatterns tools available]
72
+ shadcn MCP: [check if mcp__shadcn tools available]
73
+ Playwright MCP: [check if mcp__playwright tools available]
74
+ SuperDesign: [check if .superdesign/ folder and instructions exist]
75
+ ```
76
+
77
+ Adjust tool menus based on availability. Tools marked as unavailable should show "(not configured)" in menus.
78
+
79
+ ---
80
+
81
+ ## EXECUTION
82
+
83
+ - YOU MUST ALWAYS communicate in `{communication_language}` with a collaborative, peer-to-peer tone
84
+ - You are a design expert working WITH the user, not FOR them
85
+ - Load and execute `steps/step-01-context.md` to begin the workflow
package/uninstall.js CHANGED
@@ -51,10 +51,58 @@ const INSTALLED_FILES = {
51
51
  'NON-STORY-AGENT-TEMPLATE.md',
52
52
  'COMMUNITY-REPOS.md',
53
53
  ],
54
- scripts: [
55
- 'auto_approve_safe.py',
56
- 'auto_approve_safe.rules.json',
57
- 'context-monitor.py',
54
+ 'skills/designer-founder': [
55
+ 'workflow.md',
56
+ ],
57
+ 'skills/designer-founder/steps': [
58
+ 'step-01-context.md',
59
+ 'step-01b-continue.md',
60
+ 'step-02-scope.md',
61
+ 'step-03-design.md',
62
+ 'step-04-artifacts.md',
63
+ ],
64
+ 'skills/designer-founder/templates': [
65
+ 'component-strategy.md',
66
+ 'design-brief.md',
67
+ 'layouts.md',
68
+ 'user-journeys.md',
69
+ ],
70
+ 'skills/designer-founder/tools': [
71
+ 'conversion.md',
72
+ 'direct-mapping.md',
73
+ 'magicpatterns.md',
74
+ 'superdesign.md',
75
+ 'wireframe.md',
76
+ ],
77
+ 'skills/designer-founder/tools/superdesign-assets': [
78
+ 'generate-theme.ts',
79
+ 'superdesign-agent-instructions.md',
80
+ ],
81
+ };
82
+
83
+ /**
84
+ * BMAD workflow files installed to _bmad/ (relative to project root)
85
+ * These use ../ from .claude/ to reach project root
86
+ */
87
+ const BMAD_INSTALLED_FILES = {
88
+ '../_bmad/bmm/workflows/4-implementation/implement-epic-with-subagents': [
89
+ 'workflow.md',
90
+ 'workflow.yaml',
91
+ 'workflow-plan-implement-epic-with-subagents.md',
92
+ 'completion-summary-implement-epic-with-subagents.md',
93
+ ],
94
+ '../_bmad/bmm/workflows/4-implementation/implement-epic-with-subagents/steps': [
95
+ 'step-01-init.md',
96
+ 'step-01b-continue.md',
97
+ 'step-01c-new.md',
98
+ 'step-02-orchestrate.md',
99
+ 'step-03-complete.md',
100
+ ],
101
+ '../_bmad/bmm/workflows/4-implementation/implement-epic-with-subagents/templates': [
102
+ 'epic-completion-report.md',
103
+ ],
104
+ '../_bmad/bmm/workflows/4-implementation/implement-epic-with-subagents/validation': [
105
+ 'checklist.md',
58
106
  ],
59
107
  };
60
108
 
@@ -159,10 +207,51 @@ function uninstall() {
159
207
  }
160
208
  }
161
209
 
162
- // Try to remove agent-creator skill directory if empty
163
- const skillDir = path.join(targetBase, 'skills', 'agent-creator');
164
- if (removeEmptyDir(skillDir)) {
165
- log(` ○ Removed empty directory: skills/agent-creator/`, 'yellow');
210
+ // Remove BMAD workflow files (installed to _bmad/ relative to project root)
211
+ log('\n' + colors.bold + 'BMAD Workflows' + colors.reset);
212
+ for (const [dir, files] of Object.entries(BMAD_INSTALLED_FILES)) {
213
+ const displayDir = dir.replace('../', '');
214
+ log(`\n${colors.bold}${displayDir}/${colors.reset}`);
215
+ for (const file of files) {
216
+ const filePath = path.join(targetBase, dir, file);
217
+ removeFile(filePath, stats);
218
+ }
219
+
220
+ // Try to remove empty directories
221
+ const dirPath = path.join(targetBase, dir);
222
+ if (removeEmptyDir(dirPath)) {
223
+ log(` ○ Removed empty directory: ${displayDir}/`, 'yellow');
224
+ }
225
+ }
226
+
227
+ // Clean up empty BMAD directories (from deepest to shallowest)
228
+ const bmadDirs = [
229
+ '../_bmad/bmm/workflows/4-implementation/implement-epic-with-subagents/validation',
230
+ '../_bmad/bmm/workflows/4-implementation/implement-epic-with-subagents/templates',
231
+ '../_bmad/bmm/workflows/4-implementation/implement-epic-with-subagents/steps',
232
+ '../_bmad/bmm/workflows/4-implementation/implement-epic-with-subagents',
233
+ ];
234
+ for (const bmadSubDir of bmadDirs) {
235
+ const dirPath = path.join(targetBase, bmadSubDir);
236
+ if (removeEmptyDir(dirPath)) {
237
+ log(` ○ Removed empty directory: ${bmadSubDir.replace('../', '')}/`, 'yellow');
238
+ }
239
+ }
240
+
241
+ // Try to remove skill directories if empty
242
+ const skillDirs = [
243
+ 'skills/designer-founder/tools/superdesign-assets',
244
+ 'skills/designer-founder/tools',
245
+ 'skills/designer-founder/templates',
246
+ 'skills/designer-founder/steps',
247
+ 'skills/designer-founder',
248
+ 'skills/agent-creator',
249
+ ];
250
+ for (const skillSubDir of skillDirs) {
251
+ const dirPath = path.join(targetBase, skillSubDir);
252
+ if (removeEmptyDir(dirPath)) {
253
+ log(` ○ Removed empty directory: ${skillSubDir}/`, 'yellow');
254
+ }
166
255
  }
167
256
 
168
257
  // Summary
@@ -1,261 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Claude Code Hook: Auto-approve safe tool usage for solo dev workflows.
4
-
5
- Handles PreToolUse events to:
6
- - Auto-allow known-safe commands (read-only, tests, linting)
7
- - Deny obviously dangerous commands
8
- - Defer everything else to normal permission system ("ask")
9
-
10
- Install:
11
- 1. mkdir -p ~/.claude/hooks && chmod 700 ~/.claude/hooks
12
- 2. Save this file to ~/.claude/hooks/auto_approve_safe.py
13
- 3. chmod +x ~/.claude/hooks/auto_approve_safe.py
14
- 4. Add hook config to ~/.claude/settings.json
15
- """
16
-
17
- import json
18
- import os
19
- import re
20
- import sys
21
- from datetime import datetime, timezone
22
- from pathlib import Path
23
-
24
- # Max-autonomy default:
25
- # - Allow reads/searches
26
- # - Allow edits/writes except for sensitive paths
27
- # - Allow bash commands only if they match allowlist (supports simple compound commands)
28
- #
29
- # Debugging:
30
- # Set this to True temporarily to log every decision to a local jsonl file.
31
- ENABLE_DECISION_LOG = True
32
-
33
-
34
- def load_rules() -> dict:
35
- """Load rules from global and project-specific config files."""
36
- rules = {"allow_patterns": [], "deny_patterns": [], "sensitive_paths": []}
37
-
38
- # # Load global rules
39
- # global_rules_path = Path.home() / ".claude" / "hooks" / "auto_approve_safe.rules.json"
40
- # if global_rules_path.exists():
41
- # try:
42
- # with open(global_rules_path) as f:
43
- # global_rules = json.load(f)
44
- # for key in rules:
45
- # rules[key].extend(global_rules.get(key, []))
46
- # except (json.JSONDecodeError, IOError) as e:
47
- # print(f"Warning: Could not load global rules: {e}", file=sys.stderr)
48
-
49
- # Load project-specific rules (merge with global)
50
- project_rules_path = Path.cwd() / ".claude" / "scripts" / "auto_approve_safe.rules.json"
51
- if project_rules_path.exists():
52
- try:
53
- with open(project_rules_path) as f:
54
- project_rules = json.load(f)
55
- for key in rules:
56
- rules[key].extend(project_rules.get(key, []))
57
- except (json.JSONDecodeError, IOError) as e:
58
- print(f"Warning: Could not load project rules: {e}", file=sys.stderr)
59
-
60
- return rules
61
-
62
-
63
- def matches_any_pattern(text: str, patterns: list[str]) -> bool:
64
- """Check if text matches any of the given regex patterns."""
65
- for pattern in patterns:
66
- try:
67
- if re.search(pattern, text, re.IGNORECASE):
68
- return True
69
- except re.error:
70
- continue
71
- return False
72
-
73
-
74
- def check_sensitive_path(file_path: str, sensitive_patterns: list[str]) -> bool:
75
- """Check if file path matches sensitive path patterns."""
76
- if not file_path:
77
- return False
78
- return matches_any_pattern(file_path, sensitive_patterns)
79
-
80
-
81
- def split_compound_shell_command(command: str) -> list[str]:
82
- """Split a shell command on simple compound operators (heuristic, not a full parser)."""
83
- command = (command or "").strip()
84
- if not command:
85
- return []
86
- # Common patterns produced by agents: `cd x && pnpm test`, `cmd1; cmd2`
87
- return [p.strip() for p in re.split(r"\s*(?:&&|;)\s*", command) if p.strip()]
88
-
89
-
90
- def is_shell_file_read_command(command: str) -> bool:
91
- """Detect common shell file-read commands that could exfiltrate secrets."""
92
- return bool(re.search(r"^\s*(cat|head|tail|less)\b", command or "", re.IGNORECASE))
93
-
94
-
95
- def summarize_tool_input(tool_name: str, tool_input: dict) -> dict:
96
- """Small, reviewable summary for decision logs."""
97
- if tool_name == "Bash":
98
- return {"command": tool_input.get("command", "")}
99
- if tool_name in ("Read", "Write", "Edit", "MultiEdit"):
100
- return {"file_path": tool_input.get("file_path", "")}
101
- return {"tool_input_keys": list((tool_input or {}).keys())}
102
-
103
-
104
- def log_decision(tool_name: str, tool_input: dict, decision: str, reason: str) -> None:
105
- """Append a decision record to a jsonl file when debugging is enabled."""
106
- if not ENABLE_DECISION_LOG:
107
- return
108
-
109
- log_path = Path.cwd() / ".claude" / "auto_approve_safe.decisions.jsonl"
110
- record = {
111
- "ts": datetime.now(timezone.utc).isoformat(),
112
- "cwd": str(Path.cwd()),
113
- "tool_name": tool_name,
114
- "decision": decision,
115
- "reason": reason,
116
- "input": summarize_tool_input(tool_name, tool_input or {}),
117
- }
118
-
119
- try:
120
- log_path.parent.mkdir(parents=True, exist_ok=True)
121
- with open(log_path, "a", encoding="utf-8") as f:
122
- f.write(json.dumps(record, ensure_ascii=False) + "\n")
123
- except Exception as e:
124
- # Never break tool execution because logging failed.
125
- print(f"Warning: Could not write decision log: {e}", file=sys.stderr)
126
-
127
-
128
- def make_decision(tool_name: str, tool_input: dict, rules: dict) -> tuple[str, str]:
129
- """
130
- Determine permission decision for a tool call.
131
-
132
- Returns:
133
- tuple: (decision, reason)
134
- decision: "allow", "deny", or "ask"
135
- reason: Human-readable explanation
136
-
137
- Notes on integration with Claude Code:
138
- - "allow" short-circuits Claude Code prompts
139
- - "deny" blocks the tool
140
- - "ask" defers to Claude Code's built-in permission system
141
-
142
- This file is tuned for maximum autonomy by default, while:
143
- - denying obvious dangerous commands
144
- - blocking edits to sensitive paths
145
- - prompting for reads of sensitive paths
146
- """
147
- tool_input = tool_input or {}
148
-
149
- # Handle Bash commands
150
- if tool_name == "Bash":
151
- command = (tool_input.get("command", "") or "").strip()
152
- if not command:
153
- return "ask", "Empty command"
154
-
155
- segments = split_compound_shell_command(command)
156
- if not segments:
157
- return "ask", "Empty command"
158
-
159
- # Deny wins if any segment matches a deny pattern.
160
- for seg in segments:
161
- if matches_any_pattern(seg, rules["deny_patterns"]):
162
- return "deny", "Command matches dangerous pattern"
163
-
164
- # If a segment looks like it could read a file, apply sensitive path checks.
165
- # (Prevents silently allowing: `cat .env`, `head ~/.ssh/id_rsa`, etc.)
166
- for seg in segments:
167
- if is_shell_file_read_command(seg) and matches_any_pattern(seg, rules["sensitive_paths"]):
168
- return "ask", "Bash command may read sensitive data"
169
-
170
- # Max autonomy, but still require an allowlist match per segment.
171
- # Add common "glue" patterns that agents use.
172
- glue_allow_patterns = [
173
- r"^cd\s+\S+(\s+.*)?$",
174
- r"^pushd\s+\S+(\s+.*)?$",
175
- r"^popd$",
176
- r"^export\s+[A-Za-z_][A-Za-z0-9_]*=.*$",
177
- r"^(true|false)$",
178
- ]
179
-
180
- for seg in segments:
181
- if matches_any_pattern(seg, rules["allow_patterns"]):
182
- continue
183
- if matches_any_pattern(seg, glue_allow_patterns):
184
- continue
185
- return "ask", f"Command not in allowlist: {seg}"
186
-
187
- return "allow", "Matches safe allowlist"
188
-
189
- # Handle Read tool - check for sensitive files
190
- if tool_name == "Read":
191
- file_path = tool_input.get("file_path", "")
192
- if check_sensitive_path(file_path, rules["sensitive_paths"]):
193
- return "ask", "File may contain sensitive data"
194
- return "allow", "Read operations are generally safe"
195
-
196
- # Handle Grep/Glob - generally safe read-only operations
197
- if tool_name in ("Grep", "Glob"):
198
- return "allow", "Search operations are read-only"
199
-
200
- # Handle Write/Edit - max autonomy by default; still protect sensitive paths.
201
- if tool_name in ("Write", "Edit", "MultiEdit"):
202
- file_path = tool_input.get("file_path", "")
203
- if check_sensitive_path(file_path, rules["sensitive_paths"]):
204
- return "deny", "Cannot modify sensitive files"
205
- return "allow", "Write operations are generally safe"
206
-
207
- # Default: defer to normal permission system
208
- return "ask", "Unknown tool, deferring to permission system"
209
-
210
-
211
- def output_decision(decision: str, reason: str) -> None:
212
- """Output the hook decision in Claude Code's expected format."""
213
- output = {
214
- "hookSpecificOutput": {
215
- "hookEventName": "PreToolUse",
216
- "permissionDecision": decision,
217
- "permissionDecisionReason": reason
218
- }
219
- }
220
- print(json.dumps(output))
221
-
222
-
223
- def main():
224
- """Main entry point for the hook."""
225
- try:
226
- # Read input from stdin
227
- input_data = sys.stdin.read()
228
- if not input_data.strip():
229
- output_decision("ask", "No input received")
230
- return
231
-
232
- data = json.loads(input_data)
233
-
234
- # Extract tool information
235
- tool_name = data.get("tool_name", "")
236
- tool_input = data.get("tool_input", {})
237
-
238
- # Load rules
239
- rules = load_rules()
240
-
241
- # Make decision
242
- decision, reason = make_decision(tool_name, tool_input, rules)
243
-
244
- # Optional debug log
245
- log_decision(tool_name, tool_input, decision, reason)
246
-
247
- # Output result
248
- output_decision(decision, reason)
249
-
250
- except json.JSONDecodeError as e:
251
- print(f"Error parsing input JSON: {e}", file=sys.stderr)
252
- output_decision("ask", "Failed to parse input")
253
- except Exception as e:
254
- print(f"Hook error: {e}", file=sys.stderr)
255
- output_decision("ask", f"Hook error: {e}")
256
-
257
-
258
- if __name__ == "__main__":
259
- main()
260
-
261
-