autosnippet 2.6.0 → 2.7.1

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 (78) hide show
  1. package/README.md +137 -65
  2. package/bin/api-server.js +5 -0
  3. package/bin/cli.js +6 -1
  4. package/dashboard/dist/assets/{icons-rnn04CvH.js → icons-B_Xg4B-s.js} +148 -88
  5. package/dashboard/dist/assets/index-BjfUm8p9.js +197 -0
  6. package/dashboard/dist/assets/index-CkIih2CC.css +1 -0
  7. package/dashboard/dist/assets/{react-markdown-CWxUbOf4.js → react-markdown-BA6FB2NP.js} +1 -1
  8. package/dashboard/dist/assets/{syntax-highlighter-CJ2drQQb.js → syntax-highlighter-CVLHn9O5.js} +1 -1
  9. package/dashboard/dist/assets/{vendor-f83ah6cm.js → vendor-BotF760a.js} +61 -61
  10. package/dashboard/dist/index.html +6 -6
  11. package/lib/bootstrap.js +18 -1
  12. package/lib/cli/SetupService.js +86 -8
  13. package/lib/cli/UpgradeService.js +139 -2
  14. package/lib/core/ast/ProjectGraph.js +599 -0
  15. package/lib/core/gateway/GatewayActionRegistry.js +2 -2
  16. package/lib/domain/recipe/Recipe.js +3 -0
  17. package/lib/external/ai/AiProvider.js +83 -20
  18. package/lib/external/ai/providers/ClaudeProvider.js +208 -0
  19. package/lib/external/ai/providers/GoogleGeminiProvider.js +247 -1
  20. package/lib/external/ai/providers/OpenAiProvider.js +141 -0
  21. package/lib/external/mcp/McpServer.js +6 -1
  22. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +216 -0
  23. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +657 -0
  24. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +160 -0
  25. package/lib/external/mcp/handlers/bootstrap/skills.js +225 -0
  26. package/lib/external/mcp/handlers/bootstrap.js +159 -1634
  27. package/lib/external/mcp/handlers/browse.js +1 -1
  28. package/lib/external/mcp/handlers/candidate.js +1 -33
  29. package/lib/external/mcp/handlers/skill.js +58 -17
  30. package/lib/external/mcp/tools.js +4 -3
  31. package/lib/http/middleware/requestLogger.js +23 -4
  32. package/lib/http/routes/ai.js +158 -2
  33. package/lib/http/routes/auth.js +3 -2
  34. package/lib/http/routes/candidates.js +49 -25
  35. package/lib/http/routes/commands.js +0 -8
  36. package/lib/http/routes/guardRules.js +1 -16
  37. package/lib/http/routes/recipes.js +4 -17
  38. package/lib/http/routes/search.js +11 -19
  39. package/lib/http/routes/skills.js +2 -0
  40. package/lib/http/routes/snippets.js +0 -33
  41. package/lib/http/routes/spm.js +37 -63
  42. package/lib/http/utils/routeHelpers.js +31 -0
  43. package/lib/infrastructure/config/Paths.js +12 -0
  44. package/lib/infrastructure/database/DatabaseConnection.js +6 -1
  45. package/lib/infrastructure/logging/Logger.js +86 -3
  46. package/lib/infrastructure/realtime/RealtimeService.js +2 -5
  47. package/lib/infrastructure/vector/JsonVectorAdapter.js +26 -1
  48. package/lib/injection/ServiceContainer.js +55 -2
  49. package/lib/service/bootstrap/BootstrapTaskManager.js +400 -0
  50. package/lib/service/candidate/CandidateFileWriter.js +72 -27
  51. package/lib/service/candidate/CandidateService.js +156 -10
  52. package/lib/service/chat/AnalystAgent.js +245 -0
  53. package/lib/service/chat/CandidateGuardrail.js +134 -0
  54. package/lib/service/chat/ChatAgent.js +1055 -167
  55. package/lib/service/chat/ContextWindow.js +730 -0
  56. package/lib/service/chat/ConversationStore.js +3 -0
  57. package/lib/service/chat/HandoffProtocol.js +181 -0
  58. package/lib/service/chat/Memory.js +3 -0
  59. package/lib/service/chat/ProducerAgent.js +293 -0
  60. package/lib/service/chat/ToolRegistry.js +149 -5
  61. package/lib/service/chat/tools.js +1404 -61
  62. package/lib/service/guard/ExclusionManager.js +2 -0
  63. package/lib/service/guard/RuleLearner.js +2 -0
  64. package/lib/service/quality/FeedbackCollector.js +2 -0
  65. package/lib/service/recipe/RecipeFileWriter.js +16 -1
  66. package/lib/service/recipe/RecipeStatsTracker.js +2 -0
  67. package/lib/service/skills/SignalCollector.js +33 -6
  68. package/lib/service/skills/SkillAdvisor.js +2 -1
  69. package/lib/service/skills/SkillHooks.js +13 -5
  70. package/lib/service/spm/SpmService.js +2 -2
  71. package/lib/shared/PathGuard.js +314 -0
  72. package/package.json +1 -1
  73. package/resources/native-ui/combined-window.swift +494 -0
  74. package/templates/copilot-instructions.md +20 -3
  75. package/templates/cursor-rules/autosnippet-conventions.mdc +21 -4
  76. package/templates/cursor-rules/autosnippet-skills.mdc +45 -0
  77. package/dashboard/dist/assets/index-BBKa3Dgi.js +0 -195
  78. package/dashboard/dist/assets/index-DLsECfzW.css +0 -1
@@ -0,0 +1,494 @@
1
+ import AppKit
2
+ import Foundation
3
+
4
+ /**
5
+ * AutoSnippet 组合窗口 - 列表 + 预览一体化
6
+ * 左侧:搜索结果列表
7
+ * 右侧:代码预览(实时更新)
8
+ * 底部:操作按钮
9
+ */
10
+
11
+ // MARK: - 语法高亮
12
+
13
+ struct SyntaxHighlighter {
14
+ static let keywords = ["func", "var", "let", "if", "else", "guard", "return", "import", "class", "struct", "enum", "protocol", "extension", "private", "public", "internal", "fileprivate", "static", "override", "init", "deinit", "self", "super", "nil", "true", "false", "for", "while", "repeat", "switch", "case", "default", "break", "continue", "fallthrough", "where", "in", "throws", "throw", "try", "catch", "async", "await", "typealias", "associatedtype", "weak", "unowned", "lazy", "final", "required", "convenience", "mutating", "nonmutating", "open", "inout", "some", "any",
15
+ // Objective-C
16
+ "@interface", "@implementation", "@end", "@property", "@synthesize", "@dynamic", "@class", "@protocol", "@optional", "@required", "@public", "@private", "@protected", "@package", "@selector", "@encode", "@synchronized", "@autoreleasepool", "@try", "@catch", "@finally", "@throw", "YES", "NO", "NULL", "nil", "self", "super", "id", "Class", "SEL", "IMP", "BOOL", "instancetype", "void", "char", "short", "int", "long", "float", "double", "signed", "unsigned", "const", "static", "extern", "auto", "register", "volatile", "inline", "restrict", "typedef", "sizeof", "typeof", "return", "if", "else", "for", "while", "do", "switch", "case", "default", "break", "continue", "goto", "struct", "union", "enum",
17
+ // TypeScript/JavaScript
18
+ "function", "const", "async", "await", "new", "this", "typeof", "instanceof", "export", "import", "from", "as", "default", "extends", "implements", "interface", "type", "namespace", "module", "declare", "abstract", "readonly", "keyof", "infer", "never", "unknown", "any", "void", "null", "undefined", "number", "string", "boolean", "symbol", "bigint", "object"
19
+ ]
20
+
21
+ static let typeKeywords = ["String", "Int", "Double", "Float", "Bool", "Array", "Dictionary", "Set", "Optional", "Any", "AnyObject", "Void", "Never", "Error", "Result", "URL", "Data", "Date", "UUID", "NSObject", "NSString", "NSNumber", "NSArray", "NSDictionary", "NSSet", "NSData", "NSDate", "NSURL", "CGFloat", "CGPoint", "CGSize", "CGRect", "UIView", "UIViewController", "UIButton", "UILabel", "UIImage", "UIColor", "NSView", "NSViewController", "NSButton", "NSTextField", "NSImage", "NSColor", "Promise", "Observable", "Subject"]
22
+
23
+ static func highlight(_ code: String) -> NSAttributedString {
24
+ let attributed = NSMutableAttributedString(string: code)
25
+ let fullRange = NSRange(location: 0, length: code.utf16.count)
26
+
27
+ // 基础样式 - SF Mono 字体 (Xcode 默认) - 增大到 14
28
+ let baseFont = NSFont(name: "SFMono-Regular", size: 14) ?? NSFont.monospacedSystemFont(ofSize: 14, weight: .regular)
29
+ // 普通文本 - Xcode 默认浅灰白色
30
+ let baseColor = NSColor(calibratedRed: 0.83, green: 0.84, blue: 0.85, alpha: 1.0) // #D4D4D6
31
+ attributed.addAttribute(.font, value: baseFont, range: fullRange)
32
+ attributed.addAttribute(.foregroundColor, value: baseColor, range: fullRange)
33
+
34
+ // 高亮注释 - Xcode 默认绿色 (系统 Green)
35
+ let commentColor = NSColor(calibratedRed: 0.42, green: 0.75, blue: 0.31, alpha: 1.0) // #6BBF4F
36
+ highlightPattern(attributed, pattern: "//.*$", color: commentColor, options: .anchorsMatchLines)
37
+ highlightPattern(attributed, pattern: "/\\*[\\s\\S]*?\\*/", color: commentColor)
38
+
39
+ // 高亮字符串 - Xcode 默认橙红色 (系统 Red/Orange 混合)
40
+ let stringColor = NSColor(calibratedRed: 0.98, green: 0.42, blue: 0.33, alpha: 1.0) // #FA6B54
41
+ highlightPattern(attributed, pattern: "\"(?:[^\"\\\\]|\\\\.)*\"", color: stringColor)
42
+ highlightPattern(attributed, pattern: "'(?:[^'\\\\]|\\\\.)*'", color: stringColor)
43
+ highlightPattern(attributed, pattern: "@\"(?:[^\"\\\\]|\\\\.)*\"", color: stringColor) // ObjC string
44
+
45
+ // 高亮数字 - Xcode 默认紫色 (Light Purple)
46
+ let numberColor = NSColor(calibratedRed: 0.69, green: 0.54, blue: 0.89, alpha: 1.0) // #B08AE3
47
+ highlightPattern(attributed, pattern: "\\b\\d+\\.?\\d*\\b", color: numberColor)
48
+
49
+ // 高亮类型关键字 - Xcode 默认青绿色 (Teal/Cyan)
50
+ let typeColor = NSColor(calibratedRed: 0.40, green: 0.84, blue: 0.89, alpha: 1.0) // #66D7E3
51
+ for keyword in typeKeywords {
52
+ highlightWord(attributed, word: keyword, color: typeColor)
53
+ }
54
+
55
+ // 高亮关键字 - Xcode 默认粉紫色 (Magenta/Pink)
56
+ let keywordColor = NSColor(calibratedRed: 0.98, green: 0.42, blue: 0.69, alpha: 1.0) // #FA6BB0
57
+ // Objective-C @ 前缀关键字 - 使用浅棕色
58
+ let atKeywordColor = NSColor(calibratedRed: 0.83, green: 0.60, blue: 0.45, alpha: 1.0) // #D49973
59
+ for keyword in keywords {
60
+ if keyword.hasPrefix("@") {
61
+ highlightPattern(attributed, pattern: "\(keyword)\\b", color: atKeywordColor)
62
+ } else {
63
+ highlightWord(attributed, word: keyword, color: keywordColor)
64
+ }
65
+ }
66
+
67
+ return attributed
68
+ }
69
+
70
+ private static func highlightPattern(_ attributed: NSMutableAttributedString, pattern: String, color: NSColor, options: NSRegularExpression.Options = []) {
71
+ guard let regex = try? NSRegularExpression(pattern: pattern, options: options) else { return }
72
+ let string = attributed.string
73
+ let range = NSRange(location: 0, length: string.utf16.count)
74
+
75
+ for match in regex.matches(in: string, options: [], range: range) {
76
+ attributed.addAttribute(.foregroundColor, value: color, range: match.range)
77
+ }
78
+ }
79
+
80
+ private static func highlightWord(_ attributed: NSMutableAttributedString, word: String, color: NSColor) {
81
+ let pattern = "\\b\(NSRegularExpression.escapedPattern(for: word))\\b"
82
+ highlightPattern(attributed, pattern: pattern, color: color)
83
+ }
84
+ }
85
+
86
+ // MARK: - 数据结构
87
+
88
+ struct SearchItem {
89
+ let title: String
90
+ let code: String
91
+ let explanation: String
92
+ let groupSize: Int
93
+ }
94
+
95
+ // MARK: - Combined Window Controller
96
+
97
+ class CombinedSearchWindowController: NSObject, NSTableViewDataSource, NSTableViewDelegate, NSSearchFieldDelegate {
98
+ private var panel: NSPanel!
99
+ private var tableView: NSTableView!
100
+ private var searchField: NSSearchField!
101
+ private var codeTextView: NSTextView!
102
+ private var allItems: [SearchItem] = []
103
+ private var filteredItems: [SearchItem] = []
104
+ private var selectedIndex: Int = -1
105
+ private var confirmed: Bool = false
106
+
107
+ enum Result {
108
+ case confirmed(Int) // 用户确认插入,返回选中的索引
109
+ case cancelled // 用户取消
110
+ }
111
+
112
+ func show(items: [SearchItem], keyword: String) -> Result {
113
+ self.allItems = items
114
+ self.filteredItems = items
115
+
116
+ let panelWidth: CGFloat = 1400
117
+ let panelHeight: CGFloat = 680
118
+
119
+ // 创建面板
120
+ panel = NSPanel(
121
+ contentRect: NSRect(x: 0, y: 0, width: panelWidth, height: panelHeight),
122
+ styleMask: [.titled, .closable, .resizable, .fullSizeContentView],
123
+ backing: .buffered,
124
+ defer: false
125
+ )
126
+ panel.title = "AutoSnippet 搜索结果"
127
+ panel.level = .floating
128
+ panel.center()
129
+ panel.minSize = NSSize(width: 1000, height: 500)
130
+ panel.titlebarAppearsTransparent = true
131
+ panel.isMovableByWindowBackground = true
132
+
133
+ // 模糊背景
134
+ let visualEffect = NSVisualEffectView(frame: NSRect(x: 0, y: 0, width: panelWidth, height: panelHeight))
135
+ visualEffect.material = .hudWindow
136
+ visualEffect.blendingMode = .behindWindow
137
+ visualEffect.state = .active
138
+ visualEffect.autoresizingMask = [.width, .height]
139
+ panel.contentView = visualEffect
140
+
141
+ let contentView = NSView(frame: NSRect(x: 0, y: 0, width: panelWidth, height: panelHeight))
142
+ contentView.autoresizingMask = [.width, .height]
143
+ visualEffect.addSubview(contentView)
144
+
145
+ // 顶部标题和搜索框
146
+ let titleLabel = NSTextField(labelWithString: "找到 \(items.count) 个匹配")
147
+ titleLabel.font = NSFont.systemFont(ofSize: 13, weight: .medium)
148
+ titleLabel.textColor = .secondaryLabelColor
149
+ titleLabel.frame = NSRect(x: 20, y: panelHeight - 50, width: 200, height: 20)
150
+ titleLabel.autoresizingMask = [.minYMargin]
151
+ contentView.addSubview(titleLabel)
152
+
153
+ // 搜索框
154
+ searchField = NSSearchField(frame: NSRect(x: 20, y: panelHeight - 85, width: 350, height: 28))
155
+ searchField.placeholderString = "过滤结果..."
156
+ searchField.stringValue = keyword
157
+ searchField.autoresizingMask = [.width, .minYMargin]
158
+ searchField.target = self
159
+ searchField.action = #selector(searchFieldDidChange)
160
+ searchField.font = NSFont.systemFont(ofSize: 13)
161
+ contentView.addSubview(searchField)
162
+
163
+ // 分隔线
164
+ let separator = NSBox(frame: NSRect(x: 0, y: panelHeight - 95, width: panelWidth, height: 1))
165
+ separator.boxType = .separator
166
+ separator.autoresizingMask = [.width, .minYMargin]
167
+ contentView.addSubview(separator)
168
+
169
+ // 左侧:列表区域
170
+ let listWidth: CGFloat = 360
171
+ let listScrollView = NSScrollView(frame: NSRect(x: 20, y: 70, width: listWidth, height: panelHeight - 175))
172
+ listScrollView.hasVerticalScroller = true
173
+ listScrollView.borderType = .noBorder
174
+ listScrollView.autoresizingMask = [.height, .minYMargin]
175
+ listScrollView.wantsLayer = true
176
+ listScrollView.layer?.cornerRadius = 8
177
+ listScrollView.layer?.masksToBounds = true
178
+ listScrollView.backgroundColor = NSColor(calibratedRed: 0.12, green: 0.12, blue: 0.14, alpha: 1.0)
179
+ // 使用现代 overlay 滚动条样式(悬浮自动隐藏)
180
+ listScrollView.scrollerStyle = .overlay
181
+ listScrollView.autohidesScrollers = true
182
+ listScrollView.scrollerKnobStyle = .light
183
+ // 强制刷新滚动条样式
184
+ listScrollView.flashScrollers()
185
+
186
+ tableView = NSTableView()
187
+ tableView.headerView = nil
188
+ tableView.rowHeight = 60
189
+ tableView.intercellSpacing = NSSize(width: 0, height: 2)
190
+ tableView.backgroundColor = NSColor(calibratedRed: 0.12, green: 0.12, blue: 0.14, alpha: 1.0)
191
+ tableView.usesAlternatingRowBackgroundColors = false
192
+ tableView.selectionHighlightStyle = .regular
193
+ tableView.target = self
194
+ tableView.action = #selector(tableViewClicked)
195
+ tableView.doubleAction = #selector(tableViewDoubleClicked)
196
+
197
+ let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("item"))
198
+ column.width = listWidth - 25
199
+ tableView.addTableColumn(column)
200
+
201
+ tableView.dataSource = self
202
+ tableView.delegate = self
203
+
204
+ listScrollView.documentView = tableView
205
+ contentView.addSubview(listScrollView)
206
+
207
+ // 右侧:代码预览区域
208
+ let codeX = 20 + listWidth + 15
209
+ let codeWidth = panelWidth - codeX - 20
210
+
211
+ let previewLabel = NSTextField(labelWithString: "代码预览")
212
+ previewLabel.font = NSFont.systemFont(ofSize: 11, weight: .semibold)
213
+ previewLabel.textColor = .secondaryLabelColor
214
+ previewLabel.frame = NSRect(x: codeX, y: panelHeight - 115, width: codeWidth, height: 16)
215
+ previewLabel.autoresizingMask = [.width, .minYMargin]
216
+ contentView.addSubview(previewLabel)
217
+
218
+ let codeScrollView = NSScrollView(frame: NSRect(x: codeX, y: 70, width: codeWidth, height: panelHeight - 195))
219
+ codeScrollView.hasVerticalScroller = true
220
+ codeScrollView.hasHorizontalScroller = true
221
+ codeScrollView.borderType = .noBorder
222
+ codeScrollView.autoresizingMask = [.width, .height]
223
+ codeScrollView.wantsLayer = true
224
+ codeScrollView.layer?.cornerRadius = 8
225
+ codeScrollView.layer?.masksToBounds = true
226
+ codeScrollView.backgroundColor = NSColor(calibratedRed: 0.12, green: 0.12, blue: 0.14, alpha: 1.0)
227
+ // 使用现代 overlay 滚动条样式(悬浮自动隐藏)
228
+ codeScrollView.scrollerStyle = .overlay
229
+ codeScrollView.autohidesScrollers = true
230
+ codeScrollView.scrollerKnobStyle = .light
231
+ // 启用平滑滚动
232
+ codeScrollView.usesPredominantAxisScrolling = false
233
+ codeScrollView.horizontalScrollElasticity = .automatic
234
+ codeScrollView.verticalScrollElasticity = .automatic
235
+ // 强制刷新滚动条样式
236
+ codeScrollView.flashScrollers()
237
+
238
+ codeTextView = NSTextView(frame: codeScrollView.bounds)
239
+ codeTextView.isEditable = false
240
+ codeTextView.isSelectable = true
241
+ codeTextView.backgroundColor = NSColor(calibratedRed: 0.12, green: 0.12, blue: 0.14, alpha: 1.0)
242
+ codeTextView.textContainerInset = NSSize(width: 16, height: 16)
243
+ codeTextView.isHorizontallyResizable = true
244
+ codeTextView.isVerticallyResizable = true
245
+ codeTextView.textContainer?.widthTracksTextView = false
246
+ codeTextView.textContainer?.heightTracksTextView = false
247
+ codeTextView.textContainer?.containerSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
248
+ codeTextView.autoresizingMask = []
249
+ codeTextView.textContainer?.lineFragmentPadding = 10
250
+ // 设置默认字体大小
251
+ codeTextView.font = NSFont(name: "SFMono-Regular", size: 14) ?? NSFont.monospacedSystemFont(ofSize: 14, weight: .regular)
252
+
253
+ if let textContainer = codeTextView.textContainer {
254
+ textContainer.size = NSSize(width: codeScrollView.bounds.width - 40, height: CGFloat.greatestFiniteMagnitude)
255
+ }
256
+
257
+ codeScrollView.documentView = codeTextView
258
+ contentView.addSubview(codeScrollView)
259
+
260
+ // 底部按钮
261
+ let buttonY: CGFloat = 20
262
+
263
+ let cancelButton = NSButton(title: "取消", target: self, action: #selector(cancelClicked))
264
+ cancelButton.bezelStyle = .rounded
265
+ cancelButton.frame = NSRect(x: panelWidth - 310, y: buttonY, width: 90, height: 32)
266
+ cancelButton.keyEquivalent = "\u{1b}"
267
+ cancelButton.autoresizingMask = [.minXMargin, .maxYMargin]
268
+ cancelButton.wantsLayer = true
269
+ cancelButton.layer?.cornerRadius = 6
270
+ contentView.addSubview(cancelButton)
271
+
272
+ let copyButton = NSButton(title: "复制代码", target: self, action: #selector(copyClicked))
273
+ copyButton.bezelStyle = .rounded
274
+ copyButton.frame = NSRect(x: panelWidth - 210, y: buttonY, width: 90, height: 32)
275
+ copyButton.autoresizingMask = [.minXMargin, .maxYMargin]
276
+ copyButton.wantsLayer = true
277
+ copyButton.layer?.cornerRadius = 6
278
+ contentView.addSubview(copyButton)
279
+
280
+ let okButton = NSButton(title: "立即插入", target: self, action: #selector(okClicked))
281
+ okButton.bezelStyle = .rounded
282
+ okButton.frame = NSRect(x: panelWidth - 110, y: buttonY, width: 90, height: 32)
283
+ okButton.keyEquivalent = "\r"
284
+ okButton.autoresizingMask = [.minXMargin, .maxYMargin]
285
+ okButton.wantsLayer = true
286
+ okButton.layer?.cornerRadius = 6
287
+ contentView.addSubview(okButton)
288
+
289
+ // 初始化显示
290
+ tableView.reloadData()
291
+ if !filteredItems.isEmpty {
292
+ tableView.selectRowIndexes(IndexSet(integer: 0), byExtendingSelection: false)
293
+ updatePreview(index: 0)
294
+ } else {
295
+ showEmptyPreview()
296
+ }
297
+
298
+ // 显示窗口
299
+ panel.alphaValue = 0
300
+ NSApp.activate(ignoringOtherApps: true)
301
+ panel.makeKeyAndOrderFront(nil)
302
+ panel.makeFirstResponder(searchField)
303
+
304
+ NSAnimationContext.runAnimationGroup({ context in
305
+ context.duration = 0.2
306
+ panel.animator().alphaValue = 1.0
307
+ })
308
+
309
+ NSApp.runModal(for: panel)
310
+
311
+ // 返回结果
312
+ if confirmed && selectedIndex >= 0 {
313
+ // 找到在原始数组中的索引
314
+ if let selected = filteredItems[safe: selectedIndex] {
315
+ if let originalIndex = allItems.firstIndex(where: { $0.title == selected.title }) {
316
+ return .confirmed(originalIndex)
317
+ }
318
+ }
319
+ return .confirmed(selectedIndex)
320
+ }
321
+ return .cancelled
322
+ }
323
+
324
+ // MARK: - Table View Data Source
325
+
326
+ func numberOfRows(in tableView: NSTableView) -> Int {
327
+ return filteredItems.count
328
+ }
329
+
330
+ func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
331
+ let item = filteredItems[row]
332
+
333
+ let cellView = NSTableCellView()
334
+ cellView.wantsLayer = true
335
+
336
+ // 标题 - 支持两行显示
337
+ let textField = NSTextField(labelWithString: item.title)
338
+ textField.font = NSFont.systemFont(ofSize: 14, weight: .medium)
339
+ textField.textColor = .labelColor
340
+ textField.lineBreakMode = .byWordWrapping // 改为自动换行
341
+ textField.maximumNumberOfLines = 2 // 最多显示两行
342
+ textField.frame = NSRect(x: 12, y: 24, width: tableView.bounds.width - 24, height: 36) // 增加高度到36,调整y位置
343
+ cellView.addSubview(textField)
344
+
345
+ // 说明
346
+ var subtitleParts: [String] = []
347
+ if item.groupSize > 1 {
348
+ subtitleParts.append("同类\(item.groupSize)")
349
+ }
350
+ if !item.explanation.isEmpty {
351
+ subtitleParts.append(item.explanation)
352
+ }
353
+ let subtitle = subtitleParts.joined(separator: " · ")
354
+ if !subtitle.isEmpty {
355
+ let subtitleField = NSTextField(labelWithString: subtitle)
356
+ subtitleField.font = NSFont.systemFont(ofSize: 11, weight: .regular)
357
+ subtitleField.textColor = .secondaryLabelColor
358
+ subtitleField.lineBreakMode = .byTruncatingTail
359
+ subtitleField.frame = NSRect(x: 12, y: 8, width: tableView.bounds.width - 24, height: 16) // 调整y位置为8
360
+ cellView.addSubview(subtitleField)
361
+ }
362
+
363
+ return cellView
364
+ }
365
+
366
+ func tableViewSelectionDidChange(_ notification: Notification) {
367
+ let row = tableView.selectedRow
368
+ if row >= 0 {
369
+ selectedIndex = row
370
+ updatePreview(index: row)
371
+ }
372
+ }
373
+
374
+ // MARK: - Actions
375
+
376
+ @objc func tableViewClicked() {
377
+ let row = tableView.clickedRow
378
+ if row >= 0 {
379
+ selectedIndex = row
380
+ updatePreview(index: row)
381
+ }
382
+ }
383
+
384
+ @objc func tableViewDoubleClicked() {
385
+ let row = tableView.clickedRow
386
+ if row >= 0 {
387
+ selectedIndex = row
388
+ confirmed = true
389
+ panel.close()
390
+ NSApp.stopModal()
391
+ }
392
+ }
393
+
394
+ @objc func searchFieldDidChange() {
395
+ let query = searchField.stringValue.lowercased().trimmingCharacters(in: .whitespaces)
396
+
397
+ if query.isEmpty {
398
+ filteredItems = allItems
399
+ } else {
400
+ filteredItems = allItems.filter { $0.title.lowercased().contains(query) }
401
+ }
402
+
403
+ tableView.reloadData()
404
+
405
+ if !filteredItems.isEmpty {
406
+ tableView.selectRowIndexes(IndexSet(integer: 0), byExtendingSelection: false)
407
+ selectedIndex = 0
408
+ updatePreview(index: 0)
409
+ } else {
410
+ showEmptyPreview()
411
+ }
412
+ }
413
+
414
+ @objc func cancelClicked() {
415
+ confirmed = false
416
+ panel.close()
417
+ NSApp.stopModal()
418
+ }
419
+
420
+ @objc func copyClicked() {
421
+ if selectedIndex >= 0, let item = filteredItems[safe: selectedIndex] {
422
+ let pureCode = extractPureCode(item.code)
423
+ let pasteboard = NSPasteboard.general
424
+ pasteboard.clearContents()
425
+ pasteboard.setString(pureCode, forType: .string)
426
+ print("✅ 代码已复制到剪贴板")
427
+ // 复制后也关闭窗口
428
+ confirmed = true
429
+ panel.close()
430
+ NSApp.stopModal()
431
+ }
432
+ }
433
+
434
+ @objc func okClicked() {
435
+ if selectedIndex >= 0 {
436
+ confirmed = true
437
+ // 复制代码到剪贴板
438
+ if let item = filteredItems[safe: selectedIndex] {
439
+ let pureCode = extractPureCode(item.code)
440
+ let pasteboard = NSPasteboard.general
441
+ pasteboard.clearContents()
442
+ pasteboard.setString(pureCode, forType: .string)
443
+ }
444
+ panel.close()
445
+ NSApp.stopModal()
446
+ }
447
+ }
448
+
449
+ // MARK: - Helper Methods
450
+
451
+ private func updatePreview(index: Int) {
452
+ guard let item = filteredItems[safe: index] else {
453
+ showEmptyPreview()
454
+ return
455
+ }
456
+
457
+ let processedCode = item.code.replacingOccurrences(of: "\\n", with: "\n")
458
+ let highlightedCode = SyntaxHighlighter.highlight(processedCode)
459
+ codeTextView.textStorage?.setAttributedString(highlightedCode)
460
+ }
461
+
462
+ private func showEmptyPreview() {
463
+ let emptyText = NSAttributedString(
464
+ string: "无内容",
465
+ attributes: [
466
+ .font: NSFont.systemFont(ofSize: 14),
467
+ .foregroundColor: NSColor.tertiaryLabelColor
468
+ ]
469
+ )
470
+ codeTextView.textStorage?.setAttributedString(emptyText)
471
+ }
472
+
473
+ private func extractPureCode(_ code: String) -> String {
474
+ var lines = code.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
475
+
476
+ // 移除首尾的代码块标记(```swift、```等)
477
+ while !lines.isEmpty && (lines.first?.trimmingCharacters(in: .whitespaces).starts(with: "```") ?? false) {
478
+ lines.removeFirst()
479
+ }
480
+ while !lines.isEmpty && (lines.last?.trimmingCharacters(in: .whitespaces).starts(with: "```") ?? false) {
481
+ lines.removeLast()
482
+ }
483
+
484
+ return lines.joined(separator: "\n")
485
+ }
486
+ }
487
+
488
+ // MARK: - Array Safe Subscript
489
+
490
+ extension Array {
491
+ subscript(safe index: Int) -> Element? {
492
+ return indices.contains(index) ? self[index] : nil
493
+ }
494
+ }
@@ -9,6 +9,7 @@
9
9
  ## 知识库与结构
10
10
  - 知识库根目录:`AutoSnippet/`(用户项目可通过 boxspec `knowledgeBase.dir` 自定义)
11
11
  - Recipe:`AutoSnippet/recipes/*.md`(Markdown + Frontmatter + Snippet + Usage Guide)
12
+ - **Project Skills**:`AutoSnippet/skills/<name>/SKILL.md`(项目级 AI 知识增强文档,跟随 Git)
12
13
  - constitution.yaml:`AutoSnippet/constitution.yaml`(权限宪法:角色 + 能力 + 治理规则)
13
14
  - 运行时 DB:`.autosnippet/autosnippet.db`(SQLite,recipes/candidates/snippets 索引缓存)
14
15
  - 向量索引:`.autosnippet/context/`(`asd embed` 生成)
@@ -51,14 +52,21 @@
51
52
  - `autosnippet_submit_draft_recipes` — 解析草稿 Markdown 文件
52
53
  - `autosnippet_enrich_candidates` — AI 补全缺失语义字段
53
54
 
54
- ### 项目扫描
55
+ ### 项目扫描 & 冷启动
55
56
  - `autosnippet_get_targets` / `autosnippet_get_target_files` / `autosnippet_get_target_metadata`
56
57
  - `autosnippet_scan_project` — 轻量探查(文件清单 + Guard 审计)
57
- - `autosnippet_bootstrap_knowledge` — 冷启动知识库初始化(9 大知识维度)
58
+ - `autosnippet_bootstrap_knowledge` — 冷启动知识库初始化(9 维度 + 自动生成 Project Skills)
59
+ - `autosnippet_bootstrap_refine` — Bootstrap 候选 AI 润色(summary/insight/relations)
58
60
 
59
61
  ### Guard & 治理
60
62
  - `autosnippet_guard_check` / `autosnippet_guard_audit_files` / `autosnippet_compliance_report`
61
63
 
64
+ ### Skills
65
+ - `autosnippet_list_skills` — 列出所有可用 Skills(内置 + 项目级)
66
+ - `autosnippet_load_skill` — 加载 Skill 完整文档
67
+ - `autosnippet_create_skill` — 创建项目级 Skill(写入 `AutoSnippet/skills/`)
68
+ - `autosnippet_suggest_skills` — 基于使用模式推荐创建 Skill
69
+
62
70
  ### 其它
63
71
  - `autosnippet_health` / `autosnippet_capabilities` / `autosnippet_confirm_usage`
64
72
 
@@ -67,10 +75,19 @@
67
75
  - Frontmatter 必填字段(7):`title`、`trigger`(@开头)、`category`(8 选 1)、`language`、`summary_cn`、`summary_en`、`headers`。
68
76
  - Usage Guide 必须用 `###` 三级标题分段,列表式书写,禁止一行文字墙。
69
77
 
78
+ ## Project Skills
79
+ - **发现**:`autosnippet_list_skills` — 列出所有可用 Skills(内置 + 项目级)
80
+ - **加载**:`autosnippet_load_skill(skillName)` — 获取 Skill 完整操作指南
81
+ - **创建**:`autosnippet_create_skill(name, description, content)` — 创建项目级 Skill
82
+ - **推荐**:`autosnippet_suggest_skills` — 基于使用模式推荐创建 Skill
83
+ - **Bootstrap 自动生成**:冷启动 Phase 5.5 自动生成 4 个 Project Skills(code-standard, architecture, project-profile, agent-guidelines)
84
+ - **优先级**:项目级 Skill 同名覆盖内置;宏观知识查 Skill,微观代码模式查 Recipe
85
+
70
86
  ## 推荐工作流
71
87
  - **查找**:`autosnippet_search`(推荐)或 `autosnippet_context_search`(上下文感知)。
72
88
  - **产出候选**:`autosnippet_validate_candidate` 预校验 → `autosnippet_submit_candidate` 提交。
73
- - **批量扫描**:`autosnippet_bootstrap_knowledge`(冷启动)→ analysisFramework 逐维度分析 → `autosnippet_submit_candidates`。
89
+ - **冷启动**:`autosnippet_bootstrap_knowledge` `autosnippet_enrich_candidates` `autosnippet_bootstrap_refine` 逐 Target 深入 → `autosnippet_submit_candidates`。
90
+ - **Skills 创建**:`autosnippet_suggest_skills` 分析 → `autosnippet_create_skill` 固化知识。
74
91
  - **采纳反馈**:`autosnippet_confirm_usage`(记录使用量影响排序权重)。
75
92
 
76
93
  ## 与 Cursor 规则联动
@@ -41,9 +41,19 @@ Recipes are classified into three kinds:
41
41
  - **Retry policy**: If an MCP call fails, do not retry within the same agent turn; fall back to static docs or already-read context.
42
42
  - **Adoption tracking**: Tool `autosnippet_confirm_usage` records that a Recipe was adopted or applied (affects usage stats and authority ranking). Show it when the user explicitly adopts a recipe, not immediately after presenting one.
43
43
  - **Batch scan**: Ask Cursor in natural language (e.g. "scan all targets in this project", "batch extract candidates") to trigger the batch-scan workflow. The workflow calls `autosnippet_get_targets` → `autosnippet_get_target_files` → extract per file → `autosnippet_submit_candidates` automatically.
44
- - **Cold start**: Use `autosnippet_bootstrap_knowledge` for full project knowledge initialization covering 9 dimensions (project norms, usage patterns, architecture, code patterns, best practices, bug fixes, knowledge graph, library characteristics, agent dev notes).
44
+ - **Cold start**: Use `autosnippet_bootstrap_knowledge` for full project knowledge initialization covering 9 dimensions. Phase 5.5 auto-generates 4 Project Skills (code-standard, architecture, project-profile, agent-guidelines) to `AutoSnippet/skills/`.
45
45
 
46
- ## MCP tools (31 total)
46
+ ## Project Skills
47
+
48
+ - **Skills 目录**: `AutoSnippet/skills/<name>/SKILL.md` — 跟随项目 Git 管理
49
+ - **发现**: `autosnippet_list_skills` — 列出所有可用 Skills(内置 + 项目级)
50
+ - **加载**: `autosnippet_load_skill(skillName)` — 获取 Skill 完整文档
51
+ - **创建**: `autosnippet_create_skill(name, description, content)` — 创建项目级 Skill
52
+ - **推荐**: `autosnippet_suggest_skills` — 基于使用模式推荐创建 Skill
53
+ - **优先级**: 项目级 Skill 同名覆盖内置 Skill;Skill > Recipe(宏观知识优先查 Skill)
54
+ - **Bootstrap 自动生成**: 冷启动会自动生成 `project-code-standard`、`project-architecture`、`project-profile`、`project-agent-guidelines` 四个 Project Skills
55
+
56
+ ## MCP tools (36 total)
47
57
 
48
58
  ### Search (prefer search / context_search)
49
59
  - `autosnippet_search` — Unified search (auto: BM25 + semantic fusion)
@@ -66,14 +76,21 @@ Recipes are classified into three kinds:
66
76
  - `autosnippet_submit_draft_recipes` — Parse draft Markdown files
67
77
  - `autosnippet_enrich_candidates` — AI-fill missing semantic fields
68
78
 
69
- ### Project scanning
79
+ ### Project scanning & bootstrap
70
80
  - `autosnippet_get_targets` / `autosnippet_get_target_files` / `autosnippet_get_target_metadata`
71
81
  - `autosnippet_scan_project` — Lightweight probe (file list + Guard audit)
72
- - `autosnippet_bootstrap_knowledge` — Cold-start knowledge initialization
82
+ - `autosnippet_bootstrap_knowledge` — Cold-start knowledge initialization (9 dimensions + auto Project Skills)
83
+ - `autosnippet_bootstrap_refine` — AI refinement for bootstrap candidates (summary/insight/relations)
73
84
 
74
85
  ### Guard & governance
75
86
  - `autosnippet_guard_check` / `autosnippet_guard_audit_files` / `autosnippet_compliance_report`
76
87
 
88
+ ### Skills
89
+ - `autosnippet_list_skills` — List all available Skills (builtin + project)
90
+ - `autosnippet_load_skill` — Load a Skill's full document
91
+ - `autosnippet_create_skill` — Create a project-level Skill in `AutoSnippet/skills/`
92
+ - `autosnippet_suggest_skills` — Recommend Skills based on usage patterns
93
+
77
94
  ### Other
78
95
  - `autosnippet_health` / `autosnippet_capabilities` / `autosnippet_confirm_usage`
79
96
 
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: AutoSnippet Project Skills — 项目级知识增强文档索引与使用指引
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # AutoSnippet Project Skills
7
+
8
+ Project Skills 是针对当前项目自动生成或手动创建的 AI Agent 知识增强文档。
9
+ 它们存放在 `AutoSnippet/skills/` 目录下,跟随项目 Git 管理。
10
+
11
+ ## 如何发现与加载
12
+
13
+ 1. **列出所有 Skills**:调用 `autosnippet_list_skills` 查看当前可用的 Skills(含内置 + 项目级)
14
+ 2. **加载完整内容**:调用 `autosnippet_load_skill(skillName)` 获取具体 Skill 的完整文档
15
+ 3. **按需加载**:不确定该用哪个 Skill 时,先加载 `autosnippet-intent` 做意图路由
16
+ 4. **创建 Skill**:调用 `autosnippet_create_skill(name, description, content)` 创建项目级 Skill
17
+ 5. **Skill 推荐**:调用 `autosnippet_suggest_skills` 获取基于使用模式的 Skill 创建建议
18
+
19
+ ## Bootstrap 自动生成的 Project Skills
20
+
21
+ 冷启动(`autosnippet_bootstrap_knowledge`)Phase 5.5 会自动为 4 个宏观维度生成 Project Skills:
22
+
23
+ | Skill | 维度 | 说明 |
24
+ |-------|------|------|
25
+ | `project-code-standard` | 代码规范 | 命名约定、文件组织规范 |
26
+ | `project-architecture` | 架构模式 | 分层架构、模块边界、依赖图 |
27
+ | `project-profile` | 项目特征 | 技术栈、模块结构、代码指标 |
28
+ | `project-agent-guidelines` | Agent 约束 | 强制规则、TODO/FIXME、WARNING |
29
+
30
+ 生成后的 Skill 文件位于 `AutoSnippet/skills/<name>/SKILL.md`,自动跟随 Git。
31
+ 调用 `autosnippet_load_skill` 可加载完整内容。
32
+
33
+ ## 推荐工作流
34
+
35
+ 冷启动完整流程:
36
+ 1. `autosnippet_bootstrap_knowledge` — 9 维度分析 + 自动生成 Project Skills
37
+ 2. `autosnippet_enrich_candidates` — 补全缺失语义字段
38
+ 3. `autosnippet_bootstrap_refine` — AI 润色 summary/insight/relations
39
+ 4. 逐 Target 深入分析 → `autosnippet_submit_candidates`
40
+
41
+ ## 使用优先级
42
+
43
+ - **Project Skills > 内置 Skills**:同名时项目级覆盖内置
44
+ - **Skill > Recipe**:宏观架构/规范知识优先查 Skill,微观代码模式查 Recipe
45
+ - **Skills 是只读参考**:不要直接修改 SKILL.md,应通过 `autosnippet_create_skill` 工具创建或更新