claude-memory-layer 1.0.27 → 1.0.29
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 +7 -0
- package/AGENTS.md +11 -0
- package/README.md +374 -49
- package/benchmarks/replay/anonymized-real-sessions.json +48 -0
- package/dist/cli/index.js +10097 -6003
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +9745 -5587
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +6545 -5270
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/semantic-daemon.js +6646 -5354
- package/dist/hooks/semantic-daemon.js.map +4 -4
- package/dist/hooks/session-end.js +6618 -5347
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +6619 -5354
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +6614 -5325
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +6702 -5356
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/index.js +13537 -0
- package/dist/index.js.map +7 -0
- package/dist/mcp/index.js +20770 -0
- package/dist/mcp/index.js.map +7 -0
- package/dist/server/api/index.js +6632 -5319
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +6667 -5340
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +6568 -5350
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/assets/js/bootstrap.js +244 -0
- package/dist/ui/assets/js/chat.js +373 -0
- package/dist/ui/assets/js/disclosure.js +232 -0
- package/dist/ui/assets/js/modals.js +298 -0
- package/dist/ui/assets/js/overview.js +655 -0
- package/dist/ui/assets/js/state.js +72 -0
- package/dist/ui/assets/js/views.js +468 -0
- package/dist/ui/index.html +43 -1
- package/dist/ui/index.ts +3 -0
- package/dist/ui/style.css +222 -0
- package/docs/ARCHITECTURE_COMPARISON_AND_RECOMMENDATIONS.md +627 -0
- package/docs/HERMES_MEMORY_INGESTION_ANALYSIS.md +440 -0
- package/docs/MEMORY_USEFULNESS_AUDIT.md +371 -0
- package/docs/MEMORY_USEFULNESS_AUDIT_RAW.json +80 -0
- package/docs/MEMSEARCH_PROJECT_STRUCTURE_ANALYSIS.md +333 -0
- package/docs/PRODUCT_VALIDATION_MATRIX.md +82 -0
- package/docs/PROJECT_STRUCTURE_ANALYSIS.md +421 -0
- package/docs/REFACTORING_MILESTONES_AND_ISSUES.md +501 -0
- package/docs/REFACTORING_PLAN_THIN_CORE.md +414 -0
- package/docs/REFERENCE_PROJECT_ANALYSES.md +25 -0
- package/docs/SUPERLOCALMEMORY_PROJECT_STRUCTURE_ANALYSIS.md +452 -0
- package/docs/TARGET_ARCHITECTURE_AND_FOLDER_STRUCTURE.md +446 -0
- package/docs/architecture/comparison-index.md +47 -0
- package/docs/reports/codex-real-data-validation-20260505T040447Z.md +46 -0
- package/package.json +12 -5
- package/scripts/build.ts +25 -8
- package/scripts/generate-session-qrels.ts +126 -0
- package/scripts/postinstall-embedding-backend.cjs +142 -0
- package/scripts/replay-retrieval-benchmark.ts +69 -0
- package/specs/thin-core-refactor/context.md +275 -0
- package/specs/thin-core-refactor/plan.md +536 -0
- package/specs/thin-core-refactor/spec.md +465 -0
- package/src/adapters/claude/capture/index.ts +3 -0
- package/src/adapters/claude/context/index.ts +3 -0
- package/src/adapters/claude/hooks/index.ts +21 -0
- package/src/adapters/claude/hooks/post-tool-use.ts +239 -0
- package/src/adapters/claude/hooks/prompt-injection-policy.ts +104 -0
- package/src/adapters/claude/hooks/semantic-daemon-client.ts +209 -0
- package/src/adapters/claude/hooks/semantic-daemon.ts +283 -0
- package/src/adapters/claude/hooks/session-end.ts +59 -0
- package/src/adapters/claude/hooks/session-start.ts +73 -0
- package/src/adapters/claude/hooks/stop.ts +128 -0
- package/src/adapters/claude/hooks/user-prompt-submit.ts +361 -0
- package/src/adapters/claude/index.ts +4 -0
- package/src/adapters/claude/transcript/index.ts +4 -0
- package/src/adapters/claude/transcript/transcript-reader.ts +57 -0
- package/src/adapters/claude/transcript/turn-reconstructor.ts +65 -0
- package/src/apps/cli/claude-settings-hooks.ts +138 -0
- package/src/apps/cli/codex-import-runner.ts +125 -0
- package/src/apps/cli/codex-validation-output.ts +95 -0
- package/src/apps/cli/hermes-import-runner.ts +130 -0
- package/src/apps/cli/hermes-validation-output.ts +91 -0
- package/src/apps/cli/index.ts +1731 -0
- package/src/apps/cli/mcp-install.ts +106 -0
- package/src/apps/cli/retrieval-disclosure-output.ts +196 -0
- package/src/apps/dashboard/assets/js/bootstrap.js +244 -0
- package/src/apps/dashboard/assets/js/chat.js +373 -0
- package/src/apps/dashboard/assets/js/disclosure.js +232 -0
- package/src/apps/dashboard/assets/js/modals.js +298 -0
- package/src/apps/dashboard/assets/js/overview.js +655 -0
- package/src/apps/dashboard/assets/js/state.js +72 -0
- package/src/apps/dashboard/assets/js/views.js +468 -0
- package/src/{ui → apps/dashboard}/index.html +43 -1
- package/src/apps/dashboard/index.ts +3 -0
- package/src/{ui → apps/dashboard}/style.css +222 -0
- package/src/apps/index.ts +5 -0
- package/src/apps/server/api/chat.ts +244 -0
- package/src/apps/server/api/citations.ts +105 -0
- package/src/apps/server/api/events.ts +137 -0
- package/src/apps/server/api/health.ts +53 -0
- package/src/apps/server/api/index.ts +26 -0
- package/src/apps/server/api/projects.ts +74 -0
- package/src/apps/server/api/search.ts +184 -0
- package/src/apps/server/api/sessions.ts +115 -0
- package/src/apps/server/api/stats.ts +723 -0
- package/src/apps/server/api/turns.ts +143 -0
- package/src/apps/server/api/utils.ts +65 -0
- package/src/apps/server/index.ts +111 -0
- package/src/cli/index.ts +2 -1311
- package/src/cli/retrieval-disclosure-output.ts +2 -0
- package/src/compat/index.ts +5 -0
- package/src/core/derive/fact-deriver.ts +170 -0
- package/src/core/derive/index.ts +2 -0
- package/src/core/derive/summary-deriver.ts +76 -0
- package/src/core/embedder.ts +4 -152
- package/src/core/engine/embedding-maintenance-service.ts +187 -0
- package/src/core/engine/endless-memory-services.ts +4 -0
- package/src/core/engine/index.ts +19 -0
- package/src/core/engine/memory-engine-services.ts +170 -0
- package/src/core/engine/memory-ingest-service.ts +317 -0
- package/src/core/engine/memory-query-service.ts +173 -0
- package/src/core/engine/memory-runtime-service.ts +162 -0
- package/src/core/engine/memory-service-composition.ts +231 -0
- package/src/core/engine/retrieval-analytics-service.ts +181 -0
- package/src/core/engine/retrieval-disclosure-service.ts +420 -0
- package/src/core/engine/retrieval-orchestrator.ts +377 -0
- package/src/core/engine/retrieval-services.ts +176 -0
- package/src/core/engine/shared-memory-services.ts +4 -0
- package/src/core/entity-repo.ts +1 -3
- package/src/core/event-store.ts +3 -3
- package/src/core/evidence-aligner.ts +2 -2
- package/src/core/external-market-context.ts +582 -0
- package/src/core/graduation.ts +2 -3
- package/src/core/index.ts +21 -0
- package/src/core/matcher.ts +2 -4
- package/src/core/model/memory-fact.ts +30 -0
- package/src/core/model/memory-rule.ts +14 -0
- package/src/core/model/memory-summary.ts +21 -0
- package/src/core/model/raw-event.ts +28 -0
- package/src/core/model/retrieval-result.ts +35 -0
- package/src/core/privacy/filter.ts +21 -10
- package/src/core/product-validation-matrix.ts +314 -0
- package/src/core/progressive-retriever.ts +1 -2
- package/src/core/registry/project-path.ts +54 -0
- package/src/core/registry/session-registry.ts +69 -0
- package/src/core/replay-evaluator.ts +625 -0
- package/src/core/retrieval-benchmark.ts +117 -0
- package/src/core/retrieval-quality.ts +109 -0
- package/src/core/retriever.ts +53 -15
- package/src/core/session-qrels.ts +360 -0
- package/src/core/shared-event-store.ts +1 -1
- package/src/core/sqlite-event-store.ts +35 -11
- package/src/core/task/blocker-resolver.ts +2 -2
- package/src/core/task/task-resolver.ts +0 -1
- package/src/core/vector-outbox.ts +1 -10
- package/src/core/vector-worker.ts +1 -1
- package/src/extensions/endless-memory/endless-memory-services.ts +350 -0
- package/src/extensions/endless-memory/index.ts +1 -0
- package/src/extensions/index.ts +5 -0
- package/src/extensions/mcp/handlers.ts +960 -0
- package/src/extensions/mcp/index.ts +48 -0
- package/src/extensions/mcp/tools.ts +252 -0
- package/src/extensions/shared-memory/index.ts +1 -0
- package/src/extensions/shared-memory/shared-memory-services.ts +211 -0
- package/src/extensions/vector/embedder.ts +197 -0
- package/src/extensions/vector/index.ts +1 -0
- package/src/hooks/post-tool-use.ts +3 -236
- package/src/hooks/semantic-daemon-client.ts +1 -208
- package/src/hooks/semantic-daemon.ts +6 -271
- package/src/hooks/session-end.ts +4 -79
- package/src/hooks/session-start.ts +4 -73
- package/src/hooks/stop.ts +3 -173
- package/src/hooks/user-prompt-submit.ts +3 -338
- package/src/index.ts +13 -0
- package/src/mcp/handlers.ts +2 -212
- package/src/mcp/index.ts +3 -46
- package/src/mcp/tools.ts +2 -78
- package/src/server/api/chat.ts +2 -244
- package/src/server/api/citations.ts +2 -105
- package/src/server/api/events.ts +2 -137
- package/src/server/api/health.ts +2 -53
- package/src/server/api/index.ts +2 -26
- package/src/server/api/projects.ts +2 -74
- package/src/server/api/search.ts +2 -102
- package/src/server/api/sessions.ts +2 -115
- package/src/server/api/stats.ts +2 -724
- package/src/server/api/turns.ts +2 -143
- package/src/server/api/utils.ts +2 -46
- package/src/server/index.ts +2 -100
- package/src/services/bootstrap-organizer.ts +46 -26
- package/src/services/codex-session-history-importer.ts +521 -29
- package/src/services/hermes-session-history-importer.ts +733 -0
- package/src/services/memory-service-config.ts +36 -0
- package/src/services/memory-service-registry.ts +150 -0
- package/src/services/memory-service.ts +211 -1325
- package/src/services/session-history-importer.ts +58 -14
- package/tests/README.md +23 -0
- package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +54 -0
- package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +98 -0
- package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +99 -0
- package/tests/apps/app-layer-boundary.test.ts +48 -0
- package/tests/apps/claude-settings-hooks.test.ts +107 -0
- package/tests/apps/cli-disclosure-output.test.ts +212 -0
- package/tests/apps/codex-import-runner.test.ts +99 -0
- package/tests/apps/codex-validation-output.test.ts +100 -0
- package/tests/apps/hermes-import-runner.test.ts +99 -0
- package/tests/apps/mcp-install-command.test.ts +59 -0
- package/tests/apps/package-build-entrypoints.test.ts +30 -0
- package/tests/apps/postinstall-embedding-backend.test.ts +167 -0
- package/tests/apps/search-api-disclosure.test.ts +162 -0
- package/tests/apps/stats-api-lightweight.test.ts +67 -0
- package/tests/apps/ui-disclosure-output.test.ts +140 -0
- package/tests/{bootstrap-organizer.test.ts → core/bootstrap-organizer.test.ts} +1 -1
- package/tests/{canonical-key.test.ts → core/canonical-key.test.ts} +1 -1
- package/tests/core/codex-session-history-importer-validation.test.ts +185 -0
- package/tests/{consolidation-worker.test.ts → core/consolidation-worker.test.ts} +2 -2
- package/tests/core/embedding-maintenance-service.test.ts +282 -0
- package/tests/{evidence-aligner.test.ts → core/evidence-aligner.test.ts} +1 -1
- package/tests/core/external-market-context.test.ts +209 -0
- package/tests/core/fact-deriver.test.ts +79 -0
- package/tests/core/hermes-session-history-importer-validation.test.ts +609 -0
- package/tests/{ingest-interceptor.test.ts → core/ingest-interceptor.test.ts} +1 -1
- package/tests/{markdown-mirror.test.ts → core/markdown-mirror.test.ts} +2 -2
- package/tests/{matcher.test.ts → core/matcher.test.ts} +1 -1
- package/tests/{md-mirror.test.ts → core/md-mirror.test.ts} +2 -2
- package/tests/core/memory-engine-services.test.ts +240 -0
- package/tests/core/memory-ingest-service.test.ts +296 -0
- package/tests/core/memory-query-service.test.ts +129 -0
- package/tests/core/memory-runtime-service.test.ts +201 -0
- package/tests/core/memory-service-composition.test.ts +192 -0
- package/tests/core/memory-service-config.test.ts +41 -0
- package/tests/core/memory-service-facade.test.ts +30 -0
- package/tests/core/memory-service-registry.test.ts +206 -0
- package/tests/core/product-validation-matrix.test.ts +61 -0
- package/tests/core/project-registry.test.ts +78 -0
- package/tests/core/replay-evaluator.test.ts +181 -0
- package/tests/core/retrieval-analytics-service.test.ts +210 -0
- package/tests/core/retrieval-benchmark.test.ts +93 -0
- package/tests/core/retrieval-disclosure-service.test.ts +264 -0
- package/tests/core/retrieval-orchestrator.test.ts +403 -0
- package/tests/core/retrieval-quality.test.ts +31 -0
- package/tests/core/retrieval-services.test.ts +185 -0
- package/tests/{retriever-fallback-chain.test.ts → core/retriever-fallback-chain.test.ts} +3 -3
- package/tests/{retriever-strategy-scope.test.ts → core/retriever-strategy-scope.test.ts} +70 -3
- package/tests/{retriever.memu-adoption.test.ts → core/retriever.memu-adoption.test.ts} +3 -3
- package/tests/core/session-history-importer-filter.test.ts +78 -0
- package/tests/core/session-qrels.test.ts +250 -0
- package/tests/{sqlite-event-store-replication.test.ts → core/sqlite-event-store-replication.test.ts} +36 -1
- package/tests/core/summary-deriver.test.ts +66 -0
- package/tests/extensions/embedder-warning-suppression.test.ts +53 -0
- package/tests/extensions/endless-memory-extension-boundary.test.ts +17 -0
- package/tests/extensions/endless-memory-services.test.ts +325 -0
- package/tests/extensions/mcp-context-tools.test.ts +905 -0
- package/tests/extensions/mcp-extension-boundary.test.ts +21 -0
- package/tests/extensions/mcp-package-build.test.ts +22 -0
- package/tests/extensions/mcp-project-aware-tools.test.ts +102 -0
- package/tests/extensions/shared-memory-extension-boundary.test.ts +24 -0
- package/tests/extensions/shared-memory-services.test.ts +309 -0
- package/tests/extensions/vector-extension-boundary.test.ts +21 -0
- package/.claude/settings.local.json +0 -25
- package/.npm-cache/_cacache/content-v2/sha512/04/76/c098f88dfe584a2b80870bff7421b05d17d3d9ee1027f77772332a22d3f93a9a57101a2855107f6ad82077a818bba912b2bc317f2361b5ddb09ad284d9ce +0 -0
- package/.npm-cache/_cacache/content-v2/sha512/60/25/d2ecd39cfc7cab58351162814be77f935c6d6491c10c3745d456da7ddb2117ffd90c10e53fe3c0f1ed16b403307841543634504398b16ee4e6b6dd8e0c45 +0 -0
- package/.npm-cache/_cacache/index-v5/2b/9a/7f8f40206ed8a2e0a84efaa953ccaed1f5d001e14b931083f2e7a0738007 +0 -2
- package/.npm-cache/_cacache/index-v5/2e/d9/fcfa5c6a6abdc2a3644ab84a95936047298c465a2f47ee03db8f7fe1e946 +0 -3
- package/.npm-cache/_cacache/index-v5/a9/42/e519633356d12d3d2f19da66a8301016d496c8f5c3e0554124aaa62dc043 +0 -2
- package/.npm-cache/_logs/2026-02-26T12_04_52_729Z-debug-0.log +0 -256
- package/.npm-cache/_logs/2026-02-26T12_05_36_835Z-debug-0.log +0 -18
- package/.npm-cache/_logs/2026-02-26T12_05_45_982Z-debug-0.log +0 -32
- package/.npm-cache/_logs/2026-02-26T12_05_48_515Z-debug-0.log +0 -260
- package/.npm-cache/_logs/2026-02-26T12_05_53_567Z-debug-0.log +0 -69
- package/.npm-cache/_update-notifier-last-checked +0 -0
- package/bootstrap-kb/decisions/decisions.md +0 -244
- package/bootstrap-kb/glossary/glossary.md +0 -46
- package/bootstrap-kb/modules/.claude-plugin.md +0 -22
- package/bootstrap-kb/modules/agents.md.md +0 -15
- package/bootstrap-kb/modules/claude.md.md +0 -15
- package/bootstrap-kb/modules/context.md.md +0 -15
- package/bootstrap-kb/modules/docs.md +0 -18
- package/bootstrap-kb/modules/handoff.md.md +0 -15
- package/bootstrap-kb/modules/package-lock.json.md +0 -15
- package/bootstrap-kb/modules/package.json.md +0 -15
- package/bootstrap-kb/modules/plan.md.md +0 -15
- package/bootstrap-kb/modules/readme.md.md +0 -15
- package/bootstrap-kb/modules/scripts.md +0 -26
- package/bootstrap-kb/modules/spec.md.md +0 -15
- package/bootstrap-kb/modules/specs.md +0 -20
- package/bootstrap-kb/modules/src.md +0 -51
- package/bootstrap-kb/modules/tests.md +0 -42
- package/bootstrap-kb/modules/tsconfig.json.md +0 -15
- package/bootstrap-kb/modules/vitest.config.ts.md +0 -15
- package/bootstrap-kb/overview/overview.md +0 -40
- package/bootstrap-kb/sources/manifest.json +0 -950
- package/bootstrap-kb/sources/manifest.md +0 -227
- package/bootstrap-kb/timeline/timeline.md +0 -57
- package/claude-memory-layer-1.0.14.tgz +0 -0
- package/d.sh +0 -3
- package/deploy.sh +0 -3
- package/dist/ui/app.js +0 -2101
- package/memory/.claude-plugin/commands/2026-02-25.md +0 -263
- package/memory/_index.md +0 -419
- package/memory/agent_response/uncategorized/2026-02-26.md +0 -176
- package/memory/agent_response/uncategorized/2026-03-03.md +0 -14
- package/memory/agent_response/uncategorized/2026-03-04.md +0 -1421
- package/memory/agent_response/uncategorized/2026-03-05.md +0 -157
- package/memory/default/uncategorized/2026-02-25.md +0 -4839
- package/memory/session_summary/uncategorized/2026-02-26.md +0 -13
- package/memory/session_summary/uncategorized/2026-03-03.md +0 -5
- package/memory/session_summary/uncategorized/2026-03-04.md +0 -50
- package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +0 -142
- package/memory/specs/citations-system/2026-02-25.md +0 -1121
- package/memory/specs/endless-mode/2026-02-25.md +0 -1392
- package/memory/specs/entity-edge-model/2026-02-25.md +0 -1263
- package/memory/specs/evidence-aligner-v2/2026-02-25.md +0 -1028
- package/memory/specs/mcp-desktop-integration/2026-02-25.md +0 -1334
- package/memory/specs/post-tool-use-hook/2026-02-25.md +0 -1164
- package/memory/specs/private-tags/2026-02-25.md +0 -1057
- package/memory/specs/progressive-disclosure/2026-02-25.md +0 -1436
- package/memory/specs/task-entity-system/2026-02-25.md +0 -924
- package/memory/specs/vector-outbox-v2/2026-02-25.md +0 -1510
- package/memory/specs/web-viewer-ui/2026-02-25.md +0 -1709
- package/memory/tool_observation/uncategorized/2026-02-26.md +0 -209
- package/memory/tool_observation/uncategorized/2026-03-03.md +0 -21
- package/memory/tool_observation/uncategorized/2026-03-04.md +0 -1033
- package/memory/tool_observation/uncategorized/2026-03-05.md +0 -33
- package/memory/user_prompt/uncategorized/2026-02-26.md +0 -25
- package/memory/user_prompt/uncategorized/2026-03-04.md +0 -634
- package/memory/user_prompt/uncategorized/2026-03-05.md +0 -6
- package/specs/optional-duckdb/context.md +0 -77
- package/specs/optional-duckdb/plan.md +0 -142
- package/specs/optional-duckdb/spec.md +0 -35
- package/src/ui/app.js +0 -2101
|
@@ -1,1436 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
## 2026-02-25T12:31:26.398Z | 1e97bc37-cfdb-41d2-97fe-8adce20db52d
|
|
3
|
-
- type: session_summary
|
|
4
|
-
- session: import:organized
|
|
5
|
-
# Progressive Disclosure Context
|
|
6
|
-
|
|
7
|
-
> **Version**: 1.0.0
|
|
8
|
-
> **Created**: 2026-02-01
|
|
9
|
-
|
|
10
|
-
## 1. 배경
|
|
11
|
-
|
|
12
|
-
### 1.1 claude-mem의 접근 방식
|
|
13
|
-
|
|
14
|
-
claude-mem은 토큰 효율성을 위해 3-Layer Progressive Disclosure 패턴을 사용:
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
Layer 1: Search Index (~50-100 tokens per result)
|
|
18
|
-
↓ (필터링)
|
|
19
|
-
Layer 2: Timeline (~200 tokens)
|
|
20
|
-
↓ (선택)
|
|
21
|
-
Layer 3: Full Details (~500-1000 tokens per result)
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**주요 특징**:
|
|
25
|
-
- "필터링 후 상세 조회" 전략
|
|
26
|
-
- 약 10배 토큰 절약
|
|
27
|
-
- 사용자/AI가 필요한 것만 확장
|
|
28
|
-
|
|
29
|
-
**구현 방식**:
|
|
30
|
-
- MCP 도구로 각 레이어 노출
|
|
31
|
-
- `search` → `timeline` → `get_observations` 순서
|
|
32
|
-
- `__IMPORTANT` 도구로 워크플로우 문서화
|
|
33
|
-
|
|
34
|
-
### 1.2 현재 code-memory의 상황
|
|
35
|
-
|
|
36
|
-
현재 검색은 단일 레이어:
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
// 현재 Retriever.search()
|
|
40
|
-
async search(query: string): Promise<SearchResult[]> {
|
|
41
|
-
const vectorResults = await this.vectorStore.search(query, { topK: 5 });
|
|
42
|
-
const events = await this.enrichWithEvents(vectorResults);
|
|
43
|
-
return events; // 전체 내용 반환
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**문제점**:
|
|
48
|
-
1. 모든 결과의 전체 내용을 가져옴
|
|
49
|
-
2. 컨텍스트 크기가 토큰 제한에 쉽게 도달
|
|
50
|
-
3. 관련성 낮은 내용도 포함됨
|
|
51
|
-
|
|
52
|
-
### 1.3 토큰 비용 분석
|
|
53
|
-
|
|
54
|
-
| 시나리오 | 현재 방식 | Progressive 방식 |
|
|
55
|
-
|----------|----------|-----------------|
|
|
56
|
-
| 5개 결과, 1개만 관련 | ~5,000 tokens | ~600 tokens |
|
|
57
|
-
| 10개 결과, 2개만 관련 | ~10,000 tokens | ~1,200 tokens |
|
|
58
|
-
| 20개 결과, 3개만 관련 | ~20,000 tokens | ~2,000 tokens |
|
|
59
|
-
|
|
60
|
-
**절약 효과**: 평균 80-90% 토큰 감소
|
|
61
|
-
|
|
62
|
-
## 2. MCP 도구 설계 참고
|
|
63
|
-
|
|
64
|
-
### 2.1 claude-mem의 MCP 도구
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
// claude-mem MCP tools (추정)
|
|
68
|
-
{
|
|
69
|
-
tools: [
|
|
70
|
-
{
|
|
71
|
-
name: 'search',
|
|
72
|
-
description: 'Search memories, returns index only',
|
|
73
|
-
input_schema: {
|
|
74
|
-
query: 'string',
|
|
75
|
-
filters: { type: 'string', date: 'string' }
|
|
76
|
-
},
|
|
77
|
-
output: 'SearchIndexItem[]'
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: 'timeline',
|
|
81
|
-
description: 'Get timeline context around observations',
|
|
82
|
-
input_schema: {
|
|
83
|
-
observation_ids: 'string[]',
|
|
84
|
-
window_size: 'number'
|
|
85
|
-
},
|
|
86
|
-
output: 'TimelineItem[]'
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
name: 'get_observations',
|
|
90
|
-
description: 'Get full observation details by IDs',
|
|
91
|
-
input_schema: {
|
|
92
|
-
ids: 'string[]'
|
|
93
|
-
},
|
|
94
|
-
output: 'Observation[]'
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: '__IMPORTANT',
|
|
98
|
-
description: 'Workflow documentation for Claude',
|
|
99
|
-
// Claude가 이 도구를 보고 검색 워크플로우를 이해
|
|
100
|
-
}
|
|
101
|
-
]
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### 2.2 워크플로우 문서화
|
|
106
|
-
|
|
107
|
-
```markdown
|
|
108
|
-
# Memory Search Workflow
|
|
109
|
-
|
|
110
|
-
1. **Always start with `search`** to get compact index
|
|
111
|
-
2. **Review scores** before expanding
|
|
112
|
-
3. **Use `timeline`** if context is needed
|
|
113
|
-
4. **Only call `get_observations`** for selected IDs
|
|
114
|
-
5. **Never** fetch all details at once
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## 3. 기존 코드와의 관계
|
|
118
|
-
|
|
119
|
-
### 3.1 retriever.ts
|
|
120
|
-
|
|
121
|
-
현재 Retriever 구조:
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
export class Retriever {
|
|
125
|
-
async search(query: string): Promise<SearchResult[]> {
|
|
126
|
-
// 1. 벡터 검색
|
|
127
|
-
const vectorResults = await this.vectorStore.search(query);
|
|
128
|
-
|
|
129
|
-
// 2. 이벤트 enrichment (전체 로드)
|
|
130
|
-
const enriched = await Promise.all(
|
|
131
|
-
vectorResults.map(async (r) => {
|
|
132
|
-
const event = await this.eventStore.findById(r.id);
|
|
133
|
-
return { ...r, content: event.payload.content }; // 전체 내용
|
|
134
|
-
})
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
return enriched;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
**수정 방향**:
|
|
143
|
-
- `search()` → `searchIndex()` (Layer 1)
|
|
144
|
-
- `getTimeline()` 추가 (Layer 2)
|
|
145
|
-
- `getDetails()` 추가 (Layer 3)
|
|
146
|
-
- `smartSearch()` 추가 (자동 확장)
|
|
147
|
-
|
|
148
|
-
### 3.2 matcher.ts
|
|
149
|
-
|
|
150
|
-
현재 Matcher는 confidence 기반 분류:
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
export function matchSearchResults(results: SearchResult[]): MatchResult {
|
|
154
|
-
const high = results.filter(r => r.score >= 0.92);
|
|
155
|
-
const suggested = results.filter(r => r.score >= 0.75 && r.score < 0.92);
|
|
156
|
-
|
|
157
|
-
return { high, suggested, none: [] };
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
**확장 방향**:
|
|
162
|
-
- 기존 Matcher 로직을 확장 규칙에 통합
|
|
163
|
-
- `high` → 자동 확장 대상
|
|
164
|
-
- `suggested` → Layer 1만 표시
|
|
165
|
-
|
|
166
|
-
### 3.3 vector-store.ts
|
|
167
|
-
|
|
168
|
-
현재 VectorStore 검색:
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
async search(query: string, options: { topK: number }): Promise<VectorSearchResult[]> {
|
|
172
|
-
const queryVector = await this.embedder.embed(query);
|
|
173
|
-
return this.db.search(queryVector, options.topK);
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
**변경 불필요** - 기존 벡터 검색 그대로 사용
|
|
178
|
-
|
|
179
|
-
### 3.4 event-store.ts
|
|
180
|
-
|
|
181
|
-
필요한 추가 메서드:
|
|
182
|
-
|
|
183
|
-
```typescript
|
|
184
|
-
// 주변 이벤트 조회 (타임라인용)
|
|
185
|
-
async findSurrounding(
|
|
186
|
-
sessionId: string,
|
|
187
|
-
timestamp: Date,
|
|
188
|
-
windowSize: number
|
|
189
|
-
): Promise<Event[]> {
|
|
190
|
-
return this.db.query(`
|
|
191
|
-
SELECT * FROM events
|
|
192
|
-
WHERE session_id = ?
|
|
193
|
-
AND timestamp BETWEEN
|
|
194
|
-
datetime(?, '-${windowSize} hours') AND
|
|
195
|
-
datetime(?, '+${windowSize} hours')
|
|
196
|
-
ORDER BY timestamp
|
|
197
|
-
`, [sessionId, timestamp, timestamp]);
|
|
198
|
-
}
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
## 4. 설계 결정 사항
|
|
202
|
-
|
|
203
|
-
### 4.1 왜 3개 레이어인가?
|
|
204
|
-
|
|
205
|
-
**대안 1: 2개 레이어 (Index + Detail)**
|
|
206
|
-
- 단점: 시간 맥락 파악 어려움
|
|
207
|
-
- 단점: 모호한 결과 처리 어려움
|
|
208
|
-
|
|
209
|
-
**대안 2: 4개 이상 레이어**
|
|
210
|
-
- 단점: 복잡도 증가
|
|
211
|
-
- 단점: 실용적 이점 미미
|
|
212
|
-
|
|
213
|
-
**선택: 3개 레이어**
|
|
214
|
-
- Layer 1: What (무엇이 있는지)
|
|
215
|
-
- Layer 2: When (언제 발생했는지)
|
|
216
|
-
- Layer 3: How (구체적으로 어떻게)
|
|
217
|
-
|
|
218
|
-
### 4.2 자동 확장 vs 수동 확장
|
|
219
|
-
|
|
220
|
-
**자동 확장 장점**:
|
|
221
|
-
- 사용자 경험 향상
|
|
222
|
-
- "자세히 알려줘" 명령 불필요
|
|
223
|
-
- 높은 신뢰도 결과 즉시 제공
|
|
224
|
-
|
|
225
|
-
**자동 확장 단점**:
|
|
226
|
-
- 토큰 예측 어려움
|
|
227
|
-
- 때로는 불필요한 확장
|
|
228
|
-
|
|
229
|
-
**결론: 하이브리드 접근**
|
|
230
|
-
- 높은 신뢰도 → 자동 확장
|
|
231
|
-
- 중간 신뢰도 → Index만 제공 + 힌트
|
|
232
|
-
- 낮은 신뢰도 → Index만 제공
|
|
233
|
-
|
|
234
|
-
### 4.3 요약 생성 전략
|
|
235
|
-
|
|
236
|
-
**Option 1: LLM 요약**
|
|
237
|
-
- 장점: 고품질 요약
|
|
238
|
-
- 단점: 비용, 지연시간
|
|
239
|
-
|
|
240
|
-
**Option 2: 규칙 기반 추출**
|
|
241
|
-
- 장점: 빠름, 무료
|
|
242
|
-
- 단점: 품질 제한
|
|
243
|
-
|
|
244
|
-
**선택: 규칙 기반 + 캐싱**
|
|
245
|
-
- 첫 문장 추출
|
|
246
|
-
- 코드 블록 축약
|
|
247
|
-
- 결과 캐싱
|
|
248
|
-
|
|
249
|
-
### 4.4 토큰 추정 방식
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
// 간단한 추정 (정확도 ~85%)
|
|
253
|
-
function estimateTokens(text: string): number {
|
|
254
|
-
return Math.ceil(text.length / 4);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// 또는 정확한 추정 (tiktoken 사용)
|
|
258
|
-
import { encoding_for_model } from 'tiktoken';
|
|
259
|
-
const enc = encoding_for_model('gpt-4');
|
|
260
|
-
function estimateTokens(text: string): number {
|
|
261
|
-
return enc.encode(text).length;
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
**결론**: 간단한 추정 사용 (성능 우선)
|
|
266
|
-
|
|
267
|
-
## 5. 성능 고려사항
|
|
268
|
-
|
|
269
|
-
### 5.1 검색 지연시간
|
|
270
|
-
|
|
271
|
-
| 레이어 | 목표 지연시간 | 병목 |
|
|
272
|
-
|--------|-------------|------|
|
|
273
|
-
| Layer 1 | < 100ms | 벡터 검색 |
|
|
274
|
-
| Layer 2 | < 200ms | DB 쿼리 |
|
|
275
|
-
| Layer 3 | < 500ms | 다중 조회 |
|
|
276
|
-
|
|
277
|
-
**최적화 전략**:
|
|
278
|
-
- Layer 1: 벡터 인덱스 최적화
|
|
279
|
-
- Layer 2: 세션별 인덱스 활용
|
|
280
|
-
- Layer 3: 배치 조회
|
|
281
|
-
|
|
282
|
-
### 5.2 캐싱 전략
|
|
283
|
-
|
|
284
|
-
```typescript
|
|
285
|
-
// 레이어별 캐시 TTL
|
|
286
|
-
const CACHE_CONFIG = {
|
|
287
|
-
layer1: {
|
|
288
|
-
ttl: 60 * 1000, // 1분 (검색 결과는 자주 변함)
|
|
289
|
-
maxSize: 100
|
|
290
|
-
},
|
|
291
|
-
layer2: {
|
|
292
|
-
ttl: 5 * 60 * 1000, // 5분 (타임라인은 안정적)
|
|
293
|
-
maxSize: 500
|
|
294
|
-
},
|
|
295
|
-
layer3: {
|
|
296
|
-
ttl: 30 * 60 * 1000, // 30분 (상세 내용은 거의 안 변함)
|
|
297
|
-
maxSize: 200
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### 5.3 메모리 사용
|
|
303
|
-
|
|
304
|
-
- Layer 1 캐시: ~10KB per entry × 100 = ~1MB
|
|
305
|
-
- Layer 2 캐시: ~2KB per entry × 500 = ~1MB
|
|
306
|
-
- Layer 3 캐시: ~10KB per entry × 200 = ~2MB
|
|
307
|
-
- **총 메모리**: ~4MB (허용 범위)
|
|
308
|
-
|
|
309
|
-
## 6. UI/UX 고려사항
|
|
310
|
-
|
|
311
|
-
### 6.1 CLI 출력 포맷
|
|
312
|
-
|
|
313
|
-
```
|
|
314
|
-
🔍 Search Results (5 matches)
|
|
315
|
-
|
|
316
|
-
#1 [mem_abc] DuckDB 스키마 설계 논의 (0.94)
|
|
317
|
-
#2 [mem_def] 타입 시스템 리팩토링 (0.87)
|
|
318
|
-
#3 [mem_ghi] 벡터 저장소 설정 (0.82)
|
|
319
|
-
|
|
320
|
-
💡 Tip: Use "show mem_abc" for details
|
|
321
|
-
|
|
322
|
-
---
|
|
323
|
-
|
|
324
|
-
📅 Timeline (auto-expanded for high confidence)
|
|
325
|
-
|
|
326
|
-
14:00 → User asked about schema design
|
|
327
|
-
14:05 → **[mem_abc]** Discussed DuckDB approach
|
|
328
|
-
14:15 → Follow-up on indexing
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### 6.2 확장 힌트
|
|
332
|
-
|
|
333
|
-
```typescript
|
|
334
|
-
function formatExpansionHint(result: ProgressiveSearchResult): string {
|
|
335
|
-
if (result.meta.expandedCount === 0) {
|
|
336
|
-
return `Use "show [id]" to see details`;
|
|
337
|
-
}
|
|
338
|
-
if (result.meta.expansionReason === 'ambiguous_multiple_high') {
|
|
339
|
-
return `Multiple matches found. Use "show [id]" for specific details`;
|
|
340
|
-
}
|
|
341
|
-
return '';
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
## 7. 참고 자료
|
|
346
|
-
|
|
347
|
-
- **claude-mem README**: Progressive disclosure pattern, MCP tools
|
|
348
|
-
- **OpenAI Cookbook**: Token counting and optimization
|
|
349
|
-
- **AXIOMMIND**: Principle 7 (Standard JSON) - 포맷 일관성
|
|
350
|
-
- **기존 specs**: retriever.ts, matcher.ts 구현
|
|
351
|
-
|
|
352
|
-
## 2026-02-25T12:31:26.406Z | 65bf8cff-b5ca-4506-b45b-abb5fc746a5c
|
|
353
|
-
- type: session_summary
|
|
354
|
-
- session: import:organized
|
|
355
|
-
# Progressive Disclosure Implementation Plan
|
|
356
|
-
|
|
357
|
-
> **Version**: 1.0.0
|
|
358
|
-
> **Status**: Draft
|
|
359
|
-
> **Created**: 2026-02-01
|
|
360
|
-
|
|
361
|
-
## Phase 1: 타입 및 인터페이스 정의 (P0)
|
|
362
|
-
|
|
363
|
-
### 1.1 스키마 정의
|
|
364
|
-
|
|
365
|
-
**파일**: `src/core/types.ts` 수정
|
|
366
|
-
|
|
367
|
-
```typescript
|
|
368
|
-
// Layer 1: 검색 인덱스
|
|
369
|
-
export const SearchIndexItemSchema = z.object({
|
|
370
|
-
id: z.string(),
|
|
371
|
-
summary: z.string().max(100),
|
|
372
|
-
score: z.number(),
|
|
373
|
-
type: z.enum(['prompt', 'response', 'tool', 'insight']),
|
|
374
|
-
timestamp: z.date(),
|
|
375
|
-
sessionId: z.string()
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
// Layer 2: 타임라인
|
|
379
|
-
export const TimelineItemSchema = z.object({
|
|
380
|
-
id: z.string(),
|
|
381
|
-
timestamp: z.date(),
|
|
382
|
-
type: z.enum(['prompt', 'response', 'tool', 'insight']),
|
|
383
|
-
preview: z.string().max(200),
|
|
384
|
-
isTarget: z.boolean()
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
// Layer 3: 상세
|
|
388
|
-
export const FullDetailSchema = z.object({
|
|
389
|
-
id: z.string(),
|
|
390
|
-
content: z.string(),
|
|
391
|
-
type: z.enum(['prompt', 'response', 'tool', 'insight']),
|
|
392
|
-
timestamp: z.date(),
|
|
393
|
-
sessionId: z.string(),
|
|
394
|
-
metadata: z.object({
|
|
395
|
-
tokenCount: z.number(),
|
|
396
|
-
hasCode: z.boolean(),
|
|
397
|
-
files: z.array(z.string()).optional(),
|
|
398
|
-
tools: z.array(z.string()).optional()
|
|
399
|
-
}),
|
|
400
|
-
relations: z.object({
|
|
401
|
-
parentId: z.string().optional(),
|
|
402
|
-
childIds: z.array(z.string()),
|
|
403
|
-
relatedIds: z.array(z.string())
|
|
404
|
-
}).optional()
|
|
405
|
-
});
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
**작업 항목**:
|
|
409
|
-
- [ ] SearchIndexItemSchema 추가
|
|
410
|
-
- [ ] TimelineItemSchema 추가
|
|
411
|
-
- [ ] FullDetailSchema 추가
|
|
412
|
-
- [ ] ProgressiveSearchResultSchema 추가
|
|
413
|
-
|
|
414
|
-
### 1.2 설정 스키마 확장
|
|
415
|
-
|
|
416
|
-
**파일**: `src/core/types.ts` 수정
|
|
417
|
-
|
|
418
|
-
```typescript
|
|
419
|
-
export const ProgressiveDisclosureConfigSchema = z.object({
|
|
420
|
-
enabled: z.boolean().default(true),
|
|
421
|
-
layer1: z.object({
|
|
422
|
-
topK: z.number().default(10),
|
|
423
|
-
minScore: z.number().default(0.7)
|
|
424
|
-
}),
|
|
425
|
-
autoExpand: z.object({
|
|
426
|
-
enabled: z.boolean().default(true),
|
|
427
|
-
highConfidenceThreshold: z.number().default(0.92),
|
|
428
|
-
scoreGapThreshold: z.number().default(0.1),
|
|
429
|
-
maxAutoExpandCount: z.number().default(3)
|
|
430
|
-
}),
|
|
431
|
-
tokenBudget: z.object({
|
|
432
|
-
maxTotalTokens: z.number().default(2000),
|
|
433
|
-
layer1PerItem: z.number().default(50),
|
|
434
|
-
layer2PerItem: z.number().default(40),
|
|
435
|
-
layer3PerItem: z.number().default(500)
|
|
436
|
-
}),
|
|
437
|
-
cache: z.object({
|
|
438
|
-
layer1Ttl: z.number().default(60000),
|
|
439
|
-
layer2Ttl: z.number().default(300000),
|
|
440
|
-
layer3Ttl: z.number().default(1800000)
|
|
441
|
-
})
|
|
442
|
-
});
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
**작업 항목**:
|
|
446
|
-
- [ ] ProgressiveDisclosureConfigSchema 추가
|
|
447
|
-
- [ ] ConfigSchema에 progressiveDisclosure 필드 추가
|
|
448
|
-
|
|
449
|
-
## Phase 2: ProgressiveRetriever 구현 (P0)
|
|
450
|
-
|
|
451
|
-
### 2.1 기본 클래스 구조
|
|
452
|
-
|
|
453
|
-
**파일**: `src/core/progressive-retriever.ts` (신규)
|
|
454
|
-
|
|
455
|
-
```typescript
|
|
456
|
-
export class ProgressiveRetriever {
|
|
457
|
-
constructor(
|
|
458
|
-
private eventStore: EventStore,
|
|
459
|
-
private vectorStore: VectorStore,
|
|
460
|
-
private config: ProgressiveDisclosureConfig
|
|
461
|
-
) {}
|
|
462
|
-
|
|
463
|
-
// Layer 1: 검색 인덱스
|
|
464
|
-
async searchIndex(
|
|
465
|
-
query: string,
|
|
466
|
-
options?: { topK?: number; filter?: SearchFilter }
|
|
467
|
-
): Promise<SearchIndexItem[]> {
|
|
468
|
-
const { topK = this.config.layer1.topK } = options || {};
|
|
469
|
-
|
|
470
|
-
// 벡터 검색
|
|
471
|
-
const vectorResults = await this.vectorStore.search(query, { topK });
|
|
472
|
-
|
|
473
|
-
// 요약 생성 및 변환
|
|
474
|
-
return vectorResults.map(r => ({
|
|
475
|
-
id: r.id,
|
|
476
|
-
summary: this.generateSummary(r.content),
|
|
477
|
-
score: r.score,
|
|
478
|
-
type: r.metadata.type,
|
|
479
|
-
timestamp: r.metadata.timestamp,
|
|
480
|
-
sessionId: r.metadata.sessionId
|
|
481
|
-
}));
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Layer 2: 타임라인
|
|
485
|
-
async getTimeline(
|
|
486
|
-
targetIds: string[],
|
|
487
|
-
options?: { windowSize?: number }
|
|
488
|
-
): Promise<TimelineItem[]> {
|
|
489
|
-
const { windowSize = 3 } = options || {};
|
|
490
|
-
|
|
491
|
-
const items: TimelineItem[] = [];
|
|
492
|
-
|
|
493
|
-
for (const targetId of targetIds) {
|
|
494
|
-
const event = await this.eventStore.findById(targetId);
|
|
495
|
-
if (!event) continue;
|
|
496
|
-
|
|
497
|
-
// 주변 이벤트 조회
|
|
498
|
-
const surrounding = await this.eventStore.findSurrounding(
|
|
499
|
-
event.sessionId,
|
|
500
|
-
event.timestamp,
|
|
501
|
-
windowSize
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
items.push(...surrounding.map(e => ({
|
|
505
|
-
id: e.eventId,
|
|
506
|
-
timestamp: e.timestamp,
|
|
507
|
-
type: e.eventType,
|
|
508
|
-
preview: this.generatePreview(e.payload),
|
|
509
|
-
isTarget: e.eventId === targetId
|
|
510
|
-
})));
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
return this.deduplicateTimeline(items);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Layer 3: 상세 정보
|
|
517
|
-
async getDetails(ids: string[]): Promise<FullDetail[]> {
|
|
518
|
-
const details: FullDetail[] = [];
|
|
519
|
-
|
|
520
|
-
for (const id of ids) {
|
|
521
|
-
const event = await this.eventStore.findById(id);
|
|
522
|
-
if (!event) continue;
|
|
523
|
-
|
|
524
|
-
details.push({
|
|
525
|
-
id: event.eventId,
|
|
526
|
-
content: event.payload.content,
|
|
527
|
-
type: event.eventType,
|
|
528
|
-
timestamp: event.timestamp,
|
|
529
|
-
sessionId: event.sessionId,
|
|
530
|
-
metadata: this.extractMetadata(event),
|
|
531
|
-
relations: await this.getRelations(event)
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
return details;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
**작업 항목**:
|
|
541
|
-
- [ ] searchIndex 메서드 구현
|
|
542
|
-
- [ ] getTimeline 메서드 구현
|
|
543
|
-
- [ ] getDetails 메서드 구현
|
|
544
|
-
- [ ] generateSummary 헬퍼 구현
|
|
545
|
-
- [ ] generatePreview 헬퍼 구현
|
|
546
|
-
|
|
547
|
-
### 2.2 스마트 검색 구현
|
|
548
|
-
|
|
549
|
-
**파일**: `src/core/progressive-retriever.ts` 계속
|
|
550
|
-
|
|
551
|
-
```typescript
|
|
552
|
-
async smartSearch(
|
|
553
|
-
query: string,
|
|
554
|
-
options?: SmartSearchOptions
|
|
555
|
-
): Promise<ProgressiveSearchResult> {
|
|
556
|
-
const config = { ...this.config, ...options };
|
|
557
|
-
|
|
558
|
-
// Layer 1: 항상 실행
|
|
559
|
-
const index = await this.searchIndex(query, {
|
|
560
|
-
topK: config.layer1.topK,
|
|
561
|
-
filter: options?.filter
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const result: ProgressiveSearchResult = {
|
|
565
|
-
index,
|
|
566
|
-
meta: {
|
|
567
|
-
totalMatches: index.length,
|
|
568
|
-
expandedCount: 0,
|
|
569
|
-
estimatedTokens: this.estimateTokens(index, 'layer1')
|
|
570
|
-
}
|
|
571
|
-
};
|
|
572
|
-
|
|
573
|
-
// 자동 확장 결정
|
|
574
|
-
if (config.autoExpand.enabled) {
|
|
575
|
-
const decision = this.shouldAutoExpand(index, config);
|
|
576
|
-
|
|
577
|
-
if (decision.expandTimeline && decision.ids) {
|
|
578
|
-
result.timeline = await this.getTimeline(decision.ids);
|
|
579
|
-
result.meta.estimatedTokens += this.estimateTokens(result.timeline, 'layer2');
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
if (decision.expandDetails && decision.ids) {
|
|
583
|
-
// 토큰 예산 체크
|
|
584
|
-
const remainingBudget = config.tokenBudget.maxTotalTokens - result.meta.estimatedTokens;
|
|
585
|
-
const idsToExpand = this.selectWithinBudget(decision.ids, remainingBudget);
|
|
586
|
-
|
|
587
|
-
result.details = await this.getDetails(idsToExpand);
|
|
588
|
-
result.meta.expandedCount = idsToExpand.length;
|
|
589
|
-
result.meta.estimatedTokens += this.estimateTokens(result.details, 'layer3');
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
result.meta.expansionReason = decision.reason;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
return result;
|
|
596
|
-
}
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
**작업 항목**:
|
|
600
|
-
- [ ] smartSearch 메서드 구현
|
|
601
|
-
- [ ] shouldAutoExpand 로직 구현
|
|
602
|
-
- [ ] selectWithinBudget 토큰 예산 관리
|
|
603
|
-
|
|
604
|
-
### 2.3 확장 규칙 엔진
|
|
605
|
-
|
|
606
|
-
**파일**: `src/core/expansion-rules.ts` (신규)
|
|
607
|
-
|
|
608
|
-
```typescript
|
|
609
|
-
interface ExpansionDecision {
|
|
610
|
-
expand: boolean;
|
|
611
|
-
expandTimeline?: boolean;
|
|
612
|
-
expandDetails?: boolean;
|
|
613
|
-
ids?: string[];
|
|
614
|
-
reason: string;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
export function shouldAutoExpand(
|
|
618
|
-
results: SearchIndexItem[],
|
|
619
|
-
config: ProgressiveDisclosureConfig
|
|
620
|
-
): ExpansionDecision {
|
|
621
|
-
if (results.length === 0) {
|
|
622
|
-
return { expand: false, reason: 'no_results' };
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
const topScore = results[0].score;
|
|
626
|
-
|
|
627
|
-
// Rule 1: 높은 신뢰도 단일 결과
|
|
628
|
-
if (topScore >= config.autoExpand.highConfidenceThreshold && results.length === 1) {
|
|
629
|
-
return {
|
|
630
|
-
expand: true,
|
|
631
|
-
expandTimeline: true,
|
|
632
|
-
expandDetails: true,
|
|
633
|
-
ids: [results[0].id],
|
|
634
|
-
reason: 'high_confidence_single'
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// Rule 2: 명확한 1등
|
|
639
|
-
if (results.length >= 2) {
|
|
640
|
-
const gap = results[0].score - results[1].score;
|
|
641
|
-
if (topScore >= 0.85 && gap >= config.autoExpand.scoreGapThreshold) {
|
|
642
|
-
return {
|
|
643
|
-
expand: true,
|
|
644
|
-
expandTimeline: true,
|
|
645
|
-
expandDetails: true,
|
|
646
|
-
ids: [results[0].id],
|
|
647
|
-
reason: 'clear_winner'
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Rule 3: 모호한 결과 → 타임라인만
|
|
653
|
-
const highScoreCount = results.filter(r => r.score >= 0.8).length;
|
|
654
|
-
if (highScoreCount >= 3) {
|
|
655
|
-
return {
|
|
656
|
-
expand: true,
|
|
657
|
-
expandTimeline: true,
|
|
658
|
-
expandDetails: false,
|
|
659
|
-
ids: results.slice(0, 3).map(r => r.id),
|
|
660
|
-
reason: 'ambiguous_multiple_high'
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// Rule 4: 낮은 점수
|
|
665
|
-
return { expand: false, reason: 'low_confidence' };
|
|
666
|
-
}
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
**작업 항목**:
|
|
670
|
-
- [ ] 확장 규칙 함수 구현
|
|
671
|
-
- [ ] 규칙별 테스트 케이스
|
|
672
|
-
|
|
673
|
-
## Phase 3: 요약 및 미리보기 생성 (P0)
|
|
674
|
-
|
|
675
|
-
### 3.1 요약 생성기
|
|
676
|
-
|
|
677
|
-
**파일**: `src/core/summarizer.ts` (신규)
|
|
678
|
-
|
|
679
|
-
```typescript
|
|
680
|
-
export function generateSummary(content: string, maxLength: number = 100): string {
|
|
681
|
-
// 1. 코드 블록 제거
|
|
682
|
-
const withoutCode = content.replace(/```[\s\S]*?```/g, '[code]');
|
|
683
|
-
|
|
684
|
-
// 2. 첫 문장 추출
|
|
685
|
-
const firstSentence = withoutCode.match(/^[^.!?]+[.!?]/)?.[0] || '';
|
|
686
|
-
|
|
687
|
-
// 3. 길이 제한
|
|
688
|
-
if (firstSentence.length <= maxLength) {
|
|
689
|
-
return firstSentence.trim();
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// 4. 단어 경계에서 자르기
|
|
693
|
-
return withoutCode.slice(0, maxLength).replace(/\s+\S*$/, '') + '...';
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
export function generatePreview(content: string, maxLength: number = 200): string {
|
|
697
|
-
// 1. 코드 블록 축약
|
|
698
|
-
const withCodeSummary = content.replace(
|
|
699
|
-
/```(\w+)[\s\S]*?```/g,
|
|
700
|
-
(_, lang) => `[${lang} code]`
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
// 2. 줄바꿈 정리
|
|
704
|
-
const singleLine = withCodeSummary.replace(/\n+/g, ' ').trim();
|
|
705
|
-
|
|
706
|
-
// 3. 길이 제한
|
|
707
|
-
if (singleLine.length <= maxLength) {
|
|
708
|
-
return singleLine;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
return singleLine.slice(0, maxLength).replace(/\s+\S*$/, '') + '...';
|
|
712
|
-
}
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
**작업 항목**:
|
|
716
|
-
- [ ] generateSummary 함수 구현
|
|
717
|
-
- [ ] generatePreview 함수 구현
|
|
718
|
-
- [ ] 코드 블록 처리 로직
|
|
719
|
-
- [ ] 특수 문자 처리
|
|
720
|
-
|
|
721
|
-
### 3.2 토큰 추정기
|
|
722
|
-
|
|
723
|
-
**파일**: `src/core/token-estimator.ts` (신규)
|
|
724
|
-
|
|
725
|
-
```typescript
|
|
726
|
-
// 간단한 토큰 추정 (GPT tokenizer 근사)
|
|
727
|
-
export function estimateTokens(text: string): number {
|
|
728
|
-
// 평균적으로 4자 = 1토큰
|
|
729
|
-
return Math.ceil(text.length / 4);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
export function estimateLayerTokens(
|
|
733
|
-
items: unknown[],
|
|
734
|
-
layer: 'layer1' | 'layer2' | 'layer3',
|
|
735
|
-
config: ProgressiveDisclosureConfig
|
|
736
|
-
): number {
|
|
737
|
-
switch (layer) {
|
|
738
|
-
case 'layer1':
|
|
739
|
-
return items.length * config.tokenBudget.layer1PerItem;
|
|
740
|
-
case 'layer2':
|
|
741
|
-
return items.length * config.tokenBudget.layer2PerItem;
|
|
742
|
-
case 'layer3':
|
|
743
|
-
// Layer 3는 실제 콘텐츠 기반
|
|
744
|
-
return (items as FullDetail[]).reduce(
|
|
745
|
-
(sum, item) => sum + estimateTokens(item.content),
|
|
746
|
-
0
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
```
|
|
751
|
-
|
|
752
|
-
**작업 항목**:
|
|
753
|
-
- [ ] estimateTokens 함수 구현
|
|
754
|
-
- [ ] estimateLayerTokens 함수 구현
|
|
755
|
-
|
|
756
|
-
## Phase 4: 캐싱 (P1)
|
|
757
|
-
|
|
758
|
-
### 4.1 캐시 매니저
|
|
759
|
-
|
|
760
|
-
**파일**: `src/core/cache-manager.ts` (신규)
|
|
761
|
-
|
|
762
|
-
```typescript
|
|
763
|
-
import { LRUCache } from 'lru-cache';
|
|
764
|
-
|
|
765
|
-
export class ProgressiveCache {
|
|
766
|
-
private layer1Cache: LRUCache<string, SearchIndexItem[]>;
|
|
767
|
-
private layer2Cache: LRUCache<string, TimelineItem[]>;
|
|
768
|
-
private layer3Cache: LRUCache<string, FullDetail>;
|
|
769
|
-
|
|
770
|
-
constructor(config: ProgressiveDisclosureConfig) {
|
|
771
|
-
this.layer1Cache = new LRUCache({
|
|
772
|
-
max: 100,
|
|
773
|
-
ttl: config.cache.layer1Ttl
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
this.layer2Cache = new LRUCache({
|
|
777
|
-
max: 500,
|
|
778
|
-
ttl: config.cache.layer2Ttl
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
this.layer3Cache = new LRUCache({
|
|
782
|
-
max: 200,
|
|
783
|
-
ttl: config.cache.layer3Ttl
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// Layer 1 캐시
|
|
788
|
-
getLayer1(query: string, options: SearchOptions): SearchIndexItem[] | undefined {
|
|
789
|
-
const key = this.buildLayer1Key(query, options);
|
|
790
|
-
return this.layer1Cache.get(key);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
setLayer1(query: string, options: SearchOptions, results: SearchIndexItem[]): void {
|
|
794
|
-
const key = this.buildLayer1Key(query, options);
|
|
795
|
-
this.layer1Cache.set(key, results);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// ... Layer 2, 3 유사 구현
|
|
799
|
-
}
|
|
800
|
-
```
|
|
801
|
-
|
|
802
|
-
**작업 항목**:
|
|
803
|
-
- [ ] ProgressiveCache 클래스 구현
|
|
804
|
-
- [ ] Layer별 캐시 키 생성
|
|
805
|
-
- [ ] TTL 및 크기 제한 적용
|
|
806
|
-
|
|
807
|
-
## Phase 5: 포맷터 (P0)
|
|
808
|
-
|
|
809
|
-
### 5.1 컨텍스트 포맷터
|
|
810
|
-
|
|
811
|
-
**파일**: `src/core/context-formatter.ts` (신규)
|
|
812
|
-
|
|
813
|
-
```typescript
|
|
814
|
-
export class ContextFormatter {
|
|
815
|
-
formatProgressiveResult(result: ProgressiveSearchResult): string {
|
|
816
|
-
const parts: string[] = [];
|
|
817
|
-
|
|
818
|
-
// Layer 1: 항상 포함
|
|
819
|
-
parts.push(this.formatLayer1(result.index));
|
|
820
|
-
|
|
821
|
-
// Layer 2: 타임라인
|
|
822
|
-
if (result.timeline) {
|
|
823
|
-
parts.push(this.formatLayer2(result.timeline));
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// Layer 3: 상세
|
|
827
|
-
if (result.details) {
|
|
828
|
-
parts.push(this.formatLayer3(result.details));
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// 메타 정보
|
|
832
|
-
parts.push(this.formatMeta(result.meta));
|
|
833
|
-
|
|
834
|
-
return parts.join('\n\n');
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
private formatLayer1(items: SearchIndexItem[]): string {
|
|
838
|
-
const header = `## Related Memories (${items.length} matches)\n`;
|
|
839
|
-
const table = items.map((item, i) =>
|
|
840
|
-
`${i + 1}. [${item.id}] ${item.summary} (score: ${item.score.toFixed(2)})`
|
|
841
|
-
).join('\n');
|
|
842
|
-
|
|
843
|
-
return header + table;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
private formatLayer2(items: TimelineItem[]): string {
|
|
847
|
-
const header = '## Timeline Context\n';
|
|
848
|
-
const timeline = items.map(item => {
|
|
849
|
-
const marker = item.isTarget ? '**→**' : ' ';
|
|
850
|
-
const time = item.timestamp.toLocaleTimeString();
|
|
851
|
-
return `${marker} ${time}: ${item.preview}`;
|
|
852
|
-
}).join('\n');
|
|
853
|
-
|
|
854
|
-
return header + timeline;
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
private formatLayer3(items: FullDetail[]): string {
|
|
858
|
-
return items.map(item => {
|
|
859
|
-
const header = `## Detail: ${item.id}\n`;
|
|
860
|
-
const meta = `*Session: ${item.sessionId} | ${item.timestamp.toLocaleDateString()}*\n`;
|
|
861
|
-
return header + meta + '\n' + item.content;
|
|
862
|
-
}).join('\n\n---\n\n');
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
```
|
|
866
|
-
|
|
867
|
-
**작업 항목**:
|
|
868
|
-
- [ ] ContextFormatter 클래스 구현
|
|
869
|
-
- [ ] Layer별 포맷 함수
|
|
870
|
-
- [ ] Markdown 출력 최적화
|
|
871
|
-
|
|
872
|
-
## Phase 6: 통합 (P0)
|
|
873
|
-
|
|
874
|
-
### 6.1 Retriever 교체
|
|
875
|
-
|
|
876
|
-
**파일**: `src/core/retriever.ts` 수정
|
|
877
|
-
|
|
878
|
-
```typescript
|
|
879
|
-
export class Retriever {
|
|
880
|
-
private progressiveRetriever: ProgressiveRetriever;
|
|
881
|
-
|
|
882
|
-
constructor(/* ... */) {
|
|
883
|
-
this.progressiveRetriever = new ProgressiveRetriever(
|
|
884
|
-
this.eventStore,
|
|
885
|
-
this.vectorStore,
|
|
886
|
-
config.progressiveDisclosure
|
|
887
|
-
);
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// 기존 메서드를 progressive로 위임
|
|
891
|
-
async search(query: string): Promise<SearchResult[]> {
|
|
892
|
-
if (this.config.progressiveDisclosure?.enabled) {
|
|
893
|
-
const result = await this.progressiveRetriever.smartSearch(query);
|
|
894
|
-
return this.convertToLegacyFormat(result);
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
// 기존 로직 유지 (fallback)
|
|
898
|
-
return this.legacySearch(query);
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// 새로운 progressive 검색
|
|
902
|
-
async progressiveSearch(query: string, options?: SmartSearchOptions): Promise<ProgressiveSearchResult> {
|
|
903
|
-
return this.progressiveRetriever.smartSearch(query, options);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
```
|
|
907
|
-
|
|
908
|
-
**작업 항목**:
|
|
909
|
-
- [ ] Retriever에 progressiveRetriever 통합
|
|
910
|
-
- [ ] 기존 API 호환성 유지
|
|
911
|
-
- [ ] 새 API 추가
|
|
912
|
-
|
|
913
|
-
### 6.2 user-prompt-submit 훅 수정
|
|
914
|
-
|
|
915
|
-
**파일**: `src/hooks/user-prompt-submit.ts` 수정
|
|
916
|
-
|
|
917
|
-
```typescript
|
|
918
|
-
async function handleUserPromptSubmit(input: UserPromptInput): Promise<HookOutput> {
|
|
919
|
-
const memoryService = await MemoryService.getInstance();
|
|
920
|
-
const config = memoryService.getConfig();
|
|
921
|
-
|
|
922
|
-
// Progressive 검색 사용
|
|
923
|
-
const searchResult = await memoryService.progressiveSearch(input.prompt, {
|
|
924
|
-
maxTotalTokens: config.retrieval.maxTokens
|
|
925
|
-
});
|
|
926
|
-
|
|
927
|
-
// 포맷팅
|
|
928
|
-
const formatter = new ContextFormatter();
|
|
929
|
-
const context = formatter.formatProgressiveResult(searchResult);
|
|
930
|
-
|
|
931
|
-
return {
|
|
932
|
-
context,
|
|
933
|
-
meta: {
|
|
934
|
-
matchCount: searchResult.meta.totalMatches,
|
|
935
|
-
expandedCount: searchResult.meta.expandedCount,
|
|
936
|
-
estimatedTokens: searchResult.meta.estimatedTokens
|
|
937
|
-
}
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
```
|
|
941
|
-
|
|
942
|
-
**작업 항목**:
|
|
943
|
-
- [ ] 훅에서 progressive 검색 사용
|
|
944
|
-
- [ ] 메타 정보 반환
|
|
945
|
-
|
|
946
|
-
## 파일 목록
|
|
947
|
-
|
|
948
|
-
### 신규 파일
|
|
949
|
-
```
|
|
950
|
-
src/core/progressive-retriever.ts # 메인 검색 클래스
|
|
951
|
-
src/core/expansion-rules.ts # 확장 규칙 엔진
|
|
952
|
-
src/core/summarizer.ts # 요약/미리보기 생성
|
|
953
|
-
src/core/token-estimator.ts # 토큰 추정
|
|
954
|
-
src/core/cache-manager.ts # 캐시 관리
|
|
955
|
-
src/core/context-formatter.ts # 출력 포맷팅
|
|
956
|
-
```
|
|
957
|
-
|
|
958
|
-
### 수정 파일
|
|
959
|
-
```
|
|
960
|
-
src/core/types.ts # 스키마 추가
|
|
961
|
-
src/core/retriever.ts # Progressive 통합
|
|
962
|
-
src/hooks/user-prompt-submit.ts # 검색 방식 변경
|
|
963
|
-
src/services/memory-service.ts # 서비스 메서드 추가
|
|
964
|
-
```
|
|
965
|
-
|
|
966
|
-
## 테스트
|
|
967
|
-
|
|
968
|
-
### 필수 테스트 케이스
|
|
969
|
-
|
|
970
|
-
1. **Layer 1 검색**
|
|
971
|
-
```typescript
|
|
972
|
-
test('should return index with summaries', async () => {
|
|
973
|
-
const result = await retriever.searchIndex('DuckDB 스키마');
|
|
974
|
-
expect(result[0]).toHaveProperty('summary');
|
|
975
|
-
expect(result[0].summary.length).toBeLessThanOrEqual(100);
|
|
976
|
-
});
|
|
977
|
-
```
|
|
978
|
-
|
|
979
|
-
2. **자동 확장 규칙**
|
|
980
|
-
```typescript
|
|
981
|
-
test('should expand on high confidence', async () => {
|
|
982
|
-
const result = await retriever.smartSearch('unique term');
|
|
983
|
-
expect(result.details).toBeDefined();
|
|
984
|
-
expect(result.meta.expansionReason).toBe('high_confidence_single');
|
|
985
|
-
});
|
|
986
|
-
```
|
|
987
|
-
|
|
988
|
-
3. **토큰 예산**
|
|
989
|
-
```typescript
|
|
990
|
-
test('should respect token budget', async () => {
|
|
991
|
-
const result = await retriever.smartSearch('query', { maxTotalTokens: 500 });
|
|
992
|
-
expect(result.meta.estimatedTokens).toBeLessThanOrEqual(500);
|
|
993
|
-
});
|
|
994
|
-
```
|
|
995
|
-
|
|
996
|
-
4. **캐싱**
|
|
997
|
-
```typescript
|
|
998
|
-
test('should use cache on repeat query', async () => {
|
|
999
|
-
await retriever.searchIndex('test');
|
|
1000
|
-
const start = Date.now();
|
|
1001
|
-
await retriever.searchIndex('test');
|
|
1002
|
-
expect(Date.now() - start).toBeLessThan(10);
|
|
1003
|
-
});
|
|
1004
|
-
```
|
|
1005
|
-
|
|
1006
|
-
## 마일스톤
|
|
1007
|
-
|
|
1008
|
-
| 단계 | 완료 기준 |
|
|
1009
|
-
|------|----------|
|
|
1010
|
-
| M1 | 타입 정의 완료 |
|
|
1011
|
-
| M2 | ProgressiveRetriever 기본 구현 |
|
|
1012
|
-
| M3 | 확장 규칙 엔진 |
|
|
1013
|
-
| M4 | 요약/미리보기 생성 |
|
|
1014
|
-
| M5 | 토큰 예산 관리 |
|
|
1015
|
-
| M6 | 캐싱 구현 |
|
|
1016
|
-
| M7 | 포맷터 및 통합 |
|
|
1017
|
-
| M8 | 테스트 통과 |
|
|
1018
|
-
|
|
1019
|
-
## 2026-02-25T12:31:26.413Z | 8c526a4e-bfe7-4bdc-86c8-f16fe06f0faf
|
|
1020
|
-
- type: session_summary
|
|
1021
|
-
- session: import:organized
|
|
1022
|
-
# Progressive Disclosure Specification
|
|
1023
|
-
|
|
1024
|
-
> **Version**: 1.0.0
|
|
1025
|
-
> **Status**: Draft
|
|
1026
|
-
> **Created**: 2026-02-01
|
|
1027
|
-
> **Reference**: claude-mem (thedotmack/claude-mem)
|
|
1028
|
-
|
|
1029
|
-
## 1. 개요
|
|
1030
|
-
|
|
1031
|
-
### 1.1 문제 정의
|
|
1032
|
-
|
|
1033
|
-
현재 시스템에서 메모리 검색 시 토큰 비효율 발생:
|
|
1034
|
-
|
|
1035
|
-
1. **전체 로드 문제**: 검색 결과를 한 번에 모든 내용을 가져옴
|
|
1036
|
-
2. **토큰 낭비**: 관련 없는 내용도 컨텍스트에 포함
|
|
1037
|
-
3. **컨텍스트 한계**: 대용량 메모리 사용 시 토큰 초과
|
|
1038
|
-
|
|
1039
|
-
### 1.2 해결 방향
|
|
1040
|
-
|
|
1041
|
-
**3-Layer Progressive Disclosure**:
|
|
1042
|
-
- Layer 1: 검색 인덱스 (ID + 요약) - 최소 토큰
|
|
1043
|
-
- Layer 2: 타임라인 컨텍스트 - 시간순 맥락
|
|
1044
|
-
- Layer 3: 상세 정보 - 선택된 항목만 전체 로드
|
|
1045
|
-
|
|
1046
|
-
## 2. 핵심 개념
|
|
1047
|
-
|
|
1048
|
-
### 2.1 3-Layer 아키텍처
|
|
1049
|
-
|
|
1050
|
-
```
|
|
1051
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
1052
|
-
│ User Query │
|
|
1053
|
-
└─────────────────────────────────────────────────────────────┘
|
|
1054
|
-
│
|
|
1055
|
-
▼
|
|
1056
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
1057
|
-
│ Layer 1: Search Index (~50-100 tokens per result) │
|
|
1058
|
-
│ ┌──────────────────────────────────────────────────────┐ │
|
|
1059
|
-
│ │ { id: "mem_1", summary: "파일 구조 설명", score: 0.95 } │ │
|
|
1060
|
-
│ │ { id: "mem_2", summary: "타입 정의 논의", score: 0.87 } │ │
|
|
1061
|
-
│ │ { id: "mem_3", summary: "버그 수정 방법", score: 0.82 } │ │
|
|
1062
|
-
│ └──────────────────────────────────────────────────────┘ │
|
|
1063
|
-
└─────────────────────────────────────────────────────────────┘
|
|
1064
|
-
│
|
|
1065
|
-
(선택적 확장)
|
|
1066
|
-
▼
|
|
1067
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
1068
|
-
│ Layer 2: Timeline Context (~200 tokens) │
|
|
1069
|
-
│ ┌──────────────────────────────────────────────────────┐ │
|
|
1070
|
-
│ │ 2026-01-30 14:00: "파일 구조 변경 결정" │ │
|
|
1071
|
-
│ │ 2026-01-30 14:15: "types.ts 분리" ← mem_1 │ │
|
|
1072
|
-
│ │ 2026-01-30 14:30: "테스트 작성" │ │
|
|
1073
|
-
│ └──────────────────────────────────────────────────────┘ │
|
|
1074
|
-
└─────────────────────────────────────────────────────────────┘
|
|
1075
|
-
│
|
|
1076
|
-
(필요 시만)
|
|
1077
|
-
▼
|
|
1078
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
1079
|
-
│ Layer 3: Full Details (~500-1000 tokens per result) │
|
|
1080
|
-
│ ┌──────────────────────────────────────────────────────┐ │
|
|
1081
|
-
│ │ mem_1: { │ │
|
|
1082
|
-
│ │ content: "전체 대화 내용...", │ │
|
|
1083
|
-
│ │ metadata: {...}, │ │
|
|
1084
|
-
│ │ evidence: [...] │ │
|
|
1085
|
-
│ │ } │ │
|
|
1086
|
-
│ └──────────────────────────────────────────────────────┘ │
|
|
1087
|
-
└─────────────────────────────────────────────────────────────┘
|
|
1088
|
-
```
|
|
1089
|
-
|
|
1090
|
-
### 2.2 토큰 효율성
|
|
1091
|
-
|
|
1092
|
-
| 방식 | 5개 결과 토큰 | 20개 결과 토큰 |
|
|
1093
|
-
|------|--------------|---------------|
|
|
1094
|
-
| 기존 (전체 로드) | ~5,000 | ~20,000 |
|
|
1095
|
-
| Progressive L1 | ~500 | ~2,000 |
|
|
1096
|
-
| Progressive L1+L2 | ~700 | ~2,200 |
|
|
1097
|
-
| Progressive L1+L2+L3 (2개) | ~1,700 | ~2,200 |
|
|
1098
|
-
|
|
1099
|
-
**예상 토큰 절약: ~10배**
|
|
1100
|
-
|
|
1101
|
-
### 2.3 확장 트리거
|
|
1102
|
-
|
|
1103
|
-
```typescript
|
|
1104
|
-
type ExpansionTrigger =
|
|
1105
|
-
| 'high_confidence' // score ≥ 0.92 → 자동 L3 확장
|
|
1106
|
-
| 'user_request' // "자세히 알려줘" → L3 확장
|
|
1107
|
-
| 'temporal_proximity' // 시간적 근접 → L2 확장
|
|
1108
|
-
| 'explicit_id' // "mem_1 보여줘" → L3 확장
|
|
1109
|
-
| 'ambiguity'; // 여러 유사 결과 → L2 확장
|
|
1110
|
-
```
|
|
1111
|
-
|
|
1112
|
-
## 3. 데이터 스키마
|
|
1113
|
-
|
|
1114
|
-
### 3.1 Layer 1: SearchIndex
|
|
1115
|
-
|
|
1116
|
-
```typescript
|
|
1117
|
-
const SearchIndexItemSchema = z.object({
|
|
1118
|
-
id: z.string(), // 이벤트/메모리 ID
|
|
1119
|
-
summary: z.string().max(100), // 한 줄 요약
|
|
1120
|
-
score: z.number(), // 유사도 점수
|
|
1121
|
-
type: z.enum(['prompt', 'response', 'tool', 'insight']),
|
|
1122
|
-
timestamp: z.date(),
|
|
1123
|
-
sessionId: z.string()
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
type SearchIndexItem = z.infer<typeof SearchIndexItemSchema>;
|
|
1127
|
-
|
|
1128
|
-
// 반환 예시
|
|
1129
|
-
{
|
|
1130
|
-
id: "evt_abc123",
|
|
1131
|
-
summary: "DuckDB 스키마 설계 논의",
|
|
1132
|
-
score: 0.94,
|
|
1133
|
-
type: "response",
|
|
1134
|
-
timestamp: "2026-01-30T14:00:00Z",
|
|
1135
|
-
sessionId: "session_xyz"
|
|
1136
|
-
}
|
|
1137
|
-
```
|
|
1138
|
-
|
|
1139
|
-
### 3.2 Layer 2: TimelineContext
|
|
1140
|
-
|
|
1141
|
-
```typescript
|
|
1142
|
-
const TimelineItemSchema = z.object({
|
|
1143
|
-
id: z.string(),
|
|
1144
|
-
timestamp: z.date(),
|
|
1145
|
-
type: z.enum(['prompt', 'response', 'tool', 'insight']),
|
|
1146
|
-
preview: z.string().max(200), // 2-3문장 미리보기
|
|
1147
|
-
isTarget: z.boolean() // 검색 결과에 해당하는지
|
|
1148
|
-
});
|
|
1149
|
-
|
|
1150
|
-
type TimelineItem = z.infer<typeof TimelineItemSchema>;
|
|
1151
|
-
|
|
1152
|
-
// 반환 예시 (target ID 주변 ±3개)
|
|
1153
|
-
[
|
|
1154
|
-
{ id: "evt_1", preview: "이전 대화...", isTarget: false },
|
|
1155
|
-
{ id: "evt_2", preview: "관련 질문...", isTarget: false },
|
|
1156
|
-
{ id: "evt_abc123", preview: "DuckDB 스키마...", isTarget: true }, // 타겟
|
|
1157
|
-
{ id: "evt_3", preview: "후속 논의...", isTarget: false }
|
|
1158
|
-
]
|
|
1159
|
-
```
|
|
1160
|
-
|
|
1161
|
-
### 3.3 Layer 3: FullDetail
|
|
1162
|
-
|
|
1163
|
-
```typescript
|
|
1164
|
-
const FullDetailSchema = z.object({
|
|
1165
|
-
id: z.string(),
|
|
1166
|
-
content: z.string(), // 전체 내용
|
|
1167
|
-
type: z.enum(['prompt', 'response', 'tool', 'insight']),
|
|
1168
|
-
timestamp: z.date(),
|
|
1169
|
-
sessionId: z.string(),
|
|
1170
|
-
|
|
1171
|
-
// 메타데이터
|
|
1172
|
-
metadata: z.object({
|
|
1173
|
-
tokenCount: z.number(),
|
|
1174
|
-
hasCode: z.boolean(),
|
|
1175
|
-
files: z.array(z.string()).optional(),
|
|
1176
|
-
tools: z.array(z.string()).optional()
|
|
1177
|
-
}),
|
|
1178
|
-
|
|
1179
|
-
// 관계 정보
|
|
1180
|
-
relations: z.object({
|
|
1181
|
-
parentId: z.string().optional(),
|
|
1182
|
-
childIds: z.array(z.string()),
|
|
1183
|
-
relatedIds: z.array(z.string())
|
|
1184
|
-
}).optional()
|
|
1185
|
-
});
|
|
1186
|
-
|
|
1187
|
-
type FullDetail = z.infer<typeof FullDetailSchema>;
|
|
1188
|
-
```
|
|
1189
|
-
|
|
1190
|
-
## 4. API 인터페이스
|
|
1191
|
-
|
|
1192
|
-
### 4.1 ProgressiveRetriever
|
|
1193
|
-
|
|
1194
|
-
```typescript
|
|
1195
|
-
interface ProgressiveRetriever {
|
|
1196
|
-
// Layer 1: 검색 인덱스 반환
|
|
1197
|
-
searchIndex(
|
|
1198
|
-
query: string,
|
|
1199
|
-
options?: {
|
|
1200
|
-
topK?: number;
|
|
1201
|
-
filter?: SearchFilter;
|
|
1202
|
-
}
|
|
1203
|
-
): Promise<SearchIndexItem[]>;
|
|
1204
|
-
|
|
1205
|
-
// Layer 2: 타임라인 컨텍스트 반환
|
|
1206
|
-
getTimeline(
|
|
1207
|
-
targetIds: string[],
|
|
1208
|
-
options?: {
|
|
1209
|
-
windowSize?: number; // 앞뒤로 몇 개씩
|
|
1210
|
-
}
|
|
1211
|
-
): Promise<TimelineItem[]>;
|
|
1212
|
-
|
|
1213
|
-
// Layer 3: 상세 정보 반환
|
|
1214
|
-
getDetails(ids: string[]): Promise<FullDetail[]>;
|
|
1215
|
-
|
|
1216
|
-
// 편의 메서드: 자동 확장
|
|
1217
|
-
smartSearch(
|
|
1218
|
-
query: string,
|
|
1219
|
-
options?: SmartSearchOptions
|
|
1220
|
-
): Promise<ProgressiveSearchResult>;
|
|
1221
|
-
}
|
|
1222
|
-
```
|
|
1223
|
-
|
|
1224
|
-
### 4.2 SmartSearch 옵션
|
|
1225
|
-
|
|
1226
|
-
```typescript
|
|
1227
|
-
interface SmartSearchOptions {
|
|
1228
|
-
// Layer 1 설정
|
|
1229
|
-
topK: number; // 기본: 10
|
|
1230
|
-
minScore: number; // 기본: 0.7
|
|
1231
|
-
|
|
1232
|
-
// 자동 확장 설정
|
|
1233
|
-
autoExpandTimeline: boolean; // 기본: true (score gap 클 때)
|
|
1234
|
-
autoExpandDetails: boolean; // 기본: true (score ≥ 0.92)
|
|
1235
|
-
maxAutoExpandCount: number; // 기본: 3
|
|
1236
|
-
|
|
1237
|
-
// 토큰 제한
|
|
1238
|
-
maxTotalTokens: number; // 기본: 2000
|
|
1239
|
-
}
|
|
1240
|
-
```
|
|
1241
|
-
|
|
1242
|
-
### 4.3 ProgressiveSearchResult
|
|
1243
|
-
|
|
1244
|
-
```typescript
|
|
1245
|
-
interface ProgressiveSearchResult {
|
|
1246
|
-
// Layer 1 (항상 포함)
|
|
1247
|
-
index: SearchIndexItem[];
|
|
1248
|
-
|
|
1249
|
-
// Layer 2 (선택적)
|
|
1250
|
-
timeline?: TimelineItem[];
|
|
1251
|
-
|
|
1252
|
-
// Layer 3 (선택적)
|
|
1253
|
-
details?: FullDetail[];
|
|
1254
|
-
|
|
1255
|
-
// 메타정보
|
|
1256
|
-
meta: {
|
|
1257
|
-
totalMatches: number;
|
|
1258
|
-
expandedCount: number;
|
|
1259
|
-
estimatedTokens: number;
|
|
1260
|
-
expansionReason?: string;
|
|
1261
|
-
};
|
|
1262
|
-
}
|
|
1263
|
-
```
|
|
1264
|
-
|
|
1265
|
-
## 5. 확장 규칙
|
|
1266
|
-
|
|
1267
|
-
### 5.1 자동 확장 조건
|
|
1268
|
-
|
|
1269
|
-
```typescript
|
|
1270
|
-
function shouldAutoExpand(results: SearchIndexItem[]): ExpansionDecision {
|
|
1271
|
-
// Rule 1: 높은 신뢰도 단일 결과
|
|
1272
|
-
if (results[0]?.score >= 0.92 && results.length === 1) {
|
|
1273
|
-
return { expand: true, ids: [results[0].id], reason: 'high_confidence' };
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
// Rule 2: 명확한 1등 (2등과 gap이 큼)
|
|
1277
|
-
if (results.length >= 2) {
|
|
1278
|
-
const gap = results[0].score - results[1].score;
|
|
1279
|
-
if (results[0].score >= 0.85 && gap >= 0.1) {
|
|
1280
|
-
return { expand: true, ids: [results[0].id], reason: 'clear_winner' };
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
// Rule 3: 모호한 결과 → 타임라인만 확장
|
|
1285
|
-
if (results.length >= 3 && results[2].score >= 0.8) {
|
|
1286
|
-
return {
|
|
1287
|
-
expand: true,
|
|
1288
|
-
expandTimeline: true,
|
|
1289
|
-
expandDetails: false,
|
|
1290
|
-
ids: results.slice(0, 3).map(r => r.id),
|
|
1291
|
-
reason: 'ambiguous_results'
|
|
1292
|
-
};
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// Rule 4: 낮은 점수 → 확장 안 함
|
|
1296
|
-
return { expand: false, reason: 'low_confidence' };
|
|
1297
|
-
}
|
|
1298
|
-
```
|
|
1299
|
-
|
|
1300
|
-
### 5.2 토큰 예산 관리
|
|
1301
|
-
|
|
1302
|
-
```typescript
|
|
1303
|
-
function expandWithinBudget(
|
|
1304
|
-
index: SearchIndexItem[],
|
|
1305
|
-
budget: number
|
|
1306
|
-
): ProgressiveSearchResult {
|
|
1307
|
-
let usedTokens = estimateTokens(index); // ~50-100 per item
|
|
1308
|
-
const result: ProgressiveSearchResult = { index, meta: { ... } };
|
|
1309
|
-
|
|
1310
|
-
// 예산 내에서 확장
|
|
1311
|
-
const sortedByScore = [...index].sort((a, b) => b.score - a.score);
|
|
1312
|
-
|
|
1313
|
-
for (const item of sortedByScore) {
|
|
1314
|
-
if (usedTokens >= budget) break;
|
|
1315
|
-
|
|
1316
|
-
// 타임라인 추가 (~200 tokens)
|
|
1317
|
-
if (usedTokens + 200 <= budget && !result.timeline) {
|
|
1318
|
-
result.timeline = await getTimeline([item.id]);
|
|
1319
|
-
usedTokens += estimateTokens(result.timeline);
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
// 상세 추가 (~500-1000 tokens)
|
|
1323
|
-
if (item.score >= 0.85 && usedTokens + 800 <= budget) {
|
|
1324
|
-
const detail = await getDetails([item.id]);
|
|
1325
|
-
result.details = [...(result.details || []), ...detail];
|
|
1326
|
-
usedTokens += estimateTokens(detail);
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
result.meta.estimatedTokens = usedTokens;
|
|
1331
|
-
return result;
|
|
1332
|
-
}
|
|
1333
|
-
```
|
|
1334
|
-
|
|
1335
|
-
## 6. 컨텍스트 포맷
|
|
1336
|
-
|
|
1337
|
-
### 6.1 Layer 1 포맷 (최소)
|
|
1338
|
-
|
|
1339
|
-
```markdown
|
|
1340
|
-
## Related Memories (5 matches)
|
|
1341
|
-
|
|
1342
|
-
| ID | Summary | Score |
|
|
1343
|
-
|----|---------|-------|
|
|
1344
|
-
| mem_1 | DuckDB 스키마 설계 논의 | 0.94 |
|
|
1345
|
-
| mem_2 | 타입 시스템 리팩토링 | 0.87 |
|
|
1346
|
-
| mem_3 | 벡터 저장소 설정 | 0.82 |
|
|
1347
|
-
| mem_4 | 테스트 코드 작성 | 0.78 |
|
|
1348
|
-
| mem_5 | CI/CD 파이프라인 | 0.75 |
|
|
1349
|
-
|
|
1350
|
-
*Use "show mem_1" for details*
|
|
1351
|
-
```
|
|
1352
|
-
|
|
1353
|
-
### 6.2 Layer 2 포맷 (타임라인)
|
|
1354
|
-
|
|
1355
|
-
```markdown
|
|
1356
|
-
## Related Memories with Timeline
|
|
1357
|
-
|
|
1358
|
-
### Context around mem_1 (2026-01-30)
|
|
1359
|
-
|
|
1360
|
-
14:00 - User: "DB 스키마를 어떻게 설계할까?"
|
|
1361
|
-
14:05 - **[mem_1]** Assistant: "DuckDB를 사용하여 이벤트 소싱 패턴..."
|
|
1362
|
-
14:15 - User: "인덱스는 어떻게?"
|
|
1363
|
-
14:20 - Assistant: "다음 인덱스들을 추천..."
|
|
1364
|
-
```
|
|
1365
|
-
|
|
1366
|
-
### 6.3 Layer 3 포맷 (상세)
|
|
1367
|
-
|
|
1368
|
-
```markdown
|
|
1369
|
-
## Memory Detail: mem_1
|
|
1370
|
-
|
|
1371
|
-
**Session**: session_xyz | **Date**: 2026-01-30 14:05
|
|
1372
|
-
|
|
1373
|
-
### Content
|
|
1374
|
-
DuckDB를 사용하여 이벤트 소싱 패턴을 구현하는 방법을 설명드립니다.
|
|
1375
|
-
|
|
1376
|
-
1. events 테이블 생성:
|
|
1377
|
-
\`\`\`sql
|
|
1378
|
-
CREATE TABLE events (
|
|
1379
|
-
event_id VARCHAR PRIMARY KEY,
|
|
1380
|
-
...
|
|
1381
|
-
);
|
|
1382
|
-
\`\`\`
|
|
1383
|
-
|
|
1384
|
-
2. 인덱스 설계:
|
|
1385
|
-
- event_type별 인덱스
|
|
1386
|
-
- session_id별 인덱스
|
|
1387
|
-
...
|
|
1388
|
-
|
|
1389
|
-
**Related Files**: src/core/event-store.ts, src/core/types.ts
|
|
1390
|
-
**Tools Used**: Read, Write
|
|
1391
|
-
```
|
|
1392
|
-
|
|
1393
|
-
## 7. 캐싱 전략
|
|
1394
|
-
|
|
1395
|
-
### 7.1 Layer별 캐싱
|
|
1396
|
-
|
|
1397
|
-
```typescript
|
|
1398
|
-
interface CacheConfig {
|
|
1399
|
-
layer1: {
|
|
1400
|
-
ttl: 60 * 1000, // 1분 (검색 결과)
|
|
1401
|
-
maxSize: 100 // 최근 100개 쿼리
|
|
1402
|
-
};
|
|
1403
|
-
layer2: {
|
|
1404
|
-
ttl: 5 * 60 * 1000, // 5분 (타임라인)
|
|
1405
|
-
maxSize: 500
|
|
1406
|
-
};
|
|
1407
|
-
layer3: {
|
|
1408
|
-
ttl: 30 * 60 * 1000, // 30분 (상세 정보)
|
|
1409
|
-
maxSize: 200
|
|
1410
|
-
};
|
|
1411
|
-
}
|
|
1412
|
-
```
|
|
1413
|
-
|
|
1414
|
-
### 7.2 캐시 키
|
|
1415
|
-
|
|
1416
|
-
```typescript
|
|
1417
|
-
function getCacheKey(layer: number, params: unknown): string {
|
|
1418
|
-
switch (layer) {
|
|
1419
|
-
case 1:
|
|
1420
|
-
return `l1:${hash(params.query)}:${params.topK}`;
|
|
1421
|
-
case 2:
|
|
1422
|
-
return `l2:${params.targetIds.sort().join(',')}`;
|
|
1423
|
-
case 3:
|
|
1424
|
-
return `l3:${params.id}`;
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
```
|
|
1428
|
-
|
|
1429
|
-
## 8. 성공 기준
|
|
1430
|
-
|
|
1431
|
-
- [ ] Layer 1 검색이 100ms 이내 반환
|
|
1432
|
-
- [ ] Layer 2 타임라인이 200ms 이내 반환
|
|
1433
|
-
- [ ] Layer 3 상세가 500ms 이내 반환
|
|
1434
|
-
- [ ] 평균 토큰 사용량이 기존 대비 50% 이상 감소
|
|
1435
|
-
- [ ] 자동 확장이 적절한 경우에만 동작
|
|
1436
|
-
- [ ] 토큰 예산 내에서 최적의 결과 제공
|