@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zilliz/memsearch-opencode",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "memsearch plugin for OpenCode — semantic memory search across sessions",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -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 only when they clearly earn it.
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
- Be conservative. Quality over quantity. Most runs should return an empty list.
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 nothing in the journals qualifies. This is the common case.
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. Do not reuse an existing skill 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
- f"{args.memsearch_cmd} index '{memory_dir}' "
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
- return (result.stdout or result.stderr or "").strip()
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 = ["claude", "-p", "--strict-mcp-config", "--no-session-persistence", "--no-chrome"]
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 and install it (see **B**). Finally,
41
- check whether background distillation is on; if not, offer to enable it (so
42
- recurring workflows get captured automatically going forward) do not force it.
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
- Pick one, resolve where to install (ask if unset), then install:
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 .agents/skills
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, but it can only see the summaries; doing it here on demand lets you read the original transcripts, so the result is more accurate.
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