@cerebriline/core 4.100.263 → 4.100.265

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.
@@ -24,14 +24,18 @@
24
24
  * however poor: by keyword, "the" and "project" match every note. Asked for
25
25
  * by the model that is fine, it asked. Attached unasked to every message it
26
26
  * is noise, so a note is attached only when something says it is about the
27
- * message: the reranker passed it, or it is close by meaning, or it shares
28
- * two meaningful words with what was searched for.
27
+ * message: the reranker scored it high, or it is close by meaning, or it
28
+ * shares two meaningful words with what was searched for.
29
29
  *
30
30
  * Nothing here can fail a turn. A search that throws, an expander that times
31
31
  * out, a server that is down: the message goes to the model without notes.
32
+ * But the user is told, in other words than "nothing found": a recall that
33
+ * cannot run and a Memory with nothing to say end the same way for the
34
+ * model, and are two different things to put right.
32
35
  */
33
36
  import { type MemoryToolsConfig } from "../../extensions/tools/memory-tools";
34
37
  import { type Memory, type RecalledMemory } from "./memory";
38
+ import { type DescribedPlaceCheck } from "./memory-note";
35
39
  /** A message shorter than this is "yes", "continue", "go on": nothing to search for. */
36
40
  export declare const MEMORY_RECALL_MIN_PROMPT_CHARS = 12;
37
41
  /** A pasted log is not a question. Both ends are kept: the ask is at one of them. */
@@ -48,6 +52,25 @@ export declare const HYDE_TIMEOUT_MS = 20000;
48
52
  export declare const AUTO_RECALL_MIN_SIMILARITY = 0.5;
49
53
  /** Meaningful words a note has to share with the search to be attached unasked. */
50
54
  export declare const AUTO_RECALL_MIN_SHARED_WORDS = 2;
55
+ /**
56
+ * The reranker's score that passes a note on its own. Its own floor, apart
57
+ * from the user's threshold, which defaults to 0 and would pass every note.
58
+ * Below it the note still has the other two tests.
59
+ */
60
+ export declare const AUTO_RECALL_MIN_RELEVANCE = 0.5;
61
+ /**
62
+ * The share of the session's window the notes may take. A share and not a
63
+ * count: five notes of 4000 characters are a fifth of a 32k window.
64
+ */
65
+ export declare const MEMORY_RECALL_WINDOW_SHARE = 0.04;
66
+ /** The window assumed when the host does not say: the smallest in common use. */
67
+ export declare const MEMORY_RECALL_FALLBACK_WINDOW = 32768;
68
+ /**
69
+ * Notes added for the files a task has in play, after the message's own and
70
+ * never in their place (claude-hooks cd71cf3 adds up to 12 for a council's
71
+ * judges; a chat turn has a smaller window to share).
72
+ */
73
+ export declare const MEMORY_RECALL_PLACE_EXTRA = 3;
51
74
  export declare const HYDE_SYSTEM_PROMPT = "You are a memory recall assistant. You are given a user's message and a few related notes retrieved from a knowledge base. Using ONLY facts consistent with those notes, write the short note that would answer the message, as if it were itself a stored note. Prefer the terms and specifics of the notes given. Do not invent facts beyond them. Two or three sentences. State facts; do not explain, and do not address the user.";
52
75
  export declare function buildHydeUserPrompt(question: string, notes: string[]): string;
53
76
  /** Writes the hypothetical note. Undefined or empty: search without it. */
@@ -63,6 +86,20 @@ export interface MemoryRecallInput {
63
86
  cwd: string;
64
87
  /** Notes already given to a session are not given to it again. */
65
88
  sessionId?: string;
89
+ /**
90
+ * The notes still in the conversation, by id (`recalledMemoryIds` reads
91
+ * them from its messages). Given, they are what counts as given: a note
92
+ * a compaction folded away can be given again. Left out, a note is given
93
+ * once per session.
94
+ */
95
+ inConversation?: readonly string[];
96
+ /** The session's window, in tokens, which bounds how much is attached. */
97
+ contextWindow?: number;
98
+ /**
99
+ * The files this task has in play: read, edited, mentioned. Notes about
100
+ * them are added after the message's own.
101
+ */
102
+ places?: readonly string[];
66
103
  }
67
104
  export interface MemoryRecallResult {
68
105
  /** The block to put after the user's message. Absent: nothing found. */
@@ -71,8 +108,10 @@ export interface MemoryRecallResult {
71
108
  ids: string[];
72
109
  /** Whether the expander's note was searched with. */
73
110
  expanded: boolean;
74
- /** One line for the user: what was recalled. */
111
+ /** One line for the user: what was recalled, or why nothing could be. */
75
112
  summary?: string;
113
+ /** Why the recall did not run. Not the same as nothing being found. */
114
+ failed?: string;
76
115
  }
77
116
  export type MemoryRecaller = (input: MemoryRecallInput) => Promise<MemoryRecallResult | undefined>;
78
117
  export interface CreateMemoryRecallerOptions {
@@ -93,5 +132,26 @@ export declare function isAboutTheSearch(search: string, item: RecalledMemory):
93
132
  export declare function clampRecallQuery(query: string, maxChars?: number): string;
94
133
  /** Reciprocal-rank fusion of two orderings of notes, best first. */
95
134
  export declare function fuseRecalls(first: RecalledMemory[], second: RecalledMemory[], limit: number): RecalledMemory[];
96
- export declare function formatRecalledMemory(items: RecalledMemory[]): string;
135
+ /** A recalled note, and why it was given when it was not for the message's words. */
136
+ export type GivenNote = RecalledMemory & {
137
+ via?: string;
138
+ };
139
+ /** How many characters of notes a window of this size takes. */
140
+ export declare function recallCharBudget(contextWindow: number | undefined): number;
141
+ /**
142
+ * The notes that fit in this many characters, best first. A note that does
143
+ * not fit is left for the next, smaller ones; the best note is always given,
144
+ * cut if it has to be, with the id to recall it whole.
145
+ */
146
+ export declare function fitRecalledMemory(items: GivenNote[], maxChars: number): {
147
+ items: GivenNote[];
148
+ cut?: string;
149
+ };
150
+ export declare function formatRecalledMemory(items: GivenNote[], checksFor?: (item: GivenNote) => readonly DescribedPlaceCheck[] | undefined): string;
151
+ /**
152
+ * The ids of the notes recall attached that are still in these texts: the
153
+ * head line of each, as `formatRecalledMemory` writes it. A compaction that
154
+ * kept a note word for word kept that line with it.
155
+ */
156
+ export declare function recalledMemoryIds(texts: Iterable<string>): string[];
97
157
  export declare function createMemoryRecaller(options: CreateMemoryRecallerOptions): MemoryRecaller;
@@ -17,7 +17,8 @@
17
17
  import { type MemorySettings } from "@cerebriline/shared";
18
18
  import type { RetrievalEndpoint } from "./embedding-client";
19
19
  import { type EmbedPendingResult, type LibraryOptions, type LibraryVectorSet } from "./library";
20
- export interface MemoryItem {
20
+ import { type MemoryKind, type MemoryNoteFields, type MemoryOrigin, type MemoryPlace, type MemoryStatus } from "./memory-note";
21
+ export interface MemoryItem extends Partial<MemoryNoteFields> {
21
22
  /** `m12`: what `forget` takes. */
22
23
  id: string;
23
24
  text: string;
@@ -26,6 +27,26 @@ export interface MemoryItem {
26
27
  tags: string[];
27
28
  createdAt: string;
28
29
  }
30
+ /** What `remember` did with a note. */
31
+ export type RememberOutcome =
32
+ /** Kept as a new note. */
33
+ "added"
34
+ /** Already there; new tags, places or evidence were added to it. */
35
+ | "updated"
36
+ /** Already there, word for word, with nothing new. */
37
+ | "unchanged"
38
+ /** The note it replaces now says this, under the same id. */
39
+ | "revised"
40
+ /** Not kept: a note there says nearly the same. */
41
+ | "similar";
42
+ /**
43
+ * Two notes this alike say the same thing (claude-hooks dedup.py: 0.85 of
44
+ * the first 500 characters, there by SequenceMatcher, here by the letter
45
+ * pairs both share).
46
+ */
47
+ export declare const NEAR_DUPLICATE_SIMILARITY = 0.85;
48
+ /** How alike two notes read, 0 to 1: the share of letter pairs they have in common. */
49
+ export declare function noteSimilarity(a: string, b: string): number;
29
50
  export interface RecalledMemory extends MemoryItem {
30
51
  /** The reranker's score, 0 to 1, when there was one. */
31
52
  relevance?: number;
@@ -57,7 +78,7 @@ export interface MemoryExport {
57
78
  text: string;
58
79
  tags: string[];
59
80
  createdAt: string;
60
- }>;
81
+ } & Partial<Pick<MemoryNoteFields, "kind" | "status" | "reason" | "places" | "evidence" | "history" | "settled" | "origin" | "revisedAt">>>;
61
82
  }
62
83
  export declare const MEMORY_EXPORT_FORMAT = "cerebriline-memory";
63
84
  /** A memory is a note, not a document: longer than this belongs in the Library. */
@@ -66,6 +87,41 @@ export declare const MEMORY_NAME_MAX_CHARS = 60;
66
87
  export declare function resolveMemoryDirectory(): string;
67
88
  /** A name as it is kept: trimmed, one line, of a length a list can show. */
68
89
  export declare function normalizeMemoryName(name: string): string;
90
+ /**
91
+ * The key a note is kept under: its text with spacing and case evened out,
92
+ * hashed. "Run the tests with vitest" noted twice, once with a capital and a
93
+ * trailing newline, is one note. The key is the source of the note's
94
+ * document, so it never changes with the chunk settings and neither does
95
+ * the note's id.
96
+ */
97
+ export declare function memoryNoteKey(text: string): string;
98
+ /** What became of a place a note gives. */
99
+ export interface PlaceCheck {
100
+ place: MemoryPlace;
101
+ /**
102
+ * `ok`: there as noted. `moved`: its line is now at `line`. `changed`:
103
+ * the line is no longer in the file. `missing`: no such file.
104
+ * `ambiguous`: more than one file it could be.
105
+ */
106
+ state: "ok" | "moved" | "changed" | "missing" | "ambiguous";
107
+ line?: number;
108
+ }
109
+ /** The workspace's files, relative, bounded and kept a minute. */
110
+ export declare function workspaceFiles(cwd: string): string[];
111
+ /**
112
+ * The rubric a model distils expired notes by (claude-hooks M14): keep what
113
+ * was found and decided and what is still open; drop the process -- the
114
+ * attempts, the retries, the order things were tried in.
115
+ */
116
+ export declare const MEMORY_DISTILL_SYSTEM_PROMPT = "You distil a project's expired notes into one. Keep every finding that still says something about the project, every decision with its reason, and every open question. Drop the process: attempts, retries, dead ends that taught nothing, the order things were tried in. Keep names, paths, commands and numbers exactly as written. Write plain sentences, at most 12 of them, with nothing before or after the note.";
117
+ export declare function buildMemoryDistillPrompt(notes: readonly MemoryItem[]): string;
118
+ /** Whether two paths name one file: the same, or one a path suffix of the other. */
119
+ export declare function sameFile(a: string, b: string): boolean;
120
+ /**
121
+ * 1 when two lists of places share one (the same file, lines close or not
122
+ * given), 0.5 when they share only a file, else 0.
123
+ */
124
+ export declare function placeScore(a: readonly MemoryPlace[], b: readonly MemoryPlace[]): number;
69
125
  export declare class Memory {
70
126
  private readonly library;
71
127
  private readonly directory;
@@ -81,6 +137,13 @@ export declare class Memory {
81
137
  * memory named after the folder, made for that workspace.
82
138
  */
83
139
  private migrate;
140
+ /**
141
+ * Bring notes kept under the first key (the exact text) to the key they
142
+ * have now. Notes that turn out the same are one: the oldest stays, with
143
+ * the tags of all of them, so its id is the one that keeps working.
144
+ */
145
+ private rekeyNotes;
146
+ private textOf;
84
147
  private freeName;
85
148
  /** Every memory in the store, the main one first. */
86
149
  listMemories(): MemoryInfo[];
@@ -99,21 +162,86 @@ export declare class Memory {
99
162
  /** The collections of these memories; a name that is not a memory is left out. */
100
163
  private collections;
101
164
  private toItem;
102
- /** Keep a note. The same text in the same memory is kept once. */
165
+ /**
166
+ * The document a note id names, in the memories in reach: the note itself,
167
+ * or the one it was merged into.
168
+ */
169
+ private resolveNote;
170
+ /** One note by id, from the memories named; one merged away reads as the note it went into. */
171
+ get(id: string, options?: {
172
+ memories?: readonly string[];
173
+ }): MemoryItem | undefined;
174
+ /**
175
+ * The places a note gives, with the line each read when it was kept, so a
176
+ * recall can tell later whether the code moved under it.
177
+ */
178
+ private placesFrom;
179
+ /**
180
+ * Keep a note. The same text in the same memory is kept once, whatever
181
+ * its spacing and case; noted again, it gains the new tags, places and
182
+ * evidence. A note that says nearly what one there says is not kept: the
183
+ * answer names that one, to be revised (`replaces`) rather than doubled.
184
+ */
103
185
  remember(input: {
104
186
  text: string;
105
187
  /** @default the main memory */
106
188
  memory?: string;
107
189
  tags?: readonly string[];
190
+ /** @default "fact" */
191
+ kind?: MemoryKind;
192
+ /** Why: required for a decision. */
193
+ reason?: string;
194
+ /** `path` or `path:line`, the places in the code the note is about. */
195
+ places?: readonly string[];
196
+ /** What showed it, appended to what the note already has. */
197
+ evidence?: string;
198
+ /** A note id: that note now says this, under its own id. */
199
+ replaces?: string;
200
+ /** Who keeps it, and in which task. */
201
+ origin?: MemoryOrigin;
202
+ /** Where the places' paths are read from. */
203
+ cwd?: string;
204
+ /** Keep it even when a note says nearly the same. For imports. */
205
+ allowSimilar?: boolean;
206
+ /** Days until it expires; 0 or left out, never. */
207
+ ttlDays?: number;
108
208
  /** For an imported note: when it was first noted. */
109
209
  createdAt?: string;
110
210
  endpoints?: MemoryEndpoints;
111
211
  signal?: AbortSignal;
112
212
  }): Promise<{
113
213
  item: MemoryItem;
114
- outcome: "added" | "unchanged";
214
+ outcome: RememberOutcome;
215
+ /** For "similar": how alike, 0 to 1. */
216
+ similarity?: number;
115
217
  note?: string;
116
218
  }>;
219
+ /** Embedded now, so the next recall finds it by meaning. A failure loses nothing: it is embedded with the next note. */
220
+ private embedNew;
221
+ /**
222
+ * The note in this memory that says nearly the same, if one does: found
223
+ * by the same search a recall makes, by meaning when there are vectors,
224
+ * and judged on the text.
225
+ */
226
+ private nearest;
227
+ /** Give a note new text, keeping its id; what it said before is kept with it. */
228
+ private revise;
229
+ /**
230
+ * The user's verdict on a note: confirmed or outdated, each with why, or
231
+ * back to noted. Only the user's: no tool the model has calls this.
232
+ *
233
+ * A verdict without a reason is refused, as claude-hooks' findings ledger
234
+ * refuses one: the reason is what the reading model weighs a later
235
+ * contradiction against, and "confirmed" alone gives it nothing to weigh.
236
+ */
237
+ settle(id: string, verdict: {
238
+ status: MemoryStatus;
239
+ reason?: string;
240
+ }, options?: {
241
+ memories?: readonly string[];
242
+ }): MemoryItem;
243
+ /** Count a use of these notes: they were given to a task. */
244
+ noteRecalled(ids: readonly string[], memories: readonly string[]): void;
117
245
  /** The notes that best match, best first, from the memories named. */
118
246
  recall(query: string, options?: {
119
247
  /** @default the main memory */
@@ -122,10 +250,25 @@ export declare class Memory {
122
250
  endpoints?: MemoryEndpoints;
123
251
  limit?: number;
124
252
  signal?: AbortSignal;
253
+ /** Leave out the notes the user marked outdated. */
254
+ skipOutdated?: boolean;
125
255
  }): Promise<{
126
256
  items: RecalledMemory[];
127
257
  notes: string[];
128
258
  }>;
259
+ /**
260
+ * Notes about the files in play: those whose places, or whose text, name
261
+ * a file among `paths`, best place first. For a recall keyed on the
262
+ * prompt, which misses the notes about the code a task has open (claude-
263
+ * hooks cd71cf3: over 8 councils this pass added 5-9 findings each,
264
+ * settled ones the question alone never reached). A shared file counts;
265
+ * shared words alone never do.
266
+ */
267
+ recallAtPlaces(paths: readonly string[], options?: {
268
+ memories?: readonly string[];
269
+ limit?: number;
270
+ exclude?: readonly string[];
271
+ }): RecalledMemory[];
129
272
  /** Every note of the memories named, newest first. */
130
273
  list(options?: {
131
274
  memories?: readonly string[];
@@ -134,6 +277,45 @@ export declare class Memory {
134
277
  forget(id: string, options?: {
135
278
  memories?: readonly string[];
136
279
  }): Promise<boolean>;
280
+ /**
281
+ * Merge the notes of a memory that say nearly the same. The oldest of
282
+ * each set stays, with every tag, place and piece of evidence, and the
283
+ * ids of the others, so a reference to one still reaches it; the others
284
+ * are deleted. A consolidation that only reported merges and left the
285
+ * notes in place grew the store while it said it shrank it (claude-hooks
286
+ * v1.15). A note the user confirmed is never merged into one they did not.
287
+ */
288
+ consolidate(options?: {
289
+ memories?: readonly string[];
290
+ }): Promise<{
291
+ merged: number;
292
+ into: string[];
293
+ }>;
294
+ /**
295
+ * Hold a note's places against the code as it is now. A short path is
296
+ * found by the one file it is a suffix of (as the council's citation
297
+ * check does); two such files is ambiguous, not missing. A line kept
298
+ * with its text is looked for where it was, then anywhere in the file.
299
+ */
300
+ checkPlaces(item: Pick<MemoryItem, "places">, cwd: string, files?: () => readonly string[]): PlaceCheck[];
301
+ /**
302
+ * Distil the notes that have expired into one, then remove them: the
303
+ * notes of a fact that were only ever noted, past the day they were kept
304
+ * until. One summary per memory, kept with no expiry; the originals go
305
+ * only once it is written (claude-hooks M14). With no `distill`, or a
306
+ * distillation that fails, they stay where they are.
307
+ */
308
+ expire(options: {
309
+ distill: (notes: readonly MemoryItem[]) => Promise<string | undefined>;
310
+ nowMs?: number;
311
+ memories?: readonly string[];
312
+ endpoints?: MemoryEndpoints;
313
+ }): Promise<Array<{
314
+ memory: string;
315
+ distilled?: string;
316
+ removed: number;
317
+ kept?: string;
318
+ }>>;
137
319
  /** A memory as a file's content. The notes only: vectors are made again where it is read. */
138
320
  exportMemory(name: string): MemoryExport;
139
321
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cerebriline/core",
3
3
  "description": "Cerebriline Core SDK for Node Runtime",
4
- "version": "4.100.263",
4
+ "version": "4.100.265",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/mann1x/cline.git",
@@ -80,9 +80,9 @@
80
80
  "ws": "^8.20.0",
81
81
  "yaml": "^2.8.2",
82
82
  "zod": "4.4.3",
83
- "@cerebriline/agents": "4.100.263",
84
- "@cerebriline/llms": "4.100.263",
85
- "@cerebriline/shared": "4.100.263"
83
+ "@cerebriline/agents": "4.100.265",
84
+ "@cerebriline/llms": "4.100.265",
85
+ "@cerebriline/shared": "4.100.265"
86
86
  },
87
87
  "peerDependencies": {
88
88
  "posthog-node": "^5.8.0"