autosnippet 3.0.13 → 3.1.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.
Files changed (100) hide show
  1. package/bin/api-server.js +2 -0
  2. package/bin/cli.js +24 -19
  3. package/config/default.json +1 -1
  4. package/lib/bootstrap.js +4 -4
  5. package/lib/cli/SetupService.js +29 -29
  6. package/lib/cli/UpgradeService.js +3 -2
  7. package/lib/core/AstAnalyzer.js +1 -1
  8. package/lib/core/ast/ensure-grammars.js +1 -1
  9. package/lib/core/ast/index.js +62 -11
  10. package/lib/core/ast/lang-dart.js +27 -21
  11. package/lib/core/ast/lang-go.js +6 -20
  12. package/lib/core/ast/lang-rust.js +53 -28
  13. package/lib/core/ast/parser-init.js +9 -5
  14. package/lib/core/discovery/DartDiscoverer.js +4 -10
  15. package/lib/core/discovery/GoDiscoverer.js +45 -25
  16. package/lib/core/discovery/NodeDiscoverer.js +1 -3
  17. package/lib/core/discovery/PythonDiscoverer.js +7 -1
  18. package/lib/core/discovery/RustDiscoverer.js +111 -38
  19. package/lib/core/discovery/index.js +2 -2
  20. package/lib/core/enhancement/django-enhancement.js +10 -4
  21. package/lib/core/enhancement/fastapi-enhancement.js +16 -9
  22. package/lib/core/enhancement/go-grpc-enhancement.js +2 -1
  23. package/lib/core/enhancement/go-web-enhancement.js +3 -6
  24. package/lib/core/enhancement/ml-enhancement.js +6 -3
  25. package/lib/core/enhancement/nextjs-enhancement.js +17 -7
  26. package/lib/core/enhancement/node-server-enhancement.js +4 -2
  27. package/lib/core/enhancement/react-enhancement.js +6 -3
  28. package/lib/core/enhancement/rust-tokio-enhancement.js +6 -2
  29. package/lib/core/enhancement/rust-web-enhancement.js +13 -7
  30. package/lib/core/enhancement/vue-enhancement.js +10 -5
  31. package/lib/external/ai/AiFactory.js +3 -1
  32. package/lib/external/ai/AiProvider.js +3 -1
  33. package/lib/external/mcp/McpServer.js +2 -0
  34. package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +1 -2
  35. package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +7 -1
  36. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +55 -26
  37. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +8 -8
  38. package/lib/external/mcp/handlers/bootstrap/refine.js +3 -1
  39. package/lib/external/mcp/handlers/bootstrap.js +4 -10
  40. package/lib/external/mcp/handlers/browse.js +6 -2
  41. package/lib/external/mcp/handlers/guard.js +6 -2
  42. package/lib/external/mcp/handlers/skill.js +6 -2
  43. package/lib/http/HttpServer.js +1 -1
  44. package/lib/http/routes/candidates.js +3 -1
  45. package/lib/http/routes/extract.js +4 -5
  46. package/lib/http/routes/guardRules.js +1 -1
  47. package/lib/http/routes/modules.js +9 -3
  48. package/lib/http/routes/skills.js +54 -6
  49. package/lib/http/routes/violations.js +4 -3
  50. package/lib/infrastructure/external/ClipboardManager.js +24 -7
  51. package/lib/infrastructure/external/NativeUi.js +3 -1
  52. package/lib/infrastructure/external/OpenBrowser.js +1 -0
  53. package/lib/infrastructure/external/XcodeAutomation.js +5 -5
  54. package/lib/infrastructure/vector/IndexingPipeline.js +14 -5
  55. package/lib/injection/ServiceContainer.js +34 -11
  56. package/lib/platform/ios/index.js +20 -25
  57. package/lib/platform/ios/routes/spm.js +6 -3
  58. package/lib/platform/ios/snippet/PlaceholderConverter.js +6 -2
  59. package/lib/platform/ios/snippet/XcodeCodec.js +4 -2
  60. package/lib/platform/ios/spm/SpmDiscoverer.js +1 -1
  61. package/lib/platform/ios/spm/SpmService.js +3 -1
  62. package/lib/platform/ios/xcode/XcodeIntegration.js +10 -12
  63. package/lib/platform/ios/xcode/XcodeWriteUtils.js +6 -1
  64. package/lib/service/automation/FileWatcher.js +1 -3
  65. package/lib/service/automation/handlers/CreateHandler.js +3 -5
  66. package/lib/service/automation/handlers/GuardHandler.js +11 -32
  67. package/lib/service/automation/handlers/SearchHandler.js +9 -9
  68. package/lib/service/chat/CandidateGuardrail.js +11 -6
  69. package/lib/service/chat/ChatAgent.js +31 -22
  70. package/lib/service/chat/HandoffProtocol.js +5 -2
  71. package/lib/service/chat/tools/composite.js +3 -2
  72. package/lib/service/chat/tools/index.js +60 -71
  73. package/lib/service/chat/tools/infrastructure.js +9 -4
  74. package/lib/service/chat/tools/lifecycle.js +22 -5
  75. package/lib/service/chat/tools/project-access.js +5 -9
  76. package/lib/service/chat/tools.js +1 -2
  77. package/lib/service/cursor/AgentInstructionsGenerator.js +33 -15
  78. package/lib/service/cursor/CursorDeliveryPipeline.js +2 -1
  79. package/lib/service/cursor/KnowledgeCompressor.js +16 -7
  80. package/lib/service/guard/ComplianceReporter.js +5 -2
  81. package/lib/service/guard/GuardCheckEngine.js +53 -26
  82. package/lib/service/guard/GuardCodeChecks.js +217 -188
  83. package/lib/service/guard/GuardCrossFileChecks.js +203 -184
  84. package/lib/service/guard/GuardPatternUtils.js +17 -10
  85. package/lib/service/module/ModuleService.js +180 -56
  86. package/lib/service/recipe/RecipeCandidateValidator.js +11 -8
  87. package/lib/service/snippet/SnippetFactory.js +3 -3
  88. package/lib/service/snippet/SnippetInstaller.js +35 -11
  89. package/lib/service/snippet/codecs/VSCodeCodec.js +2 -2
  90. package/lib/service/wiki/WikiGenerator.js +54 -36
  91. package/lib/service/wiki/WikiRenderers.js +105 -80
  92. package/lib/service/wiki/WikiUtils.js +217 -80
  93. package/lib/shared/LanguageService.js +111 -53
  94. package/lib/shared/PathGuard.js +0 -8
  95. package/package.json +3 -9
  96. package/scripts/bench-real-projects.mjs +29 -29
  97. package/scripts/generate-recipe-drafts.js +17 -27
  98. package/scripts/init-snippets.js +43 -24
  99. package/scripts/install-vscode-copilot.js +3 -19
  100. package/scripts/setup-mcp-config.js +0 -4
@@ -25,7 +25,15 @@ router.get(
25
25
  '/',
26
26
  asyncHandler(async (_req, res) => {
27
27
  const raw = listSkills();
28
- const parsed = JSON.parse(raw);
28
+ let parsed;
29
+ try {
30
+ parsed = JSON.parse(raw);
31
+ } catch {
32
+ return res.status(500).json({
33
+ success: false,
34
+ error: { code: 'PARSE_ERROR', message: 'Invalid response from listSkills' },
35
+ });
36
+ }
29
37
 
30
38
  if (!parsed.success) {
31
39
  return res.status(500).json(parsed);
@@ -68,7 +76,15 @@ router.get(
68
76
  asyncHandler(async (req, res) => {
69
77
  const ctx = { container: req.app.locals?.container || null };
70
78
  const raw = await suggestSkills(ctx);
71
- const parsed = JSON.parse(raw);
79
+ let parsed;
80
+ try {
81
+ parsed = JSON.parse(raw);
82
+ } catch {
83
+ return res.status(500).json({
84
+ success: false,
85
+ error: { code: 'PARSE_ERROR', message: 'Invalid response from suggestSkills' },
86
+ });
87
+ }
72
88
 
73
89
  if (!parsed.success) {
74
90
  return res.status(500).json(parsed);
@@ -90,7 +106,15 @@ router.get(
90
106
  const { section } = req.query;
91
107
 
92
108
  const raw = loadSkill(null, { skillName: name, section });
93
- const parsed = JSON.parse(raw);
109
+ let parsed;
110
+ try {
111
+ parsed = JSON.parse(raw);
112
+ } catch {
113
+ return res.status(500).json({
114
+ success: false,
115
+ error: { code: 'PARSE_ERROR', message: 'Invalid response from loadSkill' },
116
+ });
117
+ }
94
118
 
95
119
  if (!parsed.success) {
96
120
  const status = parsed.error?.code === 'SKILL_NOT_FOUND' ? 404 : 400;
@@ -122,7 +146,15 @@ router.post(
122
146
  overwrite,
123
147
  createdBy: createdBy || 'manual',
124
148
  });
125
- const parsed = JSON.parse(raw);
149
+ let parsed;
150
+ try {
151
+ parsed = JSON.parse(raw);
152
+ } catch {
153
+ return res.status(500).json({
154
+ success: false,
155
+ error: { code: 'PARSE_ERROR', message: 'Invalid response from createSkill' },
156
+ });
157
+ }
126
158
 
127
159
  if (!parsed.success) {
128
160
  const status =
@@ -155,7 +187,15 @@ router.put(
155
187
  }
156
188
 
157
189
  const raw = updateSkill(null, { name, description, content });
158
- const parsed = JSON.parse(raw);
190
+ let parsed;
191
+ try {
192
+ parsed = JSON.parse(raw);
193
+ } catch {
194
+ return res.status(500).json({
195
+ success: false,
196
+ error: { code: 'PARSE_ERROR', message: 'Invalid response from updateSkill' },
197
+ });
198
+ }
159
199
 
160
200
  if (!parsed.success) {
161
201
  const status =
@@ -181,7 +221,15 @@ router.delete(
181
221
  const { name } = req.params;
182
222
 
183
223
  const raw = deleteSkill(null, { name });
184
- const parsed = JSON.parse(raw);
224
+ let parsed;
225
+ try {
226
+ parsed = JSON.parse(raw);
227
+ } catch {
228
+ return res.status(500).json({
229
+ success: false,
230
+ error: { code: 'PARSE_ERROR', message: 'Invalid response from deleteSkill' },
231
+ });
232
+ }
185
233
 
186
234
  if (!parsed.success) {
187
235
  const status =
@@ -126,9 +126,10 @@ router.post(
126
126
  message: String(rule.description || rule.message || '').trim(),
127
127
  severity: rule.severity === 'error' ? 'error' : 'warning',
128
128
  pattern: String(rule.pattern || '').trim(),
129
- languages: Array.isArray(rule.languages) && rule.languages.length > 0
130
- ? rule.languages
131
- : ['objc', 'swift'],
129
+ languages:
130
+ Array.isArray(rule.languages) && rule.languages.length > 0
131
+ ? rule.languages
132
+ : ['objc', 'swift'],
132
133
  note: rule.note != null ? String(rule.note).trim() : rule.description_cn || '',
133
134
  dimension: ['file', 'target', 'project'].includes(rule.dimension) ? rule.dimension : '',
134
135
  };
@@ -26,17 +26,23 @@ function _linuxBackend() {
26
26
  try {
27
27
  execSync('which wl-copy', { stdio: 'ignore', timeout: TIMEOUT });
28
28
  return 'wl';
29
- } catch { /* fallthrough */ }
29
+ } catch {
30
+ /* fallthrough */
31
+ }
30
32
  }
31
33
  // X11
32
34
  try {
33
35
  execSync('which xclip', { stdio: 'ignore', timeout: TIMEOUT });
34
36
  return 'xclip';
35
- } catch { /* fallthrough */ }
37
+ } catch {
38
+ /* fallthrough */
39
+ }
36
40
  try {
37
41
  execSync('which xsel', { stdio: 'ignore', timeout: TIMEOUT });
38
42
  return 'xsel';
39
- } catch { /* fallthrough */ }
43
+ } catch {
44
+ /* fallthrough */
45
+ }
40
46
  return null;
41
47
  }
42
48
 
@@ -60,7 +66,8 @@ export function read() {
60
66
  }
61
67
  if (PLATFORM === 'win32') {
62
68
  return execSync('powershell.exe -NoProfile -Command Get-Clipboard', {
63
- encoding: 'utf8', timeout: TIMEOUT,
69
+ encoding: 'utf8',
70
+ timeout: TIMEOUT,
64
71
  }).replace(/\r?\n$/, '');
65
72
  }
66
73
  // Linux
@@ -93,7 +100,9 @@ export function write(text) {
93
100
  }
94
101
  if (PLATFORM === 'win32') {
95
102
  execSync('powershell.exe -NoProfile -Command "$input | Set-Clipboard"', {
96
- input: text, timeout: TIMEOUT, stdio: ['pipe', 'ignore', 'ignore'],
103
+ input: text,
104
+ timeout: TIMEOUT,
105
+ stdio: ['pipe', 'ignore', 'ignore'],
97
106
  });
98
107
  return true;
99
108
  }
@@ -104,11 +113,19 @@ export function write(text) {
104
113
  return true;
105
114
  }
106
115
  if (backend === 'xclip') {
107
- execSync('xclip -selection clipboard', { input: text, timeout: TIMEOUT, stdio: ['pipe', 'ignore', 'ignore'] });
116
+ execSync('xclip -selection clipboard', {
117
+ input: text,
118
+ timeout: TIMEOUT,
119
+ stdio: ['pipe', 'ignore', 'ignore'],
120
+ });
108
121
  return true;
109
122
  }
110
123
  if (backend === 'xsel') {
111
- execSync('xsel --clipboard --input', { input: text, timeout: TIMEOUT, stdio: ['pipe', 'ignore', 'ignore'] });
124
+ execSync('xsel --clipboard --input', {
125
+ input: text,
126
+ timeout: TIMEOUT,
127
+ stdio: ['pipe', 'ignore', 'ignore'],
128
+ });
112
129
  return true;
113
130
  }
114
131
  return false;
@@ -212,7 +212,9 @@ export function notify(message, title = 'AutoSnippet') {
212
212
  stdio: 'ignore',
213
213
  });
214
214
  }
215
- } catch {}
215
+ } catch {
216
+ /* Windows toast notification is best-effort */
217
+ }
216
218
  }
217
219
 
218
220
  /**
@@ -110,6 +110,7 @@ export function openBrowserReuseTab(url, baseUrlForLookup) {
110
110
  return;
111
111
  } catch (_err) {
112
112
  if (process.env.ASD_DEBUG === '1') {
113
+ console.error(`[OpenBrowser] AppleScript failed for ${browser}:`, _err.message);
113
114
  }
114
115
  }
115
116
  }
@@ -3,13 +3,13 @@
3
3
  * This re-export shim maintains backward compatibility.
4
4
  */
5
5
  export {
6
- isXcodeRunning,
7
- isXcodeFrontmost,
8
- jumpToLineInXcode,
9
6
  cutLineInXcode,
10
7
  deleteLineContentInXcode,
11
- pasteInXcode,
12
- selectAndPasteInXcode,
13
8
  insertAtLineStartInXcode,
9
+ isXcodeFrontmost,
10
+ isXcodeRunning,
11
+ jumpToLineInXcode,
12
+ pasteInXcode,
14
13
  saveActiveDocumentInXcode,
14
+ selectAndPasteInXcode,
15
15
  } from '../../platform/ios/xcode/XcodeAutomation.js';
@@ -10,13 +10,22 @@ import { LanguageService } from '../../shared/LanguageService.js';
10
10
  import { chunk } from './Chunker.js';
11
11
 
12
12
  const SCANNABLE_EXTENSIONS = new Set([
13
- '.md', '.markdown', '.txt',
14
- '.swift', '.m', '.h',
15
- '.js', '.ts', '.jsx', '.tsx',
13
+ '.md',
14
+ '.markdown',
15
+ '.txt',
16
+ '.swift',
17
+ '.m',
18
+ '.h',
19
+ '.js',
20
+ '.ts',
21
+ '.jsx',
22
+ '.tsx',
16
23
  '.py',
17
- '.java', '.kt',
24
+ '.java',
25
+ '.kt',
18
26
  '.go',
19
- '.rs', '.rb',
27
+ '.rs',
28
+ '.rb',
20
29
  ]);
21
30
 
22
31
  export class IndexingPipeline {
@@ -17,6 +17,8 @@ import { getRealtimeService as _getRealtimeService } from '../infrastructure/rea
17
17
  import { IndexingPipeline } from '../infrastructure/vector/IndexingPipeline.js';
18
18
  // ─── P0: Vector Storage ──────────────────────────────
19
19
  import { JsonVectorAdapter } from '../infrastructure/vector/JsonVectorAdapter.js';
20
+ // ─── P2: SPM ──────────────────────────────────────────
21
+ import { SpmService } from '../platform/ios/spm/SpmService.js';
20
22
  import { KnowledgeRepositoryImpl } from '../repository/knowledge/KnowledgeRepository.impl.js';
21
23
  // ─── P1: Token Usage Tracking ─────────────────────────
22
24
  import { TokenUsageStore } from '../repository/token/TokenUsageStore.js';
@@ -44,6 +46,8 @@ import { ConfidenceRouter } from '../service/knowledge/ConfidenceRouter.js';
44
46
  import { KnowledgeFileWriter } from '../service/knowledge/KnowledgeFileWriter.js';
45
47
  import { KnowledgeGraphService } from '../service/knowledge/KnowledgeGraphService.js';
46
48
  import { KnowledgeService } from '../service/knowledge/KnowledgeService.js';
49
+ // ─── v3.2: ModuleService (统一多语言模块扫描) ──────────
50
+ import { ModuleService } from '../service/module/ModuleService.js';
47
51
  import { FeedbackCollector } from '../service/quality/FeedbackCollector.js';
48
52
  // ─── P2: Quality ──────────────────────────────────────
49
53
  import { QualityScorer } from '../service/quality/QualityScorer.js';
@@ -54,14 +58,10 @@ import { RecipeParser } from '../service/recipe/RecipeParser.js';
54
58
  import { RetrievalFunnel } from '../service/search/RetrievalFunnel.js';
55
59
  import { SearchEngine } from '../service/search/SearchEngine.js';
56
60
  import { SkillHooks } from '../service/skills/SkillHooks.js';
61
+ import { VSCodeCodec } from '../service/snippet/codecs/VSCodeCodec.js';
62
+ import { XcodeCodec } from '../service/snippet/codecs/XcodeCodec.js';
57
63
  import { SnippetFactory } from '../service/snippet/SnippetFactory.js';
58
64
  import { SnippetInstaller } from '../service/snippet/SnippetInstaller.js';
59
- import { XcodeCodec } from '../service/snippet/codecs/XcodeCodec.js';
60
- import { VSCodeCodec } from '../service/snippet/codecs/VSCodeCodec.js';
61
- // ─── P2: SPM ──────────────────────────────────────────
62
- import { SpmService } from '../platform/ios/spm/SpmService.js';
63
- // ─── v3.2: ModuleService (统一多语言模块扫描) ──────────
64
- import { ModuleService } from '../service/module/ModuleService.js';
65
65
  import { DimensionCopy } from '../shared/DimensionCopyRegistry.js';
66
66
  import { LanguageService } from '../shared/LanguageService.js';
67
67
 
@@ -107,6 +107,14 @@ export class ServiceContainer {
107
107
  this.singletons._projectRoot = bootstrapComponents.projectRoot;
108
108
  }
109
109
 
110
+ if (bootstrapComponents.config) {
111
+ this.singletons._config = bootstrapComponents.config;
112
+ }
113
+
114
+ if (bootstrapComponents.skillHooks) {
115
+ this.singletons.skillHooks = bootstrapComponents.skillHooks;
116
+ }
117
+
110
118
  // AiFactory 模块引用(用于 SpmService AI 扫描)
111
119
  try {
112
120
  this.singletons._aiFactory = await import('../external/ai/AiFactory.js');
@@ -222,10 +230,14 @@ export class ServiceContainer {
222
230
  this.logger.info('Embedding fallback provider re-created', { provider: fb });
223
231
  break;
224
232
  }
225
- } catch { /* skip */ }
233
+ } catch {
234
+ /* skip */
235
+ }
226
236
  }
227
237
  }
228
- } catch { /* no embed fallback available */ }
238
+ } catch {
239
+ /* no embed fallback available */
240
+ }
229
241
  }
230
242
 
231
243
  // 清除持有旧 aiProvider 引用的 singleton 缓存
@@ -393,7 +405,9 @@ export class ServiceContainer {
393
405
  let guardCheckEngine = null;
394
406
  try {
395
407
  guardCheckEngine = this.get('guardCheckEngine');
396
- } catch { /* engine not yet available */ }
408
+ } catch {
409
+ /* engine not yet available */
410
+ }
397
411
  this.singletons.guardService = new GuardService(knowledgeRepository, auditLogger, gateway, {
398
412
  guardCheckEngine,
399
413
  });
@@ -538,7 +552,10 @@ export class ServiceContainer {
538
552
  if (!this.singletons.vscodeSnippetInstaller) {
539
553
  const factory = this.get('snippetFactory');
540
554
  const codec = factory.getCodec('vscode');
541
- this.singletons.vscodeSnippetInstaller = new SnippetInstaller({ codec, snippetFactory: factory });
555
+ this.singletons.vscodeSnippetInstaller = new SnippetInstaller({
556
+ codec,
557
+ snippetFactory: factory,
558
+ });
542
559
  }
543
560
  return this.singletons.vscodeSnippetInstaller;
544
561
  });
@@ -716,9 +733,15 @@ export class ServiceContainer {
716
733
  });
717
734
 
718
735
  // SkillHooks (Skill 生命周期钩子 — 加载 skills/*/hooks.js)
736
+ // 注意: 优先使用 bootstrap 注入的已加载实例,避免创建未 .load() 的空实例
719
737
  this.register('skillHooks', () => {
720
738
  if (!this.singletons.skillHooks) {
721
- this.singletons.skillHooks = new SkillHooks();
739
+ const hooks = new SkillHooks();
740
+ // 同步返回空实例;异步 load 在后台执行
741
+ hooks.load().catch(() => {
742
+ /* skill hooks load is best-effort */
743
+ });
744
+ this.singletons.skillHooks = hooks;
722
745
  }
723
746
  return this.singletons.skillHooks;
724
747
  });
@@ -26,38 +26,33 @@
26
26
  * 旧路径保留了 re-export shim 确保向后兼容。
27
27
  */
28
28
 
29
+ // ── SPM Legacy Routes ──
30
+ export { default as spmRouter } from './routes/spm.js';
31
+ export { PlaceholderConverter } from './snippet/PlaceholderConverter.js';
32
+ // ── Xcode Snippet 编解码 ──
33
+ export { XcodeCodec } from './snippet/XcodeCodec.js';
34
+ export { DependencyGraph } from './spm/DependencyGraph.js';
35
+ export { PackageSwiftParser } from './spm/PackageSwiftParser.js';
36
+ export { PolicyEngine } from './spm/PolicyEngine.js';
37
+ export { SpmDiscoverer } from './spm/SpmDiscoverer.js';
38
+ // ── Swift Package Manager ──
39
+ export { SpmService } from './spm/SpmService.js';
40
+ export { saveEventFilter } from './xcode/SaveEventFilter.js';
29
41
  // ── Xcode IDE 自动化 ──
30
42
  export {
31
- isXcodeRunning,
32
- isXcodeFrontmost,
33
- jumpToLineInXcode,
34
43
  cutLineInXcode,
35
44
  deleteLineContentInXcode,
36
- pasteInXcode,
37
- selectAndPasteInXcode,
38
45
  insertAtLineStartInXcode,
46
+ isXcodeFrontmost,
47
+ isXcodeRunning,
48
+ jumpToLineInXcode,
49
+ pasteInXcode,
39
50
  saveActiveDocumentInXcode,
51
+ selectAndPasteInXcode,
40
52
  } from './xcode/XcodeAutomation.js';
41
-
42
53
  export {
43
- insertHeaders,
44
- insertCodeToXcode,
45
- findTriggerLineNumber,
46
54
  findImportInsertLine,
55
+ findTriggerLineNumber,
56
+ insertCodeToXcode,
57
+ insertHeaders,
47
58
  } from './xcode/XcodeIntegration.js';
48
-
49
- export { saveEventFilter } from './xcode/SaveEventFilter.js';
50
-
51
- // ── Xcode Snippet 编解码 ──
52
- export { XcodeCodec } from './snippet/XcodeCodec.js';
53
- export { PlaceholderConverter } from './snippet/PlaceholderConverter.js';
54
-
55
- // ── Swift Package Manager ──
56
- export { SpmService } from './spm/SpmService.js';
57
- export { SpmDiscoverer } from './spm/SpmDiscoverer.js';
58
- export { PackageSwiftParser } from './spm/PackageSwiftParser.js';
59
- export { DependencyGraph } from './spm/DependencyGraph.js';
60
- export { PolicyEngine } from './spm/PolicyEngine.js';
61
-
62
- // ── SPM Legacy Routes ──
63
- export { default as spmRouter } from './routes/spm.js';
@@ -7,11 +7,11 @@
7
7
  */
8
8
 
9
9
  import express from 'express';
10
+ import { asyncHandler } from '../../../http/middleware/errorHandler.js';
11
+ import { createStreamSession, getStreamSession } from '../../../http/utils/sse-sessions.js';
10
12
  import Logger from '../../../infrastructure/logging/Logger.js';
11
13
  import { getServiceContainer } from '../../../injection/ServiceContainer.js';
12
14
  import { ValidationError } from '../../../shared/errors/index.js';
13
- import { asyncHandler } from '../../../http/middleware/errorHandler.js';
14
- import { createStreamSession, getStreamSession } from '../../../http/utils/sse-sessions.js';
15
15
 
16
16
  const router = express.Router();
17
17
  const logger = Logger.getInstance();
@@ -241,7 +241,10 @@ router.post(
241
241
  // 异步执行扫描,通过 session 推送进度事件
242
242
  setImmediate(async () => {
243
243
  try {
244
- logger.info('Module stream scan started via /spm/', { target: tName, sessionId: session.sessionId });
244
+ logger.info('Module stream scan started via /spm/', {
245
+ target: tName,
246
+ sessionId: session.sessionId,
247
+ });
245
248
  const result = await moduleService.scanTarget(resolvedTarget, {
246
249
  ...options,
247
250
  onProgress(event) {
@@ -20,7 +20,9 @@ export class PlaceholderConverter {
20
20
  * @returns {string}
21
21
  */
22
22
  static xcodeToVSCode(code) {
23
- if (!code) return '';
23
+ if (!code) {
24
+ return '';
25
+ }
24
26
  let index = 0;
25
27
  return code.replace(/<#(.*?)#>/g, (_match, inner) => {
26
28
  index++;
@@ -39,7 +41,9 @@ export class PlaceholderConverter {
39
41
  * @returns {string}
40
42
  */
41
43
  static vscodeToXcode(code) {
42
- if (!code) return '';
44
+ if (!code) {
45
+ return '';
46
+ }
43
47
  return (
44
48
  code
45
49
  // ${1:name} → <#name#>
@@ -7,8 +7,8 @@
7
7
  * - XML 转义
8
8
  */
9
9
 
10
- import { join } from 'node:path';
11
10
  import { homedir } from 'node:os';
11
+ import { join } from 'node:path';
12
12
  import { SnippetCodec } from '../../../service/snippet/codecs/SnippetCodec.js';
13
13
 
14
14
  const XCODE_LANGUAGE_MAP = {
@@ -102,7 +102,9 @@ export class XcodeCodec extends SnippetCodec {
102
102
 
103
103
  /** XML 特殊字符转义 */
104
104
  function escapeXml(str) {
105
- if (!str) return '';
105
+ if (!str) {
106
+ return '';
107
+ }
106
108
  return String(str)
107
109
  .replace(/&/g, '&amp;')
108
110
  .replace(/</g, '&lt;')
@@ -7,8 +7,8 @@
7
7
 
8
8
  import { existsSync, readdirSync } from 'node:fs';
9
9
  import { basename, join, relative } from 'node:path';
10
- import { LanguageService } from '../../../shared/LanguageService.js';
11
10
  import { ProjectDiscoverer } from '../../../core/discovery/ProjectDiscoverer.js';
11
+ import { LanguageService } from '../../../shared/LanguageService.js';
12
12
 
13
13
  export class SpmDiscoverer extends ProjectDiscoverer {
14
14
  /** @type {import('./SpmService.js').SpmService|null} */
@@ -1002,7 +1002,9 @@ export class SpmService {
1002
1002
  // 加载语言参考 Skill 注入 AI 提取 prompt
1003
1003
  let extractOpts = {};
1004
1004
  try {
1005
- const { loadBootstrapSkills } = await import('../../../external/mcp/handlers/bootstrap.js');
1005
+ const { loadBootstrapSkills } = await import(
1006
+ '../../../external/mcp/handlers/bootstrap.js'
1007
+ );
1006
1008
  const langProfile = ai._detectLanguageProfile?.(files);
1007
1009
  const primaryLang = langProfile?.primaryLanguage;
1008
1010
  if (primaryLang) {
@@ -17,21 +17,21 @@ import { readFileSync, writeFileSync } from 'node:fs';
17
17
  import { saveEventFilter } from './SaveEventFilter.js';
18
18
 
19
19
  import {
20
- resolveHeaderFormat,
20
+ checkImportStatus,
21
21
  collectImportsFromFile,
22
22
  collectImportsFromHeaderFile,
23
- checkImportStatus,
24
23
  inferModulesFromHeaders,
24
+ resolveHeaderFormat,
25
25
  } from './XcodeImportResolver.js';
26
26
 
27
27
  import {
28
- sleep,
29
- withAutoSnippetNote,
28
+ computePasteLineNumber,
30
29
  evaluateDepResult,
31
30
  handleDepReview,
32
- writeImportLineXcode,
31
+ sleep,
32
+ withAutoSnippetNote,
33
33
  writeImportLineFile,
34
- computePasteLineNumber,
34
+ writeImportLineXcode,
35
35
  } from './XcodeWriteUtils.js';
36
36
 
37
37
  /** 常见 Apple 系统框架(无需 SPM 依赖检查) */
@@ -401,12 +401,10 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
401
401
  // ── Step 6: 计算偏移后的粘贴行号 ──
402
402
  // 使用实际插入的 header 数量计算偏移,而非期望数量
403
403
  // 当 headers 全部重复被跳过时,headerInsertCount = 0,不应偏移
404
- const pasteLineNumber = computePasteLineNumber(
405
- triggerLineNumber,
406
- headerInsertCount,
407
- fullPath,
408
- { forceOffset: headerInsertCount > 0, expectedHeaderCount: headerInsertCount }
409
- );
404
+ const pasteLineNumber = computePasteLineNumber(triggerLineNumber, headerInsertCount, fullPath, {
405
+ forceOffset: headerInsertCount > 0,
406
+ expectedHeaderCount: headerInsertCount,
407
+ });
410
408
 
411
409
  // 如果 headers 通过文件写入,等待 Xcode reload
412
410
  if (headerInsertCount > 0) {
@@ -203,7 +203,12 @@ export function getLastImportLine(filePath) {
203
203
  *
204
204
  * 如果 headers 插入在 trigger 行之前(import 区),trigger 行号需要向下偏移。
205
205
  */
206
- export function computePasteLineNumber(triggerLineNumber, headerInsertCount, filePath, options = {}) {
206
+ export function computePasteLineNumber(
207
+ triggerLineNumber,
208
+ headerInsertCount,
209
+ filePath,
210
+ options = {}
211
+ ) {
207
212
  const expectedCount = Number.isFinite(options.expectedHeaderCount)
208
213
  ? options.expectedHeaderCount
209
214
  : headerInsertCount;
@@ -12,17 +12,16 @@
12
12
  import { accessSync, readFileSync, statSync } from 'node:fs';
13
13
  import { basename, join, normalize } from 'node:path';
14
14
  import { watch as chokidarWatch } from 'chokidar';
15
+ import { saveEventFilter } from '../../platform/ios/xcode/SaveEventFilter.js';
15
16
  import { FILE_WATCHER } from '../../shared/constants.js';
16
17
  import { detectTriggers, REGEX } from './DirectiveDetector.js';
17
18
  import { handleAlink } from './handlers/AlinkHandler.js';
18
-
19
19
  /* ── Handler imports ── */
20
20
  import { handleCreate } from './handlers/CreateHandler.js';
21
21
  import { handleDraft } from './handlers/DraftHandler.js';
22
22
  import { handleGuard } from './handlers/GuardHandler.js';
23
23
  import { handleHeader } from './handlers/HeaderHandler.js';
24
24
  import { handleSearch } from './handlers/SearchHandler.js';
25
- import { saveEventFilter } from '../../platform/ios/xcode/SaveEventFilter.js';
26
25
 
27
26
  /* ────────── 配置 ────────── */
28
27
 
@@ -118,7 +117,6 @@ export class FileWatcher {
118
117
  : DEFAULT_FILE_PATTERN;
119
118
 
120
119
  if (!this.quiet) {
121
- console.log('\n👁️ 文件监听已启动 — Xcode 模式');
122
120
  }
123
121
 
124
122
  this._watcher = chokidarWatch(filePattern, {
@@ -4,9 +4,9 @@
4
4
  */
5
5
 
6
6
  import { readFileSync, writeFileSync } from 'node:fs';
7
+ import { saveEventFilter } from '../../../platform/ios/xcode/SaveEventFilter.js';
7
8
  import { LanguageService } from '../../../shared/LanguageService.js';
8
9
  import { REGEX } from '../DirectiveDetector.js';
9
- import { saveEventFilter } from '../../../platform/ios/xcode/SaveEventFilter.js';
10
10
 
11
11
  /**
12
12
  * 处理 // as:c 指令
@@ -121,11 +121,9 @@ async function silentCreateCandidate(watcher, text, relativePath) {
121
121
 
122
122
  // -c 模式:剪贴板内容整体作为一条候选(不拆分)
123
123
  // 先用 AI 生成标题和摘要,code 保持剪贴板原文
124
- const lang = relativePath ? (LanguageService.inferLang(relativePath) || 'unknown') : 'unknown';
124
+ const lang = relativePath ? LanguageService.inferLang(relativePath) || 'unknown' : 'unknown';
125
125
  const ext = LanguageService.extForLang(lang) || '.txt';
126
- const fileName = relativePath
127
- ? relativePath.split('/').pop()
128
- : `clipboard${ext}`;
126
+ const fileName = relativePath ? relativePath.split('/').pop() : `clipboard${ext}`;
129
127
 
130
128
  let title = fileName.replace(/\.\w+$/, '') || 'Clipboard Snippet';
131
129
  let summary = '';