squish-memory 1.0.2 → 1.1.5
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/.env.example +130 -0
- package/CHANGELOG.md +55 -0
- package/README.md +150 -287
- package/config/hooks/claude-code-hooks.json +39 -0
- package/config/hooks/cursor-hooks.json +30 -0
- package/config/hooks/opencode-hooks.json +30 -0
- package/config/hooks/windsurf-hooks.json +30 -0
- package/config/mcp-mode-semantics.json +23 -21
- package/config/plugin-manifest.json +101 -152
- package/{plugin.json → config/plugin.json} +2 -2
- package/config/settings.json +52 -51
- package/{commands → core/commands}/init.md +39 -39
- package/dist/config.d.ts +28 -4
- package/dist/config.js +97 -29
- package/dist/core/adapters/config/claude-code.d.ts +45 -0
- package/dist/core/adapters/config/claude-code.js +113 -0
- package/dist/core/adapters/config/cursor.d.ts +26 -0
- package/dist/core/adapters/config/cursor.js +74 -0
- package/dist/core/adapters/config/opencode.d.ts +23 -0
- package/dist/core/adapters/config/opencode.js +73 -0
- package/dist/core/adapters/config/windsurf.d.ts +26 -0
- package/dist/core/adapters/config/windsurf.js +74 -0
- package/dist/core/adapters/index.d.ts +45 -0
- package/dist/core/adapters/index.js +84 -0
- package/dist/core/adapters/scripts/install-adapter.d.ts +19 -0
- package/dist/core/adapters/scripts/install-adapter.js +149 -0
- package/dist/core/adapters/timeline.d.ts +23 -0
- package/dist/core/adapters/timeline.js +88 -0
- package/dist/core/adapters/types.d.ts +157 -0
- package/dist/core/adapters/types.js +50 -0
- package/dist/{algorithms → core/algorithms}/analytics/token-estimator.d.ts +1 -1
- package/dist/{algorithms → core/algorithms}/analytics/token-estimator.js +3 -3
- package/dist/{algorithms → core/algorithms}/detection/semantic-ranker.d.ts +1 -1
- package/dist/{algorithms → core/algorithms}/detection/semantic-ranker.js +1 -1
- package/dist/{algorithms → core/algorithms}/detection/two-stage-detector.d.ts +1 -1
- package/dist/{algorithms → core/algorithms}/detection/two-stage-detector.js +7 -10
- package/dist/{algorithms → core/algorithms}/handlers/approve-merge.js +4 -4
- package/dist/{algorithms → core/algorithms}/handlers/detect-duplicates.js +3 -3
- package/dist/{algorithms → core/algorithms}/handlers/get-stats.js +3 -3
- package/dist/{algorithms → core/algorithms}/handlers/list-proposals.js +3 -3
- package/dist/{algorithms → core/algorithms}/handlers/preview-merge.js +3 -3
- package/dist/{algorithms → core/algorithms}/handlers/reject-merge.js +3 -3
- package/dist/{algorithms → core/algorithms}/handlers/reverse-merge.js +3 -3
- package/dist/core/algorithms/index.d.ts +21 -0
- package/dist/core/algorithms/index.js +26 -0
- package/dist/core/algorithms/operations/cache-maintenance.d.ts +12 -0
- package/dist/core/algorithms/operations/cache-maintenance.js +157 -0
- package/dist/{algorithms → core/algorithms}/safety/safety-checks.d.ts +1 -1
- package/dist/{algorithms → core/algorithms}/strategies/merge-strategies.d.ts +19 -1
- package/dist/{algorithms → core/algorithms}/strategies/merge-strategies.js +74 -123
- package/dist/core/algorithms/types.d.ts +133 -0
- package/dist/core/algorithms/types.js +5 -0
- package/dist/core/associations.d.ts +1 -2
- package/dist/core/associations.js +1 -2
- package/dist/core/autosave.d.ts +19 -0
- package/dist/core/autosave.js +16 -0
- package/dist/{commands → core/commands}/managed-sync.js +5 -5
- package/dist/core/commands/mcp-server.js +739 -0
- package/dist/core/context/agent-context.d.ts +106 -0
- package/dist/core/context/agent-context.js +274 -0
- package/dist/core/{context-paging.d.ts → context/context-paging.d.ts} +2 -12
- package/dist/core/{context-paging.js → context/context-paging.js} +19 -39
- package/dist/core/context/context-window.d.ts +40 -0
- package/dist/core/context/context-window.js +177 -0
- package/dist/core/context/context.js +22 -0
- package/dist/core/embeddings.d.ts +1 -1
- package/dist/core/embeddings.js +54 -2
- package/dist/core/error-handling.d.ts +63 -0
- package/dist/core/error-handling.js +173 -0
- package/dist/core/external-folder/index.d.ts +102 -0
- package/dist/core/external-folder/index.js +294 -0
- package/dist/core/hooks/agent-hooks.d.ts +74 -0
- package/dist/core/hooks/agent-hooks.js +244 -0
- package/dist/core/hooks/auto-tagger.d.ts +19 -0
- package/dist/core/hooks/auto-tagger.js +155 -0
- package/dist/core/hooks/capture-filter.d.ts +41 -0
- package/dist/core/hooks/capture-filter.js +128 -0
- package/dist/core/index.d.ts +6 -6
- package/dist/core/index.js +6 -6
- package/dist/core/{agent-memory.js → ingestion/agent-memory.js} +5 -7
- package/dist/core/{core-memory.js → ingestion/core-memory.js} +4 -4
- package/dist/core/ingestion/learnings.d.ts +57 -0
- package/dist/core/ingestion/learnings.js +202 -0
- package/dist/core/lib/db-client.d.ts +114 -0
- package/dist/core/lib/db-client.js +130 -0
- package/dist/core/lib/schemas.d.ts +129 -0
- package/dist/core/lib/schemas.js +87 -0
- package/dist/core/{utils.d.ts → lib/utils.d.ts} +1 -0
- package/dist/core/{utils.js → lib/utils.js} +31 -15
- package/dist/core/lib/validation.d.ts +38 -0
- package/dist/core/lib/validation.js +151 -0
- package/dist/core/lifecycle.d.ts +7 -0
- package/dist/core/lifecycle.js +140 -20
- package/dist/core/local-embeddings.d.ts +6 -1
- package/dist/core/local-embeddings.js +6 -15
- package/dist/core/logger.js +7 -1
- package/dist/core/mcp/tools.js +35 -3
- package/dist/core/memory/categorizer.js +1 -0
- package/dist/core/memory/conflict-detector.js +1 -1
- package/dist/core/memory/consolidation.d.ts +1 -10
- package/dist/core/memory/consolidation.js +2 -11
- package/dist/core/memory/context-collector.js +1 -1
- package/dist/core/memory/edit-workflow.js +1 -1
- package/dist/core/memory/entity-resolver.js +7 -7
- package/dist/core/memory/fact-extractor.js +12 -12
- package/dist/core/memory/feedback-tracker.js +1 -1
- package/dist/core/memory/hooks.d.ts +88 -0
- package/dist/core/memory/hooks.js +174 -0
- package/dist/core/memory/hybrid-retrieval.js +2 -2
- package/dist/core/memory/hybrid-search.d.ts +1 -6
- package/dist/core/memory/hybrid-search.js +70 -84
- package/dist/core/memory/importance.d.ts +8 -13
- package/dist/core/memory/importance.js +47 -74
- package/dist/core/memory/loader.d.ts +31 -0
- package/dist/core/memory/loader.js +141 -0
- package/dist/core/memory/markdown/markdown-storage.d.ts +72 -0
- package/dist/core/memory/markdown/markdown-storage.js +243 -0
- package/dist/core/memory/memories.d.ts +12 -4
- package/dist/core/memory/memories.js +192 -180
- package/dist/core/memory/memory-lifecycle.d.ts +8 -0
- package/dist/core/memory/memory-lifecycle.js +55 -0
- package/dist/core/memory/migrate.d.ts +21 -0
- package/dist/core/memory/migrate.js +134 -0
- package/dist/core/memory/normalization.d.ts +22 -0
- package/dist/core/memory/normalization.js +26 -0
- package/dist/core/memory/progressive-disclosure.js +1 -1
- package/dist/core/memory/query-rewriter.js +9 -9
- package/dist/core/memory/serialization.d.ts +4 -0
- package/dist/core/memory/serialization.js +49 -0
- package/dist/core/memory/stats.d.ts +5 -0
- package/dist/core/memory/stats.js +63 -12
- package/dist/core/memory/temporal-facts.js +21 -0
- package/dist/core/memory/write-gate.js +1 -1
- package/dist/core/obsidian-vault.d.ts +30 -0
- package/dist/core/obsidian-vault.js +94 -0
- package/dist/core/places/index.d.ts +14 -0
- package/dist/core/places/index.js +14 -0
- package/dist/core/places/memory-places.d.ts +68 -0
- package/dist/core/places/memory-places.js +261 -0
- package/dist/core/places/places.d.ts +88 -0
- package/dist/core/places/places.js +314 -0
- package/dist/core/places/rules.d.ts +74 -0
- package/dist/core/places/rules.js +240 -0
- package/dist/core/places/walking.d.ts +56 -0
- package/dist/core/places/walking.js +121 -0
- package/dist/core/projects.d.ts +5 -0
- package/dist/core/projects.js +39 -18
- package/dist/core/responses.d.ts +96 -0
- package/dist/core/responses.js +122 -0
- package/dist/core/scheduler/cron-scheduler.js +29 -7
- package/dist/core/scheduler/index.d.ts +1 -1
- package/dist/core/scheduler/index.js +1 -1
- package/dist/core/scheduler/job-runner.js +1 -1
- package/dist/core/search/conversations.js +40 -42
- package/dist/core/search/entities.js +6 -9
- package/dist/core/search/graph-boost.d.ts +7 -0
- package/dist/core/search/graph-boost.js +23 -0
- package/dist/core/search/qmd-search.js +4 -4
- package/dist/core/security/encrypt.d.ts +6 -0
- package/dist/core/security/encrypt.js +47 -0
- package/dist/core/{governance.d.ts → security/governance.d.ts} +6 -1
- package/dist/core/security/governance.js +79 -0
- package/dist/core/session/auto-load.js +6 -6
- package/dist/core/session/index.d.ts +1 -1
- package/dist/core/session/index.js +1 -1
- package/dist/core/session/self-iteration-job.d.ts +20 -0
- package/dist/core/session/self-iteration-job.js +282 -0
- package/dist/core/session/session-hooks.d.ts +18 -0
- package/dist/core/session/session-hooks.js +58 -0
- package/dist/core/session-hooks/self-iteration-job.js +35 -35
- package/dist/core/{cache.js → storage/cache.js} +2 -2
- package/dist/core/sync/qmd-sync.d.ts +1 -13
- package/dist/core/sync/qmd-sync.js +1 -13
- package/dist/core/toon.d.ts +43 -0
- package/dist/core/toon.js +160 -0
- package/dist/core/utils/memory-operations.js +1 -1
- package/dist/core/utils/vector-operations.d.ts +71 -0
- package/dist/core/utils/vector-operations.js +129 -0
- package/dist/db/adapter.d.ts +3 -3
- package/dist/db/adapter.js +99 -88
- package/dist/db/bootstrap.js +820 -522
- package/dist/{drizzle → db/drizzle}/schema-sqlite.d.ts +74 -25
- package/dist/{drizzle → db/drizzle}/schema-sqlite.js +91 -24
- package/dist/{drizzle → db/drizzle}/schema.d.ts +79 -32
- package/dist/{drizzle → db/drizzle}/schema.js +106 -35
- package/dist/db/drizzle.config.d.ts +3 -0
- package/dist/db/drizzle.config.js +12 -0
- package/dist/db/index.d.ts +1 -5
- package/dist/db/index.js +51 -8
- package/dist/db/neon.d.ts +8 -0
- package/dist/db/neon.js +20 -0
- package/dist/db/schema/index.d.ts +40 -0
- package/dist/db/schema/index.js +105 -0
- package/dist/db/schema/tables/context-sessions.d.ts +9 -0
- package/dist/db/schema/tables/context-sessions.js +37 -0
- package/dist/db/schema/tables/conversations.d.ts +9 -0
- package/dist/db/schema/tables/conversations.js +47 -0
- package/dist/db/schema/tables/core-memory.d.ts +9 -0
- package/dist/db/schema/tables/core-memory.js +41 -0
- package/dist/db/schema/tables/entities.d.ts +9 -0
- package/dist/db/schema/tables/entities.js +39 -0
- package/dist/db/schema/tables/entity-relations.d.ts +9 -0
- package/dist/db/schema/tables/entity-relations.js +31 -0
- package/dist/db/schema/tables/learnings.d.ts +9 -0
- package/dist/db/schema/tables/learnings.js +66 -0
- package/dist/db/schema/tables/memories.d.ts +9 -0
- package/dist/db/schema/tables/memories.js +161 -0
- package/dist/db/schema/tables/memory-associations.d.ts +9 -0
- package/dist/db/schema/tables/memory-associations.js +39 -0
- package/dist/db/schema/tables/memory-hash-cache.d.ts +9 -0
- package/dist/db/schema/tables/memory-hash-cache.js +29 -0
- package/dist/db/schema/tables/memory-merge-history.d.ts +9 -0
- package/dist/db/schema/tables/memory-merge-history.js +33 -0
- package/dist/db/schema/tables/memory-merge-proposals.d.ts +9 -0
- package/dist/db/schema/tables/memory-merge-proposals.js +39 -0
- package/dist/db/schema/tables/messages.d.ts +9 -0
- package/dist/db/schema/tables/messages.js +41 -0
- package/dist/db/schema/tables/namespaces.d.ts +9 -0
- package/dist/db/schema/tables/namespaces.js +37 -0
- package/dist/db/schema/tables/projects.d.ts +9 -0
- package/dist/db/schema/tables/projects.js +31 -0
- package/dist/db/schema/tables/users.d.ts +9 -0
- package/dist/db/schema/tables/users.js +27 -0
- package/dist/db/schema.d.ts +1 -1
- package/dist/db/schema.js +2 -2
- package/dist/db/supabase.d.ts +9 -0
- package/dist/db/supabase.js +24 -0
- package/dist/index.d.ts +2 -14
- package/dist/index.js +1320 -640
- package/dist/vendor/sql.js/sql-wasm.wasm +0 -0
- package/dist/webui/server.d.ts +5 -0
- package/dist/{api/web/web.js → webui/server.js} +511 -508
- package/generated/mcp/manifest.json +1 -1
- package/{.mcp.json → mcp.json.example} +1 -1
- package/package.json +159 -181
- package/scripts/README.md +60 -0
- package/scripts/copy-runtime-assets.mjs +26 -0
- package/scripts/generate-mcp.mjs +264 -264
- package/scripts/github-release.sh +4 -4
- package/scripts/install-claude-code.sh +85 -0
- package/scripts/install-cursor.sh +56 -0
- package/scripts/install-hooks.sh +73 -0
- package/scripts/install-interactive.mjs +357 -677
- package/scripts/install-opencode.sh +75 -0
- package/scripts/install-windsurf.sh +67 -0
- package/skills/squish-memory/SKILL.md +104 -114
- package/skills/squish-memory/{install.mjs → scripts/install.mjs} +2 -2
- package/skills/squish-memory/{install.sh → scripts/install.sh} +2 -2
- package/skills/squish-memory/write_skill.js +2 -0
- package/.claude-plugin/marketplace.json +0 -20
- package/.claude-plugin/plugin.json +0 -32
- package/.env.mcp.example +0 -60
- package/QUICK-START.md +0 -71
- package/bin/squish-add.mjs +0 -32
- package/bin/squish-rm.mjs +0 -21
- package/commands/observe.md +0 -5
- package/dist/api/web/index.d.ts +0 -3
- package/dist/api/web/index.js +0 -4
- package/dist/api/web/web-server.d.ts +0 -3
- package/dist/api/web/web-server.js +0 -6
- package/dist/api/web/web.d.ts +0 -4
- package/dist/commands/mcp-server.js +0 -393
- package/dist/core/context.js +0 -24
- package/dist/core/governance.js +0 -64
- package/dist/core/observations.d.ts +0 -26
- package/dist/core/observations.js +0 -110
- package/dist/core/requirements.d.ts +0 -20
- package/dist/core/requirements.js +0 -35
- package/hooks/hooks.json +0 -52
- package/hooks/post-tool-use.js +0 -26
- package/hooks/session-end.js +0 -28
- package/hooks/session-start.js +0 -33
- package/hooks/user-prompt-submit.js +0 -26
- package/hooks/utils.js +0 -153
- package/npx-installer.js +0 -208
- package/packages/plugin-claude-code/README.md +0 -73
- package/packages/plugin-claude-code/dist/plugin-wrapper.d.ts +0 -35
- package/packages/plugin-claude-code/dist/plugin-wrapper.js +0 -191
- package/packages/plugin-claude-code/package.json +0 -31
- package/packages/plugin-openclaw/README.md +0 -70
- package/packages/plugin-openclaw/dist/index.d.ts +0 -49
- package/packages/plugin-openclaw/dist/index.js +0 -262
- package/packages/plugin-openclaw/openclaw.plugin.json +0 -94
- package/packages/plugin-openclaw/package.json +0 -31
- package/packages/plugin-opencode/install.mjs +0 -217
- package/packages/plugin-opencode/package.json +0 -21
- package/scripts/db/check-db.mjs +0 -88
- package/scripts/db/fix-all-columns.mjs +0 -52
- package/scripts/db/fix-schema-all.mjs +0 -55
- package/scripts/db/fix-schema-full.mjs +0 -46
- package/scripts/db/fix-schema.mjs +0 -38
- package/scripts/db/init-db.mjs +0 -13
- package/scripts/db/recreate-db.mjs +0 -14
- package/scripts/install-mcp.mjs +0 -116
- package/scripts/install-web.sh +0 -120
- package/scripts/install.mjs +0 -340
- package/scripts/openclaw-bootstrap.mjs +0 -127
- package/scripts/package-release.sh +0 -71
- package/scripts/test/test-all-systems.mjs +0 -139
- package/scripts/test/test-memory-system.mjs +0 -139
- package/scripts/test/test-v0.5.0.mjs +0 -210
- package/skills/memory-guide/SKILL.md +0 -332
- package/skills/squish-cli/SKILL.md +0 -240
- package/skills/squish-mcp/SKILL.md +0 -355
- package/skills/squish-memory/claude-desktop.json +0 -12
- package/skills/squish-memory/openclaw.json +0 -13
- package/skills/squish-memory/opencode.json +0 -14
- package/skills/squish-memory/skill.json +0 -32
- /package/{commands → core/commands}/context-paging.md +0 -0
- /package/{commands → core/commands}/context-status.md +0 -0
- /package/{commands → core/commands}/context.md +0 -0
- /package/{commands → core/commands}/core-memory.md +0 -0
- /package/{commands → core/commands}/health.md +0 -0
- /package/{commands → core/commands}/merge.md +0 -0
- /package/{commands → core/commands}/recall.md +0 -0
- /package/{commands → core/commands}/remember.md +0 -0
- /package/{commands → core/commands}/search.md +0 -0
- /package/dist/{algorithms → core/algorithms}/detection/hash-filters.d.ts +0 -0
- /package/dist/{algorithms → core/algorithms}/detection/hash-filters.js +0 -0
- /package/dist/{algorithms → core/algorithms}/handlers/approve-merge.d.ts +0 -0
- /package/dist/{algorithms → core/algorithms}/handlers/detect-duplicates.d.ts +0 -0
- /package/dist/{algorithms → core/algorithms}/handlers/get-stats.d.ts +0 -0
- /package/dist/{algorithms → core/algorithms}/handlers/list-proposals.d.ts +0 -0
- /package/dist/{algorithms → core/algorithms}/handlers/preview-merge.d.ts +0 -0
- /package/dist/{algorithms → core/algorithms}/handlers/reject-merge.d.ts +0 -0
- /package/dist/{algorithms → core/algorithms}/handlers/reverse-merge.d.ts +0 -0
- /package/dist/{algorithms → core/algorithms}/safety/safety-checks.js +0 -0
- /package/dist/{algorithms → core/algorithms}/utils/response-builder.d.ts +0 -0
- /package/dist/{algorithms → core/algorithms}/utils/response-builder.js +0 -0
- /package/dist/{commands → core/commands}/managed-sync.d.ts +0 -0
- /package/dist/{commands → core/commands}/mcp-server.d.ts +0 -0
- /package/dist/core/{context.d.ts → context/context.d.ts} +0 -0
- /package/dist/core/{agent-memory.d.ts → ingestion/agent-memory.d.ts} +0 -0
- /package/dist/core/{core-memory.d.ts → ingestion/core-memory.d.ts} +0 -0
- /package/dist/core/{privacy.d.ts → security/privacy.d.ts} +0 -0
- /package/dist/core/{privacy.js → security/privacy.js} +0 -0
- /package/dist/core/{secret-detector.d.ts → security/secret-detector.d.ts} +0 -0
- /package/dist/core/{secret-detector.js → security/secret-detector.js} +0 -0
- /package/dist/core/{cache.d.ts → storage/cache.d.ts} +0 -0
- /package/dist/core/{database.d.ts → storage/database.d.ts} +0 -0
- /package/dist/core/{database.js → storage/database.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Squish
|
|
4
|
-
*
|
|
5
|
-
* Modes:
|
|
6
|
-
* - CLI Mode: For any MCP client bash execution (e.g., `squish remember "text"`)
|
|
7
|
-
* - MCP Mode: For AI assistants (Claude Code, OpenClaw, OpenCode, Codex, etc.)
|
|
8
|
-
*
|
|
9
|
-
* Features:
|
|
10
|
-
* - Hybrid Search: BM25 + vector search with RRF
|
|
11
|
-
* - Importance Scoring: Auto-score memories with temporal decay
|
|
12
|
-
* - Consolidation: Summarize old, low-importance memory clusters
|
|
13
|
-
* - 16 MCP tools
|
|
14
|
-
* - Local mode: SQLite with FTS5
|
|
15
|
-
* - Team mode: PostgreSQL + pgvector
|
|
16
|
-
* - Universal Plugin: Works with 7+ AI assistants
|
|
3
|
+
* Squish - Universal Memory Plugin System
|
|
4
|
+
* CLI + MCP server for persistent memory with hybrid search and encryption
|
|
17
5
|
*/
|
|
18
6
|
import 'dotenv/config';
|
|
19
7
|
import fs from 'node:fs';
|
|
@@ -21,44 +9,157 @@ import { existsSync } from 'node:fs';
|
|
|
21
9
|
import os from 'node:os';
|
|
22
10
|
import path from 'node:path';
|
|
23
11
|
import { fileURLToPath } from 'node:url';
|
|
24
|
-
import { spawnSync } from 'node:child_process';
|
|
12
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
25
13
|
import { Command } from 'commander';
|
|
26
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
27
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
28
|
-
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
29
14
|
import { logger } from './core/logger.js';
|
|
30
|
-
import { checkDatabaseHealth, config } from './db/index.js';
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
15
|
+
import { checkDatabaseHealth, config, getDb } from './db/index.js';
|
|
16
|
+
import { getSchema } from './db/schema.js';
|
|
17
|
+
import { eq } from 'drizzle-orm';
|
|
18
|
+
import { checkRedisHealth } from './core/storage/cache.js';
|
|
19
|
+
import { rememberMemory, getMemory, search, getRecent, setConfidence } from './core/memory/memories.js';
|
|
20
|
+
import { serializeTags } from './core/memory/serialization.js';
|
|
21
|
+
import { createLearning } from './core/ingestion/learnings.js';
|
|
36
22
|
import { getMemoryStats } from './core/memory/stats.js';
|
|
37
|
-
import { ensureProject } from './core/projects.js';
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import { handleRejectMerge } from './algorithms/handlers/reject-merge.js';
|
|
45
|
-
import { handleReverseMerge } from './algorithms/handlers/reverse-merge.js';
|
|
46
|
-
import { handleGetMergeStats } from './algorithms/handlers/get-stats.js';
|
|
47
|
-
import { pinMemory, unpinMemory } from './core/governance.js';
|
|
48
|
-
import { searchWithQMD, isQMDAvailable } from './core/search/qmd-search.js';
|
|
49
|
-
import { initializeCoreMemory, getCoreMemory, editCoreMemorySection, appendCoreMemorySection, getCoreMemoryStats, } from './core/core-memory.js';
|
|
50
|
-
import { loadMemoryToContext, evictMemoryFromContext, viewLoadedMemories, getContextStatus, } from './core/context-paging.js';
|
|
23
|
+
import { ensureProject, getAllProjects, getOrCreateProject } from './core/projects.js';
|
|
24
|
+
import { startWebServer } from './webui/server.js';
|
|
25
|
+
import { getRelatedMemories, createAssociation } from './core/associations.js';
|
|
26
|
+
import { pinMemory, unpinMemory } from './core/security/governance.js';
|
|
27
|
+
import { determineOverallStatus } from './core/lib/utils.js';
|
|
28
|
+
import { validateLimit } from './core/lib/validation.js';
|
|
29
|
+
import { runDeduplicationJob, runFullConsolidationJob } from './core/consolidation.js';
|
|
51
30
|
import { ensureDataDirectory } from './db/bootstrap.js';
|
|
52
31
|
import { getDataDir } from './config.js';
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
32
|
+
import { handleSessionStart, handlePostToolUse, handleSessionEnd, handlePreCompact, } from './core/hooks/agent-hooks.js';
|
|
33
|
+
import { initializeDefaultPlaces, walkPlace, walkAllPlaces, quickTour, } from './core/places/index.js';
|
|
34
|
+
const VERSION = '1.1.5';
|
|
35
|
+
// Output Formatting Utilities
|
|
36
|
+
// ============================================================================
|
|
37
|
+
function formatOutput(data, pretty = false) {
|
|
38
|
+
if (!pretty) {
|
|
39
|
+
return JSON.stringify(data, null, 2);
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(data)) {
|
|
42
|
+
return data.map((item, i) => `${i + 1}. ${formatItem(item)}`).join('\n');
|
|
43
|
+
}
|
|
44
|
+
if (data.results) {
|
|
45
|
+
return data.results.map((item, i) => `${i + 1}. ${formatItem(item)}`).join('\n');
|
|
46
|
+
}
|
|
47
|
+
if (data.matches) {
|
|
48
|
+
return data.matches.map((item, i) => `${i + 1}. ${formatItem(item)}`).join('\n');
|
|
49
|
+
}
|
|
50
|
+
if (data.count !== undefined) {
|
|
51
|
+
let output = `\nFound ${data.count} results:\n`;
|
|
52
|
+
if (data.results) {
|
|
53
|
+
output += data.results.map((item, i) => ` ${i + 1}. ${formatItem(item)}`).join('\n');
|
|
54
|
+
}
|
|
55
|
+
return output;
|
|
56
|
+
}
|
|
57
|
+
return JSON.stringify(data, null, 2);
|
|
58
|
+
}
|
|
59
|
+
function formatItem(item) {
|
|
60
|
+
if (typeof item === 'string')
|
|
61
|
+
return item.substring(0, 100);
|
|
62
|
+
const content = item.content || item.summary || item.memory?.content || '';
|
|
63
|
+
const type = item.type || '';
|
|
64
|
+
return `[${type}] ${content.substring(0, 80)}${content.length > 80 ? '...' : ''}`;
|
|
65
|
+
}
|
|
66
|
+
function printSuccess(message) {
|
|
67
|
+
console.log(`\n ✓ ${message}\n`);
|
|
68
|
+
}
|
|
69
|
+
function printError(message) {
|
|
70
|
+
console.error(`\n ✗ ${message}\n`);
|
|
71
|
+
}
|
|
72
|
+
function printInfo(message) {
|
|
73
|
+
console.log(`\n ℹ ${message}\n`);
|
|
74
|
+
}
|
|
75
|
+
// Config Management (for project path persistence)
|
|
76
|
+
// ============================================================================
|
|
77
|
+
const USER_CONFIG_PATH = path.join(os.homedir(), '.squish', 'config.json');
|
|
78
|
+
function loadUserConfig() {
|
|
79
|
+
try {
|
|
80
|
+
if (fs.existsSync(USER_CONFIG_PATH)) {
|
|
81
|
+
return JSON.parse(fs.readFileSync(USER_CONFIG_PATH, 'utf-8'));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (e) { }
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
function saveUserConfig(config) {
|
|
88
|
+
const dir = path.dirname(USER_CONFIG_PATH);
|
|
89
|
+
if (!fs.existsSync(dir))
|
|
90
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
91
|
+
fs.writeFileSync(USER_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
92
|
+
}
|
|
93
|
+
function getDefaultProjectPath() {
|
|
94
|
+
const userConfig = loadUserConfig();
|
|
95
|
+
if (userConfig.project)
|
|
96
|
+
return userConfig.project;
|
|
97
|
+
return process.cwd();
|
|
98
|
+
}
|
|
99
|
+
function resolveProjectPath(projectOption) {
|
|
100
|
+
if (projectOption)
|
|
101
|
+
return projectOption;
|
|
102
|
+
return getDefaultProjectPath();
|
|
103
|
+
}
|
|
104
|
+
// Date parsing for time-based queries
|
|
105
|
+
// ============================================================================
|
|
106
|
+
function parseDate(input) {
|
|
107
|
+
if (!input)
|
|
108
|
+
return null;
|
|
109
|
+
const now = new Date();
|
|
110
|
+
const lower = input.toLowerCase().trim();
|
|
111
|
+
// Direct date parse
|
|
112
|
+
const parsed = new Date(input);
|
|
113
|
+
if (!isNaN(parsed.getTime()))
|
|
114
|
+
return parsed;
|
|
115
|
+
// Relative parsing
|
|
116
|
+
const dayMatch = lower.match(/(\d+)\s*day/i);
|
|
117
|
+
const weekMatch = lower.match(/(\d+)\s*week/i);
|
|
118
|
+
const monthMatch = lower.match(/(\d+)\s*month/i);
|
|
119
|
+
if (lower === 'today') {
|
|
120
|
+
const d = new Date(now);
|
|
121
|
+
d.setHours(0, 0, 0, 0);
|
|
122
|
+
return d;
|
|
123
|
+
}
|
|
124
|
+
if (lower === 'yesterday')
|
|
125
|
+
return new Date(now.getTime() - 86400000);
|
|
126
|
+
if (lower === 'thisweek' || lower === 'this week') {
|
|
127
|
+
const d = new Date(now);
|
|
128
|
+
d.setDate(d.getDate() - d.getDay());
|
|
129
|
+
d.setHours(0, 0, 0, 0);
|
|
130
|
+
return d;
|
|
131
|
+
}
|
|
132
|
+
if (lower === 'lastweek' || lower === 'last week') {
|
|
133
|
+
const d = new Date(now);
|
|
134
|
+
d.setDate(d.getDate() - d.getDay() - 7);
|
|
135
|
+
return d;
|
|
136
|
+
}
|
|
137
|
+
if (dayMatch)
|
|
138
|
+
return new Date(now.getTime() - parseInt(dayMatch[1]) * 86400000);
|
|
139
|
+
if (weekMatch)
|
|
140
|
+
return new Date(now.getTime() - parseInt(weekMatch[1]) * 604800000);
|
|
141
|
+
if (monthMatch)
|
|
142
|
+
return new Date(now.getTime() - parseInt(monthMatch[1]) * 2592000000);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function filterByDateRange(items, since, until) {
|
|
146
|
+
const sinceDate = parseDate(since || '');
|
|
147
|
+
const untilDate = parseDate(until || '');
|
|
148
|
+
return items.filter(item => {
|
|
149
|
+
if (!item.createdAt)
|
|
150
|
+
return true;
|
|
151
|
+
const created = new Date(item.createdAt);
|
|
152
|
+
if (sinceDate && created < sinceDate)
|
|
153
|
+
return false;
|
|
154
|
+
if (untilDate && created > untilDate)
|
|
155
|
+
return false;
|
|
156
|
+
return true;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
58
159
|
// Load plugin manifest for self-verification
|
|
59
160
|
function loadPluginManifest() {
|
|
60
161
|
try {
|
|
61
|
-
const manifestPath = path.join(
|
|
162
|
+
const manifestPath = path.join(getDefaultProjectPath(), 'config', 'plugin-manifest.json');
|
|
62
163
|
if (fs.existsSync(manifestPath)) {
|
|
63
164
|
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
64
165
|
}
|
|
@@ -90,33 +191,71 @@ function verifyManifest(manifest) {
|
|
|
90
191
|
});
|
|
91
192
|
return { ok: errors.length === 0, errors };
|
|
92
193
|
}
|
|
93
|
-
|
|
194
|
+
async function buildHealthStatus() {
|
|
195
|
+
const dbHealth = await checkDatabaseHealth();
|
|
196
|
+
const redisHealth = await checkRedisHealth();
|
|
197
|
+
const database = dbHealth ? 'ok' : 'error';
|
|
198
|
+
const cache = config.redisEnabled ? (redisHealth ? 'ok' : 'error') : 'unavailable';
|
|
199
|
+
const overallStatus = config.redisEnabled
|
|
200
|
+
? determineOverallStatus(database, redisHealth)
|
|
201
|
+
: (dbHealth ? 'ok' : 'error');
|
|
202
|
+
return {
|
|
203
|
+
ok: overallStatus === 'ok',
|
|
204
|
+
version: VERSION,
|
|
205
|
+
mode: config.isTeamMode ? 'team' : 'local',
|
|
206
|
+
database,
|
|
207
|
+
cache,
|
|
208
|
+
dataDirectory: config.dataDir,
|
|
209
|
+
dataDirectoryExists: fs.existsSync(config.dataDir),
|
|
210
|
+
status: overallStatus,
|
|
211
|
+
timestamp: new Date().toISOString(),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
94
214
|
// HELPER FUNCTIONS
|
|
95
215
|
// ============================================================================
|
|
96
216
|
function showHelp() {
|
|
97
|
-
console.log(`
|
|
98
|
-
Squish Memory v${VERSION} - Universal Memory Plugin System
|
|
99
|
-
|
|
100
|
-
Usage:
|
|
101
|
-
squish Start interactive wizard
|
|
102
|
-
squish run mcp Start MCP server
|
|
103
|
-
squish run web Start Web UI only
|
|
104
|
-
squish <command> [options] Run CLI commands for agents
|
|
105
|
-
|
|
106
|
-
CLI Commands (for agents):
|
|
107
|
-
squish
|
|
108
|
-
squish
|
|
109
|
-
squish
|
|
110
|
-
squish
|
|
111
|
-
squish
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
squish
|
|
115
|
-
squish
|
|
116
|
-
squish
|
|
117
|
-
squish
|
|
118
|
-
|
|
119
|
-
|
|
217
|
+
console.log(`
|
|
218
|
+
Squish Memory v${VERSION} - Universal Memory Plugin System
|
|
219
|
+
|
|
220
|
+
Usage:
|
|
221
|
+
squish Start interactive wizard
|
|
222
|
+
squish run mcp Start MCP server
|
|
223
|
+
squish run web Start Web UI only
|
|
224
|
+
squish <command> [options] Run CLI commands for agents
|
|
225
|
+
|
|
226
|
+
CLI Commands (for agents):
|
|
227
|
+
squish config [action] Manage Squish configuration
|
|
228
|
+
squish remember <content> Store a memory
|
|
229
|
+
squish note <content> Save a quick note
|
|
230
|
+
squish learn <type> <text> Record learning: success, failure, fix, insight
|
|
231
|
+
squish search <query> Search memories (--pretty for human output)
|
|
232
|
+
squish recall <query> Search or get by ID (--pretty for human output)
|
|
233
|
+
squish recent --period Recent memories (today/yesterday/thisweek/7days/30days)
|
|
234
|
+
squish update <memoryId> Update memory
|
|
235
|
+
squish forget <memoryId> Delete memory (single or bulk with --older-than --search)
|
|
236
|
+
squish pin <memoryId> Pin/unpin memory
|
|
237
|
+
squish confidence <id> Set confidence
|
|
238
|
+
squish tag <action> Manage tags
|
|
239
|
+
squish stale Show stale memories
|
|
240
|
+
squish link <action> Manage links (find/add/list)
|
|
241
|
+
squish migrate Migrate memories between .squish directories
|
|
242
|
+
squish clean Dedup + consolidate (maintenance)
|
|
243
|
+
squish context Show context or list projects
|
|
244
|
+
squish stats View memory statistics
|
|
245
|
+
|
|
246
|
+
Examples:
|
|
247
|
+
squish run mcp # Start MCP server (for agents)
|
|
248
|
+
squish run web # Start Web UI only
|
|
249
|
+
squish config set project /repo/path
|
|
250
|
+
squish remember "Hello" # Store memory
|
|
251
|
+
squish note "Ship v1 first" # Save a quick note
|
|
252
|
+
squish learn observation "Updated auth flow" --action edit
|
|
253
|
+
squish learn fix "Patched auth middleware" --target middleware.ts
|
|
254
|
+
squish search "query" # Search memories
|
|
255
|
+
squish context --list-projects
|
|
256
|
+
squish clean # Run deduplication and consolidation
|
|
257
|
+
|
|
258
|
+
For more info: https://github.com/michielhdoteth/squish
|
|
120
259
|
`);
|
|
121
260
|
}
|
|
122
261
|
async function runInteractiveInstaller() {
|
|
@@ -144,7 +283,8 @@ async function runInteractiveInstaller() {
|
|
|
144
283
|
switch (selected) {
|
|
145
284
|
case 'mcp':
|
|
146
285
|
log.step('Starting MCP server...');
|
|
147
|
-
await
|
|
286
|
+
const { spawn } = await import('child_process');
|
|
287
|
+
spawn('npx', ['squish-mcp'], { stdio: 'inherit', shell: true });
|
|
148
288
|
break;
|
|
149
289
|
case 'web':
|
|
150
290
|
log.step('Starting Web UI...');
|
|
@@ -172,20 +312,17 @@ async function runCliCommand(command) {
|
|
|
172
312
|
await ensureDataDirectory();
|
|
173
313
|
});
|
|
174
314
|
if (command === 'health') {
|
|
175
|
-
const
|
|
176
|
-
const redisHealth = await checkRedisHealth();
|
|
177
|
-
const dataDir = process.env.SQUISH_DATA_DIR || path.join(os.homedir(), '.squish');
|
|
178
|
-
const dirExists = fs.existsSync(dataDir);
|
|
315
|
+
const status = await buildHealthStatus();
|
|
179
316
|
console.log(`\n Squish Memory v${VERSION}`);
|
|
180
317
|
console.log(` ====================`);
|
|
181
|
-
console.log(` Mode: ${
|
|
182
|
-
console.log(` Database: ${
|
|
183
|
-
console.log(` Cache: ${
|
|
184
|
-
console.log(` Data Dir: ${
|
|
185
|
-
console.log(` Status: ${
|
|
318
|
+
console.log(` Mode: ${status.mode}`);
|
|
319
|
+
console.log(` Database: ${status.database}`);
|
|
320
|
+
console.log(` Cache: ${status.cache}`);
|
|
321
|
+
console.log(` Data Dir: ${status.dataDirectory}`);
|
|
322
|
+
console.log(` Status: ${status.ok ? 'HEALTHY' : 'UNHEALTHY'}\n`);
|
|
186
323
|
}
|
|
187
324
|
else if (command === 'stats') {
|
|
188
|
-
const stats = await getMemoryStats(
|
|
325
|
+
const stats = await getMemoryStats(getDefaultProjectPath());
|
|
189
326
|
console.log(JSON.stringify({ ok: true, ...stats }, null, 2));
|
|
190
327
|
}
|
|
191
328
|
}
|
|
@@ -218,9 +355,8 @@ function isDatabaseInitialized() {
|
|
|
218
355
|
async function runWebOnly() {
|
|
219
356
|
console.log(`[squish] Starting Web UI only...`);
|
|
220
357
|
await ensureDataDirectory();
|
|
221
|
-
startWebServer();
|
|
358
|
+
await startWebServer();
|
|
222
359
|
}
|
|
223
|
-
// ============================================================================
|
|
224
360
|
// CLI MODE DETECTION
|
|
225
361
|
// ============================================================================
|
|
226
362
|
const args = process.argv.slice(2);
|
|
@@ -236,7 +372,7 @@ if (isNoArgs) {
|
|
|
236
372
|
await spawnInstallerWizard();
|
|
237
373
|
}
|
|
238
374
|
else {
|
|
239
|
-
//
|
|
375
|
+
// INTERACTIVE WIZARD (default when no args) ===
|
|
240
376
|
runInteractiveInstaller().catch((e) => {
|
|
241
377
|
console.error('Installer error:', e.message);
|
|
242
378
|
process.exit(1);
|
|
@@ -244,13 +380,35 @@ if (isNoArgs) {
|
|
|
244
380
|
}
|
|
245
381
|
}
|
|
246
382
|
else if (isRunCommand) {
|
|
247
|
-
//
|
|
383
|
+
// RUN SUBCOMMAND ===
|
|
248
384
|
const subcommand = args[1];
|
|
249
385
|
if (subcommand === 'mcp') {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
386
|
+
(async () => {
|
|
387
|
+
try {
|
|
388
|
+
// Initialize data directory before starting MCP
|
|
389
|
+
await ensureDataDirectory();
|
|
390
|
+
// Start MCP server as child process (stdio mode for agents)
|
|
391
|
+
const mcpProcess = spawn('npx', ['squish-mcp'], {
|
|
392
|
+
stdio: 'inherit',
|
|
393
|
+
shell: true
|
|
394
|
+
});
|
|
395
|
+
// Forward MCP exit code when it exits
|
|
396
|
+
mcpProcess.on('exit', (code) => {
|
|
397
|
+
process.exit(code ?? 0);
|
|
398
|
+
});
|
|
399
|
+
// Clean shutdown: forward signals to MCP child
|
|
400
|
+
const cleanup = () => {
|
|
401
|
+
mcpProcess.kill('SIGTERM');
|
|
402
|
+
setTimeout(() => process.exit(0), 100);
|
|
403
|
+
};
|
|
404
|
+
process.on('SIGINT', cleanup);
|
|
405
|
+
process.on('SIGTERM', cleanup);
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
console.error('[squish] Failed to start MCP server:', error.message);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
})();
|
|
254
412
|
}
|
|
255
413
|
else if (subcommand === 'web') {
|
|
256
414
|
runWebOnly().catch((e) => {
|
|
@@ -259,33 +417,32 @@ else if (isRunCommand) {
|
|
|
259
417
|
});
|
|
260
418
|
}
|
|
261
419
|
else {
|
|
262
|
-
console.log(`
|
|
263
|
-
Usage: squish run <command>
|
|
264
|
-
|
|
265
|
-
Commands:
|
|
266
|
-
mcp Start MCP server
|
|
267
|
-
web Start Web UI only
|
|
268
|
-
|
|
269
|
-
Examples:
|
|
270
|
-
squish run mcp # Start MCP server
|
|
271
|
-
squish run web # Start Web UI
|
|
420
|
+
console.log(`
|
|
421
|
+
Usage: squish run <command>
|
|
422
|
+
|
|
423
|
+
Commands:
|
|
424
|
+
mcp Start MCP server (for agents like Claude Code)
|
|
425
|
+
web Start Web UI only
|
|
426
|
+
|
|
427
|
+
Examples:
|
|
428
|
+
squish run mcp # Start MCP server (agents connect automatically)
|
|
429
|
+
squish run web # Start Web UI at http://localhost:37777
|
|
272
430
|
`);
|
|
273
431
|
process.exit(subcommand ? 1 : 0);
|
|
274
432
|
}
|
|
275
433
|
}
|
|
276
434
|
else if (isHelpCommand) {
|
|
277
|
-
//
|
|
435
|
+
// SHOW HELP ===
|
|
278
436
|
showHelp();
|
|
279
437
|
process.exit(0);
|
|
280
438
|
}
|
|
281
439
|
else {
|
|
282
|
-
//
|
|
440
|
+
// CLI MODE (for agents/OpenClaw) ===
|
|
283
441
|
runCliMode().catch((e) => {
|
|
284
442
|
console.error(JSON.stringify({ error: e.message }, null, 2));
|
|
285
443
|
process.exit(1);
|
|
286
444
|
});
|
|
287
445
|
}
|
|
288
|
-
// ============================================================================
|
|
289
446
|
// CLI MODE (for OpenClaw bash execution)
|
|
290
447
|
// ============================================================================
|
|
291
448
|
async function runCliMode() {
|
|
@@ -298,20 +455,136 @@ async function runCliMode() {
|
|
|
298
455
|
program.hook('preAction', async () => {
|
|
299
456
|
await ensureDataDirectory();
|
|
300
457
|
});
|
|
458
|
+
// squish config [get] [key] or squish config set <key> <value>
|
|
459
|
+
program
|
|
460
|
+
.command('config')
|
|
461
|
+
.description('Manage Squish configuration')
|
|
462
|
+
.argument('[action]', 'get, set, or list', 'list')
|
|
463
|
+
.argument('[key]', 'Config key (e.g., project)')
|
|
464
|
+
.argument('[value]', 'Config value (for set action)')
|
|
465
|
+
.action(async (action, key, value) => {
|
|
466
|
+
const userConfig = loadUserConfig();
|
|
467
|
+
if (action === 'set') {
|
|
468
|
+
if (!key || value === undefined) {
|
|
469
|
+
console.log(JSON.stringify({ ok: false, error: 'Usage: squish config set <key> <value>' }, null, 2));
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
userConfig[key] = value;
|
|
473
|
+
saveUserConfig(userConfig);
|
|
474
|
+
console.log(JSON.stringify({ ok: true, message: `Set ${key} = ${value}` }, null, 2));
|
|
475
|
+
}
|
|
476
|
+
else if (action === 'get') {
|
|
477
|
+
if (!key) {
|
|
478
|
+
console.log(JSON.stringify({ ok: false, error: 'Usage: squish config get <key>' }, null, 2));
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
console.log(JSON.stringify({ ok: true, [key]: userConfig[key] || null }, null, 2));
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
console.log(JSON.stringify({ ok: true, config: userConfig }, null, 2));
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
// squish mount /path/to/folder - Enable external memory
|
|
488
|
+
program
|
|
489
|
+
.command('mount')
|
|
490
|
+
.description('Mount an external folder as memory storage')
|
|
491
|
+
.argument('[path]', 'Path to external folder (or "status" or "unmount")')
|
|
492
|
+
.action(async (pathOrAction) => {
|
|
493
|
+
const { getExternalMemory } = await import('./core/external-folder/index.js');
|
|
494
|
+
const externalMemory = getExternalMemory();
|
|
495
|
+
if (pathOrAction === 'status') {
|
|
496
|
+
// Show mount status
|
|
497
|
+
const status = await externalMemory.getStatus();
|
|
498
|
+
console.log(JSON.stringify({ ok: true, status }, null, 2));
|
|
499
|
+
}
|
|
500
|
+
else if (pathOrAction === 'unmount') {
|
|
501
|
+
// Unmount
|
|
502
|
+
externalMemory.unmount();
|
|
503
|
+
console.log(JSON.stringify({ ok: true, message: 'External memory unmounted' }, null, 2));
|
|
504
|
+
}
|
|
505
|
+
else if (pathOrAction) {
|
|
506
|
+
// Mount at path
|
|
507
|
+
const result = await externalMemory.mount(pathOrAction);
|
|
508
|
+
if (result.success) {
|
|
509
|
+
console.log(JSON.stringify({ ok: true, message: `Mounted at ${pathOrAction}` }, null, 2));
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
console.log(JSON.stringify({ ok: false, error: result.error }, null, 2));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
console.log(JSON.stringify({ ok: false, error: 'Usage: squish mount <path> or squish mount status' }, null, 2));
|
|
517
|
+
}
|
|
518
|
+
});
|
|
301
519
|
// squish remember "content" --type fact --tags tag1,tag2
|
|
302
520
|
program
|
|
303
521
|
.command('remember <content>')
|
|
304
522
|
.description('Store a memory')
|
|
305
523
|
.option('-t, --type <type>', 'Memory type (observation, fact, decision, context, preference)', 'observation')
|
|
306
524
|
.option('-T, --tags <tags>', 'Comma-separated tags', '')
|
|
307
|
-
.option('-p, --project <project>', 'Project path',
|
|
525
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
526
|
+
.option('-s, --source <source>', 'Source of this memory (e.g., "voice", "chat", "document")')
|
|
527
|
+
.option('-r, --reasoning <reasoning>', 'Why this memory is important')
|
|
528
|
+
.option('-c, --context <context>', 'What triggered this memory')
|
|
529
|
+
.option('-e, --examples <examples>', 'When to apply this knowledge')
|
|
530
|
+
.option('-x, --exceptions <exceptions>', 'When NOT to apply this')
|
|
531
|
+
.option('-m, --memory', 'Store as markdown file in .squish/memory/ (not in database)', false)
|
|
532
|
+
.option('-H, --hot', 'Store in hot tier (active, high priority) in database', false)
|
|
533
|
+
.option('-C, --cold', 'Store in cold tier (archived, lower priority) in database', false)
|
|
308
534
|
.action(async (content, options) => {
|
|
309
535
|
try {
|
|
536
|
+
// Markdown file storage (not in database)
|
|
537
|
+
if (options.memory) {
|
|
538
|
+
const { saveToMarkdown } = await import('./core/memory/markdown/markdown-storage.js');
|
|
539
|
+
const memoryFile = await saveToMarkdown({
|
|
540
|
+
content,
|
|
541
|
+
type: options.type,
|
|
542
|
+
tags: options.tags ? options.tags.split(',').map((t) => t.trim()) : [],
|
|
543
|
+
project: options.project,
|
|
544
|
+
source: options.source,
|
|
545
|
+
reasoning: options.reasoning,
|
|
546
|
+
memoryContext: options.context,
|
|
547
|
+
examples: options.examples,
|
|
548
|
+
exceptions: options.exceptions,
|
|
549
|
+
});
|
|
550
|
+
// Trigger hooks
|
|
551
|
+
const { triggerMemoryCreated } = await import('./core/memory/hooks.js');
|
|
552
|
+
await triggerMemoryCreated({
|
|
553
|
+
memoryId: memoryFile.id,
|
|
554
|
+
content: memoryFile.content,
|
|
555
|
+
type: memoryFile.type,
|
|
556
|
+
tags: memoryFile.tags,
|
|
557
|
+
project: memoryFile.project,
|
|
558
|
+
source: memoryFile.source,
|
|
559
|
+
tier: 'hot',
|
|
560
|
+
});
|
|
561
|
+
console.log(JSON.stringify({ ok: true, memory: true, ...memoryFile }, null, 2));
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
// Database storage: determine tier
|
|
565
|
+
const tier = options.cold ? 'cold' : 'hot';
|
|
310
566
|
const result = await rememberMemory({
|
|
311
567
|
content,
|
|
312
568
|
type: options.type,
|
|
313
569
|
tags: options.tags ? options.tags.split(',').map((t) => t.trim()) : [],
|
|
314
570
|
project: options.project,
|
|
571
|
+
source: options.source,
|
|
572
|
+
reasoning: options.reasoning,
|
|
573
|
+
memoryContext: options.context,
|
|
574
|
+
examples: options.examples,
|
|
575
|
+
exceptions: options.exceptions,
|
|
576
|
+
tier,
|
|
577
|
+
});
|
|
578
|
+
// Trigger hooks for DB storage
|
|
579
|
+
const { triggerMemoryCreated } = await import('./core/memory/hooks.js');
|
|
580
|
+
await triggerMemoryCreated({
|
|
581
|
+
memoryId: result.id,
|
|
582
|
+
content: result.content,
|
|
583
|
+
type: result.type,
|
|
584
|
+
tags: result.tags,
|
|
585
|
+
project: result.projectId || undefined,
|
|
586
|
+
source: options.source || 'cli',
|
|
587
|
+
tier,
|
|
315
588
|
});
|
|
316
589
|
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
317
590
|
}
|
|
@@ -320,218 +593,559 @@ async function runCliMode() {
|
|
|
320
593
|
process.exit(1);
|
|
321
594
|
}
|
|
322
595
|
});
|
|
323
|
-
// squish search "query" --type fact --limit 10
|
|
596
|
+
// squish search "query" --type fact --limit 10 --since "3 days ago" --place workshop
|
|
324
597
|
program
|
|
325
598
|
.command('search <query>')
|
|
326
599
|
.description('Search memories')
|
|
327
600
|
.option('-t, --type <type>', 'Filter by memory type')
|
|
328
601
|
.option('-l, --limit <number>', 'Max results', '10')
|
|
329
|
-
.option('-p, --project <project>', 'Project path',
|
|
602
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
603
|
+
.option('-s, --since <date>', 'Filter: created after this date (e.g., "3 days ago", "2026-01-01")')
|
|
604
|
+
.option('-u, --until <date>', 'Filter: created before this date (e.g., "yesterday", "2026-01-15")')
|
|
605
|
+
.option('-P, --pretty', 'Human-friendly output', false)
|
|
606
|
+
.option('-m, --memory', 'Search memory files instead of database', false)
|
|
607
|
+
.option('--place <type>', 'Filter by place type: entry_hall, library, workshop, lab, office, garden, archive')
|
|
330
608
|
.action(async (query, options) => {
|
|
331
609
|
try {
|
|
332
|
-
|
|
610
|
+
// Markdown file search
|
|
611
|
+
if (options.memory) {
|
|
612
|
+
const { getMarkdownMemories } = await import('./core/memory/markdown/markdown-storage.js');
|
|
613
|
+
const memoryFiles = await getMarkdownMemories({
|
|
614
|
+
type: options.type,
|
|
615
|
+
project: options.project,
|
|
616
|
+
});
|
|
617
|
+
// Simple text search (can be enhanced with QMD later)
|
|
618
|
+
const searchLower = query.toLowerCase();
|
|
619
|
+
const filtered = memoryFiles.filter(m => m.content.toLowerCase().includes(searchLower) ||
|
|
620
|
+
m.tags.some(t => t.toLowerCase().includes(searchLower))).slice(0, validateLimit(options.limit, 10, 1, 100));
|
|
621
|
+
if (options.pretty) {
|
|
622
|
+
console.log(`\n Memory Search: "${query}"`);
|
|
623
|
+
console.log(` Found ${filtered.length} results:\n`);
|
|
624
|
+
filtered.forEach((r, i) => {
|
|
625
|
+
console.log(` ${i + 1}. [${r.type || 'memory'}] ${(r.content || '').substring(0, 60)}...`);
|
|
626
|
+
});
|
|
627
|
+
console.log('');
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
console.log(JSON.stringify({ ok: true, query, source: 'memory', count: filtered.length, results: filtered }, null, 2));
|
|
631
|
+
}
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
// Database search
|
|
635
|
+
const results = await search({
|
|
333
636
|
query,
|
|
334
637
|
type: options.type,
|
|
335
|
-
limit:
|
|
638
|
+
limit: validateLimit(options.limit, 10, 1, 100) * 2,
|
|
336
639
|
project: options.project,
|
|
337
640
|
});
|
|
338
|
-
|
|
641
|
+
const filtered = filterByDateRange(results, options.since, options.until);
|
|
642
|
+
let limited = filtered.slice(0, validateLimit(options.limit, 10, 1, 100));
|
|
643
|
+
// Add place info to results
|
|
644
|
+
const { getMemoryPlace } = await import('./core/places/index.js');
|
|
645
|
+
const limitedWithPlace = await Promise.all(limited.map(async (r) => {
|
|
646
|
+
const placeId = await getMemoryPlace(r.id);
|
|
647
|
+
return { ...r, placeId };
|
|
648
|
+
}));
|
|
649
|
+
// Filter by place if specified
|
|
650
|
+
if (options.place) {
|
|
651
|
+
const placeFiltered = [];
|
|
652
|
+
for (const r of limitedWithPlace) {
|
|
653
|
+
if (r.placeId) {
|
|
654
|
+
const { getPlace } = await import('./core/places/index.js');
|
|
655
|
+
const place = await getPlace(r.placeId);
|
|
656
|
+
if (place && place.placeType === options.place) {
|
|
657
|
+
placeFiltered.push({ ...r, place: place.name || null, placeType: place.placeType || null });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
limited = placeFiltered;
|
|
662
|
+
}
|
|
663
|
+
else if (limitedWithPlace.length > 0) {
|
|
664
|
+
// Add place info to results even without filter
|
|
665
|
+
for (const r of limitedWithPlace) {
|
|
666
|
+
if (r.placeId) {
|
|
667
|
+
const { getPlace } = await import('./core/places/index.js');
|
|
668
|
+
const place = await getPlace(r.placeId);
|
|
669
|
+
if (place) {
|
|
670
|
+
r.place = place.name || null;
|
|
671
|
+
r.placeType = place.placeType || null;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
limited = limitedWithPlace;
|
|
676
|
+
}
|
|
677
|
+
if (options.pretty) {
|
|
678
|
+
console.log(`\n Search: "${query}"`);
|
|
679
|
+
console.log(` Found ${limited.length} results:\n`);
|
|
680
|
+
limited.forEach((r, i) => {
|
|
681
|
+
const placeTag = r.place ? ` (${r.place})` : '';
|
|
682
|
+
console.log(` ${i + 1}. [${r.type || 'memory'}] ${(r.content || '').substring(0, 60)}...${placeTag}`);
|
|
683
|
+
});
|
|
684
|
+
console.log('');
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
console.log(JSON.stringify({ ok: true, query, count: limited.length, since: options.since, until: options.until, placeFilter: options.place || null, results: limited }, null, 2));
|
|
688
|
+
}
|
|
339
689
|
}
|
|
340
690
|
catch (error) {
|
|
341
691
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
342
692
|
process.exit(1);
|
|
343
693
|
}
|
|
344
694
|
});
|
|
345
|
-
// squish
|
|
695
|
+
// squish forget <memoryId> -- Delete single or bulk delete memories
|
|
346
696
|
program
|
|
347
|
-
.command('
|
|
348
|
-
.description('
|
|
349
|
-
.
|
|
697
|
+
.command('forget [memoryId]')
|
|
698
|
+
.description('Delete a memory by ID, or bulk delete with filters')
|
|
699
|
+
.option('-o, --older-than <date>', 'Bulk delete memories older than (e.g., "30 days", "6 months")')
|
|
700
|
+
.option('-t, --type <type>', 'Filter by memory type')
|
|
701
|
+
.option('-s, --search <query>', 'Search query to match specific memories')
|
|
702
|
+
.option('-c, --confirm', 'Actually delete (default is dry-run)', false)
|
|
703
|
+
.option('-l, --limit <number>', 'Max memories to delete', '100')
|
|
704
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
705
|
+
.action(async (memoryId, options) => {
|
|
350
706
|
try {
|
|
351
|
-
|
|
352
|
-
|
|
707
|
+
// Single memory deletion
|
|
708
|
+
if (memoryId) {
|
|
709
|
+
const db = await getDb();
|
|
710
|
+
const schema = await getSchema();
|
|
711
|
+
const sqliteDb = db;
|
|
712
|
+
// Get memory content before deleting for hook
|
|
713
|
+
const [memory] = await sqliteDb.select().from(schema.memories).where(eq(schema.memories.id, memoryId));
|
|
714
|
+
await sqliteDb.delete(schema.memories).where(eq(schema.memories.id, memoryId));
|
|
715
|
+
// Trigger memoryDeleted hook
|
|
716
|
+
if (memory) {
|
|
717
|
+
const { triggerMemoryDeleted } = await import('./core/memory/hooks.js');
|
|
718
|
+
await triggerMemoryDeleted({
|
|
719
|
+
memoryId: memory.id,
|
|
720
|
+
content: memory.content,
|
|
721
|
+
type: memory.type,
|
|
722
|
+
tags: typeof memory.tags === 'string' ? memory.tags.split(',') : [],
|
|
723
|
+
project: memory.projectId || undefined,
|
|
724
|
+
source: memory.source || undefined,
|
|
725
|
+
tier: memory.tier,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
console.log(JSON.stringify({ ok: true, message: `Memory ${memoryId} deleted` }, null, 2));
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
// Bulk deletion
|
|
732
|
+
if (!options.olderThan && !options.search) {
|
|
733
|
+
console.log(JSON.stringify({ ok: false, error: 'Provide memory ID or use --older-than / --search for bulk delete' }, null, 2));
|
|
734
|
+
process.exit(1);
|
|
735
|
+
}
|
|
736
|
+
const query = options.search || '';
|
|
737
|
+
const limit = validateLimit(options.limit, 100, 1, 100);
|
|
738
|
+
const results = await search({ query, type: options.type, limit, project: options.project });
|
|
739
|
+
let filtered = results;
|
|
740
|
+
if (options.olderThan) {
|
|
741
|
+
filtered = filterByDateRange(results, '', options.olderThan);
|
|
742
|
+
}
|
|
743
|
+
const db = await getDb();
|
|
744
|
+
const schema = await getSchema();
|
|
745
|
+
const sqliteDb = db;
|
|
746
|
+
const deleted = [];
|
|
747
|
+
for (const mem of filtered) {
|
|
748
|
+
await sqliteDb.delete(schema.memories).where(eq(schema.memories.id, mem.id));
|
|
749
|
+
deleted.push(mem.id);
|
|
750
|
+
}
|
|
751
|
+
console.log(JSON.stringify({ ok: true, matched: filtered.length, deleted: deleted.length, dryRun: !options.confirm }, null, 2));
|
|
353
752
|
}
|
|
354
753
|
catch (error) {
|
|
355
754
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
356
755
|
process.exit(1);
|
|
357
756
|
}
|
|
358
757
|
});
|
|
359
|
-
// squish
|
|
360
|
-
// squish core_memory edit persona --content "I am helpful"
|
|
361
|
-
// squish core_memory append user_info --text "Prefers TypeScript"
|
|
758
|
+
// squish link - Unified graph operations (find related, add links, list associations)
|
|
362
759
|
program
|
|
363
|
-
.command('
|
|
364
|
-
.description('Manage
|
|
365
|
-
.argument('
|
|
366
|
-
.
|
|
367
|
-
.option('-
|
|
368
|
-
.
|
|
369
|
-
.option('-p, --project <project>', 'Project path', process.cwd())
|
|
370
|
-
.action(async (action, options) => {
|
|
760
|
+
.command('link')
|
|
761
|
+
.description('Manage memory associations: find, add, list')
|
|
762
|
+
.argument('<action>', 'Action: find, add, or list')
|
|
763
|
+
.argument('[args...]', 'Additional arguments')
|
|
764
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
765
|
+
.action(async (action, args, options) => {
|
|
371
766
|
try {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
767
|
+
// link find <memoryId> [--depth N] [--min-weight N]
|
|
768
|
+
if (action === 'find') {
|
|
769
|
+
const memoryId = args[0];
|
|
770
|
+
if (!memoryId) {
|
|
771
|
+
console.log(JSON.stringify({ ok: false, error: 'Usage: squish link find <memoryId> [--depth N] [--min-weight N]' }, null, 2));
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
const depth = validateLimit(args[1], 2, 1, 5);
|
|
775
|
+
const minWeight = parseFloat(args[2]) || 0.3;
|
|
776
|
+
const related = await getRelatedMemories(memoryId, depth * 5);
|
|
777
|
+
const filtered = related.filter((r) => r.weight >= minWeight);
|
|
778
|
+
console.log(JSON.stringify({ ok: true, count: filtered.length, related: filtered }, null, 2));
|
|
779
|
+
return;
|
|
377
780
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
console.log(JSON.stringify({ ok:
|
|
385
|
-
break;
|
|
386
|
-
case 'edit':
|
|
387
|
-
if (!options.section || !options.content) {
|
|
388
|
-
console.log(JSON.stringify({ ok: false, error: '--section and --content required for edit' }, null, 2));
|
|
389
|
-
process.exit(1);
|
|
390
|
-
}
|
|
391
|
-
await initializeCoreMemory(projectId);
|
|
392
|
-
const editResult = await editCoreMemorySection(projectId, options.section, String(options.content));
|
|
393
|
-
console.log(JSON.stringify({ ok: editResult.success, action: 'edit', section: options.section, ...editResult }, null, 2));
|
|
394
|
-
break;
|
|
395
|
-
case 'append':
|
|
396
|
-
if (!options.section || !options.text) {
|
|
397
|
-
console.log(JSON.stringify({ ok: false, error: '--section and --text required for append' }, null, 2));
|
|
398
|
-
process.exit(1);
|
|
399
|
-
}
|
|
400
|
-
await initializeCoreMemory(projectId);
|
|
401
|
-
const appendResult = await appendCoreMemorySection(projectId, options.section, String(options.text));
|
|
402
|
-
console.log(JSON.stringify({ ok: appendResult.success, action: 'append', section: options.section, ...appendResult }, null, 2));
|
|
403
|
-
break;
|
|
404
|
-
default:
|
|
405
|
-
console.log(JSON.stringify({ ok: false, error: `Unknown action: ${action}` }, null, 2));
|
|
781
|
+
// link add <fromId> <toId> <type>
|
|
782
|
+
if (action === 'add') {
|
|
783
|
+
const fromMemoryId = args[0];
|
|
784
|
+
const toMemoryId = args[1];
|
|
785
|
+
const type = args[2] || 'relates_to';
|
|
786
|
+
if (!fromMemoryId || !toMemoryId) {
|
|
787
|
+
console.log(JSON.stringify({ ok: false, error: 'Usage: squish link add <fromId> <toId> <type>' }, null, 2));
|
|
406
788
|
process.exit(1);
|
|
789
|
+
}
|
|
790
|
+
await createAssociation(fromMemoryId, toMemoryId, type, 0.5);
|
|
791
|
+
console.log(JSON.stringify({ ok: true, message: `Linked ${fromMemoryId} -> ${toMemoryId} (${type})` }, null, 2));
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
// link list - List all associations
|
|
795
|
+
if (action === 'list') {
|
|
796
|
+
const db = await getDb();
|
|
797
|
+
const schema = await getSchema();
|
|
798
|
+
const sqliteDb = db;
|
|
799
|
+
const associations = await sqliteDb.select().from(schema.memoryAssociations).limit(100);
|
|
800
|
+
console.log(JSON.stringify({ ok: true, count: associations.length, associations }, null, 2));
|
|
801
|
+
return;
|
|
407
802
|
}
|
|
803
|
+
console.log(JSON.stringify({ ok: false, error: 'Usage: squish link <find|add|list> [args]' }, null, 2));
|
|
408
804
|
}
|
|
409
805
|
catch (error) {
|
|
410
806
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
411
807
|
process.exit(1);
|
|
412
808
|
}
|
|
413
809
|
});
|
|
414
|
-
// squish
|
|
810
|
+
// squish learn <type> <content> - Record learning: success, failure, fix, or insight
|
|
415
811
|
program
|
|
416
|
-
.command('
|
|
417
|
-
.description('
|
|
418
|
-
.option('-
|
|
419
|
-
.
|
|
812
|
+
.command('learn <type> <content>')
|
|
813
|
+
.description('Record learning: success, failure, fix, or insight')
|
|
814
|
+
.option('-c, --context <context>', 'Additional context about what happened')
|
|
815
|
+
.option('-a, --action <action>', 'Action performed')
|
|
816
|
+
.option('-t, --target <target>', 'Target file or resource')
|
|
817
|
+
.option('-m, --memory-id <memoryId>', 'Optional memory ID to link this learning to')
|
|
818
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
819
|
+
.action(async (type, content, options) => {
|
|
420
820
|
try {
|
|
421
|
-
const
|
|
422
|
-
if (
|
|
423
|
-
console.log(JSON.stringify({ ok: false, error:
|
|
821
|
+
const validTypes = ['success', 'failure', 'fix', 'insight'];
|
|
822
|
+
if (!validTypes.includes(type)) {
|
|
823
|
+
console.log(JSON.stringify({ ok: false, error: `Invalid type. Must be: ${validTypes.join(', ')}` }, null, 2));
|
|
424
824
|
process.exit(1);
|
|
425
825
|
}
|
|
426
|
-
await
|
|
427
|
-
|
|
826
|
+
const learning = await createLearning({
|
|
827
|
+
type: type,
|
|
828
|
+
content,
|
|
829
|
+
context: options.context,
|
|
830
|
+
action: options.action,
|
|
831
|
+
target: options.target,
|
|
832
|
+
project: options.project,
|
|
833
|
+
memoryId: options.memoryId,
|
|
834
|
+
});
|
|
835
|
+
console.log(JSON.stringify({ ok: true, learning }, null, 2));
|
|
428
836
|
}
|
|
429
837
|
catch (error) {
|
|
430
838
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
431
839
|
process.exit(1);
|
|
432
840
|
}
|
|
433
841
|
});
|
|
434
|
-
// squish pin <memoryId>
|
|
435
842
|
program
|
|
436
|
-
.command('
|
|
437
|
-
.description('
|
|
438
|
-
.
|
|
843
|
+
.command('update <memoryId>')
|
|
844
|
+
.description('Update a memory')
|
|
845
|
+
.option('-c, --content <content>', 'New content')
|
|
846
|
+
.option('-t, --type <type>', 'New type (observation, fact, decision, context, preference)')
|
|
847
|
+
.option('-T, --tags <tags>', 'Comma-separated tags')
|
|
848
|
+
.action(async (memoryId, options) => {
|
|
439
849
|
try {
|
|
440
|
-
|
|
441
|
-
|
|
850
|
+
const updates = {};
|
|
851
|
+
if (options.content)
|
|
852
|
+
updates.content = options.content;
|
|
853
|
+
if (options.type)
|
|
854
|
+
updates.type = options.type;
|
|
855
|
+
if (options.tags)
|
|
856
|
+
updates.tags = serializeTags(options.tags.split(','));
|
|
857
|
+
const db = await getDb();
|
|
858
|
+
const schema = await getSchema();
|
|
859
|
+
const sqliteDb = db;
|
|
860
|
+
// Get old memory for hook
|
|
861
|
+
const [oldMemory] = await sqliteDb.select().from(schema.memories).where(eq(schema.memories.id, memoryId));
|
|
862
|
+
await sqliteDb.update(schema.memories).set(updates).where(eq(schema.memories.id, memoryId));
|
|
863
|
+
// Trigger memoryUpdated hook
|
|
864
|
+
if (oldMemory) {
|
|
865
|
+
const { triggerMemoryUpdated } = await import('./core/memory/hooks.js');
|
|
866
|
+
const newContent = options.content || oldMemory.content;
|
|
867
|
+
await triggerMemoryUpdated({
|
|
868
|
+
memoryId: oldMemory.id,
|
|
869
|
+
content: newContent,
|
|
870
|
+
type: options.type || oldMemory.type,
|
|
871
|
+
tags: options.tags ? options.tags.split(',') : (typeof oldMemory.tags === 'string' ? oldMemory.tags.split(',') : []),
|
|
872
|
+
project: oldMemory.projectId || undefined,
|
|
873
|
+
source: oldMemory.source || undefined,
|
|
874
|
+
tier: oldMemory.tier,
|
|
875
|
+
importance: oldMemory.importanceScore || oldMemory.relevanceScore || 50,
|
|
876
|
+
}, oldMemory.content);
|
|
877
|
+
}
|
|
878
|
+
console.log(JSON.stringify({ ok: true, message: `Memory ${memoryId} updated` }, null, 2));
|
|
442
879
|
}
|
|
443
880
|
catch (error) {
|
|
444
881
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
445
882
|
process.exit(1);
|
|
446
883
|
}
|
|
447
884
|
});
|
|
448
|
-
// squish
|
|
885
|
+
// squish recall <query or memoryId> - Search or get by ID
|
|
449
886
|
program
|
|
450
|
-
.command('
|
|
451
|
-
.description('
|
|
452
|
-
.
|
|
887
|
+
.command('recall <query>')
|
|
888
|
+
.description('Search memories by query or get by ID (if UUID provided)')
|
|
889
|
+
.option('-l, --limit <number>', 'Max results', '5')
|
|
890
|
+
.option('-t, --type <type>', 'Filter by memory type')
|
|
891
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
892
|
+
.option('-s, --since <date>', 'Filter: created after this date (e.g., "3 days ago", "yesterday")')
|
|
893
|
+
.option('-u, --until <date>', 'Filter: created before this date (e.g., "today", "2026-01-15")')
|
|
894
|
+
.option('-P, --pretty', 'Human-friendly output', false)
|
|
895
|
+
.option('--place <type>', 'Filter by place type: entry_hall, library, workshop, lab, office, garden, archive')
|
|
896
|
+
.action(async (query, options) => {
|
|
453
897
|
try {
|
|
454
|
-
|
|
455
|
-
|
|
898
|
+
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(query);
|
|
899
|
+
if (isUUID) {
|
|
900
|
+
const memory = await getMemory(query);
|
|
901
|
+
// Add place info to single memory retrieval
|
|
902
|
+
if (memory) {
|
|
903
|
+
const { getMemoryPlace, getPlace } = await import('./core/places/index.js');
|
|
904
|
+
const placeId = await getMemoryPlace(memory.id);
|
|
905
|
+
if (placeId) {
|
|
906
|
+
const place = await getPlace(placeId);
|
|
907
|
+
memory.place = place?.name || null;
|
|
908
|
+
memory.placeType = place?.placeType || null;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
if (options.pretty && memory) {
|
|
912
|
+
const placeInfo = memory.place ? ` (${memory.place})` : '';
|
|
913
|
+
console.log(`\n Memory: ${memory.id}`);
|
|
914
|
+
console.log(` Type: ${memory.type}`);
|
|
915
|
+
console.log(` Content: ${memory.content}\n`);
|
|
916
|
+
if (placeInfo)
|
|
917
|
+
console.log(` Place: ${memory.place}\n`);
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
console.log(JSON.stringify({ ok: true, found: !!memory, memory }, null, 2));
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
const results = await search({
|
|
925
|
+
query,
|
|
926
|
+
type: options.type,
|
|
927
|
+
limit: validateLimit(options.limit, 5, 1, 100) * 2,
|
|
928
|
+
project: options.project,
|
|
929
|
+
});
|
|
930
|
+
const filtered = filterByDateRange(results, options.since, options.until);
|
|
931
|
+
let limited = filtered.slice(0, validateLimit(options.limit, 5, 1, 100));
|
|
932
|
+
// Add place info to results
|
|
933
|
+
const { getMemoryPlace, getPlace } = await import('./core/places/index.js');
|
|
934
|
+
const limitedWithPlace = await Promise.all(limited.map(async (r) => {
|
|
935
|
+
const placeId = await getMemoryPlace(r.id);
|
|
936
|
+
let placeInfo = {};
|
|
937
|
+
if (placeId) {
|
|
938
|
+
const place = await getPlace(placeId);
|
|
939
|
+
placeInfo = { place: place?.name || null, placeType: place?.placeType || null };
|
|
940
|
+
}
|
|
941
|
+
return { ...r, ...placeInfo };
|
|
942
|
+
}));
|
|
943
|
+
// Filter by place if specified
|
|
944
|
+
if (options.place) {
|
|
945
|
+
limited = limitedWithPlace.filter((r) => r.placeType === options.place);
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
limited = limitedWithPlace;
|
|
949
|
+
}
|
|
950
|
+
if (options.pretty) {
|
|
951
|
+
console.log(`\n Recall: "${query}"`);
|
|
952
|
+
console.log(` Found ${limited.length} matches:\n`);
|
|
953
|
+
limited.forEach((r, i) => {
|
|
954
|
+
const placeTag = r.place ? ` (${r.place})` : '';
|
|
955
|
+
console.log(` ${i + 1}. [${r.type || 'memory'}] ${(r.content || '').substring(0, 60)}...${placeTag} (${(r.similarity ?? 0).toFixed(2)})`);
|
|
956
|
+
});
|
|
957
|
+
console.log('');
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
const matches = limited.map((r) => ({
|
|
961
|
+
id: r.id,
|
|
962
|
+
score: r.similarity ?? 0,
|
|
963
|
+
type: r.type,
|
|
964
|
+
content: r.content.length > 200 ? r.content.slice(0, 200) + '...' : r.content,
|
|
965
|
+
tags: r.tags,
|
|
966
|
+
place: r.place,
|
|
967
|
+
placeType: r.placeType,
|
|
968
|
+
}));
|
|
969
|
+
console.log(JSON.stringify({ ok: true, query, count: matches.length, since: options.since, until: options.until, placeFilter: options.place || null, matches }, null, 2));
|
|
970
|
+
}
|
|
971
|
+
}
|
|
456
972
|
}
|
|
457
973
|
catch (error) {
|
|
458
974
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
459
975
|
process.exit(1);
|
|
460
976
|
}
|
|
461
977
|
});
|
|
462
|
-
// squish
|
|
978
|
+
// squish recent --period <period> - Show recent memories
|
|
463
979
|
program
|
|
464
|
-
.command('
|
|
465
|
-
.description('
|
|
466
|
-
.option('-p, --
|
|
467
|
-
.option('-
|
|
468
|
-
.option('-
|
|
469
|
-
.option('-
|
|
470
|
-
.option('-
|
|
980
|
+
.command('recent')
|
|
981
|
+
.description('Show recent memories by period')
|
|
982
|
+
.option('-p, --period <period>', 'Period: today, yesterday, thisweek, 7days, 30days, or custom like "3 days"', 'today')
|
|
983
|
+
.option('-s, --since <date>', 'Start date (alternative to --period)')
|
|
984
|
+
.option('-u, --until <date>', 'End date (alternative to --period)')
|
|
985
|
+
.option('-l, --limit <number>', 'Max results', '10')
|
|
986
|
+
.option('-P, --project <project>', 'Project path', getDefaultProjectPath())
|
|
471
987
|
.action(async (options) => {
|
|
472
988
|
try {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
989
|
+
let since, until;
|
|
990
|
+
if (options.since && options.until) {
|
|
991
|
+
since = options.since;
|
|
992
|
+
until = options.until;
|
|
993
|
+
}
|
|
994
|
+
else if (options.since) {
|
|
995
|
+
since = options.since;
|
|
996
|
+
until = 'now';
|
|
997
|
+
}
|
|
998
|
+
else {
|
|
999
|
+
const periodMap = {
|
|
1000
|
+
today: ['today', 'now'],
|
|
1001
|
+
yesterday: ['yesterday', 'today'],
|
|
1002
|
+
thisweek: ['thisweek', 'now'],
|
|
1003
|
+
'7days': ['7 days', 'now'],
|
|
1004
|
+
'14days': ['14 days', 'now'],
|
|
1005
|
+
'30days': ['30 days', 'now'],
|
|
1006
|
+
'90days': ['90 days', 'now'],
|
|
1007
|
+
};
|
|
1008
|
+
const mapped = periodMap[options.period];
|
|
1009
|
+
if (mapped) {
|
|
1010
|
+
[since, until] = mapped;
|
|
1011
|
+
}
|
|
1012
|
+
else {
|
|
1013
|
+
since = options.period;
|
|
1014
|
+
until = 'now';
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
const results = await getRecent(options.project, 100);
|
|
1018
|
+
const filtered = filterByDateRange(results, since, until);
|
|
1019
|
+
const limited = filtered.slice(0, validateLimit(options.limit, 10, 1, 100));
|
|
1020
|
+
console.log(JSON.stringify({ ok: true, period: options.period, since, until, count: limited.length, results: limited }, null, 2));
|
|
481
1021
|
}
|
|
482
1022
|
catch (error) {
|
|
483
1023
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
484
1024
|
process.exit(1);
|
|
485
1025
|
}
|
|
486
1026
|
});
|
|
487
|
-
// squish
|
|
1027
|
+
// squish confidence <memoryId> [level] - Set or view confidence level
|
|
488
1028
|
program
|
|
489
|
-
.command('
|
|
490
|
-
.description('
|
|
491
|
-
.
|
|
492
|
-
.action(async (options) => {
|
|
1029
|
+
.command('confidence <memoryId> [level]')
|
|
1030
|
+
.description('Set or view confidence level (certain/speculative/outdated)')
|
|
1031
|
+
.action(async (memoryId, level) => {
|
|
493
1032
|
try {
|
|
494
|
-
|
|
495
|
-
|
|
1033
|
+
if (!level) {
|
|
1034
|
+
const memory = await getMemory(String(memoryId));
|
|
1035
|
+
if (!memory) {
|
|
1036
|
+
console.log(JSON.stringify({ ok: false, error: 'Memory not found' }, null, 2));
|
|
1037
|
+
process.exit(1);
|
|
1038
|
+
}
|
|
1039
|
+
console.log(JSON.stringify({ ok: true, memoryId, confidenceLevel: memory.confidenceLevel ?? 'certain' }, null, 2));
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
const validLevels = ['certain', 'speculative', 'outdated'];
|
|
1043
|
+
if (!validLevels.includes(level)) {
|
|
1044
|
+
console.log(JSON.stringify({ ok: false, error: 'Invalid level. Use: certain, speculative, or outdated' }, null, 2));
|
|
1045
|
+
process.exit(1);
|
|
1046
|
+
}
|
|
1047
|
+
await setConfidence(String(memoryId), level);
|
|
1048
|
+
console.log(JSON.stringify({ ok: true, memoryId, confidenceLevel: level }, null, 2));
|
|
1049
|
+
}
|
|
496
1050
|
}
|
|
497
1051
|
catch (error) {
|
|
498
1052
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
499
1053
|
process.exit(1);
|
|
500
1054
|
}
|
|
501
1055
|
});
|
|
502
|
-
// squish
|
|
1056
|
+
// squish pin <memoryId> [--unpin]
|
|
503
1057
|
program
|
|
504
|
-
.command('
|
|
505
|
-
.description('
|
|
506
|
-
.option('-
|
|
507
|
-
.action(async (options) => {
|
|
1058
|
+
.command('pin <memoryId>')
|
|
1059
|
+
.description('Pin/unpin a memory to prevent pruning/consolidation')
|
|
1060
|
+
.option('-u, --unpin', 'Unpin the memory instead of pinning', false)
|
|
1061
|
+
.action(async (memoryId, options) => {
|
|
508
1062
|
try {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
1063
|
+
if (options.unpin) {
|
|
1064
|
+
await unpinMemory(String(memoryId));
|
|
1065
|
+
console.log(JSON.stringify({ ok: true, memoryId, pinned: false }, null, 2));
|
|
1066
|
+
}
|
|
1067
|
+
else {
|
|
1068
|
+
await pinMemory(String(memoryId));
|
|
1069
|
+
console.log(JSON.stringify({ ok: true, memoryId, pinned: true }, null, 2));
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
1074
|
+
process.exit(1);
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
// squish tag add <tag> --search <query> --confirm
|
|
1078
|
+
// squish tag remove <tag> --older-than "30 days" --confirm
|
|
1079
|
+
program
|
|
1080
|
+
.command('tag')
|
|
1081
|
+
.description('Manage tags on memories (bulk)')
|
|
1082
|
+
.argument('<action>', 'add or remove')
|
|
1083
|
+
.argument('<tag>', 'Tag name')
|
|
1084
|
+
.option('-s, --search <query>', 'Search query to match memories')
|
|
1085
|
+
.option('-o, --older-than <date>', 'Only tag memories older than (e.g., "30 days")')
|
|
1086
|
+
.option('-t, --type <type>', 'Filter by memory type')
|
|
1087
|
+
.option('-c, --confirm', 'Actually execute the changes (default is dry-run)', false)
|
|
1088
|
+
.option('-l, --limit <number>', 'Max memories to process', '50')
|
|
1089
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
1090
|
+
.action(async (action, tag, options) => {
|
|
1091
|
+
try {
|
|
1092
|
+
if (!options.search && !options.olderThan) {
|
|
1093
|
+
console.log(JSON.stringify({ ok: false, error: 'Provide --search <query> or --older-than <date>' }, null, 2));
|
|
1094
|
+
process.exit(1);
|
|
1095
|
+
}
|
|
1096
|
+
const limit = validateLimit(options.limit, 50, 1, 100);
|
|
1097
|
+
let results;
|
|
1098
|
+
const searchInput = { query: options.search, limit, project: options.project };
|
|
1099
|
+
if (options.type)
|
|
1100
|
+
searchInput.type = options.type;
|
|
1101
|
+
if (options.search) {
|
|
1102
|
+
results = await search(searchInput);
|
|
1103
|
+
}
|
|
1104
|
+
else {
|
|
1105
|
+
results = await getRecent(options.project, limit * 2);
|
|
1106
|
+
}
|
|
1107
|
+
let filtered = results;
|
|
1108
|
+
if (options.olderThan) {
|
|
1109
|
+
filtered = filterByDateRange(results, '', options.olderThan);
|
|
1110
|
+
}
|
|
1111
|
+
const db = await getDb();
|
|
1112
|
+
const schema = await getSchema();
|
|
1113
|
+
if (action === 'add') {
|
|
1114
|
+
const updated = [];
|
|
1115
|
+
for (const mem of filtered) {
|
|
1116
|
+
try {
|
|
1117
|
+
const tags = new Set((mem.tags || []));
|
|
1118
|
+
if (!tags.has(tag)) {
|
|
1119
|
+
tags.add(tag);
|
|
1120
|
+
await db.update(schema.memories)
|
|
1121
|
+
.set({ tags: serializeTags(Array.from(tags)), updatedAt: new Date() })
|
|
1122
|
+
.where(eq(schema.memories.id, mem.id));
|
|
1123
|
+
updated.push(mem.id);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
catch (e) {
|
|
1127
|
+
console.error('DEBUG: error updating', mem.id, e.message);
|
|
1128
|
+
throw e;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
console.log(JSON.stringify({ ok: true, action: 'add', tag, matched: filtered.length, updated: updated.length, dryRun: !options.confirm }, null, 2));
|
|
1132
|
+
}
|
|
1133
|
+
else if (action === 'remove') {
|
|
1134
|
+
const updated = [];
|
|
1135
|
+
for (const mem of filtered) {
|
|
1136
|
+
const tags = new Set((mem.tags || []));
|
|
1137
|
+
if (tags.has(tag)) {
|
|
1138
|
+
tags.delete(tag);
|
|
1139
|
+
await db.update(schema.memories)
|
|
1140
|
+
.set({ tags: serializeTags(Array.from(tags)), updatedAt: new Date() })
|
|
1141
|
+
.where(eq(schema.memories.id, mem.id));
|
|
1142
|
+
updated.push(mem.id);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
console.log(JSON.stringify({ ok: true, action: 'remove', tag, matched: filtered.length, updated: updated.length, dryRun: !options.confirm }, null, 2));
|
|
524
1146
|
}
|
|
525
1147
|
else {
|
|
526
|
-
console.log(
|
|
527
|
-
console.log(` ====================`);
|
|
528
|
-
console.log(` Mode: ${status.mode}`);
|
|
529
|
-
console.log(` Database: ${status.database}`);
|
|
530
|
-
console.log(` Cache: ${status.cache}`);
|
|
531
|
-
console.log(` Data Dir: ${status.dataDirectory}`);
|
|
532
|
-
console.log(` Status: ${dbHealth ? 'HEALTHY' : 'UNHEALTHY'}\n`);
|
|
533
|
-
}
|
|
534
|
-
if (!dbHealth) {
|
|
1148
|
+
console.log(JSON.stringify({ ok: false, error: 'Use: squish tag add <tag> or squish tag remove <tag>' }, null, 2));
|
|
535
1149
|
process.exit(1);
|
|
536
1150
|
}
|
|
537
1151
|
}
|
|
@@ -540,13 +1154,62 @@ async function runCliMode() {
|
|
|
540
1154
|
process.exit(1);
|
|
541
1155
|
}
|
|
542
1156
|
});
|
|
1157
|
+
// squish stale --days 30 - Show old, low-confidence, unaccessed memories
|
|
1158
|
+
program
|
|
1159
|
+
.command('stale')
|
|
1160
|
+
.description('Show stale memories (old, low-confidence, or rarely accessed)')
|
|
1161
|
+
.option('-d, --days <number>', 'Show memories older than N days', '30')
|
|
1162
|
+
.option('-c, --confidence <level>', 'Max confidence level to show (outdated, speculative)', 'speculative')
|
|
1163
|
+
.option('-l, --limit <number>', 'Max results', '20')
|
|
1164
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
1165
|
+
.action(async (options) => {
|
|
1166
|
+
try {
|
|
1167
|
+
const days = validateLimit(options.days, 30, 1, 365);
|
|
1168
|
+
const cutoffDate = new Date(Date.now() - days * 86400000);
|
|
1169
|
+
// Get recent memories - larger limit to find stale ones
|
|
1170
|
+
const results = await getRecent(options.project, 500);
|
|
1171
|
+
const stale = results.filter((m) => {
|
|
1172
|
+
const created = m.createdAt ? new Date(m.createdAt) : null;
|
|
1173
|
+
const isOld = created && created < cutoffDate;
|
|
1174
|
+
const isLowConfidence = m.confidenceLevel === 'outdated' || m.confidenceLevel === 'speculative';
|
|
1175
|
+
const hasLowImportance = (m.importance || 50) < 40;
|
|
1176
|
+
return isOld || isLowConfidence || hasLowImportance;
|
|
1177
|
+
});
|
|
1178
|
+
const limited = stale.slice(0, validateLimit(options.limit, 20, 1, 100));
|
|
1179
|
+
const summary = {
|
|
1180
|
+
totalStale: stale.length,
|
|
1181
|
+
old: stale.filter((m) => m.createdAt && new Date(m.createdAt) < cutoffDate).length,
|
|
1182
|
+
lowConfidence: stale.filter((m) => m.confidenceLevel === 'outdated' || m.confidenceLevel === 'speculative').length,
|
|
1183
|
+
lowImportance: stale.filter((m) => (m.importance || 50) < 40).length,
|
|
1184
|
+
};
|
|
1185
|
+
console.log(JSON.stringify({ ok: true, summary, memories: limited }, null, 2));
|
|
1186
|
+
}
|
|
1187
|
+
catch (error) {
|
|
1188
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
1189
|
+
process.exit(1);
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
543
1192
|
// squish stats
|
|
544
1193
|
program
|
|
545
1194
|
.command('stats')
|
|
546
1195
|
.description('View statistics')
|
|
547
|
-
.option('-p, --project <project>', 'Project path',
|
|
1196
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
1197
|
+
.option('-m, --memory', 'Show memory file storage stats instead of database', false)
|
|
548
1198
|
.action(async (options) => {
|
|
549
1199
|
try {
|
|
1200
|
+
// Memory file stats
|
|
1201
|
+
if (options.memory) {
|
|
1202
|
+
const { getMemoryStats, isMemoryStorageAvailable } = await import('./core/memory/markdown/markdown-storage.js');
|
|
1203
|
+
const available = isMemoryStorageAvailable();
|
|
1204
|
+
if (!available) {
|
|
1205
|
+
console.log(JSON.stringify({ ok: false, error: 'Memory file storage not available' }, null, 2));
|
|
1206
|
+
process.exit(1);
|
|
1207
|
+
}
|
|
1208
|
+
const stats = await getMemoryStats();
|
|
1209
|
+
console.log(JSON.stringify({ ok: true, source: 'memory', ...stats }, null, 2));
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
// Database stats
|
|
550
1213
|
const stats = await getMemoryStats(options.project);
|
|
551
1214
|
console.log(JSON.stringify({ ok: true, ...stats }, null, 2));
|
|
552
1215
|
}
|
|
@@ -562,436 +1225,453 @@ async function runCliMode() {
|
|
|
562
1225
|
.action(async () => {
|
|
563
1226
|
await spawnInstallerWizard();
|
|
564
1227
|
});
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
async
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
type: 'object',
|
|
578
|
-
properties: {
|
|
579
|
-
action: { type: 'string', enum: ['view', 'edit', 'append'] },
|
|
580
|
-
projectId: { type: 'string' },
|
|
581
|
-
section: { type: 'string', enum: ['persona', 'user_info', 'project_context', 'working_notes'] },
|
|
582
|
-
content: { type: 'string' },
|
|
583
|
-
text: { type: 'string' },
|
|
584
|
-
},
|
|
585
|
-
required: ['action', 'projectId']
|
|
586
|
-
}
|
|
587
|
-
},
|
|
588
|
-
// Context Paging
|
|
589
|
-
{
|
|
590
|
-
name: 'context_paging',
|
|
591
|
-
description: 'Manage your working memory set. Load, evict, or view loaded memories.',
|
|
592
|
-
inputSchema: {
|
|
593
|
-
type: 'object',
|
|
594
|
-
properties: {
|
|
595
|
-
action: { type: 'string', enum: ['load', 'evict', 'view'] },
|
|
596
|
-
sessionId: { type: 'string' },
|
|
597
|
-
memoryId: { type: 'string' },
|
|
598
|
-
},
|
|
599
|
-
required: ['action', 'sessionId']
|
|
600
|
-
}
|
|
601
|
-
},
|
|
602
|
-
{
|
|
603
|
-
name: 'context_status',
|
|
604
|
-
description: 'View comprehensive context window status and token usage',
|
|
605
|
-
inputSchema: {
|
|
606
|
-
type: 'object',
|
|
607
|
-
properties: {
|
|
608
|
-
sessionId: { type: 'string' },
|
|
609
|
-
projectId: { type: 'string' },
|
|
610
|
-
},
|
|
611
|
-
required: ['sessionId', 'projectId']
|
|
612
|
-
}
|
|
613
|
-
},
|
|
614
|
-
// Memory Tools
|
|
615
|
-
{
|
|
616
|
-
name: 'remember',
|
|
617
|
-
description: 'Store information for future use. Perfect for facts, decisions, code snippets, configuration details, or user preferences.',
|
|
618
|
-
inputSchema: {
|
|
619
|
-
type: 'object',
|
|
620
|
-
properties: {
|
|
621
|
-
content: { type: 'string' },
|
|
622
|
-
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
|
|
623
|
-
tags: { type: 'array', items: { type: 'string' } },
|
|
624
|
-
project: { type: 'string' },
|
|
625
|
-
metadata: { type: 'object' },
|
|
626
|
-
},
|
|
627
|
-
required: ['content']
|
|
628
|
-
}
|
|
629
|
-
},
|
|
630
|
-
{
|
|
631
|
-
name: 'recall',
|
|
632
|
-
description: 'Retrieve a specific stored memory by ID',
|
|
633
|
-
inputSchema: {
|
|
634
|
-
type: 'object',
|
|
635
|
-
properties: { id: { type: 'string' } },
|
|
636
|
-
required: ['id']
|
|
637
|
-
}
|
|
638
|
-
},
|
|
639
|
-
{
|
|
640
|
-
name: 'search',
|
|
641
|
-
description: 'Search your stored memories. Leave query empty to list recent memories.',
|
|
642
|
-
inputSchema: {
|
|
643
|
-
type: 'object',
|
|
644
|
-
properties: {
|
|
645
|
-
query: { type: 'string' },
|
|
646
|
-
scope: { type: 'string', enum: ['memories', 'conversations', 'recent'], default: 'memories' },
|
|
647
|
-
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
|
|
648
|
-
tags: { type: 'array', items: { type: 'string' } },
|
|
649
|
-
limit: { type: 'number', default: 10 },
|
|
650
|
-
project: { type: 'string' },
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
},
|
|
654
|
-
{
|
|
655
|
-
name: 'observe',
|
|
656
|
-
description: 'Record an observation about your work (tool usage, patterns, errors)',
|
|
657
|
-
inputSchema: {
|
|
658
|
-
type: 'object',
|
|
659
|
-
properties: {
|
|
660
|
-
type: { type: 'string', enum: ['tool_use', 'file_change', 'error', 'pattern', 'insight'] },
|
|
661
|
-
action: { type: 'string' },
|
|
662
|
-
target: { type: 'string' },
|
|
663
|
-
summary: { type: 'string' },
|
|
664
|
-
details: { type: 'object' },
|
|
665
|
-
},
|
|
666
|
-
required: ['type', 'action', 'summary']
|
|
667
|
-
}
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
name: 'context',
|
|
671
|
-
description: 'Get project context',
|
|
672
|
-
inputSchema: {
|
|
673
|
-
type: 'object',
|
|
674
|
-
properties: {
|
|
675
|
-
project: { type: 'string' },
|
|
676
|
-
include: { type: 'array', items: { type: 'string' }, default: ['memories', 'observations'] },
|
|
677
|
-
limit: { type: 'number', default: 10 }
|
|
678
|
-
},
|
|
679
|
-
required: ['project']
|
|
680
|
-
}
|
|
681
|
-
},
|
|
682
|
-
{
|
|
683
|
-
name: 'init',
|
|
684
|
-
description: 'Initialize Squish memory system for the current project',
|
|
685
|
-
inputSchema: {
|
|
686
|
-
type: 'object',
|
|
687
|
-
properties: { projectPath: { type: 'string' } }
|
|
688
|
-
}
|
|
689
|
-
},
|
|
690
|
-
{
|
|
691
|
-
name: 'health',
|
|
692
|
-
description: 'Check service status',
|
|
693
|
-
inputSchema: { type: 'object', properties: {} }
|
|
694
|
-
},
|
|
695
|
-
{
|
|
696
|
-
name: 'merge',
|
|
697
|
-
description: 'Manage memory merges: detect, list, preview, approve, reject, reverse',
|
|
698
|
-
inputSchema: {
|
|
699
|
-
type: 'object',
|
|
700
|
-
properties: {
|
|
701
|
-
action: { type: 'string', enum: ['detect', 'list', 'preview', 'stats', 'approve', 'reject', 'reverse'] },
|
|
702
|
-
projectId: { type: 'string' },
|
|
703
|
-
proposalId: { type: 'string' },
|
|
704
|
-
threshold: { type: 'number' },
|
|
705
|
-
},
|
|
706
|
-
required: ['action']
|
|
707
|
-
}
|
|
708
|
-
},
|
|
709
|
-
{
|
|
710
|
-
name: 'qmd_search',
|
|
711
|
-
description: 'Search memories using QMD hybrid search (BM25 + vector + rerank)',
|
|
712
|
-
inputSchema: {
|
|
713
|
-
type: 'object',
|
|
714
|
-
properties: {
|
|
715
|
-
query: { type: 'string' },
|
|
716
|
-
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
|
|
717
|
-
limit: { type: 'number', default: 10 },
|
|
718
|
-
},
|
|
719
|
-
required: ['query']
|
|
720
|
-
}
|
|
721
|
-
},
|
|
722
|
-
// v0.8.0: Importance Scoring Tools
|
|
723
|
-
{
|
|
724
|
-
name: 'set_importance',
|
|
725
|
-
description: 'Manually set importance score for a memory (0-100)',
|
|
726
|
-
inputSchema: {
|
|
727
|
-
type: 'object',
|
|
728
|
-
properties: {
|
|
729
|
-
memoryId: { type: 'string' },
|
|
730
|
-
importance: { type: 'number', minimum: 0, maximum: 100 },
|
|
731
|
-
},
|
|
732
|
-
required: ['memoryId', 'importance']
|
|
733
|
-
}
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
name: 'pin_memory',
|
|
737
|
-
description: 'Pin a memory to prevent pruning/consolidation (or unpin it)',
|
|
738
|
-
inputSchema: {
|
|
739
|
-
type: 'object',
|
|
740
|
-
properties: {
|
|
741
|
-
memoryId: { type: 'string' },
|
|
742
|
-
pinned: { type: 'boolean', default: true },
|
|
743
|
-
},
|
|
744
|
-
required: ['memoryId']
|
|
745
|
-
}
|
|
746
|
-
},
|
|
747
|
-
// v0.8.0: Consolidation Tool
|
|
748
|
-
{
|
|
749
|
-
name: 'consolidate',
|
|
750
|
-
description: 'Trigger manual memory consolidation - summarizes old, low-importance memories',
|
|
751
|
-
inputSchema: {
|
|
752
|
-
type: 'object',
|
|
753
|
-
properties: {
|
|
754
|
-
projectId: { type: 'string' },
|
|
755
|
-
threshold: { type: 'number', default: 0.7 },
|
|
756
|
-
minAge: { type: 'number', default: 90 },
|
|
757
|
-
limit: { type: 'number', default: 100 },
|
|
758
|
-
},
|
|
759
|
-
required: ['projectId']
|
|
760
|
-
}
|
|
761
|
-
},
|
|
762
|
-
{
|
|
763
|
-
name: 'consolidation_stats',
|
|
764
|
-
description: 'Get consolidation statistics for a project',
|
|
765
|
-
inputSchema: {
|
|
766
|
-
type: 'object',
|
|
767
|
-
properties: {
|
|
768
|
-
projectId: { type: 'string' },
|
|
769
|
-
},
|
|
770
|
-
required: ['projectId']
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
];
|
|
774
|
-
class Squish {
|
|
775
|
-
server;
|
|
776
|
-
projectPath;
|
|
777
|
-
constructor() {
|
|
778
|
-
this.projectPath = process.env.CLAUDE_WORKING_DIRECTORY || process.cwd();
|
|
779
|
-
this.server = new Server({ name: 'squish', version: VERSION }, {
|
|
780
|
-
capabilities: { tools: {} },
|
|
1228
|
+
// squish note "my thought here" - quick brain dump
|
|
1229
|
+
program
|
|
1230
|
+
.command('note <content>')
|
|
1231
|
+
.description('Quick brain dump - store a raw memory to process later')
|
|
1232
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
1233
|
+
.action(async (content, options) => {
|
|
1234
|
+
try {
|
|
1235
|
+
const result = await rememberMemory({
|
|
1236
|
+
content,
|
|
1237
|
+
type: 'observation',
|
|
1238
|
+
tags: ['note', 'quick'],
|
|
1239
|
+
project: options.project,
|
|
781
1240
|
});
|
|
782
|
-
|
|
1241
|
+
console.log(JSON.stringify({ ok: true, message: 'Note saved', id: result.id }, null, 2));
|
|
783
1242
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1243
|
+
catch (error) {
|
|
1244
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
1245
|
+
process.exit(1);
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
// squish context - Show project context (memories + observations + places)
|
|
1249
|
+
program
|
|
1250
|
+
.command('context')
|
|
1251
|
+
.description('Show project context or list available projects')
|
|
1252
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
1253
|
+
.option('-l, --limit <number>', 'Number of items to show', '10')
|
|
1254
|
+
.option('-i, --include <items>', 'What to include: memories, observations, entities, places', 'memories,observations,places')
|
|
1255
|
+
.option('--list-projects', 'List registered projects instead of loading context', false)
|
|
1256
|
+
.option('-j, --json', 'Output as JSON', false)
|
|
1257
|
+
.option('--place <type>', 'Filter by place type: entry_hall, library, workshop, lab, office, garden, archive')
|
|
1258
|
+
.option('--tier <level>', 'Disclosure level: quick (place names), medium (top 3), full (all)', 'medium')
|
|
1259
|
+
.option('--has-memories', 'Only show places with memories', true)
|
|
1260
|
+
.option('--sync', 'Recalculate memory counts for all places', false)
|
|
1261
|
+
.option('--archive', 'Move memories > 30 days to Archive place', false)
|
|
1262
|
+
.option('--task <description>', 'Task description for auto-place detection (e.g., "fix bug", "design API")')
|
|
1263
|
+
.action(async (options) => {
|
|
1264
|
+
try {
|
|
1265
|
+
// Auto-detect place from task if provided
|
|
1266
|
+
let placeFilter = options.place || null;
|
|
1267
|
+
if (options.task && !placeFilter) {
|
|
1268
|
+
// Simple keyword detection
|
|
1269
|
+
const task = options.task.toLowerCase();
|
|
1270
|
+
if (task.includes('fix') || task.includes('bug') || task.includes('error'))
|
|
1271
|
+
placeFilter = 'workshop';
|
|
1272
|
+
else if (task.includes('design') || task.includes('plan') || task.includes('api'))
|
|
1273
|
+
placeFilter = 'library';
|
|
1274
|
+
else if (task.includes('task') || task.includes('todo') || task.includes('manage'))
|
|
1275
|
+
placeFilter = 'office';
|
|
1276
|
+
else if (task.includes('test') || task.includes('experiment'))
|
|
1277
|
+
placeFilter = 'lab';
|
|
1278
|
+
if (placeFilter)
|
|
1279
|
+
console.log(`Auto-detected place: ${placeFilter}`);
|
|
788
1280
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
logger.warn('[Session] Auto-load warnings:', result.warnings);
|
|
1281
|
+
if (options.listProjects) {
|
|
1282
|
+
const projects = await getAllProjects();
|
|
1283
|
+
if (options.json) {
|
|
1284
|
+
console.log(JSON.stringify({ ok: true, count: projects.length, projects }, null, 2));
|
|
794
1285
|
}
|
|
795
|
-
|
|
1286
|
+
else {
|
|
1287
|
+
console.log(`\n Registered Projects (${projects.length})`);
|
|
1288
|
+
console.log(` ================================`);
|
|
1289
|
+
for (const project of projects) {
|
|
1290
|
+
console.log(`\n ${project.name}`);
|
|
1291
|
+
console.log(` Path: ${project.path}`);
|
|
1292
|
+
console.log(` ID: ${project.id}`);
|
|
1293
|
+
}
|
|
1294
|
+
console.log('');
|
|
1295
|
+
}
|
|
1296
|
+
return;
|
|
796
1297
|
}
|
|
797
|
-
|
|
798
|
-
|
|
1298
|
+
// Get project context
|
|
1299
|
+
const projectPath = resolveProjectPath(options.project);
|
|
1300
|
+
await ensureProject(projectPath);
|
|
1301
|
+
const project = await getOrCreateProject(projectPath);
|
|
1302
|
+
if (!project) {
|
|
1303
|
+
console.log(JSON.stringify({ ok: false, error: 'Project not found' }, null, 2));
|
|
1304
|
+
process.exit(1);
|
|
799
1305
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1306
|
+
const limit = parseInt(options.limit);
|
|
1307
|
+
const include = (options.include || 'memories,observations,places').split(',');
|
|
1308
|
+
const tier = options.tier || 'full';
|
|
1309
|
+
const hasMemoriesOnly = options.hasMemories !== false; // Default true now
|
|
1310
|
+
const existingPlaceFilter = options.place || null;
|
|
1311
|
+
const result = { project: project.name, tier };
|
|
1312
|
+
// Get memories
|
|
1313
|
+
if (include.includes('memories')) {
|
|
1314
|
+
const memories = await getRecent(projectPath, limit);
|
|
1315
|
+
result.memories = memories.map((m) => ({
|
|
1316
|
+
id: m.id,
|
|
1317
|
+
type: m.type,
|
|
1318
|
+
content: m.content?.substring(0, 100),
|
|
1319
|
+
tags: m.tags,
|
|
1320
|
+
}));
|
|
1321
|
+
}
|
|
1322
|
+
// Get observations (learnings)
|
|
1323
|
+
if (include.includes('observations')) {
|
|
1324
|
+
const { getObservations } = await import('./core/ingestion/learnings.js');
|
|
1325
|
+
const observations = await getObservations(projectPath, limit);
|
|
1326
|
+
result.observations = observations.map((o) => ({
|
|
1327
|
+
id: o.id,
|
|
1328
|
+
type: o.type,
|
|
1329
|
+
content: o.content?.substring(0, 100),
|
|
1330
|
+
}));
|
|
1331
|
+
}
|
|
1332
|
+
// Get places (spatial memory) with filtering
|
|
1333
|
+
if (include.includes('places')) {
|
|
1334
|
+
const { initializeDefaultPlaces, getProjectPlaces, walkPlace, getPlaceByType, syncAllPlaceMemoryCounts } = await import('./core/places/index.js');
|
|
1335
|
+
await initializeDefaultPlaces(project.id);
|
|
1336
|
+
// Sync memory counts if requested
|
|
1337
|
+
if (options.sync) {
|
|
1338
|
+
await syncAllPlaceMemoryCounts(project.id);
|
|
1339
|
+
console.log('Synced memory counts for all places.');
|
|
1340
|
+
}
|
|
1341
|
+
// Auto-archive old memories if requested
|
|
1342
|
+
if (options.archive) {
|
|
1343
|
+
const { autoArchiveOldMemories } = await import('./core/places/index.js');
|
|
1344
|
+
const archiveResult = await autoArchiveOldMemories(project.id, 30);
|
|
1345
|
+
console.log(`Archived ${archiveResult.archived} old memories to Archive (${archiveResult.failed} failed).`);
|
|
1346
|
+
}
|
|
1347
|
+
let places = await getProjectPlaces(project.id);
|
|
1348
|
+
// Apply --has-memories filter
|
|
1349
|
+
if (hasMemoriesOnly) {
|
|
1350
|
+
places = places.filter((p) => p.memoryCount > 0);
|
|
1351
|
+
}
|
|
1352
|
+
// Apply --place filter (from --place or --task auto-detect)
|
|
1353
|
+
if (placeFilter) {
|
|
1354
|
+
const filtered = places.filter((p) => p.placeType === placeFilter);
|
|
1355
|
+
if (filtered.length > 0) {
|
|
1356
|
+
places = filtered;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
// Format places based on tier
|
|
1360
|
+
if (tier === 'quick') {
|
|
1361
|
+
// Just place names (~50 tokens)
|
|
1362
|
+
result.places = places.map((p) => ({
|
|
1363
|
+
name: p.name,
|
|
1364
|
+
type: p.placeType,
|
|
1365
|
+
}));
|
|
1366
|
+
}
|
|
1367
|
+
else if (tier === 'medium') {
|
|
1368
|
+
// Top 3 memories per place (~170 tokens)
|
|
1369
|
+
const placesWithMemories = [];
|
|
1370
|
+
for (const p of places) {
|
|
1371
|
+
if (p.memoryCount > 0) {
|
|
1372
|
+
const walkResult = await walkPlace(project.id, p.placeType, {
|
|
1373
|
+
tokenBudget: 170,
|
|
1374
|
+
maxMemoriesPerPlace: 3,
|
|
1375
|
+
compressWithToon: false,
|
|
854
1376
|
});
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
else {
|
|
862
|
-
await unpinMemory(String(args.memoryId));
|
|
863
|
-
}
|
|
864
|
-
return this.jsonResponse({
|
|
865
|
-
ok: true,
|
|
866
|
-
message: `Memory ${args.memoryId} ${pinned ? 'pinned' : 'unpinned'}`
|
|
1377
|
+
placesWithMemories.push({
|
|
1378
|
+
name: p.name,
|
|
1379
|
+
type: p.placeType,
|
|
1380
|
+
purpose: p.purpose,
|
|
1381
|
+
memories: p.memoryCount,
|
|
1382
|
+
preview: walkResult?.memories.slice(0, 3).map((m) => m.content?.substring(0, 80)) || [],
|
|
867
1383
|
});
|
|
868
1384
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1385
|
+
}
|
|
1386
|
+
result.places = placesWithMemories;
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
// Full - all memories (~500 tokens)
|
|
1390
|
+
result.places = places.map((p) => ({
|
|
1391
|
+
name: p.name,
|
|
1392
|
+
type: p.placeType,
|
|
1393
|
+
purpose: p.purpose,
|
|
1394
|
+
memories: p.memoryCount,
|
|
1395
|
+
}));
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
if (options.json) {
|
|
1399
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
1400
|
+
}
|
|
1401
|
+
else {
|
|
1402
|
+
// Human readable output
|
|
1403
|
+
console.log(`\n=== ${project.name} Context ===\n`);
|
|
1404
|
+
if (result.places && result.places.length > 0) {
|
|
1405
|
+
console.log('Spatial Memory Places:');
|
|
1406
|
+
result.places.forEach((p) => {
|
|
1407
|
+
if (tier === 'quick') {
|
|
1408
|
+
console.log(` ${p.name} (${p.type})`);
|
|
1409
|
+
}
|
|
1410
|
+
else if (tier === 'medium') {
|
|
1411
|
+
console.log(` ${p.name} (${p.memories} memories) - ${p.purpose}`);
|
|
1412
|
+
if (p.preview && p.preview.length > 0) {
|
|
1413
|
+
p.preview.forEach((m) => {
|
|
1414
|
+
console.log(` - ${m}...`);
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
883
1417
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
return this.jsonResponse({ ok: true, ...stats });
|
|
1418
|
+
else {
|
|
1419
|
+
console.log(` ${p.name} (${p.memories} memories) - ${p.purpose}`);
|
|
887
1420
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
}
|
|
1421
|
+
});
|
|
1422
|
+
console.log('');
|
|
891
1423
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1424
|
+
if (result.memories && result.memories.length > 0) {
|
|
1425
|
+
console.log('Recent Memories:');
|
|
1426
|
+
result.memories.slice(0, 5).forEach((m) => {
|
|
1427
|
+
console.log(` [${m.type}] ${m.content}`);
|
|
1428
|
+
});
|
|
1429
|
+
console.log('');
|
|
896
1430
|
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
catch (error) {
|
|
1434
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
1435
|
+
process.exit(1);
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
// squish migrate - Migrate memories between databases
|
|
1439
|
+
program
|
|
1440
|
+
.command('migrate')
|
|
1441
|
+
.description('Migrate memories from one .squish directory to another')
|
|
1442
|
+
.option('-f, --from <path>', 'Source .squish directory (read from)', '')
|
|
1443
|
+
.option('-t, --to <path>', 'Target .squish directory (write to)', '')
|
|
1444
|
+
.option('--delete-source', 'Delete source after migration (use with caution)', false)
|
|
1445
|
+
.option('--dry-run', 'Preview migration without applying', false)
|
|
1446
|
+
.action(async (options) => {
|
|
1447
|
+
try {
|
|
1448
|
+
if (!options.from || !options.to) {
|
|
1449
|
+
console.log(JSON.stringify({
|
|
1450
|
+
ok: false,
|
|
1451
|
+
error: 'Usage: squish migrate --from /path/to/old/.squish --to /path/to/new/.squish'
|
|
1452
|
+
}, null, 2));
|
|
1453
|
+
process.exit(1);
|
|
1454
|
+
}
|
|
1455
|
+
const sourcePath = path.join(options.from, 'squish.db');
|
|
1456
|
+
const targetPath = path.join(options.to, 'squish.db');
|
|
1457
|
+
if (!existsSync(sourcePath)) {
|
|
1458
|
+
console.log(JSON.stringify({ ok: false, error: `Source database not found: ${sourcePath}` }, null, 2));
|
|
1459
|
+
process.exit(1);
|
|
1460
|
+
}
|
|
1461
|
+
if (!existsSync(targetPath)) {
|
|
1462
|
+
console.log(JSON.stringify({ ok: false, error: `Target database not found: ${targetPath}` }, null, 2));
|
|
1463
|
+
process.exit(1);
|
|
1464
|
+
}
|
|
1465
|
+
console.log(`Migrating memories from:\n ${options.from}\nto:\n ${options.to}\n`);
|
|
1466
|
+
// Import database modules dynamically
|
|
1467
|
+
const { migrateMemories } = await import('./core/memory/migrate.js');
|
|
1468
|
+
const result = await migrateMemories(options.from, options.to, {
|
|
1469
|
+
dryRun: options.dryRun,
|
|
1470
|
+
deleteSource: options.deleteSource
|
|
897
1471
|
});
|
|
898
|
-
|
|
899
|
-
process.on('SIGINT', () => this.shutdown());
|
|
900
|
-
process.on('SIGTERM', () => this.shutdown());
|
|
1472
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
901
1473
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
};
|
|
1474
|
+
catch (error) {
|
|
1475
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
1476
|
+
process.exit(1);
|
|
906
1477
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1478
|
+
});
|
|
1479
|
+
// squish clean - Run deduplication and consolidation
|
|
1480
|
+
program
|
|
1481
|
+
.command('clean')
|
|
1482
|
+
.description('Run maintenance: deduplication + consolidation')
|
|
1483
|
+
.option('-t, --threshold <number>', 'Similarity threshold for dedup (0-1)', '0.85')
|
|
1484
|
+
.option('-d, --min-age <days>', 'Minimum age for consolidation', '90')
|
|
1485
|
+
.option('-i, --max-importance <number>', 'Max importance to consolidate (0-100)', '30')
|
|
1486
|
+
.option('-c, --min-cluster <number>', 'Minimum cluster size', '3')
|
|
1487
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
1488
|
+
.option('--dry-run', 'Preview changes without applying', false)
|
|
1489
|
+
.action(async (options) => {
|
|
1490
|
+
try {
|
|
1491
|
+
console.log('Running maintenance: deduplication + consolidation...\n');
|
|
1492
|
+
// Step 1: Deduplication
|
|
1493
|
+
console.log('Step 1: Finding duplicate memories...');
|
|
1494
|
+
const dedupResult = await runDeduplicationJob(options.project);
|
|
1495
|
+
console.log(` Found ${dedupResult.duplicatesFound} duplicates, merged ${dedupResult.mergedCount}`);
|
|
1496
|
+
// Step 2: Consolidation
|
|
1497
|
+
console.log('\nStep 2: Consolidating old memories...');
|
|
1498
|
+
const consolidateResult = await runFullConsolidationJob(options.project);
|
|
1499
|
+
console.log(` Clustered ${consolidateResult.clustered}, merged ${consolidateResult.merged}, consolidated ${consolidateResult.consolidated}`);
|
|
1500
|
+
console.log(JSON.stringify({
|
|
1501
|
+
ok: true,
|
|
1502
|
+
dedup: {
|
|
1503
|
+
duplicatesFound: dedupResult.duplicatesFound,
|
|
1504
|
+
mergedCount: dedupResult.mergedCount,
|
|
1505
|
+
tokensRecovered: dedupResult.tokensRecovered
|
|
924
1506
|
},
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
async handleContextPaging(args) {
|
|
932
|
-
const action = args.action;
|
|
933
|
-
const sessionId = String(args.sessionId);
|
|
934
|
-
const actions = {
|
|
935
|
-
load: () => loadMemoryToContext(sessionId, String(args.memoryId)),
|
|
936
|
-
evict: () => evictMemoryFromContext(sessionId, String(args.memoryId)),
|
|
937
|
-
view: () => viewLoadedMemories(sessionId),
|
|
938
|
-
};
|
|
939
|
-
const handler = actions[action];
|
|
940
|
-
if (!handler)
|
|
941
|
-
throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
|
|
942
|
-
return this.jsonResponse(await handler());
|
|
943
|
-
}
|
|
944
|
-
async handleMerge(args) {
|
|
945
|
-
const action = args.action;
|
|
946
|
-
const handlers = {
|
|
947
|
-
detect: () => handleDetectDuplicates(args),
|
|
948
|
-
list: () => handleListProposals(args),
|
|
949
|
-
preview: () => handlePreviewMerge(args),
|
|
950
|
-
stats: () => handleGetMergeStats(args),
|
|
951
|
-
approve: () => handleApproveMerge(args),
|
|
952
|
-
reject: () => handleRejectMerge(args),
|
|
953
|
-
reverse: () => handleReverseMerge(args),
|
|
954
|
-
};
|
|
955
|
-
const handler = handlers[action];
|
|
956
|
-
if (!handler)
|
|
957
|
-
throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
|
|
958
|
-
return this.jsonResponse(await handler());
|
|
1507
|
+
consolidate: {
|
|
1508
|
+
clustered: consolidateResult.clustered,
|
|
1509
|
+
merged: consolidateResult.merged,
|
|
1510
|
+
consolidated: consolidateResult.consolidated
|
|
1511
|
+
}
|
|
1512
|
+
}, null, 2));
|
|
959
1513
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
process.exit(0);
|
|
1514
|
+
catch (error) {
|
|
1515
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
963
1516
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1517
|
+
});
|
|
1518
|
+
// squish hooks session-start --agent claude-code --mode startup
|
|
1519
|
+
program
|
|
1520
|
+
.command('hooks')
|
|
1521
|
+
.description('Handle agent hooks (session-start, post-tool-use, session-end, pre-compact)')
|
|
1522
|
+
.argument('<event>', 'Event type: session-start, post-tool-use, session-end, pre-compact')
|
|
1523
|
+
.option('-a, --agent <agent>', 'Agent type: claude-code, opencode, cursor, windsurf', 'claude-code')
|
|
1524
|
+
.option('-m, --mode <mode>', 'Mode for session-start: startup, resume, compact', 'startup')
|
|
1525
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
1526
|
+
.option('-t, --tool <tool>', 'Tool name for post-tool-use')
|
|
1527
|
+
.option('--tool-input <json>', 'Tool input as JSON string')
|
|
1528
|
+
.option('--tool-result <json>', 'Tool result as JSON string')
|
|
1529
|
+
.option('--wip <work>', 'Work in progress for session-end')
|
|
1530
|
+
.action(async (event, options) => {
|
|
1531
|
+
try {
|
|
1532
|
+
const agentType = options.agent;
|
|
1533
|
+
const projectPath = options.project;
|
|
1534
|
+
let result;
|
|
1535
|
+
switch (event) {
|
|
1536
|
+
case 'session-start':
|
|
1537
|
+
result = await handleSessionStart({
|
|
1538
|
+
projectPath,
|
|
1539
|
+
mode: options.mode,
|
|
1540
|
+
agentType,
|
|
1541
|
+
});
|
|
1542
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
1543
|
+
break;
|
|
1544
|
+
case 'post-tool-use':
|
|
1545
|
+
if (!options.tool) {
|
|
1546
|
+
console.log(JSON.stringify({ ok: false, error: '--tool required for post-tool-use' }, null, 2));
|
|
1547
|
+
process.exit(1);
|
|
1548
|
+
}
|
|
1549
|
+
const toolInput = options.toolInput ? JSON.parse(options.toolInput) : {};
|
|
1550
|
+
const toolResult = options.toolResult ? JSON.parse(options.toolResult) : {};
|
|
1551
|
+
result = await handlePostToolUse({
|
|
1552
|
+
toolName: options.tool,
|
|
1553
|
+
toolInput,
|
|
1554
|
+
toolResult,
|
|
1555
|
+
projectPath,
|
|
1556
|
+
agentType,
|
|
1557
|
+
});
|
|
1558
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
1559
|
+
break;
|
|
1560
|
+
case 'session-end':
|
|
1561
|
+
result = await handleSessionEnd({
|
|
1562
|
+
projectPath,
|
|
1563
|
+
agentType,
|
|
1564
|
+
workInProgress: options.wip,
|
|
1565
|
+
});
|
|
1566
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
1567
|
+
break;
|
|
1568
|
+
case 'pre-compact':
|
|
1569
|
+
result = await handlePreCompact({
|
|
1570
|
+
projectPath,
|
|
1571
|
+
agentType,
|
|
1572
|
+
});
|
|
1573
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
1574
|
+
break;
|
|
1575
|
+
default:
|
|
1576
|
+
console.log(JSON.stringify({ ok: false, error: `Unknown event: ${event}` }, null, 2));
|
|
1577
|
+
process.exit(1);
|
|
1578
|
+
}
|
|
972
1579
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1580
|
+
catch (error) {
|
|
1581
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
1582
|
+
process.exit(1);
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
// squish walk - Walk through spatial memory places
|
|
1586
|
+
program
|
|
1587
|
+
.command('walk [place]')
|
|
1588
|
+
.description('Walk through spatial memory places (entry_hall, library, workshop, lab, office, garden, archive, or --all)')
|
|
1589
|
+
.option('-a, --all', 'Walk all places in order', false)
|
|
1590
|
+
.option('-t, --tokens <number>', 'Max tokens budget (default: 170)', '170')
|
|
1591
|
+
.option('-m, --max <number>', 'Max memories per place', '10')
|
|
1592
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
1593
|
+
.option('-q, --quick', 'Quick tour (place names only)', false)
|
|
1594
|
+
.option('-j, --json', 'Output as JSON', false)
|
|
1595
|
+
.action(async (place, options) => {
|
|
1596
|
+
try {
|
|
1597
|
+
const projectPath = resolveProjectPath(options.project);
|
|
1598
|
+
await ensureProject(projectPath);
|
|
1599
|
+
const project = await getOrCreateProject(projectPath);
|
|
1600
|
+
if (!project) {
|
|
1601
|
+
console.log(JSON.stringify({ ok: false, error: 'Project not found' }, null, 2));
|
|
1602
|
+
process.exit(1);
|
|
1603
|
+
}
|
|
1604
|
+
// Ensure places are initialized
|
|
1605
|
+
await initializeDefaultPlaces(project.id);
|
|
1606
|
+
if (options.quick) {
|
|
1607
|
+
const tour = await quickTour(project.id);
|
|
1608
|
+
if (options.json) {
|
|
1609
|
+
console.log(JSON.stringify({ ok: true, ...tour }, null, 2));
|
|
1610
|
+
}
|
|
1611
|
+
else {
|
|
1612
|
+
console.log(`\n=== Spatial Memory Tour ===\n`);
|
|
1613
|
+
console.log(`Total Memories: ${tour.totalMemories}\n`);
|
|
1614
|
+
for (const p of tour.places) {
|
|
1615
|
+
console.log(`${p.name} (${p.memoryCount} memories)`);
|
|
1616
|
+
console.log(` ${p.purpose}\n`);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
const tokenBudget = parseInt(options.tokens);
|
|
1622
|
+
const maxMemories = parseInt(options.max);
|
|
1623
|
+
if (options.all) {
|
|
1624
|
+
const results = await walkAllPlaces(project.id, {
|
|
1625
|
+
tokenBudget: Math.floor(tokenBudget / 7),
|
|
1626
|
+
maxMemoriesPerPlace: maxMemories,
|
|
1627
|
+
compressWithToon: true,
|
|
1628
|
+
});
|
|
1629
|
+
if (options.json) {
|
|
1630
|
+
console.log(JSON.stringify({ ok: true, places: results }, null, 2));
|
|
1631
|
+
}
|
|
1632
|
+
else {
|
|
1633
|
+
console.log(`\n=== Walking All Places ===\n`);
|
|
1634
|
+
for (const r of results) {
|
|
1635
|
+
console.log(`## ${r.place.name} (${r.memories.length} memories, ~${r.totalTokens} tokens)`);
|
|
1636
|
+
r.memories.forEach((m, i) => {
|
|
1637
|
+
console.log(` ${i + 1}. ${m.content.substring(0, 60)}...`);
|
|
1638
|
+
});
|
|
1639
|
+
console.log('');
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
else if (place) {
|
|
1644
|
+
const validPlaces = ['entry_hall', 'library', 'workshop', 'lab', 'office', 'garden', 'archive'];
|
|
1645
|
+
const placeType = validPlaces.includes(place) ? place : 'workshop';
|
|
1646
|
+
const result = await walkPlace(project.id, placeType, {
|
|
1647
|
+
tokenBudget,
|
|
1648
|
+
maxMemoriesPerPlace: maxMemories,
|
|
1649
|
+
compressWithToon: true,
|
|
1650
|
+
});
|
|
1651
|
+
if (options.json) {
|
|
1652
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
1653
|
+
}
|
|
1654
|
+
else if (result) {
|
|
1655
|
+
console.log(`\n=== ${result.place.name} ===\n`);
|
|
1656
|
+
console.log(`Purpose: ${result.place.purpose || 'N/A'}\n`);
|
|
1657
|
+
result.memories.forEach((m, i) => {
|
|
1658
|
+
console.log(`${i + 1}. [${m.type}] ${m.content.substring(0, 80)}`);
|
|
1659
|
+
});
|
|
1660
|
+
console.log(`\n~${result.totalTokens} tokens`);
|
|
1661
|
+
}
|
|
1662
|
+
else {
|
|
1663
|
+
console.log(JSON.stringify({ ok: false, error: `Place not found: ${place}` }, null, 2));
|
|
1664
|
+
}
|
|
979
1665
|
}
|
|
980
|
-
else {
|
|
981
|
-
logger.info(`Squish v${VERSION} - Plugin manifest verified`);
|
|
982
|
-
}
|
|
983
|
-
const transport = new StdioServerTransport();
|
|
984
|
-
await this.server.connect(transport);
|
|
985
|
-
logger.info(`v${VERSION}`);
|
|
986
|
-
registerJobHandler('nightly_maintenance', runNightlyJob);
|
|
987
|
-
registerJobHandler('weekly_maintenance', runWeeklyJob);
|
|
988
|
-
await initializeScheduler();
|
|
989
|
-
startHeartbeatChecking();
|
|
990
|
-
await this.onSessionInitialized();
|
|
991
|
-
await heartbeat();
|
|
992
|
-
startWebServer();
|
|
993
1666
|
}
|
|
994
|
-
|
|
995
|
-
|
|
1667
|
+
catch (error) {
|
|
1668
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
1669
|
+
process.exit(1);
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
await program.parseAsync(process.argv);
|
|
996
1673
|
}
|
|
1674
|
+
// MCP server: core/commands/mcp-server.ts
|
|
1675
|
+
// Run with: npx squish-mcp
|
|
1676
|
+
// ============================================================================
|
|
997
1677
|
//# sourceMappingURL=index.js.map
|