sumulige-claude 1.4.0 → 1.5.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.
- package/.claude/MEMORY.md +26 -0
- package/.claude/hooks/hook-registry.json +15 -0
- package/.claude/hooks/live-quality.cjs +286 -0
- package/.claude/hooks/plan-gate.cjs +173 -0
- package/.claude/hooks/pre-commit.cjs +15 -4
- package/.claude/quality-gate.json +19 -4
- package/.claude/rules/linus-style.md +54 -0
- package/.claude/settings.json +19 -1
- package/.claude/settings.local.json +12 -3
- package/.claude/skills/react-best-practices/SKILL.md +125 -0
- package/.claude/skills/threejs-fundamentals/SKILL.md +488 -0
- package/.claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/AGENTS.md +0 -54
- package/CHANGELOG.md +135 -0
- package/README.md +65 -1
- package/demos/power-3d-scatter.html +683 -0
- package/package.json +1 -1
- package/prompts/linus-architect.md +71 -0
- package/scripts/sync-to-home.sh +108 -0
- package/.claude/sessions/active-sessions.json +0 -1
- package/.claude/sessions/session_2026-01-22T13-07-26-625Z.json +0 -23
- package/.claude/skills/api-tester/SKILL.md +0 -61
- package/.claude/skills/api-tester/examples/basic.md +0 -3
- package/.claude/skills/api-tester/metadata.yaml +0 -30
- package/.claude/skills/api-tester/templates/default.md +0 -3
- package/.claude/skills/code-reviewer-123/SKILL.md +0 -61
- package/.claude/skills/code-reviewer-123/examples/basic.md +0 -3
- package/.claude/skills/code-reviewer-123/metadata.yaml +0 -30
- package/.claude/skills/code-reviewer-123/templates/default.md +0 -3
- package/.claude/skills/my-skill/SKILL.md +0 -61
- package/.claude/skills/my-skill/examples/basic.md +0 -3
- package/.claude/skills/my-skill/metadata.yaml +0 -30
- package/.claude/skills/my-skill/templates/default.md +0 -3
- package/.claude/skills/test-skill-name/SKILL.md +0 -61
- package/.claude/skills/test-skill-name/examples/basic.md +0 -3
- package/.claude/skills/test-skill-name/metadata.yaml +0 -30
- package/.claude/skills/test-skill-name/templates/default.md +0 -3
package/.claude/MEMORY.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
## 2026-01-23
|
|
2
|
+
|
|
3
|
+
### Session: 前端设计灵感 & 3D 可视化
|
|
4
|
+
|
|
5
|
+
- **主题**: 从 skills.sh 获取前端创意设计灵感
|
|
6
|
+
- **产出**:
|
|
7
|
+
- `demos/power-3d-scatter.html` - Three.js 功率-时间-心率 3D 散点图 demo
|
|
8
|
+
- 安装了 5 个设计相关 skills (web-design-guidelines, react-best-practices, threejs-fundamentals 等)
|
|
9
|
+
|
|
10
|
+
- **关联项目**: SynapseFlow / ApexOS
|
|
11
|
+
- 优化路线图: `synapseflow/docs/FRONTEND_OPTIMIZATION_ROADMAP.md`
|
|
12
|
+
|
|
13
|
+
- **核心改进方向**:
|
|
14
|
+
1. Typography: tabular-nums, text-wrap, 字体升级
|
|
15
|
+
2. 数据可视化: 曲线动画, hover 精确值, 历史对比
|
|
16
|
+
3. 3D 散点图: 接入真实数据, 区间筛选, 时间刷选
|
|
17
|
+
4. 交互: URL 状态同步, focus states, loading states
|
|
18
|
+
5. 品牌: 避免 AI 审美, 生成式视觉元素
|
|
19
|
+
|
|
20
|
+
- **待办**:
|
|
21
|
+
- [ ] 3D 散点图接入 ApexOS 真实 FIT 数据
|
|
22
|
+
- [ ] Power Duration 曲线添加绘制动画
|
|
23
|
+
- [ ] iLevels 进度条动画优化
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
1
27
|
## 2026-01-22
|
|
2
28
|
|
|
3
29
|
### Session 2026-01-22T13:07:26.622Z
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
"$schema": "hook-registry-schema.json",
|
|
3
3
|
"$comment": "Hook Dispatcher Registry - Controls which hooks run and when",
|
|
4
4
|
|
|
5
|
+
"plan-gate": {
|
|
6
|
+
"events": ["PreToolUse"],
|
|
7
|
+
"toolMatch": ["Write", "Edit"],
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"description": "强制规划检查 - 无批准计划阻止 Write/Edit"
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
"live-quality": {
|
|
13
|
+
"events": ["PostToolUse"],
|
|
14
|
+
"toolMatch": ["Write", "Edit"],
|
|
15
|
+
"debounce": 1000,
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"description": "实时质量检查 - 写入后立即检查代码质量"
|
|
18
|
+
},
|
|
19
|
+
|
|
5
20
|
"thinking-silent": {
|
|
6
21
|
"events": ["AgentStop"],
|
|
7
22
|
"debounce": 5000,
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Live Quality Gate - 实时质量检查
|
|
4
|
+
*
|
|
5
|
+
* PostToolUse 钩子:每次 Write/Edit 后立即检查代码质量
|
|
6
|
+
*
|
|
7
|
+
* 检查项:
|
|
8
|
+
* 1. 文件行数 ≤ 800
|
|
9
|
+
* 2. 函数长度 ≤ 50 行
|
|
10
|
+
* 3. 无硬编码密钥
|
|
11
|
+
* 4. 无 console.log
|
|
12
|
+
* 5. 基础代码规范
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
19
|
+
const GATE_CONFIG_FILE = path.join(PROJECT_DIR, '.claude/quality-gate.json');
|
|
20
|
+
|
|
21
|
+
// 默认规则配置
|
|
22
|
+
const DEFAULT_RULES = {
|
|
23
|
+
maxFileLines: 800,
|
|
24
|
+
maxFunctionLines: 50,
|
|
25
|
+
forbiddenPatterns: [
|
|
26
|
+
{
|
|
27
|
+
pattern: /console\.(log|debug|info)\s*\(/g,
|
|
28
|
+
message: '禁止 console.log/debug/info',
|
|
29
|
+
severity: 'warn'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
pattern: /api[_-]?key\s*[:=]\s*['"][^'"]{10,}['"]/gi,
|
|
33
|
+
message: '检测到可能的硬编码 API Key',
|
|
34
|
+
severity: 'error'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
pattern: /password\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
38
|
+
message: '检测到可能的硬编码密码',
|
|
39
|
+
severity: 'error'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
pattern: /secret\s*[:=]\s*['"][^'"]{10,}['"]/gi,
|
|
43
|
+
message: '检测到可能的硬编码 Secret',
|
|
44
|
+
severity: 'error'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
pattern: /TODO:|FIXME:|HACK:|XXX:/g,
|
|
48
|
+
message: '存在待处理标记',
|
|
49
|
+
severity: 'info'
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// 豁免模式
|
|
55
|
+
const EXEMPT_PATTERNS = [
|
|
56
|
+
/\.md$/,
|
|
57
|
+
/\.json$/,
|
|
58
|
+
/\.test\./,
|
|
59
|
+
/\.spec\./,
|
|
60
|
+
/__tests__/,
|
|
61
|
+
/node_modules/,
|
|
62
|
+
/\.min\./,
|
|
63
|
+
/\.bundle\./,
|
|
64
|
+
/dist\//,
|
|
65
|
+
/build\//
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 获取配置
|
|
70
|
+
*/
|
|
71
|
+
function getConfig() {
|
|
72
|
+
try {
|
|
73
|
+
if (fs.existsSync(GATE_CONFIG_FILE)) {
|
|
74
|
+
const config = JSON.parse(fs.readFileSync(GATE_CONFIG_FILE, 'utf-8'));
|
|
75
|
+
return { ...DEFAULT_RULES, ...(config.liveQuality || {}) };
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// 使用默认配置
|
|
79
|
+
}
|
|
80
|
+
return DEFAULT_RULES;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 检查文件是否豁免
|
|
85
|
+
*/
|
|
86
|
+
function isExempt(filePath) {
|
|
87
|
+
if (!filePath) return true;
|
|
88
|
+
return EXEMPT_PATTERNS.some(pattern => pattern.test(filePath));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 检测函数长度(简单实现)
|
|
93
|
+
*/
|
|
94
|
+
function detectLongFunctions(content, maxLines) {
|
|
95
|
+
const issues = [];
|
|
96
|
+
const lines = content.split('\n');
|
|
97
|
+
|
|
98
|
+
// 简单的函数检测:匹配 function 或箭头函数
|
|
99
|
+
const functionPatterns = [
|
|
100
|
+
/function\s+(\w+)\s*\(/g,
|
|
101
|
+
/(\w+)\s*[:=]\s*(?:async\s+)?function\s*\(/g,
|
|
102
|
+
/(\w+)\s*[:=]\s*(?:async\s+)?\([^)]*\)\s*=>/g,
|
|
103
|
+
/(\w+)\s*[:=]\s*(?:async\s+)?\w+\s*=>/g
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
let braceDepth = 0;
|
|
107
|
+
let functionStart = -1;
|
|
108
|
+
let functionName = '';
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < lines.length; i++) {
|
|
111
|
+
const line = lines[i];
|
|
112
|
+
|
|
113
|
+
// 检测函数开始
|
|
114
|
+
for (const pattern of functionPatterns) {
|
|
115
|
+
pattern.lastIndex = 0;
|
|
116
|
+
const match = pattern.exec(line);
|
|
117
|
+
if (match && functionStart === -1) {
|
|
118
|
+
functionStart = i;
|
|
119
|
+
functionName = match[1] || 'anonymous';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 计算大括号深度
|
|
124
|
+
const openBraces = (line.match(/{/g) || []).length;
|
|
125
|
+
const closeBraces = (line.match(/}/g) || []).length;
|
|
126
|
+
|
|
127
|
+
if (functionStart !== -1) {
|
|
128
|
+
braceDepth += openBraces - closeBraces;
|
|
129
|
+
|
|
130
|
+
if (braceDepth <= 0 && openBraces > 0) {
|
|
131
|
+
// 函数结束
|
|
132
|
+
const functionLength = i - functionStart + 1;
|
|
133
|
+
if (functionLength > maxLines) {
|
|
134
|
+
issues.push({
|
|
135
|
+
line: functionStart + 1,
|
|
136
|
+
message: `函数 '${functionName}' 超过 ${maxLines} 行 (当前 ${functionLength} 行)`,
|
|
137
|
+
severity: 'warn'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
functionStart = -1;
|
|
141
|
+
functionName = '';
|
|
142
|
+
braceDepth = 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return issues;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 检查代码质量
|
|
152
|
+
*/
|
|
153
|
+
function checkQuality(filePath, config) {
|
|
154
|
+
const issues = [];
|
|
155
|
+
|
|
156
|
+
if (!fs.existsSync(filePath)) {
|
|
157
|
+
return issues;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
161
|
+
const lines = content.split('\n');
|
|
162
|
+
|
|
163
|
+
// 1. 检查文件行数
|
|
164
|
+
if (lines.length > config.maxFileLines) {
|
|
165
|
+
issues.push({
|
|
166
|
+
severity: 'error',
|
|
167
|
+
message: `文件超过 ${config.maxFileLines} 行 (当前 ${lines.length} 行)`
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 2. 检查禁止模式
|
|
172
|
+
for (const rule of config.forbiddenPatterns) {
|
|
173
|
+
const pattern = typeof rule.pattern === 'string'
|
|
174
|
+
? new RegExp(rule.pattern, 'g')
|
|
175
|
+
: rule.pattern;
|
|
176
|
+
|
|
177
|
+
pattern.lastIndex = 0;
|
|
178
|
+
const matches = content.match(pattern);
|
|
179
|
+
if (matches && matches.length > 0) {
|
|
180
|
+
issues.push({
|
|
181
|
+
severity: rule.severity || 'warn',
|
|
182
|
+
message: `${rule.message} (${matches.length} 处)`
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 3. 检查函数长度(仅对 JS/TS 文件)
|
|
188
|
+
if (/\.(js|ts|jsx|tsx)$/.test(filePath)) {
|
|
189
|
+
const longFunctions = detectLongFunctions(content, config.maxFunctionLines);
|
|
190
|
+
issues.push(...longFunctions);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return issues;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 格式化输出
|
|
198
|
+
*/
|
|
199
|
+
function formatOutput(filePath, issues) {
|
|
200
|
+
if (issues.length === 0) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const errors = issues.filter(i => i.severity === 'error');
|
|
205
|
+
const warns = issues.filter(i => i.severity === 'warn');
|
|
206
|
+
const infos = issues.filter(i => i.severity === 'info');
|
|
207
|
+
|
|
208
|
+
const relativePath = path.relative(PROJECT_DIR, filePath);
|
|
209
|
+
|
|
210
|
+
let output = `\n╔══════════════════════════════════════════════════════════════╗\n`;
|
|
211
|
+
output += `║ 📊 Live Quality Check: ${relativePath.slice(0, 35).padEnd(35)}║\n`;
|
|
212
|
+
output += `╠══════════════════════════════════════════════════════════════╣\n`;
|
|
213
|
+
|
|
214
|
+
if (errors.length > 0) {
|
|
215
|
+
output += `║ ❌ 错误 (${errors.length}): ║\n`;
|
|
216
|
+
for (const issue of errors) {
|
|
217
|
+
output += `║ • ${issue.message.slice(0, 52).padEnd(52)}║\n`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (warns.length > 0) {
|
|
222
|
+
output += `║ ⚠️ 警告 (${warns.length}): ║\n`;
|
|
223
|
+
for (const issue of warns) {
|
|
224
|
+
output += `║ • ${issue.message.slice(0, 52).padEnd(52)}║\n`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (infos.length > 0) {
|
|
229
|
+
output += `║ ℹ️ 信息 (${infos.length}): ║\n`;
|
|
230
|
+
for (const issue of infos) {
|
|
231
|
+
output += `║ • ${issue.message.slice(0, 52).padEnd(52)}║\n`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
output += `╚══════════════════════════════════════════════════════════════╝\n`;
|
|
236
|
+
|
|
237
|
+
return output;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 从环境变量获取工具输入
|
|
242
|
+
*/
|
|
243
|
+
function getToolInput() {
|
|
244
|
+
const toolName = process.env.CLAUDE_TOOL_NAME || '';
|
|
245
|
+
const toolInput = process.env.CLAUDE_TOOL_INPUT || '{}';
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const input = JSON.parse(toolInput);
|
|
249
|
+
return {
|
|
250
|
+
tool: toolName,
|
|
251
|
+
filePath: input.file_path || input.path || ''
|
|
252
|
+
};
|
|
253
|
+
} catch (e) {
|
|
254
|
+
return { tool: toolName, filePath: '' };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 主函数
|
|
260
|
+
*/
|
|
261
|
+
function main() {
|
|
262
|
+
const { tool, filePath } = getToolInput();
|
|
263
|
+
|
|
264
|
+
// 只检查 Write 和 Edit 工具
|
|
265
|
+
if (!['Write', 'Edit'].includes(tool)) {
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// 检查是否豁免
|
|
270
|
+
if (isExempt(filePath)) {
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const config = getConfig();
|
|
275
|
+
const issues = checkQuality(filePath, config);
|
|
276
|
+
const output = formatOutput(filePath, issues);
|
|
277
|
+
|
|
278
|
+
if (output) {
|
|
279
|
+
console.log(output);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 始终返回成功(警告不阻止操作)
|
|
283
|
+
process.exit(0);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
main();
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Plan Gate - 强制规划门控
|
|
4
|
+
*
|
|
5
|
+
* PreToolUse 钩子:在任何 Write/Edit 之前检查是否有批准的计划
|
|
6
|
+
*
|
|
7
|
+
* 硬阻止模式:无批准计划直接阻止操作
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
14
|
+
const HOME_DIR = process.env.HOME || require('os').homedir();
|
|
15
|
+
|
|
16
|
+
// 检查两个位置的计划目录
|
|
17
|
+
const PLANS_DIRS = [
|
|
18
|
+
path.join(PROJECT_DIR, '.claude/plans'), // 项目级计划
|
|
19
|
+
path.join(HOME_DIR, '.claude/plans') // 全局计划
|
|
20
|
+
];
|
|
21
|
+
const GATE_CONFIG_FILE = path.join(PROJECT_DIR, '.claude/quality-gate.json');
|
|
22
|
+
|
|
23
|
+
// 豁免模式:这些文件不需要计划
|
|
24
|
+
const EXEMPT_PATTERNS = [
|
|
25
|
+
/\.md$/, // Markdown 文档
|
|
26
|
+
/\.json$/, // JSON 配置
|
|
27
|
+
/\.test\./, // 测试文件
|
|
28
|
+
/\.spec\./, // 测试文件
|
|
29
|
+
/__tests__/, // 测试目录
|
|
30
|
+
/node_modules/, // 依赖
|
|
31
|
+
/\.claude\//, // Claude 配置
|
|
32
|
+
/\.git\//, // Git 目录
|
|
33
|
+
/package-lock\.json$/, // 锁文件
|
|
34
|
+
/yarn\.lock$/, // 锁文件
|
|
35
|
+
/pnpm-lock\.yaml$/, // 锁文件
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 检查文件是否豁免
|
|
40
|
+
*/
|
|
41
|
+
function isExempt(filePath) {
|
|
42
|
+
if (!filePath) return true;
|
|
43
|
+
return EXEMPT_PATTERNS.some(pattern => pattern.test(filePath));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 获取配置
|
|
48
|
+
*/
|
|
49
|
+
function getConfig() {
|
|
50
|
+
try {
|
|
51
|
+
if (fs.existsSync(GATE_CONFIG_FILE)) {
|
|
52
|
+
const config = JSON.parse(fs.readFileSync(GATE_CONFIG_FILE, 'utf-8'));
|
|
53
|
+
return config.planGate || { enabled: true, mode: 'block' };
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
// 默认配置
|
|
57
|
+
}
|
|
58
|
+
return { enabled: true, mode: 'block' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 检查是否有批准的计划
|
|
63
|
+
*/
|
|
64
|
+
function hasApprovedPlan() {
|
|
65
|
+
for (const plansDir of PLANS_DIRS) {
|
|
66
|
+
// 确保计划目录存在
|
|
67
|
+
if (!fs.existsSync(plansDir)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const planFiles = fs.readdirSync(plansDir)
|
|
73
|
+
.filter(f => f.endsWith('.md'));
|
|
74
|
+
|
|
75
|
+
for (const planFile of planFiles) {
|
|
76
|
+
const content = fs.readFileSync(path.join(plansDir, planFile), 'utf-8');
|
|
77
|
+
|
|
78
|
+
// 检查计划是否被批准(最近 24 小时内)
|
|
79
|
+
const stat = fs.statSync(path.join(plansDir, planFile));
|
|
80
|
+
const ageHours = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);
|
|
81
|
+
|
|
82
|
+
// 只接受 24 小时内的批准计划
|
|
83
|
+
if (ageHours > 24) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 检查计划是否被批准
|
|
88
|
+
if (content.includes('## Approved Plan') ||
|
|
89
|
+
content.includes('status: approved') ||
|
|
90
|
+
content.includes('User has approved')) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
// 读取失败,继续检查下一个目录
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 从环境变量获取工具输入
|
|
104
|
+
*/
|
|
105
|
+
function getToolInput() {
|
|
106
|
+
const toolName = process.env.CLAUDE_TOOL_NAME || '';
|
|
107
|
+
const toolInput = process.env.CLAUDE_TOOL_INPUT || '{}';
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const input = JSON.parse(toolInput);
|
|
111
|
+
return {
|
|
112
|
+
tool: toolName,
|
|
113
|
+
filePath: input.file_path || input.path || ''
|
|
114
|
+
};
|
|
115
|
+
} catch (e) {
|
|
116
|
+
return { tool: toolName, filePath: '' };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 主函数
|
|
122
|
+
*/
|
|
123
|
+
function main() {
|
|
124
|
+
const config = getConfig();
|
|
125
|
+
|
|
126
|
+
// 如果禁用,直接通过
|
|
127
|
+
if (!config.enabled) {
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const { tool, filePath } = getToolInput();
|
|
132
|
+
|
|
133
|
+
// 只检查 Write 和 Edit 工具
|
|
134
|
+
if (!['Write', 'Edit'].includes(tool)) {
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 检查是否豁免
|
|
139
|
+
if (isExempt(filePath)) {
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 检查是否有批准的计划
|
|
144
|
+
if (hasApprovedPlan()) {
|
|
145
|
+
process.exit(0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 无计划,根据模式处理
|
|
149
|
+
if (config.mode === 'block') {
|
|
150
|
+
// 硬阻止:输出错误信息并退出非零状态
|
|
151
|
+
console.error(`
|
|
152
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
153
|
+
║ ⛔ Plan Gate: 操作被阻止 ║
|
|
154
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
155
|
+
║ ║
|
|
156
|
+
║ 无批准的实施计划。 ║
|
|
157
|
+
║ ║
|
|
158
|
+
║ 请先运行: /plan ║
|
|
159
|
+
║ 创建并批准实施计划后再进行代码修改。 ║
|
|
160
|
+
║ ║
|
|
161
|
+
║ 目标文件: ${filePath.slice(0, 45).padEnd(45)}║
|
|
162
|
+
║ ║
|
|
163
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
164
|
+
`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
} else {
|
|
167
|
+
// 警告模式
|
|
168
|
+
console.warn(`⚠️ Plan Gate 警告: 建议先运行 /plan 创建实施计划`);
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
main();
|
|
@@ -66,18 +66,29 @@ async function main() {
|
|
|
66
66
|
config
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
// Use severity from config, default to 'warn' for stricter checking
|
|
70
|
+
const severity = config.severity || 'warn';
|
|
71
|
+
|
|
69
72
|
const result = await gate.check({
|
|
70
73
|
files: checkable.map(f => path.join(projectDir, f)),
|
|
71
|
-
severity:
|
|
74
|
+
severity: severity // Block on configured severity (now 'warn' by default)
|
|
72
75
|
});
|
|
73
76
|
|
|
74
77
|
if (!result.passed) {
|
|
75
|
-
console.error('\
|
|
76
|
-
console.error('
|
|
78
|
+
console.error('\n╔══════════════════════════════════════════════════════════════╗');
|
|
79
|
+
console.error('║ ⛔ Pre-commit Quality Gate: 提交被阻止 ║');
|
|
80
|
+
console.error('╠══════════════════════════════════════════════════════════════╣');
|
|
81
|
+
console.error('║ ║');
|
|
82
|
+
console.error('║ 请修复上述问题后重新提交。 ║');
|
|
83
|
+
console.error('║ ║');
|
|
84
|
+
console.error('║ 如确需绕过检查(不推荐): ║');
|
|
85
|
+
console.error('║ git commit --no-verify ║');
|
|
86
|
+
console.error('║ ║');
|
|
87
|
+
console.error('╚══════════════════════════════════════════════════════════════╝\n');
|
|
77
88
|
process.exit(1);
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
console.log('Pre-commit quality checks passed.\n');
|
|
91
|
+
console.log('\n✅ Pre-commit quality checks passed.\n');
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
main().catch(err => {
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"enabled": true,
|
|
3
|
-
"severity": "
|
|
3
|
+
"severity": "warn",
|
|
4
|
+
|
|
5
|
+
"planGate": {
|
|
6
|
+
"enabled": true,
|
|
7
|
+
"mode": "block",
|
|
8
|
+
"exempt": [".md", ".json", ".test.", ".spec.", "__tests__", ".claude/"]
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
"liveQuality": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"maxFileLines": 800,
|
|
14
|
+
"maxFunctionLines": 50
|
|
15
|
+
},
|
|
4
16
|
"rules": [
|
|
5
17
|
{
|
|
6
18
|
"id": "line-count-limit",
|
|
@@ -46,13 +58,16 @@
|
|
|
46
58
|
},
|
|
47
59
|
{
|
|
48
60
|
"id": "no-console-logs",
|
|
49
|
-
"enabled":
|
|
61
|
+
"enabled": true,
|
|
50
62
|
"severity": "warn"
|
|
51
63
|
},
|
|
52
64
|
{
|
|
53
65
|
"id": "function-length",
|
|
54
|
-
"enabled":
|
|
55
|
-
"severity": "warn"
|
|
66
|
+
"enabled": true,
|
|
67
|
+
"severity": "warn",
|
|
68
|
+
"config": {
|
|
69
|
+
"maxLines": 50
|
|
70
|
+
}
|
|
56
71
|
}
|
|
57
72
|
],
|
|
58
73
|
"gates": {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Linus 编程哲学
|
|
2
|
+
|
|
3
|
+
> 审核代码时以此为准则
|
|
4
|
+
|
|
5
|
+
## 核心原则
|
|
6
|
+
|
|
7
|
+
| 原则 | 一句话 | 检查方式 |
|
|
8
|
+
|------|--------|----------|
|
|
9
|
+
| **好品味** | 消除特殊情况,而非处理它 | 有 if/else?重新设计数据结构 |
|
|
10
|
+
| **不破坏** | 向后兼容是铁律 | 现有功能全部正常? |
|
|
11
|
+
| **实用主义** | 解决真问题,拒绝臆想 | 生产环境真的需要? |
|
|
12
|
+
| **极简** | ≤3 层缩进,函数只做一件事 | 能砍掉一半代码吗? |
|
|
13
|
+
|
|
14
|
+
## 思考流程
|
|
15
|
+
|
|
16
|
+
每次改动前问三个问题:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
1. 这是真问题还是臆想? → 拒绝过度设计
|
|
20
|
+
2. 有更简单的方法吗? → 永远找最简方案
|
|
21
|
+
3. 会破坏什么吗? → 列出所有受影响项
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 代码审查输出格式
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
【品味】🟢好 / 🟡凑合 / 🔴垃圾
|
|
28
|
+
|
|
29
|
+
【问题】
|
|
30
|
+
- [最糟糕的部分]
|
|
31
|
+
|
|
32
|
+
【改法】
|
|
33
|
+
- "这个特殊情况可以消除"
|
|
34
|
+
- "10行→3行"
|
|
35
|
+
- "数据结构应该是..."
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 决策标准
|
|
39
|
+
|
|
40
|
+
**值得做**:
|
|
41
|
+
1. 先简化数据结构
|
|
42
|
+
2. 消除所有特殊情况
|
|
43
|
+
3. 用最笨但最清晰的方式
|
|
44
|
+
4. 零破坏
|
|
45
|
+
|
|
46
|
+
**不值得做**:
|
|
47
|
+
"这是在解决不存在的问题。真正的问题是 [X]。"
|
|
48
|
+
|
|
49
|
+
## 代码风格
|
|
50
|
+
|
|
51
|
+
- 函数:短小,一件事
|
|
52
|
+
- 命名:斯巴达式,直白精准
|
|
53
|
+
- 缩进:≤3 层,否则重构
|
|
54
|
+
- 分支:if/else 是坏味道的信号
|
package/.claude/settings.json
CHANGED
|
@@ -51,7 +51,20 @@
|
|
|
51
51
|
]
|
|
52
52
|
}
|
|
53
53
|
],
|
|
54
|
-
"PreToolUse": [
|
|
54
|
+
"PreToolUse": [
|
|
55
|
+
{
|
|
56
|
+
"matcher": {
|
|
57
|
+
"tool_name": "^(Write|Edit)$"
|
|
58
|
+
},
|
|
59
|
+
"hooks": [
|
|
60
|
+
{
|
|
61
|
+
"type": "command",
|
|
62
|
+
"command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/plan-gate.cjs",
|
|
63
|
+
"timeout": 2000
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
],
|
|
55
68
|
"PostToolUse": [
|
|
56
69
|
{
|
|
57
70
|
"matcher": {
|
|
@@ -62,6 +75,11 @@
|
|
|
62
75
|
"type": "command",
|
|
63
76
|
"command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/code-formatter.cjs",
|
|
64
77
|
"timeout": 5000
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"type": "command",
|
|
81
|
+
"command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/live-quality.cjs",
|
|
82
|
+
"timeout": 3000
|
|
65
83
|
}
|
|
66
84
|
]
|
|
67
85
|
}
|