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.
- package/README.md +189 -113
- package/bin/api-server.js +1 -4
- package/bin/cli.js +1 -50
- package/config/constitution.yaml +33 -107
- package/dashboard/dist/assets/{index-DbkbX1c-.js → index-B9py3ybr.js} +32 -32
- package/dashboard/dist/index.html +1 -1
- package/lib/bootstrap.js +5 -31
- package/lib/cli/SetupService.js +16 -14
- package/lib/core/capability/CapabilityProbe.js +8 -6
- package/lib/core/constitution/Constitution.js +13 -4
- package/lib/core/constitution/ConstitutionValidator.js +106 -211
- package/lib/core/gateway/Gateway.js +34 -98
- package/lib/core/gateway/GatewayActionRegistry.js +12 -1
- package/lib/core/permission/PermissionManager.js +2 -2
- package/lib/external/mcp/McpServer.js +4 -7
- package/lib/external/mcp/handlers/bootstrap.js +13 -1
- package/lib/external/mcp/handlers/browse.js +0 -7
- package/lib/external/mcp/handlers/candidate.js +1 -1
- package/lib/external/mcp/handlers/guard.js +11 -0
- package/lib/external/mcp/handlers/skill.js +186 -18
- package/lib/external/mcp/tools.js +40 -1
- package/lib/http/middleware/roleResolver.js +1 -1
- package/lib/http/routes/auth.js +2 -2
- package/lib/http/routes/monitoring.js +4 -4
- package/lib/http/routes/search.js +0 -17
- package/lib/injection/ServiceContainer.js +21 -40
- package/lib/service/candidate/CandidateService.js +12 -1
- package/lib/service/chat/ChatAgent.js +139 -18
- package/lib/service/chat/Memory.js +104 -0
- package/lib/service/chat/tools.js +244 -10
- package/lib/service/guard/GuardCheckEngine.js +9 -1
- package/lib/service/recipe/RecipeService.js +8 -0
- package/lib/service/skills/SkillHooks.js +126 -0
- package/package.json +1 -1
- package/scripts/init-db.js +1 -2
- package/templates/constitution.yaml +29 -85
- package/lib/core/session/SessionManager.js +0 -232
- package/lib/infrastructure/logging/ReasoningLogger.js +0 -269
- package/lib/infrastructure/monitoring/RoleDriftMonitor.js +0 -259
- 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;
|