autosnippet 2.0.2 → 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/{icons-B4FfLfBA.js → icons-B5rs8uNb.js} +85 -80
- package/dashboard/dist/assets/index-0YzLw2ga.css +1 -0
- package/dashboard/dist/assets/index-B9py3ybr.js +154 -0
- package/dashboard/dist/index.html +3 -3
- 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/commands.js +58 -3
- package/lib/http/routes/monitoring.js +4 -4
- package/lib/http/routes/recipes.js +96 -4
- package/lib/http/routes/search.js +34 -35
- package/lib/injection/ServiceContainer.js +21 -40
- package/lib/service/candidate/CandidateService.js +12 -1
- package/lib/service/chat/ChatAgent.js +171 -30
- 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/knowledge/KnowledgeGraphService.js +20 -9
- 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/dashboard/dist/assets/index-ChxJxX4B.js +0 -154
- package/dashboard/dist/assets/index-DwAp1mx5.css +0 -1
- 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
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>AutoSnippet Dashboard</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-B9py3ybr.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/yaml-qRaU8Ldn.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-Cky7Jynh.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/axios-C0Zqfgkc.js">
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/icons-
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/icons-B5rs8uNb.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/syntax-highlighter-C6bvFtpx.js">
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/react-markdown-Bp8u1wRC.js">
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-0YzLw2ga.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="root"></div>
|
package/lib/bootstrap.js
CHANGED
|
@@ -8,12 +8,9 @@ import Constitution from './core/constitution/Constitution.js';
|
|
|
8
8
|
import ConstitutionValidator from './core/constitution/ConstitutionValidator.js';
|
|
9
9
|
import PermissionManager from './core/permission/PermissionManager.js';
|
|
10
10
|
import Gateway from './core/gateway/Gateway.js';
|
|
11
|
-
import SessionManager from './core/session/SessionManager.js';
|
|
12
11
|
import AuditLogger from './infrastructure/audit/AuditLogger.js';
|
|
13
12
|
import AuditStore from './infrastructure/audit/AuditStore.js';
|
|
14
|
-
import {
|
|
15
|
-
import { initRoleDriftMonitor, createRoleDriftPlugin } from './infrastructure/monitoring/RoleDriftMonitor.js';
|
|
16
|
-
import { initComplianceEvaluator } from './infrastructure/quality/ComplianceEvaluator.js';
|
|
13
|
+
import { SkillHooks } from './service/skills/SkillHooks.js';
|
|
17
14
|
|
|
18
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
16
|
const __dirname = path.dirname(__filename);
|
|
@@ -148,11 +145,6 @@ export class Bootstrap {
|
|
|
148
145
|
this.components.permissionManager = permissionManager;
|
|
149
146
|
logger.info('PermissionManager initialized');
|
|
150
147
|
|
|
151
|
-
// Session Manager
|
|
152
|
-
const sessionManager = new SessionManager(db);
|
|
153
|
-
this.components.sessionManager = sessionManager;
|
|
154
|
-
logger.info('SessionManager initialized');
|
|
155
|
-
|
|
156
148
|
// Audit System
|
|
157
149
|
const auditStore = new AuditStore(db);
|
|
158
150
|
const auditLogger = new AuditLogger(auditStore);
|
|
@@ -160,20 +152,10 @@ export class Bootstrap {
|
|
|
160
152
|
this.components.auditLogger = auditLogger;
|
|
161
153
|
logger.info('Audit system initialized');
|
|
162
154
|
|
|
163
|
-
//
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// Role Drift Monitor (角色漂移检测)
|
|
169
|
-
const roleDriftMonitor = initRoleDriftMonitor(db);
|
|
170
|
-
this.components.roleDriftMonitor = roleDriftMonitor;
|
|
171
|
-
logger.info('RoleDriftMonitor initialized');
|
|
172
|
-
|
|
173
|
-
// Compliance Evaluator (合规评估)
|
|
174
|
-
const complianceEvaluator = initComplianceEvaluator(db);
|
|
175
|
-
this.components.complianceEvaluator = complianceEvaluator;
|
|
176
|
-
logger.info('ComplianceEvaluator initialized');
|
|
155
|
+
// Skill Hooks (扫描 skills/*/hooks.js + .autosnippet/skills/*/hooks.js)
|
|
156
|
+
const skillHooks = new SkillHooks();
|
|
157
|
+
await skillHooks.load();
|
|
158
|
+
this.components.skillHooks = skillHooks;
|
|
177
159
|
}
|
|
178
160
|
|
|
179
161
|
/**
|
|
@@ -191,17 +173,9 @@ export class Bootstrap {
|
|
|
191
173
|
constitutionValidator: this.components.constitutionValidator,
|
|
192
174
|
permissionManager: this.components.permissionManager,
|
|
193
175
|
auditLogger: this.components.auditLogger,
|
|
194
|
-
sessionManager: this.components.sessionManager,
|
|
195
176
|
});
|
|
196
177
|
|
|
197
178
|
this.components.gateway = gateway;
|
|
198
|
-
|
|
199
|
-
// 注册角色漂移检测插件
|
|
200
|
-
if (this.components.roleDriftMonitor) {
|
|
201
|
-
gateway.use(createRoleDriftPlugin(this.components.roleDriftMonitor));
|
|
202
|
-
this.components.logger.info('RoleDrift plugin registered in Gateway');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
179
|
this.components.logger.info('Gateway initialized');
|
|
206
180
|
}
|
|
207
181
|
|
package/lib/cli/SetupService.js
CHANGED
|
@@ -265,25 +265,27 @@ export class SetupService {
|
|
|
265
265
|
' no_remote: "allow"',
|
|
266
266
|
' cache_ttl: 86400',
|
|
267
267
|
'',
|
|
268
|
-
'
|
|
269
|
-
' - id:
|
|
270
|
-
'
|
|
271
|
-
'
|
|
272
|
-
'
|
|
273
|
-
'
|
|
274
|
-
'
|
|
268
|
+
'rules:',
|
|
269
|
+
' - id: destructive_confirm',
|
|
270
|
+
' check: "删除操作必须有 confirmed: true"',
|
|
271
|
+
' - id: content_required',
|
|
272
|
+
' check: "创建 candidate/recipe 必须提供 code 或 content"',
|
|
273
|
+
' - id: ai_no_direct_recipe',
|
|
274
|
+
' check: "AI actor 不能直接创建或批准 Recipe"',
|
|
275
|
+
' - id: batch_authorized',
|
|
276
|
+
' check: "批量操作必须有 authorized: true"',
|
|
275
277
|
'',
|
|
276
278
|
'roles:',
|
|
277
|
-
' - id: "
|
|
279
|
+
' - id: "developer"',
|
|
280
|
+
' name: "Developer"',
|
|
278
281
|
' permissions: ["*"]',
|
|
279
282
|
' requires_capability: ["git_write"]',
|
|
280
|
-
' - id: "
|
|
281
|
-
'
|
|
282
|
-
' requires_capability: ["git_write"]',
|
|
283
|
-
' - id: "cursor_agent"',
|
|
283
|
+
' - id: "external_agent"',
|
|
284
|
+
' name: "External Agent"',
|
|
284
285
|
' permissions: ["read:recipes", "read:guard_rules", "create:candidates", "submit:candidates"]',
|
|
285
|
-
' - id: "
|
|
286
|
-
'
|
|
286
|
+
' - id: "chat_agent"',
|
|
287
|
+
' name: "ChatAgent"',
|
|
288
|
+
' permissions: ["read:recipes", "read:candidates", "create:candidates", "read:guard_rules"]',
|
|
287
289
|
'',
|
|
288
290
|
].join('\n'));
|
|
289
291
|
}
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* 探测结果被缓存(默认 24h)以避免重复执行。
|
|
6
6
|
*
|
|
7
7
|
* 三种探测结果:
|
|
8
|
-
* 'admin' — 无子仓库(个人项目)/ 有 push 权限 →
|
|
9
|
-
* 'contributor' — 有子仓库但无 push 权限 →
|
|
10
|
-
* 'visitor' —
|
|
8
|
+
* 'admin' — 无子仓库(个人项目)/ 有 push 权限 → developer
|
|
9
|
+
* 'contributor' — 有子仓库但无 push 权限 → developer(本地用户 = 项目 Owner)
|
|
10
|
+
* 'visitor' — noRemote=deny 严格模式 → developer(仅探针级别区分,角色统一为 developer)
|
|
11
11
|
*
|
|
12
12
|
* 当没有 remote 时根据 constitution capabilities.git_write.no_remote 策略决定:
|
|
13
13
|
* 'allow' (默认) — 本地开发,视为 admin
|
|
@@ -78,11 +78,13 @@ export class CapabilityProbe {
|
|
|
78
78
|
* @returns {string}
|
|
79
79
|
*/
|
|
80
80
|
toRole(probeResult) {
|
|
81
|
+
// 本地运行 AutoSnippet 的用户 = 项目 Owner = developer
|
|
82
|
+
// 探针级别的 admin/contributor/visitor 仅做信息记录,角色统一为 developer
|
|
81
83
|
switch (probeResult) {
|
|
82
|
-
case 'admin':
|
|
83
|
-
case 'contributor':
|
|
84
|
+
case 'admin':
|
|
85
|
+
case 'contributor':
|
|
84
86
|
case 'visitor':
|
|
85
|
-
default: return '
|
|
87
|
+
default: return 'developer';
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -10,6 +10,7 @@ export class Constitution {
|
|
|
10
10
|
this.configPath = configPath;
|
|
11
11
|
this.config = this.loadConfig();
|
|
12
12
|
this.priorities = this.config.priorities || [];
|
|
13
|
+
this.rules = this.config.rules || [];
|
|
13
14
|
this.roles = new Map(this.config.roles?.map((r) => [r.id, r]) || []);
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -32,6 +33,13 @@ export class Constitution {
|
|
|
32
33
|
return this.priorities;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* 获取所有数据守护规则
|
|
38
|
+
*/
|
|
39
|
+
getRules() {
|
|
40
|
+
return this.rules;
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
/**
|
|
36
44
|
* 获取能力定义
|
|
37
45
|
*/
|
|
@@ -97,6 +105,7 @@ export class Constitution {
|
|
|
97
105
|
reload() {
|
|
98
106
|
this.config = this.loadConfig();
|
|
99
107
|
this.priorities = this.config.priorities || [];
|
|
108
|
+
this.rules = this.config.rules || [];
|
|
100
109
|
this.roles = new Map(this.config.roles?.map((r) => [r.id, r]) || []);
|
|
101
110
|
}
|
|
102
111
|
|
|
@@ -107,10 +116,10 @@ export class Constitution {
|
|
|
107
116
|
return {
|
|
108
117
|
version: this.config.version,
|
|
109
118
|
effectiveDate: this.config.effective_date,
|
|
110
|
-
priorities: this.priorities
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
description:
|
|
119
|
+
priorities: this.priorities,
|
|
120
|
+
rules: this.rules.map((r) => ({
|
|
121
|
+
id: r.id,
|
|
122
|
+
description: r.description,
|
|
114
123
|
})),
|
|
115
124
|
roles: Array.from(this.roles.values()).map((r) => ({
|
|
116
125
|
id: r.id,
|
|
@@ -2,258 +2,153 @@ import { ConstitutionViolation } from '../../shared/errors/BaseError.js';
|
|
|
2
2
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* ConstitutionValidator
|
|
5
|
+
* ConstitutionValidator — 数据守护验证器
|
|
6
|
+
*
|
|
7
|
+
* 精简设计: 4 条纯数据完整性规则,不做伦理/价值观判断。
|
|
8
|
+
* 每条规则对应 constitution.yaml 中的一个 rule.check 值。
|
|
6
9
|
*/
|
|
7
10
|
export class ConstitutionValidator {
|
|
8
11
|
constructor(constitution) {
|
|
9
12
|
this.constitution = constitution;
|
|
10
13
|
this.logger = Logger.getInstance();
|
|
14
|
+
|
|
15
|
+
/** rule.check → 检查函数 */
|
|
16
|
+
this.checkers = {
|
|
17
|
+
destructive_needs_confirmation: this._checkDestructive.bind(this),
|
|
18
|
+
creation_needs_content: this._checkContent.bind(this),
|
|
19
|
+
ai_cannot_approve_recipe: this._checkAiRecipe.bind(this),
|
|
20
|
+
batch_needs_authorization: this._checkBatch.bind(this),
|
|
21
|
+
};
|
|
11
22
|
}
|
|
12
23
|
|
|
13
24
|
/**
|
|
14
|
-
*
|
|
25
|
+
* 验证操作,返回违规列表
|
|
15
26
|
*/
|
|
16
27
|
async validate(request) {
|
|
17
28
|
const violations = [];
|
|
18
|
-
const
|
|
29
|
+
const rules = this.constitution.getRules?.() || this.constitution.rules || [];
|
|
19
30
|
|
|
20
|
-
for (const
|
|
21
|
-
const
|
|
22
|
-
|
|
31
|
+
for (const rule of rules) {
|
|
32
|
+
const checker = this.checkers[rule.check];
|
|
33
|
+
if (checker) {
|
|
34
|
+
const v = checker(request, rule);
|
|
35
|
+
if (v) violations.push(v);
|
|
36
|
+
}
|
|
23
37
|
}
|
|
24
38
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
39
|
+
// 兼容旧 priorities 格式(如果新 rules 为空但有旧 priorities)
|
|
40
|
+
if (rules.length === 0) {
|
|
41
|
+
const priorities = this.constitution.getPriorities?.() || [];
|
|
42
|
+
for (const p of priorities) {
|
|
43
|
+
const pvs = await this._checkLegacyPriority(p, request);
|
|
44
|
+
violations.push(...pvs);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
33
47
|
|
|
34
|
-
if (
|
|
35
|
-
this.logger.warn('Constitution violations
|
|
48
|
+
if (violations.length > 0) {
|
|
49
|
+
this.logger.warn('Constitution violations', {
|
|
36
50
|
actor: request.actor,
|
|
37
51
|
action: request.action,
|
|
38
|
-
|
|
52
|
+
count: violations.length,
|
|
39
53
|
});
|
|
40
54
|
}
|
|
41
55
|
|
|
42
|
-
return
|
|
56
|
+
return { compliant: violations.length === 0, violations };
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
/**
|
|
46
|
-
*
|
|
60
|
+
* 强制验证(违规时抛异常)
|
|
47
61
|
*/
|
|
48
|
-
async
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Priority 1: Data Integrity
|
|
54
|
-
if (priority.id === 1) {
|
|
55
|
-
// 检查破坏性操作
|
|
56
|
-
if (this.isDestructiveOperation(request)) {
|
|
57
|
-
if (!request.data?.confirmed && !request.confirmed) {
|
|
58
|
-
violations.push({
|
|
59
|
-
priority: 1,
|
|
60
|
-
rule: '删除操作必须有确认步骤',
|
|
61
|
-
reason: '操作未经确认',
|
|
62
|
-
suggestion: '添加 confirmed: true 或 --confirm 标志',
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 检查数据完整性
|
|
68
|
-
// 支持 Gateway 格式 (candidate:create) + REST 格式 (create + /candidates) + Legacy 格式 (create_candidate)
|
|
69
|
-
const isCandidateOrRecipeCreation =
|
|
70
|
-
(verb === 'create' && (resName.includes('candidate') || resName.includes('recipe'))) ||
|
|
71
|
-
(request.action === 'create' && (request.resource?.includes('/candidates') || request.resource?.includes('/recipes'))) ||
|
|
72
|
-
request.action === 'create_candidate' ||
|
|
73
|
-
request.action === 'create_recipe';
|
|
74
|
-
|
|
75
|
-
if (isCandidateOrRecipeCreation) {
|
|
76
|
-
// 单条: data.code / data.content
|
|
77
|
-
// 批量: data.items (每条内含 code)
|
|
78
|
-
// 草稿: data.filePaths (内容在文件中)
|
|
79
|
-
const hasContent = request.data?.code || request.code || request.data?.content
|
|
80
|
-
|| (Array.isArray(request.data?.items) && request.data.items.length > 0)
|
|
81
|
-
|| request.data?.filePaths;
|
|
82
|
-
if (!hasContent) {
|
|
83
|
-
violations.push({
|
|
84
|
-
priority: 1,
|
|
85
|
-
rule: '所有内容必须可验证',
|
|
86
|
-
reason: '缺少 code/content 字段',
|
|
87
|
-
suggestion: '提供完整的代码或内容',
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Priority 2: Human Oversight
|
|
94
|
-
if (priority.id === 2) {
|
|
95
|
-
// AI 不能直接批准或创建 Recipe
|
|
96
|
-
// 支持 Gateway 格式 (recipe:create, candidate:approve, recipe:publish) + Legacy 格式
|
|
97
|
-
const isRecipeModification =
|
|
98
|
-
(verb === 'create' && resName.includes('recipe')) ||
|
|
99
|
-
(verb === 'approve') ||
|
|
100
|
-
(verb === 'publish' && resName.includes('recipe')) ||
|
|
101
|
-
(request.action === 'create' && request.resource?.includes('/recipes')) ||
|
|
102
|
-
request.action === 'approve_candidate' ||
|
|
103
|
-
request.action === 'approve_recipe' ||
|
|
104
|
-
request.action === 'create_recipe';
|
|
105
|
-
|
|
106
|
-
if (isRecipeModification) {
|
|
107
|
-
if (this.isAIActor(request.actor)) {
|
|
108
|
-
violations.push({
|
|
109
|
-
priority: 2,
|
|
110
|
-
rule: 'AI 生成的 Candidate 必须经人工审核',
|
|
111
|
-
reason: 'AI 不能直接批准或创建 Recipe',
|
|
112
|
-
suggestion: '通过 Dashboard 人工审批或使用 developer_admin 角色',
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 批量操作需要授权
|
|
118
|
-
if (request.action === 'batch_update' || request.action === 'batch_delete') {
|
|
119
|
-
if (!request.data?.authorized && !request.authorized) {
|
|
120
|
-
violations.push({
|
|
121
|
-
priority: 2,
|
|
122
|
-
rule: '批量操作需要明确授权',
|
|
123
|
-
reason: '缺少授权标志',
|
|
124
|
-
suggestion: '添加 authorized: true 标志',
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Priority 3: AI Transparency
|
|
131
|
-
if (priority.id === 3) {
|
|
132
|
-
// AI 生成必须有 reasoning
|
|
133
|
-
// 支持 Gateway 格式 (candidate:create) + REST 格式 + Legacy 格式
|
|
134
|
-
const isCandidateCreation =
|
|
135
|
-
(verb === 'create' && resName.includes('candidate')) ||
|
|
136
|
-
(request.action === 'create' && request.resource?.includes('/candidates')) ||
|
|
137
|
-
request.action === 'create_candidate';
|
|
138
|
-
|
|
139
|
-
if (isCandidateCreation) {
|
|
140
|
-
if (this.isAIActor(request.actor)) {
|
|
141
|
-
// MCP handler (_createCandidateItem) 会自动生成默认 reasoning,
|
|
142
|
-
// 此处仅在 reasoning 明确为空对象时警告,不阻断提交
|
|
143
|
-
const r = request.data?.reasoning;
|
|
144
|
-
if (r && typeof r === 'object') {
|
|
145
|
-
if (!r.whyStandard && !r.sources) {
|
|
146
|
-
violations.push({
|
|
147
|
-
priority: 3,
|
|
148
|
-
rule: 'Reasoning 信息必须完整',
|
|
149
|
-
reason: '提供了 reasoning 但缺少 whyStandard 和 sources',
|
|
150
|
-
suggestion: '提供完整的推理过程说明',
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
// 未传 reasoning 时不视为违规 — handler 层会自动生成默认值
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Guard 规则(现在是 Recipe)必须有来源
|
|
159
|
-
// 支持 Gateway 格式 (guard_rule:create) + REST 格式 + Legacy 格式
|
|
160
|
-
const isGuardRuleModification =
|
|
161
|
-
((verb === 'create' || verb === 'update' || verb === 'enable') && resName.includes('guard')) ||
|
|
162
|
-
(request.action === 'create' && request.resource?.includes('/guard')) ||
|
|
163
|
-
(request.action === 'update' && request.resource?.includes('/guard')) ||
|
|
164
|
-
request.action === 'create_guard_rule' ||
|
|
165
|
-
request.action === 'update_guard_rule';
|
|
166
|
-
|
|
167
|
-
if (isGuardRuleModification) {
|
|
168
|
-
if (!request.data?.source_recipe_id && !request.data?.sourceCandidate) {
|
|
169
|
-
violations.push({
|
|
170
|
-
priority: 3,
|
|
171
|
-
rule: 'Guard 规则必须关联来源',
|
|
172
|
-
reason: '缺少 source_recipe_id 或 sourceCandidate',
|
|
173
|
-
suggestion: '指定规则来源的 Recipe ID 或 Candidate ID',
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
62
|
+
async enforce(request) {
|
|
63
|
+
const result = await this.validate(request);
|
|
64
|
+
if (!result.compliant) {
|
|
65
|
+
throw new ConstitutionViolation(result.violations);
|
|
177
66
|
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
178
69
|
|
|
179
|
-
|
|
180
|
-
// (此优先级主要用于质量评估,而非硬性限制)
|
|
70
|
+
// ─── 规则检查器 ────────────────────────────────────────
|
|
181
71
|
|
|
182
|
-
|
|
72
|
+
/** 删除操作需要确认 */
|
|
73
|
+
_checkDestructive(req, rule) {
|
|
74
|
+
const destructive = ['delete', 'remove', 'destroy', 'purge', 'batch_delete'];
|
|
75
|
+
if (!destructive.some(w => req.action?.toLowerCase().includes(w))) return null;
|
|
76
|
+
if (req.data?.confirmed || req.confirmed) return null;
|
|
77
|
+
return { rule: rule.id, reason: '操作未经确认', suggestion: '添加 confirmed: true' };
|
|
183
78
|
}
|
|
184
79
|
|
|
185
|
-
/**
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
'
|
|
191
|
-
'
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
80
|
+
/** 创建候选/Recipe 需要内容 */
|
|
81
|
+
_checkContent(req, rule) {
|
|
82
|
+
const verb = this._verb(req.action);
|
|
83
|
+
const res = this._resource(req.action, req.resource);
|
|
84
|
+
const isCreation =
|
|
85
|
+
(verb === 'create' && (res.includes('candidate') || res.includes('recipe'))) ||
|
|
86
|
+
req.action === 'create_candidate' || req.action === 'create_recipe';
|
|
87
|
+
if (!isCreation) return null;
|
|
88
|
+
const ok = req.data?.code || req.code || req.data?.content
|
|
89
|
+
|| (Array.isArray(req.data?.items) && req.data.items.length > 0)
|
|
90
|
+
|| req.data?.filePaths;
|
|
91
|
+
if (ok) return null;
|
|
92
|
+
return { rule: rule.id, reason: '缺少 code/content', suggestion: '提供代码或内容' };
|
|
93
|
+
}
|
|
198
94
|
|
|
199
|
-
|
|
95
|
+
/** AI 不能直接创建/批准 Recipe */
|
|
96
|
+
_checkAiRecipe(req, rule) {
|
|
97
|
+
if (!this._isAI(req.actor)) return null;
|
|
98
|
+
const verb = this._verb(req.action);
|
|
99
|
+
const res = this._resource(req.action, req.resource);
|
|
100
|
+
const isRecipeMod =
|
|
101
|
+
(verb === 'create' && res.includes('recipe')) || verb === 'approve' || verb === 'publish'
|
|
102
|
+
|| req.action === 'approve_candidate' || req.action === 'create_recipe';
|
|
103
|
+
if (!isRecipeMod) return null;
|
|
104
|
+
return { rule: rule.id, reason: 'AI 不能直接操作 Recipe', suggestion: '通过 Dashboard 人工审批' };
|
|
200
105
|
}
|
|
201
106
|
|
|
202
|
-
/**
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return aiActors.some((ai) => actor.toLowerCase().includes(ai));
|
|
107
|
+
/** 批量操作需要授权 */
|
|
108
|
+
_checkBatch(req, rule) {
|
|
109
|
+
if (!req.action?.includes('batch_')) return null;
|
|
110
|
+
if (req.data?.authorized || req.authorized) return null;
|
|
111
|
+
return { rule: rule.id, reason: '缺少授权标志', suggestion: '添加 authorized: true' };
|
|
208
112
|
}
|
|
209
113
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
* 'create' → 'create'
|
|
214
|
-
* 'candidate:create' → 'create' (Gateway)
|
|
215
|
-
* 'create_candidate' → 'create' (Legacy)
|
|
216
|
-
*/
|
|
217
|
-
_extractVerb(action) {
|
|
114
|
+
// ─── 辅助方法 ──────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
_verb(action) {
|
|
218
117
|
if (!action) return '';
|
|
219
|
-
|
|
220
|
-
if (action.includes(':')) return action.split(':').pop();
|
|
221
|
-
// 其他格式直接返回
|
|
222
|
-
return action;
|
|
118
|
+
return action.includes(':') ? action.split(':').pop() : action;
|
|
223
119
|
}
|
|
224
120
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
* 'candidates' → 'candidates' (plain)
|
|
231
|
-
*/
|
|
232
|
-
_extractResourceName(action, resource) {
|
|
233
|
-
// 优先从 Gateway action 格式提取
|
|
234
|
-
if (action?.includes(':')) {
|
|
235
|
-
return action.split(':')[0];
|
|
236
|
-
}
|
|
237
|
-
// 从 resource 提取
|
|
238
|
-
if (typeof resource === 'string') {
|
|
239
|
-
if (resource.startsWith('/')) {
|
|
240
|
-
const match = resource.match(/^\/([^/]+)/);
|
|
241
|
-
return match ? match[1] : resource;
|
|
242
|
-
}
|
|
243
|
-
return resource;
|
|
121
|
+
_resource(action, resource) {
|
|
122
|
+
if (action?.includes(':')) return action.split(':')[0];
|
|
123
|
+
if (typeof resource === 'string' && resource.startsWith('/')) {
|
|
124
|
+
const m = resource.match(/^\/([^/]+)/);
|
|
125
|
+
return m ? m[1] : resource;
|
|
244
126
|
}
|
|
245
|
-
return '';
|
|
127
|
+
return resource || '';
|
|
246
128
|
}
|
|
247
129
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
130
|
+
_isAI(actor) {
|
|
131
|
+
return ['external_agent', 'chat_agent'].some(a => actor?.toLowerCase().includes(a));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── 旧格式兼容 ───────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
/** 兼容旧 priorities 格式 */
|
|
137
|
+
async _checkLegacyPriority(priority, request) {
|
|
138
|
+
const violations = [];
|
|
139
|
+
if (priority.id === 1) {
|
|
140
|
+
const v = this._checkDestructive(request, { id: 'destructive_confirm' });
|
|
141
|
+
if (v) violations.push(v);
|
|
142
|
+
const v2 = this._checkContent(request, { id: 'content_required' });
|
|
143
|
+
if (v2) violations.push(v2);
|
|
255
144
|
}
|
|
256
|
-
|
|
145
|
+
if (priority.id === 2) {
|
|
146
|
+
const v = this._checkAiRecipe(request, { id: 'ai_no_direct_recipe' });
|
|
147
|
+
if (v) violations.push(v);
|
|
148
|
+
const v2 = this._checkBatch(request, { id: 'batch_authorized' });
|
|
149
|
+
if (v2) violations.push(v2);
|
|
150
|
+
}
|
|
151
|
+
return violations;
|
|
257
152
|
}
|
|
258
153
|
}
|
|
259
154
|
|