autosnippet 2.12.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,13 +31,15 @@ AI 编码助手生成的代码往往脱离项目上下文——不知道团队
31
31
 
32
32
  | 概念 | 说明 |
33
33
  |------|------|
34
- | **Recipe** | 知识库的基本单元——一段代码模式 + 使用说明 + 元数据,存储在 SQLite 中,可导出为 Markdown 到 `AutoSnippet/recipes/` |
34
+ | **Recipe** | 知识库的基本单元——一段代码模式 + 使用说明 + 元数据,以 Markdown 文件(`AutoSnippet/recipes/*.md`)为 Source of Truth,SQLite 作为检索缓存 |
35
35
  | **Candidate** | 待审核的候选知识——来自 AI 扫描、手动提交、剪贴板或 Bootstrap 冷启动,经 Dashboard 人工审核后晋升为 Recipe |
36
- | **Dashboard** | Web 管理后台(`asd ui`),10 个功能视图:Recipes / Candidates / AI Chat / SPM Explorer / 知识图谱 / 依赖图 / Guard / Skills / Xcode 模拟器 / Help |
36
+ | **Dashboard** | Web 管理后台(`asd ui`),10+ 功能视图:Recipes / Candidates / Knowledge / AI Chat / SPM Explorer / 知识图谱 / 依赖图 / Guard / Skills / Xcode 模拟器 / Help |
37
37
  | **Guard** | 代码审查引擎——基于知识库中的规则对代码做合规检查,支持文件 / Target / 项目三级范围 |
38
38
  | **Skills** | 13 个 Cursor Agent 技能包——覆盖候选生成、冷启动、Guard 审计、意图路由、生命周期管理等场景 |
39
- | **Bootstrap** | 冷启动引擎——自动扫描 SPM Target + AST 分析,9 维度启发式提取代码模式,AI 精炼后生成 Candidate |
40
- | **ChatAgent** | Agent 协作对话系统(Analyst + Producer),支持项目感知、信心信号、组合工具链和跨对话轻量记忆 |
39
+ | **Bootstrap** | 冷启动引擎——自动扫描 SPM Target + AST 分析,9 维度启发式提取代码模式,AI 精炼后生成 Candidate;支持增量模式(IncrementalBootstrap),文件变更检测 + 受影响维度重跑 |
40
+ | **Agent Memory** | 四层记忆架构——WorkingMemory(会话级)→ EpisodicMemory(跨维度共享)→ ProjectSemanticMemory(项目级永久语义记忆)→ ToolResultCache(工具结果去重),支撑 Bootstrap 和 ChatAgent 的跨对话知识积累 |
41
+ | **ChatAgent** | 多 Agent 协作对话系统(Analyst + Producer),支持项目感知、信心信号、组合工具链和跨对话记忆 |
42
+ | **CodeEntityGraph** | 代码实体关系图谱——基于 AST 解析构建 class / protocol / category / module 间的继承、遵循、依赖、数据流等关系,供 Bootstrap 和搜索使用 |
41
43
 
42
44
  ## 快速开始
43
45
 
@@ -63,36 +65,102 @@ asd status # 自检项目根、AI Provider、索引、Dashboard
63
65
 
64
66
  ## 工作流
65
67
 
66
- ### 知识沉淀闭环
68
+ ### 端到端使用流程
69
+
70
+ 从零开始到知识库持续运转的完整路径——以 Cursor 为例:
71
+
72
+ ```
73
+ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
74
+ │ ① 初始化 │──→│ ② 冷启动 │──→│ ③ 逐Target │──→│ ④ 审核发布 │──→│ ⑤ 注入 IDE │
75
+ │ asd setup │ │ Bootstrap │ │ 扫描 │ │ Dashboard │ │ Cursor │
76
+ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘
77
+
78
+ ┌───────────────────────────────────────────────────────────────────────┘
79
+
80
+ ┌─────────────┐ ┌─────────────┐
81
+ │ ⑥ AI 按 │──→│ ⑦ 新模式 │──→ 回到 ③
82
+ │ 规范生成 │ │ 再沉淀 │
83
+ └─────────────┘ └─────────────┘
84
+ ```
85
+
86
+ **① 初始化项目**
87
+
88
+ ```bash
89
+ cd /path/to/your-project
90
+ asd setup # 创建 AutoSnippet/ 目录、数据库、配置文件
91
+ asd install:full # 安装 Cursor Skills (13个) + MCP 配置 + Cursor Rules
92
+ asd ui # 启动 Dashboard + API 服务 + 文件监听
93
+ ```
94
+
95
+ 完成后,你的项目目录下会生成 `.cursor/mcp.json`、`.cursor/skills/`、`.cursor/rules/` 等集成文件,Cursor 已经可以通过 MCP 与 AutoSnippet 通信。
96
+
97
+ **② 冷启动——全局扫描建立基线**
98
+
99
+ 在 Cursor 中用自然语言触发冷启动:
100
+
101
+ ```
102
+ 你:「对项目做一次全量冷启动,提取所有代码模式」
103
+ ```
104
+
105
+ Bootstrap 引擎自动完成:SPM Target 发现 → 文件收集 → AST 结构分析 → 9 维度启发式扫描(架构 / 命名 / 网络 / 数据流 / 错误处理等) → Analyst Agent 深度分析 → Producer Agent 格式化提交。
106
+
107
+ 冷启动结束后,Dashboard Candidates 页面会出现数十条候选知识条目。
108
+
109
+ **③ 逐 Target 精细扫描**
110
+
111
+ 冷启动覆盖全局,但每个 Target 的独特模式需要针对性提取:
67
112
 
68
113
  ```
69
- ┌─────────────────────────────────────────────────────────────┐
70
- │ │
71
- │ ① 扫描提取 ② 人工审核 │
72
- │ Cursor AI 扫描 Target ──→ Candidates ──→ Recipe 入库 │
73
- │ asd ais <Target> Dashboard │
74
- │ Bootstrap 冷启动 / 剪贴板 │
75
- │ │
76
- │ ③ AI 按规范生成 ④ 持续沉淀 │
77
- │ Cursor/Copilot 检索 Recipe ──→ 生成代码 ──→ 好代码再入库 │
78
- │ MCP 工具 / Xcode Snippet │
79
- │ │
80
- └─────────────────────────────────────────────────────────────┘
114
+ 你:「扫描 NetworkModule 这个 Target,把里面的请求封装模式提取出来」
115
+ 你:「分析 UIComponents Target 的自定义控件实现」
81
116
  ```
82
117
 
83
- ### 三种使用方式
118
+ Cursor 调用 `get_targets` → `get_target_files` → 逐文件 AI 分析 → `submit_knowledge_batch`,将发现的代码模式作为 Candidate 提交到知识库。你可以逐个 Target 推进,每个 Target 的扫描结果会独立进入审核队列。
119
+
120
+ **④ 审核发布——人工把关质量**
121
+
122
+ 打开 Dashboard(`asd ui`),进入 **Candidates** 页面:
123
+
124
+ - 候选按置信度排序,高信心的排在前面
125
+ - 点击候选卡片展开详情:查看代码片段、AI 分析理由、来源文件
126
+ - **接受** → 候选晋升为 Recipe,进入活跃知识库
127
+ - **编辑后接受** → 审核时可修改标题、描述、代码、标签
128
+ - **拒绝** → 低质量或重复的候选直接归档
129
+
130
+ 批量操作:一键接受所有高信心候选,或一键拒绝低质量条目。
131
+
132
+ **⑤ 注入 Cursor——知识即规则**
84
133
 
85
- **① Cursor AI(推荐)**:自然语言驱动,Cursor 通过 13 个 Skills + 38 个 MCP 工具与知识库交互。
134
+ Recipe 发布后,Cursor 立即可以通过三种通道获取知识:
135
+
136
+ | 通道 | 机制 | 时效 |
137
+ |------|------|------|
138
+ | **MCP 工具检索** | Cursor 通过 `autosnippet_search` 等 38 个工具实时查询知识库 | 实时 |
139
+ | **Cursor Rules** | `asd upgrade` 将 Recipe 导出为 `.cursor/rules/autosnippet-*.mdc` 文件 | 手动触发 |
140
+ | **Agent Skills** | 13 个 Skill 文档引导 Cursor 在正确场景自动调用知识库 | 常驻 |
141
+
142
+ **⑥ AI 按规范生成代码**
143
+
144
+ 当你在 Cursor 中编写代码时,AI 会自动检索知识库:
86
145
 
87
146
  ```
88
- 用户:「扫描 NetworkModule 这个 Target,提取最佳实践」
89
- Cursor → get_targetsget_target_files 逐文件提取 submit_knowledge_batch
90
- Dashboard Candidates 页面审核 → 保存为 Recipe
147
+ 你:「写一个网络请求方法,获取用户信息」
148
+ Cursor → 检索知识库命中 Recipe: "Network Layer Pattern"
149
+ 按团队封装的 NetworkManager 生成代码,而非裸调 URLSession
91
150
  ```
92
151
 
93
- **② Dashboard Web UI**:可视化管理一切——Recipe 编辑、Candidate 审核、AI Chat 对话、知识图谱、SPM 探索、Guard 审查、冷启动向导。
152
+ Guard 规则也在同步工作——如果生成的代码违反了知识库中的规则(如 `kind=rule` 的条目),会实时提醒和纠正。
153
+
154
+ **⑦ 持续沉淀——知识库越用越好**
94
155
 
95
- **③ 命令行 CLI**:`asd` 命令覆盖全部操作,适合自动化和脚本集成。
156
+ 日常开发中发现新的代码模式或团队约定?随时沉淀:
157
+
158
+ - 在 Cursor 中:`「把这段 error handling 模式提取为知识库条目」`
159
+ - 在 Xcode 中:代码注释写 `// as:create` 然后 `⌘S`
160
+ - 在 Dashboard 中:AI Chat 对话式提交
161
+ - 通过剪贴板:复制代码后自动检测并建议入库
162
+
163
+ 知识库形成飞轮:**代码沉淀 → Recipe 增长 → AI 生成质量提升 → 团队效率提高 → 更多代码模式沉淀**。
96
164
 
97
165
  ## Dashboard
98
166
 
@@ -100,13 +168,14 @@ Cursor → get_targets → get_target_files → 逐文件提取 → submit_knowl
100
168
 
101
169
  ![Dashboard](./resources/ASImage02.png)
102
170
 
103
- **10 个功能视图**:
171
+ **功能视图**:
104
172
 
105
173
  | 视图 | 说明 |
106
174
  |------|------|
107
175
  | **Recipes** | 浏览、编辑、发布、弃用知识条目;详情抽屉支持 Markdown 编辑与关联关系管理 |
108
176
  | **Candidates** | 审核 AI / 手动提交的候选,一键入库或批量操作,支持 AI 润色 |
109
- | **AI Chat** | ChatAgent 智能对话(Analyst 分析 + Producer 生产),项目感知 + 跨对话记忆 |
177
+ | **Knowledge** | 统一知识条目浏览(V3 格式),双列卡片布局,代码预览 + 详情抽屉 |
178
+ | **AI Chat** | ChatAgent 智能对话(Analyst 分析 + Producer 生产),项目感知 + 四层记忆架构 |
110
179
  | **SPM Explorer** | SPM Target 浏览与扫描,候选 vs Recipe 对比抽屉,头文件编辑 |
111
180
  | **Dep Graph** | 依赖关系图可视化 |
112
181
  | **Knowledge Graph** | Recipe 关联关系的知识图谱可视化(依赖 / 扩展 / 冲突等),AI 自动发现关系,按 category 分组 |
@@ -123,9 +192,9 @@ Cursor → get_targets → get_target_files → 逐文件提取 → submit_knowl
123
192
 
124
193
  AutoSnippet 为 Cursor 提供完整的 MCP + Skills 集成:
125
194
 
126
- - **38 个 MCP 工具**:搜索(4 种模式)、Guard 检查、候选提交 / 校验 / 查重、知识图谱查询、Bootstrap 冷启动、Skills 管理等
195
+ - **38 个 MCP 工具**:搜索(4 种模式)、Guard 检查、候选提交 / 校验 / 查重、知识图谱查询、Bootstrap 冷启动、Skills 管理、知识生命周期等
127
196
  - **13 个 Agent Skills**:`autosnippet-candidates`、`autosnippet-guard`、`autosnippet-coldstart`、`autosnippet-intent` 等,引导 AI 正确使用工具
128
- - **写操作 Gateway 保护**:9 个写操作经过权限 / 宪法 / 审计三重检查
197
+ - **写操作 Gateway 保护**:11 个写操作经过权限 / 宪法 / 审计三重检查
129
198
 
130
199
  ```bash
131
200
  asd install:cursor-skill --mcp # 安装 Skills + MCP 配置
@@ -171,20 +240,21 @@ asd install:vscode-copilot # 配置 MCP 和 Copilot 指令
171
240
 
172
241
  ## MCP 工具一览
173
242
 
174
- 38 个 MCP 工具按功能分组(省略了 **autosnippet_** 前缀):
243
+ 39 个 MCP 工具按功能分组(省略了 **autosnippet_** 前缀):
175
244
 
176
245
  | 分类 | 工具 |
177
246
  |------|------|
178
247
  | **系统** | `health`、`capabilities` |
179
248
  | **搜索** | `search`(统合入口)、`context_search`(4 层漏斗)、`keyword_search`、`semantic_search` |
180
249
  | **Recipe 浏览** | `list_recipes`、`get_recipe`、`list_rules`、`patterns`、`list_facts`、`recipe_insights`、`confirm_usage` |
181
- | **候选管理** | `validate_candidate`、`check_duplicate`、`submit_knowledge`、`submit_knowledge_batch`、`submit_knowledge_batch`、`enrich_candidates` |
250
+ | **候选管理** | `validate_candidate`、`check_duplicate`、`submit_knowledge`、`submit_knowledge_batch`、`enrich_candidates` |
251
+ | **开发文档** | `save_document` |
182
252
  | **知识图谱** | `graph_query`、`graph_impact`、`graph_path`、`graph_stats` |
183
253
  | **项目结构** | `get_targets`、`get_target_files`、`get_target_metadata` |
184
254
  | **Guard** | `guard_check`、`guard_audit_files`、`scan_project` |
185
255
  | **冷启动** | `bootstrap_knowledge`、`bootstrap_refine` |
186
256
  | **Skills** | `list_skills`、`load_skill`、`create_skill`、`delete_skill`、`update_skill`、`suggest_skills` |
187
- | **治理** | `compliance_report` |
257
+ | **知识管理** | `knowledge_lifecycle`、`compliance_report` |
188
258
 
189
259
  ## 配置
190
260
 
@@ -237,18 +307,23 @@ your-project/
237
307
  │ AutoSnippet Core │
238
308
  │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
239
309
  │ │ Gateway │ │ ChatAgent│ │ Bootstrap│ │ Dashboard │ │
240
- │ │ (权限/ │ │ (Dual │ │ (SPM + │ │ (React 19 +│ │
241
- │ │ 宪法/ │ │ Agent + │ │ AST + │ │ Vite 6 + │ │
242
- │ │ 审计) │ │ Memory) │ │ AI) │ │ Tailwind) │ │
310
+ │ │ (权限/ │ │ (Dual │ │ (Incremen│ │ (React 19 +│ │
311
+ │ │ 宪法/ │ │ Agent + │ │ tal + │ │ Vite 6 + │ │
312
+ │ │ 审计) │ │ Memory) │ │ AST+AI) │ │ Tailwind) │ │
243
313
  │ └──────────┘ └──────────┘ └──────────┘ └─────────────┘ │
244
314
  │ ┌────────────────────────────────────────────────────┐ │
245
- │ │ 13 Services: Recipe Candidate │ Guard │ Search │
315
+ │ │ Agent Memory (4-Tier): │ │
316
+ │ │ WorkingMemory → EpisodicMemory → │ │
317
+ │ │ ProjectSemanticMemory → ToolResultCache │ │
318
+ │ └────────────────────────────────────────────────────┘ │
319
+ │ ┌────────────────────────────────────────────────────┐ │
320
+ │ │ 14 Services: Recipe │ Candidate │ Guard │ Search │ │
246
321
  │ │ Knowledge Graph │ SPM │ Bootstrap │ Chat │ Skills │ │
247
- │ │ Quality │ Context │ Automation │ Snippet │ │
322
+ │ │ Quality │ Context │ Automation │ Snippet Cursor │
248
323
  │ └────────────────────────────────────────────────────┘ │
249
324
  │ ┌────────────────────────────────────────────────────┐ │
250
325
  │ │ Core: Gateway │ Constitution │ Permission │ AST │ │
251
- │ │ Session │ Capability │ │
326
+ │ │ Session │ Capability CodeEntityGraph │
252
327
  │ └────────────────────────────────────────────────────┘ │
253
328
  │ ┌────────────────────────────────────────────────────┐ │
254
329
  │ │ Storage: SQLite (better-sqlite3) + 向量索引 │ │
@@ -267,7 +342,7 @@ your-project/
267
342
  | **前端** | React 19 + TypeScript 5 + Vite 6 + Tailwind CSS 4 |
268
343
  | **AI** | Gemini / OpenAI / Claude(通过 AiProvider 抽象层) |
269
344
  | **AST** | Tree-sitter(Swift / ObjC) |
270
- | **搜索** | 4 层检索管线:InvertedIndex → CoarseRanker → MultiSignalRanker → RetrievalFunnel |
345
+ | **搜索** | 5 层检索管线:InvertedIndex → Semantic Rerank → CoarseRanker (E-E-A-T) → MultiSignalRanker → RetrievalFunnel |
271
346
  | **实时通信** | WebSocket(Socket.IO),Dashboard 实时更新 |
272
347
  | **动画** | Framer Motion |
273
348
  | **代码高亮** | Prism.js + react-syntax-highlighter |
package/bin/cli.js CHANGED
@@ -221,6 +221,150 @@ program
221
221
  }
222
222
  });
223
223
 
224
+ // ─────────────────────────────────────────────────────
225
+ // guard:ci 命令
226
+ // ─────────────────────────────────────────────────────
227
+ program
228
+ .command('guard:ci [path]')
229
+ .description('CI/CD 模式运行全项目 Guard 检查')
230
+ .option('--fail-on-error', '有 error 级违规时 exit 1', true)
231
+ .option('--fail-on-warning', '超过 warning 阈值时 exit 2')
232
+ .option('--max-warnings <n>', 'warning 阈值', '20')
233
+ .option('--report <format>', '报告格式: json | text | markdown', 'text')
234
+ .option('--output <file>', '报告输出文件')
235
+ .option('--min-score <n>', 'Quality Gate 最低分', '70')
236
+ .option('--max-files <n>', '最大扫描文件数', '500')
237
+ .action(async (scanPath, opts) => {
238
+ try {
239
+ const projectRoot = resolve(scanPath || '.');
240
+ const { bootstrap, container } = await initContainer({ projectRoot });
241
+ const reporter = container.get('complianceReporter');
242
+
243
+ const report = await reporter.generate(projectRoot, {
244
+ qualityGate: {
245
+ maxErrors: 0,
246
+ maxWarnings: parseInt(opts.maxWarnings, 10),
247
+ minScore: parseInt(opts.minScore, 10),
248
+ },
249
+ maxFiles: parseInt(opts.maxFiles, 10),
250
+ });
251
+
252
+ // 输出报告
253
+ if (opts.report === 'json') {
254
+ const output = JSON.stringify(report, null, 2);
255
+ if (opts.output) {
256
+ const { writeFileSync } = await import('fs');
257
+ writeFileSync(opts.output, output, 'utf8');
258
+ console.log(`Report written to ${opts.output}`);
259
+ } else {
260
+ console.log(output);
261
+ }
262
+ } else {
263
+ reporter.printReport(report, { format: opts.report });
264
+ }
265
+
266
+ // 如果也要写文件(非 JSON 格式)
267
+ if (opts.output && opts.report !== 'json') {
268
+ const { writeFileSync } = await import('fs');
269
+ writeFileSync(opts.output, JSON.stringify(report, null, 2), 'utf8');
270
+ console.log(`Report data written to ${opts.output}`);
271
+ }
272
+
273
+ await bootstrap.shutdown();
274
+
275
+ // Exit code
276
+ if (report.qualityGate.status === 'FAIL') {
277
+ process.exit(report.summary.errors > 0 ? 1 : 2);
278
+ }
279
+ process.exit(0);
280
+ } catch (err) {
281
+ console.error('Error:', err.message);
282
+ if (process.env.ASD_DEBUG === '1') console.error(err.stack);
283
+ process.exit(1);
284
+ }
285
+ });
286
+
287
+ // ─────────────────────────────────────────────────────
288
+ // guard:staged 命令
289
+ // ─────────────────────────────────────────────────────
290
+ program
291
+ .command('guard:staged')
292
+ .description('检查 git staged 文件')
293
+ .option('--fail-on-error', '有 error 时 exit 1', true)
294
+ .option('--json', '以 JSON 格式输出')
295
+ .action(async (opts) => {
296
+ try {
297
+ const { execSync } = await import('child_process');
298
+
299
+ // 获取 staged 文件列表
300
+ let stagedFiles;
301
+ try {
302
+ stagedFiles = execSync('git diff --cached --name-only --diff-filter=ACM', { encoding: 'utf8' })
303
+ .trim().split('\n').filter(Boolean);
304
+ } catch (err) {
305
+ console.error('❌ 无法获取 git staged 文件(是否在 git 仓库中?)');
306
+ process.exit(1);
307
+ }
308
+
309
+ if (stagedFiles.length === 0) {
310
+ console.log('✅ No staged files');
311
+ process.exit(0);
312
+ }
313
+
314
+ // 过滤源文件
315
+ const { SOURCE_EXTS } = await import('../lib/service/guard/SourceFileCollector.js');
316
+ const { extname: _extname } = await import('path');
317
+ const sourceFiles = stagedFiles.filter(f => SOURCE_EXTS.has(_extname(f).toLowerCase()));
318
+
319
+ if (sourceFiles.length === 0) {
320
+ console.log('✅ No source files in staged changes');
321
+ process.exit(0);
322
+ }
323
+
324
+ const { bootstrap, container } = await initContainer();
325
+ const engine = container.get('guardCheckEngine');
326
+ const { detectLanguage } = await import('../lib/service/guard/GuardCheckEngine.js');
327
+
328
+ // 读取文件内容并检查
329
+ const files = [];
330
+ for (const f of sourceFiles) {
331
+ const filePath = resolve(f);
332
+ if (existsSync(filePath)) {
333
+ files.push({ path: filePath, content: readFileSync(filePath, 'utf8') });
334
+ }
335
+ }
336
+
337
+ const result = engine.auditFiles(files, { scope: 'file' });
338
+ const { summary } = result;
339
+
340
+ if (opts.json) {
341
+ console.log(JSON.stringify(result, null, 2));
342
+ } else if (summary.totalViolations === 0) {
343
+ console.log(`✅ ${summary.filesChecked} staged files passed Guard check`);
344
+ } else {
345
+ console.log(`\n🛡️ Guard check on ${summary.filesChecked} staged files:`);
346
+ console.log(` ${summary.totalErrors} errors, ${summary.totalViolations - summary.totalErrors} warnings\n`);
347
+
348
+ const filesWithIssues = result.files.filter(f => f.summary.total > 0);
349
+ for (const file of filesWithIssues.slice(0, 10)) {
350
+ console.log(` 📄 ${basename(file.filePath)} (${file.summary.errors}E / ${file.summary.warnings}W)`);
351
+ for (const v of file.violations.slice(0, 5)) {
352
+ const icon = v.severity === 'error' ? '❌' : '⚠️';
353
+ console.log(` ${icon} L${v.line} [${v.ruleId}] ${v.message}`);
354
+ }
355
+ if (file.violations.length > 5) console.log(` ... ${file.violations.length - 5} more`);
356
+ }
357
+ }
358
+
359
+ await bootstrap.shutdown();
360
+ process.exit(summary.totalErrors > 0 ? 1 : 0);
361
+ } catch (err) {
362
+ console.error('Error:', err.message);
363
+ if (process.env.ASD_DEBUG === '1') console.error(err.stack);
364
+ process.exit(1);
365
+ }
366
+ });
367
+
224
368
  // ─────────────────────────────────────────────────────
225
369
  // watch 命令
226
370
  // ─────────────────────────────────────────────────────
@@ -514,6 +658,7 @@ program
514
658
  console.log(` Channel A (Always-On Rules): ${result.channelA.rulesCount} 条规则 (${result.channelA.tokensUsed} tokens)`);
515
659
  console.log(` Channel B (Smart Rules): ${result.channelB.topicCount} 个主题, ${result.channelB.patternsCount} 个模式 (${result.channelB.totalTokens} tokens)`);
516
660
  console.log(` Channel C (Agent Skills): ${result.channelC.synced} 个 Skills 已同步`);
661
+ console.log(` Channel D (Dev Documents): ${result.channelD?.documentsCount || 0} 篇文档`);
517
662
  if (result.channelC.errors > 0) {
518
663
  console.log(` ⚠️ ${result.channelC.errors} 个错误`);
519
664
  }
@@ -47,5 +47,10 @@
47
47
  "enabled": true,
48
48
  "dimensions": 1536,
49
49
  "indexPath": "./data/vector-index"
50
+ },
51
+ "qualityGate": {
52
+ "maxErrors": 0,
53
+ "maxWarnings": 20,
54
+ "minScore": 70
50
55
  }
51
56
  }
@@ -181,7 +181,8 @@ export class UpgradeService {
181
181
  pipeline.deliver()
182
182
  .then(result => {
183
183
  console.log(` ✅ Cursor Delivery: ${result.channelA.rulesCount} rules, ` +
184
- `${result.channelB.topicCount} topics, ${result.channelC.synced} skills`);
184
+ `${result.channelB.topicCount} topics, ${result.channelC.synced} skills, ` +
185
+ `${result.channelD?.documentsCount || 0} documents`);
185
186
  })
186
187
  .catch(err => {
187
188
  console.log(` ⚠️ Cursor Delivery 跳过: ${err.message}`);
@@ -981,6 +981,181 @@ function _findIdentifier(node) {
981
981
  return null;
982
982
  }
983
983
 
984
+ // ──────────────────────────────────────────────────────────────────
985
+ // Guard AST 查询 API — 供 GuardCheckEngine AST 规则使用
986
+ // ──────────────────────────────────────────────────────────────────
987
+
988
+ /**
989
+ * 在 AST 中搜索特定调用表达式
990
+ * @param {string} source 源代码
991
+ * @param {string} lang 'objectivec' | 'swift'
992
+ * @param {string} targetCallee 目标调用,如 'URLSession.shared', 'dispatch_sync'
993
+ * @returns {Array<{ line: number, snippet: string, enclosingClass: string|null }>}
994
+ */
995
+ function findCallExpressions(source, lang, targetCallee) {
996
+ const parser = _getParser(lang);
997
+ if (!parser) return [];
998
+
999
+ const tree = parser.parse(source);
1000
+ const results = [];
1001
+ const lines = source.split(/\r?\n/);
1002
+
1003
+ function walk(node, enclosingClass) {
1004
+ // 更新当前所处的类
1005
+ let currentClass = enclosingClass;
1006
+ if (['class_declaration', 'struct_declaration', 'class_interface', 'class_implementation'].includes(node.type)) {
1007
+ currentClass = _findIdentifier(node) || enclosingClass;
1008
+ }
1009
+
1010
+ // 检查调用表达式
1011
+ const isCallLike = ['call_expression', 'message_expression', 'function_call_expression'].includes(node.type);
1012
+ if (isCallLike) {
1013
+ const nodeText = node.text || '';
1014
+ if (nodeText.includes(targetCallee)) {
1015
+ results.push({
1016
+ line: node.startPosition.row + 1,
1017
+ snippet: lines[node.startPosition.row]?.trim().slice(0, 120) || '',
1018
+ enclosingClass: currentClass,
1019
+ });
1020
+ }
1021
+ }
1022
+
1023
+ // 对 Swift,也检查 member_access + call 的组合,如 URLSession.shared.data(...)
1024
+ if (node.type === 'navigation_expression' || node.type === 'member_expression') {
1025
+ const nodeText = node.text || '';
1026
+ if (nodeText.includes(targetCallee)) {
1027
+ // 只有当父节点是 call 时才算
1028
+ const parent = node.parent;
1029
+ if (parent && ['call_expression', 'function_call_expression'].includes(parent.type)) {
1030
+ // 已在 call_expression 中处理,跳过避免重复
1031
+ } else {
1032
+ results.push({
1033
+ line: node.startPosition.row + 1,
1034
+ snippet: lines[node.startPosition.row]?.trim().slice(0, 120) || '',
1035
+ enclosingClass: currentClass,
1036
+ });
1037
+ }
1038
+ }
1039
+ }
1040
+
1041
+ for (let i = 0; i < node.childCount; i++) {
1042
+ walk(node.child(i), currentClass);
1043
+ }
1044
+ }
1045
+
1046
+ walk(tree.rootNode, null);
1047
+ return results;
1048
+ }
1049
+
1050
+ /**
1051
+ * 搜索特定模式在特定上下文中的出现
1052
+ * @param {string} source 源代码
1053
+ * @param {string} lang 'objectivec' | 'swift'
1054
+ * @param {string} pattern 要查找的文本模式(普通字符串匹配)
1055
+ * @param {{ forbiddenContext?: string, requiredContext?: string }} contextFilter
1056
+ * forbiddenContext: 如果在此上下文中出现则报告 (如 'dealloc')
1057
+ * requiredContext: 如果不在此上下文中出现则报告
1058
+ * @returns {Array<{ line: number, snippet: string, context: string|null }>}
1059
+ */
1060
+ function findPatternInContext(source, lang, pattern, contextFilter = {}) {
1061
+ const parser = _getParser(lang);
1062
+ if (!parser) return [];
1063
+
1064
+ const tree = parser.parse(source);
1065
+ const results = [];
1066
+ const lines = source.split(/\r?\n/);
1067
+
1068
+ function getEnclosingMethodName(node) {
1069
+ let current = node.parent;
1070
+ while (current) {
1071
+ if (['method_definition', 'method_declaration', 'function_declaration', 'function_definition'].includes(current.type)) {
1072
+ return _findIdentifier(current) || null;
1073
+ }
1074
+ current = current.parent;
1075
+ }
1076
+ return null;
1077
+ }
1078
+
1079
+ function getEnclosingClassName(node) {
1080
+ let current = node.parent;
1081
+ while (current) {
1082
+ if (['class_declaration', 'struct_declaration', 'class_interface', 'class_implementation'].includes(current.type)) {
1083
+ return _findIdentifier(current) || null;
1084
+ }
1085
+ current = current.parent;
1086
+ }
1087
+ return null;
1088
+ }
1089
+
1090
+ function walk(node) {
1091
+ const nodeText = node.text || '';
1092
+ if (nodeText.includes(pattern) && node.childCount === 0) {
1093
+ // 叶节点匹配
1094
+ const methodName = getEnclosingMethodName(node);
1095
+ const className = getEnclosingClassName(node);
1096
+
1097
+ if (contextFilter.forbiddenContext) {
1098
+ // 在禁止上下文中出现 → 报告
1099
+ if (methodName === contextFilter.forbiddenContext || className === contextFilter.forbiddenContext) {
1100
+ results.push({
1101
+ line: node.startPosition.row + 1,
1102
+ snippet: lines[node.startPosition.row]?.trim().slice(0, 120) || '',
1103
+ context: methodName || className,
1104
+ });
1105
+ }
1106
+ } else if (contextFilter.requiredContext) {
1107
+ // 不在要求的上下文中 → 报告
1108
+ if (className !== contextFilter.requiredContext && methodName !== contextFilter.requiredContext) {
1109
+ results.push({
1110
+ line: node.startPosition.row + 1,
1111
+ snippet: lines[node.startPosition.row]?.trim().slice(0, 120) || '',
1112
+ context: className || methodName,
1113
+ });
1114
+ }
1115
+ }
1116
+ }
1117
+
1118
+ for (let i = 0; i < node.childCount; i++) {
1119
+ walk(node.child(i));
1120
+ }
1121
+ }
1122
+
1123
+ walk(tree.rootNode);
1124
+ return results;
1125
+ }
1126
+
1127
+ /**
1128
+ * 检查类是否遵循指定协议
1129
+ * @param {string} source 源代码
1130
+ * @param {string} lang 'objectivec' | 'swift'
1131
+ * @param {string} className 类名
1132
+ * @param {string} protocolName 协议名
1133
+ * @returns {{ conforms: boolean, classFound: boolean, classDeclLine: number|null }}
1134
+ */
1135
+ function checkProtocolConformance(source, lang, className, protocolName) {
1136
+ const summary = analyzeFile(source, lang);
1137
+ if (!summary) return { conforms: false, classFound: false, classDeclLine: null };
1138
+
1139
+ // 在 classes 中查找
1140
+ const cls = summary.classes.find(c => c.name === className);
1141
+ if (!cls) return { conforms: false, classFound: false, classDeclLine: null };
1142
+
1143
+ // 直接遵循
1144
+ if (cls.protocols?.includes(protocolName)) {
1145
+ return { conforms: true, classFound: true, classDeclLine: cls.line };
1146
+ }
1147
+
1148
+ // 通过 extension/category 遵循
1149
+ const catConforms = summary.categories.some(
1150
+ cat => cat.className === className && cat.protocols?.includes(protocolName)
1151
+ );
1152
+ if (catConforms) {
1153
+ return { conforms: true, classFound: true, classDeclLine: cls.line };
1154
+ }
1155
+
1156
+ return { conforms: false, classFound: true, classDeclLine: cls.line };
1157
+ }
1158
+
984
1159
  // ──────────────────────────────────────────────────────────────────
985
1160
  // 导出
986
1161
  // ──────────────────────────────────────────────────────────────────
@@ -991,4 +1166,8 @@ export {
991
1166
  generateContextForAgent,
992
1167
  isAvailable,
993
1168
  supportedLanguages,
1169
+ // Guard AST 查询 API
1170
+ findCallExpressions,
1171
+ findPatternInContext,
1172
+ checkProtocolConformance,
994
1173
  };
@@ -85,6 +85,7 @@ const KIND_MAP = {
85
85
  'call-chain': 'fact',
86
86
  'data-flow': 'fact',
87
87
  'module-dependency': 'fact',
88
+ 'dev-document': 'fact',
88
89
  };
89
90
 
90
91
  /**
@@ -163,6 +163,7 @@ export class McpServer {
163
163
  case 'autosnippet_submit_knowledge': return knowledgeHandlers.submitKnowledge(ctx, args);
164
164
  case 'autosnippet_submit_knowledge_batch': return knowledgeHandlers.submitKnowledgeBatch(ctx, args);
165
165
  case 'autosnippet_knowledge_lifecycle': return knowledgeHandlers.knowledgeLifecycle(ctx, args);
166
+ case 'autosnippet_save_document': return knowledgeHandlers.saveDocument(ctx, args);
166
167
  default: throw new Error(`Unknown tool: ${name}`);
167
168
  }
168
169
  }
@@ -1108,7 +1108,8 @@ export async function fillDimensionsV3(fillContext) {
1108
1108
  logger.info(`[Bootstrap-v3] 🚀 Cursor Delivery complete — ` +
1109
1109
  `A: ${deliveryResult.channelA.rulesCount} rules, ` +
1110
1110
  `B: ${deliveryResult.channelB.topicCount} topics, ` +
1111
- `C: ${deliveryResult.channelC.synced} skills`);
1111
+ `C: ${deliveryResult.channelC.synced} skills, ` +
1112
+ `D: ${deliveryResult.channelD?.documentsCount || 0} documents`);
1112
1113
  }
1113
1114
  } catch (deliveryErr) {
1114
1115
  logger.warn(`[Bootstrap-v3] Cursor Delivery failed (non-blocking): ${deliveryErr.message}`);
@@ -65,7 +65,7 @@ export async function guardAuditFiles(ctx, args) {
65
65
 
66
66
  const result = engine.auditFiles(filesToAudit, { scope });
67
67
 
68
- // 写入 ViolationsStore
68
+ // 写入 ViolationsStore + GuardFeedbackLoop
69
69
  try {
70
70
  const violationsStore = ctx.container.get('violationsStore');
71
71
  for (const fileResult of (result.files || [])) {
@@ -76,6 +76,12 @@ export async function guardAuditFiles(ctx, args) {
76
76
  summary: `MCP audit (${scope}): ${fileResult.summary.errors}E ${fileResult.summary.warnings}W`,
77
77
  });
78
78
  }
79
+
80
+ // Guard ↔ Recipe 闭环:检测修复并自动确认使用
81
+ try {
82
+ const feedbackLoop = ctx.container.get('guardFeedbackLoop');
83
+ feedbackLoop.processFixDetection(fileResult, fileResult.filePath);
84
+ } catch { /* guardFeedbackLoop not available */ }
79
85
  }
80
86
  } catch { /* ViolationsStore not available */ }
81
87