synergyspec-selfevolving 1.4.0 → 2.0.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 (69) hide show
  1. package/README.md +31 -18
  2. package/dist/commands/learn.d.ts +12 -1
  3. package/dist/commands/learn.js +151 -11
  4. package/dist/commands/self-evolution-episode.d.ts +177 -0
  5. package/dist/commands/self-evolution-episode.js +423 -0
  6. package/dist/commands/self-evolution.d.ts +12 -190
  7. package/dist/commands/self-evolution.js +114 -866
  8. package/dist/core/archive.d.ts +0 -1
  9. package/dist/core/archive.js +0 -58
  10. package/dist/core/artifact-graph/instruction-loader.d.ts +2 -4
  11. package/dist/core/artifact-graph/instruction-loader.js +3 -31
  12. package/dist/core/fitness/loss.d.ts +5 -5
  13. package/dist/core/fitness/loss.js +4 -4
  14. package/dist/core/project-config.d.ts +2 -0
  15. package/dist/core/project-config.js +28 -0
  16. package/dist/core/self-evolution/candidate-fitness.d.ts +23 -1
  17. package/dist/core/self-evolution/candidate-fitness.js +31 -5
  18. package/dist/core/self-evolution/candidates.d.ts +0 -9
  19. package/dist/core/self-evolution/critic-agent.d.ts +150 -0
  20. package/dist/core/self-evolution/critic-agent.js +487 -0
  21. package/dist/core/self-evolution/edits-contract.d.ts +53 -0
  22. package/dist/core/self-evolution/edits-contract.js +89 -0
  23. package/dist/core/self-evolution/episode-orchestrator.d.ts +197 -0
  24. package/dist/core/self-evolution/episode-orchestrator.js +534 -0
  25. package/dist/core/self-evolution/episode-store.d.ts +266 -0
  26. package/dist/core/self-evolution/episode-store.js +573 -0
  27. package/dist/core/self-evolution/evolution-switches.d.ts +1 -1
  28. package/dist/core/self-evolution/evolution-switches.js +5 -10
  29. package/dist/core/self-evolution/evolving-agent.d.ts +162 -0
  30. package/dist/core/self-evolution/evolving-agent.js +449 -0
  31. package/dist/core/self-evolution/host-harness.d.ts +1 -2
  32. package/dist/core/self-evolution/host-harness.js +1 -2
  33. package/dist/core/self-evolution/index.d.ts +9 -6
  34. package/dist/core/self-evolution/index.js +18 -6
  35. package/dist/core/self-evolution/line-diff.d.ts +60 -0
  36. package/dist/core/self-evolution/line-diff.js +130 -0
  37. package/dist/core/self-evolution/policy/fs-safe.d.ts +19 -0
  38. package/dist/core/self-evolution/policy/fs-safe.js +89 -0
  39. package/dist/core/self-evolution/policy/index.d.ts +13 -0
  40. package/dist/core/self-evolution/policy/index.js +13 -0
  41. package/dist/core/self-evolution/policy/policy-store.d.ts +217 -0
  42. package/dist/core/self-evolution/policy/policy-store.js +774 -0
  43. package/dist/core/self-evolution/policy/reject-buffer.d.ts +48 -0
  44. package/dist/core/self-evolution/policy/reject-buffer.js +168 -0
  45. package/dist/core/self-evolution/promote.d.ts +1 -1
  46. package/dist/core/self-evolution/promote.js +6 -33
  47. package/dist/core/self-evolution/promotion.js +1 -2
  48. package/dist/core/self-evolution/reward-agent.d.ts +234 -0
  49. package/dist/core/self-evolution/reward-agent.js +564 -0
  50. package/dist/core/self-evolution/scope-gate.d.ts +66 -0
  51. package/dist/core/self-evolution/scope-gate.js +107 -0
  52. package/dist/core/self-evolution/success-channel.js +2 -2
  53. package/dist/core/self-evolution/tool-evolution.js +2 -13
  54. package/dist/core/self-evolution/verdict.d.ts +8 -5
  55. package/dist/core/self-evolution/verdict.js +4 -7
  56. package/dist/core/templates/workflows/learn.d.ts +3 -2
  57. package/dist/core/templates/workflows/learn.js +18 -16
  58. package/dist/core/templates/workflows/self-evolving.d.ts +6 -4
  59. package/dist/core/templates/workflows/self-evolving.js +62 -172
  60. package/dist/dashboard/data.d.ts +25 -51
  61. package/dist/dashboard/data.js +68 -180
  62. package/dist/dashboard/react-client.js +458 -503
  63. package/dist/dashboard/react-styles.js +3 -3
  64. package/dist/dashboard/server.js +23 -17
  65. package/dist/ui/ascii-patterns.d.ts +7 -15
  66. package/dist/ui/ascii-patterns.js +123 -54
  67. package/dist/ui/welcome-screen.d.ts +0 -14
  68. package/dist/ui/welcome-screen.js +16 -35
  69. package/package.json +1 -1
@@ -0,0 +1,423 @@
1
+ import * as path from 'node:path';
2
+ import {
3
+ // Orchestrator (the three-agent episode runner).
4
+ captureMainArm as captureMainArmImpl, runEpisode as runEpisodeImpl, resumeEpisode as resumeEpisodeImpl,
5
+ // 版本账本 ledger + 否决缓冲 reject-buffer (read-only views + manual rollback).
6
+ readPolicyLedger, readRejectBuffer, currentPolicyVersion, rollbackPolicyVersion, appendRejectBufferEntry,
7
+ // Canonical target registry.
8
+ lookupCanonicalTarget, listCanonicalTargets, DESIGN_ARTIFACT_TARGET_ID, } from '../core/self-evolution/index.js';
9
+ import { generateLearnReport } from '../core/learn.js';
10
+ import { validateExplicitTrajectoryHandle } from '../core/learn/trajectory-discovery.js';
11
+ import { validateChangeExists } from './workflow/shared.js';
12
+ /**
13
+ * The 主智能体 MAIN AGENT arm is graded from a learn report exactly the way the
14
+ * `learn` command grades it (the orchestrator REUSES that grading; it never
15
+ * re-runs the tests). The default target is the design artifact-template — the
16
+ * opinionated design-only evolution default — but any registered canonical
17
+ * target id is accepted via `--target`.
18
+ */
19
+ const DEFAULT_EPISODE_TARGET = DESIGN_ARTIFACT_TARGET_ID;
20
+ // ---------------------------------------------------------------------------
21
+ // CLI registration
22
+ // ---------------------------------------------------------------------------
23
+ /**
24
+ * Attach the loop-v2 `episode` + `policy` subcommands to the parent
25
+ * `self-evolution` command. Called once from {@link registerSelfEvolutionCommand}.
26
+ */
27
+ export function attachSelfEvolutionEpisodeCommands(parent) {
28
+ const episode = parent
29
+ .command('episode [change]')
30
+ .description('Run ONE loop-v2 episode (self-evolution as in-context RL) for a change: build the 主智能体 MAIN AGENT arm from the change\'s learn report, rerun the CRITIC AGENT(基线智能体 baseline agent)arm, score advantage with the 奖励智能体 REWARD AGENT, roll back the 策略 POLICY on a bad advantage, then let the 演进智能体 EVOLVING AGENT take ONE bounded edit.')
31
+ .option('--change <name>', 'the completed change to run the episode for (or positional)')
32
+ .option('--target <id>', `canonical target id to evolve (default ${DEFAULT_EPISODE_TARGET})`)
33
+ .option('--no-baseline', 'skip the CRITIC AGENT(基线智能体 baseline agent)arm for this episode')
34
+ .option('--transcript <path>', 'Explicit transcript .jsonl to grade (bypasses change-window discovery; Claude transcript store only)')
35
+ .option('--session-id <id>', 'Explicit Claude session id to grade (bypasses change-window discovery; Claude transcript store only)')
36
+ .option('--json', 'output the full RunEpisodeResult JSON')
37
+ .action(async (changeArg, options) => {
38
+ const changeName = options.change ?? changeArg;
39
+ const result = await runEpisodeCommand({
40
+ changeName,
41
+ target: options.target,
42
+ // commander sets `baseline` to false when --no-baseline is passed.
43
+ noBaseline: options.baseline === false,
44
+ transcript: options.transcript,
45
+ sessionId: options.sessionId,
46
+ json: options.json,
47
+ }, { repoRoot: process.cwd() });
48
+ process.exitCode = result.exitCode;
49
+ });
50
+ episode
51
+ .command('resume <episodeId>')
52
+ .description('Re-enter a partially-run loop-v2 episode at its recorded stage and idempotently finish the remaining steps (decision → 演进智能体 EVOLVING AGENT → close).')
53
+ .option('--json', 'output the full ResumeEpisodeResult JSON')
54
+ .action(async (episodeId, options) => {
55
+ const result = await runResumeEpisodeCommand({ episodeId, json: options.json }, { repoRoot: process.cwd() });
56
+ process.exitCode = result.exitCode;
57
+ });
58
+ const policy = parent
59
+ .command('policy')
60
+ .description('Inspect or manually roll back the 策略 POLICY lineage (版本账本 ledger + 否决缓冲 reject-buffer) for loop-v2 targets');
61
+ policy
62
+ .command('show')
63
+ .description('READ-ONLY: print the 版本账本 ledger (versions, actions, Δ stats, predictions) and the 否决缓冲 reject-buffer for the target(s). Replaces the read-only role of the trajectory command.')
64
+ .option('--target <id>', 'restrict to a single canonical target id (default: all registered targets with a lineage)')
65
+ .option('--json', 'output { targets: [{ targetId, head, ledger, rejectBuffer }] } JSON')
66
+ .action(async (options) => {
67
+ const result = await runPolicyShowCommand({ target: options.target, json: options.json }, { repoRoot: process.cwd() });
68
+ process.exitCode = result.exitCode;
69
+ });
70
+ policy
71
+ .command('rollback')
72
+ .description('Manual 策略 POLICY rollback: restore the previous version (recorded as a new monotonic head, git-revert style) and append a `human-reject` 否决缓冲 reject-buffer entry. Requires --yes.')
73
+ .requiredOption('--target <id>', 'the canonical target id whose lineage to roll back')
74
+ .option('--reason <text>', 'why the version is being rejected (recorded on the 否决缓冲 entry)')
75
+ .option('--yes', 'required: confirm the manual rollback')
76
+ .option('--json', 'output JSON')
77
+ .action(async (options) => {
78
+ const result = await runPolicyRollbackCommand({ target: options.target, reason: options.reason, yes: options.yes, json: options.json }, { repoRoot: process.cwd() });
79
+ process.exitCode = result.exitCode;
80
+ });
81
+ }
82
+ /**
83
+ * Programmatic entrypoint for `self-evolution episode`. Exported so tests can
84
+ * drive the full episode flow with an injected orchestrator seam (no real agent
85
+ * spawn).
86
+ */
87
+ export async function runEpisodeCommand(args, opts) {
88
+ const stdout = opts.stdout ?? ((l) => console.log(l));
89
+ const stderr = opts.stderr ?? ((l) => console.error(l));
90
+ const generateReport = opts.generateReport ??
91
+ ((changeName) => generateLearnReport({ projectRoot: opts.repoRoot, changeName }));
92
+ const captureMainArm = opts.captureMainArm ?? captureMainArmImpl;
93
+ const runEpisode = opts.runEpisode ?? runEpisodeImpl;
94
+ const targetId = args.target ?? DEFAULT_EPISODE_TARGET;
95
+ // Validate the target up front so a typo fails loud (exit 1) rather than deep
96
+ // in the orchestrator with a bare lineage-init error.
97
+ if (!lookupCanonicalTarget(targetId)) {
98
+ return failEpisode(args.json, stdout, stderr, `Unknown canonical target: ${targetId}`);
99
+ }
100
+ // USER-TYPED handle flags fail LOUD on a miss (mirrors the learn /
101
+ // evolve-from-edits commands) BEFORE the env is mutated.
102
+ const handleError = await validateExplicitTrajectoryHandle({
103
+ projectRoot: opts.repoRoot,
104
+ transcriptPath: args.transcript,
105
+ sessionId: args.sessionId,
106
+ });
107
+ if (handleError) {
108
+ return failEpisode(args.json, stdout, stderr, handleError);
109
+ }
110
+ // The change must exist (active) so the orchestrator has a real changeDirPath.
111
+ let changeName;
112
+ try {
113
+ changeName = await validateChangeExists(args.changeName, opts.repoRoot);
114
+ }
115
+ catch (err) {
116
+ return failEpisode(args.json, stdout, stderr, err instanceof Error ? err.message : String(err));
117
+ }
118
+ const changeDirPath = path.join(opts.repoRoot, 'synergyspec-selfevolving', 'changes', changeName);
119
+ // Build the 主智能体 MAIN AGENT arm from the change's learn report (the SAME
120
+ // grading the `learn` command uses; the orchestrator never re-grades). The
121
+ // explicit trajectory handle reaches the discovery layer via env (kept in this
122
+ // action layer so the injected-generateReport test seam stays untouched).
123
+ const prevTranscriptEnv = process.env.SYNERGYSPEC_SELFEVOLVING_TRANSCRIPT;
124
+ const prevSessionEnv = process.env.SYNERGYSPEC_SELFEVOLVING_SESSION_ID;
125
+ if (args.transcript)
126
+ process.env.SYNERGYSPEC_SELFEVOLVING_TRANSCRIPT = args.transcript;
127
+ if (args.sessionId)
128
+ process.env.SYNERGYSPEC_SELFEVOLVING_SESSION_ID = args.sessionId;
129
+ let mainArm;
130
+ try {
131
+ const report = await generateReport(changeName);
132
+ mainArm = await captureMainArm({ repoRoot: opts.repoRoot, changeName, report });
133
+ }
134
+ catch (err) {
135
+ return failEpisode(args.json, stdout, stderr, `failed to build main arm for "${changeName}": ${err instanceof Error ? err.message : String(err)}`);
136
+ }
137
+ finally {
138
+ restoreEnv('SYNERGYSPEC_SELFEVOLVING_TRANSCRIPT', args.transcript, prevTranscriptEnv);
139
+ restoreEnv('SYNERGYSPEC_SELFEVOLVING_SESSION_ID', args.sessionId, prevSessionEnv);
140
+ }
141
+ // Run the episode. The CRITIC AGENT(基线智能体 baseline agent)arm is gated by
142
+ // the orchestrator's own shouldRunCriticAgent ledger read (it skips when the
143
+ // lineage has < 2 versions or last action refused), so the orchestrator owns
144
+ // the skip decision. `--no-baseline` is surfaced as `skipBaseline` on the
145
+ // options the command forwards to the (injectable) runEpisode seam so a caller
146
+ // can request the skip explicitly; the field is additive (the base
147
+ // RunEpisodeOptions ignores unknown keys), so this never alters the real
148
+ // orchestrator's behavior when it is unaware of the flag.
149
+ let outcome;
150
+ try {
151
+ const episodeOptions = {
152
+ repoRoot: opts.repoRoot,
153
+ targetId,
154
+ changeName,
155
+ changeDirPath,
156
+ mainArm,
157
+ ...(args.noBaseline ? { skipBaseline: true } : {}),
158
+ };
159
+ outcome = await runEpisode(episodeOptions);
160
+ }
161
+ catch (err) {
162
+ return failEpisode(args.json, stdout, stderr, err instanceof Error ? err.message : String(err));
163
+ }
164
+ if ('busy' in outcome && outcome.busy) {
165
+ if (args.json) {
166
+ stdout(JSON.stringify({ exitCode: 0, busy: outcome }, null, 2));
167
+ }
168
+ else {
169
+ stdout(`Episode not started for ${targetId}: ${outcome.reason}`);
170
+ }
171
+ return { exitCode: 0, busy: outcome };
172
+ }
173
+ const ran = outcome;
174
+ if (args.json) {
175
+ stdout(JSON.stringify({ exitCode: 0, result: ran }, null, 2));
176
+ }
177
+ else {
178
+ printEpisodeSummary(ran, targetId, changeName, stdout);
179
+ }
180
+ return { exitCode: 0, result: ran };
181
+ }
182
+ function printEpisodeSummary(result, targetId, changeName, stdout) {
183
+ stdout(`episode ${result.episodeId} (target ${targetId}, change ${changeName})`);
184
+ stdout(` advantage: ${result.advantage === null ? 'n/a' : result.advantage.toFixed(3)} ` +
185
+ `(baseline arm ${result.baselineSkipped ? 'skipped' : 'rerun'})`);
186
+ stdout(` decision: ${result.decision}`);
187
+ stdout(` evolution: ${describeEvolution(result.evolution)}`);
188
+ stdout(` new policy version: ${result.newPolicyVersion === null ? 'n/a' : `v${result.newPolicyVersion}`}`);
189
+ }
190
+ function describeEvolution(evolution) {
191
+ if (evolution === null)
192
+ return 'not spawned (no decision branch reached the 演进智能体 EVOLVING AGENT)';
193
+ switch (evolution.kind) {
194
+ case 'not-spawned':
195
+ return `not spawned (${evolution.reason})`;
196
+ case 'refused':
197
+ return `refused (${evolution.reason})`;
198
+ case 'evolved':
199
+ return `evolved → v${evolution.ledgerEntry.version}`;
200
+ }
201
+ }
202
+ function restoreEnv(name, applied, previous) {
203
+ if (!applied)
204
+ return;
205
+ if (previous === undefined)
206
+ delete process.env[name];
207
+ else
208
+ process.env[name] = previous;
209
+ }
210
+ function failEpisode(json, stdout, stderr, message) {
211
+ if (json) {
212
+ stdout(JSON.stringify({ exitCode: 1, error: message }, null, 2));
213
+ }
214
+ else {
215
+ stderr(`episode failed: ${message}`);
216
+ }
217
+ return { exitCode: 1, error: message };
218
+ }
219
+ /**
220
+ * Programmatic entrypoint for `self-evolution episode resume <id>`. Exported for
221
+ * tests; the orchestrator seam is injectable.
222
+ */
223
+ export async function runResumeEpisodeCommand(args, opts) {
224
+ const stdout = opts.stdout ?? ((l) => console.log(l));
225
+ const stderr = opts.stderr ?? ((l) => console.error(l));
226
+ const resumeEpisode = opts.resumeEpisode ?? resumeEpisodeImpl;
227
+ let result;
228
+ try {
229
+ result = await resumeEpisode({ repoRoot: opts.repoRoot, episodeId: args.episodeId });
230
+ }
231
+ catch (err) {
232
+ const message = err instanceof Error ? err.message : String(err);
233
+ if (args.json) {
234
+ stdout(JSON.stringify({ exitCode: 1, error: message }, null, 2));
235
+ }
236
+ else {
237
+ stderr(`episode resume failed: ${message}`);
238
+ }
239
+ return { exitCode: 1, error: message };
240
+ }
241
+ if (args.json) {
242
+ stdout(JSON.stringify({ exitCode: 0, result }, null, 2));
243
+ }
244
+ else {
245
+ stdout(`resumed episode ${result.episodeId}`);
246
+ stdout(` from stage: ${result.resumedFrom}`);
247
+ stdout(` now at stage: ${result.stage}`);
248
+ stdout(` evolution: ${describeEvolution(result.evolution)}`);
249
+ }
250
+ return { exitCode: 0, result };
251
+ }
252
+ /**
253
+ * Programmatic entrypoint for `self-evolution policy show`. READ-ONLY: reads the
254
+ * 版本账本 ledger + 否决缓冲 reject-buffer for the target(s) and renders them.
255
+ * Never mutates anything.
256
+ */
257
+ export async function runPolicyShowCommand(args, opts) {
258
+ const stdout = opts.stdout ?? ((l) => console.log(l));
259
+ const stderr = opts.stderr ?? ((l) => console.error(l));
260
+ // Resolve the target list. A named --target must be registered; otherwise show
261
+ // every registered canonical target (those with no lineage render head=null).
262
+ let targetIds;
263
+ if (args.target !== undefined) {
264
+ if (!lookupCanonicalTarget(args.target)) {
265
+ const message = `Unknown canonical target: ${args.target}`;
266
+ if (args.json)
267
+ stdout(JSON.stringify({ error: message }, null, 2));
268
+ else
269
+ stderr(message);
270
+ return { exitCode: 1, targets: [], error: message };
271
+ }
272
+ targetIds = [args.target];
273
+ }
274
+ else {
275
+ targetIds = listCanonicalTargets().map((t) => t.id);
276
+ }
277
+ const targets = [];
278
+ try {
279
+ for (const targetId of targetIds) {
280
+ const ledger = await readPolicyLedger(opts.repoRoot, targetId);
281
+ const rejectBuffer = await readRejectBuffer(opts.repoRoot, targetId);
282
+ const head = await currentPolicyVersion(opts.repoRoot, targetId);
283
+ // Skip targets with NEITHER a lineage NOR a reject-buffer when showing
284
+ // ALL targets (keep the default view focused on live lineages). A named
285
+ // --target is always shown, even when empty.
286
+ if (args.target === undefined && ledger.length === 0 && rejectBuffer.length === 0) {
287
+ continue;
288
+ }
289
+ targets.push({ targetId, head, ledger, rejectBuffer });
290
+ }
291
+ }
292
+ catch (err) {
293
+ const message = err instanceof Error ? err.message : String(err);
294
+ if (args.json)
295
+ stdout(JSON.stringify({ error: message }, null, 2));
296
+ else
297
+ stderr(`policy show failed: ${message}`);
298
+ return { exitCode: 1, targets: [], error: message };
299
+ }
300
+ if (args.json) {
301
+ stdout(JSON.stringify({ targets }, null, 2));
302
+ return { exitCode: 0, targets };
303
+ }
304
+ if (targets.length === 0) {
305
+ stdout('No policy lineage recorded yet.');
306
+ return { exitCode: 0, targets };
307
+ }
308
+ for (const view of targets) {
309
+ stdout(renderPolicyTargetView(view));
310
+ }
311
+ return { exitCode: 0, targets };
312
+ }
313
+ function renderPolicyTargetView(view) {
314
+ const lines = [];
315
+ lines.push(`# ${view.targetId} (head ${view.head === null ? 'uninitialized' : `v${view.head}`})`);
316
+ lines.push('## 版本账本 ledger');
317
+ if (view.ledger.length === 0) {
318
+ lines.push('- (no versions)');
319
+ }
320
+ else {
321
+ for (const entry of view.ledger) {
322
+ const delta = entry.deltaStats
323
+ ? ` Δ ${entry.deltaStats.filesChanged}f +${entry.deltaStats.linesAdded}/-${entry.deltaStats.linesRemoved}`
324
+ : '';
325
+ const prediction = entry.prediction
326
+ ? ` predicts ${entry.prediction.metric} ${entry.prediction.direction} by ${entry.prediction.checkBy}`
327
+ : '';
328
+ const advantage = entry.advantage !== undefined ? ` advantage=${entry.advantage.toFixed(3)}` : '';
329
+ const reason = entry.reason ? ` (${entry.reason})` : '';
330
+ lines.push(`- v${entry.version} ${entry.action} ${entry.at}${delta}${prediction}${advantage}${reason}`);
331
+ }
332
+ }
333
+ lines.push('## 否决缓冲 reject-buffer');
334
+ if (view.rejectBuffer.length === 0) {
335
+ lines.push('- (no rejections)');
336
+ }
337
+ else {
338
+ for (const entry of view.rejectBuffer) {
339
+ const advantage = entry.advantage === null ? 'n/a' : entry.advantage.toFixed(3);
340
+ const files = entry.editSummary.files.join(', ') || '(none)';
341
+ lines.push(`- ${entry.at} ${entry.reason} v${entry.fromVersion}→v${entry.toVersion} advantage=${advantage} files=[${files}]`);
342
+ if (entry.textualGradientTried) {
343
+ lines.push(` gradient: ${entry.textualGradientTried.slice(0, 200)}`);
344
+ }
345
+ }
346
+ }
347
+ return lines.join('\n');
348
+ }
349
+ /**
350
+ * Programmatic entrypoint for `self-evolution policy rollback`. Manually rolls
351
+ * the 策略 POLICY lineage back to the previous version (recorded as a NEW
352
+ * monotonic head, git-revert style) and appends a `human-reject` 否决缓冲
353
+ * reject-buffer entry so the next 演进智能体 EVOLVING AGENT step sees the rejected
354
+ * direction. Requires --yes.
355
+ */
356
+ export async function runPolicyRollbackCommand(args, opts) {
357
+ const stdout = opts.stdout ?? ((l) => console.log(l));
358
+ const stderr = opts.stderr ?? ((l) => console.error(l));
359
+ const now = opts.now ?? (() => new Date());
360
+ const fail = (code, message) => {
361
+ if (args.json)
362
+ stdout(JSON.stringify({ exitCode: code, error: message }, null, 2));
363
+ else
364
+ stderr(`policy rollback failed: ${message}`);
365
+ return { exitCode: code, error: message };
366
+ };
367
+ if (!args.yes) {
368
+ return fail(2, '--yes is required: policy rollback restores the previous version of your local policy file(s).');
369
+ }
370
+ if (!lookupCanonicalTarget(args.target)) {
371
+ return fail(1, `Unknown canonical target: ${args.target}`);
372
+ }
373
+ const head = await currentPolicyVersion(opts.repoRoot, args.target);
374
+ if (head === null) {
375
+ return fail(1, `No policy lineage for ${args.target}; nothing to roll back.`);
376
+ }
377
+ if (head < 1) {
378
+ return fail(1, `Policy lineage for ${args.target} is at v0; there is no earlier version to roll back to.`);
379
+ }
380
+ const toVersion = head - 1;
381
+ const reason = (args.reason ?? '').trim() || 'human: manual rollback to the previous policy version';
382
+ let entry;
383
+ try {
384
+ // Restore the previous version (recorded as a new monotonic head). The manual
385
+ // rollback path has no per-arm advantage, so none is recorded on the ledger.
386
+ entry = await rollbackPolicyVersion({
387
+ repoRoot: opts.repoRoot,
388
+ targetId: args.target,
389
+ episodeId: `manual-rollback-${now().toISOString().replace(/[^0-9]/g, '').slice(0, 14)}`,
390
+ toVersion,
391
+ });
392
+ // Append the 否决缓冲 reject-buffer entry so the rejected direction stays as
393
+ // evidence (the rolled-back files themselves are 产物即弃).
394
+ await appendRejectBufferEntry(opts.repoRoot, {
395
+ schemaVersion: 1,
396
+ at: now().toISOString(),
397
+ episodeId: entry.episodeId ?? 'manual-rollback',
398
+ targetId: args.target,
399
+ // fromVersion = the version restored TO; toVersion = the rejected head.
400
+ fromVersion: toVersion,
401
+ toVersion: head,
402
+ advantage: null,
403
+ rewardMain: null,
404
+ rewardBaseline: null,
405
+ textualGradientTried: '',
406
+ editSummary: { files: [], linesAdded: 0, linesRemoved: 0, rationaleExcerpt: reason },
407
+ reason: 'human-reject',
408
+ });
409
+ }
410
+ catch (err) {
411
+ return fail(1, err instanceof Error ? err.message : String(err));
412
+ }
413
+ if (args.json) {
414
+ stdout(JSON.stringify({ exitCode: 0, entry, toVersion }, null, 2));
415
+ }
416
+ else {
417
+ stdout(`Rolled back ${args.target} to v${toVersion} (recorded as new head v${entry.version}).`);
418
+ stdout(`Recorded a human-reject 否决缓冲 reject-buffer entry: ${reason}`);
419
+ stdout('Restored your LOCAL policy file(s) — effective immediately, no rebuild/republish.');
420
+ }
421
+ return { exitCode: 0, entry, toVersion };
422
+ }
423
+ //# sourceMappingURL=self-evolution-episode.js.map
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { type AggregationOptions, type CanonicalCandidate, type CanonicalCandidateSource, type CanonicalCandidateStatus, type PromotionReportArtifact, type StaticGateResult, type RankedCandidate, type RunChangeFn, type CandidateReplayScore, type PromotionApplyResult, type RollbackResult } from '../core/self-evolution/index.js';
2
+ import { type AggregationOptions, type CanonicalCandidate, type CanonicalCandidateSource, type CanonicalCandidateStatus, type PromotionReportArtifact, type StaticGateResult, type PromotionApplyResult, type RollbackResult } from '../core/self-evolution/index.js';
3
3
  import { type LearnReport } from '../core/learn.js';
4
4
  export declare function registerSelfEvolutionCommand(program: Command): void;
5
5
  /**
@@ -41,34 +41,19 @@ export interface RunProposeCanonicalArgs {
41
41
  /** If true, emit JSON output. */
42
42
  json?: boolean;
43
43
  /**
44
- * HEADLESS FALLBACK. When true (and `editsInput` is absent), spawn the
45
- * canonical proposer agent to draft a real diff for each group's target
46
- * (proposal-only) — for cron/CI runs with no host agent. Ignored when
47
- * `editsInput` is present. When both are false/omitted, the candidate carries
48
- * the placeholder diff and a human supplies the change before the gate.
49
- */
50
- agent?: boolean;
51
- /**
52
- * Population-based generation: draft N competing variant candidates (clamped
53
- * 1-5; default 1) for the single surviving group, each on a distinct
54
- * improvement angle, so the GA outer loop (`evolve`) can select the best.
55
- * Requires `agent` (divergence is prompt-side; the host `--from-edits` path
56
- * carries one payload). Ignored/treated as 1 otherwise.
57
- */
58
- variants?: number;
59
- /**
60
- * HOST-AGENT path (preferred). Candidate edits the host code agent already
61
- * authored. When present, the CLI validates + packages these (frozen + target
62
- * scope) instead of spawning the proposer agent, and `agent` is ignored.
44
+ * HOST-AGENT path. Candidate edits the host code agent already authored. When
45
+ * present, the CLI validates + packages these (frozen + target scope).
63
46
  * Requires exactly one surviving hint group (narrow via --target /
64
- * --threshold-key) so the single payload is not misattributed.
47
+ * --threshold-key) so the single payload is not misattributed. When absent the
48
+ * candidate carries the placeholder diff and a human supplies the change
49
+ * before the gate.
65
50
  */
66
51
  editsInput?: HostEditsInput;
67
52
  /**
68
53
  * Aggregation thresholds. Self-evolution is gradient descent: ONE forward
69
54
  * pass (a change's trajectory + pass-rate) is a complete loss signal, so
70
- * auto-evolve passes single-change thresholds (all min-occurrences = 1, no
71
- * diversity requirement) to act on one change. Omitted → conservative
55
+ * the host evolve-from-edits path passes single-change thresholds (all
56
+ * min-occurrences = 1, no diversity requirement) to act on one change. Omitted → conservative
72
57
  * cross-change defaults (recurrence required).
73
58
  */
74
59
  aggregationOptions?: AggregationOptions;
@@ -76,10 +61,6 @@ export interface RunProposeCanonicalArgs {
76
61
  export interface RunProposeCanonicalOptions {
77
62
  /** Project root used to resolve the candidate base dir. */
78
63
  repoRoot: string;
79
- /** Test seam: spawn used by the proposer agent (defaults to node's spawn). */
80
- proposerSpawn?: typeof import('node:child_process').spawn;
81
- /** Test seam: proposer agent binary override. */
82
- proposerBinary?: string;
83
64
  /** Override for stdout (defaults to console.log). */
84
65
  stdout?: (line: string) => void;
85
66
  /** Override for stderr (defaults to console.error). */
@@ -124,9 +105,8 @@ export declare function parseHostEditsInput(raw: string): HostEditsInput;
124
105
  *
125
106
  * SAFETY:
126
107
  * - Never writes outside `<repoRoot>/.synergyspec-selfevolving/self-evolution/candidates/`.
127
- * - Generation is EITHER the host-agent `--from-edits` path (validate + package)
128
- * OR the headless `--agent` proposer fallback; without either, diff.patch is
129
- * the placeholder for a human to complete.
108
+ * - Generation is the host-agent `--from-edits` path (validate + package); when
109
+ * absent, diff.patch is the placeholder for a human to complete.
130
110
  */
131
111
  export declare function runProposeCanonical(args: RunProposeCanonicalArgs, opts: RunProposeCanonicalOptions): Promise<RunProposeCanonicalResult>;
132
112
  export interface RunPromoteArgs {
@@ -146,7 +126,7 @@ export interface RunPromoteResult {
146
126
  }
147
127
  /**
148
128
  * Programmatic entrypoint for `self-evolution promote <id>` — the close-the-loop
149
- * apply/rollback. Exported so tests + auto-evolve can drive it directly.
129
+ * apply/rollback. Exported so tests + the host evolve-from-edits path can drive it directly.
150
130
  */
151
131
  export declare function runPromoteCommand(args: RunPromoteArgs, opts: {
152
132
  repoRoot: string;
@@ -175,95 +155,6 @@ export declare function runRejectCommand(args: RunRejectArgs, opts: {
175
155
  stderr?: (l: string) => void;
176
156
  now?: () => Date;
177
157
  }): Promise<RunRejectResult>;
178
- export interface RunTrajectoryArgs {
179
- targetId: string;
180
- /** Cap the number of prior candidates shown (default in buildOptimizationTrajectory: 6). */
181
- maxEntries?: number;
182
- json?: boolean;
183
- }
184
- export interface RunTrajectoryResult {
185
- exitCode: number;
186
- error?: string;
187
- }
188
- /**
189
- * Programmatic entrypoint for `self-evolution trajectory <targetId>` — a
190
- * READ-ONLY view of the scored optimization-trajectory block the headless
191
- * proposer receives, so a HOST code agent (which authors edits via
192
- * `--from-edits` and never sees that prompt) can read the same prior-candidate
193
- * loss/verdict history before authoring. Reuses the exact builder/renderer the
194
- * proposer uses. Never mutates anything.
195
- */
196
- export declare function runTrajectoryCommand(args: RunTrajectoryArgs, opts: {
197
- repoRoot: string;
198
- stdout?: (l: string) => void;
199
- stderr?: (l: string) => void;
200
- }): Promise<RunTrajectoryResult>;
201
- export interface RunAutoEvolveArgs {
202
- /**
203
- * One or more completed changes to learn from and evolve. A SINGLE change is
204
- * enough; pass several to let a signal recurring across them be the trigger.
205
- */
206
- changeNames: string[];
207
- /** Default true; false (via --no-auto) runs the pipeline but stops before applying. */
208
- auto?: boolean;
209
- requireProven?: boolean;
210
- /**
211
- * Minimum occurrences a signal must reach to be proposed. Default 1 — so a
212
- * single change CAN evolve. Raise it (and pass several `changeNames`) to
213
- * require a signal to RECUR across changes before it evolves. Neither
214
- * single-change nor multi-change is mandatory.
215
- */
216
- minOccurrences?: number;
217
- /** Force-propose only the aggregated signal with this exact thresholdKey. */
218
- thresholdKey?: string;
219
- evolveTarget?: string;
220
- freezeTarget?: string;
221
- json?: boolean;
222
- }
223
- export interface RunAutoEvolveOptions {
224
- repoRoot: string;
225
- stdout?: (l: string) => void;
226
- stderr?: (l: string) => void;
227
- now?: () => Date;
228
- /** Injected for tests; forwarded to the proposer agent. */
229
- proposerSpawn?: typeof import('node:child_process').spawn;
230
- proposerBinary?: string;
231
- }
232
- export interface AutoEvolveReport {
233
- exitCode: number;
234
- changeNames: string[];
235
- /** Mean per-change loss (functional ⊕ health) from learn; null when unmeasurable. */
236
- loss: number | null;
237
- /** Mean RAW code-health penalty across the change(s); null when no health signal. */
238
- healthPenalty?: number | null;
239
- hintCount: number;
240
- hintsPaths: string[];
241
- proposed: string[];
242
- gated: {
243
- candidateId: string;
244
- passed: boolean;
245
- }[];
246
- promoted: {
247
- candidateId: string;
248
- targetIds: string[];
249
- files: string[];
250
- }[];
251
- skipped: {
252
- candidateId: string;
253
- reason: string;
254
- }[];
255
- error?: string;
256
- }
257
- /**
258
- * ONE-BUTTON auto-evolve: learn → hints → propose(--agent) → gate → promote, in
259
- * one motion. Auto-applies the gate-passing winner per target onto the canonical
260
- * template (no per-change human approval), honoring the per-target switch + the
261
- * oracle/gate freeze, and snapshotting every write for rollback.
262
- *
263
- * Exported + fully injectable (proposer spawn, clock, io) so it is unit-testable
264
- * without a real `claude` binary.
265
- */
266
- export declare function runAutoEvolve(args: RunAutoEvolveArgs, opts: RunAutoEvolveOptions): Promise<AutoEvolveReport>;
267
158
  export interface RunEvolveFromEditsArgs {
268
159
  /** The change's learn hints.json to aggregate into a single signal/target. */
269
160
  fromLearn: string;
@@ -285,7 +176,7 @@ export interface RunEvolveFromEditsArgs {
285
176
  requireProven?: boolean;
286
177
  /** Mutually exclusive with this host path; presence is a hard error. */
287
178
  agent?: boolean;
288
- /** Non-interactive confirmation; required to run (like auto-evolve). */
179
+ /** Non-interactive confirmation; required to run (one-button host-authored apply). */
289
180
  yes?: boolean;
290
181
  json?: boolean;
291
182
  }
@@ -402,73 +293,4 @@ export interface RunPromotionReportResult {
402
293
  * - Exit code 0 on success, non-zero when the candidate cannot be loaded.
403
294
  */
404
295
  export declare function runPromotionReportCommand(args: RunPromotionReportArgs, opts: RunPromotionReportOptions): Promise<RunPromotionReportResult>;
405
- export interface RunEvolveOuterLoopArgs {
406
- /** Restrict the loop to a single canonical target id. */
407
- target?: string;
408
- /** Replay a corpus to score candidates (requires `changeIds`). */
409
- replay?: boolean;
410
- /** The replay corpus (change ids). Only used when `replay` is true. */
411
- changeIds?: string[];
412
- /** Write a human-gated promotion-report.md for each selected best. */
413
- write?: boolean;
414
- /**
415
- * Mark proven sibling variants that lost the ranking to `best` (same
416
- * variantGroup, higher meanLoss) with verdict `outcompeted`, so the
417
- * optimization-trajectory block shows them as negative examples. Never
418
- * transitions status (rejected is human-owned); advisory metadata only.
419
- */
420
- markOutcompeted?: boolean;
421
- /** Per-target evolution policy overrides (supports all/none). */
422
- evolveTarget?: string;
423
- freezeTarget?: string;
424
- json?: boolean;
425
- /**
426
- * ISO-8601 timestamp stamped onto appended fitness records. Defaults to the
427
- * current time; supplied explicitly by tests for determinism.
428
- */
429
- at?: string;
430
- /**
431
- * Injected replay re-runner (see {@link makeReplayRunChange}). Defaults to a
432
- * live agent shell-out; tests pass a fake to drive the loop without an agent.
433
- */
434
- runChange?: RunChangeFn;
435
- }
436
- export interface RunEvolveOuterLoopOptions {
437
- repoRoot: string;
438
- stdout?: (line: string) => void;
439
- stderr?: (line: string) => void;
440
- }
441
- export interface TargetEvolveSummary {
442
- targetId: string;
443
- candidateIds: string[];
444
- /** Skipped because the per-target policy froze this target. */
445
- frozen: boolean;
446
- /** Replay scoring outcomes (only present in --replay mode). */
447
- scored?: CandidateReplayScore[];
448
- /** Candidates ranked best-first by accumulated fitness. */
449
- ranked: RankedCandidate[];
450
- /** The selected best candidate id, or null when there are no candidates. */
451
- best: string | null;
452
- /** Path to the promotion report written for `best` (only with --write). */
453
- promotionReportPath?: string;
454
- /** Sibling variants marked `outcompeted` this run (only with --mark-outcompeted). */
455
- outcompeted?: string[];
456
- }
457
- export interface RunEvolveOuterLoopResult {
458
- exitCode: number;
459
- targets: TargetEvolveSummary[];
460
- error?: string;
461
- }
462
- /**
463
- * Programmatic entrypoint for `self-evolution evolve` — the GA outer loop.
464
- *
465
- * Chains the previously-inert pieces into one live pass:
466
- * groupCandidatesByTarget → (optional replay scoring that APPENDS fitness)
467
- * → rankCandidatesForTarget → select best → human-gated promotion report.
468
- *
469
- * Invariants: frozen targets (per the resolved policy) are skipped; promotion
470
- * is NEVER applied here (the report keeps its human-review gate); the oracle is
471
- * never touched (replay only runs tests).
472
- */
473
- export declare function runEvolveOuterLoopCommand(args: RunEvolveOuterLoopArgs, opts: RunEvolveOuterLoopOptions): Promise<RunEvolveOuterLoopResult>;
474
296
  //# sourceMappingURL=self-evolution.d.ts.map