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,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一次性作业的作业体(trellis-evolve 任务 11.5;14.1 fixture E2E 的流水
|
|
3
|
+
* 在此落地为生产入口)。作业体是下述 rule-based 治理流水;引擎内核
|
|
4
|
+
* (packages/evolve-engine 的 schedule/recursion/proposer/selection/judge)
|
|
5
|
+
* golden 锚定但生产未经此消费,待 L2/探针任务集接入(review-fixes 纠偏
|
|
6
|
+
* 旧注"作业即运行移植引擎"的名不副实)。
|
|
7
|
+
*
|
|
8
|
+
* 流水(顺序即依赖,与 fixtureE2e 同链):
|
|
9
|
+
* 0. 级别闸:没有任何 propose/auto-A 设置 → 零副作用返回(触发器只在
|
|
10
|
+
* propose+ 拉起;这里是直接 spawn 的防御)。
|
|
11
|
+
* 1. 可选源探测(11.3):agent-bridge 缺席记 unavailable,不失败。
|
|
12
|
+
* 2. 信号合并(自上次作业的信号合并时刻以来;首次为全部,11.5"未
|
|
13
|
+
* 消费"语义)。
|
|
14
|
+
* 3. 检查发现:全局 + 信号层归属出现过/级别策略声明过的 workspace,
|
|
15
|
+
* 各自 `runChecks`(L1 的 doctor oracle)。观察面即发现面——只有
|
|
16
|
+
* 进过信号/策略的 workspace 会被扫描(与"信号是观察记录"一致)。
|
|
17
|
+
* 4. 提案草稿:rule-based 通道——`proposeFromSignals` 的信号对齐路径
|
|
18
|
+
* ∪ 当前发现直接 `ruleBasedProposal`(同一内核、提案 id 同源
|
|
19
|
+
* `rule-<findingId>` 天然去重;已入 pending 的跳过)。editor/spawn
|
|
20
|
+
* 通道(design D7)在 `runDelegatedCall` 就绪前不在作业里拉真实
|
|
21
|
+
* CLI 会话,见 `ProposalChannelAvailability` 注释。
|
|
22
|
+
* 5. 逐草稿治理:域分派(配置类才进通道)→ 机械校验(无 editor 不
|
|
23
|
+
* 重试)→ L1 前闸(verdict 差集,唯一 verdict)→ 风险分类(不采信
|
|
24
|
+
* 自报)→ 采纳门(A 仅 auto-A 且闸通过自动采纳并通知;B 永远待批)。
|
|
25
|
+
* 草稿的提案/采纳级别按其目标层解析(workspace > default;agent
|
|
26
|
+
* 粒度只闸观察与触发,不进提案产出——提案的来源 agent 不可考,
|
|
27
|
+
* 发现是 doctor 检查面而非 agent 行为)。
|
|
28
|
+
* 6. 应用阶段(12.5):全部已批准未应用的编辑在同一备份会话写入;
|
|
29
|
+
* 按 delivery 分类对原生渲染 agent 触发同步(host 门面 `triggerRenderSync`)。
|
|
30
|
+
* 7. lesson(12.6):本次应用的每条提案蒸馏一条 lesson;记忆启用走
|
|
31
|
+
* 记忆管道,否则只留档案(skipped 不是 failed)。
|
|
32
|
+
*
|
|
33
|
+
* 每步都只经 `src/host/*` 门面与本目录模块(边界 10.3);无模型调用、
|
|
34
|
+
* 不 spawn 真实 agent CLI。摘要返回给 jobMain 并入 job-run 档案。
|
|
35
|
+
*/
|
|
36
|
+
import { classifyManagedObservation, isMemoryDeliveryEnabled, loadEvolveLevelPolicy, resolveEvolveLevel, runChecks, triggerRenderSync, } from "../host/index.js";
|
|
37
|
+
import { appendArchiveRecord, readArchiveRecords } from "./archive.js";
|
|
38
|
+
import { applyApprovedProposals } from "./apply.js";
|
|
39
|
+
import { dispatchProposalDomain, evaluateL1Proposal } from "./gate.js";
|
|
40
|
+
import { recordLesson } from "./lesson.js";
|
|
41
|
+
import { decideProposal, enqueueProposal, readPendingRecord, readPendingRecords, safeProposalFileName, } from "./pending.js";
|
|
42
|
+
import { proposeFromSignals, ruleBasedProposal } from "./propose.js";
|
|
43
|
+
import { readAllSignals } from "./recorder.js";
|
|
44
|
+
import { classifyProposalRisk, evaluateAdoptionGate, retargetNotificationRollback } from "./risk.js";
|
|
45
|
+
import { probeAgentBridgeSource } from "./sources.js";
|
|
46
|
+
import { PROPOSAL_ARCHIVE_KIND, lastSignalWatermarkMs, } from "./trigger.js";
|
|
47
|
+
import { validateProposalWithRetry } from "./validate.js";
|
|
48
|
+
/** 作业级闸:任一粒度出现 propose/auto-A 才值得运行(observe 只采集)。
|
|
49
|
+
* `trellis evolve run` 的早退判定与作业体同源(1.3)。 */
|
|
50
|
+
export function anyProposalLevel(policy) {
|
|
51
|
+
const levels = [
|
|
52
|
+
policy.defaultLevel,
|
|
53
|
+
...Object.values(policy.agents),
|
|
54
|
+
...Object.values(policy.workspaces),
|
|
55
|
+
];
|
|
56
|
+
return levels.some((level) => level === "propose" || level === "auto-A");
|
|
57
|
+
}
|
|
58
|
+
/** 草稿目标层的生效级别(workspace > agent > default > off)。 */
|
|
59
|
+
function draftLevel(policy, layer) {
|
|
60
|
+
return resolveEvolveLevel(policy, layer.kind === "workspace" ? { workspaceRoot: layer.root } : {});
|
|
61
|
+
}
|
|
62
|
+
/** 缺省草稿组装:信号对齐 ∪ 当前发现直提,id 同源去重。 */
|
|
63
|
+
function defaultPropose(input) {
|
|
64
|
+
// editor/spawn 通道就绪前,生产走 rule-based 兜底(design D7:委派不可
|
|
65
|
+
// 用时 doctor finding 形态信号走 rule-based)。ruleBasedProposal 是
|
|
66
|
+
// proposeFromSignals 的同一内核;这里补上"无对齐信号但发现仍在"的条目
|
|
67
|
+
// ——发现由作业的 runChecks 观察(9.4①:doctor/sync 检查即原生渲染
|
|
68
|
+
// agent 的信号来源),生产信号面里事件信号 stableId 恒为 null。
|
|
69
|
+
const loop = proposeFromSignals({
|
|
70
|
+
signals: input.signals,
|
|
71
|
+
findings: input.findings,
|
|
72
|
+
availability: { delegate: false, spawn: false },
|
|
73
|
+
now: input.now,
|
|
74
|
+
});
|
|
75
|
+
const drafts = [...loop.proposals];
|
|
76
|
+
const covered = new Set(drafts.map((draft) => draft.id));
|
|
77
|
+
for (const entry of input.findings) {
|
|
78
|
+
const draft = ruleBasedProposal(entry, { now: input.now });
|
|
79
|
+
if (draft === null || covered.has(draft.id))
|
|
80
|
+
continue;
|
|
81
|
+
covered.add(draft.id);
|
|
82
|
+
drafts.push(draft);
|
|
83
|
+
}
|
|
84
|
+
return drafts;
|
|
85
|
+
}
|
|
86
|
+
/** 上一作业的信号水位线(优先"信号合并时刻",旧档案回退 endedAtMs);
|
|
87
|
+
* 从未跑过为 null。 */
|
|
88
|
+
function lastSignalWatermark(homeDir) {
|
|
89
|
+
return lastSignalWatermarkMs(readArchiveRecords(homeDir));
|
|
90
|
+
}
|
|
91
|
+
export async function runEvolveJob(homeDir, options = {}) {
|
|
92
|
+
const now = options.now ?? Date.now;
|
|
93
|
+
const empty = {
|
|
94
|
+
signalsConsidered: 0,
|
|
95
|
+
findingsConsidered: 0,
|
|
96
|
+
draftCount: 0,
|
|
97
|
+
enqueuedIds: [],
|
|
98
|
+
autoAdoptedIds: [],
|
|
99
|
+
discarded: [],
|
|
100
|
+
skippedNotes: [],
|
|
101
|
+
applied: { runId: null, appliedProposalIds: [], syncExitCode: null, syncError: null, effects: [] },
|
|
102
|
+
lessonIds: [],
|
|
103
|
+
notices: [],
|
|
104
|
+
optionalSources: [],
|
|
105
|
+
signalsMergedAtMs: null,
|
|
106
|
+
};
|
|
107
|
+
// ── 0. 级别闸 ─────────────────────────────────────────────────────────
|
|
108
|
+
const policy = loadEvolveLevelPolicy(homeDir);
|
|
109
|
+
if (!anyProposalLevel(policy))
|
|
110
|
+
return empty;
|
|
111
|
+
// ── 1. 可选源探测(缺席合法,记状态不失败)────────────────────────────
|
|
112
|
+
const optionalSources = [probeAgentBridgeSource(homeDir)];
|
|
113
|
+
// ── 2. 信号合并(自上次作业的信号水位线以来)────────────────────────
|
|
114
|
+
const signalsMergedAtMs = now();
|
|
115
|
+
const signals = readAllSignals(homeDir, lastSignalWatermark(homeDir));
|
|
116
|
+
// ── 3. 检查发现(全局 + 观察面内的 workspace)────────────────────────
|
|
117
|
+
const workspaceRoots = new Set();
|
|
118
|
+
for (const signal of signals) {
|
|
119
|
+
if (signal.layer.kind === "workspace")
|
|
120
|
+
workspaceRoots.add(signal.layer.root);
|
|
121
|
+
}
|
|
122
|
+
for (const root of Object.keys(policy.workspaces))
|
|
123
|
+
workspaceRoots.add(root);
|
|
124
|
+
const findingsById = new Map();
|
|
125
|
+
const runOne = async (runOptions) => {
|
|
126
|
+
const result = await runChecks(runOptions);
|
|
127
|
+
for (const entry of result.entries)
|
|
128
|
+
findingsById.set(entry.id, entry);
|
|
129
|
+
};
|
|
130
|
+
await runOne({ homeDir });
|
|
131
|
+
for (const root of workspaceRoots)
|
|
132
|
+
await runOne({ homeDir, workspaceRoot: root });
|
|
133
|
+
const findings = [...findingsById.values()];
|
|
134
|
+
// ── 4. 提案草稿 ───────────────────────────────────────────────────────
|
|
135
|
+
const proposeFn = options.proposeFn ?? defaultPropose;
|
|
136
|
+
const drafts = proposeFn({ homeDir, signals, findings, now });
|
|
137
|
+
// ── 5. 逐草稿治理 ─────────────────────────────────────────────────────
|
|
138
|
+
const enqueuedIds = [];
|
|
139
|
+
const autoAdoptedIds = [];
|
|
140
|
+
const discarded = [];
|
|
141
|
+
const skippedNotes = [];
|
|
142
|
+
const notices = [];
|
|
143
|
+
// 自动采纳通知先收集、应用后定稿:决策时备份会话尚不存在,回滚行只能
|
|
144
|
+
// 是泛化占位,拿到真实 runId 再重写(review-fixes ❌5)。
|
|
145
|
+
const adoptedNotifications = [];
|
|
146
|
+
const known = new Set(readPendingRecords(homeDir).map((record) => record.id));
|
|
147
|
+
for (const draft of drafts) {
|
|
148
|
+
if (known.has(draft.id)) {
|
|
149
|
+
skippedNotes.push(`${draft.id}:已有提案记录,不重复产出`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const level = draftLevel(policy, draft.layer);
|
|
153
|
+
if (level !== "propose" && level !== "auto-A") {
|
|
154
|
+
skippedNotes.push(`${draft.id}:目标层级别 ${level},不产出提案`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const domain = dispatchProposalDomain(draft.kind);
|
|
158
|
+
if (!domain.entersProposalChannel) {
|
|
159
|
+
skippedNotes.push(`${draft.id}:${domain.note}`);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const validated = await validateProposalWithRetry({ homeDir, proposal: draft, now });
|
|
163
|
+
if (validated.status === "discarded") {
|
|
164
|
+
discarded.push({ id: draft.id, code: validated.code, reason: validated.reason });
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const proposal = validated.proposal;
|
|
168
|
+
const evaluateL1 = options.evaluateL1Fn ?? evaluateL1Proposal;
|
|
169
|
+
const gate = await evaluateL1({
|
|
170
|
+
homeDir,
|
|
171
|
+
workspaceRoot: proposal.layer.kind === "workspace" ? proposal.layer.root : null,
|
|
172
|
+
proposal,
|
|
173
|
+
now,
|
|
174
|
+
});
|
|
175
|
+
const classification = classifyProposalRisk(proposal, {
|
|
176
|
+
homeDir,
|
|
177
|
+
...(validated.validation.needsExplicitApproval !== undefined
|
|
178
|
+
? { needsExplicitApproval: validated.validation.needsExplicitApproval }
|
|
179
|
+
: {}),
|
|
180
|
+
});
|
|
181
|
+
const adoption = evaluateAdoptionGate({
|
|
182
|
+
level,
|
|
183
|
+
classification,
|
|
184
|
+
probePassed: gate.decision === "acceptable",
|
|
185
|
+
proposal,
|
|
186
|
+
});
|
|
187
|
+
if (gate.decision !== "acceptable") {
|
|
188
|
+
skippedNotes.push(`${proposal.id}:L1 前闸不可采纳(${gate.reason})`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
enqueueProposal({
|
|
192
|
+
homeDir,
|
|
193
|
+
proposal,
|
|
194
|
+
riskClass: classification.riskClass,
|
|
195
|
+
riskRuleId: classification.ruleId,
|
|
196
|
+
now,
|
|
197
|
+
});
|
|
198
|
+
appendArchiveRecord(homeDir, {
|
|
199
|
+
kind: PROPOSAL_ARCHIVE_KIND,
|
|
200
|
+
payload: { proposalId: proposal.id, riskClass: classification.riskClass, riskRuleId: classification.ruleId },
|
|
201
|
+
}, () => new Date(now()));
|
|
202
|
+
known.add(proposal.id);
|
|
203
|
+
if (adoption.action === "auto-adopt") {
|
|
204
|
+
decideProposal({ homeDir, id: proposal.id, decision: "approved", now });
|
|
205
|
+
autoAdoptedIds.push(proposal.id);
|
|
206
|
+
adoptedNotifications.push(adoption.notification);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
enqueuedIds.push(proposal.id);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ── 6. 应用阶段(全部已批准未应用,同一备份会话)──────────────────────
|
|
213
|
+
const agents = options.applyAgents ?? classifyManagedObservation(homeDir).map((entry) => ({
|
|
214
|
+
agent: entry.agent,
|
|
215
|
+
observation: entry.observation,
|
|
216
|
+
}));
|
|
217
|
+
const triggerSync = options.triggerSync ?? (async () => await triggerRenderSync({ homeDir }));
|
|
218
|
+
const applied = await applyApprovedProposals({
|
|
219
|
+
homeDir,
|
|
220
|
+
agents,
|
|
221
|
+
triggerSync,
|
|
222
|
+
now: () => new Date(now()),
|
|
223
|
+
});
|
|
224
|
+
for (const id of applied.skippedInvalidIds) {
|
|
225
|
+
skippedNotes.push(`${id}:应用前重校验不通过(pending 磁盘状态漂移?),跳过`);
|
|
226
|
+
}
|
|
227
|
+
// 自动采纳通知定稿:回滚行重写为真实备份会话标识(应用失败/未开会话时
|
|
228
|
+
// 保留泛化占位——没有真实 runId 可指)。
|
|
229
|
+
for (const notification of adoptedNotifications) {
|
|
230
|
+
notices.push(applied.runId === null
|
|
231
|
+
? notification.message
|
|
232
|
+
: retargetNotificationRollback(notification, `trellis evolve rollback ${applied.runId}`).message);
|
|
233
|
+
}
|
|
234
|
+
// ── 7. lesson(本次应用的每条提案蒸馏一条)───────────────────────────
|
|
235
|
+
const memoryEnabled = isMemoryDeliveryEnabled(homeDir);
|
|
236
|
+
const lessonIds = [];
|
|
237
|
+
for (const id of applied.appliedProposalIds) {
|
|
238
|
+
const record = readPendingRecord(homeDir, id);
|
|
239
|
+
if (record === null)
|
|
240
|
+
continue;
|
|
241
|
+
const lesson = {
|
|
242
|
+
id: `lesson-${safeProposalFileName(id)}`,
|
|
243
|
+
createdAt: now(),
|
|
244
|
+
cue: record.proposal.summary,
|
|
245
|
+
body: `应用阶段已写入备份会话 ${applied.runId ?? "?"}(风险 ${record.riskClass});` +
|
|
246
|
+
`编辑:${record.proposal.edits.map((edit) => `${edit.op} ${edit.path}`).join("; ")}(kind ${record.proposal.kind})。`,
|
|
247
|
+
evidence: record.proposal.evidence,
|
|
248
|
+
layer: record.proposal.layer,
|
|
249
|
+
};
|
|
250
|
+
recordLesson({ homeDir, lesson, memoryEnabled });
|
|
251
|
+
lessonIds.push(lesson.id);
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
signalsConsidered: signals.length,
|
|
255
|
+
findingsConsidered: findings.length,
|
|
256
|
+
draftCount: drafts.length,
|
|
257
|
+
enqueuedIds,
|
|
258
|
+
autoAdoptedIds,
|
|
259
|
+
discarded,
|
|
260
|
+
skippedNotes,
|
|
261
|
+
applied: {
|
|
262
|
+
runId: applied.runId,
|
|
263
|
+
appliedProposalIds: applied.appliedProposalIds,
|
|
264
|
+
syncExitCode: applied.syncExitCode,
|
|
265
|
+
syncError: applied.syncError,
|
|
266
|
+
effects: applied.effects,
|
|
267
|
+
},
|
|
268
|
+
lessonIds,
|
|
269
|
+
notices,
|
|
270
|
+
optionalSources,
|
|
271
|
+
signalsMergedAtMs,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 作业壳(trellis-evolve-run-backfill 任务 1.1)。
|
|
3
|
+
*
|
|
4
|
+
* jobMain(detached 入口)与 `trellis evolve run`(前台手动触发)共享的
|
|
5
|
+
* 一次作业运行外壳:抢锁 → 跑作业体 → 落 job-run 档案 → 释锁,错误收口
|
|
6
|
+
* (失败也落档案——不落档案会让水位线与冷却都不推进,同一确定性错误在
|
|
7
|
+
* 每次会话结束被立即重拉成崩溃循环)。job-run payload 带 `trigger` 触发
|
|
8
|
+
* 来源(`event` / `manual` / `chain`);旧档案无该字段时消费端视作 `event`。
|
|
9
|
+
*
|
|
10
|
+
* 本模块只做壳:作业体在 `jobRun.js`(rule-based 治理流水),锁与档案
|
|
11
|
+
* 原语在 `trigger.js`。jobMain 仍是唯一 detached 入口(链起用),CLI
|
|
12
|
+
* 前台调用本壳即手动触发(D1:不另写执行路径)。
|
|
13
|
+
*/
|
|
14
|
+
import { type EvolveJobSummary } from "./jobRun.js";
|
|
15
|
+
import { type JobTriggerKind, type TryLaunchJobOptions } from "./trigger.js";
|
|
16
|
+
/** 触发来源(单一来源在 trigger.js;此处 re-export 保持壳类型面完整)。 */
|
|
17
|
+
export type { JobTriggerKind } from "./trigger.js";
|
|
18
|
+
export interface RunJobShellOptions {
|
|
19
|
+
/** 触发来源,入 job-run 档案。 */
|
|
20
|
+
readonly trigger: JobTriggerKind;
|
|
21
|
+
/** 持锁 N 毫秒后再跑作业体(jobMain `--hold-ms` 测试缝;生产不传)。 */
|
|
22
|
+
readonly holdMs?: number;
|
|
23
|
+
/** 时间源(毫秒);缺省 Date.now(测试注入固定时钟)。 */
|
|
24
|
+
readonly now?: () => number;
|
|
25
|
+
/** 补跑链起的作业命令/ spawn 注入缝(测试);缺省真实 jobMain 与 spawn。 */
|
|
26
|
+
readonly chainJobCommand?: TryLaunchJobOptions["jobCommand"];
|
|
27
|
+
readonly chainSpawnFn?: TryLaunchJobOptions["spawnFn"];
|
|
28
|
+
}
|
|
29
|
+
export type RunJobShellResult =
|
|
30
|
+
/** 抢锁失败:已有作业在跑(竞态窗口),不做任何工作、不排队。 */
|
|
31
|
+
{
|
|
32
|
+
readonly status: "lock-busy";
|
|
33
|
+
} | {
|
|
34
|
+
readonly status: "ok";
|
|
35
|
+
readonly summary: EvolveJobSummary;
|
|
36
|
+
}
|
|
37
|
+
/** 作业体抛错:job-run 档案已落 error 记录(推进冷却)。 */
|
|
38
|
+
| {
|
|
39
|
+
readonly status: "error";
|
|
40
|
+
readonly message: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* 跑一次作业(壳语义):锁由本壳持有,收尾无论成败都释锁并落 job-run。
|
|
44
|
+
* lock-busy 时不落任何档案(一次根本没发生的运行不在档案说谎)。
|
|
45
|
+
*/
|
|
46
|
+
export declare function runJobShell(homeDir: string, options: RunJobShellOptions): Promise<RunJobShellResult>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 作业壳(trellis-evolve-run-backfill 任务 1.1)。
|
|
3
|
+
*
|
|
4
|
+
* jobMain(detached 入口)与 `trellis evolve run`(前台手动触发)共享的
|
|
5
|
+
* 一次作业运行外壳:抢锁 → 跑作业体 → 落 job-run 档案 → 释锁,错误收口
|
|
6
|
+
* (失败也落档案——不落档案会让水位线与冷却都不推进,同一确定性错误在
|
|
7
|
+
* 每次会话结束被立即重拉成崩溃循环)。job-run payload 带 `trigger` 触发
|
|
8
|
+
* 来源(`event` / `manual` / `chain`);旧档案无该字段时消费端视作 `event`。
|
|
9
|
+
*
|
|
10
|
+
* 本模块只做壳:作业体在 `jobRun.js`(rule-based 治理流水),锁与档案
|
|
11
|
+
* 原语在 `trigger.js`。jobMain 仍是唯一 detached 入口(链起用),CLI
|
|
12
|
+
* 前台调用本壳即手动触发(D1:不另写执行路径)。
|
|
13
|
+
*/
|
|
14
|
+
import { loadEvolveLevelPolicy } from "../host/index.js";
|
|
15
|
+
import { appendArchiveRecord } from "./archive.js";
|
|
16
|
+
import { runEvolveJob } from "./jobRun.js";
|
|
17
|
+
import { acquireJobLock, chainAfterJob, consumeRunRequest, releaseJobLock, JOB_RUN_KIND, } from "./trigger.js";
|
|
18
|
+
/**
|
|
19
|
+
* 跑一次作业(壳语义):锁由本壳持有,收尾无论成败都释锁并落 job-run。
|
|
20
|
+
* lock-busy 时不落任何档案(一次根本没发生的运行不在档案说谎)。
|
|
21
|
+
*/
|
|
22
|
+
export async function runJobShell(homeDir, options) {
|
|
23
|
+
const now = options.now ?? Date.now;
|
|
24
|
+
const pid = process.pid;
|
|
25
|
+
if (!acquireJobLock(homeDir, pid, now())) {
|
|
26
|
+
return { status: "lock-busy" };
|
|
27
|
+
}
|
|
28
|
+
// 任何作业启动即清补跑标记(D3:该请求已被本次运行满足)。
|
|
29
|
+
consumeRunRequest(homeDir);
|
|
30
|
+
const startedAtMs = now();
|
|
31
|
+
try {
|
|
32
|
+
if (options.holdMs !== undefined && options.holdMs > 0) {
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, options.holdMs));
|
|
34
|
+
}
|
|
35
|
+
const summary = await runEvolveJob(homeDir, { now });
|
|
36
|
+
appendArchiveRecord(homeDir, {
|
|
37
|
+
kind: JOB_RUN_KIND,
|
|
38
|
+
payload: {
|
|
39
|
+
pid,
|
|
40
|
+
startedAtMs,
|
|
41
|
+
endedAtMs: now(),
|
|
42
|
+
trigger: options.trigger,
|
|
43
|
+
signalsMergedAtMs: summary.signalsMergedAtMs,
|
|
44
|
+
summary,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return { status: "ok", summary };
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
+
try {
|
|
52
|
+
appendArchiveRecord(homeDir, {
|
|
53
|
+
kind: JOB_RUN_KIND,
|
|
54
|
+
payload: { pid, startedAtMs, endedAtMs: now(), trigger: options.trigger, error: message },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// 档案写入自身失败(如磁盘满)时不再连锁,锁仍由 finally 释放。
|
|
59
|
+
}
|
|
60
|
+
return { status: "error", message };
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
releaseJobLock(homeDir, pid);
|
|
64
|
+
// 收尾(释锁后)消费补跑标记并链起至多一次补跑(D3):运行期间到达的
|
|
65
|
+
// 请求塌缩为一个标记,在这里被消费;无标记不链。
|
|
66
|
+
const policy = loadEvolveLevelPolicy(homeDir);
|
|
67
|
+
chainAfterJob({
|
|
68
|
+
homeDir,
|
|
69
|
+
now,
|
|
70
|
+
...(policy.trigger !== undefined ? { limits: policy.trigger } : {}),
|
|
71
|
+
...(options.chainJobCommand !== undefined ? { jobCommand: options.chainJobCommand } : {}),
|
|
72
|
+
...(options.chainSpawnFn !== undefined ? { spawnFn: options.chainSpawnFn } : {}),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2 匿名前后对比记录协议(trellis-evolve 任务 13.1)。
|
|
3
|
+
*
|
|
4
|
+
* spec"匿名前后对比记录协议(L2)":内容类对比的比较记录以类型化档案
|
|
5
|
+
* 格式持久化——baseline 与候选各自的运行标识、冻结的交付物引用、按
|
|
6
|
+
* 任务设种子的匿名标识映射、选择器结果与失败收口的 verdict;base 保留在
|
|
7
|
+
* 比较中;记录格式表达两种收口:选中 baseline ⟹ 不采纳,选择器失败 ⟹
|
|
8
|
+
* invalid。本模块只定义记录格式与边界;选择器执行(运行比较与裁决)不
|
|
9
|
+
* 在本期范围。
|
|
10
|
+
*
|
|
11
|
+
* 匿名性:呈现给选择器的匿名标识(anonId)由任务种子 + 运行标识派生,
|
|
12
|
+
* 本身不含角色词;选择器视图(`presentL2Arms`)只给 anonId 与冻结交付
|
|
13
|
+
* 物引用,不带 role/runId。角色映射只存在于档案(审计用)。
|
|
14
|
+
*/
|
|
15
|
+
import type { ArchiveEntry } from "./archive.js";
|
|
16
|
+
/** 运行档案 kind:一条 L2 比较记录。 */
|
|
17
|
+
export declare const L2_COMPARISON_ARCHIVE_KIND = "l2-comparison";
|
|
18
|
+
export type L2ArmRole = "baseline" | "candidate";
|
|
19
|
+
/** 冻结的交付物引用:标签 + 内容摘要键(切片任何变化即键变)。 */
|
|
20
|
+
export interface L2FrozenArtifact {
|
|
21
|
+
readonly label: string;
|
|
22
|
+
readonly digest: string;
|
|
23
|
+
}
|
|
24
|
+
export interface L2Arm {
|
|
25
|
+
/** 种子派生的匿名标识(呈现面);不含角色词。 */
|
|
26
|
+
readonly anonId: string;
|
|
27
|
+
/** 该臂自己的运行标识。 */
|
|
28
|
+
readonly runId: string;
|
|
29
|
+
readonly role: L2ArmRole;
|
|
30
|
+
readonly artifacts: readonly L2FrozenArtifact[];
|
|
31
|
+
}
|
|
32
|
+
/** 按任务设种子:每条探针任务一个种子,匿名标识由此派生。 */
|
|
33
|
+
export interface L2TaskSeed {
|
|
34
|
+
readonly task: string;
|
|
35
|
+
readonly seed: string;
|
|
36
|
+
}
|
|
37
|
+
export type L2SelectorResult = {
|
|
38
|
+
readonly status: "selected";
|
|
39
|
+
readonly anonId: string;
|
|
40
|
+
} | {
|
|
41
|
+
readonly status: "failed";
|
|
42
|
+
readonly reason: string;
|
|
43
|
+
};
|
|
44
|
+
/** 失败收口的 verdict:
|
|
45
|
+
* - selected baseline → not-adopted(不采纳);
|
|
46
|
+
* - 选择器失败 / 任一臂未交付 → invalid(同样不采纳,收口形态不同)。 */
|
|
47
|
+
export type L2Verdict = {
|
|
48
|
+
readonly kind: "adopt-candidate";
|
|
49
|
+
readonly anonId: string;
|
|
50
|
+
} | {
|
|
51
|
+
readonly kind: "not-adopted-baseline";
|
|
52
|
+
readonly anonId: string;
|
|
53
|
+
} | {
|
|
54
|
+
readonly kind: "invalid";
|
|
55
|
+
readonly reason: string;
|
|
56
|
+
};
|
|
57
|
+
export interface L2ComparisonRecord {
|
|
58
|
+
readonly comparisonId: string;
|
|
59
|
+
readonly createdAt: number;
|
|
60
|
+
readonly taskSeeds: readonly L2TaskSeed[];
|
|
61
|
+
/** baseline 与候选全在(base 留池,13.2 的偏差在引擎侧,记录层照存)。 */
|
|
62
|
+
readonly arms: readonly L2Arm[];
|
|
63
|
+
readonly selector: L2SelectorResult;
|
|
64
|
+
readonly verdict: L2Verdict;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 匿名标识:按任务种子 + 运行标识派生的稳定短哈希。构造上不可能
|
|
68
|
+
* 含角色词——呈现面与档案里的值同源,泄露面为零。
|
|
69
|
+
*/
|
|
70
|
+
export declare function anonArmId(taskSeeds: readonly L2TaskSeed[], runId: string): string;
|
|
71
|
+
export interface BuildL2ComparisonInput {
|
|
72
|
+
readonly comparisonId: string;
|
|
73
|
+
readonly createdAt: number;
|
|
74
|
+
readonly taskSeeds: readonly L2TaskSeed[];
|
|
75
|
+
/** 每臂运行标识 + 角色 + 冻结交付物;anonId 由本函数派生。 */
|
|
76
|
+
readonly arms: readonly {
|
|
77
|
+
readonly runId: string;
|
|
78
|
+
readonly role: L2ArmRole;
|
|
79
|
+
readonly artifacts: readonly L2FrozenArtifact[];
|
|
80
|
+
}[];
|
|
81
|
+
readonly selector: L2SelectorResult;
|
|
82
|
+
}
|
|
83
|
+
/** 由臂与选择器结果构建完整记录(派生匿名映射 + 失败收口 verdict)。 */
|
|
84
|
+
export declare function buildL2Comparison(input: BuildL2ComparisonInput): L2ComparisonRecord;
|
|
85
|
+
/** 追加一条比较记录到运行档案。 */
|
|
86
|
+
export declare function appendL2Comparison(homeDir: string, record: L2ComparisonRecord, now?: () => Date): ArchiveEntry;
|
|
87
|
+
/** 读回全部比较记录(档案读取,零副作用)。 */
|
|
88
|
+
export declare function readL2Comparisons(homeDir: string): L2ComparisonRecord[];
|
|
89
|
+
/** 选择器视图:只给匿名标识与冻结交付物引用——不带 role/runId。 */
|
|
90
|
+
export declare function presentL2Arms(record: L2ComparisonRecord): {
|
|
91
|
+
readonly anonId: string;
|
|
92
|
+
readonly artifacts: readonly L2FrozenArtifact[];
|
|
93
|
+
}[];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2 匿名前后对比记录协议(trellis-evolve 任务 13.1)。
|
|
3
|
+
*
|
|
4
|
+
* spec"匿名前后对比记录协议(L2)":内容类对比的比较记录以类型化档案
|
|
5
|
+
* 格式持久化——baseline 与候选各自的运行标识、冻结的交付物引用、按
|
|
6
|
+
* 任务设种子的匿名标识映射、选择器结果与失败收口的 verdict;base 保留在
|
|
7
|
+
* 比较中;记录格式表达两种收口:选中 baseline ⟹ 不采纳,选择器失败 ⟹
|
|
8
|
+
* invalid。本模块只定义记录格式与边界;选择器执行(运行比较与裁决)不
|
|
9
|
+
* 在本期范围。
|
|
10
|
+
*
|
|
11
|
+
* 匿名性:呈现给选择器的匿名标识(anonId)由任务种子 + 运行标识派生,
|
|
12
|
+
* 本身不含角色词;选择器视图(`presentL2Arms`)只给 anonId 与冻结交付
|
|
13
|
+
* 物引用,不带 role/runId。角色映射只存在于档案(审计用)。
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { appendArchiveRecord, readArchiveRecords } from "./archive.js";
|
|
17
|
+
/** 运行档案 kind:一条 L2 比较记录。 */
|
|
18
|
+
export const L2_COMPARISON_ARCHIVE_KIND = "l2-comparison";
|
|
19
|
+
/**
|
|
20
|
+
* 匿名标识:按任务种子 + 运行标识派生的稳定短哈希。构造上不可能
|
|
21
|
+
* 含角色词——呈现面与档案里的值同源,泄露面为零。
|
|
22
|
+
*/
|
|
23
|
+
export function anonArmId(taskSeeds, runId) {
|
|
24
|
+
const seedKey = taskSeeds
|
|
25
|
+
.map((entry) => `${entry.task}=${entry.seed}`)
|
|
26
|
+
.join("&");
|
|
27
|
+
const digest = createHash("sha256").update(`${seedKey}#${runId}`).digest("hex");
|
|
28
|
+
return `arm-${digest.slice(0, 16)}`;
|
|
29
|
+
}
|
|
30
|
+
function verdictFor(arms, selector) {
|
|
31
|
+
if (selector.status === "failed") {
|
|
32
|
+
return { kind: "invalid", reason: selector.reason };
|
|
33
|
+
}
|
|
34
|
+
const picked = arms.find((arm) => arm.anonId === selector.anonId);
|
|
35
|
+
if (picked === undefined) {
|
|
36
|
+
return { kind: "invalid", reason: `selector picked unknown arm ${selector.anonId}` };
|
|
37
|
+
}
|
|
38
|
+
// 任一臂未交付:无 artifacts 的臂视为未交付(design:不采纳收口)。
|
|
39
|
+
if (arms.some((arm) => arm.artifacts.length === 0)) {
|
|
40
|
+
return { kind: "invalid", reason: "arm-undelivered" };
|
|
41
|
+
}
|
|
42
|
+
if (picked.role === "baseline") {
|
|
43
|
+
return { kind: "not-adopted-baseline", anonId: picked.anonId };
|
|
44
|
+
}
|
|
45
|
+
return { kind: "adopt-candidate", anonId: picked.anonId };
|
|
46
|
+
}
|
|
47
|
+
/** 由臂与选择器结果构建完整记录(派生匿名映射 + 失败收口 verdict)。 */
|
|
48
|
+
export function buildL2Comparison(input) {
|
|
49
|
+
const arms = input.arms.map((arm) => ({
|
|
50
|
+
anonId: anonArmId(input.taskSeeds, arm.runId),
|
|
51
|
+
runId: arm.runId,
|
|
52
|
+
role: arm.role,
|
|
53
|
+
artifacts: arm.artifacts,
|
|
54
|
+
}));
|
|
55
|
+
return {
|
|
56
|
+
comparisonId: input.comparisonId,
|
|
57
|
+
createdAt: input.createdAt,
|
|
58
|
+
taskSeeds: input.taskSeeds,
|
|
59
|
+
arms,
|
|
60
|
+
selector: input.selector,
|
|
61
|
+
verdict: verdictFor(arms, input.selector),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** 追加一条比较记录到运行档案。 */
|
|
65
|
+
export function appendL2Comparison(homeDir, record, now = () => new Date()) {
|
|
66
|
+
return appendArchiveRecord(homeDir, { kind: L2_COMPARISON_ARCHIVE_KIND, payload: record }, now);
|
|
67
|
+
}
|
|
68
|
+
/** 读回全部比较记录(档案读取,零副作用)。 */
|
|
69
|
+
export function readL2Comparisons(homeDir) {
|
|
70
|
+
const records = [];
|
|
71
|
+
for (const entry of readArchiveRecords(homeDir)) {
|
|
72
|
+
if (entry.kind !== L2_COMPARISON_ARCHIVE_KIND)
|
|
73
|
+
continue;
|
|
74
|
+
records.push(entry.payload);
|
|
75
|
+
}
|
|
76
|
+
return records;
|
|
77
|
+
}
|
|
78
|
+
/** 选择器视图:只给匿名标识与冻结交付物引用——不带 role/runId。 */
|
|
79
|
+
export function presentL2Arms(record) {
|
|
80
|
+
return record.arms.map((arm) => ({ anonId: arm.anonId, artifacts: arm.artifacts }));
|
|
81
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lesson 记录(trellis-evolve 任务 12.6)。
|
|
3
|
+
*
|
|
4
|
+
* 被采纳的编辑产出一条 lesson(cue / 正文 / 证据链接 / 可选 confidence /
|
|
5
|
+
* 层作用域,类型见 types.ts)。spec"lesson 记录与上下文呈现":
|
|
6
|
+
*
|
|
7
|
+
* - canonical 记忆已启用 → 经现有记忆管道写入(缺省内置写入器把
|
|
8
|
+
* lesson 落成 canonical `memories/lesson-<id>.md`;调用方可注入
|
|
9
|
+
* 自有管道);
|
|
10
|
+
* - 未启用 → 只留运行档案,运行不失败:返回 skipped 而非 failed;
|
|
11
|
+
* - 后续提案只呈现匹配上下文的 lesson:全局 lesson 处处可见,
|
|
12
|
+
* workspace lesson 只给该 workspace;对其他上下文仅作为升格审批
|
|
13
|
+
* 的跨项目佐证(单独出口,不混入提案上下文)。
|
|
14
|
+
*
|
|
15
|
+
* "记忆已启用"是调用方判定后的显式参数(共享 Memory MCP 的 on/off
|
|
16
|
+
* 属托管配置面,消费者不读 mcp 配置 —— 10.3 边界)。档案恒写:lesson
|
|
17
|
+
* 的审计源是运行档案,记忆管道是投递产物。
|
|
18
|
+
*/
|
|
19
|
+
import type { Lesson } from "./types.js";
|
|
20
|
+
/** 运行档案 kind:一条 lesson。payload 即 Lesson 的 JSON 形态。 */
|
|
21
|
+
export declare const LESSON_ARCHIVE_KIND = "lesson";
|
|
22
|
+
export type LessonMemoryStatus = "written" | "skipped" | "failed";
|
|
23
|
+
export interface RecordLessonOptions {
|
|
24
|
+
readonly homeDir: string;
|
|
25
|
+
readonly lesson: Lesson;
|
|
26
|
+
/** canonical 记忆是否已启用(调用方按托管配置判定)。 */
|
|
27
|
+
readonly memoryEnabled: boolean;
|
|
28
|
+
/** 记忆管道写入缝;缺省内置写入器落 `memories/lesson-<id>.md`。
|
|
29
|
+
* 抛错按 failed 收口(仍留档案),不拖垮运行。 */
|
|
30
|
+
readonly writeMemory?: (lesson: Lesson) => void;
|
|
31
|
+
}
|
|
32
|
+
export interface RecordLessonResult {
|
|
33
|
+
readonly memoryStatus: LessonMemoryStatus;
|
|
34
|
+
/** 内置写入器落盘的路径(注入自有管道时为 undefined)。 */
|
|
35
|
+
readonly memoryPath?: string;
|
|
36
|
+
/** 写入失败时的错误信息(仅 failed)。 */
|
|
37
|
+
readonly error?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 记录一条 lesson:恒写运行档案;记忆已启用才走记忆管道(未启用
|
|
41
|
+
* skipped;管道抛错 failed 但运行不中断)。
|
|
42
|
+
*/
|
|
43
|
+
export declare function recordLesson(options: RecordLessonOptions): RecordLessonResult;
|
|
44
|
+
/** 全部 lesson(档案读取;档案是 lesson 的审计源)。 */
|
|
45
|
+
export declare function readAllLessons(homeDir: string): Lesson[];
|
|
46
|
+
/**
|
|
47
|
+
* 提案上下文用:只呈现匹配上下文的 lesson——全局处处可见;workspace
|
|
48
|
+
* lesson 只给同一 workspace root;其他 workspace 的 lesson 不进提案
|
|
49
|
+
* 上下文(spec 场景:仓库 X 的 lesson 不原样进入 Y)。
|
|
50
|
+
*/
|
|
51
|
+
export declare function lessonsForContext(homeDir: string, workspaceRoot?: string): Lesson[];
|
|
52
|
+
/**
|
|
53
|
+
* 升格审批的跨项目佐证:当前上下文之外的 workspace lesson(全局
|
|
54
|
+
* lesson 不在此列——它们本就处处可见)。仅供升格审批参考,绝不混入
|
|
55
|
+
* 提案生成上下文。
|
|
56
|
+
*/
|
|
57
|
+
export declare function lessonsAsCorroboration(homeDir: string, workspaceRoot?: string): Lesson[];
|