@clawmem-ai/clawmem 0.1.13 → 0.1.15

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/README.md CHANGED
@@ -138,7 +138,7 @@ Full config with all options:
138
138
  turnCommentDelayMs: 1000,
139
139
  summaryWaitTimeoutMs: 120000,
140
140
  memoryRecallLimit: 5,
141
- memoryAutoRecallLimit: 5
141
+ memoryAutoRecallLimit: 3
142
142
  }
143
143
  }
144
144
  }
@@ -152,8 +152,9 @@ Full config with all options:
152
152
 
153
153
  - Conversation comments exclude tool calls, tool results, system messages, and heartbeat noise.
154
154
  - Summary failures do not block finalization; the `summary` field is written as `failed: ...`.
155
- - Memory search and auto-injection only return open `type:memory` issues. Closed memory issues are treated as stale.
156
- - `memory_recall` now prefers the backend `/api/v3/search/issues` endpoint scoped to the current repo plus `label:"type:memory"`, with a simple local lexical fallback when backend search is unavailable or returns no matches.
155
+ - Memory search and auto-recall only return open `type:memory` issues. Closed memory issues are treated as stale.
156
+ - ClawMem automatically injects a small set of relevant memories before each turn using the agent's default repo and the backend recall API. Auto-recall is best-effort and quietly skips injection when backend recall is unavailable.
157
+ - `memory_recall` uses the backend `/api/v3/search/issues` endpoint scoped to the current repo plus `label:"type:memory"`. When backend recall is unavailable, use `memory_list` or `memory_get` to inspect memories explicitly.
157
158
  - Durable memories are extracted best-effort during later request-scoped maintenance, not by background subagent work after a request has already ended.
158
159
  - The plugin exposes `memory_repos`, `memory_repo_create`, `memory_list`, `memory_get`, `memory_labels`, `memory_recall`, `memory_store`, `memory_update`, and `memory_forget` for mid-session use.
159
160
  - Route resolution is now: agent identity supplies credentials, `defaultRepo` is the fallback memory space, and explicit tool calls may override repo per operation.
@@ -162,4 +163,4 @@ Full config with all options:
162
163
  - `memory_update` updates one existing memory issue in place; use it for evolving canonical facts or active tasks instead of creating a duplicate node.
163
164
  - Conversation lifecycle is stored in native issue state (`open` while live, `closed` after finalize); memory lifecycle uses native issue state too (`open` active, `closed` stale).
164
165
  - Memory extraction now prefers one atomic fact per memory item instead of bundling whole sessions into a single node.
165
- - Memory issue bodies store the durable detail plus flat metadata such as `memory_hash` and logical `date`; labels are reserved for schema and routing.
166
+ - Memory issue bodies store the durable detail in a YAML `detail` field plus flat metadata such as `memory_hash` and logical `date`; this matches the current Console parser in `agent-git-service/web`.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "clawmem",
3
3
  "name": "ClawMem",
4
- "version": "0.1.13",
4
+ "version": "0.1.15",
5
5
  "description": "Mirror OpenClaw sessions into GitHub-compatible issues and comments.",
6
6
  "kind": "memory",
7
7
  "skills": [
@@ -127,7 +127,7 @@
127
127
  },
128
128
  "memoryAutoRecallLimit": {
129
129
  "label": "Auto Recall Limit",
130
- "help": "Maximum number of active memories injected into context before an agent starts."
130
+ "help": "Maximum number of active memories automatically injected before each turn."
131
131
  }
132
132
  }
133
133
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawmem-ai/clawmem",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Mirror OpenClaw sessions into GitHub-compatible issues and comments.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,20 +25,22 @@ Memory hygiene matters: lock important insights deliberately, update canonical f
25
25
  The ClawMem plugin automatically handles:
26
26
  - Per-agent provisioning of credentials plus a default memory repo
27
27
  - Session mirroring into `type:conversation` issues
28
+ - Best-effort automatic memory recall before each turn, scoped to the current agent's `defaultRepo`
28
29
  - Best-effort durable memory extraction during later request-scoped maintenance
29
- - Automatic recall of relevant active memories at session start
30
30
  - Mid-session memory tools: `memory_repos`, `memory_repo_create`, `memory_list`, `memory_get`, `memory_labels`, `memory_recall`, `memory_store`, `memory_update`, and `memory_forget`
31
31
 
32
- Automatic recall is only a bootstrap. You still need to retrieve before answering when memory may matter, and save after learning something durable.
33
-
34
32
  ## Mandatory turn loop
35
33
 
36
34
  On every user turn, run this loop:
37
35
 
38
36
  1. Before answering, ask: could ClawMem improve this answer?
39
37
  - Default to yes for user preferences, project history, prior decisions, lessons, conventions, terminology, recurring problems, and active tasks.
40
- - Before explicit memory work, choose the right repo. If unclear, inspect `memory_repos` and fall back to the agent's `defaultRepo`.
41
- - Start with `memory_recall`.
38
+ - Auto-recall may already inject useful context from the current agent's `defaultRepo`, but it is only a hint. Do not treat missing auto-recall context as proof that no relevant memory exists.
39
+ - If the injected context already answers the question, you do not need to immediately call `memory_recall` again.
40
+ - Auto-recall does not currently fan out across every accessible repo. Shared organization memory, team memory, and project memory outside the current `defaultRepo` will not be recalled automatically.
41
+ - Before explicit memory work, choose the right repo. If unclear, inspect `memory_repos` and fall back to the agent's `defaultRepo`. If the likely memory lives outside the default repo, use explicit repo selection instead of relying on auto-recall.
42
+ - Use `memory_recall` when injected context is missing, weak, cross-repo, high-stakes, or when you need an explicit retrieval trace.
43
+ - Write `memory_recall.query` as a short natural-language intent. Do not paste long code blocks, full logs, tool chatter, or system prompt text unless the exact wording is necessary.
42
44
  - When the question spans more than one angle, run more than one recall query across keywords, topics, synonyms, and likely project phrasing.
43
45
  - If `memory_recall` is weak or empty and the answer depends on whether a memory exists, cross-check with `memory_list`.
44
46
  - If the first recall pass is weak, broaden with shorter terms, adjacent topics, or alternate phrasing before concluding a miss.
@@ -50,14 +52,16 @@ On every user turn, run this loop:
50
52
  - Use `memory_update` when the same canonical fact or ongoing task should keep evolving as one node.
51
53
  - When updating an existing memory, preserve that node's current language unless the user explicitly asks for a rewrite.
52
54
  - Use `memory_store` when this is a genuinely new memory.
55
+ - When using `memory_store`, pass both `title` and `detail` when you can. Keep the title concise and human-readable, and keep `detail` as the full durable fact.
56
+ - When using `memory_update`, pass `title` as well if the existing title is too short, outdated, or less precise than the current canonical fact.
53
57
  - For new memories, write the memory title and body in the user's current language by default.
54
58
  - Use `memory_forget` when a memory is stale, superseded, or harmful if reused.
55
59
  3. Keep the user posted.
56
- - If a retrieved memory materially shaped the answer, including automatic session-start recall, briefly surface that fact in the user's current language.
60
+ - If a retrieved memory materially shaped the answer, briefly surface that fact in the user's current language.
57
61
  - Include the memory id and title only when they help with debugging, traceability, or an explicit user request.
58
62
  - After creating or updating a memory, give a short confirmation in the user's current language instead of forcing fixed English phrasing.
59
63
 
60
- Bias toward retrieving and saving. A missed search or missed memory is worse than an extra search.
64
+ Bias toward saving, and use explicit retrieval whenever auto-recall is absent, weak, cross-repo, or too ambiguous to trust on its own.
61
65
 
62
66
  ## Retrieval and storage rules
63
67
 
@@ -67,7 +71,10 @@ Bias toward retrieving and saving. A missed search or missed memory is worse tha
67
71
  - Private personal memory usually belongs in the agent's `defaultRepo`.
68
72
  - Project memory belongs in the relevant project repo.
69
73
  - Shared or team knowledge belongs in the shared repo for that group.
74
+ - Shared or team knowledge in another repo is not part of default auto-recall today. To use it, select that repo explicitly with `memory_recall`, `memory_list`, or `memory_get`.
70
75
  - Memory titles and bodies default to the user's current language for new memories.
76
+ - Prefer a short standalone title plus a fuller `detail` body instead of stuffing the whole memory into the title.
77
+ - If you omit `title`, the plugin may derive it from `detail`, but providing an explicit title is preferred for readability in the Console.
71
78
  - When updating an existing memory, keep that node in its current language unless the user explicitly asks to rewrite it.
72
79
  - Keep schema labels and machine-oriented fields stable. Do not translate `type:*`, `kind:*`, `topic:*`, or other structural identifiers.
73
80
  - If the user is asking about collaboration, organizations, teams, invitations, collaborators, shared repo access, or why someone can or cannot access a memory repo, switch from normal memory reasoning to the collaboration workflow in `references/collaboration.md`.
@@ -76,6 +76,7 @@ Do not export `GH_HOST` or `GH_ENTERPRISE_TOKEN` globally for unrelated github.c
76
76
  Use the tool path first. If raw issue control is required:
77
77
 
78
78
  - For new memories, write the issue title and body in the user's current language.
79
+ - Prefer a concise standalone title and keep the full durable fact in the body.
79
80
  - When manually updating an existing memory, preserve that memory's current language unless the user explicitly asks for a rewrite.
80
81
  - Keep labels and schema markers such as `type:*`, `kind:*`, and `topic:*` in their fixed machine-readable form.
81
82
 
@@ -61,6 +61,8 @@ If you create a curated memory manually, include:
61
61
  ## Storage language
62
62
 
63
63
  - For new memory nodes, write the human-readable title and body in the user's current language by default.
64
+ - When using plugin tools, prefer passing an explicit short `title` plus a fuller `detail` body.
65
+ - Do not treat the title as the only durable content. The body detail should still contain the full reusable fact.
64
66
  - When updating an existing memory node, preserve that node's current language unless the user explicitly asks for a rewrite.
65
67
  - Do not translate schema or routing markers such as `type:*`, `kind:*`, `topic:*`, or other machine-oriented field names.
66
68
 
@@ -27,7 +27,7 @@ function baseConfig(): ClawMemPluginConfig {
27
27
  },
28
28
  },
29
29
  memoryRecallLimit: 5,
30
- memoryAutoRecallLimit: 5,
30
+ memoryAutoRecallLimit: 3,
31
31
  turnCommentDelayMs: 1000,
32
32
  summaryWaitTimeoutMs: 120000,
33
33
  };
package/src/config.ts CHANGED
@@ -46,7 +46,7 @@ export function resolvePluginConfig(api: OpenClawPluginApi): ClawMemPluginConfig
46
46
  authScheme: raw.authScheme === "bearer" ? "bearer" : "token",
47
47
  agents,
48
48
  memoryRecallLimit: clamp(num(raw.memoryRecallLimit, 5), 1, 20),
49
- memoryAutoRecallLimit: clamp(num(raw.memoryAutoRecallLimit, num(raw.memoryRecallLimit, 5)), 1, 20),
49
+ memoryAutoRecallLimit: clamp(num(raw.memoryAutoRecallLimit, 3), 1, 20),
50
50
  turnCommentDelayMs: num(raw.turnCommentDelayMs, 1000),
51
51
  summaryWaitTimeoutMs: clamp(num(raw.summaryWaitTimeoutMs, 120000), 1000, 600000),
52
52
  };
@@ -44,46 +44,53 @@ function assert(condition: unknown, message: string): void {
44
44
  function testConfig(): never {
45
45
  return {
46
46
  memoryRecallLimit: 5,
47
- memoryAutoRecallLimit: 5,
47
+ memoryAutoRecallLimit: 3,
48
48
  turnCommentDelayMs: 1000,
49
49
  summaryWaitTimeoutMs: 120000,
50
50
  } as never;
51
51
  }
52
52
 
53
- async function testSearchRanking(): Promise<void> {
54
- const issues = [
55
- issueFromMemory(memory({
56
- issueNumber: 1,
57
- title: "Memory: Redis rate limit tuning",
58
- detail: "Distributed Redis rate limiting must use Lua scripts to stay atomic.",
59
- kind: "lesson",
60
- topics: ["redis", "rate-limiting"],
61
- })),
62
- issueFromMemory(memory({
63
- issueNumber: 2,
64
- title: "Memory: Generic backend notes",
65
- detail: "We use Redis in several services, but this one is not about rate limiting.",
66
- topics: ["backend"],
67
- })),
68
- ];
53
+ async function testBackendSearchBuildsSingleCleanedQuery(): Promise<void> {
54
+ const queries: string[] = [];
69
55
  const client = {
70
- listIssues: async () => issues,
56
+ repo: () => "owner/main-memory",
57
+ searchIssues: async (query: string) => {
58
+ queries.push(query);
59
+ return [] as IssueRecord[];
60
+ },
71
61
  };
72
62
  const store = new MemoryStore(client as never, {} as never, testConfig());
73
- const found = await store.search("redis rate limiting", 5);
74
- assert(found.length === 2, "expected both memories to match");
75
- assert(found[0]?.issueNumber === 1, "expected the more specific Redis rate limiting memory to rank first");
63
+ await store.search([
64
+ "<clawmem-context>",
65
+ "- [11] Previous memory that should be stripped",
66
+ "</clawmem-context>",
67
+ "Conversation info (untrusted metadata):",
68
+ "```json",
69
+ '{"channel":"slack"}',
70
+ "```",
71
+ "",
72
+ "[message_id: abc-123]",
73
+ "",
74
+ "[Slack 2026-04-03 09:30]: Please help debug the Redis rate limiting path.",
75
+ "See https://example.com/debug for more context.",
76
+ "throw new TimeoutError('lua script timeout')",
77
+ "[System: auto-translated]",
78
+ ].join("\n"), 5);
79
+
80
+ assert(queries.length === 1, "expected a single backend search query");
81
+ assert(queries[0]?.includes("repo:owner/main-memory"), "expected the backend query to stay scoped to the repo");
82
+ assert(queries[0]?.includes('label:"type:memory"'), "expected the backend query to filter memory issues");
83
+ assert((queries[0] ?? "").length <= 1610, "expected the backend search query to stay within the configured cap plus qualifiers");
84
+ assert(queries[0]?.toLowerCase().includes("redis"), "expected the backend query to retain key terms");
85
+ assert(!queries[0]?.includes("<clawmem-context>"), "expected injected clawmem context to be stripped");
86
+ assert(!queries[0]?.includes("https://example.com/debug"), "expected URLs to be stripped from backend recall");
87
+ assert(!queries[0]?.includes("Conversation info (untrusted metadata):"), "expected inbound metadata blocks to be stripped");
88
+ assert(!queries[0]?.includes("[message_id:"), "expected message id hints to be stripped");
89
+ assert(!queries[0]?.includes("[Slack 2026-04-03 09:30]"), "expected envelope prefixes to be stripped");
90
+ assert(!queries[0]?.includes("[System: auto-translated]"), "expected trailing system hints to be stripped");
76
91
  }
77
92
 
78
93
  async function testBackendSearchPreferredForRecall(): Promise<void> {
79
- const listed = [
80
- issueFromMemory(memory({
81
- issueNumber: 1,
82
- title: "Memory: lexical decoy",
83
- detail: "redis rate limiting checklist",
84
- kind: "lesson",
85
- })),
86
- ];
87
94
  const searched = [
88
95
  issueFromMemory(memory({
89
96
  issueNumber: 2,
@@ -96,14 +103,13 @@ async function testBackendSearchPreferredForRecall(): Promise<void> {
96
103
  const queries: string[] = [];
97
104
  const client = {
98
105
  repo: () => "owner/main-memory",
99
- listIssues: async () => listed,
100
106
  searchIssues: async (query: string) => {
101
107
  queries.push(query);
102
108
  return searched;
103
109
  },
104
110
  };
105
111
  const store = new MemoryStore(client as never, {} as never, testConfig());
106
- const found = await store.search("redis rate limiting", 5);
112
+ const found = await store.search("redis rate limiting", 1);
107
113
 
108
114
  assert(queries.length === 1, "expected backend search to be called once");
109
115
  assert(queries[0]?.includes('repo:owner/main-memory'), "expected backend query to scope to the current repo");
@@ -111,7 +117,7 @@ async function testBackendSearchPreferredForRecall(): Promise<void> {
111
117
  assert(found.length === 1 && found[0]?.issueNumber === 2, "expected backend search results to be preferred");
112
118
  }
113
119
 
114
- async function testBackendSearchFallsBackToLocalLexical(): Promise<void> {
120
+ async function testBackendSearchReturnsEmptyWithoutLexicalFallback(): Promise<void> {
115
121
  const issues = [
116
122
  issueFromMemory(memory({
117
123
  issueNumber: 3,
@@ -124,12 +130,28 @@ async function testBackendSearchFallsBackToLocalLexical(): Promise<void> {
124
130
  const client = {
125
131
  repo: () => "owner/main-memory",
126
132
  listIssues: async () => issues,
127
- searchIssues: async () => { throw new Error("search unavailable"); },
133
+ searchIssues: async () => [] as IssueRecord[],
128
134
  };
129
- const store = new MemoryStore(client as never, { logger: { warn: () => {} } } as never, testConfig());
135
+ const store = new MemoryStore(client as never, {} as never, testConfig());
130
136
  const found = await store.search("redis rate limiting", 5);
131
137
 
132
- assert(found.length === 1 && found[0]?.issueNumber === 3, "expected lexical fallback when backend search fails");
138
+ assert(found.length === 0, "expected backend-only recall to return no results when the backend finds nothing");
139
+ }
140
+
141
+ async function testBackendSearchPropagatesErrors(): Promise<void> {
142
+ const client = {
143
+ repo: () => "owner/main-memory",
144
+ searchIssues: async () => { throw new Error("search unavailable"); },
145
+ };
146
+ const store = new MemoryStore(client as never, {} as never, testConfig());
147
+ let message = "";
148
+ try {
149
+ await store.search("redis rate limiting", 5);
150
+ } catch (error) {
151
+ message = String(error);
152
+ }
153
+
154
+ assert(message.includes("search unavailable"), "expected backend failures to propagate instead of falling back locally");
133
155
  }
134
156
 
135
157
  function testCjkScoring(): void {
@@ -177,12 +199,36 @@ async function testStructuredStoreAndSchema(): Promise<void> {
177
199
  assert(created[0]?.labels.includes("topic:rate-limit"), "expected created labels to include normalized topic");
178
200
  assert(!created[0]?.labels.some((label) => label.startsWith("session:")), "expected manual memory_store writes to omit synthetic session labels");
179
201
  assert(!created[0]?.labels.some((label) => label.startsWith("date:")), "expected new memory labels to omit date labels");
202
+ assert(created[0]?.body.includes("memory_hash:"), "expected new memory body to retain metadata fields");
203
+ assert(created[0]?.body.includes("detail: Redis Lua scripts are required for atomic rate limiting."), "expected new memory body to store detail in YAML");
180
204
  assert(created[0]?.body.includes(`date: ${result.memory.date}`), "expected new memory body to retain logical date metadata");
181
205
  assert(ensured[0]?.includes("kind:lesson"), "expected ensureLabels to include kind label");
182
206
  assert(schema.kinds.includes("lesson"), "expected schema to expose existing kind labels");
183
207
  assert(schema.topics.includes("redis"), "expected schema to expose existing topic labels");
184
208
  }
185
209
 
210
+ async function testStoreKeepsFullAutoTitleAndSupportsExplicitTitle(): Promise<void> {
211
+ const created: Array<{ title: string; body: string; labels: string[] }> = [];
212
+ const client = {
213
+ listIssues: async () => [] as IssueRecord[],
214
+ listLabels: async () => [] as LabelRecord[],
215
+ ensureLabels: async () => {},
216
+ createIssue: async (payload: { title: string; body: string; labels: string[] }) => {
217
+ created.push(payload);
218
+ return { number: created.length + 100, title: payload.title };
219
+ },
220
+ };
221
+ const store = new MemoryStore(client as never, {} as never, testConfig());
222
+ const longDetail = "Tech Decision #001: Frontend = React Native, Backend = FastAPI, Database = PostgreSQL, and analytics events must stay append-only for auditability.";
223
+ const auto = await store.store({ detail: longDetail });
224
+ const explicit = await store.store({ title: "Architecture Decision #001", detail: "Use React Native + FastAPI for the first mobile stack." });
225
+
226
+ assert(auto.memory.title === `Memory: ${longDetail}`, "expected auto-generated memory title to keep the full detail without truncation");
227
+ assert(explicit.memory.title === "Memory: Architecture Decision #001", "expected explicit memory title to be preserved");
228
+ assert(created[0]?.title === `Memory: ${longDetail}`, "expected created issue title to keep the full auto title");
229
+ assert(created[1]?.title === "Memory: Architecture Decision #001", "expected created issue title to use the explicit title");
230
+ }
231
+
186
232
  async function testGetAndListMemories(): Promise<void> {
187
233
  const issues = [
188
234
  issueFromMemory(memory({
@@ -241,6 +287,7 @@ async function testLegacyMemoriesWithoutSessionOrDate(): Promise<void> {
241
287
  },
242
288
  ];
243
289
  const client = {
290
+ repo: () => "owner/main-memory",
244
291
  listIssues: async (params?: { labels?: string[]; state?: "open" | "closed" | "all" }) => {
245
292
  const labels = params?.labels ?? [];
246
293
  const state = params?.state ?? "open";
@@ -251,6 +298,7 @@ async function testLegacyMemoriesWithoutSessionOrDate(): Promise<void> {
251
298
  return (issue.state ?? "open") === state;
252
299
  });
253
300
  },
301
+ searchIssues: async () => issues,
254
302
  };
255
303
  const store = new MemoryStore(client as never, {} as never, testConfig());
256
304
  const exact = await store.get("4");
@@ -312,10 +360,52 @@ async function testUpdateMemoryInPlace(): Promise<void> {
312
360
  assert(JSON.stringify(updated?.topics) === JSON.stringify(["preferences", "sports"]), "expected topics to be replaced");
313
361
  assert(updatedIssues.length === 1, "expected a single issue update");
314
362
  assert(updatedIssues[0]?.title !== "Memory: xiangz preferences", "expected title to refresh from updated detail");
363
+ assert(updatedIssues[0]?.body?.includes("memory_hash:"), "expected updated body to retain metadata");
364
+ assert(updatedIssues[0]?.body?.includes("detail:"), "expected updated body to store a detail field in YAML");
365
+ assert(updatedIssues[0]?.body?.includes("recently follows tennis"), "expected updated body to contain the updated detail text");
315
366
  assert(ensured[0]?.includes("topic:sports"), "expected new topic label to be ensured");
316
367
  assert(syncedLabels[0]?.labels.includes("kind:core-fact"), "expected existing kind label to be preserved");
317
368
  }
318
369
 
370
+ async function testUpdateSupportsExplicitRetitle(): Promise<void> {
371
+ const issues: IssueRecord[] = [
372
+ issueFromMemory(memory({
373
+ issueNumber: 20,
374
+ title: "Memory: old short title",
375
+ detail: "We use append-only audit events for billing changes.",
376
+ kind: "convention",
377
+ })),
378
+ ];
379
+ const updatedIssues: Array<{ number: number; title?: string; body?: string }> = [];
380
+ const client = {
381
+ listIssues: async (params?: { labels?: string[]; state?: "open" | "closed" | "all" }) => {
382
+ const labels = params?.labels ?? [];
383
+ const state = params?.state ?? "open";
384
+ return issues.filter((issue) => {
385
+ const issueLabels = issue.labels ?? [];
386
+ if (!labels.every((label) => issueLabels.includes(label))) return false;
387
+ if (state === "all") return true;
388
+ return (issue.state ?? "open") === state;
389
+ });
390
+ },
391
+ ensureLabels: async () => {},
392
+ updateIssue: async (number: number, patch: { title?: string; body?: string }) => {
393
+ updatedIssues.push({ number, ...patch });
394
+ const issue = issues.find((entry) => entry.number === number);
395
+ if (!issue) throw new Error("issue missing");
396
+ if (patch.title) issue.title = patch.title;
397
+ if (patch.body) issue.body = patch.body;
398
+ return issue;
399
+ },
400
+ syncManagedLabels: async () => {},
401
+ };
402
+ const store = new MemoryStore(client as never, {} as never, testConfig());
403
+ const updated = await store.update("20", { title: "Billing Audit Convention" });
404
+
405
+ assert(updated?.title === "Memory: Billing Audit Convention", "expected memory_update to support explicit retitle");
406
+ assert(updatedIssues[0]?.title === "Memory: Billing Audit Convention", "expected issue title patch to use the explicit retitle");
407
+ }
408
+
319
409
  async function testForgetClosesMemoryIssue(): Promise<void> {
320
410
  const issues: IssueRecord[] = [
321
411
  issueFromMemory(memory({
@@ -362,14 +452,17 @@ async function testForgetClosesMemoryIssue(): Promise<void> {
362
452
  }
363
453
 
364
454
  async function main(): Promise<void> {
365
- await testSearchRanking();
455
+ await testBackendSearchBuildsSingleCleanedQuery();
366
456
  await testBackendSearchPreferredForRecall();
367
- await testBackendSearchFallsBackToLocalLexical();
457
+ await testBackendSearchReturnsEmptyWithoutLexicalFallback();
458
+ await testBackendSearchPropagatesErrors();
368
459
  testCjkScoring();
369
460
  await testStructuredStoreAndSchema();
461
+ await testStoreKeepsFullAutoTitleAndSupportsExplicitTitle();
370
462
  await testGetAndListMemories();
371
463
  await testLegacyMemoriesWithoutSessionOrDate();
372
464
  await testUpdateMemoryInPlace();
465
+ await testUpdateSupportsExplicitRetitle();
373
466
  await testForgetClosesMemoryIssue();
374
467
  console.log("memory tests passed");
375
468
  }