autosnippet 2.1.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +189 -113
  2. package/bin/api-server.js +1 -4
  3. package/bin/cli.js +1 -50
  4. package/config/constitution.yaml +33 -107
  5. package/dashboard/dist/assets/{icons-B5rs8uNb.js → icons-Dtm0E6DS.js} +95 -85
  6. package/dashboard/dist/assets/index-B7VpZOCz.css +1 -0
  7. package/dashboard/dist/assets/index-D87IZTmZ.js +187 -0
  8. package/dashboard/dist/assets/{react-markdown-Bp8u1wRC.js → react-markdown-CWxUbOf4.js} +1 -1
  9. package/dashboard/dist/assets/{syntax-highlighter-C6bvFtpx.js → syntax-highlighter-CJ2drQQb.js} +1 -1
  10. package/dashboard/dist/assets/{vendor-Cky7Jynh.js → vendor-f83ah6cm.js} +13 -13
  11. package/dashboard/dist/index.html +6 -6
  12. package/lib/bootstrap.js +5 -31
  13. package/lib/cli/SetupService.js +46 -18
  14. package/lib/core/capability/CapabilityProbe.js +8 -6
  15. package/lib/core/constitution/Constitution.js +13 -4
  16. package/lib/core/constitution/ConstitutionValidator.js +106 -211
  17. package/lib/core/gateway/Gateway.js +34 -98
  18. package/lib/core/gateway/GatewayActionRegistry.js +12 -1
  19. package/lib/core/permission/PermissionManager.js +2 -2
  20. package/lib/external/ai/AiProvider.js +47 -7
  21. package/lib/external/mcp/McpServer.js +4 -7
  22. package/lib/external/mcp/handlers/bootstrap.js +13 -1
  23. package/lib/external/mcp/handlers/browse.js +0 -7
  24. package/lib/external/mcp/handlers/candidate.js +1 -1
  25. package/lib/external/mcp/handlers/guard.js +11 -0
  26. package/lib/external/mcp/handlers/skill.js +186 -18
  27. package/lib/external/mcp/tools.js +40 -1
  28. package/lib/http/HttpServer.js +4 -0
  29. package/lib/http/middleware/roleResolver.js +1 -1
  30. package/lib/http/routes/auth.js +2 -2
  31. package/lib/http/routes/monitoring.js +4 -4
  32. package/lib/http/routes/search.js +0 -17
  33. package/lib/http/routes/skills.js +73 -0
  34. package/lib/injection/ServiceContainer.js +21 -40
  35. package/lib/service/candidate/CandidateService.js +12 -1
  36. package/lib/service/chat/ChatAgent.js +139 -18
  37. package/lib/service/chat/Memory.js +104 -0
  38. package/lib/service/chat/tools.js +244 -10
  39. package/lib/service/guard/GuardCheckEngine.js +9 -1
  40. package/lib/service/recipe/RecipeService.js +8 -0
  41. package/lib/service/skills/SkillHooks.js +126 -0
  42. package/package.json +1 -1
  43. package/scripts/init-db.js +1 -2
  44. package/templates/constitution.yaml +29 -85
  45. package/dashboard/dist/assets/index-0YzLw2ga.css +0 -1
  46. package/dashboard/dist/assets/index-DbkbX1c-.js +0 -154
  47. package/lib/core/session/SessionManager.js +0 -232
  48. package/lib/infrastructure/logging/ReasoningLogger.js +0 -269
  49. package/lib/infrastructure/monitoring/RoleDriftMonitor.js +0 -259
  50. package/lib/infrastructure/quality/ComplianceEvaluator.js +0 -326
@@ -5,12 +5,10 @@ import { InternalError } from '../../shared/errors/BaseError.js';
5
5
 
6
6
  /**
7
7
  * Gateway - 统一网关
8
- * 所有操作的唯一入口,负责:
9
- * 1. 权限检查
10
- * 2. 宪法验证
11
- * 3. 审计日志
12
- * 4. 会话管理
13
- * 5. 事件分发
8
+ * 所有操作的唯一入口。
9
+ *
10
+ * Pipeline (4 步):
11
+ * validate guard → route → audit
14
12
  */
15
13
  export class Gateway extends EventEmitter {
16
14
  constructor(config) {
@@ -18,14 +16,12 @@ export class Gateway extends EventEmitter {
18
16
  this.config = config;
19
17
  this.logger = Logger.getInstance();
20
18
  this.routes = new Map();
21
- this.plugins = [];
22
19
 
23
20
  // 依赖注入(稍后设置)
24
21
  this.constitution = null;
25
22
  this.constitutionValidator = null;
26
23
  this.permissionManager = null;
27
24
  this.auditLogger = null;
28
- this.sessionManager = null;
29
25
  }
30
26
 
31
27
  /**
@@ -36,13 +32,11 @@ export class Gateway extends EventEmitter {
36
32
  constitutionValidator,
37
33
  permissionManager,
38
34
  auditLogger,
39
- sessionManager,
40
35
  }) {
41
36
  this.constitution = constitution;
42
37
  this.constitutionValidator = constitutionValidator;
43
38
  this.permissionManager = permissionManager;
44
39
  this.auditLogger = auditLogger;
45
- this.sessionManager = sessionManager;
46
40
  }
47
41
 
48
42
  /**
@@ -63,14 +57,6 @@ export class Gateway extends EventEmitter {
63
57
  return [...this.routes.keys()];
64
58
  }
65
59
 
66
- /**
67
- * 注册插件
68
- */
69
- use(plugin) {
70
- this.plugins.push(plugin);
71
- this.logger.debug(`Plugin registered: ${plugin.name}`);
72
- }
73
-
74
60
  /**
75
61
  * 执行操作(主入口)
76
62
  */
@@ -95,28 +81,18 @@ export class Gateway extends EventEmitter {
95
81
  });
96
82
 
97
83
  try {
98
- // 1. 验证请求
84
+ // 1. validate — 请求格式
99
85
  this.validateRequest(request);
100
86
 
101
- // 2. 权限检查
102
- await this.checkPermission(context);
87
+ // 2. guard — 权限 + 宪法规则
88
+ await this.guard(context);
103
89
 
104
- // 3. 宪法验证
105
- await this.validateConstitution(context);
106
-
107
- // 4. 执行插件(pre-hook)
108
- await this.runPlugins('pre', context);
109
-
110
- // 5. 路由到处理器
90
+ // 3. route — 路由到处理器
111
91
  const result = await this.routeToHandler(context);
112
92
 
113
- // 6. 执行插件(post-hook)
114
- await this.runPlugins('post', context, result);
115
-
116
- // 7. 审计日志(成功)
93
+ // 4. audit — 记录成功
117
94
  await this.auditSuccess(context, result);
118
95
 
119
- // 8. 返回结果
120
96
  const duration = Date.now() - startTime;
121
97
  this.logger.info('Gateway: Request completed', {
122
98
  requestId,
@@ -130,7 +106,6 @@ export class Gateway extends EventEmitter {
130
106
  duration,
131
107
  };
132
108
  } catch (error) {
133
- // 审计日志(失败)
134
109
  await this.auditFailure(context, error);
135
110
 
136
111
  const duration = Date.now() - startTime;
@@ -155,7 +130,7 @@ export class Gateway extends EventEmitter {
155
130
 
156
131
  /**
157
132
  * 仅检查权限与宪法(不执行业务逻辑)
158
- * 用于 MCP Gateway gating — 只做 permission + constitution + audit,不路由到 handler
133
+ * 用于 MCP Gateway gating
159
134
  */
160
135
  async checkOnly(request) {
161
136
  const requestId = uuidv4();
@@ -173,13 +148,9 @@ export class Gateway extends EventEmitter {
173
148
 
174
149
  try {
175
150
  this.validateRequest(request);
176
- await this.checkPermission(context);
177
- await this.validateConstitution(context);
178
- await this.runPlugins('pre', context);
151
+ await this.guard(context);
179
152
 
180
- // 记录成功的 checkOnly 审计日志(供 MCP Gateway gating 审计追踪)
181
153
  await this.auditSuccess(context, { checkOnly: true });
182
-
183
154
  return { success: true, requestId };
184
155
  } catch (error) {
185
156
  await this.auditFailure(context, error);
@@ -195,8 +166,10 @@ export class Gateway extends EventEmitter {
195
166
  }
196
167
  }
197
168
 
169
+ // ─── Pipeline Steps ────────────────────────────────────
170
+
198
171
  /**
199
- * 验证请求格式
172
+ * validate — 验证请求格式
200
173
  */
201
174
  validateRequest(request) {
202
175
  if (!request.actor) {
@@ -208,38 +181,27 @@ export class Gateway extends EventEmitter {
208
181
  }
209
182
 
210
183
  /**
211
- * 权限检查
184
+ * guard — 权限检查 + 宪法验证
212
185
  */
213
- async checkPermission(context) {
214
- if (!this.permissionManager) {
215
- this.logger.warn('PermissionManager not set, skipping permission check');
216
- return;
186
+ async guard(context) {
187
+ // 权限检查
188
+ if (this.permissionManager) {
189
+ this.permissionManager.enforce(context.actor, context.action, context.resource);
217
190
  }
218
191
 
219
- this.permissionManager.enforce(context.actor, context.action, context.resource);
220
- }
221
-
222
- /**
223
- * 宪法验证
224
- */
225
- async validateConstitution(context) {
226
- if (!this.constitutionValidator) {
227
- this.logger.warn('ConstitutionValidator not set, skipping validation');
228
- return;
192
+ // 宪法数据完整性规则
193
+ if (this.constitutionValidator) {
194
+ await this.constitutionValidator.enforce({
195
+ actor: context.actor,
196
+ action: context.action,
197
+ resource: context.resource,
198
+ data: context.data,
199
+ });
229
200
  }
230
-
231
- const request = {
232
- actor: context.actor,
233
- action: context.action,
234
- resource: context.resource,
235
- data: context.data,
236
- };
237
-
238
- await this.constitutionValidator.enforce(request);
239
201
  }
240
202
 
241
203
  /**
242
- * 路由到处理器
204
+ * route — 路由到处理器
243
205
  */
244
206
  async routeToHandler(context) {
245
207
  const handler = this.routes.get(context.action);
@@ -252,23 +214,10 @@ export class Gateway extends EventEmitter {
252
214
  }
253
215
 
254
216
  /**
255
- * 执行插件
256
- */
257
- async runPlugins(phase, context, result = null) {
258
- for (const plugin of this.plugins) {
259
- if (plugin[phase]) {
260
- await plugin[phase](context, result);
261
- }
262
- }
263
- }
264
-
265
- /**
266
- * 审计成功
217
+ * audit — 记录成功
267
218
  */
268
219
  async auditSuccess(context, result) {
269
- if (!this.auditLogger) {
270
- return;
271
- }
220
+ if (!this.auditLogger) return;
272
221
 
273
222
  await this.auditLogger.log({
274
223
  requestId: context.requestId,
@@ -277,19 +226,15 @@ export class Gateway extends EventEmitter {
277
226
  resource: context.resource,
278
227
  result: 'success',
279
228
  duration: Date.now() - context.startTime,
280
- context: {
281
- session: context.session,
282
- },
229
+ context: { session: context.session },
283
230
  });
284
231
  }
285
232
 
286
233
  /**
287
- * 审计失败
234
+ * audit — 记录失败
288
235
  */
289
236
  async auditFailure(context, error) {
290
- if (!this.auditLogger) {
291
- return;
292
- }
237
+ if (!this.auditLogger) return;
293
238
 
294
239
  await this.auditLogger.log({
295
240
  requestId: context.requestId,
@@ -299,9 +244,7 @@ export class Gateway extends EventEmitter {
299
244
  result: 'failure',
300
245
  error: error.message,
301
246
  duration: Date.now() - context.startTime,
302
- context: {
303
- session: context.session,
304
- },
247
+ context: { session: context.session },
305
248
  });
306
249
  }
307
250
 
@@ -311,13 +254,6 @@ export class Gateway extends EventEmitter {
311
254
  getRoutes() {
312
255
  return Array.from(this.routes.keys());
313
256
  }
314
-
315
- /**
316
- * 获取所有插件
317
- */
318
- getPlugins() {
319
- return this.plugins.map((p) => p.name || 'anonymous');
320
- }
321
257
  }
322
258
 
323
259
  export default Gateway;
@@ -206,8 +206,19 @@ export function registerGatewayActions(gateway, container) {
206
206
 
207
207
  // ========== Search Actions ==========
208
208
 
209
+ // ========== Candidate Update (enrich/refine) ==========
210
+
211
+ gateway.register('candidate:update', async (ctx) => {
212
+ const service = container.get('candidateService');
213
+ return service.updateCandidate
214
+ ? service.updateCandidate(ctx.data.id, ctx.data, { userId: ctx.actor })
215
+ : service.createCandidate(ctx.data, { userId: ctx.actor });
216
+ });
217
+
218
+ // ========== Search ==========
219
+
209
220
  gateway.register('search:query', async (ctx) => {
210
- const service = container.get('searchService');
221
+ const service = container.get('searchEngine');
211
222
  return service.search(ctx.data.keyword, ctx.data.options);
212
223
  });
213
224
 
@@ -123,7 +123,7 @@ export class PermissionManager {
123
123
  * 处理多种格式:
124
124
  * - read_recipes -> read:recipes
125
125
  * - read:recipes -> read:recipes(已规范化)
126
- * - perm_cursor_agent_read_recipes -> read:recipes(测试使用的格式)
126
+ * - perm_external_agent_read_recipes -> read:recipes(测试使用的格式)
127
127
  */
128
128
  _normalizeAction(action) {
129
129
  // 如果已经包含冒号,直接返回
@@ -134,7 +134,7 @@ export class PermissionManager {
134
134
  // 处理测试格式:perm_actor_action_resource -> action:resource
135
135
  if (action.startsWith('perm_')) {
136
136
  const parts = action.split('_');
137
- // perm_cursor_agent_read_recipes -> ['perm', 'cursor', 'agent', 'read', 'recipes']
137
+ // perm_external_agent_read_recipes -> ['perm', 'cursor', 'agent', 'read', 'recipes']
138
138
  // 跳过 'perm' 和 actor 名称部分,从实际的 action 部分开始
139
139
  if (parts.length >= 4) {
140
140
  // 尝试找到 action 部分(常见的 action 包括 read, create, delete, submit, approve, reject)
@@ -472,11 +472,25 @@ ${items}`;
472
472
 
473
473
  /**
474
474
  * 修复被截断的 JSON 数组 — 回收已完成的对象
475
- * 策略:找到最后一个完整的 {...} 对象,截断后闭合数组
475
+ * 策略 1(主路径): 字符级解析找到最后一个完整的顶层 {...} 对象
476
+ * 策略 2(回退路径): 正则 + 渐进 JSON.parse 尝试(应对代码段中未转义引号导致 inString 追踪失效)
476
477
  */
477
478
  _repairTruncatedArray(text) {
478
- // 找到最后一个完整对象的结尾 "}," 或 "}\n" 或直接 "}"
479
- // 从后向前搜索最后一个顶层 "}" 后跟 "," 或空白
479
+ // ── 策略 1:字符级深度追踪 ──
480
+ const charResult = this._repairByCharTracking(text);
481
+ if (charResult) return charResult;
482
+
483
+ // ── 策略 2:正则回退 — 找所有 "}," 或 "}\n" 位置,从后向前逐一尝试 JSON.parse ──
484
+ const regexResult = this._repairByRegexFallback(text);
485
+ if (regexResult) return regexResult;
486
+
487
+ return null;
488
+ }
489
+
490
+ /**
491
+ * 字符级深度追踪修复(原逻辑,处理标准 JSON)
492
+ */
493
+ _repairByCharTracking(text) {
480
494
  let depth = 0;
481
495
  let inString = false;
482
496
  let escape = false;
@@ -500,13 +514,39 @@ ${items}`;
500
514
  }
501
515
 
502
516
  if (lastCompleteObjEnd === -1) return null;
517
+ return this._tryRepairAt(text, lastCompleteObjEnd);
518
+ }
519
+
520
+ /**
521
+ * 正则回退修复 — 不依赖 inString 追踪
522
+ * 寻找所有 "},\s*{" 或 "}\s*]" 边界,从后往前尝试 JSON.parse
523
+ */
524
+ _repairByRegexFallback(text) {
525
+ // 收集所有 "}" 后跟 "," 或空白的位置(可能是对象边界)
526
+ const candidates = [];
527
+ const re = /\}[\s,]*(?=\s*[\[{]|$)/g;
528
+ let m;
529
+ while ((m = re.exec(text)) !== null) {
530
+ candidates.push(m.index); // "}" 的位置
531
+ }
503
532
 
504
- // 截取到最后一个完整对象,闭合数组
505
- let repaired = text.slice(0, lastCompleteObjEnd + 1);
533
+ // 从后往前尝试
534
+ for (let i = candidates.length - 1; i >= 0; i--) {
535
+ const result = this._tryRepairAt(text, candidates[i]);
536
+ if (result) return result;
537
+ }
538
+ return null;
539
+ }
540
+
541
+ /**
542
+ * 在指定位置截断并尝试闭合 JSON 数组
543
+ */
544
+ _tryRepairAt(text, endPos) {
545
+ let repaired = text.slice(0, endPos + 1);
506
546
  // 去掉尾逗号
507
547
  repaired = repaired.replace(/,\s*$/, '');
508
548
  repaired += ']';
509
- // 修复尾逗号
549
+ // 修复尾逗号(对象/数组末尾多余逗号)
510
550
  repaired = repaired.replace(/,\s*([}\]])/g, '$1');
511
551
 
512
552
  try {
@@ -515,7 +555,7 @@ ${items}`;
515
555
  this._log('warn', `[extractJSON] Repaired truncated JSON array: recovered ${result.length} items from truncated response`);
516
556
  return result;
517
557
  }
518
- } catch { /* repair failed */ }
558
+ } catch { /* this position didn't work, try next */ }
519
559
  return null;
520
560
  }
521
561
 
@@ -61,10 +61,7 @@ export class McpServer {
61
61
  db: components.db,
62
62
  auditLogger: components.auditLogger,
63
63
  gateway: components.gateway,
64
- reasoningLogger: components.reasoningLogger,
65
- roleDriftMonitor: components.roleDriftMonitor,
66
- complianceEvaluator: components.complianceEvaluator,
67
- sessionManager: components.sessionManager,
64
+ constitution: components.constitution,
68
65
  projectRoot: process.env.ASD_PROJECT_DIR || process.cwd(),
69
66
  });
70
67
 
@@ -123,7 +120,6 @@ export class McpServer {
123
120
  case 'autosnippet_list_recipes': return browseHandlers.listRecipes(ctx, args);
124
121
  case 'autosnippet_get_recipe': return browseHandlers.getRecipe(ctx, args);
125
122
  case 'autosnippet_recipe_insights': return browseHandlers.recipeInsights(ctx, args);
126
- case 'autosnippet_compliance_report': return browseHandlers.complianceReport(ctx, args);
127
123
  case 'autosnippet_confirm_usage': return browseHandlers.confirmUsage(ctx, args);
128
124
  // 项目结构 & 图谱
129
125
  case 'autosnippet_get_targets': return structureHandlers.getTargets(ctx);
@@ -147,9 +143,10 @@ export class McpServer {
147
143
  // Bootstrap 冷启动
148
144
  case 'autosnippet_bootstrap_knowledge': return bootstrapHandlers.bootstrapKnowledge(ctx, args);
149
145
  case 'autosnippet_bootstrap_refine': return bootstrapHandlers.bootstrapRefine(ctx, args);
150
- // Skills 加载
146
+ // Skills 加载 & 创建
151
147
  case 'autosnippet_list_skills': return skillHandlers.listSkills();
152
148
  case 'autosnippet_load_skill': return skillHandlers.loadSkill(ctx, args);
149
+ case 'autosnippet_create_skill': return skillHandlers.createSkill(ctx, args);
153
150
  default: throw new Error(`Unknown tool: ${name}`);
154
151
  }
155
152
  }
@@ -167,7 +164,7 @@ export class McpServer {
167
164
  if (!gateway) return; // Gateway 未初始化,降级放行
168
165
 
169
166
  const result = await gateway.checkOnly({
170
- actor: 'cursor_agent',
167
+ actor: 'external_agent',
171
168
  action: mapping.action,
172
169
  resource: mapping.resource,
173
170
  data: args || {},
@@ -664,6 +664,18 @@ export async function bootstrapKnowledge(ctx, args) {
664
664
  responseData.skillsEnhanced = skillsEnhanced;
665
665
  responseData.message = `Bootstrap 完成: ${allFiles.length} files, ${allTargets.length} targets, ${depEdgesWritten} graph edges, ${candidateResults.created} 条单一职责候选已创建${skillsEnhanced ? '(Skill 增强)' : ''}。${skillContext?.loaded?.length ? `已加载 Skills: ${skillContext.loaded.join(', ')}。` : ''}⚠️ 候选为启发式初稿,请务必执行后续 AI 精炼步骤提升质量。`;
666
666
 
667
+ // ── SkillHooks: onBootstrapComplete (fire-and-forget) ──
668
+ try {
669
+ const skillHooks = ctx.container.get('skillHooks');
670
+ skillHooks.run('onBootstrapComplete', {
671
+ filesScanned: allFiles.length,
672
+ targetsFound: allTargets.length,
673
+ candidatesCreated: candidateResults.created,
674
+ candidatesFailed: candidateResults.failed,
675
+ }, { projectRoot: ctx.container.get('database')?.filename || '' })
676
+ .catch(() => {}); // fire-and-forget
677
+ } catch { /* skillHooks not available */ }
678
+
667
679
  return envelope({
668
680
  success: true,
669
681
  data: responseData,
@@ -695,7 +707,7 @@ export async function bootstrapRefine(ctx, args) {
695
707
  const result = await candidateService.refineBootstrapCandidates(
696
708
  aiProvider,
697
709
  { candidateIds: args.candidateIds, userPrompt: args.userPrompt, dryRun: args.dryRun },
698
- { userId: 'cursor_agent' },
710
+ { userId: 'external_agent' },
699
711
  );
700
712
 
701
713
  return envelope({
@@ -119,13 +119,6 @@ export async function recipeInsights(ctx, args) {
119
119
  return envelope({ success: true, data: insights, meta: { tool: 'autosnippet_recipe_insights' } });
120
120
  }
121
121
 
122
- export async function complianceReport(ctx, args = {}) {
123
- const evaluator = ctx.container.get('complianceEvaluator');
124
- if (!evaluator) return envelope({ success: false, message: 'ComplianceEvaluator not available', meta: { tool: 'autosnippet_compliance_report' } });
125
- const report = await evaluator.evaluate({ period: args.period || 'all' });
126
- return envelope({ success: true, data: report, meta: { tool: 'autosnippet_compliance_report' } });
127
- }
128
-
129
122
  export async function confirmUsage(ctx, args) {
130
123
  if (!args.recipeId) throw new Error('recipeId is required');
131
124
  const recipeService = ctx.container.get('recipeService');
@@ -65,7 +65,7 @@ export function buildCandidateMetadata(obj) {
65
65
  * 保留此函数作为 MCP handler 层的快捷入口,保持向后兼容。
66
66
  */
67
67
  async function _createCandidateItem(candidateService, item, source, extraMeta = {}) {
68
- return candidateService.createFromToolParams(item, source, extraMeta, { userId: 'cursor_agent' });
68
+ return candidateService.createFromToolParams(item, source, extraMeta, { userId: 'external_agent' });
69
69
  }
70
70
 
71
71
  // ─── 限流检查 ──────────────────────────────────────────────
@@ -24,6 +24,17 @@ export async function guardCheck(ctx, args) {
24
24
  const language = args.language || detectLanguage(args.filePath || '');
25
25
  const violations = engine.checkCode(args.code, language);
26
26
 
27
+ // ── SkillHooks: onGuardCheck — 允许 hooks 修改 violations ──
28
+ try {
29
+ const skillHooks = ctx.container.get('skillHooks');
30
+ if (skillHooks.has('onGuardCheck')) {
31
+ for (let i = 0; i < violations.length; i++) {
32
+ const modified = await skillHooks.run('onGuardCheck', violations[i], { language });
33
+ if (modified && typeof modified === 'object') violations[i] = modified;
34
+ }
35
+ }
36
+ } catch { /* skillHooks not available */ }
37
+
27
38
  const warnings = [];
28
39
  if (language === 'unknown') {
29
40
  warnings.push('未能识别语言,部分语言相关规则可能未执行。建议提供 language 或 filePath 参数。');