create-claude-cabinet 0.8.5 → 0.10.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 +156 -13
- package/lib/copy.js +14 -0
- package/lib/omega-setup.js +48 -3
- 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/scripts/work-tracker-server.mjs +188 -0
- package/templates/scripts/work-tracker-ui.html +948 -0
- package/templates/skills/cabinet-cc-health/SKILL.md +7 -8
- package/templates/skills/cabinet-historian/SKILL.md +84 -9
- package/templates/skills/cc-upgrade/SKILL.md +97 -0
- package/templates/skills/debrief/SKILL.md +39 -4
- package/templates/skills/debrief/phases/close-work.md +33 -0
- package/templates/skills/debrief/phases/record-lessons.md +30 -1
- package/templates/skills/execute-plans/SKILL.md +273 -0
- package/templates/skills/execute-plans/scripts/build-conflict-graph.js +281 -0
- package/templates/skills/memory/SKILL.md +79 -15
- package/templates/skills/onboard/SKILL.md +1 -1
- package/templates/skills/onboard/phases/detect-state.md +1 -1
- package/templates/skills/orient/SKILL.md +71 -13
- 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 +11 -2
- package/templates/scripts/__pycache__/cabinet-memory-adapter.cpython-314.pyc +0 -0
package/lib/cli.js
CHANGED
|
@@ -33,7 +33,7 @@ const MODULES = {
|
|
|
33
33
|
mandatory: false,
|
|
34
34
|
default: true,
|
|
35
35
|
lean: false,
|
|
36
|
-
templates: ['scripts/pib-db.js', 'scripts/pib-db-schema.sql'],
|
|
36
|
+
templates: ['scripts/pib-db.js', 'scripts/pib-db-schema.sql', 'scripts/work-tracker-server.mjs', 'scripts/work-tracker-ui.html'],
|
|
37
37
|
needsDb: true,
|
|
38
38
|
},
|
|
39
39
|
'planning': {
|
|
@@ -42,7 +42,7 @@ const MODULES = {
|
|
|
42
42
|
mandatory: false,
|
|
43
43
|
default: true,
|
|
44
44
|
lean: true,
|
|
45
|
-
templates: ['skills/plan', 'skills/execute', 'skills/investigate'],
|
|
45
|
+
templates: ['skills/plan', 'skills/execute', 'skills/execute-plans', 'skills/investigate'],
|
|
46
46
|
},
|
|
47
47
|
'compliance': {
|
|
48
48
|
name: 'Compliance Stack (rules + enforcement)',
|
|
@@ -103,10 +103,26 @@ const MODULES = {
|
|
|
103
103
|
default: true,
|
|
104
104
|
lean: false,
|
|
105
105
|
needsOmega: true,
|
|
106
|
-
templates: ['skills/memory', '
|
|
106
|
+
templates: ['skills/memory', 'scripts/cabinet-memory-adapter.py', 'rules/memory-capture.md'],
|
|
107
107
|
},
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
+
/** Recursively collect all relative file paths under a directory. */
|
|
111
|
+
function walkDir(dir, base) {
|
|
112
|
+
if (!base) base = dir;
|
|
113
|
+
const results = [];
|
|
114
|
+
if (!fs.existsSync(dir)) return results;
|
|
115
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
116
|
+
const full = path.join(dir, entry.name);
|
|
117
|
+
if (entry.isDirectory()) {
|
|
118
|
+
results.push(...walkDir(full, base));
|
|
119
|
+
} else {
|
|
120
|
+
results.push(path.relative(base, full));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return results;
|
|
124
|
+
}
|
|
125
|
+
|
|
110
126
|
// Signals that a directory contains a real project (not just empty)
|
|
111
127
|
const PROJECT_SIGNALS = [
|
|
112
128
|
'package.json', 'Cargo.toml', 'requirements.txt', 'pyproject.toml',
|
|
@@ -542,8 +558,7 @@ async function run() {
|
|
|
542
558
|
|
|
543
559
|
// --- Merge hooks into settings.json ---
|
|
544
560
|
if (selectedModules.includes('hooks') && !flags.dryRun) {
|
|
545
|
-
const
|
|
546
|
-
const settingsPath = mergeSettings(projectDir, { includeDb, includeMemory });
|
|
561
|
+
const settingsPath = mergeSettings(projectDir, { includeDb });
|
|
547
562
|
console.log(` ⚙️ Merged hooks into ${path.relative(projectDir, settingsPath)}`);
|
|
548
563
|
}
|
|
549
564
|
|
|
@@ -572,24 +587,152 @@ async function run() {
|
|
|
572
587
|
}
|
|
573
588
|
}
|
|
574
589
|
|
|
575
|
-
// ---
|
|
576
|
-
//
|
|
577
|
-
//
|
|
590
|
+
// --- Manifest key migration (act:d1f16bee) ---
|
|
591
|
+
// When CC renames directories (e.g., perspectives/ → cabinet-*/), old manifest
|
|
592
|
+
// keys no longer match new template paths. Migrate keys BEFORE cleanup so the
|
|
593
|
+
// cleanup loop doesn't treat renamed files as removed.
|
|
594
|
+
if (Object.keys(existingManifest).length > 0) {
|
|
595
|
+
const existing = readMetadata(projectDir);
|
|
596
|
+
const fromVersion = existing.version;
|
|
597
|
+
let migrationCount = 0;
|
|
598
|
+
// v0.5.x → v0.6.x: perspectives/ → cabinet-*/
|
|
599
|
+
if (fromVersion && /^0\.[0-5]\./.test(fromVersion)) {
|
|
600
|
+
for (const key of Object.keys(existingManifest)) {
|
|
601
|
+
const match = key.match(/\.claude\/skills\/perspectives\/([^/]+)\//);
|
|
602
|
+
if (match) {
|
|
603
|
+
const newKey = key.replace(`perspectives/${match[1]}/`, `cabinet-${match[1]}/`);
|
|
604
|
+
existingManifest[newKey] = existingManifest[key];
|
|
605
|
+
delete existingManifest[key];
|
|
606
|
+
migrationCount++;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
// Future manifest key migrations go here
|
|
611
|
+
if (migrationCount > 0) {
|
|
612
|
+
console.log(` 🔄 Migrated ${migrationCount} manifest key${migrationCount === 1 ? '' : 's'} for directory rename`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// --- Clean up files removed upstream (safeguarded) ---
|
|
617
|
+
// Four safeguards prevent the v0.6.8 incident from recurring:
|
|
618
|
+
// S1: Classify — only delete files that map to a known CC template path
|
|
619
|
+
// S2: Scope — only delete files from modules the user selected
|
|
620
|
+
// S3: Itemize — list all deletions and confirm before proceeding
|
|
621
|
+
// S4: Backup — copy files to .cc-backup/<timestamp>/ before deletion
|
|
578
622
|
if (Object.keys(existingManifest).length > 0) {
|
|
579
|
-
|
|
623
|
+
// S1: Build complete template path set (ALL modules) for classification
|
|
624
|
+
const allTemplatePaths = new Set();
|
|
625
|
+
for (const mod of Object.values(MODULES)) {
|
|
626
|
+
for (const tmpl of mod.templates || []) {
|
|
627
|
+
const srcPath = path.join(templateRoot, tmpl);
|
|
628
|
+
if (fs.existsSync(srcPath) && fs.statSync(srcPath).isDirectory()) {
|
|
629
|
+
for (const rel of walkDir(srcPath)) {
|
|
630
|
+
allTemplatePaths.add(manifestPath(tmpl) + '/' + rel);
|
|
631
|
+
}
|
|
632
|
+
} else if (fs.existsSync(srcPath)) {
|
|
633
|
+
allTemplatePaths.add(manifestPath(tmpl));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// S2: Build selected-module template paths for scoping
|
|
639
|
+
const selectedTemplatePaths = new Set();
|
|
640
|
+
for (const modKey of selectedModules) {
|
|
641
|
+
const mod = MODULES[modKey];
|
|
642
|
+
for (const tmpl of mod.templates || []) {
|
|
643
|
+
const srcPath = path.join(templateRoot, tmpl);
|
|
644
|
+
if (fs.existsSync(srcPath) && fs.statSync(srcPath).isDirectory()) {
|
|
645
|
+
for (const rel of walkDir(srcPath)) {
|
|
646
|
+
selectedTemplatePaths.add(manifestPath(tmpl) + '/' + rel);
|
|
647
|
+
}
|
|
648
|
+
} else if (fs.existsSync(srcPath)) {
|
|
649
|
+
selectedTemplatePaths.add(manifestPath(tmpl));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Build CC template skill name set for project-skill guard (act:4ac281ba)
|
|
655
|
+
const ccSkillNames = new Set();
|
|
656
|
+
for (const mod of Object.values(MODULES)) {
|
|
657
|
+
for (const tmpl of mod.templates || []) {
|
|
658
|
+
if (tmpl.startsWith('skills/')) {
|
|
659
|
+
const skillName = tmpl.split('/')[1];
|
|
660
|
+
ccSkillNames.add(skillName);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Collect files that would be removed
|
|
666
|
+
const toRemove = [];
|
|
580
667
|
for (const oldPath of Object.keys(existingManifest)) {
|
|
581
668
|
if (!allManifest[oldPath]) {
|
|
582
669
|
// Skip phase files — they may be user-customized
|
|
583
670
|
if (/\/phases\//.test(oldPath)) continue;
|
|
671
|
+
|
|
672
|
+
// S1: Only delete if the path maps to a known CC template
|
|
673
|
+
if (!allTemplatePaths.has(oldPath)) {
|
|
674
|
+
console.log(` Keeping non-template file: ${oldPath}`);
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// S2: Only delete from selected modules (don't purge deselected modules)
|
|
679
|
+
if (!selectedTemplatePaths.has(oldPath)) {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Project-skill guard: never delete skills CC didn't create
|
|
684
|
+
const skillDirMatch = oldPath.match(/^\.claude\/skills\/([^/]+)\//);
|
|
685
|
+
if (skillDirMatch && !ccSkillNames.has(skillDirMatch[1])) {
|
|
686
|
+
console.log(` Keeping project skill: ${oldPath}`);
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
|
|
584
690
|
const fullPath = path.join(projectDir, oldPath);
|
|
585
691
|
if (fs.existsSync(fullPath)) {
|
|
586
|
-
|
|
587
|
-
totalRemoved++;
|
|
692
|
+
toRemove.push(oldPath);
|
|
588
693
|
}
|
|
589
694
|
}
|
|
590
695
|
}
|
|
591
|
-
|
|
592
|
-
|
|
696
|
+
|
|
697
|
+
// S3: Itemize and confirm before deleting
|
|
698
|
+
if (toRemove.length > 0) {
|
|
699
|
+
console.log(`\n Files no longer in upstream (${toRemove.length}):`);
|
|
700
|
+
for (const f of toRemove) {
|
|
701
|
+
console.log(` - ${f}`);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
let confirmed = flags.yes;
|
|
705
|
+
if (!confirmed && !flags.dryRun) {
|
|
706
|
+
const response = await prompts({
|
|
707
|
+
type: 'confirm',
|
|
708
|
+
name: 'confirmed',
|
|
709
|
+
message: `Remove ${toRemove.length} file${toRemove.length === 1 ? '' : 's'}?`,
|
|
710
|
+
initial: false,
|
|
711
|
+
});
|
|
712
|
+
confirmed = response.confirmed;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (confirmed && !flags.dryRun) {
|
|
716
|
+
// S4: Backup before deleting
|
|
717
|
+
const backupDir = path.join(projectDir, '.cc-backup', new Date().toISOString().replace(/[:.]/g, '-'));
|
|
718
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
719
|
+
for (const filePath of toRemove) {
|
|
720
|
+
const src = path.join(projectDir, filePath);
|
|
721
|
+
const dest = path.join(backupDir, filePath);
|
|
722
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
723
|
+
fs.copyFileSync(src, dest);
|
|
724
|
+
}
|
|
725
|
+
console.log(` 📦 Backed up ${toRemove.length} file${toRemove.length === 1 ? '' : 's'} to ${path.relative(projectDir, backupDir)}/`);
|
|
726
|
+
|
|
727
|
+
for (const filePath of toRemove) {
|
|
728
|
+
fs.unlinkSync(path.join(projectDir, filePath));
|
|
729
|
+
}
|
|
730
|
+
console.log(` 🧹 Removed ${toRemove.length} file${toRemove.length === 1 ? '' : 's'} no longer in upstream`);
|
|
731
|
+
} else if (flags.dryRun) {
|
|
732
|
+
console.log(` [dry run — ${toRemove.length} file${toRemove.length === 1 ? '' : 's'} would be removed]`);
|
|
733
|
+
} else {
|
|
734
|
+
console.log(' Skipped file removal.');
|
|
735
|
+
}
|
|
593
736
|
}
|
|
594
737
|
}
|
|
595
738
|
|
package/lib/copy.js
CHANGED
|
@@ -45,6 +45,19 @@ async function walkAndCopy(srcRoot, destRoot, currentSrc, results, dryRun, skipC
|
|
|
45
45
|
if (fs.existsSync(destPath)) {
|
|
46
46
|
const existing = fs.readFileSync(destPath, 'utf8');
|
|
47
47
|
|
|
48
|
+
// Phase file guard (act:98d74381) — independent of skipPhases flag.
|
|
49
|
+
// If a phase file on disk has been customized (differs from template
|
|
50
|
+
// and isn't empty), never overwrite it regardless of other flags.
|
|
51
|
+
if (relPath.includes('phases' + path.sep) || relPath.includes('phases/')) {
|
|
52
|
+
const trimmedExisting = existing.trim();
|
|
53
|
+
if (trimmedExisting !== '' && trimmedExisting !== incoming.trim()) {
|
|
54
|
+
results.skipped.push(relPath);
|
|
55
|
+
results.manifest[relPath] = hashContent(existing);
|
|
56
|
+
console.log(` Preserved customized phase: ${displayPath}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
if (existing === incoming) {
|
|
49
62
|
results.skipped.push(relPath);
|
|
50
63
|
results.manifest[relPath] = incomingHash;
|
|
@@ -62,6 +75,7 @@ async function walkAndCopy(srcRoot, destRoot, currentSrc, results, dryRun, skipC
|
|
|
62
75
|
if (!dryRun) fs.copyFileSync(srcPath, destPath);
|
|
63
76
|
results.overwritten.push(relPath);
|
|
64
77
|
results.manifest[relPath] = incomingHash;
|
|
78
|
+
console.log(` Updated: ${displayPath}`);
|
|
65
79
|
} else {
|
|
66
80
|
results.skipped.push(relPath);
|
|
67
81
|
// Record the hash of what's actually on disk, not the template —
|
package/lib/omega-setup.js
CHANGED
|
@@ -7,17 +7,18 @@ const OMEGA_HOME = path.join(os.homedir(), '.claude-cabinet');
|
|
|
7
7
|
const VENV_DIR = path.join(OMEGA_HOME, 'omega-venv');
|
|
8
8
|
const VENV_PYTHON = path.join(VENV_DIR, 'bin', 'python3');
|
|
9
9
|
|
|
10
|
-
// Ordered by preference:
|
|
10
|
+
// Ordered by preference: 3.13 first (ONNX runtime segfaults on 3.14 during
|
|
11
|
+
// shutdown — exit 139, triggers macOS crash dialogs). 3.14 last as fallback.
|
|
11
12
|
// Apple Silicon paths, then Intel Mac paths, then PATH fallback.
|
|
12
13
|
const PYTHON_CANDIDATES = [
|
|
13
|
-
'/opt/homebrew/opt/python@3.14/bin/python3.14',
|
|
14
14
|
'/opt/homebrew/opt/python@3.13/bin/python3.13',
|
|
15
15
|
'/opt/homebrew/opt/python@3.12/bin/python3.12',
|
|
16
16
|
'/opt/homebrew/opt/python@3.11/bin/python3.11',
|
|
17
|
-
'/
|
|
17
|
+
'/opt/homebrew/opt/python@3.14/bin/python3.14',
|
|
18
18
|
'/usr/local/opt/python@3.13/bin/python3.13',
|
|
19
19
|
'/usr/local/opt/python@3.12/bin/python3.12',
|
|
20
20
|
'/usr/local/opt/python@3.11/bin/python3.11',
|
|
21
|
+
'/usr/local/opt/python@3.14/bin/python3.14',
|
|
21
22
|
'/opt/homebrew/bin/python3',
|
|
22
23
|
'/usr/local/bin/python3',
|
|
23
24
|
];
|
|
@@ -105,6 +106,21 @@ function setupOmega() {
|
|
|
105
106
|
try {
|
|
106
107
|
execSync(`"${VENV_PYTHON}" -c "import omega"`, { stdio: 'pipe' });
|
|
107
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 */ }
|
|
108
124
|
return results;
|
|
109
125
|
} catch {
|
|
110
126
|
// Venv is broken — nuke and rebuild (D5)
|
|
@@ -134,6 +150,35 @@ function setupOmega() {
|
|
|
134
150
|
});
|
|
135
151
|
results.push('Downloaded ONNX embedding model (bge-small-en-v1.5)');
|
|
136
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
|
+
|
|
137
182
|
return results;
|
|
138
183
|
}
|
|
139
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.
|