claude-memory-layer 1.0.26 → 1.0.28
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 +184 -41
- 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 +9 -5
- package/scripts/build.ts +25 -8
- package/scripts/generate-session-qrels.ts +126 -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/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 -418
- 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 -48
- 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 -29
- package/memory/user_prompt/uncategorized/2026-02-26.md +0 -25
- package/memory/user_prompt/uncategorized/2026-03-04.md +0 -634
- 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,1057 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
## 2026-02-25T12:31:26.376Z | 0bd11cc2-d4f5-4b9b-ac8b-213ffdd8f6c4
|
|
3
|
-
- type: session_summary
|
|
4
|
-
- session: import:organized
|
|
5
|
-
# Private Tags 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은 `<private>` 태그를 통한 명시적 프라이버시 제어 지원:
|
|
15
|
-
|
|
16
|
-
```markdown
|
|
17
|
-
<private>
|
|
18
|
-
API_KEY=sk-xxxx
|
|
19
|
-
</private>
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**특징**:
|
|
23
|
-
- 사용자가 직접 비공개 영역 지정
|
|
24
|
-
- 태그 내 내용은 저장되지 않음
|
|
25
|
-
- 간단하고 직관적인 문법
|
|
26
|
-
|
|
27
|
-
### 1.2 현재 code-memory의 상황
|
|
28
|
-
|
|
29
|
-
현재 패턴 기반 필터링만 지원:
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
const config = {
|
|
33
|
-
privacy: {
|
|
34
|
-
excludePatterns: ['password', 'secret', 'api_key']
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**한계**:
|
|
40
|
-
1. 고정된 패턴만 감지
|
|
41
|
-
2. 컨텍스트 무시 (실제 비밀번호 아닌 "password"도 필터링)
|
|
42
|
-
3. 사용자 의도 반영 불가
|
|
43
|
-
|
|
44
|
-
### 1.3 두 접근법 비교
|
|
45
|
-
|
|
46
|
-
| 패턴 기반 | 태그 기반 |
|
|
47
|
-
|----------|----------|
|
|
48
|
-
| 자동 감지 | 명시적 지정 |
|
|
49
|
-
| False positive 가능 | 정확한 제어 |
|
|
50
|
-
| 설정 필요 | 즉시 사용 |
|
|
51
|
-
| 패턴 외 누락 가능 | 사용자 책임 |
|
|
52
|
-
|
|
53
|
-
**결론**: 두 방식 병행이 최선
|
|
54
|
-
|
|
55
|
-
## 2. 태그 문법 선택
|
|
56
|
-
|
|
57
|
-
### 2.1 고려한 옵션들
|
|
58
|
-
|
|
59
|
-
| 옵션 | 예시 | 장점 | 단점 |
|
|
60
|
-
|------|------|------|------|
|
|
61
|
-
| XML 스타일 | `<private>...</private>` | 직관적, 중첩 가능 | Markdown과 충돌 가능 |
|
|
62
|
-
| 대괄호 | `[private]...[/private]` | Markdown 친화적 | 덜 직관적 |
|
|
63
|
-
| HTML 주석 | `<!-- private -->` | 렌더링 안 됨 | 복잡함 |
|
|
64
|
-
| 펜스 스타일 | `:::private\n...\n:::` | Markdown 확장 스타일 | 비표준 |
|
|
65
|
-
|
|
66
|
-
### 2.2 선택: XML 스타일 + 대안 지원
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
// 기본: XML 스타일
|
|
70
|
-
<private>...</private>
|
|
71
|
-
|
|
72
|
-
// 대안 1: 대괄호 (Markdown 문서용)
|
|
73
|
-
[private]...[/private]
|
|
74
|
-
|
|
75
|
-
// 대안 2: HTML 주석 (렌더링 방지)
|
|
76
|
-
<!-- private -->...<!-- /private -->
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### 2.3 claude-mem과의 호환성
|
|
80
|
-
|
|
81
|
-
claude-mem이 `<private>` 태그를 사용하므로 동일한 문법을 기본으로 채택하여 사용자 경험 일관성 유지.
|
|
82
|
-
|
|
83
|
-
## 3. 기존 코드와의 관계
|
|
84
|
-
|
|
85
|
-
### 3.1 types.ts
|
|
86
|
-
|
|
87
|
-
현재 Privacy 관련 타입:
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
// 현재
|
|
91
|
-
export const PrivacyConfigSchema = z.object({
|
|
92
|
-
excludePatterns: z.array(z.string()),
|
|
93
|
-
anonymize: z.boolean()
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// 확장
|
|
97
|
-
export const PrivacyConfigSchema = z.object({
|
|
98
|
-
excludePatterns: z.array(z.string()),
|
|
99
|
-
anonymize: z.boolean(),
|
|
100
|
-
privateTags: PrivateTagsConfigSchema // 추가
|
|
101
|
-
});
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### 3.2 훅 연동
|
|
105
|
-
|
|
106
|
-
영향받는 훅:
|
|
107
|
-
- `user-prompt-submit.ts`: 사용자 입력 필터링
|
|
108
|
-
- `stop.ts`: AI 응답 필터링
|
|
109
|
-
- `post-tool-use.ts`: 도구 출력 필터링
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
// 모든 훅에서 동일한 필터 사용
|
|
113
|
-
const filtered = applyPrivacyFilter(content, config.privacy);
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### 3.3 검색 영향
|
|
117
|
-
|
|
118
|
-
- **벡터 검색**: `[PRIVATE]` 마커가 임베딩에 포함되지만, 원본 내용은 검색 불가
|
|
119
|
-
- **전문 검색**: 마커는 검색 가능, 원본 내용 불가
|
|
120
|
-
|
|
121
|
-
## 4. 설계 결정 사항
|
|
122
|
-
|
|
123
|
-
### 4.1 마커 선택
|
|
124
|
-
|
|
125
|
-
**옵션들**:
|
|
126
|
-
1. `[PRIVATE]` - 명확하고 검색 가능
|
|
127
|
-
2. `[REDACTED]` - 일반적인 검열 용어
|
|
128
|
-
3. `""` (빈 문자열) - 흔적 없이 제거
|
|
129
|
-
4. `[...]` - 간결하지만 모호
|
|
130
|
-
|
|
131
|
-
**선택**: `[PRIVATE]`
|
|
132
|
-
- 명확한 의미 전달
|
|
133
|
-
- 검색/필터링 가능
|
|
134
|
-
- 설정으로 변경 가능
|
|
135
|
-
|
|
136
|
-
### 4.2 코드 블록 처리
|
|
137
|
-
|
|
138
|
-
**문제**: 코드 블록 내 `<private>` 태그를 리터럴로 취급해야 함
|
|
139
|
-
|
|
140
|
-
```markdown
|
|
141
|
-
```xml
|
|
142
|
-
<private>이것은 예시 코드입니다</private>
|
|
143
|
-
```
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
**해결**: 코드 블록을 먼저 추출하고 보호
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
// 1. 코드 블록 임시 치환
|
|
150
|
-
// 2. private 태그 파싱
|
|
151
|
-
// 3. 코드 블록 복원
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### 4.3 불완전한 태그 처리
|
|
155
|
-
|
|
156
|
-
**시나리오**:
|
|
157
|
-
```markdown
|
|
158
|
-
<private>
|
|
159
|
-
시작은 있지만 끝이 없음...
|
|
160
|
-
(사용자가 실수로 닫지 않음)
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
**옵션**:
|
|
164
|
-
1. 끝까지 private로 처리 → 데이터 손실 위험
|
|
165
|
-
2. 무시 (원본 유지) → 보수적, 안전
|
|
166
|
-
|
|
167
|
-
**선택**: 무시 (보수적 접근)
|
|
168
|
-
- 데이터 손실 방지
|
|
169
|
-
- 사용자에게 경고 표시 가능
|
|
170
|
-
|
|
171
|
-
### 4.4 중첩 태그 처리
|
|
172
|
-
|
|
173
|
-
```markdown
|
|
174
|
-
<private>
|
|
175
|
-
외부
|
|
176
|
-
<private>내부</private>
|
|
177
|
-
외부 계속
|
|
178
|
-
</private>
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**선택**: 중첩 지원하지 않음
|
|
182
|
-
- 외부 태그만 처리
|
|
183
|
-
- 복잡도 감소
|
|
184
|
-
- 실용적 케이스 드묾
|
|
185
|
-
|
|
186
|
-
## 5. 성능 고려사항
|
|
187
|
-
|
|
188
|
-
### 5.1 정규식 성능
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
// 비효율적 (매번 새 정규식)
|
|
192
|
-
for (const format of formats) {
|
|
193
|
-
const regex = new RegExp(...); // 매번 생성
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// 효율적 (캐싱)
|
|
197
|
-
const TAG_PATTERNS = {
|
|
198
|
-
xml: /<private>[\s\S]*?<\/private>/gi,
|
|
199
|
-
// ...
|
|
200
|
-
};
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### 5.2 대용량 텍스트
|
|
204
|
-
|
|
205
|
-
긴 텍스트의 경우:
|
|
206
|
-
- 정규식 `[\s\S]*?` 사용 (non-greedy)
|
|
207
|
-
- 스트리밍 파싱 고려 (향후)
|
|
208
|
-
|
|
209
|
-
### 5.3 캐싱
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
// 동일 입력에 대한 결과 캐싱
|
|
213
|
-
const filterCache = new LRUCache<string, FilterResult>({
|
|
214
|
-
max: 100,
|
|
215
|
-
ttl: 60000
|
|
216
|
-
});
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## 6. 보안 고려사항
|
|
220
|
-
|
|
221
|
-
### 6.1 태그 우회 시도
|
|
222
|
-
|
|
223
|
-
```markdown
|
|
224
|
-
<!-- 공격자가 태그를 깨뜨리려는 시도 -->
|
|
225
|
-
<private
|
|
226
|
-
>secret</private>
|
|
227
|
-
|
|
228
|
-
<pri
|
|
229
|
-
vate>secret</private>
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
**대응**: 엄격한 정규식 매칭 (정확한 `<private>` 패턴만)
|
|
233
|
-
|
|
234
|
-
### 6.2 메모리 내 노출
|
|
235
|
-
|
|
236
|
-
- 파싱 중 원본 내용이 메모리에 일시적으로 존재
|
|
237
|
-
- 디스크에는 저장되지 않음
|
|
238
|
-
- 로그에 원본 출력 금지
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
// 안전하지 않음
|
|
242
|
-
console.log(`Parsing: ${content}`);
|
|
243
|
-
|
|
244
|
-
// 안전
|
|
245
|
-
console.log(`Parsing content of length ${content.length}`);
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
## 7. 사용자 경험
|
|
249
|
-
|
|
250
|
-
### 7.1 문서화
|
|
251
|
-
|
|
252
|
-
```markdown
|
|
253
|
-
## Privacy Tags
|
|
254
|
-
|
|
255
|
-
Wrap sensitive content in `<private>` tags to prevent storage:
|
|
256
|
-
|
|
257
|
-
\`\`\`
|
|
258
|
-
<private>
|
|
259
|
-
Your sensitive data here
|
|
260
|
-
</private>
|
|
261
|
-
\`\`\`
|
|
262
|
-
|
|
263
|
-
Content inside these tags will NOT be stored in memory.
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### 7.2 피드백
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
// 훅에서 사용자에게 피드백
|
|
270
|
-
if (filterResult.metadata.privateTagCount > 0) {
|
|
271
|
-
return {
|
|
272
|
-
message: `🔒 ${filterResult.metadata.privateTagCount} private section(s) excluded from memory`
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### 7.3 경고
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
// 불완전한 태그 감지
|
|
281
|
-
if (hasUnmatchedOpenTag(content)) {
|
|
282
|
-
return {
|
|
283
|
-
warning: '⚠️ Unclosed <private> tag detected. Content was NOT filtered.'
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
## 8. 참고 자료
|
|
289
|
-
|
|
290
|
-
- **claude-mem README**: Privacy controls using `<private>` tags
|
|
291
|
-
- **OWASP**: Sensitive Data Exposure guidelines
|
|
292
|
-
- **GDPR**: Right to erasure (잊혀질 권리)
|
|
293
|
-
|
|
294
|
-
## 2026-02-25T12:31:26.383Z | a1b87a8c-0080-4962-a7a0-5074c74455a9
|
|
295
|
-
- type: session_summary
|
|
296
|
-
- session: import:organized
|
|
297
|
-
# Private Tags Implementation Plan
|
|
298
|
-
|
|
299
|
-
> **Version**: 1.0.0
|
|
300
|
-
> **Status**: Draft
|
|
301
|
-
> **Created**: 2026-02-01
|
|
302
|
-
|
|
303
|
-
## Phase 1: 파서 구현 (P0)
|
|
304
|
-
|
|
305
|
-
### 1.1 태그 파서
|
|
306
|
-
|
|
307
|
-
**파일**: `src/core/privacy/tag-parser.ts` (신규)
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
310
|
-
export interface PrivateSection {
|
|
311
|
-
start: number;
|
|
312
|
-
end: number;
|
|
313
|
-
content: string;
|
|
314
|
-
format: 'xml' | 'bracket' | 'comment';
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export interface ParseResult {
|
|
318
|
-
filtered: string;
|
|
319
|
-
sections: PrivateSection[];
|
|
320
|
-
stats: {
|
|
321
|
-
count: number;
|
|
322
|
-
totalLength: number;
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const TAG_PATTERNS: Record<string, RegExp> = {
|
|
327
|
-
xml: /<private>([\s\S]*?)<\/private>/gi,
|
|
328
|
-
bracket: /\[private\]([\s\S]*?)\[\/private\]/gi,
|
|
329
|
-
comment: /<!--\s*private\s*-->([\s\S]*?)<!--\s*\/private\s*-->/gi
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
export function parsePrivateTags(
|
|
333
|
-
text: string,
|
|
334
|
-
options: { formats: string[]; marker: string }
|
|
335
|
-
): ParseResult {
|
|
336
|
-
const sections: PrivateSection[] = [];
|
|
337
|
-
let filtered = text;
|
|
338
|
-
|
|
339
|
-
for (const format of options.formats) {
|
|
340
|
-
const pattern = TAG_PATTERNS[format];
|
|
341
|
-
if (!pattern) continue;
|
|
342
|
-
|
|
343
|
-
let match;
|
|
344
|
-
// Reset lastIndex for global regex
|
|
345
|
-
pattern.lastIndex = 0;
|
|
346
|
-
|
|
347
|
-
while ((match = pattern.exec(text)) !== null) {
|
|
348
|
-
sections.push({
|
|
349
|
-
start: match.index,
|
|
350
|
-
end: match.index + match[0].length,
|
|
351
|
-
content: match[1],
|
|
352
|
-
format: format as PrivateSection['format']
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// 모든 태그 제거 및 마커로 대체
|
|
358
|
-
for (const format of options.formats) {
|
|
359
|
-
const pattern = TAG_PATTERNS[format];
|
|
360
|
-
filtered = filtered.replace(pattern, (match, content) => {
|
|
361
|
-
// 빈 태그는 완전히 제거
|
|
362
|
-
if (!content.trim()) return '';
|
|
363
|
-
return options.marker;
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return {
|
|
368
|
-
filtered,
|
|
369
|
-
sections,
|
|
370
|
-
stats: {
|
|
371
|
-
count: sections.length,
|
|
372
|
-
totalLength: sections.reduce((sum, s) => sum + s.content.length, 0)
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
**작업 항목**:
|
|
379
|
-
- [ ] parsePrivateTags 함수 구현
|
|
380
|
-
- [ ] 각 포맷별 정규식 테스트
|
|
381
|
-
- [ ] 중첩 태그 처리
|
|
382
|
-
|
|
383
|
-
### 1.2 코드 블록 보호
|
|
384
|
-
|
|
385
|
-
**파일**: `src/core/privacy/tag-parser.ts` 계속
|
|
386
|
-
|
|
387
|
-
```typescript
|
|
388
|
-
export function parsePrivateTagsSafe(
|
|
389
|
-
text: string,
|
|
390
|
-
options: { formats: string[]; marker: string }
|
|
391
|
-
): ParseResult {
|
|
392
|
-
// 1. 코드 블록 임시 치환
|
|
393
|
-
const codeBlocks: string[] = [];
|
|
394
|
-
const textWithPlaceholders = text.replace(
|
|
395
|
-
/```[\s\S]*?```/g,
|
|
396
|
-
(match) => {
|
|
397
|
-
codeBlocks.push(match);
|
|
398
|
-
return `__CODE_BLOCK_${codeBlocks.length - 1}__`;
|
|
399
|
-
}
|
|
400
|
-
);
|
|
401
|
-
|
|
402
|
-
// 2. private 태그 파싱
|
|
403
|
-
const result = parsePrivateTags(textWithPlaceholders, options);
|
|
404
|
-
|
|
405
|
-
// 3. 코드 블록 복원
|
|
406
|
-
result.filtered = result.filtered.replace(
|
|
407
|
-
/__CODE_BLOCK_(\d+)__/g,
|
|
408
|
-
(_, idx) => codeBlocks[Number(idx)]
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
return result;
|
|
412
|
-
}
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
**작업 항목**:
|
|
416
|
-
- [ ] 코드 블록 감지 및 보호
|
|
417
|
-
- [ ] 인라인 코드 처리
|
|
418
|
-
- [ ] 복원 로직
|
|
419
|
-
|
|
420
|
-
## Phase 2: 설정 통합 (P0)
|
|
421
|
-
|
|
422
|
-
### 2.1 설정 스키마 확장
|
|
423
|
-
|
|
424
|
-
**파일**: `src/core/types.ts` 수정
|
|
425
|
-
|
|
426
|
-
```typescript
|
|
427
|
-
export const PrivateTagsConfigSchema = z.object({
|
|
428
|
-
enabled: z.boolean().default(true),
|
|
429
|
-
marker: z.enum(['[PRIVATE]', '[REDACTED]', '']).default('[PRIVATE]'),
|
|
430
|
-
preserveLineCount: z.boolean().default(false),
|
|
431
|
-
supportedFormats: z.array(
|
|
432
|
-
z.enum(['xml', 'bracket', 'comment'])
|
|
433
|
-
).default(['xml'])
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
// PrivacyConfigSchema 확장
|
|
437
|
-
export const PrivacyConfigSchema = z.object({
|
|
438
|
-
excludePatterns: z.array(z.string()).default([...]),
|
|
439
|
-
privateTags: PrivateTagsConfigSchema.optional(),
|
|
440
|
-
// ...
|
|
441
|
-
});
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
**작업 항목**:
|
|
445
|
-
- [ ] PrivateTagsConfigSchema 추가
|
|
446
|
-
- [ ] 기본값 설정
|
|
447
|
-
- [ ] 설정 마이그레이션
|
|
448
|
-
|
|
449
|
-
## Phase 3: 필터링 파이프라인 (P0)
|
|
450
|
-
|
|
451
|
-
### 3.1 통합 필터
|
|
452
|
-
|
|
453
|
-
**파일**: `src/core/privacy/filter.ts` (신규 또는 확장)
|
|
454
|
-
|
|
455
|
-
```typescript
|
|
456
|
-
export interface FilterResult {
|
|
457
|
-
content: string;
|
|
458
|
-
metadata: {
|
|
459
|
-
hasPrivateTags: boolean;
|
|
460
|
-
privateTagCount: number;
|
|
461
|
-
patternMatchCount: number;
|
|
462
|
-
originalLength: number;
|
|
463
|
-
filteredLength: number;
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export function applyPrivacyFilter(
|
|
468
|
-
content: string,
|
|
469
|
-
config: PrivacyConfig
|
|
470
|
-
): FilterResult {
|
|
471
|
-
let filtered = content;
|
|
472
|
-
let privateTagCount = 0;
|
|
473
|
-
let patternMatchCount = 0;
|
|
474
|
-
|
|
475
|
-
// 1. Private 태그 필터링
|
|
476
|
-
if (config.privateTags?.enabled) {
|
|
477
|
-
const tagResult = parsePrivateTagsSafe(filtered, {
|
|
478
|
-
formats: config.privateTags.supportedFormats,
|
|
479
|
-
marker: config.privateTags.marker
|
|
480
|
-
});
|
|
481
|
-
filtered = tagResult.filtered;
|
|
482
|
-
privateTagCount = tagResult.stats.count;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// 2. 패턴 기반 필터링
|
|
486
|
-
for (const pattern of config.excludePatterns) {
|
|
487
|
-
const regex = new RegExp(
|
|
488
|
-
`(${pattern})\\s*[:=]\\s*['"]?[^\\s'"]+`,
|
|
489
|
-
'gi'
|
|
490
|
-
);
|
|
491
|
-
const matches = filtered.match(regex);
|
|
492
|
-
if (matches) {
|
|
493
|
-
patternMatchCount += matches.length;
|
|
494
|
-
filtered = filtered.replace(regex, '[REDACTED]');
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// 3. 연속 마커 정리
|
|
499
|
-
filtered = filtered.replace(/(\[PRIVATE\]\s*)+/g, '[PRIVATE]\n');
|
|
500
|
-
filtered = filtered.replace(/(\[REDACTED\]\s*)+/g, '[REDACTED] ');
|
|
501
|
-
|
|
502
|
-
return {
|
|
503
|
-
content: filtered,
|
|
504
|
-
metadata: {
|
|
505
|
-
hasPrivateTags: privateTagCount > 0,
|
|
506
|
-
privateTagCount,
|
|
507
|
-
patternMatchCount,
|
|
508
|
-
originalLength: content.length,
|
|
509
|
-
filteredLength: filtered.length
|
|
510
|
-
}
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
**작업 항목**:
|
|
516
|
-
- [ ] applyPrivacyFilter 함수 구현
|
|
517
|
-
- [ ] 태그 + 패턴 조합 필터링
|
|
518
|
-
- [ ] 마커 정리 로직
|
|
519
|
-
|
|
520
|
-
### 3.2 훅 연동
|
|
521
|
-
|
|
522
|
-
**파일**: `src/hooks/stop.ts` 수정
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
import { applyPrivacyFilter } from '../core/privacy/filter';
|
|
526
|
-
|
|
527
|
-
export async function handleStop(input: StopInput): Promise<void> {
|
|
528
|
-
const memoryService = await MemoryService.getInstance();
|
|
529
|
-
const config = await memoryService.getConfig();
|
|
530
|
-
|
|
531
|
-
// 응답 내용 필터링
|
|
532
|
-
const filterResult = applyPrivacyFilter(
|
|
533
|
-
input.response_content,
|
|
534
|
-
config.privacy
|
|
535
|
-
);
|
|
536
|
-
|
|
537
|
-
// 필터링된 내용 저장
|
|
538
|
-
await memoryService.storeResponse({
|
|
539
|
-
content: filterResult.content,
|
|
540
|
-
privacy: filterResult.metadata
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
**작업 항목**:
|
|
546
|
-
- [ ] stop 훅에 필터링 적용
|
|
547
|
-
- [ ] user-prompt-submit 훅에 필터링 적용
|
|
548
|
-
- [ ] 메타데이터 저장
|
|
549
|
-
|
|
550
|
-
## Phase 4: UI 표시 (P1)
|
|
551
|
-
|
|
552
|
-
### 4.1 CLI 출력
|
|
553
|
-
|
|
554
|
-
**파일**: `src/cli/commands/history.ts` 수정
|
|
555
|
-
|
|
556
|
-
```typescript
|
|
557
|
-
function formatEventContent(event: Event): string {
|
|
558
|
-
const content = event.payload.content;
|
|
559
|
-
|
|
560
|
-
// [PRIVATE] 마커 강조
|
|
561
|
-
return content.replace(
|
|
562
|
-
/\[PRIVATE\]/g,
|
|
563
|
-
chalk.yellow('[🔒 PRIVATE]')
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
**작업 항목**:
|
|
569
|
-
- [ ] CLI에서 마커 강조
|
|
570
|
-
- [ ] 통계 표시 옵션
|
|
571
|
-
|
|
572
|
-
### 4.2 Web Viewer
|
|
573
|
-
|
|
574
|
-
**파일**: `src/ui/components/EventContent.ts` 수정
|
|
575
|
-
|
|
576
|
-
```typescript
|
|
577
|
-
function EventContent({ content }) {
|
|
578
|
-
// [PRIVATE] 마커를 컴포넌트로 변환
|
|
579
|
-
const parts = content.split(/(\[PRIVATE\])/g);
|
|
580
|
-
|
|
581
|
-
return h('div', { class: 'event-content' },
|
|
582
|
-
parts.map(part =>
|
|
583
|
-
part === '[PRIVATE]'
|
|
584
|
-
? h('span', { class: 'private-marker' }, '🔒 Private content')
|
|
585
|
-
: h('span', {}, part)
|
|
586
|
-
)
|
|
587
|
-
);
|
|
588
|
-
}
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
**작업 항목**:
|
|
592
|
-
- [ ] 마커를 시각적 컴포넌트로 변환
|
|
593
|
-
- [ ] 툴팁 추가
|
|
594
|
-
|
|
595
|
-
## Phase 5: 통계 및 모니터링 (P1)
|
|
596
|
-
|
|
597
|
-
### 5.1 통계 수집
|
|
598
|
-
|
|
599
|
-
**파일**: `src/services/memory-service.ts` 수정
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
export class MemoryService {
|
|
603
|
-
async getPrivacyStats(): Promise<PrivacyStats> {
|
|
604
|
-
const events = await this.eventStore.query({
|
|
605
|
-
filter: { 'payload.privacy.hasPrivateTags': true }
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
return {
|
|
609
|
-
totalPrivateSections: events.reduce(
|
|
610
|
-
(sum, e) => sum + (e.payload.privacy?.privateTagCount || 0),
|
|
611
|
-
0
|
|
612
|
-
),
|
|
613
|
-
totalCharactersFiltered: events.reduce(
|
|
614
|
-
(sum, e) => sum + (
|
|
615
|
-
(e.payload.privacy?.originalLength || 0) -
|
|
616
|
-
(e.payload.privacy?.filteredLength || 0)
|
|
617
|
-
),
|
|
618
|
-
0
|
|
619
|
-
),
|
|
620
|
-
sessionsWithPrivate: new Set(events.map(e => e.sessionId)).size
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
**작업 항목**:
|
|
627
|
-
- [ ] 프라이버시 통계 수집
|
|
628
|
-
- [ ] Stats API에 추가
|
|
629
|
-
- [ ] 대시보드 표시
|
|
630
|
-
|
|
631
|
-
## 파일 목록
|
|
632
|
-
|
|
633
|
-
### 신규 파일
|
|
634
|
-
```
|
|
635
|
-
src/core/privacy/tag-parser.ts # 태그 파서
|
|
636
|
-
src/core/privacy/filter.ts # 통합 필터 (기존 확장 가능)
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
### 수정 파일
|
|
640
|
-
```
|
|
641
|
-
src/core/types.ts # 설정 스키마
|
|
642
|
-
src/hooks/stop.ts # 응답 필터링
|
|
643
|
-
src/hooks/user-prompt-submit.ts # 프롬프트 필터링
|
|
644
|
-
src/cli/commands/history.ts # CLI 표시
|
|
645
|
-
src/ui/components/EventContent.ts # Web 표시
|
|
646
|
-
src/services/memory-service.ts # 통계
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
## 테스트
|
|
650
|
-
|
|
651
|
-
### 필수 테스트 케이스
|
|
652
|
-
|
|
653
|
-
1. **기본 태그 파싱**
|
|
654
|
-
```typescript
|
|
655
|
-
test('should remove private tag content', () => {
|
|
656
|
-
const result = parsePrivateTags(
|
|
657
|
-
'before <private>secret</private> after',
|
|
658
|
-
{ formats: ['xml'], marker: '[PRIVATE]' }
|
|
659
|
-
);
|
|
660
|
-
expect(result.filtered).toBe('before [PRIVATE] after');
|
|
661
|
-
});
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
2. **코드 블록 보호**
|
|
665
|
-
```typescript
|
|
666
|
-
test('should not parse tags inside code blocks', () => {
|
|
667
|
-
const result = parsePrivateTagsSafe(
|
|
668
|
-
'```\n<private>code</private>\n```',
|
|
669
|
-
{ formats: ['xml'], marker: '[PRIVATE]' }
|
|
670
|
-
);
|
|
671
|
-
expect(result.filtered).toContain('<private>code</private>');
|
|
672
|
-
});
|
|
673
|
-
```
|
|
674
|
-
|
|
675
|
-
3. **불완전한 태그**
|
|
676
|
-
```typescript
|
|
677
|
-
test('should ignore incomplete tags', () => {
|
|
678
|
-
const result = parsePrivateTags(
|
|
679
|
-
'<private>no closing tag',
|
|
680
|
-
{ formats: ['xml'], marker: '[PRIVATE]' }
|
|
681
|
-
);
|
|
682
|
-
expect(result.filtered).toBe('<private>no closing tag');
|
|
683
|
-
});
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
4. **빈 태그**
|
|
687
|
-
```typescript
|
|
688
|
-
test('should remove empty tags completely', () => {
|
|
689
|
-
const result = parsePrivateTags(
|
|
690
|
-
'text <private></private> more',
|
|
691
|
-
{ formats: ['xml'], marker: '[PRIVATE]' }
|
|
692
|
-
);
|
|
693
|
-
expect(result.filtered).toBe('text more');
|
|
694
|
-
});
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
## 마일스톤
|
|
698
|
-
|
|
699
|
-
| 단계 | 완료 기준 |
|
|
700
|
-
|------|----------|
|
|
701
|
-
| M1 | 태그 파서 구현 |
|
|
702
|
-
| M2 | 코드 블록 보호 |
|
|
703
|
-
| M3 | 설정 통합 |
|
|
704
|
-
| M4 | 훅 연동 |
|
|
705
|
-
| M5 | CLI 표시 |
|
|
706
|
-
| M6 | Web 표시 |
|
|
707
|
-
| M7 | 통계 수집 |
|
|
708
|
-
| M8 | 테스트 통과 |
|
|
709
|
-
|
|
710
|
-
## 2026-02-25T12:31:26.391Z | 42126f8a-c371-4f17-81e0-e1a7a739f342
|
|
711
|
-
- type: session_summary
|
|
712
|
-
- session: import:organized
|
|
713
|
-
# Private Tags Specification
|
|
714
|
-
|
|
715
|
-
> **Version**: 1.0.0
|
|
716
|
-
> **Status**: Draft
|
|
717
|
-
> **Created**: 2026-02-01
|
|
718
|
-
> **Reference**: claude-mem (thedotmack/claude-mem)
|
|
719
|
-
|
|
720
|
-
## 1. 개요
|
|
721
|
-
|
|
722
|
-
### 1.1 문제 정의
|
|
723
|
-
|
|
724
|
-
현재 프라이버시 필터링의 한계:
|
|
725
|
-
|
|
726
|
-
1. **패턴 기반만 지원**: `password`, `api_key` 등 고정 패턴만 필터링
|
|
727
|
-
2. **사용자 제어 부족**: 특정 내용을 명시적으로 제외할 방법 없음
|
|
728
|
-
3. **컨텍스트 무시**: 의도적으로 공유하고 싶지 않은 대화 부분 지정 불가
|
|
729
|
-
|
|
730
|
-
### 1.2 해결 방향
|
|
731
|
-
|
|
732
|
-
**명시적 `<private>` 태그 지원**:
|
|
733
|
-
- 사용자가 직접 비공개 영역 지정
|
|
734
|
-
- 태그 내 내용은 메모리에 저장되지 않음
|
|
735
|
-
- 패턴 기반 필터링과 병행
|
|
736
|
-
|
|
737
|
-
## 2. 핵심 개념
|
|
738
|
-
|
|
739
|
-
### 2.1 태그 문법
|
|
740
|
-
|
|
741
|
-
```markdown
|
|
742
|
-
이것은 저장됩니다.
|
|
743
|
-
|
|
744
|
-
<private>
|
|
745
|
-
이 부분은 메모리에 저장되지 않습니다.
|
|
746
|
-
API_KEY=sk-xxxx
|
|
747
|
-
SECRET_TOKEN=abc123
|
|
748
|
-
</private>
|
|
749
|
-
|
|
750
|
-
이것도 저장됩니다.
|
|
751
|
-
```
|
|
752
|
-
|
|
753
|
-
### 2.2 태그 변형
|
|
754
|
-
|
|
755
|
-
```typescript
|
|
756
|
-
// 지원하는 태그 형식
|
|
757
|
-
const PRIVATE_TAG_PATTERNS = [
|
|
758
|
-
/<private>[\s\S]*?<\/private>/gi, // 기본
|
|
759
|
-
/<private\s*\/>[\s\S]*?<\/private>/gi, // self-closing 시작
|
|
760
|
-
/\[private\][\s\S]*?\[\/private\]/gi, // 대괄호 형식
|
|
761
|
-
/<!--\s*private\s*-->[\s\S]*?<!--\s*\/private\s*-->/gi // HTML 주석 형식
|
|
762
|
-
];
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
### 2.3 중첩 처리
|
|
766
|
-
|
|
767
|
-
```markdown
|
|
768
|
-
<private>
|
|
769
|
-
외부 비공개
|
|
770
|
-
<private>
|
|
771
|
-
중첩된 비공개 (지원하지 않음 - 외부 태그만 처리)
|
|
772
|
-
</private>
|
|
773
|
-
내용 계속
|
|
774
|
-
</private>
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
## 3. 처리 로직
|
|
778
|
-
|
|
779
|
-
### 3.1 파싱 알고리즘
|
|
780
|
-
|
|
781
|
-
```typescript
|
|
782
|
-
interface PrivateSection {
|
|
783
|
-
start: number;
|
|
784
|
-
end: number;
|
|
785
|
-
content: string;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
function findPrivateSections(text: string): PrivateSection[] {
|
|
789
|
-
const sections: PrivateSection[] = [];
|
|
790
|
-
const regex = /<private>([\s\S]*?)<\/private>/gi;
|
|
791
|
-
|
|
792
|
-
let match;
|
|
793
|
-
while ((match = regex.exec(text)) !== null) {
|
|
794
|
-
sections.push({
|
|
795
|
-
start: match.index,
|
|
796
|
-
end: match.index + match[0].length,
|
|
797
|
-
content: match[1]
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
return sections;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
function removePrivateSections(text: string): string {
|
|
805
|
-
return text.replace(/<private>[\s\S]*?<\/private>/gi, '[PRIVATE]');
|
|
806
|
-
}
|
|
807
|
-
```
|
|
808
|
-
|
|
809
|
-
### 3.2 저장 전 필터링
|
|
810
|
-
|
|
811
|
-
```typescript
|
|
812
|
-
async function storeWithPrivacyFilter(content: string): Promise<string> {
|
|
813
|
-
// 1. <private> 태그 제거
|
|
814
|
-
let filtered = removePrivateSections(content);
|
|
815
|
-
|
|
816
|
-
// 2. 패턴 기반 필터링 (기존)
|
|
817
|
-
filtered = maskSensitivePatterns(filtered);
|
|
818
|
-
|
|
819
|
-
// 3. 빈 줄 정리
|
|
820
|
-
filtered = filtered.replace(/\n{3,}/g, '\n\n');
|
|
821
|
-
|
|
822
|
-
return filtered;
|
|
823
|
-
}
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
### 3.3 마커 옵션
|
|
827
|
-
|
|
828
|
-
```typescript
|
|
829
|
-
interface PrivacyConfig {
|
|
830
|
-
privateTag: {
|
|
831
|
-
enabled: boolean;
|
|
832
|
-
marker: '[PRIVATE]' | '[REDACTED]' | ''; // 대체 텍스트
|
|
833
|
-
preserveStructure: boolean; // 줄바꿈 유지 여부
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
// preserveStructure: true
|
|
838
|
-
"Before\n<private>\nSecret\nData\n</private>\nAfter"
|
|
839
|
-
→ "Before\n[PRIVATE]\n\n\nAfter"
|
|
840
|
-
|
|
841
|
-
// preserveStructure: false
|
|
842
|
-
"Before\n<private>\nSecret\nData\n</private>\nAfter"
|
|
843
|
-
→ "Before\n[PRIVATE]\nAfter"
|
|
844
|
-
```
|
|
845
|
-
|
|
846
|
-
## 4. 데이터 스키마
|
|
847
|
-
|
|
848
|
-
### 4.1 이벤트 메타데이터
|
|
849
|
-
|
|
850
|
-
```typescript
|
|
851
|
-
const EventPayloadSchema = z.object({
|
|
852
|
-
content: z.string(),
|
|
853
|
-
// 프라이버시 메타데이터 추가
|
|
854
|
-
privacy: z.object({
|
|
855
|
-
hasPrivateSections: z.boolean(),
|
|
856
|
-
privateCount: z.number(),
|
|
857
|
-
originalLength: z.number(),
|
|
858
|
-
filteredLength: z.number()
|
|
859
|
-
}).optional()
|
|
860
|
-
});
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
### 4.2 통계
|
|
864
|
-
|
|
865
|
-
```typescript
|
|
866
|
-
interface PrivacyStats {
|
|
867
|
-
totalPrivateSections: number;
|
|
868
|
-
totalCharactersFiltered: number;
|
|
869
|
-
sessionsWithPrivate: number;
|
|
870
|
-
}
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
## 5. 사용 시나리오
|
|
874
|
-
|
|
875
|
-
### 5.1 API 키 보호
|
|
876
|
-
|
|
877
|
-
```markdown
|
|
878
|
-
User: 이 API 키로 요청해줘
|
|
879
|
-
|
|
880
|
-
<private>
|
|
881
|
-
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxx
|
|
882
|
-
</private>
|
|
883
|
-
|
|
884
|
-
응답 형식은 JSON으로 해줘.
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
**저장 결과**:
|
|
888
|
-
```
|
|
889
|
-
User: 이 API 키로 요청해줘
|
|
890
|
-
|
|
891
|
-
[PRIVATE]
|
|
892
|
-
|
|
893
|
-
응답 형식은 JSON으로 해줘.
|
|
894
|
-
```
|
|
895
|
-
|
|
896
|
-
### 5.2 민감한 비즈니스 로직
|
|
897
|
-
|
|
898
|
-
```markdown
|
|
899
|
-
User: 다음 알고리즘을 최적화해줘
|
|
900
|
-
|
|
901
|
-
<private>
|
|
902
|
-
// 회사 기밀 알고리즘
|
|
903
|
-
function proprietaryAlgorithm() {
|
|
904
|
-
// ...
|
|
905
|
-
}
|
|
906
|
-
</private>
|
|
907
|
-
|
|
908
|
-
특히 시간 복잡도를 개선하고 싶어.
|
|
909
|
-
```
|
|
910
|
-
|
|
911
|
-
### 5.3 개인 정보
|
|
912
|
-
|
|
913
|
-
```markdown
|
|
914
|
-
User: 이메일 템플릿 작성해줘
|
|
915
|
-
|
|
916
|
-
<private>
|
|
917
|
-
받는 사람: john.doe@company.com
|
|
918
|
-
참조: secret-team@company.com
|
|
919
|
-
</private>
|
|
920
|
-
|
|
921
|
-
공식적인 톤으로 작성해줘.
|
|
922
|
-
```
|
|
923
|
-
|
|
924
|
-
## 6. 검색 영향
|
|
925
|
-
|
|
926
|
-
### 6.1 벡터 검색
|
|
927
|
-
|
|
928
|
-
- `[PRIVATE]` 마커는 임베딩에 포함
|
|
929
|
-
- 원본 private 내용은 검색 불가
|
|
930
|
-
- 주변 컨텍스트는 검색 가능
|
|
931
|
-
|
|
932
|
-
### 6.2 전문 검색 (FTS)
|
|
933
|
-
|
|
934
|
-
```sql
|
|
935
|
-
-- [PRIVATE] 마커 제외 검색
|
|
936
|
-
SELECT * FROM events_fts
|
|
937
|
-
WHERE content MATCH :query
|
|
938
|
-
AND content NOT LIKE '%[PRIVATE]%';
|
|
939
|
-
|
|
940
|
-
-- 또는 마커 포함 결과도 표시
|
|
941
|
-
SELECT * FROM events_fts
|
|
942
|
-
WHERE content MATCH :query;
|
|
943
|
-
```
|
|
944
|
-
|
|
945
|
-
## 7. UI 표시
|
|
946
|
-
|
|
947
|
-
### 7.1 CLI 출력
|
|
948
|
-
|
|
949
|
-
```
|
|
950
|
-
$ code-memory history
|
|
951
|
-
|
|
952
|
-
[2026-02-01 14:00] User Prompt
|
|
953
|
-
이 API 키로 요청해줘
|
|
954
|
-
[🔒 PRIVATE CONTENT REDACTED]
|
|
955
|
-
응답 형식은 JSON으로 해줘.
|
|
956
|
-
```
|
|
957
|
-
|
|
958
|
-
### 7.2 Web Viewer
|
|
959
|
-
|
|
960
|
-
```html
|
|
961
|
-
<div class="event-content">
|
|
962
|
-
<p>이 API 키로 요청해줘</p>
|
|
963
|
-
<div class="private-marker">
|
|
964
|
-
<span class="icon">🔒</span>
|
|
965
|
-
<span>Private content (not stored)</span>
|
|
966
|
-
</div>
|
|
967
|
-
<p>응답 형식은 JSON으로 해줘.</p>
|
|
968
|
-
</div>
|
|
969
|
-
```
|
|
970
|
-
|
|
971
|
-
## 8. 설정
|
|
972
|
-
|
|
973
|
-
### 8.1 설정 스키마
|
|
974
|
-
|
|
975
|
-
```typescript
|
|
976
|
-
const PrivacyConfigSchema = z.object({
|
|
977
|
-
// 기존 패턴 기반 필터링
|
|
978
|
-
excludePatterns: z.array(z.string()).default([
|
|
979
|
-
'password', 'secret', 'api_key', 'token', 'bearer'
|
|
980
|
-
]),
|
|
981
|
-
|
|
982
|
-
// 새로운 태그 기반 필터링
|
|
983
|
-
privateTags: z.object({
|
|
984
|
-
enabled: z.boolean().default(true),
|
|
985
|
-
marker: z.enum(['[PRIVATE]', '[REDACTED]', '']).default('[PRIVATE]'),
|
|
986
|
-
preserveLineCount: z.boolean().default(false),
|
|
987
|
-
supportedFormats: z.array(z.enum([
|
|
988
|
-
'xml', // <private>
|
|
989
|
-
'bracket', // [private]
|
|
990
|
-
'comment' // <!-- private -->
|
|
991
|
-
])).default(['xml'])
|
|
992
|
-
}),
|
|
993
|
-
|
|
994
|
-
// 자동 감지
|
|
995
|
-
autoDetect: z.object({
|
|
996
|
-
enabled: z.boolean().default(true),
|
|
997
|
-
patterns: z.array(z.string()) // 정규식
|
|
998
|
-
}).optional()
|
|
999
|
-
});
|
|
1000
|
-
```
|
|
1001
|
-
|
|
1002
|
-
### 8.2 설정 예시
|
|
1003
|
-
|
|
1004
|
-
```json
|
|
1005
|
-
{
|
|
1006
|
-
"privacy": {
|
|
1007
|
-
"excludePatterns": ["password", "secret", "api_key"],
|
|
1008
|
-
"privateTags": {
|
|
1009
|
-
"enabled": true,
|
|
1010
|
-
"marker": "[PRIVATE]",
|
|
1011
|
-
"supportedFormats": ["xml", "bracket"]
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
## 9. 경계 케이스
|
|
1018
|
-
|
|
1019
|
-
### 9.1 불완전한 태그
|
|
1020
|
-
|
|
1021
|
-
```markdown
|
|
1022
|
-
<private>
|
|
1023
|
-
시작은 있지만 끝이 없음
|
|
1024
|
-
```
|
|
1025
|
-
→ 끝까지 private로 처리? 또는 무시?
|
|
1026
|
-
|
|
1027
|
-
**결정**: 불완전한 태그는 무시 (보수적 접근)
|
|
1028
|
-
|
|
1029
|
-
### 9.2 코드 블록 내 태그
|
|
1030
|
-
|
|
1031
|
-
```markdown
|
|
1032
|
-
```python
|
|
1033
|
-
# 예시 코드
|
|
1034
|
-
print("<private>not actually private</private>")
|
|
1035
|
-
```
|
|
1036
|
-
```
|
|
1037
|
-
|
|
1038
|
-
**결정**: 코드 블록 내 태그는 무시 (리터럴로 취급)
|
|
1039
|
-
|
|
1040
|
-
### 9.3 빈 태그
|
|
1041
|
-
|
|
1042
|
-
```markdown
|
|
1043
|
-
<private></private>
|
|
1044
|
-
<private> </private>
|
|
1045
|
-
```
|
|
1046
|
-
|
|
1047
|
-
**결정**: 빈 태그는 완전히 제거 (마커도 남기지 않음)
|
|
1048
|
-
|
|
1049
|
-
## 10. 성공 기준
|
|
1050
|
-
|
|
1051
|
-
- [ ] `<private>` 태그 내 내용이 메모리에 저장되지 않음
|
|
1052
|
-
- [ ] `[PRIVATE]` 마커로 대체됨
|
|
1053
|
-
- [ ] 기존 패턴 기반 필터링과 병행 동작
|
|
1054
|
-
- [ ] 불완전한 태그 안전하게 처리
|
|
1055
|
-
- [ ] 코드 블록 내 태그 무시
|
|
1056
|
-
- [ ] 통계에 필터링 정보 포함
|
|
1057
|
-
- [ ] CLI와 Web UI에서 적절히 표시
|