agileflow 2.78.0 → 2.80.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.
@@ -0,0 +1,227 @@
1
+ # AgileFlow Damage Control - Security Patterns
2
+ #
3
+ # This file defines patterns for blocking destructive commands and protecting
4
+ # sensitive paths. The damage control hooks read this file to determine
5
+ # whether to allow, block, or ask for confirmation before executing commands.
6
+ #
7
+ # Exit codes from hooks:
8
+ # 0 = Allow command to proceed
9
+ # 2 = Block command (show error to user)
10
+ #
11
+ # To ask for confirmation, hooks output JSON: {"result": "ask", "message": "..."}
12
+
13
+ # ============================================================================
14
+ # BASH TOOL PATTERNS
15
+ # ============================================================================
16
+ # Regex patterns to match against bash commands
17
+ # If matched with ask: false (or no ask key), command is BLOCKED
18
+ # If matched with ask: true, user is asked for confirmation
19
+
20
+ bashToolPatterns:
21
+ # Recursive/force deletion
22
+ - pattern: '\brm\s+-[rRf]'
23
+ reason: 'rm with recursive or force flags can destroy entire directories'
24
+
25
+ - pattern: '\brm\s+.*--no-preserve-root'
26
+ reason: 'rm with --no-preserve-root is catastrophically dangerous'
27
+
28
+ - pattern: '\brm\s+-rf\s+/'
29
+ reason: 'rm -rf on root directory would destroy the entire system'
30
+
31
+ # SQL destructive commands without WHERE clause
32
+ - pattern: 'DELETE\s+FROM\s+\w+\s*;'
33
+ reason: 'DELETE without WHERE clause would delete all records'
34
+
35
+ - pattern: 'TRUNCATE\s+(TABLE\s+)?\w+'
36
+ reason: 'TRUNCATE removes all data from table'
37
+
38
+ - pattern: 'DROP\s+(TABLE|DATABASE|SCHEMA|INDEX)'
39
+ reason: 'DROP commands permanently destroy database objects'
40
+
41
+ # Git force operations
42
+ - pattern: 'git\s+push\s+.*--force'
43
+ reason: 'Force push can overwrite remote history'
44
+ ask: true
45
+
46
+ - pattern: 'git\s+push\s+.*-f\b'
47
+ reason: 'Force push can overwrite remote history'
48
+ ask: true
49
+
50
+ - pattern: 'git\s+reset\s+--hard'
51
+ reason: 'Hard reset discards uncommitted changes'
52
+ ask: true
53
+
54
+ # Format/wipe operations
55
+ - pattern: '\bmkfs\b'
56
+ reason: 'mkfs formats filesystems, destroying all data'
57
+
58
+ - pattern: '\bdd\s+.*of=/dev/'
59
+ reason: 'dd writing to device can destroy disk data'
60
+
61
+ - pattern: '\bshred\b'
62
+ reason: 'shred permanently destroys file data'
63
+
64
+ # Credential/secret exposure
65
+ - pattern: 'cat\s+.*\.env'
66
+ reason: 'Displaying .env may expose secrets'
67
+ ask: true
68
+
69
+ - pattern: 'cat\s+.*/\.ssh/'
70
+ reason: 'Displaying SSH keys is a security risk'
71
+
72
+ - pattern: 'cat\s+.*/credentials'
73
+ reason: 'Displaying credentials files is a security risk'
74
+
75
+ # Cloud CLI destructive operations
76
+ - pattern: 'aws\s+s3\s+rm\s+--recursive'
77
+ reason: 'Recursive S3 delete can destroy entire buckets'
78
+ ask: true
79
+
80
+ - pattern: 'aws\s+ec2\s+terminate-instances'
81
+ reason: 'Terminating EC2 instances is irreversible'
82
+ ask: true
83
+
84
+ - pattern: 'gcloud\s+.*delete'
85
+ reason: 'GCloud delete operations may be destructive'
86
+ ask: true
87
+
88
+ # Docker cleanup commands
89
+ - pattern: 'docker\s+system\s+prune\s+-a'
90
+ reason: 'Docker prune -a removes all unused images'
91
+ ask: true
92
+
93
+ - pattern: 'docker\s+volume\s+rm'
94
+ reason: 'Docker volume removal may delete persistent data'
95
+ ask: true
96
+
97
+ # npm/package manager dangerous commands
98
+ - pattern: 'npm\s+unpublish'
99
+ reason: 'npm unpublish can break dependent packages'
100
+ ask: true
101
+
102
+ - pattern: 'npm\s+deprecate'
103
+ reason: 'npm deprecate affects package visibility'
104
+ ask: true
105
+
106
+ # ============================================================================
107
+ # ASK PATTERNS (CONFIRMATION REQUIRED)
108
+ # ============================================================================
109
+ # These patterns trigger a confirmation prompt but don't block by default
110
+
111
+ askPatterns:
112
+ - pattern: 'DELETE\s+FROM\s+\w+\s+WHERE'
113
+ reason: 'Deleting specific records - confirm data is correct'
114
+
115
+ - pattern: 'UPDATE\s+\w+\s+SET'
116
+ reason: 'Updating records - confirm scope is correct'
117
+
118
+ - pattern: 'npm\s+publish'
119
+ reason: 'Publishing to npm is permanent'
120
+
121
+ - pattern: 'git\s+tag\s+-d'
122
+ reason: 'Deleting git tags'
123
+
124
+ - pattern: 'kubectl\s+delete'
125
+ reason: 'Kubernetes delete operations'
126
+
127
+ # ============================================================================
128
+ # PATH PROTECTION
129
+ # ============================================================================
130
+ # Three protection levels:
131
+ # zeroAccessPaths: Cannot read, write, edit, or delete
132
+ # readOnlyPaths: Can read, cannot modify or delete
133
+ # noDeletePaths: Can read and modify, cannot delete
134
+
135
+ zeroAccessPaths:
136
+ # System credentials
137
+ - ~/.ssh/
138
+ - ~/.gnupg/
139
+ - ~/.aws/credentials
140
+ - ~/.config/gcloud/
141
+
142
+ # Environment secrets
143
+ - .env
144
+ - .env.local
145
+ - .env.production
146
+ - .env.*.local
147
+
148
+ # Secret files
149
+ - '**/secrets/**'
150
+ - '**/credentials/**'
151
+ - '**/*.pem'
152
+ - '**/*.key'
153
+ - '**/id_rsa'
154
+ - '**/id_ed25519'
155
+
156
+ readOnlyPaths:
157
+ # System config
158
+ - /etc/
159
+ - ~/.bashrc
160
+ - ~/.zshrc
161
+ - ~/.profile
162
+
163
+ # Package locks (should use npm install, not edit directly)
164
+ - package-lock.json
165
+ - yarn.lock
166
+ - pnpm-lock.yaml
167
+
168
+ # Git internals
169
+ - .git/
170
+
171
+ noDeletePaths:
172
+ # AgileFlow core
173
+ - .agileflow/
174
+ - .agileflow/config.json
175
+ - .agileflow/scripts/
176
+
177
+ # Claude Code hooks and commands
178
+ - .claude/
179
+ - .claude/hooks/
180
+ - .claude/commands/
181
+ - .claude/settings.json
182
+
183
+ # Project documentation (can edit, can't delete)
184
+ - docs/09-agents/status.json
185
+ - CLAUDE.md
186
+ - AGENTS.md
187
+
188
+ # ============================================================================
189
+ # AGILEFLOW-SPECIFIC PROTECTION
190
+ # ============================================================================
191
+ # Additional patterns specific to AgileFlow projects
192
+
193
+ agileflowPatterns:
194
+ # Protect AgileFlow infrastructure
195
+ - pattern: 'rm.*\.agileflow'
196
+ reason: 'Deleting .agileflow would break AgileFlow installation'
197
+
198
+ - pattern: 'rm.*\.claude'
199
+ reason: 'Deleting .claude would break Claude Code configuration'
200
+
201
+ - pattern: 'rm.*status\.json'
202
+ reason: 'Deleting status.json would lose story tracking data'
203
+
204
+ # Dangerous npm operations in AgileFlow context
205
+ - pattern: 'npm\s+uninstall\s+agileflow'
206
+ reason: 'Uninstalling AgileFlow - confirm this is intentional'
207
+ ask: true
208
+
209
+ # ============================================================================
210
+ # CONFIGURATION
211
+ # ============================================================================
212
+ # Settings for the damage control system
213
+
214
+ config:
215
+ # Log blocked commands for pattern refinement
216
+ logBlocked: true
217
+ logPath: .agileflow/logs/damage-control.log
218
+
219
+ # Show detailed reason when blocking
220
+ showBlockReason: true
221
+
222
+ # Timeout for hook execution (seconds)
223
+ hookTimeout: 5
224
+
225
+ # Enable/disable prompt hooks (AI-based evaluation)
226
+ promptHooksEnabled: false
227
+ promptHookMessage: 'Evaluate if this command could cause irreversible damage. Block if dangerous.'
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * write-tool-damage-control.js - Enforce path protection for Write tool
5
+ *
6
+ * This PreToolUse hook runs before every Write tool execution.
7
+ * It checks the file path against patterns.yaml to block writes
8
+ * to protected paths.
9
+ *
10
+ * Path protection levels:
11
+ * zeroAccessPaths: Cannot read, write, edit, or delete
12
+ * readOnlyPaths: Can read, cannot write or delete
13
+ * noDeletePaths: Can read and write, cannot delete (Write is allowed)
14
+ *
15
+ * Exit codes:
16
+ * 0 = Allow write to proceed
17
+ * 2 = Block write
18
+ *
19
+ * Usage (as PreToolUse hook):
20
+ * node .claude/hooks/damage-control/write-tool-damage-control.js
21
+ *
22
+ * Environment:
23
+ * CLAUDE_TOOL_INPUT - JSON string with tool input (contains "file_path")
24
+ * CLAUDE_PROJECT_DIR - Project root directory
25
+ */
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+
30
+ // ANSI colors for output
31
+ const c = {
32
+ reset: '\x1b[0m',
33
+ bold: '\x1b[1m',
34
+ red: '\x1b[31m',
35
+ yellow: '\x1b[33m',
36
+ cyan: '\x1b[36m',
37
+ };
38
+
39
+ // Exit codes
40
+ const EXIT_ALLOW = 0;
41
+ const EXIT_BLOCK = 2;
42
+
43
+ /**
44
+ * Load path protection rules from patterns.yaml
45
+ */
46
+ function loadPathRules(projectDir) {
47
+ const locations = [
48
+ path.join(projectDir, '.claude/hooks/damage-control/patterns.yaml'),
49
+ path.join(projectDir, '.agileflow/hooks/damage-control/patterns.yaml'),
50
+ path.join(projectDir, 'patterns.yaml'),
51
+ ];
52
+
53
+ for (const loc of locations) {
54
+ if (fs.existsSync(loc)) {
55
+ try {
56
+ const content = fs.readFileSync(loc, 'utf8');
57
+ return parsePathRules(content);
58
+ } catch (e) {
59
+ console.error(`Warning: Could not parse ${loc}: ${e.message}`);
60
+ }
61
+ }
62
+ }
63
+
64
+ return getDefaultPathRules();
65
+ }
66
+
67
+ /**
68
+ * Parse path rules from YAML content
69
+ */
70
+ function parsePathRules(content) {
71
+ const rules = {
72
+ zeroAccessPaths: [],
73
+ readOnlyPaths: [],
74
+ noDeletePaths: [],
75
+ };
76
+
77
+ let currentSection = null;
78
+
79
+ const lines = content.split('\n');
80
+
81
+ for (const line of lines) {
82
+ if (line.trim().startsWith('#') || line.trim() === '') continue;
83
+
84
+ if (line.match(/^zeroAccessPaths:/)) {
85
+ currentSection = 'zeroAccessPaths';
86
+ continue;
87
+ }
88
+ if (line.match(/^readOnlyPaths:/)) {
89
+ currentSection = 'readOnlyPaths';
90
+ continue;
91
+ }
92
+ if (line.match(/^noDeletePaths:/)) {
93
+ currentSection = 'noDeletePaths';
94
+ continue;
95
+ }
96
+ if (line.match(/^(bashToolPatterns|askPatterns|agileflowPatterns|config):/)) {
97
+ currentSection = null;
98
+ continue;
99
+ }
100
+
101
+ if (currentSection && rules[currentSection]) {
102
+ const pathMatch = line.match(/^\s+-\s*['"]?(.+?)['"]?\s*$/);
103
+ if (pathMatch) {
104
+ rules[currentSection].push(pathMatch[1]);
105
+ }
106
+ }
107
+ }
108
+
109
+ return rules;
110
+ }
111
+
112
+ /**
113
+ * Default path rules if patterns.yaml not found
114
+ */
115
+ function getDefaultPathRules() {
116
+ return {
117
+ zeroAccessPaths: ['~/.ssh/', '~/.aws/credentials', '.env', '.env.local', '.env.production'],
118
+ readOnlyPaths: ['/etc/', '~/.bashrc', '~/.zshrc', 'package-lock.json', 'yarn.lock', '.git/'],
119
+ noDeletePaths: ['.agileflow/', '.claude/', 'docs/09-agents/status.json', 'CLAUDE.md'],
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Expand home directory in path
125
+ */
126
+ function expandHome(filePath) {
127
+ if (filePath.startsWith('~/')) {
128
+ return path.join(process.env.HOME || '', filePath.slice(2));
129
+ }
130
+ return filePath;
131
+ }
132
+
133
+ /**
134
+ * Check if a file path matches a pattern
135
+ */
136
+ function pathMatches(filePath, pattern) {
137
+ const expandedPattern = expandHome(pattern);
138
+ const normalizedFile = path.normalize(filePath);
139
+ const normalizedPattern = path.normalize(expandedPattern);
140
+
141
+ // Exact match
142
+ if (normalizedFile === normalizedPattern) return true;
143
+
144
+ // Directory prefix match
145
+ if (pattern.endsWith('/')) {
146
+ if (normalizedFile.startsWith(normalizedPattern)) return true;
147
+ }
148
+
149
+ // Glob pattern (**)
150
+ if (pattern.includes('**/')) {
151
+ const globPart = pattern.split('**/')[1];
152
+ if (normalizedFile.includes(globPart)) return true;
153
+ }
154
+
155
+ // Wildcard at end
156
+ if (pattern.endsWith('*')) {
157
+ const prefix = normalizedPattern.slice(0, -1);
158
+ if (normalizedFile.startsWith(prefix)) return true;
159
+ }
160
+
161
+ // Basename match
162
+ if (!pattern.includes('/') && !pattern.includes(path.sep)) {
163
+ const basename = path.basename(normalizedFile);
164
+ if (basename === pattern) return true;
165
+ if (pattern.endsWith('*')) {
166
+ const patternBase = pattern.slice(0, -1);
167
+ if (basename.startsWith(patternBase)) return true;
168
+ }
169
+ }
170
+
171
+ return false;
172
+ }
173
+
174
+ /**
175
+ * Check if file path is protected for writing
176
+ * Returns: { blocked: boolean, reason: string, level: string }
177
+ */
178
+ function checkPath(filePath, rules) {
179
+ // Check zero access paths (blocked completely)
180
+ for (const pattern of rules.zeroAccessPaths) {
181
+ if (pathMatches(filePath, pattern)) {
182
+ return {
183
+ blocked: true,
184
+ reason: `Path is in zero-access protected list: ${pattern}`,
185
+ level: 'zero-access',
186
+ };
187
+ }
188
+ }
189
+
190
+ // Check read-only paths (cannot write)
191
+ for (const pattern of rules.readOnlyPaths) {
192
+ if (pathMatches(filePath, pattern)) {
193
+ return {
194
+ blocked: true,
195
+ reason: `Path is read-only: ${pattern}`,
196
+ level: 'read-only',
197
+ };
198
+ }
199
+ }
200
+
201
+ // noDeletePaths allows writing, only blocks deletion
202
+ // so we don't block writes here
203
+
204
+ return { blocked: false, reason: null, level: null };
205
+ }
206
+
207
+ /**
208
+ * Main entry point
209
+ */
210
+ function main() {
211
+ const toolInput = process.env.CLAUDE_TOOL_INPUT;
212
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
213
+
214
+ if (!toolInput) {
215
+ process.exit(EXIT_ALLOW);
216
+ }
217
+
218
+ let input;
219
+ try {
220
+ input = JSON.parse(toolInput);
221
+ } catch (e) {
222
+ console.error('Error parsing CLAUDE_TOOL_INPUT:', e.message);
223
+ process.exit(EXIT_ALLOW);
224
+ }
225
+
226
+ const filePath = input.file_path;
227
+ if (!filePath) {
228
+ process.exit(EXIT_ALLOW);
229
+ }
230
+
231
+ // Resolve to absolute path
232
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectDir, filePath);
233
+
234
+ // Load rules
235
+ const rules = loadPathRules(projectDir);
236
+
237
+ // Check path
238
+ const result = checkPath(absolutePath, rules);
239
+
240
+ if (result.blocked) {
241
+ console.error(`${c.red}${c.bold}BLOCKED${c.reset}: ${result.reason}`);
242
+ console.error(`${c.yellow}File: ${filePath}${c.reset}`);
243
+ console.error(`${c.cyan}This file is protected by damage control (${result.level}).${c.reset}`);
244
+ process.exit(EXIT_BLOCK);
245
+ }
246
+
247
+ process.exit(EXIT_ALLOW);
248
+ }
249
+
250
+ if (require.main === module) {
251
+ main();
252
+ }
253
+
254
+ module.exports = { checkPath, loadPathRules, pathMatches };
@@ -17,14 +17,7 @@
17
17
 
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
-
21
- // Color codes for output
22
- const c = {
23
- red: '\x1b[38;5;203m',
24
- yellow: '\x1b[38;5;215m',
25
- reset: '\x1b[0m',
26
- dim: '\x1b[2m'
27
- };
20
+ const { c } = require('../lib/colors');
28
21
 
29
22
  /**
30
23
  * Find project root by looking for .agileflow directory
@@ -48,7 +41,7 @@ function parseSimpleYAML(content) {
48
41
  const config = {
49
42
  bashToolPatterns: [],
50
43
  askPatterns: [],
51
- agileflowProtections: []
44
+ agileflowProtections: [],
52
45
  };
53
46
 
54
47
  let currentSection = null;
@@ -76,13 +69,22 @@ function parseSimpleYAML(content) {
76
69
  currentPattern = null;
77
70
  } else if (trimmed.startsWith('- pattern:') && currentSection) {
78
71
  // New pattern entry
79
- const patternValue = trimmed.replace('- pattern:', '').trim().replace(/^["']|["']$/g, '');
72
+ const patternValue = trimmed
73
+ .replace('- pattern:', '')
74
+ .trim()
75
+ .replace(/^["']|["']$/g, '');
80
76
  currentPattern = { pattern: patternValue };
81
77
  config[currentSection].push(currentPattern);
82
78
  } else if (trimmed.startsWith('reason:') && currentPattern) {
83
- currentPattern.reason = trimmed.replace('reason:', '').trim().replace(/^["']|["']$/g, '');
79
+ currentPattern.reason = trimmed
80
+ .replace('reason:', '')
81
+ .trim()
82
+ .replace(/^["']|["']$/g, '');
84
83
  } else if (trimmed.startsWith('flags:') && currentPattern) {
85
- currentPattern.flags = trimmed.replace('flags:', '').trim().replace(/^["']|["']$/g, '');
84
+ currentPattern.flags = trimmed
85
+ .replace('flags:', '')
86
+ .trim()
87
+ .replace(/^["']|["']$/g, '');
86
88
  }
87
89
  }
88
90
 
@@ -96,7 +98,7 @@ function loadPatterns(projectRoot) {
96
98
  const configPaths = [
97
99
  path.join(projectRoot, '.agileflow/config/damage-control-patterns.yaml'),
98
100
  path.join(projectRoot, '.agileflow/config/damage-control-patterns.yml'),
99
- path.join(projectRoot, '.agileflow/templates/damage-control-patterns.yaml')
101
+ path.join(projectRoot, '.agileflow/templates/damage-control-patterns.yaml'),
100
102
  ];
101
103
 
102
104
  for (const configPath of configPaths) {
@@ -135,14 +137,14 @@ function validateCommand(command, config) {
135
137
  // Check blocked patterns (bashToolPatterns + agileflowProtections)
136
138
  const blockedPatterns = [
137
139
  ...(config.bashToolPatterns || []),
138
- ...(config.agileflowProtections || [])
140
+ ...(config.agileflowProtections || []),
139
141
  ];
140
142
 
141
143
  for (const rule of blockedPatterns) {
142
144
  if (matchesPattern(command, rule)) {
143
145
  return {
144
146
  action: 'block',
145
- reason: rule.reason || 'Command blocked by damage control'
147
+ reason: rule.reason || 'Command blocked by damage control',
146
148
  };
147
149
  }
148
150
  }
@@ -152,7 +154,7 @@ function validateCommand(command, config) {
152
154
  if (matchesPattern(command, rule)) {
153
155
  return {
154
156
  action: 'ask',
155
- reason: rule.reason || 'Please confirm this command'
157
+ reason: rule.reason || 'Please confirm this command',
156
158
  };
157
159
  }
158
160
  }
@@ -192,17 +194,21 @@ function main() {
192
194
  switch (result.action) {
193
195
  case 'block':
194
196
  // Output error message and block
195
- console.error(`${c.red}[BLOCKED]${c.reset} ${result.reason}`);
196
- console.error(`${c.dim}Command: ${command.substring(0, 100)}${command.length > 100 ? '...' : ''}${c.reset}`);
197
+ console.error(`${c.coral}[BLOCKED]${c.reset} ${result.reason}`);
198
+ console.error(
199
+ `${c.dim}Command: ${command.substring(0, 100)}${command.length > 100 ? '...' : ''}${c.reset}`
200
+ );
197
201
  process.exit(2);
198
202
  break;
199
203
 
200
204
  case 'ask':
201
205
  // Output JSON to trigger user confirmation
202
- console.log(JSON.stringify({
203
- result: 'ask',
204
- message: result.reason
205
- }));
206
+ console.log(
207
+ JSON.stringify({
208
+ result: 'ask',
209
+ message: result.reason,
210
+ })
211
+ );
206
212
  process.exit(0);
207
213
  break;
208
214
 
@@ -15,13 +15,7 @@
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
  const os = require('os');
18
-
19
- // Color codes for output
20
- const c = {
21
- red: '\x1b[38;5;203m',
22
- reset: '\x1b[0m',
23
- dim: '\x1b[2m'
24
- };
18
+ const { c } = require('../lib/colors');
25
19
 
26
20
  /**
27
21
  * Find project root by looking for .agileflow directory
@@ -54,7 +48,7 @@ function parseSimpleYAML(content) {
54
48
  const config = {
55
49
  zeroAccessPaths: [],
56
50
  readOnlyPaths: [],
57
- noDeletePaths: []
51
+ noDeletePaths: [],
58
52
  };
59
53
 
60
54
  let currentSection = null;
@@ -92,7 +86,7 @@ function loadPatterns(projectRoot) {
92
86
  const configPaths = [
93
87
  path.join(projectRoot, '.agileflow/config/damage-control-patterns.yaml'),
94
88
  path.join(projectRoot, '.agileflow/config/damage-control-patterns.yml'),
95
- path.join(projectRoot, '.agileflow/templates/damage-control-patterns.yaml')
89
+ path.join(projectRoot, '.agileflow/templates/damage-control-patterns.yaml'),
96
90
  ];
97
91
 
98
92
  for (const configPath of configPaths) {
@@ -168,7 +162,7 @@ function validatePath(filePath, config) {
168
162
  return {
169
163
  action: 'block',
170
164
  reason: `Zero-access path: ${zeroMatch}`,
171
- detail: 'This file is protected and cannot be accessed'
165
+ detail: 'This file is protected and cannot be accessed',
172
166
  };
173
167
  }
174
168
 
@@ -178,7 +172,7 @@ function validatePath(filePath, config) {
178
172
  return {
179
173
  action: 'block',
180
174
  reason: `Read-only path: ${readOnlyMatch}`,
181
- detail: 'This file is read-only and cannot be edited'
175
+ detail: 'This file is read-only and cannot be edited',
182
176
  };
183
177
  }
184
178
 
@@ -215,7 +209,7 @@ function main() {
215
209
  const result = validatePath(filePath, config);
216
210
 
217
211
  if (result.action === 'block') {
218
- console.error(`${c.red}[BLOCKED]${c.reset} ${result.reason}`);
212
+ console.error(`${c.coral}[BLOCKED]${c.reset} ${result.reason}`);
219
213
  console.error(`${c.dim}${result.detail}${c.reset}`);
220
214
  console.error(`${c.dim}File: ${filePath}${c.reset}`);
221
215
  process.exit(2);