ai-engineering-init 1.3.0 → 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/README.md +31 -5
- package/bin/index.js +52 -5
- 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/.claude/hooks/stop.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Stop Hook - Claude 回答结束时触发
|
|
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,31 +27,7 @@ const findAndDeleteNul = (dir, depth = 0) => {
|
|
|
28
27
|
};
|
|
29
28
|
findAndDeleteNul(process.cwd());
|
|
30
29
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const platform = process.platform;
|
|
36
|
-
if (platform === 'darwin') {
|
|
37
|
-
execSync(`afplay "${audioFile}"`, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
38
|
-
} else if (platform === 'win32') {
|
|
39
|
-
execSync(`powershell -c "(New-Object Media.SoundPlayer '${audioFile.replace(/'/g, "''")}').PlaySync()"`, {
|
|
40
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
41
|
-
});
|
|
42
|
-
} else if (platform === 'linux') {
|
|
43
|
-
try {
|
|
44
|
-
execSync(`aplay "${audioFile}"`, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
45
|
-
} catch {
|
|
46
|
-
try {
|
|
47
|
-
execSync(`paplay "${audioFile}"`, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
48
|
-
} catch {
|
|
49
|
-
// 播放失败,静默忽略
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
} catch {
|
|
55
|
-
// 播放失败时静默忽略
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
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,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"notify": {
|
|
3
|
+
"methods": ["sound"],
|
|
4
|
+
"sound": { "enabled": true, "file": "completed.wav" },
|
|
5
|
+
"notification": { "enabled": false, "title": "AI 任务完成" },
|
|
6
|
+
"tts": { "enabled": false, "prefix": "任务完成", "maxLength": 80 },
|
|
7
|
+
"summary": { "maxLength": 100, "strategy": "first-sentence", "stripMarkdown": true }
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -75,11 +75,23 @@ git commit -m "feat(system): 新增xxx功能"
|
|
|
75
75
|
### 不要提交的文件
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
# ❌
|
|
78
|
+
# ❌ 团队项目配置文件(绝对不提交,包含数据库/中间件/环境信息)
|
|
79
|
+
core-base/src/main/resources/bootstrap.yml
|
|
80
|
+
core-base/src/main/resources/bootstrap-dev.yml
|
|
81
|
+
core-base/src/main/resources/ConfigSource.properties
|
|
82
|
+
core-base/src/main/resources/license.dat
|
|
83
|
+
core-base/src/main/resources/logback-spring.xml
|
|
84
|
+
core-base/src/main/resources/message_*.properties
|
|
85
|
+
|
|
86
|
+
# ❌ 通用配置文件(绝对不提交)
|
|
79
87
|
ruoyi-admin/src/main/resources/application.yml
|
|
80
88
|
ruoyi-admin/src/main/resources/application-*.yml
|
|
81
89
|
ruoyi-common/**/resources/*.yml
|
|
82
90
|
|
|
91
|
+
# ❌ 测试/迁移基础设施文件(包含本地数据源配置,不提交)
|
|
92
|
+
**/WebMvcTestConfig.java
|
|
93
|
+
**/DataSourceMigration.java
|
|
94
|
+
|
|
83
95
|
# ❌ IDE 配置
|
|
84
96
|
.idea/
|
|
85
97
|
.vscode/
|
|
@@ -335,14 +347,15 @@ git log --author="作者名"
|
|
|
335
347
|
|
|
336
348
|
1. **不要强制推送到主分支**
|
|
337
349
|
```bash
|
|
338
|
-
# ❌
|
|
339
|
-
git push --force origin
|
|
350
|
+
# ❌ 禁止(团队主分支为 master)
|
|
351
|
+
git push --force origin master
|
|
352
|
+
git push --force origin main
|
|
340
353
|
```
|
|
341
354
|
|
|
342
355
|
2. **不要在主分支直接开发**
|
|
343
356
|
```bash
|
|
344
|
-
# ❌
|
|
345
|
-
git checkout
|
|
357
|
+
# ❌ 禁止(团队主分支为 master,不在此分支直接提交)
|
|
358
|
+
git checkout master
|
|
346
359
|
# 直接修改代码...
|
|
347
360
|
```
|
|
348
361
|
|
|
@@ -352,8 +365,17 @@ git log --author="作者名"
|
|
|
352
365
|
application.yml
|
|
353
366
|
application-dev.yml
|
|
354
367
|
application-prod.yml
|
|
368
|
+
bootstrap.yml
|
|
369
|
+
bootstrap-dev.yml
|
|
370
|
+
ConfigSource.properties
|
|
371
|
+
license.dat
|
|
372
|
+
logback-spring.xml
|
|
373
|
+
message_*.properties
|
|
355
374
|
credentials.json
|
|
356
375
|
password.txt
|
|
376
|
+
# ❌ 禁止提交(包含本地数据源/测试环境配置)
|
|
377
|
+
WebMvcTestConfig.java
|
|
378
|
+
DataSourceMigration.java
|
|
357
379
|
```
|
|
358
380
|
|
|
359
381
|
4. **不要自动 push(除非用户明确要求)**
|
|
@@ -337,19 +337,22 @@ if (matchedSkills.length === 0) {
|
|
|
337
337
|
// 构建技能文档路径列表
|
|
338
338
|
const skillPaths = matchedSkills.map(name => `.cursor/skills/${name}/SKILL.md`);
|
|
339
339
|
|
|
340
|
-
const
|
|
340
|
+
const skillList = matchedSkills.map((name, i) => `- **${name}**: \`${skillPaths[i]}\``).join('\n');
|
|
341
341
|
|
|
342
|
-
|
|
342
|
+
const injectedPrompt = `${prompt}
|
|
343
343
|
|
|
344
|
-
|
|
344
|
+
---
|
|
345
|
+
[系统提示] 检测到以下匹配技能,**请先用 Read 工具读取对应 SKILL.md 文件,再回答**:
|
|
346
|
+
${skillList}
|
|
345
347
|
|
|
346
|
-
|
|
348
|
+
执行顺序:① 读取 SKILL.md → ② 理解规范 → ③ 实现功能`;
|
|
347
349
|
|
|
348
|
-
1
|
|
349
|
-
2
|
|
350
|
-
3.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
350
|
+
// 策略1:尝试修改 prompt(若 Cursor 支持则直接生效,是最理想方案)
|
|
351
|
+
// 策略2:fallback 到 user_message 软提示(会打断用户但能传达信息)
|
|
352
|
+
// 策略3:rules/skill-activation.mdc 兜底(alwaysApply 永久生效,不依赖 hook)
|
|
353
|
+
console.log(JSON.stringify({
|
|
354
|
+
continue: true,
|
|
355
|
+
prompt: injectedPrompt, // 策略1:修改 prompt(测试 Cursor 是否支持)
|
|
356
|
+
user_message: skillList // 策略2:fallback(continue:true 时可能作为备注显示)
|
|
357
|
+
}));
|
|
355
358
|
process.exit(0);
|