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 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', 'hooks/memory-session-start.sh', 'hooks/memory-post-compact.sh', 'scripts/cabinet-memory-adapter.py', 'rules/memory-capture.md'],
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 includeMemory = selectedModules.includes('memory');
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
- // --- Clean up files removed upstream ---
576
- // Phase files are excluded from the manifest (they're user-customized),
577
- // so skip them during cleanup even if they were in the old manifest.
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
- let totalRemoved = 0;
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
- if (!flags.dryRun) fs.unlinkSync(fullPath);
587
- totalRemoved++;
692
+ toRemove.push(oldPath);
588
693
  }
589
694
  }
590
695
  }
591
- if (totalRemoved > 0) {
592
- console.log(` 🧹 Removed ${totalRemoved} file${totalRemoved === 1 ? '' : 's'} no longer in upstream`);
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 —
@@ -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: stable Cellar symlinks first, newest Python first.
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
- '/usr/local/opt/python@3.14/bin/python3.14',
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
 
@@ -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, includeMemory = false } = {}) {
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
- // 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
- }
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(allHooks)) {
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, MEMORY_HOOKS };
109
+ module.exports = { mergeSettings, DEFAULT_HOOKS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-cabinet",
3
- "version": "0.8.5",
3
+ "version": "0.10.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"
@@ -1,43 +1,42 @@
1
1
  # Memory Capture Rules
2
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.
3
+ When omega memory is active (check: `omega hooks doctor` reports OK),
4
+ these rules govern what gets captured and when.
6
5
 
7
- ## During Sessions — What to Capture
6
+ ## How Capture Works
8
7
 
9
- Capture to omega when you observe any of these during a session:
8
+ Omega handles memory capture natively through its hooks in
9
+ `~/.claude/settings.json` (global). No project-level hook scripts needed.
10
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."
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
- **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.
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
- **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
+ **Memory types:** `decision`, `lesson_learned`, `user_preference`, `constraint`, `error_pattern`
25
26
 
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.
27
+ ## What to Capture Manually
30
28
 
31
- ## How to Capture
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
- Use the adapter never call omega directly from shell:
32
+ **Decisions with reasoning.** Non-obvious architectural choices where
33
+ the "why" matters as much as the "what."
34
34
 
35
- ```bash
36
- echo '{"text": "the memory", "type": "decision"}' | \
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
- Memory types: `decision`, `lesson`, `preference`, `constraint`, `pattern`
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
- Do NOT capture after every interaction. Capture when something worth
53
- remembering actually happens. Most messages in a session produce nothing
54
- worth storing.
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. The debrief sweep catches anything
65
- important that was missed during the session.
58
+ noise or ephemera, skip it.