dsh-continual-evolve 0.10.5 → 0.11.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 +25 -31
  2. package/README.zh.md +18 -21
  3. package/lib/approval.d.ts +19 -6
  4. package/lib/approval.js +73 -12
  5. package/lib/audit-log.d.ts +19 -0
  6. package/lib/audit-log.js +52 -0
  7. package/lib/auto.js +1 -0
  8. package/lib/benchmark-command.d.ts +1 -1
  9. package/lib/benchmark-command.js +1 -1
  10. package/lib/command-util.d.ts +70 -0
  11. package/lib/command-util.js +123 -0
  12. package/lib/command.d.ts +7 -40
  13. package/lib/command.js +25 -459
  14. package/lib/copy.d.ts +4 -41
  15. package/lib/copy.js +0 -102
  16. package/lib/declines.d.ts +2 -0
  17. package/lib/declines.js +4 -0
  18. package/lib/evolve-event.js +2 -1
  19. package/lib/extraction-phase.d.ts +132 -0
  20. package/lib/extraction-phase.js +236 -0
  21. package/lib/index.d.ts +26 -42
  22. package/lib/index.js +24 -34
  23. package/lib/inject.d.ts +13 -119
  24. package/lib/inject.js +12 -260
  25. package/lib/injection-caps.d.ts +4 -0
  26. package/lib/injection-caps.js +4 -0
  27. package/lib/injection-directory.d.ts +29 -0
  28. package/lib/injection-directory.js +78 -0
  29. package/lib/injection-rank.d.ts +85 -0
  30. package/lib/injection-rank.js +161 -0
  31. package/lib/listener.d.ts +45 -0
  32. package/lib/listener.js +199 -0
  33. package/lib/maintenance-commands.d.ts +23 -0
  34. package/lib/maintenance-commands.js +136 -0
  35. package/lib/memory-agent.d.ts +7 -41
  36. package/lib/memory-agent.js +8 -276
  37. package/lib/memory-apply.d.ts +44 -0
  38. package/lib/memory-apply.js +202 -0
  39. package/lib/memory-manifest.d.ts +16 -0
  40. package/lib/memory-manifest.js +70 -0
  41. package/lib/mount-command.js +1 -1
  42. package/lib/notify.d.ts +9 -20
  43. package/lib/notify.js +0 -40
  44. package/lib/plan-command.d.ts +12 -0
  45. package/lib/plan-command.js +38 -0
  46. package/lib/prefix-cache.d.ts +2 -1
  47. package/lib/review-scheduler.d.ts +1 -1
  48. package/lib/review-scheduler.js +2 -10
  49. package/lib/runtime-commands.d.ts +14 -0
  50. package/lib/runtime-commands.js +115 -0
  51. package/lib/service.d.ts +16 -2
  52. package/lib/service.js +74 -1
  53. package/lib/store.d.ts +1 -2
  54. package/lib/store.js +2 -2
  55. package/lib/tool.d.ts +7 -0
  56. package/lib/tool.js +4 -4
  57. package/lib/turn-snapshot.d.ts +7 -1
  58. package/lib/turn-snapshot.js +14 -0
  59. package/lib/types.d.ts +10 -0
  60. package/lib/types.js +6 -0
  61. package/lib/wrapup-candidates.d.ts +127 -0
  62. package/lib/wrapup-candidates.js +161 -0
  63. package/lib/wrapup-command.d.ts +2 -1
  64. package/lib/wrapup-command.js +7 -4
  65. package/lib/wrapup-proposals.d.ts +101 -0
  66. package/lib/wrapup-proposals.js +281 -0
  67. package/lib/wrapup.d.ts +8 -212
  68. package/lib/wrapup.js +4 -438
  69. package/package.json +1 -1
package/lib/command.js CHANGED
@@ -1,24 +1,13 @@
1
- import { ARCHIVED_AT_KEY } from "./types.js";
2
1
  import { formatHarnessStateForPrompt, historyForPrompt } from "./render.js";
3
- import { planWithLlm } from "./planner.js";
4
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
5
- import { join } from "node:path";
6
- import { requireGlobalApproval } from "./approval.js";
7
- import { saveHarnessState } from "./state.js";
8
- import { appendResult, pruneJsonlFile, storePaths } from "./store.js";
9
- import { DEFAULT_REFINEMENTS_RETAIN } from "./store.js";
10
- import { entrySourceOf } from "./source.js";
11
- import { filterLogBySession, formatLogLine, pluginLogFilePath } from "./logfile.js";
12
- import { collectFailureSummary, formatFailureSummary } from "./failures.js";
2
+ import { findEntryById, renderResult, scopeArg, stripAngleBrackets, storeIdForCommand, success, error, tokenizeEvolveInput, } from "./command-util.js";
3
+ import { executePlanCommand } from "./plan-command.js";
4
+ import { executeArchiveCommand, executeConsolidateCommand, executeExportCommand, executeFailuresCommand, executeImportCommand, executeLogCommand, } from "./maintenance-commands.js";
5
+ import { executePauseResumeCommand, executeStatusCommand, executeUsageCommand } from "./runtime-commands.js";
13
6
  import { executeGoalCommand } from "./goal-command.js";
14
7
  import { executeMountCommand, executeUnmountCommand } from "./mount-command.js";
15
8
  import { executeBenchmarkCommand } from "./benchmark-command.js";
16
9
  import { executeWrapupCommand } from "./wrapup-command.js";
17
10
  import { projectKeyOf } from "./project.js";
18
- import { loadUsage, getUsageCount } from "./usage.js";
19
- import { loadGateRuntime, saveGateRuntime } from "./runtime.js";
20
- import { planConsolidation } from "./consolidate.js";
21
- import { loadTokenUsage, renderTokenUsageReport } from "./token-usage.js";
22
11
  const USAGE_COMMON = `Usage:
23
12
  /evolve show this help and the current local store
24
13
  /evolve list [project|global] list entries (default local staging; "project" = this project's cross-session store, "global" = cross-project store)
@@ -51,6 +40,7 @@ const USAGE_ADVANCED = `Advanced:
51
40
  /evolve usage injection counts + exact direct-call token usage (benchmark subagents excluded)`;
52
41
  /** Default help (`/evolve`, `/evolve help`): the commands worth knowing. */
53
42
  const USAGE = USAGE_COMMON;
43
+ export { findEntryById, stripAngleBrackets, tokenizeEvolveInput };
54
44
  export function registerEvolveCommand(ctx, engine, opts, runtime) {
55
45
  ctx.commands.register({
56
46
  name: "evolve",
@@ -59,96 +49,6 @@ export function registerEvolveCommand(ctx, engine, opts, runtime) {
59
49
  handler: (invocation) => executeEvolveCommand(ctx, engine, invocation, opts, runtime),
60
50
  });
61
51
  }
62
- function scopeArg(tokens) {
63
- if (tokens[0] === "global") {
64
- return { scope: "global", rest: tokens.slice(1) };
65
- }
66
- if (tokens[0] === "project") {
67
- return { scope: "project", rest: tokens.slice(1) };
68
- }
69
- return { scope: "local", rest: tokens };
70
- }
71
- /**
72
- * The store id a scope reads/writes from the human command: live session id
73
- * for local, derived project key for project, undefined for global. Throws
74
- * for project when the session cwd is unavailable.
75
- */
76
- function storeIdForCommand(scope, invocation) {
77
- if (scope === "local") {
78
- return invocation.agent.id;
79
- }
80
- if (scope === "project") {
81
- const key = projectKeyOf(invocation.agent);
82
- if (!key) {
83
- throw new Error("project scope needs the session cwd (unavailable here) — use local or global instead");
84
- }
85
- return key;
86
- }
87
- return undefined;
88
- }
89
- /**
90
- * Tokenize a command's raw input with shell-like quoting:
91
- * - a `#` outside quotes starts a comment (rest of the line is dropped);
92
- * - whitespace separates tokens;
93
- * - double or single quotes group words into one token and are stripped.
94
- *
95
- * This lets users paste help-text examples verbatim, e.g.
96
- * `/evolve benchmark add-case <bid> "<title>" "<statement>" "<rubric>"`.
97
- */
98
- export function tokenizeEvolveInput(rawInput) {
99
- const tokens = [];
100
- let current = "";
101
- let quote = null;
102
- for (const char of rawInput) {
103
- if (quote !== null) {
104
- if (char === quote) {
105
- quote = null;
106
- }
107
- else {
108
- current += char;
109
- }
110
- continue;
111
- }
112
- if (char === '"' || char === "'") {
113
- quote = char;
114
- continue;
115
- }
116
- if (char === "#") {
117
- break; // rest of the line is a comment
118
- }
119
- if (/\s/.test(char)) {
120
- if (current.length > 0) {
121
- tokens.push(current);
122
- current = "";
123
- }
124
- continue;
125
- }
126
- current += char;
127
- }
128
- if (current.length > 0) {
129
- tokens.push(current);
130
- }
131
- return tokens;
132
- }
133
- /** Accept both `<id>` (help-text placeholder form) and bare `id`. */
134
- export function stripAngleBrackets(value) {
135
- return value.replace(/^<|>$/g, "");
136
- }
137
- /**
138
- * Locate an entry by id across every kind of a store. Ids are only unique
139
- * within a kind, so the lookup scans all four and returns the first match
140
- * (kind + entry) or undefined. Used by archive/unarchive, which take a bare
141
- * id from the user.
142
- */
143
- export function findEntryById(state, id) {
144
- for (const kind of Object.keys(state.entries)) {
145
- const entry = state.entries[kind][id];
146
- if (entry) {
147
- return [kind, entry];
148
- }
149
- }
150
- return undefined;
151
- }
152
52
  async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
153
53
  const tokens = tokenizeEvolveInput(invocation.rawInput);
154
54
  const sub = tokens[0] ?? "";
@@ -181,226 +81,36 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
181
81
  }
182
82
  case "archive":
183
83
  case "unarchive": {
184
- const { scope, rest: after } = scopeArg(rest);
185
- const id = stripAngleBrackets(after[0] ?? "");
186
- if (!id) {
187
- return error(`${sub} requires an entry id.\n${USAGE}`);
188
- }
189
- const state = engine.load(scope, storeIdForCommand(scope, invocation));
190
- const found = findEntryById(state, id);
191
- if (!found) {
192
- return error(`entry ${id} not found in the ${scope} store`);
193
- }
194
- const [kind, entry] = found;
195
- const metadata = { ...entry.metadata };
196
- if (sub === "archive") {
197
- metadata[ARCHIVED_AT_KEY] = new Date().toISOString();
198
- }
199
- else {
200
- delete metadata[ARCHIVED_AT_KEY];
201
- }
202
- const archived = sub === "archive";
203
- const result = engine.apply(scope, storeIdForCommand(scope, invocation), {
204
- summary: `${archived ? "Archive" : "Unarchive"} entry ${kind}:${id}`,
205
- rationale: "Human-invoked archive/unarchive via the /evolve command.",
206
- expectedOutcome: `Entry ${archived ? "is hidden from injection (data kept, restorable)" : "is injected again"}.`,
207
- edits: [{ action: "update", kind, id, title: entry.title, content: entry.content, metadata }],
208
- }, { scope });
209
- return success(renderResult(result));
210
- }
211
- case "consolidate": {
212
- // R3: deterministic global-store hygiene. Report by default;
213
- // `apply` re-scans fresh state and lands the whole batch as ONE
214
- // refinement (single snapshot + audit record, fully rollback-able).
215
- // `merge` (P1 反膨胀) additionally merges conflict-pair content
216
- // into the surviving original instead of only archiving.
217
- const apply = rest[0] === "apply";
218
- const merge = rest.includes("merge");
219
- const state = engine.load("global", undefined);
220
- const { candidates, edits } = planConsolidation(state, loadUsage(engine.baseDir), Date.now(), { mergeDuplicates: merge });
221
- if (candidates.length === 0) {
222
- return success("global store is already consolidated — no conflict-hinted or stale zero-use entries.");
223
- }
224
- const mergeCount = merge ? candidates.filter((candidate) => candidate.mergeInto).length : 0;
225
- const report = candidates
226
- .map((candidate, index) => {
227
- const mergeNote = candidate.mergeInto && merge ? ` → 内容并入 ${candidate.mergeInto.id}` : "";
228
- return `${index + 1}. [${candidate.kind}:${candidate.id}] ${candidate.title}${mergeNote}\n ${candidate.reason}`;
229
- })
230
- .join("\n");
231
- if (!apply) {
232
- return success(`consolidation plan — ${candidates.length} archive candidate(s):\n${report}\n(run "/evolve consolidate apply" to archive all of them in one refinement; add "merge" to fold near-duplicate content into the survivors)`);
233
- }
234
- const result = engine.apply("global", undefined, {
235
- summary: `Consolidate global store: archive ${candidates.length} entries${mergeCount > 0 ? `, merge ${mergeCount} into survivors` : ""}`,
236
- rationale: "Human-invoked batch consolidation via /evolve consolidate apply.",
237
- expectedOutcome: "Candidate entries are hidden from injection (data kept; restorable via /evolve unarchive). Merged survivors carry the near-duplicate content with a mergedFrom provenance stamp.",
238
- edits,
239
- }, { scope: "global" });
240
- return success(`${report}\n\napplied:\n${renderResult(result)}`);
241
- }
242
- case "failures": {
243
- // /evolve failures — failure-signature aggregation (D1 observation):
244
- // failed review-gate records + failed benchmark cells, counted by class.
245
- const { summary, records } = collectFailureSummary(engine.baseDir);
246
- const parts = formatFailureSummary(summary).split("\n");
247
- const recent = records
248
- .sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""))
249
- .slice(0, 10)
250
- .map((f) => ` [${f.timestamp ?? "(benchmark)"}] ${f.kind} · ${f.source}: ${f.message.slice(0, 140)}`);
251
- if (recent.length > 0) {
252
- parts.push("recent 10:");
253
- parts.push(...recent);
254
- }
255
- return success(parts.join("\n"));
256
- }
257
- case "log": {
258
- // /evolve log [tail N] [session <sessionId>]
259
- let tail = 50;
260
- let sessionFilter;
261
- for (let i = 0; i < rest.length; i += 1) {
262
- const token = rest[i] ?? "";
263
- if (token === "session") {
264
- sessionFilter = stripAngleBrackets(rest[i + 1] ?? "");
265
- if (!sessionFilter) {
266
- return error(`log session requires a session id (e.g. /evolve log session session-abc123).\n${USAGE}`);
267
- }
268
- i += 1;
269
- }
270
- else {
271
- tail = Math.min(Math.max(parsePositiveInt(token, "tail"), 1), 1000);
272
- }
273
- }
274
- const path = pluginLogFilePath(engine.baseDir);
275
- if (!existsSync(path)) {
276
- return success(`(no plugin log yet — ${path} is created on the first log message)`);
277
- }
278
- const lines = readFileSync(path, "utf8").trimEnd().split("\n").filter((line) => line.length > 0);
279
- if (lines.length === 0) {
280
- return success(`(empty plugin log: ${path})`);
281
- }
282
- const filtered = sessionFilter ? filterLogBySession(lines, sessionFilter) : lines;
283
- const shown = filtered.slice(-tail);
284
- const scopeNote = sessionFilter ? `, ${filtered.length} for session ${sessionFilter}` : "";
285
- return success(`plugin log ${path} (${lines.length} lines${scopeNote}, showing last ${shown.length}):\n${shown.map(formatLogLine).join("\n")}`);
286
- }
84
+ return await executeArchiveCommand(ctx, engine, invocation, sub, scopeArg(rest), USAGE);
85
+ }
86
+ case "consolidate":
87
+ return executeConsolidateCommand(engine, rest);
88
+ case "failures":
89
+ return executeFailuresCommand(engine);
90
+ case "log":
91
+ return executeLogCommand(engine, rest, USAGE);
287
92
  case "export": {
288
- const { scope, rest: after } = scopeArg(rest);
289
- const path = after[0];
290
- if (!path) {
291
- return error(`export requires an output path.\n${USAGE}`);
292
- }
293
- const state = engine.load(scope, storeIdForCommand(scope, invocation));
294
- const history = engine.history(scope, storeIdForCommand(scope, invocation));
295
- const payload = {
296
- version: 1,
297
- scope,
298
- schema: state.schema,
299
- entries: state.entries,
300
- refinements: state.refinements,
301
- history,
302
- };
303
- writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
304
- return success(`exported ${scope} store (${Object.values(state.entries).reduce((n, e) => n + Object.keys(e).length, 0)} entries, ${history.length} refinements) to ${path}`);
93
+ const parsed = scopeArg(rest);
94
+ return executeExportCommand(engine, invocation, parsed, USAGE);
305
95
  }
306
96
  case "import": {
307
- const { scope, rest: after } = scopeArg(rest);
308
- const path = after[0];
309
- if (!path) {
310
- return error(`import requires an input path.\n${USAGE}`);
311
- }
312
- const payload = JSON.parse(readFileSync(path, "utf8"));
313
- if (!isValidExport(payload)) {
314
- return error(`invalid export file shape: expected {version, entries: {prompt, memory, skill, subagent}, refinements, history}`);
315
- }
316
- const state = {
317
- schema: typeof payload["schema"] === "number" ? payload["schema"] : 1,
318
- entries: {
319
- prompt: toEntryRecord(payload["entries"]["prompt"]),
320
- memory: toEntryRecord(payload["entries"]["memory"]),
321
- skill: toEntryRecord(payload["entries"]["skill"]),
322
- subagent: toEntryRecord(payload["entries"]["subagent"]),
323
- },
324
- refinements: Array.isArray(payload["refinements"]) ? payload["refinements"] : [],
325
- };
326
- const paths = storePaths(engine.baseDir, scope, storeIdForCommand(scope, invocation));
327
- saveHarnessState(paths.stateDir, state);
328
- if (Array.isArray(payload["history"])) {
329
- for (const result of payload["history"]) {
330
- if (isResultRecord(result)) {
331
- appendResult(paths, result);
332
- }
333
- }
334
- // Storage hygiene (#20): an imported history obeys the
335
- // same tail budget as a live one.
336
- try {
337
- pruneJsonlFile(paths.resultsPath, engine.retention?.refinements ?? DEFAULT_REFINEMENTS_RETAIN);
338
- }
339
- catch {
340
- // ignored — the next apply retries
341
- }
342
- }
343
- return success(`imported ${scope} store from ${path}`);
97
+ const parsed = scopeArg(rest);
98
+ return executeImportCommand(engine, invocation, parsed, parsed.rest[0]);
344
99
  }
345
100
  case "plan": {
346
101
  const { scope, rest: after } = scopeArg(rest);
347
- const instructions = after.length > 0 ? after.join(" ") : undefined;
348
- const state = engine.load(scope, storeIdForCommand(scope, invocation));
349
- const history = engine.history(scope, storeIdForCommand(scope, invocation));
350
- const proposal = await planWithLlm(ctx, {
351
- agent: invocation.agent,
352
- state,
353
- history,
354
- ...(instructions ? { instructions } : {}),
355
- global: scope === "global",
356
- signal: invocation.signal,
357
- tokenUsage: {
358
- baseDir: engine.baseDir,
359
- sessionId,
360
- retain: engine.retention.tokenUsage,
361
- onError: (cause) => ctx.logger("continual-evolve").warn(`token-usage ledger failed for ${sessionId}: ${cause instanceof Error ? cause.message : String(cause)}`),
362
- },
363
- // skill-creator template facts (fallback: builtin guide).
364
- skillsRoot: join(engine.baseDir, "skills"),
365
- });
366
- if ((scope === "global" || scope === "project") && opts.requireGlobalApproval && proposal.edits.length > 0) {
367
- await requireGlobalApproval(ctx, invocation.agent, invocation.signal, `/evolve plan ${scope} 将应用 ${proposal.edits.length} 条编辑到${scope === "project" ? "本项目" : "跨会话"} store:${proposal.summary}`);
368
- }
369
- const result = engine.apply(scope, storeIdForCommand(scope, invocation), proposal, {
370
- scope,
371
- baselineState: state,
372
- ...(entrySourceOf(invocation.agent, sessionId) ? { source: entrySourceOf(invocation.agent, sessionId) } : {}),
373
- });
374
- return success(renderResult(result));
102
+ return await executePlanCommand(ctx, engine, invocation, scope, after, opts, runtime);
375
103
  }
376
104
  case "wrapup": {
377
- return await executeWrapupCommand(ctx, engine, invocation, runtime.promotionPolicy);
105
+ return await executeWrapupCommand(ctx, engine, invocation, runtime.promotionPolicy, runtime.recordLanguage);
378
106
  }
379
107
  case "pause":
380
- case "resume": {
381
- // The switch is independent of the static registration flag. A
382
- // resume explicitly enables the Memory Agent; a pause leaves the
383
- // enable bit intact so status explains the paused state.
384
- const pausing = sub === "pause";
385
- const defaultEnabled = runtime.autoReview ?? false;
386
- const current = loadGateRuntime(engine.baseDir, defaultEnabled);
387
- if (pausing && current.paused) {
388
- return success("automatic Memory Agent is already paused (no change).");
389
- }
390
- if (!pausing && current.enabled && !current.paused) {
391
- return success("automatic Memory Agent is already running (no change).");
392
- }
393
- saveGateRuntime(engine.baseDir, pausing, pausing ? current.enabled : true);
394
- return success(pausing
395
- ? "automatic Memory Agent paused: no automatic memory extraction calls until /evolve resume. Manual evolve_* tools and /evolve commands keep working."
396
- : "automatic Memory Agent resumed: successful turns will produce incremental memory snapshots immediately.");
397
- }
398
- case "status": {
399
- return success(renderGateStatus(engine, sessionId, projectKeyOf(invocation.agent), runtime));
400
- }
401
- case "usage": {
402
- return success(renderUsageReport(engine, sessionId, projectKeyOf(invocation.agent)));
403
- }
108
+ case "resume":
109
+ return executePauseResumeCommand(engine, sub, runtime);
110
+ case "status":
111
+ return executeStatusCommand(engine, sessionId, projectKeyOf(invocation.agent), runtime);
112
+ case "usage":
113
+ return executeUsageCommand(engine, sessionId, projectKeyOf(invocation.agent));
404
114
  case "goal": {
405
115
  return executeGoalCommand(ctx, invocation, rest);
406
116
  }
@@ -421,148 +131,4 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
421
131
  return error(cause instanceof Error ? cause.message : String(cause));
422
132
  }
423
133
  }
424
- /**
425
- * Status answers whether the automatic listener is wired and what
426
- * stores/retention it currently sees.
427
- */
428
- function renderGateStatus(engine, sessionId, projectKey, runtime) {
429
- const configuredDefault = runtime.autoReview === undefined ? "unknown" : runtime.autoReview ? "on" : "off";
430
- const current = loadGateRuntime(engine.baseDir, runtime.autoReview ?? false);
431
- const effective = current.paused
432
- ? "PAUSED (resume with /evolve resume)"
433
- : current.enabled
434
- ? "Memory Agent running"
435
- : "Memory Agent off (enable with /evolve resume)";
436
- const gateLine = `gate: Memory Agent-only listener registered · config default ${configuredDefault} · runtime ${effective}`;
437
- const countEntries = (state) => Object.values(state.entries).reduce((n, byKind) => n + Object.keys(byKind).length, 0);
438
- const lines = [gateLine];
439
- lines.push(`stores: global ${countEntries(engine.load("global", undefined))} entries · local(${sessionId}) ${countEntries(engine.load("local", sessionId))} entries`);
440
- if (projectKey) {
441
- try {
442
- lines[lines.length - 1] += ` · project ${countEntries(engine.load("project", projectKey))} entries`;
443
- }
444
- catch {
445
- lines[lines.length - 1] += " · project (unavailable)";
446
- }
447
- }
448
- const retention = engine.retention;
449
- if (retention) {
450
- lines.push(`retention: snapshots ${retention.snapshots} · refinements ${retention.refinements} · reviews ${retention.reviews} · token usage ${retention.tokenUsage} (historyRetain)`);
451
- }
452
- return lines.join("\n");
453
- }
454
- /**
455
- * #21 P0 usage ledger: injection counts per entry across the stores the
456
- * human can see (global + this session + this project). Counts are
457
- * per-session since the v2 usage shape — "in how many sessions did this
458
- * entry surface" — the exposure half of the #18a归零实验 verdict.
459
- */
460
- function renderUsageReport(engine, sessionId, projectKey) {
461
- const store = loadUsage(engine.baseDir);
462
- const rows = [];
463
- const seen = new Set();
464
- const collect = (state) => {
465
- for (const kind of Object.keys(state.entries)) {
466
- for (const entry of Object.values(state.entries[kind])) {
467
- const key = `${kind}:${entry.id}`;
468
- if (seen.has(key))
469
- continue;
470
- seen.add(key);
471
- const count = getUsageCount(store, kind, entry.id);
472
- rows.push({
473
- key,
474
- title: entry.title,
475
- count,
476
- ...(store.lastSession?.[`${kind}:${entry.id}`] ? { lastSession: store.lastSession[`${kind}:${entry.id}`] } : {}),
477
- });
478
- }
479
- }
480
- };
481
- collect(engine.load("global", undefined));
482
- collect(engine.load("local", sessionId));
483
- if (projectKey) {
484
- try {
485
- collect(engine.load("project", projectKey));
486
- }
487
- catch {
488
- // project store unavailable here — global+local still report
489
- }
490
- }
491
- const liveKeys = new Set(rows.map((r) => r.key));
492
- const orphaned = Object.keys(store.counts).filter((k) => !liveKeys.has(k)).length;
493
- const total = rows.reduce((n, r) => n + r.count, 0);
494
- const injected = rows.filter((r) => r.count > 0).sort((a, b) => b.count - a.count || (a.key < b.key ? -1 : 1));
495
- const stale = rows.filter((r) => r.count === 0).sort((a, b) => (a.key < b.key ? -1 : 1));
496
- const lines = [`injected entries: ${total} injections across ${injected.length} of ${rows.length} stored entries${orphaned > 0 ? ` (+${orphaned} historical key(s) for deleted entries)` : ""}`];
497
- if (injected.length > 0) {
498
- lines.push("injected (top 15):");
499
- for (const row of injected.slice(0, 15)) {
500
- lines.push(` ${row.key} — ${row.count}× · ${row.title}${row.lastSession ? ` (last in ${row.lastSession})` : ""}`);
501
- }
502
- if (injected.length > 15) {
503
- lines.push(` … and ${injected.length - 15} more`);
504
- }
505
- }
506
- else {
507
- lines.push("injected: (none yet — nothing has surfaced into a session prompt)");
508
- }
509
- if (stale.length > 0) {
510
- lines.push(`never injected (${stale.length}):`);
511
- for (const row of stale.slice(0, 20)) {
512
- lines.push(` ${row.key} · ${row.title}`);
513
- }
514
- if (stale.length > 20) {
515
- lines.push(` … and ${stale.length - 20} more`);
516
- }
517
- }
518
- lines.push("", ...renderTokenUsageReport(loadTokenUsage(engine.baseDir), engine.retention.tokenUsage));
519
- return lines.join("\n");
520
- }
521
- function renderResult(result) {
522
- const applied = result.appliedEdits.filter((e) => e.applied);
523
- const failed = result.appliedEdits.filter((e) => !e.applied);
524
- const lines = [
525
- `refinement ${result.id}${result.rollbackOf ? ` (rollback of ${result.rollbackOf})` : ""}: ${applied.length} applied, ${failed.length} failed`,
526
- `summary: ${result.summary}`,
527
- ];
528
- for (const e of applied) {
529
- lines.push(`- ${e.action} ${e.kind}:${e.id} (v${(e.after?.version ?? e.before?.version) ?? "?"})`);
530
- }
531
- for (const e of failed) {
532
- lines.push(`- failed ${e.action} ${e.kind}:${e.id ?? "(computed)"} — ${e.error ?? "unknown error"}`);
533
- }
534
- lines.push(`expected outcome: ${result.expectedOutcome}`);
535
- return lines.join("\n");
536
- }
537
- function toEntryRecord(value) {
538
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
539
- return {};
540
- }
541
- return value;
542
- }
543
- function parsePositiveInt(value, what) {
544
- const n = Number(value);
545
- if (!Number.isInteger(n) || n < 1) {
546
- throw new Error(`${what} must be a positive integer, got "${value}"`);
547
- }
548
- return n;
549
- }
550
- function isValidExport(payload) {
551
- if (typeof payload !== "object" || payload === null)
552
- return false;
553
- const entries = payload["entries"];
554
- if (typeof entries !== "object" || entries === null || Array.isArray(entries))
555
- return false;
556
- const kinds = ["prompt", "memory", "skill", "subagent"];
557
- return kinds.every((kind) => Object.prototype.hasOwnProperty.call(entries, kind));
558
- }
559
- function isResultRecord(value) {
560
- return typeof value === "object" && value !== null && "id" in value && "appliedEdits" in value;
561
- }
562
- function success(text) {
563
- return { kind: "success", text };
564
- }
565
- function error(text) {
566
- return { kind: "error", text };
567
- }
568
134
  //# sourceMappingURL=command.js.map
package/lib/copy.d.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Backend user-visible copy for the four evolution confirmation dialogs,
3
- * in both shipped languages. Labels stay literal per language (the parsers
4
- * match them); guidance lives in `description`.
2
+ * Backend user-visible copy for the evolution confirmation dialogs, in both
3
+ * shipped languages. Labels stay literal per language (the parsers match
4
+ * them); guidance lives in `description`.
5
5
  *
6
6
  * Language is never hardcoded at a call site: every builder takes an
7
7
  * explicit `lang`, and dialog entry points resolve it per call
8
8
  * (explicit config upstream, else the durable DSH client preference,
9
9
  * else `en`) when the caller does not supply one.
10
10
  */
11
- import { type RecordLanguage } from "./record-language.js";
11
+ import type { RecordLanguage } from "./record-language.js";
12
12
  export interface ConfirmOption {
13
13
  label: string;
14
14
  description: string;
@@ -27,18 +27,6 @@ export declare const COPY_WHAT_MAX_CHARS = 300;
27
27
  * @param lang Dialog language.
28
28
  */
29
29
  export declare function approvalCopy(scope: "project" | "global", what: string, lang: RecordLanguage): ConfirmCopy;
30
- export interface FateConsultSections {
31
- promotable: string[];
32
- splits: string[];
33
- reviewArchives: string[];
34
- }
35
- /**
36
- * Local-fate homing consult (`evolve-fate-consult`).
37
- *
38
- * @param sections Pre-rendered per-item lines per group.
39
- * @param lang Dialog language.
40
- */
41
- export declare function fateConsultCopy(sections: FateConsultSections, lang: RecordLanguage): ConfirmCopy;
42
30
  /**
43
31
  * Single-entry wrap-up archive confirm (`evolve-wrapup-archive-review`).
44
32
  *
@@ -46,29 +34,4 @@ export declare function fateConsultCopy(sections: FateConsultSections, lang: Rec
46
34
  * @param lang Dialog language.
47
35
  */
48
36
  export declare function wrapupArchiveCopy(title: string, lang: RecordLanguage): ConfirmCopy;
49
- /**
50
- * Skill solidification consult (`evolve-skill-consult`).
51
- *
52
- * @param description Pre-rendered per-edit lines.
53
- * @param lang Dialog language.
54
- */
55
- export declare function skillConsultCopy(description: string, lang: RecordLanguage): ConfirmCopy;
56
- /**
57
- * One skill-edit line inside the solidification consult.
58
- *
59
- * @param action Edit action (`create` etc.).
60
- * @param title Entry title or id fallback.
61
- * @param skillKind `guidance` skills are SKILL.md documents.
62
- * @param lang Dialog language.
63
- */
64
- export declare function skillEditLine(action: string, title: string, skillKind: string | undefined, lang: RecordLanguage): string;
65
- /**
66
- * Resolve the dialog language for a call site that holds no explicit
67
- * choice: durable client preference first, `en` otherwise. (Dialogs carry
68
- * no trajectory text, so there is no detection tier here.)
69
- *
70
- * @param ctx Host context for the durable-preference read.
71
- * @returns The dialog language.
72
- */
73
- export declare function resolveDialogLanguage(ctx: unknown): RecordLanguage;
74
37
  //# sourceMappingURL=copy.d.ts.map