autosnippet 3.0.11 → 3.0.13

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 (56) hide show
  1. package/bin/cli.js +64 -1
  2. package/config/default.json +9 -0
  3. package/dashboard/dist/assets/{index-I2ySoCmF.js → index-Bnm26ulL.js} +47 -47
  4. package/dashboard/dist/index.html +1 -1
  5. package/lib/cli/SetupService.js +92 -5
  6. package/lib/cli/UpgradeService.js +14 -5
  7. package/lib/core/discovery/GenericDiscoverer.js +4 -28
  8. package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +246 -0
  9. package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +80 -0
  10. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +275 -0
  11. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +600 -0
  12. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +125 -342
  13. package/lib/external/mcp/handlers/bootstrap/refine.js +362 -0
  14. package/lib/external/mcp/handlers/bootstrap.js +6 -590
  15. package/lib/external/mcp/handlers/browse.js +119 -9
  16. package/lib/external/mcp/handlers/guard.js +25 -6
  17. package/lib/external/mcp/handlers/search.js +56 -24
  18. package/lib/http/routes/guardRules.js +9 -17
  19. package/lib/injection/ServiceContainer.js +12 -3
  20. package/lib/platform/ios/xcode/XcodeImportResolver.js +434 -0
  21. package/lib/platform/ios/xcode/XcodeIntegration.js +40 -659
  22. package/lib/platform/ios/xcode/XcodeWriteUtils.js +220 -0
  23. package/lib/service/chat/ChatAgent.js +39 -418
  24. package/lib/service/chat/ChatAgentPrompts.js +149 -0
  25. package/lib/service/chat/ChatAgentTasks.js +297 -0
  26. package/lib/service/chat/tools/_shared.js +61 -0
  27. package/lib/service/chat/tools/ai-analysis.js +284 -0
  28. package/lib/service/chat/tools/ast-graph.js +681 -0
  29. package/lib/service/chat/tools/composite.js +496 -0
  30. package/lib/service/chat/tools/guard.js +265 -0
  31. package/lib/service/chat/tools/index.js +250 -0
  32. package/lib/service/chat/tools/infrastructure.js +222 -0
  33. package/lib/service/chat/tools/knowledge-graph.js +234 -0
  34. package/lib/service/chat/tools/lifecycle.js +469 -0
  35. package/lib/service/chat/tools/project-access.js +923 -0
  36. package/lib/service/chat/tools/query.js +264 -0
  37. package/lib/service/chat/tools.js +14 -3994
  38. package/lib/service/cursor/AgentInstructionsGenerator.js +395 -0
  39. package/lib/service/cursor/CursorDeliveryPipeline.js +70 -11
  40. package/lib/service/cursor/FileProtection.js +116 -0
  41. package/lib/service/cursor/KnowledgeCompressor.js +61 -11
  42. package/lib/service/cursor/SkillsSyncer.js +5 -3
  43. package/lib/service/cursor/TopicClassifier.js +19 -3
  44. package/lib/service/guard/ExclusionManager.js +26 -2
  45. package/lib/service/guard/GuardCheckEngine.js +38 -370
  46. package/lib/service/guard/GuardCodeChecks.js +362 -0
  47. package/lib/service/guard/GuardCrossFileChecks.js +307 -0
  48. package/lib/service/guard/GuardPatternUtils.js +180 -0
  49. package/lib/service/guard/GuardService.js +80 -38
  50. package/lib/service/module/ModuleService.js +1 -0
  51. package/lib/service/search/SearchEngine.js +10 -2
  52. package/lib/service/wiki/WikiGenerator.js +226 -1532
  53. package/lib/service/wiki/WikiRenderers.js +1878 -0
  54. package/lib/service/wiki/WikiUtils.js +907 -0
  55. package/lib/shared/LanguageService.js +299 -0
  56. package/package.json +1 -1
@@ -13,267 +13,26 @@
13
13
  * §9 完整插入流程 — cut 触发行 → preflight → headers → offset → paste
14
14
  */
15
15
 
16
- import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
17
- import { basename, dirname, resolve as pathResolve, relative, sep } from 'node:path';
16
+ import { readFileSync, writeFileSync } from 'node:fs';
18
17
  import { saveEventFilter } from './SaveEventFilter.js';
19
18
 
20
- // ═══════════════════════════════════════════════════════════════
21
- // §1 常量与工具函数
22
- // ═══════════════════════════════════════════════════════════════
23
-
24
- function _sleep(ms) {
25
- return new Promise((resolve) => setTimeout(resolve, ms));
26
- }
27
-
28
- /**
29
- * 在 import 行末尾附加来源标记注释
30
- */
31
- function _withAutoSnippetNote(importLine) {
32
- if (!importLine) {
33
- return importLine;
34
- }
35
- const note = '// AutoSnippet: 自动插入';
36
- if (importLine.includes(note)) {
37
- return importLine;
38
- }
39
- return `${importLine} ${note}`;
40
- }
41
-
42
- /**
43
- * 解析原始 header 字符串,提取 moduleName 和 headerName
44
- *
45
- * 支持格式:
46
- * #import <Module/Header.h> → { moduleName: 'Module', headerName: 'Header.h', isAngle: true }
47
- * #import "Header.h" → { moduleName: '', headerName: 'Header.h', isAngle: false }
48
- * @import Module; → { moduleName: 'Module', headerName: '', isAngle: false, isAtImport: true }
49
- * import Module (Swift) → { moduleName: 'Module', headerName: '', isAngle: false, isSwiftImport: true }
50
- * Header.h → { moduleName: '', headerName: 'Header.h', isAngle: false, isRaw: true }
51
- */
52
- function _parseHeaderString(header) {
53
- const t = header.trim();
54
- // #import <Module/Header.h>
55
- let m = t.match(/^#(?:import|include)\s+<([^/> ]+)\/([^>]+)>/);
56
- if (m) {
57
- return { moduleName: m[1], headerName: m[2], isAngle: true };
58
- }
59
- // #import <Module> (framework umbrella)
60
- m = t.match(/^#(?:import|include)\s+<([^>]+)>/);
61
- if (m) {
62
- return { moduleName: m[1], headerName: '', isAngle: true };
63
- }
64
- // #import "Header.h" or #import "Dir/Header.h"
65
- m = t.match(/^#(?:import|include)\s+"([^"]+)"/);
66
- if (m) {
67
- const parts = m[1].split('/');
68
- return {
69
- moduleName: '',
70
- headerName: parts[parts.length - 1],
71
- isAngle: false,
72
- quotedPath: m[1],
73
- };
74
- }
75
- // @import Module;
76
- m = t.match(/^@import\s+(\w+)/);
77
- if (m) {
78
- return { moduleName: m[1], headerName: '', isAngle: false, isAtImport: true };
79
- }
80
- // import Module (Swift)
81
- m = t.match(/^import\s+(\w+)/);
82
- if (m && !['class', 'struct', 'enum', 'protocol', 'func', 'var', 'let'].includes(m[1])) {
83
- return { moduleName: m[1], headerName: '', isAngle: false, isSwiftImport: true };
84
- }
85
- // 裸 header 名: Header.h
86
- if (/\.(h|hpp|hh)$/i.test(t)) {
87
- return { moduleName: '', headerName: t, isAngle: false, isRaw: true };
88
- }
89
- return { moduleName: '', headerName: t, isAngle: false, isRaw: true };
90
- }
91
-
92
- /**
93
- * 在 target 源目录中搜索头文件,返回相对于当前文件的路径
94
- *
95
- * 搜索策略:
96
- * 1. 当前文件同目录
97
- * 2. 从项目根目录递归查找(最多深度 6 层,优先 Sources/ 下)
98
- * 3. 找到后计算相对于当前文件目录的路径
99
- *
100
- * @param {string} headerName - 头文件名 (如 "Foo.h")
101
- * @param {string} currentFilePath - 当前正在编辑的文件绝对路径
102
- * @param {string} [projectRoot] - 项目根目录
103
- * @returns {string|null} 相对路径 (如 "Foo.h" 或 "../SubDir/Foo.h"),null 表示未找到
104
- */
105
- function _findHeaderRelativePath(headerName, currentFilePath, projectRoot) {
106
- if (!headerName || !currentFilePath) {
107
- return null;
108
- }
109
- try {
110
- const currentDir = dirname(currentFilePath);
111
-
112
- // 1. 同目录检查
113
- const sameDir = pathResolve(currentDir, headerName);
114
- if (existsSync(sameDir)) {
115
- return headerName;
116
- }
117
-
118
- // 2. 向上找 Sources/ 或 target 根目录,在其下递归搜索
119
- const searchRoots = [];
120
- if (projectRoot) {
121
- const sourcesDir = pathResolve(projectRoot, 'Sources');
122
- if (existsSync(sourcesDir)) {
123
- searchRoots.push(sourcesDir);
124
- }
125
- searchRoots.push(projectRoot);
126
- }
127
- // 也从当前文件向上找 Sources 目录
128
- let dir = currentDir;
129
- for (let i = 0; i < 8; i++) {
130
- const base = basename(dir);
131
- if (base === 'Sources' || base === 'Source' || base === 'src') {
132
- searchRoots.unshift(dir);
133
- break;
134
- }
135
- const parent = dirname(dir);
136
- if (parent === dir) {
137
- break;
138
- }
139
- dir = parent;
140
- }
141
-
142
- // 在 searchRoots 中递归查找 headerName(限深度 6)
143
- for (const root of searchRoots) {
144
- const found = _findFileRecursive(root, headerName, 6);
145
- if (found) {
146
- let rel = relative(currentDir, found);
147
- // 统一用 / 分隔
148
- rel = rel.split(sep).join('/');
149
- return rel;
150
- }
151
- }
152
-
153
- return null;
154
- } catch {
155
- return null;
156
- }
157
- }
158
-
159
- /**
160
- * 递归查找文件(限最大深度)
161
- */
162
- function _findFileRecursive(dir, fileName, maxDepth) {
163
- if (maxDepth <= 0) {
164
- return null;
165
- }
166
- try {
167
- const entries = readdirSync(dir);
168
- // 先在当前层查找
169
- for (const e of entries) {
170
- if (e === fileName) {
171
- return pathResolve(dir, e);
172
- }
173
- }
174
- // 再递归子目录(跳过隐藏目录和常见无关目录)
175
- for (const e of entries) {
176
- if (e.startsWith('.') || e === 'node_modules' || e === 'build' || e === 'DerivedData') {
177
- continue;
178
- }
179
- const full = pathResolve(dir, e);
180
- try {
181
- if (statSync(full).isDirectory()) {
182
- const found = _findFileRecursive(full, fileName, maxDepth - 1);
183
- if (found) {
184
- return found;
185
- }
186
- }
187
- } catch {
188
- /* 跳过不可访问的目录 */
189
- }
190
- }
191
- } catch {
192
- /* 跳过不可读目录 */
193
- }
194
- return null;
195
- }
196
-
197
- /**
198
- * 根据当前文件 target 和 header 的 module 关系,生成正确格式的 import 行
199
- *
200
- * 规则:
201
- * Swift: 始终 `import Module`(无 quote/angle 区别)
202
- * ObjC 同 target: `#import "Header.h"` (引号格式)
203
- * ObjC 跨 target: `#import <Module/Header.h>` (尖括号格式)
204
- * @import 格式保持原样(已经模块级)
205
- *
206
- * @param {string} rawHeader 原始 header 字符串
207
- * @param {object} ctx { currentTarget, headerModuleName, isSwift, fullPath, projectRoot }
208
- * - currentTarget: 当前文件所属的 target 名
209
- * - headerModuleName: header 所属的 module/target 名(来自 recipe.moduleName 或推断)
210
- * - isSwift: 目标文件是否是 Swift
211
- * - fullPath: 当前编辑文件的绝对路径(用于计算同 target 相对路径)
212
- * - projectRoot: 项目根目录(用于搜索头文件物理位置)
213
- * @returns {string} 格式化后的完整 import 行
214
- */
215
- function _resolveHeaderFormat(rawHeader, ctx) {
216
- const { currentTarget, headerModuleName, isSwift, fullPath, projectRoot } = ctx;
217
- const parsed = _parseHeaderString(rawHeader);
218
-
219
- // Swift: 始终 `import Module`
220
- if (isSwift || parsed.isSwiftImport) {
221
- // 已经是完整 swift import 语句
222
- if (parsed.isSwiftImport) {
223
- return rawHeader.trim();
224
- }
225
- // 从 ObjC 格式推断 swift import
226
- const mod = parsed.moduleName || headerModuleName || '';
227
- if (mod) {
228
- return `import ${mod}`;
229
- }
230
- return rawHeader.trim(); // 无法推断,原样返回
231
- }
232
-
233
- // @import 保持原样(模块级引用不受 target 影响)
234
- if (parsed.isAtImport) {
235
- return rawHeader.trim();
236
- }
237
-
238
- // 已经是尖括号格式 → 保持(明确的跨模块引用)
239
- if (parsed.isAngle) {
240
- return rawHeader.trim();
241
- }
242
-
243
- // ── ObjC: 判断同 target vs 跨 target ──
244
- const effectiveModule = parsed.moduleName || headerModuleName || '';
245
-
246
- // 如果没有 target 信息,无法判断,保持原样
247
- if (!currentTarget || !effectiveModule) {
248
- return rawHeader.trim();
249
- }
250
-
251
- const isSameTarget = currentTarget === effectiveModule;
252
-
253
- if (isSameTarget) {
254
- // 同 target → 引号格式,计算相对路径
255
- if (parsed.headerName && fullPath) {
256
- const relPath = _findHeaderRelativePath(parsed.headerName, fullPath, projectRoot);
257
- if (relPath) {
258
- return `#import "${relPath}"`;
259
- }
260
- }
261
- if (parsed.quotedPath) {
262
- return `#import "${parsed.quotedPath}"`;
263
- }
264
- if (parsed.headerName) {
265
- return `#import "${parsed.headerName}"`;
266
- }
267
- return rawHeader.trim();
268
- }
269
-
270
- // 跨 target → 尖括号格式 <Module/Header.h>
271
- if (parsed.headerName) {
272
- return `#import <${effectiveModule}/${parsed.headerName}>`;
273
- }
274
- // 没有 headerName(裸模块名),用 @import
275
- return `@import ${effectiveModule};`;
276
- }
19
+ import {
20
+ resolveHeaderFormat,
21
+ collectImportsFromFile,
22
+ collectImportsFromHeaderFile,
23
+ checkImportStatus,
24
+ inferModulesFromHeaders,
25
+ } from './XcodeImportResolver.js';
26
+
27
+ import {
28
+ sleep,
29
+ withAutoSnippetNote,
30
+ evaluateDepResult,
31
+ handleDepReview,
32
+ writeImportLineXcode,
33
+ writeImportLineFile,
34
+ computePasteLineNumber,
35
+ } from './XcodeWriteUtils.js';
277
36
 
278
37
  /** 常见 Apple 系统框架(无需 SPM 依赖检查) */
279
38
  const _SYSTEM_FRAMEWORKS = new Set([
@@ -329,384 +88,6 @@ const _SYSTEM_FRAMEWORKS = new Set([
329
88
  'XCTest',
330
89
  ]);
331
90
 
332
- // ═══════════════════════════════════════════════════════════════
333
- // §4 三级 import 去重
334
- // ═══════════════════════════════════════════════════════════════
335
-
336
- /**
337
- * 从文件中收集已有的 import 语句
338
- */
339
- function _collectImportsFromFile(filePath, isSwift) {
340
- try {
341
- if (!existsSync(filePath)) {
342
- return [];
343
- }
344
- const content = readFileSync(filePath, 'utf8');
345
- const lines = content.split(/\r?\n/);
346
- const imports = [];
347
- for (const line of lines) {
348
- const t = line.trim();
349
- if (isSwift) {
350
- if (t.startsWith('import ')) {
351
- imports.push(t);
352
- }
353
- } else {
354
- if (t.startsWith('#import ') || t.startsWith('@import ') || t.startsWith('#include ')) {
355
- imports.push(t);
356
- }
357
- }
358
- }
359
- return imports;
360
- } catch {
361
- return [];
362
- }
363
- }
364
-
365
- /**
366
- * 收集 .m 文件对应 .h 文件中的 imports(ObjC 接口/实现配对去重)
367
- */
368
- function _collectImportsFromHeaderFile(sourcePath, importArray) {
369
- const dotIndex = sourcePath.lastIndexOf('.');
370
- if (dotIndex <= 0) {
371
- return;
372
- }
373
- const headerPath = `${sourcePath.substring(0, dotIndex)}.h`;
374
- const importReg = /^#import\s*<[A-Za-z0-9_]+\/[A-Za-z0-9_+.-]+\.h>$/;
375
- try {
376
- if (!existsSync(headerPath)) {
377
- return;
378
- }
379
- const data = readFileSync(headerPath, 'utf8');
380
- for (const line of data.split('\n')) {
381
- const t = line.trim();
382
- if (importReg.test(t) && !importArray.includes(t)) {
383
- importArray.push(t);
384
- }
385
- }
386
- } catch {
387
- /* ignore */
388
- }
389
- }
390
-
391
- /**
392
- * 三级 import 去重检查
393
- *
394
- * hasHeader 精确匹配(同一 import 行)
395
- * hasModule 模块级匹配(同模块不同头文件,或 @import)
396
- * hasSimilarHeader 文件名 case-insensitive 匹配
397
- *
398
- * @param {string[]} importArray 已有的 import 行
399
- * @param {string} headerLine 待插入的 import 行
400
- * @param {boolean} isSwift
401
- */
402
- function _checkImportStatus(importArray, headerLine, isSwift) {
403
- const trimmed = headerLine.trim();
404
-
405
- // 提取 module / headerFileName
406
- let moduleName = '';
407
- let headerFileName = '';
408
-
409
- if (isSwift) {
410
- const m = trimmed.match(/^import\s+(\w+)/);
411
- if (m) {
412
- moduleName = m[1];
413
- }
414
- headerFileName = moduleName;
415
- } else {
416
- const angle = trimmed.match(/<([^/]+)\/([^>]+)>/);
417
- if (angle) {
418
- moduleName = angle[1];
419
- headerFileName = angle[2];
420
- }
421
- const quote = trimmed.match(/"([^"]+)"/);
422
- if (quote) {
423
- headerFileName = basename(quote[1]);
424
- }
425
- }
426
-
427
- const headerFileNameLower = headerFileName.toLowerCase();
428
-
429
- for (const imp of importArray) {
430
- const impT = imp.trim();
431
-
432
- // ── 级别 1: 精确匹配 ──
433
- if (impT === trimmed) {
434
- return { hasHeader: true, hasModule: false, hasSimilarHeader: false };
435
- }
436
- // 去掉可能的 AutoSnippet 注释后缀再比较
437
- const impTClean = impT.replace(/\s*\/\/\s*AutoSnippet.*$/, '').trim();
438
- if (impTClean === trimmed) {
439
- return { hasHeader: true, hasModule: false, hasSimilarHeader: false };
440
- }
441
-
442
- if (isSwift) {
443
- // ── 级别 2: Swift 模块匹配 ──
444
- const m2 = impT.match(/^import\s+(\w+)/);
445
- if (m2 && m2[1] === moduleName) {
446
- return { hasHeader: false, hasModule: true, hasSimilarHeader: false };
447
- }
448
- } else {
449
- // ── 级别 2: ObjC 模块匹配(<Module/xxx> 或 @import Module) ──
450
- if (moduleName) {
451
- const impAngle = impT.match(/<([^/]+)\//);
452
- if (impAngle && impAngle[1] === moduleName) {
453
- return { hasHeader: false, hasModule: true, hasSimilarHeader: false };
454
- }
455
- const impAt = impT.match(/@import\s+(\w+)/);
456
- if (impAt && impAt[1] === moduleName) {
457
- return { hasHeader: false, hasModule: true, hasSimilarHeader: false };
458
- }
459
- }
460
-
461
- // ── 级别 3: 相似头文件名匹配(case-insensitive) ──
462
- if (headerFileNameLower) {
463
- let importedFileName = null;
464
- const a = impT.match(/<[^/]+\/([^>]+)>/);
465
- if (a) {
466
- importedFileName = a[1].toLowerCase();
467
- }
468
- const q = impT.match(/"([^"]+)"/);
469
- if (q) {
470
- importedFileName = basename(q[1]).toLowerCase();
471
- }
472
- if (importedFileName && importedFileName === headerFileNameLower) {
473
- return { hasHeader: false, hasModule: false, hasSimilarHeader: true };
474
- }
475
- }
476
- }
477
- }
478
-
479
- return { hasHeader: false, hasModule: false, hasSimilarHeader: false };
480
- }
481
-
482
- // ═══════════════════════════════════════════════════════════════
483
- // §5 模块名推断
484
- // ═══════════════════════════════════════════════════════════════
485
-
486
- /**
487
- * 从 import 语句推断模块名
488
- *
489
- * #import <Module/Header.h> → Module
490
- * @import Module; → Module
491
- * import Module (Swift) → Module
492
- * #import "Local.h" → null
493
- */
494
- function _inferModulesFromHeaders(headers) {
495
- const modules = new Set();
496
- for (const h of headers) {
497
- const t = h.trim();
498
- let m;
499
- m = t.match(/^#import\s+<([^/> ]+)/);
500
- if (m) {
501
- modules.add(m[1]);
502
- continue;
503
- }
504
- m = t.match(/^@import\s+(\w+)/);
505
- if (m) {
506
- modules.add(m[1]);
507
- continue;
508
- }
509
- m = t.match(/^import\s+(\w+)/);
510
- if (m && !['class', 'struct', 'enum', 'protocol'].includes(m[1])) {
511
- modules.add(m[1]);
512
- }
513
- }
514
- return [...modules];
515
- }
516
-
517
- // ═══════════════════════════════════════════════════════════════
518
- // §6 SPM 依赖检查决策引擎
519
- // ═══════════════════════════════════════════════════════════════
520
-
521
- /**
522
- * 将 SpmService.ensureDependency 返回值映射为三种动作:
523
- * continue — 依赖已存在
524
- * block — 循环/反向依赖,禁止插入
525
- * review — 依赖缺失但可添加,需用户确认
526
- */
527
- function _evaluateDepResult(ensureResult, from, to) {
528
- if (ensureResult.exists) {
529
- return { action: 'continue' };
530
- }
531
- if (!ensureResult.canAdd) {
532
- return { action: 'block', reason: ensureResult.reason || 'cycleBlocked', from, to };
533
- }
534
- return { action: 'review', reason: ensureResult.reason || 'missingDependency', from, to };
535
- }
536
-
537
- /**
538
- * 公共依赖审查弹窗逻辑(insertHeaders 和 _preflightDeps 共享)
539
- *
540
- * @param {object} ctx - { spmService, currentTarget, mod, ensureResult, NU, depWarnings, label }
541
- * @returns {{ blocked: boolean }}
542
- */
543
- function _handleDepReview(ctx) {
544
- const { spmService, currentTarget, mod, ensureResult, NU, depWarnings, label = '' } = ctx;
545
-
546
- const fixMode = spmService.getFixMode();
547
- const buttons =
548
- fixMode === 'fix'
549
- ? ['直接插入(信任架构)', '提示操作插入', '自动修复依赖', '取消操作']
550
- : ['直接插入(信任架构)', '提示操作插入', '取消操作'];
551
-
552
- const crossTag = ensureResult.crossPackage ? ' (跨包)' : '';
553
- const prefix = label ? `[${label}] ` : '';
554
-
555
- const userChoice = NU.promptWithButtons(
556
- `检测到依赖缺失:${currentTarget} -> ${mod}${crossTag}\n\n请选择处理方式:`,
557
- buttons,
558
- 'AutoSnippet SPM 依赖决策'
559
- );
560
-
561
- if (
562
- userChoice === '取消操作' ||
563
- (!userChoice && !['直接插入(信任架构)', '提示操作插入', '自动修复依赖'].includes(userChoice))
564
- ) {
565
- return { blocked: true };
566
- }
567
-
568
- if (userChoice === '提示操作插入') {
569
- depWarnings.set(mod, `${currentTarget} -> ${mod}`);
570
- }
571
-
572
- if (userChoice === '自动修复依赖') {
573
- const fixResult = spmService.addDependency(currentTarget, mod);
574
- if (fixResult.ok) {
575
- NU.notify(`已补齐依赖:${currentTarget} -> ${mod}`, 'AutoSnippet SPM');
576
- } else {
577
- console.warn(` ⚠️ ${prefix}自动修复失败: ${fixResult.error},继续插入`);
578
- depWarnings.set(mod, `${currentTarget} -> ${mod}`);
579
- }
580
- }
581
-
582
- return { blocked: false };
583
- }
584
-
585
- // ═══════════════════════════════════════════════════════════════
586
- // §7 Xcode osascript 单条 import 写入
587
- // ═══════════════════════════════════════════════════════════════
588
-
589
- /**
590
- * 通过 Xcode 自动化插入一条 import,保持 Xcode Undo 可用。
591
- *
592
- * 流程:保存剪贴板 → 写入 import 内容 → osascript 跳转+粘贴 → 恢复剪贴板
593
- *
594
- * @param {string} importLine 完整的 import 文本
595
- * @param {number} insertLine 1-based 行号
596
- * @param {object} XA XcodeAutomation 模块
597
- * @param {object} CM ClipboardManager 模块
598
- * @returns {boolean}
599
- */
600
- function _writeImportLineXcode(importLine, insertLine, XA, CM) {
601
- if (!XA.isXcodeRunning()) {
602
- return false;
603
- }
604
- try {
605
- const contentToWrite = `${String(importLine).trim()}\n`;
606
- const previousClipboard = CM.read();
607
-
608
- CM.write(contentToWrite);
609
- const ok = XA.insertAtLineStartInXcode(insertLine);
610
-
611
- // 始终恢复剪贴板
612
- if (typeof previousClipboard === 'string') {
613
- CM.write(previousClipboard);
614
- }
615
- return ok;
616
- } catch {
617
- return false;
618
- }
619
- }
620
-
621
- // ═══════════════════════════════════════════════════════════════
622
- // §8 文件写入回退
623
- // ═══════════════════════════════════════════════════════════════
624
-
625
- /**
626
- * 纯文件写入插入单条 import。
627
- * Xcode 会因文件变更而自动 reload。
628
- */
629
- function _writeImportLineFile(filePath, importLine, isSwift) {
630
- try {
631
- const content = readFileSync(filePath, 'utf8');
632
- const lines = content.split('\n');
633
- let lastImportIdx = -1;
634
- for (let i = 0; i < lines.length; i++) {
635
- const t = lines[i].trim();
636
- if (isSwift) {
637
- if (t.startsWith('import ') && !t.startsWith('import (')) {
638
- lastImportIdx = i;
639
- }
640
- } else {
641
- if (t.startsWith('#import ') || t.startsWith('#include ') || t.startsWith('@import ')) {
642
- lastImportIdx = i;
643
- }
644
- }
645
- }
646
- const insertAt = lastImportIdx >= 0 ? lastImportIdx + 1 : 0;
647
- lines.splice(insertAt, 0, importLine);
648
- const newContent = lines.join('\n');
649
- saveEventFilter.markWrite(filePath, newContent);
650
- writeFileSync(filePath, newContent, 'utf8');
651
- return true;
652
- } catch {
653
- return false;
654
- }
655
- }
656
-
657
- // ═══════════════════════════════════════════════════════════════
658
- // §9 粘贴行号偏移计算
659
- // ═══════════════════════════════════════════════════════════════
660
-
661
- /**
662
- * 查找文件中最后一个 import 行的行号(1-based,0 表示无 import)
663
- */
664
- function _getLastImportLine(filePath) {
665
- try {
666
- if (!existsSync(filePath)) {
667
- return 0;
668
- }
669
- const content = readFileSync(filePath, 'utf8');
670
- const lines = content.split(/\r?\n/);
671
- let lastIdx = -1;
672
- for (let i = 0; i < lines.length; i++) {
673
- const t = lines[i].trim();
674
- if (
675
- t.startsWith('#import ') ||
676
- t.startsWith('@import ') ||
677
- t.startsWith('#include ') ||
678
- t.startsWith('import ')
679
- ) {
680
- lastIdx = i;
681
- }
682
- }
683
- return lastIdx >= 0 ? lastIdx + 1 : 0;
684
- } catch {
685
- return 0;
686
- }
687
- }
688
-
689
- /**
690
- * 计算代码粘贴行号
691
- *
692
- * 如果 headers 插入在 trigger 行之前(import 区),trigger 行号需要向下偏移。
693
- */
694
- function _computePasteLineNumber(triggerLineNumber, headerInsertCount, filePath, options = {}) {
695
- const expectedCount = Number.isFinite(options.expectedHeaderCount)
696
- ? options.expectedHeaderCount
697
- : headerInsertCount;
698
- if (expectedCount > 0) {
699
- if (options.forceOffset) {
700
- return triggerLineNumber + expectedCount;
701
- }
702
- const headerInsertPosition = _getLastImportLine(filePath);
703
- if (headerInsertPosition > 0 && headerInsertPosition < triggerLineNumber) {
704
- return triggerLineNumber + expectedCount;
705
- }
706
- }
707
- return triggerLineNumber;
708
- }
709
-
710
91
  // ═══════════════════════════════════════════════════════════════
711
92
  // §10 导出:insertHeaders
712
93
  // ═══════════════════════════════════════════════════════════════
@@ -741,10 +122,10 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
741
122
  const isSwift = opts.isSwift ?? fullPath.endsWith('.swift');
742
123
 
743
124
  // ── Step 1: 收集已有 imports ──
744
- const importArray = _collectImportsFromFile(fullPath, isSwift);
125
+ const importArray = collectImportsFromFile(fullPath, isSwift);
745
126
  // .m 文件还要收集对应 .h 的 imports
746
127
  if (!isSwift && !fullPath.endsWith('.h')) {
747
- _collectImportsFromHeaderFile(fullPath, importArray);
128
+ collectImportsFromHeaderFile(fullPath, importArray);
748
129
  }
749
130
 
750
131
  // ── Step 2: SPM/模块 服务准备 ──
@@ -752,7 +133,7 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
752
133
  let spmService = opts._spmService || null;
753
134
  let currentTarget = opts._currentTarget || null;
754
135
  if (!spmService && !opts.skipDepCheck) {
755
- const inferredModules = _inferModulesFromHeaders(headers);
136
+ const inferredModules = inferModulesFromHeaders(headers);
756
137
  if (opts.moduleName && !inferredModules.includes(opts.moduleName)) {
757
138
  inferredModules.push(opts.moduleName);
758
139
  }
@@ -802,17 +183,17 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
802
183
 
803
184
  // ── 三级去重 ──
804
185
  // 先按原始格式检查,再按解析后格式检查(同一 header 可能格式不同)
805
- const preResolvedHeader = _resolveHeaderFormat(headerTrimmed, {
186
+ const preResolvedHeader = resolveHeaderFormat(headerTrimmed, {
806
187
  currentTarget,
807
188
  headerModuleName: opts.moduleName || null,
808
189
  isSwift,
809
190
  fullPath,
810
191
  projectRoot: watcher?.projectRoot || null,
811
192
  });
812
- const status = _checkImportStatus(importArray, headerTrimmed, isSwift);
193
+ const status = checkImportStatus(importArray, headerTrimmed, isSwift);
813
194
  const statusResolved =
814
195
  preResolvedHeader !== headerTrimmed
815
- ? _checkImportStatus(importArray, preResolvedHeader, isSwift)
196
+ ? checkImportStatus(importArray, preResolvedHeader, isSwift)
816
197
  : status;
817
198
  if (status.hasHeader || statusResolved.hasHeader) {
818
199
  result.skipped.push(preResolvedHeader);
@@ -828,7 +209,7 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
828
209
  }
829
210
 
830
211
  // ── SPM 依赖检查 ──
831
- const headerModules = _inferModulesFromHeaders([headerTrimmed]);
212
+ const headerModules = inferModulesFromHeaders([headerTrimmed]);
832
213
  if (spmService && currentTarget && !opts.skipDepCheck) {
833
214
  for (const mod of headerModules) {
834
215
  if (_SYSTEM_FRAMEWORKS.has(mod) || mod === currentTarget) {
@@ -836,7 +217,7 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
836
217
  }
837
218
 
838
219
  const ensureResult = spmService.ensureDependency(currentTarget, mod);
839
- const decision = _evaluateDepResult(ensureResult, currentTarget, mod);
220
+ const decision = evaluateDepResult(ensureResult, currentTarget, mod);
840
221
 
841
222
  if (decision.action === 'block') {
842
223
  console.warn(` ⛔ 依赖被阻止: ${currentTarget} -> ${mod} (${decision.reason})`);
@@ -849,7 +230,7 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
849
230
  }
850
231
 
851
232
  if (decision.action === 'review') {
852
- const reviewResult = _handleDepReview({
233
+ const reviewResult = handleDepReview({
853
234
  spmService,
854
235
  currentTarget,
855
236
  mod,
@@ -870,22 +251,22 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
870
251
  const resolvedHeader = preResolvedHeader;
871
252
  const depHint = headerModules.find((m) => depWarnings.has(m));
872
253
  const importLine = depHint
873
- ? `${_withAutoSnippetNote(resolvedHeader)} // ⚠️ 依赖缺失: ${depWarnings.get(depHint)},需手动补齐 Package.swift`
874
- : _withAutoSnippetNote(resolvedHeader);
254
+ ? `${withAutoSnippetNote(resolvedHeader)} // ⚠️ 依赖缺失: ${depWarnings.get(depHint)},需手动补齐 Package.swift`
255
+ : withAutoSnippetNote(resolvedHeader);
875
256
 
876
257
  // ── 写入:Xcode 自动化优先 → 文件写入回退 ──
877
258
  let inserted = false;
878
259
 
879
260
  if (xcodeReady && !fileWriteUsed) {
880
261
  // 逐条 osascript 跳转 + 粘贴
881
- inserted = _writeImportLineXcode(importLine, baseInsertLine + xcodeOffset, XA, CM);
262
+ inserted = writeImportLineXcode(importLine, baseInsertLine + xcodeOffset, XA, CM);
882
263
  if (inserted) {
883
264
  xcodeOffset++;
884
265
  }
885
266
  }
886
267
 
887
268
  if (!inserted) {
888
- _writeImportLineFile(fullPath, importLine, isSwift);
269
+ writeImportLineFile(fullPath, importLine, isSwift);
889
270
  fileWriteUsed = true;
890
271
  }
891
272
 
@@ -989,7 +370,7 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
989
370
  skipDepCheck: true,
990
371
  });
991
372
  }
992
- await _sleep(300);
373
+ await sleep(300);
993
374
 
994
375
  // ── Step 4: 构建带缩进的代码块 ──
995
376
  const codeLines = code.split(/\r?\n/);
@@ -1020,7 +401,7 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
1020
401
  // ── Step 6: 计算偏移后的粘贴行号 ──
1021
402
  // 使用实际插入的 header 数量计算偏移,而非期望数量
1022
403
  // 当 headers 全部重复被跳过时,headerInsertCount = 0,不应偏移
1023
- const pasteLineNumber = _computePasteLineNumber(
404
+ const pasteLineNumber = computePasteLineNumber(
1024
405
  triggerLineNumber,
1025
406
  headerInsertCount,
1026
407
  fullPath,
@@ -1029,7 +410,7 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
1029
410
 
1030
411
  // 如果 headers 通过文件写入,等待 Xcode reload
1031
412
  if (headerInsertCount > 0) {
1032
- await _sleep(600);
413
+ await sleep(600);
1033
414
  }
1034
415
 
1035
416
  // ── Step 7: Jump + 选中行内容 + 粘贴替换 ──
@@ -1039,11 +420,11 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
1039
420
  console.warn(` ⚠️ 剪贴板写入失败`);
1040
421
  return;
1041
422
  }
1042
- await _sleep(100);
423
+ await sleep(100);
1043
424
  XA.jumpToLineInXcode(pasteLineNumber);
1044
- await _sleep(500);
425
+ await sleep(500);
1045
426
  XA.selectAndPasteInXcode();
1046
- await _sleep(300);
427
+ await sleep(300);
1047
428
  });
1048
429
  NU.notify(`已插入「${selected.title || '代码片段'}」`, 'AutoSnippet');
1049
430
  return;
@@ -1069,7 +450,7 @@ async function _preflightDeps(fullPath, headers, selected, NU) {
1069
450
  const result = { blocked: false };
1070
451
 
1071
452
  // 始终从所有 headers 推断模块(不仅依赖 selected.moduleName)
1072
- const inferredModules = _inferModulesFromHeaders(headers);
453
+ const inferredModules = inferModulesFromHeaders(headers);
1073
454
  if (selected.moduleName && !inferredModules.includes(selected.moduleName)) {
1074
455
  inferredModules.push(selected.moduleName);
1075
456
  }
@@ -1108,7 +489,7 @@ async function _preflightDeps(fullPath, headers, selected, NU) {
1108
489
  }
1109
490
 
1110
491
  const ensureResult = spmService.ensureDependency(currentTarget, mod);
1111
- const decision = _evaluateDepResult(ensureResult, currentTarget, mod);
492
+ const decision = evaluateDepResult(ensureResult, currentTarget, mod);
1112
493
 
1113
494
  if (decision.action === 'block') {
1114
495
  console.warn(
@@ -1126,7 +507,7 @@ async function _preflightDeps(fullPath, headers, selected, NU) {
1126
507
  if (!result.depWarnings) {
1127
508
  result.depWarnings = new Map();
1128
509
  }
1129
- const reviewResult = _handleDepReview({
510
+ const reviewResult = handleDepReview({
1130
511
  spmService,
1131
512
  currentTarget,
1132
513
  mod,