opencode-omni-memory-plugin 2.0.0

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/install.sh ADDED
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPO_URL="https://github.com/caoool/opencode-omni-memory-plugin.git"
5
+ REF="${OPENCODE_OMNI_MEMORY_REF:-main}"
6
+ CONFIG_DIR="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}"
7
+ DATA_ROOT="${OPENCODE_OMNI_MEMORY_HOME:-${XDG_DATA_HOME:-$HOME/.local/share}/opencode-omni-memory-plugin}"
8
+ MANAGED_REPO="$DATA_ROOT/repo"
9
+ INSTALLED_MANIFEST="$DATA_ROOT/installed-files.txt"
10
+ INSTALLED_VERSION="$DATA_ROOT/installed-version"
11
+ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd -P || true)"
12
+ ACTION="${1:-install}"
13
+ TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
14
+ BACKUP_ROOT="$DATA_ROOT/backups/$TIMESTAMP"
15
+ SOURCE_DIR=""
16
+ CONFIG_FILE=""
17
+
18
+ usage() {
19
+ cat <<'EOF'
20
+ Usage: install.sh [install|update|status|uninstall|help]
21
+
22
+ Commands:
23
+ install Install the skill, commands, instructions, and missing memory files.
24
+ update Pull the configured Git ref and reinstall managed files.
25
+ status Report installed version and integration health.
26
+ uninstall Remove managed skill/command/instruction wiring; preserve memory data.
27
+ help Show this message.
28
+
29
+ Environment:
30
+ OPENCODE_CONFIG_DIR Override ~/.config/opencode.
31
+ OPENCODE_OMNI_MEMORY_HOME Override updater data/checkout location.
32
+ OPENCODE_OMNI_MEMORY_REF Git branch or ref to install (default: main).
33
+ OPENCODE_OMNI_MEMORY_SOURCE Use a local source checkout (development/testing).
34
+ OPENCODE_MEMORY_HOSTNAME Override the generated host-memory filename.
35
+ EOF
36
+ }
37
+
38
+ log() {
39
+ printf '[opencode-omni-memory-plugin] %s\n' "$*"
40
+ }
41
+
42
+ die() {
43
+ printf '[opencode-omni-memory-plugin] ERROR: %s\n' "$*" >&2
44
+ exit 1
45
+ }
46
+
47
+ need() {
48
+ command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
49
+ }
50
+
51
+ prepare_source() {
52
+ need git
53
+ need node
54
+
55
+ if [[ -n "${OPENCODE_OMNI_MEMORY_SOURCE:-}" ]]; then
56
+ SOURCE_DIR="$OPENCODE_OMNI_MEMORY_SOURCE"
57
+ elif [[ -n "$SCRIPT_DIR" && -f "$SCRIPT_DIR/VERSION" && -d "$SCRIPT_DIR/skills/omni-memory" ]]; then
58
+ SOURCE_DIR="$SCRIPT_DIR"
59
+ else
60
+ SOURCE_DIR="$MANAGED_REPO"
61
+ if [[ ! -d "$SOURCE_DIR/.git" ]]; then
62
+ mkdir -p "$DATA_ROOT"
63
+ log "Cloning $REPO_URL at $REF"
64
+ git clone --depth 1 --branch "$REF" "$REPO_URL" "$SOURCE_DIR"
65
+ fi
66
+ fi
67
+
68
+ [[ -f "$SOURCE_DIR/VERSION" ]] || die "Invalid source: VERSION is missing from $SOURCE_DIR"
69
+ [[ -f "$SOURCE_DIR/manifest.txt" ]] || die "Invalid source: manifest.txt is missing from $SOURCE_DIR"
70
+ [[ -d "$SOURCE_DIR/skills/omni-memory" ]] || die "Invalid source: omni-memory skill is missing"
71
+
72
+ if [[ "$ACTION" == "update" && -z "${OPENCODE_OMNI_MEMORY_SOURCE:-}" ]]; then
73
+ [[ -d "$SOURCE_DIR/.git" ]] || die "Update requires a Git checkout: $SOURCE_DIR"
74
+ log "Updating $SOURCE_DIR from $REF"
75
+ git -C "$SOURCE_DIR" fetch origin "$REF"
76
+ git -C "$SOURCE_DIR" merge --ff-only FETCH_HEAD
77
+ fi
78
+ }
79
+
80
+ choose_config_file() {
81
+ mkdir -p "$CONFIG_DIR"
82
+ if [[ -e "$CONFIG_DIR/opencode.json" ]]; then
83
+ CONFIG_FILE="$CONFIG_DIR/opencode.json"
84
+ elif [[ -e "$CONFIG_DIR/opencode.jsonc" ]]; then
85
+ CONFIG_FILE="$CONFIG_DIR/opencode.jsonc"
86
+ else
87
+ CONFIG_FILE="$CONFIG_DIR/opencode.json"
88
+ fi
89
+
90
+ if [[ -e "$CONFIG_FILE" ]]; then
91
+ if ! node -e 'JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"))' "$CONFIG_FILE" >/dev/null 2>&1; then
92
+ die "$CONFIG_FILE contains JSONC comments or otherwise is not strict JSON. Add the three documented instructions manually, or provide a strict opencode.json, then rerun."
93
+ fi
94
+ fi
95
+ }
96
+
97
+ global_memory_instruction() {
98
+ local path="$CONFIG_DIR/memory/GLOBAL.md"
99
+ case "$path" in
100
+ "$HOME"/*) printf '~/%s' "${path#"$HOME"/}" ;;
101
+ *) printf '%s' "$path" ;;
102
+ esac
103
+ }
104
+
105
+ backup_path() {
106
+ local path="$1"
107
+ [[ -e "$path" || -L "$path" ]] || return 0
108
+ local rel="${path#"$CONFIG_DIR"/}"
109
+ local target="$BACKUP_ROOT/$rel"
110
+ mkdir -p "$(dirname -- "$target")"
111
+ cp -R -- "$path" "$target"
112
+ log "Backed up $path to $target"
113
+ }
114
+
115
+ paths_differ() {
116
+ local source="$1"
117
+ local target="$2"
118
+ if [[ ! -e "$target" && ! -L "$target" ]]; then
119
+ return 0
120
+ fi
121
+ if [[ -d "$source" ]]; then
122
+ ! diff -qr -- "$source" "$target" >/dev/null 2>&1
123
+ else
124
+ ! cmp -s -- "$source" "$target"
125
+ fi
126
+ }
127
+
128
+ install_managed_path() {
129
+ local rel="$1"
130
+ local source="$SOURCE_DIR/$rel"
131
+ local target="$CONFIG_DIR/$rel"
132
+ [[ -e "$source" ]] || die "Manifest source is missing: $source"
133
+
134
+ if ! paths_differ "$source" "$target"; then
135
+ return 0
136
+ fi
137
+
138
+ backup_path "$target"
139
+ mkdir -p "$(dirname -- "$target")"
140
+ if [[ -d "$source" ]]; then
141
+ local staging="${target}.tmp.$$"
142
+ rm -rf -- "$staging"
143
+ cp -R -- "$source" "$staging"
144
+ rm -rf -- "$target"
145
+ mv -- "$staging" "$target"
146
+ else
147
+ cp -- "$source" "$target"
148
+ fi
149
+ log "Installed $rel"
150
+ }
151
+
152
+ install_default_file() {
153
+ local source="$1"
154
+ local target="$2"
155
+ if [[ -e "$target" ]]; then
156
+ return 0
157
+ fi
158
+ mkdir -p "$(dirname -- "$target")"
159
+ cp -- "$source" "$target"
160
+ log "Created $target"
161
+ }
162
+
163
+ install_host_file() {
164
+ local hostname="${OPENCODE_MEMORY_HOSTNAME:-${HOSTNAME:-$(uname -n)}}"
165
+ local target="$CONFIG_DIR/memory/hosts/$hostname.md"
166
+ [[ -e "$target" ]] && return 0
167
+ mkdir -p "$(dirname -- "$target")"
168
+ while IFS= read -r line || [[ -n "$line" ]]; do
169
+ printf '%s\n' "${line//__HOSTNAME__/$hostname}"
170
+ done < "$SOURCE_DIR/defaults/memory/HOST.md" > "$target"
171
+ log "Created $target"
172
+ }
173
+
174
+ render_config() {
175
+ local mode="$1"
176
+ local output="$2"
177
+ local global_instruction
178
+ global_instruction="$(global_memory_instruction)"
179
+ node - "$CONFIG_FILE" "$output" "$mode" "$global_instruction" <<'NODE'
180
+ const fs = require("fs")
181
+ const [input, output, mode, globalInstruction] = process.argv.slice(2)
182
+ const wanted = [
183
+ globalInstruction,
184
+ ".opencode/project/MEMORY.md",
185
+ ".opencode/project/HANDOFF.md",
186
+ ]
187
+ const original = fs.existsSync(input) ? fs.readFileSync(input, "utf8") : ""
188
+ let config = { $schema: "https://opencode.ai/config.json" }
189
+ if (original) config = JSON.parse(original)
190
+ const before = JSON.stringify(config)
191
+ const current = Array.isArray(config.instructions) ? config.instructions : []
192
+ if (mode === "install") {
193
+ config.instructions = [...new Set([...current, ...wanted])]
194
+ } else {
195
+ const removable = new Set([...wanted, "~/.config/opencode/memory/GLOBAL.md"])
196
+ const next = current.filter((item) => !removable.has(item))
197
+ if (next.length) config.instructions = next
198
+ else delete config.instructions
199
+ }
200
+ const rendered = JSON.stringify(config) === before && original
201
+ ? original
202
+ : JSON.stringify(config, null, 2) + "\n"
203
+ fs.writeFileSync(output, rendered)
204
+ NODE
205
+ }
206
+
207
+ patch_config() {
208
+ local mode="$1"
209
+ choose_config_file
210
+ local output="${CONFIG_FILE}.tmp.$$"
211
+ render_config "$mode" "$output"
212
+ if [[ -e "$CONFIG_FILE" ]] && cmp -s -- "$CONFIG_FILE" "$output"; then
213
+ rm -f -- "$output"
214
+ return 0
215
+ fi
216
+ backup_path "$CONFIG_FILE"
217
+ mv -- "$output" "$CONFIG_FILE"
218
+ log "Updated $CONFIG_FILE"
219
+ }
220
+
221
+ render_agents() {
222
+ local mode="$1"
223
+ local agents="$CONFIG_DIR/AGENTS.md"
224
+ local output="${agents}.tmp.$$"
225
+ local snippet="$SOURCE_DIR/snippets/AGENTS.md"
226
+ node - "$agents" "$snippet" "$output" "$mode" <<'NODE'
227
+ const fs = require("fs")
228
+ const [agentsPath, snippetPath, output, mode] = process.argv.slice(2)
229
+ let text = fs.existsSync(agentsPath) ? fs.readFileSync(agentsPath, "utf8") : ""
230
+ // Current markers, plus the legacy pre-rename markers so installs migrate
231
+ // the old opencode-markdown-memory block in place instead of duplicating it.
232
+ const markerPairs = [
233
+ ["<!-- opencode-omni-memory-plugin:start -->", "<!-- opencode-omni-memory-plugin:end -->"],
234
+ ["<!-- opencode-markdown-memory:start -->", "<!-- opencode-markdown-memory:end -->"],
235
+ ]
236
+ let start = markerPairs[0][0]
237
+ let end = markerPairs[0][1]
238
+ let startIndex = -1
239
+ let endIndex = -1
240
+ for (const [s, e] of markerPairs) {
241
+ const si = text.indexOf(s)
242
+ const ei = text.indexOf(e)
243
+ if ((si === -1) !== (ei === -1) || (si !== -1 && ei < si)) {
244
+ throw new Error("AGENTS.md contains an incomplete managed memory block (" + s + ")")
245
+ }
246
+ if (si !== -1) {
247
+ start = s
248
+ end = e
249
+ startIndex = si
250
+ endIndex = ei
251
+ break
252
+ }
253
+ }
254
+ if (mode === "install") {
255
+ const snippet = fs.readFileSync(snippetPath, "utf8").trim()
256
+ if (startIndex !== -1) {
257
+ const after = endIndex + end.length
258
+ const existing = text.slice(startIndex, after).trim()
259
+ if (existing === snippet) {
260
+ fs.writeFileSync(output, text)
261
+ process.exit(0)
262
+ }
263
+ text = text.slice(0, startIndex) + snippet + text.slice(after)
264
+ } else {
265
+ text = text.trimEnd()
266
+ text = (text ? text + "\n\n" : "") + snippet + "\n"
267
+ }
268
+ } else {
269
+ if (startIndex !== -1) {
270
+ const after = endIndex + end.length
271
+ text = text.slice(0, startIndex).trimEnd() + text.slice(after)
272
+ text = text.replace(/^\s+/, "")
273
+ }
274
+ text = text.trim()
275
+ text = text ? text + "\n" : ""
276
+ }
277
+ fs.writeFileSync(output, text)
278
+ NODE
279
+
280
+ if [[ -e "$agents" ]] && cmp -s -- "$agents" "$output"; then
281
+ rm -f -- "$output"
282
+ return 0
283
+ fi
284
+ backup_path "$agents"
285
+ mv -- "$output" "$agents"
286
+ log "Updated $agents"
287
+ }
288
+
289
+ remove_legacy_paths() {
290
+ # Pre-rename installs used skills/markdown-memory; leaving it alongside
291
+ # skills/omni-memory would register two overlapping skills.
292
+ local legacy="$CONFIG_DIR/skills/markdown-memory"
293
+ if [[ -e "$legacy" ]]; then
294
+ backup_path "$legacy"
295
+ rm -rf -- "$legacy"
296
+ log "Removed legacy skills/markdown-memory (backed up)"
297
+ fi
298
+ }
299
+
300
+ install_payload() {
301
+ choose_config_file
302
+ mkdir -p "$DATA_ROOT"
303
+
304
+ while IFS= read -r rel || [[ -n "$rel" ]]; do
305
+ [[ -z "$rel" || "$rel" == \#* ]] && continue
306
+ install_managed_path "$rel"
307
+ done < "$SOURCE_DIR/manifest.txt"
308
+
309
+ remove_legacy_paths
310
+
311
+ install_default_file "$SOURCE_DIR/defaults/memory/GLOBAL.md" "$CONFIG_DIR/memory/GLOBAL.md"
312
+ install_default_file "$SOURCE_DIR/defaults/memory/HANDOFF.md" "$CONFIG_DIR/memory/HANDOFF.md"
313
+ install_default_file "$SOURCE_DIR/defaults/memory/archive/README.md" "$CONFIG_DIR/memory/archive/README.md"
314
+ install_host_file
315
+ patch_config install
316
+ render_agents install
317
+
318
+ cp -- "$SOURCE_DIR/manifest.txt" "$INSTALLED_MANIFEST"
319
+ cp -- "$SOURCE_DIR/VERSION" "$INSTALLED_VERSION"
320
+ log "Installed version $(<"$SOURCE_DIR/VERSION")"
321
+ log "Restart OpenCode to load configuration-time changes."
322
+ }
323
+
324
+ remove_managed_paths() {
325
+ local manifest="$INSTALLED_MANIFEST"
326
+ if [[ ! -f "$manifest" ]]; then
327
+ [[ -n "$SOURCE_DIR" && -f "$SOURCE_DIR/manifest.txt" ]] || die "No installed manifest found"
328
+ manifest="$SOURCE_DIR/manifest.txt"
329
+ fi
330
+
331
+ while IFS= read -r rel || [[ -n "$rel" ]]; do
332
+ [[ -z "$rel" || "$rel" == \#* ]] && continue
333
+ local target="$CONFIG_DIR/$rel"
334
+ case "$target" in
335
+ "$CONFIG_DIR"/*) ;;
336
+ *) die "Unsafe uninstall path: $target" ;;
337
+ esac
338
+ if [[ -d "$target" ]]; then rm -rf -- "$target"; else rm -f -- "$target"; fi
339
+ log "Removed $rel"
340
+ done < "$manifest"
341
+ }
342
+
343
+ status() {
344
+ local version="not installed"
345
+ [[ -f "$INSTALLED_VERSION" ]] && version="$(<"$INSTALLED_VERSION")"
346
+ printf 'Version: %s\n' "$version"
347
+ printf 'Config: %s\n' "$CONFIG_DIR"
348
+
349
+ local missing=0
350
+ local manifest="$INSTALLED_MANIFEST"
351
+ if [[ ! -f "$manifest" && -n "$SCRIPT_DIR" && -f "$SCRIPT_DIR/manifest.txt" ]]; then
352
+ manifest="$SCRIPT_DIR/manifest.txt"
353
+ fi
354
+ if [[ -f "$manifest" ]]; then
355
+ while IFS= read -r rel || [[ -n "$rel" ]]; do
356
+ [[ -z "$rel" || "$rel" == \#* ]] && continue
357
+ if [[ ! -e "$CONFIG_DIR/$rel" ]]; then
358
+ printf 'Missing: %s\n' "$rel"
359
+ missing=$((missing + 1))
360
+ fi
361
+ done < "$manifest"
362
+ else
363
+ printf 'Missing: installation manifest\n'
364
+ missing=$((missing + 1))
365
+ fi
366
+
367
+ [[ -f "$CONFIG_DIR/memory/GLOBAL.md" ]] || { printf 'Missing: memory/GLOBAL.md\n'; missing=$((missing + 1)); }
368
+ [[ -f "$CONFIG_DIR/memory/HANDOFF.md" ]] || { printf 'Missing: memory/HANDOFF.md\n'; missing=$((missing + 1)); }
369
+
370
+ local config_path=""
371
+ if [[ -f "$CONFIG_DIR/opencode.json" ]]; then
372
+ config_path="$CONFIG_DIR/opencode.json"
373
+ elif [[ -f "$CONFIG_DIR/opencode.jsonc" ]]; then
374
+ config_path="$CONFIG_DIR/opencode.jsonc"
375
+ fi
376
+ local global_instruction
377
+ global_instruction="$(global_memory_instruction)"
378
+ if [[ -n "$config_path" ]] && node -e '
379
+ const fs = require("fs")
380
+ const [path, globalInstruction] = process.argv.slice(1)
381
+ const config = JSON.parse(fs.readFileSync(path, "utf8"))
382
+ const instructions = Array.isArray(config.instructions) ? config.instructions : []
383
+ const wanted = [globalInstruction, ".opencode/project/MEMORY.md", ".opencode/project/HANDOFF.md"]
384
+ if (!wanted.every((item) => instructions.includes(item))) process.exit(1)
385
+ ' "$config_path" "$global_instruction" >/dev/null 2>&1; then
386
+ printf 'Config instructions: present\n'
387
+ else
388
+ printf 'Config instructions: missing or unreadable\n'
389
+ missing=$((missing + 1))
390
+ fi
391
+ if [[ -f "$CONFIG_DIR/AGENTS.md" ]] && grep -q '<!-- opencode-omni-memory-plugin:start -->' "$CONFIG_DIR/AGENTS.md"; then
392
+ printf 'AGENTS.md integration: present\n'
393
+ else
394
+ printf 'AGENTS.md integration: missing\n'
395
+ missing=$((missing + 1))
396
+ fi
397
+
398
+ if (( missing == 0 )); then
399
+ printf 'Status: healthy\n'
400
+ else
401
+ printf 'Status: incomplete (%d issue(s))\n' "$missing"
402
+ return 1
403
+ fi
404
+ }
405
+
406
+ uninstall() {
407
+ need node
408
+ choose_config_file
409
+ remove_managed_paths
410
+ patch_config uninstall
411
+ if [[ -n "$SOURCE_DIR" && -f "$SOURCE_DIR/snippets/AGENTS.md" ]]; then
412
+ render_agents uninstall
413
+ elif [[ -f "$CONFIG_DIR/AGENTS.md" ]]; then
414
+ local placeholder="$DATA_ROOT/uninstall-source/snippets"
415
+ mkdir -p "$placeholder"
416
+ : > "$placeholder/AGENTS.md"
417
+ SOURCE_DIR="$DATA_ROOT/uninstall-source"
418
+ render_agents uninstall
419
+ rm -rf -- "$DATA_ROOT/uninstall-source"
420
+ fi
421
+ rm -f -- "$INSTALLED_MANIFEST" "$INSTALLED_VERSION"
422
+ log "Uninstalled managed skill, commands, and instruction wiring."
423
+ log "Preserved memory data under $CONFIG_DIR/memory and project .opencode/project directories."
424
+ log "Restart OpenCode to unload configuration-time changes."
425
+ }
426
+
427
+ case "$ACTION" in
428
+ install)
429
+ prepare_source
430
+ install_payload
431
+ ;;
432
+ update)
433
+ prepare_source
434
+ install_payload
435
+ ;;
436
+ status)
437
+ status
438
+ ;;
439
+ uninstall)
440
+ if [[ -n "${OPENCODE_OMNI_MEMORY_SOURCE:-}" ]]; then SOURCE_DIR="$OPENCODE_OMNI_MEMORY_SOURCE"; fi
441
+ if [[ -z "$SOURCE_DIR" && -n "$SCRIPT_DIR" && -f "$SCRIPT_DIR/snippets/AGENTS.md" ]]; then SOURCE_DIR="$SCRIPT_DIR"; fi
442
+ uninstall
443
+ ;;
444
+ help|-h|--help)
445
+ usage
446
+ ;;
447
+ *)
448
+ usage >&2
449
+ die "Unknown command: $ACTION"
450
+ ;;
451
+ esac
package/manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ skills/omni-memory
2
+ plugins/omni-memory.js
3
+ commands/project-init.md
4
+ commands/remember.md
5
+ commands/recall.md
6
+ commands/forget.md
7
+ commands/handoff.md
8
+ commands/project-status.md
9
+ commands/memory-audit.md
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "opencode-omni-memory-plugin",
3
+ "version": "2.0.0",
4
+ "description": "Native, local, Git-friendly long-term memory and project continuity for OpenCode: a global skill, companion plugin, slash commands, and instruction wiring.",
5
+ "keywords": [
6
+ "opencode",
7
+ "opencode-plugin",
8
+ "memory",
9
+ "long-term-memory",
10
+ "agent",
11
+ "ai",
12
+ "skill"
13
+ ],
14
+ "homepage": "https://github.com/caoool/opencode-omni-memory-plugin#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/caoool/opencode-omni-memory-plugin/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/caoool/opencode-omni-memory-plugin.git"
21
+ },
22
+ "license": "MIT",
23
+ "author": "caoool",
24
+ "type": "commonjs",
25
+ "bin": {
26
+ "opencode-omni-memory-plugin": "bin/cli.js"
27
+ },
28
+ "files": [
29
+ "bin/",
30
+ "install.sh",
31
+ "manifest.txt",
32
+ "VERSION",
33
+ "skills/",
34
+ "plugins/",
35
+ "commands/",
36
+ "defaults/",
37
+ "snippets/"
38
+ ],
39
+ "scripts": {
40
+ "test": "bash tests/install-smoke.sh"
41
+ },
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public",
47
+ "provenance": true
48
+ }
49
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * omni-memory plugin for OpenCode.
3
+ *
4
+ * Companion to the `omni-memory` skill (skills/omni-memory/SKILL.md). Two hooks:
5
+ *
6
+ * 1. `experimental.chat.messages.transform` — prepends a compact orientation
7
+ * bootstrap to the first user message of each session so memory behavior,
8
+ * self-evolution capture, and the single-writer rule are unconditionally in
9
+ * context from turn one (the skill itself stays lazily loaded).
10
+ * 2. `experimental.session.compacting` — instructs the compaction summarizer to
11
+ * carry forward memory pointers and any un-persisted decisions, findings,
12
+ * blockers, and self-evolution lessons, so continuation summaries do not
13
+ * silently drop durable state.
14
+ *
15
+ * The bootstrap is embedded as a constant: no per-step fs reads, and the hook
16
+ * fires on every agent step, so idempotence is guarded by BOOTSTRAP_MARK.
17
+ */
18
+
19
+ const BOOTSTRAP_MARK = "<omni-memory:bootstrap>";
20
+
21
+ const BOOTSTRAP = `${BOOTSTRAP_MARK}
22
+ You have persistent Markdown memory (omni-memory). This bootstrap is already loaded; do not re-load it as a skill for orientation alone.
23
+
24
+ Auto-loaded every turn via instructions: global memory (~/.config/opencode/memory/GLOBAL.md), project memory (.opencode/project/MEMORY.md), and project handoff (.opencode/project/HANDOFF.md). Detailed state lives in .opencode/project/ (INDEX, CHARTER, SPEC, ROADMAP, PROGRESS, FINDINGS, DECISIONS, sessions/, archive/) — retrieve on demand.
25
+
26
+ Rules that hold for every substantive task (implementation, debugging, refactor, planning, review, memory commands):
27
+ 1. Orient silently before acting: use the auto-loaded memory, read INDEX/CHARTER when present, and compare the handoff against the real repository state. Verified current state beats remembered state. Load the omni-memory skill for full procedure when work is substantive; skip orientation for trivial or scratch work.
28
+ 2. Update state at meaningful transitions (material decision, milestone change, new blocker, verified finding, user correction) — not after every edit, and never when nothing durable was learned.
29
+ 3. Self-evolution is automatic: capture explicit user corrections and repeated friction as one consolidated entry in the narrowest correct scope, at the moment they occur. When a lesson is really a standing behavior rule, promote it into the governing configuration (AGENTS.md section, agent, skill, or command) and shrink the memory entry to a pointer. Rewrite or merge instead of appending; fix stale or contradictory memory in place immediately.
30
+ 4. Single writer: only the primary agent writes memory/ or .opencode/project/. If you are a subagent, treat memory as read-only and return candidate lessons, decisions, and findings in your report instead of writing them.
31
+ 5. Never retain secrets, credentials, raw logs, transcripts, temporary status, or unverified assumptions.
32
+
33
+ Red flags — if you think any of these, stop and check memory or persist state instead:
34
+ "I remember this project's state" (reread the files) · "this edit is too small to checkpoint" (was there a decision, correction, or blocker? then record it) · "I'll update the handoff later" (later never comes; update at the transition) · "the user already told me this once" (a second occurrence of the same friction is a capture trigger) · "the subagent can note that in memory" (it cannot; single writer).
35
+
36
+ Commands: /project-init, /remember, /recall, /forget, /handoff, /project-status, /memory-audit.
37
+ </omni-memory:bootstrap>`;
38
+
39
+ const COMPACTION_CONTEXT = `## omni-memory continuation requirements
40
+
41
+ This session uses persistent Markdown memory. The continuation summary MUST preserve:
42
+ 1. The memory system pointers: global memory at ~/.config/opencode/memory/GLOBAL.md; project state under <project-root>/.opencode/project/ (MEMORY.md and HANDOFF.md auto-load; INDEX, CHARTER, SPEC, ROADMAP, PROGRESS, FINDINGS, DECISIONS, sessions/, archive/ on demand).
43
+ 2. Every decision, verified finding, user correction, or blocker from this session that has NOT yet been written to those files — list each one explicitly so the continuation can persist it at the next meaningful transition.
44
+ 3. Any pending self-evolution obligations: lessons that should be captured to memory, or memory entries that should be promoted into AGENTS.md, an agent, skill, or command.
45
+ 4. The current handoff intent: branch, objective, exact next actions.
46
+ After compaction, the agent must re-orient from the memory files rather than trusting the summary alone; verified repository state overrides remembered state.`;
47
+
48
+ export const OmniMemoryPlugin = async () => {
49
+ return {
50
+ "experimental.chat.messages.transform": async (_input, output) => {
51
+ if (!output.messages.length) return;
52
+ const firstUser = output.messages.find((m) => m.info.role === "user");
53
+ if (!firstUser || !firstUser.parts.length) return;
54
+ // Idempotence: the hook fires on every agent step and message arrays may
55
+ // be rebuilt from the DB, so skip if the bootstrap is already present.
56
+ if (
57
+ firstUser.parts.some(
58
+ (p) => p.type === "text" && typeof p.text === "string" && p.text.includes(BOOTSTRAP_MARK),
59
+ )
60
+ )
61
+ return;
62
+ const ref = firstUser.parts[0];
63
+ firstUser.parts.unshift({ ...ref, type: "text", text: BOOTSTRAP });
64
+ },
65
+
66
+ "experimental.session.compacting": async (_input, output) => {
67
+ output.context.push(COMPACTION_CONTEXT);
68
+ },
69
+ };
70
+ };