autosnippet 2.8.3 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +5 -5
  2. package/bin/cli.js +5 -33
  3. package/config/constitution.yaml +9 -2
  4. package/dashboard/dist/assets/{icons-B_Xg4B-s.js → icons-BkT3XrKf.js} +105 -100
  5. package/dashboard/dist/assets/index-BsB7DzW4.css +1 -0
  6. package/dashboard/dist/assets/index-DdmQMrJJ.js +155 -0
  7. package/dashboard/dist/index.html +3 -3
  8. package/lib/cli/AiScanService.js +13 -11
  9. package/lib/cli/KnowledgeSyncService.js +343 -0
  10. package/lib/cli/SetupService.js +9 -27
  11. package/lib/core/ast/ProjectGraph.js +160 -0
  12. package/lib/core/gateway/GatewayActionRegistry.js +48 -58
  13. package/lib/domain/index.js +16 -11
  14. package/lib/domain/knowledge/KnowledgeEntry.js +351 -0
  15. package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
  16. package/lib/domain/knowledge/Lifecycle.js +109 -0
  17. package/lib/domain/knowledge/index.js +27 -0
  18. package/lib/domain/knowledge/values/Constraints.js +125 -0
  19. package/lib/domain/knowledge/values/Content.js +86 -0
  20. package/lib/domain/knowledge/values/Quality.js +93 -0
  21. package/lib/domain/knowledge/values/Reasoning.js +69 -0
  22. package/lib/domain/knowledge/values/Relations.js +168 -0
  23. package/lib/domain/knowledge/values/Stats.js +87 -0
  24. package/lib/domain/knowledge/values/index.js +9 -0
  25. package/lib/external/ai/AiProvider.js +48 -0
  26. package/lib/external/ai/providers/GoogleGeminiProvider.js +12 -3
  27. package/lib/external/mcp/McpServer.js +7 -5
  28. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +3 -2
  29. package/lib/external/mcp/handlers/bootstrap.js +121 -12
  30. package/lib/external/mcp/handlers/browse.js +77 -73
  31. package/lib/external/mcp/handlers/candidate.js +29 -276
  32. package/lib/external/mcp/handlers/guard.js +2 -0
  33. package/lib/external/mcp/handlers/knowledge.js +205 -0
  34. package/lib/external/mcp/handlers/skill.js +4 -2
  35. package/lib/external/mcp/handlers/structure.js +25 -23
  36. package/lib/external/mcp/handlers/system.js +10 -12
  37. package/lib/external/mcp/tools.js +125 -138
  38. package/lib/http/HttpServer.js +4 -8
  39. package/lib/http/middleware/requestLogger.js +3 -3
  40. package/lib/http/routes/ai.js +17 -1
  41. package/lib/http/routes/extract.js +48 -4
  42. package/lib/http/routes/knowledge.js +246 -0
  43. package/lib/http/routes/search.js +12 -17
  44. package/lib/http/routes/skills.js +44 -1
  45. package/lib/infrastructure/cache/GraphCache.js +143 -0
  46. package/lib/infrastructure/database/migrations/015_create_token_usage.js +27 -0
  47. package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
  48. package/lib/infrastructure/external/XcodeAutomation.js +187 -103
  49. package/lib/infrastructure/realtime/RealtimeService.js +14 -2
  50. package/lib/injection/ServiceContainer.js +164 -63
  51. package/lib/repository/knowledge/KnowledgeRepository.impl.js +373 -0
  52. package/lib/repository/token/TokenUsageStore.js +162 -0
  53. package/lib/service/automation/DirectiveDetector.js +2 -3
  54. package/lib/service/automation/FileWatcher.js +67 -28
  55. package/lib/service/automation/XcodeIntegration.js +931 -156
  56. package/lib/service/automation/handlers/AlinkHandler.js +6 -4
  57. package/lib/service/automation/handlers/CreateHandler.js +53 -18
  58. package/lib/service/automation/handlers/GuardHandler.js +183 -20
  59. package/lib/service/automation/handlers/SearchHandler.js +35 -17
  60. package/lib/service/chat/AnalystAgent.js +25 -14
  61. package/lib/service/chat/CandidateGuardrail.js +1 -1
  62. package/lib/service/chat/ChatAgent.js +280 -48
  63. package/lib/service/chat/ContextWindow.js +92 -8
  64. package/lib/service/chat/HandoffProtocol.js +26 -1
  65. package/lib/service/chat/ProducerAgent.js +11 -9
  66. package/lib/service/chat/tools.js +298 -194
  67. package/lib/service/guard/GuardCheckEngine.js +114 -10
  68. package/lib/service/guard/GuardService.js +59 -48
  69. package/lib/service/knowledge/ConfidenceRouter.js +159 -0
  70. package/lib/service/knowledge/KnowledgeFileWriter.js +602 -0
  71. package/lib/service/knowledge/KnowledgeService.js +725 -0
  72. package/lib/service/search/SearchEngine.js +92 -19
  73. package/lib/service/skills/SignalCollector.js +15 -9
  74. package/lib/service/skills/SkillAdvisor.js +13 -11
  75. package/lib/service/snippet/SnippetFactory.js +5 -5
  76. package/lib/service/spm/SpmService.js +119 -18
  77. package/package.json +1 -1
  78. package/scripts/install-cursor-skill.js +0 -6
  79. package/scripts/migrate-md-to-knowledge.mjs +364 -0
  80. package/skills/autosnippet-analysis/SKILL.md +15 -7
  81. package/skills/autosnippet-candidates/SKILL.md +6 -6
  82. package/skills/autosnippet-coldstart/SKILL.md +7 -3
  83. package/skills/autosnippet-concepts/SKILL.md +7 -6
  84. package/skills/autosnippet-create/SKILL.md +13 -13
  85. package/skills/autosnippet-intent/SKILL.md +3 -2
  86. package/skills/autosnippet-lifecycle/SKILL.md +5 -5
  87. package/skills/autosnippet-recipes/SKILL.md +16 -4
  88. package/templates/constitution.yaml +1 -1
  89. package/templates/copilot-instructions.md +6 -6
  90. package/templates/recipes-setup/README.md +3 -3
  91. package/dashboard/dist/assets/index-CkIih2CC.css +0 -1
  92. package/dashboard/dist/assets/index-Duc8Qk-c.js +0 -197
  93. package/lib/cli/CandidateSyncService.js +0 -261
  94. package/lib/cli/SyncService.js +0 -356
  95. package/lib/domain/candidate/Candidate.js +0 -196
  96. package/lib/domain/candidate/CandidateRepository.js +0 -107
  97. package/lib/domain/candidate/Reasoning.js +0 -52
  98. package/lib/domain/recipe/Recipe.js +0 -421
  99. package/lib/domain/recipe/RecipeRepository.js +0 -54
  100. package/lib/domain/types/CandidateStatus.js +0 -52
  101. package/lib/http/routes/candidates.js +0 -559
  102. package/lib/http/routes/recipes.js +0 -397
  103. package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
  104. package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
  105. package/lib/service/candidate/CandidateAggregator.js +0 -52
  106. package/lib/service/candidate/CandidateFileWriter.js +0 -383
  107. package/lib/service/candidate/CandidateService.js +0 -973
  108. package/lib/service/recipe/RecipeFileWriter.js +0 -514
  109. package/lib/service/recipe/RecipeService.js +0 -786
  110. package/lib/service/recipe/RecipeStatsTracker.js +0 -148
@@ -30,9 +30,11 @@ export async function handleAlink(alinkLine) {
30
30
 
31
31
  let recipeId = null;
32
32
  if (db) {
33
+ const rawDb = typeof db.getDb === 'function' ? db.getDb() : db;
33
34
  try {
34
- const row = db.prepare(
35
- 'SELECT id FROM recipes WHERE trigger = ? LIMIT 1',
35
+ // V3: knowledge_entries, trigger_key
36
+ const row = rawDb.prepare(
37
+ 'SELECT id FROM knowledge_entries WHERE trigger_key = ? AND lifecycle = \'active\' LIMIT 1',
36
38
  ).get(completionKey);
37
39
  if (row) recipeId = row.id;
38
40
  } catch {
@@ -43,8 +45,8 @@ export async function handleAlink(alinkLine) {
43
45
  if (!recipeId) {
44
46
  try {
45
47
  const escaped = completionKey.replace(/[%_\\]/g, ch => `\\${ch}`);
46
- const row = db.prepare(
47
- "SELECT id FROM recipes WHERE trigger LIKE ? ESCAPE '\\' OR title LIKE ? ESCAPE '\\' LIMIT 1",
48
+ const row = rawDb.prepare(
49
+ "SELECT id FROM knowledge_entries WHERE (trigger_key LIKE ? ESCAPE '\\' OR title LIKE ? ESCAPE '\\') AND lifecycle = 'active' LIMIT 1",
48
50
  ).get(`%${escaped}%`, `%${escaped}%`);
49
51
  if (row) recipeId = row.id;
50
52
  } catch { /* silent */ }
@@ -95,16 +95,17 @@ async function silentCreateCandidate(watcher, text, relativePath) {
95
95
  headers: r.headers || [],
96
96
  }));
97
97
 
98
- // 先尝试批量解析
98
+ // 先尝试批量解析(仅对 Recipe Markdown 格式有效)
99
99
  const allRecipes = parser.parseAll(text);
100
- if (allRecipes.length > 0) {
101
- const items = normalize(allRecipes);
100
+ const validRecipes = allRecipes.filter(r => r.title && r.title.trim());
101
+ if (validRecipes.length > 0) {
102
+ const items = normalize(validRecipes);
102
103
  await watcher._resolveHeadersIfNeeded(items[0], relativePath, text);
103
104
  await watcher._appendCandidates(items, 'watch-create');
104
105
  const msg =
105
- allRecipes.length === 1
106
- ? `已创建候选「${allRecipes[0].title}」,请在 Dashboard Candidates 页审核`
107
- : `已创建 ${allRecipes.length} 条候选,请在 Dashboard Candidates 页审核`;
106
+ validRecipes.length === 1
107
+ ? `已创建候选「${validRecipes[0].title}」,请在 Dashboard Candidates 页审核`
108
+ : `已创建 ${validRecipes.length} 条候选,请在 Dashboard Candidates 页审核`;
108
109
  console.log(`✅ [as:create] ${msg}`);
109
110
  watcher._notify(msg);
110
111
  return;
@@ -113,7 +114,7 @@ async function silentCreateCandidate(watcher, text, relativePath) {
113
114
  // 尝试单条解析
114
115
  if (parser.isCompleteRecipe(text)) {
115
116
  const one = parser.parse(text);
116
- if (one) {
117
+ if (one && one.title && one.title.trim()) {
117
118
  const item = normalize([one])[0];
118
119
  await watcher._resolveHeadersIfNeeded(item, relativePath, text);
119
120
  await watcher._appendCandidates([item], 'watch-create');
@@ -123,22 +124,56 @@ async function silentCreateCandidate(watcher, text, relativePath) {
123
124
  }
124
125
  }
125
126
 
126
- // 最后用 AI 摘要(通过 ChatAgent 统一入口)
127
+ // -c 模式:剪贴板内容整体作为一条候选(不拆分)
128
+ // 先用 AI 生成标题和摘要,code 保持剪贴板原文
129
+ const lang = relativePath && /\.swift$/i.test(relativePath) ? 'swift' : 'objc';
130
+ const fileName = relativePath
131
+ ? relativePath.split('/').pop()
132
+ : `clipboard.${lang === 'swift' ? 'swift' : 'm'}`;
133
+
134
+ let title = fileName.replace(/\.\w+$/, '') || 'Clipboard Snippet';
135
+ let summary = '';
136
+ let usageGuide = '';
137
+ let category = 'Utility';
138
+ let headers = [];
139
+ let tags = [];
140
+ let trigger = '';
141
+
142
+ // 尝试用 AI 生成摘要信息(但 code 始终保持原文)
127
143
  try {
128
144
  const { getServiceContainer } = await import('../../../injection/ServiceContainer.js');
129
145
  const container = getServiceContainer();
130
146
  const chatAgent = container.get('chatAgent');
131
- const lang = relativePath && /\.swift$/i.test(relativePath) ? 'swift' : 'objc';
132
- const result = await chatAgent.executeTool('summarize_code', { code: text, language: lang });
133
- if (result && !result.error && result.title && result.code) {
134
- await watcher._appendCandidates([result], 'watch-create');
135
- console.log(`✅ [as:create] 已静默创建候选「${result.title}」`);
136
- watcher._notify(`已创建候选「${result.title}」,请在 Candidates 页审核`);
137
- return;
147
+ const aiResult = await chatAgent.executeTool('summarize_code', { code: text, language: lang });
148
+ if (aiResult && !aiResult.error) {
149
+ title = aiResult.title || title;
150
+ summary = aiResult.summary || aiResult.summary_cn || '';
151
+ usageGuide = aiResult.usageGuide || aiResult.usageGuide_cn || '';
152
+ category = aiResult.category || category;
153
+ headers = aiResult.headers || [];
154
+ tags = aiResult.tags || [];
155
+ trigger = aiResult.trigger || '';
138
156
  }
139
- } catch { /* ChatAgent 不可用 */ }
140
-
141
- throw new Error('无法从剪贴板内容解析出候选');
157
+ } catch { /* AI 不可用,使用默认值 */ }
158
+
159
+ const item = {
160
+ title,
161
+ summary,
162
+ summary_cn: summary,
163
+ trigger,
164
+ category,
165
+ language: lang,
166
+ code: text, // 剪贴板原文整体
167
+ usageGuide,
168
+ usageGuide_cn: usageGuide,
169
+ headers,
170
+ tags,
171
+ };
172
+
173
+ await watcher._resolveHeadersIfNeeded(item, relativePath, text);
174
+ await watcher._appendCandidates([item], 'watch-create');
175
+ console.log(`✅ [as:create -c] 已创建候选「${title}」,请在 Candidates 页审核`);
176
+ watcher._notify(`已创建候选「${title}」,请在 Candidates 页审核`);
142
177
  }
143
178
 
144
179
  /**
@@ -1,42 +1,183 @@
1
1
  /**
2
2
  * GuardHandler — 处理 // as:a (audit/guard/lint) 指令
3
+ *
4
+ * 用法:
5
+ * // as:a — 检查当前文件 (scope=file)
6
+ * // as:a file — 同上,显式 file scope
7
+ * // as:a target — 检查当前文件所在目录树 (scope=target)
8
+ * // as:a project — 检查整个项目所有源文件 (scope=project)
9
+ * // as:a <keyword> — 检查当前文件 + 搜索相关规范
3
10
  */
4
11
 
5
- import { basename } from 'node:path';
12
+ import { basename, join, extname, dirname } from 'node:path';
13
+ import { readFile } from 'node:fs/promises';
14
+
15
+ /** 已知的 scope 关键词 */
16
+ const SCOPE_KEYWORDS = new Set(['file', 'target', 'project', 'all']);
17
+
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
+ ]);
6
25
 
7
26
  /**
8
- * @param {string} fullPath
9
- * @param {string} code
10
- * @param {string} guardLine
27
+ * 递归收集目录下所有源文件路径
11
28
  */
12
- export async function handleGuard(fullPath, code, guardLine) {
29
+ async function collectSourceFiles(dir) {
30
+ const { readdir } = await import('node:fs/promises');
31
+ const files = [];
32
+
33
+ // 跳过的目录
34
+ const SKIP_DIRS = new Set([
35
+ 'node_modules', '.git', 'build', 'DerivedData',
36
+ 'Pods', '.build', 'vendor', 'dist', '.next',
37
+ 'Carthage', 'xcuserdata', '__pycache__',
38
+ ]);
39
+
40
+ async function walk(currentDir) {
41
+ let entries;
42
+ try {
43
+ entries = await readdir(currentDir, { withFileTypes: true });
44
+ } catch {
45
+ return; // 权限不足等情况跳过
46
+ }
47
+ for (const entry of entries) {
48
+ if (entry.name.startsWith('.') && entry.name !== '.') continue;
49
+ const fullPath = join(currentDir, entry.name);
50
+ if (entry.isDirectory()) {
51
+ if (!SKIP_DIRS.has(entry.name)) {
52
+ await walk(fullPath);
53
+ }
54
+ } else if (entry.isFile() && SOURCE_EXTS.has(extname(entry.name).toLowerCase())) {
55
+ files.push(fullPath);
56
+ }
57
+ }
58
+ }
59
+
60
+ await walk(dir);
61
+ return files;
62
+ }
63
+
64
+ /**
65
+ * @param {import('../FileWatcher').default} watcher FileWatcher 实例
66
+ * @param {string} fullPath 当前文件绝对路径
67
+ * @param {string} code 当前文件内容
68
+ * @param {string} guardLine 触发行原文
69
+ */
70
+ export async function handleGuard(watcher, fullPath, code, guardLine) {
13
71
  const rest = guardLine.replace(/^\/\/\s*as:(?:audit|a|lint|l|guard|g)\s*/, '').trim();
14
- console.log(`\n🛡️ [Guard] 正在检查文件: ${basename(fullPath)}`);
72
+ const scopeArg = rest.toLowerCase();
73
+ const isScope = SCOPE_KEYWORDS.has(scopeArg);
74
+ // 确定 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
15
79
 
16
80
  try {
17
81
  const { detectLanguage } = await import('../../guard/GuardCheckEngine.js');
18
82
  const { ServiceContainer } = await import('../../../injection/ServiceContainer.js');
19
83
  const container = ServiceContainer.getInstance();
20
84
  const engine = container.get('guardCheckEngine');
21
- const language = detectLanguage(fullPath);
22
- const violations = engine.checkCode(code, language);
23
85
 
24
- if (violations.length === 0) {
25
- console.log(` ✅ 无违规`);
26
- } else {
27
- const errors = violations.filter((v) => v.severity === 'error');
28
- const warnings = violations.filter((v) => v.severity === 'warning');
29
- console.log(` 🛡️ ${errors.length} errors, ${warnings.length} warnings`);
30
- for (const v of errors) {
31
- console.log(` ❌ L${v.line} [${v.ruleId}] ${v.message}`);
86
+ /* ── 多文件审计 (target / project) ── */
87
+ if (scope === 'project' || scope === 'target') {
88
+ let scanRoot;
89
+ if (scope === 'project') {
90
+ scanRoot = watcher?.projectRoot;
91
+ } else {
92
+ // target: 扫描当前文件所在目录树
93
+ scanRoot = dirname(fullPath);
94
+ }
95
+
96
+ if (!scanRoot) {
97
+ console.warn(' ⚠️ 无法确定扫描根目录,回退到单文件检查');
98
+ return _auditSingleFile(engine, fullPath, code, detectLanguage, 'file');
99
+ }
100
+
101
+ const scopeLabel = scope === 'project' ? '整个项目' : '当前目录';
102
+ console.log(`\n🛡️ [Guard] 正在扫描${scopeLabel}: ${scanRoot}`);
103
+ const sourcePaths = await collectSourceFiles(scanRoot);
104
+ console.log(` 📁 找到 ${sourcePaths.length} 个源文件`);
105
+
106
+ if (sourcePaths.length === 0) {
107
+ console.log(' ✅ 未找到源文件');
108
+ return;
109
+ }
110
+
111
+ // 读取所有文件内容
112
+ const fileEntries = [];
113
+ let readErrors = 0;
114
+ for (const p of sourcePaths) {
115
+ try {
116
+ const content = await readFile(p, 'utf-8');
117
+ fileEntries.push({ path: p, content });
118
+ } catch {
119
+ readErrors++;
120
+ }
32
121
  }
33
- for (const v of warnings.slice(0, 5)) {
34
- console.log(` ⚠️ L${v.line} [${v.ruleId}] ${v.message}`);
122
+ if (readErrors > 0) {
123
+ console.log(` ⚠️ ${readErrors} 个文件无法读取,已跳过`);
35
124
  }
125
+
126
+ // 批量审计(传递 scope 以启用对应维度规则)
127
+ const report = engine.auditFiles(fileEntries, { scope });
128
+ const { summary } = report;
129
+
130
+ if (summary.totalViolations === 0) {
131
+ console.log(` ✅ ${summary.filesChecked} 个文件全部通过,无违规`);
132
+ } 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
+ // 按文件输出详情(限制输出前 10 个有问题的文件)
138
+ const filesWithIssues = report.files.filter(f => f.summary.total > 0);
139
+ 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');
144
+ for (const v of errors.slice(0, 5)) {
145
+ console.log(` ❌ L${v.line} [${v.ruleId}] ${v.message}`);
146
+ }
147
+ if (errors.length > 5) console.log(` ... 还有 ${errors.length - 5} 个 errors`);
148
+ for (const v of warnings.slice(0, 3)) {
149
+ console.log(` ⚠️ L${v.line} [${v.ruleId}] ${v.message}`);
150
+ }
151
+ if (warnings.length > 3) console.log(` ... 还有 ${warnings.length - 3} 个 warnings`);
152
+ }
153
+ if (filesWithIssues.length > 10) {
154
+ console.log(`\n ... 还有 ${filesWithIssues.length - 10} 个文件有问题,已省略`);
155
+ }
156
+
157
+ // 跨文件问题汇总
158
+ if (report.crossFileViolations?.length > 0) {
159
+ console.log(`\n 🔗 跨文件问题 (${report.crossFileViolations.length}):`);
160
+ for (const v of report.crossFileViolations.slice(0, 10)) {
161
+ console.log(` ⚠️ [${v.ruleId}] ${v.message}`);
162
+ if (v.locations) {
163
+ for (const loc of v.locations.slice(0, 5)) {
164
+ const relLoc = loc.filePath.replace(scanRoot + '/', '');
165
+ console.log(` 📄 ${relLoc}:L${loc.line}`);
166
+ }
167
+ if (v.locations.length > 5) console.log(` ... 还有 ${v.locations.length - 5} 处`);
168
+ }
169
+ }
170
+ }
171
+ }
172
+ return;
36
173
  }
37
174
 
38
- // 如果有关键词,也做语义搜索
39
- if (rest) {
175
+ /* ── 单文件审计 (file scope) ── */
176
+ console.log(`\n🛡️ [Guard] 正在检查文件: ${basename(fullPath)}`);
177
+ _auditSingleFile(engine, fullPath, code, detectLanguage, scope);
178
+
179
+ // 如果有非 scope 关键词,也做语义搜索
180
+ if (rest && !isScope) {
40
181
  try {
41
182
  const searchEngine = container.get('searchEngine');
42
183
  const results = await searchEngine.search(rest, { limit: 3, mode: 'keyword' });
@@ -54,3 +195,25 @@ export async function handleGuard(fullPath, code, guardLine) {
54
195
  console.warn(` ⚠️ Guard 检查失败: ${err.message}`);
55
196
  }
56
197
  }
198
+
199
+ /**
200
+ * 检查单个文件并打印结果
201
+ */
202
+ function _auditSingleFile(engine, fullPath, code, detectLanguage, scope = 'file') {
203
+ const language = detectLanguage(fullPath);
204
+ const violations = engine.checkCode(code, language, { scope });
205
+
206
+ if (violations.length === 0) {
207
+ console.log(` ✅ 无违规`);
208
+ } else {
209
+ const errors = violations.filter((v) => v.severity === 'error');
210
+ const warnings = violations.filter((v) => v.severity === 'warning');
211
+ console.log(` 🛡️ ${errors.length} errors, ${warnings.length} warnings`);
212
+ for (const v of errors) {
213
+ console.log(` ❌ L${v.line} [${v.ruleId}] ${v.message}`);
214
+ }
215
+ for (const v of warnings.slice(0, 5)) {
216
+ console.log(` ⚠️ L${v.line} [${v.ruleId}] ${v.message}`);
217
+ }
218
+ }
219
+ }
@@ -26,22 +26,35 @@ export async function handleSearch(watcher, fullPath, relativePath, searchLine)
26
26
  const container = ServiceContainer.getInstance();
27
27
  const searchEngine = container.get('searchEngine');
28
28
 
29
+ // 诊断:输出索引状态
30
+ const stats = searchEngine.getStats();
31
+ if (stats.totalDocuments === 0) {
32
+ console.log(` ⚠️ 知识库为空(索引 0 条记录),请先通过 asd setup / Dashboard 添加知识条目`);
33
+ } else {
34
+ console.log(` 📊 索引 ${stats.totalDocuments} 条知识`);
35
+ }
36
+
37
+ // BM25 → keyword 逐级降级:空结果也触发降级(中文分词不足时 BM25 可能零命中)
29
38
  try {
30
39
  results = await searchEngine.search(query, { limit: 10, mode: 'bm25' });
40
+ if (!results || (results.items || []).length === 0) {
41
+ results = await searchEngine.search(query, { limit: 10, mode: 'keyword' });
42
+ }
31
43
  } catch {
32
44
  results = await searchEngine.search(query, { limit: 10, mode: 'keyword' });
33
45
  }
34
46
  } catch (err) {
35
47
  console.warn(` ⚠️ 搜索失败: ${err.message}`);
36
- watcher._openDashboard(`/?action=search&q=${encodeURIComponent(query)}`);
48
+ console.log(` ℹ️ 未找到「${query}」的相关结果`);
49
+ watcher._notify(`搜索「${query}」失败: ${err.message}`);
37
50
  return;
38
51
  }
39
52
 
40
53
  const items = normalizeSearchResults(results);
41
54
 
42
55
  if (items.length === 0) {
43
- console.log(` ℹ️ 未找到相关结果`);
44
- watcher._openDashboard(`/?action=search&q=${encodeURIComponent(query)}&path=${encodeURIComponent(relativePath)}`);
56
+ console.log(` ℹ️ 未找到「${query}」的相关结果`);
57
+ watcher._notify(`未找到「${query}」的相关结果`);
45
58
  return;
46
59
  }
47
60
 
@@ -103,35 +116,40 @@ export function normalizeSearchResults(results) {
103
116
  }
104
117
  } catch { /* ignore */ }
105
118
  }
106
- // headers 优先从 dimensions_json 取(V2 标准存储位置)
107
- if (headers.length === 0 && r.dimensions_json) {
119
+ // V3: headers 是独立 JSON 列(字符串),优先解析
120
+ if (headers.length === 0 && r.headers) {
108
121
  try {
109
- const dims = typeof r.dimensions_json === 'string' ? JSON.parse(r.dimensions_json) : r.dimensions_json;
110
- if (Array.isArray(dims.headers) && dims.headers.length > 0) {
111
- headers = dims.headers;
122
+ const parsed = typeof r.headers === 'string' ? JSON.parse(r.headers) : r.headers;
123
+ if (Array.isArray(parsed) && parsed.length > 0) {
124
+ headers = parsed;
112
125
  }
113
126
  } catch { /* ignore */ }
114
127
  }
115
- // 兜底:顶层 r.headers
116
- if (headers.length === 0 && Array.isArray(r.headers) && r.headers.length > 0) {
117
- headers = r.headers;
118
- }
119
- // moduleName: 从 dimensions_json 或 content_json 或顶层提取
120
- let moduleName = null;
121
- if (r.dimensions_json) {
128
+ // V2 兜底:从 dimensions_json 取
129
+ if (headers.length === 0 && r.dimensions_json) {
122
130
  try {
123
131
  const dims = typeof r.dimensions_json === 'string' ? JSON.parse(r.dimensions_json) : r.dimensions_json;
124
- moduleName = dims.moduleName || dims.module_name || null;
132
+ if (Array.isArray(dims.headers) && dims.headers.length > 0) {
133
+ headers = dims.headers;
134
+ }
125
135
  } catch { /* ignore */ }
126
136
  }
137
+ // moduleName: V3 独立列 > content_json > dimensions_json > 顶层
138
+ let moduleName = r.module_name || null;
127
139
  if (!moduleName && r.content_json) {
128
140
  try {
129
141
  const content = typeof r.content_json === 'string' ? JSON.parse(r.content_json) : r.content_json;
130
142
  moduleName = content.moduleName || content.module_name || null;
131
143
  } catch { /* ignore */ }
132
144
  }
145
+ if (!moduleName && r.dimensions_json) {
146
+ try {
147
+ const dims = typeof r.dimensions_json === 'string' ? JSON.parse(r.dimensions_json) : r.dimensions_json;
148
+ moduleName = dims.moduleName || dims.module_name || null;
149
+ } catch { /* ignore */ }
150
+ }
133
151
  if (!moduleName) {
134
- moduleName = r.moduleName || r.module_name || null;
152
+ moduleName = r.moduleName || null;
135
153
  }
136
154
 
137
155
  return {
@@ -19,20 +19,31 @@ import Logger from '../../infrastructure/logging/Logger.js';
19
19
  // System Prompt — Analyst 专用 (~100 tokens)
20
20
  // ──────────────────────────────────────────────────────────────────
21
21
 
22
- const ANALYST_SYSTEM_PROMPT = `你是一位高级软件架构师,正在深度分析一个真实项目。
22
+ const ANALYST_SYSTEM_PROMPT = `你是一位高级软件架构师,正在深度分析一个真实项目的某个维度。
23
23
 
24
- ## 工具使用优先级
25
- 1. **结构化查询优先** — get_project_overview → get_class_hierarchy → get_class_info
26
- 这些工具返回精确的类继承、协议实现、方法签名信息,比文本搜索更高效。
27
- 2. **文本搜索补充** — search_project_code 用于查找特定模式/关键字
28
- 3. **文件阅读验证** — read_project_file 用于确认具体实现细节
24
+ ## 执行计划
25
+ 你有 **N 轮**工具调用机会(系统会告知具体数字)。请严格按以下节奏分配:
29
26
 
30
- > ⚠️ 避免反复调用 search_project_code 搜索不同关键词。
31
- > 如果需要了解类结构,直接用 get_class_info 查询。
27
+ | 阶段 | 轮次占比 | 目标 |
28
+ |------|---------|------|
29
+ | 1. 全局扫描 | 第 1-3 轮 | get_project_overview + list_project_structure 了解项目结构 |
30
+ | 2. 结构化探索 | 第 4-N×60% 轮 | get_class_hierarchy / get_class_info 理解核心类;search_project_code 批量搜索关键模式 |
31
+ | 3. 深度验证 | 第 N×60%-N×80% 轮 | read_project_file 阅读关键实现,确认细节 |
32
+ | 4. 输出总结 | 最后 20% | **停止调用工具**,直接输出你的分析文本 |
32
33
 
34
+ ## 关键规则
35
+ - **到达 80% 轮次时必须开始写总结**,不要等系统提醒
36
+ - 每一轮都必须调用工具获取新信息,不要花轮次在纯文本思考上
37
+ - 不要重复搜索相同关键词或读取相同文件(系统会返回缓存并扣轮次)
38
+
39
+ ## 工具效率
40
+ - **批量搜索**: search_project_code({ patterns: ["keywordA", "keywordB", "keywordC"] }) — 一次搜 3-5 个
41
+ - **批量读文件**: read_project_file({ filePaths: ["a.m", "b.m", "c.m"] }) — 一次读 3-5 个
42
+ - **结构化查询优先**: get_class_hierarchy / get_class_info 比文本搜索更精确高效
43
+
44
+ ## 输出要求
33
45
  输出你的分析发现,包括具体的文件路径和代码位置。
34
- 不需要任何特定格式,用自然语言描述你的理解即可。
35
- 尽可能多地使用工具来获取准确信息,不要猜测。`;
46
+ 用自然语言描述你的理解,不需要特定格式。`;
36
47
 
37
48
  // ──────────────────────────────────────────────────────────────────
38
49
  // Analyst 可用工具白名单 — 只做探索,不做提交
@@ -61,12 +72,12 @@ const ANALYST_TOOLS = [
61
72
  // ──────────────────────────────────────────────────────────────────
62
73
 
63
74
  const ANALYST_BUDGET = {
64
- maxIterations: 12, // was 20多数维度 5-9 轮完成
65
- searchBudget: 10, // was 15探索为主,精简预算
66
- searchBudgetGrace: 6, // was 10
75
+ maxIterations: 24, // was 18大项目维度需要充足探索轮次
76
+ searchBudget: 18, // was 14匹配更大探索空间
77
+ searchBudgetGrace: 10, // was 8
67
78
  maxSubmits: 0, // Analyst 不提交候选
68
79
  softSubmitLimit: 0,
69
- idleRoundsToExit: 2, // was 3 — 减少空转
80
+ idleRoundsToExit: 2, // 减少空转
70
81
  };
71
82
 
72
83
  // ──────────────────────────────────────────────────────────────────
@@ -27,7 +27,7 @@ export class CandidateGuardrail {
27
27
 
28
28
  /**
29
29
  * 验证候选结构
30
- * @param {object} candidate — submit_candidate 工具参数
30
+ * @param {object} candidate — submit_knowledge 工具参数
31
31
  * @returns {{ valid: boolean, error?: string }}
32
32
  */
33
33
  validateStructure(candidate) {