autosnippet 2.7.1 → 2.8.2
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/README.md +13 -9
- package/bin/api-server.js +6 -0
- package/bin/cli.js +26 -0
- package/bin/mcp-server.js +22 -0
- package/dashboard/dist/assets/{index-BjfUm8p9.js → index-Duc8Qk-c.js} +30 -30
- package/dashboard/dist/index.html +1 -1
- package/lib/external/mcp/McpServer.js +11 -3
- package/lib/external/mcp/handlers/skill.js +198 -0
- package/lib/external/mcp/tools.js +54 -1
- package/lib/http/routes/candidates.js +1 -1
- package/lib/infrastructure/database/DatabaseConnection.js +11 -2
- package/lib/service/automation/handlers/AlinkHandler.js +43 -4
- package/lib/shared/PathGuard.js +2 -2
- package/package.json +1 -1
- package/skills/autosnippet-coldstart/SKILL.md +0 -2
- package/skills/autosnippet-reference-jsts/SKILL.md +0 -2
- package/skills/autosnippet-reference-objc/SKILL.md +0 -2
- package/skills/autosnippet-reference-swift/SKILL.md +0 -2
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>AutoSnippet Dashboard</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Duc8Qk-c.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/yaml-qRaU8Ldn.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-BotF760a.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/axios-C0Zqfgkc.js">
|
|
@@ -54,6 +54,12 @@ export class McpServer {
|
|
|
54
54
|
|
|
55
55
|
// 路径安全守卫 — 在任何写操作前配置
|
|
56
56
|
const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
|
|
57
|
+
|
|
58
|
+
// 切换工作目录到项目根 — 确保 DB 等相对路径正确解析
|
|
59
|
+
if (projectRoot !== process.cwd()) {
|
|
60
|
+
process.chdir(projectRoot);
|
|
61
|
+
}
|
|
62
|
+
|
|
57
63
|
Bootstrap.configurePathGuard(projectRoot);
|
|
58
64
|
|
|
59
65
|
this.bootstrap = new Bootstrap();
|
|
@@ -148,10 +154,12 @@ export class McpServer {
|
|
|
148
154
|
// Bootstrap 冷启动
|
|
149
155
|
case 'autosnippet_bootstrap_knowledge': return bootstrapHandlers.bootstrapKnowledge(ctx, args);
|
|
150
156
|
case 'autosnippet_bootstrap_refine': return bootstrapHandlers.bootstrapRefine(ctx, args);
|
|
151
|
-
// Skills 加载 & 创建 & 推荐
|
|
157
|
+
// Skills 加载 & 创建 & 管理 & 推荐
|
|
152
158
|
case 'autosnippet_list_skills': return skillHandlers.listSkills();
|
|
153
159
|
case 'autosnippet_load_skill': return skillHandlers.loadSkill(ctx, args);
|
|
154
160
|
case 'autosnippet_create_skill': return skillHandlers.createSkill(ctx, args);
|
|
161
|
+
case 'autosnippet_delete_skill': return skillHandlers.deleteSkill(ctx, args);
|
|
162
|
+
case 'autosnippet_update_skill': return skillHandlers.updateSkill(ctx, args);
|
|
155
163
|
case 'autosnippet_suggest_skills': return skillHandlers.suggestSkills(ctx);
|
|
156
164
|
default: throw new Error(`Unknown tool: ${name}`);
|
|
157
165
|
}
|
|
@@ -200,9 +208,9 @@ export class McpServer {
|
|
|
200
208
|
await this.initialize();
|
|
201
209
|
const transport = new StdioServerTransport();
|
|
202
210
|
await this.server.connect(transport);
|
|
203
|
-
this.logger.info('MCP Server started (stdio) —
|
|
211
|
+
this.logger.info('MCP Server started (stdio) — 38 tools');
|
|
204
212
|
// 在 stderr 写一行简洁的就绪通知(不使用 winston,仅用于 Cursor 日志面板 & 调试)
|
|
205
|
-
process.stderr.write('AutoSnippet MCP ready —
|
|
213
|
+
process.stderr.write('AutoSnippet MCP ready — 38 tools\n');
|
|
206
214
|
}
|
|
207
215
|
|
|
208
216
|
async shutdown() {
|
|
@@ -399,6 +399,204 @@ function _regenerateEditorIndex() {
|
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
+
// ═══════════════════════════════════════════════════════════
|
|
403
|
+
// Handler: deleteSkill
|
|
404
|
+
// ═══════════════════════════════════════════════════════════
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* 删除项目级 Skill — 移除 {projectRoot}/AutoSnippet/skills/<name>/ 整个目录
|
|
408
|
+
* 内置 Skill 不可删除。删除后自动 regenerate 编辑器索引。
|
|
409
|
+
*
|
|
410
|
+
* @param {object} _ctx MCP context
|
|
411
|
+
* @param {object} args { name: string }
|
|
412
|
+
* @returns {string} JSON envelope
|
|
413
|
+
*/
|
|
414
|
+
export function deleteSkill(_ctx, args) {
|
|
415
|
+
const { name } = args || {};
|
|
416
|
+
|
|
417
|
+
if (!name) {
|
|
418
|
+
return JSON.stringify({
|
|
419
|
+
success: false,
|
|
420
|
+
error: { code: 'MISSING_PARAM', message: 'name is required' },
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// 不允许删除内置 Skill
|
|
425
|
+
const builtinSkillPath = path.join(SKILLS_DIR, name);
|
|
426
|
+
if (fs.existsSync(builtinSkillPath)) {
|
|
427
|
+
return JSON.stringify({
|
|
428
|
+
success: false,
|
|
429
|
+
error: {
|
|
430
|
+
code: 'BUILTIN_PROTECTED',
|
|
431
|
+
message: `"${name}" is a built-in Skill and cannot be deleted.`,
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// 检查项目级 Skill 是否存在
|
|
437
|
+
const projectSkillsDir = _getProjectSkillsDir();
|
|
438
|
+
const skillDir = path.join(projectSkillsDir, name);
|
|
439
|
+
if (!fs.existsSync(skillDir)) {
|
|
440
|
+
return JSON.stringify({
|
|
441
|
+
success: false,
|
|
442
|
+
error: {
|
|
443
|
+
code: 'SKILL_NOT_FOUND',
|
|
444
|
+
message: `Project skill "${name}" not found.`,
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ── 路径安全检查 ──
|
|
450
|
+
try {
|
|
451
|
+
pathGuard.assertProjectWriteSafe(skillDir);
|
|
452
|
+
} catch (err) {
|
|
453
|
+
return JSON.stringify({
|
|
454
|
+
success: false,
|
|
455
|
+
error: { code: 'PATH_GUARD', message: err.message },
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ── 删除目录 ──
|
|
460
|
+
try {
|
|
461
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
462
|
+
} catch (err) {
|
|
463
|
+
return JSON.stringify({
|
|
464
|
+
success: false,
|
|
465
|
+
error: { code: 'DELETE_ERROR', message: `Failed to delete skill: ${err.message}` },
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ── regenerate 编辑器索引 ──
|
|
470
|
+
const indexResult = _regenerateEditorIndex();
|
|
471
|
+
|
|
472
|
+
return JSON.stringify({
|
|
473
|
+
success: true,
|
|
474
|
+
data: {
|
|
475
|
+
skillName: name,
|
|
476
|
+
deleted: true,
|
|
477
|
+
editorIndex: indexResult,
|
|
478
|
+
hint: `Skill "${name}" deleted successfully.`,
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ═══════════════════════════════════════════════════════════
|
|
484
|
+
// Handler: updateSkill
|
|
485
|
+
// ═══════════════════════════════════════════════════════════
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* 更新项目级 Skill — 修改 description 和/或 content
|
|
489
|
+
* 内置 Skill 不可更新。更新后自动 regenerate 编辑器索引。
|
|
490
|
+
*
|
|
491
|
+
* @param {object} _ctx MCP context
|
|
492
|
+
* @param {object} args { name, description?, content? }
|
|
493
|
+
* @returns {string} JSON envelope
|
|
494
|
+
*/
|
|
495
|
+
export function updateSkill(_ctx, args) {
|
|
496
|
+
const { name, description, content } = args || {};
|
|
497
|
+
|
|
498
|
+
if (!name) {
|
|
499
|
+
return JSON.stringify({
|
|
500
|
+
success: false,
|
|
501
|
+
error: { code: 'MISSING_PARAM', message: 'name is required' },
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (!description && !content) {
|
|
506
|
+
return JSON.stringify({
|
|
507
|
+
success: false,
|
|
508
|
+
error: { code: 'NOTHING_TO_UPDATE', message: 'At least one of description or content must be provided.' },
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// 不允许更新内置 Skill
|
|
513
|
+
const builtinSkillPath = path.join(SKILLS_DIR, name, 'SKILL.md');
|
|
514
|
+
if (fs.existsSync(builtinSkillPath)) {
|
|
515
|
+
return JSON.stringify({
|
|
516
|
+
success: false,
|
|
517
|
+
error: {
|
|
518
|
+
code: 'BUILTIN_PROTECTED',
|
|
519
|
+
message: `"${name}" is a built-in Skill and cannot be updated. Fork it as a project skill instead.`,
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// 检查项目级 Skill 是否存在
|
|
525
|
+
const projectSkillsDir = _getProjectSkillsDir();
|
|
526
|
+
const skillPath = path.join(projectSkillsDir, name, 'SKILL.md');
|
|
527
|
+
if (!fs.existsSync(skillPath)) {
|
|
528
|
+
return JSON.stringify({
|
|
529
|
+
success: false,
|
|
530
|
+
error: {
|
|
531
|
+
code: 'SKILL_NOT_FOUND',
|
|
532
|
+
message: `Project skill "${name}" not found. Use autosnippet_create_skill to create it first.`,
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
// ── 读取现有文件 ──
|
|
539
|
+
const existing = fs.readFileSync(skillPath, 'utf8');
|
|
540
|
+
|
|
541
|
+
// 解析现有 frontmatter
|
|
542
|
+
const fmMatch = existing.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
543
|
+
let oldFm = '';
|
|
544
|
+
let oldBody = existing;
|
|
545
|
+
if (fmMatch) {
|
|
546
|
+
oldFm = fmMatch[1];
|
|
547
|
+
oldBody = fmMatch[2];
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// 解析已有字段
|
|
551
|
+
const getField = (fm, key) => {
|
|
552
|
+
const m = fm.match(new RegExp(`^${key}:\\s*(.+?)$`, 'm'));
|
|
553
|
+
return m ? m[1].trim() : null;
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const newDesc = description || getField(oldFm, 'description') || name;
|
|
557
|
+
const newBody = content !== undefined && content !== null ? content : oldBody;
|
|
558
|
+
|
|
559
|
+
// 保留原有字段
|
|
560
|
+
const createdBy = getField(oldFm, 'createdBy') || 'external-ai';
|
|
561
|
+
const createdAt = getField(oldFm, 'createdAt') || new Date().toISOString();
|
|
562
|
+
const title = getField(oldFm, 'title');
|
|
563
|
+
|
|
564
|
+
// 重建 frontmatter
|
|
565
|
+
const fmLines = ['---', `name: ${name}`];
|
|
566
|
+
if (title) fmLines.push(`title: ${title}`);
|
|
567
|
+
fmLines.push(
|
|
568
|
+
`description: ${newDesc}`,
|
|
569
|
+
`createdBy: ${createdBy}`,
|
|
570
|
+
`createdAt: ${createdAt}`,
|
|
571
|
+
`updatedAt: ${new Date().toISOString()}`,
|
|
572
|
+
'---',
|
|
573
|
+
'',
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
pathGuard.assertProjectWriteSafe(path.join(projectSkillsDir, name));
|
|
577
|
+
fs.writeFileSync(skillPath, fmLines.join('\n') + newBody, 'utf8');
|
|
578
|
+
} catch (err) {
|
|
579
|
+
return JSON.stringify({
|
|
580
|
+
success: false,
|
|
581
|
+
error: { code: 'UPDATE_ERROR', message: `Failed to update skill: ${err.message}` },
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// ── regenerate 编辑器索引 ──
|
|
586
|
+
const indexResult = _regenerateEditorIndex();
|
|
587
|
+
|
|
588
|
+
return JSON.stringify({
|
|
589
|
+
success: true,
|
|
590
|
+
data: {
|
|
591
|
+
skillName: name,
|
|
592
|
+
updated: true,
|
|
593
|
+
fieldsUpdated: [description ? 'description' : null, content ? 'content' : null].filter(Boolean),
|
|
594
|
+
editorIndex: indexResult,
|
|
595
|
+
hint: `Skill "${name}" updated. Use autosnippet_load_skill to verify content.`,
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
402
600
|
// ═══════════════════════════════════════════════════════════
|
|
403
601
|
// Handler: suggestSkills
|
|
404
602
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP 工具定义(
|
|
2
|
+
* MCP 工具定义(38 个)+ Gateway 映射
|
|
3
3
|
*
|
|
4
4
|
* 只包含 JSON Schema 级别的声明,不含任何业务逻辑。
|
|
5
5
|
*/
|
|
@@ -18,6 +18,8 @@ export const TOOL_GATEWAY_MAP = {
|
|
|
18
18
|
autosnippet_bootstrap_knowledge: { action: 'knowledge:bootstrap', resource: 'knowledge' },
|
|
19
19
|
autosnippet_bootstrap_refine: { action: 'candidate:update', resource: 'candidates' },
|
|
20
20
|
autosnippet_create_skill: { action: 'create:skills', resource: 'skills' },
|
|
21
|
+
autosnippet_delete_skill: { action: 'delete:skills', resource: 'skills' },
|
|
22
|
+
autosnippet_update_skill: { action: 'update:skills', resource: 'skills' },
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export const TOOLS = [
|
|
@@ -684,6 +686,57 @@ export const TOOLS = [
|
|
|
684
686
|
' • 候选被大量驳回时',
|
|
685
687
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
686
688
|
},
|
|
689
|
+
// 37. 删除项目级 Skill
|
|
690
|
+
{
|
|
691
|
+
name: 'autosnippet_delete_skill',
|
|
692
|
+
description:
|
|
693
|
+
'删除一个项目级 Skill 及其目录。\n' +
|
|
694
|
+
'⚠️ 内置 Skill 不可删除。删除后自动更新编辑器索引。\n' +
|
|
695
|
+
'\n' +
|
|
696
|
+
'使用场景:\n' +
|
|
697
|
+
' • 清理不再需要的自定义 Skill\n' +
|
|
698
|
+
' • 移除过时或错误的操作指南',
|
|
699
|
+
inputSchema: {
|
|
700
|
+
type: 'object',
|
|
701
|
+
properties: {
|
|
702
|
+
name: {
|
|
703
|
+
type: 'string',
|
|
704
|
+
description: 'Skill 名称(如 my-auth-guide)',
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
required: ['name'],
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
// 38. 更新项目级 Skill
|
|
711
|
+
{
|
|
712
|
+
name: 'autosnippet_update_skill',
|
|
713
|
+
description:
|
|
714
|
+
'更新已存在的项目级 Skill 的描述或内容。\n' +
|
|
715
|
+
'⚠️ 内置 Skill 不可更新。更新后自动刷新编辑器索引。\n' +
|
|
716
|
+
'\n' +
|
|
717
|
+
'使用场景:\n' +
|
|
718
|
+
' • 迭代改进已有 Skill 的操作指南\n' +
|
|
719
|
+
' • 更新过时的最佳实践内容\n' +
|
|
720
|
+
' • 修正 Skill 描述或补充新章节',
|
|
721
|
+
inputSchema: {
|
|
722
|
+
type: 'object',
|
|
723
|
+
properties: {
|
|
724
|
+
name: {
|
|
725
|
+
type: 'string',
|
|
726
|
+
description: 'Skill 名称(必须已存在于项目级 Skills 中)',
|
|
727
|
+
},
|
|
728
|
+
description: {
|
|
729
|
+
type: 'string',
|
|
730
|
+
description: '新的一句话描述(可选,不传则保持原值)',
|
|
731
|
+
},
|
|
732
|
+
content: {
|
|
733
|
+
type: 'string',
|
|
734
|
+
description: '新的正文内容(Markdown 格式,不含 frontmatter)。不传则保持原值',
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
required: ['name'],
|
|
738
|
+
},
|
|
739
|
+
},
|
|
687
740
|
// 36. ② 内容润色:Bootstrap 候选 AI 精炼(Phase 6)
|
|
688
741
|
{
|
|
689
742
|
name: 'autosnippet_bootstrap_refine',
|
|
@@ -22,7 +22,7 @@ const MAX_BATCH_SIZE = 100;
|
|
|
22
22
|
router.get('/', asyncHandler(async (req, res) => {
|
|
23
23
|
const { status, language, category, keyword } = req.query;
|
|
24
24
|
const page = safeInt(req.query.page, 1);
|
|
25
|
-
const pageSize = safeInt(req.query.limit, 20, 1,
|
|
25
|
+
const pageSize = safeInt(req.query.limit, 20, 1, 1000);
|
|
26
26
|
|
|
27
27
|
const container = getServiceContainer();
|
|
28
28
|
const candidateService = container.get('candidateService');
|
|
@@ -9,6 +9,9 @@ const __dirname = path.dirname(__filename);
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* DatabaseConnection - 数据库连接管理器
|
|
12
|
+
*
|
|
13
|
+
* 重要:相对 DB 路径通过 projectRoot 解析,而非 process.cwd()。
|
|
14
|
+
* 这样即使 MCP 服务器的 cwd 不是项目目录,DB 也不会创建到项目外。
|
|
12
15
|
*/
|
|
13
16
|
export class DatabaseConnection {
|
|
14
17
|
constructor(config) {
|
|
@@ -22,8 +25,14 @@ export class DatabaseConnection {
|
|
|
22
25
|
async connect() {
|
|
23
26
|
const dbPath = this.config.path;
|
|
24
27
|
|
|
28
|
+
// 使用 projectRoot(PathGuard 已配置)优先解析相对路径,
|
|
29
|
+
// 而非 path.resolve()(依赖 cwd,MCP 场景下 cwd 可能是用户主目录)
|
|
30
|
+
const projectRoot = pathGuard.projectRoot;
|
|
31
|
+
const resolvedDbPath = (projectRoot && !path.isAbsolute(dbPath))
|
|
32
|
+
? path.resolve(projectRoot, dbPath)
|
|
33
|
+
: path.resolve(dbPath);
|
|
34
|
+
|
|
25
35
|
// 路径安全检查 — 防止 DB 文件创建到项目允许范围外
|
|
26
|
-
const resolvedDbPath = path.resolve(dbPath);
|
|
27
36
|
pathGuard.assertProjectWriteSafe(resolvedDbPath);
|
|
28
37
|
|
|
29
38
|
// 确保数据目录存在
|
|
@@ -32,7 +41,7 @@ export class DatabaseConnection {
|
|
|
32
41
|
fs.mkdirSync(dbDir, { recursive: true });
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
this.db = new Database(
|
|
44
|
+
this.db = new Database(resolvedDbPath, {
|
|
36
45
|
verbose: this.config.verbose ? (msg) => process.stderr.write(`[SQL] ${msg}\n`) : null,
|
|
37
46
|
});
|
|
38
47
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AlinkHandler — 处理 alink 指令
|
|
3
|
+
*
|
|
4
|
+
* 解析编辑器中的 alink 触发行,提取 completionKey,
|
|
5
|
+
* 通过数据库查找匹配的 Recipe,打开 Dashboard 详情页。
|
|
3
6
|
*/
|
|
4
7
|
|
|
8
|
+
import { getServiceContainer } from '../../../injection/ServiceContainer.js';
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* @param {string} alinkLine
|
|
7
12
|
*/
|
|
@@ -19,11 +24,45 @@ export async function handleAlink(alinkLine) {
|
|
|
19
24
|
|
|
20
25
|
if (completionKey != null) {
|
|
21
26
|
try {
|
|
27
|
+
// 从 DI 容器获取数据库实例,查找匹配 trigger 的 Recipe
|
|
28
|
+
const container = getServiceContainer();
|
|
29
|
+
const db = container.get('database');
|
|
30
|
+
|
|
31
|
+
let recipeId = null;
|
|
32
|
+
if (db) {
|
|
33
|
+
try {
|
|
34
|
+
const row = db.prepare(
|
|
35
|
+
'SELECT id FROM recipes WHERE trigger = ? LIMIT 1',
|
|
36
|
+
).get(completionKey);
|
|
37
|
+
if (row) recipeId = row.id;
|
|
38
|
+
} catch {
|
|
39
|
+
// DB 查询失败时回退到搜索
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 若精确匹配失败,尝试模糊搜索
|
|
43
|
+
if (!recipeId) {
|
|
44
|
+
try {
|
|
45
|
+
const escaped = completionKey.replace(/[%_\\]/g, ch => `\\${ch}`);
|
|
46
|
+
const row = db.prepare(
|
|
47
|
+
"SELECT id FROM recipes WHERE trigger LIKE ? ESCAPE '\\' OR title LIKE ? ESCAPE '\\' LIMIT 1",
|
|
48
|
+
).get(`%${escaped}%`, `%${escaped}%`);
|
|
49
|
+
if (row) recipeId = row.id;
|
|
50
|
+
} catch { /* silent */ }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 构建 Dashboard URL 并打开
|
|
55
|
+
const port = process.env.ASD_DASHBOARD_PORT || 3000;
|
|
56
|
+
const host = process.env.ASD_DASHBOARD_HOST || 'localhost';
|
|
57
|
+
const url = recipeId
|
|
58
|
+
? `http://${host}:${port}/#/recipes/${recipeId}`
|
|
59
|
+
: `http://${host}:${port}/#/search?q=${encodeURIComponent(completionKey)}`;
|
|
60
|
+
|
|
22
61
|
const open = (await import('open')).default;
|
|
23
|
-
|
|
24
|
-
console.log(`[alink] completionKey=${completionKey}
|
|
25
|
-
} catch {
|
|
26
|
-
|
|
62
|
+
await open(url);
|
|
63
|
+
console.log(`[alink] completionKey=${completionKey} → ${url}`);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.warn(`[alink] Failed to open link: ${err.message}`);
|
|
27
66
|
}
|
|
28
67
|
}
|
|
29
68
|
}
|
package/lib/shared/PathGuard.js
CHANGED
|
@@ -104,11 +104,11 @@ class PathGuard {
|
|
|
104
104
|
this.#packageRoot = packageRoot ? path.resolve(packageRoot) : null;
|
|
105
105
|
this.#knowledgeBaseDir = knowledgeBaseDir || null; // 延迟解析
|
|
106
106
|
|
|
107
|
-
// 默认白名单:Xcode snippets
|
|
107
|
+
// 默认白名单:Xcode snippets 目录、全局缓存(cache 子目录,不含整个 ~/.autosnippet)
|
|
108
108
|
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
109
109
|
if (HOME) {
|
|
110
110
|
this.#allowList.add(path.join(HOME, 'Library/Developer/Xcode/UserData/CodeSnippets'));
|
|
111
|
-
this.#allowList.add(path.join(HOME, '.autosnippet'));
|
|
111
|
+
this.#allowList.add(path.join(HOME, '.autosnippet', 'cache'));
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
// 用户自定义白名单
|
package/package.json
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
```skill
|
|
2
1
|
---
|
|
3
2
|
name: autosnippet-coldstart
|
|
4
3
|
description: Cold-start knowledge base initialization. Full 9-dimension analysis workflow — works with both external Agent and internal AI. Call bootstrap_knowledge, then systematically analyze code and submit candidates.
|
|
@@ -465,4 +464,3 @@ description: Cold-start knowledge base initialization. Full 9-dimension analysis
|
|
|
465
464
|
- **autosnippet-reference-swift**: Swift 业界最佳实践参考(命名/并发/错误处理/内存/设计模式)
|
|
466
465
|
- **autosnippet-reference-objc**: Objective-C 业界最佳实践参考(ARC/Block/Delegate/Prefix/GCD)
|
|
467
466
|
- **autosnippet-reference-jsts**: JavaScript/TypeScript 业界最佳实践参考(async/React/Node/ESM/类型系统)
|
|
468
|
-
```
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
```skill
|
|
2
1
|
---
|
|
3
2
|
name: autosnippet-reference-jsts
|
|
4
3
|
description: JavaScript/TypeScript 业界最佳实践参考。涵盖类型系统、模块化、错误处理、async/await、命名、ESLint 规则,为冷启动分析提供高质量参考标准。
|
|
@@ -437,4 +436,3 @@ const url = baseUrl + '/api/v' + version + '/users/' + userId;
|
|
|
437
436
|
- **autosnippet-coldstart**: 完整冷启动流程(本 Skill 的主 Skill)
|
|
438
437
|
- **autosnippet-reference-swift**: Swift 业界最佳实践参考
|
|
439
438
|
- **autosnippet-reference-objc**: Objective-C 业界最佳实践参考
|
|
440
|
-
```
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
```skill
|
|
2
1
|
---
|
|
3
2
|
name: autosnippet-reference-objc
|
|
4
3
|
description: Objective-C 业界最佳实践参考。涵盖命名前缀、属性声明、Delegate 模式、内存管理、nullability、错误处理,为冷启动分析提供高质量参考标准。
|
|
@@ -424,4 +423,3 @@ if ([NSThread isMainThread]) {
|
|
|
424
423
|
- **autosnippet-coldstart**: 完整冷启动流程(本 Skill 的主 Skill)
|
|
425
424
|
- **autosnippet-reference-swift**: Swift 业界最佳实践参考
|
|
426
425
|
- **autosnippet-reference-jsts**: JavaScript/TypeScript 业界最佳实践参考
|
|
427
|
-
```
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
```skill
|
|
2
1
|
---
|
|
3
2
|
name: autosnippet-reference-swift
|
|
4
3
|
description: Swift 业界最佳实践参考。涵盖命名规范、Swift Concurrency、SwiftUI 模式、错误处理、内存管理、设计模式,为冷启动分析提供高质量参考标准。
|
|
@@ -458,4 +457,3 @@ UIView.animate(
|
|
|
458
457
|
- **autosnippet-coldstart**: 完整冷启动流程(本 Skill 的主 Skill)
|
|
459
458
|
- **autosnippet-reference-objc**: Objective-C 业界最佳实践参考
|
|
460
459
|
- **autosnippet-reference-jsts**: JavaScript/TypeScript 业界最佳实践参考
|
|
461
|
-
```
|