@zilliz/memsearch-opencode 0.3.6 → 0.3.7
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/package.json
CHANGED
|
@@ -9,9 +9,12 @@ error). It is NOT a fact, a decision, or a preference — those belong in
|
|
|
9
9
|
PROJECT.md and USER.md.
|
|
10
10
|
|
|
11
11
|
You will receive recent memory journal entries and a list of existing skills
|
|
12
|
-
(which you MAY revise). Propose or revise skills
|
|
12
|
+
(which you MAY revise). Propose or revise candidate skills when the journals show
|
|
13
|
+
the same user-requested operation repeating enough times to be useful.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
Candidates are review drafts, not installed skills. Do not enforce a fixed ratio,
|
|
16
|
+
quota, or expectation that most runs return nothing. The number of candidates
|
|
17
|
+
should follow the evidence in the journals.
|
|
15
18
|
|
|
16
19
|
A NEW workflow qualifies as a candidate ONLY when ALL of these hold:
|
|
17
20
|
- It is a repeatable multi-step procedure, not a one-off action or a fact.
|
|
@@ -19,9 +22,10 @@ A NEW workflow qualifies as a candidate ONLY when ALL of these hold:
|
|
|
19
22
|
journals below.
|
|
20
23
|
- It generalizes into a reusable capability. If it is tightly coupled to one
|
|
21
24
|
specific ticket, one specific value, or a one-time condition, do NOT propose it.
|
|
22
|
-
- The recent runs were not immediately corrected, abandoned, or undone (a
|
|
23
|
-
workflow the user kept fighting is not a good skill).
|
|
24
25
|
- It is not already covered by an existing skill.
|
|
26
|
+
- The overall procedure was not abandoned, undone, or contradicted by later
|
|
27
|
+
evidence. Do not reject a workflow merely because individual runs included
|
|
28
|
+
failed attempts, retries, or corrected commands.
|
|
25
29
|
|
|
26
30
|
You may also REVISE an existing skill (re-emit it with the SAME name and a
|
|
27
31
|
corrected body) when the recent journals show that procedure has genuinely
|
|
@@ -30,17 +34,21 @@ there is clear evidence of change; do not rewrite for style.
|
|
|
30
34
|
|
|
31
35
|
Rules:
|
|
32
36
|
- Return only a JSON object, with no prose outside JSON.
|
|
33
|
-
- Use {"skills": []} when
|
|
37
|
+
- Use {"skills": []} when no repeated workflow qualifies.
|
|
34
38
|
- For each candidate, write a clean, portable SKILL.md body in "body": step-by-step
|
|
35
39
|
instructions an agent can follow. Do not include YAML frontmatter — only the
|
|
36
40
|
markdown body. Keep it concise and concrete.
|
|
37
41
|
- "name" must be a short lowercase slug (letters, digits, dashes), e.g. "run-tests".
|
|
38
|
-
It becomes the skill's command name.
|
|
42
|
+
It becomes the skill's command name. For new candidates, do not reuse an
|
|
43
|
+
existing skill name; for revisions, reuse the exact existing name.
|
|
39
44
|
- "description" is one line: what the skill does AND when it should trigger.
|
|
40
45
|
Lead with the verbs a user would actually type ("run", "deploy", "debug").
|
|
41
46
|
- "occurrences" is how many distinct sessions/days you saw this workflow.
|
|
42
47
|
- "sources" lists the journal file names the workflow was observed in.
|
|
43
48
|
- Do not copy raw journal text wholesale into the body; abstract it into a procedure.
|
|
49
|
+
- If a workflow qualifies but some exact commands, paths, or flags are not
|
|
50
|
+
confirmed, still create the candidate. Keep those details general and add a
|
|
51
|
+
short "Before installing" verification step instead of inventing specifics.
|
|
44
52
|
|
|
45
53
|
How to write a good skill body (this is what makes the skill usable):
|
|
46
54
|
- Write imperative, numbered steps an agent can follow top to bottom.
|
|
@@ -66,6 +74,8 @@ Accuracy — confirm from the original, do not hallucinate:
|
|
|
66
74
|
- If you cannot read the transcript or confirm a detail, capture only what the
|
|
67
75
|
summary clearly states; keep the step general or omit it — a plausible-but-wrong
|
|
68
76
|
command is worse than none. Never invent specifics.
|
|
77
|
+
- Do not drop the whole candidate only because a few low-level details remain
|
|
78
|
+
unconfirmed; make the uncertainty visible for human review before installation.
|
|
69
79
|
- Prefer fewer, verified steps over a complete-looking but partly-guessed procedure.
|
|
70
80
|
|
|
71
81
|
JSON examples:
|
|
@@ -459,6 +459,28 @@ def summarize_with_llm(
|
|
|
459
459
|
return None
|
|
460
460
|
|
|
461
461
|
|
|
462
|
+
def wake_maintenance(project_dir: str) -> None:
|
|
463
|
+
"""Start maintenance in a hook-disabled child environment."""
|
|
464
|
+
runner = Path(__file__).resolve().parent / "maintenance-runner.py"
|
|
465
|
+
subprocess.Popen(
|
|
466
|
+
[
|
|
467
|
+
"python3",
|
|
468
|
+
str(runner),
|
|
469
|
+
"--platform",
|
|
470
|
+
"opencode",
|
|
471
|
+
"--project-dir",
|
|
472
|
+
project_dir,
|
|
473
|
+
"--memsearch-dir",
|
|
474
|
+
os.path.join(project_dir, ".memsearch"),
|
|
475
|
+
],
|
|
476
|
+
env={**os.environ, "MEMSEARCH_NO_WATCH": "1", "MEMSEARCH_DISABLE": "1"},
|
|
477
|
+
stdin=subprocess.DEVNULL,
|
|
478
|
+
stdout=subprocess.DEVNULL,
|
|
479
|
+
stderr=subprocess.DEVNULL,
|
|
480
|
+
start_new_session=True,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
|
|
462
484
|
def get_session_ids(conn: sqlite3.Connection, project_dir: str) -> list[str]:
|
|
463
485
|
"""Find OpenCode sessions that belong to the given project directory."""
|
|
464
486
|
sessions = conn.execute(
|
|
@@ -790,16 +812,8 @@ def main() -> None:
|
|
|
790
812
|
)
|
|
791
813
|
|
|
792
814
|
if any_new:
|
|
793
|
-
os.system(
|
|
794
|
-
|
|
795
|
-
f"--collection {args.collection_name} &"
|
|
796
|
-
)
|
|
797
|
-
os.system(
|
|
798
|
-
f"python3 {shlex.quote(str(Path(__file__).resolve().parent / 'maintenance-runner.py'))} "
|
|
799
|
-
f"--platform opencode "
|
|
800
|
-
f"--project-dir {shlex.quote(args.project_dir)} "
|
|
801
|
-
f"--memsearch-dir {shlex.quote(os.path.join(args.project_dir, '.memsearch'))} &"
|
|
802
|
-
)
|
|
815
|
+
os.system(f"{args.memsearch_cmd} index '{memory_dir}' --collection {args.collection_name} &")
|
|
816
|
+
wake_maintenance(args.project_dir)
|
|
803
817
|
except Exception:
|
|
804
818
|
pass
|
|
805
819
|
finally:
|
|
@@ -13,6 +13,7 @@ import contextlib
|
|
|
13
13
|
import json
|
|
14
14
|
import os
|
|
15
15
|
import re
|
|
16
|
+
import shlex
|
|
16
17
|
import shutil
|
|
17
18
|
import subprocess
|
|
18
19
|
import sys
|
|
@@ -264,7 +265,50 @@ def run_command(cmd: list[str], *, env: dict[str, str], cwd: Path, timeout: int)
|
|
|
264
265
|
timeout=timeout,
|
|
265
266
|
check=False,
|
|
266
267
|
)
|
|
267
|
-
|
|
268
|
+
stdout = (result.stdout or "").strip()
|
|
269
|
+
stderr = (result.stderr or "").strip()
|
|
270
|
+
if result.returncode != 0:
|
|
271
|
+
detail = "\n".join(part for part in (stdout, stderr) if part)
|
|
272
|
+
if len(detail) > 2000:
|
|
273
|
+
detail = detail[:1975] + "... [truncated]"
|
|
274
|
+
command = " ".join(shlex.quote(_describe_arg(part)) for part in cmd)
|
|
275
|
+
if len(command) > 300:
|
|
276
|
+
command = command[:275] + "... [truncated]"
|
|
277
|
+
message = f"Command failed ({result.returncode}): {command}"
|
|
278
|
+
if detail:
|
|
279
|
+
message = f"{message}\n{detail}"
|
|
280
|
+
raise RuntimeError(message)
|
|
281
|
+
return stdout or stderr
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _describe_arg(arg: str, limit: int = 120) -> str:
|
|
285
|
+
if len(arg) <= limit:
|
|
286
|
+
return arg
|
|
287
|
+
return f"<arg:{len(arg)} chars>"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
_CLAUDE_SAFE_MODE_ARGS: list[str] | None = None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def claude_safe_mode_args() -> list[str]:
|
|
294
|
+
"""Return Claude args that suppress hooks when supported."""
|
|
295
|
+
global _CLAUDE_SAFE_MODE_ARGS
|
|
296
|
+
if _CLAUDE_SAFE_MODE_ARGS is not None:
|
|
297
|
+
return _CLAUDE_SAFE_MODE_ARGS
|
|
298
|
+
try:
|
|
299
|
+
result = subprocess.run(
|
|
300
|
+
["claude", "--help"],
|
|
301
|
+
capture_output=True,
|
|
302
|
+
text=True,
|
|
303
|
+
timeout=5,
|
|
304
|
+
check=False,
|
|
305
|
+
)
|
|
306
|
+
except (OSError, subprocess.SubprocessError):
|
|
307
|
+
_CLAUDE_SAFE_MODE_ARGS = []
|
|
308
|
+
else:
|
|
309
|
+
help_text = f"{result.stdout}\n{result.stderr}"
|
|
310
|
+
_CLAUDE_SAFE_MODE_ARGS = ["--safe-mode"] if "--safe-mode" in help_text else []
|
|
311
|
+
return _CLAUDE_SAFE_MODE_ARGS
|
|
268
312
|
|
|
269
313
|
|
|
270
314
|
def extract_task_json_output(output: str) -> str:
|
|
@@ -376,7 +420,14 @@ def run_native_provider(ctx, prompt: str) -> str:
|
|
|
376
420
|
env = {**os.environ, "MEMSEARCH_NO_WATCH": "1"}
|
|
377
421
|
|
|
378
422
|
if ctx.platform == "claude-code":
|
|
379
|
-
cmd = [
|
|
423
|
+
cmd = [
|
|
424
|
+
"claude",
|
|
425
|
+
"-p",
|
|
426
|
+
*claude_safe_mode_args(),
|
|
427
|
+
"--strict-mcp-config",
|
|
428
|
+
"--no-session-persistence",
|
|
429
|
+
"--no-chrome",
|
|
430
|
+
]
|
|
380
431
|
# Skill distillation needs to read original transcripts for exact commands.
|
|
381
432
|
# Open a narrow hole — Bash limited to the two read-only memsearch drill
|
|
382
433
|
# commands — only for that task; other maintenance keeps all tools off.
|
|
@@ -392,6 +443,7 @@ def run_native_provider(ctx, prompt: str) -> str:
|
|
|
392
443
|
prompt,
|
|
393
444
|
]
|
|
394
445
|
env["CLAUDECODE"] = ""
|
|
446
|
+
env["MEMSEARCH_DISABLE"] = "1"
|
|
395
447
|
return run_command(cmd, env=env, cwd=ctx.project_dir, timeout=120)
|
|
396
448
|
|
|
397
449
|
if ctx.platform == "codex":
|
|
@@ -189,6 +189,10 @@ Model guidance:
|
|
|
189
189
|
|
|
190
190
|
Advanced maintenance runs after the plugin wakes it, only when enabled, journal input changed, and `min_interval_hours` elapsed. `PROJECT.md` and `USER.md` are maintenance artifacts by default and are not automatically indexed.
|
|
191
191
|
|
|
192
|
+
If advanced maintenance or `memory_to_skill` seems silent, check
|
|
193
|
+
`.memsearch/.maintenance-state.json` for `<plugin>.<task>.last_error` and
|
|
194
|
+
`last_failed_at`; background hook errors may not surface in the chat.
|
|
195
|
+
|
|
192
196
|
Before enabling advanced maintenance, ask which provider to use, whether the default 24-hour interval is acceptable, and whether `.memsearch/PROJECT.md` / `.memsearch/USER.md` are acceptable output files.
|
|
193
197
|
|
|
194
198
|
Prompt overrides:
|
|
@@ -11,6 +11,8 @@ OpenCode's built-in skills system.
|
|
|
11
11
|
Stages: **0** memory journals → **1** candidate (`.memsearch/skill-candidates/`,
|
|
12
12
|
a git-tracked store that keeps evolving) → **2** installed (an agent skill dir).
|
|
13
13
|
Candidates are never installed automatically; installing is always a human step.
|
|
14
|
+
User requests may stop at candidate creation/review, or continue to installation
|
|
15
|
+
in the same turn after explicit approval; match the requested stage.
|
|
14
16
|
|
|
15
17
|
## Intent routing
|
|
16
18
|
|
|
@@ -37,24 +39,35 @@ printf '%s' "## <title>\n\n1. ...\n2. ..." | memsearch skills add \
|
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
`add` handles slugging, standard frontmatter, meta.json, and the git commit — no
|
|
40
|
-
LLM is involved. Then show it to the user
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
LLM is involved. Then show it to the user; install it only if the user asked for
|
|
43
|
+
that or explicitly approves (see **B**). Finally, check whether background
|
|
44
|
+
distillation is on; if not, offer to enable it (so recurring workflows get
|
|
45
|
+
captured automatically going forward) — do not force it.
|
|
43
46
|
|
|
44
47
|
## B. Review & install candidates (1→2)
|
|
45
48
|
|
|
46
49
|
```bash
|
|
47
50
|
memsearch skills list # add -j for sources / installed paths
|
|
51
|
+
git -C .memsearch/skill-candidates log --oneline -5 2>/dev/null || true
|
|
48
52
|
```
|
|
49
53
|
|
|
50
54
|
Before recommending or installing, skim the candidate's body: if a step looks uncertain or loosely summarized, re-check it against the source (open the transcript if needed) or flag it to the user and let them decide — installing copies the candidate as-is, so this is the last chance to catch a wrong step.
|
|
55
|
+
When showing candidates, mention the store's recent git history when it helps
|
|
56
|
+
explain whether a candidate is new, evolved, removed, or re-created.
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
Treat installation as an interactive checkpoint. Show the candidate, apply any
|
|
59
|
+
requested tweaks before installing, and confirm the install destination with the
|
|
60
|
+
user. Resolve install targets from config first: if `paths` is a non-empty
|
|
61
|
+
list, present those paths as the proposed destinations and pass each entry as a
|
|
62
|
+
`--path` after confirmation. If it is empty, ask the user where to install; do
|
|
63
|
+
not silently fall back to a default path.
|
|
53
64
|
|
|
54
65
|
```bash
|
|
55
66
|
memsearch config get plugins.opencode.memory_to_skill.paths 2>/dev/null || echo "[]"
|
|
56
|
-
memsearch skills install <name> --path
|
|
67
|
+
memsearch skills install <name> --path <configured-or-user-approved-path>
|
|
57
68
|
```
|
|
69
|
+
After installation, remind the user to start a fresh agent session or reopen the
|
|
70
|
+
conversation so the newly installed skill is loaded.
|
|
58
71
|
|
|
59
72
|
If the list is **empty**, background distillation is likely off or has not run.
|
|
60
73
|
Offer the user a choice: capture from recent work now (**A**), distill from
|
|
@@ -71,7 +84,7 @@ generalize, not one-offs from a single day.
|
|
|
71
84
|
|
|
72
85
|
**Drill into the original before drafting.** The journal bullets are a lossy summary; the exact commands, flags, and paths live in the original conversation. OpenCode stores transcripts in its session database (not a file), and the journal anchor carries a `session:` id (and `turn:` when present). Run `python3 __INSTALL_DIR__/scripts/parse-transcript.py <session_id>` (add `--turn <id>` when the anchor has one) to read the original turns with their tool calls — that is where the executed commands and output live. Write the skill from that. If the shown excerpt feels incomplete, skim nearby turns in the same original source before committing to exact commands or paths. If you cannot read it or confirm a detail, keep the step general or omit it — never fabricate.
|
|
73
86
|
|
|
74
|
-
The background pass mines automatically when enabled,
|
|
87
|
+
The background pass mines automatically when enabled, starting from the summaries; doing it here on demand lets you inspect the original transcripts more deliberately, so the result can be more accurate.
|
|
75
88
|
|
|
76
89
|
## D. Configure
|
|
77
90
|
|