ai-engineering-init 1.3.1 → 1.3.3
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/hooks/lib/notify.js +310 -0
- package/.claude/hooks/stop.js +5 -30
- package/.claude/notify-config.json +9 -0
- package/.claude/skills/git-workflow/SKILL.md +27 -5
- package/.cursor/hooks/cursor-skill-eval.js +14 -11
- package/.cursor/hooks/lib/notify.js +310 -0
- package/.cursor/hooks/stop.js +5 -34
- package/.cursor/rules/skill-activation.mdc +94 -0
- package/bin/index.js +8 -2
- package/package.json +1 -1
- package/.claude/skills/leniu-java-code-style/SKILL.md +0 -510
- package/.codex/skills/leniu-java-code-style/SKILL.md +0 -510
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 智能完成通知模块
|
|
3
|
+
* 支持音效、系统通知弹窗、TTS 语音播报三种方式的任意组合
|
|
4
|
+
* 跨平台兼容:macOS / Windows / Linux
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { spawn } = require('child_process');
|
|
12
|
+
|
|
13
|
+
// ── 默认配置 ─────────────────────────────────────────────────────────────
|
|
14
|
+
const DEFAULT_CONFIG = {
|
|
15
|
+
notify: {
|
|
16
|
+
methods: ['sound'],
|
|
17
|
+
sound: { enabled: true, file: 'completed.wav' },
|
|
18
|
+
notification: { enabled: false, title: 'AI 任务完成' },
|
|
19
|
+
tts: { enabled: false, prefix: '任务完成', maxLength: 80 },
|
|
20
|
+
summary: { maxLength: 100, strategy: 'first-sentence', stripMarkdown: true },
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// ── 工具函数 ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/** 深度合并对象(src 补充到 dest,dest 已有的键保留) */
|
|
27
|
+
function deepMerge(dest, src) {
|
|
28
|
+
const result = { ...dest };
|
|
29
|
+
for (const [key, value] of Object.entries(src)) {
|
|
30
|
+
if (key in result && typeof result[key] === 'object' && result[key] !== null
|
|
31
|
+
&& !Array.isArray(result[key]) && typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
32
|
+
result[key] = deepMerge(result[key], value);
|
|
33
|
+
} else if (!(key in result)) {
|
|
34
|
+
result[key] = value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 带超时的 spawn,返回 Promise */
|
|
41
|
+
function spawnWithTimeout(cmd, args, opts, timeoutMs) {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
try {
|
|
44
|
+
const child = spawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'], ...opts });
|
|
45
|
+
const timer = setTimeout(() => {
|
|
46
|
+
try { child.kill('SIGTERM'); } catch { /* ignore */ }
|
|
47
|
+
resolve(false);
|
|
48
|
+
}, timeoutMs);
|
|
49
|
+
child.on('close', () => { clearTimeout(timer); resolve(true); });
|
|
50
|
+
child.on('error', () => { clearTimeout(timer); resolve(false); });
|
|
51
|
+
} catch {
|
|
52
|
+
resolve(false);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── 1. readStdin ─────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/** 读取 hook stdin JSON(2s 超时,失败返回 null) */
|
|
60
|
+
function readStdin() {
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
// 非 TTY 且有 pipe 数据时才读取
|
|
63
|
+
if (process.stdin.isTTY) {
|
|
64
|
+
resolve(null);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let data = '';
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
process.stdin.removeAllListeners();
|
|
71
|
+
process.stdin.destroy();
|
|
72
|
+
resolve(null);
|
|
73
|
+
}, 2000);
|
|
74
|
+
|
|
75
|
+
process.stdin.setEncoding('utf8');
|
|
76
|
+
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
77
|
+
process.stdin.on('end', () => {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
try {
|
|
80
|
+
resolve(JSON.parse(data));
|
|
81
|
+
} catch {
|
|
82
|
+
resolve(null);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
process.stdin.on('error', () => {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
resolve(null);
|
|
88
|
+
});
|
|
89
|
+
process.stdin.resume();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── 2. loadConfig ────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/** 加载配置文件,按查找顺序:工作区 → 全局 → 内置默认值 */
|
|
96
|
+
function loadConfig(cwd) {
|
|
97
|
+
const homeDir = os.homedir();
|
|
98
|
+
const candidates = [
|
|
99
|
+
path.join(cwd, '.claude', 'notify-config.json'),
|
|
100
|
+
path.join(cwd, '.cursor', 'notify-config.json'),
|
|
101
|
+
path.join(homeDir, '.claude', 'notify-config.json'),
|
|
102
|
+
path.join(homeDir, '.cursor', 'notify-config.json'),
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
for (const configPath of candidates) {
|
|
106
|
+
try {
|
|
107
|
+
if (fs.existsSync(configPath)) {
|
|
108
|
+
const userConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
109
|
+
// 用户配置优先,缺失字段用默认值补充
|
|
110
|
+
return deepMerge(userConfig, DEFAULT_CONFIG);
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// 配置文件解析失败,继续尝试下一个
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── 3. extractSummary ────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
/** 从 last_assistant_message 提取摘要 */
|
|
123
|
+
function extractSummary(message, config) {
|
|
124
|
+
if (!message || typeof message !== 'string') return '';
|
|
125
|
+
|
|
126
|
+
const summaryConfig = config.notify.summary;
|
|
127
|
+
let text = message;
|
|
128
|
+
|
|
129
|
+
// 去除 markdown 标记
|
|
130
|
+
if (summaryConfig.stripMarkdown) {
|
|
131
|
+
text = text
|
|
132
|
+
.replace(/```[\s\S]*?```/g, '') // 代码块
|
|
133
|
+
.replace(/`[^`]+`/g, '') // 行内代码
|
|
134
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') // 链接 → 文字
|
|
135
|
+
.replace(/^#{1,6}\s+.*$/gm, '') // 整行标题(含文字)
|
|
136
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1') // 粗体
|
|
137
|
+
.replace(/\*([^*]+)\*/g, '$1') // 斜体
|
|
138
|
+
.replace(/~~([^~]+)~~/g, '$1') // 删除线
|
|
139
|
+
.replace(/^[-*+]\s+/gm, '') // 无序列表标记
|
|
140
|
+
.replace(/^\d+\.\s+/gm, '') // 有序列表标记
|
|
141
|
+
.replace(/^>\s+/gm, '') // 引用
|
|
142
|
+
.replace(/\n{2,}/g, '\n') // 多余空行
|
|
143
|
+
.trim();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!text) return '';
|
|
147
|
+
|
|
148
|
+
const maxLen = summaryConfig.maxLength || 100;
|
|
149
|
+
const strategy = summaryConfig.strategy || 'first-sentence';
|
|
150
|
+
|
|
151
|
+
let result = '';
|
|
152
|
+
|
|
153
|
+
switch (strategy) {
|
|
154
|
+
case 'first-sentence': {
|
|
155
|
+
// 匹配第一个句末标点(中文标点后无需空格,英文句号需后跟空格或结尾)
|
|
156
|
+
const match = text.match(/^(.+?(?:[。!?!?]|\.(?:\s|$)))/);
|
|
157
|
+
result = match ? match[1] : text.split('\n')[0];
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case 'first-line': {
|
|
161
|
+
result = text.split('\n')[0];
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case 'truncate':
|
|
165
|
+
default: {
|
|
166
|
+
result = text;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 截断到 maxLength
|
|
172
|
+
if (result.length > maxLen) {
|
|
173
|
+
result = result.substring(0, maxLen - 3) + '...';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return result.trim();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── 4. playSound ─────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
/** 跨平台播放音效(5s 超时) */
|
|
182
|
+
function playSound(cwd, fileName) {
|
|
183
|
+
const homeDir = os.homedir();
|
|
184
|
+
const candidates = [
|
|
185
|
+
path.join(cwd, '.claude', 'audio', fileName),
|
|
186
|
+
path.join(cwd, '.cursor', 'audio', fileName),
|
|
187
|
+
path.join(homeDir, '.claude', 'audio', fileName),
|
|
188
|
+
path.join(homeDir, '.cursor', 'audio', fileName),
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
const audioFile = candidates.find(f => {
|
|
192
|
+
try { return fs.existsSync(f); } catch { return false; }
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (!audioFile) return Promise.resolve(false);
|
|
196
|
+
|
|
197
|
+
const platform = process.platform;
|
|
198
|
+
|
|
199
|
+
if (platform === 'darwin') {
|
|
200
|
+
return spawnWithTimeout('afplay', [audioFile], {}, 5000);
|
|
201
|
+
} else if (platform === 'win32') {
|
|
202
|
+
return spawnWithTimeout('powershell', [
|
|
203
|
+
'-c', `(New-Object Media.SoundPlayer '${audioFile.replace(/'/g, "''")}').PlaySync()`,
|
|
204
|
+
], {}, 5000);
|
|
205
|
+
} else {
|
|
206
|
+
// Linux: 尝试 aplay → paplay
|
|
207
|
+
return spawnWithTimeout('aplay', [audioFile], {}, 5000).then(ok => {
|
|
208
|
+
if (!ok) return spawnWithTimeout('paplay', [audioFile], {}, 5000);
|
|
209
|
+
return true;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ── 5. sendNotification ──────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
/** 跨平台系统通知弹窗(2s 超时) */
|
|
217
|
+
function sendNotification(title, body) {
|
|
218
|
+
const safeTitle = (title || '').replace(/"/g, '\\"');
|
|
219
|
+
const safeBody = (body || '').replace(/"/g, '\\"');
|
|
220
|
+
const platform = process.platform;
|
|
221
|
+
|
|
222
|
+
if (platform === 'darwin') {
|
|
223
|
+
return spawnWithTimeout('osascript', [
|
|
224
|
+
'-e', `display notification "${safeBody}" with title "${safeTitle}"`,
|
|
225
|
+
], {}, 2000);
|
|
226
|
+
} else if (platform === 'win32') {
|
|
227
|
+
const ps = `
|
|
228
|
+
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null;
|
|
229
|
+
$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02);
|
|
230
|
+
$textNodes = $template.GetElementsByTagName('text');
|
|
231
|
+
$textNodes.Item(0).AppendChild($template.CreateTextNode('${safeTitle}')) | Out-Null;
|
|
232
|
+
$textNodes.Item(1).AppendChild($template.CreateTextNode('${safeBody}')) | Out-Null;
|
|
233
|
+
$toast = [Windows.UI.Notifications.ToastNotification]::new($template);
|
|
234
|
+
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('AI Assistant').Show($toast);
|
|
235
|
+
`.replace(/\n\s*/g, ' ');
|
|
236
|
+
return spawnWithTimeout('powershell', ['-c', ps], {}, 2000);
|
|
237
|
+
} else {
|
|
238
|
+
// Linux: notify-send
|
|
239
|
+
return spawnWithTimeout('notify-send', [safeTitle, safeBody], {}, 2000);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── 6. speakTTS ──────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
/** 跨平台语音播报(7s 超时) */
|
|
246
|
+
function speakTTS(text, config) {
|
|
247
|
+
if (!text) return Promise.resolve(false);
|
|
248
|
+
|
|
249
|
+
const prefix = config.notify.tts.prefix || '';
|
|
250
|
+
const fullText = prefix ? `${prefix},${text}` : text;
|
|
251
|
+
const maxLen = config.notify.tts.maxLength || 80;
|
|
252
|
+
const spoken = fullText.length > maxLen ? fullText.substring(0, maxLen) : fullText;
|
|
253
|
+
|
|
254
|
+
const platform = process.platform;
|
|
255
|
+
|
|
256
|
+
if (platform === 'darwin') {
|
|
257
|
+
return spawnWithTimeout('say', [spoken], {}, 7000);
|
|
258
|
+
} else if (platform === 'win32') {
|
|
259
|
+
const safeTxt = spoken.replace(/'/g, "''");
|
|
260
|
+
return spawnWithTimeout('powershell', [
|
|
261
|
+
'-c', `Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('${safeTxt}')`,
|
|
262
|
+
], {}, 7000);
|
|
263
|
+
} else {
|
|
264
|
+
// Linux: spd-say → espeak
|
|
265
|
+
return spawnWithTimeout('spd-say', [spoken], {}, 7000).then(ok => {
|
|
266
|
+
if (!ok) return spawnWithTimeout('espeak', [spoken], {}, 7000);
|
|
267
|
+
return true;
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── 7. run(主入口)──────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
/** 主入口:读配置 → 读 stdin → 提取摘要 → 并行执行通知 */
|
|
275
|
+
async function run(cwd) {
|
|
276
|
+
const config = loadConfig(cwd);
|
|
277
|
+
const methods = config.notify.methods || ['sound'];
|
|
278
|
+
|
|
279
|
+
// 读取 stdin(Claude/Cursor hook 通过 stdin 传入 JSON)
|
|
280
|
+
const stdinData = await readStdin();
|
|
281
|
+
const lastMessage = stdinData && stdinData.last_assistant_message
|
|
282
|
+
? stdinData.last_assistant_message
|
|
283
|
+
: null;
|
|
284
|
+
|
|
285
|
+
// 提取摘要
|
|
286
|
+
const summary = lastMessage ? extractSummary(lastMessage, config) : '';
|
|
287
|
+
|
|
288
|
+
// 并行执行各通知方式
|
|
289
|
+
const tasks = [];
|
|
290
|
+
|
|
291
|
+
if (methods.includes('sound') && config.notify.sound.enabled !== false) {
|
|
292
|
+
tasks.push(playSound(cwd, config.notify.sound.file || 'completed.wav'));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (methods.includes('notification') && config.notify.notification.enabled !== false) {
|
|
296
|
+
const title = config.notify.notification.title || 'AI 任务完成';
|
|
297
|
+
const body = summary || '回答已完成';
|
|
298
|
+
tasks.push(sendNotification(title, body));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (methods.includes('tts') && config.notify.tts.enabled !== false) {
|
|
302
|
+
const ttsText = summary || '回答已完成';
|
|
303
|
+
tasks.push(speakTTS(ttsText, config));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 等待所有通知完成(任何失败都静默忽略)
|
|
307
|
+
await Promise.allSettled(tasks);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
module.exports = { run, readStdin, loadConfig, extractSummary, playSound, sendNotification, speakTTS };
|
package/.cursor/hooks/stop.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Stop Hook - Cursor 回答结束时触发
|
|
4
|
-
* 功能: nul 文件清理 +
|
|
4
|
+
* 功能: nul 文件清理 + 智能完成通知(音效/系统通知/TTS)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const { execSync } = require('child_process');
|
|
10
9
|
|
|
11
10
|
// 清理可能误创建的 nul 文件(Windows 下 > nul 可能创建该文件)
|
|
12
11
|
const findAndDeleteNul = (dir, depth = 0) => {
|
|
@@ -28,35 +27,7 @@ const findAndDeleteNul = (dir, depth = 0) => {
|
|
|
28
27
|
};
|
|
29
28
|
findAndDeleteNul(process.cwd());
|
|
30
29
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const audioFile = fs.existsSync(cursorAudio) ? cursorAudio : (fs.existsSync(claudeAudio) ? claudeAudio : null);
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
if (audioFile) {
|
|
39
|
-
const platform = process.platform;
|
|
40
|
-
if (platform === 'darwin') {
|
|
41
|
-
execSync(`afplay "${audioFile}"`, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
42
|
-
} else if (platform === 'win32') {
|
|
43
|
-
execSync(`powershell -c "(New-Object Media.SoundPlayer '${audioFile.replace(/'/g, "''")}').PlaySync()"`, {
|
|
44
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
45
|
-
});
|
|
46
|
-
} else if (platform === 'linux') {
|
|
47
|
-
try {
|
|
48
|
-
execSync(`aplay "${audioFile}"`, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
49
|
-
} catch {
|
|
50
|
-
try {
|
|
51
|
-
execSync(`paplay "${audioFile}"`, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
52
|
-
} catch {
|
|
53
|
-
// 播放失败,静默忽略
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} catch {
|
|
59
|
-
// 播放失败时静默忽略
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
process.exit(0);
|
|
30
|
+
// 执行智能通知
|
|
31
|
+
require('./lib/notify.js').run(process.cwd())
|
|
32
|
+
.then(() => process.exit(0))
|
|
33
|
+
.catch(() => process.exit(0));
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 技能激活规则 - 根据用户问题自动读取对应 SKILL.md 文件
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## 技能激活规则(必须遵守)
|
|
7
|
+
|
|
8
|
+
当用户问题涉及以下主题时,**必须先读取对应 SKILL.md 文件**,再回答:
|
|
9
|
+
|
|
10
|
+
### leniu 专项技能
|
|
11
|
+
|
|
12
|
+
| 触发词 | SKILL.md 路径 |
|
|
13
|
+
|--------|--------------|
|
|
14
|
+
| CRUD/增删改查/新建模块/LeRequest/PageDTO/leniu-CRUD | `.cursor/skills/leniu-crud-development/SKILL.md` |
|
|
15
|
+
| leniu-API/LeResult/LeResponse/云食堂接口 | `.cursor/skills/leniu-api-development/SKILL.md` |
|
|
16
|
+
| leniu-数据库/leniu-建表/双库/商户库/系统库/leniu-SQL | `.cursor/skills/leniu-database-ops/SKILL.md` |
|
|
17
|
+
| leniu-Entity/leniu-VO/leniu-DTO/@TableName/@TableField | `.cursor/skills/leniu-java-entity/SKILL.md` |
|
|
18
|
+
| leniu-MyBatis/leniu-Mapper/LambdaQueryWrapper/leniu-XML | `.cursor/skills/leniu-java-mybatis/SKILL.md` |
|
|
19
|
+
| leniu-并发/CompletableFuture/leniu-线程池 | `.cursor/skills/leniu-java-concurrent/SKILL.md` |
|
|
20
|
+
| leniu-导出/leniu-Excel导出/leniu-@ExcelProperty | `.cursor/skills/leniu-java-export/SKILL.md` |
|
|
21
|
+
| leniu-日志/@Slf4j/leniu-log.info | `.cursor/skills/leniu-java-logging/SKILL.md` |
|
|
22
|
+
| leniu-消息队列/leniu-MQ/MqUtil/@MqConsumer | `.cursor/skills/leniu-java-mq/SKILL.md` |
|
|
23
|
+
| leniu-定时任务/leniu-XXL-Job/@XxlJob | `.cursor/skills/leniu-java-task/SKILL.md` |
|
|
24
|
+
| leniu-合计行/totalLine/ReportBaseTotalVO | `.cursor/skills/leniu-java-total-line/SKILL.md` |
|
|
25
|
+
| leniu-金额/leniu-分转元/leniu-元转分/amountFen | `.cursor/skills/leniu-java-amount-handling/SKILL.md` |
|
|
26
|
+
| leniu-报表查询/ReportBaseParam/leniu-分页参数 | `.cursor/skills/leniu-java-report-query-param/SKILL.md` |
|
|
27
|
+
| leniu-异常/LeException/leniu-全局异常/leniu-I18n | `.cursor/skills/leniu-error-handler/SKILL.md` |
|
|
28
|
+
| leniu-注解/@RequiresAuthentication/@Validated | `.cursor/skills/leniu-backend-annotations/SKILL.md` |
|
|
29
|
+
| leniu-工具类/leniu-BeanUtil/leniu-StrUtil/LeBeanUtil | `.cursor/skills/leniu-utils-toolkit/SKILL.md` |
|
|
30
|
+
| leniu-架构/云食堂架构/双库架构/pigx框架 | `.cursor/skills/leniu-architecture-design/SKILL.md` |
|
|
31
|
+
| leniu-安全/leniu-认证/leniu-权限/leniu-加密 | `.cursor/skills/leniu-security-guard/SKILL.md` |
|
|
32
|
+
| leniu-缓存/leniu-Redis/leniu-RedisUtil/leniu-分布式锁 | `.cursor/skills/leniu-redis-cache/SKILL.md` |
|
|
33
|
+
| leniu-数据权限/DataScope/leniu-行级权限 | `.cursor/skills/leniu-data-permission/SKILL.md` |
|
|
34
|
+
| leniu-规范/leniu-禁止/leniu-命名/leniu-代码风格 | `.cursor/skills/leniu-code-patterns/SKILL.md` |
|
|
35
|
+
| 营销计费/计费规则/RulePriceHandler | `.cursor/skills/leniu-marketing-price-rule-customizer/SKILL.md` |
|
|
36
|
+
| 营销充值/充值规则/RuleRechargeHandler/满赠规则 | `.cursor/skills/leniu-marketing-recharge-rule-customizer/SKILL.md` |
|
|
37
|
+
| 餐次/早餐/午餐/下午茶/晚餐/夜宵/mealtimeTypes | `.cursor/skills/leniu-mealtime/SKILL.md` |
|
|
38
|
+
|
|
39
|
+
### OpenSpec 工作流技能
|
|
40
|
+
|
|
41
|
+
| 触发词 | SKILL.md 路径 |
|
|
42
|
+
|--------|--------------|
|
|
43
|
+
| 新建变更/opsx:new/openspec new | `.cursor/skills/openspec-new-change/SKILL.md` |
|
|
44
|
+
| 快速推进/opsx:ff/openspec ff | `.cursor/skills/openspec-ff-change/SKILL.md` |
|
|
45
|
+
| 实现任务/opsx:apply/openspec apply | `.cursor/skills/openspec-apply-change/SKILL.md` |
|
|
46
|
+
| 继续变更/opsx:continue/openspec continue | `.cursor/skills/openspec-continue-change/SKILL.md` |
|
|
47
|
+
| 归档变更/opsx:archive/openspec archive | `.cursor/skills/openspec-archive-change/SKILL.md` |
|
|
48
|
+
| 批量归档/opsx:bulk-archive | `.cursor/skills/openspec-bulk-archive-change/SKILL.md` |
|
|
49
|
+
| 探索模式/opsx:explore/openspec explore | `.cursor/skills/openspec-explore/SKILL.md` |
|
|
50
|
+
| 同步规格/opsx:sync/openspec sync | `.cursor/skills/openspec-sync-specs/SKILL.md` |
|
|
51
|
+
| 验证变更/opsx:verify/openspec verify | `.cursor/skills/openspec-verify-change/SKILL.md` |
|
|
52
|
+
|
|
53
|
+
### 通用技能
|
|
54
|
+
|
|
55
|
+
| 触发词 | SKILL.md 路径 |
|
|
56
|
+
|--------|--------------|
|
|
57
|
+
| CRUD/增删改查/Entity/Service/Controller/业务模块 | `.cursor/skills/crud-development/SKILL.md` |
|
|
58
|
+
| API设计/RESTful/接口规范/Swagger | `.cursor/skills/api-development/SKILL.md` |
|
|
59
|
+
| 数据库/SQL/建表/字典/菜单配置 | `.cursor/skills/database-ops/SKILL.md` |
|
|
60
|
+
| @RateLimiter/@DataScope/@RepeatSubmit | `.cursor/skills/backend-annotations/SKILL.md` |
|
|
61
|
+
| MapstructUtils/StreamUtils/TreeBuildUtils | `.cursor/skills/utils-toolkit/SKILL.md` |
|
|
62
|
+
| 文件上传/OSS/云存储/MinIO/预签名URL | `.cursor/skills/file-oss-management/SKILL.md` |
|
|
63
|
+
| Bug/报错/异常/不工作/NullPointerException | `.cursor/skills/bug-detective/SKILL.md` |
|
|
64
|
+
| 异常处理/ServiceException/全局异常 | `.cursor/skills/error-handler/SKILL.md` |
|
|
65
|
+
| 性能/慢查询/接口慢/N+1问题 | `.cursor/skills/performance-doctor/SKILL.md` |
|
|
66
|
+
| 数据权限/@DataPermission/DataScope/行级权限 | `.cursor/skills/data-permission/SKILL.md` |
|
|
67
|
+
| Sa-Token/认证授权/XSS/SQL注入/数据脱敏 | `.cursor/skills/security-guard/SKILL.md` |
|
|
68
|
+
| 架构设计/模块划分/重构/技术栈 | `.cursor/skills/architecture-design/SKILL.md` |
|
|
69
|
+
| 规范/禁止/命名规范/代码风格 | `.cursor/skills/code-patterns/SKILL.md` |
|
|
70
|
+
| 项目结构/文件在哪/定位代码/模块职责 | `.cursor/skills/project-navigator/SKILL.md` |
|
|
71
|
+
| git/commit/提交/分支/合并/push/pull/冲突 | `.cursor/skills/git-workflow/SKILL.md` |
|
|
72
|
+
| 任务跟踪/记录进度/继续任务/跨会话 | `.cursor/skills/task-tracker/SKILL.md` |
|
|
73
|
+
| 技术选型/方案对比/哪个好/优缺点 | `.cursor/skills/tech-decision/SKILL.md` |
|
|
74
|
+
| 头脑风暴/方案设计/怎么设计/有什么办法 | `.cursor/skills/brainstorm/SKILL.md` |
|
|
75
|
+
| 工作流/审批流/Flowable/WarmFlow | `.cursor/skills/workflow-engine/SKILL.md` |
|
|
76
|
+
| 单元测试/@Test/JUnit5/Mockito | `.cursor/skills/test-development/SKILL.md` |
|
|
77
|
+
| JSON/序列化/反序列化/JsonUtils/BigDecimal精度 | `.cursor/skills/json-serialization/SKILL.md` |
|
|
78
|
+
| Redis/缓存/@Cacheable/@CacheEvict/分布式锁/RLock | `.cursor/skills/redis-cache/SKILL.md` |
|
|
79
|
+
| 定时任务/SnailJob/@Scheduled/@JobExecutor | `.cursor/skills/scheduled-jobs/SKILL.md` |
|
|
80
|
+
| WebSocket/SSE/实时推送/消息通知/双向通信 | `.cursor/skills/websocket-sse/SKILL.md` |
|
|
81
|
+
| 短信/邮件/SMS/验证码/MailUtils | `.cursor/skills/sms-mail/SKILL.md` |
|
|
82
|
+
| 第三方登录/微信登录/OAuth/JustAuth/扫码登录 | `.cursor/skills/social-login/SKILL.md` |
|
|
83
|
+
| 多租户/租户隔离/TenantEntity/tenantId/SaaS | `.cursor/skills/tenant-management/SKILL.md` |
|
|
84
|
+
| Element UI/前端组件/el-table/el-form/管理页面 | `.cursor/skills/ui-pc/SKILL.md` |
|
|
85
|
+
| Vuex/store/mapState/mapActions/状态管理 | `.cursor/skills/store-pc/SKILL.md` |
|
|
86
|
+
|
|
87
|
+
### 执行规则
|
|
88
|
+
|
|
89
|
+
1. **识别触发词** → 找到匹配的技能
|
|
90
|
+
2. **读取 SKILL.md** → 使用 Read 工具读取对应文件
|
|
91
|
+
3. **严格遵循规范** → 按照文档中的规范实现,不得违反禁令
|
|
92
|
+
4. **多技能时逐个读取** → 有多个匹配技能时,依次读取所有 SKILL.md
|
|
93
|
+
|
|
94
|
+
> SKILL.md 文档包含本项目特定的实现规范,必须优先参考,禁止使用通用写法替代。
|
package/bin/index.js
CHANGED
|
@@ -149,11 +149,13 @@ const UPDATE_RULES = {
|
|
|
149
149
|
{ src: '.claude/agents', dest: '.claude/agents', label: 'Agents(子代理)', isDir: true },
|
|
150
150
|
{ src: '.claude/hooks', dest: '.claude/hooks', label: 'Hooks(钩子脚本)', isDir: true },
|
|
151
151
|
{ src: '.claude/templates', dest: '.claude/templates', label: 'Templates', isDir: true },
|
|
152
|
+
{ src: '.claude/audio', dest: '.claude/audio', label: 'Audio(完成音效)', isDir: true },
|
|
152
153
|
{ src: '.claude/framework-config.json', dest: '.claude/framework-config.json', label: 'framework-config.json' },
|
|
153
154
|
],
|
|
154
155
|
preserve: [
|
|
155
|
-
{ dest: '.claude/settings.json',
|
|
156
|
-
{ dest: '
|
|
156
|
+
{ dest: '.claude/settings.json', reason: '包含用户 MCP 配置和权限设置' },
|
|
157
|
+
{ dest: '.claude/notify-config.json', reason: '包含用户通知偏好设置' },
|
|
158
|
+
{ dest: 'CLAUDE.md', reason: '包含项目自定义规范' },
|
|
157
159
|
],
|
|
158
160
|
},
|
|
159
161
|
cursor: {
|
|
@@ -163,6 +165,7 @@ const UPDATE_RULES = {
|
|
|
163
165
|
{ src: '.cursor/skills', dest: '.cursor/skills', label: 'Skills(技能库)', isDir: true },
|
|
164
166
|
{ src: '.cursor/agents', dest: '.cursor/agents', label: 'Agents(子代理)', isDir: true },
|
|
165
167
|
{ src: '.cursor/hooks', dest: '.cursor/hooks', label: 'Hooks(钩子脚本)', isDir: true },
|
|
168
|
+
{ src: '.cursor/audio', dest: '.cursor/audio', label: 'Audio(完成音效)', isDir: true },
|
|
166
169
|
{ src: '.cursor/hooks.json', dest: '.cursor/hooks.json', label: 'hooks.json(Hooks 配置)' },
|
|
167
170
|
],
|
|
168
171
|
preserve: [
|
|
@@ -194,7 +197,9 @@ const GLOBAL_RULES = {
|
|
|
194
197
|
{ src: '.claude/commands', dest: 'commands', label: 'Commands(全局命令)', isDir: true },
|
|
195
198
|
{ src: '.claude/agents', dest: 'agents', label: 'Agents(全局子代理)', isDir: true },
|
|
196
199
|
{ src: '.claude/hooks', dest: 'hooks', label: 'Hooks(全局钩子)', isDir: true },
|
|
200
|
+
{ src: '.claude/audio', dest: 'audio', label: 'Audio(完成音效)', isDir: true },
|
|
197
201
|
{ src: '.claude/framework-config.json', dest: 'framework-config.json', label: 'framework-config.json' },
|
|
202
|
+
{ src: '.claude/notify-config.json', dest: 'notify-config.json', label: 'notify-config.json(通知偏好)' },
|
|
198
203
|
{ src: '.claude/settings.json', dest: 'settings.json', label: 'settings.json(Hooks + MCP 配置)', merge: true, rewritePrefix: '.claude/' },
|
|
199
204
|
],
|
|
200
205
|
preserve: [],
|
|
@@ -207,6 +212,7 @@ const GLOBAL_RULES = {
|
|
|
207
212
|
{ src: '.cursor/skills', dest: 'skills', label: 'Skills(全局技能库)', isDir: true },
|
|
208
213
|
{ src: '.cursor/agents', dest: 'agents', label: 'Agents(全局子代理)', isDir: true },
|
|
209
214
|
{ src: '.cursor/hooks', dest: 'hooks', label: 'Hooks(全局钩子脚本)', isDir: true },
|
|
215
|
+
{ src: '.cursor/audio', dest: 'audio', label: 'Audio(完成音效)', isDir: true },
|
|
210
216
|
{ src: '.cursor/hooks.json', dest: 'hooks.json', label: 'hooks.json(Hooks 触发配置)', rewritePrefix: '.cursor/' },
|
|
211
217
|
{ src: '.cursor/mcp.json', dest: 'mcp.json', label: 'mcp.json(MCP 服务器配置)', merge: true },
|
|
212
218
|
],
|