autosnippet 2.9.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/bin/cli.js +53 -40
- package/config/constitution.yaml +9 -2
- package/dashboard/dist/assets/{icons-CH-H9x0E.js → icons-D4IWpDIk.js} +105 -100
- package/dashboard/dist/assets/index-CWBNcF9z.css +1 -0
- package/dashboard/dist/assets/index-DHtzhbuG.js +120 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/AiScanService.js +35 -36
- package/lib/cli/KnowledgeSyncService.js +345 -0
- package/lib/cli/SetupService.js +8 -26
- package/lib/cli/UpgradeService.js +28 -0
- package/lib/core/gateway/GatewayActionRegistry.js +48 -58
- package/lib/domain/index.js +16 -11
- package/lib/domain/knowledge/KnowledgeEntry.js +289 -0
- package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
- package/lib/domain/knowledge/Lifecycle.js +99 -0
- package/lib/domain/knowledge/index.js +27 -0
- package/lib/domain/knowledge/values/Constraints.js +128 -0
- package/lib/domain/knowledge/values/Content.js +69 -0
- package/lib/domain/knowledge/values/Quality.js +81 -0
- package/lib/domain/knowledge/values/Reasoning.js +70 -0
- package/lib/domain/knowledge/values/Relations.js +142 -0
- package/lib/domain/knowledge/values/Stats.js +72 -0
- package/lib/domain/knowledge/values/index.js +9 -0
- package/lib/external/ai/AiProvider.js +85 -11
- package/lib/external/mcp/McpServer.js +7 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +18 -2
- package/lib/external/mcp/handlers/bootstrap.js +116 -11
- package/lib/external/mcp/handlers/browse.js +76 -73
- package/lib/external/mcp/handlers/candidate.js +26 -275
- package/lib/external/mcp/handlers/guard.js +2 -0
- package/lib/external/mcp/handlers/knowledge.js +267 -0
- package/lib/external/mcp/handlers/structure.js +25 -23
- package/lib/external/mcp/handlers/system.js +10 -12
- package/lib/external/mcp/tools.js +134 -140
- package/lib/http/HttpServer.js +14 -8
- package/lib/http/routes/ai.js +4 -3
- package/lib/http/routes/extract.js +48 -4
- package/lib/http/routes/knowledge.js +246 -0
- package/lib/http/routes/search.js +12 -17
- package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
- package/lib/infrastructure/database/migrations/017_camelcase_knowledge_entries.js +107 -0
- package/lib/infrastructure/external/XcodeAutomation.js +187 -103
- package/lib/injection/ServiceContainer.js +69 -60
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +338 -0
- package/lib/service/automation/DirectiveDetector.js +2 -3
- package/lib/service/automation/FileWatcher.js +59 -28
- package/lib/service/automation/XcodeIntegration.js +931 -156
- package/lib/service/automation/handlers/AlinkHandler.js +5 -4
- package/lib/service/automation/handlers/CreateHandler.js +53 -19
- package/lib/service/automation/handlers/DraftHandler.js +1 -1
- package/lib/service/automation/handlers/GuardHandler.js +183 -20
- package/lib/service/automation/handlers/SearchHandler.js +25 -22
- package/lib/service/candidate/SimilarityService.js +2 -2
- package/lib/service/chat/AnalystAgent.js +9 -0
- package/lib/service/chat/CandidateGuardrail.js +22 -11
- package/lib/service/chat/ChatAgent.js +132 -54
- package/lib/service/chat/ContextWindow.js +5 -5
- package/lib/service/chat/HandoffProtocol.js +1 -0
- package/lib/service/chat/ProducerAgent.js +40 -13
- package/lib/service/chat/ReasoningLayer.js +854 -0
- package/lib/service/chat/ReasoningTrace.js +329 -0
- package/lib/service/chat/tools.js +308 -205
- package/lib/service/cursor/CursorDeliveryPipeline.js +279 -0
- package/lib/service/cursor/KnowledgeCompressor.js +87 -0
- package/lib/service/cursor/RulesGenerator.js +168 -0
- package/lib/service/cursor/SkillsSyncer.js +268 -0
- package/lib/service/cursor/TokenBudget.js +58 -0
- package/lib/service/cursor/TopicClassifier.js +141 -0
- package/lib/service/guard/GuardCheckEngine.js +99 -10
- package/lib/service/guard/GuardService.js +57 -46
- package/lib/service/knowledge/ConfidenceRouter.js +159 -0
- package/lib/service/knowledge/KnowledgeFileWriter.js +595 -0
- package/lib/service/knowledge/KnowledgeService.js +802 -0
- package/lib/service/recipe/RecipeParser.js +3 -12
- package/lib/service/search/SearchEngine.js +67 -22
- package/lib/service/skills/SignalCollector.js +14 -9
- package/lib/service/skills/SkillAdvisor.js +13 -11
- package/lib/service/snippet/SnippetFactory.js +5 -5
- package/lib/service/spm/SpmService.js +15 -48
- package/lib/shared/RecipeReadinessChecker.js +6 -11
- package/package.json +1 -1
- package/scripts/install-cursor-skill.js +0 -6
- package/scripts/migrate-md-to-knowledge.mjs +364 -0
- package/skills/autosnippet-analysis/SKILL.md +15 -7
- package/skills/autosnippet-candidates/SKILL.md +8 -8
- package/skills/autosnippet-coldstart/SKILL.md +8 -4
- package/skills/autosnippet-concepts/SKILL.md +7 -6
- package/skills/autosnippet-create/SKILL.md +13 -13
- package/skills/autosnippet-intent/SKILL.md +3 -2
- package/skills/autosnippet-lifecycle/SKILL.md +5 -5
- package/skills/autosnippet-recipes/SKILL.md +18 -6
- package/templates/constitution.yaml +1 -1
- package/templates/copilot-instructions.md +6 -6
- package/templates/recipes-setup/README.md +3 -3
- package/dashboard/dist/assets/index-CqJRvYRL.js +0 -197
- package/dashboard/dist/assets/index-DICm9PNa.css +0 -1
- package/lib/cli/CandidateSyncService.js +0 -261
- package/lib/cli/SyncService.js +0 -356
- package/lib/domain/candidate/Candidate.js +0 -196
- package/lib/domain/candidate/CandidateRepository.js +0 -107
- package/lib/domain/candidate/Reasoning.js +0 -52
- package/lib/domain/recipe/Recipe.js +0 -421
- package/lib/domain/recipe/RecipeRepository.js +0 -54
- package/lib/domain/types/CandidateStatus.js +0 -52
- package/lib/http/routes/candidates.js +0 -559
- package/lib/http/routes/recipes.js +0 -397
- package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
- package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
- package/lib/service/candidate/CandidateAggregator.js +0 -52
- package/lib/service/candidate/CandidateFileWriter.js +0 -383
- package/lib/service/candidate/CandidateService.js +0 -1001
- package/lib/service/recipe/RecipeFileWriter.js +0 -514
- package/lib/service/recipe/RecipeService.js +0 -786
- package/lib/service/recipe/RecipeStatsTracker.js +0 -148
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { BaseRepository } from '../base/BaseRepository.js';
|
|
2
|
+
import { KnowledgeEntry, Lifecycle, inferKind } from '../../domain/knowledge/index.js';
|
|
3
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* KnowledgeRepositoryImpl — 统一知识实体仓储实现
|
|
7
|
+
*
|
|
8
|
+
* 面向 knowledge_entries 表的 SQLite 持久化。
|
|
9
|
+
* 全链路 camelCase — DB 列名 = 实体属性名。
|
|
10
|
+
*/
|
|
11
|
+
export class KnowledgeRepositoryImpl extends BaseRepository {
|
|
12
|
+
constructor(database) {
|
|
13
|
+
super(database, 'knowledge_entries');
|
|
14
|
+
this.logger = Logger.getInstance();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* ═══ CRUD ═══════════════════════════════════════════ */
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 创建 KnowledgeEntry
|
|
21
|
+
* @param {KnowledgeEntry} entry
|
|
22
|
+
* @returns {Promise<KnowledgeEntry>}
|
|
23
|
+
*/
|
|
24
|
+
async create(entry) {
|
|
25
|
+
if (!entry || !entry.isValid()) {
|
|
26
|
+
throw new Error('Invalid knowledge entry: title + content required');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const row = this._entityToRow(entry);
|
|
31
|
+
const keys = Object.keys(row);
|
|
32
|
+
const placeholders = keys.map(() => '?').join(', ');
|
|
33
|
+
const query = `INSERT INTO knowledge_entries (${keys.join(', ')}) VALUES (${placeholders})`;
|
|
34
|
+
this.db.prepare(query).run(...Object.values(row));
|
|
35
|
+
return this.findById(entry.id);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
this.logger.error('Error creating knowledge entry', {
|
|
38
|
+
entryId: entry.id,
|
|
39
|
+
error: error.message,
|
|
40
|
+
});
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 更新 KnowledgeEntry(接受完整实体或部分数据)
|
|
47
|
+
* @param {string} id
|
|
48
|
+
* @param {Object|KnowledgeEntry} updates
|
|
49
|
+
* @returns {Promise<KnowledgeEntry>}
|
|
50
|
+
*/
|
|
51
|
+
async update(id, updates) {
|
|
52
|
+
try {
|
|
53
|
+
const existing = await this.findById(id);
|
|
54
|
+
if (!existing) throw new Error(`Knowledge entry not found: ${id}`);
|
|
55
|
+
|
|
56
|
+
if (updates instanceof KnowledgeEntry) {
|
|
57
|
+
const row = this._entityToRow(updates);
|
|
58
|
+
delete row.id;
|
|
59
|
+
delete row.createdAt;
|
|
60
|
+
row.updatedAt = Math.floor(Date.now() / 1000);
|
|
61
|
+
|
|
62
|
+
const setClauses = Object.keys(row).map(k => `${k} = ?`).join(', ');
|
|
63
|
+
this.db.prepare(`UPDATE knowledge_entries SET ${setClauses} WHERE id = ?`)
|
|
64
|
+
.run(...Object.values(row), id);
|
|
65
|
+
return this.findById(id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 部分更新 — 合并到现有实体
|
|
69
|
+
const merged = KnowledgeEntry.fromJSON({
|
|
70
|
+
...existing.toJSON(),
|
|
71
|
+
...updates,
|
|
72
|
+
updatedAt: Math.floor(Date.now() / 1000),
|
|
73
|
+
});
|
|
74
|
+
const row = this._entityToRow(merged);
|
|
75
|
+
delete row.id;
|
|
76
|
+
delete row.createdAt;
|
|
77
|
+
|
|
78
|
+
const setClauses = Object.keys(row).map(k => `${k} = ?`).join(', ');
|
|
79
|
+
this.db.prepare(`UPDATE knowledge_entries SET ${setClauses} WHERE id = ?`)
|
|
80
|
+
.run(...Object.values(row), id);
|
|
81
|
+
return this.findById(id);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
this.logger.error('Error updating knowledge entry', {
|
|
84
|
+
id,
|
|
85
|
+
error: error.message,
|
|
86
|
+
});
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 删除
|
|
93
|
+
* @param {string} id
|
|
94
|
+
* @returns {Promise<boolean>}
|
|
95
|
+
*/
|
|
96
|
+
async delete(id) {
|
|
97
|
+
try {
|
|
98
|
+
const result = this.db.prepare('DELETE FROM knowledge_entries WHERE id = ?').run(id);
|
|
99
|
+
return result.changes > 0;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.logger.error('Error deleting knowledge entry', { id, error: error.message });
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ═══ 查询 ═══════════════════════════════════════════ */
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 分页查询
|
|
110
|
+
* @override
|
|
111
|
+
*/
|
|
112
|
+
async findWithPagination(filters = {}, options = {}) {
|
|
113
|
+
const { page = 1, pageSize = 20, orderBy = 'createdAt', order = 'DESC' } = options;
|
|
114
|
+
const offset = (page - 1) * pageSize;
|
|
115
|
+
|
|
116
|
+
const conditions = [];
|
|
117
|
+
const params = [];
|
|
118
|
+
|
|
119
|
+
const { _tagLike, _search, lifecycle: lcFilter, ...normalFilters } = filters;
|
|
120
|
+
|
|
121
|
+
if (lcFilter) {
|
|
122
|
+
conditions.push(`lifecycle = ?`);
|
|
123
|
+
params.push(lcFilter);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const [key, value] of Object.entries(normalFilters)) {
|
|
127
|
+
if (value == null) continue;
|
|
128
|
+
this._assertSafeColumn(key);
|
|
129
|
+
conditions.push(`${key} = ?`);
|
|
130
|
+
params.push(value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (_tagLike) {
|
|
134
|
+
conditions.push(`tags LIKE ?`);
|
|
135
|
+
const escaped = _tagLike.replace(/[%_\\]/g, ch => `\\${ch}`);
|
|
136
|
+
params.push(`%"${escaped}"%`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (_search) {
|
|
140
|
+
const escaped = _search.replace(/[%_\\]/g, ch => `\\${ch}`);
|
|
141
|
+
const like = `%${escaped}%`;
|
|
142
|
+
conditions.push(`(title LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\' OR trigger LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\' OR tags LIKE ? ESCAPE '\\')`);
|
|
143
|
+
params.push(like, like, like, like, like);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';
|
|
147
|
+
|
|
148
|
+
this._assertSafeColumn(orderBy);
|
|
149
|
+
const orderClause = ` ORDER BY ${orderBy} ${order === 'ASC' ? 'ASC' : 'DESC'}`;
|
|
150
|
+
|
|
151
|
+
const total = this.db.prepare(`SELECT COUNT(*) as count FROM knowledge_entries${where}`).get(...params).count;
|
|
152
|
+
const data = this.db.prepare(`SELECT * FROM knowledge_entries${where}${orderClause} LIMIT ? OFFSET ?`)
|
|
153
|
+
.all(...params, pageSize, offset);
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
data: data.map(row => this._rowToEntity(row)),
|
|
157
|
+
pagination: { page, pageSize, total, pages: Math.ceil(total / pageSize) },
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 根据生命周期状态查询
|
|
163
|
+
*/
|
|
164
|
+
async findByLifecycle(lifecycle, pagination = {}) {
|
|
165
|
+
return this.findWithPagination({ lifecycle }, pagination);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 根据 kind 查询
|
|
170
|
+
*/
|
|
171
|
+
async findByKind(kind, options = {}) {
|
|
172
|
+
const { lifecycle, ...pagination } = options;
|
|
173
|
+
const filters = { kind };
|
|
174
|
+
if (lifecycle) filters.lifecycle = lifecycle;
|
|
175
|
+
return this.findWithPagination(filters, pagination);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 查询所有 active 的 rule 类型(Guard 消费热路径)
|
|
180
|
+
* @returns {Promise<KnowledgeEntry[]>}
|
|
181
|
+
*/
|
|
182
|
+
async findActiveRules() {
|
|
183
|
+
try {
|
|
184
|
+
const rows = this.db.prepare(`
|
|
185
|
+
SELECT * FROM knowledge_entries
|
|
186
|
+
WHERE kind = 'rule' AND lifecycle = 'active'
|
|
187
|
+
`).all();
|
|
188
|
+
return rows.map(row => this._rowToEntity(row));
|
|
189
|
+
} catch (error) {
|
|
190
|
+
this.logger.error('Error finding active rules', { error: error.message });
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 根据语言查询
|
|
197
|
+
*/
|
|
198
|
+
async findByLanguage(language, pagination = {}) {
|
|
199
|
+
return this.findWithPagination({ language }, pagination);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 根据分类查询
|
|
204
|
+
*/
|
|
205
|
+
async findByCategory(category, pagination = {}) {
|
|
206
|
+
return this.findWithPagination({ category }, pagination);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 搜索
|
|
211
|
+
*/
|
|
212
|
+
async search(keyword, pagination = {}) {
|
|
213
|
+
return this.findWithPagination({ _search: keyword }, pagination);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 获取统计信息
|
|
218
|
+
*/
|
|
219
|
+
async getStats() {
|
|
220
|
+
try {
|
|
221
|
+
return this.db.prepare(`
|
|
222
|
+
SELECT
|
|
223
|
+
COUNT(*) as total,
|
|
224
|
+
SUM(CASE WHEN lifecycle = 'pending' THEN 1 ELSE 0 END) as pending,
|
|
225
|
+
SUM(CASE WHEN lifecycle = 'active' THEN 1 ELSE 0 END) as active,
|
|
226
|
+
SUM(CASE WHEN lifecycle = 'deprecated' THEN 1 ELSE 0 END) as deprecated,
|
|
227
|
+
SUM(CASE WHEN kind = 'rule' THEN 1 ELSE 0 END) as rules,
|
|
228
|
+
SUM(CASE WHEN kind = 'pattern' THEN 1 ELSE 0 END) as patterns,
|
|
229
|
+
SUM(CASE WHEN kind = 'fact' THEN 1 ELSE 0 END) as facts
|
|
230
|
+
FROM knowledge_entries
|
|
231
|
+
`).get();
|
|
232
|
+
} catch (error) {
|
|
233
|
+
this.logger.error('Error getting knowledge stats', { error: error.message });
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* ═══ 行 ↔ 实体 映射 ═══════════════════════════════ */
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* DB Row → KnowledgeEntry (camelCase 列名 = 属性名,直传)
|
|
242
|
+
* @param {Object} row
|
|
243
|
+
* @returns {KnowledgeEntry}
|
|
244
|
+
*/
|
|
245
|
+
_rowToEntity(row) {
|
|
246
|
+
if (!row) return null;
|
|
247
|
+
|
|
248
|
+
return new KnowledgeEntry({
|
|
249
|
+
...row,
|
|
250
|
+
// JSON 列需要 parse
|
|
251
|
+
lifecycleHistory: this._parseJson(row.lifecycleHistory, []),
|
|
252
|
+
tags: this._parseJson(row.tags, []),
|
|
253
|
+
content: this._parseJson(row.content, {}),
|
|
254
|
+
relations: this._parseJson(row.relations, {}),
|
|
255
|
+
constraints: this._parseJson(row.constraints, {}),
|
|
256
|
+
reasoning: this._parseJson(row.reasoning, {}),
|
|
257
|
+
quality: this._parseJson(row.quality, {}),
|
|
258
|
+
stats: this._parseJson(row.stats, {}),
|
|
259
|
+
headers: this._parseJson(row.headers, []),
|
|
260
|
+
headerPaths: this._parseJson(row.headerPaths, []),
|
|
261
|
+
agentNotes: this._parseJson(row.agentNotes, null),
|
|
262
|
+
// SQLite INTEGER → boolean
|
|
263
|
+
autoApprovable: !!row.autoApprovable,
|
|
264
|
+
includeHeaders: !!row.includeHeaders,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* KnowledgeEntry → DB Row (camelCase 列名 = 属性名,直传)
|
|
270
|
+
* @param {KnowledgeEntry} e
|
|
271
|
+
* @returns {Object}
|
|
272
|
+
*/
|
|
273
|
+
_entityToRow(e) {
|
|
274
|
+
const now = Math.floor(Date.now() / 1000);
|
|
275
|
+
return {
|
|
276
|
+
id: e.id,
|
|
277
|
+
title: e.title,
|
|
278
|
+
description: e.description || '',
|
|
279
|
+
lifecycle: e.lifecycle,
|
|
280
|
+
lifecycleHistory: JSON.stringify(e.lifecycleHistory || []),
|
|
281
|
+
autoApprovable: e.autoApprovable ? 1 : 0,
|
|
282
|
+
language: e.language,
|
|
283
|
+
category: e.category,
|
|
284
|
+
kind: e.kind || inferKind(e.knowledgeType),
|
|
285
|
+
knowledgeType: e.knowledgeType || 'code-pattern',
|
|
286
|
+
complexity: e.complexity || 'intermediate',
|
|
287
|
+
scope: e.scope || null,
|
|
288
|
+
difficulty: e.difficulty || null,
|
|
289
|
+
tags: JSON.stringify(e.tags || []),
|
|
290
|
+
trigger: e.trigger || '',
|
|
291
|
+
topicHint: e.topicHint || '',
|
|
292
|
+
whenClause: e.whenClause || '',
|
|
293
|
+
doClause: e.doClause || '',
|
|
294
|
+
dontClause: e.dontClause || '',
|
|
295
|
+
coreCode: e.coreCode || '',
|
|
296
|
+
content: JSON.stringify(typeof e.content?.toJSON === 'function' ? e.content.toJSON() : (e.content || {})),
|
|
297
|
+
relations: JSON.stringify(typeof e.relations?.toJSON === 'function' ? e.relations.toJSON() : (e.relations || {})),
|
|
298
|
+
constraints: JSON.stringify(typeof e.constraints?.toJSON === 'function' ? e.constraints.toJSON() : (e.constraints || {})),
|
|
299
|
+
reasoning: JSON.stringify(typeof e.reasoning?.toJSON === 'function' ? e.reasoning.toJSON() : (e.reasoning || {})),
|
|
300
|
+
quality: JSON.stringify(typeof e.quality?.toJSON === 'function' ? e.quality.toJSON() : (e.quality || {})),
|
|
301
|
+
stats: JSON.stringify(typeof e.stats?.toJSON === 'function' ? e.stats.toJSON() : (e.stats || {})),
|
|
302
|
+
headers: JSON.stringify(e.headers || []),
|
|
303
|
+
headerPaths: JSON.stringify(e.headerPaths || []),
|
|
304
|
+
moduleName: e.moduleName || null,
|
|
305
|
+
includeHeaders: e.includeHeaders ? 1 : 0,
|
|
306
|
+
agentNotes: e.agentNotes ? JSON.stringify(e.agentNotes) : null,
|
|
307
|
+
aiInsight: e.aiInsight || null,
|
|
308
|
+
reviewedBy: e.reviewedBy || null,
|
|
309
|
+
reviewedAt: e.reviewedAt || null,
|
|
310
|
+
rejectionReason: e.rejectionReason || null,
|
|
311
|
+
source: e.source || 'manual',
|
|
312
|
+
sourceFile: e.sourceFile || null,
|
|
313
|
+
sourceCandidateId: e.sourceCandidateId || null,
|
|
314
|
+
createdBy: e.createdBy || 'system',
|
|
315
|
+
createdAt: e.createdAt || now,
|
|
316
|
+
updatedAt: e.updatedAt || now,
|
|
317
|
+
publishedAt: e.publishedAt || null,
|
|
318
|
+
publishedBy: e.publishedBy || null,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* 覆写 BaseRepository 的 _mapRowToEntity
|
|
324
|
+
* @override
|
|
325
|
+
*/
|
|
326
|
+
_mapRowToEntity(row) {
|
|
327
|
+
return this._rowToEntity(row);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** @private 安全解析 JSON */
|
|
331
|
+
_parseJson(value, fallback) {
|
|
332
|
+
if (!value || value === 'null') return fallback;
|
|
333
|
+
if (typeof value === 'object') return value;
|
|
334
|
+
try { return JSON.parse(value); } catch { return fallback; }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export default KnowledgeRepositoryImpl;
|
|
@@ -123,9 +123,8 @@ function _isHeaderDirective(line) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
function _isGuardDirective(line) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
);
|
|
126
|
+
// 精确匹配: // as:audit 或 // as:a,后面只能是空格或行尾
|
|
127
|
+
return /^\/\/\s*as:(?:audit|a)(?:\s|$)/.test(line);
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
function _isSearchDirective(line) {
|
|
@@ -187,7 +187,7 @@ export class FileWatcher {
|
|
|
187
187
|
|
|
188
188
|
// // as:a — Guard 检查
|
|
189
189
|
if (triggers.guardLine) {
|
|
190
|
-
await handleGuard(fullPath, data, triggers.guardLine);
|
|
190
|
+
await handleGuard(this, fullPath, data, triggers.guardLine);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
// // as:s — 搜索
|
|
@@ -230,46 +230,77 @@ export class FileWatcher {
|
|
|
230
230
|
* 追加候选项(通过 ServiceContainer 或 HTTP API)
|
|
231
231
|
*/
|
|
232
232
|
async _appendCandidates(items, source) {
|
|
233
|
+
// 过滤空 title / 空 code 的无效条目
|
|
234
|
+
const validItems = items.filter(item => {
|
|
235
|
+
const title = (item.title || '').trim();
|
|
236
|
+
const code = (item.code || '').trim();
|
|
237
|
+
if (!title || !code) {
|
|
238
|
+
console.warn(`[Watcher] 跳过无效候选: title=${JSON.stringify(title)}, code length=${code.length}`);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
});
|
|
243
|
+
if (validItems.length === 0) {
|
|
244
|
+
throw new Error('所有候选条目缺少 title 或 code,无法提交');
|
|
245
|
+
}
|
|
246
|
+
|
|
233
247
|
// 优先 ServiceContainer
|
|
248
|
+
let serviceError = null;
|
|
234
249
|
try {
|
|
235
250
|
const { ServiceContainer } = await import('../../injection/ServiceContainer.js');
|
|
236
251
|
const container = ServiceContainer.getInstance();
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
category: item.category || 'Utility',
|
|
244
|
-
source: source || 'watch',
|
|
245
|
-
metadata: {
|
|
246
|
-
title: item.title,
|
|
247
|
-
summary: item.summary,
|
|
248
|
-
trigger: item.trigger,
|
|
249
|
-
usageGuide: item.usageGuide,
|
|
250
|
-
headers: item.headers,
|
|
251
|
-
},
|
|
252
|
+
const knowledgeService = container.get('knowledgeService');
|
|
253
|
+
const context = { userId: 'filewatcher' };
|
|
254
|
+
for (const item of validItems) {
|
|
255
|
+
await knowledgeService.create({
|
|
256
|
+
content: {
|
|
257
|
+
pattern: item.code || '',
|
|
252
258
|
},
|
|
253
|
-
|
|
254
|
-
|
|
259
|
+
language: item.language || 'objc',
|
|
260
|
+
category: item.category || 'Utility',
|
|
261
|
+
source: source || 'watch',
|
|
262
|
+
title: item.title,
|
|
263
|
+
description: item.summary || item.description || '',
|
|
264
|
+
moduleName: item.moduleName || 'watch-create',
|
|
265
|
+
trigger: item.trigger || '',
|
|
266
|
+
headers: item.headers || [],
|
|
267
|
+
tags: item.tags || [],
|
|
268
|
+
}, context);
|
|
255
269
|
}
|
|
256
270
|
return;
|
|
257
|
-
} catch {
|
|
258
|
-
|
|
271
|
+
} catch (err) {
|
|
272
|
+
serviceError = err;
|
|
273
|
+
console.warn('[Watcher] KnowledgeService 创建失败,尝试 HTTP 回退:', err.message);
|
|
259
274
|
}
|
|
260
275
|
|
|
261
|
-
// 回退:HTTP API
|
|
276
|
+
// 回退:HTTP API(使用 knowledge 端点而非 candidates)
|
|
262
277
|
const dashboardUrl = process.env.ASD_DASHBOARD_URL || 'http://localhost:3000';
|
|
263
278
|
try {
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
279
|
+
for (const item of validItems) {
|
|
280
|
+
const resp = await fetch(`${dashboardUrl}/api/v1/knowledge`, {
|
|
281
|
+
method: 'POST',
|
|
282
|
+
headers: { 'Content-Type': 'application/json' },
|
|
283
|
+
body: JSON.stringify({
|
|
284
|
+
title: item.title,
|
|
285
|
+
content: { pattern: item.code || '' },
|
|
286
|
+
language: item.language || 'objc',
|
|
287
|
+
category: item.category || 'Utility',
|
|
288
|
+
source: source || 'watch',
|
|
289
|
+
description: item.summary || item.description || '',
|
|
290
|
+
moduleName: item.moduleName || 'watch-create',
|
|
291
|
+
trigger: item.trigger || '',
|
|
292
|
+
headers: item.headers || [],
|
|
293
|
+
}),
|
|
294
|
+
});
|
|
295
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
270
298
|
} catch (err) {
|
|
271
|
-
console.warn(`[Watcher]
|
|
299
|
+
console.warn(`[Watcher] HTTP 候选提交也失败: ${err.message}`);
|
|
272
300
|
}
|
|
301
|
+
|
|
302
|
+
// 两条路径都失败 → 抛出原始错误
|
|
303
|
+
throw serviceError || new Error('候选提交失败:ServiceContainer 和 HTTP 均不可用');
|
|
273
304
|
}
|
|
274
305
|
|
|
275
306
|
/**
|