dsh-continual-evolve 0.10.4 → 0.10.6

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/lib/command.js CHANGED
@@ -1,6 +1,5 @@
1
- import { ARCHIVED_AT_KEY, MEMORY_TYPE_KEY } from "./types.js";
1
+ import { ARCHIVED_AT_KEY } from "./types.js";
2
2
  import { formatHarnessStateForPrompt, historyForPrompt } from "./render.js";
3
- import { compactText } from "./render.js";
4
3
  import { planWithLlm } from "./planner.js";
5
4
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
5
  import { join } from "node:path";
@@ -19,9 +18,8 @@ import { projectKeyOf } from "./project.js";
19
18
  import { loadUsage, getUsageCount } from "./usage.js";
20
19
  import { loadGateRuntime, saveGateRuntime } from "./runtime.js";
21
20
  import { planConsolidation } from "./consolidate.js";
22
- import { formatRecallResult, recallMemories, RECALL_MEMORY_TYPES } from "./recall.js";
23
21
  import { loadTokenUsage, renderTokenUsageReport } from "./token-usage.js";
24
- const USAGE = `Usage:
22
+ const USAGE_COMMON = `Usage:
25
23
  /evolve show this help and the current local store
26
24
  /evolve list [project|global] list entries (default local staging; "project" = this project's cross-session store, "global" = cross-project store)
27
25
  /evolve history [global] show applied refinements (rollback ids)
@@ -31,19 +29,16 @@ const USAGE = `Usage:
31
29
  to the global store (approval required), archive one-offs
32
30
  /evolve archive <id> [global] hide an entry from injection (data kept, restorable)
33
31
  /evolve unarchive <id> [global] restore an archived entry
34
- /evolve demote <id> hide a (global) entry from injection, keep data
35
- /evolve recall <query...> [scope] targeted memory recall: full content with version,
36
- source, and staleness (same engine as the evolve_recall tool)
37
- /evolve remember <type> [scope] <text...>
38
- immediately persist one memory (type = user|feedback|project|reference;
39
- feedback/project text must carry Why + How; default scope local)
40
- /evolve forget [scope] <query...> locate one memory by query and archive it (restorable via /evolve unarchive)
32
+ /evolve pause | resume pause/resume the automatic Memory Agent (manual tools and commands keep working)
33
+ /evolve status automatic Memory Agent state plus store entry counts
34
+ /evolve help all every subcommand, including the advanced ones below`;
35
+ const USAGE_ADVANCED = `Advanced:
41
36
  /evolve consolidate [apply] [merge]
42
- report (or apply) a batch archive of conflict-hinted
43
- and stale zero-use global entries; "merge" folds
44
- near-duplicate content into the surviving original
45
- /evolve log [tail N] show the recent plugin log (default 50 lines)
37
+ report (or apply) a batch archive of conflict-hinted
38
+ and stale zero-use global entries; "merge" folds
39
+ near-duplicate content into the surviving original
46
40
  /evolve failures aggregated failure counts (gate + benchmark, by class)
41
+ /evolve log [tail N] show the recent plugin log (default 50 lines)
47
42
  /evolve export [global] <path> backup a store to a JSON file
48
43
  /evolve import [global] <path> restore a store from an export file
49
44
  /evolve mount <skillId> hot-mount a skill entry as a live cordis plugin
@@ -52,14 +47,15 @@ const USAGE = `Usage:
52
47
  /evolve goal show the evolution goal (round-driven auto-review)
53
48
  /evolve goal <objective> create/update the evolution goal
54
49
  /evolve goal done complete the evolution goal
55
- /evolve pause | resume pause/resume the automatic Memory Agent (manual tools and commands keep working)
56
- /evolve status automatic Memory Agent state plus store entry counts
50
+ /evolve benchmark ... case lifecycle, runs, acceptance
57
51
  /evolve usage injection counts + exact direct-call token usage (benchmark subagents excluded)`;
52
+ /** Default help (`/evolve`, `/evolve help`): the commands worth knowing. */
53
+ const USAGE = USAGE_COMMON;
58
54
  export function registerEvolveCommand(ctx, engine, opts, runtime) {
59
55
  ctx.commands.register({
60
56
  name: "evolve",
61
57
  description: "自进化:检查并演进 harness 状态(记忆/技能/提示词/子代理) | Inspect and evolve the continual harness state (memories, skills, prompt notes, subagent specs)",
62
- input: { hint: "[list [global] | history [global] | rollback <id> [global] | plan [msg]]" },
58
+ input: { hint: "[list [global] | history [global] | rollback <id> [global] | plan [msg] | help all]" },
63
59
  handler: (invocation) => executeEvolveCommand(ctx, engine, invocation, opts, runtime),
64
60
  });
65
61
  }
@@ -162,7 +158,9 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
162
158
  switch (sub) {
163
159
  case "":
164
160
  case "help":
165
- return success(`${USAGE}\n\n${formatHarnessStateForPrompt(engine.load("local", sessionId))}`);
161
+ // Layered help (2026-10-01): the default listing stays short;
162
+ // `help all` expands the advanced half.
163
+ return success(`${rest[0] === "all" ? `${USAGE_COMMON}\n\n${USAGE_ADVANCED}` : USAGE_COMMON}\n\n${formatHarnessStateForPrompt(engine.load("local", sessionId))}`);
166
164
  case "list": {
167
165
  const { scope } = scopeArg(rest);
168
166
  return success(formatHarnessStateForPrompt(engine.load(scope, storeIdForCommand(scope, invocation))));
@@ -182,16 +180,12 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
182
180
  return success(renderResult(result));
183
181
  }
184
182
  case "archive":
185
- case "unarchive":
186
- case "demote": {
183
+ case "unarchive": {
187
184
  const { scope, rest: after } = scopeArg(rest);
188
185
  const id = stripAngleBrackets(after[0] ?? "");
189
186
  if (!id) {
190
187
  return error(`${sub} requires an entry id.\n${USAGE}`);
191
188
  }
192
- if (sub === "demote") {
193
- return demoteEntry(engine, id, sessionId, projectKeyOf(invocation.agent));
194
- }
195
189
  const state = engine.load(scope, storeIdForCommand(scope, invocation));
196
190
  const found = findEntryById(state, id);
197
191
  if (!found) {
@@ -214,90 +208,6 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
214
208
  }, { scope });
215
209
  return success(renderResult(result));
216
210
  }
217
- case "recall": {
218
- // Human counterpart of the evolve_recall tool: same engine,
219
- // same filters (memory kind, one scope or all three).
220
- const { scope, rest: after } = scopeArg(rest);
221
- const explicitScope = rest.length > 0 && (rest[0] === "global" || rest[0] === "project" || rest[0] === "local");
222
- const query = after.join(" ");
223
- const result = recallMemories(engine, { sessionId, projectKey: projectKeyOf(invocation.agent) ?? undefined }, { kinds: ["memory"], ...(explicitScope ? { scopes: [scope] } : {}), ...(query ? { query } : {}), limit: 10 });
224
- return success(formatRecallResult(result, query || undefined));
225
- }
226
- case "remember": {
227
- // Immediate manual memory path (P1 §6.6): one fact, explicit
228
- // type, straight into the engine — validation, snapshot,
229
- // versioning, and approval behave exactly like any other
230
- // write. feedback/project text must already carry Why + How;
231
- // the engine rejects it loudly otherwise.
232
- const type = rest[0] ?? "";
233
- if (!RECALL_MEMORY_TYPES.includes(type)) {
234
- return error(`remember requires a memory type: user|feedback|project|reference.\nExample: /evolve remember feedback local 对方偏好深色主题。Why:…… How to apply:……\n${USAGE}`);
235
- }
236
- const maybeScope = rest[1] ?? "";
237
- const scoped = maybeScope === "global" || maybeScope === "project" || maybeScope === "local";
238
- const scope = scoped ? maybeScope : "local";
239
- const text = (scoped ? rest.slice(2) : rest.slice(1)).join(" ").trim();
240
- if (!text) {
241
- return error(`remember requires the memory text after the type.\n${USAGE}`);
242
- }
243
- const storeId = storeIdForCommand(scope, invocation);
244
- if ((scope === "global" || scope === "project") && opts.requireGlobalApproval) {
245
- await requireGlobalApproval(ctx, invocation.agent, invocation.signal, `/evolve remember ${type} → ${scope} store: ${compactText(text, 120)}`);
246
- }
247
- const result = engine.apply(scope, storeId, {
248
- summary: `remember: ${compactText(text, 80)}`,
249
- rationale: "Human-invoked immediate memory via /evolve remember.",
250
- expectedOutcome: "The fact is persisted as a typed memory entry.",
251
- edits: [{
252
- action: "create",
253
- kind: "memory",
254
- title: compactText(text, 80),
255
- content: text,
256
- metadata: { [MEMORY_TYPE_KEY]: type },
257
- }],
258
- }, { scope });
259
- return success(renderResult(result));
260
- }
261
- case "forget": {
262
- // Manual forget path (P1 §6.6): locate by query, archive the
263
- // single match (restorable via unarchive). An ambiguous query
264
- // lists candidates instead of archiving — a silent multi-
265
- // archive would look like success while hiding too much.
266
- const { scope, rest: after } = scopeArg(rest);
267
- const explicitScope = rest.length > 0 && (rest[0] === "global" || rest[0] === "project" || rest[0] === "local");
268
- const query = after.join(" ").trim();
269
- if (!query) {
270
- return error(`forget requires a query to locate the memory.\n${USAGE}`);
271
- }
272
- const recalled = recallMemories(engine, { sessionId, projectKey: projectKeyOf(invocation.agent) ?? undefined }, { query, kinds: ["memory"], ...(explicitScope ? { scopes: [scope] } : {}), limit: 6, includeArchived: false });
273
- if (recalled.hits.length === 0) {
274
- return error(`no memory matches "${query}"${recalled.notes.length > 0 ? ` (${recalled.notes.join("; ")})` : ""} — try fewer words, or /evolve list to browse.`);
275
- }
276
- if (recalled.hits.length > 1) {
277
- const list = recalled.hits.map((hit) => `- [${hit.scope}:${hit.id}] ${hit.title}`).join("\n");
278
- return success(`"${query}" matches ${recalled.hits.length} memories — refine the query so exactly one matches, then forget archives it:\n${list}`);
279
- }
280
- const hit = recalled.hits[0];
281
- const state = engine.load(hit.scope, hit.scope === "local" ? sessionId : hit.scope === "project" ? (projectKeyOf(invocation.agent) ?? undefined) : undefined);
282
- const entry = state.entries.memory[hit.id];
283
- if (!entry) {
284
- return error(`memory ${hit.scope}:${hit.id} vanished since recall — run /evolve forget again.`);
285
- }
286
- const result = engine.apply(hit.scope, hit.scope === "local" ? sessionId : hit.scope === "project" ? (projectKeyOf(invocation.agent) ?? undefined) : undefined, {
287
- summary: `forget: archive memory ${hit.scope}:${hit.id}`,
288
- rationale: `Human-invoked forget via /evolve forget "${query}".`,
289
- expectedOutcome: "The entry is hidden from injection (data kept; restorable via /evolve unarchive).",
290
- edits: [{
291
- action: "update",
292
- kind: "memory",
293
- id: hit.id,
294
- title: entry.title,
295
- content: entry.content,
296
- metadata: { ...entry.metadata, [ARCHIVED_AT_KEY]: new Date().toISOString() },
297
- }],
298
- }, { scope: hit.scope });
299
- return success(`forgot [${hit.scope}:${hit.id}] ${hit.title} (archived — restore with /evolve unarchive ${hit.id}${hit.scope === "local" ? "" : ` ${hit.scope}`})\n${renderResult(result)}`);
300
- }
301
211
  case "consolidate": {
302
212
  // R3: deterministic global-store hygiene. Report by default;
303
213
  // `apply` re-scans fresh state and lands the whole batch as ONE
@@ -454,7 +364,7 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
454
364
  skillsRoot: join(engine.baseDir, "skills"),
455
365
  });
456
366
  if ((scope === "global" || scope === "project") && opts.requireGlobalApproval && proposal.edits.length > 0) {
457
- await requireGlobalApproval(ctx, invocation.agent, invocation.signal, `/evolve plan ${scope} 将应用 ${proposal.edits.length} 条编辑到${scope === "project" ? "本项目" : "跨会话"} store:${proposal.summary}`);
367
+ await requireGlobalApproval(ctx, invocation.agent, invocation.signal, `/evolve plan ${scope} 将应用 ${proposal.edits.length} 条编辑到${scope === "project" ? "本项目" : "跨会话"} store:${proposal.summary}`, runtime.recordLanguage);
458
368
  }
459
369
  const result = engine.apply(scope, storeIdForCommand(scope, invocation), proposal, {
460
370
  scope,
@@ -464,7 +374,7 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
464
374
  return success(renderResult(result));
465
375
  }
466
376
  case "wrapup": {
467
- return await executeWrapupCommand(ctx, engine, invocation, runtime.promotionPolicy);
377
+ return await executeWrapupCommand(ctx, engine, invocation, runtime.promotionPolicy, runtime.recordLanguage);
468
378
  }
469
379
  case "pause":
470
380
  case "resume": {
@@ -511,44 +421,6 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
511
421
  return error(cause instanceof Error ? cause.message : String(cause));
512
422
  }
513
423
  }
514
- /**
515
- * Demote (2026-08-22): hide an entry from injection WITHOUT deleting it —
516
- * the one-command remedy for store pollution. Searches the global store
517
- * first (the primary target: cross-project noise), then the project store,
518
- * then the session's local store. The data stays; `/evolve unarchive` restores it.
519
- */
520
- function demoteEntry(engine, id, sessionId, projectKey) {
521
- const targets = [
522
- { scope: "global", storeId: undefined },
523
- ...(projectKey ? [{ scope: "project", storeId: projectKey }] : []),
524
- { scope: "local", storeId: sessionId },
525
- ];
526
- for (const { scope, storeId } of targets) {
527
- const state = engine.load(scope, storeId);
528
- const found = findEntryById(state, id);
529
- if (!found)
530
- continue;
531
- const [kind, entry] = found;
532
- const result = engine.apply(scope, storeId, {
533
- summary: `demote: archive ${kind}:${id} from the ${scope} store`,
534
- rationale: "Human-invoked demote via the /evolve command.",
535
- expectedOutcome: "The entry is hidden from injection in every scope it touched; data is kept and restorable.",
536
- edits: [
537
- {
538
- action: "update",
539
- kind,
540
- id,
541
- title: entry.title,
542
- content: entry.content,
543
- metadata: { ...entry.metadata, [ARCHIVED_AT_KEY]: new Date().toISOString() },
544
- },
545
- ],
546
- }, { scope });
547
- const restoreScope = scope === "global" ? " global" : scope === "project" ? " project" : "";
548
- return success(`demoted ${kind}:${id} from the ${scope} store (archived — restore with /evolve unarchive ${id}${restoreScope})\n${renderResult(result)}`);
549
- }
550
- return error(`entry ${id} not found in the global, project, or local store`);
551
- }
552
424
  /**
553
425
  * Status answers whether the automatic listener is wired and what
554
426
  * stores/retention it currently sees.
package/lib/index.d.ts CHANGED
@@ -98,6 +98,24 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
98
98
  promotionMinChars: z<number, number, "defined">;
99
99
  /** Entry-directory lines injected per build before folding into a counter. */
100
100
  injectionDirectoryLines: z<number, number, "defined">;
101
+ /**
102
+ * evolve v2 memory index: the section that injects memory CONTENT (not just
103
+ * an id/title directory) at session start, frozen per session to keep the
104
+ * system prompt byte-stable (prompt cache). `enabled`/`guide`/`order`/
105
+ * `maxChars` default to true/true/400/6000; with `guide` off and an empty
106
+ * store the section renders to "" and costs no tokens.
107
+ */
108
+ memoryIndex: z<Schemastery.ObjectS<NoInfer<{
109
+ enabled: z<boolean, boolean, "plain">;
110
+ guide: z<boolean, boolean, "plain">;
111
+ order: z<number, number, "plain">;
112
+ maxChars: z<number, number, "plain">;
113
+ }>>, Schemastery.ObjectT<NoInfer<{
114
+ enabled: z<boolean, boolean, "plain">;
115
+ guide: z<boolean, boolean, "plain">;
116
+ order: z<number, number, "plain">;
117
+ maxChars: z<number, number, "plain">;
118
+ }>>, "plain">;
101
119
  /**
102
120
  * Storage hygiene (#20): how much append-only past each write keeps.
103
121
  * `snapshots` = full-state copies per store, `refinements` = tail lines
@@ -210,6 +228,24 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
210
228
  promotionMinChars: z<number, number, "defined">;
211
229
  /** Entry-directory lines injected per build before folding into a counter. */
212
230
  injectionDirectoryLines: z<number, number, "defined">;
231
+ /**
232
+ * evolve v2 memory index: the section that injects memory CONTENT (not just
233
+ * an id/title directory) at session start, frozen per session to keep the
234
+ * system prompt byte-stable (prompt cache). `enabled`/`guide`/`order`/
235
+ * `maxChars` default to true/true/400/6000; with `guide` off and an empty
236
+ * store the section renders to "" and costs no tokens.
237
+ */
238
+ memoryIndex: z<Schemastery.ObjectS<NoInfer<{
239
+ enabled: z<boolean, boolean, "plain">;
240
+ guide: z<boolean, boolean, "plain">;
241
+ order: z<number, number, "plain">;
242
+ maxChars: z<number, number, "plain">;
243
+ }>>, Schemastery.ObjectT<NoInfer<{
244
+ enabled: z<boolean, boolean, "plain">;
245
+ guide: z<boolean, boolean, "plain">;
246
+ order: z<number, number, "plain">;
247
+ maxChars: z<number, number, "plain">;
248
+ }>>, "plain">;
213
249
  /**
214
250
  * Storage hygiene (#20): how much append-only past each write keeps.
215
251
  * `snapshots` = full-state copies per store, `refinements` = tail lines
package/lib/index.js CHANGED
@@ -8,6 +8,10 @@
8
8
  * initial default when `evolve/runtime.json` does not exist yet, and
9
9
  * `/evolve pause|resume` own it afterwards. Generic review/planner/fate are
10
10
  * not reachable from this listener.
11
+ *
12
+ * evolve v2 registers a third section (`evolve:memory-index`) that injects
13
+ * memory content plus the when_to_save guide, frozen per session — see
14
+ * memory-index.ts.
11
15
  */
12
16
  import { join } from "node:path";
13
17
  import z from "@deepseek-ai/schemastery";
@@ -18,6 +22,7 @@ import { registerEvolveCommand } from "./command.js";
18
22
  import { registerAutoReview } from "./auto.js";
19
23
  import { syncSkillsFromResult } from "./skill.js";
20
24
  import { entriesSectionText } from "./inject.js";
25
+ import { createFrozenMemorySection, DEFAULT_MEMORY_INDEX_MAX_CHARS, DEFAULT_MEMORY_SECTION_ORDER, MEMORY_SECTION_NAME, memoryIndexSectionText, } from "./memory-index.js";
21
26
  import { resolveRubricKey } from "./rubric.js";
22
27
  import { restoreMounted } from "./mount.js";
23
28
  import { registerFileLogger } from "./logfile.js";
@@ -119,6 +124,19 @@ export const Config = z.object({
119
124
  promotionMinChars: z.natural().default(100),
120
125
  /** Entry-directory lines injected per build before folding into a counter. */
121
126
  injectionDirectoryLines: z.natural().default(15),
127
+ /**
128
+ * evolve v2 memory index: the section that injects memory CONTENT (not just
129
+ * an id/title directory) at session start, frozen per session to keep the
130
+ * system prompt byte-stable (prompt cache). `enabled`/`guide`/`order`/
131
+ * `maxChars` default to true/true/400/6000; with `guide` off and an empty
132
+ * store the section renders to "" and costs no tokens.
133
+ */
134
+ memoryIndex: z.object({
135
+ enabled: z.boolean(),
136
+ guide: z.boolean(),
137
+ order: z.natural(),
138
+ maxChars: z.natural(),
139
+ }),
122
140
  /**
123
141
  * Storage hygiene (#20): how much append-only past each write keeps.
124
142
  * `snapshots` = full-state copies per store, `refinements` = tail lines
@@ -162,12 +180,39 @@ export function apply(ctx, config) {
162
180
  // entries real delegation specs. The text is a provider evaluated at every
163
181
  // assembly with the assembling agent; a store without prompt/subagent
164
182
  // entries renders to "" and the prompt renderer drops the section.
183
+ //
184
+ // evolve v2: when the memory index section carries memory content, the
185
+ // directory stops listing memories so the same entries are never paid for
186
+ // twice (one fact, one home).
187
+ const memoryIndexEnabled = config.memoryIndex?.enabled ?? true;
165
188
  ctx.systemPrompt.section({
166
189
  name: "tool:continual-evolve:entries",
167
190
  order: (config.sectionOrder ?? 118) + 1,
168
- text: (context) => entriesSectionText(engine, context.agent, undefined, { directoryLines: config.injectionDirectoryLines ?? 15 }),
191
+ text: (context) => entriesSectionText(engine, context.agent, undefined, {
192
+ directoryLines: config.injectionDirectoryLines ?? 15,
193
+ includeMemoryDirectory: !memoryIndexEnabled,
194
+ }),
169
195
  });
170
- const gate = { requireGlobalApproval: config.requireGlobalApproval ?? true };
196
+ // evolve v2 passive read path: memory content in the prompt at session
197
+ // start, frozen per session (see memory-index.ts) so the whole system
198
+ // prompt stays byte-stable within a session and prompt-cache reads keep
199
+ // hitting. Registered in the empty 1–499 order slot — the two sections
200
+ // above sit at 118/119, upstream's named slots start at PLAN_POLICY=500.
201
+ if (memoryIndexEnabled) {
202
+ const frozenMemories = createFrozenMemorySection();
203
+ const memoryIndexOrder = config.memoryIndex?.order ?? DEFAULT_MEMORY_SECTION_ORDER;
204
+ const memoryIndexMaxChars = config.memoryIndex?.maxChars ?? DEFAULT_MEMORY_INDEX_MAX_CHARS;
205
+ const memoryIndexGuide = config.memoryIndex?.guide ?? true;
206
+ ctx.systemPrompt.section({
207
+ name: MEMORY_SECTION_NAME,
208
+ order: memoryIndexOrder,
209
+ text: (context) => frozenMemories.textFor(context.agent, (agent) => memoryIndexSectionText(engine, agent, { maxChars: memoryIndexMaxChars, guide: memoryIndexGuide })),
210
+ });
211
+ }
212
+ const gate = {
213
+ requireGlobalApproval: config.requireGlobalApproval ?? true,
214
+ ...(config.recordLanguage !== undefined ? { recordLanguage: config.recordLanguage } : {}),
215
+ };
171
216
  const promotionPolicy = resolvePromotionPolicy({
172
217
  blockPatterns: config.promotionBlockPatterns,
173
218
  minPromoteChars: config.promotionMinChars,
@@ -180,6 +225,7 @@ export function apply(ctx, config) {
180
225
  autoCase: config.autoCase ?? true,
181
226
  promotionPolicy,
182
227
  autoReview: config.autoReview ?? false,
228
+ ...(config.recordLanguage !== undefined ? { recordLanguage: config.recordLanguage } : {}),
183
229
  });
184
230
  // Plugin-owned file logging: every cordis log message lands in
185
231
  // <baseDir>/evolve/plugin.log regardless of how dsh web was launched —
package/lib/inject.d.ts CHANGED
@@ -158,6 +158,16 @@ export declare function rankedDirectoryEntries(maxLines: number, query: string |
158
158
  * beyond the nearest carrying store).
159
159
  */
160
160
  export declare function nearestLocalStateWithEntries(engine: EvolutionEngine, agent: AgentLike): HarnessState | undefined;
161
+ /**
162
+ * The single three-store merge every injection path reads from: the global
163
+ * store merged with this project's store (when the session cwd resolves one)
164
+ * and the nearest carrying local store (precedence global < project < local —
165
+ * see {@link mergeHarnessStates}). One home, so the memory section and the
166
+ * entry directory can never disagree about what the model can see.
167
+ */
168
+ export declare function mergedInjectionState(engine: EvolutionEngine, agent: AgentLike, opts?: {
169
+ projectKey?: string;
170
+ }): HarnessState;
161
171
  /**
162
172
  * Compose the full injected block for one assembling agent: global entries
163
173
  * merged with this project's store (when the session cwd resolves one) and
@@ -171,6 +181,9 @@ export declare function nearestLocalStateWithEntries(engine: EvolutionEngine, ag
171
181
  * `opts.directoryLines` caps the entry-directory index (2026-08-22 throttle);
172
182
  * `opts.projectKey` pins the project layer explicitly (tests, tools) —
173
183
  * otherwise it is derived from the agent's session cwd, best-effort.
184
+ * `opts.includeMemoryDirectory` (default true) drops memory lines from the
185
+ * directory when the memory section already carries them (evolve v2) — one
186
+ * fact, one home, so the same memories are never paid for twice.
174
187
  * Usage recording covers ALL kinds — memories and skills appear as directory
175
188
  * lines, prompts/subagents as content — and is deduped per session so the
176
189
  * counts read "how many sessions saw this", not "how many prompt builds".
@@ -178,5 +191,6 @@ export declare function nearestLocalStateWithEntries(engine: EvolutionEngine, ag
178
191
  export declare function entriesSectionText(engine: EvolutionEngine, agent: AgentLike | undefined, query?: string, opts?: {
179
192
  directoryLines?: number;
180
193
  projectKey?: string;
194
+ includeMemoryDirectory?: boolean;
181
195
  }): string;
182
196
  //# sourceMappingURL=inject.d.ts.map
package/lib/inject.js CHANGED
@@ -324,6 +324,20 @@ export function nearestLocalStateWithEntries(engine, agent) {
324
324
  }
325
325
  return undefined;
326
326
  }
327
+ /**
328
+ * The single three-store merge every injection path reads from: the global
329
+ * store merged with this project's store (when the session cwd resolves one)
330
+ * and the nearest carrying local store (precedence global < project < local —
331
+ * see {@link mergeHarnessStates}). One home, so the memory section and the
332
+ * entry directory can never disagree about what the model can see.
333
+ */
334
+ export function mergedInjectionState(engine, agent, opts) {
335
+ const globalState = engine.load("global", undefined);
336
+ const projectKey = opts?.projectKey ?? projectKeyOf(agent);
337
+ const projectState = projectKey ? engine.load("project", projectKey) : undefined;
338
+ const localState = nearestLocalStateWithEntries(engine, agent);
339
+ return mergeHarnessStates(globalState, localState, projectState ? { projectState } : undefined);
340
+ }
327
341
  /**
328
342
  * Compose the full injected block for one assembling agent: global entries
329
343
  * merged with this project's store (when the session cwd resolves one) and
@@ -337,6 +351,9 @@ export function nearestLocalStateWithEntries(engine, agent) {
337
351
  * `opts.directoryLines` caps the entry-directory index (2026-08-22 throttle);
338
352
  * `opts.projectKey` pins the project layer explicitly (tests, tools) —
339
353
  * otherwise it is derived from the agent's session cwd, best-effort.
354
+ * `opts.includeMemoryDirectory` (default true) drops memory lines from the
355
+ * directory when the memory section already carries them (evolve v2) — one
356
+ * fact, one home, so the same memories are never paid for twice.
340
357
  * Usage recording covers ALL kinds — memories and skills appear as directory
341
358
  * lines, prompts/subagents as content — and is deduped per session so the
342
359
  * counts read "how many sessions saw this", not "how many prompt builds".
@@ -345,11 +362,7 @@ export function entriesSectionText(engine, agent, query, opts) {
345
362
  if (!agent) {
346
363
  return "";
347
364
  }
348
- const globalState = engine.load("global", undefined);
349
- const projectKey = opts?.projectKey ?? projectKeyOf(agent);
350
- const projectState = projectKey ? engine.load("project", projectKey) : undefined;
351
- const localState = nearestLocalStateWithEntries(engine, agent);
352
- const merged = mergeHarnessStates(globalState, localState, projectState ? { projectState } : undefined);
365
+ const merged = mergedInjectionState(engine, agent, opts);
353
366
  const promptEntries = Object.values(merged.entries.prompt);
354
367
  const subagentEntries = Object.values(merged.entries.subagent);
355
368
  const relevanceQuery = (query ?? recentUserText(agent)).trim();
@@ -371,7 +384,9 @@ export function entriesSectionText(engine, agent, query, opts) {
371
384
  const directoryLines = opts?.directoryLines ?? DEFAULT_DIRECTORY_LINES;
372
385
  const promptKind = Object.values(merged.entries.prompt);
373
386
  const subagentKind = Object.values(merged.entries.subagent);
374
- const memoryKind = Object.values(merged.entries.memory);
387
+ // v2: memories live in their own content-carrying section, so the directory
388
+ // stops listing them (passing [] keeps the positional contract intact).
389
+ const memoryKind = (opts?.includeMemoryDirectory ?? true) ? Object.values(merged.entries.memory) : [];
375
390
  const skillKind = Object.values(merged.entries.skill);
376
391
  const directoryText = formatEntriesDirectoryRanked(directoryLines, relevanceQuery, promptKind, subagentKind, memoryKind, skillKind);
377
392
  // Directory-visible keys count too: a memory's injection IS its directory
@@ -15,7 +15,7 @@ import { type LlmSessionId } from "./llm-text.js";
15
15
  import { type PrefixCacheOptions } from "./prefix-cache.js";
16
16
  import { type EvolutionEngine } from "./service.js";
17
17
  import { type TokenUsageTarget } from "./token-usage.js";
18
- import { type RecordLanguage } from "./record-language.js";
18
+ import { type RecordLanguage, type RecordLanguagePreference } from "./record-language.js";
19
19
  import type { EntrySource, HarnessEntry, HarnessScope, HarnessState, RefinementEdit, RefinementProposal, RefinementResult } from "./types.js";
20
20
  /** ZCode parity: the extractor may inspect memory and propose at most five times. */
21
21
  export declare const MEMORY_AGENT_MAX_TURNS = 5;
@@ -110,6 +110,8 @@ export interface ApplyMemoryProposalOptions {
110
110
  scopeDecisions?: Readonly<Record<string, ScopeApprovalDecision>>;
111
111
  /** Persist each decision before a later scope can fail. */
112
112
  onScopeDecision?: (key: string, decision: ScopeApprovalDecision) => void;
113
+ /** Plugin `recordLanguage` preference for the approval dialog copy. */
114
+ recordLanguage?: RecordLanguagePreference;
113
115
  }
114
116
  /**
115
117
  * Render the authoritative persistent-scope diff shown to the human. Model
@@ -362,7 +362,7 @@ export async function applyMemoryExtractionProposal(ctx, engine, proposal, optio
362
362
  declinedScopes.push(scope);
363
363
  continue;
364
364
  }
365
- const approved = await requestScopeApproval(ctx, options.agent, options.signal, scope, renderMemoryApprovalDetails(proposal, scope, baseline));
365
+ const approved = await requestScopeApproval(ctx, options.agent, options.signal, scope, renderMemoryApprovalDetails(proposal, scope, baseline), options.recordLanguage);
366
366
  if (decisionKey !== undefined)
367
367
  options.onScopeDecision?.(decisionKey, approved);
368
368
  if (approved === "approved")
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The persistent-memory guide injected as part of the `evolve:memory-index`
3
+ * system-prompt section (evolve v2, change two).
4
+ *
5
+ * This text is the write-path half of the v2 plan: it teaches the model when a
6
+ * durable memory is worth creating, which recall type it belongs to, what body
7
+ * structure each type owes, and that the main session may write directly with
8
+ * `evolve_add` instead of waiting for the background pipeline.
9
+ *
10
+ * Provenance: `docs/research/evolve-v2-passive-read-write-plan.md` §改动二
11
+ * (Chinese full text, adapted from ZCode's persistent-memory prompt). Kept in
12
+ * its own module so the wording is a single home — `memory-index.ts` only
13
+ * composes it with the `<memories>` block.
14
+ */
15
+ /** Heading and framing before the `<memories>` block. */
16
+ export declare const MEMORY_GUIDE_INTRO = "# \u6301\u4E45\u8BB0\u5FC6\n\n\u4F60\u6709\u4E00\u4E2A\u8DE8\u4F1A\u8BDD\u7684\u6301\u4E45\u8BB0\u5FC6\u7CFB\u7EDF\u3002\u4EE5\u4E0B\u662F\u5F53\u524D\u8BB0\u5FC6\uFF08\u5DF2\u76F4\u63A5\u6CE8\u5165\uFF0C\u65E0\u9700\u67E5\u8BE2\u5373\u53EF\u4F7F\u7528\uFF1B\u9700\u8981\u68C0\u7D22\u5B8C\u6574\u5185\u5BB9\u6216\u786E\u8BA4\u662F\u5426\u6709\u76F8\u5173\u8BB0\u5FC6\u65F6\uFF0C\u8C03\u7528 evolve_recall\uFF09\u3002\u672C\u4F1A\u8BDD\u5185\u53EF\u4EE5\u76F4\u63A5\u7528 evolve_add \u843D\u76D8\uFF0C\u4E0D\u5FC5\u7B49\u540E\u53F0\u6D41\u7A0B\u2014\u2014\u9ED8\u8BA4\u5199 local\uFF0C\u53EA\u6709\u7A33\u5B9A\u7684\u8DE8\u9879\u76EE\u6559\u8BAD\u624D\u5199 global\uFF08global \u5199\u5165\u4F1A\u5F39\u4EBA\u5DE5\u6279\u51C6\uFF09\u3002";
17
+ /** The when_to_save / how_to_use rules and maintenance discipline. */
18
+ export declare const MEMORY_GUIDE_RULES = "## \u4F55\u65F6\u5199\u5165\u8BB0\u5FC6\n\n\u7528\u6237\u660E\u786E\u8981\u6C42\u8BB0\u4F4F\u65F6\uFF0C\u7ACB\u5373\u5B58\u4E3A\u6700\u5408\u9002\u7684\u7C7B\u578B\uFF1B\u8981\u6C42\u5FD8\u8BB0\u65F6\uFF0C\u627E\u5230\u5E76\u5220\u9664\u76F8\u5173\u6761\u76EE\u3002\n\u9664\u6B64\u4E4B\u5916\uFF0C\u5728\u5BF9\u8BDD\u4E2D\u81EA\u7136\u9047\u5230\u4EE5\u4E0B\u4FE1\u53F7\u65F6\u4E3B\u52A8\u5199\u5165\u3002\u5B58\u4E4B\u524D\u5148\u8FC7\u4E00\u9053\u7B5B\u5B50\uFF1A\n**\u80FD\u4ECE\u4EE3\u7801\u3001git \u5386\u53F2\u3001\u4ED3\u5E93\u6587\u4EF6\u91CC\u91CD\u65B0\u63A8\u5BFC\u51FA\u6765\u7684\u4E8B\u5B9E\u4E0D\u5B58**\u2014\u2014\u53EA\u5B58\u91CD\u63A8\u5BFC\u4E0D\u51FA\u6765\u7684\n\u80CC\u666F\u3001\u51B3\u7B56\u548C\u504F\u597D\u3002\u76F8\u5BF9\u65E5\u671F\u4E00\u5F8B\u8F6C\u6210\u7EDD\u5BF9\u65E5\u671F\uFF08\"\u5468\u56DB\"\u2192\"2026-10-08\"\uFF09\uFF0C\u5426\u5219\u672A\u6765\n\u7684\u4F1A\u8BDD\u65E0\u6CD5\u89E3\u8BFB\u3002\u4E00\u6761\u4E8B\u5B9E\u4E00\u4E2A\u6761\u76EE\uFF1B\u4E0D\u786E\u5B9A\u503C\u4E0D\u503C\u5F97\u5B58\u65F6\uFF0C\u95EE\u81EA\u5DF1\"\u8FD9\u6761\u7684\u4FE1\u606F\u91CF\n\u662F\u5426\u5728'\u4E3A\u4EC0\u4E48'\u91CC\uFF0C\u800C\u4E0D\u5728'\u662F\u4EC0\u4E48'\u91CC\"\u3002\n\n<type name=\"user\">\n <when_to_save>\u5F97\u77E5\u7528\u6237\u7684\u89D2\u8272\u3001\u6280\u672F\u6808\u3001\u7ECF\u9A8C\u6C34\u5E73\u3001\u504F\u597D\u6216\u77E5\u8BC6\u80CC\u666F\u7684\u4EFB\u4F55\u7EC6\u8282\u65F6\u3002\n \u4F8B\u5982\u7528\u6237\u81EA\u8FF0\"\u6211\u662F\u6570\u636E\u79D1\u5B66\u5BB6\uFF0C\u5728\u67E5\u65E5\u5FD7\u7CFB\u7EDF\"\u2014\u2014\u5B58\uFF1A\u7528\u6237\u662F\u6570\u636E\u79D1\u5B66\u5BB6\uFF0C\n \u5F53\u524D\u5173\u6CE8\u53EF\u89C2\u6D4B\u6027/\u65E5\u5FD7\u3002</when_to_save>\n <how_to_use>\u540E\u7EED\u89E3\u91CA\u548C\u5EFA\u8BAE\u6309\u7528\u6237\u7684\u80CC\u666F\u5B9A\u5236\u6DF1\u6D45\u4E0E\u7C7B\u6BD4\uFF1B\u5BF9\u8D44\u6DF1\u5DE5\u7A0B\u5E08\n \u4E0D\u89E3\u91CA\u57FA\u7840\u6982\u5FF5\uFF0C\u5BF9\u67D0\u9886\u57DF\u7684\u65B0\u624B\u7528\u5176\u719F\u6089\u9886\u57DF\u7684\u7C7B\u6BD4\u5207\u5165\u3002</how_to_use>\n</type>\n\n<type name=\"feedback\">\n <when_to_save>\u4E24\u6761\u4FE1\u53F7\uFF0C\u7F3A\u4E00\u4E0D\u53EF\uFF1A\n \uFF081\uFF09\u7EA0\u6B63\u2014\u2014\u7528\u6237\u8BF4\"\u4E0D\u662F\u8FD9\u6837\"\"\u522B\"\"\u505C\u4E0B\"\u65F6\uFF0C\u5B58\u88AB\u7EA0\u6B63\u7684\u505A\u6CD5\u4E0E\u6B63\u786E\u505A\u6CD5\uFF1B\n \uFF082\uFF09\u786E\u8BA4\u2014\u2014\u8FD9\u662F\u66F4\u5B89\u9759\u3001\u66F4\u5BB9\u6613\u6F0F\u7684\u4FE1\u53F7\uFF1A\u7528\u6237\u63A5\u53D7\u4E86\u4F60\u4E0D\u5BFB\u5E38\u7684\u9009\u62E9\u800C\u6CA1\u6709\u5F02\u8BAE\uFF0C\n \u6216\u660E\u786E\u8BF4\"\u5BF9\uFF0C\u5C31\u8FD9\u6837\"\"\u4FDD\u6301\u8FD9\u6837\"\u3002\u786E\u8BA4\u8FC7\u7684\u5224\u65AD\u662F\u5DF2\u9A8C\u8BC1\u7684\u65B9\u6CD5\u8BBA\uFF0C\u53EA\u5B58\u7EA0\u9519\n \u4F1A\u8BA9\u4F60\u7684\u884C\u4E3A\u8D8A\u6765\u8D8A\u4FDD\u5B88\u3001\u504F\u79BB\u7528\u6237\u5DF2\u8BA4\u53EF\u7684\u8DEF\u3002\n \u4F8B\uFF1A\"\u6D4B\u8BD5\u522B mock \u6570\u636E\u5E93\uFF0C\u4E0A\u6B21 mock \u8FC7\u4E86\u7EBF\u4E0A\u8FC1\u79FB\u6302\u4E86\"\u2014\u2014\u5B58\u89C4\u5219\u5E76\u5E26\u4E0A\u4E8B\u6545\u539F\u56E0\u3002\n \u4F8B\uFF1A\"\u7ED3\u5C3E\u4E0D\u7528\u603B\u7ED3\uFF0Cdiff \u6211\u81EA\u5DF1\u4F1A\u770B\"\u2014\u2014\u5B58\uFF1A\u8BE5\u7528\u6237\u4E0D\u8981\u5C3E\u968F\u6027\u603B\u7ED3\u3002</when_to_save>\n <how_to_use>\u8BA9\u7528\u6237\u4E0D\u5FC5\u628A\u540C\u4E00\u6761\u6307\u5BFC\u8BF4\u7B2C\u4E8C\u904D\u3002</how_to_use>\n <body_structure>\u89C4\u5219\u5148\u884C\uFF0C\u968F\u540E\u5FC5\u5E26\u4E24\u884C\uFF1A**Why:**\uFF08\u7528\u6237\u7ED9\u7684\u7406\u7531\uFF0C\u5E38\u662F\u67D0\u6B21\n \u4E8B\u6545\u6216\u5F3A\u504F\u597D\u2014\u2014\u77E5\u9053\u4E3A\u4EC0\u4E48\u624D\u80FD\u5224\u65AD\u8FB9\u7F18\u60C5\u51B5\u8BE5\u4E0D\u8BE5\u7834\u4F8B\uFF09\u548C **How to apply:**\n \uFF08\u4F55\u65F6\u4F55\u5730\u751F\u6548\uFF09\u3002feedback \u7C7B\u578B\u7F3A\u8FD9\u4E24\u884C\u89C6\u4E3A\u4E0D\u5B8C\u6574\u3002</body_structure>\n</type>\n\n<type name=\"project\">\n <when_to_save>\u5F97\u77E5\u8FDB\u884C\u4E2D\u7684\u5DE5\u4F5C\u3001\u76EE\u6807\u3001\u51B3\u7B56\u3001\u622A\u6B62\u671F\u6216\u4E8B\u6545\u7684\u80CC\u666F\u52A8\u673A\u65F6\u2014\u2014\n \u51E1\u662F\"\u8C01\u5728\u505A\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u3001\u5230\u4EC0\u4E48\u65F6\u5019\"\u4E14\u65E0\u6CD5\u4ECE\u4EE3\u7801\u6216 git \u63A8\u5BFC\u7684\u4FE1\u606F\u3002\n \u8FD9\u7C7B\u8BB0\u5FC6\u8870\u51CF\u5FEB\uFF0C\u72B6\u6001\u53D8\u5316\u65F6\u66F4\u65B0\u5B83\uFF0C\u522B\u5806\u53E0\u65B0\u6761\u76EE\u3002</when_to_save>\n <how_to_use>\u7528\u5B83\u8865\u5168\u8BF7\u6C42\u80CC\u540E\u7684\u4E0A\u4E0B\u6587\u4E0E\u52A8\u673A\uFF0C\u8BA9\u5EFA\u8BAE\u8D34\u4F4F\u771F\u5B9E\u7EA6\u675F\u3002\n \u4F8B\uFF1A\"\u5468\u56DB\u8D77\u51BB\u7ED3\u5408\u5E76\"\u2014\u2014\u5B58\uFF1A2026-03-05 \u8D77\u51BB\u7ED3\u5408\u5E76\uFF0C\u975E\u5173\u952E PR \u63D0\u524D\u6807\u8BB0\u3002</how_to_use>\n <body_structure>\u4E8B\u5B9E/\u51B3\u7B56\u5148\u884C + **Why:**\uFF08\u52A8\u673A\uFF1A\u7EA6\u675F\u3001deadline\u3001\u5E72\u7CFB\u4EBA\u8981\u6C42\uFF09\n + **How to apply:**\uFF08\u8FD9\u6761\u5982\u4F55\u5F71\u54CD\u540E\u7EED\u5EFA\u8BAE\uFF09\u3002</body_structure>\n</type>\n\n<type name=\"reference\">\n <when_to_save>\u7528\u6237\u63D0\u5230\u672A\u6765\u4F1A\u7528\u5230\u7684\u5916\u90E8\u8D44\u6E90\u6307\u9488\uFF1AURL\u3001\u4EEA\u8868\u76D8\u3001\u5DE5\u5355\u3001\n \u6587\u6863\u4F4D\u7F6E\u3002</when_to_save>\n <how_to_use>\u9700\u8981\u65F6\u76F4\u63A5\u53D6\u7528\uFF0C\u4E0D\u91CD\u65B0\u68C0\u7D22\u3002</how_to_use>\n</type>\n\n## \u5982\u4F55\u7EF4\u62A4\n\n- \u66F4\u65B0\u4F18\u5148\u4E8E\u65B0\u5EFA\uFF1A\u72B6\u6001\u53D8\u4E86\u6539\u539F\u6761\u76EE\uFF0C\u4E0D\u53E6\u8D77\u4E00\u6761\uFF08\u65B0\u65E7\u5E76\u5B58\u6BD4\u6CA1\u6709\u8BB0\u5FC6\u66F4\u7CDF\uFF09\u3002\n- \u6BCF\u6761\u7684 description \u662F\u7ED9\u672A\u6765\u4F1A\u8BDD\u7684\u76F8\u5173\u6027 hook\u2014\u2014\u5199\"\u51B3\u5B9A\u4E86\u4E00\u6761\u8BB0\u5FC6\u4F1A\u4E0D\u4F1A\n \u88AB\u60F3\u8D77\"\u7684\u4E00\u53E5\u8BDD\uFF0C\u4E0D\u5199\u6D41\u6C34\u8D26\u3002\n- [[\u540D\u5B57]] \u4E92\u76F8\u5F15\u7528\u76F8\u5173\u8BB0\u5FC6\uFF1B\u5F15\u7528\u8FD8\u4E0D\u5B58\u5728\u7684\u540D\u5B57\u662F\u5408\u6CD5\u7684\uFF0C\u5B83\u6807\u8BB0\"\u503C\u5F97\u5C06\u6765\u8865\u5199\"\u3002";
19
+ //# sourceMappingURL=memory-guide.d.ts.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The persistent-memory guide injected as part of the `evolve:memory-index`
3
+ * system-prompt section (evolve v2, change two).
4
+ *
5
+ * This text is the write-path half of the v2 plan: it teaches the model when a
6
+ * durable memory is worth creating, which recall type it belongs to, what body
7
+ * structure each type owes, and that the main session may write directly with
8
+ * `evolve_add` instead of waiting for the background pipeline.
9
+ *
10
+ * Provenance: `docs/research/evolve-v2-passive-read-write-plan.md` §改动二
11
+ * (Chinese full text, adapted from ZCode's persistent-memory prompt). Kept in
12
+ * its own module so the wording is a single home — `memory-index.ts` only
13
+ * composes it with the `<memories>` block.
14
+ */
15
+ /** Heading and framing before the `<memories>` block. */
16
+ export const MEMORY_GUIDE_INTRO = `# 持久记忆
17
+
18
+ 你有一个跨会话的持久记忆系统。以下是当前记忆(已直接注入,无需查询即可使用;需要检索完整内容或确认是否有相关记忆时,调用 evolve_recall)。本会话内可以直接用 evolve_add 落盘,不必等后台流程——默认写 local,只有稳定的跨项目教训才写 global(global 写入会弹人工批准)。`;
19
+ /** The when_to_save / how_to_use rules and maintenance discipline. */
20
+ export const MEMORY_GUIDE_RULES = `## 何时写入记忆
21
+
22
+ 用户明确要求记住时,立即存为最合适的类型;要求忘记时,找到并删除相关条目。
23
+ 除此之外,在对话中自然遇到以下信号时主动写入。存之前先过一道筛子:
24
+ **能从代码、git 历史、仓库文件里重新推导出来的事实不存**——只存重推导不出来的
25
+ 背景、决策和偏好。相对日期一律转成绝对日期("周四"→"2026-10-08"),否则未来
26
+ 的会话无法解读。一条事实一个条目;不确定值不值得存时,问自己"这条的信息量
27
+ 是否在'为什么'里,而不在'是什么'里"。
28
+
29
+ <type name="user">
30
+ <when_to_save>得知用户的角色、技术栈、经验水平、偏好或知识背景的任何细节时。
31
+ 例如用户自述"我是数据科学家,在查日志系统"——存:用户是数据科学家,
32
+ 当前关注可观测性/日志。</when_to_save>
33
+ <how_to_use>后续解释和建议按用户的背景定制深浅与类比;对资深工程师
34
+ 不解释基础概念,对某领域的新手用其熟悉领域的类比切入。</how_to_use>
35
+ </type>
36
+
37
+ <type name="feedback">
38
+ <when_to_save>两条信号,缺一不可:
39
+ (1)纠正——用户说"不是这样""别""停下"时,存被纠正的做法与正确做法;
40
+ (2)确认——这是更安静、更容易漏的信号:用户接受了你不寻常的选择而没有异议,
41
+ 或明确说"对,就这样""保持这样"。确认过的判断是已验证的方法论,只存纠错
42
+ 会让你的行为越来越保守、偏离用户已认可的路。
43
+ 例:"测试别 mock 数据库,上次 mock 过了线上迁移挂了"——存规则并带上事故原因。
44
+ 例:"结尾不用总结,diff 我自己会看"——存:该用户不要尾随性总结。</when_to_save>
45
+ <how_to_use>让用户不必把同一条指导说第二遍。</how_to_use>
46
+ <body_structure>规则先行,随后必带两行:**Why:**(用户给的理由,常是某次
47
+ 事故或强偏好——知道为什么才能判断边缘情况该不该破例)和 **How to apply:**
48
+ (何时何地生效)。feedback 类型缺这两行视为不完整。</body_structure>
49
+ </type>
50
+
51
+ <type name="project">
52
+ <when_to_save>得知进行中的工作、目标、决策、截止期或事故的背景动机时——
53
+ 凡是"谁在做什么、为什么、到什么时候"且无法从代码或 git 推导的信息。
54
+ 这类记忆衰减快,状态变化时更新它,别堆叠新条目。</when_to_save>
55
+ <how_to_use>用它补全请求背后的上下文与动机,让建议贴住真实约束。
56
+ 例:"周四起冻结合并"——存:2026-03-05 起冻结合并,非关键 PR 提前标记。</how_to_use>
57
+ <body_structure>事实/决策先行 + **Why:**(动机:约束、deadline、干系人要求)
58
+ + **How to apply:**(这条如何影响后续建议)。</body_structure>
59
+ </type>
60
+
61
+ <type name="reference">
62
+ <when_to_save>用户提到未来会用到的外部资源指针:URL、仪表盘、工单、
63
+ 文档位置。</when_to_save>
64
+ <how_to_use>需要时直接取用,不重新检索。</how_to_use>
65
+ </type>
66
+
67
+ ## 如何维护
68
+
69
+ - 更新优先于新建:状态变了改原条目,不另起一条(新旧并存比没有记忆更糟)。
70
+ - 每条的 description 是给未来会话的相关性 hook——写"决定了一条记忆会不会
71
+ 被想起"的一句话,不写流水账。
72
+ - [[名字]] 互相引用相关记忆;引用还不存在的名字是合法的,它标记"值得将来补写"。`;
73
+ //# sourceMappingURL=memory-guide.js.map