@kisev/memomatic 1.0.0-dev.72.g0dfe98eca874 → 1.0.0-dev.74.ge851c073615d
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 +25 -16
- package/README.ru.md +24 -16
- package/assets/systemd/memomatic-dream.timer +2 -2
- package/assets/systemd/memomatic-sessions.service +9 -0
- package/assets/systemd/memomatic-sessions.timer +10 -0
- package/dist/cli.js +129 -37
- package/dist/dream.d.ts +1 -23
- package/dist/dream.js +7 -224
- package/dist/gates.d.ts +5 -0
- package/dist/gates.js +17 -0
- package/dist/rules.d.ts +1 -0
- package/dist/rules.js +3 -1
- package/dist/service.js +21 -7
- package/dist/sessions.d.ts +36 -0
- package/dist/sessions.js +238 -0
- package/dist/settings.d.ts +11 -4
- package/dist/settings.js +54 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
`@kisev/memomatic` is a personal learning memory for agents following the OpenClaw
|
|
6
6
|
memory architecture: a tiered Markdown corpus you can read as plain files, a
|
|
7
|
-
rebuildable SQLite index with FTS5 and optional local embeddings,
|
|
8
|
-
`dream` consolidation sweep.
|
|
7
|
+
rebuildable SQLite index with FTS5 and optional local embeddings, a `sessions`
|
|
8
|
+
extraction pass, and a nightly `dream` consolidation sweep.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -21,24 +21,32 @@ automatic context injection is used. For background processing, run the CLI:
|
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
memomatic process # validate the inbox and index accepted entries (no model)
|
|
24
|
-
memomatic
|
|
24
|
+
memomatic sessions # extract episodic memory from OpenCode sessions (model)
|
|
25
|
+
memomatic dream # consolidation: inbox + promotion + bounded rewrite + archive
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
(`memomatic-
|
|
28
|
+
Nightly runs are scheduled by the systemd user units in `assets/systemd/`
|
|
29
|
+
(`memomatic-sessions.service`/`.timer` for extraction and
|
|
30
|
+
`memomatic-dream.service`/`.timer` for consolidation).
|
|
29
31
|
|
|
30
|
-
Configure `dream.model` and an OpenCode
|
|
31
|
-
extraction; without a model, the session
|
|
32
|
+
Configure `sessions.model` (or keep a legacy `dream.model`) and an OpenCode
|
|
33
|
+
provider before expecting session extraction; without a model, the session
|
|
34
|
+
watermark is preserved. Follow the
|
|
32
35
|
[setup, first-entry, and scheduling guide](../../docs/how-to/memomatic.md).
|
|
33
36
|
|
|
34
|
-
## CLI and Dream
|
|
37
|
+
## CLI, Sessions, and Dream
|
|
35
38
|
|
|
36
39
|
See [CLI conventions](../../docs/reference/cli.md) and
|
|
37
40
|
[Dream controls](../../docs/how-to/memomatic.md#observe-limit-and-resume-dream).
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
`sessions --plan` inspects pending session work without a model; `dream --plan`
|
|
42
|
+
counts pending inbox files and promotion candidates. `--dry-run` previews
|
|
43
|
+
either command and may still call models. Sessions drain the eligible snapshot
|
|
44
|
+
with per-fragment checkpoints, reusing one OpenCode server and unchanged
|
|
45
|
+
embeddings; Dream then promotes usage-gated episodic entries into `MEMORY.md`
|
|
46
|
+
through bounded consolidation and archives old entries. The first
|
|
47
|
+
legacy-cursor migration checks prior history once without discarding existing
|
|
48
|
+
memory. `status` shows the queue: pending inbox files, session backlog,
|
|
49
|
+
promotion candidates, and the last run of each sweep.
|
|
42
50
|
|
|
43
51
|
## Inbox
|
|
44
52
|
|
|
@@ -63,13 +71,14 @@ is exposed in search responses.
|
|
|
63
71
|
|
|
64
72
|
## Surfaces
|
|
65
73
|
|
|
66
|
-
- `memomatic` CLI: `process`, `dream`, `search`, `status`, `index`.
|
|
74
|
+
- `memomatic` CLI: `process`, `sessions`, `dream`, `search`, `status`, `index`.
|
|
67
75
|
- MCP stdio server with `memory_search`, `memory_get`, `memory_write`, and
|
|
68
76
|
`memory_forget` tools.
|
|
69
77
|
|
|
70
|
-
The model initiates memory searches through visible MCP tool calls.
|
|
71
|
-
processes OpenCode history separately; connecting another
|
|
72
|
-
its session history. Agentomatic and memomatic are
|
|
78
|
+
The model initiates memory searches through visible MCP tool calls. The
|
|
79
|
+
`sessions` command processes OpenCode history separately; connecting another
|
|
80
|
+
host does not import its session history. Agentomatic and memomatic are
|
|
81
|
+
installed independently.
|
|
73
82
|
|
|
74
83
|
Nothing is deleted without the explicit directives documented in
|
|
75
84
|
`MEMORY_RULES.md`.
|
package/README.ru.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
`@kisev/memomatic` — персональная обучающая память для агентов по архитектуре
|
|
6
6
|
памяти OpenClaw: ярусный Markdown-корпус, который можно читать как обычные файлы,
|
|
7
|
-
пересобираемый SQLite-индекс с FTS5 и опциональными локальными
|
|
8
|
-
ночной проход консолидации `dream`.
|
|
7
|
+
пересобираемый SQLite-индекс с FTS5 и опциональными локальными эмбеддингами,
|
|
8
|
+
проход извлечения `sessions` и ночной проход консолидации `dream`.
|
|
9
9
|
|
|
10
10
|
## Установка
|
|
11
11
|
|
|
@@ -21,24 +21,32 @@ npm install --global @kisev/memomatic
|
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
memomatic process # валидация inbox и индексация принятого (без модели)
|
|
24
|
-
memomatic
|
|
24
|
+
memomatic sessions # извлечение эпизодической памяти из сессий OpenCode (модель)
|
|
25
|
+
memomatic dream # консолидация: inbox + продвижение + перезапись + архив
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
(`memomatic-
|
|
28
|
+
Ночные проходы планируют user-юниты systemd из `assets/systemd/`
|
|
29
|
+
(`memomatic-sessions.service`/`.timer` для извлечения и
|
|
30
|
+
`memomatic-dream.service`/`.timer` для консолидации).
|
|
29
31
|
|
|
30
|
-
Перед извлечением сессий настройте `
|
|
31
|
-
без модели отметка обработанных сессий
|
|
32
|
+
Перед извлечением сессий настройте `sessions.model` (или оставьте старый
|
|
33
|
+
`dream.model`) и провайдера OpenCode; без модели отметка обработанных сессий
|
|
34
|
+
сохраняется. См.
|
|
32
35
|
[настройку, первую запись и расписание](../../docs/ru/how-to/memomatic.md).
|
|
33
36
|
|
|
34
|
-
## CLI и Dream
|
|
37
|
+
## CLI, Sessions и Dream
|
|
35
38
|
|
|
36
39
|
См. [соглашения CLI](../../docs/ru/reference/cli.md) и
|
|
37
40
|
[управление Dream](../../docs/ru/how-to/memomatic.md#наблюдение-ограничения-и-продолжение-dream).
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
`sessions --plan` читает очередь сессий без модели; `dream --plan` считает
|
|
42
|
+
ожидающие файлы inbox и кандидатов на продвижение. `--dry-run` показывает
|
|
43
|
+
превью любой команды и может вызывать модели. Sessions обрабатывают весь
|
|
44
|
+
подходящий снимок с контрольными точками фрагментов, переиспользуя один сервер
|
|
45
|
+
OpenCode и неизменённые эмбеддинги; Dream затем продвигает эпизодические записи,
|
|
46
|
+
прошедшие пороги использования, в `MEMORY.md` через ограниченную консолидацию и
|
|
47
|
+
архивирует старое. Первая миграция старого курсора перепроверяет историю один
|
|
48
|
+
раз, сохраняя накопленную память. `status` показывает очередь: ожидающие файлы
|
|
49
|
+
inbox, бэклог сессий, кандидатов на продвижение и время последнего прохода.
|
|
42
50
|
|
|
43
51
|
## Inbox
|
|
44
52
|
|
|
@@ -63,12 +71,12 @@ memomatic dream # полный проход: inbox + сессии + кон
|
|
|
63
71
|
|
|
64
72
|
## Поверхности
|
|
65
73
|
|
|
66
|
-
- CLI `memomatic`: `process`, `dream`, `search`, `status`, `index`.
|
|
74
|
+
- CLI `memomatic`: `process`, `sessions`, `dream`, `search`, `status`, `index`.
|
|
67
75
|
- MCP-сервер stdio с инструментами `memory_search`, `memory_get`,
|
|
68
76
|
`memory_write` и `memory_forget`.
|
|
69
77
|
|
|
70
|
-
Модель сама инициирует поиск памяти через видимые MCP-вызовы.
|
|
71
|
-
обрабатывает историю OpenCode; подключение другого хоста
|
|
72
|
-
сессии. Agentomatic и memomatic устанавливаются независимо.
|
|
78
|
+
Модель сама инициирует поиск памяти через видимые MCP-вызовы. Команда
|
|
79
|
+
`sessions` отдельно обрабатывает историю OpenCode; подключение другого хоста
|
|
80
|
+
не импортирует его сессии. Agentomatic и memomatic устанавливаются независимо.
|
|
73
81
|
|
|
74
82
|
Без явных директив, описанных в `MEMORY_RULES.md`, ничего не удаляется.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Memomatic sessions: extract episodic memory from OpenCode sessions
|
|
3
|
+
Documentation=https://github.com/kisev/skills#memomatic
|
|
4
|
+
After=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=oneshot
|
|
8
|
+
ExecStart=/bin/sh -lc 'exec memomatic sessions'
|
|
9
|
+
Environment=MEMOMATIC_SESSIONS=1
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
+
import { readdir, stat } from "node:fs/promises";
|
|
3
4
|
import { isAbsolute } from "node:path";
|
|
4
5
|
import { openMemomatic, rebuildIndex, searchMemory } from "./service.js";
|
|
5
6
|
import { runDream } from "./dream.js";
|
|
7
|
+
import { runSessions } from "./sessions.js";
|
|
6
8
|
import { processInbox, withRunLock } from "./inbox.js";
|
|
7
9
|
import { OpenCodeExecutor } from "./executor.js";
|
|
8
10
|
import { normalizeSettings } from "./settings.js";
|
|
9
11
|
import { planIngestion } from "./ingestion.js";
|
|
10
12
|
import { opencodeDatabasePath } from "./ingest.js";
|
|
13
|
+
import { promotionCandidates, relevanceByFts } from "./gates.js";
|
|
11
14
|
import { memomaticPaths } from "./paths.js";
|
|
12
15
|
import { program, common, Option, configFile, applyConfig, reporter, cancellation, duration, integer, fail, } from "./generated/cli.js";
|
|
13
16
|
const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
@@ -49,26 +52,35 @@ const run = async (command, action) => {
|
|
|
49
52
|
context = await openMemomatic({
|
|
50
53
|
readOnly: opts.dryRun || opts.plan || command.name() === "status",
|
|
51
54
|
configure: (settings) => {
|
|
55
|
+
const sessions = { ...settings.sessions };
|
|
52
56
|
const dream = { ...settings.dream };
|
|
53
57
|
for (const [flag, key] of [
|
|
54
58
|
["model", "model"],
|
|
55
59
|
["variant", "variant"],
|
|
56
60
|
["timeout", "timeoutMs"],
|
|
57
61
|
["maxDuration", "maxDurationMs"],
|
|
62
|
+
["retries", "retries"],
|
|
63
|
+
]) {
|
|
64
|
+
if (opts[flag] !== undefined) {
|
|
65
|
+
Object.assign(sessions, { [key]: opts[flag] });
|
|
66
|
+
Object.assign(dream, { [key]: opts[flag] });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const [flag, key] of [
|
|
58
70
|
["maxSessions", "maxSessions"],
|
|
59
71
|
["chunkChars", "maxChars"],
|
|
60
|
-
["retries", "retries"],
|
|
61
72
|
["idle", "idleMs"],
|
|
62
73
|
["opencodeUrl", "opencodeUrl"],
|
|
63
74
|
])
|
|
64
75
|
if (opts[flag] !== undefined)
|
|
65
|
-
Object.assign(
|
|
66
|
-
return normalizeSettings({ ...settings, dream });
|
|
76
|
+
Object.assign(sessions, { [key]: opts[flag] });
|
|
77
|
+
return normalizeSettings({ ...settings, sessions, dream });
|
|
67
78
|
},
|
|
68
79
|
});
|
|
69
|
-
const
|
|
70
|
-
? AbortSignal.any([limited, AbortSignal.timeout(context.settings.
|
|
80
|
+
const deadline = (section) => context.settings[section].maxDurationMs && !opts.maxDuration
|
|
81
|
+
? AbortSignal.any([limited, AbortSignal.timeout(context.settings[section].maxDurationMs)])
|
|
71
82
|
: limited;
|
|
83
|
+
const signal = command.name() === "sessions" ? deadline("sessions") : deadline("dream");
|
|
72
84
|
await action(context, opts, { signal, observe: log.emit });
|
|
73
85
|
}
|
|
74
86
|
finally {
|
|
@@ -78,39 +90,61 @@ const run = async (command, action) => {
|
|
|
78
90
|
}
|
|
79
91
|
};
|
|
80
92
|
const output = (value, opts, summary) => console.log(opts.json || !process.stdout.isTTY ? JSON.stringify(value, null, 2) : summary);
|
|
81
|
-
const
|
|
82
|
-
.
|
|
83
|
-
.option("--dry-run", "preview without persistent writes (model calls still occur)")
|
|
84
|
-
.addOption(new Option("--model <provider/model>", "OpenCode model; defaults to settings.dream.model").env("MEMOMATIC_MODEL"))
|
|
93
|
+
const modelOptions = (command) => command
|
|
94
|
+
.addOption(new Option("--model <provider/model>", "OpenCode model; defaults to settings").env("MEMOMATIC_MODEL"))
|
|
85
95
|
.addOption(new Option("--variant <name>", "provider reasoning variant").env("MEMOMATIC_VARIANT"))
|
|
86
96
|
.addOption(new Option("--timeout <duration>", "per-model-call timeout (default 3m)")
|
|
87
97
|
.argParser(duration)
|
|
88
98
|
.env("MEMOMATIC_TIMEOUT"))
|
|
89
|
-
.addOption(new Option("--max-duration <duration>", "whole-run deadline; 0 means
|
|
99
|
+
.addOption(new Option("--max-duration <duration>", "whole-run deadline; 0 means unlimited")
|
|
90
100
|
.argParser(duration)
|
|
91
101
|
.env("MEMOMATIC_MAX_DURATION"))
|
|
102
|
+
.addOption(new Option("--retries <count>", "additional model attempts (default 1)")
|
|
103
|
+
.argParser(integer)
|
|
104
|
+
.env("MEMOMATIC_RETRIES"));
|
|
105
|
+
const executor = (context, runtime) => {
|
|
106
|
+
const sections = {
|
|
107
|
+
sessions: context.settings.sessions,
|
|
108
|
+
dream: context.settings.dream,
|
|
109
|
+
};
|
|
110
|
+
const create = (section) => {
|
|
111
|
+
const config = sections[section];
|
|
112
|
+
return config.model
|
|
113
|
+
? new OpenCodeExecutor({
|
|
114
|
+
model: config.model,
|
|
115
|
+
variant: config.variant,
|
|
116
|
+
url: "opencodeUrl" in config ? config.opencodeUrl : null,
|
|
117
|
+
timeoutMs: config.timeoutMs,
|
|
118
|
+
retries: config.retries,
|
|
119
|
+
...runtime,
|
|
120
|
+
})
|
|
121
|
+
: null;
|
|
122
|
+
};
|
|
123
|
+
return create;
|
|
124
|
+
};
|
|
125
|
+
const sessions = add("sessions", "Drain the OpenCode session snapshot into episodic memory, checkpointing fragments")
|
|
126
|
+
.option("--plan", "count pending work without model calls or writes")
|
|
127
|
+
.option("--dry-run", "preview without persistent writes (model calls still occur)");
|
|
128
|
+
modelOptions(sessions)
|
|
92
129
|
.addOption(new Option("--max-sessions <count>", "optional session limit; 0 means all")
|
|
93
130
|
.argParser(integer)
|
|
94
131
|
.env("MEMOMATIC_MAX_SESSIONS"))
|
|
95
132
|
.addOption(new Option("--chunk-chars <count>", "maximum new text per fragment (default 24000)")
|
|
96
133
|
.argParser(integer)
|
|
97
134
|
.env("MEMOMATIC_CHUNK_CHARS"))
|
|
98
|
-
.addOption(new Option("--retries <count>", "additional model attempts (default 1)")
|
|
99
|
-
.argParser(integer)
|
|
100
|
-
.env("MEMOMATIC_RETRIES"))
|
|
101
135
|
.addOption(new Option("--idle <duration>", "minimum session idle age (default 10m)")
|
|
102
136
|
.argParser(duration)
|
|
103
137
|
.env("MEMOMATIC_IDLE"))
|
|
104
138
|
.addOption(new Option("--opencode-url <url>", "reuse an existing OpenCode server; otherwise start one per run").env("MEMOMATIC_OPENCODE_URL"))
|
|
105
139
|
.addOption(new Option("--database <path>", "OpenCode SQLite session database").env("MEMOMATIC_DATABASE"))
|
|
106
|
-
.addHelpText("after", "\nExamples:\n memomatic
|
|
107
|
-
|
|
140
|
+
.addHelpText("after", "\nExamples:\n memomatic sessions\n memomatic sessions --max-duration 15m --log-format json\n memomatic sessions --dry-run --max-sessions 2\n\nCompleted fragments survive cancellation. Logs use stderr; --json keeps stdout machine-readable.");
|
|
141
|
+
sessions.action(async () => run(sessions, async (context, opts, runtime) => {
|
|
108
142
|
if (opts.plan) {
|
|
109
143
|
const start = Date.now();
|
|
110
144
|
const plan = planIngestion(opts.database ?? opencodeDatabasePath(), context.store, {
|
|
111
|
-
before: Date.now() - context.settings.
|
|
112
|
-
maxChars: context.settings.
|
|
113
|
-
maxSessions: context.settings.
|
|
145
|
+
before: Date.now() - context.settings.sessions.idleMs,
|
|
146
|
+
maxChars: context.settings.sessions.maxChars,
|
|
147
|
+
maxSessions: context.settings.sessions.maxSessions,
|
|
114
148
|
});
|
|
115
149
|
const summary = {
|
|
116
150
|
sessions: plan.sessions,
|
|
@@ -125,27 +159,43 @@ dream.action(async () => run(dream, async (context, opts, runtime) => {
|
|
|
125
159
|
output(summary, opts, `${summary.sessions} sessions, ${summary.fragments} fragments, ${summary.characters} characters (${summary.scanMs}ms scan). No model calls.`);
|
|
126
160
|
return;
|
|
127
161
|
}
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
model: context.settings.dream.model,
|
|
131
|
-
variant: context.settings.dream.variant,
|
|
132
|
-
url: context.settings.dream.opencodeUrl,
|
|
133
|
-
timeoutMs: context.settings.dream.timeoutMs,
|
|
134
|
-
retries: context.settings.dream.retries,
|
|
135
|
-
...runtime,
|
|
136
|
-
})
|
|
137
|
-
: null;
|
|
162
|
+
const create = executor(context, runtime);
|
|
163
|
+
const model = create("sessions");
|
|
138
164
|
try {
|
|
139
|
-
const execute = () =>
|
|
165
|
+
const execute = () => runSessions(context, model, {
|
|
140
166
|
...runtime,
|
|
141
167
|
dryRun: opts.dryRun,
|
|
142
168
|
databaseFile: opts.database,
|
|
143
169
|
});
|
|
144
170
|
const result = opts.dryRun ? await execute() : await withRunLock(context.paths, execute);
|
|
145
|
-
output({ ...result, modelUsage:
|
|
171
|
+
output({ ...result, modelUsage: model?.usage ?? null }, opts, `Sessions complete: ${result.sessionsIngested} sessions, ${result.candidatesExtracted} candidates, ${result.elapsedMs}ms`);
|
|
146
172
|
}
|
|
147
173
|
finally {
|
|
148
|
-
await
|
|
174
|
+
await model?.close();
|
|
175
|
+
}
|
|
176
|
+
}));
|
|
177
|
+
const dream = add("dream", "Consolidate memory: inbox, promotion, bounded rewrite, archive")
|
|
178
|
+
.option("--plan", "count pending work without model calls or writes")
|
|
179
|
+
.option("--dry-run", "preview without persistent writes (model calls still occur)");
|
|
180
|
+
modelOptions(dream).addHelpText("after", "\nExamples:\n memomatic dream\n memomatic dream --max-duration 15m --log-format json\n memomatic dream --dry-run\n\nSession extraction lives in `memomatic sessions`. Logs use stderr; --json keeps stdout machine-readable.");
|
|
181
|
+
dream.action(async () => run(dream, async (context, opts, runtime) => {
|
|
182
|
+
if (opts.plan) {
|
|
183
|
+
const relevance = relevanceByFts(context.store, context.settings);
|
|
184
|
+
const candidates = promotionCandidates(context.store, context.settings, relevance);
|
|
185
|
+
const inboxFiles = (await readdir(context.paths.inboxDir).catch(() => [])).filter((name) => name.endsWith(".md")).length;
|
|
186
|
+
const summary = { inboxFiles, promotionCandidates: candidates.length };
|
|
187
|
+
output(summary, opts, `${summary.inboxFiles} inbox files, ${summary.promotionCandidates} promotion candidates. No model calls.`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const create = executor(context, runtime);
|
|
191
|
+
const model = create("dream");
|
|
192
|
+
try {
|
|
193
|
+
const execute = () => runDream(context, model, { ...runtime, dryRun: opts.dryRun });
|
|
194
|
+
const result = opts.dryRun ? await execute() : await withRunLock(context.paths, execute);
|
|
195
|
+
output({ ...result, modelUsage: model?.usage ?? null }, opts, `Dream complete: ${result.promoted.length} promoted, ${result.superseded.length} superseded, ${result.elapsedMs}ms`);
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
await model?.close();
|
|
149
199
|
}
|
|
150
200
|
}));
|
|
151
201
|
const processCommand = add("process", "Process explicit inbox entries without LLM extraction").option("--dry-run", "preview without writes");
|
|
@@ -162,12 +212,54 @@ index.action(() => run(index, async (context, opts, runtime) => {
|
|
|
162
212
|
else
|
|
163
213
|
console.log(`${entries} entries indexed`);
|
|
164
214
|
}));
|
|
165
|
-
const status = add("status", "Read corpus/index status without indexing or model calls");
|
|
166
|
-
status.action(() => run(status, async (context, opts) =>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
215
|
+
const status = add("status", "Read corpus/index/queue status without indexing or model calls");
|
|
216
|
+
status.action(() => run(status, async (context, opts, runtime) => {
|
|
217
|
+
const inboxFiles = (await readdir(context.paths.inboxDir).catch(() => [])).filter((name) => name.endsWith(".md")).length;
|
|
218
|
+
let sessionBacklog = null;
|
|
219
|
+
const databaseFile = opencodeDatabasePath();
|
|
220
|
+
if (await stat(databaseFile).catch(() => null)) {
|
|
221
|
+
try {
|
|
222
|
+
const plan = planIngestion(databaseFile, context.store, {
|
|
223
|
+
before: Date.now() - context.settings.sessions.idleMs,
|
|
224
|
+
maxChars: context.settings.sessions.maxChars,
|
|
225
|
+
maxSessions: context.settings.sessions.maxSessions,
|
|
226
|
+
});
|
|
227
|
+
sessionBacklog = {
|
|
228
|
+
sessions: plan.sessions,
|
|
229
|
+
messages: plan.messages,
|
|
230
|
+
fragments: plan.fragments.length,
|
|
231
|
+
characters: plan.characters,
|
|
232
|
+
cachedFragments: plan.cached,
|
|
233
|
+
cachedSessions: plan.cachedSessions,
|
|
234
|
+
unscannedSessions: plan.unscannedSessions,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
runtime.observe({
|
|
239
|
+
phase: "status.sessions",
|
|
240
|
+
message: `Cannot read OpenCode sessions: ${error.message}`,
|
|
241
|
+
level: "warn",
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const relevance = relevanceByFts(context.store, context.settings);
|
|
246
|
+
const candidates = promotionCandidates(context.store, context.settings, relevance);
|
|
247
|
+
const report = {
|
|
248
|
+
entries: context.store.allEntries().length,
|
|
249
|
+
fts5: context.store.hasFts(),
|
|
250
|
+
stateRoot: context.paths.stateRoot,
|
|
251
|
+
queue: {
|
|
252
|
+
inboxFiles,
|
|
253
|
+
sessions: sessionBacklog,
|
|
254
|
+
promotionCandidates: candidates.length,
|
|
255
|
+
},
|
|
256
|
+
lastRun: {
|
|
257
|
+
dream: context.store.getMeta("last-dream-at"),
|
|
258
|
+
sessions: context.store.getMeta("last-sessions-at"),
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
output(report, opts, `${report.entries} indexed entries, ${inboxFiles} inbox files, ${sessionBacklog ? `${sessionBacklog.fragments} pending fragments` : "no session database"}, ${candidates.length} promotion candidates in ${context.paths.stateRoot}`);
|
|
262
|
+
}));
|
|
171
263
|
const search = add("search <query...>", "Find relevant memory or return no matches")
|
|
172
264
|
.option("--explain", "include relevance diagnostics")
|
|
173
265
|
.addOption(new Option("--project <name>", "restrict to a project and user-level memory").env("MEMOMATIC_PROJECT"));
|
package/dist/dream.d.ts
CHANGED
|
@@ -2,42 +2,20 @@ import { type InboxReport } from "./inbox.js";
|
|
|
2
2
|
import { type ModelExecutor } from "./executor.js";
|
|
3
3
|
import { type OperationOptions } from "./operations.js";
|
|
4
4
|
import type { MemomaticContext } from "./service.js";
|
|
5
|
+
export declare const LAST_RUN_KEY = "last-dream-at";
|
|
5
6
|
export type DreamReport = {
|
|
6
7
|
inbox: InboxReport;
|
|
7
|
-
sessionsIngested: number;
|
|
8
|
-
candidatesExtracted: number;
|
|
9
8
|
promoted: string[];
|
|
10
9
|
superseded: string[];
|
|
11
10
|
dropped: string[];
|
|
12
|
-
rejected: Array<{
|
|
13
|
-
text: string;
|
|
14
|
-
reason: string;
|
|
15
|
-
}>;
|
|
16
11
|
archived: string[];
|
|
17
12
|
appendOnlyFallback: boolean;
|
|
18
13
|
dryRun: boolean;
|
|
19
|
-
ingestion?: {
|
|
20
|
-
messages: number;
|
|
21
|
-
fragments: number;
|
|
22
|
-
completed: number;
|
|
23
|
-
cached: number;
|
|
24
|
-
characters: number;
|
|
25
|
-
remaining: number;
|
|
26
|
-
cachedSessions: number;
|
|
27
|
-
unscannedSessions: number;
|
|
28
|
-
};
|
|
29
14
|
elapsedMs?: number;
|
|
30
15
|
};
|
|
31
16
|
export type DreamOptions = OperationOptions & {
|
|
32
17
|
dryRun?: boolean;
|
|
33
|
-
databaseFile?: string;
|
|
34
18
|
};
|
|
35
|
-
export type ExtractedCandidate = {
|
|
36
|
-
text: string;
|
|
37
|
-
key: string | null;
|
|
38
|
-
reason: string;
|
|
39
|
-
};
|
|
40
|
-
export declare function parseExtraction(response: string): ExtractedCandidate[];
|
|
41
19
|
export type ConsolidationOperation = {
|
|
42
20
|
op: "add";
|
|
43
21
|
line: string;
|