autosnippet 3.0.1 → 3.0.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.
Files changed (290) hide show
  1. package/README.md +230 -324
  2. package/bin/api-server.js +1 -1
  3. package/bin/cli.js +204 -244
  4. package/bin/mcp-server.js +5 -3
  5. package/config/knowledge-base.config.js +132 -132
  6. package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
  7. package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
  8. package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
  9. package/dashboard/dist/index.html +3 -3
  10. package/lib/bootstrap.js +8 -8
  11. package/lib/cli/AiScanService.js +86 -40
  12. package/lib/cli/KnowledgeSyncService.js +113 -74
  13. package/lib/cli/SetupService.js +439 -277
  14. package/lib/cli/UpgradeService.js +63 -100
  15. package/lib/core/AstAnalyzer.js +276 -597
  16. package/lib/core/ast/ProjectGraph.js +101 -40
  17. package/lib/core/ast/ensure-grammars.js +232 -0
  18. package/lib/core/ast/index.js +115 -0
  19. package/lib/core/ast/lang-dart.js +661 -0
  20. package/lib/core/ast/lang-go.js +530 -0
  21. package/lib/core/ast/lang-java.js +435 -0
  22. package/lib/core/ast/lang-javascript.js +272 -0
  23. package/lib/core/ast/lang-kotlin.js +423 -0
  24. package/lib/core/ast/lang-objc.js +388 -0
  25. package/lib/core/ast/lang-python.js +371 -0
  26. package/lib/core/ast/lang-swift.js +337 -0
  27. package/lib/core/ast/lang-typescript.js +503 -0
  28. package/lib/core/capability/CapabilityProbe.js +18 -9
  29. package/lib/core/constitution/Constitution.js +2 -3
  30. package/lib/core/constitution/ConstitutionValidator.js +65 -24
  31. package/lib/core/discovery/DartDiscoverer.js +534 -0
  32. package/lib/core/discovery/DiscovererRegistry.js +83 -0
  33. package/lib/core/discovery/GenericDiscoverer.js +225 -0
  34. package/lib/core/discovery/GoDiscoverer.js +541 -0
  35. package/lib/core/discovery/JvmDiscoverer.js +506 -0
  36. package/lib/core/discovery/NodeDiscoverer.js +466 -0
  37. package/lib/core/discovery/ProjectDiscoverer.js +93 -0
  38. package/lib/core/discovery/PythonDiscoverer.js +338 -0
  39. package/lib/core/discovery/SpmDiscoverer.js +5 -0
  40. package/lib/core/discovery/index.js +53 -0
  41. package/lib/core/enhancement/EnhancementPack.js +71 -0
  42. package/lib/core/enhancement/EnhancementRegistry.js +47 -0
  43. package/lib/core/enhancement/android-enhancement.js +102 -0
  44. package/lib/core/enhancement/django-enhancement.js +70 -0
  45. package/lib/core/enhancement/fastapi-enhancement.js +63 -0
  46. package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
  47. package/lib/core/enhancement/go-web-enhancement.js +201 -0
  48. package/lib/core/enhancement/index.js +65 -0
  49. package/lib/core/enhancement/node-server-enhancement.js +88 -0
  50. package/lib/core/enhancement/react-enhancement.js +86 -0
  51. package/lib/core/enhancement/spring-enhancement.js +112 -0
  52. package/lib/core/enhancement/vue-enhancement.js +96 -0
  53. package/lib/core/gateway/Gateway.js +8 -9
  54. package/lib/core/gateway/GatewayActionRegistry.js +1 -1
  55. package/lib/core/permission/PermissionManager.js +12 -8
  56. package/lib/domain/index.js +13 -9
  57. package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
  58. package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
  59. package/lib/domain/knowledge/Lifecycle.js +22 -22
  60. package/lib/domain/knowledge/index.js +9 -12
  61. package/lib/domain/knowledge/values/Constraints.js +31 -21
  62. package/lib/domain/knowledge/values/Content.js +21 -13
  63. package/lib/domain/knowledge/values/Quality.js +31 -18
  64. package/lib/domain/knowledge/values/Reasoning.js +20 -12
  65. package/lib/domain/knowledge/values/Relations.js +37 -25
  66. package/lib/domain/knowledge/values/Stats.js +18 -12
  67. package/lib/domain/knowledge/values/index.js +4 -3
  68. package/lib/domain/snippet/Snippet.js +35 -10
  69. package/lib/external/ai/AiFactory.js +48 -16
  70. package/lib/external/ai/AiProvider.js +184 -90
  71. package/lib/external/ai/providers/ClaudeProvider.js +25 -12
  72. package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
  73. package/lib/external/ai/providers/MockProvider.js +9 -3
  74. package/lib/external/ai/providers/OpenAiProvider.js +51 -29
  75. package/lib/external/mcp/McpServer.js +66 -36
  76. package/lib/external/mcp/errorHandler.js +23 -11
  77. package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
  78. package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
  79. package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
  80. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
  81. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
  82. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
  83. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
  84. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
  85. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
  86. package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
  87. package/lib/external/mcp/handlers/bootstrap.js +691 -168
  88. package/lib/external/mcp/handlers/browse.js +66 -22
  89. package/lib/external/mcp/handlers/candidate.js +118 -35
  90. package/lib/external/mcp/handlers/consolidated.js +49 -17
  91. package/lib/external/mcp/handlers/guard.js +104 -39
  92. package/lib/external/mcp/handlers/knowledge.js +60 -36
  93. package/lib/external/mcp/handlers/search.js +43 -14
  94. package/lib/external/mcp/handlers/skill.js +120 -45
  95. package/lib/external/mcp/handlers/structure.js +240 -86
  96. package/lib/external/mcp/handlers/system.js +42 -12
  97. package/lib/external/mcp/handlers/wiki.js +58 -33
  98. package/lib/external/mcp/tools.js +306 -123
  99. package/lib/http/HttpServer.js +72 -47
  100. package/lib/http/middleware/RateLimiter.js +5 -3
  101. package/lib/http/middleware/errorHandler.js +6 -1
  102. package/lib/http/middleware/requestLogger.js +14 -3
  103. package/lib/http/middleware/roleResolver.js +30 -23
  104. package/lib/http/routes/ai.js +387 -265
  105. package/lib/http/routes/auth.js +81 -61
  106. package/lib/http/routes/candidates.js +430 -320
  107. package/lib/http/routes/commands.js +289 -189
  108. package/lib/http/routes/extract.js +158 -125
  109. package/lib/http/routes/guardRules.js +309 -217
  110. package/lib/http/routes/knowledge.js +213 -154
  111. package/lib/http/routes/modules.js +578 -0
  112. package/lib/http/routes/monitoring.js +6 -6
  113. package/lib/http/routes/recipes.js +104 -93
  114. package/lib/http/routes/search.js +361 -305
  115. package/lib/http/routes/skills.js +145 -98
  116. package/lib/http/routes/snippets.js +42 -30
  117. package/lib/http/routes/spm.js +3 -405
  118. package/lib/http/routes/violations.js +113 -93
  119. package/lib/http/routes/wiki.js +211 -170
  120. package/lib/http/utils/routeHelpers.js +3 -1
  121. package/lib/http/utils/sse-sessions.js +16 -6
  122. package/lib/http/utils/sse.js +15 -5
  123. package/lib/infrastructure/audit/AuditLogger.js +5 -2
  124. package/lib/infrastructure/audit/AuditStore.js +10 -7
  125. package/lib/infrastructure/cache/CacheService.js +3 -1
  126. package/lib/infrastructure/cache/GraphCache.js +8 -4
  127. package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
  128. package/lib/infrastructure/config/ConfigLoader.js +9 -5
  129. package/lib/infrastructure/config/Defaults.js +30 -10
  130. package/lib/infrastructure/config/Paths.js +28 -8
  131. package/lib/infrastructure/config/TriggerSymbol.js +22 -10
  132. package/lib/infrastructure/database/DatabaseConnection.js +15 -10
  133. package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
  134. package/lib/infrastructure/external/ClipboardManager.js +6 -2
  135. package/lib/infrastructure/external/NativeUi.js +50 -43
  136. package/lib/infrastructure/external/OpenBrowser.js +14 -17
  137. package/lib/infrastructure/external/XcodeAutomation.js +14 -258
  138. package/lib/infrastructure/logging/Logger.js +46 -30
  139. package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
  140. package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
  141. package/lib/infrastructure/paths/HeaderResolver.js +25 -9
  142. package/lib/infrastructure/paths/PathFinder.js +34 -12
  143. package/lib/infrastructure/plugin/PluginManager.js +26 -8
  144. package/lib/infrastructure/realtime/RealtimeService.js +2 -2
  145. package/lib/infrastructure/vector/Chunker.js +22 -7
  146. package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
  147. package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
  148. package/lib/infrastructure/vector/VectorStore.js +28 -10
  149. package/lib/injection/ServiceContainer.js +247 -93
  150. package/lib/platform/ios/index.js +63 -0
  151. package/lib/platform/ios/routes/spm.js +437 -0
  152. package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
  153. package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
  154. package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
  155. package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
  156. package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
  157. package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
  158. package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
  159. package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
  160. package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
  161. package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
  162. package/lib/repository/base/BaseRepository.js +7 -9
  163. package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
  164. package/lib/repository/token/TokenUsageStore.js +4 -2
  165. package/lib/service/automation/ActionPipeline.js +1 -1
  166. package/lib/service/automation/AutomationOrchestrator.js +8 -4
  167. package/lib/service/automation/ContextCollector.js +7 -5
  168. package/lib/service/automation/DirectiveDetector.js +23 -16
  169. package/lib/service/automation/FileWatcher.js +112 -56
  170. package/lib/service/automation/TriggerResolver.js +6 -4
  171. package/lib/service/automation/handlers/AlinkHandler.js +24 -12
  172. package/lib/service/automation/handlers/CreateHandler.js +19 -20
  173. package/lib/service/automation/handlers/DraftHandler.js +14 -8
  174. package/lib/service/automation/handlers/GuardHandler.js +93 -63
  175. package/lib/service/automation/handlers/HeaderHandler.js +1 -6
  176. package/lib/service/automation/handlers/SearchHandler.js +155 -88
  177. package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
  178. package/lib/service/candidate/SimilarityService.js +25 -9
  179. package/lib/service/chat/AnalystAgent.js +50 -24
  180. package/lib/service/chat/CandidateGuardrail.js +143 -17
  181. package/lib/service/chat/ChatAgent.js +655 -260
  182. package/lib/service/chat/ContextWindow.js +116 -71
  183. package/lib/service/chat/ConversationStore.js +77 -36
  184. package/lib/service/chat/EpisodicConsolidator.js +47 -23
  185. package/lib/service/chat/HandoffProtocol.js +98 -22
  186. package/lib/service/chat/Memory.js +34 -14
  187. package/lib/service/chat/ProducerAgent.js +40 -20
  188. package/lib/service/chat/ProjectSemanticMemory.js +109 -78
  189. package/lib/service/chat/ReasoningLayer.js +148 -70
  190. package/lib/service/chat/ReasoningTrace.js +44 -32
  191. package/lib/service/chat/TaskPipeline.js +39 -19
  192. package/lib/service/chat/ToolRegistry.js +48 -29
  193. package/lib/service/chat/WorkingMemory.js +44 -18
  194. package/lib/service/chat/tools.js +1096 -494
  195. package/lib/service/context/RecipeExtractor.js +132 -51
  196. package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
  197. package/lib/service/cursor/KnowledgeCompressor.js +25 -22
  198. package/lib/service/cursor/RulesGenerator.js +13 -7
  199. package/lib/service/cursor/SkillsSyncer.js +77 -27
  200. package/lib/service/cursor/TokenBudget.js +2 -2
  201. package/lib/service/cursor/TopicClassifier.js +54 -20
  202. package/lib/service/guard/ComplianceReporter.js +55 -43
  203. package/lib/service/guard/ExclusionManager.js +67 -29
  204. package/lib/service/guard/GuardCheckEngine.js +381 -86
  205. package/lib/service/guard/GuardFeedbackLoop.js +22 -10
  206. package/lib/service/guard/GuardService.js +29 -19
  207. package/lib/service/guard/RuleLearner.js +55 -23
  208. package/lib/service/guard/SourceFileCollector.js +27 -20
  209. package/lib/service/guard/ViolationsStore.js +43 -38
  210. package/lib/service/knowledge/CodeEntityGraph.js +147 -82
  211. package/lib/service/knowledge/ConfidenceRouter.js +12 -10
  212. package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
  213. package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
  214. package/lib/service/knowledge/KnowledgeService.js +222 -112
  215. package/lib/service/module/ModuleService.js +969 -0
  216. package/lib/service/quality/FeedbackCollector.js +27 -15
  217. package/lib/service/quality/QualityScorer.js +78 -24
  218. package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
  219. package/lib/service/recipe/RecipeParser.js +78 -45
  220. package/lib/service/search/CoarseRanker.js +43 -28
  221. package/lib/service/search/CrossEncoderReranker.js +32 -21
  222. package/lib/service/search/InvertedIndex.js +21 -7
  223. package/lib/service/search/MultiSignalRanker.js +90 -28
  224. package/lib/service/search/RetrievalFunnel.js +45 -24
  225. package/lib/service/search/SearchEngine.js +255 -103
  226. package/lib/service/skills/EventAggregator.js +32 -15
  227. package/lib/service/skills/SignalCollector.js +140 -64
  228. package/lib/service/skills/SkillAdvisor.js +79 -42
  229. package/lib/service/skills/SkillHooks.js +16 -14
  230. package/lib/service/snippet/PlaceholderConverter.js +5 -0
  231. package/lib/service/snippet/SnippetFactory.js +116 -99
  232. package/lib/service/snippet/SnippetInstaller.js +234 -62
  233. package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
  234. package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
  235. package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
  236. package/lib/service/wiki/WikiGenerator.js +637 -263
  237. package/lib/shared/DimensionCopyRegistry.js +472 -0
  238. package/lib/shared/LanguageService.js +399 -0
  239. package/lib/shared/PathGuard.js +45 -28
  240. package/lib/shared/RecipeReadinessChecker.js +72 -12
  241. package/lib/shared/constants.js +41 -41
  242. package/lib/shared/errors/BaseError.js +2 -2
  243. package/lib/shared/errors/index.js +4 -4
  244. package/lib/shared/similarity.js +25 -8
  245. package/lib/shared/token-utils.js +6 -2
  246. package/lib/shared/utils/common.js +12 -4
  247. package/package.json +49 -13
  248. package/scripts/bench-real-projects.mjs +256 -0
  249. package/scripts/build-native-ui.js +30 -30
  250. package/scripts/clear-old-vector-index.js +5 -35
  251. package/scripts/clear-vector-cache.js +7 -37
  252. package/scripts/collect-test-project-stats.mjs +160 -0
  253. package/scripts/diagnose-mcp.js +41 -32
  254. package/scripts/ensure-parse-package.js +6 -9
  255. package/scripts/generate-recipe-drafts.js +116 -77
  256. package/scripts/init-db.js +3 -20
  257. package/scripts/init-snippets.js +305 -0
  258. package/scripts/init-vector-db.js +173 -170
  259. package/scripts/install-cursor-skill.js +148 -104
  260. package/scripts/install-full.js +8 -21
  261. package/scripts/install-vscode-copilot.js +146 -145
  262. package/scripts/migrate-md-to-knowledge.mjs +139 -151
  263. package/scripts/postinstall-safe.js +5 -17
  264. package/scripts/recipe-audit.js +106 -82
  265. package/scripts/release.js +283 -323
  266. package/scripts/setup-mcp-config.js +60 -52
  267. package/scripts/verify-context-api.js +20 -20
  268. package/skills/autosnippet-analysis/SKILL.md +10 -6
  269. package/skills/autosnippet-candidates/SKILL.md +27 -26
  270. package/skills/autosnippet-coldstart/SKILL.md +555 -38
  271. package/skills/autosnippet-concepts/SKILL.md +349 -337
  272. package/skills/autosnippet-create/SKILL.md +5 -5
  273. package/skills/autosnippet-reference-dart/SKILL.md +543 -0
  274. package/skills/autosnippet-reference-go/SKILL.md +539 -0
  275. package/skills/autosnippet-reference-java/SKILL.md +534 -0
  276. package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
  277. package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
  278. package/skills/autosnippet-reference-objc/SKILL.md +29 -6
  279. package/skills/autosnippet-reference-python/SKILL.md +800 -0
  280. package/skills/autosnippet-reference-swift/SKILL.md +70 -14
  281. package/skills/autosnippet-structure/SKILL.md +4 -4
  282. package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
  283. package/templates/recipes-setup/README.md +2 -2
  284. package/templates/recipes-setup/_template.md +1 -1
  285. package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
  286. package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
  287. package/resources/asd-entry/main.swift +0 -159
  288. package/scripts/build-asd-entry.js +0 -51
  289. package/scripts/init-xcode-snippets.js +0 -311
  290. package/template.json +0 -39
@@ -9,19 +9,15 @@
9
9
  * // as:a <keyword> — 检查当前文件 + 搜索相关规范
10
10
  */
11
11
 
12
- import { basename, join, extname, dirname } from 'node:path';
13
12
  import { readFile } from 'node:fs/promises';
13
+ import { dirname, extname, join } from 'node:path';
14
+ import { LanguageService } from '../../../shared/LanguageService.js';
14
15
 
15
16
  /** 已知的 scope 关键词 */
16
17
  const SCOPE_KEYWORDS = new Set(['file', 'target', 'project', 'all']);
17
18
 
18
- /** 支持审计的源文件扩展名 */
19
- const SOURCE_EXTS = new Set([
20
- '.m', '.mm', '.h', '.swift',
21
- '.c', '.cpp', '.cc', '.cxx', '.hpp',
22
- '.js', '.ts', '.jsx', '.tsx',
23
- '.java', '.kt', '.py', '.rb', '.go', '.rs',
24
- ]);
19
+ /** 支持审计的源文件扩展名 — 委托给 LanguageService */
20
+ const SOURCE_EXTS = LanguageService.sourceExts;
25
21
 
26
22
  /**
27
23
  * 递归收集目录下所有源文件路径
@@ -32,9 +28,18 @@ async function collectSourceFiles(dir) {
32
28
 
33
29
  // 跳过的目录
34
30
  const SKIP_DIRS = new Set([
35
- 'node_modules', '.git', 'build', 'DerivedData',
36
- 'Pods', '.build', 'vendor', 'dist', '.next',
37
- 'Carthage', 'xcuserdata', '__pycache__',
31
+ 'node_modules',
32
+ '.git',
33
+ 'build',
34
+ 'DerivedData',
35
+ 'Pods',
36
+ '.build',
37
+ 'vendor',
38
+ 'dist',
39
+ '.next',
40
+ 'Carthage',
41
+ 'xcuserdata',
42
+ '__pycache__',
38
43
  ]);
39
44
 
40
45
  async function walk(currentDir) {
@@ -45,7 +50,9 @@ async function collectSourceFiles(dir) {
45
50
  return; // 权限不足等情况跳过
46
51
  }
47
52
  for (const entry of entries) {
48
- if (entry.name.startsWith('.') && entry.name !== '.') continue;
53
+ if (entry.name.startsWith('.') && entry.name !== '.') {
54
+ continue;
55
+ }
49
56
  const fullPath = join(currentDir, entry.name);
50
57
  if (entry.isDirectory()) {
51
58
  if (!SKIP_DIRS.has(entry.name)) {
@@ -72,10 +79,14 @@ export async function handleGuard(watcher, fullPath, code, guardLine) {
72
79
  const scopeArg = rest.toLowerCase();
73
80
  const isScope = SCOPE_KEYWORDS.has(scopeArg);
74
81
  // 确定 scope:无参数或 'file' → file;'target' → target;'project'/'all' → project
75
- const scope = !rest || scopeArg === 'file' ? 'file'
76
- : scopeArg === 'target' ? 'target'
77
- : (scopeArg === 'project' || scopeArg === 'all') ? 'project'
78
- : 'file'; // 非 scope 关键词回退到 file
82
+ const scope =
83
+ !rest || scopeArg === 'file'
84
+ ? 'file'
85
+ : scopeArg === 'target'
86
+ ? 'target'
87
+ : scopeArg === 'project' || scopeArg === 'all'
88
+ ? 'project'
89
+ : 'file'; // 非 scope 关键词回退到 file
79
90
 
80
91
  try {
81
92
  const { detectLanguage } = await import('../../guard/GuardCheckEngine.js');
@@ -95,18 +106,19 @@ export async function handleGuard(watcher, fullPath, code, guardLine) {
95
106
 
96
107
  if (!scanRoot) {
97
108
  console.warn(' ⚠️ 无法确定扫描根目录,回退到单文件检查');
98
- return _auditSingleFile(engine, fullPath, code, detectLanguage, 'file');
109
+ return _auditSingleFile(watcher, engine, fullPath, code, detectLanguage, 'file');
99
110
  }
100
111
 
101
112
  const scopeLabel = scope === 'project' ? '整个项目' : '当前目录';
102
- console.log(`\n🛡️ [Guard] 正在扫描${scopeLabel}: ${scanRoot}`);
113
+ console.log(`\n🔍 Guard 审计 — ${scopeLabel} (${scanRoot})`);
103
114
  const sourcePaths = await collectSourceFiles(scanRoot);
104
- console.log(` 📁 找到 ${sourcePaths.length} 个源文件`);
105
115
 
106
116
  if (sourcePaths.length === 0) {
107
- console.log(' 未找到源文件');
117
+ console.log(' ℹ️ 未找到可审计的源文件');
118
+ watcher._notify?.('未找到可审计的源文件');
108
119
  return;
109
120
  }
121
+ console.log(` 📁 扫描到 ${sourcePaths.length} 个源文件`);
110
122
 
111
123
  // 读取所有文件内容
112
124
  const fileEntries = [];
@@ -120,7 +132,7 @@ export async function handleGuard(watcher, fullPath, code, guardLine) {
120
132
  }
121
133
  }
122
134
  if (readErrors > 0) {
123
- console.log(` ⚠️ ${readErrors} 个文件无法读取,已跳过`);
135
+ console.warn(` ⚠️ ${readErrors} 个文件读取失败,已跳过`);
124
136
  }
125
137
 
126
138
  // 批量审计(传递 scope 以启用对应维度规则)
@@ -128,63 +140,66 @@ export async function handleGuard(watcher, fullPath, code, guardLine) {
128
140
  const { summary } = report;
129
141
 
130
142
  if (summary.totalViolations === 0) {
131
- console.log(` ✅ ${summary.filesChecked} 个文件全部通过,无违规`);
143
+ console.log(`\n审计通过 — ${fileEntries.length} 个文件,无违规`);
144
+ watcher._notify?.(`${scopeLabel}审计通过 ✅ ${fileEntries.length} 个文件,无违规`);
132
145
  } else {
133
- console.log(` 🛡️ 扫描 ${summary.filesChecked} 个文件:`);
134
- console.log(` ${summary.totalErrors} errors, ${summary.totalViolations - summary.totalErrors} warnings`);
135
- console.log(` ${summary.filesWithViolations} 个文件存在问题\n`);
136
-
137
146
  // 按文件输出详情(限制输出前 10 个有问题的文件)
138
- const filesWithIssues = report.files.filter(f => f.summary.total > 0);
147
+ console.log(`\n ❌ 审计发现 ${summary.totalViolations} 个问题 (${summary.errors ?? 0} 错误, ${summary.warnings ?? 0} 警告)`);
148
+ watcher._notify?.(`${scopeLabel}审计: ${summary.totalViolations} 个问题 (${summary.errors ?? 0} 错误, ${summary.warnings ?? 0} 警告)`);
149
+ const filesWithIssues = report.files.filter((f) => f.summary.total > 0);
139
150
  for (const file of filesWithIssues.slice(0, 10)) {
140
- const rel = file.filePath.replace(scanRoot + '/', '');
141
- console.log(` 📄 ${rel} (${file.summary.errors}E / ${file.summary.warnings}W)`);
142
- const errors = file.violations.filter(v => v.severity === 'error');
143
- const warnings = file.violations.filter(v => v.severity === 'warning');
151
+ const rel = file.filePath.replace(`${scanRoot}/`, '');
152
+ const errors = file.violations.filter((v) => v.severity === 'error');
153
+ const warnings = file.violations.filter((v) => v.severity === 'warning');
154
+ console.log(`\n 📄 ${rel} (${errors.length} 错误, ${warnings.length} 警告)`);
144
155
  for (const v of errors.slice(0, 5)) {
145
- console.log(` L${v.line} [${v.ruleId}] ${v.message}`);
156
+ console.log(` 🔴 [${v.ruleId || 'unknown'}] ${v.message}${v.line ? ` (行 ${v.line})` : ''}`);
157
+ }
158
+ if (errors.length > 5) {
159
+ console.log(` ... 还有 ${errors.length - 5} 个错误`);
146
160
  }
147
- if (errors.length > 5) console.log(` ... 还有 ${errors.length - 5} 个 errors`);
148
161
  for (const v of warnings.slice(0, 3)) {
149
- console.log(` ⚠️ L${v.line} [${v.ruleId}] ${v.message}`);
162
+ console.log(` 🟡 [${v.ruleId || 'unknown'}] ${v.message}${v.line ? ` (行 ${v.line})` : ''}`);
163
+ }
164
+ if (warnings.length > 3) {
165
+ console.log(` ... 还有 ${warnings.length - 3} 个警告`);
150
166
  }
151
- if (warnings.length > 3) console.log(` ... 还有 ${warnings.length - 3} 个 warnings`);
152
167
  }
153
168
  if (filesWithIssues.length > 10) {
154
- console.log(`\n ... 还有 ${filesWithIssues.length - 10} 个文件有问题,已省略`);
169
+ console.log(`\n ... 还有 ${filesWithIssues.length - 10} 个文件有问题`);
155
170
  }
156
171
 
157
172
  // 跨文件问题汇总
158
173
  if (report.crossFileViolations?.length > 0) {
159
- console.log(`\n 🔗 跨文件问题 (${report.crossFileViolations.length}):`);
174
+ console.log(`\n 🔗 跨文件问题 (${report.crossFileViolations.length}):`);
160
175
  for (const v of report.crossFileViolations.slice(0, 10)) {
161
- console.log(` ⚠️ [${v.ruleId}] ${v.message}`);
176
+ console.log(` ⚠️ [${v.ruleId || 'cross-file'}] ${v.message}`);
162
177
  if (v.locations) {
163
178
  for (const loc of v.locations.slice(0, 5)) {
164
- const relLoc = loc.filePath.replace(scanRoot + '/', '');
165
- console.log(` 📄 ${relLoc}:L${loc.line}`);
179
+ const relLoc = loc.filePath.replace(`${scanRoot}/`, '');
180
+ console.log(` 📍 ${relLoc}${loc.line ? `:${loc.line}` : ''}`);
181
+ }
182
+ if (v.locations.length > 5) {
183
+ console.log(` ... 还有 ${v.locations.length - 5} 个位置`);
166
184
  }
167
- if (v.locations.length > 5) console.log(` ... 还有 ${v.locations.length - 5} 处`);
168
185
  }
169
186
  }
170
187
  }
171
188
  }
172
189
  return;
173
190
  }
174
-
175
- /* ── 单文件审计 (file scope) ── */
176
- console.log(`\n🛡️ [Guard] 正在检查文件: ${basename(fullPath)}`);
177
- _auditSingleFile(engine, fullPath, code, detectLanguage, scope);
191
+ _auditSingleFile(watcher, engine, fullPath, code, detectLanguage, scope);
178
192
 
179
193
  // 如果有非 scope 关键词,也做语义搜索
180
194
  if (rest && !isScope) {
181
195
  try {
182
196
  const searchEngine = container.get('searchEngine');
183
197
  const results = await searchEngine.search(rest, { limit: 3, mode: 'keyword' });
184
- if (results.length > 0) {
185
- console.log(` 🧠 相关规范 (${results.length}条):`);
186
- for (const r of results) {
187
- console.log(` - ${r.title || r.id}`);
198
+ const items = Array.isArray(results) ? results : results.items || [];
199
+ if (items.length > 0) {
200
+ console.log(`\n 📚 相关规范 ("${rest}"):`);
201
+ for (const r of items) {
202
+ console.log(` • ${r.title || r.name || r.id}`);
188
203
  }
189
204
  }
190
205
  } catch {
@@ -199,32 +214,47 @@ export async function handleGuard(watcher, fullPath, code, guardLine) {
199
214
  /**
200
215
  * 检查单个文件并打印结果
201
216
  */
202
- function _auditSingleFile(engine, fullPath, code, detectLanguage, scope = 'file') {
217
+ function _auditSingleFile(watcher, engine, fullPath, code, detectLanguage, scope = 'file') {
203
218
  const language = detectLanguage(fullPath);
204
219
  const violations = engine.checkCode(code, language, { scope });
205
220
 
206
221
  if (violations.length === 0) {
207
- console.log(`无违规`);
222
+ console.log(`\nGuard 审计通过 — 无违规 (${fullPath.split('/').pop()})`);
223
+ watcher._notify?.('审计通过 ✅ 无违规');
208
224
  } else {
209
225
  const errors = violations.filter((v) => v.severity === 'error');
210
226
  const warnings = violations.filter((v) => v.severity === 'warning');
211
- console.log(` 🛡️ ${errors.length} errors, ${warnings.length} warnings`);
227
+ console.log(`\n 发现 ${violations.length} 个问题 (${errors.length} 错误, ${warnings.length} 警告)`);
228
+ watcher._notify?.(`审计: ${violations.length} 个问题 (${errors.length} 错误, ${warnings.length} 警告)`);
212
229
  for (const v of errors) {
213
- console.log(` L${v.line} [${v.ruleId}] ${v.message}`);
214
- if (v.fixSuggestion) console.log(` 🔧 修复建议: ${v.fixSuggestion}`);
230
+ console.log(` 🔴 [${v.ruleId || 'unknown'}] ${v.message}${v.line ? ` (行 ${v.line})` : ''}`);
231
+ if (v.fixSuggestion) {
232
+ console.log(` 💡 建议: ${v.fixSuggestion}`);
233
+ }
215
234
  }
216
235
  for (const v of warnings.slice(0, 5)) {
217
- console.log(` ⚠️ L${v.line} [${v.ruleId}] ${v.message}`);
218
- if (v.fixSuggestion) console.log(` 🔧 修复建议: ${v.fixSuggestion}`);
236
+ console.log(` 🟡 [${v.ruleId || 'unknown'}] ${v.message}${v.line ? ` (行 ${v.line})` : ''}`);
237
+ if (v.fixSuggestion) {
238
+ console.log(` 💡 建议: ${v.fixSuggestion}`);
239
+ }
240
+ }
241
+ if (warnings.length > 5) {
242
+ console.log(` ... 还有 ${warnings.length - 5} 个警告`);
219
243
  }
220
244
  }
221
245
 
222
246
  // Guard ↔ Recipe 闭环:检测修复并自动确认使用(fire-and-forget)
223
- import('../../../injection/ServiceContainer.js').then(({ ServiceContainer }) => {
224
- try {
225
- const container = ServiceContainer.getInstance();
226
- const feedbackLoop = container.get('guardFeedbackLoop');
227
- feedbackLoop.processFixDetection({ violations }, fullPath);
228
- } catch { /* guardFeedbackLoop not available */ }
229
- }).catch(() => { /* ignored */ });
247
+ import('../../../injection/ServiceContainer.js')
248
+ .then(({ ServiceContainer }) => {
249
+ try {
250
+ const container = ServiceContainer.getInstance();
251
+ const feedbackLoop = container.get('guardFeedbackLoop');
252
+ feedbackLoop.processFixDetection({ violations }, fullPath);
253
+ } catch {
254
+ /* guardFeedbackLoop not available */
255
+ }
256
+ })
257
+ .catch(() => {
258
+ /* ignored */
259
+ });
230
260
  }
@@ -18,12 +18,9 @@ export async function handleHeader(watcher, fullPath, headerLine, importArray, i
18
18
  const parsed = HeaderResolver.parseImportLine(headerLine);
19
19
 
20
20
  if (!parsed) {
21
- console.log(`[Header] 无法解析指令: ${headerLine}`);
22
21
  return;
23
22
  }
24
23
 
25
- console.log(`\n📦 [Header] ${isSwift ? 'import' : '#import'} ${parsed.moduleName || parsed.headerName || headerLine}`);
26
-
27
24
  const resolved = await HeaderResolver.resolveHeadersForText(
28
25
  watcher.projectRoot,
29
26
  basename(fullPath),
@@ -31,11 +28,10 @@ export async function handleHeader(watcher, fullPath, headerLine, importArray, i
31
28
  );
32
29
 
33
30
  if (!resolved || !resolved.headers || resolved.headers.length === 0) {
34
- console.log(` ℹ️ 未找到需要添加的头文件`);
35
31
  return;
36
32
  }
37
33
 
38
- const { insertHeaders } = await import('../XcodeIntegration.js');
34
+ const { insertHeaders } = await import('../../../platform/ios/xcode/XcodeIntegration.js');
39
35
  const result = await insertHeaders(watcher, fullPath, resolved.headers, {
40
36
  isSwift,
41
37
  moduleName: resolved.moduleName || null,
@@ -45,7 +41,6 @@ export async function handleHeader(watcher, fullPath, headerLine, importArray, i
45
41
  return;
46
42
  }
47
43
  if (result.inserted.length === 0 && result.skipped.length > 0) {
48
- console.log(` ✅ 头文件已全部导入`);
49
44
  }
50
45
  } catch (err) {
51
46
  console.warn(` ⚠️ Header 处理失败: ${err.message}`);
@@ -9,17 +9,12 @@
9
9
  * @param {string} searchLine
10
10
  */
11
11
  export async function handleSearch(watcher, fullPath, relativePath, searchLine) {
12
- const query = searchLine
13
- .replace(/^\/\/\s*(?:autosnippet|as):(?:search|s)\s*/, '')
14
- .trim();
12
+ const query = searchLine.replace(/^\/\/\s*(?:autosnippet|as):(?:search|s)\s*/, '').trim();
15
13
 
16
14
  if (!query) {
17
- console.log(`[as:search] 请在指令后写搜索关键词,如 // as:s 网络请求`);
18
15
  return;
19
16
  }
20
17
 
21
- console.log(`\n🔍 [Search] "${query}" ...`);
22
-
23
18
  let results = [];
24
19
  try {
25
20
  const { ServiceContainer } = await import('../../../injection/ServiceContainer.js');
@@ -30,16 +25,16 @@ export async function handleSearch(watcher, fullPath, relativePath, searchLine)
30
25
  searchEngine.ensureIndex();
31
26
  const stats = searchEngine.getStats();
32
27
  if (stats.totalDocuments === 0) {
33
- console.log(` ⚠️ 知识库为空(索引 0 条记录),请先通过 asd setup / Dashboard 添加知识条目`);
34
28
  } else {
35
- console.log(` 📊 索引 ${stats.totalDocuments} 条知识`);
36
29
  }
37
30
 
38
31
  // auto (BM25+semantic 融合 + Ranking Pipeline) → keyword (SQL LIKE) 降级链
39
32
  // Xcode/IDE 场景: 传递 generate intent,让排序器使用代码生成权重
40
33
  try {
41
34
  results = await searchEngine.search(query, {
42
- limit: 10, mode: 'auto', rank: true,
35
+ limit: 10,
36
+ mode: 'auto',
37
+ rank: true,
43
38
  context: { intent: 'generate' },
44
39
  });
45
40
  // auto 零结果 → keyword (SQL LIKE) 兆底
@@ -49,11 +44,12 @@ export async function handleSearch(watcher, fullPath, relativePath, searchLine)
49
44
  } catch {
50
45
  try {
51
46
  results = await searchEngine.search(query, { limit: 10, mode: 'keyword' });
52
- } catch { /* 全部失败 */ }
47
+ } catch {
48
+ /* 全部失败 */
49
+ }
53
50
  }
54
51
  } catch (err) {
55
52
  console.warn(` ⚠️ 搜索失败: ${err.message}`);
56
- console.log(` ℹ️ 未找到「${query}」的相关结果`);
57
53
  watcher._notify(`搜索「${query}」失败: ${err.message}`);
58
54
  return;
59
55
  }
@@ -68,24 +64,19 @@ export async function handleSearch(watcher, fullPath, relativePath, searchLine)
68
64
  });
69
65
 
70
66
  if (items.length === 0) {
71
- console.log(` ℹ️ 未找到「${query}」的相关结果`);
72
67
  watcher._notify(`未找到「${query}」的相关结果`);
73
68
  return;
74
69
  }
75
70
 
76
- console.log(` 📋 找到 ${items.length} 条结果`);
77
-
78
71
  // NativeUI 交互选择
79
72
  const NU = await import('../../../infrastructure/external/NativeUi.js');
80
73
  const selectedIndex = NU.showCombinedWindow(items, query);
81
74
 
82
75
  if (selectedIndex < 0 || selectedIndex >= items.length) {
83
- console.log(` ℹ️ 用户取消选择`);
84
76
  return;
85
77
  }
86
78
 
87
79
  const selected = items[selectedIndex];
88
- console.log(` ✅ 选中: ${selected.title}`);
89
80
 
90
81
  // 如果 selected 没有 moduleName,尝试从当前文件路径推断
91
82
  if (!selected.moduleName && selected.headers && selected.headers.length > 0) {
@@ -96,14 +87,16 @@ export async function handleSearch(watcher, fullPath, relativePath, searchLine)
96
87
  relativePath,
97
88
  (await import('node:fs')).readFileSync(fullPath, 'utf8')
98
89
  );
99
- if (resolved && resolved.moduleName) {
90
+ if (resolved?.moduleName) {
100
91
  selected.moduleName = resolved.moduleName;
101
92
  }
102
- } catch { /* 解析失败不阻塞 */ }
93
+ } catch {
94
+ /* 解析失败不阻塞 */
95
+ }
103
96
  }
104
97
 
105
- // 自动插入代码到 Xcode
106
- const { insertCodeToXcode } = await import('../XcodeIntegration.js');
98
+ // Xcode 代码自动插入(osascript 跳转 + 粘贴)
99
+ const { insertCodeToXcode } = await import('../../../platform/ios/xcode/XcodeIntegration.js');
107
100
  await insertCodeToXcode(watcher, fullPath, selected, searchLine);
108
101
  }
109
102
 
@@ -111,81 +104,103 @@ export async function handleSearch(watcher, fullPath, relativePath, searchLine)
111
104
  * 将搜索结果标准化为 NativeUI 可展示格式
112
105
  */
113
106
  export function normalizeSearchResults(results) {
114
- if (!results) return [];
115
- const arr = Array.isArray(results) ? results : (results.items || []);
116
-
117
- return arr.map(r => {
118
- let code = '';
119
- let explanation = '';
120
- let headers = [];
121
- if (r.content) {
122
- try {
123
- const content = typeof r.content === 'string' ? JSON.parse(r.content) : r.content;
124
- code = content.code || content.pattern || content.markdown
125
- || content.content || content.body || content.snippet
126
- || content.solution || content.example || '';
127
- explanation = content.rationale || content.description
128
- || content.summary || content.explanation || '';
129
- if (Array.isArray(content.headers) && content.headers.length > 0) {
130
- headers = content.headers;
131
- }
132
- // 如果主字段为空,尝试从 Markdown 内容提取代码块
133
- if (!code && content.markdown) {
134
- const fenced = content.markdown.match(/```[\w]*\n([\s\S]*?)```/);
135
- if (fenced) code = fenced[1].trim();
107
+ if (!results) {
108
+ return [];
109
+ }
110
+ const arr = Array.isArray(results) ? results : results.items || [];
111
+
112
+ return arr
113
+ .map((r) => {
114
+ let code = '';
115
+ let explanation = '';
116
+ let headers = [];
117
+ if (r.content) {
118
+ try {
119
+ const content = typeof r.content === 'string' ? JSON.parse(r.content) : r.content;
120
+ // 注意: 不直接使用 content.markdown —— markdown 是完整文档,不是纯代码
121
+ code =
122
+ content.code ||
123
+ content.pattern ||
124
+ content.content ||
125
+ content.body ||
126
+ content.snippet ||
127
+ content.solution ||
128
+ content.example ||
129
+ '';
130
+ explanation =
131
+ content.rationale ||
132
+ content.description ||
133
+ content.summary ||
134
+ content.explanation ||
135
+ '';
136
+ if (Array.isArray(content.headers) && content.headers.length > 0) {
137
+ headers = content.headers;
138
+ }
139
+ // 如果主字段为空,尝试从 Markdown 内容提取代码块
140
+ if (!code && content.markdown) {
141
+ code = _extractCodeFromMarkdown(content.markdown);
142
+ // 若 markdown 中提取不出代码,且 explanation 为空,用 markdown 生成摘要
143
+ if (!code && !explanation) {
144
+ explanation = _stripMarkdownFormatting(content.markdown).substring(0, 500);
145
+ }
146
+ }
147
+ } catch {
148
+ // content 不是 JSON,可能是纯文本/代码 — 直接使用
149
+ if (typeof r.content === 'string' && r.content.length > 10) {
150
+ code = r.content.substring(0, 2000);
151
+ }
136
152
  }
137
- } catch {
138
- // content 不是 JSON,可能是纯文本/代码 直接使用
139
- if (typeof r.content === 'string' && r.content.length > 10) {
140
- code = r.content.substring(0, 2000);
153
+ }
154
+ // 如果 Ranking Pipeline 已提取 code 字段,优先使用
155
+ if (!code && r.code && r.code.length > 5) {
156
+ code = r.code;
157
+ }
158
+ // V3: headers 是独立 JSON 列(字符串),优先解析
159
+ if (headers.length === 0 && r.headers) {
160
+ try {
161
+ const parsed = typeof r.headers === 'string' ? JSON.parse(r.headers) : r.headers;
162
+ if (Array.isArray(parsed) && parsed.length > 0) {
163
+ headers = parsed;
164
+ }
165
+ } catch {
166
+ /* ignore */
141
167
  }
142
168
  }
143
- }
144
- // 如果 Ranking Pipeline 已提取 code 字段,优先使用
145
- if (!code && r.code && r.code.length > 5) {
146
- code = r.code;
147
- }
148
- // V3: headers 是独立 JSON 列(字符串),优先解析
149
- if (headers.length === 0 && r.headers) {
150
- try {
151
- const parsed = typeof r.headers === 'string' ? JSON.parse(r.headers) : r.headers;
152
- if (Array.isArray(parsed) && parsed.length > 0) {
153
- headers = parsed;
169
+ // moduleName: 优先从独立列取
170
+ let moduleName = r.moduleName || null;
171
+ if (!moduleName && r.content) {
172
+ try {
173
+ const content = typeof r.content === 'string' ? JSON.parse(r.content) : r.content;
174
+ moduleName = content.moduleName || null;
175
+ } catch {
176
+ /* ignore */
154
177
  }
155
- } catch { /* ignore */ }
156
- }
157
- // moduleName: 优先从独立列取
158
- let moduleName = r.moduleName || null;
159
- if (!moduleName && r.content) {
160
- try {
161
- const content = typeof r.content === 'string' ? JSON.parse(r.content) : r.content;
162
- moduleName = content.moduleName || null;
163
- } catch { /* ignore */ }
164
- }
165
- if (!moduleName) {
166
- moduleName = r.moduleName || null;
167
- }
178
+ }
179
+ if (!moduleName) {
180
+ moduleName = r.moduleName || null;
181
+ }
168
182
 
169
- // ── 从 code 中分离 #import / @import / import 行,归入 headers ──
170
- const finalCode = code || r.code || r.description || r.trigger || '(无预览内容)';
171
- const { cleanedCode, extractedHeaders } = _separateImportsFromCode(finalCode);
172
- if (extractedHeaders.length > 0) {
173
- for (const h of extractedHeaders) {
174
- if (!headers.some(existing => existing.trim() === h.trim())) {
175
- headers.push(h);
183
+ // ── 从 code 中分离 #import / @import / import 行,归入 headers ──
184
+ const finalCode = code || r.code || r.description || r.trigger || '(无预览内容)';
185
+ const { cleanedCode, extractedHeaders } = _separateImportsFromCode(finalCode);
186
+ if (extractedHeaders.length > 0) {
187
+ for (const h of extractedHeaders) {
188
+ if (!headers.some((existing) => existing.trim() === h.trim())) {
189
+ headers.push(h);
190
+ }
176
191
  }
177
192
  }
178
- }
179
193
 
180
- return {
181
- title: r.title || r.name || r.id || 'Recipe',
182
- code: cleanedCode || '(无预览内容)',
183
- explanation: explanation || r.summary || r.description || '',
184
- headers,
185
- moduleName,
186
- trigger: r.trigger || r.completionKey || '',
187
- };
188
- }).filter(item => item.title);
194
+ return {
195
+ title: r.title || r.name || r.id || 'Recipe',
196
+ code: cleanedCode || '(无预览内容)',
197
+ explanation: explanation || r.summary || r.description || '',
198
+ headers,
199
+ moduleName,
200
+ trigger: r.trigger || r.completionKey || '',
201
+ };
202
+ })
203
+ .filter((item) => item.title);
189
204
  }
190
205
 
191
206
  /**
@@ -210,7 +225,9 @@ function _separateImportsFromCode(code) {
210
225
  const trimmed = lines[i].trim();
211
226
  if (!trimmed) {
212
227
  // 空行:如果前面已有 import,继续扫描
213
- if (lastImportIdx >= 0) continue;
228
+ if (lastImportIdx >= 0) {
229
+ continue;
230
+ }
214
231
  // 前面没 import,遇到前导空行也继续
215
232
  continue;
216
233
  }
@@ -236,3 +253,53 @@ function _separateImportsFromCode(code) {
236
253
  const cleanedCode = remaining.join('\n').trim();
237
254
  return { cleanedCode, extractedHeaders };
238
255
  }
256
+
257
+ /**
258
+ * 从 Markdown 文本中提取所有 fenced code blocks,合并为纯代码
259
+ *
260
+ * 支持 ```lang\n...\n``` 格式,提取多个代码块并用空行分隔。
261
+ * 如果没有找到代码块,返回空字符串。
262
+ *
263
+ * @param {string} md - Markdown 文本
264
+ * @returns {string} 提取出的纯代码,或空字符串
265
+ */
266
+ function _extractCodeFromMarkdown(md) {
267
+ if (!md) {
268
+ return '';
269
+ }
270
+ const fencedRe = /```[\w]*\n([\s\S]*?)```/g;
271
+ const blocks = [];
272
+ let match;
273
+ while ((match = fencedRe.exec(md)) !== null) {
274
+ const block = match[1].trim();
275
+ if (block) {
276
+ blocks.push(block);
277
+ }
278
+ }
279
+ return blocks.join('\n\n');
280
+ }
281
+
282
+ /**
283
+ * 移除 Markdown 格式标记,返回纯文本摘要
284
+ *
285
+ * 用于在无法提取代码时,从 markdown 生成 explanation 文本。
286
+ *
287
+ * @param {string} md - Markdown 文本
288
+ * @returns {string} 纯文本
289
+ */
290
+ function _stripMarkdownFormatting(md) {
291
+ if (!md) {
292
+ return '';
293
+ }
294
+ return md
295
+ .replace(/```[\w]*\n[\s\S]*?```/g, '') // 移除代码块
296
+ .replace(/^#{1,6}\s+/gm, '') // 移除标题标记
297
+ .replace(/\*\*([^*]+)\*\*/g, '$1') // 移除粗体
298
+ .replace(/\*([^*]+)\*/g, '$1') // 移除斜体
299
+ .replace(/`([^`]+)`/g, '$1') // 移除行内代码
300
+ .replace(/^\s*[-*+]\s+/gm, '') // 移除列表标记
301
+ .replace(/^\s*\d+\.\s+/gm, '') // 移除有序列表
302
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // 移除链接,保留文字
303
+ .replace(/\n{3,}/g, '\n\n') // 压缩多余空行
304
+ .trim();
305
+ }