autosnippet 2.6.0 → 2.7.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/bin/cli.js +1 -1
- package/dashboard/dist/assets/{icons-rnn04CvH.js → icons-Cq4-iQhP.js} +148 -88
- package/dashboard/dist/assets/index-DBxH7pVn.css +1 -0
- package/dashboard/dist/assets/index-Dw2F6qAS.js +197 -0
- package/dashboard/dist/assets/{react-markdown-CWxUbOf4.js → react-markdown-BA6FB2NP.js} +1 -1
- package/dashboard/dist/assets/{syntax-highlighter-CJ2drQQb.js → syntax-highlighter-CVLHn9O5.js} +1 -1
- package/dashboard/dist/assets/{vendor-f83ah6cm.js → vendor-BotF760a.js} +61 -61
- package/dashboard/dist/index.html +6 -6
- package/lib/bootstrap.js +1 -1
- package/lib/cli/SetupService.js +33 -8
- package/lib/cli/UpgradeService.js +139 -2
- package/lib/core/ast/ProjectGraph.js +599 -0
- package/lib/core/gateway/GatewayActionRegistry.js +2 -2
- package/lib/domain/recipe/Recipe.js +3 -0
- package/lib/external/ai/AiProvider.js +83 -20
- package/lib/external/ai/providers/ClaudeProvider.js +197 -0
- package/lib/external/ai/providers/GoogleGeminiProvider.js +235 -1
- package/lib/external/ai/providers/OpenAiProvider.js +131 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +216 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +468 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +162 -0
- package/lib/external/mcp/handlers/bootstrap/skills.js +225 -0
- package/lib/external/mcp/handlers/bootstrap.js +151 -1634
- package/lib/external/mcp/handlers/browse.js +1 -1
- package/lib/external/mcp/handlers/candidate.js +1 -33
- package/lib/external/mcp/handlers/skill.js +54 -17
- package/lib/external/mcp/tools.js +4 -3
- package/lib/http/middleware/requestLogger.js +23 -4
- package/lib/http/routes/ai.js +3 -1
- package/lib/http/routes/auth.js +3 -2
- package/lib/http/routes/candidates.js +49 -25
- package/lib/http/routes/commands.js +0 -8
- package/lib/http/routes/guardRules.js +1 -16
- package/lib/http/routes/recipes.js +4 -17
- package/lib/http/routes/search.js +11 -19
- package/lib/http/routes/skills.js +2 -0
- package/lib/http/routes/snippets.js +0 -33
- package/lib/http/routes/spm.js +37 -63
- package/lib/http/utils/routeHelpers.js +31 -0
- package/lib/infrastructure/config/Paths.js +9 -0
- package/lib/infrastructure/logging/Logger.js +86 -3
- package/lib/infrastructure/realtime/RealtimeService.js +2 -5
- package/lib/infrastructure/vector/JsonVectorAdapter.js +24 -1
- package/lib/injection/ServiceContainer.js +55 -2
- package/lib/service/bootstrap/BootstrapTaskManager.js +400 -0
- package/lib/service/candidate/CandidateFileWriter.js +68 -27
- package/lib/service/candidate/CandidateService.js +156 -10
- package/lib/service/chat/AnalystAgent.js +216 -0
- package/lib/service/chat/CandidateGuardrail.js +134 -0
- package/lib/service/chat/ChatAgent.js +1036 -167
- package/lib/service/chat/ContextWindow.js +730 -0
- package/lib/service/chat/HandoffProtocol.js +180 -0
- package/lib/service/chat/ProducerAgent.js +240 -0
- package/lib/service/chat/ToolRegistry.js +149 -5
- package/lib/service/chat/tools.js +1397 -61
- package/lib/service/recipe/RecipeFileWriter.js +12 -1
- package/lib/service/skills/SignalCollector.js +31 -6
- package/lib/service/skills/SkillAdvisor.js +2 -1
- package/lib/service/skills/SkillHooks.js +13 -5
- package/lib/service/spm/SpmService.js +2 -2
- package/package.json +1 -1
- package/templates/copilot-instructions.md +20 -3
- package/templates/cursor-rules/autosnippet-conventions.mdc +21 -4
- package/templates/cursor-rules/autosnippet-skills.mdc +45 -0
- package/dashboard/dist/assets/index-BBKa3Dgi.js +0 -195
- package/dashboard/dist/assets/index-DLsECfzW.css +0 -1
|
@@ -1,265 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP Handlers — Bootstrap 冷启动知识库初始化 (
|
|
2
|
+
* MCP Handlers — Bootstrap 冷启动知识库初始化 (v5 + Async Fill)
|
|
3
3
|
*
|
|
4
4
|
* 统一底层逻辑:ChatAgent 和外部 Agent (MCP) 共享同一套 Skill 增强的 Bootstrap。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Phase
|
|
6
|
+
* v5 架构变更:快速骨架 + 异步逐维度填充(前端 loading 卡片 → 完成通知)
|
|
7
|
+
*
|
|
8
|
+
* 同步阶段(快速返回,~1-3s):
|
|
9
|
+
* Phase 1 → 文件收集(SPM Target 源文件扫描)
|
|
10
|
+
* Phase 1.5 → AST 代码结构分析(Tree-sitter)
|
|
11
|
+
* Phase 2 → SPM 依赖关系 → knowledge_edges(模块级图谱)
|
|
12
|
+
* Phase 3 → Guard 规则审计
|
|
10
13
|
* Phase 3.5 → [Skill-aware] 加载 coldstart + language-reference Skills → 增强维度定义
|
|
11
|
-
* Phase 4
|
|
12
|
-
* Phase 5 → 逐维度 × 子主题提取代码特征 → 创建 N 条 Candidate(PENDING 状态)
|
|
14
|
+
* Phase 4 → 构建响应骨架(filesByTarget + analysisFramework + 任务清单)
|
|
13
15
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* 异步阶段(后台逐一填充,通过 Socket.io 推送进度):
|
|
17
|
+
* Phase 5 → 微观维度 × 子主题提取代码特征 → 创建 N 条 Candidate(PENDING 状态)
|
|
18
|
+
* skillWorthy 维度仅提取内容,不创建 Candidate(避免与 Skill 重复)
|
|
19
|
+
* anti-pattern 已移除 — 代码问题由 Guard 独立处理
|
|
20
|
+
* Phase 5.5 → 宏观维度(architecture/code-standard/project-profile/agent-guidelines)
|
|
21
|
+
* 自动聚合为 Project Skill → 写入 AutoSnippet/skills/(不产生 Candidate)
|
|
17
22
|
*
|
|
18
|
-
*
|
|
23
|
+
* 进度推送事件(Socket.io + EventBus):
|
|
24
|
+
* bootstrap:started — 骨架创建完成,携带任务清单
|
|
25
|
+
* bootstrap:task-started — 单个维度开始填充
|
|
26
|
+
* bootstrap:task-completed — 单个维度填充完成
|
|
27
|
+
* bootstrap:task-failed — 单个维度失败
|
|
28
|
+
* bootstrap:all-completed — 全部维度完成(前端弹出通知)
|
|
19
29
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
30
|
+
* 模块结构:
|
|
31
|
+
* bootstrap.js ← 主入口 (本文件)
|
|
32
|
+
* bootstrap/skills.js ← Skill 加载与维度增强
|
|
33
|
+
* bootstrap/patterns.js ← 多语言代码模式匹配
|
|
34
|
+
* bootstrap/dimensions.js ← 7 维度知识提取器
|
|
35
|
+
* bootstrap/projectSkills.js ← Phase 5.5 Project Skill 生成
|
|
22
36
|
*/
|
|
23
37
|
|
|
24
38
|
import fs from 'node:fs';
|
|
25
39
|
import path from 'node:path';
|
|
26
|
-
import { fileURLToPath } from 'node:url';
|
|
27
40
|
import { envelope } from '../envelope.js';
|
|
28
41
|
import { inferLang, detectPrimaryLanguage, buildLanguageExtension } from './LanguageExtensions.js';
|
|
29
42
|
import { inferTargetRole, inferFilePriority } from './TargetClassifier.js';
|
|
30
43
|
import { analyzeProject, generateContextForAgent, isAvailable as astIsAvailable } from '../../../core/AstAnalyzer.js';
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// ═══════════════════════════════════════════════════════════
|
|
36
|
-
// 共享基础设施:Skills 加载(ChatAgent + MCP 统一使用)
|
|
37
|
-
// ═══════════════════════════════════════════════════════════
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 语言 → 参考 Skill 映射
|
|
41
|
-
*/
|
|
42
|
-
const LANG_SKILL_MAP = {
|
|
43
|
-
swift: 'autosnippet-reference-swift',
|
|
44
|
-
objectivec: 'autosnippet-reference-objc',
|
|
45
|
-
javascript: 'autosnippet-reference-jsts',
|
|
46
|
-
typescript: 'autosnippet-reference-jsts',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* loadBootstrapSkills — 加载冷启动相关 Skills(共享层)
|
|
51
|
-
*
|
|
52
|
-
* 统一入口:无论是 ChatAgent 还是 MCP 外部 Agent,都通过这个函数加载 Skills。
|
|
53
|
-
* 返回的 skillContext 可直接传给 bootstrapKnowledge 增强维度定义。
|
|
54
|
-
*
|
|
55
|
-
* @param {string} [primaryLanguage] 项目主语言(如 'swift')。传入后加载对应 reference Skill。
|
|
56
|
-
* @param {object} [logger] 可选日志实例
|
|
57
|
-
* @returns {{ coldstartSkill: string|null, languageSkill: string|null, languageSkillName: string|null, loaded: string[] }}
|
|
58
|
-
*/
|
|
59
|
-
export function loadBootstrapSkills(primaryLanguage, logger) {
|
|
60
|
-
const result = {
|
|
61
|
-
coldstartSkill: null,
|
|
62
|
-
languageSkill: null,
|
|
63
|
-
languageSkillName: null,
|
|
64
|
-
loaded: [],
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// 加载 coldstart Skill
|
|
68
|
-
try {
|
|
69
|
-
const coldstartPath = path.join(SKILLS_DIR, 'autosnippet-coldstart', 'SKILL.md');
|
|
70
|
-
result.coldstartSkill = fs.readFileSync(coldstartPath, 'utf8');
|
|
71
|
-
result.loaded.push('autosnippet-coldstart');
|
|
72
|
-
} catch {
|
|
73
|
-
logger?.warn?.('[Bootstrap] coldstart Skill not found, using default dimensions');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// 按主语言加载 reference Skill
|
|
77
|
-
const langSkillName = primaryLanguage ? LANG_SKILL_MAP[primaryLanguage.toLowerCase()] : null;
|
|
78
|
-
if (langSkillName) {
|
|
79
|
-
try {
|
|
80
|
-
const langPath = path.join(SKILLS_DIR, langSkillName, 'SKILL.md');
|
|
81
|
-
result.languageSkill = fs.readFileSync(langPath, 'utf8');
|
|
82
|
-
result.languageSkillName = langSkillName;
|
|
83
|
-
result.loaded.push(langSkillName);
|
|
84
|
-
} catch {
|
|
85
|
-
logger?.warn?.(`[Bootstrap] Language Skill ${langSkillName} not found`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return result;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Skill 中提取维度增强指引
|
|
94
|
-
*
|
|
95
|
-
* 1) coldstart SKILL.md 中的 "Per-Dimension Industry Reference Templates" 是**格式示例**(Swift 代码),
|
|
96
|
-
* 仅当无语言 Skill 时才用作 fallback;
|
|
97
|
-
* 2) 语言 Skill(如 reference-objc)包含真正的业界最佳实践内容,优先使用;
|
|
98
|
-
* 3) 返回 per-section 结构以便后续 per-candidate 精准匹配。
|
|
99
|
-
*
|
|
100
|
-
* @param {object} skillContext — 由 loadBootstrapSkills 返回
|
|
101
|
-
* @returns {{ guides: Record<string, string>, sectionMap: Record<string, Array<{title: string, content: string, keywords: string[]}>> }}
|
|
102
|
-
*/
|
|
103
|
-
function _extractSkillDimensionGuides(skillContext) {
|
|
104
|
-
const guides = {}; // dimId → summary guide text
|
|
105
|
-
const sectionMap = {}; // dimId → [{title, content, keywords}]
|
|
106
|
-
const hasLanguageSkill = !!skillContext.languageSkill;
|
|
107
|
-
|
|
108
|
-
// ── coldstart 模板: 仅在无语言 Skill 时用作 fallback ──
|
|
109
|
-
// coldstart 中的 rationale/whyStandard 是 Swift 示例,不适合直接注入其它语言项目
|
|
110
|
-
if (skillContext.coldstartSkill && !hasLanguageSkill) {
|
|
111
|
-
const content = skillContext.coldstartSkill;
|
|
112
|
-
const dimBlocks = content.matchAll(/###\s+维度\s*\d+\s*[::]\s*(.+?)\s*\(([^)]+)\)\s*[—–-]\s*参考模板\s*\n([\s\S]*?)(?=\n###\s|\n##\s)/g);
|
|
113
|
-
for (const match of dimBlocks) {
|
|
114
|
-
let dimId = match[2].trim();
|
|
115
|
-
if (/solution|antiPattern|bug/i.test(dimId)) dimId = 'bug-fix';
|
|
116
|
-
dimId = dimId.replace(/\s+/g, '-');
|
|
117
|
-
const block = match[3];
|
|
118
|
-
const rationaleMatch = block.match(/"rationale"\s*:\s*"([^"]{20,300})"/);
|
|
119
|
-
const whyMatch = block.match(/"whyStandard"\s*:\s*"([^"]{20,200})"/);
|
|
120
|
-
const extraGuide = [rationaleMatch?.[1], whyMatch?.[1]].filter(Boolean).join('。');
|
|
121
|
-
if (extraGuide) {
|
|
122
|
-
guides[dimId] = extraGuide;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ── 语言 Skill: 逐 section 提取丰富内容作为业界参考(PRIMARY) ──
|
|
128
|
-
if (skillContext.languageSkill) {
|
|
129
|
-
const content = skillContext.languageSkill;
|
|
130
|
-
|
|
131
|
-
// heading → dimension(s) + 子主题匹配关键词
|
|
132
|
-
const HEADING_DIM_MAP = [
|
|
133
|
-
{ pattern: /命名|naming|前缀|prefix/i, dims: ['code-standard', 'code-pattern'], keywords: ['naming', 'prefix', '命名', '前缀', 'category'] },
|
|
134
|
-
{ pattern: /属性|propert/i, dims: ['code-standard', 'best-practice', 'bug-fix'], keywords: ['property', '属性', 'copy', 'weak', 'strong', 'memory', 'retain', 'cycle', 'leak'] },
|
|
135
|
-
{ pattern: /delegate|委托/i, dims: ['call-chain', 'code-pattern'], keywords: ['delegate', 'protocol', '委托', '协议'] },
|
|
136
|
-
{ pattern: /初始化|initializ/i, dims: ['code-pattern'], keywords: ['init', 'initializer', '初始化', 'factory'] },
|
|
137
|
-
{ pattern: /null|可选/i, dims: ['code-standard'], keywords: ['nullable', 'nonnull', 'nullability'] },
|
|
138
|
-
{ pattern: /错误处理|error/i, dims: ['best-practice'], keywords: ['error', 'NSError', '错误', 'error-handling'] },
|
|
139
|
-
{ pattern: /bool|陷阱/i, dims: ['bug-fix'], keywords: ['BOOL', 'bool', '陷阱', 'trap'] },
|
|
140
|
-
{ pattern: /gcd|线程|thread|并发|concurrent/i, dims: ['best-practice', 'bug-fix'], keywords: ['GCD', 'dispatch', 'thread', '线程', 'main', 'concurrency', 'main-thread'] },
|
|
141
|
-
{ pattern: /泛型|generic/i, dims: ['code-standard'], keywords: ['generics', '泛型', 'generic'] },
|
|
142
|
-
{ pattern: /import|导入/i, dims: ['code-standard'], keywords: ['import', '#import', '导入', 'file-organization'] },
|
|
143
|
-
{ pattern: /特有维度|extra.?dim/i, dims: ['agent-guidelines'], keywords: ['agent', '注意', '维度', 'extra'] },
|
|
144
|
-
{ pattern: /category|扩展(?!.*特有)/i, dims: ['code-pattern'], keywords: ['category', 'extension', '扩展'] },
|
|
145
|
-
{ pattern: /singleton|单例/i, dims: ['code-pattern'], keywords: ['singleton', '单例', 'dispatch_once'] },
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
// 用 --- 分割section,更可靠地提取完整 section body
|
|
149
|
-
const sectionParts = content.split(/\n---\n/);
|
|
150
|
-
for (const part of sectionParts) {
|
|
151
|
-
const headingMatch = part.match(/^##\s+\d+\.\s+(.+?)(?:\s*\(.+\))?\s*$/m);
|
|
152
|
-
if (!headingMatch) continue;
|
|
153
|
-
|
|
154
|
-
const heading = headingMatch[1].trim();
|
|
155
|
-
const bodyStart = part.indexOf(headingMatch[0]) + headingMatch[0].length;
|
|
156
|
-
const body = part.substring(bodyStart);
|
|
157
|
-
|
|
158
|
-
// 查找匹配的 dimension(s)
|
|
159
|
-
let matchedDims = [];
|
|
160
|
-
let matchedKeywords = [];
|
|
161
|
-
for (const mapping of HEADING_DIM_MAP) {
|
|
162
|
-
if (mapping.pattern.test(heading)) {
|
|
163
|
-
matchedDims.push(...mapping.dims);
|
|
164
|
-
matchedKeywords.push(...mapping.keywords);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
matchedDims = [...new Set(matchedDims)];
|
|
168
|
-
if (matchedDims.length === 0) continue;
|
|
169
|
-
|
|
170
|
-
// 提取有意义的摘要内容
|
|
171
|
-
let summary = _extractSectionSummary(body);
|
|
172
|
-
// 如果 section body 主要是代码块导致摘要太短,用 heading 本身作为摘要前缀
|
|
173
|
-
if (summary.length < 20) {
|
|
174
|
-
summary = `${heading}:${summary}`;
|
|
175
|
-
}
|
|
176
|
-
if (summary.length < 10) continue;
|
|
177
|
-
|
|
178
|
-
const sectionData = {
|
|
179
|
-
title: heading,
|
|
180
|
-
content: summary.substring(0, 500),
|
|
181
|
-
keywords: [...new Set(matchedKeywords)],
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
for (const dimId of matchedDims) {
|
|
185
|
-
if (!sectionMap[dimId]) sectionMap[dimId] = [];
|
|
186
|
-
sectionMap[dimId].push(sectionData);
|
|
187
|
-
|
|
188
|
-
const shortContent = summary.substring(0, 120);
|
|
189
|
-
if (!guides[dimId]) {
|
|
190
|
-
guides[dimId] = `[${heading}] ${shortContent}`;
|
|
191
|
-
} else if (guides[dimId].length < 500) {
|
|
192
|
-
guides[dimId] += `; [${heading}] ${shortContent}`;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return { guides, sectionMap };
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* 从 Skill section body 中提取有意义的摘要内容
|
|
203
|
-
* 跳过 JSON 模板、代码块(保留关键注释),保留表格和文字描述
|
|
204
|
-
*/
|
|
205
|
-
function _extractSectionSummary(body) {
|
|
206
|
-
const lines = body.split('\n');
|
|
207
|
-
const parts = [];
|
|
208
|
-
let inCodeBlock = false;
|
|
209
|
-
let inJsonBlock = false;
|
|
210
|
-
|
|
211
|
-
for (const line of lines) {
|
|
212
|
-
const trimmed = line.trim();
|
|
213
|
-
|
|
214
|
-
// 跳过 JSON 模板块(候选格式示例)
|
|
215
|
-
if (trimmed.startsWith('```json')) { inJsonBlock = true; continue; }
|
|
216
|
-
if (inJsonBlock) { if (trimmed === '```') inJsonBlock = false; continue; }
|
|
217
|
-
|
|
218
|
-
// 追踪代码块 — 保留 ✅/❌ 关键注释
|
|
219
|
-
if (trimmed.startsWith('```')) { inCodeBlock = !inCodeBlock; continue; }
|
|
220
|
-
if (inCodeBlock) {
|
|
221
|
-
if (/[✅❌]/.test(trimmed) && parts.length < 12) parts.push(trimmed);
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (!trimmed) continue;
|
|
226
|
-
if (trimmed.startsWith('###') || trimmed.startsWith('####')) continue;
|
|
227
|
-
if (/^\|[-\s|:]+\|$/.test(trimmed)) continue; // 表格分隔线
|
|
228
|
-
|
|
229
|
-
parts.push(trimmed);
|
|
230
|
-
if (parts.length >= 10) break;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return parts.join('; ').replace(/\s{2,}/g, ' ').trim();
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* 增强 9 维度定义 — 将 Skill 提供的参考指引注入 dimensions[].guide
|
|
238
|
-
*
|
|
239
|
-
* @param {Array} dimensions — 原始维度数组
|
|
240
|
-
* @param {Record<string, string>} skillGuides — guides 部分
|
|
241
|
-
* @param {Record<string, Array>} skillSections — sectionMap 部分(per-candidate 匹配用)
|
|
242
|
-
* @returns {Array} 增强后的维度数组(原数组不变,返回新数组)
|
|
243
|
-
*/
|
|
244
|
-
function _enhanceDimensions(dimensions, skillGuides, skillSections) {
|
|
245
|
-
if (!skillGuides || Object.keys(skillGuides).length === 0) return dimensions;
|
|
45
|
+
// ── Sub-modules ──
|
|
46
|
+
import { loadBootstrapSkills, extractSkillDimensionGuides, enhanceDimensions } from './bootstrap/skills.js';
|
|
47
|
+
import { fillDimensionsV3 } from './bootstrap/pipeline/orchestrator.js';
|
|
246
48
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (!extra) return dim;
|
|
250
|
-
return {
|
|
251
|
-
...dim,
|
|
252
|
-
guide: `${dim.guide}。[Skill 参考] ${extra}`,
|
|
253
|
-
_skillEnhanced: true,
|
|
254
|
-
_skillSections: skillSections?.[dim.id] || [],
|
|
255
|
-
};
|
|
256
|
-
});
|
|
257
|
-
}
|
|
49
|
+
// Re-export for external consumers
|
|
50
|
+
export { loadBootstrapSkills };
|
|
258
51
|
|
|
259
52
|
/**
|
|
260
53
|
* bootstrapKnowledge — 一键初始化知识库 (Skill-aware)
|
|
261
54
|
*
|
|
262
|
-
* 覆盖
|
|
55
|
+
* 覆盖 7 大知识维度: 项目规范、使用习惯、架构模式、代码模式、最佳实践、项目库特征、Agent开发注意事项
|
|
56
|
+
* (注意:反模式/代码问题由 Guard 独立处理,不在 Bootstrap 覆盖范围)
|
|
263
57
|
* 为每个维度自动创建 Candidate(PENDING),外部 Agent 可按文件粒度补充更多候选。
|
|
264
58
|
*
|
|
265
59
|
* @param {object} ctx { container, logger }
|
|
@@ -465,7 +259,7 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
465
259
|
ctx.logger.info(`[Bootstrap] Skills loaded: ${skillContext.loaded.join(', ') || 'none'}`);
|
|
466
260
|
}
|
|
467
261
|
const { guides: skillGuides, sectionMap: skillSections } = skillContext
|
|
468
|
-
?
|
|
262
|
+
? extractSkillDimensionGuides(skillContext)
|
|
469
263
|
: { guides: {}, sectionMap: {} };
|
|
470
264
|
const skillsEnhanced = Object.keys(skillGuides).length > 0;
|
|
471
265
|
|
|
@@ -491,20 +285,44 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
491
285
|
};
|
|
492
286
|
|
|
493
287
|
// 9 维度定义(Phase 4 响应 + Phase 5 候选创建共用)
|
|
288
|
+
// skillWorthy 维度会在 Phase 5.5 自动生成 Project Skill(宏观叙事性知识)
|
|
289
|
+
// 注意:anti-pattern 已移除 — 代码问题由 Guard 独立处理,bootstrap 不做错误检测
|
|
290
|
+
//
|
|
291
|
+
// 执行顺序优化(v5.1):
|
|
292
|
+
// ⑧ ⑨ 提前到 ⑥ 前面执行 → deep-scan / category-scan 的中间结果
|
|
293
|
+
// 通过 PipelineContext 缓存,供 project-profile 复用,避免重复扫描
|
|
494
294
|
const baseDimensions = [
|
|
495
|
-
|
|
496
|
-
{ id: 'code-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
{ id: '
|
|
500
|
-
|
|
501
|
-
{ id: '
|
|
502
|
-
|
|
503
|
-
|
|
295
|
+
// ① 代码规范(Dual: Skill + Candidate)— 新增 api-naming、comment-style 子主题
|
|
296
|
+
{ id: 'code-standard', label: '代码规范', guide: '命名约定(类名前缀/方法签名风格/API 命名)、注释风格(语言/格式/MARK 分段)、文件组织规范', knowledgeTypes: ['code-standard', 'code-style'],
|
|
297
|
+
skillWorthy: true, dualOutput: true, skillMeta: { name: 'project-code-standard', description: 'Project coding standards and naming conventions (auto-generated by bootstrap)' } },
|
|
298
|
+
// ② 代码模式(Candidate)— 新增 builder、observer、coordinator
|
|
299
|
+
{ id: 'code-pattern', label: '设计模式与代码惯例', guide: '单例/委托/Category·Extension/工厂/Builder/观察者/Coordinator 模式、继承关系', knowledgeTypes: ['code-pattern', 'code-relation', 'inheritance'] },
|
|
300
|
+
// ③ 架构模式(Dual: Skill + Candidate)— 新增 boundary-rules 子主题
|
|
301
|
+
{ id: 'architecture', label: '架构模式', guide: '分层架构、模块职责与边界、依赖图、导入约束规则', knowledgeTypes: ['architecture', 'module-dependency', 'boundary-constraint'],
|
|
302
|
+
skillWorthy: true, dualOutput: true, skillMeta: { name: 'project-architecture', description: 'Project architecture layers, module boundaries and dependency graph (auto-generated by bootstrap)' } },
|
|
303
|
+
// ④ 最佳实践(Candidate)— 新增 logging、testing 子主题
|
|
304
|
+
{ id: 'best-practice', label: '最佳实践', guide: '错误处理、并发安全、内存管理、日志规范、测试模式', knowledgeTypes: ['best-practice'] },
|
|
305
|
+
// ⑤ 事件与数据流(Candidate)— 合并原 call-chain + data-flow,消除重叠
|
|
306
|
+
{ id: 'event-and-data-flow', label: '事件与数据流', guide: '事件传播(Delegate/Notification/Block·Closure/Target-Action)、数据状态管理(KVO/属性观察/响应式/持久化)', knowledgeTypes: ['call-chain', 'data-flow'] },
|
|
307
|
+
// ⑧ ObjC/Swift 深度扫描(dualOutput: Skill + Candidate → Recipe → Snippet)— 常量 + Hook
|
|
308
|
+
// ★ 提前到 ⑥ 前面执行,中间结果缓存到 PipelineContext 供 project-profile 复用
|
|
309
|
+
{ id: 'objc-deep-scan', label: '深度扫描(常量/Hook)', guide: '全量扫描 #define 值宏/函数宏、extern/static 常量、Method Swizzling hook 对(Agent 必须使用项目常量,修改被 hook 方法前必须查阅 hook 清单)', knowledgeTypes: ['code-standard', 'code-pattern'],
|
|
310
|
+
skillWorthy: true, dualOutput: true, skillMeta: { name: 'project-objc-deep-scan', description: 'Project #define macros, static constants, and Method Swizzling hooks (auto-generated by bootstrap)' } },
|
|
311
|
+
// ⑨ Foundation/UIKit Category/Extension 专项扫描(dualOutput: Skill + Candidate → Recipe → Snippet)
|
|
312
|
+
// ★ 提前到 ⑥ 前面执行,分类方法清单缓存到 PipelineContext 供 project-profile/base-extensions 复用
|
|
313
|
+
{ id: 'category-scan', label: '基础类分类方法扫描', guide: 'Foundation/UIKit Category/Extension 逐方法清单(含完整实现代码与项目使用频次),仅扫描基础类分类、不含业务代码(Agent 遇到同等功能必须使用项目已有分类方法,禁止重复实现)', knowledgeTypes: ['code-standard', 'code-pattern'],
|
|
314
|
+
skillWorthy: true, dualOutput: true, skillMeta: { name: 'project-category-scan', description: 'Foundation/UIKit Category and Extension methods with implementations and usage patterns — base classes only, no business code (auto-generated by bootstrap)' } },
|
|
315
|
+
// ⑥ 项目特征(Dual: Skill + Candidate)— v4.2: 8 个子主题
|
|
316
|
+
// ★ 排在 ⑧⑨ 之后,可从 PipelineContext 读取 deep-scan/category-scan 的缓存结果
|
|
317
|
+
{ id: 'project-profile', label: '项目特征', guide: '技术栈、目录结构、三方依赖枚举与用途、Extension/Category 分类聚合、自定义基类层级与全局定义(宏/typealias/PCH)、系统事件 hook 与生命周期入口、基础设施服务注册表、Runtime 与语言互操作', knowledgeTypes: ['architecture'],
|
|
318
|
+
skillWorthy: true, dualOutput: true, skillMeta: { name: 'project-profile', description: 'Project tech stack, module structure, third-party dependencies, base extensions/classes, event hooks, infrastructure services, and runtime/interop features (auto-generated by bootstrap)' } },
|
|
319
|
+
// ⑦ Agent 开发注意事项(Skill)— 新增 deprecated-api、arch-constraints、coding-principles 子主题
|
|
320
|
+
{ id: 'agent-guidelines', label: 'Agent开发注意事项', guide: '三大核心原则(严谨性/深度特征挖掘/完整性)、命名强制、线程安全、内存约束、已废弃 API 标记、架构约束注释、TODO/FIXME', knowledgeTypes: ['boundary-constraint', 'code-standard'],
|
|
321
|
+
skillWorthy: true, skillMeta: { name: 'project-agent-guidelines', description: 'Mandatory coding rules, deprecated APIs and agent constraints for this project (auto-generated by bootstrap)' } },
|
|
504
322
|
];
|
|
505
323
|
|
|
506
324
|
// 用 Skill 内容增强维度 guide(共享层增强点)
|
|
507
|
-
const dimensions =
|
|
325
|
+
const dimensions = enhanceDimensions(baseDimensions, skillGuides, skillSections);
|
|
508
326
|
|
|
509
327
|
const responseData = {
|
|
510
328
|
report,
|
|
@@ -541,12 +359,15 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
541
359
|
}))
|
|
542
360
|
: [],
|
|
543
361
|
|
|
544
|
-
// 9
|
|
362
|
+
// 9 维度分析框架(4 Skill-only + 2 dualOutput + 3 Candidate-only)
|
|
363
|
+
// 注意:anti-pattern 已移除,代码问题由 Guard 独立处理
|
|
545
364
|
analysisFramework: {
|
|
546
365
|
dimensions,
|
|
366
|
+
skillWorthyDimensions: dimensions.filter(d => d.skillWorthy).map(d => d.id),
|
|
367
|
+
candidateOnlyDimensions: dimensions.filter(d => !d.skillWorthy).map(d => d.id),
|
|
547
368
|
candidateRequiredFields: ['title', 'code', 'language', 'category', 'knowledgeType', 'reasoning'],
|
|
548
369
|
submissionTool: 'autosnippet_submit_candidates',
|
|
549
|
-
expectedOutput: '
|
|
370
|
+
expectedOutput: '候选知识(微观代码维度:code-pattern/best-practice/event-and-data-flow + 深度扫描:objc-deep-scan/category-scan)+ 6 个 Project Skills(宏观叙事维度:code-standard/architecture/project-profile/agent-guidelines + 深度扫描:objc-deep-scan/category-scan)',
|
|
550
371
|
},
|
|
551
372
|
|
|
552
373
|
// AST 代码结构分析上下文(供 ChatAgent 使用)
|
|
@@ -592,86 +413,94 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
592
413
|
'4. 逐 Target 深入分析 filesByTarget,按 analysisFramework.dimensions 补充更细粒度候选',
|
|
593
414
|
'5. 优先分析 priority=high 的文件和 inferredRole=core/service 的 Target',
|
|
594
415
|
'6. 参考 dependencyGraph 理解模块间依赖关系',
|
|
595
|
-
'7. 参考 guardViolationFiles
|
|
596
|
-
'8.
|
|
597
|
-
'9.
|
|
598
|
-
'
|
|
416
|
+
'7. 参考 guardViolationFiles 了解已发现的规范违反(代码问题由 Guard 独立处理)',
|
|
417
|
+
'8. Agent注意事项维度需包含 trigger 字段(如 @agent-threading)',
|
|
418
|
+
'9. 新提交的候选重复 Step 1-3 确保质量',
|
|
419
|
+
'',
|
|
420
|
+
'== 注意:宏观维度走 Project Skills,不产生 Candidate ==',
|
|
421
|
+
'宏观维度(architecture、code-standard、project-profile、agent-guidelines)',
|
|
422
|
+
'已自动生成 Project Skill 到 AutoSnippet/skills/,可通过 autosnippet_load_skill 加载。',
|
|
423
|
+
'这些维度不会创建 Candidate/Recipe,避免重复。后续 AI 精炼步骤仅针对微观维度的 Candidate。',
|
|
599
424
|
'',
|
|
600
425
|
'质量红线:summary 不得包含「本模块」「该文件」等泛化措辞;每条候选必须有 reasoning;confidence < 0.5 的需标注原因。',
|
|
426
|
+
'三大核心原则(严谨性·深度特征·完整性)已写入 project-agent-guidelines Skill,Agent 加载后自动遵循。',
|
|
601
427
|
],
|
|
602
428
|
};
|
|
603
429
|
|
|
604
430
|
// ═══════════════════════════════════════════════════════════
|
|
605
|
-
// Phase 5:
|
|
431
|
+
// Phase 5: 创建异步任务 — 骨架先返回,内容后填充
|
|
432
|
+
//
|
|
433
|
+
// 策略变更(v5):
|
|
434
|
+
// 旧:同步遍历所有维度 → 提取 + 创建 Candidate → 一次性返回
|
|
435
|
+
// 新:快速创建任务清单 → 立即返回骨架 → 异步逐维度填充内容
|
|
436
|
+
// 前端通过 Socket.io 接收进度更新,卡片 loading → 完成
|
|
606
437
|
// ═══════════════════════════════════════════════════════════
|
|
607
|
-
const candidateResults = { created: 0, failed: 0, errors: [] };
|
|
608
|
-
try {
|
|
609
|
-
const candidateService = ctx.container.get('candidateService');
|
|
610
|
-
if (candidateService) {
|
|
611
|
-
for (const dim of dimensions) {
|
|
612
|
-
try {
|
|
613
|
-
// v3: 每维度返回 N 条候选(单一职责拆分)
|
|
614
|
-
const candidates = _extractDimensionCandidates(dim, allFiles, targetFileMap, {
|
|
615
|
-
depGraphData, guardAudit, langStats, primaryLang, astProjectSummary,
|
|
616
|
-
});
|
|
617
438
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
relations: c.relations || undefined,
|
|
630
|
-
reasoning: {
|
|
631
|
-
whyStandard: c._skillReference || c.summary,
|
|
632
|
-
sources: c.sources,
|
|
633
|
-
confidence: c._skillEnhanced ? 0.7 : 0.6,
|
|
634
|
-
qualitySignals: {
|
|
635
|
-
completeness: c._skillEnhanced ? 'skill-enhanced' : 'partial',
|
|
636
|
-
origin: 'bootstrap-scan',
|
|
637
|
-
...(c._skillEnhanced ? { skillSource: true } : {}),
|
|
638
|
-
},
|
|
639
|
-
},
|
|
640
|
-
}, 'bootstrap', {}, { userId: 'bootstrap_agent' });
|
|
439
|
+
// 构建任务定义列表
|
|
440
|
+
const taskDefs = dimensions.map(dim => ({
|
|
441
|
+
id: dim.id,
|
|
442
|
+
meta: {
|
|
443
|
+
type: dim.skillWorthy ? 'skill' : 'candidate',
|
|
444
|
+
dimId: dim.id,
|
|
445
|
+
label: dim.label,
|
|
446
|
+
skillWorthy: !!dim.skillWorthy,
|
|
447
|
+
skillMeta: dim.skillMeta || null,
|
|
448
|
+
},
|
|
449
|
+
}));
|
|
641
450
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
} catch (dimErr) {
|
|
649
|
-
candidateResults.failed++;
|
|
650
|
-
candidateResults.errors.push({ dimension: dim.id, error: dimErr.message });
|
|
651
|
-
ctx.logger.warn(`[Bootstrap] Candidate creation failed for ${dim.id}: ${dimErr.message}`);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
451
|
+
// 启动 BootstrapTaskManager 会话(通过正式 DI 获取单例)
|
|
452
|
+
let bootstrapSession = null;
|
|
453
|
+
try {
|
|
454
|
+
const taskManager = ctx.container.get('bootstrapTaskManager');
|
|
455
|
+
bootstrapSession = taskManager.startSession(taskDefs);
|
|
655
456
|
} catch (e) {
|
|
656
|
-
ctx.logger.warn(`[Bootstrap]
|
|
457
|
+
ctx.logger.warn(`[Bootstrap] BootstrapTaskManager init failed (graceful degradation): ${e.message}`);
|
|
657
458
|
}
|
|
658
459
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
responseData.
|
|
460
|
+
// 立即构建骨架响应
|
|
461
|
+
responseData.bootstrapSession = bootstrapSession ? bootstrapSession.toJSON() : null;
|
|
462
|
+
responseData.bootstrapCandidates = { created: 0, failed: 0, errors: [], status: 'filling' };
|
|
463
|
+
responseData.autoSkills = { created: 0, failed: 0, skills: [], errors: [], status: 'filling' };
|
|
663
464
|
responseData.skillsLoaded = skillContext?.loaded || [];
|
|
664
465
|
responseData.skillsEnhanced = skillsEnhanced;
|
|
665
|
-
responseData.message = `Bootstrap
|
|
466
|
+
responseData.message = `Bootstrap 骨架已创建: ${allFiles.length} files, ${allTargets.length} targets, ${taskDefs.length} 个维度任务已排队,正在后台逐一填充...`;
|
|
467
|
+
|
|
468
|
+
// ── 异步后台填充(fire-and-forget)──
|
|
469
|
+
const fillContext = {
|
|
470
|
+
ctx,
|
|
471
|
+
dimensions,
|
|
472
|
+
allFiles,
|
|
473
|
+
targetFileMap,
|
|
474
|
+
depGraphData,
|
|
475
|
+
guardAudit,
|
|
476
|
+
langStats,
|
|
477
|
+
primaryLang,
|
|
478
|
+
astProjectSummary,
|
|
479
|
+
skillContext,
|
|
480
|
+
skillsEnhanced,
|
|
481
|
+
taskManager: (() => { try { return ctx.container.get('bootstrapTaskManager'); } catch { return null; } })(),
|
|
482
|
+
sessionId: bootstrapSession?.id || null,
|
|
483
|
+
projectRoot,
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// 使用 setImmediate 避免阻塞 HTTP 响应
|
|
487
|
+
setImmediate(() => {
|
|
488
|
+
ctx.logger.info(`[Bootstrap] Dispatching v3 AI-First pipeline`);
|
|
489
|
+
fillDimensionsV3(fillContext).catch(e => {
|
|
490
|
+
ctx.logger.error(`[Bootstrap] Async fill (v3) failed: ${e.message}`);
|
|
491
|
+
});
|
|
492
|
+
});
|
|
666
493
|
|
|
667
|
-
// ── SkillHooks:
|
|
494
|
+
// ── SkillHooks: onBootstrapStarted (fire-and-forget) ──
|
|
668
495
|
try {
|
|
669
496
|
const skillHooks = ctx.container.get('skillHooks');
|
|
670
497
|
skillHooks.run('onBootstrapComplete', {
|
|
671
498
|
filesScanned: allFiles.length,
|
|
672
499
|
targetsFound: allTargets.length,
|
|
673
|
-
candidatesCreated:
|
|
674
|
-
candidatesFailed:
|
|
500
|
+
candidatesCreated: 0, // 异步填充中,初始为 0
|
|
501
|
+
candidatesFailed: 0,
|
|
502
|
+
autoSkillsCreated: 0,
|
|
503
|
+
autoSkills: [],
|
|
675
504
|
}, { projectRoot: ctx.container.get('database')?.filename || '' })
|
|
676
505
|
.catch(() => {}); // fire-and-forget
|
|
677
506
|
} catch { /* skillHooks not available */ }
|
|
@@ -701,12 +530,19 @@ export async function bootstrapRefine(ctx, args) {
|
|
|
701
530
|
const aiProvider = ctx.container.get('aiProvider');
|
|
702
531
|
|
|
703
532
|
if (!aiProvider) {
|
|
704
|
-
return envelope({ success: false,
|
|
533
|
+
return envelope({ success: false, message: 'AI provider not configured', errorCode: 'MISSING_AI_PROVIDER' });
|
|
705
534
|
}
|
|
706
535
|
|
|
536
|
+
// 接入 BootstrapTaskManager 双通道推送 refine:* 事件
|
|
537
|
+
let onProgress = null;
|
|
538
|
+
try {
|
|
539
|
+
const taskManager = ctx.container.get('bootstrapTaskManager');
|
|
540
|
+
onProgress = (eventName, data) => taskManager.emitProgress(eventName, data);
|
|
541
|
+
} catch { /* optional */ }
|
|
542
|
+
|
|
707
543
|
const result = await candidateService.refineBootstrapCandidates(
|
|
708
544
|
aiProvider,
|
|
709
|
-
{ candidateIds: args.candidateIds, userPrompt: args.userPrompt, dryRun: args.dryRun },
|
|
545
|
+
{ candidateIds: args.candidateIds, userPrompt: args.userPrompt, dryRun: args.dryRun, onProgress },
|
|
710
546
|
{ userId: 'external_agent' },
|
|
711
547
|
);
|
|
712
548
|
|
|
@@ -719,1322 +555,3 @@ export async function bootstrapRefine(ctx, args) {
|
|
|
719
555
|
meta: { tool: 'autosnippet_bootstrap_refine', responseTimeMs: Date.now() - t0 },
|
|
720
556
|
});
|
|
721
557
|
}
|
|
722
|
-
|
|
723
|
-
// ─── Phase 5 辅助:多语言代码提取 ───────────────────────────
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* 获取语言对应的类型定义正则
|
|
727
|
-
*/
|
|
728
|
-
function _getTypeDefPattern(lang) {
|
|
729
|
-
switch (lang) {
|
|
730
|
-
case 'objectivec':
|
|
731
|
-
return /^\s*@(interface|implementation|protocol)\s+\w+/m;
|
|
732
|
-
case 'swift':
|
|
733
|
-
return /^\s*(public |open |internal |private |fileprivate )?(final\s+)?(class|struct|protocol|enum)\s+\w+/m;
|
|
734
|
-
case 'javascript': case 'typescript':
|
|
735
|
-
return /^\s*(export\s+)?(default\s+)?(abstract\s+)?(class|interface|type|enum)\s+\w+/m;
|
|
736
|
-
case 'python':
|
|
737
|
-
return /^\s*class\s+\w+/m;
|
|
738
|
-
case 'java': case 'kotlin':
|
|
739
|
-
return /^\s*(public |private |protected )?(abstract |data |sealed |open )?(class|interface|enum|object)\s+\w+/m;
|
|
740
|
-
case 'go':
|
|
741
|
-
return /^\s*type\s+\w+\s+(struct|interface)\b/m;
|
|
742
|
-
case 'rust':
|
|
743
|
-
return /^\s*(pub\s+)?(struct|enum|trait|impl)\s+\w+/m;
|
|
744
|
-
default:
|
|
745
|
-
return /^\s*(class|struct|protocol|enum|interface|type)\s+\w+/m;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
/**
|
|
750
|
-
* 获取语言对应的最佳实践模式集合
|
|
751
|
-
*/
|
|
752
|
-
function _getBestPracticePatterns(lang) {
|
|
753
|
-
switch (lang) {
|
|
754
|
-
case 'objectivec':
|
|
755
|
-
return {
|
|
756
|
-
errorHandling: { label: '错误处理',
|
|
757
|
-
regex: /\b(NSError\s*\*|@try\b|@catch\b|@throw\b|@finally\b|error:\s*\(NSError|if\s*\(\s*error\b|if\s*\(\s*!\s*\w+\s*\))/,
|
|
758
|
-
},
|
|
759
|
-
concurrency: { label: '并发/异步',
|
|
760
|
-
regex: /\b(dispatch_async|dispatch_sync|dispatch_queue_create|dispatch_group|dispatch_semaphore|NSOperation|NSThread|@synchronized|performSelector.*Thread|dispatch_barrier)/,
|
|
761
|
-
},
|
|
762
|
-
memoryMgmt: { label: '内存管理',
|
|
763
|
-
regex: /\b(__weak|__strong|__unsafe_unretained|weakSelf|strongSelf|typeof\(self\)|__block|dealloc\b|autoreleasepool|removeObserver)/,
|
|
764
|
-
},
|
|
765
|
-
};
|
|
766
|
-
case 'swift':
|
|
767
|
-
return {
|
|
768
|
-
errorHandling: { label: '错误处理',
|
|
769
|
-
regex: /\b(guard .+ else|throw\s|catch\s*[{(]|do\s*\{|Result<|try[?!]?\s)/,
|
|
770
|
-
},
|
|
771
|
-
concurrency: { label: '并发/异步',
|
|
772
|
-
regex: /\b(async\b|await\b|Task\s*\{|Task\.detached|actor\b|@MainActor|@Sendable|DispatchQueue|TaskGroup)/,
|
|
773
|
-
},
|
|
774
|
-
memoryMgmt: { label: '内存管理',
|
|
775
|
-
regex: /\b(\[weak\s|weak\s+var|unowned\s|autoreleasepool|deinit\b|\[unowned\s)/,
|
|
776
|
-
},
|
|
777
|
-
};
|
|
778
|
-
case 'javascript': case 'typescript':
|
|
779
|
-
return {
|
|
780
|
-
errorHandling: { label: '错误处理',
|
|
781
|
-
regex: /\b(try\s*\{|catch\s*\(|throw\s+new|\.catch\(|Promise\.reject|if\s*\(\s*err\b)/,
|
|
782
|
-
},
|
|
783
|
-
concurrency: { label: '并发/异步',
|
|
784
|
-
regex: /\b(async\s+function|await\s|Promise\.all|Promise\.allSettled|new\s+Worker|setTimeout|setInterval|process\.nextTick)/,
|
|
785
|
-
},
|
|
786
|
-
memoryMgmt: { label: '资源管理',
|
|
787
|
-
regex: /\b(\.close\(\)|\.destroy\(\)|\.dispose\(\)|finally\s*\{|AbortController|clearTimeout|clearInterval|removeEventListener)/,
|
|
788
|
-
},
|
|
789
|
-
};
|
|
790
|
-
case 'python':
|
|
791
|
-
return {
|
|
792
|
-
errorHandling: { label: '错误处理',
|
|
793
|
-
regex: /\b(try:|except\s|raise\s|finally:|with\s.*as\s)/,
|
|
794
|
-
},
|
|
795
|
-
concurrency: { label: '并发/异步',
|
|
796
|
-
regex: /\b(async\s+def|await\s|asyncio\.|threading\.|multiprocessing\.|concurrent\.futures|Lock\(\)|Semaphore\()/,
|
|
797
|
-
},
|
|
798
|
-
memoryMgmt: { label: '资源管理',
|
|
799
|
-
regex: /\b(with\s+open|__enter__|__exit__|contextmanager|\.close\(\)|atexit|weakref|gc\.collect)/,
|
|
800
|
-
},
|
|
801
|
-
};
|
|
802
|
-
default:
|
|
803
|
-
return {
|
|
804
|
-
errorHandling: { label: '错误处理',
|
|
805
|
-
regex: /\b(try|catch|throw|except|raise|error|Error)\b/,
|
|
806
|
-
},
|
|
807
|
-
concurrency: { label: '并发/异步',
|
|
808
|
-
regex: /\b(async|await|thread|Thread|dispatch|concurrent|parallel|mutex|lock|Lock)\b/,
|
|
809
|
-
},
|
|
810
|
-
memoryMgmt: { label: '内存/资源管理',
|
|
811
|
-
regex: /\b(close|dispose|destroy|cleanup|dealloc|free|release|finalize|defer)\b/,
|
|
812
|
-
},
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
/**
|
|
818
|
-
* 获取语言对应的调用链模式集合
|
|
819
|
-
*/
|
|
820
|
-
function _getCallChainPatterns(lang) {
|
|
821
|
-
switch (lang) {
|
|
822
|
-
case 'objectivec':
|
|
823
|
-
return {
|
|
824
|
-
delegate: { label: 'Delegate 委托',
|
|
825
|
-
regex: /\b(delegate\b|Delegate\b|<\w+Delegate>|setDelegate:|\.delegate\s*=)/,
|
|
826
|
-
},
|
|
827
|
-
notification: { label: 'Notification 通知',
|
|
828
|
-
regex: /\b(NSNotificationCenter|addObserver:|removeObserver:|postNotificationName:|NSNotification\b|\[\[NSNotificationCenter)/,
|
|
829
|
-
},
|
|
830
|
-
callback: { label: 'Block 回调',
|
|
831
|
-
regex: /\b(completion[Hh]andler|completionBlock|success[Bb]lock|failure[Bb]lock|callback\b|\^\s*void|\^\s*\(|typedef\s+void\s*\(\^)/,
|
|
832
|
-
},
|
|
833
|
-
target_action: { label: 'Target-Action',
|
|
834
|
-
regex: /\b(addTarget:|@selector\(|performSelector|action:@selector|SEL\s)/,
|
|
835
|
-
},
|
|
836
|
-
};
|
|
837
|
-
case 'swift':
|
|
838
|
-
return {
|
|
839
|
-
delegate: { label: 'Delegate 委托',
|
|
840
|
-
regex: /\b(delegate\b|Delegate\b|\.delegate\s*=|protocol\s+\w+Delegate)/,
|
|
841
|
-
},
|
|
842
|
-
notification: { label: 'Notification 通知',
|
|
843
|
-
regex: /\b(NotificationCenter|\.post\(|\.addObserver|Notification\.Name)/,
|
|
844
|
-
},
|
|
845
|
-
reactive: { label: '响应式 (Combine/Rx)',
|
|
846
|
-
regex: /\b(Publisher|Subscriber|\.sink\s*\{|\.subscribe|Combine|RxSwift|AnyPublisher|eraseToAnyPublisher)/,
|
|
847
|
-
},
|
|
848
|
-
callback: { label: 'Callback / Closure',
|
|
849
|
-
regex: /\b(completion\s*:|handler\s*:|callback\s*:|escaping\s|@escaping)/,
|
|
850
|
-
},
|
|
851
|
-
};
|
|
852
|
-
case 'javascript': case 'typescript':
|
|
853
|
-
return {
|
|
854
|
-
eventEmitter: { label: 'EventEmitter',
|
|
855
|
-
regex: /\b(\.on\(|\.emit\(|\.addEventListener\(|\.removeEventListener|EventEmitter|EventTarget)/,
|
|
856
|
-
},
|
|
857
|
-
callback: { label: 'Callback / Promise',
|
|
858
|
-
regex: /\b(\.then\(|\.catch\(|callback\s*[:(]|\.subscribe\(|new\s+Promise)/,
|
|
859
|
-
},
|
|
860
|
-
observable: { label: '响应式 (RxJS)',
|
|
861
|
-
regex: /\b(Observable|Subject|BehaviorSubject|pipe\(|switchMap|mergeMap|combineLatest)/,
|
|
862
|
-
},
|
|
863
|
-
};
|
|
864
|
-
default:
|
|
865
|
-
return {
|
|
866
|
-
delegate: { label: 'Delegate / 委托',
|
|
867
|
-
regex: /\b(delegate|Delegate|listener|Listener|handler|Handler|callback|Callback)\b/,
|
|
868
|
-
},
|
|
869
|
-
notification: { label: '事件/通知',
|
|
870
|
-
regex: /\b(notify|Notification|event|Event|emit|signal|Signal|publish|subscribe)\b/,
|
|
871
|
-
},
|
|
872
|
-
};
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
/**
|
|
877
|
-
* 获取语言对应的数据流模式集合
|
|
878
|
-
*/
|
|
879
|
-
function _getDataFlowPatterns(lang) {
|
|
880
|
-
switch (lang) {
|
|
881
|
-
case 'objectivec':
|
|
882
|
-
return {
|
|
883
|
-
kvo: { label: 'KVO',
|
|
884
|
-
regex: /\b(addObserver:.*forKeyPath|observeValueForKeyPath|removeObserver:.*forKeyPath|NSKeyValueObservingOptionNew)/,
|
|
885
|
-
},
|
|
886
|
-
property: { label: '属性声明',
|
|
887
|
-
regex: /^\s*@property\s*\(/m,
|
|
888
|
-
},
|
|
889
|
-
persistence: { label: '数据持久化',
|
|
890
|
-
regex: /\b(NSUserDefaults|NSCoding|NSKeyedArchiver|NSKeyedUnarchiver|NSCoreDataStack|NSManagedObject|NSFetchRequest|CoreData\b|sqlite)/,
|
|
891
|
-
},
|
|
892
|
-
singleton: { label: 'Singleton',
|
|
893
|
-
regex: /\b(sharedInstance|shared\b|defaultManager|dispatch_once|static\s+\w+\s*\*\s*_instance)/,
|
|
894
|
-
},
|
|
895
|
-
};
|
|
896
|
-
case 'swift':
|
|
897
|
-
return {
|
|
898
|
-
swiftui: { label: 'SwiftUI 状态',
|
|
899
|
-
regex: /\b(@Published|@State|@Binding|@Observable|@Environment|@ObservedObject|@StateObject|@EnvironmentObject)\b/,
|
|
900
|
-
},
|
|
901
|
-
combine: { label: 'Combine / Subject',
|
|
902
|
-
regex: /\b(CurrentValueSubject|PassthroughSubject|AnyPublisher|Just\(|Future\(|\.assign\(to:)/,
|
|
903
|
-
},
|
|
904
|
-
kvo: { label: 'KVO / 属性观察',
|
|
905
|
-
regex: /\b(willSet|didSet|observe\(|@objc\s+dynamic)/,
|
|
906
|
-
},
|
|
907
|
-
};
|
|
908
|
-
case 'javascript': case 'typescript':
|
|
909
|
-
return {
|
|
910
|
-
stateManagement: { label: '状态管理',
|
|
911
|
-
regex: /\b(useState|useReducer|createStore|createSlice|atom\(|ref\(|reactive\(|writable\(|signal\()/,
|
|
912
|
-
},
|
|
913
|
-
dataBinding: { label: '数据绑定',
|
|
914
|
-
regex: /\b(useEffect|useMemo|computed|watch\(|subscribe|mobx|observable)/,
|
|
915
|
-
},
|
|
916
|
-
};
|
|
917
|
-
case 'python':
|
|
918
|
-
return {
|
|
919
|
-
dataclass: { label: '数据模型',
|
|
920
|
-
regex: /\b(@dataclass|BaseModel|pydantic|@property|__init__\s*\(self)/,
|
|
921
|
-
},
|
|
922
|
-
stateManagement: { label: '状态管理',
|
|
923
|
-
regex: /\b(signal|slot|@receiver|django\.dispatch|celery|redis|queue\.Queue)/,
|
|
924
|
-
},
|
|
925
|
-
};
|
|
926
|
-
default:
|
|
927
|
-
return {
|
|
928
|
-
stateManagement: { label: '状态/数据管理',
|
|
929
|
-
regex: /\b(state|State|store|Store|model|Model|repository|Repository|cache|Cache)\b/,
|
|
930
|
-
},
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
/**
|
|
936
|
-
* 提取包含目标行的完整方法/函数体(大括号平衡法,适用 C 系语言)。
|
|
937
|
-
* 返回提取的代码行数组。如果无法找到方法边界则返回上下文。
|
|
938
|
-
*
|
|
939
|
-
* @param {string[]} lines - 文件所有行
|
|
940
|
-
* @param {number} targetIdx - 0-based 行索引
|
|
941
|
-
* @param {string} lang - 语言标识
|
|
942
|
-
* @param {number} maxLines - 最大提取行数
|
|
943
|
-
* @returns {string[]}
|
|
944
|
-
*/
|
|
945
|
-
function _extractEnclosingBlock(lines, targetIdx, lang, maxLines = 40) {
|
|
946
|
-
// Python:基于缩进
|
|
947
|
-
if (lang === 'python') {
|
|
948
|
-
let startIdx = targetIdx;
|
|
949
|
-
for (let i = targetIdx; i >= Math.max(0, targetIdx - 50); i--) {
|
|
950
|
-
if (/^\s*(def |class |async\s+def )/.test(lines[i])) { startIdx = i; break; }
|
|
951
|
-
}
|
|
952
|
-
const baseIndent = lines[startIdx].search(/\S/);
|
|
953
|
-
let endIdx = startIdx;
|
|
954
|
-
for (let i = startIdx + 1; i < Math.min(lines.length, startIdx + maxLines); i++) {
|
|
955
|
-
if (lines[i].trim() === '') { endIdx = i; continue; }
|
|
956
|
-
const indent = lines[i].search(/\S/);
|
|
957
|
-
if (indent <= baseIndent) break;
|
|
958
|
-
endIdx = i;
|
|
959
|
-
}
|
|
960
|
-
return lines.slice(startIdx, endIdx + 1);
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// C 系语言(ObjC, Swift, JS/TS, Java, Go, Rust, etc.):大括号平衡
|
|
964
|
-
// Step 1: 向上找方法/函数起始行
|
|
965
|
-
let startIdx = targetIdx;
|
|
966
|
-
const methodStartRe = _getMethodStartRe(lang);
|
|
967
|
-
for (let i = targetIdx; i >= Math.max(0, targetIdx - 60); i--) {
|
|
968
|
-
if (methodStartRe.test(lines[i])) { startIdx = i; break; }
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
// Step 2: 向下用大括号计数找结束
|
|
972
|
-
let braceCount = 0;
|
|
973
|
-
let foundBrace = false;
|
|
974
|
-
let endIdx = startIdx;
|
|
975
|
-
for (let i = startIdx; i < Math.min(lines.length, startIdx + maxLines + 20); i++) {
|
|
976
|
-
for (const ch of lines[i]) {
|
|
977
|
-
if (ch === '{') { braceCount++; foundBrace = true; }
|
|
978
|
-
if (ch === '}') braceCount--;
|
|
979
|
-
}
|
|
980
|
-
endIdx = i;
|
|
981
|
-
if (foundBrace && braceCount <= 0) break;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// 限制最大行数
|
|
985
|
-
const extracted = lines.slice(startIdx, endIdx + 1);
|
|
986
|
-
if (extracted.length > maxLines) {
|
|
987
|
-
return [...extracted.slice(0, maxLines - 1), ' // ... (truncated)'];
|
|
988
|
-
}
|
|
989
|
-
return extracted;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
/**
|
|
993
|
-
* 获取"方法/函数起始行"识别正则
|
|
994
|
-
*/
|
|
995
|
-
function _getMethodStartRe(lang) {
|
|
996
|
-
switch (lang) {
|
|
997
|
-
case 'objectivec':
|
|
998
|
-
return /^[-+]\s*\(|^@implementation\b|^@interface\b|^@protocol\b/;
|
|
999
|
-
case 'swift':
|
|
1000
|
-
return /^\s*(public |open |internal |private |fileprivate )?(override\s+)?(static |class )?(func\s|init[?(]|deinit\b|subscript\s*[[(]|var\s+\w+.*\{\s*$)/;
|
|
1001
|
-
case 'javascript': case 'typescript':
|
|
1002
|
-
return /^\s*(export\s+)?(default\s+)?(async\s+)?function\b|^\s*(export\s+)?(default\s+)?class\b|^\s*(public |private |protected |static |async |get |set |readonly )*[\w$]+\s*\(|^\s*(const|let|var)\s+\w+\s*=/;
|
|
1003
|
-
case 'python':
|
|
1004
|
-
return /^\s*(async\s+)?def\s+|^\s*class\s+/;
|
|
1005
|
-
case 'java': case 'kotlin':
|
|
1006
|
-
return /^\s*(public |private |protected |static |final |abstract |override |suspend |open )*(fun |void |int |long |String |boolean |class |interface |Object |List |Map )/;
|
|
1007
|
-
case 'go':
|
|
1008
|
-
return /^\s*func\s/;
|
|
1009
|
-
case 'rust':
|
|
1010
|
-
return /^\s*(pub\s+)?(fn|impl|struct|enum|trait)\s/;
|
|
1011
|
-
default:
|
|
1012
|
-
return /^\s*(function|def|class|func|fn|pub fn|pub async fn|sub|proc)\b/;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
/**
|
|
1017
|
-
* 对一组文件按模式集合收集代码块。
|
|
1018
|
-
* 每个模式最多收集 samplePerPattern 个文件的代码块。
|
|
1019
|
-
* 返回 { codeBlocks: string[], sources: string[], stats: {label→fileCount} }
|
|
1020
|
-
*/
|
|
1021
|
-
function _collectPatternBlocks(patterns, allFiles, lang, { maxFileScan = 150, samplePerPattern = 3, maxLinesPerBlock = 35 } = {}) {
|
|
1022
|
-
const codeBlocks = [];
|
|
1023
|
-
const sources = [];
|
|
1024
|
-
const stats = {};
|
|
1025
|
-
|
|
1026
|
-
for (const [key, p] of Object.entries(patterns)) {
|
|
1027
|
-
p._collected = [];
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
for (const f of allFiles.slice(0, maxFileScan)) {
|
|
1031
|
-
const lines = f.content.split('\n');
|
|
1032
|
-
for (const [key, p] of Object.entries(patterns)) {
|
|
1033
|
-
if (p._collected.length >= samplePerPattern) continue;
|
|
1034
|
-
if (!p.regex.test(f.content)) continue;
|
|
1035
|
-
|
|
1036
|
-
// 找到第一个匹配行,提取完整方法体
|
|
1037
|
-
const matchIdx = lines.findIndex(l => p.regex.test(l));
|
|
1038
|
-
if (matchIdx < 0) continue;
|
|
1039
|
-
|
|
1040
|
-
const block = _extractEnclosingBlock(lines, matchIdx, lang, maxLinesPerBlock);
|
|
1041
|
-
if (block.length < 2) continue; // 太短没意义
|
|
1042
|
-
|
|
1043
|
-
const header = `// ── ${f.relativePath}:${matchIdx + 1} ──`;
|
|
1044
|
-
codeBlocks.push(header, ...block, '');
|
|
1045
|
-
p._collected.push(f.relativePath);
|
|
1046
|
-
if (!sources.includes(f.relativePath)) sources.push(f.relativePath);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
// 统计每个模式的全量命中文件数
|
|
1051
|
-
for (const [key, p] of Object.entries(patterns)) {
|
|
1052
|
-
stats[p.label] = allFiles.filter(f => p.regex.test(f.content)).length;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
return { codeBlocks, sources, stats };
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
// ─── Phase 5 辅助:Skill 内容解析与融合 ────────────────────
|
|
1059
|
-
|
|
1060
|
-
/**
|
|
1061
|
-
* 将 Skill section 内容(分号分隔的摘要)解析为结构化规则列表
|
|
1062
|
-
* 处理 3 种格式:✅/❌ 代码注释、Markdown 表格行、纯文本描述
|
|
1063
|
-
* @param {string} content — _extractSectionSummary 输出
|
|
1064
|
-
* @returns {string[]} 简洁的规则列表(最多 6 条)
|
|
1065
|
-
*/
|
|
1066
|
-
function _parseSkillContentToRules(content) {
|
|
1067
|
-
const rules = [];
|
|
1068
|
-
const parts = content.split(/;\s*/);
|
|
1069
|
-
|
|
1070
|
-
for (const part of parts) {
|
|
1071
|
-
let trimmed = part.trim();
|
|
1072
|
-
if (!trimmed || trimmed.length < 5) continue;
|
|
1073
|
-
|
|
1074
|
-
// ✅/❌ 代码注释 → 清理为规则
|
|
1075
|
-
if (/^\/\/\s*[✅❌]/.test(trimmed)) {
|
|
1076
|
-
rules.push(trimmed.replace(/^\/\/\s*/, ''));
|
|
1077
|
-
continue;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
// 表格行 → 解析单元格
|
|
1081
|
-
if (/^\|/.test(trimmed) && /\|$/.test(trimmed)) {
|
|
1082
|
-
const cells = trimmed.split('|').map(c => c.trim().replace(/`/g, '').trim()).filter(Boolean);
|
|
1083
|
-
// 跳过表头行
|
|
1084
|
-
if (cells.some(c => /^(标识符类型|类型|规则|反模式|额外维度|注解|含义|候选类型|寻找什么)$/.test(c))) continue;
|
|
1085
|
-
if (cells.length >= 3) {
|
|
1086
|
-
rules.push(`${cells[0]}:${cells[1]}(${cells[2]})`);
|
|
1087
|
-
} else if (cells.length === 2) {
|
|
1088
|
-
rules.push(`${cells[0]}:${cells[1]}`);
|
|
1089
|
-
}
|
|
1090
|
-
continue;
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// 有意义的纯文本
|
|
1094
|
-
if (trimmed.length > 8 && trimmed.length < 120 && !trimmed.startsWith('#')) {
|
|
1095
|
-
rules.push(trimmed);
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
return rules.slice(0, 6);
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
/**
|
|
1103
|
-
* 将业界规范规则融入候选 Markdown 文档结构
|
|
1104
|
-
*
|
|
1105
|
-
* 策略:
|
|
1106
|
-
* - 有 `## 约定` → 规则作为子标题追加
|
|
1107
|
-
* - 无 `## 约定` → 在 `## 代码示例` 前插入 `## 规范要点`
|
|
1108
|
-
* - 有 `## Agent 注意事项` → 追加最重要的 1-2 条规范提醒
|
|
1109
|
-
*/
|
|
1110
|
-
function _fuseSkillRulesIntoDoc(codeDoc, rules, sectionTitle) {
|
|
1111
|
-
if (!rules.length || !codeDoc) return codeDoc;
|
|
1112
|
-
|
|
1113
|
-
const rulesText = rules.map(r => `- ${r}`).join('\n');
|
|
1114
|
-
let result = codeDoc;
|
|
1115
|
-
|
|
1116
|
-
// ── 融合到 ## 约定 section ──
|
|
1117
|
-
const convMarker = '\n## 约定\n';
|
|
1118
|
-
const convIdx = result.indexOf(convMarker);
|
|
1119
|
-
if (convIdx >= 0) {
|
|
1120
|
-
// 在约定 section 末尾追加业界规范子块
|
|
1121
|
-
const afterConv = convIdx + convMarker.length;
|
|
1122
|
-
const nextSection = result.indexOf('\n## ', afterConv);
|
|
1123
|
-
const insertAt = nextSection >= 0 ? nextSection : result.length;
|
|
1124
|
-
result = result.slice(0, insertAt) +
|
|
1125
|
-
`\n**${sectionTitle}**:\n${rulesText}\n` +
|
|
1126
|
-
result.slice(insertAt);
|
|
1127
|
-
} else {
|
|
1128
|
-
// 无约定 section → 在代码示例之前插入
|
|
1129
|
-
const codeIdx = result.indexOf('\n## 代码示例\n');
|
|
1130
|
-
const agentIdx = result.indexOf('\n## Agent 注意事项\n');
|
|
1131
|
-
const insertIdx = codeIdx >= 0 ? codeIdx : (agentIdx >= 0 ? agentIdx : result.length);
|
|
1132
|
-
result = result.slice(0, insertIdx) +
|
|
1133
|
-
`\n## 规范要点\n\n**${sectionTitle}**:\n${rulesText}\n` +
|
|
1134
|
-
result.slice(insertIdx);
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
// Agent 注意事项不另外注入 — 规范已融合在 ## 约定 section 中
|
|
1138
|
-
return result;
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
// ─── Phase 5 辅助:根据维度提取代表性代码 ───────────────────
|
|
1142
|
-
|
|
1143
|
-
/**
|
|
1144
|
-
* v3: 从扫描文件中按维度提取 N 条单一职责候选。
|
|
1145
|
-
* 每条候选: { title, subTopic, code, language, sources, summary, knowledgeType, tags, relations }
|
|
1146
|
-
* 不使用 AI,纯启发式。未检测到内容的子主题不产出。
|
|
1147
|
-
*/
|
|
1148
|
-
function _extractDimensionCandidates(dim, allFiles, targetFileMap, context) {
|
|
1149
|
-
const { depGraphData, guardAudit, langStats, primaryLang, astProjectSummary } = context;
|
|
1150
|
-
const lang = primaryLang || 'swift';
|
|
1151
|
-
const ast = astProjectSummary || null;
|
|
1152
|
-
|
|
1153
|
-
let candidates;
|
|
1154
|
-
switch (dim.id) {
|
|
1155
|
-
case 'code-standard': candidates = _extractCodeStandard(allFiles, lang, ast); break;
|
|
1156
|
-
case 'code-pattern': candidates = _extractCodePattern(allFiles, lang, ast); break;
|
|
1157
|
-
case 'architecture': candidates = _extractArchitecture(targetFileMap, depGraphData, lang, ast); break;
|
|
1158
|
-
case 'best-practice': candidates = _extractBestPractice(allFiles, lang); break;
|
|
1159
|
-
case 'call-chain': candidates = _extractCallChain(allFiles, lang); break;
|
|
1160
|
-
case 'data-flow': candidates = _extractDataFlow(allFiles, lang); break;
|
|
1161
|
-
case 'bug-fix': candidates = _extractBugFix(allFiles, guardAudit, lang); break;
|
|
1162
|
-
case 'project-profile': candidates = _extractProjectProfile(allFiles, targetFileMap, depGraphData, guardAudit, langStats, lang, ast); break;
|
|
1163
|
-
case 'agent-guidelines': candidates = _extractAgentGuidelines(allFiles, lang); break;
|
|
1164
|
-
default:
|
|
1165
|
-
candidates = [];
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
// ── Skill 增强:按 subTopic 精准匹配最相关的 Skill section ──
|
|
1169
|
-
if (dim._skillEnhanced && candidates.length > 0) {
|
|
1170
|
-
const skillSections = dim._skillSections || [];
|
|
1171
|
-
|
|
1172
|
-
for (const c of candidates) {
|
|
1173
|
-
let bestSection = null;
|
|
1174
|
-
|
|
1175
|
-
if (skillSections.length > 0) {
|
|
1176
|
-
// 按关键词匹配 candidate subTopic / summary → 最相关的 Skill section
|
|
1177
|
-
const subTopicLower = (c.subTopic || '').toLowerCase().replace(/[_-]/g, ' ');
|
|
1178
|
-
const summaryLower = (c.summary || '').toLowerCase();
|
|
1179
|
-
let bestScore = 0;
|
|
1180
|
-
|
|
1181
|
-
for (const section of skillSections) {
|
|
1182
|
-
let score = 0;
|
|
1183
|
-
for (const kw of section.keywords) {
|
|
1184
|
-
const kwLower = kw.toLowerCase().replace(/[_-]/g, ' ');
|
|
1185
|
-
if (subTopicLower.includes(kwLower)) score += 3;
|
|
1186
|
-
if (summaryLower.includes(kwLower)) score += 1;
|
|
1187
|
-
}
|
|
1188
|
-
// section title 也参与匹配
|
|
1189
|
-
const titleLower = section.title.toLowerCase();
|
|
1190
|
-
if (subTopicLower.split(' ').some(w => w.length > 2 && titleLower.includes(w))) score += 2;
|
|
1191
|
-
if (summaryLower.split(/[^a-z\u4e00-\u9fff]+/).some(w => w.length > 2 && titleLower.includes(w))) score += 1;
|
|
1192
|
-
if (score > bestScore) {
|
|
1193
|
-
bestScore = score;
|
|
1194
|
-
bestSection = section;
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
// bestScore === 0 → 无关键词匹配 → 不注入(不兜底到无关 section)
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
if (bestSection) {
|
|
1201
|
-
const rules = _parseSkillContentToRules(bestSection.content);
|
|
1202
|
-
const sectionTitle = bestSection.title;
|
|
1203
|
-
|
|
1204
|
-
// ── 1. 融合到 code 文档结构(嵌入约定 + Agent 注意事项)──
|
|
1205
|
-
if (rules.length > 0 && c.code && typeof c.code === 'string') {
|
|
1206
|
-
c.code = _fuseSkillRulesIntoDoc(c.code, rules, sectionTitle);
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
// ── 2. summary:自然语言融合,不加标签 ──
|
|
1210
|
-
if (c.summary) {
|
|
1211
|
-
const sfx = /(规范|标准|模式)$/.test(sectionTitle) ? '' : '规范';
|
|
1212
|
-
c.summary = `${c.summary},遵循${sectionTitle}${sfx}`;
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
// ── 3. whyStandard:项目特征 + 业界规范融合 ──
|
|
1216
|
-
c._skillEnhanced = true;
|
|
1217
|
-
const shortRules = rules.slice(0, 3).join(';');
|
|
1218
|
-
c._skillReference = `${c.summary || ''}。${sectionTitle}:${shortRules || bestSection.content.substring(0, 100)}`;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
return candidates;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
// ── 辅助:构建 Markdown 候选文档 ──────────────────────────
|
|
1227
|
-
|
|
1228
|
-
function _buildCandidateDoc({ heading, oneLiner, bodyLines, codeBlocks, agentNotes, relationLines }) {
|
|
1229
|
-
const lines = [`# ${heading}`, '', `> ${oneLiner}`, ''];
|
|
1230
|
-
if (bodyLines?.length) {
|
|
1231
|
-
lines.push('## 约定', '', ...bodyLines, '');
|
|
1232
|
-
}
|
|
1233
|
-
if (codeBlocks?.length) {
|
|
1234
|
-
lines.push('## 代码示例', '');
|
|
1235
|
-
for (const cb of codeBlocks) {
|
|
1236
|
-
lines.push(`\`\`\`${cb.language}`, `// ── ${cb.source} ──`, ...cb.lines, '```', '');
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
if (agentNotes?.length) {
|
|
1240
|
-
lines.push('## Agent 注意事项', '', ...agentNotes.map(n => `- ${n}`), '');
|
|
1241
|
-
}
|
|
1242
|
-
if (relationLines?.length) {
|
|
1243
|
-
lines.push('## 关联知识', '', ...relationLines.map(r => `- ${r}`), '');
|
|
1244
|
-
}
|
|
1245
|
-
return lines.join('\n');
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
function _makeTitle(dimId, subTopic) {
|
|
1249
|
-
return `[Bootstrap] ${dimId}/${subTopic}`;
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// ── code-standard ─────────────────────────────────────────
|
|
1253
|
-
|
|
1254
|
-
function _extractCodeStandard(allFiles, lang, ast) {
|
|
1255
|
-
const results = [];
|
|
1256
|
-
const highPriFiles = allFiles.filter(f => inferFilePriority(f.name) === 'high');
|
|
1257
|
-
const samples = highPriFiles.length > 0 ? highPriFiles.slice(0, 6) : allFiles.filter(f => inferFilePriority(f.name) === 'medium').slice(0, 4);
|
|
1258
|
-
|
|
1259
|
-
// ── naming ──
|
|
1260
|
-
const prefixCounts = {};
|
|
1261
|
-
|
|
1262
|
-
// AST 增强:直接从解析的类名统计前缀(比 regex 更精确)
|
|
1263
|
-
if (ast && ast.classes.length > 0) {
|
|
1264
|
-
for (const cls of ast.classes) {
|
|
1265
|
-
const name = cls.name;
|
|
1266
|
-
if (!name || name.length < 3) continue;
|
|
1267
|
-
const prefix3 = name.slice(0, 3);
|
|
1268
|
-
const prefix2 = name.slice(0, 2);
|
|
1269
|
-
if (/^[A-Z]{2,3}$/.test(prefix3)) {
|
|
1270
|
-
prefixCounts[prefix3] = (prefixCounts[prefix3] || 0) + 1;
|
|
1271
|
-
} else if (/^[A-Z]{2}$/.test(prefix2)) {
|
|
1272
|
-
prefixCounts[prefix2] = (prefixCounts[prefix2] || 0) + 1;
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
} else {
|
|
1276
|
-
// 降级:regex 扫描
|
|
1277
|
-
const typeDefRe = _getTypeDefPattern(lang);
|
|
1278
|
-
for (const f of allFiles.slice(0, 100)) {
|
|
1279
|
-
const match = f.content.match(typeDefRe);
|
|
1280
|
-
if (!match) continue;
|
|
1281
|
-
const name = match[0].trim().split(/\s+/).pop();
|
|
1282
|
-
if (!name || name.length < 3) continue;
|
|
1283
|
-
const prefix2 = name.slice(0, 2);
|
|
1284
|
-
const prefix3 = name.slice(0, 3);
|
|
1285
|
-
if (/^[A-Z]{2,3}$/.test(prefix3)) {
|
|
1286
|
-
prefixCounts[prefix3] = (prefixCounts[prefix3] || 0) + 1;
|
|
1287
|
-
} else if (/^[A-Z]{2}$/.test(prefix2)) {
|
|
1288
|
-
prefixCounts[prefix2] = (prefixCounts[prefix2] || 0) + 1;
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
const topPrefix = Object.entries(prefixCounts).sort((a, b) => b[1] - a[1])[0];
|
|
1293
|
-
|
|
1294
|
-
if (samples.length > 0) {
|
|
1295
|
-
const codeBlocks = [];
|
|
1296
|
-
const sources = [];
|
|
1297
|
-
const typeDefRe = _getTypeDefPattern(lang);
|
|
1298
|
-
for (const f of samples.slice(0, 2)) {
|
|
1299
|
-
const fileLines = f.content.split('\n');
|
|
1300
|
-
const typeIdx = fileLines.findIndex(l => typeDefRe.test(l));
|
|
1301
|
-
const endLine = typeIdx >= 0 && typeIdx < 80 ? Math.min(fileLines.length, typeIdx + 20) : Math.min(fileLines.length, 40);
|
|
1302
|
-
codeBlocks.push({ language: lang, source: `${f.relativePath}:1`, lines: fileLines.slice(0, endLine) });
|
|
1303
|
-
sources.push(f.relativePath);
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
const prefixNote = topPrefix ? `项目使用 ${topPrefix[0]} 前缀(出现 ${topPrefix[1]} 次)` : '未检测到统一前缀';
|
|
1307
|
-
const bodyLines = [
|
|
1308
|
-
topPrefix ? `- **类名前缀**:统一使用 \`${topPrefix[0]}\` 前缀` : '- **类名前缀**:未检测到统一前缀',
|
|
1309
|
-
'- **命名风格**:以采样代码为准(见下方代码示例)',
|
|
1310
|
-
];
|
|
1311
|
-
// AST 增强:补充类型声明统计
|
|
1312
|
-
if (ast) {
|
|
1313
|
-
bodyLines.push(`- **类型声明**:${ast.classes.length} 个类/结构体, ${ast.protocols.length} 个协议, ${ast.categories.length} 个 Category`);
|
|
1314
|
-
// 展示主要协议遵循关系
|
|
1315
|
-
const conformances = ast.classes.filter(c => c.protocols && c.protocols.length > 0);
|
|
1316
|
-
if (conformances.length > 0) {
|
|
1317
|
-
bodyLines.push(`- **协议遵循**:${conformances.length} 个类声明了协议遵循`);
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
results.push({
|
|
1321
|
-
title: _makeTitle('code-standard', 'naming'),
|
|
1322
|
-
subTopic: 'naming',
|
|
1323
|
-
code: _buildCandidateDoc({
|
|
1324
|
-
heading: `${lang === 'objectivec' ? 'ObjC' : lang} 命名约定`,
|
|
1325
|
-
oneLiner: `${prefixNote},从 ${ast ? ast.classes.length + ' 个 AST 类声明' : samples.length + ' 个核心文件'}中分析`,
|
|
1326
|
-
bodyLines,
|
|
1327
|
-
codeBlocks,
|
|
1328
|
-
agentNotes: [
|
|
1329
|
-
topPrefix ? `新建类必须使用 \`${topPrefix[0]}\` 前缀` : '遵循项目现有命名风格',
|
|
1330
|
-
],
|
|
1331
|
-
relationLines: ['ENFORCES: [code-standard/file-organization] — 文件名需与类名匹配'],
|
|
1332
|
-
}),
|
|
1333
|
-
language: lang,
|
|
1334
|
-
sources,
|
|
1335
|
-
summary: `命名约定:${prefixNote}`,
|
|
1336
|
-
knowledgeType: 'code-standard',
|
|
1337
|
-
tags: ['naming'],
|
|
1338
|
-
relations: [{ type: 'ENFORCES', target: _makeTitle('code-standard', 'file-organization'), description: '文件名需与类名匹配' }],
|
|
1339
|
-
});
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
// ── file-organization ──
|
|
1343
|
-
const markCount = allFiles.filter(f => /\/\/\s*MARK:\s*-|#pragma\s+mark\s+/m.test(f.content)).length;
|
|
1344
|
-
const docCommentCount = allFiles.filter(f => /\/\/\/\s|\/\*\*[\s\S]*?\*\//.test(f.content)).length;
|
|
1345
|
-
|
|
1346
|
-
if (samples.length > 0) {
|
|
1347
|
-
// 提取有 MARK 分段的文件示例
|
|
1348
|
-
const markSample = allFiles.find(f => /\/\/\s*MARK:\s*-|#pragma\s+mark\s+/.test(f.content));
|
|
1349
|
-
const codeBlocks = [];
|
|
1350
|
-
const sources = [];
|
|
1351
|
-
if (markSample) {
|
|
1352
|
-
const fileLines = markSample.content.split('\n');
|
|
1353
|
-
codeBlocks.push({ language: lang, source: `${markSample.relativePath}:1`, lines: fileLines.slice(0, 50) });
|
|
1354
|
-
sources.push(markSample.relativePath);
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
results.push({
|
|
1358
|
-
title: _makeTitle('code-standard', 'file-organization'),
|
|
1359
|
-
subTopic: 'file-organization',
|
|
1360
|
-
code: _buildCandidateDoc({
|
|
1361
|
-
heading: '文件组织与分段',
|
|
1362
|
-
oneLiner: `${markCount} 个文件使用 MARK 分段,${docCommentCount} 个文件使用文档注释`,
|
|
1363
|
-
bodyLines: [
|
|
1364
|
-
`- **MARK 分段**:${markCount} 个文件使用 MARK/pragma mark 分段`,
|
|
1365
|
-
`- **文档注释**:${docCommentCount} 个文件使用 /// 或 /** */ 注释`,
|
|
1366
|
-
],
|
|
1367
|
-
codeBlocks,
|
|
1368
|
-
agentNotes: markCount > 0 ? ['新代码应使用 MARK: - 对代码分段'] : [],
|
|
1369
|
-
}),
|
|
1370
|
-
language: lang,
|
|
1371
|
-
sources,
|
|
1372
|
-
summary: `文件组织:MARK 分段 ${markCount} 文件, 文档注释 ${docCommentCount} 文件`,
|
|
1373
|
-
knowledgeType: 'code-standard',
|
|
1374
|
-
tags: ['file-organization'],
|
|
1375
|
-
});
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
return results;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// ── code-pattern ──────────────────────────────────────────
|
|
1382
|
-
|
|
1383
|
-
function _extractCodePattern(allFiles, lang, ast) {
|
|
1384
|
-
const results = [];
|
|
1385
|
-
|
|
1386
|
-
// 检测设计模式
|
|
1387
|
-
const patternDefs = {
|
|
1388
|
-
singleton: { regex: lang === 'objectivec' ? /\bsharedInstance\b|dispatch_once/ : lang === 'swift' ? /\bstatic\s+(let|var)\s+shared\b/ : /\bgetInstance\b|\binstance\b.*=.*null/, label: '单例模式' },
|
|
1389
|
-
'protocol-delegate': { regex: lang === 'objectivec' ? /@protocol\s+\w+Delegate|<\w+Delegate>/ : lang === 'swift' ? /protocol\s+\w+Delegate|\.delegate\s*=/ : /implements\s+\w+Listener|\.addListener/, label: '协议委托模式' },
|
|
1390
|
-
category: { regex: lang === 'objectivec' ? /@interface\s+\w+\s*\(\w+\)/ : /extension\s+\w+/, label: lang === 'objectivec' ? 'Category 扩展' : 'Extension 扩展' },
|
|
1391
|
-
factory: { regex: /\+\s*\(instancetype\)|class\s+func\s+make|static\s+(func|create|from)/, label: '工厂方法' },
|
|
1392
|
-
};
|
|
1393
|
-
|
|
1394
|
-
for (const [patternName, patternDef] of Object.entries(patternDefs)) {
|
|
1395
|
-
// AST 增强:优先用 AST 检测的模式来统计数量
|
|
1396
|
-
let astCount = 0;
|
|
1397
|
-
let astInstances = [];
|
|
1398
|
-
if (ast && ast.patternStats[patternName]) {
|
|
1399
|
-
astCount = ast.patternStats[patternName].count;
|
|
1400
|
-
astInstances = ast.patternStats[patternName].instances || [];
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
const matchingFiles = allFiles.filter(f => patternDef.regex.test(f.content));
|
|
1404
|
-
// AST + regex 联合:取更大的覆盖面
|
|
1405
|
-
const totalCount = Math.max(matchingFiles.length, astCount);
|
|
1406
|
-
if (totalCount === 0) continue;
|
|
1407
|
-
|
|
1408
|
-
const sorted = matchingFiles
|
|
1409
|
-
.sort((a, b) => {
|
|
1410
|
-
const p = { high: 0, medium: 1, low: 2 };
|
|
1411
|
-
return (p[inferFilePriority(a.name)] || 1) - (p[inferFilePriority(b.name)] || 1);
|
|
1412
|
-
})
|
|
1413
|
-
.slice(0, 2);
|
|
1414
|
-
|
|
1415
|
-
const codeBlocks = [];
|
|
1416
|
-
const sources = [];
|
|
1417
|
-
for (const f of sorted) {
|
|
1418
|
-
const fileLines = f.content.split('\n');
|
|
1419
|
-
const matchIdx = fileLines.findIndex(l => patternDef.regex.test(l));
|
|
1420
|
-
if (matchIdx < 0) continue;
|
|
1421
|
-
const block = _extractEnclosingBlock(fileLines, matchIdx, lang, 40);
|
|
1422
|
-
codeBlocks.push({ language: lang, source: `${f.relativePath}:${matchIdx + 1}`, lines: block });
|
|
1423
|
-
sources.push(f.relativePath);
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
if (codeBlocks.length === 0) continue;
|
|
1427
|
-
|
|
1428
|
-
// AST 增强:补充结构化信息到 bodyLines
|
|
1429
|
-
const bodyLines = [
|
|
1430
|
-
`- **使用范围**:${totalCount} 处检测到此模式(AST: ${astCount}, 正则: ${matchingFiles.length})`,
|
|
1431
|
-
`- **代表文件**:${sorted.map(f => f.relativePath).join(', ')}`,
|
|
1432
|
-
];
|
|
1433
|
-
|
|
1434
|
-
// AST 详细信息
|
|
1435
|
-
if (patternName === 'singleton' && astInstances.length > 0) {
|
|
1436
|
-
bodyLines.push(`- **单例方法**:${astInstances.map(i => '`' + (i.className || '') + '.' + (i.methodName || '') + '`').join(', ')}`);
|
|
1437
|
-
}
|
|
1438
|
-
if (patternName === 'protocol-delegate' && ast) {
|
|
1439
|
-
const delegateProps = (ast.fileSummaries || []).flatMap(fs => (fs.properties || []).filter(p => /delegate/i.test(p.name)));
|
|
1440
|
-
if (delegateProps.length > 0) {
|
|
1441
|
-
const weakCount = delegateProps.filter(p => (p.attributes || []).includes('weak')).length;
|
|
1442
|
-
bodyLines.push(`- **Delegate 属性**:${delegateProps.length} 个(weak: ${weakCount}, 非 weak: ${delegateProps.length - weakCount})`);
|
|
1443
|
-
if (weakCount < delegateProps.length) {
|
|
1444
|
-
bodyLines.push(`- ⚠️ **建议**:${delegateProps.length - weakCount} 个 delegate 属性未使用 weak,存在循环引用风险`);
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
if (patternName === 'category' && ast && ast.categories.length > 0) {
|
|
1449
|
-
bodyLines.push(`- **Category 列表**:${ast.categories.slice(0, 8).map(c => '`' + c.className + '(' + c.categoryName + ')' + '`').join(', ')}`);
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
results.push({
|
|
1453
|
-
title: _makeTitle('code-pattern', patternName),
|
|
1454
|
-
subTopic: patternName,
|
|
1455
|
-
code: _buildCandidateDoc({
|
|
1456
|
-
heading: patternDef.label,
|
|
1457
|
-
oneLiner: `${totalCount} 处使用${patternDef.label}`,
|
|
1458
|
-
bodyLines,
|
|
1459
|
-
codeBlocks,
|
|
1460
|
-
agentNotes: [`使用${patternDef.label}时参考以上实现方式`],
|
|
1461
|
-
}),
|
|
1462
|
-
language: lang,
|
|
1463
|
-
sources,
|
|
1464
|
-
summary: `${patternDef.label}:${totalCount} 处检测到`,
|
|
1465
|
-
knowledgeType: 'code-pattern',
|
|
1466
|
-
tags: [patternName],
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
// AST 增强:添加继承关系候选(仅当 AST 可用时)
|
|
1471
|
-
if (ast && ast.inheritanceGraph.length > 0) {
|
|
1472
|
-
const inheritEdges = ast.inheritanceGraph.filter(e => e.type === 'inherits');
|
|
1473
|
-
const conformEdges = ast.inheritanceGraph.filter(e => e.type === 'conforms');
|
|
1474
|
-
|
|
1475
|
-
if (inheritEdges.length > 0 || conformEdges.length > 0) {
|
|
1476
|
-
const bodyLines = [`- **继承边**:${inheritEdges.length} 条`, `- **协议遵循边**:${conformEdges.length} 条`];
|
|
1477
|
-
|
|
1478
|
-
// 构建继承树文本
|
|
1479
|
-
const treeLines = [];
|
|
1480
|
-
// 按超类分组
|
|
1481
|
-
const bySuper = {};
|
|
1482
|
-
for (const e of inheritEdges) {
|
|
1483
|
-
if (!bySuper[e.to]) bySuper[e.to] = [];
|
|
1484
|
-
bySuper[e.to].push(e.from);
|
|
1485
|
-
}
|
|
1486
|
-
for (const [superClass, subs] of Object.entries(bySuper).slice(0, 10)) {
|
|
1487
|
-
treeLines.push(`${superClass}`);
|
|
1488
|
-
for (const sub of subs.slice(0, 5)) {
|
|
1489
|
-
const protos = conformEdges.filter(e => e.from === sub).map(e => e.to);
|
|
1490
|
-
const protoStr = protos.length > 0 ? ` <${protos.join(', ')}>` : '';
|
|
1491
|
-
treeLines.push(` └─ ${sub}${protoStr}`);
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
results.push({
|
|
1496
|
-
title: _makeTitle('code-pattern', 'inheritance'),
|
|
1497
|
-
subTopic: 'inheritance',
|
|
1498
|
-
code: _buildCandidateDoc({
|
|
1499
|
-
heading: '类继承与协议遵循关系(AST)',
|
|
1500
|
-
oneLiner: `${inheritEdges.length} 条继承关系,${conformEdges.length} 条协议遵循`,
|
|
1501
|
-
bodyLines,
|
|
1502
|
-
codeBlocks: [{ language: 'text', source: 'AST 继承树', lines: treeLines }],
|
|
1503
|
-
agentNotes: [
|
|
1504
|
-
'新建类时注意选择合适的父类和协议遵循',
|
|
1505
|
-
'保持继承层级扁平,避免过深继承链',
|
|
1506
|
-
],
|
|
1507
|
-
}),
|
|
1508
|
-
language: lang,
|
|
1509
|
-
sources: [...new Set(ast.classes.slice(0, 10).map(c => c.file).filter(Boolean))],
|
|
1510
|
-
summary: `继承关系:${inheritEdges.length} 条继承, ${conformEdges.length} 条协议遵循`,
|
|
1511
|
-
knowledgeType: 'code-pattern',
|
|
1512
|
-
tags: ['inheritance', 'protocol-conformance'],
|
|
1513
|
-
});
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
return results;
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
// ── architecture ──────────────────────────────────────────
|
|
1521
|
-
|
|
1522
|
-
function _extractArchitecture(targetFileMap, depGraphData, lang, ast) {
|
|
1523
|
-
const results = [];
|
|
1524
|
-
const targetNames = Object.keys(targetFileMap);
|
|
1525
|
-
if (targetNames.length === 0) return results;
|
|
1526
|
-
|
|
1527
|
-
// ── layer-overview ──
|
|
1528
|
-
const roles = {};
|
|
1529
|
-
for (const tn of targetNames) {
|
|
1530
|
-
const role = inferTargetRole(tn);
|
|
1531
|
-
if (!roles[role]) roles[role] = [];
|
|
1532
|
-
roles[role].push({ name: tn, files: (targetFileMap[tn] || []).length });
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
const bodyLines = [];
|
|
1536
|
-
bodyLines.push('| 角色 | 模块 | 文件数 |', '|------|------|--------|');
|
|
1537
|
-
for (const [role, targets] of Object.entries(roles).sort((a, b) => b[1].length - a[1].length)) {
|
|
1538
|
-
for (const t of targets) {
|
|
1539
|
-
bodyLines.push(`| ${role} | ${t.name} | ${t.files} |`);
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
// AST 增强:补充代码结构统计
|
|
1544
|
-
if (ast) {
|
|
1545
|
-
bodyLines.push('');
|
|
1546
|
-
bodyLines.push(`- **AST 类型统计**:${ast.classes.length} 类, ${ast.protocols.length} 协议, ${ast.categories.length} Category`);
|
|
1547
|
-
if (ast.projectMetrics) {
|
|
1548
|
-
bodyLines.push(`- **方法统计**:${ast.projectMetrics.totalMethods} 个方法,平均 ${ast.projectMetrics.avgMethodsPerClass.toFixed(1)} 个/类`);
|
|
1549
|
-
bodyLines.push(`- **最大嵌套深度**:${ast.projectMetrics.maxNestingDepth}`);
|
|
1550
|
-
if (ast.projectMetrics.complexMethods.length > 0) {
|
|
1551
|
-
bodyLines.push(`- ⚠️ **高复杂度方法**:${ast.projectMetrics.complexMethods.length} 个 (cyclomatic > 10)`);
|
|
1552
|
-
}
|
|
1553
|
-
if (ast.projectMetrics.longMethods.length > 0) {
|
|
1554
|
-
bodyLines.push(`- ⚠️ **过长方法**:${ast.projectMetrics.longMethods.length} 个 (> 50 行)`);
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
results.push({
|
|
1560
|
-
title: _makeTitle('architecture', 'layer-overview'),
|
|
1561
|
-
subTopic: 'layer-overview',
|
|
1562
|
-
code: _buildCandidateDoc({
|
|
1563
|
-
heading: '分层架构概览',
|
|
1564
|
-
oneLiner: `${targetNames.length} 个模块/Target,按职责分为 ${Object.keys(roles).length} 种角色`,
|
|
1565
|
-
bodyLines,
|
|
1566
|
-
agentNotes: ['新增模块需明确所属层级', '遵循已有分层结构'],
|
|
1567
|
-
relationLines: ['PREREQUISITE: [project-profile] — 理解项目全貌后使用'],
|
|
1568
|
-
}),
|
|
1569
|
-
language: 'markdown',
|
|
1570
|
-
sources: targetNames.slice(0, 10),
|
|
1571
|
-
summary: `${targetNames.length} 个模块按职责分类`,
|
|
1572
|
-
knowledgeType: 'architecture',
|
|
1573
|
-
tags: ['layer-overview'],
|
|
1574
|
-
relations: [{ type: 'PREREQUISITE', target: _makeTitle('project-profile', 'overview'), description: '理解项目全貌后使用' }],
|
|
1575
|
-
});
|
|
1576
|
-
|
|
1577
|
-
// ── dependency-graph ──
|
|
1578
|
-
if (depGraphData?.edges?.length > 0) {
|
|
1579
|
-
const depLines = depGraphData.edges.slice(0, 30).map(e => `- \`${e.from}\` → \`${e.to}\``);
|
|
1580
|
-
if (depGraphData.edges.length > 30) depLines.push(`- …另有 ${depGraphData.edges.length - 30} 条依赖`);
|
|
1581
|
-
|
|
1582
|
-
results.push({
|
|
1583
|
-
title: _makeTitle('architecture', 'dependency-graph'),
|
|
1584
|
-
subTopic: 'dependency-graph',
|
|
1585
|
-
code: _buildCandidateDoc({
|
|
1586
|
-
heading: '模块依赖关系',
|
|
1587
|
-
oneLiner: `${depGraphData.edges.length} 条模块间依赖`,
|
|
1588
|
-
bodyLines: depLines,
|
|
1589
|
-
agentNotes: ['新增模块间依赖需遵循已有方向,禁止反向引入'],
|
|
1590
|
-
}),
|
|
1591
|
-
language: 'markdown',
|
|
1592
|
-
sources: ['SPM manifest'],
|
|
1593
|
-
summary: `${depGraphData.edges.length} 条模块间依赖关系`,
|
|
1594
|
-
knowledgeType: 'module-dependency',
|
|
1595
|
-
tags: ['dependency-graph'],
|
|
1596
|
-
relations: [{ type: 'RELATED', target: _makeTitle('architecture', 'layer-overview'), description: '依赖图支撑分层概览' }],
|
|
1597
|
-
});
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
return results;
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
// ── best-practice ─────────────────────────────────────────
|
|
1604
|
-
|
|
1605
|
-
function _extractBestPractice(allFiles, lang) {
|
|
1606
|
-
const results = [];
|
|
1607
|
-
const patterns = _getBestPracticePatterns(lang);
|
|
1608
|
-
|
|
1609
|
-
for (const [key, pattern] of Object.entries(patterns)) {
|
|
1610
|
-
const matchingFiles = allFiles.filter(f => pattern.regex.test(f.content));
|
|
1611
|
-
if (matchingFiles.length === 0) continue;
|
|
1612
|
-
|
|
1613
|
-
const sorted = matchingFiles.sort((a, b) => {
|
|
1614
|
-
const p = { high: 0, medium: 1, low: 2 };
|
|
1615
|
-
return (p[inferFilePriority(a.name)] || 1) - (p[inferFilePriority(b.name)] || 1);
|
|
1616
|
-
});
|
|
1617
|
-
|
|
1618
|
-
const codeBlocks = [];
|
|
1619
|
-
const sources = [];
|
|
1620
|
-
for (const f of sorted.slice(0, 2)) {
|
|
1621
|
-
const fileLines = f.content.split('\n');
|
|
1622
|
-
const matchIdx = fileLines.findIndex(l => pattern.regex.test(l));
|
|
1623
|
-
if (matchIdx < 0) continue;
|
|
1624
|
-
const block = _extractEnclosingBlock(fileLines, matchIdx, lang, 35);
|
|
1625
|
-
if (block.length < 2) continue;
|
|
1626
|
-
codeBlocks.push({ language: lang, source: `${f.relativePath}:${matchIdx + 1}`, lines: block });
|
|
1627
|
-
sources.push(f.relativePath);
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
if (codeBlocks.length === 0) continue;
|
|
1631
|
-
|
|
1632
|
-
// 构建关联关系
|
|
1633
|
-
const relations = [];
|
|
1634
|
-
if (key === 'errorHandling') {
|
|
1635
|
-
relations.push({ type: 'DEPENDS_ON', target: _makeTitle('code-standard', 'naming'), description: 'Error domain 遵循类名命名' });
|
|
1636
|
-
}
|
|
1637
|
-
if (key === 'memoryMgmt') {
|
|
1638
|
-
relations.push({ type: 'RELATED', target: _makeTitle('best-practice', 'concurrency'), description: 'weakSelf 与 GCD 结合使用' });
|
|
1639
|
-
}
|
|
1640
|
-
if (key === 'concurrency') {
|
|
1641
|
-
relations.push({ type: 'EXTENDS', target: _makeTitle('best-practice', 'memoryMgmt'), description: 'GCD 中的 weakSelf 模式' });
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
// 映射 key 到可读的 subTopic
|
|
1645
|
-
const subTopicMap = { errorHandling: 'error-handling', concurrency: 'concurrency', memoryMgmt: 'memory-mgmt' };
|
|
1646
|
-
const subTopic = subTopicMap[key] || key;
|
|
1647
|
-
|
|
1648
|
-
results.push({
|
|
1649
|
-
title: _makeTitle('best-practice', subTopic),
|
|
1650
|
-
subTopic,
|
|
1651
|
-
code: _buildCandidateDoc({
|
|
1652
|
-
heading: `${pattern.label}约定`,
|
|
1653
|
-
oneLiner: `${matchingFiles.length} 个文件使用${pattern.label}模式`,
|
|
1654
|
-
bodyLines: [
|
|
1655
|
-
`- **使用范围**:${matchingFiles.length} 个文件`,
|
|
1656
|
-
`- **模式特征**:参考以下代码示例中的实际用法`,
|
|
1657
|
-
],
|
|
1658
|
-
codeBlocks,
|
|
1659
|
-
agentNotes: [`新代码应遵循项目现有的${pattern.label}模式`],
|
|
1660
|
-
relationLines: relations.map(r => `${r.type}: [${r.target}] — ${r.description}`),
|
|
1661
|
-
}),
|
|
1662
|
-
language: lang,
|
|
1663
|
-
sources,
|
|
1664
|
-
summary: `${pattern.label}:${matchingFiles.length} 个文件使用`,
|
|
1665
|
-
knowledgeType: 'best-practice',
|
|
1666
|
-
tags: [subTopic],
|
|
1667
|
-
relations,
|
|
1668
|
-
});
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
return results;
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
// ── call-chain ────────────────────────────────────────────
|
|
1675
|
-
|
|
1676
|
-
function _extractCallChain(allFiles, lang) {
|
|
1677
|
-
const results = [];
|
|
1678
|
-
const patterns = _getCallChainPatterns(lang);
|
|
1679
|
-
|
|
1680
|
-
for (const [key, pattern] of Object.entries(patterns)) {
|
|
1681
|
-
const matchingFiles = allFiles.filter(f => pattern.regex.test(f.content));
|
|
1682
|
-
if (matchingFiles.length === 0) continue;
|
|
1683
|
-
|
|
1684
|
-
const sorted = matchingFiles.sort((a, b) => {
|
|
1685
|
-
const p = { high: 0, medium: 1, low: 2 };
|
|
1686
|
-
return (p[inferFilePriority(a.name)] || 1) - (p[inferFilePriority(b.name)] || 1);
|
|
1687
|
-
});
|
|
1688
|
-
|
|
1689
|
-
const codeBlocks = [];
|
|
1690
|
-
const sources = [];
|
|
1691
|
-
for (const f of sorted.slice(0, 2)) {
|
|
1692
|
-
const fileLines = f.content.split('\n');
|
|
1693
|
-
const matchIdx = fileLines.findIndex(l => pattern.regex.test(l));
|
|
1694
|
-
if (matchIdx < 0) continue;
|
|
1695
|
-
const block = _extractEnclosingBlock(fileLines, matchIdx, lang, 35);
|
|
1696
|
-
if (block.length < 2) continue;
|
|
1697
|
-
codeBlocks.push({ language: lang, source: `${f.relativePath}:${matchIdx + 1}`, lines: block });
|
|
1698
|
-
sources.push(f.relativePath);
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
if (codeBlocks.length === 0) continue;
|
|
1702
|
-
|
|
1703
|
-
results.push({
|
|
1704
|
-
title: _makeTitle('call-chain', key),
|
|
1705
|
-
subTopic: key,
|
|
1706
|
-
code: _buildCandidateDoc({
|
|
1707
|
-
heading: `${pattern.label}`,
|
|
1708
|
-
oneLiner: `${matchingFiles.length} 个文件使用${pattern.label}`,
|
|
1709
|
-
bodyLines: [
|
|
1710
|
-
`- **使用范围**:${matchingFiles.length} 个文件`,
|
|
1711
|
-
`- **使用场景**:参考以下代码示例`,
|
|
1712
|
-
],
|
|
1713
|
-
codeBlocks,
|
|
1714
|
-
agentNotes: [`实现${pattern.label}时参考项目现有模式`],
|
|
1715
|
-
}),
|
|
1716
|
-
language: lang,
|
|
1717
|
-
sources,
|
|
1718
|
-
summary: `${pattern.label}:${matchingFiles.length} 个文件使用`,
|
|
1719
|
-
knowledgeType: 'call-chain',
|
|
1720
|
-
tags: [key],
|
|
1721
|
-
});
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
return results;
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
// ── data-flow ─────────────────────────────────────────────
|
|
1728
|
-
|
|
1729
|
-
function _extractDataFlow(allFiles, lang) {
|
|
1730
|
-
const results = [];
|
|
1731
|
-
const patterns = _getDataFlowPatterns(lang);
|
|
1732
|
-
|
|
1733
|
-
for (const [key, pattern] of Object.entries(patterns)) {
|
|
1734
|
-
const matchingFiles = allFiles.filter(f => pattern.regex.test(f.content));
|
|
1735
|
-
if (matchingFiles.length === 0) continue;
|
|
1736
|
-
|
|
1737
|
-
const sorted = matchingFiles.sort((a, b) => {
|
|
1738
|
-
const p = { high: 0, medium: 1, low: 2 };
|
|
1739
|
-
return (p[inferFilePriority(a.name)] || 1) - (p[inferFilePriority(b.name)] || 1);
|
|
1740
|
-
});
|
|
1741
|
-
|
|
1742
|
-
const codeBlocks = [];
|
|
1743
|
-
const sources = [];
|
|
1744
|
-
for (const f of sorted.slice(0, 2)) {
|
|
1745
|
-
const fileLines = f.content.split('\n');
|
|
1746
|
-
const matchIdx = fileLines.findIndex(l => pattern.regex.test(l));
|
|
1747
|
-
if (matchIdx < 0) continue;
|
|
1748
|
-
const block = _extractEnclosingBlock(fileLines, matchIdx, lang, 35);
|
|
1749
|
-
if (block.length < 2) continue;
|
|
1750
|
-
codeBlocks.push({ language: lang, source: `${f.relativePath}:${matchIdx + 1}`, lines: block });
|
|
1751
|
-
sources.push(f.relativePath);
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
if (codeBlocks.length === 0) continue;
|
|
1755
|
-
|
|
1756
|
-
results.push({
|
|
1757
|
-
title: _makeTitle('data-flow', key),
|
|
1758
|
-
subTopic: key,
|
|
1759
|
-
code: _buildCandidateDoc({
|
|
1760
|
-
heading: `${pattern.label}`,
|
|
1761
|
-
oneLiner: `${matchingFiles.length} 个文件使用${pattern.label}`,
|
|
1762
|
-
bodyLines: [
|
|
1763
|
-
`- **使用范围**:${matchingFiles.length} 个文件`,
|
|
1764
|
-
],
|
|
1765
|
-
codeBlocks,
|
|
1766
|
-
agentNotes: [`数据管理应遵循项目现有的${pattern.label}模式`],
|
|
1767
|
-
}),
|
|
1768
|
-
language: lang,
|
|
1769
|
-
sources,
|
|
1770
|
-
summary: `${pattern.label}:${matchingFiles.length} 个文件使用`,
|
|
1771
|
-
knowledgeType: 'data-flow',
|
|
1772
|
-
tags: [key],
|
|
1773
|
-
});
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
return results;
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
// ── bug-fix ───────────────────────────────────────────────
|
|
1780
|
-
|
|
1781
|
-
function _extractBugFix(allFiles, guardAudit, lang) {
|
|
1782
|
-
const results = [];
|
|
1783
|
-
|
|
1784
|
-
if (!guardAudit?.files) return results;
|
|
1785
|
-
|
|
1786
|
-
const violationFiles = guardAudit.files.filter(f => f.violations.length > 0);
|
|
1787
|
-
if (violationFiles.length === 0) return results;
|
|
1788
|
-
|
|
1789
|
-
// 按 ruleId 聚合
|
|
1790
|
-
const byRule = {};
|
|
1791
|
-
for (const vf of violationFiles) {
|
|
1792
|
-
for (const v of vf.violations) {
|
|
1793
|
-
if (!byRule[v.ruleId]) byRule[v.ruleId] = { ruleId: v.ruleId, severity: v.severity, message: v.message, files: [] };
|
|
1794
|
-
if (!byRule[v.ruleId].files.find(f => f.filePath === vf.filePath)) {
|
|
1795
|
-
byRule[v.ruleId].files.push({ filePath: vf.filePath, line: v.line });
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
for (const [ruleId, ruleData] of Object.entries(byRule)) {
|
|
1801
|
-
const codeBlocks = [];
|
|
1802
|
-
const sources = [];
|
|
1803
|
-
// 找到第一个有实际源码的违规
|
|
1804
|
-
for (const vf of ruleData.files.slice(0, 2)) {
|
|
1805
|
-
const matchFile = allFiles.find(f => f.path === vf.filePath);
|
|
1806
|
-
if (!matchFile) continue;
|
|
1807
|
-
const fileLines = matchFile.content.split('\n');
|
|
1808
|
-
const vLine = Math.max(0, (vf.line || 1) - 1);
|
|
1809
|
-
const block = _extractEnclosingBlock(fileLines, Math.min(vLine, fileLines.length - 1), lang, 30);
|
|
1810
|
-
codeBlocks.push({
|
|
1811
|
-
language: lang,
|
|
1812
|
-
source: `${matchFile.relativePath}:${vf.line || 1} ⚠️ [${ruleData.severity}]`,
|
|
1813
|
-
lines: [`// ⚠️ ${ruleData.ruleId}: ${ruleData.message}`, ...block],
|
|
1814
|
-
});
|
|
1815
|
-
sources.push(matchFile.relativePath);
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
if (codeBlocks.length === 0 && sources.length === 0) {
|
|
1819
|
-
// 没有源码但有违规记录
|
|
1820
|
-
sources.push('guard-audit');
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
// 推断关联的 best-practice
|
|
1824
|
-
const relations = [];
|
|
1825
|
-
if (/thread|sync|dispatch|main/.test(ruleId)) {
|
|
1826
|
-
relations.push({ type: 'ENFORCES', target: _makeTitle('best-practice', 'concurrency'), description: '并发安全约束' });
|
|
1827
|
-
}
|
|
1828
|
-
if (/retain|cycle|leak|memory/.test(ruleId)) {
|
|
1829
|
-
relations.push({ type: 'ENFORCES', target: _makeTitle('best-practice', 'memory-mgmt'), description: '内存管理约束' });
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
const slugRule = ruleId.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
|
|
1833
|
-
|
|
1834
|
-
results.push({
|
|
1835
|
-
title: _makeTitle('bug-fix', slugRule),
|
|
1836
|
-
subTopic: slugRule,
|
|
1837
|
-
code: _buildCandidateDoc({
|
|
1838
|
-
heading: `⚠️ ${ruleId}`,
|
|
1839
|
-
oneLiner: `${ruleData.files.length} 处违规 [${ruleData.severity}]: ${ruleData.message}`,
|
|
1840
|
-
bodyLines: [
|
|
1841
|
-
`- **严重级别**:${ruleData.severity}`,
|
|
1842
|
-
`- **违规次数**:${ruleData.files.length} 个文件`,
|
|
1843
|
-
`- **说明**:${ruleData.message}`,
|
|
1844
|
-
],
|
|
1845
|
-
codeBlocks,
|
|
1846
|
-
agentNotes: [`⛔ 禁止触发 ${ruleId} 规则`],
|
|
1847
|
-
relationLines: relations.map(r => `${r.type}: [${r.target}] — ${r.description}`),
|
|
1848
|
-
}),
|
|
1849
|
-
language: lang,
|
|
1850
|
-
sources,
|
|
1851
|
-
summary: `${ruleId}: ${ruleData.files.length} 处违规 [${ruleData.severity}]`,
|
|
1852
|
-
knowledgeType: 'solution',
|
|
1853
|
-
tags: [slugRule, ruleData.severity],
|
|
1854
|
-
relations,
|
|
1855
|
-
});
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
return results;
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
// ── project-profile ───────────────────────────────────────
|
|
1862
|
-
|
|
1863
|
-
function _extractProjectProfile(allFiles, targetFileMap, depGraphData, guardAudit, langStats, lang, ast) {
|
|
1864
|
-
const targetNames = Object.keys(targetFileMap);
|
|
1865
|
-
const totalFiles = allFiles.length;
|
|
1866
|
-
const sortedLangs = Object.entries(langStats).sort((a, b) => b[1] - a[1]);
|
|
1867
|
-
|
|
1868
|
-
const bodyLines = [];
|
|
1869
|
-
bodyLines.push('| 指标 | 值 |', '|------|-----|');
|
|
1870
|
-
bodyLines.push(`| 主语言 | ${lang} |`);
|
|
1871
|
-
bodyLines.push(`| 扫描文件数 | ${totalFiles} |`);
|
|
1872
|
-
bodyLines.push(`| 模块/Target 数 | ${targetNames.length} |`);
|
|
1873
|
-
bodyLines.push(`| SPM 依赖边数 | ${depGraphData?.edges?.length || 0} |`);
|
|
1874
|
-
bodyLines.push(`| Guard 违规数 | ${guardAudit?.summary?.totalViolations || 0} |`);
|
|
1875
|
-
|
|
1876
|
-
// AST 增强指标
|
|
1877
|
-
if (ast) {
|
|
1878
|
-
bodyLines.push(`| 类/结构体 (AST) | ${ast.classes.length} |`);
|
|
1879
|
-
bodyLines.push(`| 协议 (AST) | ${ast.protocols.length} |`);
|
|
1880
|
-
bodyLines.push(`| Category/Extension (AST) | ${ast.categories.length} |`);
|
|
1881
|
-
bodyLines.push(`| 方法总数 (AST) | ${ast.projectMetrics?.totalMethods || 0} |`);
|
|
1882
|
-
bodyLines.push(`| 平均方法数/类 (AST) | ${(ast.projectMetrics?.avgMethodsPerClass || 0).toFixed(1)} |`);
|
|
1883
|
-
bodyLines.push(`| 最大嵌套深度 (AST) | ${ast.projectMetrics?.maxNestingDepth || 0} |`);
|
|
1884
|
-
if (ast.projectMetrics?.complexMethods?.length > 0) {
|
|
1885
|
-
bodyLines.push(`| ⚠️ 高复杂度方法 (AST) | ${ast.projectMetrics.complexMethods.length} |`);
|
|
1886
|
-
}
|
|
1887
|
-
if (ast.projectMetrics?.longMethods?.length > 0) {
|
|
1888
|
-
bodyLines.push(`| ⚠️ 过长方法 (AST) | ${ast.projectMetrics.longMethods.length} |`);
|
|
1889
|
-
}
|
|
1890
|
-
// 设计模式汇总
|
|
1891
|
-
const patternNames = Object.keys(ast.patternStats || {});
|
|
1892
|
-
if (patternNames.length > 0) {
|
|
1893
|
-
bodyLines.push(`| 检测到设计模式 (AST) | ${patternNames.join(', ')} |`);
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
bodyLines.push('');
|
|
1897
|
-
bodyLines.push('### 语言分布', '', '| 扩展名 | 文件数 | 占比 |', '|--------|--------|------|');
|
|
1898
|
-
for (const [ext, count] of sortedLangs.slice(0, 8)) {
|
|
1899
|
-
bodyLines.push(`| .${ext} | ${count} | ${((count / totalFiles) * 100).toFixed(1)}% |`);
|
|
1900
|
-
}
|
|
1901
|
-
bodyLines.push('');
|
|
1902
|
-
bodyLines.push('### 模块结构', '', '| 模块名 | 职责 | 文件数 |', '|--------|------|--------|');
|
|
1903
|
-
for (const tn of targetNames.slice(0, 15)) {
|
|
1904
|
-
bodyLines.push(`| ${tn} | ${inferTargetRole(tn)} | ${(targetFileMap[tn] || []).length} |`);
|
|
1905
|
-
}
|
|
1906
|
-
if (targetNames.length > 15) bodyLines.push(`| …另有 ${targetNames.length - 15} 个模块 | | |`);
|
|
1907
|
-
|
|
1908
|
-
const topLang = sortedLangs[0];
|
|
1909
|
-
return [{
|
|
1910
|
-
title: _makeTitle('project-profile', 'overview'),
|
|
1911
|
-
subTopic: 'overview',
|
|
1912
|
-
code: _buildCandidateDoc({
|
|
1913
|
-
heading: '项目概况',
|
|
1914
|
-
oneLiner: `${lang} 项目,${totalFiles} 个文件,${targetNames.length} 个模块`,
|
|
1915
|
-
bodyLines,
|
|
1916
|
-
agentNotes: ['阅读此文档了解项目全貌,再应用具体 Recipe'],
|
|
1917
|
-
}),
|
|
1918
|
-
language: 'markdown',
|
|
1919
|
-
sources: ['SPM manifest', 'file scan'],
|
|
1920
|
-
summary: `${lang} 项目,${totalFiles} 个文件,${targetNames.length} 个模块,主语言 ${topLang ? `.${topLang[0]}(${topLang[1]})` : '未知'}`,
|
|
1921
|
-
knowledgeType: 'architecture',
|
|
1922
|
-
tags: ['overview'],
|
|
1923
|
-
}];
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
// ── agent-guidelines ──────────────────────────────────────
|
|
1927
|
-
|
|
1928
|
-
function _extractAgentGuidelines(allFiles, lang) {
|
|
1929
|
-
const results = [];
|
|
1930
|
-
|
|
1931
|
-
// ── TODO/FIXME 提取 ──
|
|
1932
|
-
const markerRe = /\/\/\s*(TODO|FIXME|WARNING|⚠️|IMPORTANT|HACK|XXX):\s*/;
|
|
1933
|
-
const pragmaRe = /^#pragma\s+mark\s+/;
|
|
1934
|
-
const hashMarkerRe = /^#\s*(TODO|FIXME|HACK|XXX|WARNING|IMPORTANT):\s*/;
|
|
1935
|
-
|
|
1936
|
-
const markersByType = {};
|
|
1937
|
-
const markerSources = [];
|
|
1938
|
-
|
|
1939
|
-
for (const f of allFiles.slice(0, 60)) {
|
|
1940
|
-
const fileLines = f.content.split('\n');
|
|
1941
|
-
for (let i = 0; i < fileLines.length; i++) {
|
|
1942
|
-
const line = fileLines[i];
|
|
1943
|
-
const match = line.match(markerRe) || line.match(hashMarkerRe);
|
|
1944
|
-
if (!match && !pragmaRe.test(line)) continue;
|
|
1945
|
-
|
|
1946
|
-
const markerType = match ? match[1] : 'MARK';
|
|
1947
|
-
if (!markersByType[markerType]) markersByType[markerType] = [];
|
|
1948
|
-
if (markersByType[markerType].length >= 4) continue;
|
|
1949
|
-
|
|
1950
|
-
const start = Math.max(0, i - 2);
|
|
1951
|
-
const end = Math.min(fileLines.length, i + 6);
|
|
1952
|
-
markersByType[markerType].push({
|
|
1953
|
-
file: f.relativePath,
|
|
1954
|
-
line: i + 1,
|
|
1955
|
-
context: fileLines.slice(start, end),
|
|
1956
|
-
});
|
|
1957
|
-
if (!markerSources.includes(f.relativePath)) markerSources.push(f.relativePath);
|
|
1958
|
-
i = end; // skip context
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
// TODO/FIXME → 单独一条
|
|
1963
|
-
const todoFixmeTypes = ['TODO', 'FIXME', 'HACK', 'XXX'];
|
|
1964
|
-
const todoMarkers = todoFixmeTypes.flatMap(t => (markersByType[t] || []).map(m => ({ ...m, type: t })));
|
|
1965
|
-
|
|
1966
|
-
if (todoMarkers.length > 0) {
|
|
1967
|
-
const codeBlocks = todoMarkers.slice(0, 6).map(m => ({
|
|
1968
|
-
language: lang,
|
|
1969
|
-
source: `${m.file}:${m.line} [${m.type}]`,
|
|
1970
|
-
lines: m.context,
|
|
1971
|
-
}));
|
|
1972
|
-
|
|
1973
|
-
results.push({
|
|
1974
|
-
title: _makeTitle('agent-guidelines', 'todo-fixme'),
|
|
1975
|
-
subTopic: 'todo-fixme',
|
|
1976
|
-
code: _buildCandidateDoc({
|
|
1977
|
-
heading: '待办事项 (TODO/FIXME)',
|
|
1978
|
-
oneLiner: `${todoMarkers.length} 条待办标注`,
|
|
1979
|
-
bodyLines: todoFixmeTypes.filter(t => markersByType[t]?.length).map(t => `- **${t}**: ${markersByType[t].length} 条`),
|
|
1980
|
-
codeBlocks,
|
|
1981
|
-
agentNotes: ['修改相关代码时注意处理这些 TODO/FIXME'],
|
|
1982
|
-
}),
|
|
1983
|
-
language: lang,
|
|
1984
|
-
sources: markerSources,
|
|
1985
|
-
summary: `${todoMarkers.length} 条待办标注`,
|
|
1986
|
-
knowledgeType: 'boundary-constraint',
|
|
1987
|
-
tags: ['todo-fixme'],
|
|
1988
|
-
});
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
|
-
// WARNING/IMPORTANT → mandatory-rules
|
|
1992
|
-
const warnTypes = ['WARNING', '⚠️', 'IMPORTANT'];
|
|
1993
|
-
const warnMarkers = warnTypes.flatMap(t => (markersByType[t] || []).map(m => ({ ...m, type: t })));
|
|
1994
|
-
|
|
1995
|
-
if (warnMarkers.length > 0) {
|
|
1996
|
-
const codeBlocks = warnMarkers.slice(0, 4).map(m => ({
|
|
1997
|
-
language: lang,
|
|
1998
|
-
source: `${m.file}:${m.line} [${m.type}]`,
|
|
1999
|
-
lines: m.context,
|
|
2000
|
-
}));
|
|
2001
|
-
|
|
2002
|
-
results.push({
|
|
2003
|
-
title: _makeTitle('agent-guidelines', 'mandatory-rules'),
|
|
2004
|
-
subTopic: 'mandatory-rules',
|
|
2005
|
-
code: _buildCandidateDoc({
|
|
2006
|
-
heading: '强制规则 (WARNING/IMPORTANT)',
|
|
2007
|
-
oneLiner: `${warnMarkers.length} 条强制约束标注`,
|
|
2008
|
-
bodyLines: warnTypes.filter(t => markersByType[t]?.length).map(t => `- **${t}**: ${markersByType[t].length} 条`),
|
|
2009
|
-
codeBlocks,
|
|
2010
|
-
agentNotes: ['⛔ 这些标注是强制约束,必须遵守'],
|
|
2011
|
-
}),
|
|
2012
|
-
language: lang,
|
|
2013
|
-
sources: markerSources,
|
|
2014
|
-
summary: `${warnMarkers.length} 条强制约束`,
|
|
2015
|
-
knowledgeType: 'boundary-constraint',
|
|
2016
|
-
tags: ['mandatory-rules'],
|
|
2017
|
-
});
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
// fallback:如果什么都没检测到
|
|
2021
|
-
if (results.length === 0) {
|
|
2022
|
-
results.push({
|
|
2023
|
-
title: _makeTitle('agent-guidelines', 'todo-fixme'),
|
|
2024
|
-
subTopic: 'todo-fixme',
|
|
2025
|
-
code: _buildCandidateDoc({
|
|
2026
|
-
heading: '注释标注',
|
|
2027
|
-
oneLiner: '未发现 TODO/FIXME/WARNING/IMPORTANT 注释标注',
|
|
2028
|
-
bodyLines: ['- 已扫描 60 个文件,未检测到注释标注'],
|
|
2029
|
-
agentNotes: [],
|
|
2030
|
-
}),
|
|
2031
|
-
language: lang,
|
|
2032
|
-
sources: ['bootstrap-scan'],
|
|
2033
|
-
summary: '未发现注释标注',
|
|
2034
|
-
knowledgeType: 'boundary-constraint',
|
|
2035
|
-
tags: ['todo-fixme'],
|
|
2036
|
-
});
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
return results;
|
|
2040
|
-
}
|