forked from popup-studio-ai/bkit-claude-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpre-write.js
More file actions
197 lines (175 loc) · 6.25 KB
/
pre-write.js
File metadata and controls
197 lines (175 loc) · 6.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#!/usr/bin/env node
/**
* pre-write.js - Unified PreToolUse/BeforeTool hook for Write|Edit operations (v1.4.2)
* Supports: Claude Code (PreToolUse), Gemini CLI (BeforeTool)
*
* Purpose: PDCA check, task classification, convention hints, permission check
* Hook: PreToolUse (Claude Code) / BeforeTool (Gemini CLI)
* Philosophy: Automation First - Guide, don't block
*
* v1.4.2 Changes:
* - Added permission check integration (FR-05)
*
* v1.4.0 Changes:
* - Added debug logging for hook verification
* - Added PDCA status update for "do" phase
*
* Converted from: scripts/pre-write.sh
*/
const {
readStdinSync,
parseHookInput,
isSourceFile,
isCodeFile,
isEnvFile,
extractFeature,
findDesignDoc,
findPlanDoc,
classifyTaskByLines,
getPdcaLevel,
outputAllow,
outputBlock,
outputEmpty,
generateTaskGuidance,
debugLog,
updatePdcaStatus
} = require('../lib/common.js');
// v1.4.2: Permission Manager (FR-05)
let permissionManager;
try {
permissionManager = require('../lib/permission-manager.js');
} catch (e) {
// Fallback if module not available
permissionManager = null;
}
// Read input from stdin
const input = readStdinSync();
const { filePath, content } = parseHookInput(input);
// Debug log hook execution
debugLog('PreToolUse', 'Hook started', { filePath: filePath || 'none' });
// Skip if no file path
if (!filePath) {
debugLog('PreToolUse', 'Skipped - no file path');
outputEmpty();
process.exit(0);
}
// Collect context messages
const contextParts = [];
// ============================================================
// 0. Permission Check (v1.4.2 - FR-05)
// ============================================================
if (permissionManager) {
const toolName = input.tool_name || 'Write'; // Write or Edit
const permission = permissionManager.checkPermission(toolName, filePath);
if (permission === 'deny') {
debugLog('PreToolUse', 'Permission denied', { filePath, tool: toolName });
outputBlock(`${toolName} to ${filePath} is denied by permission policy.`);
process.exit(2);
}
if (permission === 'ask') {
contextParts.push(`${toolName} to ${filePath} requires confirmation.`);
debugLog('PreToolUse', 'Permission requires confirmation', { filePath, tool: toolName });
}
}
// ============================================================
// 1. Task Classification (v1.3.0 - Line-based, Automation First)
// ============================================================
let classification = 'quick_fix';
let pdcaLevel = 'none';
let lineCount = 0;
if (content) {
lineCount = content.split('\n').length;
classification = classifyTaskByLines(content);
pdcaLevel = getPdcaLevel(classification);
}
// ============================================================
// 2. PDCA Document Check (for source files)
// ============================================================
let feature = '';
let designDoc = '';
let planDoc = '';
if (isSourceFile(filePath)) {
feature = extractFeature(filePath);
if (feature) {
designDoc = findDesignDoc(feature);
planDoc = findPlanDoc(feature);
// Update PDCA status to "do" phase when source file is being written
updatePdcaStatus(feature, 'do', {
lastFile: filePath
});
debugLog('PreToolUse', 'PDCA status updated', {
feature,
phase: 'do',
hasDesignDoc: !!designDoc
});
}
}
// ============================================================
// 3. Generate PDCA Guidance (v1.3.0 - No blocking, guide only)
// ============================================================
switch (pdcaLevel) {
case 'none':
// Quick Fix - no guidance needed
break;
case 'light':
// Minor Change - light mention
contextParts.push(`Minor change (${lineCount} lines). PDCA optional.`);
break;
case 'recommended':
// Feature - recommend design doc
if (designDoc) {
contextParts.push(`Feature (${lineCount} lines). Design doc exists: ${designDoc}`);
} else if (feature) {
contextParts.push(`Feature (${lineCount} lines). Design doc recommended for '${feature}'. Consider /pdca-design ${feature}`);
} else {
contextParts.push(`Feature-level change (${lineCount} lines). Design doc recommended.`);
}
break;
case 'required':
// Major Feature - strongly recommend (but don't block)
if (designDoc) {
contextParts.push(`Major feature (${lineCount} lines). Design doc exists: ${designDoc}. Refer during implementation.`);
} else if (feature) {
contextParts.push(`Major feature (${lineCount} lines) without design doc. Strongly recommend /pdca-design ${feature} first.`);
} else {
contextParts.push(`Major feature (${lineCount} lines). Design doc strongly recommended before implementation.`);
}
break;
}
// Add reference to existing PDCA docs if not already mentioned
if (planDoc && !designDoc && pdcaLevel !== 'none' && pdcaLevel !== 'light') {
contextParts.push(`Plan exists at ${planDoc}. Design doc not yet created.`);
}
// ============================================================
// 4. Convention Hints (for code files)
// ============================================================
if (isCodeFile(filePath)) {
// Only add convention hints for larger changes
if (pdcaLevel === 'recommended' || pdcaLevel === 'required') {
contextParts.push('Conventions: Components=PascalCase, Functions=camelCase, Constants=UPPER_SNAKE_CASE');
}
} else if (isEnvFile(filePath)) {
contextParts.push('Env naming: NEXT_PUBLIC_* (client), DB_* (database), API_* (external), AUTH_* (auth)');
}
// ============================================================
// 5. Task System Guidance (v1.3.1 - FR-02)
// ============================================================
if (feature && (pdcaLevel === 'recommended' || pdcaLevel === 'required')) {
const taskHint = generateTaskGuidance('do', feature, 'design');
contextParts.push(taskHint);
}
// ============================================================
// Output combined context
// ============================================================
debugLog('PreToolUse', 'Hook completed', {
classification,
pdcaLevel,
feature: feature || 'none',
contextCount: contextParts.length
});
if (contextParts.length > 0) {
// v1.4.0: PreToolUse hook에 맞는 스키마 사용
outputAllow(contextParts.join(' | '), 'PreToolUse');
} else {
outputEmpty();
}