opencode-claude-memory 1.6.2 → 1.6.3

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 CHANGED
@@ -29,6 +29,30 @@ Claude Code writes memory → OpenCode reads it. OpenCode writes memory → Clau
29
29
 
30
30
  ## 🚀 Quick Start
31
31
 
32
+ ### Prerequisites
33
+
34
+ - `opencode`
35
+ - `python3` available in `PATH`
36
+
37
+ `python3` is a runtime dependency for the wrapper's scoped session detection and fork cleanup logic.
38
+ If it is missing or not executable, post-session maintenance becomes less reliable: session targeting can fall back to less precise heuristics, and fork cleanup is skipped for safety.
39
+
40
+ Common install commands:
41
+
42
+ ```bash
43
+ # macOS (Homebrew)
44
+ brew install python
45
+
46
+ # Ubuntu / Debian
47
+ sudo apt-get update && sudo apt-get install -y python3
48
+
49
+ # Fedora
50
+ sudo dnf install -y python3
51
+
52
+ # Arch Linux
53
+ sudo pacman -S python
54
+ ```
55
+
32
56
  ### 1. Install
33
57
 
34
58
  ```bash
@@ -41,6 +65,8 @@ This installs:
41
65
  - The `opencode-memory` **CLI** — wraps opencode with automatic memory extraction + auto-dream consolidation
42
66
  - A **shell hook** — defines an `opencode()` function in your `.zshrc`/`.bashrc` that delegates to `opencode-memory`
43
67
 
68
+ If `python3` is not installed yet, install it first using the commands above before enabling the shell hook.
69
+
44
70
  ### 2. Configure
45
71
 
46
72
  ```jsonc
@@ -118,6 +144,18 @@ The shell hook defines an `opencode()` function that delegates to `opencode-memo
118
144
  8. Maintenance runs **in the background** unless `OPENCODE_MEMORY_FOREGROUND=1`
119
145
  9. Terminal maintenance logs are shown in foreground mode by default, or can be forced on/off with `OPENCODE_MEMORY_TERMINAL_LOG=1|0`
120
146
 
147
+ ### Runtime dependencies
148
+
149
+ The wrapper expects `python3` to be available at runtime.
150
+
151
+ It is used for:
152
+
153
+ - scoped session selection from `opencode session list`
154
+ - parsing `opencode export` output to resolve session directories
155
+ - safely identifying and cleaning up forked extraction / auto-dream sessions
156
+
157
+ Without `python3`, the plugin tools still load, but wrapper maintenance is degraded and fork cleanup is intentionally skipped to avoid deleting the wrong session.
158
+
121
159
  ### Compatibility details
122
160
 
123
161
  The implementation ports core logic from Claude Code for path hashing, git-root/worktree handling, memory format, and memory prompting behavior, so both tools can operate on the same files safely.
@@ -478,17 +478,18 @@ get_session_target_id() {
478
478
  local started_at_ms="$2"
479
479
  local workdir="$3"
480
480
  local project_dir="$4"
481
+ local allow_existing_fallback="${5:-1}"
481
482
  local after_json
482
483
 
483
484
  after_json=$(get_session_list_json "$AUTODREAM_SCAN_LIMIT") || return 1
484
485
 
485
486
  if command -v python3 >/dev/null 2>&1; then
486
- python3 - "$before_json" "$after_json" "$started_at_ms" "$workdir" "$project_dir" <<'PY'
487
+ python3 - "$before_json" "$after_json" "$started_at_ms" "$workdir" "$project_dir" "$allow_existing_fallback" <<'PY'
487
488
  import json
488
489
  import os
489
490
  import sys
490
491
 
491
- before_raw, after_raw, started_at_ms_raw, workdir, project_dir = sys.argv[1:6]
492
+ before_raw, after_raw, started_at_ms_raw, workdir, project_dir, allow_existing_fallback_raw = sys.argv[1:7]
492
493
 
493
494
  def parse(raw):
494
495
  try:
@@ -522,6 +523,7 @@ def normalize(path):
522
523
  before = parse(before_raw)
523
524
  after = parse(after_raw)
524
525
  started_at_ms = int(started_at_ms_raw or "0")
526
+ allow_existing_fallback = allow_existing_fallback_raw == "1"
525
527
  before_ids = {item.get("id") for item in before if item.get("id")}
526
528
  workdir = normalize(workdir)
527
529
  project_dir = normalize(project_dir)
@@ -544,11 +546,15 @@ def choose(candidates):
544
546
  new_sessions = [item for item in after if item.get("id") not in before_ids]
545
547
  updated_sessions = [item for item in after if timestamp(item) > started_at_ms]
546
548
 
547
- for pool in (
549
+ candidate_pools = [
548
550
  [item for item in new_sessions if in_scope(item)],
549
551
  [item for item in updated_sessions if in_scope(item)],
550
- [item for item in after if in_scope(item)],
551
- ):
552
+ ]
553
+
554
+ if allow_existing_fallback:
555
+ candidate_pools.append([item for item in after if in_scope(item)])
556
+
557
+ for pool in candidate_pools:
552
558
  if choose(pool):
553
559
  break
554
560
  PY
@@ -795,17 +801,19 @@ main_prompt_requests_ignore_memory() {
795
801
  printf '%s\n' "$joined" | grep -Eq "(ignore|don't use|do not use|without|skip)[[:space:]]+(the[[:space:]]+)?memory|memory[[:space:]]+((should|must)[[:space:]]+be[[:space:]]+)?ignored"
796
802
  }
797
803
 
798
- wait_for_session_target_id() {
804
+ wait_for_scoped_session_id_since() {
799
805
  local before_json="$1"
800
806
  local started_at_ms="$2"
801
- local wait_seconds="${3:-5}"
807
+ local timestamp_file="$3"
808
+ local wait_seconds="${4:-5}"
809
+ local allow_existing_fallback="${5:-1}"
802
810
  local attempt=0
803
811
  local session_id=""
804
812
 
805
813
  while [ "$attempt" -lt "$wait_seconds" ]; do
806
- session_id=$(get_session_target_id "$before_json" "$started_at_ms" "$WORKING_DIR" "$PROJECT_SCOPE_DIR" || true)
814
+ session_id=$(get_session_target_id "$before_json" "$started_at_ms" "$WORKING_DIR" "$PROJECT_SCOPE_DIR" "$allow_existing_fallback" || true)
807
815
  if [ -z "$session_id" ]; then
808
- session_id=$(get_scoped_artifact_session_id_since "$TIMESTAMP_FILE" "$WORKING_DIR" "$PROJECT_SCOPE_DIR" || true)
816
+ session_id=$(get_scoped_artifact_session_id_since "$timestamp_file" "$WORKING_DIR" "$PROJECT_SCOPE_DIR" || true)
809
817
  fi
810
818
  if [ -n "$session_id" ]; then
811
819
  printf '%s\n' "$session_id"
@@ -818,6 +826,14 @@ wait_for_session_target_id() {
818
826
  return 1
819
827
  }
820
828
 
829
+ wait_for_session_target_id() {
830
+ local before_json="$1"
831
+ local started_at_ms="$2"
832
+ local wait_seconds="${3:-5}"
833
+
834
+ wait_for_scoped_session_id_since "$before_json" "$started_at_ms" "$TIMESTAMP_FILE" "$wait_seconds"
835
+ }
836
+
821
837
  file_mtime_secs() {
822
838
  local file="$1"
823
839
  if [ ! -f "$file" ]; then
@@ -958,58 +974,32 @@ rollback_consolidation_lock() {
958
974
 
959
975
  cleanup_forked_sessions() {
960
976
  local before_json="$1"
977
+ local started_at_ms="$2"
978
+ local timestamp_file="$3"
961
979
 
962
- if ! command -v python3 >/dev/null 2>&1; then
980
+ if [ -z "$before_json" ] || [ -z "$started_at_ms" ] || [ -z "$timestamp_file" ]; then
963
981
  return 0
964
982
  fi
965
983
 
966
- local after_json
967
- after_json=$(get_session_list_json 10 2>/dev/null || true)
968
-
969
- if [ -z "$before_json" ] || [ -z "$after_json" ]; then
984
+ if ! command -v python3 >/dev/null 2>&1; then
970
985
  return 0
971
986
  fi
972
987
 
973
- local fork_ids
974
- fork_ids=$(python3 - "$before_json" "$after_json" "$WORKING_DIR" "$PROJECT_SCOPE_DIR" <<'PY'
975
- import json
976
- import os
977
- import sys
978
-
979
- def parse(raw):
980
- try:
981
- data = json.loads(raw)
982
- return data if isinstance(data, list) else []
983
- except Exception:
984
- return []
985
-
986
- before_raw, after_raw, workdir, project_dir = sys.argv[1:5]
987
- before = parse(before_raw)
988
- after = parse(after_raw)
988
+ if ! python3 - <<'PY' >/dev/null 2>&1
989
+ pass
990
+ PY
991
+ then
992
+ return 0
993
+ fi
989
994
 
990
- before_ids = {item.get("id") for item in before if item.get("id")}
991
- workdir = os.path.realpath(workdir)
992
- project_dir = os.path.realpath(project_dir)
995
+ local fork_id
996
+ fork_id=$(wait_for_scoped_session_id_since "$before_json" "$started_at_ms" "$timestamp_file" "$SESSION_WAIT_SECONDS" 0 || true)
993
997
 
994
- for item in after:
995
- sid = item.get("id", "")
996
- if not sid or sid in before_ids:
997
- continue
998
- directory = item.get("directory", "")
999
- if not directory:
1000
- continue
1001
- d = os.path.realpath(directory)
1002
- if d in (workdir, project_dir):
1003
- print(sid)
1004
- PY
1005
- ) || return 0
998
+ [ -n "$fork_id" ] || return 0
1006
999
 
1007
- while IFS= read -r fork_id; do
1008
- [ -n "$fork_id" ] || continue
1009
- if "$REAL_OPENCODE" session delete "$fork_id" >/dev/null 2>&1; then
1010
- log "Cleaned up forked session $fork_id"
1011
- fi
1012
- done <<< "$fork_ids"
1000
+ if "$REAL_OPENCODE" session delete "$fork_id" >/dev/null 2>&1; then
1001
+ log "Cleaned up forked session $fork_id"
1002
+ fi
1013
1003
  }
1014
1004
 
1015
1005
  session_has_conversation() {
@@ -1060,7 +1050,11 @@ run_extraction_if_needed() {
1060
1050
  cmd+=("$EXTRACT_PROMPT")
1061
1051
 
1062
1052
  local pre_fork_json
1063
- pre_fork_json=$(get_session_list_json 5 2>/dev/null || true)
1053
+ pre_fork_json=$(get_session_list_json "$AUTODREAM_SCAN_LIMIT" 2>/dev/null || true)
1054
+ local fork_timestamp_file
1055
+ fork_timestamp_file=$(mktemp)
1056
+ local fork_started_at_ms
1057
+ fork_started_at_ms=$(( $(date +%s) * 1000 ))
1064
1058
 
1065
1059
  if "${cmd[@]}" >> "$EXTRACT_LOG_FILE" 2>&1; then
1066
1060
  log "Memory extraction completed successfully"
@@ -1069,7 +1063,8 @@ run_extraction_if_needed() {
1069
1063
  log "Memory extraction failed (exit code $code). Check $EXTRACT_LOG_FILE for details"
1070
1064
  fi
1071
1065
 
1072
- cleanup_forked_sessions "$pre_fork_json"
1066
+ cleanup_forked_sessions "$pre_fork_json" "$fork_started_at_ms" "$fork_timestamp_file"
1067
+ rm -f "$fork_timestamp_file"
1073
1068
  release_simple_lock "$EXTRACT_LOCK_FILE"
1074
1069
  }
1075
1070
 
@@ -1122,7 +1117,11 @@ run_autodream_if_needed() {
1122
1117
  cmd+=("$AUTODREAM_PROMPT")
1123
1118
 
1124
1119
  local pre_fork_json
1125
- pre_fork_json=$(get_session_list_json 5 2>/dev/null || true)
1120
+ pre_fork_json=$(get_session_list_json "$AUTODREAM_SCAN_LIMIT" 2>/dev/null || true)
1121
+ local fork_timestamp_file
1122
+ fork_timestamp_file=$(mktemp)
1123
+ local fork_started_at_ms
1124
+ fork_started_at_ms=$(( $(date +%s) * 1000 ))
1126
1125
 
1127
1126
  if "${cmd[@]}" >> "$AUTODREAM_LOG_FILE" 2>&1; then
1128
1127
  log "Auto-dream consolidation completed successfully"
@@ -1133,7 +1132,8 @@ run_autodream_if_needed() {
1133
1132
  rollback_consolidation_lock "$CONSOLIDATION_PRIOR_MTIME"
1134
1133
  fi
1135
1134
 
1136
- cleanup_forked_sessions "$pre_fork_json"
1135
+ cleanup_forked_sessions "$pre_fork_json" "$fork_started_at_ms" "$fork_timestamp_file"
1136
+ rm -f "$fork_timestamp_file"
1137
1137
  }
1138
1138
 
1139
1139
  run_post_session_tasks() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-claude-memory",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "type": "module",
5
5
  "description": "Claude Code-compatible memory compatibility layer for OpenCode — zero config, local-first, no migration",
6
6
  "main": "dist/index.js",