create-claude-cabinet 0.7.5 → 0.8.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/lib/cli.js CHANGED
@@ -7,6 +7,7 @@ const { copyTemplates } = require('./copy');
7
7
  const { mergeSettings } = require('./settings-merge');
8
8
  const { create: createMetadata, read: readMetadata } = require('./metadata');
9
9
  const { setupDb } = require('./db-setup');
10
+ const { setupOmega } = require('./omega-setup');
10
11
  const { reset } = require('./reset');
11
12
 
12
13
  const VERSION = require('../package.json').version;
@@ -95,6 +96,15 @@ const MODULES = {
95
96
  lean: false,
96
97
  templates: ['skills/validate'],
97
98
  },
99
+ 'memory': {
100
+ name: 'Semantic Memory (omega)',
101
+ description: 'Rich session memory via omega-memory. Captures decisions, reasoning chains, and lessons between sessions. Requires Python 3.11+.',
102
+ mandatory: false,
103
+ default: true,
104
+ lean: false,
105
+ needsOmega: true,
106
+ templates: ['hooks/memory-session-start.sh', 'hooks/memory-post-compact.sh', 'scripts/cabinet-memory-adapter.py', 'rules/memory-capture.md'],
107
+ },
98
108
  };
99
109
 
100
110
  // Signals that a directory contains a real project (not just empty)
@@ -532,7 +542,8 @@ async function run() {
532
542
 
533
543
  // --- Merge hooks into settings.json ---
534
544
  if (selectedModules.includes('hooks') && !flags.dryRun) {
535
- const settingsPath = mergeSettings(projectDir, { includeDb });
545
+ const includeMemory = selectedModules.includes('memory');
546
+ const settingsPath = mergeSettings(projectDir, { includeDb, includeMemory });
536
547
  console.log(` ⚙️ Merged hooks into ${path.relative(projectDir, settingsPath)}`);
537
548
  }
538
549
 
@@ -547,6 +558,20 @@ async function run() {
547
558
  }
548
559
  }
549
560
 
561
+ // --- Set up omega memory ---
562
+ const needsOmega = selectedModules.some(m => MODULES[m].needsOmega);
563
+ if (needsOmega && !flags.dryRun) {
564
+ try {
565
+ console.log('');
566
+ const omegaResults = setupOmega();
567
+ for (const r of omegaResults) console.log(` 🧠 ${r}`);
568
+ } catch (err) {
569
+ console.log(` ⚠ Omega memory setup skipped: ${err.message}`);
570
+ console.log(' Memory module will be inactive until Python 3.11+ is available.');
571
+ console.log(' Re-run the installer after installing Python to enable it.');
572
+ }
573
+ }
574
+
550
575
  // --- Clean up files removed upstream ---
551
576
  // Phase files are excluded from the manifest (they're user-customized),
552
577
  // so skip them during cleanup even if they were in the old manifest.
@@ -0,0 +1,153 @@
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ const OMEGA_HOME = path.join(os.homedir(), '.claude-cabinet');
7
+ const VENV_DIR = path.join(OMEGA_HOME, 'omega-venv');
8
+ const VENV_PYTHON = path.join(VENV_DIR, 'bin', 'python3');
9
+
10
+ // Ordered by preference: stable Cellar symlinks first, newest Python first.
11
+ // Apple Silicon paths, then Intel Mac paths, then PATH fallback.
12
+ const PYTHON_CANDIDATES = [
13
+ '/opt/homebrew/opt/python@3.14/bin/python3.14',
14
+ '/opt/homebrew/opt/python@3.13/bin/python3.13',
15
+ '/opt/homebrew/opt/python@3.12/bin/python3.12',
16
+ '/opt/homebrew/opt/python@3.11/bin/python3.11',
17
+ '/usr/local/opt/python@3.14/bin/python3.14',
18
+ '/usr/local/opt/python@3.13/bin/python3.13',
19
+ '/usr/local/opt/python@3.12/bin/python3.12',
20
+ '/usr/local/opt/python@3.11/bin/python3.11',
21
+ '/opt/homebrew/bin/python3',
22
+ '/usr/local/bin/python3',
23
+ ];
24
+
25
+ /**
26
+ * Find a Python >= 3.11 on this system.
27
+ * Returns the absolute path or null if none found.
28
+ */
29
+ function findPython() {
30
+ for (const candidate of PYTHON_CANDIDATES) {
31
+ if (!fs.existsSync(candidate)) continue;
32
+ try {
33
+ const version = execSync(`"${candidate}" --version 2>&1`, { encoding: 'utf8' }).trim();
34
+ const match = version.match(/Python (\d+)\.(\d+)/);
35
+ if (match) {
36
+ const major = parseInt(match[1], 10);
37
+ const minor = parseInt(match[2], 10);
38
+ if (major === 3 && minor >= 11) return candidate;
39
+ }
40
+ } catch { /* skip */ }
41
+ }
42
+
43
+ // Last resort: bare python3 on PATH
44
+ try {
45
+ const version = execSync('python3 --version 2>&1', { encoding: 'utf8' }).trim();
46
+ const match = version.match(/Python (\d+)\.(\d+)/);
47
+ if (match) {
48
+ const major = parseInt(match[1], 10);
49
+ const minor = parseInt(match[2], 10);
50
+ if (major === 3 && minor >= 11) {
51
+ const which = execSync('which python3', { encoding: 'utf8' }).trim();
52
+ return which;
53
+ }
54
+ }
55
+ } catch { /* skip */ }
56
+
57
+ return null;
58
+ }
59
+
60
+ /**
61
+ * Set up the omega-memory venv and download the embedding model.
62
+ *
63
+ * Steps:
64
+ * 1. Find Python 3.11+
65
+ * 2. Create venv at ~/.claude-cabinet/omega-venv/
66
+ * 3. pip install omega-memory
67
+ * 4. Run omega setup --download-model (downloads ONNX model at install time)
68
+ *
69
+ * Returns an array of status messages (like db-setup.js).
70
+ * Throws on failure.
71
+ */
72
+ function setupOmega() {
73
+ const results = [];
74
+
75
+ // 1. Find Python
76
+ const pythonPath = findPython();
77
+ if (!pythonPath) {
78
+ throw new Error(
79
+ 'Python 3.11+ not found. Install via Homebrew: brew install python@3.13\n' +
80
+ ' On Debian/Ubuntu: sudo apt install python3.13 python3.13-venv'
81
+ );
82
+ }
83
+ results.push(`Found Python: ${pythonPath}`);
84
+
85
+ // 2. Check ensurepip (Debian/Ubuntu strips it)
86
+ try {
87
+ execSync(`"${pythonPath}" -c "import ensurepip"`, { stdio: 'pipe' });
88
+ } catch {
89
+ const version = execSync(`"${pythonPath}" --version`, { encoding: 'utf8' }).trim();
90
+ const match = version.match(/Python (\d+)\.(\d+)/);
91
+ const pkg = match ? `python${match[1]}.${match[2]}-venv` : 'python3-venv';
92
+ throw new Error(
93
+ `Python venv module not found. On Debian/Ubuntu, install it:\n` +
94
+ ` sudo apt install ${pkg}`
95
+ );
96
+ }
97
+
98
+ // 3. Create or verify venv
99
+ if (!fs.existsSync(OMEGA_HOME)) {
100
+ fs.mkdirSync(OMEGA_HOME, { recursive: true });
101
+ }
102
+
103
+ if (fs.existsSync(VENV_PYTHON)) {
104
+ // Venv already exists — verify it works
105
+ try {
106
+ execSync(`"${VENV_PYTHON}" -c "import omega"`, { stdio: 'pipe' });
107
+ results.push('Existing omega venv is valid');
108
+ return results;
109
+ } catch {
110
+ // Venv is broken — nuke and rebuild (D5)
111
+ results.push('Existing venv broken, rebuilding...');
112
+ fs.rmSync(VENV_DIR, { recursive: true, force: true });
113
+ }
114
+ }
115
+
116
+ console.log(' Creating Python venv...');
117
+ execSync(`"${pythonPath}" -m venv "${VENV_DIR}"`, { stdio: 'pipe' });
118
+ results.push('Created venv at ~/.claude-cabinet/omega-venv/');
119
+
120
+ // 4. Install omega-memory
121
+ console.log(' Installing omega-memory...');
122
+ execSync(`"${VENV_PYTHON}" -m pip install --quiet omega-memory`, {
123
+ stdio: 'pipe',
124
+ timeout: 120000,
125
+ });
126
+ results.push('Installed omega-memory');
127
+
128
+ // 5. Download embedding model at install time (D4)
129
+ console.log(' Downloading embedding model...');
130
+ execSync(`"${VENV_PYTHON}" -m omega.cli setup --download-model`, {
131
+ stdio: 'pipe',
132
+ timeout: 120000,
133
+ env: { ...process.env, OMEGA_TELEMETRY: '0' },
134
+ });
135
+ results.push('Downloaded ONNX embedding model (bge-small-en-v1.5)');
136
+
137
+ return results;
138
+ }
139
+
140
+ /**
141
+ * Check if omega is already set up and functional.
142
+ */
143
+ function isOmegaReady() {
144
+ if (!fs.existsSync(VENV_PYTHON)) return false;
145
+ try {
146
+ execSync(`"${VENV_PYTHON}" -c "import omega"`, { stdio: 'pipe' });
147
+ return true;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ module.exports = { setupOmega, isOmegaReady, findPython, VENV_DIR, VENV_PYTHON };
@@ -1,6 +1,31 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
+ const MEMORY_HOOKS = {
5
+ SessionStart: [
6
+ {
7
+ matcher: 'startup|resume|compact',
8
+ hooks: [
9
+ {
10
+ type: 'command',
11
+ command: '.claude/hooks/memory-session-start.sh',
12
+ },
13
+ ],
14
+ },
15
+ ],
16
+ PostCompact: [
17
+ {
18
+ matcher: '',
19
+ hooks: [
20
+ {
21
+ type: 'command',
22
+ command: '.claude/hooks/memory-post-compact.sh',
23
+ },
24
+ ],
25
+ },
26
+ ],
27
+ };
28
+
4
29
  const DEFAULT_HOOKS = {
5
30
  PreToolUse: [
6
31
  {
@@ -50,7 +75,7 @@ const DEFAULT_HOOKS = {
50
75
  * Merge PIB hooks into the project's .claude/settings.json.
51
76
  * Creates the file if it doesn't exist. Preserves existing hooks.
52
77
  */
53
- function mergeSettings(projectDir, { includeDb = true } = {}) {
78
+ function mergeSettings(projectDir, { includeDb = true, includeMemory = false } = {}) {
54
79
  const settingsDir = path.join(projectDir, '.claude');
55
80
  const settingsPath = path.join(settingsDir, 'settings.json');
56
81
 
@@ -65,8 +90,16 @@ function mergeSettings(projectDir, { includeDb = true } = {}) {
65
90
 
66
91
  if (!settings.hooks) settings.hooks = {};
67
92
 
93
+ // Build the complete hook set for this install
94
+ const allHooks = { ...DEFAULT_HOOKS };
95
+ if (includeMemory) {
96
+ for (const [event, hooks] of Object.entries(MEMORY_HOOKS)) {
97
+ allHooks[event] = [...(allHooks[event] || []), ...hooks];
98
+ }
99
+ }
100
+
68
101
  // Merge each hook event type
69
- for (const [event, newHooks] of Object.entries(DEFAULT_HOOKS)) {
102
+ for (const [event, newHooks] of Object.entries(allHooks)) {
70
103
  if (!settings.hooks[event]) {
71
104
  settings.hooks[event] = newHooks;
72
105
  } else {
@@ -90,4 +123,4 @@ function mergeSettings(projectDir, { includeDb = true } = {}) {
90
123
  return settingsPath;
91
124
  }
92
125
 
93
- module.exports = { mergeSettings, DEFAULT_HOOKS };
126
+ module.exports = { mergeSettings, DEFAULT_HOOKS, MEMORY_HOOKS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-cabinet",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "description": "Claude Cabinet — opinionated process scaffolding for Claude Code projects",
5
5
  "bin": {
6
6
  "create-claude-cabinet": "bin/create-claude-cabinet.js"
@@ -0,0 +1,43 @@
1
+ #!/bin/bash
2
+ # PostCompact hook — capture session context before it's lost
3
+ #
4
+ # Fires on: manual (/compact) and auto (context limit) compaction
5
+ # Output: read-only (observability only, 10K char cap)
6
+ # Design: D1 (absolute venv path), D2 (never block), D3 (graceful degradation)
7
+
8
+ VENV_PYTHON="$HOME/.claude-cabinet/omega-venv/bin/python3"
9
+ ADAPTER="scripts/cabinet-memory-adapter.py"
10
+
11
+ # D3: graceful degradation — if venv or adapter missing, exit silently
12
+ if [ ! -x "$VENV_PYTHON" ] || [ ! -f "$ADAPTER" ]; then
13
+ exit 0
14
+ fi
15
+
16
+ # Read hook input from stdin (includes compact_summary)
17
+ INPUT=$(cat)
18
+
19
+ # Call adapter — pipe hook input as stdin for capture
20
+ RESULT=$(echo "$INPUT" | "$VENV_PYTHON" "$ADAPTER" capture 2>/dev/null)
21
+
22
+ # Log result for observability (PostCompact output is read-only)
23
+ STORED=$(echo "$RESULT" | "$VENV_PYTHON" -c "
24
+ import sys, json
25
+ try:
26
+ data = json.load(sys.stdin)
27
+ if data.get('ok'):
28
+ count = data.get('stored', 0)
29
+ if count:
30
+ print(f'cabinet-memory: captured {count} memories from compaction')
31
+ else:
32
+ print('cabinet-memory: no memories captured (nothing noteworthy)')
33
+ else:
34
+ print(f'cabinet-memory: {data.get(\"error\", \"unknown error\")}')
35
+ except:
36
+ pass
37
+ " 2>/dev/null)
38
+
39
+ if [ -n "$STORED" ]; then
40
+ echo "$STORED"
41
+ fi
42
+
43
+ exit 0
@@ -0,0 +1,59 @@
1
+ #!/bin/bash
2
+ # SessionStart hook — surface relevant memories via omega
3
+ #
4
+ # Fires on: startup, resume, compact (not clear — fresh context)
5
+ # Output: relevant memories injected into session context
6
+ # Design: D1 (absolute venv path), D2 (never block), D3 (graceful degradation)
7
+
8
+ VENV_PYTHON="$HOME/.claude-cabinet/omega-venv/bin/python3"
9
+ ADAPTER="scripts/cabinet-memory-adapter.py"
10
+
11
+ # D3: graceful degradation — if venv or adapter missing, warn but don't block
12
+ if [ ! -x "$VENV_PYTHON" ] || [ ! -f "$ADAPTER" ]; then
13
+ echo ""
14
+ echo "**Note:** Memory module is installed but omega is not available."
15
+ echo "Semantic memory (decisions, lessons, preferences) is not being captured or recalled."
16
+ echo "Run \`npx create-claude-cabinet\` to set up the omega venv."
17
+ exit 0
18
+ fi
19
+
20
+ # Read hook input from stdin
21
+ INPUT=$(cat)
22
+
23
+ # Extract source field to skip /clear (fresh context, no memories needed)
24
+ SOURCE=$(echo "$INPUT" | "$VENV_PYTHON" -c "
25
+ import sys, json
26
+ try:
27
+ data = json.load(sys.stdin)
28
+ print(data.get('source', 'startup'))
29
+ except:
30
+ print('startup')
31
+ " 2>/dev/null)
32
+
33
+ if [ "$SOURCE" = "clear" ]; then
34
+ exit 0
35
+ fi
36
+
37
+ # Call adapter — pipe hook input as stdin
38
+ RESULT=$(echo "$INPUT" | "$VENV_PYTHON" "$ADAPTER" welcome 2>/dev/null)
39
+
40
+ # Extract context from result
41
+ CONTEXT=$(echo "$RESULT" | "$VENV_PYTHON" -c "
42
+ import sys, json
43
+ try:
44
+ data = json.load(sys.stdin)
45
+ if data.get('ok') and data.get('context'):
46
+ print(data['context'])
47
+ except:
48
+ pass
49
+ " 2>/dev/null)
50
+
51
+ # Output context to stdout — SessionStart hook stdout becomes session context
52
+ if [ -n "$CONTEXT" ]; then
53
+ echo ""
54
+ echo "## Recalled Memories (omega)"
55
+ echo ""
56
+ echo "$CONTEXT"
57
+ fi
58
+
59
+ exit 0
@@ -0,0 +1,57 @@
1
+ # Memory Capture Rules
2
+
3
+ When omega memory is active (check: `~/.claude-cabinet/omega-venv/bin/python3`
4
+ exists and `scripts/cabinet-memory-adapter.py` exists), these rules govern
5
+ what gets captured and when.
6
+
7
+ ## During Sessions — What to Capture
8
+
9
+ Capture to omega when you observe any of these during a session:
10
+
11
+ **Decisions with reasoning.** When the user makes a non-obvious choice
12
+ (architecture, naming, tradeoffs, tool selection), capture the decision
13
+ AND the reasoning. "Chose SQLite over Postgres because single-user,
14
+ no server dependency" — not just "uses SQLite."
15
+
16
+ **Discovered constraints.** When something that seemed possible turns
17
+ out to have a limitation, gotcha, or prerequisite. "Python venv on
18
+ Debian requires separate python3-venv package" — the kind of thing
19
+ that wastes 30 minutes if you don't know it.
20
+
21
+ **User preferences revealed through correction.** When the user says
22
+ "no, not like that" or redirects your approach, capture what they
23
+ actually want. "User prefers single bundled PRs for refactors, not
24
+ many small ones."
25
+
26
+ **Pattern establishment.** When a convention is established for the
27
+ first time — naming pattern, file organization, workflow step. Not
28
+ the convention itself (that's in the code), but that it was a
29
+ deliberate choice.
30
+
31
+ ## How to Capture
32
+
33
+ Use the adapter — never call omega directly from shell:
34
+
35
+ ```bash
36
+ echo '{"text": "the memory", "type": "decision"}' | \
37
+ ~/.claude-cabinet/omega-venv/bin/python3 scripts/cabinet-memory-adapter.py store
38
+ ```
39
+
40
+ Memory types: `decision`, `lesson`, `preference`, `constraint`, `pattern`
41
+
42
+ ## What NOT to Capture
43
+
44
+ - Code patterns visible by reading current files
45
+ - Git history (use `git log`)
46
+ - Anything already in CLAUDE.md or briefing files
47
+ - Ephemeral debugging details
48
+ - Information that changes frequently (use state files instead)
49
+
50
+ ## Capture Cadence
51
+
52
+ Do NOT capture after every interaction. Capture when something worth
53
+ remembering actually happens. Most messages in a session produce nothing
54
+ worth storing. A typical session might generate 0-3 memories.
55
+
56
+ Over-capturing degrades retrieval quality. When in doubt, don't capture.
57
+ The debrief sweep catches anything important that was missed.
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Cabinet Memory Adapter — single Python file wrapping all omega interaction.
4
+
5
+ Called by hook scripts via the venv Python. All omega calls go through here.
6
+ Designed for D2 (never block Claude Code) and D3 (graceful degradation).
7
+
8
+ Usage:
9
+ cabinet-memory-adapter.py <command> [options]
10
+
11
+ Commands:
12
+ welcome Surface relevant memories for session start
13
+ capture Store a memory from hook input (PostCompact)
14
+ store Store a memory directly (called by debrief)
15
+ query Query memories by text
16
+ status Check omega health
17
+
18
+ All commands read JSON from stdin when applicable.
19
+ All commands output JSON to stdout.
20
+ All commands exit 0 even on failure (D2: never block).
21
+ """
22
+
23
+ import json
24
+ import os
25
+ import sys
26
+
27
+ # Disable omega telemetry (PB2: opt-in only, we opt out)
28
+ os.environ["OMEGA_TELEMETRY"] = "0"
29
+
30
+
31
+ def _output(data):
32
+ """Write JSON to stdout and exit cleanly."""
33
+ print(json.dumps(data))
34
+ sys.exit(0)
35
+
36
+
37
+ def _error(msg):
38
+ """Log error to stderr, output empty result, exit 0 (D2: never block)."""
39
+ print(f"cabinet-memory: {msg}", file=sys.stderr)
40
+ _output({"ok": False, "error": msg})
41
+
42
+
43
+ def _read_stdin():
44
+ """Read JSON from stdin, return dict or empty dict on failure."""
45
+ try:
46
+ return json.load(sys.stdin)
47
+ except Exception:
48
+ return {}
49
+
50
+
51
+ def _import_omega():
52
+ """Import omega, returning the module or None on failure."""
53
+ try:
54
+ import omega
55
+ return omega
56
+ except ImportError:
57
+ return None
58
+
59
+
60
+ def cmd_welcome():
61
+ """Surface relevant memories for session start.
62
+
63
+ Reads session context from stdin (session_id, cwd, source).
64
+ Calls omega.welcome() to get relevant memories.
65
+ Outputs memories as context text for SessionStart hook stdout.
66
+ """
67
+ data = _read_stdin()
68
+ omega = _import_omega()
69
+ if not omega:
70
+ _error("omega not available")
71
+ return
72
+
73
+ try:
74
+ cwd = data.get("cwd", os.getcwd())
75
+ project_name = os.path.basename(cwd)
76
+
77
+ result = omega.welcome(project=project_name)
78
+ if not result:
79
+ _output({"ok": True, "context": ""})
80
+ return
81
+
82
+ # welcome() returns a dict with memory count, recent memories, etc.
83
+ if isinstance(result, dict):
84
+ context = (
85
+ result.get("observation_prefix", "")
86
+ or result.get("summary", "")
87
+ or result.get("context", "")
88
+ )
89
+ if not context and result.get("memory_count", 0) == 0:
90
+ _output({"ok": True, "context": ""})
91
+ return
92
+ if not context:
93
+ context = json.dumps(result, indent=2)
94
+ _output({"ok": True, "context": context})
95
+ elif isinstance(result, str):
96
+ _output({"ok": True, "context": result})
97
+ else:
98
+ _output({"ok": True, "context": str(result)})
99
+ except Exception as e:
100
+ _error(f"welcome failed: {e}")
101
+
102
+
103
+ def cmd_capture():
104
+ """Capture context from PostCompact summary.
105
+
106
+ Reads compact_summary from stdin.
107
+ Extracts key decisions, lessons, and reasoning chains.
108
+ Stores them in omega.
109
+ """
110
+ data = _read_stdin()
111
+ omega = _import_omega()
112
+ if not omega:
113
+ _error("omega not available")
114
+ return
115
+
116
+ summary = data.get("compact_summary", "")
117
+ if not summary:
118
+ _output({"ok": True, "stored": 0, "reason": "no summary"})
119
+ return
120
+
121
+ session_id = data.get("session_id", "unknown")
122
+ cwd = data.get("cwd", os.getcwd())
123
+ project_name = os.path.basename(cwd)
124
+
125
+ try:
126
+ result = omega.auto_capture(
127
+ summary,
128
+ event_type="compaction",
129
+ session_id=session_id,
130
+ project=project_name,
131
+ )
132
+ count = 0
133
+ if isinstance(result, dict):
134
+ count = result.get("stored", 0)
135
+ elif isinstance(result, (list, tuple)):
136
+ count = len(result)
137
+
138
+ _output({"ok": True, "stored": count})
139
+ except Exception as e:
140
+ _error(f"capture failed: {e}")
141
+
142
+
143
+ def cmd_store():
144
+ """Store a memory directly.
145
+
146
+ Reads JSON from stdin with fields:
147
+ text: the memory content (required)
148
+ type: event_type for omega (default: "lesson")
149
+ tags: list of tags (stored in metadata, optional)
150
+ """
151
+ data = _read_stdin()
152
+ omega = _import_omega()
153
+ if not omega:
154
+ _error("omega not available")
155
+ return
156
+
157
+ text = data.get("text", "")
158
+ if not text:
159
+ _error("no text provided")
160
+ return
161
+
162
+ try:
163
+ event_type = data.get("type", "lesson")
164
+ metadata = {}
165
+ if data.get("tags"):
166
+ metadata["tags"] = data["tags"]
167
+ result = omega.store(
168
+ text,
169
+ event_type=event_type,
170
+ metadata=metadata if metadata else None,
171
+ )
172
+ _output({"ok": True, "id": result if isinstance(result, str) else None})
173
+ except Exception as e:
174
+ _error(f"store failed: {e}")
175
+
176
+
177
+ def cmd_query():
178
+ """Query memories by text.
179
+
180
+ Reads JSON from stdin with fields:
181
+ text: the query text (required)
182
+ limit: max results (default: 5)
183
+ """
184
+ data = _read_stdin()
185
+ omega = _import_omega()
186
+ if not omega:
187
+ _error("omega not available")
188
+ return
189
+
190
+ text = data.get("text", "")
191
+ if not text:
192
+ _error("no query text provided")
193
+ return
194
+
195
+ try:
196
+ limit = data.get("limit", 5)
197
+ event_type = data.get("type")
198
+ results = omega.query(
199
+ text,
200
+ limit=limit,
201
+ event_type=event_type,
202
+ )
203
+
204
+ # query() returns a formatted string of results
205
+ if isinstance(results, str):
206
+ _output({"ok": True, "results": results})
207
+ elif isinstance(results, (list, tuple)):
208
+ memories = []
209
+ for r in results:
210
+ if isinstance(r, dict):
211
+ memories.append({
212
+ "text": r.get("content", r.get("text", str(r))),
213
+ "type": r.get("event_type", "unknown"),
214
+ "score": r.get("score", 0),
215
+ })
216
+ else:
217
+ memories.append({"text": str(r)})
218
+ _output({"ok": True, "memories": memories})
219
+ else:
220
+ _output({"ok": True, "results": str(results)})
221
+ except Exception as e:
222
+ _error(f"query failed: {e}")
223
+
224
+
225
+ def cmd_status():
226
+ """Check omega health status."""
227
+ omega = _import_omega()
228
+ if not omega:
229
+ _output({"ok": False, "status": "omega not available"})
230
+ return
231
+
232
+ try:
233
+ result = omega.status()
234
+ if isinstance(result, dict):
235
+ _output({"ok": True, **result})
236
+ else:
237
+ _output({"ok": True, "status": str(result)})
238
+ except Exception as e:
239
+ _error(f"status failed: {e}")
240
+
241
+
242
+ COMMANDS = {
243
+ "welcome": cmd_welcome,
244
+ "capture": cmd_capture,
245
+ "store": cmd_store,
246
+ "query": cmd_query,
247
+ "status": cmd_status,
248
+ }
249
+
250
+ if __name__ == "__main__":
251
+ if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS:
252
+ cmds = ", ".join(COMMANDS.keys())
253
+ _error(f"usage: cabinet-memory-adapter.py <{cmds}>")
254
+ else:
255
+ try:
256
+ COMMANDS[sys.argv[1]]()
257
+ except Exception as e:
258
+ # Outermost catch — D2: never block, never crash
259
+ _error(f"unexpected error: {e}")
@@ -384,7 +384,52 @@ directory structure:
384
384
  and the expected state. These are typically easy fixes but indicate an
385
385
  incomplete migration.
386
386
 
387
- ### 8. Anti-Bloat
387
+ ### 8. Memory System Health
388
+
389
+ If the memory module is installed (check `.ccrc.json` modules list for
390
+ `"memory"`), verify the omega memory infrastructure:
391
+
392
+ - **Venv integrity.** Does `~/.claude-cabinet/omega-venv/bin/python3`
393
+ exist? Can it import omega? Run:
394
+ ```bash
395
+ ~/.claude-cabinet/omega-venv/bin/python3 -c "import omega; print('ok')"
396
+ ```
397
+ A broken venv means all memory hooks silently degrade.
398
+
399
+ - **Adapter availability.** Does `scripts/cabinet-memory-adapter.py`
400
+ exist in the project? Without it, hooks have nothing to call.
401
+
402
+ - **Hook registration.** Check `.claude/settings.json` for:
403
+ - `memory-session-start.sh` in `SessionStart` hooks
404
+ - `memory-post-compact.sh` in `PostCompact` hooks
405
+ Missing hooks mean omega was installed but never wired in.
406
+
407
+ - **Omega database.** Run the adapter's `status` command:
408
+ ```bash
409
+ echo '{}' | ~/.claude-cabinet/omega-venv/bin/python3 \
410
+ scripts/cabinet-memory-adapter.py status
411
+ ```
412
+ Check: is the database growing? Zero memories after multiple sessions
413
+ suggests capture isn't working.
414
+
415
+ - **ONNX model presence.** Check `~/.cache/omega/models/` for the
416
+ embedding model directory. Missing model means semantic search falls
417
+ back to hash-based pseudo-embeddings (much lower quality).
418
+
419
+ - **Rules file.** Does `.claude/rules/memory-capture.md` exist? Without
420
+ it, in-session capture guidance is missing.
421
+
422
+ **What to report:** Infrastructure gaps (broken venv, missing hooks,
423
+ missing model), capture failures (zero memories after active sessions),
424
+ configuration mismatches (module installed but hooks not registered).
425
+
426
+ **Severity guidance:**
427
+ - Broken venv or missing adapter → **warn** (all memory silently disabled)
428
+ - Missing hooks → **warn** (capture not happening)
429
+ - Missing ONNX model → **info** (degraded but functional)
430
+ - Zero memories after 3+ sessions → **warn** (capture failing silently)
431
+
432
+ ### 9. Anti-Bloat
388
433
 
389
434
  Apply `_lifecycle.md` retirement criteria proactively. A lean cabinet is
390
435
  better than a comprehensive one with dead weight:
@@ -61,6 +61,20 @@ to help you do your job.
61
61
 
62
62
  ### Sources of Institutional Memory (check in this order)
63
63
 
64
+ 0. **Omega semantic memory** — if `~/.claude-cabinet/omega-venv/bin/python3`
65
+ and `scripts/cabinet-memory-adapter.py` both exist, query omega FIRST.
66
+ It stores decisions, lessons, preferences, and constraints with semantic
67
+ retrieval — meaning you can search by concept, not just keyword.
68
+
69
+ ```bash
70
+ echo '{"text": "your query here", "limit": 10}' | \
71
+ ~/.claude-cabinet/omega-venv/bin/python3 scripts/cabinet-memory-adapter.py query
72
+ ```
73
+
74
+ Omega returns memories ranked by relevance. This is the richest source
75
+ of institutional memory when available. If omega is not available, skip
76
+ to source 1.
77
+
64
78
  1. **Memory files** — `.claude/memory/*.md` and any project-level memory
65
79
  index (e.g., `MEMORY.md`). These are the distilled, catalogued lessons.
66
80
  Check here first. Read the index for orientation, then read relevant
@@ -142,6 +156,18 @@ what was about to happen next. This is the historian's moment.
142
156
  in a session should survive compaction because it's been written
143
157
  down *during* the session, not just summarized after truncation.
144
158
 
159
+ **Omega and compaction:** If omega memory is active, the PostCompact
160
+ hook (`memory-post-compact.sh`) automatically captures key context from
161
+ the compaction summary. After compaction, query omega to see what was
162
+ preserved:
163
+
164
+ ```bash
165
+ echo '{"text": "session context before compaction", "limit": 5}' | \
166
+ ~/.claude-cabinet/omega-venv/bin/python3 scripts/cabinet-memory-adapter.py query
167
+ ```
168
+
169
+ This supplements (not replaces) the manual recovery protocol above.
170
+
145
171
  **The meta-lesson:** Compaction is an entropy event. The historian's
146
172
  job is to ensure the memory system is robust enough that compaction
147
173
  merely loses conversational tone, not institutional knowledge. If
@@ -205,6 +205,26 @@ anything that future sessions need to know? A new pattern, a gotcha,
205
205
  a process gap, a user preference? Lessons are perishable — capture
206
206
  them now while context is fresh.
207
207
 
208
+ **Omega-primary:** If `~/.claude-cabinet/omega-venv/bin/python3` and
209
+ `scripts/cabinet-memory-adapter.py` both exist, write lessons to omega
210
+ as the primary destination — not flat markdown. Use the adapter:
211
+
212
+ ```bash
213
+ echo '{"text": "the lesson", "type": "lesson"}' | \
214
+ ~/.claude-cabinet/omega-venv/bin/python3 scripts/cabinet-memory-adapter.py store
215
+ ```
216
+
217
+ Types: `decision`, `lesson`, `preference`, `constraint`, `pattern`.
218
+ Fall back to flat markdown memory only if omega is unavailable.
219
+
220
+ **Omega broken:** If the memory module is installed (check `.ccrc.json`
221
+ for `"memory": true`) but the venv or adapter is missing, surface this
222
+ in the debrief report:
223
+
224
+ > **⚠ Memory module is installed but omega is not working.**
225
+ > Lessons from this session were saved to flat markdown instead of omega.
226
+ > Run `npx create-claude-cabinet` to rebuild the omega venv.
227
+
208
228
  > **Debrief lessons vs audit findings:** Debrief captures session-specific
209
229
  > learnings — what was discovered while doing this work, what surprised
210
230
  > you, what should change. Audit captures systematic observations from
@@ -12,19 +12,34 @@ Lessons are perishable. A lesson captured while context is fresh is worth
12
12
  ten captured from memory next week. This is why recording happens during
13
13
  debrief, not "sometime later."
14
14
 
15
- ## What to Include
15
+ ## Where to Record — Omega Primary
16
16
 
17
- - **What to look for** — types of lessons worth capturing
18
- - **Where to record them** — memory files, logs, documentation
19
- - **How to organize** — categories, patterns, cross-references
20
- - **What NOT to record** — ephemeral details, things derivable from code
17
+ Check whether omega memory is available:
18
+ - `~/.claude-cabinet/omega-venv/bin/python3` exists AND
19
+ - `scripts/cabinet-memory-adapter.py` exists
21
20
 
22
- ## Example Lesson Recording
21
+ **When omega is available (primary path):** Write lessons to omega via
22
+ the adapter. This is the durable, semantic memory store that persists
23
+ across sessions and supports retrieval by meaning, not just keyword.
23
24
 
24
- Uncomment and adapt these for your project:
25
+ ```bash
26
+ echo '{"text": "the lesson", "type": "lesson", "tags": ["tag1"]}' | \
27
+ ~/.claude-cabinet/omega-venv/bin/python3 scripts/cabinet-memory-adapter.py store
28
+ ```
25
29
 
26
- <!--
27
- ### What to Look For
30
+ Memory types to use:
31
+ - `decision` architectural choices, tradeoff resolutions
32
+ - `lesson` — gotchas, discoveries, things that surprised
33
+ - `preference` — user corrections, style choices, workflow preferences
34
+ - `constraint` — limitations discovered, prerequisites found
35
+ - `pattern` — conventions established, recurring solutions
36
+
37
+ **When omega is NOT available (fallback):** Use the flat markdown memory
38
+ system (auto-memory in `~/.claude/projects/` memory directory). This is
39
+ the same system Claude Code uses natively. It works, but lacks semantic
40
+ retrieval.
41
+
42
+ ## What to Look For
28
43
 
29
44
  Review the session and ask:
30
45
  - Did we learn something future sessions need to know?
@@ -36,32 +51,16 @@ Review the session and ask:
36
51
  of problem keeps recurring, the lesson is "create a prevention mechanism"
37
52
  not just "remember this."
38
53
  - Did the session's work contradict any existing recorded knowledge?
39
- If so, update or remove the stale record.
40
-
41
- ### Where to Record
42
-
43
- **Feedback patterns** (corrections, confirmations):
44
- Write to memory/patterns/ if it matches an existing pattern; write to
45
- memory/archive/ as a raw observation if it's new. If 3+ raw observations
46
- accumulate around a theme, consolidate into a pattern.
47
-
48
- **Project state** (decisions, milestones, architecture changes):
49
- Update the relevant project memory file or create one.
50
-
51
- **User context** (preferences, role changes, domain knowledge):
52
- Update the user context memory file.
53
-
54
- **References** (external resources, tool URLs, account details):
55
- Create or update a reference memory file.
54
+ If so, update or remove the stale record (in omega: use `query` to
55
+ find it, then note the contradiction in the new memory).
56
56
 
57
- ### What NOT to Record
57
+ ## What NOT to Record
58
58
  - Code patterns derivable by reading current files
59
59
  - Git history (use git log)
60
60
  - Debugging solutions (the fix is in the code)
61
61
  - Anything already in CLAUDE.md files
62
62
  - Ephemeral task details only relevant to this session
63
63
 
64
- ### Report What Was Recorded
64
+ ## Report What Was Recorded
65
65
  Tell the user what memories were created or updated so they know what
66
- the system will remember next time.
67
- -->
66
+ the system will remember next time. Include the count and types.
@@ -39,6 +39,38 @@ but know they exist — if work in this session touches something that
39
39
  relates to another project, mention it. "This API change might affect
40
40
  your investor-reports project too."
41
41
 
42
+ ### Omega Semantic Memory
43
+
44
+ If `~/.claude-cabinet/omega-venv/bin/python3` and
45
+ `scripts/cabinet-memory-adapter.py` both exist, surface relevant
46
+ memories from omega at session start:
47
+
48
+ ```bash
49
+ echo '{}' | ~/.claude-cabinet/omega-venv/bin/python3 \
50
+ scripts/cabinet-memory-adapter.py welcome
51
+ ```
52
+
53
+ The `welcome` command returns memories relevant to the current project.
54
+ The SessionStart hook (`memory-session-start.sh`) also does this
55
+ automatically, but during orient you can query for specific context:
56
+
57
+ ```bash
58
+ echo '{"text": "recent decisions and lessons", "limit": 5}' | \
59
+ ~/.claude-cabinet/omega-venv/bin/python3 \
60
+ scripts/cabinet-memory-adapter.py query
61
+ ```
62
+
63
+ If omega is not available, check whether the memory module is installed
64
+ (look for `"memory": true` in `.ccrc.json`). If it IS installed but the
65
+ venv or adapter is missing, surface a warning:
66
+
67
+ > **⚠ Memory module is installed but omega is not available.**
68
+ > The venv at `~/.claude-cabinet/omega-venv/` may be missing or broken.
69
+ > Re-run `npx create-claude-cabinet` to rebuild it.
70
+
71
+ If the memory module is NOT installed, skip silently — the user opted
72
+ out. Fall back to flat markdown memory (MEMORY.md) either way.
73
+
42
74
  ## Additional Context Sources
43
75
 
44
76
  Uncomment and adapt these for your project: