autosnippet 2.1.0 → 2.4.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 (40) 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/{index-DbkbX1c-.js → index-B9py3ybr.js} +32 -32
  6. package/dashboard/dist/index.html +1 -1
  7. package/lib/bootstrap.js +5 -31
  8. package/lib/cli/SetupService.js +16 -14
  9. package/lib/core/capability/CapabilityProbe.js +8 -6
  10. package/lib/core/constitution/Constitution.js +13 -4
  11. package/lib/core/constitution/ConstitutionValidator.js +106 -211
  12. package/lib/core/gateway/Gateway.js +34 -98
  13. package/lib/core/gateway/GatewayActionRegistry.js +12 -1
  14. package/lib/core/permission/PermissionManager.js +2 -2
  15. package/lib/external/mcp/McpServer.js +4 -7
  16. package/lib/external/mcp/handlers/bootstrap.js +13 -1
  17. package/lib/external/mcp/handlers/browse.js +0 -7
  18. package/lib/external/mcp/handlers/candidate.js +1 -1
  19. package/lib/external/mcp/handlers/guard.js +11 -0
  20. package/lib/external/mcp/handlers/skill.js +186 -18
  21. package/lib/external/mcp/tools.js +40 -1
  22. package/lib/http/middleware/roleResolver.js +1 -1
  23. package/lib/http/routes/auth.js +2 -2
  24. package/lib/http/routes/monitoring.js +4 -4
  25. package/lib/http/routes/search.js +0 -17
  26. package/lib/injection/ServiceContainer.js +21 -40
  27. package/lib/service/candidate/CandidateService.js +12 -1
  28. package/lib/service/chat/ChatAgent.js +139 -18
  29. package/lib/service/chat/Memory.js +104 -0
  30. package/lib/service/chat/tools.js +244 -10
  31. package/lib/service/guard/GuardCheckEngine.js +9 -1
  32. package/lib/service/recipe/RecipeService.js +8 -0
  33. package/lib/service/skills/SkillHooks.js +126 -0
  34. package/package.json +1 -1
  35. package/scripts/init-db.js +1 -2
  36. package/templates/constitution.yaml +29 -85
  37. package/lib/core/session/SessionManager.js +0 -232
  38. package/lib/infrastructure/logging/ReasoningLogger.js +0 -269
  39. package/lib/infrastructure/monitoring/RoleDriftMonitor.js +0 -259
  40. package/lib/infrastructure/quality/ComplianceEvaluator.js +0 -326
@@ -1,259 +0,0 @@
1
- /**
2
- * RoleDriftMonitor - 角色漂移检测系统
3
- *
4
- * 功能:
5
- * - 监控 AI 角色切换行为
6
- * - 检测非法角色转移
7
- * - 记录漂移事件供审计
8
- * - 提供角色稳定性评分
9
- */
10
-
11
- import Logger from '../logging/Logger.js';
12
-
13
- /**
14
- * 角色转移矩阵
15
- * 定义每个角色允许转移到哪些角色
16
- */
17
- const ALLOWED_TRANSITIONS = {
18
- cursor_agent: ['cursor_agent'],
19
- asd_ais: ['asd_ais'],
20
- guard_engine: ['guard_engine'],
21
- developer_admin: ['developer_admin', 'developer_contributor'],
22
- developer_contributor: ['developer_contributor'],
23
- };
24
-
25
- /**
26
- * 角色能力边界
27
- * 每个角色可以执行的 action 前缀
28
- */
29
- const ROLE_CAPABILITIES = {
30
- cursor_agent: ['candidate:create', 'candidate:list', 'candidate:search', 'recipe:list', 'recipe:search', 'recipe:get', 'recipe:guard_check', 'search:query'],
31
- asd_ais: ['candidate:create', 'candidate:list', 'candidate:search', 'recipe:list', 'recipe:search', 'search:query'],
32
- guard_engine: ['recipe:guard_check', 'recipe:list', 'recipe:get'],
33
- developer_admin: ['*'],
34
- developer_contributor: ['candidate:create', 'candidate:list', 'candidate:approve', 'candidate:reject', 'recipe:create', 'recipe:list', 'recipe:search', 'recipe:publish', 'recipe:guard_create', 'search:query'],
35
- };
36
-
37
- export class RoleDriftMonitor {
38
- constructor(db) {
39
- this.db = typeof db?.getDb === 'function' ? db.getDb() : db;
40
- this.logger = Logger.getInstance();
41
- this.sessionRoles = new Map(); // sessionId → { currentRole, history[], driftCount }
42
- this._ensureTable();
43
- }
44
-
45
- _ensureTable() {
46
- this.db.exec(`
47
- CREATE TABLE IF NOT EXISTS role_drift_events (
48
- id INTEGER PRIMARY KEY AUTOINCREMENT,
49
- session_id TEXT NOT NULL,
50
- actor TEXT NOT NULL,
51
- from_role TEXT,
52
- to_role TEXT,
53
- action_attempted TEXT,
54
- drift_type TEXT NOT NULL,
55
- severity TEXT NOT NULL DEFAULT 'warning',
56
- details_json TEXT DEFAULT '{}',
57
- created_at INTEGER NOT NULL
58
- )
59
- `);
60
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_drift_session ON role_drift_events(session_id)`);
61
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_drift_actor ON role_drift_events(actor)`);
62
- }
63
-
64
- /**
65
- * 检查角色行为是否合法
66
- * @param {string} actor - 角色 ID
67
- * @param {string} action - 尝试执行的操作
68
- * @param {string} sessionId - 会话 ID
69
- * @returns {{ allowed: boolean, drift?: object }}
70
- */
71
- checkAction(actor, action, sessionId) {
72
- // 获取角色能力
73
- const capabilities = ROLE_CAPABILITIES[actor];
74
- if (!capabilities) {
75
- const drift = this._recordDrift(sessionId, actor, null, null, action, 'unknown_role', 'error');
76
- return { allowed: false, drift };
77
- }
78
-
79
- // 通配符 (admin)
80
- if (capabilities.includes('*')) {
81
- return { allowed: true };
82
- }
83
-
84
- // 检查 action 是否在角色能力范围内
85
- const isAllowed = capabilities.some(cap => {
86
- if (cap === action) return true;
87
- // 前缀匹配: 'candidate:*' 匹配 'candidate:create'
88
- if (cap.endsWith(':*')) {
89
- return action.startsWith(cap.slice(0, -1));
90
- }
91
- return false;
92
- });
93
-
94
- if (!isAllowed) {
95
- const drift = this._recordDrift(
96
- sessionId, actor, actor, actor, action,
97
- 'capability_violation', 'warning'
98
- );
99
- return { allowed: false, drift };
100
- }
101
-
102
- return { allowed: true };
103
- }
104
-
105
- /**
106
- * 检查角色转移是否合法
107
- * @param {string} sessionId - 会话 ID
108
- * @param {string} currentRole - 当前角色
109
- * @param {string} nextRole - 目标角色
110
- * @returns {{ allowed: boolean, drift?: object }}
111
- */
112
- checkRoleTransition(sessionId, currentRole, nextRole) {
113
- if (currentRole === nextRole) return { allowed: true };
114
-
115
- const allowed = ALLOWED_TRANSITIONS[currentRole];
116
- if (!allowed || !allowed.includes(nextRole)) {
117
- const drift = this._recordDrift(
118
- sessionId, currentRole, currentRole, nextRole, null,
119
- 'illegal_transition', 'error'
120
- );
121
- return { allowed: false, drift };
122
- }
123
-
124
- return { allowed: true };
125
- }
126
-
127
- /**
128
- * 获取会话的漂移统计
129
- */
130
- getSessionDriftStats(sessionId) {
131
- const events = this.db.prepare(
132
- `SELECT drift_type, severity, COUNT(*) as count FROM role_drift_events WHERE session_id = ? GROUP BY drift_type, severity`
133
- ).all(sessionId);
134
-
135
- const total = events.reduce((sum, e) => sum + e.count, 0);
136
- const errorCount = events.filter(e => e.severity === 'error').reduce((s, e) => s + e.count, 0);
137
-
138
- return {
139
- sessionId,
140
- totalDrifts: total,
141
- errorDrifts: errorCount,
142
- warningDrifts: total - errorCount,
143
- stability: total === 0 ? 1.0 : Math.max(0, 1 - total * 0.1),
144
- byType: events,
145
- };
146
- }
147
-
148
- /**
149
- * 获取全局漂移统计
150
- */
151
- getGlobalStats(since = null) {
152
- const params = [];
153
- let where = '1=1';
154
- if (since) {
155
- where = 'created_at >= ?';
156
- params.push(since);
157
- }
158
-
159
- const stats = this.db.prepare(`
160
- SELECT
161
- actor,
162
- drift_type,
163
- severity,
164
- COUNT(*) as count
165
- FROM role_drift_events
166
- WHERE ${where}
167
- GROUP BY actor, drift_type, severity
168
- ORDER BY count DESC
169
- `).all(...params);
170
-
171
- const total = this.db.prepare(
172
- `SELECT COUNT(*) as total FROM role_drift_events WHERE ${where}`
173
- ).get(...params);
174
-
175
- return {
176
- totalDrifts: total.total,
177
- byActor: stats,
178
- };
179
- }
180
-
181
- /**
182
- * 获取最近的漂移事件
183
- */
184
- getRecentDrifts(limit = 20) {
185
- const rows = this.db.prepare(
186
- `SELECT * FROM role_drift_events ORDER BY created_at DESC LIMIT ?`
187
- ).all(limit);
188
-
189
- return rows.map(r => ({
190
- id: r.id,
191
- sessionId: r.session_id,
192
- actor: r.actor,
193
- fromRole: r.from_role,
194
- toRole: r.to_role,
195
- actionAttempted: r.action_attempted,
196
- driftType: r.drift_type,
197
- severity: r.severity,
198
- details: JSON.parse(r.details_json || '{}'),
199
- createdAt: r.created_at,
200
- }));
201
- }
202
-
203
- // ========== Private ==========
204
-
205
- _recordDrift(sessionId, actor, fromRole, toRole, action, driftType, severity) {
206
- const now = Math.floor(Date.now() / 1000);
207
- const details = { timestamp: now };
208
-
209
- this.db.prepare(`
210
- INSERT INTO role_drift_events (session_id, actor, from_role, to_role, action_attempted, drift_type, severity, details_json, created_at)
211
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
212
- `).run(sessionId || 'unknown', actor, fromRole, toRole, action, driftType, severity, JSON.stringify(details), now);
213
-
214
- this.logger.warn('Role drift detected', {
215
- sessionId, actor, fromRole, toRole, action, driftType, severity,
216
- });
217
-
218
- return { driftType, severity, actor, fromRole, toRole, action };
219
- }
220
- }
221
-
222
- /**
223
- * Gateway 插件:角色漂移检测
224
- */
225
- export function createRoleDriftPlugin(roleDriftMonitor) {
226
- return {
227
- name: 'RoleDriftPlugin',
228
- async pre(context) {
229
- // Monitor-only: log drift events but don't block
230
- // Authorization is handled by PermissionManager
231
- const result = roleDriftMonitor.checkAction(
232
- context.actor,
233
- context.action,
234
- context.session || 'default'
235
- );
236
- if (!result.allowed && result.drift) {
237
- // 仅记录漂移事件,不阻断请求
238
- roleDriftMonitor.logger.warn('Role drift detected', {
239
- actor: context.actor,
240
- action: context.action,
241
- driftType: result.drift.driftType,
242
- });
243
- }
244
- },
245
- };
246
- }
247
-
248
- let instance = null;
249
-
250
- export function initRoleDriftMonitor(db) {
251
- instance = new RoleDriftMonitor(db);
252
- return instance;
253
- }
254
-
255
- export function getRoleDriftMonitor() {
256
- return instance;
257
- }
258
-
259
- export default RoleDriftMonitor;
@@ -1,326 +0,0 @@
1
- /**
2
- * ComplianceEvaluator - 宪法合规评估工具
3
- *
4
- * 自动评估系统在四大优先级上的合规程度,生成结构化报告。
5
- * 可通过 CLI 命令 `asd compliance` 或 API 调用。
6
- *
7
- * 四大优先级:
8
- * 1. Data Integrity - 数据完整性
9
- * 2. Human Oversight - 人工监督
10
- * 3. AI Transparency - AI 透明性
11
- * 4. Helpfulness - 帮助性
12
- */
13
-
14
- import Logger from '../logging/Logger.js';
15
-
16
- export class ComplianceEvaluator {
17
- constructor(db) {
18
- this.db = typeof db?.getDb === 'function' ? db.getDb() : db;
19
- this.logger = Logger.getInstance();
20
- }
21
-
22
- /**
23
- * 执行完整合规评估
24
- * @param {object} options - { period: 'all'|'weekly'|'monthly', priority: null|string }
25
- * @returns {object} 合规报告
26
- */
27
- evaluate(options = {}) {
28
- const since = this._getSinceTimestamp(options.period);
29
- const report = {
30
- evaluatedAt: new Date().toISOString(),
31
- period: options.period || 'all',
32
- priorities: {},
33
- overallScore: 0,
34
- recommendations: [],
35
- };
36
-
37
- // 评估每个优先级
38
- report.priorities.dataIntegrity = this._evaluateDataIntegrity(since);
39
- report.priorities.humanOversight = this._evaluateHumanOversight(since);
40
- report.priorities.aiTransparency = this._evaluateAITransparency(since);
41
- report.priorities.helpfulness = this._evaluateHelpfulness(since);
42
-
43
- // 加权总分 (P1=35%, P2=30%, P3=20%, P4=15%)
44
- report.overallScore = Math.round((
45
- report.priorities.dataIntegrity.score * 0.35 +
46
- report.priorities.humanOversight.score * 0.30 +
47
- report.priorities.aiTransparency.score * 0.20 +
48
- report.priorities.helpfulness.score * 0.15
49
- ) * 100) / 100;
50
-
51
- // 生成改进建议
52
- report.recommendations = this._generateRecommendations(report.priorities);
53
-
54
- return report;
55
- }
56
-
57
- // ========== Priority 1: Data Integrity ==========
58
-
59
- _evaluateDataIntegrity(since) {
60
- const result = { score: 0, metrics: {}, issues: [] };
61
- const whereTime = since ? 'AND created_at >= ?' : '';
62
- const whereParams = since ? [since] : [];
63
-
64
- // 1. Candidate → Recipe 转化率 (目标 > 60%)
65
- const candidateStats = this._safeQuery(`
66
- SELECT
67
- COUNT(*) as total,
68
- SUM(CASE WHEN status = 'applied' THEN 1 ELSE 0 END) as applied,
69
- SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) as approved
70
- FROM candidates WHERE 1=1 ${whereTime}
71
- `, whereParams);
72
- const conversionRate = candidateStats.total > 0
73
- ? (candidateStats.applied + candidateStats.approved) / candidateStats.total
74
- : 0;
75
- result.metrics.candidateConversionRate = Math.round(conversionRate * 100) / 100;
76
-
77
- if (conversionRate < 0.6) {
78
- result.issues.push({ severity: 'warning', message: `Candidate conversion rate ${(conversionRate * 100).toFixed(0)}% is below 60% target` });
79
- }
80
-
81
- // 2. Recipe 版本追踪 (audit_logs 覆盖率)
82
- const recipeCount = this._safeQuery(`SELECT COUNT(*) as total FROM recipes WHERE 1=1 ${whereTime}`, whereParams);
83
- const auditedRecipes = this._safeQuery(`
84
- SELECT COUNT(DISTINCT resource_id) as total FROM audit_logs
85
- WHERE resource_type = 'recipe' ${whereTime}
86
- `, whereParams);
87
- const auditCoverage = recipeCount.total > 0 ? auditedRecipes.total / recipeCount.total : 1;
88
- result.metrics.recipeAuditCoverage = Math.round(auditCoverage * 100) / 100;
89
-
90
- if (auditCoverage < 1) {
91
- result.issues.push({ severity: 'info', message: `${((1 - auditCoverage) * 100).toFixed(0)}% of recipes lack audit trail` });
92
- }
93
-
94
- // 3. Candidate 有推理过程的比率
95
- const withReasoning = this._safeQuery(`
96
- SELECT
97
- COUNT(*) as total,
98
- SUM(CASE WHEN reasoning_json IS NOT NULL AND reasoning_json != '{}' AND reasoning_json != 'null' THEN 1 ELSE 0 END) as with_reasoning
99
- FROM candidates WHERE 1=1 ${whereTime}
100
- `, whereParams);
101
- const reasoningRate = withReasoning.total > 0 ? withReasoning.with_reasoning / withReasoning.total : 0;
102
- result.metrics.reasoningRate = Math.round(reasoningRate * 100) / 100;
103
-
104
- // 综合评分
105
- result.score = Math.round((conversionRate * 0.4 + auditCoverage * 0.3 + reasoningRate * 0.3) * 100) / 100;
106
- return result;
107
- }
108
-
109
- // ========== Priority 2: Human Oversight ==========
110
-
111
- _evaluateHumanOversight(since) {
112
- const result = { score: 0, metrics: {}, issues: [] };
113
- const whereTime = since ? 'AND created_at >= ?' : '';
114
- const whereParams = since ? [since] : [];
115
-
116
- // 1. 零自动修改事件 (AI 不应直接修改 Recipe)
117
- const autoModifications = this._safeQuery(`
118
- SELECT COUNT(*) as total FROM audit_logs
119
- WHERE action IN ('create_recipe', 'publish_recipe', 'update_recipe')
120
- AND actor IN ('cursor_agent', 'asd_ais', 'guard_engine') ${whereTime}
121
- `, whereParams);
122
- result.metrics.autoModifications = autoModifications.total;
123
- const noAutoModScore = autoModifications.total === 0 ? 1 : Math.max(0, 1 - autoModifications.total * 0.1);
124
-
125
- if (autoModifications.total > 0) {
126
- result.issues.push({ severity: 'error', message: `${autoModifications.total} auto-modifications by AI actors detected` });
127
- }
128
-
129
- // 2. 审核覆盖率 (所有 Candidate 都有人工操作)
130
- const reviewed = this._safeQuery(`
131
- SELECT
132
- COUNT(*) as total,
133
- SUM(CASE WHEN status IN ('approved', 'rejected', 'applied') THEN 1 ELSE 0 END) as reviewed
134
- FROM candidates WHERE 1=1 ${whereTime}
135
- `, whereParams);
136
- const reviewRate = reviewed.total > 0 ? reviewed.reviewed / reviewed.total : 1;
137
- result.metrics.reviewRate = Math.round(reviewRate * 100) / 100;
138
-
139
- // 3. 审计日志完整性
140
- const totalActions = this._safeQuery(`SELECT COUNT(*) as total FROM audit_logs WHERE 1=1 ${whereTime}`, whereParams);
141
- const failedAudits = this._safeQuery(`
142
- SELECT COUNT(*) as total FROM audit_logs WHERE result = 'failure' ${whereTime}
143
- `, whereParams);
144
- const auditSuccess = totalActions.total > 0 ? 1 - (failedAudits.total / totalActions.total) : 1;
145
- result.metrics.auditSuccessRate = Math.round(auditSuccess * 100) / 100;
146
-
147
- result.score = Math.round((noAutoModScore * 0.5 + reviewRate * 0.3 + auditSuccess * 0.2) * 100) / 100;
148
- return result;
149
- }
150
-
151
- // ========== Priority 3: AI Transparency ==========
152
-
153
- _evaluateAITransparency(since) {
154
- const result = { score: 0, metrics: {}, issues: [] };
155
- const whereTime = since ? 'AND created_at >= ?' : '';
156
- const whereParams = since ? [since] : [];
157
-
158
- // 1. AI 创建的 Candidate 推理完整性
159
- const aiCandidates = this._safeQuery(`
160
- SELECT
161
- COUNT(*) as total,
162
- SUM(CASE WHEN reasoning_json IS NOT NULL AND reasoning_json != '{}' AND reasoning_json != 'null' AND LENGTH(reasoning_json) > 20 THEN 1 ELSE 0 END) as with_full_reasoning
163
- FROM candidates
164
- WHERE source IN ('cursor_agent', 'asd_ais', 'ai') ${whereTime}
165
- `, whereParams);
166
- const aiReasoningRate = aiCandidates.total > 0 ? aiCandidates.with_full_reasoning / aiCandidates.total : 1;
167
- result.metrics.aiReasoningCompleteness = Math.round(aiReasoningRate * 100) / 100;
168
-
169
- if (aiReasoningRate < 0.7) {
170
- result.issues.push({ severity: 'warning', message: `Only ${(aiReasoningRate * 100).toFixed(0)}% of AI candidates have complete reasoning (target: 70%)` });
171
- }
172
-
173
- // 2. Guard 规则(boundary-constraint 类型 Recipe)有来源的比率
174
- const guardRecipes = this._safeQuery(`
175
- SELECT
176
- COUNT(*) as total,
177
- SUM(CASE WHEN source_candidate_id IS NOT NULL AND source_candidate_id != '' THEN 1 ELSE 0 END) as with_source
178
- FROM recipes WHERE knowledge_type = 'boundary-constraint' ${whereTime}
179
- `, whereParams);
180
- const guardSourceRate = guardRecipes.total > 0 ? guardRecipes.with_source / guardRecipes.total : 1;
181
- result.metrics.guardRuleSourceRate = Math.round(guardSourceRate * 100) / 100;
182
-
183
- // 3. 推理日志记录率
184
- let reasoningLogRate = 1;
185
- try {
186
- const hasTable = this.db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='reasoning_logs'`).get();
187
- if (hasTable) {
188
- const logs = this._safeQuery(`SELECT COUNT(*) as total FROM reasoning_logs WHERE 1=1 ${whereTime}`, whereParams);
189
- const expected = aiCandidates.total + (guardRecipes.total > 0 ? 1 : 0);
190
- reasoningLogRate = expected > 0 ? Math.min(1, logs.total / expected) : 1;
191
- }
192
- } catch {}
193
- result.metrics.reasoningLogRate = Math.round(reasoningLogRate * 100) / 100;
194
-
195
- result.score = Math.round((aiReasoningRate * 0.4 + guardSourceRate * 0.3 + reasoningLogRate * 0.3) * 100) / 100;
196
- return result;
197
- }
198
-
199
- // ========== Priority 4: Helpfulness ==========
200
-
201
- _evaluateHelpfulness(since) {
202
- const result = { score: 0, metrics: {}, issues: [] };
203
- const whereTime = since ? 'AND created_at >= ?' : '';
204
- const whereParams = since ? [since] : [];
205
-
206
- // 1. Recipe 采纳率
207
- const recipes = this._safeQuery(`
208
- SELECT
209
- COUNT(*) as total,
210
- SUM(adoption_count) as total_adoptions,
211
- SUM(application_count) as total_applications,
212
- AVG(quality_overall) as avg_quality
213
- FROM recipes WHERE status = 'active' ${whereTime}
214
- `, whereParams);
215
- result.metrics.activeRecipes = recipes.total;
216
- result.metrics.totalAdoptions = recipes.total_adoptions || 0;
217
- result.metrics.totalApplications = recipes.total_applications || 0;
218
- result.metrics.avgQuality = Math.round((recipes.avg_quality || 0) * 100) / 100;
219
-
220
- // 活跃 Recipe 有使用记录的比率
221
- const usedRecipes = this._safeQuery(`
222
- SELECT COUNT(*) as total FROM recipes
223
- WHERE status = 'active' AND (adoption_count > 0 OR application_count > 0) ${whereTime}
224
- `, whereParams);
225
- const usageRate = recipes.total > 0 ? usedRecipes.total / recipes.total : 0;
226
- result.metrics.recipeUsageRate = Math.round(usageRate * 100) / 100;
227
-
228
- // 2. Guard 规则启用率(boundary-constraint 类型 Recipe 中 active 的比率)
229
- const guardStats = this._safeQuery(`
230
- SELECT
231
- COUNT(*) as total,
232
- SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as enabled_count
233
- FROM recipes WHERE knowledge_type = 'boundary-constraint'
234
- `);
235
- const enabledRate = guardStats.total > 0 ? guardStats.enabled_count / guardStats.total : 0;
236
- result.metrics.guardRuleEnabledRate = Math.round(enabledRate * 100) / 100;
237
-
238
- // 3. Candidate 采纳率 (approved+applied vs total)
239
- const candidates = this._safeQuery(`
240
- SELECT
241
- COUNT(*) as total,
242
- SUM(CASE WHEN status IN ('approved', 'applied') THEN 1 ELSE 0 END) as accepted
243
- FROM candidates WHERE 1=1 ${whereTime}
244
- `, whereParams);
245
- const acceptRate = candidates.total > 0 ? candidates.accepted / candidates.total : 0;
246
- result.metrics.candidateAcceptRate = Math.round(acceptRate * 100) / 100;
247
-
248
- if (acceptRate < 0.6) {
249
- result.issues.push({ severity: 'info', message: `Candidate acceptance rate ${(acceptRate * 100).toFixed(0)}% is below 60% target` });
250
- }
251
-
252
- result.score = Math.round((usageRate * 0.3 + enabledRate * 0.2 + acceptRate * 0.3 + (recipes.avg_quality || 0) * 0.2) * 100) / 100;
253
- return result;
254
- }
255
-
256
- // ========== Recommendations ==========
257
-
258
- _generateRecommendations(priorities) {
259
- const recs = [];
260
-
261
- if (priorities.dataIntegrity.score < 0.7) {
262
- recs.push({
263
- priority: 'P1:DataIntegrity',
264
- action: 'Improve candidate reasoning completeness and conversion rate',
265
- severity: 'high',
266
- });
267
- }
268
- if (priorities.humanOversight.metrics.autoModifications > 0) {
269
- recs.push({
270
- priority: 'P2:HumanOversight',
271
- action: 'Investigate and block AI auto-modifications to recipes',
272
- severity: 'critical',
273
- });
274
- }
275
- if (priorities.aiTransparency.metrics.aiReasoningCompleteness < 0.7) {
276
- recs.push({
277
- priority: 'P3:AITransparency',
278
- action: 'Enforce reasoning field for AI-generated candidates',
279
- severity: 'medium',
280
- });
281
- }
282
- if (priorities.helpfulness.metrics.candidateAcceptRate < 0.6) {
283
- recs.push({
284
- priority: 'P4:Helpfulness',
285
- action: 'Improve AI candidate quality to increase acceptance rate',
286
- severity: 'medium',
287
- });
288
- }
289
-
290
- return recs;
291
- }
292
-
293
- // ========== Helpers ==========
294
-
295
- _getSinceTimestamp(period) {
296
- if (!period || period === 'all') return null;
297
- const now = Math.floor(Date.now() / 1000);
298
- switch (period) {
299
- case 'weekly': return now - 7 * 86400;
300
- case 'monthly': return now - 30 * 86400;
301
- case 'daily': return now - 86400;
302
- default: return null;
303
- }
304
- }
305
-
306
- _safeQuery(sql, params = []) {
307
- try {
308
- return this.db.prepare(sql).get(...params) || {};
309
- } catch {
310
- return {};
311
- }
312
- }
313
- }
314
-
315
- let instance = null;
316
-
317
- export function initComplianceEvaluator(db) {
318
- instance = new ComplianceEvaluator(db);
319
- return instance;
320
- }
321
-
322
- export function getComplianceEvaluator() {
323
- return instance;
324
- }
325
-
326
- export default ComplianceEvaluator;