omo-memory 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 +10 -61
- package/dist/cli.js +6 -89
- package/dist/globalMemory.js +22 -1
- package/dist/mcp.js +2 -2
- package/dist/mcpGlobalTools.js +23 -0
- package/dist/memory.js +37 -31
- package/dist/memoryDb.js +43 -113
- package/dist/memoryReport.js +2 -2
- package/docs/adapter-integration.md +17 -89
- package/docs/epic-omo-memory.md +29 -51
- package/package.json +5 -3
- package/scripts/omo-memory-user-prompt.mjs +107 -0
- package/dist/conceptExtraction.js +0 -188
- package/dist/graphTui.js +0 -239
- package/dist/mcpOntologyTools.js +0 -117
- package/dist/ontologyCore.js +0 -142
- package/dist/ontologyGraph.js +0 -173
- package/dist/ontologyQueries.js +0 -30
- package/dist/ontologySupersede.js +0 -49
- package/dist/retentionPolicy.js +0 -76
- package/dist/retentionRecompute.js +0 -175
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Adapter Integration
|
|
2
2
|
|
|
3
|
-
OMO Memory is the shared local work ledger for OMO adapters. It is host-neutral: lazycodex, omo-on-opencode, lfg, and future adapters all write summaries, decisions, QA evidence, task state, and handoffs to the same project-local SQLite database at `<project-root>/.omo/memory/state.sqlite` by default.
|
|
3
|
+
OMO Memory is the shared local session/work/event ledger for OMO adapters. It is host-neutral: lazycodex, omo-on-opencode, lfg, and future adapters all write summaries, decisions, QA evidence, task state, and handoffs to the same project-local SQLite database at `<project-root>/.omo/memory/state.sqlite` by default.
|
|
4
4
|
|
|
5
5
|
No full transcript capture by default. Do not store API keys, tokens, `.env` contents, auth files, raw tool logs, auth headers, cookies, or any other secret-bearing material.
|
|
6
6
|
|
|
@@ -118,14 +118,14 @@ During the session, hooks should write concise user-action summaries, task state
|
|
|
118
118
|
}
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
This package is the local MCP-to-SQLite router. It does not scrape host transcripts or centralize cloud state. Hosts and adapters must call the MCP tools at their own
|
|
121
|
+
This package is the local MCP-to-SQLite router. It does not scrape host transcripts, store assistant output, or centralize cloud state. Hosts and adapters must call the MCP tools at their own integration points.
|
|
122
122
|
|
|
123
123
|
Retrieval is opt-in or intent-gated:
|
|
124
124
|
|
|
125
125
|
- Use `memory_recent_events` only when the user explicitly asks for recent OMO Memory context.
|
|
126
126
|
- Use `memory_recall_events` when the current user input has a concrete query that can be matched to recorded summaries, decisions, or evidence.
|
|
127
127
|
- Do not automatically attach the last session to every user prompt.
|
|
128
|
-
-
|
|
128
|
+
- To preserve user intent across sessions, adapters may invoke the packaged `scripts/omo-memory-user-prompt.mjs` UserPromptSubmit helper with the hook payload on stdin. The helper records only the current user prompt as a redacted `user_prompt` event and ignores assistant output.
|
|
129
129
|
|
|
130
130
|
Use these tools:
|
|
131
131
|
|
|
@@ -142,51 +142,28 @@ Use these tools:
|
|
|
142
142
|
- `memory_global_scan`
|
|
143
143
|
- `memory_global_migrate`
|
|
144
144
|
- `memory_global_list`
|
|
145
|
-
|
|
146
|
-
- `memory_ontology_extract`
|
|
147
|
-
- `memory_ontology_score`
|
|
148
|
-
- `memory_ontology_promote`
|
|
149
|
-
- `memory_ontology_demote`
|
|
150
|
-
- `memory_ontology_supersede`
|
|
151
|
-
- `memory_ontology_recall`
|
|
145
|
+
|
|
152
146
|
|
|
153
147
|
CLI updates: normal CLI commands automatically launch a quiet background `npm install -g omo-memory@latest` at most once per day. MCP startup intentionally does not auto-update because stdout/stderr must remain reserved for the protocol. Hosts that need pinned installs should set `OMO_MEMORY_AUTO_UPDATE=0` in the CLI environment.
|
|
154
148
|
|
|
155
|
-
Global migration is copy/import only. Adapters may scan for existing project-local `.omo/memory/state.sqlite` databases and import
|
|
149
|
+
Global migration is copy/import only. Adapters may scan for existing project-local `.omo/memory/state.sqlite` databases and import their event/session/handoff ledgers into a user-selected global SQLite file, but they must preserve source DBs and retain source provenance in the global store.
|
|
156
150
|
|
|
157
|
-
Global
|
|
151
|
+
Global import is an event-log surface, not an ontology, graph, concept-extraction, retention-scoring, or curated durable-memory layer.
|
|
158
152
|
|
|
159
|
-
Example global
|
|
153
|
+
Example global event import flow:
|
|
160
154
|
|
|
161
155
|
```sh
|
|
162
156
|
omo-memory global scan --root /Users/ilseoblee/workspace
|
|
163
157
|
omo-memory global migrate --root /Users/ilseoblee/workspace --global-db ~/.omo/memory/global.sqlite
|
|
164
|
-
|
|
165
|
-
OMO_MEMORY_DB=~/.omo/memory/global.sqlite omo-memory
|
|
166
|
-
bun --version
|
|
167
|
-
omo-memory graph tui --db ~/.omo/memory/global.sqlite --query linaforge
|
|
158
|
+
omo-memory global list --global-db ~/.omo/memory/global.sqlite
|
|
159
|
+
OMO_MEMORY_DB=~/.omo/memory/global.sqlite omo-memory recall --query linaforge
|
|
168
160
|
```
|
|
169
161
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Lifecycle commands:
|
|
173
|
-
|
|
174
|
-
- `memory_ontology_candidates`: derive candidate terms from concise summaries.
|
|
175
|
-
- `memory_ontology_extract`: explicitly extract candidates from one summary/event.
|
|
176
|
-
- `memory_ontology_score`: recompute deterministic scores and retention classes.
|
|
177
|
-
- `memory_ontology_promote`: curate a concept into durable memory.
|
|
178
|
-
- `memory_ontology_demote`: lower a durable memory's retention class.
|
|
179
|
-
- `memory_ontology_supersede`: preserve the old memory and create a replacement.
|
|
180
|
-
- `memory_ontology_recall`: retrieve ontology-backed memories only for an explicit query.
|
|
162
|
+
Unavailable surfaces:
|
|
181
163
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
- `
|
|
185
|
-
- `Up` / `Down`: move selected concept.
|
|
186
|
-
- `Tab`: move to the next concept.
|
|
187
|
-
- `/` or `f`: focus filter input when supported by the terminal runtime.
|
|
188
|
-
|
|
189
|
-
The graph UI is local terminal UI. It does not require a browser, web server, cloud account, or vector service.
|
|
164
|
+
- `memory_ontology_*` MCP tools are absent.
|
|
165
|
+
- `omo-memory ontology ...` CLI commands are unknown.
|
|
166
|
+
- `omo-memory graph tui` is unknown.
|
|
190
167
|
|
|
191
168
|
Example session start:
|
|
192
169
|
|
|
@@ -218,59 +195,10 @@ Example QA evidence:
|
|
|
218
195
|
- Do not read or record `.env`, auth files, tokens, cookies, API keys, bearer headers, or raw secret-bearing logs.
|
|
219
196
|
- Store sanitized summaries and evidence references instead of full logs.
|
|
220
197
|
- Host-specific values may appear only in small redacted metadata payloads and must not require schema branches.
|
|
221
|
-
- Export and purge are explicit
|
|
222
|
-
|
|
223
|
-
## Ontology Schema Boundary
|
|
224
|
-
|
|
225
|
-
OMO Memory's chronological ledger remains authoritative: sessions, events, and handoffs record what happened in a project. The ontology schema is an additive layer for durable memory derived from that ledger:
|
|
226
|
-
|
|
227
|
-
- `concepts` stores vocabulary entries such as project terms, practices, tools, and recurring ideas.
|
|
228
|
-
- `relations` stores typed links between concepts, decisions, events, sessions, and handoffs.
|
|
229
|
-
- `durable_memories` stores approved long-term facts, preferences, and working rules.
|
|
230
|
-
- `decision_records` stores important choices with rationale, evidence, status, reversibility, and provenance.
|
|
231
|
-
|
|
232
|
-
Adapters must treat ontology rows as curated local memory, not as raw capture. Do not write full transcripts, raw logs, `.env` contents, auth files, cookies, bearer headers, or secret-bearing payloads into ontology tables. User-authored text must pass through the same redaction boundary used by event and handoff writes before it is promoted into durable memory.
|
|
233
|
-
|
|
234
|
-
The ontology layer is intentionally not a new adapter surface by itself. CLI and MCP commands should continue to call shared core functions, and future concept/decision commands must not create host-specific schemas or side databases.
|
|
235
|
-
|
|
236
|
-
## Retention Scoring Policy (Deterministic Contract)
|
|
237
|
-
|
|
238
|
-
Retention classification is strictly deterministic. There is no ML, embeddings, or hidden model. A pure function maps explicit signals to a numeric score and then to one of five classes.
|
|
239
|
-
|
|
240
|
-
Classes (in ascending durability):
|
|
241
|
-
|
|
242
|
-
- `forget`: score < 30 (or after decay/contradiction). Safe to drop.
|
|
243
|
-
- `temporary`: 30 <= score < 50. Short-term context only.
|
|
244
|
-
- `working`: 50 <= score < 75. Active task/iteration memory.
|
|
245
|
-
- `durable`: 75 <= score < 90. Cross-session value; survives typical decay.
|
|
246
|
-
- `permanent`: score >= 90, or any manually pinned item. Manual pin is a hard override.
|
|
247
|
-
|
|
248
|
-
Manual pin rule (critical): an item with `manualPin: true` is always classified `permanent` regardless of raw score, age, or frequency. Decay jobs and age-based expiration MUST NOT remove or downgrade pinned permanent memory; only explicit supersede/demote/purge may change it.
|
|
249
|
-
|
|
250
|
-
### Score Formula (pure, exposed)
|
|
251
|
-
|
|
252
|
-
Exported constants (see `src/retentionPolicy.ts`):
|
|
253
|
-
|
|
254
|
-
- `RETENTION_CLASSES = ["forget", "temporary", "working", "durable", "permanent"] as const`
|
|
255
|
-
- `RETENTION_THRESHOLDS = { forget: 0, temporary: 30, working: 50, durable: 75, permanent: 90 } as const`
|
|
256
|
-
- `RETENTION_WEIGHTS = { frequency: 4.5, spread: 7, decision: 12, qa: 10, relation: 4, confidence: 10, recencyBase: 18, recencyPerDay: 0.55, agePerDay: 0.12, ageCap: 22, contradiction: 9 } as const`
|
|
257
|
-
|
|
258
|
-
```
|
|
259
|
-
score = clamp(
|
|
260
|
-
frequency * 4.5
|
|
261
|
-
+ spread * 7
|
|
262
|
-
+ decisionWeight * 12
|
|
263
|
-
+ qaWeight * 10
|
|
264
|
-
+ relationDegree * 4
|
|
265
|
-
+ confidence * 10
|
|
266
|
-
+ max(0, 18 - recencyDays * 0.55)
|
|
267
|
-
- min(22, ageDays * 0.12)
|
|
268
|
-
- contradictionCount * 9
|
|
269
|
-
, 0, 110)
|
|
270
|
-
```
|
|
198
|
+
- Export and purge are explicit local data commands; purge requires explicit confirmation.
|
|
271
199
|
|
|
272
|
-
|
|
200
|
+
## Ledger Boundary
|
|
273
201
|
|
|
274
|
-
|
|
202
|
+
OMO Memory's chronological ledger is authoritative: sessions, events, and handoffs record what happened in a project. Adapters should write concise summaries and evidence references to that ledger, then use explicit `recent`, `recall`, `handoff`, `export`, `purge`, and global event import/list operations.
|
|
275
203
|
|
|
276
|
-
|
|
204
|
+
OMO Memory does not provide automatic ontology extraction, graph visualization, retention scoring, durable-memory promotion, or OpenTUI workflows. Do not build adapter behavior that depends on those unavailable surfaces.
|
package/docs/epic-omo-memory.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# Epic: OMO Memory — shared local
|
|
1
|
+
# Epic: OMO Memory — shared local session/work ledger for OMO adapters
|
|
2
2
|
|
|
3
3
|
## Goal
|
|
4
4
|
|
|
5
|
-
Build a host-neutral local memory layer for OMO so lazycodex, omo-on-opencode, lfg, and future adapters can share task context,
|
|
5
|
+
Build a host-neutral local memory layer for OMO so lazycodex, omo-on-opencode, lfg, and future adapters can share task context, recorded decisions, QA evidence, event history, and handoffs across coding-tool sessions.
|
|
6
6
|
|
|
7
7
|
The product ships as both:
|
|
8
8
|
|
|
9
|
-
- CLI: install, inspect, global
|
|
9
|
+
- CLI: install, inspect, global event import, search, export, purge, and handoff operations.
|
|
10
10
|
- MCP server: standard stdio tools for agents and coding tools to explicitly read/write OMO memory.
|
|
11
11
|
|
|
12
12
|
## Problem
|
|
@@ -23,7 +23,7 @@ Codegraph solves code intelligence, not work/session memory. OMO needs a separat
|
|
|
23
23
|
- Local-first privacy: no cloud sync and no secret storage by default
|
|
24
24
|
- Adapter-neutral schema: `host` and `adapter` are metadata, not separate products
|
|
25
25
|
- Shared API: CLI and MCP use the same core DB layer
|
|
26
|
-
-
|
|
26
|
+
- Explicit recall only: no automatic prompt injection, ontology extraction, graph TUI, concept extraction, retention scoring, or durable-memory lifecycle.
|
|
27
27
|
|
|
28
28
|
## In scope
|
|
29
29
|
|
|
@@ -32,6 +32,7 @@ Codegraph solves code intelligence, not work/session memory. OMO needs a separat
|
|
|
32
32
|
- `init`
|
|
33
33
|
- `global scan`
|
|
34
34
|
- `global migrate`
|
|
35
|
+
- `global list`
|
|
35
36
|
- `session start`
|
|
36
37
|
- `session bootstrap`
|
|
37
38
|
- `event record`
|
|
@@ -39,13 +40,9 @@ Codegraph solves code intelligence, not work/session memory. OMO needs a separat
|
|
|
39
40
|
- `recall`
|
|
40
41
|
- `handoff write`
|
|
41
42
|
- `doctor`
|
|
42
|
-
- `
|
|
43
|
-
- `
|
|
44
|
-
|
|
45
|
-
- `ontology demote`
|
|
46
|
-
- `ontology supersede`
|
|
47
|
-
- `ontology recall`
|
|
48
|
-
- `graph tui`
|
|
43
|
+
- `export`
|
|
44
|
+
- `purge`
|
|
45
|
+
|
|
49
46
|
- MCP stdio server with tools:
|
|
50
47
|
- `memory_init`
|
|
51
48
|
- `memory_project_context`
|
|
@@ -58,24 +55,15 @@ Codegraph solves code intelligence, not work/session memory. OMO needs a separat
|
|
|
58
55
|
- `memory_global_scan`
|
|
59
56
|
- `memory_global_migrate`
|
|
60
57
|
- `memory_global_list`
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
|
|
64
|
-
- `memory_ontology_promote`
|
|
65
|
-
- `memory_ontology_demote`
|
|
66
|
-
- `memory_ontology_supersede`
|
|
67
|
-
- `memory_ontology_recall`
|
|
58
|
+
- `memory_export`
|
|
59
|
+
- `memory_purge`
|
|
60
|
+
|
|
68
61
|
- Privacy guardrails:
|
|
69
62
|
- local-only default
|
|
70
63
|
- no API key/token/env capture
|
|
71
64
|
- explicit purge/export commands
|
|
72
65
|
- Adapter integration notes for lazycodex, omo-on-opencode, and lfg
|
|
73
|
-
-
|
|
74
|
-
- `forget`: safe to drop after low score or decay
|
|
75
|
-
- `temporary`: short-term task context
|
|
76
|
-
- `working`: active project memory
|
|
77
|
-
- `durable`: cross-session knowledge
|
|
78
|
-
- `permanent`: manual-pin or high-score knowledge; only explicit demote/supersede/purge changes it
|
|
66
|
+
- Cross-project event import with source provenance through user-selected global SQLite files.
|
|
79
67
|
|
|
80
68
|
## Out of scope
|
|
81
69
|
|
|
@@ -83,7 +71,9 @@ Codegraph solves code intelligence, not work/session memory. OMO needs a separat
|
|
|
83
71
|
- Team sharing by default
|
|
84
72
|
- Full transcript capture by default
|
|
85
73
|
- Vector/embedding search in MVP
|
|
86
|
-
-
|
|
74
|
+
- Ontology/concept extraction
|
|
75
|
+
- Graph UI, graph TUI, and OpenTUI
|
|
76
|
+
- Retention scoring or durable-memory curation lifecycle
|
|
87
77
|
- Automatic prompt injection from the last session
|
|
88
78
|
- Replacing codegraph
|
|
89
79
|
- Host-specific private APIs as required dependencies
|
|
@@ -95,21 +85,22 @@ Codegraph solves code intelligence, not work/session memory. OMO needs a separat
|
|
|
95
85
|
- `omo-memory init` creates or migrates `<project-root>/.omo/memory/state.sqlite`.
|
|
96
86
|
- `omo-memory global scan --root <path>` discovers project-local OMO DBs and reports schema versions.
|
|
97
87
|
- `omo-memory global migrate --root <path> --global-db <path>` copies source DB data into one global SQLite file without mutating source DBs.
|
|
88
|
+
- `omo-memory global list --global-db <path>` lists imported global event history with source provenance.
|
|
98
89
|
- `omo-memory session start --host grok --adapter lfg` records a session for the current project.
|
|
99
90
|
- `omo-memory session bootstrap --host codex --adapter lazycodex` returns a session id and project metadata without injecting recent events.
|
|
100
91
|
- `omo-memory event record --type decision --summary "..."` appends a project/session event.
|
|
101
92
|
- `omo-memory recent` lists recent project events.
|
|
102
93
|
- `omo-memory recall --query "..."` performs explicit query-gated event recall.
|
|
103
94
|
- `omo-memory handoff write --summary-file path.md` stores a handoff summary.
|
|
104
|
-
- `omo-memory
|
|
105
|
-
- `omo-memory
|
|
106
|
-
- `omo-memory
|
|
107
|
-
|
|
95
|
+
- `omo-memory doctor` reports DB path, schema version, and project identity.
|
|
96
|
+
- `omo-memory export` writes explicit local ledger export data.
|
|
97
|
+
- `omo-memory purge --yes` removes local ledger data only after explicit confirmation.
|
|
98
|
+
|
|
108
99
|
|
|
109
100
|
### MCP
|
|
110
101
|
|
|
111
102
|
- `omo-memory mcp` starts a stdio MCP server.
|
|
112
|
-
- MCP tools can initialize the DB, read project context, record events, list/retrieve explicit memory, write handoffs,
|
|
103
|
+
- MCP tools can initialize the DB, read project context, record events, list/retrieve explicit memory, write handoffs, export/purge local data, and scan/import/list global event memory.
|
|
113
104
|
- MCP tool responses are structured JSON text.
|
|
114
105
|
- `memory_bootstrap_session` remains write-only/session-only and does not return `recentEvents`.
|
|
115
106
|
|
|
@@ -119,25 +110,14 @@ Codegraph solves code intelligence, not work/session memory. OMO needs a separat
|
|
|
119
110
|
- DB path can be overridden only by explicit `OMO_MEMORY_DB`.
|
|
120
111
|
- `doctor` reports DB path, schema version, and project identity without leaking sensitive values.
|
|
121
112
|
- Global migration is copy/import only; source project DB mtimes and row counts must remain unchanged.
|
|
122
|
-
-
|
|
113
|
+
- Recall is local-first and explicit; no cloud sync, browser server, raw transcript capture, ontology extraction, graph UI, OpenTUI, retention scoring, durable-memory curation, or embeddings are required.
|
|
123
114
|
|
|
124
115
|
### QA evidence
|
|
125
116
|
|
|
126
|
-
-
|
|
127
|
-
- `
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
- `node dist/cli.js init`
|
|
131
|
-
- `node dist/cli.js global scan --root <tmp-workspace>`
|
|
132
|
-
- `node dist/cli.js global migrate --root <tmp-workspace> --global-db <tmp-global.sqlite>`
|
|
133
|
-
- `node dist/cli.js session start --host grok --adapter lfg`
|
|
134
|
-
- `node dist/cli.js event record --type decision --summary "smoke"`
|
|
135
|
-
- `node dist/cli.js ontology candidates`
|
|
136
|
-
- `node dist/cli.js ontology score`
|
|
137
|
-
- `bun dist/cli.js graph tui --db <tmp-db> --query smoke`
|
|
138
|
-
- `node dist/cli.js recent`
|
|
139
|
-
- MCP smoke: start `node dist/cli.js mcp` and verify `tools/list` includes memory tools.
|
|
140
|
-
- Backfill verifier: synthetic Linaforge/omo-memory/lfg/omo-phone DB replicas plus read-only scan of real local DB paths.
|
|
117
|
+
- Focused CLI smoke covers init, global scan/migrate/list, session start/bootstrap, event record, recent, recall, handoff write, export, and purge.
|
|
118
|
+
- MCP smoke verifies `tools/list` includes the core `memory_*` ledger tools plus `memory_global_scan`, `memory_global_migrate`, and `memory_global_list`, and excludes `memory_ontology_*`.
|
|
119
|
+
- Global import smoke verifies source project DBs are copied/imported with provenance and not mutated.
|
|
120
|
+
- Documentation review verifies removed ontology, graph TUI/OpenTUI, concept extraction, retention scoring, and durable-memory curation surfaces are not documented as shipped features.
|
|
141
121
|
|
|
142
122
|
## Follow-up issue breakdown
|
|
143
123
|
|
|
@@ -147,8 +127,6 @@ Codegraph solves code intelligence, not work/session memory. OMO needs a separat
|
|
|
147
127
|
4. Project identity and git metadata
|
|
148
128
|
5. Privacy/redaction/purge/export
|
|
149
129
|
6. Global SQLite migration and source provenance
|
|
150
|
-
7.
|
|
151
|
-
8.
|
|
152
|
-
9.
|
|
153
|
-
10. Adapter integration docs for lazycodex, omo-on-opencode, and lfg
|
|
154
|
-
11. QA harness for CLI, MCP, global backfill, and TUI smoke tests
|
|
130
|
+
7. Adapter integration docs for lazycodex, omo-on-opencode, and lfg
|
|
131
|
+
8. QA harness for CLI, MCP, and global import smoke tests
|
|
132
|
+
9. Removal verification for ontology, graph TUI/OpenTUI, concept extraction, retention scoring, and durable-memory curation docs
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omo-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Host-neutral local SQLite memory and session ledger for OMO adapters, exposed through CLI and MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
8
8
|
"docs",
|
|
9
|
+
"scripts/omo-memory-user-prompt.mjs",
|
|
9
10
|
"README.md"
|
|
10
11
|
],
|
|
11
12
|
"bin": {
|
|
@@ -32,15 +33,16 @@
|
|
|
32
33
|
"start": "node dist/cli.js",
|
|
33
34
|
"prepack": "npm run build",
|
|
34
35
|
"presmoke": "npm run build",
|
|
35
|
-
"smoke": "npm run smoke:cli && npm run smoke:mcp",
|
|
36
|
+
"smoke": "npm run smoke:cli && npm run smoke:mcp && npm run smoke:hook && npm run smoke:legacy",
|
|
36
37
|
"smoke:cli": "node scripts/smoke-cli.mjs",
|
|
37
38
|
"smoke:mcp": "node scripts/smoke-mcp.mjs",
|
|
39
|
+
"smoke:hook": "node scripts/verify-user-prompt-hook.mjs",
|
|
40
|
+
"smoke:legacy": "node scripts/verify-legacy-schema-compat.mjs",
|
|
38
41
|
"mcp": "node dist/cli.js mcp",
|
|
39
42
|
"issue:epic": "node scripts/create-epic-issue.mjs"
|
|
40
43
|
},
|
|
41
44
|
"dependencies": {
|
|
42
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
|
-
"@opentui/core": "0.4.2",
|
|
44
46
|
"better-sqlite3": "^12.11.1",
|
|
45
47
|
"zod": "^4.4.3"
|
|
46
48
|
},
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
const MAX_SUMMARY_CHARS = 4000;
|
|
6
|
+
|
|
7
|
+
const host = process.env["OMO_MEMORY_HOST"] ?? "unknown";
|
|
8
|
+
const adapter = process.env["OMO_MEMORY_ADAPTER"] ?? "unknown";
|
|
9
|
+
|
|
10
|
+
const input = readStdin();
|
|
11
|
+
const payload = parseJson(input);
|
|
12
|
+
const prompt = extractPrompt(payload);
|
|
13
|
+
|
|
14
|
+
if (prompt === null) process.exit(0);
|
|
15
|
+
|
|
16
|
+
const summary = truncate(prompt.replace(/\s+/g, " ").trim(), MAX_SUMMARY_CHARS);
|
|
17
|
+
if (summary.length === 0) process.exit(0);
|
|
18
|
+
|
|
19
|
+
const hookSessionId = readString(payload, "sessionId") ?? readString(payload, "session_id");
|
|
20
|
+
const workspaceRoot = readString(payload, "workspaceRoot") ?? readString(payload, "cwd");
|
|
21
|
+
const metadata = {
|
|
22
|
+
source: "hook",
|
|
23
|
+
hookEventName: readString(payload, "hookEventName") ?? process.env["GROK_HOOK_EVENT"] ?? process.env["CODEX_HOOK_EVENT"] ?? "UserPromptSubmit",
|
|
24
|
+
host,
|
|
25
|
+
adapter,
|
|
26
|
+
...(hookSessionId === null ? {} : { hookSessionId }),
|
|
27
|
+
...(workspaceRoot === null ? {} : { workspaceRoot }),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const args = ["event", "record", "--type", "user_prompt", "--summary", summary, "--payload-json", JSON.stringify(metadata)];
|
|
31
|
+
|
|
32
|
+
const result = runOmoMemory(args) ?? runNpx(args);
|
|
33
|
+
if (result === undefined || result.status !== 0) process.exit(0);
|
|
34
|
+
|
|
35
|
+
function readStdin() {
|
|
36
|
+
try {
|
|
37
|
+
return readFileSync(0, "utf8");
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof Error) process.exit(0);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseJson(raw) {
|
|
45
|
+
if (raw.trim().length === 0) return {};
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
return isRecord(parsed) ? parsed : {};
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error instanceof SyntaxError) return {};
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function extractPrompt(value) {
|
|
56
|
+
if (!isRecord(value)) return null;
|
|
57
|
+
for (const key of ["prompt", "userPrompt", "user_prompt", "message", "text", "input"]) {
|
|
58
|
+
const direct = readString(value, key);
|
|
59
|
+
if (direct !== null) return direct;
|
|
60
|
+
}
|
|
61
|
+
const nestedPrompt =
|
|
62
|
+
readNestedString(value, ["toolInput", "prompt"]) ?? readNestedString(value, ["payload", "prompt"]) ?? readNestedString(value, ["data", "prompt"]);
|
|
63
|
+
if (nestedPrompt !== null) return nestedPrompt;
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readNestedString(value, path) {
|
|
68
|
+
let cursor = value;
|
|
69
|
+
for (const key of path) {
|
|
70
|
+
if (!isRecord(cursor)) return null;
|
|
71
|
+
cursor = cursor[key];
|
|
72
|
+
}
|
|
73
|
+
return typeof cursor === "string" ? cursor : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readString(value, key) {
|
|
77
|
+
const item = value[key];
|
|
78
|
+
return typeof item === "string" ? item : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function truncate(value, maxLength) {
|
|
82
|
+
if (value.length <= maxLength) return value;
|
|
83
|
+
return `${value.slice(0, maxLength - 15)} [TRUNCATED]`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function runOmoMemory(args) {
|
|
87
|
+
const command = process.env["OMO_MEMORY_CLI"] ?? "omo-memory";
|
|
88
|
+
return run(command, args);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function runNpx(args) {
|
|
92
|
+
return run("npx", ["-y", "omo-memory", ...args]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function run(command, args) {
|
|
96
|
+
const result = spawnSync(command, args, {
|
|
97
|
+
encoding: "utf8",
|
|
98
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
99
|
+
timeout: 5000,
|
|
100
|
+
});
|
|
101
|
+
if (result.error?.code === "ENOENT") return undefined;
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isRecord(value) {
|
|
106
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
107
|
+
}
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import { recordMemoryReference, upsertConcept } from "./ontologyCore.js";
|
|
2
|
-
import { redactSecrets } from "./privacy.js";
|
|
3
|
-
// Comprehensive generic/hook stopwords. Keep domain terms out.
|
|
4
|
-
const STOPWORDS = new Set([
|
|
5
|
-
// from recall + hook words
|
|
6
|
-
"action",
|
|
7
|
-
"asked",
|
|
8
|
-
"current",
|
|
9
|
-
"help",
|
|
10
|
-
"memory",
|
|
11
|
-
"need",
|
|
12
|
-
"needs",
|
|
13
|
-
"omo",
|
|
14
|
-
"please",
|
|
15
|
-
"prompt",
|
|
16
|
-
"request",
|
|
17
|
-
"requested",
|
|
18
|
-
"session",
|
|
19
|
-
"user",
|
|
20
|
-
"want",
|
|
21
|
-
"wants",
|
|
22
|
-
"work",
|
|
23
|
-
"working",
|
|
24
|
-
// common English + dev noise
|
|
25
|
-
"the",
|
|
26
|
-
"and",
|
|
27
|
-
"for",
|
|
28
|
-
"with",
|
|
29
|
-
"from",
|
|
30
|
-
"into",
|
|
31
|
-
"about",
|
|
32
|
-
"this",
|
|
33
|
-
"that",
|
|
34
|
-
"these",
|
|
35
|
-
"those",
|
|
36
|
-
"start",
|
|
37
|
-
"now",
|
|
38
|
-
"test",
|
|
39
|
-
"run",
|
|
40
|
-
"check",
|
|
41
|
-
"fix",
|
|
42
|
-
"add",
|
|
43
|
-
"change",
|
|
44
|
-
"update",
|
|
45
|
-
"get",
|
|
46
|
-
"set",
|
|
47
|
-
"make",
|
|
48
|
-
"create",
|
|
49
|
-
"delete",
|
|
50
|
-
"remove",
|
|
51
|
-
"show",
|
|
52
|
-
"list",
|
|
53
|
-
"use",
|
|
54
|
-
"using",
|
|
55
|
-
"via",
|
|
56
|
-
"task",
|
|
57
|
-
"todo",
|
|
58
|
-
"step",
|
|
59
|
-
"item",
|
|
60
|
-
"thing",
|
|
61
|
-
"stuff",
|
|
62
|
-
"new",
|
|
63
|
-
"old",
|
|
64
|
-
"one",
|
|
65
|
-
"two",
|
|
66
|
-
"three",
|
|
67
|
-
"four",
|
|
68
|
-
"five",
|
|
69
|
-
"six",
|
|
70
|
-
"seven",
|
|
71
|
-
"eight",
|
|
72
|
-
"nine",
|
|
73
|
-
"ten",
|
|
74
|
-
"please",
|
|
75
|
-
"thanks",
|
|
76
|
-
"thank",
|
|
77
|
-
"also",
|
|
78
|
-
"just",
|
|
79
|
-
"only",
|
|
80
|
-
"like",
|
|
81
|
-
"such",
|
|
82
|
-
"very",
|
|
83
|
-
"really",
|
|
84
|
-
"should",
|
|
85
|
-
"could",
|
|
86
|
-
"would",
|
|
87
|
-
"will",
|
|
88
|
-
"can",
|
|
89
|
-
"may",
|
|
90
|
-
"must",
|
|
91
|
-
"have",
|
|
92
|
-
"has",
|
|
93
|
-
"had",
|
|
94
|
-
"been",
|
|
95
|
-
"being",
|
|
96
|
-
"are",
|
|
97
|
-
"was",
|
|
98
|
-
"were",
|
|
99
|
-
"is",
|
|
100
|
-
"be",
|
|
101
|
-
"to",
|
|
102
|
-
"of",
|
|
103
|
-
"in",
|
|
104
|
-
"on",
|
|
105
|
-
"at",
|
|
106
|
-
"by",
|
|
107
|
-
"as",
|
|
108
|
-
"it",
|
|
109
|
-
"its",
|
|
110
|
-
"if",
|
|
111
|
-
"or",
|
|
112
|
-
"and",
|
|
113
|
-
"but",
|
|
114
|
-
"not",
|
|
115
|
-
"no",
|
|
116
|
-
"yes",
|
|
117
|
-
"all",
|
|
118
|
-
"any",
|
|
119
|
-
"each",
|
|
120
|
-
"few",
|
|
121
|
-
"more",
|
|
122
|
-
"most",
|
|
123
|
-
"other",
|
|
124
|
-
"some",
|
|
125
|
-
"such",
|
|
126
|
-
"than",
|
|
127
|
-
"too",
|
|
128
|
-
"very",
|
|
129
|
-
]);
|
|
130
|
-
function normalizeLabel(raw) {
|
|
131
|
-
return raw.trim().toLowerCase();
|
|
132
|
-
}
|
|
133
|
-
function tokenize(input) {
|
|
134
|
-
// Preserve hyphenated compounds (local-first), keep alnum _ -
|
|
135
|
-
const cleaned = input
|
|
136
|
-
.toLowerCase()
|
|
137
|
-
.replace(/[^a-z0-9_\-\s]/g, " ")
|
|
138
|
-
.replace(/\s+/g, " ")
|
|
139
|
-
.trim();
|
|
140
|
-
if (!cleaned)
|
|
141
|
-
return [];
|
|
142
|
-
return cleaned.split(" ").filter((t) => t.length >= 3);
|
|
143
|
-
}
|
|
144
|
-
export function extractConceptCandidates(summary, _eventType) {
|
|
145
|
-
if (!summary || typeof summary !== "string")
|
|
146
|
-
return [];
|
|
147
|
-
// Never operate on large blobs: caller must pass short summary only.
|
|
148
|
-
const redacted = redactSecrets(summary);
|
|
149
|
-
const tokens = tokenize(redacted);
|
|
150
|
-
const seen = new Set();
|
|
151
|
-
const out = [];
|
|
152
|
-
for (const t of tokens) {
|
|
153
|
-
if (STOPWORDS.has(t))
|
|
154
|
-
continue;
|
|
155
|
-
const norm = normalizeLabel(t);
|
|
156
|
-
if (norm.length < 3)
|
|
157
|
-
continue;
|
|
158
|
-
if (seen.has(norm))
|
|
159
|
-
continue;
|
|
160
|
-
seen.add(norm);
|
|
161
|
-
out.push(norm);
|
|
162
|
-
}
|
|
163
|
-
return out;
|
|
164
|
-
}
|
|
165
|
-
export function applyConceptExtraction(dbPath, project, sourceEventId, summary, eventType) {
|
|
166
|
-
const candidates = extractConceptCandidates(summary, eventType);
|
|
167
|
-
const concepts = [];
|
|
168
|
-
const references = [];
|
|
169
|
-
for (const label of candidates) {
|
|
170
|
-
// upsertConcept will be made idempotent + bump ref_count by label
|
|
171
|
-
const concept = upsertConcept(dbPath, project, {
|
|
172
|
-
kind: "term",
|
|
173
|
-
label,
|
|
174
|
-
// score/retention left to scorer (Todo 6); default working
|
|
175
|
-
});
|
|
176
|
-
concepts.push(concept);
|
|
177
|
-
const ref = recordMemoryReference(dbPath, project, {
|
|
178
|
-
sourceType: "event",
|
|
179
|
-
sourceId: sourceEventId,
|
|
180
|
-
targetType: "concept",
|
|
181
|
-
targetId: concept.id,
|
|
182
|
-
refKind: "mentions",
|
|
183
|
-
weight: 1,
|
|
184
|
-
});
|
|
185
|
-
references.push(ref);
|
|
186
|
-
}
|
|
187
|
-
return { concepts, references };
|
|
188
|
-
}
|