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/LICENSE +21 -0
- package/README.md +285 -0
- package/VERSION +1 -0
- package/bin/cli.js +37 -0
- package/commands/forget.md +7 -0
- package/commands/handoff.md +9 -0
- package/commands/memory-audit.md +7 -0
- package/commands/project-init.md +9 -0
- package/commands/project-status.md +9 -0
- package/commands/recall.md +7 -0
- package/commands/remember.md +7 -0
- package/defaults/memory/GLOBAL.md +15 -0
- package/defaults/memory/HANDOFF.md +18 -0
- package/defaults/memory/HOST.md +11 -0
- package/defaults/memory/archive/README.md +3 -0
- package/install.sh +451 -0
- package/manifest.txt +9 -0
- package/package.json +49 -0
- package/plugins/omni-memory.js +70 -0
- package/skills/omni-memory/SKILL.md +230 -0
- package/skills/omni-memory/templates/project/ARCHIVE.md +12 -0
- package/skills/omni-memory/templates/project/CHARTER.md +11 -0
- package/skills/omni-memory/templates/project/DECISIONS.md +19 -0
- package/skills/omni-memory/templates/project/FINDINGS.md +12 -0
- package/skills/omni-memory/templates/project/HANDOFF.md +20 -0
- package/skills/omni-memory/templates/project/INDEX.md +25 -0
- package/skills/omni-memory/templates/project/MEMORY.md +13 -0
- package/skills/omni-memory/templates/project/PROGRESS.md +14 -0
- package/skills/omni-memory/templates/project/ROADMAP.md +12 -0
- package/skills/omni-memory/templates/project/SESSION.md +19 -0
- package/skills/omni-memory/templates/project/SPEC.md +14 -0
- package/snippets/AGENTS.md +10 -0
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
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
|
+
};
|