agent-trellis 0.19.0 → 0.21.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/dist/adapters/claude-code.js +2 -2
- package/dist/adapters/codex.js +3 -3
- package/dist/adapters/jsonMcp.d.ts +1 -1
- package/dist/adapters/jsonMcp.js +2 -2
- package/dist/adapters/kimi-code.js +2 -2
- package/dist/adapters/kiro.js +11 -9
- package/dist/adapters/mcpPlan.d.ts +1 -1
- package/dist/adapters/mcpPlan.js +35 -12
- package/dist/adapters/pi.js +5 -5
- package/dist/adapters/zcode.d.ts +1 -1
- package/dist/adapters/zcode.js +6 -6
- package/dist/cli.js +12 -1
- package/dist/commands/doctor.d.ts +9 -1
- package/dist/commands/doctor.js +92 -7
- package/dist/commands/evolve.d.ts +43 -0
- package/dist/commands/evolve.js +479 -0
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/mcpAuth.js +1 -1
- package/dist/commands/mcpGateway.d.ts +30 -2
- package/dist/commands/mcpGateway.js +44 -10
- package/dist/commands/mcpImport.js +2 -1
- package/dist/commands/secretsAudit.d.ts +1 -1
- package/dist/commands/secretsAudit.js +36 -13
- package/dist/evolve/agentView.d.ts +32 -0
- package/dist/evolve/agentView.js +34 -0
- package/dist/evolve/apply.d.ts +71 -0
- package/dist/evolve/apply.js +162 -0
- package/dist/evolve/archive.d.ts +23 -0
- package/dist/evolve/archive.js +58 -0
- package/dist/evolve/erase.d.ts +17 -0
- package/dist/evolve/erase.js +65 -0
- package/dist/evolve/extension.d.ts +37 -0
- package/dist/evolve/extension.js +63 -0
- package/dist/evolve/gate.d.ts +95 -0
- package/dist/evolve/gate.js +295 -0
- package/dist/evolve/importHistory.d.ts +91 -0
- package/dist/evolve/importHistory.js +239 -0
- package/dist/evolve/jobMain.d.ts +22 -0
- package/dist/evolve/jobMain.js +52 -0
- package/dist/evolve/jobRun.d.ts +94 -0
- package/dist/evolve/jobRun.js +273 -0
- package/dist/evolve/jobShell.d.ts +46 -0
- package/dist/evolve/jobShell.js +75 -0
- package/dist/evolve/l2.d.ts +93 -0
- package/dist/evolve/l2.js +81 -0
- package/dist/evolve/lesson.d.ts +57 -0
- package/dist/evolve/lesson.js +97 -0
- package/dist/evolve/pending.d.ts +69 -0
- package/dist/evolve/pending.js +116 -0
- package/dist/evolve/probeWorkspace.d.ts +57 -0
- package/dist/evolve/probeWorkspace.js +69 -0
- package/dist/evolve/propose.d.ts +73 -0
- package/dist/evolve/propose.js +136 -0
- package/dist/evolve/recorder.d.ts +68 -0
- package/dist/evolve/recorder.js +169 -0
- package/dist/evolve/report.d.ts +26 -0
- package/dist/evolve/report.js +37 -0
- package/dist/evolve/reportView.d.ts +96 -0
- package/dist/evolve/reportView.js +311 -0
- package/dist/evolve/risk.d.ts +106 -0
- package/dist/evolve/risk.js +224 -0
- package/dist/evolve/sources.d.ts +19 -0
- package/dist/evolve/sources.js +29 -0
- package/dist/evolve/stateDir.d.ts +21 -0
- package/dist/evolve/stateDir.js +56 -0
- package/dist/evolve/surface.d.ts +34 -0
- package/dist/evolve/surface.js +72 -0
- package/dist/evolve/trajectory.d.ts +25 -0
- package/dist/evolve/trajectory.js +88 -0
- package/dist/evolve/treeToCanonical.d.ts +28 -0
- package/dist/evolve/treeToCanonical.js +79 -0
- package/dist/evolve/trellisSubstrate.d.ts +65 -0
- package/dist/evolve/trellisSubstrate.js +154 -0
- package/dist/evolve/trigger.d.ts +177 -0
- package/dist/evolve/trigger.js +400 -0
- package/dist/evolve/types.d.ts +126 -0
- package/dist/evolve/types.js +15 -0
- package/dist/evolve/validate.d.ts +139 -0
- package/dist/evolve/validate.js +315 -0
- package/dist/host/checks.d.ts +57 -0
- package/dist/host/checks.js +126 -0
- package/dist/host/delivery.d.ts +42 -0
- package/dist/host/delivery.js +59 -0
- package/dist/host/edits.d.ts +87 -0
- package/dist/host/edits.js +102 -0
- package/dist/host/hostEvents.d.ts +74 -0
- package/dist/host/hostEvents.js +45 -0
- package/dist/host/index.d.ts +22 -0
- package/dist/host/index.js +17 -0
- package/dist/host/levels.d.ts +106 -0
- package/dist/host/levels.js +206 -0
- package/dist/host/staging.d.ts +56 -0
- package/dist/host/staging.js +106 -0
- package/dist/host/surface.d.ts +36 -0
- package/dist/host/surface.js +20 -0
- package/dist/lib/backup.js +12 -3
- package/dist/lib/envVarNames.js +7 -0
- package/dist/lib/fsAtomic.d.ts +7 -0
- package/dist/lib/fsAtomic.js +14 -0
- package/dist/lib/hostEventBus.d.ts +47 -0
- package/dist/lib/hostEventBus.js +94 -0
- package/dist/lib/hostExtensions.d.ts +75 -0
- package/dist/lib/hostExtensions.js +196 -0
- package/dist/lib/mcpConnect.d.ts +1 -1
- package/dist/lib/mcpConnect.js +19 -8
- package/dist/lib/mcpMigrateRead.js +14 -2
- package/dist/lib/mcpRuntime.d.ts +12 -1
- package/dist/lib/mcpRuntime.js +34 -2
- package/dist/lib/mcpToolRegistry.d.ts +18 -2
- package/dist/lib/mcpToolRegistry.js +29 -9
- package/dist/lib/secretEnv.d.ts +53 -8
- package/dist/lib/secretEnv.js +78 -15
- package/dist/lib/tomlSection.js +3 -2
- package/dist/lib/workspaceDoctor.d.ts +1 -1
- package/dist/lib/workspaceDoctor.js +8 -4
- package/dist/lib/workspaceMcp.d.ts +1 -1
- package/dist/lib/workspaceMcp.js +5 -5
- package/dist/pi-bridge/bundle.js +42236 -2701
- package/dist/pi-bridge/index.js +2 -2
- package/docs/magpie-gateway-research.md +422 -0
- package/docs/research.md +4 -1
- package/docs/roadmap.md +22 -12
- package/docs/runtime-infra.md +41 -22
- package/docs/self-evolution.md +138 -44
- package/package.json +8 -2
- package/schema/secrets.policy.example.yaml +5 -0
- package/schema/servers.example.yaml +5 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 进化可视化聚合层(trellis-evolve-visualization 任务 1.1)。
|
|
3
|
+
*
|
|
4
|
+
* 单一计算源:从三个只读来源(~/.trellis/evolve/archive/records.jsonl、
|
|
5
|
+
* evolve/pending/*.json、evolve.yaml)聚合出 `EvolveReport`,CLI 的
|
|
6
|
+
* `evolve report` / `status --json` 与 GUI sidecar 路由只是它的渲染/传输
|
|
7
|
+
* 壳——绝不维护第二份独立计算(gui-sidecar-api 的"读视图镜像 CLI"规约)。
|
|
8
|
+
*
|
|
9
|
+
* 只读零副作用:本模块永不 mkdir/append/write;全部指标可从档案重算,
|
|
10
|
+
* 每条趋势指标带口径说明(spec"趋势指标只来自真实测量")。数值评分
|
|
11
|
+
* (0-100 健康分之类)一律不合成——分数轴等 L2 探针任务集落地后另行变更。
|
|
12
|
+
*/
|
|
13
|
+
import { loadEvolveLevelPolicy } from "../host/index.js";
|
|
14
|
+
import { readArchiveRecords } from "./archive.js";
|
|
15
|
+
import { readPendingRecords, safeProposalFileName } from "./pending.js";
|
|
16
|
+
import { JOB_RUN_KIND, PROPOSAL_ARCHIVE_KIND } from "./trigger.js";
|
|
17
|
+
import { VERDICT_ARCHIVE_KIND } from "./gate.js";
|
|
18
|
+
import { PROPOSAL_DECISION_ARCHIVE_KIND } from "./pending.js";
|
|
19
|
+
import { PROPOSAL_REJECT_ARCHIVE_KIND } from "./validate.js";
|
|
20
|
+
import { LESSON_ARCHIVE_KIND } from "./lesson.js";
|
|
21
|
+
function isoTime(entry) {
|
|
22
|
+
return entry.recordedAt;
|
|
23
|
+
}
|
|
24
|
+
function payloadOf(entry) {
|
|
25
|
+
return (entry.payload ?? {});
|
|
26
|
+
}
|
|
27
|
+
function levelMap(policy) {
|
|
28
|
+
return {
|
|
29
|
+
defaultLevel: policy.defaultLevel ?? null,
|
|
30
|
+
agents: { ...policy.agents },
|
|
31
|
+
workspaces: { ...policy.workspaces },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 聚合进化报告(纯读)。timeline 按提案 id 关联 proposal / proposal-reject /
|
|
36
|
+
* verdict / proposal-decision / apply-run / lesson 档案;链上缺环如实标注,
|
|
37
|
+
* 不推断补齐。
|
|
38
|
+
*/
|
|
39
|
+
export function buildEvolveReport(homeDir) {
|
|
40
|
+
const entries = readArchiveRecords(homeDir);
|
|
41
|
+
const pending = readPendingRecords(homeDir);
|
|
42
|
+
const policy = loadEvolveLevelPolicy(homeDir);
|
|
43
|
+
const drafts = new Map();
|
|
44
|
+
const ensureDraft = (id) => {
|
|
45
|
+
let draft = drafts.get(id);
|
|
46
|
+
if (draft === undefined) {
|
|
47
|
+
draft = {
|
|
48
|
+
id,
|
|
49
|
+
summary: id,
|
|
50
|
+
kind: "",
|
|
51
|
+
riskClass: null,
|
|
52
|
+
layer: null,
|
|
53
|
+
edits: [],
|
|
54
|
+
chain: [],
|
|
55
|
+
measurement: null,
|
|
56
|
+
backupRunId: null,
|
|
57
|
+
lessonId: null,
|
|
58
|
+
endedInReject: false,
|
|
59
|
+
decided: null,
|
|
60
|
+
applied: false,
|
|
61
|
+
};
|
|
62
|
+
drafts.set(id, draft);
|
|
63
|
+
}
|
|
64
|
+
return draft;
|
|
65
|
+
};
|
|
66
|
+
// pending 存储给出提案正文(摘要/编辑/层);档案只追加,pending 是可变态。
|
|
67
|
+
const pendingById = new Map();
|
|
68
|
+
for (const record of pending)
|
|
69
|
+
pendingById.set(record.id, record);
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
const payload = payloadOf(entry);
|
|
72
|
+
switch (entry.kind) {
|
|
73
|
+
case PROPOSAL_ARCHIVE_KIND: {
|
|
74
|
+
const id = String(payload.proposalId ?? "");
|
|
75
|
+
if (id === "")
|
|
76
|
+
break;
|
|
77
|
+
const draft = ensureDraft(id);
|
|
78
|
+
draft.riskClass = payload.riskClass ?? draft.riskClass;
|
|
79
|
+
draft.chain.push({ stage: "proposed", at: isoTime(entry), note: "提案入队" });
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case PROPOSAL_REJECT_ARCHIVE_KIND: {
|
|
83
|
+
const id = String(payload.proposalId ?? "");
|
|
84
|
+
if (id === "")
|
|
85
|
+
break;
|
|
86
|
+
const draft = ensureDraft(id);
|
|
87
|
+
draft.endedInReject = true;
|
|
88
|
+
draft.chain.push({
|
|
89
|
+
stage: "discarded",
|
|
90
|
+
at: isoTime(entry),
|
|
91
|
+
note: `${String(payload.phase ?? "")} 丢弃:${String(payload.code ?? "")} — ${String(payload.reason ?? "")}`,
|
|
92
|
+
});
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case VERDICT_ARCHIVE_KIND: {
|
|
96
|
+
const id = String(payload.proposalId ?? "");
|
|
97
|
+
if (id === "")
|
|
98
|
+
break;
|
|
99
|
+
const draft = ensureDraft(id);
|
|
100
|
+
draft.measurement = {
|
|
101
|
+
decision: String(payload.decision ?? ""),
|
|
102
|
+
reason: String(payload.reason ?? ""),
|
|
103
|
+
targetIds: payload.targetIds ?? [],
|
|
104
|
+
resolvedIds: payload.resolvedIds ?? [],
|
|
105
|
+
introduced: payload.introduced ?? [],
|
|
106
|
+
};
|
|
107
|
+
draft.chain.push({
|
|
108
|
+
stage: "verdict",
|
|
109
|
+
at: isoTime(entry),
|
|
110
|
+
note: `L1 ${draft.measurement.decision}:${draft.measurement.reason}`,
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case PROPOSAL_DECISION_ARCHIVE_KIND: {
|
|
115
|
+
const id = String(payload.proposalId ?? "");
|
|
116
|
+
if (id === "")
|
|
117
|
+
break;
|
|
118
|
+
const draft = ensureDraft(id);
|
|
119
|
+
draft.decided = payload.decision === "rejected" ? "rejected" : "approved";
|
|
120
|
+
draft.chain.push({
|
|
121
|
+
stage: "decision",
|
|
122
|
+
at: isoTime(entry),
|
|
123
|
+
note: `管理面${draft.decided === "rejected" ? "拒绝" : "批准"}`,
|
|
124
|
+
});
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case "apply-run": {
|
|
128
|
+
const runId = String(payload.runId ?? "");
|
|
129
|
+
const failed = payload.failed === true;
|
|
130
|
+
const ids = payload.proposalIds ?? [];
|
|
131
|
+
for (const id of ids) {
|
|
132
|
+
const draft = ensureDraft(id);
|
|
133
|
+
if (failed) {
|
|
134
|
+
draft.chain.push({ stage: "applied", at: isoTime(entry), note: `应用部分失败(run ${runId}):${String(payload.error ?? "")}` });
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
draft.applied = true;
|
|
138
|
+
draft.backupRunId = runId;
|
|
139
|
+
draft.chain.push({ stage: "applied", at: isoTime(entry), note: `应用完成(备份会话 ${runId},可回滚)` });
|
|
140
|
+
// 生效时点回放(review-fixes ❌3):effects 随 apply-run 档案入档;
|
|
141
|
+
// 旧档案无此字段如实跳过,不伪造。
|
|
142
|
+
const effects = payload.effects ?? [];
|
|
143
|
+
for (const effect of effects) {
|
|
144
|
+
draft.chain.push({
|
|
145
|
+
stage: "applied",
|
|
146
|
+
at: isoTime(entry),
|
|
147
|
+
note: `生效时点 ${String(effect.agent ?? "?")}:${String(effect.effect ?? "")}`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case LESSON_ARCHIVE_KIND: {
|
|
155
|
+
const lesson = entry.payload;
|
|
156
|
+
// lesson.id = `lesson-<safeProposalFileName>`:反查提案链。
|
|
157
|
+
const stem = lesson.id.startsWith("lesson-") ? lesson.id.slice("lesson-".length) : lesson.id;
|
|
158
|
+
for (const [id, draft] of drafts) {
|
|
159
|
+
if (safeProposalFileName(id) === stem) {
|
|
160
|
+
draft.lessonId = lesson.id;
|
|
161
|
+
draft.chain.push({ stage: "lesson", at: isoTime(entry), note: `蒸馏 lesson: ${lesson.cue}` });
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
default:
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// pending 是提案正文源,且可能先于/独立于档案存在(如管理面直写):
|
|
172
|
+
// 以 pending 记录同样播种追踪链,档案缺环如实空缺,不伪造补齐。
|
|
173
|
+
for (const [id, record] of pendingById) {
|
|
174
|
+
const draft = ensureDraft(id);
|
|
175
|
+
if (draft.summary === id)
|
|
176
|
+
draft.summary = record.proposal.summary;
|
|
177
|
+
if (draft.kind === "")
|
|
178
|
+
draft.kind = record.proposal.kind;
|
|
179
|
+
if (draft.layer === null)
|
|
180
|
+
draft.layer = record.proposal.layer;
|
|
181
|
+
if (draft.edits.length === 0)
|
|
182
|
+
draft.edits = [...record.proposal.edits];
|
|
183
|
+
if (draft.riskClass === null)
|
|
184
|
+
draft.riskClass = record.riskClass;
|
|
185
|
+
if (draft.decided === null)
|
|
186
|
+
draft.decided = record.status === "pending" ? null : record.status;
|
|
187
|
+
}
|
|
188
|
+
const timeline = [...drafts.values()]
|
|
189
|
+
.map((draft) => {
|
|
190
|
+
const status = draft.applied
|
|
191
|
+
? "applied"
|
|
192
|
+
: draft.endedInReject && draft.decided === null
|
|
193
|
+
? "discarded"
|
|
194
|
+
: draft.decided === "rejected"
|
|
195
|
+
? "rejected"
|
|
196
|
+
: draft.decided === "approved"
|
|
197
|
+
? "approved"
|
|
198
|
+
: "pending";
|
|
199
|
+
return {
|
|
200
|
+
id: draft.id,
|
|
201
|
+
summary: draft.summary,
|
|
202
|
+
kind: draft.kind,
|
|
203
|
+
riskClass: draft.riskClass,
|
|
204
|
+
layer: draft.layer,
|
|
205
|
+
status,
|
|
206
|
+
chain: [...draft.chain].sort((a, b) => (a.at < b.at ? -1 : a.at > b.at ? 1 : 0)),
|
|
207
|
+
measurement: draft.measurement,
|
|
208
|
+
edits: draft.edits,
|
|
209
|
+
backupRunId: draft.backupRunId,
|
|
210
|
+
lessonId: draft.lessonId,
|
|
211
|
+
};
|
|
212
|
+
})
|
|
213
|
+
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
214
|
+
// ---- 趋势指标(可从档案重算;口径随数据导出) ----
|
|
215
|
+
let resolvedTotal = 0;
|
|
216
|
+
let decidedCount = 0;
|
|
217
|
+
let approvedCount = 0;
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
if (entry.kind === VERDICT_ARCHIVE_KIND) {
|
|
220
|
+
resolvedTotal += payloadOf(entry).resolvedIds?.length ?? 0;
|
|
221
|
+
}
|
|
222
|
+
if (entry.kind === PROPOSAL_DECISION_ARCHIVE_KIND) {
|
|
223
|
+
decidedCount += 1;
|
|
224
|
+
if (payloadOf(entry).decision === "approved")
|
|
225
|
+
approvedCount += 1;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const jobEntries = entries.filter((entry) => entry.kind === JOB_RUN_KIND);
|
|
229
|
+
const jobFailures = jobEntries.filter((entry) => typeof payloadOf(entry).error === "string");
|
|
230
|
+
const everMeasured = entries.some((entry) => entry.kind === VERDICT_ARCHIVE_KIND);
|
|
231
|
+
const everDecided = decidedCount > 0;
|
|
232
|
+
const trends = [
|
|
233
|
+
{
|
|
234
|
+
id: "findings-resolved",
|
|
235
|
+
label: "累计发现解决数",
|
|
236
|
+
value: everMeasured ? resolvedTotal : null,
|
|
237
|
+
source: "verdict 档案 resolvedIds 累计",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "adoption-rate",
|
|
241
|
+
label: "提案采纳率",
|
|
242
|
+
value: everDecided ? approvedCount / decidedCount : null,
|
|
243
|
+
source: "proposal-decision 档案 approved / 全部决策",
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: "rejection-rate",
|
|
247
|
+
label: "提案拒绝率",
|
|
248
|
+
value: everDecided ? (decidedCount - approvedCount) / decidedCount : null,
|
|
249
|
+
source: "proposal-decision 档案 rejected / 全部决策",
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: "job-runs",
|
|
253
|
+
label: "作业运行次数",
|
|
254
|
+
value: jobEntries.length > 0 ? jobEntries.length : null,
|
|
255
|
+
source: "job-run 档案条数",
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "job-failures",
|
|
259
|
+
label: "作业失败次数",
|
|
260
|
+
value: jobEntries.length > 0 ? jobFailures.length : null,
|
|
261
|
+
source: "job-run 档案含 error 字段的条数",
|
|
262
|
+
},
|
|
263
|
+
];
|
|
264
|
+
// ---- 作业运行与通知 ----
|
|
265
|
+
const jobRuns = jobEntries.map((entry) => {
|
|
266
|
+
const payload = payloadOf(entry);
|
|
267
|
+
const summary = (payload.summary ?? {});
|
|
268
|
+
return {
|
|
269
|
+
startedAt: new Date(Number(payload.startedAtMs ?? 0)).toISOString(),
|
|
270
|
+
endedAt: typeof payload.endedAtMs === "number" ? new Date(payload.endedAtMs).toISOString() : null,
|
|
271
|
+
ok: typeof payload.error !== "string",
|
|
272
|
+
error: typeof payload.error === "string" ? payload.error : null,
|
|
273
|
+
trigger: payload.trigger === "manual" || payload.trigger === "chain" ? payload.trigger : "event",
|
|
274
|
+
summary: {
|
|
275
|
+
signalsConsidered: typeof summary.signalsConsidered === "number" ? summary.signalsConsidered : null,
|
|
276
|
+
enqueued: Array.isArray(summary.enqueuedIds) ? summary.enqueuedIds.length : null,
|
|
277
|
+
autoAdopted: Array.isArray(summary.autoAdoptedIds) ? summary.autoAdoptedIds.length : null,
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
const notices = [];
|
|
282
|
+
for (const entry of jobEntries) {
|
|
283
|
+
const summary = (payloadOf(entry).summary ?? {});
|
|
284
|
+
if (Array.isArray(summary.notices)) {
|
|
285
|
+
for (const notice of summary.notices)
|
|
286
|
+
if (typeof notice === "string")
|
|
287
|
+
notices.push(notice);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// ---- 状态与计数 ----
|
|
291
|
+
const counts = {
|
|
292
|
+
pending: pending.filter((record) => record.status === "pending").length,
|
|
293
|
+
approved: pending.filter((record) => record.status === "approved").length,
|
|
294
|
+
rejected: pending.filter((record) => record.status === "rejected").length,
|
|
295
|
+
approvedUnapplied: timeline.filter((trace) => trace.status === "approved").length,
|
|
296
|
+
};
|
|
297
|
+
const hasPolicy = policy.defaultLevel !== undefined ||
|
|
298
|
+
Object.keys(policy.agents).length > 0 ||
|
|
299
|
+
Object.keys(policy.workspaces).length > 0;
|
|
300
|
+
const hasActivity = entries.length > 0 || pending.length > 0;
|
|
301
|
+
const state = !hasActivity && !hasPolicy ? "not-enabled" : hasActivity ? "active" : "enabled-idle";
|
|
302
|
+
return {
|
|
303
|
+
state,
|
|
304
|
+
levels: levelMap(policy),
|
|
305
|
+
counts,
|
|
306
|
+
timeline,
|
|
307
|
+
trends,
|
|
308
|
+
jobRuns,
|
|
309
|
+
notices,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 风险分类器与采纳门(trellis-evolve 任务 12.3)。
|
|
3
|
+
*
|
|
4
|
+
* 分级由代码按 **kind + target 形态** 数据驱动计算,不采信 editor 自报的
|
|
5
|
+
* `riskClass`(design D9):
|
|
6
|
+
*
|
|
7
|
+
* - Class A(可逆、零爆炸半径、证据确凿的机器本地单文件修复)—— 仅
|
|
8
|
+
* 级别 `auto-A` 且 probe 通过时自动采纳,采纳后通知(指明编辑、证据
|
|
9
|
+
* 与回滚路径);
|
|
10
|
+
* - Class B(内容/路由/对外副作用非零)—— 永远需要人对 diff 与证据的
|
|
11
|
+
* 显式批准,不受级别影响;
|
|
12
|
+
* - Class C(受保护目标)—— 已在 12.2 机械校验阶段拒绝,分类器只为
|
|
13
|
+
* 完备性映射,不可达;
|
|
14
|
+
* - 无法分类(未知 kind / target 形态不匹配)→ 一律按 B;
|
|
15
|
+
* - 以全局层为目标、仅由单 workspace 证据(或证据层未知)支撑的升格
|
|
16
|
+
* 编辑 → 一律 B(消费 12.2 的 `needsExplicitApproval` 标记)。
|
|
17
|
+
*
|
|
18
|
+
* 分类表的 A 例取自 `docs/self-evolution.md` 的既定设计:"补一个
|
|
19
|
+
* `${VAR}` 名"(secrets-policy-fix → secrets.policy.yaml)与"修复 doctor
|
|
20
|
+
* 已报告的 drift"(sync-config-fix → agent 原生 home 配置面);workspace
|
|
21
|
+
* 配置文件常被 git 跟踪、团队可见,对外副作用非零 → B(与 tasks 14.1
|
|
22
|
+
* E2E 的"Class B 呈现、带外批准"一致);MCP 路由变更 → B("内容或路由
|
|
23
|
+
* 编辑")。
|
|
24
|
+
*/
|
|
25
|
+
import type { Level } from "../host/index.js";
|
|
26
|
+
import type { EvidenceLink, Proposal, RiskClass } from "./types.js";
|
|
27
|
+
/** target 形态:规则表的第二维。 */
|
|
28
|
+
export type TargetShape = "any" | "agent-native" | "secrets-policy";
|
|
29
|
+
export interface RiskRule {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
/** C 类不进规则表(前置硬排除),表内只有 A/B。 */
|
|
32
|
+
readonly riskClass: "A" | "B";
|
|
33
|
+
readonly kind: string;
|
|
34
|
+
readonly targetShape: TargetShape;
|
|
35
|
+
readonly note: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 分类表(按序首条命中即用;未命中走兜底 B)。数据驱动:新增形态加行,
|
|
39
|
+
* 不写分支特例。
|
|
40
|
+
*/
|
|
41
|
+
export declare const RISK_RULES: readonly RiskRule[];
|
|
42
|
+
export interface RiskClassification {
|
|
43
|
+
readonly riskClass: RiskClass;
|
|
44
|
+
/** 命中的规则 id,或前置/兜底标识。 */
|
|
45
|
+
readonly ruleId: string;
|
|
46
|
+
readonly reason: string;
|
|
47
|
+
/** 升格降级是否发生(12.2 `needsExplicitApproval` 的消费痕迹)。 */
|
|
48
|
+
readonly demotedByPromotion: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface ClassifyOptions {
|
|
51
|
+
readonly homeDir: string;
|
|
52
|
+
/**
|
|
53
|
+
* 12.2 的不自动升格标记;缺省按 `evaluatePromotion` 从提案自身证据层
|
|
54
|
+
* 重算(同源判定)。
|
|
55
|
+
*/
|
|
56
|
+
readonly needsExplicitApproval?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 风险分类(kind + target 形态 → A/B/C)。不采信 `proposal.riskClass`
|
|
60
|
+
* 自报值;C 类目标在 12.2 校验阶段已拒,此处只为完备性映射。
|
|
61
|
+
*/
|
|
62
|
+
export declare function classifyProposalRisk(proposal: Proposal, options: ClassifyOptions): RiskClassification;
|
|
63
|
+
/** 自动采纳的通知负载:指明编辑、证据与回滚路径(spec 场景 1)。 */
|
|
64
|
+
export interface AdoptionNotification {
|
|
65
|
+
readonly message: string;
|
|
66
|
+
readonly proposalId: string;
|
|
67
|
+
readonly editPaths: readonly string[];
|
|
68
|
+
readonly evidence: readonly EvidenceLink[];
|
|
69
|
+
readonly rollbackPath: string;
|
|
70
|
+
}
|
|
71
|
+
export type AdoptionDecision = {
|
|
72
|
+
readonly action: "auto-adopt";
|
|
73
|
+
readonly riskClass: "A";
|
|
74
|
+
readonly reason: string;
|
|
75
|
+
readonly notification: AdoptionNotification;
|
|
76
|
+
} | {
|
|
77
|
+
readonly action: "await-approval";
|
|
78
|
+
readonly riskClass: RiskClass;
|
|
79
|
+
readonly reason: string;
|
|
80
|
+
};
|
|
81
|
+
export interface AdoptionGateInput {
|
|
82
|
+
readonly level: Level;
|
|
83
|
+
readonly classification: RiskClassification;
|
|
84
|
+
/** L1 前闸 verdict 差集可采纳(true = probe 通过)。 */
|
|
85
|
+
readonly probePassed: boolean;
|
|
86
|
+
readonly proposal: Proposal;
|
|
87
|
+
/**
|
|
88
|
+
* 回滚路径指引;缺省指向标准备份会话语义(12.5 应用时会写入真实会话
|
|
89
|
+
* 标识,由调用方替换为具体指引)。
|
|
90
|
+
*/
|
|
91
|
+
readonly rollbackHint?: string;
|
|
92
|
+
}
|
|
93
|
+
export declare const DEFAULT_ROLLBACK_HINT = "trellis rollback --list(\u6807\u51C6\u5907\u4EFD\u4F1A\u8BDD\u5185\u9010\u5B57\u8282\u8FD8\u539F)";
|
|
94
|
+
/**
|
|
95
|
+
* 应用后用真实备份会话标识重写通知的回滚行(review-fixes ❌5)。决策时
|
|
96
|
+
* 备份会话尚不存在,回滚行只能是泛化占位;应用拿到真实 runId 后由调用
|
|
97
|
+
* 方经本函数回填。只重写 `回滚:` 行,其余行不动;格式由 buildNotification
|
|
98
|
+
* 单点控制。
|
|
99
|
+
*/
|
|
100
|
+
export declare function retargetNotificationRollback(notification: AdoptionNotification, rollbackPath: string): AdoptionNotification;
|
|
101
|
+
/**
|
|
102
|
+
* 采纳门:A 仅在级别 `auto-A` 且 probe 通过时自动采纳(并产出通知);
|
|
103
|
+
* B 永远需要显式批准,不受级别影响;C 不可达(校验阶段已拒)防御性按
|
|
104
|
+
* 待批准收口。probe 未通过或级别低于 `auto-A` 时 Class A 也不自动采纳。
|
|
105
|
+
*/
|
|
106
|
+
export declare function evaluateAdoptionGate(input: AdoptionGateInput): AdoptionDecision;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 风险分类器与采纳门(trellis-evolve 任务 12.3)。
|
|
3
|
+
*
|
|
4
|
+
* 分级由代码按 **kind + target 形态** 数据驱动计算,不采信 editor 自报的
|
|
5
|
+
* `riskClass`(design D9):
|
|
6
|
+
*
|
|
7
|
+
* - Class A(可逆、零爆炸半径、证据确凿的机器本地单文件修复)—— 仅
|
|
8
|
+
* 级别 `auto-A` 且 probe 通过时自动采纳,采纳后通知(指明编辑、证据
|
|
9
|
+
* 与回滚路径);
|
|
10
|
+
* - Class B(内容/路由/对外副作用非零)—— 永远需要人对 diff 与证据的
|
|
11
|
+
* 显式批准,不受级别影响;
|
|
12
|
+
* - Class C(受保护目标)—— 已在 12.2 机械校验阶段拒绝,分类器只为
|
|
13
|
+
* 完备性映射,不可达;
|
|
14
|
+
* - 无法分类(未知 kind / target 形态不匹配)→ 一律按 B;
|
|
15
|
+
* - 以全局层为目标、仅由单 workspace 证据(或证据层未知)支撑的升格
|
|
16
|
+
* 编辑 → 一律 B(消费 12.2 的 `needsExplicitApproval` 标记)。
|
|
17
|
+
*
|
|
18
|
+
* 分类表的 A 例取自 `docs/self-evolution.md` 的既定设计:"补一个
|
|
19
|
+
* `${VAR}` 名"(secrets-policy-fix → secrets.policy.yaml)与"修复 doctor
|
|
20
|
+
* 已报告的 drift"(sync-config-fix → agent 原生 home 配置面);workspace
|
|
21
|
+
* 配置文件常被 git 跟踪、团队可见,对外副作用非零 → B(与 tasks 14.1
|
|
22
|
+
* E2E 的"Class B 呈现、带外批准"一致);MCP 路由变更 → B("内容或路由
|
|
23
|
+
* 编辑")。
|
|
24
|
+
*/
|
|
25
|
+
import { basename, join, relative, resolve, sep } from "node:path";
|
|
26
|
+
import { NATIVE_CHECK_SURFACE } from "../host/index.js";
|
|
27
|
+
import { evaluatePromotion, protectedPathReason } from "./validate.js";
|
|
28
|
+
/**
|
|
29
|
+
* 分类表(按序首条命中即用;未命中走兜底 B)。数据驱动:新增形态加行,
|
|
30
|
+
* 不写分支特例。
|
|
31
|
+
*/
|
|
32
|
+
export const RISK_RULES = [
|
|
33
|
+
// A:可逆、零爆炸半径 —— 机器本地单文件,备份会话内逐字节可回滚。
|
|
34
|
+
{
|
|
35
|
+
id: "sync-config-fix/agent-native",
|
|
36
|
+
riskClass: "A",
|
|
37
|
+
kind: "sync-config-fix",
|
|
38
|
+
targetShape: "agent-native",
|
|
39
|
+
note: "修复 doctor 已报告的投递面 drift:agent 原生 home 配置单文件,零对外半径",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "secrets-policy-fix/secrets-policy",
|
|
43
|
+
riskClass: "A",
|
|
44
|
+
kind: "secrets-policy-fix",
|
|
45
|
+
targetShape: "secrets-policy",
|
|
46
|
+
note: "策略文件只含变量名/拒绝模式(补 `${VAR}` 名形态),机器本地可回滚",
|
|
47
|
+
},
|
|
48
|
+
// B:内容/路由/对外副作用非零。
|
|
49
|
+
{
|
|
50
|
+
id: "workspace-config-fix/any",
|
|
51
|
+
riskClass: "B",
|
|
52
|
+
kind: "workspace-config-fix",
|
|
53
|
+
targetShape: "any",
|
|
54
|
+
note: "workspace 配置常被 git 跟踪(团队可见),对外副作用非零",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "mcp-config-fix/any",
|
|
58
|
+
riskClass: "B",
|
|
59
|
+
kind: "mcp-config-fix",
|
|
60
|
+
targetShape: "any",
|
|
61
|
+
note: "MCP 路由变更,永远人批",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "oauth-config-fix/any",
|
|
65
|
+
riskClass: "B",
|
|
66
|
+
kind: "oauth-config-fix",
|
|
67
|
+
targetShape: "any",
|
|
68
|
+
note: "auth 相邻配置,不自动",
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
/** 提案涉及的全部路径(target 描述 + 各编辑路径)。 */
|
|
72
|
+
function pathsOf(proposal) {
|
|
73
|
+
return [proposal.target, ...proposal.edits.map((edit) => edit.path)];
|
|
74
|
+
}
|
|
75
|
+
/** homeDir 相对路径形态;home 外或非绝对形态返回原始串(走尾缀匹配)。 */
|
|
76
|
+
function relOfHome(path, homeDir) {
|
|
77
|
+
const abs = resolve(path);
|
|
78
|
+
const rel = relative(resolve(homeDir), abs);
|
|
79
|
+
return rel !== "" && (rel === ".." || rel.startsWith(`..${sep}`)) ? path : rel;
|
|
80
|
+
}
|
|
81
|
+
function matchesShape(shape, proposal, homeDir) {
|
|
82
|
+
if (shape === "any")
|
|
83
|
+
return true;
|
|
84
|
+
const rels = pathsOf(proposal).map((path) => relOfHome(path, homeDir));
|
|
85
|
+
if (shape === "secrets-policy") {
|
|
86
|
+
const policy = join(".trellis", "secrets.policy.yaml");
|
|
87
|
+
return rels.some((rel) => rel === policy || basename(rel) === "secrets.policy.yaml");
|
|
88
|
+
}
|
|
89
|
+
// agent-native:NATIVE_CHECK_SURFACE 条目(或其内)。
|
|
90
|
+
return rels.some((rel) => NATIVE_CHECK_SURFACE.some((entry) => rel === entry || rel.startsWith(entry + sep)));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 风险分类(kind + target 形态 → A/B/C)。不采信 `proposal.riskClass`
|
|
94
|
+
* 自报值;C 类目标在 12.2 校验阶段已拒,此处只为完备性映射。
|
|
95
|
+
*/
|
|
96
|
+
export function classifyProposalRisk(proposal, options) {
|
|
97
|
+
// C 前置:target 描述符的 auth 形态 + 受保护路径(与 12.2 同源)。
|
|
98
|
+
if (/\bauth\b/i.test(proposal.target)) {
|
|
99
|
+
return {
|
|
100
|
+
riskClass: "C",
|
|
101
|
+
ruleId: "protected/auth-descriptor",
|
|
102
|
+
reason: `提案目标 "${proposal.target}" 为 MCP auth 配置形态(校验阶段已拒)`,
|
|
103
|
+
demotedByPromotion: false,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
for (const path of pathsOf(proposal)) {
|
|
107
|
+
const reason = protectedPathReason(path, options.homeDir);
|
|
108
|
+
if (reason !== null) {
|
|
109
|
+
return {
|
|
110
|
+
riskClass: "C",
|
|
111
|
+
ruleId: "protected/path",
|
|
112
|
+
reason: `${path}:${reason}(校验阶段已拒)`,
|
|
113
|
+
demotedByPromotion: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// 升格降级:全局层目标 + 仅单 workspace 证据(或层未知)→ 一律 B。
|
|
118
|
+
const needsExplicitApproval = options.needsExplicitApproval ?? evaluatePromotion(proposal).needsExplicitApproval;
|
|
119
|
+
const demotedByPromotion = needsExplicitApproval && proposal.layer.kind === "global";
|
|
120
|
+
if (demotedByPromotion) {
|
|
121
|
+
return {
|
|
122
|
+
riskClass: "B",
|
|
123
|
+
ruleId: "promotion-demotion",
|
|
124
|
+
reason: "全局层升格编辑仅由单 workspace 证据(或证据层未知)支撑,一律按 B 处理",
|
|
125
|
+
demotedByPromotion: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// 规则表:按序首条命中。
|
|
129
|
+
for (const rule of RISK_RULES) {
|
|
130
|
+
if (rule.kind !== proposal.kind)
|
|
131
|
+
continue;
|
|
132
|
+
if (!matchesShape(rule.targetShape, proposal, options.homeDir))
|
|
133
|
+
continue;
|
|
134
|
+
return {
|
|
135
|
+
riskClass: rule.riskClass,
|
|
136
|
+
ruleId: rule.id,
|
|
137
|
+
reason: rule.note,
|
|
138
|
+
demotedByPromotion: false,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// 兜底:未知 kind / 形态不匹配 → 无法分类按 B。
|
|
142
|
+
return {
|
|
143
|
+
riskClass: "B",
|
|
144
|
+
ruleId: "fallback-unclassified",
|
|
145
|
+
reason: `kind "${proposal.kind}" / target "${proposal.target}" 不匹配任何已知分类规则,无法分类按 B 处理`,
|
|
146
|
+
demotedByPromotion: false,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export const DEFAULT_ROLLBACK_HINT = "trellis rollback --list(标准备份会话内逐字节还原)";
|
|
150
|
+
function describeEvidence(link) {
|
|
151
|
+
const id = link.kind === "run" ? link.runId : link.kind === "doctor-finding" ? link.findingId : link.signalId;
|
|
152
|
+
return `${link.kind}:${id}`;
|
|
153
|
+
}
|
|
154
|
+
function buildNotification(input, reason) {
|
|
155
|
+
const { proposal, classification } = input;
|
|
156
|
+
const rollbackPath = input.rollbackHint ?? DEFAULT_ROLLBACK_HINT;
|
|
157
|
+
const editPaths = proposal.edits.map((edit) => edit.path);
|
|
158
|
+
const message = [
|
|
159
|
+
`自动采纳 Class A 提案 ${proposal.id}(${classification.ruleId}):${reason}`,
|
|
160
|
+
`编辑:${editPaths.join(", ")}`,
|
|
161
|
+
`证据:${proposal.evidence.map(describeEvidence).join("; ")}`,
|
|
162
|
+
`回滚:${rollbackPath}`,
|
|
163
|
+
].join("\n");
|
|
164
|
+
return { message, proposalId: proposal.id, editPaths, evidence: proposal.evidence, rollbackPath };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 应用后用真实备份会话标识重写通知的回滚行(review-fixes ❌5)。决策时
|
|
168
|
+
* 备份会话尚不存在,回滚行只能是泛化占位;应用拿到真实 runId 后由调用
|
|
169
|
+
* 方经本函数回填。只重写 `回滚:` 行,其余行不动;格式由 buildNotification
|
|
170
|
+
* 单点控制。
|
|
171
|
+
*/
|
|
172
|
+
export function retargetNotificationRollback(notification, rollbackPath) {
|
|
173
|
+
const message = notification.message
|
|
174
|
+
.split("\n")
|
|
175
|
+
.map((line) => (line.startsWith("回滚:") ? `回滚:${rollbackPath}` : line))
|
|
176
|
+
.join("\n");
|
|
177
|
+
return { ...notification, message, rollbackPath };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 采纳门:A 仅在级别 `auto-A` 且 probe 通过时自动采纳(并产出通知);
|
|
181
|
+
* B 永远需要显式批准,不受级别影响;C 不可达(校验阶段已拒)防御性按
|
|
182
|
+
* 待批准收口。probe 未通过或级别低于 `auto-A` 时 Class A 也不自动采纳。
|
|
183
|
+
*/
|
|
184
|
+
export function evaluateAdoptionGate(input) {
|
|
185
|
+
const { classification, level, probePassed } = input;
|
|
186
|
+
if (classification.riskClass === "C") {
|
|
187
|
+
return {
|
|
188
|
+
action: "await-approval",
|
|
189
|
+
riskClass: "C",
|
|
190
|
+
reason: "C 类受保护目标在校验阶段已拒绝,不进入自动采纳",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (classification.riskClass === "B") {
|
|
194
|
+
return {
|
|
195
|
+
action: "await-approval",
|
|
196
|
+
riskClass: "B",
|
|
197
|
+
reason: classification.demotedByPromotion
|
|
198
|
+
? `${classification.reason}(不自动采纳,呈现等待显式批准)`
|
|
199
|
+
: "B 类编辑永远需要人对 diff 与证据的显式批准,不受级别影响",
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// Class A。
|
|
203
|
+
if (level !== "auto-A") {
|
|
204
|
+
return {
|
|
205
|
+
action: "await-approval",
|
|
206
|
+
riskClass: "A",
|
|
207
|
+
reason: `级别 "${level}" 低于 auto-A,Class A 也不自动采纳(呈现等待批准)`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (!probePassed) {
|
|
211
|
+
return {
|
|
212
|
+
action: "await-approval",
|
|
213
|
+
riskClass: "A",
|
|
214
|
+
reason: "probe 未通过(L1 前闸不可采纳),不自动采纳",
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const reason = "级别 auto-A、Class A 且 probe 通过";
|
|
218
|
+
return {
|
|
219
|
+
action: "auto-adopt",
|
|
220
|
+
riskClass: "A",
|
|
221
|
+
reason,
|
|
222
|
+
notification: buildNotification(input, reason),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 可选信号源探测(trellis-evolve 任务 11.3)。
|
|
3
|
+
*
|
|
4
|
+
* agent-bridge 委托记录是可选信号源:缺席是合法状态,SHALL NOT 使运行
|
|
5
|
+
* 失败——探测返回 `unavailable` 而非抛错,作业评估(11.5)以此继续并把
|
|
6
|
+
* 状态记录下来。委托记录存储取 Trellis 真实落盘的 agent 间委托(task
|
|
7
|
+
* store,`~/.trellis/tasks/tasks.json`);委托记录到信号的映射设计未定,
|
|
8
|
+
* 本期只做源可用性探测,不派生信号内容。
|
|
9
|
+
*/
|
|
10
|
+
export type OptionalSourceStatus = "available" | "unavailable";
|
|
11
|
+
export interface OptionalSourceProbe {
|
|
12
|
+
readonly source: "agent-bridge";
|
|
13
|
+
readonly status: OptionalSourceStatus;
|
|
14
|
+
/** 人类可读的状态说明(写入档案时直接采用)。 */
|
|
15
|
+
readonly note: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function agentBridgeRecordsPath(homeDir: string): string;
|
|
18
|
+
/** 探测 agent-bridge 委托记录是否可用;永不抛错。 */
|
|
19
|
+
export declare function probeAgentBridgeSource(homeDir: string): OptionalSourceProbe;
|