agent-trellis 0.19.0 → 0.20.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 (96) hide show
  1. package/dist/cli.js +12 -1
  2. package/dist/commands/doctor.d.ts +8 -0
  3. package/dist/commands/doctor.js +87 -2
  4. package/dist/commands/evolve.d.ts +43 -0
  5. package/dist/commands/evolve.js +479 -0
  6. package/dist/commands/mcpGateway.d.ts +28 -0
  7. package/dist/commands/mcpGateway.js +40 -6
  8. package/dist/evolve/agentView.d.ts +32 -0
  9. package/dist/evolve/agentView.js +34 -0
  10. package/dist/evolve/apply.d.ts +71 -0
  11. package/dist/evolve/apply.js +162 -0
  12. package/dist/evolve/archive.d.ts +23 -0
  13. package/dist/evolve/archive.js +58 -0
  14. package/dist/evolve/erase.d.ts +17 -0
  15. package/dist/evolve/erase.js +65 -0
  16. package/dist/evolve/extension.d.ts +37 -0
  17. package/dist/evolve/extension.js +63 -0
  18. package/dist/evolve/gate.d.ts +95 -0
  19. package/dist/evolve/gate.js +295 -0
  20. package/dist/evolve/importHistory.d.ts +91 -0
  21. package/dist/evolve/importHistory.js +239 -0
  22. package/dist/evolve/jobMain.d.ts +22 -0
  23. package/dist/evolve/jobMain.js +52 -0
  24. package/dist/evolve/jobRun.d.ts +94 -0
  25. package/dist/evolve/jobRun.js +273 -0
  26. package/dist/evolve/jobShell.d.ts +46 -0
  27. package/dist/evolve/jobShell.js +75 -0
  28. package/dist/evolve/l2.d.ts +93 -0
  29. package/dist/evolve/l2.js +81 -0
  30. package/dist/evolve/lesson.d.ts +57 -0
  31. package/dist/evolve/lesson.js +97 -0
  32. package/dist/evolve/pending.d.ts +69 -0
  33. package/dist/evolve/pending.js +116 -0
  34. package/dist/evolve/probeWorkspace.d.ts +57 -0
  35. package/dist/evolve/probeWorkspace.js +69 -0
  36. package/dist/evolve/propose.d.ts +73 -0
  37. package/dist/evolve/propose.js +136 -0
  38. package/dist/evolve/recorder.d.ts +68 -0
  39. package/dist/evolve/recorder.js +169 -0
  40. package/dist/evolve/report.d.ts +26 -0
  41. package/dist/evolve/report.js +37 -0
  42. package/dist/evolve/reportView.d.ts +96 -0
  43. package/dist/evolve/reportView.js +311 -0
  44. package/dist/evolve/risk.d.ts +106 -0
  45. package/dist/evolve/risk.js +224 -0
  46. package/dist/evolve/sources.d.ts +19 -0
  47. package/dist/evolve/sources.js +29 -0
  48. package/dist/evolve/stateDir.d.ts +21 -0
  49. package/dist/evolve/stateDir.js +56 -0
  50. package/dist/evolve/surface.d.ts +34 -0
  51. package/dist/evolve/surface.js +72 -0
  52. package/dist/evolve/trajectory.d.ts +25 -0
  53. package/dist/evolve/trajectory.js +88 -0
  54. package/dist/evolve/treeToCanonical.d.ts +28 -0
  55. package/dist/evolve/treeToCanonical.js +79 -0
  56. package/dist/evolve/trellisSubstrate.d.ts +65 -0
  57. package/dist/evolve/trellisSubstrate.js +154 -0
  58. package/dist/evolve/trigger.d.ts +177 -0
  59. package/dist/evolve/trigger.js +400 -0
  60. package/dist/evolve/types.d.ts +126 -0
  61. package/dist/evolve/types.js +15 -0
  62. package/dist/evolve/validate.d.ts +139 -0
  63. package/dist/evolve/validate.js +315 -0
  64. package/dist/host/checks.d.ts +57 -0
  65. package/dist/host/checks.js +126 -0
  66. package/dist/host/delivery.d.ts +42 -0
  67. package/dist/host/delivery.js +59 -0
  68. package/dist/host/edits.d.ts +87 -0
  69. package/dist/host/edits.js +102 -0
  70. package/dist/host/hostEvents.d.ts +74 -0
  71. package/dist/host/hostEvents.js +45 -0
  72. package/dist/host/index.d.ts +22 -0
  73. package/dist/host/index.js +17 -0
  74. package/dist/host/levels.d.ts +106 -0
  75. package/dist/host/levels.js +206 -0
  76. package/dist/host/staging.d.ts +56 -0
  77. package/dist/host/staging.js +106 -0
  78. package/dist/host/surface.d.ts +36 -0
  79. package/dist/host/surface.js +20 -0
  80. package/dist/lib/backup.js +12 -3
  81. package/dist/lib/fsAtomic.d.ts +7 -0
  82. package/dist/lib/fsAtomic.js +14 -0
  83. package/dist/lib/hostEventBus.d.ts +47 -0
  84. package/dist/lib/hostEventBus.js +94 -0
  85. package/dist/lib/hostExtensions.d.ts +75 -0
  86. package/dist/lib/hostExtensions.js +196 -0
  87. package/dist/lib/mcpRuntime.d.ts +12 -1
  88. package/dist/lib/mcpRuntime.js +34 -2
  89. package/dist/lib/mcpToolRegistry.d.ts +18 -2
  90. package/dist/lib/mcpToolRegistry.js +29 -9
  91. package/dist/pi-bridge/bundle.js +16 -6
  92. package/docs/magpie-gateway-research.md +422 -0
  93. package/docs/roadmap.md +22 -12
  94. package/docs/runtime-infra.md +41 -22
  95. package/docs/self-evolution.md +138 -44
  96. package/package.json +7 -2
@@ -0,0 +1,71 @@
1
+ /**
2
+ * 应用与生效(trellis-evolve 任务 12.5)。
3
+ *
4
+ * 把**已批准**提案的全部编辑写入托管备份会话的同一会话内
5
+ * (spec"原子运行与回滚":一次运行的全部写入在同一备份会话,一次回滚
6
+ * 恢复运行前状态),并把备份会话标识记入运行档案(kind `apply-run`)。
7
+ *
8
+ * 采纳后的生效边界(spec"采纳的生效边界"):
9
+ * - runtime 投递("events")的 agent:后续会话读取 canonical 时自然
10
+ * 生效,无需同步(9.4 定案:runtime provider 每请求实时读盘);
11
+ * - 仅原生渲染("doctor-only")的 agent:触发渲染同步,报告同步结果。
12
+ *
13
+ * 同步经注入缝触发:`triggerSync` 由管理面/作业层注入 `runSync` 包装
14
+ * (消费者边界 10.3:本目录不 import `commands/*`),测试注入桩。缺省
15
+ * 未注入时只报告生效时点,不假装同步已经发生。
16
+ *
17
+ * 已应用查重以档案为准(apply-run 的 proposalIds):重复调用跳过,
18
+ * 不开第二个会话;部分失败的 HostEditError 批次照记档案(failed 标记,
19
+ * 不计入查重),提案不标已应用,重试重放(编辑幂等:write-file 整文件
20
+ * 写,remove-symlink 对已不存在目标视为成功)。
21
+ *
22
+ * 生效时点说明(effects)与同步结果随 apply-run 档案入档(trellis-
23
+ * evolve-review-fixes:此前算完即弃,报告不可回放)。同步触发失败收口
24
+ * 为效果文本并入档,不丢应用档案——编辑已写入,同步可人工补。
25
+ */
26
+ /** 运行档案 kind:一次应用运行(含备份会话标识与提案集合)。 */
27
+ export declare const APPLY_ARCHIVE_KIND = "apply-run";
28
+ /** 与 host/levels 的 EvolveObservation 同语义:由调用方按 delivery
29
+ * 分类注入("events" = runtime 交付;"doctor-only" = 仅原生渲染)。 */
30
+ export type ApplyObservation = "events" | "doctor-only";
31
+ export interface ApplyAgent {
32
+ readonly agent: string;
33
+ readonly observation: ApplyObservation;
34
+ }
35
+ export interface AgentEffectiveness {
36
+ readonly agent: string;
37
+ readonly observation: ApplyObservation;
38
+ /** 人类可读的生效时点说明(应用后向用户展示)。 */
39
+ readonly effect: string;
40
+ }
41
+ export interface ApplyApprovedOptions {
42
+ readonly homeDir: string;
43
+ /** 托管 agent 及其交付形态;缺省空(只应用,不逐 agent 说明)。 */
44
+ readonly agents?: readonly ApplyAgent[];
45
+ /** 原生渲染 agent 的同步触发注入缝;存在 doctor-only agent 且本次
46
+ * 有编辑被应用时调用一次。返回值的 exitCode 进入结果与生效说明。 */
47
+ readonly triggerSync?: () => Promise<{
48
+ exitCode: number;
49
+ }>;
50
+ /** 时间源;缺省 Date.now(仅透传档案打戳用,档案自带 now 缺省)。 */
51
+ readonly now?: () => Date;
52
+ }
53
+ export interface ApplyApprovedResult {
54
+ /** 本次备份会话标识;无未应用的已批准提案时 null(不开会话)。 */
55
+ readonly runId: string | null;
56
+ readonly appliedProposalIds: string[];
57
+ readonly alreadyAppliedIds: string[];
58
+ readonly effects: AgentEffectiveness[];
59
+ /** 原生渲染同步的退出码;未触发为 null。 */
60
+ readonly syncExitCode: number | null;
61
+ /** 同步触发抛错时的错误文本(收口为效果文本,不中断应用);无为 null。 */
62
+ readonly syncError: string | null;
63
+ /** 应用前重校验不通过而跳过的提案 id(纵深防御,见实现注释)。 */
64
+ readonly skippedInvalidIds: string[];
65
+ }
66
+ /**
67
+ * 应用全部已批准且未应用的提案:编辑合并进**同一个**备份会话写入,
68
+ * 档案记会话标识,再按 agent 交付形态说明生效时点并对原生渲染 agent
69
+ * 触发同步。无事可做时零副作用(不开会话、不触发同步、不写档案)。
70
+ */
71
+ export declare function applyApprovedProposals(options: ApplyApprovedOptions): Promise<ApplyApprovedResult>;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * 应用与生效(trellis-evolve 任务 12.5)。
3
+ *
4
+ * 把**已批准**提案的全部编辑写入托管备份会话的同一会话内
5
+ * (spec"原子运行与回滚":一次运行的全部写入在同一备份会话,一次回滚
6
+ * 恢复运行前状态),并把备份会话标识记入运行档案(kind `apply-run`)。
7
+ *
8
+ * 采纳后的生效边界(spec"采纳的生效边界"):
9
+ * - runtime 投递("events")的 agent:后续会话读取 canonical 时自然
10
+ * 生效,无需同步(9.4 定案:runtime provider 每请求实时读盘);
11
+ * - 仅原生渲染("doctor-only")的 agent:触发渲染同步,报告同步结果。
12
+ *
13
+ * 同步经注入缝触发:`triggerSync` 由管理面/作业层注入 `runSync` 包装
14
+ * (消费者边界 10.3:本目录不 import `commands/*`),测试注入桩。缺省
15
+ * 未注入时只报告生效时点,不假装同步已经发生。
16
+ *
17
+ * 已应用查重以档案为准(apply-run 的 proposalIds):重复调用跳过,
18
+ * 不开第二个会话;部分失败的 HostEditError 批次照记档案(failed 标记,
19
+ * 不计入查重),提案不标已应用,重试重放(编辑幂等:write-file 整文件
20
+ * 写,remove-symlink 对已不存在目标视为成功)。
21
+ *
22
+ * 生效时点说明(effects)与同步结果随 apply-run 档案入档(trellis-
23
+ * evolve-review-fixes:此前算完即弃,报告不可回放)。同步触发失败收口
24
+ * 为效果文本并入档,不丢应用档案——编辑已写入,同步可人工补。
25
+ */
26
+ import { appendArchiveRecord, readArchiveRecords } from "./archive.js";
27
+ import { readProposalsByStatus } from "./pending.js";
28
+ import { validateProposal } from "./validate.js";
29
+ import { applyEdits, HostEditError } from "../host/index.js";
30
+ /** 运行档案 kind:一次应用运行(含备份会话标识与提案集合)。 */
31
+ export const APPLY_ARCHIVE_KIND = "apply-run";
32
+ function appliedIdsFromArchive(homeDir) {
33
+ const applied = new Set();
34
+ for (const entry of readArchiveRecords(homeDir)) {
35
+ if (entry.kind !== APPLY_ARCHIVE_KIND)
36
+ continue;
37
+ const payload = entry.payload;
38
+ // 部分失败的批次(failed)不计入"已应用":重试按幂等重放全部编辑。
39
+ if (payload === null || payload.failed === true || !Array.isArray(payload.proposalIds))
40
+ continue;
41
+ for (const id of payload.proposalIds) {
42
+ if (typeof id === "string")
43
+ applied.add(id);
44
+ }
45
+ }
46
+ return applied;
47
+ }
48
+ function effectivenessFor(agent, syncExitCode, syncTriggered, syncError) {
49
+ if (agent.observation === "events") {
50
+ return {
51
+ agent: agent.agent,
52
+ observation: agent.observation,
53
+ effect: "后续会话读取 canonical 时自然生效(runtime 投递,无需同步)",
54
+ };
55
+ }
56
+ if (syncTriggered) {
57
+ return {
58
+ agent: agent.agent,
59
+ observation: agent.observation,
60
+ effect: `已触发渲染同步(exit ${String(syncExitCode ?? "?")}),同步完成后生效`,
61
+ };
62
+ }
63
+ if (syncError !== null) {
64
+ return {
65
+ agent: agent.agent,
66
+ observation: agent.observation,
67
+ effect: `触发渲染同步失败:${syncError};需运行 \`trellis sync\` 后生效`,
68
+ };
69
+ }
70
+ return {
71
+ agent: agent.agent,
72
+ observation: agent.observation,
73
+ effect: "需运行 `trellis sync` 后生效(本次未提供同步触发)",
74
+ };
75
+ }
76
+ /**
77
+ * 应用全部已批准且未应用的提案:编辑合并进**同一个**备份会话写入,
78
+ * 档案记会话标识,再按 agent 交付形态说明生效时点并对原生渲染 agent
79
+ * 触发同步。无事可做时零副作用(不开会话、不触发同步、不写档案)。
80
+ */
81
+ export async function applyApprovedProposals(options) {
82
+ const { homeDir } = options;
83
+ const alreadyApplied = appliedIdsFromArchive(homeDir);
84
+ const approved = readProposalsByStatus(homeDir, "approved");
85
+ const appliedProposalIds = [];
86
+ const alreadyAppliedIds = [];
87
+ const skippedInvalidIds = [];
88
+ const edits = [];
89
+ for (const record of approved) {
90
+ if (alreadyApplied.has(record.id)) {
91
+ alreadyAppliedIds.push(record.id);
92
+ continue;
93
+ }
94
+ // 纵深防御:应用前重跑机械校验。pending 是可变磁盘状态,approve 只
95
+ // 翻 status 字段——入队时的白名单/路径穿越约束在这一步重放,磁盘
96
+ // 漂移或被改写的编辑到不了写入面。
97
+ const validation = validateProposal(record.proposal, { homeDir });
98
+ if (!validation.ok) {
99
+ skippedInvalidIds.push(record.id);
100
+ continue;
101
+ }
102
+ appliedProposalIds.push(record.id);
103
+ for (const edit of record.proposal.edits)
104
+ edits.push(edit);
105
+ }
106
+ if (appliedProposalIds.length === 0) {
107
+ return {
108
+ runId: null,
109
+ appliedProposalIds: [],
110
+ alreadyAppliedIds,
111
+ effects: [],
112
+ syncExitCode: null,
113
+ syncError: null,
114
+ skippedInvalidIds,
115
+ };
116
+ }
117
+ let runId;
118
+ try {
119
+ ({ runId } = await applyEdits({ homeDir, edits, command: "evolve-apply" }));
120
+ }
121
+ catch (err) {
122
+ if (err instanceof HostEditError) {
123
+ // 部分失败也落 apply-run 档案(failed 不计入已应用查重):runId
124
+ // 可经 `trellis rollback` 撤销已应用部分,审计不留空白。
125
+ appendArchiveRecord(homeDir, {
126
+ kind: APPLY_ARCHIVE_KIND,
127
+ payload: { runId: err.runId, proposalIds: appliedProposalIds, failed: true, error: err.message },
128
+ }, options.now ?? (() => new Date()));
129
+ }
130
+ throw err;
131
+ }
132
+ // 生效:原生渲染 agent 触发同步(runtime agent 无需——9.4 实时读取)。
133
+ // 同步失败收口为效果文本(syncError),不丢应用档案:编辑已写入,同步可人工补。
134
+ const agents = options.agents ?? [];
135
+ const needsSync = agents.some((a) => a.observation === "doctor-only");
136
+ let syncExitCode = null;
137
+ let syncTriggered = false;
138
+ let syncError = null;
139
+ if (needsSync && options.triggerSync !== undefined) {
140
+ try {
141
+ syncExitCode = (await options.triggerSync()).exitCode;
142
+ syncTriggered = true;
143
+ }
144
+ catch (err) {
145
+ syncError = err instanceof Error ? err.message : String(err);
146
+ }
147
+ }
148
+ const effects = agents.map((agent) => effectivenessFor(agent, syncExitCode, syncTriggered, syncError));
149
+ // 档案记会话标识 + 逐 agent 生效时点 + 同步结果(review-fixes ❌3:此前
150
+ // effects 算完即弃);单条 apply-run 记录承载全部应用元数据,报告回放同源。
151
+ appendArchiveRecord(homeDir, {
152
+ kind: APPLY_ARCHIVE_KIND,
153
+ payload: {
154
+ runId,
155
+ proposalIds: appliedProposalIds,
156
+ effects,
157
+ syncExitCode,
158
+ ...(syncError !== null ? { syncError } : {}),
159
+ },
160
+ }, options.now ?? (() => new Date()));
161
+ return { runId, appliedProposalIds, alreadyAppliedIds, effects, syncExitCode, syncError, skippedInvalidIds };
162
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 运行档案写入器(trellis-evolve 任务 10.4)。
3
+ *
4
+ * `archive/records.jsonl` 只追加 JSONL:提案/探针/verdict/备份会话标识/
5
+ * L2 比较记录按发生顺序落行,永不覆盖、不重排、不去重。读取侧严格解析
6
+ * ——档案是治理审计数据,坏行必须带行号抛出而不是静默跳过。
7
+ *
8
+ * 记录格式:`{ kind, recordedAt, payload }`。`kind` 开放(各任务逐步收
9
+ * 敛:proposal/verdict/lesson/L2 比较等),`payload` 为 JSON 安全的具体
10
+ * 记录;`recordedAt` 由写入器打戳(ISO 8601)。
11
+ */
12
+ export interface ArchiveRecord {
13
+ readonly kind: string;
14
+ readonly payload: unknown;
15
+ }
16
+ export interface ArchiveEntry extends ArchiveRecord {
17
+ readonly recordedAt: string;
18
+ }
19
+ export declare function archiveRecordsPath(homeDir: string): string;
20
+ /** 只追加一条记录,返回落盘后的完整条目(含打戳)。 */
21
+ export declare function appendArchiveRecord(homeDir: string, record: ArchiveRecord, now?: () => Date): ArchiveEntry;
22
+ /** 读回全部档案条目;文件不存在返回 [](读取零副作用,不建目录)。 */
23
+ export declare function readArchiveRecords(homeDir: string): ArchiveEntry[];
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 运行档案写入器(trellis-evolve 任务 10.4)。
3
+ *
4
+ * `archive/records.jsonl` 只追加 JSONL:提案/探针/verdict/备份会话标识/
5
+ * L2 比较记录按发生顺序落行,永不覆盖、不重排、不去重。读取侧严格解析
6
+ * ——档案是治理审计数据,坏行必须带行号抛出而不是静默跳过。
7
+ *
8
+ * 记录格式:`{ kind, recordedAt, payload }`。`kind` 开放(各任务逐步收
9
+ * 敛:proposal/verdict/lesson/L2 比较等),`payload` 为 JSON 安全的具体
10
+ * 记录;`recordedAt` 由写入器打戳(ISO 8601)。
11
+ */
12
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { evolveStateDir } from "./stateDir.js";
15
+ export function archiveRecordsPath(homeDir) {
16
+ return join(evolveStateDir(homeDir), "archive", "records.jsonl");
17
+ }
18
+ /** 只追加一条记录,返回落盘后的完整条目(含打戳)。 */
19
+ export function appendArchiveRecord(homeDir, record, now = () => new Date()) {
20
+ const entry = { kind: record.kind, recordedAt: now().toISOString(), payload: record.payload };
21
+ mkdirSync(join(evolveStateDir(homeDir), "archive"), { recursive: true, mode: 0o700 });
22
+ // 单行约定:JSON.stringify 会把 payload 内的换行转义,行边界天然成立。
23
+ // 新建 0600:档案含提案内容(配置文件片段),不收世界可读。
24
+ appendFileSync(archiveRecordsPath(homeDir), JSON.stringify(entry) + "\n", { mode: 0o600 });
25
+ return entry;
26
+ }
27
+ /** 读回全部档案条目;文件不存在返回 [](读取零副作用,不建目录)。 */
28
+ export function readArchiveRecords(homeDir) {
29
+ const path = archiveRecordsPath(homeDir);
30
+ if (!existsSync(path))
31
+ return [];
32
+ const entries = [];
33
+ const lines = readFileSync(path, "utf-8").split("\n");
34
+ for (const [index, line] of lines.entries()) {
35
+ // 末尾换行产生一条尾随空行,不算记录;中间空行是损坏。
36
+ if (line === "" && index === lines.length - 1)
37
+ continue;
38
+ if (line.trim() === "")
39
+ throw new Error(`archive: 第 ${index + 1} 行为空行(只追加档案不允许空行)`);
40
+ let parsed;
41
+ try {
42
+ parsed = JSON.parse(line);
43
+ }
44
+ catch {
45
+ throw new Error(`archive: 第 ${index + 1} 行不是合法 JSON`);
46
+ }
47
+ const entry = parsed;
48
+ if (typeof entry !== "object" ||
49
+ entry === null ||
50
+ typeof entry.kind !== "string" ||
51
+ typeof entry.recordedAt !== "string" ||
52
+ !("payload" in entry)) {
53
+ throw new Error(`archive: 第 ${index + 1} 行缺少 kind/recordedAt/payload`);
54
+ }
55
+ entries.push(entry);
56
+ }
57
+ return entries;
58
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * secrets 擦除(trellis-evolve 任务 11.1)。
3
+ *
4
+ * 信号写入前,所有字符串字段经此擦除(11.2 的用户纠正文本同样如此)。
5
+ * 模式取已知凭据形态:API token 前缀、Bearer 头、key=value 凭据赋值、
6
+ * PEM 私钥块。不承诺穷尽 —— 主防线是事件层根本不收载荷,这里是纵深。
7
+ */
8
+ /** 对单个字符串字段做 secrets 擦除;非敏感内容原样保留。 */
9
+ export declare function eraseSecrets(text: string): string;
10
+ /** 对记录中所有字符串值递归擦除(载荷级防御)。 */
11
+ export declare function eraseSecretsDeep(value: unknown): unknown;
12
+ /**
13
+ * 凭据形态**字面值**检测(12.2 的 workspace 可提交约束):先把
14
+ * `${VAR}` 引用挖掉(合法引用不算字面值),再用与擦除同源的凭据形态
15
+ * 模式判定剩余文本。命中即 true —— 只认形态,不承诺穷尽。
16
+ */
17
+ export declare function containsCredentialLiteral(text: string): boolean;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * secrets 擦除(trellis-evolve 任务 11.1)。
3
+ *
4
+ * 信号写入前,所有字符串字段经此擦除(11.2 的用户纠正文本同样如此)。
5
+ * 模式取已知凭据形态:API token 前缀、Bearer 头、key=value 凭据赋值、
6
+ * PEM 私钥块。不承诺穷尽 —— 主防线是事件层根本不收载荷,这里是纵深。
7
+ */
8
+ const PATTERNS = [
9
+ // OpenAI / Anthropic 风格:sk-...、sk-ant-...
10
+ [/sk-[A-Za-z0-9_-]{8,}/g, "[REDACTED]"],
11
+ // GitHub token:ghp_/gho_/ghs_/ghr_/github_pat_
12
+ [/gh[pousr]_[A-Za-z0-9]{16,}/g, "[REDACTED]"],
13
+ [/github_pat_[A-Za-z0-9_]{20,}/g, "[REDACTED]"],
14
+ // AWS access key id
15
+ [/AKIA[0-9A-Z]{12,}/g, "[REDACTED]"],
16
+ // JWT(三段 base64url,header 恒以 eyJ 开头)
17
+ [/eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, "[REDACTED]"],
18
+ // Slack token:xoxb/xoxp/xoxa/xoxs/xoxr
19
+ [/xox[bpasr]-[A-Za-z0-9-]{8,}/g, "[REDACTED]"],
20
+ // Bearer 头
21
+ [/Bearer\s+[A-Za-z0-9._~+/=-]{8,}/g, "Bearer [REDACTED]"],
22
+ // key=value / key: value 凭据赋值形态(值若是 Bearer/Basic 头留给上面
23
+ // 的 Bearer 规则处理,避免只吃掉 scheme 词、漏掉真实 token)。
24
+ [
25
+ /((?:api[_-]?key|apikey|token|secret|password|passwd|credential|authorization|access[_-]?key)["']?\s*[:=]\s*["']?)(?!(?:Bearer|Basic|Token)\b)[^\s"'&;,]{4,}/gi,
26
+ "$1[REDACTED]",
27
+ ],
28
+ // PEM 私钥块
29
+ [/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, "[REDACTED]"],
30
+ ];
31
+ /** 对单个字符串字段做 secrets 擦除;非敏感内容原样保留。 */
32
+ export function eraseSecrets(text) {
33
+ let out = text;
34
+ for (const [pattern, replacement] of PATTERNS)
35
+ out = out.replace(pattern, replacement);
36
+ return out;
37
+ }
38
+ /** 对记录中所有字符串值递归擦除(载荷级防御)。 */
39
+ export function eraseSecretsDeep(value) {
40
+ if (typeof value === "string")
41
+ return eraseSecrets(value);
42
+ if (Array.isArray(value))
43
+ return value.map(eraseSecretsDeep);
44
+ if (typeof value === "object" && value !== null) {
45
+ const out = {};
46
+ for (const [key, v] of Object.entries(value))
47
+ out[key] = eraseSecretsDeep(v);
48
+ return out;
49
+ }
50
+ return value;
51
+ }
52
+ /** `${VAR}` 引用(workspace 可提交约束明确允许)。 */
53
+ const VAR_REF = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/g;
54
+ /**
55
+ * 凭据形态**字面值**检测(12.2 的 workspace 可提交约束):先把
56
+ * `${VAR}` 引用挖掉(合法引用不算字面值),再用与擦除同源的凭据形态
57
+ * 模式判定剩余文本。命中即 true —— 只认形态,不承诺穷尽。
58
+ */
59
+ export function containsCredentialLiteral(text) {
60
+ const withoutRefs = text.replace(VAR_REF, "");
61
+ return PATTERNS.some(([pattern]) => {
62
+ pattern.lastIndex = 0;
63
+ return pattern.test(withoutRefs);
64
+ });
65
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 进化订阅者型扩展的 `activate` 包装(design 8.1 定案的装载契约;trees
3
+ * 11.1/11.5 的生产接线)。
4
+ *
5
+ * 装载契约:模块导出 `activate(ctx)` 返回 `{ providers?, subscribers? }`,
6
+ * 由 `~/.trellis/mcp/extensions.yaml` 的清单条目(进化启用命令写入)
7
+ * 指向本模块;gateway 启动时动态 import 并装载。契约的结构形状与
8
+ * `src/lib/hostExtensions.ts` 的 `HostExtension*` 一致——import 边界
9
+ * (10.3)不允许 `src/evolve/` 引 `src/lib/*`,这里以结构类型对接,
10
+ * 由装载集成测试证明形状兼容。
11
+ *
12
+ * 订阅者按级别(activate 时解析,本进程恒定——gateway 按会话拉起):
13
+ * - `off`:零订阅者(结构上零副作用;清单条目本应在置 off 时被移除,
14
+ * 此处是防御)。
15
+ * - `observe` 及以上:recorder(事件 → 信号,enabled 恒真——级别已判)。
16
+ * - `propose`/`auto-A`:另有触发器订阅者(会话结束判定拉起作业、会话
17
+ * 启动补偿判定)。observe 是"只采集信号",不拉起作业。
18
+ */
19
+ import type { HostEvent } from "../host/hostEvents.js";
20
+ /** 清单条目 id(进化启用命令写入 `~/.trellis/mcp/extensions.yaml` 用)。 */
21
+ export declare const EVOLVE_EXTENSION_ID = "evolve";
22
+ /** 装载契约的结构形状(与 src/lib/hostExtensions.ts 同形,不 import)。 */
23
+ interface ActivateContext {
24
+ readonly homeDir: string;
25
+ readonly agent: string;
26
+ readonly workspaceRoot: string | null;
27
+ }
28
+ interface Activation {
29
+ readonly subscribers?: ReadonlyArray<(event: HostEvent) => void>;
30
+ }
31
+ export declare function activate(context: ActivateContext): Activation;
32
+ /**
33
+ * 清单条目的 `load` 模块说明符:本模块的绝对路径(dist 的 .js;经 tsx
34
+ * 直跑源码时为 .ts,动态 import 都能吃)。进化启用命令写清单时取此值。
35
+ */
36
+ export declare function evolveExtensionLoadSpecifier(): string;
37
+ export {};
@@ -0,0 +1,63 @@
1
+ /**
2
+ * 进化订阅者型扩展的 `activate` 包装(design 8.1 定案的装载契约;trees
3
+ * 11.1/11.5 的生产接线)。
4
+ *
5
+ * 装载契约:模块导出 `activate(ctx)` 返回 `{ providers?, subscribers? }`,
6
+ * 由 `~/.trellis/mcp/extensions.yaml` 的清单条目(进化启用命令写入)
7
+ * 指向本模块;gateway 启动时动态 import 并装载。契约的结构形状与
8
+ * `src/lib/hostExtensions.ts` 的 `HostExtension*` 一致——import 边界
9
+ * (10.3)不允许 `src/evolve/` 引 `src/lib/*`,这里以结构类型对接,
10
+ * 由装载集成测试证明形状兼容。
11
+ *
12
+ * 订阅者按级别(activate 时解析,本进程恒定——gateway 按会话拉起):
13
+ * - `off`:零订阅者(结构上零副作用;清单条目本应在置 off 时被移除,
14
+ * 此处是防御)。
15
+ * - `observe` 及以上:recorder(事件 → 信号,enabled 恒真——级别已判)。
16
+ * - `propose`/`auto-A`:另有触发器订阅者(会话结束判定拉起作业、会话
17
+ * 启动补偿判定)。observe 是"只采集信号",不拉起作业。
18
+ */
19
+ import { fileURLToPath } from "node:url";
20
+ import { loadEvolveLevelPolicy, resolveEvolveLevel } from "../host/levels.js";
21
+ import { createSignalRecorder } from "./recorder.js";
22
+ import { maybeLaunchOnSessionEnd, maybeLaunchOnSessionStart } from "./trigger.js";
23
+ /** 清单条目 id(进化启用命令写入 `~/.trellis/mcp/extensions.yaml` 用)。 */
24
+ export const EVOLVE_EXTENSION_ID = "evolve";
25
+ function levelAt(context) {
26
+ const policy = loadEvolveLevelPolicy(context.homeDir);
27
+ return resolveEvolveLevel(policy, {
28
+ agent: context.agent,
29
+ workspaceRoot: context.workspaceRoot,
30
+ });
31
+ }
32
+ export function activate(context) {
33
+ const level = levelAt(context);
34
+ if (level === "off")
35
+ return {};
36
+ const recorder = createSignalRecorder({
37
+ homeDir: context.homeDir,
38
+ enabled: () => true,
39
+ });
40
+ const subscribers = [recorder.handle];
41
+ if (level !== "observe") {
42
+ const policy = loadEvolveLevelPolicy(context.homeDir);
43
+ const options = {
44
+ homeDir: context.homeDir,
45
+ enabled: () => true,
46
+ ...(policy.trigger !== undefined ? { limits: policy.trigger } : {}),
47
+ };
48
+ subscribers.push((event) => {
49
+ if (event.kind === "session_end")
50
+ maybeLaunchOnSessionEnd(options);
51
+ else if (event.kind === "session_start")
52
+ maybeLaunchOnSessionStart(options);
53
+ });
54
+ }
55
+ return { subscribers };
56
+ }
57
+ /**
58
+ * 清单条目的 `load` 模块说明符:本模块的绝对路径(dist 的 .js;经 tsx
59
+ * 直跑源码时为 .ts,动态 import 都能吃)。进化启用命令写清单时取此值。
60
+ */
61
+ export function evolveExtensionLoadSpecifier() {
62
+ return fileURLToPath(import.meta.url);
63
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * L1 doctor-oracle 前闸(trellis-evolve 任务 12.1)。
3
+ *
4
+ * 两副职责:
5
+ *
6
+ * 1. **提案类域分派**:仅 `L1_CONFIG_KINDS` 白名单内的配置类提案进入
7
+ * 提案通道;内容类提案本期不产出(等 L2 选择器执行落地)。design D8
8
+ * "L1 适用域的判定由 L4 治理层按 kind 白名单执行"——本模块就是该判定
9
+ * 点,12.2 的机械校验对提案 kind 的准入与此同源。
10
+ * 2. **确定性候选 verdict 差集**:对真实 baseline 与 staging 候选各运行
11
+ * 一组结构化检查(doctor findings、`sync --check`、secrets audit),
12
+ * 按 (稳定 id, 层归属) 集合差判定。在 L1 适用域内该差集是**唯一
13
+ * verdict**,配置类提案不运行引擎评分链。
14
+ *
15
+ * 可采纳判据(design D8,从严):无新条目 ∧ 全部目标条目被消除 ∧ 其余
16
+ * 条目不变(未声明的消除也算变化)。目标条目 = 提案 evidence 中
17
+ * `doctor-finding` 链接的 `findingId`(即 CheckEntry.id);`Proposal.target`
18
+ * 本身是被编辑面的锚点(spec:"目标为某 MCP server auth 块"、"目标路径
19
+ * 位于某仓库的 .agents/ 内"),不是检查条目——两者职责分开,本闸只消费
20
+ * findingId。目标条目不在 baseline 时"无法确认修复",按不采纳收口。
21
+ *
22
+ * staging 复现层分裂:`createStaging` 拷 scratch 全局 HOME + 临时
23
+ * workspace root(墓碑生效),提案 edits 由本模块把真实面绝对路径重定位
24
+ * 到 scratch(越界不采纳);baseline 与候选都带 `logicalWorkspaceRoot`
25
+ * 归一化,条目比较键含层归属(`workspace(<root>)` 与全局不混)。
26
+ *
27
+ * 失败收口(Risks 8):staging/应用/测量任何意外错误一律"不采纳",
28
+ * 测量结果记 `unknown`(空条目集,绝不表示为零分或通过);域外提案同样
29
+ * 不采纳。全部判定都写入运行档案(kind `"verdict"`,含 baseline 与
30
+ * candidate 两份完整测量)。
31
+ */
32
+ import type { EvidenceLink, Proposal, Verdict, VerdictEntry } from "./types.js";
33
+ import { type ApplyEditsOptions, type ApplyEditsResult, type CheckResult, type CreateStagingOptions, type RunChecksOptions, type Staging } from "../host/index.js";
34
+ /** 档案 kind:一次 L1 verdict(两份完整测量 + 判定原因)。 */
35
+ export declare const VERDICT_ARCHIVE_KIND = "verdict";
36
+ /**
37
+ * L1 适用域:配置类 kind 白名单(design D8)。名单内提案进入提案通道,
38
+ * L1 差集作唯一 verdict;名单外(含内容类——skill/记忆/指令正文等)本
39
+ * 期不产出提案,等 L2 选择器执行落地。命名与 12.2 rule-based/editor
40
+ * 提案的产出侧保持一致。
41
+ */
42
+ export declare const L1_CONFIG_KINDS: ReadonlySet<string>;
43
+ export interface ProposalDomainDispatch {
44
+ readonly domain: "config" | "content";
45
+ /** 仅配置类进入提案通道;内容类本期不产出。 */
46
+ readonly entersProposalChannel: boolean;
47
+ readonly note: string;
48
+ }
49
+ /** 提案类域分派:白名单内 = 配置类(进通道),其余 = 内容类(不产出)。 */
50
+ export declare function dispatchProposalDomain(kind: string): ProposalDomainDispatch;
51
+ /**
52
+ * 条目比较键:(稳定 id, 层归属)。"条目 SHALL 带着层归属参与比较"——
53
+ * 同一 id 在全局层与 workspace 层是两条不同记录,不互相抵消。
54
+ */
55
+ export declare function entryKey(entry: VerdictEntry): string;
56
+ export interface VerdictDiff {
57
+ /** 候选有、baseline 没有(引入的回归)。 */
58
+ readonly introduced: VerdictEntry[];
59
+ /** baseline 有、候选没有(被消除的条目)。 */
60
+ readonly resolved: VerdictEntry[];
61
+ }
62
+ /** 按 (id, layer) 做集合差;message 不参与比较(host/checks.ts 契约)。 */
63
+ export declare function diffVerdictEntries(baseline: readonly VerdictEntry[], candidate: readonly VerdictEntry[]): VerdictDiff;
64
+ /** 提案 evidence 中的目标条目(evidence 的 doctor-finding.findingId
65
+ * 即 CheckEntry.id;`Proposal.target` 是编辑面锚点,不在此列)。 */
66
+ export declare function doctorFindingTargetIds(evidence: readonly EvidenceLink[]): string[];
67
+ export interface EvaluateL1Options {
68
+ /** 真实面 HOME(档案也写在这里的 `~/.trellis/evolve/`)。 */
69
+ readonly homeDir: string;
70
+ /** 被测上下文的 workspace root;缺省/null = 纯全局上下文。 */
71
+ readonly workspaceRoot?: string | null;
72
+ readonly proposal: Proposal;
73
+ /** 注入缝(场景测试的强制报错收口);缺省走 host 真实实现。 */
74
+ readonly runChecksFn?: (options: RunChecksOptions) => Promise<CheckResult>;
75
+ readonly createStagingFn?: (options: CreateStagingOptions) => Staging;
76
+ readonly applyEditsFn?: (options: ApplyEditsOptions) => Promise<ApplyEditsResult>;
77
+ /** 时间源(毫秒);缺省 Date.now(档案打戳)。 */
78
+ readonly now?: () => number;
79
+ }
80
+ export interface L1GateResult {
81
+ readonly decision: "acceptable" | "not-acceptable";
82
+ readonly reason: string;
83
+ readonly domain: "config" | "content";
84
+ readonly verdict: Verdict;
85
+ readonly targetIds: readonly string[];
86
+ /** 引入的新条目(不可采纳时入档)。 */
87
+ readonly introduced: readonly VerdictEntry[];
88
+ readonly resolvedIds: readonly string[];
89
+ }
90
+ /**
91
+ * L1 前闸完整判定:域分派 → staging 副本(层分裂复现)→ edits 重定位并
92
+ * 应用 → baseline/候选各跑三组结构化检查 → (id, layer) 差集 → 判定并
93
+ * 入档。任何意外错误按"不采纳"收口、测量记 unknown;不吞掉入档写失败。
94
+ */
95
+ export declare function evaluateL1Proposal(options: EvaluateL1Options): Promise<L1GateResult>;