squish-memory 1.0.1 → 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 +155 -274
- 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/server.js +27 -1
- package/dist/core/mcp/tools.js +35 -3
- package/dist/core/mcp/types.d.ts +4 -4
- 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 +196 -181
- 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 +89 -14
- 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 +115 -36
- package/dist/db/bootstrap.js +927 -555
- 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 +1457 -631
- 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} +538 -509
- package/generated/mcp/manifest.json +23 -23
- package/{.mcp.json → mcp.json.example} +1 -1
- package/package.json +159 -169
- 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 -674
- package/scripts/install-opencode.sh +75 -0
- package/scripts/install-windsurf.sh +67 -0
- package/skills/squish-memory/SKILL.md +104 -100
- 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 -56
- 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/algorithms/analytics/token-estimator.d.ts.map +0 -1
- package/dist/algorithms/analytics/token-estimator.js.map +0 -1
- package/dist/algorithms/detection/hash-filters.d.ts.map +0 -1
- package/dist/algorithms/detection/hash-filters.js.map +0 -1
- package/dist/algorithms/detection/semantic-ranker.d.ts.map +0 -1
- package/dist/algorithms/detection/semantic-ranker.js.map +0 -1
- package/dist/algorithms/detection/two-stage-detector.d.ts.map +0 -1
- package/dist/algorithms/detection/two-stage-detector.js.map +0 -1
- package/dist/algorithms/handlers/approve-merge.d.ts.map +0 -1
- package/dist/algorithms/handlers/approve-merge.js.map +0 -1
- package/dist/algorithms/handlers/detect-duplicates.d.ts.map +0 -1
- package/dist/algorithms/handlers/detect-duplicates.js.map +0 -1
- package/dist/algorithms/handlers/get-stats.d.ts.map +0 -1
- package/dist/algorithms/handlers/get-stats.js.map +0 -1
- package/dist/algorithms/handlers/list-proposals.d.ts.map +0 -1
- package/dist/algorithms/handlers/list-proposals.js.map +0 -1
- package/dist/algorithms/handlers/preview-merge.d.ts.map +0 -1
- package/dist/algorithms/handlers/preview-merge.js.map +0 -1
- package/dist/algorithms/handlers/reject-merge.d.ts.map +0 -1
- package/dist/algorithms/handlers/reject-merge.js.map +0 -1
- package/dist/algorithms/handlers/reverse-merge.d.ts.map +0 -1
- package/dist/algorithms/handlers/reverse-merge.js.map +0 -1
- package/dist/algorithms/safety/safety-checks.d.ts.map +0 -1
- package/dist/algorithms/safety/safety-checks.js.map +0 -1
- package/dist/algorithms/strategies/merge-strategies.d.ts.map +0 -1
- package/dist/algorithms/strategies/merge-strategies.js.map +0 -1
- package/dist/algorithms/utils/response-builder.d.ts.map +0 -1
- package/dist/algorithms/utils/response-builder.js.map +0 -1
- package/dist/api/web/index.d.ts +0 -3
- package/dist/api/web/index.d.ts.map +0 -1
- package/dist/api/web/index.js +0 -4
- package/dist/api/web/index.js.map +0 -1
- package/dist/api/web/web-server.d.ts +0 -3
- package/dist/api/web/web-server.d.ts.map +0 -1
- package/dist/api/web/web-server.js +0 -6
- package/dist/api/web/web-server.js.map +0 -1
- package/dist/api/web/web.d.ts +0 -4
- package/dist/api/web/web.d.ts.map +0 -1
- package/dist/api/web/web.js.map +0 -1
- package/dist/commands/managed-sync.d.ts.map +0 -1
- package/dist/commands/managed-sync.js.map +0 -1
- package/dist/commands/mcp-server.d.ts.map +0 -1
- package/dist/commands/mcp-server.js +0 -393
- package/dist/commands/mcp-server.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/core/agent-memory.d.ts.map +0 -1
- package/dist/core/agent-memory.js.map +0 -1
- package/dist/core/associations.d.ts.map +0 -1
- package/dist/core/associations.js.map +0 -1
- package/dist/core/cache.d.ts.map +0 -1
- package/dist/core/cache.js.map +0 -1
- package/dist/core/consolidation.d.ts.map +0 -1
- package/dist/core/consolidation.js.map +0 -1
- package/dist/core/context-paging.d.ts.map +0 -1
- package/dist/core/context-paging.js.map +0 -1
- package/dist/core/context.d.ts.map +0 -1
- package/dist/core/context.js +0 -24
- package/dist/core/context.js.map +0 -1
- package/dist/core/core-memory.d.ts.map +0 -1
- package/dist/core/core-memory.js.map +0 -1
- package/dist/core/database.d.ts.map +0 -1
- package/dist/core/database.js.map +0 -1
- package/dist/core/embeddings/google-multimodal.d.ts.map +0 -1
- package/dist/core/embeddings/google-multimodal.js.map +0 -1
- package/dist/core/embeddings/qmd-client.d.ts.map +0 -1
- package/dist/core/embeddings/qmd-client.js.map +0 -1
- package/dist/core/embeddings.d.ts.map +0 -1
- package/dist/core/embeddings.js.map +0 -1
- package/dist/core/governance.d.ts.map +0 -1
- package/dist/core/governance.js +0 -64
- package/dist/core/governance.js.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js.map +0 -1
- package/dist/core/layers/generator.d.ts.map +0 -1
- package/dist/core/layers/generator.js.map +0 -1
- package/dist/core/lifecycle.d.ts.map +0 -1
- package/dist/core/lifecycle.js.map +0 -1
- package/dist/core/local-embeddings.d.ts.map +0 -1
- package/dist/core/local-embeddings.js.map +0 -1
- package/dist/core/logger.d.ts.map +0 -1
- package/dist/core/logger.js.map +0 -1
- package/dist/core/mcp/client.d.ts.map +0 -1
- package/dist/core/mcp/client.js.map +0 -1
- package/dist/core/mcp/index.d.ts.map +0 -1
- package/dist/core/mcp/index.js.map +0 -1
- package/dist/core/mcp/server.d.ts.map +0 -1
- package/dist/core/mcp/server.js.map +0 -1
- package/dist/core/mcp/standalone-server.d.ts.map +0 -1
- package/dist/core/mcp/standalone-server.js.map +0 -1
- package/dist/core/mcp/tools.d.ts.map +0 -1
- package/dist/core/mcp/tools.js.map +0 -1
- package/dist/core/mcp/types.d.ts.map +0 -1
- package/dist/core/mcp/types.js.map +0 -1
- package/dist/core/memory/bridge-discovery.d.ts.map +0 -1
- package/dist/core/memory/bridge-discovery.js.map +0 -1
- package/dist/core/memory/categorizer.d.ts.map +0 -1
- package/dist/core/memory/categorizer.js.map +0 -1
- package/dist/core/memory/conflict-detector.d.ts.map +0 -1
- package/dist/core/memory/conflict-detector.js.map +0 -1
- package/dist/core/memory/consolidation.d.ts.map +0 -1
- package/dist/core/memory/consolidation.js.map +0 -1
- package/dist/core/memory/context-collector.d.ts.map +0 -1
- package/dist/core/memory/context-collector.js.map +0 -1
- package/dist/core/memory/contradiction-resolver.d.ts.map +0 -1
- package/dist/core/memory/contradiction-resolver.js.map +0 -1
- package/dist/core/memory/edit-workflow.d.ts.map +0 -1
- package/dist/core/memory/edit-workflow.js.map +0 -1
- package/dist/core/memory/entity-extractor.d.ts.map +0 -1
- package/dist/core/memory/entity-extractor.js.map +0 -1
- package/dist/core/memory/entity-resolver.d.ts.map +0 -1
- package/dist/core/memory/entity-resolver.js.map +0 -1
- package/dist/core/memory/fact-extractor.d.ts.map +0 -1
- package/dist/core/memory/fact-extractor.js.map +0 -1
- package/dist/core/memory/feedback-tracker.d.ts.map +0 -1
- package/dist/core/memory/feedback-tracker.js.map +0 -1
- package/dist/core/memory/hybrid-retrieval.d.ts.map +0 -1
- package/dist/core/memory/hybrid-retrieval.js.map +0 -1
- package/dist/core/memory/hybrid-scorer.d.ts.map +0 -1
- package/dist/core/memory/hybrid-scorer.js.map +0 -1
- package/dist/core/memory/hybrid-search.d.ts.map +0 -1
- package/dist/core/memory/hybrid-search.js.map +0 -1
- package/dist/core/memory/importance.d.ts.map +0 -1
- package/dist/core/memory/importance.js.map +0 -1
- package/dist/core/memory/index.d.ts.map +0 -1
- package/dist/core/memory/index.js.map +0 -1
- package/dist/core/memory/memories.d.ts.map +0 -1
- package/dist/core/memory/memories.js.map +0 -1
- package/dist/core/memory/memory-manager.d.ts.map +0 -1
- package/dist/core/memory/memory-manager.js.map +0 -1
- package/dist/core/memory/progressive-disclosure.d.ts.map +0 -1
- package/dist/core/memory/progressive-disclosure.js.map +0 -1
- package/dist/core/memory/query-processor.d.ts.map +0 -1
- package/dist/core/memory/query-processor.js.map +0 -1
- package/dist/core/memory/query-rewriter.d.ts.map +0 -1
- package/dist/core/memory/query-rewriter.js.map +0 -1
- package/dist/core/memory/response-analyzer.d.ts.map +0 -1
- package/dist/core/memory/response-analyzer.js.map +0 -1
- package/dist/core/memory/serialization.d.ts.map +0 -1
- package/dist/core/memory/serialization.js.map +0 -1
- package/dist/core/memory/stats.d.ts.map +0 -1
- package/dist/core/memory/stats.js.map +0 -1
- package/dist/core/memory/telemetry.d.ts.map +0 -1
- package/dist/core/memory/telemetry.js.map +0 -1
- package/dist/core/memory/temporal-facts.d.ts.map +0 -1
- package/dist/core/memory/temporal-facts.js.map +0 -1
- package/dist/core/memory/temporal-parser.d.ts.map +0 -1
- package/dist/core/memory/temporal-parser.js.map +0 -1
- package/dist/core/memory/trigger-detector.d.ts.map +0 -1
- package/dist/core/memory/trigger-detector.js.map +0 -1
- package/dist/core/memory/write-gate.d.ts.map +0 -1
- package/dist/core/memory/write-gate.js.map +0 -1
- package/dist/core/namespaces/index.d.ts.map +0 -1
- package/dist/core/namespaces/index.js.map +0 -1
- package/dist/core/namespaces/uri-parser.d.ts.map +0 -1
- package/dist/core/namespaces/uri-parser.js.map +0 -1
- package/dist/core/observations.d.ts +0 -26
- package/dist/core/observations.d.ts.map +0 -1
- package/dist/core/observations.js +0 -110
- package/dist/core/observations.js.map +0 -1
- package/dist/core/privacy.d.ts.map +0 -1
- package/dist/core/privacy.js.map +0 -1
- package/dist/core/projects.d.ts.map +0 -1
- package/dist/core/projects.js.map +0 -1
- package/dist/core/redis.d.ts.map +0 -1
- package/dist/core/redis.js.map +0 -1
- package/dist/core/requirements.d.ts +0 -20
- package/dist/core/requirements.d.ts.map +0 -1
- package/dist/core/requirements.js +0 -35
- package/dist/core/requirements.js.map +0 -1
- package/dist/core/scheduler/cron-scheduler.d.ts.map +0 -1
- package/dist/core/scheduler/cron-scheduler.js.map +0 -1
- package/dist/core/scheduler/heartbeat.d.ts.map +0 -1
- package/dist/core/scheduler/heartbeat.js.map +0 -1
- package/dist/core/scheduler/index.d.ts.map +0 -1
- package/dist/core/scheduler/index.js.map +0 -1
- package/dist/core/scheduler/job-runner.d.ts.map +0 -1
- package/dist/core/scheduler/job-runner.js.map +0 -1
- package/dist/core/search/conversations.d.ts.map +0 -1
- package/dist/core/search/conversations.js.map +0 -1
- package/dist/core/search/entities.d.ts.map +0 -1
- package/dist/core/search/entities.js.map +0 -1
- package/dist/core/search/folder-context.d.ts.map +0 -1
- package/dist/core/search/folder-context.js.map +0 -1
- package/dist/core/search/index.d.ts.map +0 -1
- package/dist/core/search/index.js.map +0 -1
- package/dist/core/search/qmd-search.d.ts.map +0 -1
- package/dist/core/search/qmd-search.js.map +0 -1
- package/dist/core/secret-detector.d.ts.map +0 -1
- package/dist/core/secret-detector.js.map +0 -1
- package/dist/core/session/auto-load.d.ts.map +0 -1
- package/dist/core/session/auto-load.js.map +0 -1
- package/dist/core/session/index.d.ts.map +0 -1
- package/dist/core/session/index.js.map +0 -1
- package/dist/core/session/types.d.ts.map +0 -1
- package/dist/core/session/types.js.map +0 -1
- package/dist/core/session-hooks/self-iteration-job.d.ts.map +0 -1
- package/dist/core/session-hooks/self-iteration-job.js.map +0 -1
- package/dist/core/session-hooks/session-hooks.d.ts.map +0 -1
- package/dist/core/session-hooks/session-hooks.js.map +0 -1
- package/dist/core/snapshots/cleanup.d.ts.map +0 -1
- package/dist/core/snapshots/cleanup.js.map +0 -1
- package/dist/core/snapshots/comparison.d.ts.map +0 -1
- package/dist/core/snapshots/comparison.js.map +0 -1
- package/dist/core/snapshots/creation.d.ts.map +0 -1
- package/dist/core/snapshots/creation.js.map +0 -1
- package/dist/core/snapshots/retrieval.d.ts.map +0 -1
- package/dist/core/snapshots/retrieval.js.map +0 -1
- package/dist/core/snapshots/stats.d.ts.map +0 -1
- package/dist/core/snapshots/stats.js.map +0 -1
- package/dist/core/snapshots.d.ts.map +0 -1
- package/dist/core/snapshots.js.map +0 -1
- package/dist/core/summarization/cleanup.d.ts.map +0 -1
- package/dist/core/summarization/cleanup.js.map +0 -1
- package/dist/core/summarization/queries.d.ts.map +0 -1
- package/dist/core/summarization/queries.js.map +0 -1
- package/dist/core/summarization/stats.d.ts.map +0 -1
- package/dist/core/summarization/stats.js.map +0 -1
- package/dist/core/summarization/strategies.d.ts.map +0 -1
- package/dist/core/summarization/strategies.js.map +0 -1
- package/dist/core/summarization.d.ts.map +0 -1
- package/dist/core/summarization.js.map +0 -1
- package/dist/core/sync/qmd-sync.d.ts.map +0 -1
- package/dist/core/sync/qmd-sync.js.map +0 -1
- package/dist/core/temporal-facts.d.ts.map +0 -1
- package/dist/core/temporal-facts.js.map +0 -1
- package/dist/core/tracing/collector.d.ts.map +0 -1
- package/dist/core/tracing/collector.js.map +0 -1
- package/dist/core/tracing/visualizer.d.ts.map +0 -1
- package/dist/core/tracing/visualizer.js.map +0 -1
- package/dist/core/utils/cleanup-operations.d.ts.map +0 -1
- package/dist/core/utils/cleanup-operations.js.map +0 -1
- package/dist/core/utils/content-extraction.d.ts.map +0 -1
- package/dist/core/utils/content-extraction.js.map +0 -1
- package/dist/core/utils/filter-builder.d.ts.map +0 -1
- package/dist/core/utils/filter-builder.js.map +0 -1
- package/dist/core/utils/history-traversal.d.ts.map +0 -1
- package/dist/core/utils/history-traversal.js.map +0 -1
- package/dist/core/utils/memory-operations.d.ts.map +0 -1
- package/dist/core/utils/memory-operations.js.map +0 -1
- package/dist/core/utils/query-operations.d.ts.map +0 -1
- package/dist/core/utils/query-operations.js.map +0 -1
- package/dist/core/utils/summarization-helpers.d.ts.map +0 -1
- package/dist/core/utils/summarization-helpers.js.map +0 -1
- package/dist/core/utils/temporal-queries.d.ts.map +0 -1
- package/dist/core/utils/temporal-queries.js.map +0 -1
- package/dist/core/utils/version-management.d.ts.map +0 -1
- package/dist/core/utils/version-management.js.map +0 -1
- package/dist/core/utils.d.ts.map +0 -1
- package/dist/core/utils.js.map +0 -1
- package/dist/core/worker.d.ts.map +0 -1
- package/dist/core/worker.js.map +0 -1
- package/dist/db/adapter.d.ts.map +0 -1
- package/dist/db/adapter.js.map +0 -1
- package/dist/db/bootstrap.d.ts.map +0 -1
- package/dist/db/bootstrap.js.map +0 -1
- package/dist/db/index.d.ts.map +0 -1
- package/dist/db/index.js.map +0 -1
- package/dist/db/schema.d.ts.map +0 -1
- package/dist/db/schema.js.map +0 -1
- package/dist/drizzle/schema-sqlite.d.ts.map +0 -1
- package/dist/drizzle/schema-sqlite.js.map +0 -1
- package/dist/drizzle/schema.d.ts.map +0 -1
- package/dist/drizzle/schema.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- 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.d.ts.map +0 -1
- package/packages/plugin-claude-code/dist/plugin-wrapper.js +0 -191
- package/packages/plugin-claude-code/dist/plugin-wrapper.js.map +0 -1
- 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.d.ts.map +0 -1
- package/packages/plugin-openclaw/dist/index.js +0 -262
- package/packages/plugin-openclaw/dist/index.js.map +0 -1
- 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 -256
- package/skills/squish-cli/SKILL.md +0 -200
- package/skills/squish-mcp/SKILL.md +0 -311
- 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,62 +1,165 @@
|
|
|
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';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
20
9
|
import os from 'node:os';
|
|
21
10
|
import path from 'node:path';
|
|
22
11
|
import { fileURLToPath } from 'node:url';
|
|
23
|
-
import { spawnSync } from 'node:child_process';
|
|
12
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
24
13
|
import { Command } from 'commander';
|
|
25
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
26
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
27
|
-
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
28
14
|
import { logger } from './core/logger.js';
|
|
29
|
-
import { checkDatabaseHealth, config } from './db/index.js';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
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';
|
|
35
22
|
import { getMemoryStats } from './core/memory/stats.js';
|
|
36
|
-
import { ensureProject } from './core/projects.js';
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import { handleRejectMerge } from './algorithms/handlers/reject-merge.js';
|
|
44
|
-
import { handleReverseMerge } from './algorithms/handlers/reverse-merge.js';
|
|
45
|
-
import { handleGetMergeStats } from './algorithms/handlers/get-stats.js';
|
|
46
|
-
import { pinMemory, unpinMemory } from './core/governance.js';
|
|
47
|
-
import { searchWithQMD, isQMDAvailable } from './core/search/qmd-search.js';
|
|
48
|
-
import { initializeCoreMemory, getCoreMemory, editCoreMemorySection, appendCoreMemorySection, getCoreMemoryStats, } from './core/core-memory.js';
|
|
49
|
-
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';
|
|
50
30
|
import { ensureDataDirectory } from './db/bootstrap.js';
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
|
|
55
|
-
|
|
31
|
+
import { getDataDir } from './config.js';
|
|
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
|
+
}
|
|
56
159
|
// Load plugin manifest for self-verification
|
|
57
160
|
function loadPluginManifest() {
|
|
58
161
|
try {
|
|
59
|
-
const manifestPath = path.join(
|
|
162
|
+
const manifestPath = path.join(getDefaultProjectPath(), 'config', 'plugin-manifest.json');
|
|
60
163
|
if (fs.existsSync(manifestPath)) {
|
|
61
164
|
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
62
165
|
}
|
|
@@ -88,26 +191,258 @@ function verifyManifest(manifest) {
|
|
|
88
191
|
});
|
|
89
192
|
return { ok: errors.length === 0, errors };
|
|
90
193
|
}
|
|
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
|
+
}
|
|
214
|
+
// HELPER FUNCTIONS
|
|
91
215
|
// ============================================================================
|
|
216
|
+
function showHelp() {
|
|
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
|
|
259
|
+
`);
|
|
260
|
+
}
|
|
261
|
+
async function runInteractiveInstaller() {
|
|
262
|
+
const { select } = await import('@clack/prompts');
|
|
263
|
+
const { isCancel } = await import('@clack/prompts');
|
|
264
|
+
const { log } = await import('@clack/prompts');
|
|
265
|
+
const { intro, outro } = await import('@clack/prompts');
|
|
266
|
+
intro(`Squish Memory v${VERSION}`);
|
|
267
|
+
const options = [
|
|
268
|
+
{ value: 'mcp', label: 'Start MCP Server (for AI Assistants: Claude Code, OpenCode, etc.)' },
|
|
269
|
+
{ value: 'web', label: 'Start Web UI Only' },
|
|
270
|
+
{ value: 'health', label: 'Health & Stats' },
|
|
271
|
+
{ value: 'help', label: 'Show Help' },
|
|
272
|
+
{ value: 'exit', label: 'Exit' }
|
|
273
|
+
];
|
|
274
|
+
const selected = await select({
|
|
275
|
+
message: 'What would you like to do?',
|
|
276
|
+
options: options,
|
|
277
|
+
});
|
|
278
|
+
if (isCancel(selected)) {
|
|
279
|
+
outro('Cancelled');
|
|
280
|
+
process.exit(0);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
switch (selected) {
|
|
284
|
+
case 'mcp':
|
|
285
|
+
log.step('Starting MCP server...');
|
|
286
|
+
const { spawn } = await import('child_process');
|
|
287
|
+
spawn('npx', ['squish-mcp'], { stdio: 'inherit', shell: true });
|
|
288
|
+
break;
|
|
289
|
+
case 'web':
|
|
290
|
+
log.step('Starting Web UI...');
|
|
291
|
+
await runWebOnly();
|
|
292
|
+
break;
|
|
293
|
+
case 'health':
|
|
294
|
+
log.step('Checking health...');
|
|
295
|
+
await runCliCommand('health');
|
|
296
|
+
await runCliCommand('stats');
|
|
297
|
+
break;
|
|
298
|
+
case 'help':
|
|
299
|
+
showHelp();
|
|
300
|
+
process.exit(0);
|
|
301
|
+
break;
|
|
302
|
+
case 'exit':
|
|
303
|
+
outro('Goodbye! 👋');
|
|
304
|
+
process.exit(0);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function runCliCommand(command) {
|
|
309
|
+
// Run CLI command programmatically
|
|
310
|
+
const program = new Command();
|
|
311
|
+
program.hook('preAction', async () => {
|
|
312
|
+
await ensureDataDirectory();
|
|
313
|
+
});
|
|
314
|
+
if (command === 'health') {
|
|
315
|
+
const status = await buildHealthStatus();
|
|
316
|
+
console.log(`\n Squish Memory v${VERSION}`);
|
|
317
|
+
console.log(` ====================`);
|
|
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`);
|
|
323
|
+
}
|
|
324
|
+
else if (command === 'stats') {
|
|
325
|
+
const stats = await getMemoryStats(getDefaultProjectPath());
|
|
326
|
+
console.log(JSON.stringify({ ok: true, ...stats }, null, 2));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async function spawnInstallerWizard() {
|
|
330
|
+
const distDir = path.dirname(fileURLToPath(import.meta.url));
|
|
331
|
+
const packageDir = path.dirname(distDir);
|
|
332
|
+
const installScript = path.join(packageDir, 'scripts', 'install-interactive.mjs');
|
|
333
|
+
if (!fs.existsSync(installScript)) {
|
|
334
|
+
console.error('Installer not found at:', installScript);
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
console.log('\nLaunching full installer wizard...\n');
|
|
338
|
+
const result = spawnSync('node', [`"${installScript}"`], {
|
|
339
|
+
stdio: 'inherit',
|
|
340
|
+
shell: true,
|
|
341
|
+
cwd: packageDir
|
|
342
|
+
});
|
|
343
|
+
process.exit(result.status || 0);
|
|
344
|
+
}
|
|
345
|
+
function isDatabaseInitialized() {
|
|
346
|
+
try {
|
|
347
|
+
const dataDir = getDataDir();
|
|
348
|
+
const dbPath = path.join(dataDir, 'squish.db');
|
|
349
|
+
return existsSync(dataDir) && existsSync(dbPath);
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async function runWebOnly() {
|
|
356
|
+
console.log(`[squish] Starting Web UI only...`);
|
|
357
|
+
await ensureDataDirectory();
|
|
358
|
+
await startWebServer();
|
|
359
|
+
}
|
|
92
360
|
// CLI MODE DETECTION
|
|
93
361
|
// ============================================================================
|
|
94
362
|
const args = process.argv.slice(2);
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
363
|
+
const firstArg = args[0];
|
|
364
|
+
// Detect command type
|
|
365
|
+
const isNoArgs = args.length === 0;
|
|
366
|
+
const isRunCommand = firstArg === 'run';
|
|
367
|
+
const isHelpCommand = firstArg === '--help' || firstArg === '-h' || firstArg === 'help';
|
|
368
|
+
if (isNoArgs) {
|
|
369
|
+
// Check if database exists - if not, run installer automatically
|
|
370
|
+
if (!isDatabaseInitialized()) {
|
|
371
|
+
console.log(`[squish] No existing database found. Launching installer wizard...\n`);
|
|
372
|
+
await spawnInstallerWizard();
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
// INTERACTIVE WIZARD (default when no args) ===
|
|
376
|
+
runInteractiveInstaller().catch((e) => {
|
|
377
|
+
console.error('Installer error:', e.message);
|
|
378
|
+
process.exit(1);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else if (isRunCommand) {
|
|
383
|
+
// RUN SUBCOMMAND ===
|
|
384
|
+
const subcommand = args[1];
|
|
385
|
+
if (subcommand === 'mcp') {
|
|
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
|
+
})();
|
|
412
|
+
}
|
|
413
|
+
else if (subcommand === 'web') {
|
|
414
|
+
runWebOnly().catch((e) => {
|
|
415
|
+
logger.error('Web server error', e);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
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
|
|
430
|
+
`);
|
|
431
|
+
process.exit(subcommand ? 1 : 0);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else if (isHelpCommand) {
|
|
435
|
+
// SHOW HELP ===
|
|
436
|
+
showHelp();
|
|
437
|
+
process.exit(0);
|
|
102
438
|
}
|
|
103
439
|
else {
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
440
|
+
// CLI MODE (for agents/OpenClaw) ===
|
|
441
|
+
runCliMode().catch((e) => {
|
|
442
|
+
console.error(JSON.stringify({ error: e.message }, null, 2));
|
|
107
443
|
process.exit(1);
|
|
108
444
|
});
|
|
109
445
|
}
|
|
110
|
-
// ============================================================================
|
|
111
446
|
// CLI MODE (for OpenClaw bash execution)
|
|
112
447
|
// ============================================================================
|
|
113
448
|
async function runCliMode() {
|
|
@@ -120,20 +455,136 @@ async function runCliMode() {
|
|
|
120
455
|
program.hook('preAction', async () => {
|
|
121
456
|
await ensureDataDirectory();
|
|
122
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
|
+
});
|
|
123
519
|
// squish remember "content" --type fact --tags tag1,tag2
|
|
124
520
|
program
|
|
125
521
|
.command('remember <content>')
|
|
126
522
|
.description('Store a memory')
|
|
127
523
|
.option('-t, --type <type>', 'Memory type (observation, fact, decision, context, preference)', 'observation')
|
|
128
524
|
.option('-T, --tags <tags>', 'Comma-separated tags', '')
|
|
129
|
-
.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)
|
|
130
534
|
.action(async (content, options) => {
|
|
131
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';
|
|
132
566
|
const result = await rememberMemory({
|
|
133
567
|
content,
|
|
134
568
|
type: options.type,
|
|
135
569
|
tags: options.tags ? options.tags.split(',').map((t) => t.trim()) : [],
|
|
136
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,
|
|
137
588
|
});
|
|
138
589
|
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
139
590
|
}
|
|
@@ -142,218 +593,559 @@ async function runCliMode() {
|
|
|
142
593
|
process.exit(1);
|
|
143
594
|
}
|
|
144
595
|
});
|
|
145
|
-
// squish search "query" --type fact --limit 10
|
|
596
|
+
// squish search "query" --type fact --limit 10 --since "3 days ago" --place workshop
|
|
146
597
|
program
|
|
147
598
|
.command('search <query>')
|
|
148
599
|
.description('Search memories')
|
|
149
600
|
.option('-t, --type <type>', 'Filter by memory type')
|
|
150
601
|
.option('-l, --limit <number>', 'Max results', '10')
|
|
151
|
-
.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')
|
|
152
608
|
.action(async (query, options) => {
|
|
153
609
|
try {
|
|
154
|
-
|
|
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({
|
|
155
636
|
query,
|
|
156
637
|
type: options.type,
|
|
157
|
-
limit:
|
|
638
|
+
limit: validateLimit(options.limit, 10, 1, 100) * 2,
|
|
158
639
|
project: options.project,
|
|
159
640
|
});
|
|
160
|
-
|
|
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
|
+
}
|
|
161
689
|
}
|
|
162
690
|
catch (error) {
|
|
163
691
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
164
692
|
process.exit(1);
|
|
165
693
|
}
|
|
166
694
|
});
|
|
167
|
-
// squish
|
|
695
|
+
// squish forget <memoryId> -- Delete single or bulk delete memories
|
|
168
696
|
program
|
|
169
|
-
.command('
|
|
170
|
-
.description('
|
|
171
|
-
.
|
|
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) => {
|
|
172
706
|
try {
|
|
173
|
-
|
|
174
|
-
|
|
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));
|
|
175
752
|
}
|
|
176
753
|
catch (error) {
|
|
177
754
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
178
755
|
process.exit(1);
|
|
179
756
|
}
|
|
180
757
|
});
|
|
181
|
-
// squish
|
|
182
|
-
// squish core_memory edit persona --content "I am helpful"
|
|
183
|
-
// squish core_memory append user_info --text "Prefers TypeScript"
|
|
758
|
+
// squish link - Unified graph operations (find related, add links, list associations)
|
|
184
759
|
program
|
|
185
|
-
.command('
|
|
186
|
-
.description('Manage
|
|
187
|
-
.argument('
|
|
188
|
-
.
|
|
189
|
-
.option('-
|
|
190
|
-
.
|
|
191
|
-
.option('-p, --project <project>', 'Project path', process.cwd())
|
|
192
|
-
.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) => {
|
|
193
766
|
try {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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;
|
|
199
780
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
console.log(JSON.stringify({ ok:
|
|
207
|
-
break;
|
|
208
|
-
case 'edit':
|
|
209
|
-
if (!options.section || !options.content) {
|
|
210
|
-
console.log(JSON.stringify({ ok: false, error: '--section and --content required for edit' }, null, 2));
|
|
211
|
-
process.exit(1);
|
|
212
|
-
}
|
|
213
|
-
await initializeCoreMemory(projectId);
|
|
214
|
-
const editResult = await editCoreMemorySection(projectId, options.section, String(options.content));
|
|
215
|
-
console.log(JSON.stringify({ ok: editResult.success, action: 'edit', section: options.section, ...editResult }, null, 2));
|
|
216
|
-
break;
|
|
217
|
-
case 'append':
|
|
218
|
-
if (!options.section || !options.text) {
|
|
219
|
-
console.log(JSON.stringify({ ok: false, error: '--section and --text required for append' }, null, 2));
|
|
220
|
-
process.exit(1);
|
|
221
|
-
}
|
|
222
|
-
await initializeCoreMemory(projectId);
|
|
223
|
-
const appendResult = await appendCoreMemorySection(projectId, options.section, String(options.text));
|
|
224
|
-
console.log(JSON.stringify({ ok: appendResult.success, action: 'append', section: options.section, ...appendResult }, null, 2));
|
|
225
|
-
break;
|
|
226
|
-
default:
|
|
227
|
-
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));
|
|
228
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;
|
|
229
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;
|
|
802
|
+
}
|
|
803
|
+
console.log(JSON.stringify({ ok: false, error: 'Usage: squish link <find|add|list> [args]' }, null, 2));
|
|
230
804
|
}
|
|
231
805
|
catch (error) {
|
|
232
806
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
233
807
|
process.exit(1);
|
|
234
808
|
}
|
|
235
809
|
});
|
|
236
|
-
// squish
|
|
810
|
+
// squish learn <type> <content> - Record learning: success, failure, fix, or insight
|
|
237
811
|
program
|
|
238
|
-
.command('
|
|
239
|
-
.description('
|
|
240
|
-
.option('-
|
|
241
|
-
.
|
|
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) => {
|
|
242
820
|
try {
|
|
243
|
-
const
|
|
244
|
-
if (
|
|
245
|
-
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));
|
|
246
824
|
process.exit(1);
|
|
247
825
|
}
|
|
248
|
-
await
|
|
249
|
-
|
|
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));
|
|
250
836
|
}
|
|
251
837
|
catch (error) {
|
|
252
838
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
253
839
|
process.exit(1);
|
|
254
840
|
}
|
|
255
841
|
});
|
|
256
|
-
// squish pin <memoryId>
|
|
257
842
|
program
|
|
258
|
-
.command('
|
|
259
|
-
.description('
|
|
260
|
-
.
|
|
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) => {
|
|
261
849
|
try {
|
|
262
|
-
|
|
263
|
-
|
|
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));
|
|
264
879
|
}
|
|
265
880
|
catch (error) {
|
|
266
881
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
267
882
|
process.exit(1);
|
|
268
883
|
}
|
|
269
884
|
});
|
|
270
|
-
// squish
|
|
885
|
+
// squish recall <query or memoryId> - Search or get by ID
|
|
271
886
|
program
|
|
272
|
-
.command('
|
|
273
|
-
.description('
|
|
274
|
-
.
|
|
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) => {
|
|
275
897
|
try {
|
|
276
|
-
|
|
277
|
-
|
|
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
|
+
}
|
|
278
972
|
}
|
|
279
973
|
catch (error) {
|
|
280
974
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
281
975
|
process.exit(1);
|
|
282
976
|
}
|
|
283
977
|
});
|
|
284
|
-
// squish
|
|
978
|
+
// squish recent --period <period> - Show recent memories
|
|
285
979
|
program
|
|
286
|
-
.command('
|
|
287
|
-
.description('
|
|
288
|
-
.option('-p, --
|
|
289
|
-
.option('-
|
|
290
|
-
.option('-
|
|
291
|
-
.option('-
|
|
292
|
-
.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())
|
|
293
987
|
.action(async (options) => {
|
|
294
988
|
try {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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));
|
|
303
1021
|
}
|
|
304
1022
|
catch (error) {
|
|
305
1023
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
306
1024
|
process.exit(1);
|
|
307
1025
|
}
|
|
308
1026
|
});
|
|
309
|
-
// squish
|
|
1027
|
+
// squish confidence <memoryId> [level] - Set or view confidence level
|
|
310
1028
|
program
|
|
311
|
-
.command('
|
|
312
|
-
.description('
|
|
313
|
-
.
|
|
314
|
-
.action(async (options) => {
|
|
1029
|
+
.command('confidence <memoryId> [level]')
|
|
1030
|
+
.description('Set or view confidence level (certain/speculative/outdated)')
|
|
1031
|
+
.action(async (memoryId, level) => {
|
|
315
1032
|
try {
|
|
316
|
-
|
|
317
|
-
|
|
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
|
+
}
|
|
318
1050
|
}
|
|
319
1051
|
catch (error) {
|
|
320
1052
|
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
321
1053
|
process.exit(1);
|
|
322
1054
|
}
|
|
323
1055
|
});
|
|
324
|
-
// squish
|
|
1056
|
+
// squish pin <memoryId> [--unpin]
|
|
325
1057
|
program
|
|
326
|
-
.command('
|
|
327
|
-
.description('
|
|
328
|
-
.option('-
|
|
329
|
-
.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) => {
|
|
330
1062
|
try {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const dirExists = fs.existsSync(dataDir);
|
|
335
|
-
const status = {
|
|
336
|
-
version: VERSION,
|
|
337
|
-
mode: config.isTeamMode ? 'team' : 'local',
|
|
338
|
-
database: dbHealth ? 'ok' : 'error',
|
|
339
|
-
cache: redisHealth ? 'ok' : 'unavailable',
|
|
340
|
-
dataDirectory: dataDir,
|
|
341
|
-
dataDirectoryExists: dirExists,
|
|
342
|
-
timestamp: new Date().toISOString()
|
|
343
|
-
};
|
|
344
|
-
if (options.json) {
|
|
345
|
-
console.log(JSON.stringify({ ok: true, ...status }, null, 2));
|
|
1063
|
+
if (options.unpin) {
|
|
1064
|
+
await unpinMemory(String(memoryId));
|
|
1065
|
+
console.log(JSON.stringify({ ok: true, memoryId, pinned: false }, null, 2));
|
|
346
1066
|
}
|
|
347
1067
|
else {
|
|
348
|
-
|
|
349
|
-
console.log(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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));
|
|
1146
|
+
}
|
|
1147
|
+
else {
|
|
1148
|
+
console.log(JSON.stringify({ ok: false, error: 'Use: squish tag add <tag> or squish tag remove <tag>' }, null, 2));
|
|
357
1149
|
process.exit(1);
|
|
358
1150
|
}
|
|
359
1151
|
}
|
|
@@ -362,13 +1154,62 @@ async function runCliMode() {
|
|
|
362
1154
|
process.exit(1);
|
|
363
1155
|
}
|
|
364
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
|
+
});
|
|
365
1192
|
// squish stats
|
|
366
1193
|
program
|
|
367
1194
|
.command('stats')
|
|
368
|
-
.description('
|
|
369
|
-
.option('-p, --project <project>', 'Project path',
|
|
1195
|
+
.description('View statistics')
|
|
1196
|
+
.option('-p, --project <project>', 'Project path', getDefaultProjectPath())
|
|
1197
|
+
.option('-m, --memory', 'Show memory file storage stats instead of database', false)
|
|
370
1198
|
.action(async (options) => {
|
|
371
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
|
|
372
1213
|
const stats = await getMemoryStats(options.project);
|
|
373
1214
|
console.log(JSON.stringify({ ok: true, ...stats }, null, 2));
|
|
374
1215
|
}
|
|
@@ -377,475 +1218,460 @@ async function runCliMode() {
|
|
|
377
1218
|
process.exit(1);
|
|
378
1219
|
}
|
|
379
1220
|
});
|
|
380
|
-
// squish install
|
|
1221
|
+
// squish install
|
|
381
1222
|
program
|
|
382
1223
|
.command('install')
|
|
383
|
-
.description('
|
|
384
|
-
.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
1224
|
+
.description('Run the interactive installer wizard')
|
|
1225
|
+
.action(async () => {
|
|
1226
|
+
await spawnInstallerWizard();
|
|
1227
|
+
});
|
|
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,
|
|
1240
|
+
});
|
|
1241
|
+
console.log(JSON.stringify({ ok: true, message: 'Note saved', id: result.id }, null, 2));
|
|
401
1242
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
args.push('--dry-run');
|
|
406
|
-
if (options.openclawDir)
|
|
407
|
-
args.push('--openclaw-dir', `"${options.openclawDir}"`);
|
|
408
|
-
if (options.skipInstall)
|
|
409
|
-
args.push('--skip-install');
|
|
410
|
-
const result = spawnSync('node', args, {
|
|
411
|
-
stdio: 'inherit',
|
|
412
|
-
shell: true,
|
|
413
|
-
cwd: packageDir
|
|
414
|
-
});
|
|
415
|
-
if (result.status !== 0) {
|
|
416
|
-
process.exit(result.status || 1);
|
|
1243
|
+
catch (error) {
|
|
1244
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
1245
|
+
process.exit(1);
|
|
417
1246
|
}
|
|
418
1247
|
});
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
memoryId: { type: 'string' },
|
|
452
|
-
},
|
|
453
|
-
required: ['action', 'sessionId']
|
|
454
|
-
}
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
name: 'context_status',
|
|
458
|
-
description: 'View comprehensive context window status and token usage',
|
|
459
|
-
inputSchema: {
|
|
460
|
-
type: 'object',
|
|
461
|
-
properties: {
|
|
462
|
-
sessionId: { type: 'string' },
|
|
463
|
-
projectId: { type: 'string' },
|
|
464
|
-
},
|
|
465
|
-
required: ['sessionId', 'projectId']
|
|
466
|
-
}
|
|
467
|
-
},
|
|
468
|
-
// Memory Tools
|
|
469
|
-
{
|
|
470
|
-
name: 'remember',
|
|
471
|
-
description: 'Store information for future use. Perfect for facts, decisions, code snippets, configuration details, or user preferences.',
|
|
472
|
-
inputSchema: {
|
|
473
|
-
type: 'object',
|
|
474
|
-
properties: {
|
|
475
|
-
content: { type: 'string' },
|
|
476
|
-
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
|
|
477
|
-
tags: { type: 'array', items: { type: 'string' } },
|
|
478
|
-
project: { type: 'string' },
|
|
479
|
-
metadata: { type: 'object' },
|
|
480
|
-
},
|
|
481
|
-
required: ['content']
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
|
-
{
|
|
485
|
-
name: 'recall',
|
|
486
|
-
description: 'Retrieve a specific stored memory by ID',
|
|
487
|
-
inputSchema: {
|
|
488
|
-
type: 'object',
|
|
489
|
-
properties: { id: { type: 'string' } },
|
|
490
|
-
required: ['id']
|
|
491
|
-
}
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
name: 'search',
|
|
495
|
-
description: 'Search your stored memories. Leave query empty to list recent memories.',
|
|
496
|
-
inputSchema: {
|
|
497
|
-
type: 'object',
|
|
498
|
-
properties: {
|
|
499
|
-
query: { type: 'string' },
|
|
500
|
-
scope: { type: 'string', enum: ['memories', 'conversations', 'recent'], default: 'memories' },
|
|
501
|
-
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
|
|
502
|
-
tags: { type: 'array', items: { type: 'string' } },
|
|
503
|
-
limit: { type: 'number', default: 10 },
|
|
504
|
-
project: { type: 'string' },
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
},
|
|
508
|
-
{
|
|
509
|
-
name: 'observe',
|
|
510
|
-
description: 'Record an observation about your work (tool usage, patterns, errors)',
|
|
511
|
-
inputSchema: {
|
|
512
|
-
type: 'object',
|
|
513
|
-
properties: {
|
|
514
|
-
type: { type: 'string', enum: ['tool_use', 'file_change', 'error', 'pattern', 'insight'] },
|
|
515
|
-
action: { type: 'string' },
|
|
516
|
-
target: { type: 'string' },
|
|
517
|
-
summary: { type: 'string' },
|
|
518
|
-
details: { type: 'object' },
|
|
519
|
-
},
|
|
520
|
-
required: ['type', 'action', 'summary']
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
{
|
|
524
|
-
name: 'context',
|
|
525
|
-
description: 'Get project context',
|
|
526
|
-
inputSchema: {
|
|
527
|
-
type: 'object',
|
|
528
|
-
properties: {
|
|
529
|
-
project: { type: 'string' },
|
|
530
|
-
include: { type: 'array', items: { type: 'string' }, default: ['memories', 'observations'] },
|
|
531
|
-
limit: { type: 'number', default: 10 }
|
|
532
|
-
},
|
|
533
|
-
required: ['project']
|
|
534
|
-
}
|
|
535
|
-
},
|
|
536
|
-
{
|
|
537
|
-
name: 'init',
|
|
538
|
-
description: 'Initialize Squish memory system for the current project',
|
|
539
|
-
inputSchema: {
|
|
540
|
-
type: 'object',
|
|
541
|
-
properties: { projectPath: { type: 'string' } }
|
|
542
|
-
}
|
|
543
|
-
},
|
|
544
|
-
{
|
|
545
|
-
name: 'health',
|
|
546
|
-
description: 'Check service status',
|
|
547
|
-
inputSchema: { type: 'object', properties: {} }
|
|
548
|
-
},
|
|
549
|
-
{
|
|
550
|
-
name: 'merge',
|
|
551
|
-
description: 'Manage memory merges: detect, list, preview, approve, reject, reverse',
|
|
552
|
-
inputSchema: {
|
|
553
|
-
type: 'object',
|
|
554
|
-
properties: {
|
|
555
|
-
action: { type: 'string', enum: ['detect', 'list', 'preview', 'stats', 'approve', 'reject', 'reverse'] },
|
|
556
|
-
projectId: { type: 'string' },
|
|
557
|
-
proposalId: { type: 'string' },
|
|
558
|
-
threshold: { type: 'number' },
|
|
559
|
-
},
|
|
560
|
-
required: ['action']
|
|
561
|
-
}
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
name: 'qmd_search',
|
|
565
|
-
description: 'Search memories using QMD hybrid search (BM25 + vector + rerank)',
|
|
566
|
-
inputSchema: {
|
|
567
|
-
type: 'object',
|
|
568
|
-
properties: {
|
|
569
|
-
query: { type: 'string' },
|
|
570
|
-
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
|
|
571
|
-
limit: { type: 'number', default: 10 },
|
|
572
|
-
},
|
|
573
|
-
required: ['query']
|
|
574
|
-
}
|
|
575
|
-
},
|
|
576
|
-
// v0.8.0: Importance Scoring Tools
|
|
577
|
-
{
|
|
578
|
-
name: 'set_importance',
|
|
579
|
-
description: 'Manually set importance score for a memory (0-100)',
|
|
580
|
-
inputSchema: {
|
|
581
|
-
type: 'object',
|
|
582
|
-
properties: {
|
|
583
|
-
memoryId: { type: 'string' },
|
|
584
|
-
importance: { type: 'number', minimum: 0, maximum: 100 },
|
|
585
|
-
},
|
|
586
|
-
required: ['memoryId', 'importance']
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
{
|
|
590
|
-
name: 'pin_memory',
|
|
591
|
-
description: 'Pin a memory to prevent pruning/consolidation (or unpin it)',
|
|
592
|
-
inputSchema: {
|
|
593
|
-
type: 'object',
|
|
594
|
-
properties: {
|
|
595
|
-
memoryId: { type: 'string' },
|
|
596
|
-
pinned: { type: 'boolean', default: true },
|
|
597
|
-
},
|
|
598
|
-
required: ['memoryId']
|
|
599
|
-
}
|
|
600
|
-
},
|
|
601
|
-
// v0.8.0: Consolidation Tool
|
|
602
|
-
{
|
|
603
|
-
name: 'consolidate',
|
|
604
|
-
description: 'Trigger manual memory consolidation - summarizes old, low-importance memories',
|
|
605
|
-
inputSchema: {
|
|
606
|
-
type: 'object',
|
|
607
|
-
properties: {
|
|
608
|
-
projectId: { type: 'string' },
|
|
609
|
-
threshold: { type: 'number', default: 0.7 },
|
|
610
|
-
minAge: { type: 'number', default: 90 },
|
|
611
|
-
limit: { type: 'number', default: 100 },
|
|
612
|
-
},
|
|
613
|
-
required: ['projectId']
|
|
614
|
-
}
|
|
615
|
-
},
|
|
616
|
-
{
|
|
617
|
-
name: 'consolidation_stats',
|
|
618
|
-
description: 'Get consolidation statistics for a project',
|
|
619
|
-
inputSchema: {
|
|
620
|
-
type: 'object',
|
|
621
|
-
properties: {
|
|
622
|
-
projectId: { type: 'string' },
|
|
623
|
-
},
|
|
624
|
-
required: ['projectId']
|
|
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}`);
|
|
625
1280
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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));
|
|
1285
|
+
}
|
|
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
|
+
}
|
|
641
1296
|
return;
|
|
642
1297
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
649
|
-
|
|
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);
|
|
650
1305
|
}
|
|
651
|
-
|
|
652
|
-
|
|
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
|
+
}));
|
|
653
1321
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
await
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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,
|
|
708
1376
|
});
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
else {
|
|
716
|
-
await unpinMemory(String(args.memoryId));
|
|
717
|
-
}
|
|
718
|
-
return this.jsonResponse({
|
|
719
|
-
ok: true,
|
|
720
|
-
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)) || [],
|
|
721
1383
|
});
|
|
722
1384
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
+
}
|
|
737
1417
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
return this.jsonResponse({ ok: true, ...stats });
|
|
1418
|
+
else {
|
|
1419
|
+
console.log(` ${p.name} (${p.memories} memories) - ${p.purpose}`);
|
|
741
1420
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}
|
|
1421
|
+
});
|
|
1422
|
+
console.log('');
|
|
745
1423
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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('');
|
|
750
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
|
|
751
1471
|
});
|
|
752
|
-
|
|
753
|
-
process.on('SIGINT', () => this.shutdown());
|
|
754
|
-
process.on('SIGTERM', () => this.shutdown());
|
|
1472
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
755
1473
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
};
|
|
1474
|
+
catch (error) {
|
|
1475
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
1476
|
+
process.exit(1);
|
|
760
1477
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
|
778
1506
|
},
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
async handleContextPaging(args) {
|
|
786
|
-
const action = args.action;
|
|
787
|
-
const sessionId = String(args.sessionId);
|
|
788
|
-
const actions = {
|
|
789
|
-
load: () => loadMemoryToContext(sessionId, String(args.memoryId)),
|
|
790
|
-
evict: () => evictMemoryFromContext(sessionId, String(args.memoryId)),
|
|
791
|
-
view: () => viewLoadedMemories(sessionId),
|
|
792
|
-
};
|
|
793
|
-
const handler = actions[action];
|
|
794
|
-
if (!handler)
|
|
795
|
-
throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
|
|
796
|
-
return this.jsonResponse(await handler());
|
|
797
|
-
}
|
|
798
|
-
async handleMerge(args) {
|
|
799
|
-
const action = args.action;
|
|
800
|
-
const handlers = {
|
|
801
|
-
detect: () => handleDetectDuplicates(args),
|
|
802
|
-
list: () => handleListProposals(args),
|
|
803
|
-
preview: () => handlePreviewMerge(args),
|
|
804
|
-
stats: () => handleGetMergeStats(args),
|
|
805
|
-
approve: () => handleApproveMerge(args),
|
|
806
|
-
reject: () => handleRejectMerge(args),
|
|
807
|
-
reverse: () => handleReverseMerge(args),
|
|
808
|
-
};
|
|
809
|
-
const handler = handlers[action];
|
|
810
|
-
if (!handler)
|
|
811
|
-
throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
|
|
812
|
-
return this.jsonResponse(await handler());
|
|
1507
|
+
consolidate: {
|
|
1508
|
+
clustered: consolidateResult.clustered,
|
|
1509
|
+
merged: consolidateResult.merged,
|
|
1510
|
+
consolidated: consolidateResult.consolidated
|
|
1511
|
+
}
|
|
1512
|
+
}, null, 2));
|
|
813
1513
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
process.exit(0);
|
|
1514
|
+
catch (error) {
|
|
1515
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
817
1516
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
catch (error) {
|
|
1581
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
1582
|
+
process.exit(1);
|
|
826
1583
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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
|
+
}
|
|
833
1665
|
}
|
|
834
|
-
else {
|
|
835
|
-
logger.info(`Squish v${VERSION} - Plugin manifest verified`);
|
|
836
|
-
}
|
|
837
|
-
const transport = new StdioServerTransport();
|
|
838
|
-
await this.server.connect(transport);
|
|
839
|
-
logger.info(`v${VERSION}`);
|
|
840
|
-
registerJobHandler('nightly_maintenance', runNightlyJob);
|
|
841
|
-
registerJobHandler('weekly_maintenance', runWeeklyJob);
|
|
842
|
-
await initializeScheduler();
|
|
843
|
-
startHeartbeatChecking();
|
|
844
|
-
await this.onSessionInitialized();
|
|
845
|
-
await heartbeat();
|
|
846
|
-
startWebServer();
|
|
847
1666
|
}
|
|
848
|
-
|
|
849
|
-
|
|
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);
|
|
850
1673
|
}
|
|
1674
|
+
// MCP server: core/commands/mcp-server.ts
|
|
1675
|
+
// Run with: npx squish-mcp
|
|
1676
|
+
// ============================================================================
|
|
851
1677
|
//# sourceMappingURL=index.js.map
|