shieldcortex 2.0.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/LICENSE +21 -0
- package/README.md +282 -0
- package/dashboard/components.json +22 -0
- package/dashboard/eslint.config.mjs +42 -0
- package/dashboard/next.config.ts +7 -0
- package/dashboard/package-lock.json +8053 -0
- package/dashboard/package.json +44 -0
- package/dashboard/postcss.config.mjs +7 -0
- package/dashboard/public/file.svg +1 -0
- package/dashboard/public/globe.svg +1 -0
- package/dashboard/public/next.svg +1 -0
- package/dashboard/public/vercel.svg +1 -0
- package/dashboard/public/window.svg +1 -0
- package/dashboard/scripts/ensure-api.mjs +76 -0
- package/dashboard/src/app/error.tsx +49 -0
- package/dashboard/src/app/favicon.ico +0 -0
- package/dashboard/src/app/globals.css +130 -0
- package/dashboard/src/app/layout.tsx +35 -0
- package/dashboard/src/app/page.tsx +364 -0
- package/dashboard/src/components/Providers.tsx +27 -0
- package/dashboard/src/components/brain/ActivityPulseSystem.tsx +229 -0
- package/dashboard/src/components/brain/BrainMesh.tsx +133 -0
- package/dashboard/src/components/brain/BrainRegions.tsx +254 -0
- package/dashboard/src/components/brain/BrainScene.tsx +255 -0
- package/dashboard/src/components/brain/CategoryLabels.tsx +103 -0
- package/dashboard/src/components/brain/CoreSphere.tsx +215 -0
- package/dashboard/src/components/brain/DataFlowParticles.tsx +123 -0
- package/dashboard/src/components/brain/DataStreamRings.tsx +161 -0
- package/dashboard/src/components/brain/ElectronFlow.tsx +323 -0
- package/dashboard/src/components/brain/HolographicGrid.tsx +235 -0
- package/dashboard/src/components/brain/MemoryLinks.tsx +271 -0
- package/dashboard/src/components/brain/MemoryNode.tsx +245 -0
- package/dashboard/src/components/brain/NeuralPathways.tsx +441 -0
- package/dashboard/src/components/brain/SynapseNodes.tsx +312 -0
- package/dashboard/src/components/brain/TimelineControls.tsx +205 -0
- package/dashboard/src/components/chip/ChipScene.tsx +497 -0
- package/dashboard/src/components/chip/ChipSubstrate.tsx +238 -0
- package/dashboard/src/components/chip/CortexCore.tsx +210 -0
- package/dashboard/src/components/chip/DataBus.tsx +416 -0
- package/dashboard/src/components/chip/MemoryCell.tsx +225 -0
- package/dashboard/src/components/chip/MemoryGrid.tsx +328 -0
- package/dashboard/src/components/chip/QuantumCell.tsx +316 -0
- package/dashboard/src/components/chip/SectionLabel.tsx +113 -0
- package/dashboard/src/components/chip/index.ts +14 -0
- package/dashboard/src/components/controls/ControlPanel.tsx +106 -0
- package/dashboard/src/components/controls/VersionPanel.tsx +185 -0
- package/dashboard/src/components/dashboard/StatsPanel.tsx +164 -0
- package/dashboard/src/components/debug/ActivityLog.tsx +250 -0
- package/dashboard/src/components/debug/DebugPanel.tsx +101 -0
- package/dashboard/src/components/debug/QueryTester.tsx +192 -0
- package/dashboard/src/components/debug/RelationshipGraph.tsx +403 -0
- package/dashboard/src/components/debug/SqlConsole.tsx +319 -0
- package/dashboard/src/components/graph/KnowledgeGraph.tsx +230 -0
- package/dashboard/src/components/graph/OntologyGraph.tsx +631 -0
- package/dashboard/src/components/insights/ActivityHeatmap.tsx +131 -0
- package/dashboard/src/components/insights/InsightsView.tsx +46 -0
- package/dashboard/src/components/insights/KnowledgeMapPanel.tsx +80 -0
- package/dashboard/src/components/insights/QualityPanel.tsx +116 -0
- package/dashboard/src/components/memories/MemoriesView.tsx +150 -0
- package/dashboard/src/components/memories/MemoryCard.tsx +103 -0
- package/dashboard/src/components/memory/MemoryDetail.tsx +325 -0
- package/dashboard/src/components/nav/NavRail.tsx +54 -0
- package/dashboard/src/components/ui/button.tsx +62 -0
- package/dashboard/src/components/ui/card.tsx +92 -0
- package/dashboard/src/components/ui/input.tsx +21 -0
- package/dashboard/src/hooks/useDebouncedValue.ts +24 -0
- package/dashboard/src/hooks/useMemories.ts +458 -0
- package/dashboard/src/hooks/useSuggestions.ts +46 -0
- package/dashboard/src/lib/category-colors.ts +84 -0
- package/dashboard/src/lib/position-algorithm.ts +177 -0
- package/dashboard/src/lib/simplex-noise.ts +217 -0
- package/dashboard/src/lib/store.ts +88 -0
- package/dashboard/src/lib/utils.ts +6 -0
- package/dashboard/src/lib/websocket.ts +249 -0
- package/dashboard/src/types/memory.ts +73 -0
- package/dashboard/tsconfig.json +34 -0
- package/dist/__tests__/consolidation-merge.test.d.ts +9 -0
- package/dist/__tests__/consolidation-merge.test.d.ts.map +1 -0
- package/dist/__tests__/consolidation-merge.test.js +137 -0
- package/dist/__tests__/consolidation-merge.test.js.map +1 -0
- package/dist/__tests__/contradictions.test.d.ts +8 -0
- package/dist/__tests__/contradictions.test.d.ts.map +1 -0
- package/dist/__tests__/contradictions.test.js +78 -0
- package/dist/__tests__/contradictions.test.js.map +1 -0
- package/dist/__tests__/salience-evolution.test.d.ts +7 -0
- package/dist/__tests__/salience-evolution.test.d.ts.map +1 -0
- package/dist/__tests__/salience-evolution.test.js +151 -0
- package/dist/__tests__/salience-evolution.test.js.map +1 -0
- package/dist/__tests__/store.test.d.ts +7 -0
- package/dist/__tests__/store.test.d.ts.map +1 -0
- package/dist/__tests__/store.test.js +582 -0
- package/dist/__tests__/store.test.js.map +1 -0
- package/dist/api/control.d.ts +27 -0
- package/dist/api/control.d.ts.map +1 -0
- package/dist/api/control.js +60 -0
- package/dist/api/control.js.map +1 -0
- package/dist/api/events.d.ts +159 -0
- package/dist/api/events.d.ts.map +1 -0
- package/dist/api/events.js +155 -0
- package/dist/api/events.js.map +1 -0
- package/dist/api/version.d.ts +36 -0
- package/dist/api/version.d.ts.map +1 -0
- package/dist/api/version.js +146 -0
- package/dist/api/version.js.map +1 -0
- package/dist/api/visualization-server.d.ts +11 -0
- package/dist/api/visualization-server.d.ts.map +1 -0
- package/dist/api/visualization-server.js +1186 -0
- package/dist/api/visualization-server.js.map +1 -0
- package/dist/context/project-context.d.ts +57 -0
- package/dist/context/project-context.d.ts.map +1 -0
- package/dist/context/project-context.js +135 -0
- package/dist/context/project-context.js.map +1 -0
- package/dist/database/init.d.ts +49 -0
- package/dist/database/init.d.ts.map +1 -0
- package/dist/database/init.js +567 -0
- package/dist/database/init.js.map +1 -0
- package/dist/defence/__tests__/firewall.test.d.ts +8 -0
- package/dist/defence/__tests__/firewall.test.d.ts.map +1 -0
- package/dist/defence/__tests__/firewall.test.js +123 -0
- package/dist/defence/__tests__/firewall.test.js.map +1 -0
- package/dist/defence/__tests__/fragmentation.test.d.ts +7 -0
- package/dist/defence/__tests__/fragmentation.test.d.ts.map +1 -0
- package/dist/defence/__tests__/fragmentation.test.js +51 -0
- package/dist/defence/__tests__/fragmentation.test.js.map +1 -0
- package/dist/defence/__tests__/pipeline.test.d.ts +8 -0
- package/dist/defence/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/defence/__tests__/pipeline.test.js +61 -0
- package/dist/defence/__tests__/pipeline.test.js.map +1 -0
- package/dist/defence/__tests__/sensitivity.test.d.ts +7 -0
- package/dist/defence/__tests__/sensitivity.test.d.ts.map +1 -0
- package/dist/defence/__tests__/sensitivity.test.js +61 -0
- package/dist/defence/__tests__/sensitivity.test.js.map +1 -0
- package/dist/defence/__tests__/trust.test.d.ts +7 -0
- package/dist/defence/__tests__/trust.test.d.ts.map +1 -0
- package/dist/defence/__tests__/trust.test.js +49 -0
- package/dist/defence/__tests__/trust.test.js.map +1 -0
- package/dist/defence/audit/index.d.ts +4 -0
- package/dist/defence/audit/index.d.ts.map +1 -0
- package/dist/defence/audit/index.js +3 -0
- package/dist/defence/audit/index.js.map +1 -0
- package/dist/defence/audit/logger.d.ts +14 -0
- package/dist/defence/audit/logger.d.ts.map +1 -0
- package/dist/defence/audit/logger.js +54 -0
- package/dist/defence/audit/logger.js.map +1 -0
- package/dist/defence/audit/queries.d.ts +33 -0
- package/dist/defence/audit/queries.d.ts.map +1 -0
- package/dist/defence/audit/queries.js +103 -0
- package/dist/defence/audit/queries.js.map +1 -0
- package/dist/defence/firewall/anomaly-scorer.d.ts +8 -0
- package/dist/defence/firewall/anomaly-scorer.d.ts.map +1 -0
- package/dist/defence/firewall/anomaly-scorer.js +58 -0
- package/dist/defence/firewall/anomaly-scorer.js.map +1 -0
- package/dist/defence/firewall/encoding-detector.d.ts +13 -0
- package/dist/defence/firewall/encoding-detector.d.ts.map +1 -0
- package/dist/defence/firewall/encoding-detector.js +120 -0
- package/dist/defence/firewall/encoding-detector.js.map +1 -0
- package/dist/defence/firewall/index.d.ts +21 -0
- package/dist/defence/firewall/index.d.ts.map +1 -0
- package/dist/defence/firewall/index.js +133 -0
- package/dist/defence/firewall/index.js.map +1 -0
- package/dist/defence/firewall/instruction-detector.d.ts +12 -0
- package/dist/defence/firewall/instruction-detector.d.ts.map +1 -0
- package/dist/defence/firewall/instruction-detector.js +99 -0
- package/dist/defence/firewall/instruction-detector.js.map +1 -0
- package/dist/defence/firewall/privilege-detector.d.ts +13 -0
- package/dist/defence/firewall/privilege-detector.d.ts.map +1 -0
- package/dist/defence/firewall/privilege-detector.js +89 -0
- package/dist/defence/firewall/privilege-detector.js.map +1 -0
- package/dist/defence/fragmentation/assembly-detector.d.ts +18 -0
- package/dist/defence/fragmentation/assembly-detector.d.ts.map +1 -0
- package/dist/defence/fragmentation/assembly-detector.js +72 -0
- package/dist/defence/fragmentation/assembly-detector.js.map +1 -0
- package/dist/defence/fragmentation/entity-extractor.d.ts +19 -0
- package/dist/defence/fragmentation/entity-extractor.d.ts.map +1 -0
- package/dist/defence/fragmentation/entity-extractor.js +86 -0
- package/dist/defence/fragmentation/entity-extractor.js.map +1 -0
- package/dist/defence/fragmentation/index.d.ts +23 -0
- package/dist/defence/fragmentation/index.d.ts.map +1 -0
- package/dist/defence/fragmentation/index.js +49 -0
- package/dist/defence/fragmentation/index.js.map +1 -0
- package/dist/defence/fragmentation/temporal-analyzer.d.ts +28 -0
- package/dist/defence/fragmentation/temporal-analyzer.d.ts.map +1 -0
- package/dist/defence/fragmentation/temporal-analyzer.js +41 -0
- package/dist/defence/fragmentation/temporal-analyzer.js.map +1 -0
- package/dist/defence/index.d.ts +12 -0
- package/dist/defence/index.d.ts.map +1 -0
- package/dist/defence/index.js +18 -0
- package/dist/defence/index.js.map +1 -0
- package/dist/defence/pipeline.d.ts +9 -0
- package/dist/defence/pipeline.d.ts.map +1 -0
- package/dist/defence/pipeline.js +115 -0
- package/dist/defence/pipeline.js.map +1 -0
- package/dist/defence/scanner/index.d.ts +5 -0
- package/dist/defence/scanner/index.d.ts.map +1 -0
- package/dist/defence/scanner/index.js +5 -0
- package/dist/defence/scanner/index.js.map +1 -0
- package/dist/defence/scanner/scan-existing.d.ts +34 -0
- package/dist/defence/scanner/scan-existing.d.ts.map +1 -0
- package/dist/defence/scanner/scan-existing.js +136 -0
- package/dist/defence/scanner/scan-existing.js.map +1 -0
- package/dist/defence/sensitivity/classifier.d.ts +6 -0
- package/dist/defence/sensitivity/classifier.d.ts.map +1 -0
- package/dist/defence/sensitivity/classifier.js +50 -0
- package/dist/defence/sensitivity/classifier.js.map +1 -0
- package/dist/defence/sensitivity/index.d.ts +11 -0
- package/dist/defence/sensitivity/index.d.ts.map +1 -0
- package/dist/defence/sensitivity/index.js +13 -0
- package/dist/defence/sensitivity/index.js.map +1 -0
- package/dist/defence/sensitivity/patterns.d.ts +14 -0
- package/dist/defence/sensitivity/patterns.d.ts.map +1 -0
- package/dist/defence/sensitivity/patterns.js +67 -0
- package/dist/defence/sensitivity/patterns.js.map +1 -0
- package/dist/defence/sensitivity/redaction.d.ts +17 -0
- package/dist/defence/sensitivity/redaction.d.ts.map +1 -0
- package/dist/defence/sensitivity/redaction.js +47 -0
- package/dist/defence/sensitivity/redaction.js.map +1 -0
- package/dist/defence/trust/index.d.ts +3 -0
- package/dist/defence/trust/index.d.ts.map +1 -0
- package/dist/defence/trust/index.js +3 -0
- package/dist/defence/trust/index.js.map +1 -0
- package/dist/defence/trust/recall-filter.d.ts +10 -0
- package/dist/defence/trust/recall-filter.d.ts.map +1 -0
- package/dist/defence/trust/recall-filter.js +38 -0
- package/dist/defence/trust/recall-filter.js.map +1 -0
- package/dist/defence/trust/source-scorer.d.ts +6 -0
- package/dist/defence/trust/source-scorer.d.ts.map +1 -0
- package/dist/defence/trust/source-scorer.js +34 -0
- package/dist/defence/trust/source-scorer.js.map +1 -0
- package/dist/defence/types.d.ts +88 -0
- package/dist/defence/types.d.ts.map +1 -0
- package/dist/defence/types.js +15 -0
- package/dist/defence/types.js.map +1 -0
- package/dist/embeddings/generator.d.ts +20 -0
- package/dist/embeddings/generator.d.ts.map +1 -0
- package/dist/embeddings/generator.js +83 -0
- package/dist/embeddings/generator.js.map +1 -0
- package/dist/embeddings/index.d.ts +2 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +2 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/errors.d.ts +74 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +131 -0
- package/dist/errors.js.map +1 -0
- package/dist/graph/backfill.d.ts +6 -0
- package/dist/graph/backfill.d.ts.map +1 -0
- package/dist/graph/backfill.js +33 -0
- package/dist/graph/backfill.js.map +1 -0
- package/dist/graph/extract.d.ts +21 -0
- package/dist/graph/extract.d.ts.map +1 -0
- package/dist/graph/extract.js +231 -0
- package/dist/graph/extract.js.map +1 -0
- package/dist/graph/resolve.d.ts +6 -0
- package/dist/graph/resolve.d.ts.map +1 -0
- package/dist/graph/resolve.js +126 -0
- package/dist/graph/resolve.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +248 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/activation.d.ts +69 -0
- package/dist/memory/activation.d.ts.map +1 -0
- package/dist/memory/activation.js +168 -0
- package/dist/memory/activation.js.map +1 -0
- package/dist/memory/consolidate.d.ts +98 -0
- package/dist/memory/consolidate.d.ts.map +1 -0
- package/dist/memory/consolidate.js +511 -0
- package/dist/memory/consolidate.js.map +1 -0
- package/dist/memory/contradiction.d.ts +69 -0
- package/dist/memory/contradiction.d.ts.map +1 -0
- package/dist/memory/contradiction.js +286 -0
- package/dist/memory/contradiction.js.map +1 -0
- package/dist/memory/decay.d.ts +62 -0
- package/dist/memory/decay.d.ts.map +1 -0
- package/dist/memory/decay.js +184 -0
- package/dist/memory/decay.js.map +1 -0
- package/dist/memory/salience.d.ts +36 -0
- package/dist/memory/salience.d.ts.map +1 -0
- package/dist/memory/salience.js +216 -0
- package/dist/memory/salience.js.map +1 -0
- package/dist/memory/similarity.d.ts +57 -0
- package/dist/memory/similarity.d.ts.map +1 -0
- package/dist/memory/similarity.js +114 -0
- package/dist/memory/similarity.js.map +1 -0
- package/dist/memory/store.d.ts +179 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +1184 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/types.d.ts +97 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +30 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +568 -0
- package/dist/server.js.map +1 -0
- package/dist/service/install.d.ts +15 -0
- package/dist/service/install.d.ts.map +1 -0
- package/dist/service/install.js +178 -0
- package/dist/service/install.js.map +1 -0
- package/dist/service/templates.d.ts +13 -0
- package/dist/service/templates.d.ts.map +1 -0
- package/dist/service/templates.js +58 -0
- package/dist/service/templates.js.map +1 -0
- package/dist/setup/claude-md.d.ts +12 -0
- package/dist/setup/claude-md.d.ts.map +1 -0
- package/dist/setup/claude-md.js +68 -0
- package/dist/setup/claude-md.js.map +1 -0
- package/dist/setup/clawdbot.d.ts +15 -0
- package/dist/setup/clawdbot.d.ts.map +1 -0
- package/dist/setup/clawdbot.js +118 -0
- package/dist/setup/clawdbot.js.map +1 -0
- package/dist/setup/doctor.d.ts +5 -0
- package/dist/setup/doctor.d.ts.map +1 -0
- package/dist/setup/doctor.js +141 -0
- package/dist/setup/doctor.js.map +1 -0
- package/dist/setup/hooks.d.ts +6 -0
- package/dist/setup/hooks.d.ts.map +1 -0
- package/dist/setup/hooks.js +36 -0
- package/dist/setup/hooks.js.map +1 -0
- package/dist/setup/migrate.d.ts +16 -0
- package/dist/setup/migrate.d.ts.map +1 -0
- package/dist/setup/migrate.js +164 -0
- package/dist/setup/migrate.js.map +1 -0
- package/dist/setup/settings-hooks.d.ts +7 -0
- package/dist/setup/settings-hooks.d.ts.map +1 -0
- package/dist/setup/settings-hooks.js +83 -0
- package/dist/setup/settings-hooks.js.map +1 -0
- package/dist/setup/uninstall.d.ts +12 -0
- package/dist/setup/uninstall.d.ts.map +1 -0
- package/dist/setup/uninstall.js +125 -0
- package/dist/setup/uninstall.js.map +1 -0
- package/dist/tools/context.d.ts +135 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +273 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/forget.d.ts +53 -0
- package/dist/tools/forget.d.ts.map +1 -0
- package/dist/tools/forget.js +179 -0
- package/dist/tools/forget.js.map +1 -0
- package/dist/tools/graph.d.ts +46 -0
- package/dist/tools/graph.d.ts.map +1 -0
- package/dist/tools/graph.js +206 -0
- package/dist/tools/graph.js.map +1 -0
- package/dist/tools/recall.d.ts +79 -0
- package/dist/tools/recall.d.ts.map +1 -0
- package/dist/tools/recall.js +156 -0
- package/dist/tools/recall.js.map +1 -0
- package/dist/tools/remember.d.ts +83 -0
- package/dist/tools/remember.d.ts.map +1 -0
- package/dist/tools/remember.js +151 -0
- package/dist/tools/remember.js.map +1 -0
- package/dist/worker/brain-worker.d.ts +100 -0
- package/dist/worker/brain-worker.d.ts.map +1 -0
- package/dist/worker/brain-worker.js +283 -0
- package/dist/worker/brain-worker.js.map +1 -0
- package/dist/worker/link-discovery.d.ts +47 -0
- package/dist/worker/link-discovery.d.ts.map +1 -0
- package/dist/worker/link-discovery.js +103 -0
- package/dist/worker/link-discovery.js.map +1 -0
- package/dist/worker/predictive-consolidation.d.ts +46 -0
- package/dist/worker/predictive-consolidation.d.ts.map +1 -0
- package/dist/worker/predictive-consolidation.js +110 -0
- package/dist/worker/predictive-consolidation.js.map +1 -0
- package/dist/worker/types.d.ts +91 -0
- package/dist/worker/types.d.ts.map +1 -0
- package/dist/worker/types.js +22 -0
- package/dist/worker/types.js.map +1 -0
- package/hooks/clawdbot/cortex-memory/HOOK.md +71 -0
- package/hooks/clawdbot/cortex-memory/handler.js +279 -0
- package/package.json +73 -0
- package/scripts/pre-compact-hook.mjs +716 -0
- package/scripts/session-end-hook.mjs +548 -0
- package/scripts/session-start-hook.mjs +221 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/stop-dashboard.sh +21 -0
- package/scripts/stop-hook.mjs +163 -0
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Pre-compact hook for ShieldCortex - Automatic Memory Extraction
|
|
4
|
+
*
|
|
5
|
+
* This script runs before context compaction and:
|
|
6
|
+
* 1. Analyzes conversation content for important information
|
|
7
|
+
* 2. Auto-extracts high-salience items (decisions, patterns, errors, etc.)
|
|
8
|
+
* 3. Saves them to the memory database automatically
|
|
9
|
+
* 4. Creates a session marker for continuity
|
|
10
|
+
*
|
|
11
|
+
* The goal: Never lose important context during compaction.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import Database from 'better-sqlite3';
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
16
|
+
import { join } from 'path';
|
|
17
|
+
import { homedir } from 'os';
|
|
18
|
+
|
|
19
|
+
// Database paths (with legacy fallback)
|
|
20
|
+
const NEW_DB_DIR = join(homedir(), '.shieldcortex');
|
|
21
|
+
const LEGACY_DB_DIR = join(homedir(), '.claude-cortex');
|
|
22
|
+
|
|
23
|
+
// Auto-detect: use new path if it exists, or if legacy doesn't exist (new install)
|
|
24
|
+
function getDbPath() {
|
|
25
|
+
const newPath = join(NEW_DB_DIR, 'memories.db');
|
|
26
|
+
const legacyPath = join(LEGACY_DB_DIR, 'memories.db');
|
|
27
|
+
if (existsSync(newPath) || !existsSync(legacyPath)) {
|
|
28
|
+
return { dir: NEW_DB_DIR, path: newPath };
|
|
29
|
+
}
|
|
30
|
+
return { dir: LEGACY_DB_DIR, path: legacyPath };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { dir: DB_DIR, path: DB_PATH } = getDbPath();
|
|
34
|
+
|
|
35
|
+
// Memory limits (should match src/memory/types.ts DEFAULT_CONFIG)
|
|
36
|
+
const MAX_SHORT_TERM_MEMORIES = 100;
|
|
37
|
+
const MAX_LONG_TERM_MEMORIES = 1000;
|
|
38
|
+
|
|
39
|
+
// Base salience threshold (will be adjusted dynamically)
|
|
40
|
+
// Lowered from 0.45 to capture more content
|
|
41
|
+
const BASE_THRESHOLD = 0.35;
|
|
42
|
+
|
|
43
|
+
// Category-specific extraction thresholds (lower = easier to extract)
|
|
44
|
+
// Lowered across the board to be more permissive
|
|
45
|
+
const CATEGORY_EXTRACTION_THRESHOLDS = {
|
|
46
|
+
architecture: 0.28, // Very valuable - extract readily
|
|
47
|
+
error: 0.30, // Valuable for debugging
|
|
48
|
+
context: 0.32, // Important decisions
|
|
49
|
+
learning: 0.32, // Useful learnings
|
|
50
|
+
pattern: 0.35, // Code patterns
|
|
51
|
+
preference: 0.38, // User preferences
|
|
52
|
+
note: 0.42, // General notes
|
|
53
|
+
todo: 0.40, // Moderate
|
|
54
|
+
relationship: 0.35,
|
|
55
|
+
custom: 0.35,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ==================== PROJECT DETECTION (Mirrors src/context/project-context.ts) ====================
|
|
59
|
+
|
|
60
|
+
/** Directories to skip when extracting project name from path */
|
|
61
|
+
const SKIP_DIRECTORIES = [
|
|
62
|
+
'src', 'lib', 'dist', 'build', 'out',
|
|
63
|
+
'node_modules', '.git', '.next', '.cache',
|
|
64
|
+
'test', 'tests', '__tests__', 'spec',
|
|
65
|
+
'bin', 'scripts', 'config', 'public', 'static',
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Extract project name from a file path.
|
|
70
|
+
* Skips common directory names that don't represent projects.
|
|
71
|
+
*/
|
|
72
|
+
function extractProjectFromPath(path) {
|
|
73
|
+
if (!path) return null;
|
|
74
|
+
|
|
75
|
+
const segments = path.split(/[/\\]/).filter(Boolean);
|
|
76
|
+
if (segments.length === 0) return null;
|
|
77
|
+
|
|
78
|
+
// Start from the end and find first non-skipped segment
|
|
79
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
80
|
+
const segment = segments[i];
|
|
81
|
+
if (!SKIP_DIRECTORIES.includes(segment.toLowerCase())) {
|
|
82
|
+
// Skip hidden directories (starting with .)
|
|
83
|
+
if (segment.startsWith('.')) continue;
|
|
84
|
+
return segment;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Maximum memories to auto-create per compaction
|
|
92
|
+
const MAX_AUTO_MEMORIES = 5;
|
|
93
|
+
|
|
94
|
+
// ==================== DYNAMIC THRESHOLD CALCULATION ====================
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get current memory stats from database
|
|
98
|
+
*/
|
|
99
|
+
function getMemoryStats(db) {
|
|
100
|
+
try {
|
|
101
|
+
const stats = db.prepare(`
|
|
102
|
+
SELECT
|
|
103
|
+
COUNT(*) as total,
|
|
104
|
+
SUM(CASE WHEN type = 'short_term' THEN 1 ELSE 0 END) as shortTerm,
|
|
105
|
+
SUM(CASE WHEN type = 'long_term' THEN 1 ELSE 0 END) as longTerm
|
|
106
|
+
FROM memories
|
|
107
|
+
`).get();
|
|
108
|
+
return stats || { total: 0, shortTerm: 0, longTerm: 0 };
|
|
109
|
+
} catch {
|
|
110
|
+
return { total: 0, shortTerm: 0, longTerm: 0 };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Calculate dynamic threshold based on memory fullness
|
|
116
|
+
* When memory is full, be more selective. When sparse, be more permissive.
|
|
117
|
+
* Lowered thresholds to capture more content.
|
|
118
|
+
*/
|
|
119
|
+
function getDynamicThreshold(memoryCount, maxMemories) {
|
|
120
|
+
const fullness = memoryCount / maxMemories;
|
|
121
|
+
|
|
122
|
+
// More selective when memory is full, more permissive when sparse
|
|
123
|
+
if (fullness > 0.8) return 0.50; // Very full - highly selective
|
|
124
|
+
if (fullness > 0.6) return 0.42; // Getting full - moderately selective
|
|
125
|
+
if (fullness > 0.4) return 0.35; // Normal - standard threshold
|
|
126
|
+
if (fullness > 0.2) return 0.30; // Sparse - more permissive
|
|
127
|
+
return 0.25; // Very sparse - accept most valuable items
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get extraction threshold for a specific category
|
|
132
|
+
* Combines dynamic threshold with category-specific adjustments
|
|
133
|
+
*/
|
|
134
|
+
function getExtractionThreshold(category, dynamicThreshold) {
|
|
135
|
+
const categoryThreshold = CATEGORY_EXTRACTION_THRESHOLDS[category] || BASE_THRESHOLD;
|
|
136
|
+
// Use whichever is lower (more permissive for valuable categories when memory is sparse)
|
|
137
|
+
return Math.min(categoryThreshold, dynamicThreshold);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ==================== SALIENCE DETECTION (Mirrors src/memory/salience.ts) ====================
|
|
141
|
+
|
|
142
|
+
const ARCHITECTURE_KEYWORDS = [
|
|
143
|
+
'architecture', 'design', 'pattern', 'structure', 'system',
|
|
144
|
+
'database', 'api', 'schema', 'model', 'framework', 'stack',
|
|
145
|
+
'microservice', 'monolith', 'serverless', 'infrastructure'
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
const ERROR_KEYWORDS = [
|
|
149
|
+
'error', 'bug', 'fix', 'issue', 'problem', 'crash', 'fail',
|
|
150
|
+
'exception', 'debug', 'resolve', 'solution', 'workaround'
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const PREFERENCE_KEYWORDS = [
|
|
154
|
+
'prefer', 'always', 'never', 'style', 'convention', 'standard',
|
|
155
|
+
'like', 'want', 'should', 'must', 'require'
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const PATTERN_KEYWORDS = [
|
|
159
|
+
'pattern', 'practice', 'approach', 'method', 'technique',
|
|
160
|
+
'implementation', 'strategy', 'algorithm', 'workflow'
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
const DECISION_KEYWORDS = [
|
|
164
|
+
'decided', 'decision', 'chose', 'chosen', 'selected', 'going with',
|
|
165
|
+
'will use', 'opted for', 'settled on', 'agreed'
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
const LEARNING_KEYWORDS = [
|
|
169
|
+
'learned', 'discovered', 'realized', 'found out', 'turns out',
|
|
170
|
+
'TIL', 'now know', 'understand now', 'figured out'
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const EMOTIONAL_MARKERS = [
|
|
174
|
+
'important', 'critical', 'crucial', 'essential', 'key',
|
|
175
|
+
'finally', 'breakthrough', 'eureka', 'aha', 'got it',
|
|
176
|
+
'frustrating', 'annoying', 'tricky', 'remember'
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
const CODE_REFERENCE_PATTERNS = [
|
|
180
|
+
/\b[A-Z][a-zA-Z]*\.[a-zA-Z]+\b/,
|
|
181
|
+
/\b[a-z_][a-zA-Z0-9_]*\.(ts|js|py|go|rs)\b/,
|
|
182
|
+
/`[^`]+`/,
|
|
183
|
+
/\b(function|class|interface|type|const|let|var)\s+\w+/i,
|
|
184
|
+
/\bline\s*\d+\b/i,
|
|
185
|
+
/\b(src|lib|app|components?)\/\S+/,
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
function detectKeywords(text, keywords) {
|
|
189
|
+
const lower = text.toLowerCase();
|
|
190
|
+
return keywords.some(keyword => lower.includes(keyword.toLowerCase()));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function detectCodeReferences(content) {
|
|
194
|
+
return CODE_REFERENCE_PATTERNS.some(pattern => pattern.test(content));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function detectExplicitRequest(text) {
|
|
198
|
+
const patterns = [
|
|
199
|
+
/\bremember\s+(this|that)\b/i,
|
|
200
|
+
/\bdon'?t\s+forget\b/i,
|
|
201
|
+
/\bkeep\s+(in\s+)?mind\b/i,
|
|
202
|
+
/\bnote\s+(this|that)\b/i,
|
|
203
|
+
/\bsave\s+(this|that)\b/i,
|
|
204
|
+
/\bimportant[:\s]/i,
|
|
205
|
+
/\bfor\s+future\s+reference\b/i,
|
|
206
|
+
];
|
|
207
|
+
return patterns.some(pattern => pattern.test(text));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function calculateSalience(text) {
|
|
211
|
+
let score = 0.25; // Base score
|
|
212
|
+
|
|
213
|
+
if (detectExplicitRequest(text)) score += 0.5;
|
|
214
|
+
if (detectKeywords(text, ARCHITECTURE_KEYWORDS)) score += 0.4;
|
|
215
|
+
if (detectKeywords(text, ERROR_KEYWORDS)) score += 0.35;
|
|
216
|
+
if (detectKeywords(text, DECISION_KEYWORDS)) score += 0.35;
|
|
217
|
+
if (detectKeywords(text, LEARNING_KEYWORDS)) score += 0.3;
|
|
218
|
+
if (detectKeywords(text, PATTERN_KEYWORDS)) score += 0.25;
|
|
219
|
+
if (detectKeywords(text, PREFERENCE_KEYWORDS)) score += 0.25;
|
|
220
|
+
if (detectCodeReferences(text)) score += 0.15;
|
|
221
|
+
if (detectKeywords(text, EMOTIONAL_MARKERS)) score += 0.2;
|
|
222
|
+
|
|
223
|
+
return Math.min(1.0, score);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function suggestCategory(text) {
|
|
227
|
+
const lower = text.toLowerCase();
|
|
228
|
+
|
|
229
|
+
if (detectKeywords(lower, ARCHITECTURE_KEYWORDS)) return 'architecture';
|
|
230
|
+
if (detectKeywords(lower, ERROR_KEYWORDS)) return 'error';
|
|
231
|
+
if (detectKeywords(lower, DECISION_KEYWORDS)) return 'context';
|
|
232
|
+
if (detectKeywords(lower, LEARNING_KEYWORDS)) return 'learning';
|
|
233
|
+
if (detectKeywords(lower, PREFERENCE_KEYWORDS)) return 'preference';
|
|
234
|
+
if (detectKeywords(lower, PATTERN_KEYWORDS)) return 'pattern';
|
|
235
|
+
if (/\b(todo|fixme|hack|xxx)\b/i.test(lower)) return 'todo';
|
|
236
|
+
|
|
237
|
+
return 'note';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function extractTags(text, extractorName = null) {
|
|
241
|
+
const tags = new Set();
|
|
242
|
+
|
|
243
|
+
// Extract hashtags
|
|
244
|
+
const hashtagMatches = text.match(/#[a-zA-Z][a-zA-Z0-9_-]*/g);
|
|
245
|
+
if (hashtagMatches) {
|
|
246
|
+
hashtagMatches.forEach(tag => tags.add(tag.slice(1).toLowerCase()));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Extract common tech terms
|
|
250
|
+
const techTerms = [
|
|
251
|
+
'react', 'vue', 'angular', 'node', 'python', 'typescript', 'javascript',
|
|
252
|
+
'api', 'database', 'sql', 'mongodb', 'postgresql', 'mysql',
|
|
253
|
+
'docker', 'kubernetes', 'aws', 'git', 'testing', 'auth', 'security'
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
const lowerText = text.toLowerCase();
|
|
257
|
+
techTerms.forEach(term => {
|
|
258
|
+
if (lowerText.includes(term)) tags.add(term);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Add auto-extracted tag
|
|
262
|
+
tags.add('auto-extracted');
|
|
263
|
+
|
|
264
|
+
// Add source extractor tag for tracking
|
|
265
|
+
if (extractorName) {
|
|
266
|
+
tags.add(`source:${extractorName}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return Array.from(tags).slice(0, 12);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Calculate frequency boost based on how often key terms appear
|
|
274
|
+
* across all extracted segments. Repeated topics are more important.
|
|
275
|
+
*/
|
|
276
|
+
function calculateFrequencyBoost(segment, allSegments) {
|
|
277
|
+
// Extract key terms (words > 5 chars that aren't common)
|
|
278
|
+
const commonWords = new Set([
|
|
279
|
+
'about', 'after', 'before', 'being', 'between', 'could', 'during',
|
|
280
|
+
'every', 'found', 'through', 'would', 'should', 'which', 'where',
|
|
281
|
+
'there', 'these', 'their', 'other', 'using', 'because', 'without'
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
const words = segment.content.toLowerCase().split(/\s+/);
|
|
285
|
+
const keyTerms = words.filter(w =>
|
|
286
|
+
w.length > 5 &&
|
|
287
|
+
!commonWords.has(w) &&
|
|
288
|
+
/^[a-z]+$/.test(w)
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
let boost = 0;
|
|
292
|
+
const seenTerms = new Set();
|
|
293
|
+
|
|
294
|
+
for (const term of keyTerms) {
|
|
295
|
+
if (seenTerms.has(term)) continue;
|
|
296
|
+
seenTerms.add(term);
|
|
297
|
+
|
|
298
|
+
// Count how many other segments mention this term
|
|
299
|
+
const mentions = allSegments.filter(s =>
|
|
300
|
+
s !== segment &&
|
|
301
|
+
s.content.toLowerCase().includes(term)
|
|
302
|
+
).length;
|
|
303
|
+
|
|
304
|
+
// Boost for repeated topics (cap at 5 mentions)
|
|
305
|
+
if (mentions > 1) {
|
|
306
|
+
boost += 0.03 * Math.min(mentions, 5);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Cap total frequency boost at 0.15
|
|
311
|
+
return Math.min(0.15, boost);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ==================== CONTENT EXTRACTION ====================
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Extract meaningful segments from conversation text
|
|
318
|
+
* Looks for decisions, learnings, fixes, patterns, etc.
|
|
319
|
+
*/
|
|
320
|
+
function extractMemorableSegments(conversationText) {
|
|
321
|
+
const segments = [];
|
|
322
|
+
|
|
323
|
+
// Pattern matchers for different types of important content
|
|
324
|
+
// Expanded patterns with lower minimum lengths for better capture
|
|
325
|
+
const extractors = [
|
|
326
|
+
{
|
|
327
|
+
name: 'decision',
|
|
328
|
+
patterns: [
|
|
329
|
+
/(?:we\s+)?decided\s+(?:to\s+)?(.{15,200})/gi,
|
|
330
|
+
/(?:going|went)\s+with\s+(.{15,150})/gi,
|
|
331
|
+
/(?:chose|chosen|selected)\s+(.{15,150})/gi,
|
|
332
|
+
/the\s+(?:approach|solution|fix)\s+(?:is|was)\s+(.{15,200})/gi,
|
|
333
|
+
// New patterns
|
|
334
|
+
/(?:using|will\s+use)\s+(.{15,150})/gi,
|
|
335
|
+
/(?:opted\s+for|settled\s+on)\s+(.{15,150})/gi,
|
|
336
|
+
],
|
|
337
|
+
titlePrefix: 'Decision: ',
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: 'error-fix',
|
|
341
|
+
patterns: [
|
|
342
|
+
/(?:fixed|solved|resolved)\s+(?:by\s+)?(.{15,200})/gi,
|
|
343
|
+
/the\s+(?:fix|solution|workaround)\s+(?:is|was)\s+(.{15,200})/gi,
|
|
344
|
+
/(?:root\s+cause|issue)\s+(?:is|was)\s+(.{15,200})/gi,
|
|
345
|
+
/(?:error|bug)\s+(?:was\s+)?caused\s+by\s+(.{15,200})/gi,
|
|
346
|
+
// New patterns
|
|
347
|
+
/(?:problem|issue)\s+was\s+(.{15,150})/gi,
|
|
348
|
+
/(?:the\s+)?bug\s+(?:is|was)\s+(.{15,150})/gi,
|
|
349
|
+
/(?:debugging|debugged)\s+(.{15,150})/gi,
|
|
350
|
+
],
|
|
351
|
+
titlePrefix: 'Fix: ',
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: 'learning',
|
|
355
|
+
patterns: [
|
|
356
|
+
/(?:learned|discovered|realized|found\s+out)\s+(?:that\s+)?(.{15,200})/gi,
|
|
357
|
+
/turns\s+out\s+(?:that\s+)?(.{15,200})/gi,
|
|
358
|
+
/(?:TIL|today\s+I\s+learned)[:\s]+(.{15,200})/gi,
|
|
359
|
+
// New patterns
|
|
360
|
+
/(?:now\s+)?(?:understand|know)\s+(?:that\s+)?(.{15,150})/gi,
|
|
361
|
+
/(?:figured\s+out|worked\s+out)\s+(.{15,150})/gi,
|
|
362
|
+
],
|
|
363
|
+
titlePrefix: 'Learned: ',
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: 'architecture',
|
|
367
|
+
patterns: [
|
|
368
|
+
/the\s+architecture\s+(?:is|uses|consists\s+of)\s+(.{15,200})/gi,
|
|
369
|
+
/(?:design|pattern)\s+(?:is|uses)\s+(.{15,200})/gi,
|
|
370
|
+
/(?:system|api|database)\s+(?:structure|design)\s+(?:is|uses)\s+(.{15,200})/gi,
|
|
371
|
+
// New patterns
|
|
372
|
+
/(?:created|added|implemented|built)\s+(?:a\s+)?(.{15,200})/gi,
|
|
373
|
+
/(?:refactored|updated|changed)\s+(?:the\s+)?(.{15,150})/gi,
|
|
374
|
+
],
|
|
375
|
+
titlePrefix: 'Architecture: ',
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: 'preference',
|
|
379
|
+
patterns: [
|
|
380
|
+
/(?:always|never)\s+(.{10,150})/gi,
|
|
381
|
+
/(?:prefer|want)\s+to\s+(.{10,150})/gi,
|
|
382
|
+
/(?:should|must)\s+(?:always\s+)?(.{10,150})/gi,
|
|
383
|
+
],
|
|
384
|
+
titlePrefix: 'Preference: ',
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: 'important-note',
|
|
388
|
+
patterns: [
|
|
389
|
+
/important[:\s]+(.{15,200})/gi,
|
|
390
|
+
/(?:note|remember)[:\s]+(.{15,200})/gi,
|
|
391
|
+
/(?:key|critical)\s+(?:point|thing)[:\s]+(.{15,200})/gi,
|
|
392
|
+
// New patterns
|
|
393
|
+
/(?:this\s+is\s+)?(?:crucial|essential)[:\s]+(.{15,150})/gi,
|
|
394
|
+
/(?:don't\s+forget|keep\s+in\s+mind)[:\s]+(.{15,150})/gi,
|
|
395
|
+
],
|
|
396
|
+
titlePrefix: 'Note: ',
|
|
397
|
+
},
|
|
398
|
+
];
|
|
399
|
+
|
|
400
|
+
for (const extractor of extractors) {
|
|
401
|
+
for (const pattern of extractor.patterns) {
|
|
402
|
+
let match;
|
|
403
|
+
while ((match = pattern.exec(conversationText)) !== null) {
|
|
404
|
+
const content = match[1].trim();
|
|
405
|
+
if (content.length >= 20) {
|
|
406
|
+
// Generate a title from first ~50 chars
|
|
407
|
+
const titleContent = content.slice(0, 50).replace(/\s+/g, ' ').trim();
|
|
408
|
+
const title = extractor.titlePrefix + (titleContent.length < 50 ? titleContent : titleContent + '...');
|
|
409
|
+
|
|
410
|
+
segments.push({
|
|
411
|
+
title,
|
|
412
|
+
content: content.slice(0, 500), // Cap content length
|
|
413
|
+
extractorType: extractor.name,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return segments;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Deduplicate and score segments
|
|
425
|
+
* @param {Array} segments - Raw extracted segments
|
|
426
|
+
* @param {number} dynamicThreshold - Dynamic threshold based on memory fullness
|
|
427
|
+
*/
|
|
428
|
+
function processSegments(segments, dynamicThreshold = BASE_THRESHOLD) {
|
|
429
|
+
// Remove near-duplicates (segments with >80% overlap)
|
|
430
|
+
const unique = [];
|
|
431
|
+
for (const seg of segments) {
|
|
432
|
+
const isDupe = unique.some(existing => {
|
|
433
|
+
const overlap = calculateOverlap(existing.content, seg.content);
|
|
434
|
+
return overlap > 0.8;
|
|
435
|
+
});
|
|
436
|
+
if (!isDupe) {
|
|
437
|
+
const text = seg.title + ' ' + seg.content;
|
|
438
|
+
const baseSalience = calculateSalience(text);
|
|
439
|
+
const category = suggestCategory(text);
|
|
440
|
+
|
|
441
|
+
unique.push({
|
|
442
|
+
...seg,
|
|
443
|
+
baseSalience,
|
|
444
|
+
category,
|
|
445
|
+
tags: extractTags(text, seg.extractorType),
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Calculate frequency boost after we have all unique segments
|
|
451
|
+
for (const seg of unique) {
|
|
452
|
+
const frequencyBoost = calculateFrequencyBoost(seg, unique);
|
|
453
|
+
seg.salience = Math.min(1.0, seg.baseSalience + frequencyBoost);
|
|
454
|
+
seg.frequencyBoost = frequencyBoost;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Sort by salience (highest first)
|
|
458
|
+
unique.sort((a, b) => b.salience - a.salience);
|
|
459
|
+
|
|
460
|
+
// Filter by category-specific threshold (combined with dynamic threshold)
|
|
461
|
+
const filtered = unique.filter(seg => {
|
|
462
|
+
const threshold = getExtractionThreshold(seg.category, dynamicThreshold);
|
|
463
|
+
return seg.salience >= threshold;
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
return filtered.slice(0, MAX_AUTO_MEMORIES);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Simple overlap calculation (Jaccard similarity on words)
|
|
471
|
+
*/
|
|
472
|
+
function calculateOverlap(text1, text2) {
|
|
473
|
+
const words1 = new Set(text1.toLowerCase().split(/\s+/));
|
|
474
|
+
const words2 = new Set(text2.toLowerCase().split(/\s+/));
|
|
475
|
+
|
|
476
|
+
const intersection = new Set([...words1].filter(w => words2.has(w)));
|
|
477
|
+
const union = new Set([...words1, ...words2]);
|
|
478
|
+
|
|
479
|
+
return intersection.size / union.size;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ==================== DATABASE OPERATIONS ====================
|
|
483
|
+
|
|
484
|
+
function saveMemory(db, memory, project) {
|
|
485
|
+
const timestamp = new Date().toISOString();
|
|
486
|
+
|
|
487
|
+
const stmt = db.prepare(`
|
|
488
|
+
INSERT INTO memories (title, content, type, category, salience, tags, project, created_at, last_accessed)
|
|
489
|
+
VALUES (?, ?, 'short_term', ?, ?, ?, ?, ?, ?)
|
|
490
|
+
`);
|
|
491
|
+
|
|
492
|
+
stmt.run(
|
|
493
|
+
memory.title,
|
|
494
|
+
memory.content,
|
|
495
|
+
memory.category,
|
|
496
|
+
memory.salience,
|
|
497
|
+
JSON.stringify(memory.tags),
|
|
498
|
+
project || null,
|
|
499
|
+
timestamp,
|
|
500
|
+
timestamp
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
// ==================== MAIN HOOK LOGIC ====================
|
|
506
|
+
|
|
507
|
+
let input = '';
|
|
508
|
+
process.stdin.setEncoding('utf8');
|
|
509
|
+
|
|
510
|
+
process.stdin.on('readable', () => {
|
|
511
|
+
let chunk;
|
|
512
|
+
while ((chunk = process.stdin.read()) !== null) {
|
|
513
|
+
input += chunk;
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
process.stdin.on('end', () => {
|
|
518
|
+
try {
|
|
519
|
+
const hookData = JSON.parse(input || '{}');
|
|
520
|
+
|
|
521
|
+
const trigger = hookData.trigger || 'unknown';
|
|
522
|
+
const project = extractProjectFromPath(hookData.cwd);
|
|
523
|
+
|
|
524
|
+
// Extract conversation text from hook data
|
|
525
|
+
// Claude Code passes conversation in various formats
|
|
526
|
+
const conversationText = extractConversationText(hookData);
|
|
527
|
+
|
|
528
|
+
// Ensure database directory exists
|
|
529
|
+
if (!existsSync(DB_DIR)) {
|
|
530
|
+
mkdirSync(DB_DIR, { recursive: true });
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Check if database exists
|
|
534
|
+
if (!existsSync(DB_PATH)) {
|
|
535
|
+
console.error('[pre-compact] Memory database not found, skipping auto-extraction');
|
|
536
|
+
outputReminder(0, BASE_THRESHOLD);
|
|
537
|
+
process.exit(0);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Connect to database with timeout to handle concurrent access
|
|
541
|
+
// timeout: 5000ms prevents hook from hanging if DB is locked
|
|
542
|
+
const db = new Database(DB_PATH, { timeout: 5000 });
|
|
543
|
+
|
|
544
|
+
// Get current memory stats for dynamic threshold calculation
|
|
545
|
+
const stats = getMemoryStats(db);
|
|
546
|
+
const totalMemories = stats.shortTerm + stats.longTerm;
|
|
547
|
+
const maxMemories = MAX_SHORT_TERM_MEMORIES + MAX_LONG_TERM_MEMORIES;
|
|
548
|
+
const dynamicThreshold = getDynamicThreshold(totalMemories, maxMemories);
|
|
549
|
+
|
|
550
|
+
console.error(`[auto-extract] Memory status: ${totalMemories}/${maxMemories} (${(totalMemories/maxMemories*100).toFixed(0)}% full)`);
|
|
551
|
+
console.error(`[auto-extract] Dynamic threshold: ${dynamicThreshold.toFixed(2)}`);
|
|
552
|
+
|
|
553
|
+
let autoExtractedCount = 0;
|
|
554
|
+
|
|
555
|
+
// Only attempt extraction if we have conversation content
|
|
556
|
+
if (conversationText && conversationText.length > 100) {
|
|
557
|
+
// Extract memorable segments
|
|
558
|
+
const segments = extractMemorableSegments(conversationText);
|
|
559
|
+
const processedSegments = processSegments(segments, dynamicThreshold);
|
|
560
|
+
|
|
561
|
+
// Save auto-extracted memories
|
|
562
|
+
for (const memory of processedSegments) {
|
|
563
|
+
try {
|
|
564
|
+
saveMemory(db, memory, project);
|
|
565
|
+
autoExtractedCount++;
|
|
566
|
+
const boostInfo = memory.frequencyBoost > 0 ? ` +${memory.frequencyBoost.toFixed(2)} boost` : '';
|
|
567
|
+
console.error(`[auto-extract] Saved: ${memory.title} (salience: ${memory.salience.toFixed(2)}${boostInfo}, category: ${memory.category})`);
|
|
568
|
+
} catch (err) {
|
|
569
|
+
console.error(`[auto-extract] Failed to save "${memory.title}": ${err.message}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
console.error(`[shieldcortex] Pre-compact complete: ${autoExtractedCount} memories auto-extracted`);
|
|
575
|
+
|
|
576
|
+
outputReminder(autoExtractedCount, dynamicThreshold);
|
|
577
|
+
|
|
578
|
+
db.close();
|
|
579
|
+
process.exit(0);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
console.error(`[pre-compact] Error: ${error.message}`);
|
|
582
|
+
outputReminder(0, BASE_THRESHOLD);
|
|
583
|
+
process.exit(0); // Don't block compaction on errors
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Read conversation text from the current session's JSONL file.
|
|
589
|
+
* Claude Code stores sessions in ~/.claude/projects/<project-slug>/<session-id>.jsonl
|
|
590
|
+
*/
|
|
591
|
+
function readSessionConversation(cwd) {
|
|
592
|
+
if (!cwd) return '';
|
|
593
|
+
|
|
594
|
+
try {
|
|
595
|
+
// Claude Code uses the absolute path with slashes replaced by dashes as the project folder name
|
|
596
|
+
const projectSlug = cwd.replace(/^\//, '').replace(/\//g, '-');
|
|
597
|
+
const projectDir = join(homedir(), '.claude', 'projects', `-${projectSlug}`);
|
|
598
|
+
|
|
599
|
+
if (!existsSync(projectDir)) {
|
|
600
|
+
console.error(`[auto-extract] Session dir not found: ${projectDir}`);
|
|
601
|
+
return '';
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Find the most recently modified .jsonl file (= current session)
|
|
605
|
+
const files = readdirSync(projectDir)
|
|
606
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
607
|
+
.map(f => ({ name: f, mtime: statSync(join(projectDir, f)).mtimeMs }))
|
|
608
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
609
|
+
|
|
610
|
+
if (files.length === 0) {
|
|
611
|
+
console.error('[auto-extract] No session JSONL files found');
|
|
612
|
+
return '';
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const sessionFile = join(projectDir, files[0].name);
|
|
616
|
+
const content = readFileSync(sessionFile, 'utf-8');
|
|
617
|
+
const lines = content.trim().split('\n');
|
|
618
|
+
|
|
619
|
+
// Read last 50 lines to get recent conversation
|
|
620
|
+
const recentLines = lines.slice(-50);
|
|
621
|
+
const messages = [];
|
|
622
|
+
|
|
623
|
+
for (const line of recentLines) {
|
|
624
|
+
try {
|
|
625
|
+
const entry = JSON.parse(line);
|
|
626
|
+
// Claude Code JSONL: entry.type is "user"|"assistant", entry.message has API content
|
|
627
|
+
const role = entry.type || entry.message?.role;
|
|
628
|
+
const content = entry.message?.content;
|
|
629
|
+
if ((role === 'user' || role === 'assistant') && content) {
|
|
630
|
+
const text = Array.isArray(content)
|
|
631
|
+
? content.filter(c => c.type === 'text').map(c => c.text).join('\n')
|
|
632
|
+
: content;
|
|
633
|
+
if (text && !text.startsWith('/')) {
|
|
634
|
+
messages.push(text);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
} catch {
|
|
638
|
+
// Skip invalid lines
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const result = messages.join('\n\n');
|
|
643
|
+
console.error(`[auto-extract] Read ${messages.length} messages from session JSONL (${result.length} chars)`);
|
|
644
|
+
return result;
|
|
645
|
+
} catch (err) {
|
|
646
|
+
console.error(`[auto-extract] Failed to read session: ${err.message}`);
|
|
647
|
+
return '';
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Extract conversation text from hook data, falling back to session JSONL
|
|
653
|
+
*/
|
|
654
|
+
function extractConversationText(hookData) {
|
|
655
|
+
// Try different possible locations for conversation content
|
|
656
|
+
const sources = [
|
|
657
|
+
hookData.conversation,
|
|
658
|
+
hookData.messages,
|
|
659
|
+
hookData.transcript,
|
|
660
|
+
hookData.content,
|
|
661
|
+
hookData.context,
|
|
662
|
+
hookData.text,
|
|
663
|
+
];
|
|
664
|
+
|
|
665
|
+
for (const source of sources) {
|
|
666
|
+
if (typeof source === 'string' && source.length > 0) {
|
|
667
|
+
return source;
|
|
668
|
+
}
|
|
669
|
+
if (Array.isArray(source)) {
|
|
670
|
+
return source
|
|
671
|
+
.map(msg => {
|
|
672
|
+
if (typeof msg === 'string') return msg;
|
|
673
|
+
if (msg.content) return msg.content;
|
|
674
|
+
if (msg.text) return msg.text;
|
|
675
|
+
return '';
|
|
676
|
+
})
|
|
677
|
+
.join('\n');
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Fallback: read the session JSONL file directly
|
|
682
|
+
return readSessionConversation(hookData.cwd);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Output reminder message to stdout.
|
|
687
|
+
* Context retrieval is handled by the SessionStart compact hook, so we only
|
|
688
|
+
* need to report auto-extraction results and proactive memory instructions.
|
|
689
|
+
*/
|
|
690
|
+
function outputReminder(autoExtractedCount, dynamicThreshold) {
|
|
691
|
+
if (autoExtractedCount > 0) {
|
|
692
|
+
console.log(`\n🧠AUTO-MEMORY: ${autoExtractedCount} important items were automatically saved before compaction.`);
|
|
693
|
+
} else {
|
|
694
|
+
const thresholdNote = dynamicThreshold > 0.5
|
|
695
|
+
? ' (Memory near capacity - being selective)'
|
|
696
|
+
: '';
|
|
697
|
+
console.log(`\n🧠PRE-COMPACT: No auto-extractable content found with high enough salience${thresholdNote}.
|
|
698
|
+
If there's something important, use 'remember' to save it explicitly.`);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Proactive memory instructions
|
|
702
|
+
console.log(`
|
|
703
|
+
## IMPORTANT: Proactive Memory Use
|
|
704
|
+
|
|
705
|
+
You have access to a persistent memory system. Use it proactively:
|
|
706
|
+
|
|
707
|
+
**ALWAYS use \`remember\` immediately when:**
|
|
708
|
+
- Making architecture/design decisions
|
|
709
|
+
- Fixing bugs (capture the root cause and solution)
|
|
710
|
+
- Learning something new about the codebase
|
|
711
|
+
- User states a preference
|
|
712
|
+
- Completing significant features
|
|
713
|
+
|
|
714
|
+
**Don't wait** - call \`remember\` right after the event, not at the end of the session.
|
|
715
|
+
`);
|
|
716
|
+
}
|