autosnippet 1.3.4 → 1.3.6
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 +1 -1
- package/bin/create.js +9 -8
- package/bin/share.js +3 -2
- package/bin/ui.js +10 -9
- package/lib/ai/providers/ClaudeProvider.js +2 -2
- package/lib/ai/providers/GoogleGeminiProvider.js +2 -2
- package/lib/ai/providers/MockProvider.js +2 -2
- package/lib/ai/providers/OpenAiProvider.js +2 -2
- package/lib/infra/cacheStore.js +2 -1
- package/lib/infra/triggerSymbol.js +73 -0
- package/lib/injection/directiveParser.js +3 -2
- package/lib/snippet/snippetFactory.js +4 -3
- package/lib/snippet/specRepository.js +7 -9
- package/lib/watch/fileWatcher.js +16 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ asd ui
|
|
|
95
95
|
|
|
96
96
|
## 术语
|
|
97
97
|
|
|
98
|
-
- **Snippet**:写入 Xcode 的代码片段,通过 trigger
|
|
98
|
+
- **Snippet**:写入 Xcode 的代码片段,通过 trigger(补全键)或库面板使用;trigger 以配置的触发符开头(默认 **@**,可通过环境变量 `ASD_TRIGGER_SYMBOL` 更换)。
|
|
99
99
|
- **Recipe(配方)**:存放在 `Knowledge/recipes/` 下的 Markdown 知识,供 AI 检索、Guard 审查与搜索。
|
|
100
100
|
- **项目根**:含 `AutoSnippetRoot.boxspec.json` 的目录。
|
|
101
101
|
|
package/bin/create.js
CHANGED
|
@@ -22,6 +22,7 @@ const fs = require('fs');
|
|
|
22
22
|
const path = require('path');
|
|
23
23
|
const readline = require('readline');
|
|
24
24
|
const cache = require('../lib/infra/cacheStore.js');
|
|
25
|
+
const triggerSymbol = require('../lib/infra/triggerSymbol.js');
|
|
25
26
|
const findPath = require('./findPath.js');
|
|
26
27
|
const specRepository = require('../lib/snippet/specRepository.js');
|
|
27
28
|
const snippetFactory = require('../lib/snippet/snippetFactory.js');
|
|
@@ -92,8 +93,8 @@ function updateCodeSnippets(specFile, word, key, value) {
|
|
|
92
93
|
let placeItem = placeholder.list[index];
|
|
93
94
|
|
|
94
95
|
const t = placeItem && placeItem.trigger ? String(placeItem.trigger) : '';
|
|
95
|
-
const raw =
|
|
96
|
-
if (raw === word || t === word || t === (
|
|
96
|
+
const raw = triggerSymbol.stripTriggerPrefix(t);
|
|
97
|
+
if (raw === word || t === word || t === (triggerSymbol.TRIGGER_SYMBOL + word)) {
|
|
97
98
|
snippet = placeItem;
|
|
98
99
|
if (key) {
|
|
99
100
|
snippet[key] = value;
|
|
@@ -174,8 +175,8 @@ function createCodeSnippets(specFile, answers, updateSnippet, selectedFilePath)
|
|
|
174
175
|
snippet = {
|
|
175
176
|
identifier: identifier,
|
|
176
177
|
title: answers.title,
|
|
177
|
-
trigger:
|
|
178
|
-
completion:
|
|
178
|
+
trigger: triggerSymbol.TRIGGER_SYMBOL + answers.completion_first,
|
|
179
|
+
completion: triggerSymbol.TRIGGER_SYMBOL + answers.completion_first + completionMoreStr,
|
|
179
180
|
summary: answers.summary,
|
|
180
181
|
languageShort: 'objc',
|
|
181
182
|
};
|
|
@@ -418,11 +419,11 @@ async function createFromExtracted(projectRoot, rootSpecPath, extracted) {
|
|
|
418
419
|
console.error('❌ AI 结果不完整:需要 title / code');
|
|
419
420
|
return;
|
|
420
421
|
}
|
|
421
|
-
const trigger = (extracted.trigger || '').trim() || (
|
|
422
|
-
const prefix =
|
|
423
|
-
const normalizedTrigger =
|
|
422
|
+
const trigger = (extracted.trigger || '').trim() || (triggerSymbol.TRIGGER_SYMBOL + extracted.title.replace(/\s+/g, ''));
|
|
423
|
+
const prefix = triggerSymbol.hasTriggerPrefix(trigger) ? '' : triggerSymbol.TRIGGER_SYMBOL;
|
|
424
|
+
const normalizedTrigger = triggerSymbol.hasTriggerPrefix(trigger) ? trigger : prefix + trigger;
|
|
424
425
|
const category = extracted.category || 'Utility';
|
|
425
|
-
const categoryPart = category ? (normalizedTrigger
|
|
426
|
+
const categoryPart = category ? triggerSymbol.getPrefixFromTrigger(normalizedTrigger) + category : '';
|
|
426
427
|
const isSwift = (extracted.language || '').toLowerCase() === 'swift';
|
|
427
428
|
const codeLines = extracted.code.split('\n').map(s => s.replace(/\r$/, ''));
|
|
428
429
|
let body = codeLines;
|
package/bin/share.js
CHANGED
|
@@ -22,6 +22,7 @@ const inquirer = require('inquirer');
|
|
|
22
22
|
|
|
23
23
|
const specRepository = require('../lib/snippet/specRepository.js');
|
|
24
24
|
const cache = require('../lib/infra/cacheStore.js');
|
|
25
|
+
const triggerSymbol = require('../lib/infra/triggerSymbol.js');
|
|
25
26
|
const config = require('../lib/infra/paths.js');
|
|
26
27
|
|
|
27
28
|
function shareCodeSnippets(specFile) {
|
|
@@ -246,7 +247,7 @@ function shareTheSnippet(specFile, filePath, answers) {
|
|
|
246
247
|
array[0] = completion_first;
|
|
247
248
|
createFromLocal(specFile, filedir, array, answers.completion_more);
|
|
248
249
|
});
|
|
249
|
-
} else if (array[0]
|
|
250
|
+
} else if (triggerSymbol.hasTriggerPrefix(array[0]) || array[0].endsWith('@Moudle')) {
|
|
250
251
|
console.log('这个文件已经是共享版本,不需要再处理。');
|
|
251
252
|
} else {
|
|
252
253
|
createFromLocal(specFile, filedir, array, answers.completion_more);
|
|
@@ -262,7 +263,7 @@ function shareTheSnippet(specFile, filePath, answers) {
|
|
|
262
263
|
function createFromLocal(specFile, filedir, array, completion_more) {
|
|
263
264
|
const xcodeLang = array[3];
|
|
264
265
|
const languageShort = xcodeLang === 'Xcode.SourceCodeLanguage.Swift' ? 'swift' : 'objc';
|
|
265
|
-
const prefix =
|
|
266
|
+
const prefix = triggerSymbol.TRIGGER_SYMBOL;
|
|
266
267
|
const snippet = {
|
|
267
268
|
identifier: 'AutoSnip_' + array[2],
|
|
268
269
|
title: array[5],
|
package/bin/ui.js
CHANGED
|
@@ -14,6 +14,7 @@ const targetScanner = require('../lib/spm/targetScanner');
|
|
|
14
14
|
const candidateService = require('../lib/ai/candidateService');
|
|
15
15
|
const headerResolution = require('../lib/ai/headerResolution');
|
|
16
16
|
const markerLine = require('../lib/snippet/markerLine');
|
|
17
|
+
const triggerSymbol = require('../lib/infra/triggerSymbol');
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* 检测当前进程是否已有控制 Chromium 系浏览器的权限(与 openChrome.applescript 所需一致)
|
|
@@ -177,7 +178,7 @@ function launch(projectRoot, port = 3000, options = {}) {
|
|
|
177
178
|
const searchMark = /\/\/\s*(?:autosnippet|as):search(\s|$)/;
|
|
178
179
|
let found = -1;
|
|
179
180
|
for (let i = 0; i < lines.length; i++) {
|
|
180
|
-
const t = lines[i].
|
|
181
|
+
const t = triggerSymbol.stripTriggerPrefix(lines[i].trim()).trim();
|
|
181
182
|
if (searchMark.test(t) || t === '// as:search' || t.startsWith('// as:search ') || t.startsWith('// autosnippet:search')) {
|
|
182
183
|
found = i;
|
|
183
184
|
break;
|
|
@@ -608,12 +609,11 @@ function launch(projectRoot, port = 3000, options = {}) {
|
|
|
608
609
|
const { snippet } = req.body;
|
|
609
610
|
const rootSpecPath = path.join(projectRoot, 'AutoSnippetRoot.boxspec.json');
|
|
610
611
|
|
|
611
|
-
// ✅ 映射 Dashboard Snippet 格式到内部 specRepository
|
|
612
|
-
const triggerBase = snippet.
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
const
|
|
616
|
-
const categoryPart = snippet.category ? `${triggerPrefix}${snippet.category}` : '';
|
|
612
|
+
// ✅ 映射 Dashboard Snippet 格式到内部 specRepository 格式(Trigger 输入框绑定的是 completionKey,保存时优先用其值以同步用户编辑)
|
|
613
|
+
const triggerBase = snippet.completionKey ?? snippet.trigger ?? '';
|
|
614
|
+
const sym = triggerSymbol.TRIGGER_SYMBOL;
|
|
615
|
+
const normalizedTrigger = triggerSymbol.ensureTriggerPrefix(triggerBase);
|
|
616
|
+
const categoryPart = snippet.category ? `${sym}${snippet.category}` : '';
|
|
617
617
|
|
|
618
618
|
// 处理 body:确保是数组;若前端误传了已转义内容则先还原,再清理触发符,最后只转义一次写入
|
|
619
619
|
const rawBody = snippet.body || snippet.content || [];
|
|
@@ -624,10 +624,11 @@ function launch(projectRoot, port = 3000, options = {}) {
|
|
|
624
624
|
if (firstLine === normalizedTrigger || firstLine === triggerBase || firstLine === normalizedTrigger.slice(1)) {
|
|
625
625
|
cleanedBody.shift();
|
|
626
626
|
}
|
|
627
|
-
|
|
627
|
+
const symEsc = sym.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
628
|
+
while (cleanedBody.length && new RegExp('^' + symEsc + '$').test(String(cleanedBody[0]).trim())) cleanedBody.shift();
|
|
628
629
|
if (cleanedBody.length) {
|
|
629
630
|
firstLine = String(cleanedBody[0]).trim();
|
|
630
|
-
if (
|
|
631
|
+
if (new RegExp('^' + symEsc + '\\s*\\/\\/\\s*as:(include|import)\\s+').test(firstLine)) cleanedBody[0] = firstLine.replace(new RegExp('^' + symEsc + '\\s*'), '');
|
|
631
632
|
}
|
|
632
633
|
}
|
|
633
634
|
|
|
@@ -88,7 +88,7 @@ class ClaudeProvider extends AiProvider {
|
|
|
88
88
|
- title_cn: Concise name (Chinese).
|
|
89
89
|
- summary_cn: Description (Chinese).
|
|
90
90
|
- summary_en: Description (English).
|
|
91
|
-
- trigger: Short shortcut (starts with
|
|
91
|
+
- trigger: Short shortcut (starts with @).
|
|
92
92
|
- category: One of the above.
|
|
93
93
|
- language: "swift" or "objectivec".
|
|
94
94
|
- tags: Array of tags.
|
|
@@ -128,7 +128,7 @@ class ClaudeProvider extends AiProvider {
|
|
|
128
128
|
- title: Concise name (English).
|
|
129
129
|
- summary_cn: Description in Chinese.
|
|
130
130
|
- summary_en: Description in English.
|
|
131
|
-
- trigger: Shortcut (starts with
|
|
131
|
+
- trigger: Shortcut (starts with @).
|
|
132
132
|
- category: [View, Service, Tool, Model, Network, Storage, UI, Utility].
|
|
133
133
|
- language: "swift" or "objectivec".
|
|
134
134
|
- code: Reusable usage example (with placeholders).
|
|
@@ -93,7 +93,7 @@ class GoogleGeminiProvider extends AiProvider {
|
|
|
93
93
|
- title_cn: Concise name (Chinese).
|
|
94
94
|
- summary_cn: Description (Chinese).
|
|
95
95
|
- summary_en: Description (English).
|
|
96
|
-
- trigger: Short shortcut (starts with
|
|
96
|
+
- trigger: Short shortcut (starts with @).
|
|
97
97
|
- category: One of the above.
|
|
98
98
|
- language: "swift" or "objectivec".
|
|
99
99
|
- tags: Array of tags.
|
|
@@ -139,7 +139,7 @@ class GoogleGeminiProvider extends AiProvider {
|
|
|
139
139
|
- title: Concise name (English).
|
|
140
140
|
- summary_cn: Description in Chinese.
|
|
141
141
|
- summary_en: Description in English.
|
|
142
|
-
- trigger: Shortcut (starts with
|
|
142
|
+
- trigger: Shortcut (starts with @).
|
|
143
143
|
- category: [View, Service, Tool, Model, Network, Storage, UI, Utility].
|
|
144
144
|
- language: "swift" or "objectivec".
|
|
145
145
|
- code: Reusable usage example (with placeholders).
|
|
@@ -21,7 +21,7 @@ class MockProvider extends AiProvider {
|
|
|
21
21
|
title_cn: '模拟代码片段标题',
|
|
22
22
|
summary_cn: '这是一个模拟的代码片段摘要。',
|
|
23
23
|
summary_en: 'This is a mock code snippet summary.',
|
|
24
|
-
trigger: '
|
|
24
|
+
trigger: '@mock',
|
|
25
25
|
category: 'Utility',
|
|
26
26
|
language: language,
|
|
27
27
|
tags: ['mock', 'test'],
|
|
@@ -36,7 +36,7 @@ class MockProvider extends AiProvider {
|
|
|
36
36
|
title: 'Mock Extracted Recipe',
|
|
37
37
|
summary_cn: '从文件中提取的模拟配方',
|
|
38
38
|
summary_en: 'Mock recipe extracted from files',
|
|
39
|
-
trigger: '
|
|
39
|
+
trigger: '@mock_skill',
|
|
40
40
|
category: 'Tool',
|
|
41
41
|
language: 'swift',
|
|
42
42
|
code: '// Mock code',
|
|
@@ -104,7 +104,7 @@ class OpenAiProvider extends AiProvider {
|
|
|
104
104
|
- title_cn: Concise name (Chinese).
|
|
105
105
|
- summary_cn: Description (Chinese).
|
|
106
106
|
- summary_en: Description (English).
|
|
107
|
-
- trigger: Short shortcut (starts with
|
|
107
|
+
- trigger: Short shortcut (starts with @).
|
|
108
108
|
- category: One of the above.
|
|
109
109
|
- language: "swift" or "objectivec".
|
|
110
110
|
- tags: Array of tags.
|
|
@@ -144,7 +144,7 @@ class OpenAiProvider extends AiProvider {
|
|
|
144
144
|
- title: Concise name (English).
|
|
145
145
|
- summary_cn: Description in Chinese.
|
|
146
146
|
- summary_en: Description in English.
|
|
147
|
-
- trigger: Shortcut (starts with
|
|
147
|
+
- trigger: Shortcut (starts with @).
|
|
148
148
|
- category: [View, Service, Tool, Model, Network, Storage, UI, Utility].
|
|
149
149
|
- language: "swift" or "objectivec".
|
|
150
150
|
- code: Reusable usage example (with placeholders).
|
package/lib/infra/cacheStore.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
+
const triggerSymbol = require('./triggerSymbol.js');
|
|
14
15
|
const paths = require('./paths.js');
|
|
15
16
|
|
|
16
17
|
const SpecCache = 'SpecCache_';
|
|
@@ -37,7 +38,7 @@ async function updateCache(specFile, content) {
|
|
|
37
38
|
|
|
38
39
|
cache.list.forEach(element => {
|
|
39
40
|
const trigger = element && element.trigger ? String(element.trigger) : '';
|
|
40
|
-
const rawKey =
|
|
41
|
+
const rawKey = triggerSymbol.stripTriggerPrefix(trigger);
|
|
41
42
|
if (element && rawKey) {
|
|
42
43
|
let key = rawKey;
|
|
43
44
|
keysCache.list.push(key);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Snippet 触发符配置:支持灵活更换默认符号(如 @)。
|
|
5
|
+
* - 默认使用 @,可通过环境变量 ASD_TRIGGER_SYMBOL 覆盖(单字符)。
|
|
6
|
+
* - 仅支持配置的触发符,统一由此模块提供,不做其它符号的差别处理。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const DEFAULT_SYMBOL = '@';
|
|
10
|
+
|
|
11
|
+
function fromEnv() {
|
|
12
|
+
const raw = process.env.ASD_TRIGGER_SYMBOL;
|
|
13
|
+
if (raw != null && String(raw).length === 1) return String(raw);
|
|
14
|
+
return DEFAULT_SYMBOL;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 当前触发符(可配置) */
|
|
18
|
+
const TRIGGER_SYMBOL = fromEnv();
|
|
19
|
+
|
|
20
|
+
/** 用于拆分的触发符集合(仅当前配置的符号) */
|
|
21
|
+
const TRIGGER_SYMBOLS = [TRIGGER_SYMBOL];
|
|
22
|
+
|
|
23
|
+
/** 用于按触发符拆分的正则(如 completion 拆 category) */
|
|
24
|
+
const TRIGGER_SPLIT_REGEX = new RegExp('[' + TRIGGER_SYMBOLS.map(escapeRegExp).join('') + ']');
|
|
25
|
+
|
|
26
|
+
function escapeRegExp(s) {
|
|
27
|
+
return s.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** str 是否以任一触发符开头 */
|
|
31
|
+
function hasTriggerPrefix(str) {
|
|
32
|
+
if (!str || typeof str !== 'string') return false;
|
|
33
|
+
const s = String(str).trim();
|
|
34
|
+
return TRIGGER_SYMBOLS.some((sym) => s.startsWith(sym));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 去掉 str 开头的连续触发符 */
|
|
38
|
+
function stripTriggerPrefix(str) {
|
|
39
|
+
if (!str || typeof str !== 'string') return String(str);
|
|
40
|
+
let s = String(str).trim();
|
|
41
|
+
while (s.length && TRIGGER_SYMBOLS.some((sym) => s.startsWith(sym))) {
|
|
42
|
+
s = s.slice(1).trimStart();
|
|
43
|
+
}
|
|
44
|
+
return s;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 若 str 不以任一触发符开头,则加上默认触发符 */
|
|
48
|
+
function ensureTriggerPrefix(str) {
|
|
49
|
+
if (!str || typeof str !== 'string') return str;
|
|
50
|
+
const s = String(str).trim();
|
|
51
|
+
if (!s) return s;
|
|
52
|
+
return hasTriggerPrefix(s) ? s : TRIGGER_SYMBOL + s;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 若 str 已带触发符则返回该符号,否则返回默认触发符(用于 category 等) */
|
|
56
|
+
function getPrefixFromTrigger(str) {
|
|
57
|
+
if (!str || typeof str !== 'string') return TRIGGER_SYMBOL;
|
|
58
|
+
const s = String(str).trim();
|
|
59
|
+
for (const sym of TRIGGER_SYMBOLS) {
|
|
60
|
+
if (s.startsWith(sym)) return sym;
|
|
61
|
+
}
|
|
62
|
+
return TRIGGER_SYMBOL;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
TRIGGER_SYMBOL,
|
|
67
|
+
TRIGGER_SYMBOLS,
|
|
68
|
+
TRIGGER_SPLIT_REGEX,
|
|
69
|
+
hasTriggerPrefix,
|
|
70
|
+
stripTriggerPrefix,
|
|
71
|
+
ensureTriggerPrefix,
|
|
72
|
+
getPrefixFromTrigger,
|
|
73
|
+
};
|
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
* - 提供“是否为指令行”的判断(用于移除标记行)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
const triggerSymbol = require('../infra/triggerSymbol.js');
|
|
10
11
|
const HEADER_MARK_INCLUDE = '// autosnippet:include ';
|
|
11
12
|
const HEADER_MARK_IMPORT = '// autosnippet:import ';
|
|
12
13
|
const HEADER_MARK_INCLUDE_SHORT = '// as:include ';
|
|
13
14
|
const HEADER_MARK_IMPORT_SHORT = '// as:import ';
|
|
14
15
|
|
|
15
16
|
function normalizeDirectiveLine(line) {
|
|
16
|
-
//
|
|
17
|
+
// 去掉前导触发符,便于匹配 as:include / as:import
|
|
17
18
|
const s = String(line || '').trim();
|
|
18
|
-
return
|
|
19
|
+
return triggerSymbol.stripTriggerPrefix(s);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
function parseDirectiveLine(line) {
|
|
@@ -23,6 +23,8 @@ function buildIdentifier(title, completionFirst, completionMoreStr) {
|
|
|
23
23
|
return 'AutoSnip_' + answersIdBuff.toString('base64').replace(/\//g, '');
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const triggerSymbol = require('../infra/triggerSymbol.js');
|
|
27
|
+
|
|
26
28
|
function fromText(answers, text, options = {}) {
|
|
27
29
|
if (!text || !String(text).trim()) return null;
|
|
28
30
|
|
|
@@ -33,7 +35,7 @@ function fromText(answers, text, options = {}) {
|
|
|
33
35
|
// 如果有 explicit category 且不在 completionMoreStr 中,则添加
|
|
34
36
|
let categoryPart = '';
|
|
35
37
|
if (answers.category) {
|
|
36
|
-
const cat = answers.category
|
|
38
|
+
const cat = triggerSymbol.hasTriggerPrefix(answers.category) ? answers.category : triggerSymbol.TRIGGER_SYMBOL + answers.category;
|
|
37
39
|
if (!completionMoreStr.includes(cat)) {
|
|
38
40
|
categoryPart = cat;
|
|
39
41
|
}
|
|
@@ -42,8 +44,7 @@ function fromText(answers, text, options = {}) {
|
|
|
42
44
|
const identifier = buildIdentifier(answers.title, answers.completion_first, completionMoreStr + categoryPart);
|
|
43
45
|
const isSwift = options && options.language === 'swift';
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
const prefix = '#';
|
|
47
|
+
const prefix = triggerSymbol.TRIGGER_SYMBOL;
|
|
47
48
|
const snippet = {
|
|
48
49
|
identifier: identifier,
|
|
49
50
|
title: answers.title,
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
|
+
const triggerSymbol = require('../infra/triggerSymbol.js');
|
|
15
16
|
const cacheStore = require('../infra/cacheStore.js');
|
|
16
17
|
const paths = require('../infra/paths.js');
|
|
17
18
|
const findPath = require('../../bin/findPath.js'); // Phase 1 先复用现有实现,后续再迁移
|
|
@@ -43,14 +44,11 @@ function applySpecDefaults(specObj, specFile) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function parseCategoriesFromCompletion(completion) {
|
|
46
|
-
// completion
|
|
47
|
+
// completion 形如:@key@View@Tool(按配置的触发符拆分)
|
|
47
48
|
const s = String(completion || '');
|
|
48
|
-
if (!s.includes(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const parts = s.split(/[@#]/).map(p => p.trim()).filter(Boolean);
|
|
52
|
-
|
|
53
|
-
// 去掉最后的 Moudle 标记(兼容旧版本)
|
|
49
|
+
if (!s.includes(triggerSymbol.TRIGGER_SYMBOL)) return [];
|
|
50
|
+
|
|
51
|
+
const parts = s.split(triggerSymbol.TRIGGER_SPLIT_REGEX).map(p => p.trim()).filter(Boolean);
|
|
54
52
|
return parts.filter(p => p !== 'Moudle');
|
|
55
53
|
}
|
|
56
54
|
|
|
@@ -64,7 +62,7 @@ function normalizeTrigger(raw) {
|
|
|
64
62
|
|
|
65
63
|
function rawKeyFromTrigger(trigger) {
|
|
66
64
|
const t = normalizeTrigger(trigger);
|
|
67
|
-
return
|
|
65
|
+
return triggerSymbol.stripTriggerPrefix(t);
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
/** 去除 title 中多余的 category 前缀(如 [Service]),避免与 category 字段重复 */
|
|
@@ -170,7 +168,7 @@ async function augmentSnippetForAi(snippet, specFile) {
|
|
|
170
168
|
// ✅ skill 视角(不改变运行时:仅增强语义)
|
|
171
169
|
const categories = parseCategoriesFromCompletion(completion);
|
|
172
170
|
const tags = categories
|
|
173
|
-
.map((c) =>
|
|
171
|
+
.map((c) => triggerSymbol.stripTriggerPrefix(c))
|
|
174
172
|
.filter((c) => c && c !== rawKeyFromTrigger(snippet.trigger));
|
|
175
173
|
|
|
176
174
|
snippet.skill = snippet.skill && typeof snippet.skill === 'object' ? snippet.skill : {};
|
package/lib/watch/fileWatcher.js
CHANGED
|
@@ -11,6 +11,7 @@ const path = require('path');
|
|
|
11
11
|
const open = require('open');
|
|
12
12
|
const injection = require('../injection/injectionService.js');
|
|
13
13
|
const cache = require('../infra/cacheStore.js');
|
|
14
|
+
const triggerSymbol = require('../infra/triggerSymbol.js');
|
|
14
15
|
|
|
15
16
|
const CMD_PATH = process.cwd();
|
|
16
17
|
|
|
@@ -23,8 +24,8 @@ const guardMarkShort = '// as:guard';
|
|
|
23
24
|
const searchMarkShort = '// as:search';
|
|
24
25
|
const searchMarkLong = '// autosnippet:search';
|
|
25
26
|
const alinkMark = 'alink';
|
|
26
|
-
const wellMark =
|
|
27
|
-
const atMark =
|
|
27
|
+
const wellMark = triggerSymbol.TRIGGER_SYMBOL;
|
|
28
|
+
const atMark = triggerSymbol.TRIGGER_SYMBOL;
|
|
28
29
|
|
|
29
30
|
// ObjC 头文件名常见包含 `+`(Category)、`-`、`.` 等字符
|
|
30
31
|
const headerReg = /^@?\/\/\s*(?:autosnippet|as):include\s+<([A-Za-z0-9_]+)\/([A-Za-z0-9_+.-]+\.h)>(\s+.+)?$/;
|
|
@@ -181,8 +182,7 @@ function processFileChange(specFile, updateFile, relativePath, options) {
|
|
|
181
182
|
const lineArray = data.split('\n');
|
|
182
183
|
lineArray.forEach(element => {
|
|
183
184
|
const lineVal = element.trim();
|
|
184
|
-
let normalizedLineVal =
|
|
185
|
-
if (normalizedLineVal.startsWith('#')) normalizedLineVal = normalizedLineVal.slice(1).trimStart();
|
|
185
|
+
let normalizedLineVal = triggerSymbol.stripTriggerPrefix(lineVal);
|
|
186
186
|
if (currImportReg.test(lineVal)) {
|
|
187
187
|
importArray.push(lineVal);
|
|
188
188
|
}
|
|
@@ -281,14 +281,18 @@ function checkAnotherFile(specFile, updateFile, headerLine, importArray, isSwift
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
function openLink(specFile, inputWord) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
284
|
+
const sym = triggerSymbol.TRIGGER_SYMBOL;
|
|
285
|
+
let completionKey = null;
|
|
286
|
+
if (inputWord.includes(sym)) {
|
|
287
|
+
const parts = inputWord.split(sym).map(p => p.trim()).filter(Boolean);
|
|
288
|
+
if (parts.length >= 2 && parts[parts.length - 1] === alinkMark) {
|
|
289
|
+
completionKey = parts[parts.length - 2];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (completionKey != null) {
|
|
293
|
+
cache.getLinkCache(specFile).then(function (linkCache) {
|
|
294
|
+
if (linkCache) {
|
|
295
|
+
let link = decodeURI(linkCache[completionKey]);
|
|
292
296
|
|
|
293
297
|
if (!link.startsWith('http')) {
|
|
294
298
|
const specSlashIndex = specFile.lastIndexOf('/');
|
|
@@ -301,7 +305,6 @@ function openLink(specFile, inputWord) {
|
|
|
301
305
|
}
|
|
302
306
|
}
|
|
303
307
|
});
|
|
304
|
-
}
|
|
305
308
|
}
|
|
306
309
|
}
|
|
307
310
|
|