create-claude-cabinet 0.9.0 → 0.11.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 +183 -5
- package/lib/copy.js +1 -0
- package/lib/omega-setup.js +44 -0
- package/lib/settings-merge.js +17 -34
- package/package.json +1 -1
- package/templates/rules/memory-capture.md +32 -39
- package/templates/scripts/cabinet-memory-adapter.py +23 -110
- package/templates/skills/cabinet/SKILL.md +53 -0
- package/templates/skills/cabinet-architecture/SKILL.md +8 -0
- package/templates/skills/cabinet-boundary-man/SKILL.md +5 -0
- package/templates/skills/cabinet-cc-health/SKILL.md +7 -8
- package/templates/skills/cabinet-data-integrity/SKILL.md +1 -0
- package/templates/skills/cabinet-debugger/SKILL.md +6 -0
- package/templates/skills/cabinet-framework-quality/SKILL.md +1 -0
- package/templates/skills/cabinet-historian/SKILL.md +99 -9
- package/templates/skills/cabinet-organized-mind/SKILL.md +6 -0
- package/templates/skills/cabinet-process-therapist/SKILL.md +6 -1
- package/templates/skills/cabinet-qa/SKILL.md +8 -0
- package/templates/skills/cabinet-record-keeper/SKILL.md +6 -1
- package/templates/skills/cabinet-roster-check/SKILL.md +5 -1
- package/templates/skills/cabinet-security/SKILL.md +8 -0
- package/templates/skills/cabinet-speed-freak/SKILL.md +1 -0
- package/templates/skills/cabinet-system-advocate/SKILL.md +8 -0
- package/templates/skills/cabinet-technical-debt/SKILL.md +1 -0
- package/templates/skills/cabinet-usability/SKILL.md +1 -0
- package/templates/skills/cabinet-user-advocate/SKILL.md +5 -0
- package/templates/skills/cabinet-workflow-cop/SKILL.md +1 -0
- package/templates/skills/cc-upgrade/SKILL.md +35 -0
- package/templates/skills/debrief/SKILL.md +74 -27
- package/templates/skills/debrief/phases/record-lessons.md +30 -1
- package/templates/skills/execute/SKILL.md +8 -4
- package/templates/skills/investigate/SKILL.md +15 -4
- package/templates/skills/memory/SKILL.md +79 -15
- package/templates/skills/menu/SKILL.md +19 -24
- package/templates/skills/onboard/SKILL.md +1 -1
- package/templates/skills/onboard/phases/detect-state.md +1 -1
- package/templates/skills/orient/SKILL.md +60 -14
- package/templates/skills/orient/phases/auto-maintenance.md +67 -23
- package/templates/skills/orient/phases/context.md +6 -13
- package/templates/skills/plan/SKILL.md +7 -4
- package/templates/skills/seed/SKILL.md +17 -3
- package/templates/skills/work-tracker/SKILL.md +56 -0
- package/templates/hooks/memory-post-compact.sh +0 -43
- package/templates/hooks/memory-session-start.sh +0 -59
package/lib/cli.js
CHANGED
|
@@ -12,6 +12,177 @@ const { reset } = require('./reset');
|
|
|
12
12
|
|
|
13
13
|
const VERSION = require('../package.json').version;
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Parse YAML frontmatter from a SKILL.md file (between first two --- lines).
|
|
17
|
+
* Returns an object with extracted fields, or null if no frontmatter found.
|
|
18
|
+
* Supports one level of nesting (e.g., directives: { orient: "...", debrief: "..." }).
|
|
19
|
+
*/
|
|
20
|
+
function parseFrontmatter(content) {
|
|
21
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
22
|
+
if (!match) return null;
|
|
23
|
+
const fm = {};
|
|
24
|
+
let currentKey = null;
|
|
25
|
+
let currentValue = '';
|
|
26
|
+
let nestedKey = null; // Parent key when parsing a nested map
|
|
27
|
+
let nestedMap = null; // The nested map being built
|
|
28
|
+
let nestedSubKey = null; // Current sub-key within the nested map
|
|
29
|
+
let nestedSubValue = '';
|
|
30
|
+
|
|
31
|
+
function flushNested() {
|
|
32
|
+
if (nestedSubKey && nestedMap) {
|
|
33
|
+
nestedMap[nestedSubKey] = nestedSubValue.trim();
|
|
34
|
+
nestedSubKey = null;
|
|
35
|
+
nestedSubValue = '';
|
|
36
|
+
}
|
|
37
|
+
if (nestedKey && nestedMap) {
|
|
38
|
+
fm[nestedKey] = nestedMap;
|
|
39
|
+
nestedKey = null;
|
|
40
|
+
nestedMap = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function flushCurrent() {
|
|
45
|
+
if (currentKey) {
|
|
46
|
+
fm[currentKey] = currentValue.trim();
|
|
47
|
+
currentKey = null;
|
|
48
|
+
currentValue = '';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const line of match[1].split('\n')) {
|
|
53
|
+
// Inside a nested map?
|
|
54
|
+
if (nestedKey) {
|
|
55
|
+
// Nested sub-key (indented key: value)
|
|
56
|
+
const subKv = line.match(/^ ([a-z][a-z0-9_-]*)\s*:\s*(.*)/);
|
|
57
|
+
if (subKv) {
|
|
58
|
+
// Save previous sub-key
|
|
59
|
+
if (nestedSubKey) {
|
|
60
|
+
nestedMap[nestedSubKey] = nestedSubValue.trim();
|
|
61
|
+
}
|
|
62
|
+
nestedSubKey = subKv[1];
|
|
63
|
+
nestedSubValue = subKv[2].replace(/^[>|]\s*$/, '');
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Continuation of nested sub-value (deeply indented)
|
|
67
|
+
if (nestedSubKey && /^ /.test(line)) {
|
|
68
|
+
nestedSubValue += ' ' + line.trim();
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Not indented — end of nested map
|
|
72
|
+
if (nestedSubKey) {
|
|
73
|
+
nestedMap[nestedSubKey] = nestedSubValue.trim();
|
|
74
|
+
nestedSubKey = null;
|
|
75
|
+
nestedSubValue = '';
|
|
76
|
+
}
|
|
77
|
+
fm[nestedKey] = nestedMap;
|
|
78
|
+
nestedKey = null;
|
|
79
|
+
nestedMap = null;
|
|
80
|
+
// Fall through to parse this line as a top-level key
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Continuation line (indented, for scalar values)
|
|
84
|
+
if (currentKey && /^[\s]/.test(line)) {
|
|
85
|
+
currentValue += ' ' + line.trim();
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
flushCurrent();
|
|
90
|
+
|
|
91
|
+
// New key-value pair
|
|
92
|
+
const kvMatch = line.match(/^([a-z][a-z0-9_-]*)\s*:\s*(.*)/);
|
|
93
|
+
if (kvMatch) {
|
|
94
|
+
const val = kvMatch[2].trim();
|
|
95
|
+
// Empty value after colon = start of nested map or block scalar
|
|
96
|
+
if (val === '' || val === '>' || val === '|') {
|
|
97
|
+
// Peek: could be a nested map or a block scalar.
|
|
98
|
+
// We'll treat it as a nested map if the next indented line has a colon.
|
|
99
|
+
// For now, start as nested and fall back if no sub-keys found.
|
|
100
|
+
nestedKey = kvMatch[1];
|
|
101
|
+
nestedMap = {};
|
|
102
|
+
currentKey = null;
|
|
103
|
+
} else {
|
|
104
|
+
currentKey = kvMatch[1];
|
|
105
|
+
currentValue = val;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Flush remaining
|
|
110
|
+
if (nestedSubKey && nestedMap) {
|
|
111
|
+
nestedMap[nestedSubKey] = nestedSubValue.trim();
|
|
112
|
+
}
|
|
113
|
+
if (nestedKey && nestedMap) {
|
|
114
|
+
fm[nestedKey] = Object.keys(nestedMap).length > 0 ? nestedMap : '';
|
|
115
|
+
}
|
|
116
|
+
flushCurrent();
|
|
117
|
+
return fm;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Generate .claude/skills/_index.json from all installed SKILL.md files.
|
|
122
|
+
* Consumers (menu, cabinet, audit, plan, execute) read this instead of
|
|
123
|
+
* scanning and parsing dozens of individual files.
|
|
124
|
+
*/
|
|
125
|
+
function generateSkillIndex(projectDir) {
|
|
126
|
+
const skillsDir = path.join(projectDir, '.claude', 'skills');
|
|
127
|
+
if (!fs.existsSync(skillsDir)) return 0;
|
|
128
|
+
|
|
129
|
+
const entries = [];
|
|
130
|
+
const dirs = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
131
|
+
for (const dir of dirs) {
|
|
132
|
+
if (!dir.isDirectory()) continue;
|
|
133
|
+
const skillFile = path.join(skillsDir, dir.name, 'SKILL.md');
|
|
134
|
+
if (!fs.existsSync(skillFile)) continue;
|
|
135
|
+
|
|
136
|
+
const content = fs.readFileSync(skillFile, 'utf8');
|
|
137
|
+
const fm = parseFrontmatter(content);
|
|
138
|
+
if (!fm || !fm.name) continue;
|
|
139
|
+
|
|
140
|
+
// Extract first sentence of description (before "Use when:")
|
|
141
|
+
let shortDesc = (fm.description || '').replace(/\s*Use when:.*$/is, '').trim();
|
|
142
|
+
// Collapse to first sentence
|
|
143
|
+
const sentenceEnd = shortDesc.match(/\.\s/);
|
|
144
|
+
if (sentenceEnd) shortDesc = shortDesc.slice(0, sentenceEnd.index + 1);
|
|
145
|
+
|
|
146
|
+
const isCabinet = dir.name.startsWith('cabinet-');
|
|
147
|
+
const entry = {
|
|
148
|
+
name: fm.name,
|
|
149
|
+
path: `.claude/skills/${dir.name}/SKILL.md`,
|
|
150
|
+
description: shortDesc,
|
|
151
|
+
type: isCabinet ? 'cabinet' : 'workflow',
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Invocability flags
|
|
155
|
+
if (fm['disable-model-invocation'] === 'true') entry.manual = true;
|
|
156
|
+
if (fm['user-invocable'] === 'false') entry.userInvocable = false;
|
|
157
|
+
|
|
158
|
+
// Standing mandate (for audit/plan/execute/orient/debrief member selection)
|
|
159
|
+
if (fm['standing-mandate']) {
|
|
160
|
+
entry.standingMandate = fm['standing-mandate'].split(/,\s*/).map(s => s.trim());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Directives (scoped tasks for orient/debrief/etc.)
|
|
164
|
+
if (fm.directives && typeof fm.directives === 'object') {
|
|
165
|
+
entry.directives = fm.directives;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
entries.push(entry);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
172
|
+
|
|
173
|
+
const index = {
|
|
174
|
+
skills: entries,
|
|
175
|
+
generatedAt: new Date().toISOString(),
|
|
176
|
+
version: VERSION,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
fs.writeFileSync(
|
|
180
|
+
path.join(skillsDir, '_index.json'),
|
|
181
|
+
JSON.stringify(index, null, 2) + '\n'
|
|
182
|
+
);
|
|
183
|
+
return entries.length;
|
|
184
|
+
}
|
|
185
|
+
|
|
15
186
|
const MODULES = {
|
|
16
187
|
'session-loop': {
|
|
17
188
|
name: 'Session Loop (orient + debrief)',
|
|
@@ -33,7 +204,7 @@ const MODULES = {
|
|
|
33
204
|
mandatory: false,
|
|
34
205
|
default: true,
|
|
35
206
|
lean: false,
|
|
36
|
-
templates: ['scripts/pib-db.js', 'scripts/pib-db-schema.sql', 'scripts/work-tracker-server.mjs', 'scripts/work-tracker-ui.html'],
|
|
207
|
+
templates: ['scripts/pib-db.js', 'scripts/pib-db-schema.sql', 'scripts/work-tracker-server.mjs', 'scripts/work-tracker-ui.html', 'skills/work-tracker'],
|
|
37
208
|
needsDb: true,
|
|
38
209
|
},
|
|
39
210
|
'planning': {
|
|
@@ -59,7 +230,7 @@ const MODULES = {
|
|
|
59
230
|
default: true,
|
|
60
231
|
lean: true,
|
|
61
232
|
templates: [
|
|
62
|
-
'skills/audit', 'skills/pulse', 'skills/triage-audit',
|
|
233
|
+
'skills/audit', 'skills/pulse', 'skills/triage-audit', 'skills/cabinet',
|
|
63
234
|
'cabinet', 'briefing',
|
|
64
235
|
'skills/cabinet-accessibility', 'skills/cabinet-anti-confirmation',
|
|
65
236
|
'skills/cabinet-architecture', 'skills/cabinet-boundary-man',
|
|
@@ -103,7 +274,7 @@ const MODULES = {
|
|
|
103
274
|
default: true,
|
|
104
275
|
lean: false,
|
|
105
276
|
needsOmega: true,
|
|
106
|
-
templates: ['skills/memory', '
|
|
277
|
+
templates: ['skills/memory', 'scripts/cabinet-memory-adapter.py', 'rules/memory-capture.md'],
|
|
107
278
|
},
|
|
108
279
|
};
|
|
109
280
|
|
|
@@ -558,8 +729,7 @@ async function run() {
|
|
|
558
729
|
|
|
559
730
|
// --- Merge hooks into settings.json ---
|
|
560
731
|
if (selectedModules.includes('hooks') && !flags.dryRun) {
|
|
561
|
-
const
|
|
562
|
-
const settingsPath = mergeSettings(projectDir, { includeDb, includeMemory });
|
|
732
|
+
const settingsPath = mergeSettings(projectDir, { includeDb });
|
|
563
733
|
console.log(` ⚙️ Merged hooks into ${path.relative(projectDir, settingsPath)}`);
|
|
564
734
|
}
|
|
565
735
|
|
|
@@ -737,6 +907,14 @@ async function run() {
|
|
|
737
907
|
}
|
|
738
908
|
}
|
|
739
909
|
|
|
910
|
+
// --- Generate skill index ---
|
|
911
|
+
if (!flags.dryRun) {
|
|
912
|
+
const indexCount = generateSkillIndex(projectDir);
|
|
913
|
+
if (indexCount > 0) {
|
|
914
|
+
console.log(` 📇 Indexed ${indexCount} skills in .claude/skills/_index.json`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
740
918
|
// --- Write metadata ---
|
|
741
919
|
if (!flags.dryRun) {
|
|
742
920
|
createMetadata(projectDir, {
|
package/lib/copy.js
CHANGED
|
@@ -75,6 +75,7 @@ async function walkAndCopy(srcRoot, destRoot, currentSrc, results, dryRun, skipC
|
|
|
75
75
|
if (!dryRun) fs.copyFileSync(srcPath, destPath);
|
|
76
76
|
results.overwritten.push(relPath);
|
|
77
77
|
results.manifest[relPath] = incomingHash;
|
|
78
|
+
console.log(` Updated: ${displayPath}`);
|
|
78
79
|
} else {
|
|
79
80
|
results.skipped.push(relPath);
|
|
80
81
|
// Record the hash of what's actually on disk, not the template —
|
package/lib/omega-setup.js
CHANGED
|
@@ -106,6 +106,21 @@ function setupOmega() {
|
|
|
106
106
|
try {
|
|
107
107
|
execSync(`"${VENV_PYTHON}" -c "import omega"`, { stdio: 'pipe' });
|
|
108
108
|
results.push('Existing omega venv is valid');
|
|
109
|
+
// Ensure cross-encoder model is downloaded (added in v0.9.1)
|
|
110
|
+
try {
|
|
111
|
+
const hasReranker = execSync(
|
|
112
|
+
`"${VENV_PYTHON}" -c "from omega.reranker import _get_model_dir; print('yes' if _get_model_dir() else 'no')"`,
|
|
113
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
114
|
+
).trim();
|
|
115
|
+
if (hasReranker === 'no') {
|
|
116
|
+
console.log(' Downloading cross-encoder model...');
|
|
117
|
+
execSync(`"${VENV_PYTHON}" -c "from omega.reranker import download_model; download_model()"`, {
|
|
118
|
+
stdio: 'pipe',
|
|
119
|
+
timeout: 120000,
|
|
120
|
+
});
|
|
121
|
+
results.push('Downloaded cross-encoder reranker model');
|
|
122
|
+
}
|
|
123
|
+
} catch { /* non-fatal */ }
|
|
109
124
|
return results;
|
|
110
125
|
} catch {
|
|
111
126
|
// Venv is broken — nuke and rebuild (D5)
|
|
@@ -135,6 +150,35 @@ function setupOmega() {
|
|
|
135
150
|
});
|
|
136
151
|
results.push('Downloaded ONNX embedding model (bge-small-en-v1.5)');
|
|
137
152
|
|
|
153
|
+
// 6. Download cross-encoder reranker model (improves query result ranking)
|
|
154
|
+
console.log(' Downloading cross-encoder model...');
|
|
155
|
+
try {
|
|
156
|
+
execSync(`"${VENV_PYTHON}" -c "from omega.reranker import download_model; download_model()"`, {
|
|
157
|
+
stdio: 'pipe',
|
|
158
|
+
timeout: 120000,
|
|
159
|
+
});
|
|
160
|
+
results.push('Downloaded cross-encoder reranker model');
|
|
161
|
+
} catch {
|
|
162
|
+
// Non-fatal — queries work without it, just less accurate ranking
|
|
163
|
+
results.push('Cross-encoder model download skipped (optional)');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 7. Configure omega native hooks in global settings
|
|
167
|
+
// Omega hooks live in ~/.claude/settings.json (global) — they run for all
|
|
168
|
+
// projects and handle memory capture/recall natively. This is idempotent:
|
|
169
|
+
// omega checks if hooks already exist before adding them.
|
|
170
|
+
console.log(' Configuring omega hooks...');
|
|
171
|
+
try {
|
|
172
|
+
execSync(`"${VENV_PYTHON}" -m omega.cli hooks setup`, {
|
|
173
|
+
stdio: 'pipe',
|
|
174
|
+
timeout: 30000,
|
|
175
|
+
});
|
|
176
|
+
results.push('Configured omega native hooks (global settings)');
|
|
177
|
+
} catch {
|
|
178
|
+
// Non-fatal — hooks can be set up manually with `omega hooks setup`
|
|
179
|
+
results.push('Omega hooks setup skipped (run `omega hooks setup` manually)');
|
|
180
|
+
}
|
|
181
|
+
|
|
138
182
|
return results;
|
|
139
183
|
}
|
|
140
184
|
|
package/lib/settings-merge.js
CHANGED
|
@@ -1,31 +1,6 @@
|
|
|
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
|
-
|
|
29
4
|
const DEFAULT_HOOKS = {
|
|
30
5
|
PreToolUse: [
|
|
31
6
|
{
|
|
@@ -75,7 +50,7 @@ const DEFAULT_HOOKS = {
|
|
|
75
50
|
* Merge PIB hooks into the project's .claude/settings.json.
|
|
76
51
|
* Creates the file if it doesn't exist. Preserves existing hooks.
|
|
77
52
|
*/
|
|
78
|
-
function mergeSettings(projectDir, { includeDb = true
|
|
53
|
+
function mergeSettings(projectDir, { includeDb = true } = {}) {
|
|
79
54
|
const settingsDir = path.join(projectDir, '.claude');
|
|
80
55
|
const settingsPath = path.join(settingsDir, 'settings.json');
|
|
81
56
|
|
|
@@ -90,16 +65,24 @@ function mergeSettings(projectDir, { includeDb = true, includeMemory = false } =
|
|
|
90
65
|
|
|
91
66
|
if (!settings.hooks) settings.hooks = {};
|
|
92
67
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
68
|
+
// Remove legacy CC memory hooks (v0.9.x and earlier).
|
|
69
|
+
// These are now handled by omega's native hooks in global settings.
|
|
70
|
+
const LEGACY_MEMORY_COMMANDS = [
|
|
71
|
+
'memory-session-start.sh',
|
|
72
|
+
'memory-post-compact.sh',
|
|
73
|
+
];
|
|
74
|
+
for (const [event, entries] of Object.entries(settings.hooks)) {
|
|
75
|
+
if (!Array.isArray(entries)) continue;
|
|
76
|
+
settings.hooks[event] = entries.filter(entry => {
|
|
77
|
+
if (!entry.hooks || !Array.isArray(entry.hooks)) return true;
|
|
78
|
+
return !entry.hooks.some(h =>
|
|
79
|
+
LEGACY_MEMORY_COMMANDS.some(cmd => (h.command || '').includes(cmd))
|
|
80
|
+
);
|
|
81
|
+
});
|
|
99
82
|
}
|
|
100
83
|
|
|
101
84
|
// Merge each hook event type
|
|
102
|
-
for (const [event, newHooks] of Object.entries(
|
|
85
|
+
for (const [event, newHooks] of Object.entries(DEFAULT_HOOKS)) {
|
|
103
86
|
if (!settings.hooks[event]) {
|
|
104
87
|
settings.hooks[event] = newHooks;
|
|
105
88
|
} else {
|
|
@@ -123,4 +106,4 @@ function mergeSettings(projectDir, { includeDb = true, includeMemory = false } =
|
|
|
123
106
|
return settingsPath;
|
|
124
107
|
}
|
|
125
108
|
|
|
126
|
-
module.exports = { mergeSettings, DEFAULT_HOOKS
|
|
109
|
+
module.exports = { mergeSettings, DEFAULT_HOOKS };
|
package/package.json
CHANGED
|
@@ -1,43 +1,42 @@
|
|
|
1
1
|
# Memory Capture Rules
|
|
2
2
|
|
|
3
|
-
When omega memory is active (check:
|
|
4
|
-
|
|
5
|
-
what gets captured and when.
|
|
3
|
+
When omega memory is active (check: `omega hooks doctor` reports OK),
|
|
4
|
+
these rules govern what gets captured and when.
|
|
6
5
|
|
|
7
|
-
##
|
|
6
|
+
## How Capture Works
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
Omega handles memory capture natively through its hooks in
|
|
9
|
+
`~/.claude/settings.json` (global). No project-level hook scripts needed.
|
|
10
10
|
|
|
11
|
-
**
|
|
12
|
-
(
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
**Automatic capture (omega native hooks):**
|
|
12
|
+
- `auto_capture` (UserPromptSubmit) — detects decisions/lessons from user messages in real time
|
|
13
|
+
- `assistant_capture` (Stop) — extracts insights from assistant responses at session end
|
|
14
|
+
- `session_stop` (Stop) — session summary, activity report, auto-reflection
|
|
15
|
+
- `surface_memories` (PostToolUse) — surfaces relevant memories before file edits
|
|
15
16
|
|
|
16
|
-
**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
**Manual capture (adapter or omega MCP tools):**
|
|
18
|
+
- Use `omega_store()` MCP tool directly, or
|
|
19
|
+
- Use the adapter for project-scoped storage:
|
|
20
|
+
```bash
|
|
21
|
+
echo '{"text": "the memory", "type": "decision"}' | \
|
|
22
|
+
~/.claude-cabinet/omega-venv/bin/python3 scripts/cabinet-memory-adapter.py store
|
|
23
|
+
```
|
|
20
24
|
|
|
21
|
-
**
|
|
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
|
+
**Memory types:** `decision`, `lesson_learned`, `user_preference`, `constraint`, `error_pattern`
|
|
25
26
|
|
|
26
|
-
|
|
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.
|
|
27
|
+
## What to Capture Manually
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
Omega's auto_capture hook catches many decisions and lessons from
|
|
30
|
+
conversation flow. Manual capture is for things the hooks miss:
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
**Decisions with reasoning.** Non-obvious architectural choices where
|
|
33
|
+
the "why" matters as much as the "what."
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
~/.claude-cabinet/omega-venv/bin/python3 scripts/cabinet-memory-adapter.py store
|
|
38
|
-
```
|
|
35
|
+
**Discovered constraints.** Limitations or gotchas that waste time
|
|
36
|
+
if you don't know them in advance.
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
**User preferences revealed through correction.** When the user
|
|
39
|
+
redirects your approach — capture what they actually want.
|
|
41
40
|
|
|
42
41
|
## What NOT to Capture
|
|
43
42
|
|
|
@@ -49,17 +48,11 @@ Memory types: `decision`, `lesson`, `preference`, `constraint`, `pattern`
|
|
|
49
48
|
|
|
50
49
|
## Capture Cadence
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Cadence scales with session length and discovery density. A short
|
|
57
|
-
focused session might produce 0-1 memories. A long session with
|
|
58
|
-
multiple discoveries, corrections, and decisions could produce 5-10+.
|
|
59
|
-
The right number is however many genuinely worth-remembering things
|
|
60
|
-
happened — no artificial cap.
|
|
51
|
+
Omega's native hooks handle most capture automatically. Manual capture
|
|
52
|
+
should be rare — only when something important happened that the hooks
|
|
53
|
+
wouldn't detect (e.g., a nuanced architectural decision discussed
|
|
54
|
+
verbally, or a constraint discovered through external research).
|
|
61
55
|
|
|
62
56
|
Over-capturing degrades retrieval quality. The test: *"Would a future
|
|
63
57
|
session benefit from knowing this?"* If yes, capture it. If it's just
|
|
64
|
-
noise or ephemera, skip it.
|
|
65
|
-
important that was missed during the session.
|
|
58
|
+
noise or ephemera, skip it.
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
Cabinet Memory Adapter — single Python file wrapping
|
|
3
|
+
Cabinet Memory Adapter — single Python file wrapping omega interaction.
|
|
4
4
|
|
|
5
|
-
Called by
|
|
5
|
+
Called by skills and scripts via the venv Python. Provides project-scoped
|
|
6
|
+
tiered retrieval (omega's main gap) and a stable JSON-in/JSON-out interface.
|
|
6
7
|
Designed for D2 (never block Claude Code) and D3 (graceful degradation).
|
|
7
8
|
|
|
9
|
+
Note: Session-level hooks (welcome, capture, session start/stop) are handled
|
|
10
|
+
by omega's native hooks configured in ~/.claude/settings.json (global).
|
|
11
|
+
This adapter handles skill-invoked operations only.
|
|
12
|
+
|
|
8
13
|
Usage:
|
|
9
14
|
cabinet-memory-adapter.py <command> [options]
|
|
10
15
|
|
|
11
16
|
Commands:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
store Store a memory directly (called by debrief)
|
|
15
|
-
query Query memories by text
|
|
17
|
+
store Store a memory directly (called by debrief, /memory)
|
|
18
|
+
query Query memories by text (with project-scoped tiering)
|
|
16
19
|
delete Delete a memory by full node_id
|
|
17
20
|
list List all memories with full node_ids
|
|
18
|
-
status Check omega health
|
|
19
21
|
|
|
20
22
|
All commands read JSON from stdin when applicable.
|
|
21
23
|
All commands output JSON to stdout.
|
|
@@ -59,87 +61,15 @@ def _import_omega():
|
|
|
59
61
|
return None
|
|
60
62
|
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if not omega:
|
|
72
|
-
_error("omega not available")
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
try:
|
|
76
|
-
cwd = data.get("cwd", os.getcwd())
|
|
77
|
-
project_name = os.path.basename(cwd)
|
|
78
|
-
|
|
79
|
-
result = omega.welcome(project=project_name)
|
|
80
|
-
if not result:
|
|
81
|
-
_output({"ok": True, "context": ""})
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
# welcome() returns a dict with memory count, recent memories, etc.
|
|
85
|
-
if isinstance(result, dict):
|
|
86
|
-
context = (
|
|
87
|
-
result.get("observation_prefix", "")
|
|
88
|
-
or result.get("summary", "")
|
|
89
|
-
or result.get("context", "")
|
|
90
|
-
)
|
|
91
|
-
if not context and result.get("memory_count", 0) == 0:
|
|
92
|
-
_output({"ok": True, "context": ""})
|
|
93
|
-
return
|
|
94
|
-
if not context:
|
|
95
|
-
context = json.dumps(result, indent=2)
|
|
96
|
-
_output({"ok": True, "context": context})
|
|
97
|
-
elif isinstance(result, str):
|
|
98
|
-
_output({"ok": True, "context": result})
|
|
99
|
-
else:
|
|
100
|
-
_output({"ok": True, "context": str(result)})
|
|
101
|
-
except Exception as e:
|
|
102
|
-
_error(f"welcome failed: {e}")
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def cmd_capture():
|
|
106
|
-
"""Capture context from PostCompact summary.
|
|
107
|
-
|
|
108
|
-
Reads compact_summary from stdin.
|
|
109
|
-
Extracts key decisions, lessons, and reasoning chains.
|
|
110
|
-
Stores them in omega.
|
|
111
|
-
"""
|
|
112
|
-
data = _read_stdin()
|
|
113
|
-
omega = _import_omega()
|
|
114
|
-
if not omega:
|
|
115
|
-
_error("omega not available")
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
summary = data.get("compact_summary", "")
|
|
119
|
-
if not summary:
|
|
120
|
-
_output({"ok": True, "stored": 0, "reason": "no summary"})
|
|
121
|
-
return
|
|
122
|
-
|
|
123
|
-
session_id = data.get("session_id", "unknown")
|
|
124
|
-
cwd = data.get("cwd", os.getcwd())
|
|
125
|
-
project_name = os.path.basename(cwd)
|
|
126
|
-
|
|
127
|
-
try:
|
|
128
|
-
result = omega.auto_capture(
|
|
129
|
-
summary,
|
|
130
|
-
event_type="compaction",
|
|
131
|
-
session_id=session_id,
|
|
132
|
-
project=project_name,
|
|
133
|
-
)
|
|
134
|
-
count = 0
|
|
135
|
-
if isinstance(result, dict):
|
|
136
|
-
count = result.get("stored", 0)
|
|
137
|
-
elif isinstance(result, (list, tuple)):
|
|
138
|
-
count = len(result)
|
|
139
|
-
|
|
140
|
-
_output({"ok": True, "stored": count})
|
|
141
|
-
except Exception as e:
|
|
142
|
-
_error(f"capture failed: {e}")
|
|
64
|
+
# Map friendly type names to omega's native event_type values.
|
|
65
|
+
# This ensures permanent TTL for types that should never expire.
|
|
66
|
+
_TYPE_MAP = {
|
|
67
|
+
"lesson": "lesson_learned",
|
|
68
|
+
"preference": "user_preference",
|
|
69
|
+
"error": "error_pattern",
|
|
70
|
+
# These already match omega's native types:
|
|
71
|
+
# "decision", "constraint", "error_pattern", "lesson_learned", "user_preference"
|
|
72
|
+
}
|
|
143
73
|
|
|
144
74
|
|
|
145
75
|
def cmd_store():
|
|
@@ -147,7 +77,9 @@ def cmd_store():
|
|
|
147
77
|
|
|
148
78
|
Reads JSON from stdin with fields:
|
|
149
79
|
text: the memory content (required)
|
|
150
|
-
type: event_type for omega (default: "
|
|
80
|
+
type: event_type for omega (default: "lesson_learned")
|
|
81
|
+
Accepts friendly names: lesson, preference, error
|
|
82
|
+
which are mapped to omega native types.
|
|
151
83
|
tags: list of tags (stored in metadata, optional)
|
|
152
84
|
project: project name (default: basename of cwd)
|
|
153
85
|
"""
|
|
@@ -163,7 +95,8 @@ def cmd_store():
|
|
|
163
95
|
return
|
|
164
96
|
|
|
165
97
|
try:
|
|
166
|
-
|
|
98
|
+
raw_type = data.get("type", "lesson")
|
|
99
|
+
event_type = _TYPE_MAP.get(raw_type, raw_type)
|
|
167
100
|
project = data.get("project", os.path.basename(os.getcwd()))
|
|
168
101
|
metadata = {}
|
|
169
102
|
if data.get("tags"):
|
|
@@ -335,23 +268,6 @@ def cmd_query():
|
|
|
335
268
|
_error(f"query failed: {e}")
|
|
336
269
|
|
|
337
270
|
|
|
338
|
-
def cmd_status():
|
|
339
|
-
"""Check omega health status."""
|
|
340
|
-
omega = _import_omega()
|
|
341
|
-
if not omega:
|
|
342
|
-
_output({"ok": False, "status": "omega not available"})
|
|
343
|
-
return
|
|
344
|
-
|
|
345
|
-
try:
|
|
346
|
-
result = omega.status()
|
|
347
|
-
if isinstance(result, dict):
|
|
348
|
-
_output({"ok": True, **result})
|
|
349
|
-
else:
|
|
350
|
-
_output({"ok": True, "status": str(result)})
|
|
351
|
-
except Exception as e:
|
|
352
|
-
_error(f"status failed: {e}")
|
|
353
|
-
|
|
354
|
-
|
|
355
271
|
def cmd_delete():
|
|
356
272
|
"""Delete a memory by its full node_id.
|
|
357
273
|
|
|
@@ -433,13 +349,10 @@ def cmd_list():
|
|
|
433
349
|
|
|
434
350
|
|
|
435
351
|
COMMANDS = {
|
|
436
|
-
"welcome": cmd_welcome,
|
|
437
|
-
"capture": cmd_capture,
|
|
438
352
|
"store": cmd_store,
|
|
439
353
|
"query": cmd_query,
|
|
440
354
|
"delete": cmd_delete,
|
|
441
355
|
"list": cmd_list,
|
|
442
|
-
"status": cmd_status,
|
|
443
356
|
}
|
|
444
357
|
|
|
445
358
|
if __name__ == "__main__":
|