agileflow 2.94.1 → 2.95.1
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/CHANGELOG.md +20 -0
- package/README.md +3 -3
- package/lib/colors.generated.js +117 -0
- package/lib/colors.js +59 -109
- package/lib/generator-factory.js +333 -0
- package/lib/path-utils.js +49 -0
- package/lib/session-registry.js +25 -15
- package/lib/smart-json-file.js +40 -32
- package/lib/state-machine.js +286 -0
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +7 -6
- package/scripts/archive-completed-stories.sh +86 -11
- package/scripts/babysit-context-restore.js +89 -0
- package/scripts/claude-tmux.sh +111 -5
- package/scripts/damage-control/bash-tool-damage-control.js +11 -247
- package/scripts/damage-control/edit-tool-damage-control.js +9 -249
- package/scripts/damage-control/write-tool-damage-control.js +9 -244
- package/scripts/generate-colors.js +314 -0
- package/scripts/lib/colors.generated.sh +82 -0
- package/scripts/lib/colors.sh +10 -70
- package/scripts/lib/configure-features.js +401 -0
- package/scripts/lib/context-loader.js +181 -52
- package/scripts/precompact-context.sh +54 -17
- package/scripts/session-coordinator.sh +2 -2
- package/scripts/session-manager.js +653 -10
- package/src/core/commands/audit.md +93 -0
- package/src/core/commands/auto.md +73 -0
- package/src/core/commands/babysit.md +169 -13
- package/src/core/commands/baseline.md +73 -0
- package/src/core/commands/batch.md +64 -0
- package/src/core/commands/blockers.md +60 -0
- package/src/core/commands/board.md +66 -0
- package/src/core/commands/choose.md +77 -0
- package/src/core/commands/ci.md +77 -0
- package/src/core/commands/compress.md +27 -1
- package/src/core/commands/configure.md +126 -10
- package/src/core/commands/council.md +74 -0
- package/src/core/commands/debt.md +72 -0
- package/src/core/commands/deploy.md +73 -0
- package/src/core/commands/deps.md +68 -0
- package/src/core/commands/docs.md +60 -0
- package/src/core/commands/feedback.md +68 -0
- package/src/core/commands/ideate.md +74 -0
- package/src/core/commands/impact.md +74 -0
- package/src/core/commands/install.md +529 -0
- package/src/core/commands/maintain.md +558 -0
- package/src/core/commands/metrics.md +75 -0
- package/src/core/commands/multi-expert.md +74 -0
- package/src/core/commands/packages.md +69 -0
- package/src/core/commands/readme-sync.md +64 -0
- package/src/core/commands/research/analyze.md +285 -121
- package/src/core/commands/research/import.md +281 -109
- package/src/core/commands/retro.md +76 -0
- package/src/core/commands/review.md +72 -0
- package/src/core/commands/rlm.md +83 -0
- package/src/core/commands/rpi.md +90 -0
- package/src/core/commands/session/cleanup.md +214 -12
- package/src/core/commands/session/end.md +155 -17
- package/src/core/commands/sprint.md +72 -0
- package/src/core/commands/story-validate.md +68 -0
- package/src/core/commands/template.md +69 -0
- package/src/core/commands/tests.md +83 -0
- package/src/core/commands/update.md +59 -0
- package/src/core/commands/validate-expertise.md +76 -0
- package/src/core/commands/velocity.md +74 -0
- package/src/core/commands/verify.md +91 -0
- package/src/core/commands/whats-new.md +69 -0
- package/src/core/commands/workflow.md +88 -0
- package/src/core/templates/command-documentation.md +187 -0
- package/tools/cli/commands/session.js +1171 -0
- package/tools/cli/commands/setup.js +2 -81
- package/tools/cli/installers/core/installer.js +0 -5
- package/tools/cli/installers/ide/claude-code.js +6 -0
- package/tools/cli/lib/config-manager.js +42 -5
|
@@ -1,259 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* edit-tool-damage-control.js -
|
|
4
|
+
* edit-tool-damage-control.js - PreToolUse hook for Edit tool
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* to protected paths.
|
|
9
|
-
*
|
|
10
|
-
* Path protection levels:
|
|
11
|
-
* zeroAccessPaths: Cannot read, write, edit, or delete
|
|
12
|
-
* readOnlyPaths: Can read, cannot modify or delete
|
|
13
|
-
* noDeletePaths: Can read and modify, cannot delete (Edit is allowed)
|
|
6
|
+
* Validates file paths against access control patterns in damage-control-patterns.yaml
|
|
7
|
+
* before allowing file edits. Part of AgileFlow's damage control system.
|
|
14
8
|
*
|
|
15
9
|
* Exit codes:
|
|
16
|
-
* 0
|
|
17
|
-
* 2
|
|
18
|
-
*
|
|
19
|
-
* Usage (as PreToolUse hook):
|
|
20
|
-
* node .claude/hooks/damage-control/edit-tool-damage-control.js
|
|
10
|
+
* 0 - Allow operation
|
|
11
|
+
* 2 - Block operation
|
|
21
12
|
*
|
|
22
|
-
*
|
|
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
|
|
13
|
+
* Usage: Configured as PreToolUse hook in .claude/settings.json
|
|
69
14
|
*/
|
|
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
|
-
* Supports:
|
|
136
|
-
* - Exact paths
|
|
137
|
-
* - Directory prefixes (ending with /)
|
|
138
|
-
* - Glob wildcards (**)
|
|
139
|
-
*/
|
|
140
|
-
function pathMatches(filePath, pattern) {
|
|
141
|
-
const expandedPattern = expandHome(pattern);
|
|
142
|
-
const normalizedFile = path.normalize(filePath);
|
|
143
|
-
const normalizedPattern = path.normalize(expandedPattern);
|
|
144
|
-
|
|
145
|
-
// Exact match
|
|
146
|
-
if (normalizedFile === normalizedPattern) return true;
|
|
147
|
-
|
|
148
|
-
// Directory prefix match (pattern ends with /)
|
|
149
|
-
if (pattern.endsWith('/')) {
|
|
150
|
-
if (normalizedFile.startsWith(normalizedPattern)) return true;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Glob pattern (**/)
|
|
154
|
-
if (pattern.includes('**/')) {
|
|
155
|
-
const globPart = pattern.split('**/')[1];
|
|
156
|
-
if (normalizedFile.includes(globPart)) return true;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Simple wildcard at end
|
|
160
|
-
if (pattern.endsWith('*')) {
|
|
161
|
-
const prefix = normalizedPattern.slice(0, -1);
|
|
162
|
-
if (normalizedFile.startsWith(prefix)) return true;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Basename match (for patterns like .env)
|
|
166
|
-
if (!pattern.includes('/') && !pattern.includes(path.sep)) {
|
|
167
|
-
const basename = path.basename(normalizedFile);
|
|
168
|
-
if (basename === pattern) return true;
|
|
169
|
-
// Pattern like .env* matching .env.local
|
|
170
|
-
if (pattern.endsWith('*')) {
|
|
171
|
-
const patternBase = pattern.slice(0, -1);
|
|
172
|
-
if (basename.startsWith(patternBase)) return true;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Check if file path is protected
|
|
181
|
-
* Returns: { blocked: boolean, reason: string, level: string }
|
|
182
|
-
*/
|
|
183
|
-
function checkPath(filePath, rules) {
|
|
184
|
-
// Check zero access paths (blocked completely)
|
|
185
|
-
for (const pattern of rules.zeroAccessPaths) {
|
|
186
|
-
if (pathMatches(filePath, pattern)) {
|
|
187
|
-
return {
|
|
188
|
-
blocked: true,
|
|
189
|
-
reason: `Path is in zero-access protected list: ${pattern}`,
|
|
190
|
-
level: 'zero-access',
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Check read-only paths (cannot edit)
|
|
196
|
-
for (const pattern of rules.readOnlyPaths) {
|
|
197
|
-
if (pathMatches(filePath, pattern)) {
|
|
198
|
-
return {
|
|
199
|
-
blocked: true,
|
|
200
|
-
reason: `Path is read-only: ${pattern}`,
|
|
201
|
-
level: 'read-only',
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// noDeletePaths allows editing, so we don't block here
|
|
207
|
-
// (deletion is handled by a different mechanism or file watcher)
|
|
208
|
-
|
|
209
|
-
return { blocked: false, reason: null, level: null };
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Main entry point
|
|
214
|
-
*/
|
|
215
|
-
function main() {
|
|
216
|
-
const toolInput = process.env.CLAUDE_TOOL_INPUT;
|
|
217
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
218
|
-
|
|
219
|
-
if (!toolInput) {
|
|
220
|
-
process.exit(EXIT_ALLOW);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
let input;
|
|
224
|
-
try {
|
|
225
|
-
input = JSON.parse(toolInput);
|
|
226
|
-
} catch (e) {
|
|
227
|
-
console.error('Error parsing CLAUDE_TOOL_INPUT:', e.message);
|
|
228
|
-
process.exit(EXIT_ALLOW);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const filePath = input.file_path;
|
|
232
|
-
if (!filePath) {
|
|
233
|
-
process.exit(EXIT_ALLOW);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Resolve to absolute path
|
|
237
|
-
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectDir, filePath);
|
|
238
|
-
|
|
239
|
-
// Load rules
|
|
240
|
-
const rules = loadPathRules(projectDir);
|
|
241
|
-
|
|
242
|
-
// Check path
|
|
243
|
-
const result = checkPath(absolutePath, rules);
|
|
244
|
-
|
|
245
|
-
if (result.blocked) {
|
|
246
|
-
console.error(`${c.red}${c.bold}BLOCKED${c.reset}: ${result.reason}`);
|
|
247
|
-
console.error(`${c.yellow}File: ${filePath}${c.reset}`);
|
|
248
|
-
console.error(`${c.cyan}This file is protected by damage control (${result.level}).${c.reset}`);
|
|
249
|
-
process.exit(EXIT_BLOCK);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
process.exit(EXIT_ALLOW);
|
|
253
|
-
}
|
|
254
15
|
|
|
255
|
-
|
|
256
|
-
main();
|
|
257
|
-
}
|
|
16
|
+
const { createPathHook } = require('../lib/damage-control-utils');
|
|
258
17
|
|
|
259
|
-
|
|
18
|
+
// Run the hook using factory
|
|
19
|
+
createPathHook('edit')();
|
|
@@ -1,254 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* write-tool-damage-control.js -
|
|
4
|
+
* write-tool-damage-control.js - PreToolUse hook for Write tool
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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)
|
|
6
|
+
* Validates file paths against access control patterns in damage-control-patterns.yaml
|
|
7
|
+
* before allowing file writes. Part of AgileFlow's damage control system.
|
|
14
8
|
*
|
|
15
9
|
* Exit codes:
|
|
16
|
-
* 0
|
|
17
|
-
* 2
|
|
18
|
-
*
|
|
19
|
-
* Usage (as PreToolUse hook):
|
|
20
|
-
* node .claude/hooks/damage-control/write-tool-damage-control.js
|
|
10
|
+
* 0 - Allow operation
|
|
11
|
+
* 2 - Block operation
|
|
21
12
|
*
|
|
22
|
-
*
|
|
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
|
|
13
|
+
* Usage: Configured as PreToolUse hook in .claude/settings.json
|
|
69
14
|
*/
|
|
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
15
|
|
|
250
|
-
|
|
251
|
-
main();
|
|
252
|
-
}
|
|
16
|
+
const { createPathHook } = require('../lib/damage-control-utils');
|
|
253
17
|
|
|
254
|
-
|
|
18
|
+
// Run the hook using factory
|
|
19
|
+
createPathHook('write')();
|