claude-memory-layer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.claude-plugin/commands/memory-forget.md +42 -0
  2. package/.claude-plugin/commands/memory-history.md +34 -0
  3. package/.claude-plugin/commands/memory-import.md +56 -0
  4. package/.claude-plugin/commands/memory-list.md +37 -0
  5. package/.claude-plugin/commands/memory-search.md +36 -0
  6. package/.claude-plugin/commands/memory-stats.md +34 -0
  7. package/.claude-plugin/hooks.json +59 -0
  8. package/.claude-plugin/plugin.json +24 -0
  9. package/.history/package_20260201112328.json +45 -0
  10. package/.history/package_20260201113602.json +45 -0
  11. package/.history/package_20260201113713.json +45 -0
  12. package/.history/package_20260201114110.json +45 -0
  13. package/Memo.txt +558 -0
  14. package/README.md +520 -0
  15. package/context.md +636 -0
  16. package/dist/.claude-plugin/commands/memory-forget.md +42 -0
  17. package/dist/.claude-plugin/commands/memory-history.md +34 -0
  18. package/dist/.claude-plugin/commands/memory-import.md +56 -0
  19. package/dist/.claude-plugin/commands/memory-list.md +37 -0
  20. package/dist/.claude-plugin/commands/memory-search.md +36 -0
  21. package/dist/.claude-plugin/commands/memory-stats.md +34 -0
  22. package/dist/.claude-plugin/hooks.json +59 -0
  23. package/dist/.claude-plugin/plugin.json +24 -0
  24. package/dist/cli/index.js +3539 -0
  25. package/dist/cli/index.js.map +7 -0
  26. package/dist/core/index.js +4408 -0
  27. package/dist/core/index.js.map +7 -0
  28. package/dist/hooks/session-end.js +2971 -0
  29. package/dist/hooks/session-end.js.map +7 -0
  30. package/dist/hooks/session-start.js +2969 -0
  31. package/dist/hooks/session-start.js.map +7 -0
  32. package/dist/hooks/stop.js +3123 -0
  33. package/dist/hooks/stop.js.map +7 -0
  34. package/dist/hooks/user-prompt-submit.js +2960 -0
  35. package/dist/hooks/user-prompt-submit.js.map +7 -0
  36. package/dist/services/memory-service.js +2931 -0
  37. package/dist/services/memory-service.js.map +7 -0
  38. package/package.json +45 -0
  39. package/plan.md +1642 -0
  40. package/scripts/build.ts +102 -0
  41. package/spec.md +624 -0
  42. package/specs/citations-system/context.md +243 -0
  43. package/specs/citations-system/plan.md +495 -0
  44. package/specs/citations-system/spec.md +371 -0
  45. package/specs/endless-mode/context.md +305 -0
  46. package/specs/endless-mode/plan.md +620 -0
  47. package/specs/endless-mode/spec.md +455 -0
  48. package/specs/entity-edge-model/context.md +401 -0
  49. package/specs/entity-edge-model/plan.md +459 -0
  50. package/specs/entity-edge-model/spec.md +391 -0
  51. package/specs/evidence-aligner-v2/context.md +401 -0
  52. package/specs/evidence-aligner-v2/plan.md +303 -0
  53. package/specs/evidence-aligner-v2/spec.md +312 -0
  54. package/specs/mcp-desktop-integration/context.md +278 -0
  55. package/specs/mcp-desktop-integration/plan.md +550 -0
  56. package/specs/mcp-desktop-integration/spec.md +494 -0
  57. package/specs/post-tool-use-hook/context.md +319 -0
  58. package/specs/post-tool-use-hook/plan.md +469 -0
  59. package/specs/post-tool-use-hook/spec.md +364 -0
  60. package/specs/private-tags/context.md +288 -0
  61. package/specs/private-tags/plan.md +412 -0
  62. package/specs/private-tags/spec.md +345 -0
  63. package/specs/progressive-disclosure/context.md +346 -0
  64. package/specs/progressive-disclosure/plan.md +663 -0
  65. package/specs/progressive-disclosure/spec.md +415 -0
  66. package/specs/task-entity-system/context.md +297 -0
  67. package/specs/task-entity-system/plan.md +301 -0
  68. package/specs/task-entity-system/spec.md +314 -0
  69. package/specs/vector-outbox-v2/context.md +470 -0
  70. package/specs/vector-outbox-v2/plan.md +562 -0
  71. package/specs/vector-outbox-v2/spec.md +466 -0
  72. package/specs/web-viewer-ui/context.md +384 -0
  73. package/specs/web-viewer-ui/plan.md +797 -0
  74. package/specs/web-viewer-ui/spec.md +516 -0
  75. package/src/cli/index.ts +570 -0
  76. package/src/core/canonical-key.ts +186 -0
  77. package/src/core/citation-generator.ts +63 -0
  78. package/src/core/consolidated-store.ts +279 -0
  79. package/src/core/consolidation-worker.ts +384 -0
  80. package/src/core/context-formatter.ts +276 -0
  81. package/src/core/continuity-manager.ts +336 -0
  82. package/src/core/edge-repo.ts +324 -0
  83. package/src/core/embedder.ts +124 -0
  84. package/src/core/entity-repo.ts +342 -0
  85. package/src/core/event-store.ts +672 -0
  86. package/src/core/evidence-aligner.ts +635 -0
  87. package/src/core/graduation.ts +365 -0
  88. package/src/core/index.ts +32 -0
  89. package/src/core/matcher.ts +210 -0
  90. package/src/core/metadata-extractor.ts +203 -0
  91. package/src/core/privacy/filter.ts +179 -0
  92. package/src/core/privacy/index.ts +20 -0
  93. package/src/core/privacy/tag-parser.ts +145 -0
  94. package/src/core/progressive-retriever.ts +415 -0
  95. package/src/core/retriever.ts +235 -0
  96. package/src/core/task/blocker-resolver.ts +325 -0
  97. package/src/core/task/index.ts +9 -0
  98. package/src/core/task/task-matcher.ts +238 -0
  99. package/src/core/task/task-projector.ts +345 -0
  100. package/src/core/task/task-resolver.ts +414 -0
  101. package/src/core/types.ts +841 -0
  102. package/src/core/vector-outbox.ts +295 -0
  103. package/src/core/vector-store.ts +182 -0
  104. package/src/core/vector-worker.ts +488 -0
  105. package/src/core/working-set-store.ts +244 -0
  106. package/src/hooks/post-tool-use.ts +127 -0
  107. package/src/hooks/session-end.ts +78 -0
  108. package/src/hooks/session-start.ts +57 -0
  109. package/src/hooks/stop.ts +78 -0
  110. package/src/hooks/user-prompt-submit.ts +54 -0
  111. package/src/mcp/handlers.ts +212 -0
  112. package/src/mcp/index.ts +47 -0
  113. package/src/mcp/tools.ts +78 -0
  114. package/src/server/api/citations.ts +101 -0
  115. package/src/server/api/events.ts +101 -0
  116. package/src/server/api/index.ts +18 -0
  117. package/src/server/api/search.ts +98 -0
  118. package/src/server/api/sessions.ts +111 -0
  119. package/src/server/api/stats.ts +97 -0
  120. package/src/server/index.ts +91 -0
  121. package/src/services/memory-service.ts +626 -0
  122. package/src/services/session-history-importer.ts +367 -0
  123. package/tests/canonical-key.test.ts +101 -0
  124. package/tests/evidence-aligner.test.ts +152 -0
  125. package/tests/matcher.test.ts +112 -0
  126. package/tsconfig.json +24 -0
  127. package/vitest.config.ts +15 -0
@@ -0,0 +1,412 @@
1
+ # Private Tags Implementation Plan
2
+
3
+ > **Version**: 1.0.0
4
+ > **Status**: Draft
5
+ > **Created**: 2026-02-01
6
+
7
+ ## Phase 1: 파서 구현 (P0)
8
+
9
+ ### 1.1 태그 파서
10
+
11
+ **파일**: `src/core/privacy/tag-parser.ts` (신규)
12
+
13
+ ```typescript
14
+ export interface PrivateSection {
15
+ start: number;
16
+ end: number;
17
+ content: string;
18
+ format: 'xml' | 'bracket' | 'comment';
19
+ }
20
+
21
+ export interface ParseResult {
22
+ filtered: string;
23
+ sections: PrivateSection[];
24
+ stats: {
25
+ count: number;
26
+ totalLength: number;
27
+ };
28
+ }
29
+
30
+ const TAG_PATTERNS: Record<string, RegExp> = {
31
+ xml: /<private>([\s\S]*?)<\/private>/gi,
32
+ bracket: /\[private\]([\s\S]*?)\[\/private\]/gi,
33
+ comment: /<!--\s*private\s*-->([\s\S]*?)<!--\s*\/private\s*-->/gi
34
+ };
35
+
36
+ export function parsePrivateTags(
37
+ text: string,
38
+ options: { formats: string[]; marker: string }
39
+ ): ParseResult {
40
+ const sections: PrivateSection[] = [];
41
+ let filtered = text;
42
+
43
+ for (const format of options.formats) {
44
+ const pattern = TAG_PATTERNS[format];
45
+ if (!pattern) continue;
46
+
47
+ let match;
48
+ // Reset lastIndex for global regex
49
+ pattern.lastIndex = 0;
50
+
51
+ while ((match = pattern.exec(text)) !== null) {
52
+ sections.push({
53
+ start: match.index,
54
+ end: match.index + match[0].length,
55
+ content: match[1],
56
+ format: format as PrivateSection['format']
57
+ });
58
+ }
59
+ }
60
+
61
+ // 모든 태그 제거 및 마커로 대체
62
+ for (const format of options.formats) {
63
+ const pattern = TAG_PATTERNS[format];
64
+ filtered = filtered.replace(pattern, (match, content) => {
65
+ // 빈 태그는 완전히 제거
66
+ if (!content.trim()) return '';
67
+ return options.marker;
68
+ });
69
+ }
70
+
71
+ return {
72
+ filtered,
73
+ sections,
74
+ stats: {
75
+ count: sections.length,
76
+ totalLength: sections.reduce((sum, s) => sum + s.content.length, 0)
77
+ }
78
+ };
79
+ }
80
+ ```
81
+
82
+ **작업 항목**:
83
+ - [ ] parsePrivateTags 함수 구현
84
+ - [ ] 각 포맷별 정규식 테스트
85
+ - [ ] 중첩 태그 처리
86
+
87
+ ### 1.2 코드 블록 보호
88
+
89
+ **파일**: `src/core/privacy/tag-parser.ts` 계속
90
+
91
+ ```typescript
92
+ export function parsePrivateTagsSafe(
93
+ text: string,
94
+ options: { formats: string[]; marker: string }
95
+ ): ParseResult {
96
+ // 1. 코드 블록 임시 치환
97
+ const codeBlocks: string[] = [];
98
+ const textWithPlaceholders = text.replace(
99
+ /```[\s\S]*?```/g,
100
+ (match) => {
101
+ codeBlocks.push(match);
102
+ return `__CODE_BLOCK_${codeBlocks.length - 1}__`;
103
+ }
104
+ );
105
+
106
+ // 2. private 태그 파싱
107
+ const result = parsePrivateTags(textWithPlaceholders, options);
108
+
109
+ // 3. 코드 블록 복원
110
+ result.filtered = result.filtered.replace(
111
+ /__CODE_BLOCK_(\d+)__/g,
112
+ (_, idx) => codeBlocks[Number(idx)]
113
+ );
114
+
115
+ return result;
116
+ }
117
+ ```
118
+
119
+ **작업 항목**:
120
+ - [ ] 코드 블록 감지 및 보호
121
+ - [ ] 인라인 코드 처리
122
+ - [ ] 복원 로직
123
+
124
+ ## Phase 2: 설정 통합 (P0)
125
+
126
+ ### 2.1 설정 스키마 확장
127
+
128
+ **파일**: `src/core/types.ts` 수정
129
+
130
+ ```typescript
131
+ export const PrivateTagsConfigSchema = z.object({
132
+ enabled: z.boolean().default(true),
133
+ marker: z.enum(['[PRIVATE]', '[REDACTED]', '']).default('[PRIVATE]'),
134
+ preserveLineCount: z.boolean().default(false),
135
+ supportedFormats: z.array(
136
+ z.enum(['xml', 'bracket', 'comment'])
137
+ ).default(['xml'])
138
+ });
139
+
140
+ // PrivacyConfigSchema 확장
141
+ export const PrivacyConfigSchema = z.object({
142
+ excludePatterns: z.array(z.string()).default([...]),
143
+ privateTags: PrivateTagsConfigSchema.optional(),
144
+ // ...
145
+ });
146
+ ```
147
+
148
+ **작업 항목**:
149
+ - [ ] PrivateTagsConfigSchema 추가
150
+ - [ ] 기본값 설정
151
+ - [ ] 설정 마이그레이션
152
+
153
+ ## Phase 3: 필터링 파이프라인 (P0)
154
+
155
+ ### 3.1 통합 필터
156
+
157
+ **파일**: `src/core/privacy/filter.ts` (신규 또는 확장)
158
+
159
+ ```typescript
160
+ export interface FilterResult {
161
+ content: string;
162
+ metadata: {
163
+ hasPrivateTags: boolean;
164
+ privateTagCount: number;
165
+ patternMatchCount: number;
166
+ originalLength: number;
167
+ filteredLength: number;
168
+ };
169
+ }
170
+
171
+ export function applyPrivacyFilter(
172
+ content: string,
173
+ config: PrivacyConfig
174
+ ): FilterResult {
175
+ let filtered = content;
176
+ let privateTagCount = 0;
177
+ let patternMatchCount = 0;
178
+
179
+ // 1. Private 태그 필터링
180
+ if (config.privateTags?.enabled) {
181
+ const tagResult = parsePrivateTagsSafe(filtered, {
182
+ formats: config.privateTags.supportedFormats,
183
+ marker: config.privateTags.marker
184
+ });
185
+ filtered = tagResult.filtered;
186
+ privateTagCount = tagResult.stats.count;
187
+ }
188
+
189
+ // 2. 패턴 기반 필터링
190
+ for (const pattern of config.excludePatterns) {
191
+ const regex = new RegExp(
192
+ `(${pattern})\\s*[:=]\\s*['"]?[^\\s'"]+`,
193
+ 'gi'
194
+ );
195
+ const matches = filtered.match(regex);
196
+ if (matches) {
197
+ patternMatchCount += matches.length;
198
+ filtered = filtered.replace(regex, '[REDACTED]');
199
+ }
200
+ }
201
+
202
+ // 3. 연속 마커 정리
203
+ filtered = filtered.replace(/(\[PRIVATE\]\s*)+/g, '[PRIVATE]\n');
204
+ filtered = filtered.replace(/(\[REDACTED\]\s*)+/g, '[REDACTED] ');
205
+
206
+ return {
207
+ content: filtered,
208
+ metadata: {
209
+ hasPrivateTags: privateTagCount > 0,
210
+ privateTagCount,
211
+ patternMatchCount,
212
+ originalLength: content.length,
213
+ filteredLength: filtered.length
214
+ }
215
+ };
216
+ }
217
+ ```
218
+
219
+ **작업 항목**:
220
+ - [ ] applyPrivacyFilter 함수 구현
221
+ - [ ] 태그 + 패턴 조합 필터링
222
+ - [ ] 마커 정리 로직
223
+
224
+ ### 3.2 훅 연동
225
+
226
+ **파일**: `src/hooks/stop.ts` 수정
227
+
228
+ ```typescript
229
+ import { applyPrivacyFilter } from '../core/privacy/filter';
230
+
231
+ export async function handleStop(input: StopInput): Promise<void> {
232
+ const memoryService = await MemoryService.getInstance();
233
+ const config = await memoryService.getConfig();
234
+
235
+ // 응답 내용 필터링
236
+ const filterResult = applyPrivacyFilter(
237
+ input.response_content,
238
+ config.privacy
239
+ );
240
+
241
+ // 필터링된 내용 저장
242
+ await memoryService.storeResponse({
243
+ content: filterResult.content,
244
+ privacy: filterResult.metadata
245
+ });
246
+ }
247
+ ```
248
+
249
+ **작업 항목**:
250
+ - [ ] stop 훅에 필터링 적용
251
+ - [ ] user-prompt-submit 훅에 필터링 적용
252
+ - [ ] 메타데이터 저장
253
+
254
+ ## Phase 4: UI 표시 (P1)
255
+
256
+ ### 4.1 CLI 출력
257
+
258
+ **파일**: `src/cli/commands/history.ts` 수정
259
+
260
+ ```typescript
261
+ function formatEventContent(event: Event): string {
262
+ const content = event.payload.content;
263
+
264
+ // [PRIVATE] 마커 강조
265
+ return content.replace(
266
+ /\[PRIVATE\]/g,
267
+ chalk.yellow('[🔒 PRIVATE]')
268
+ );
269
+ }
270
+ ```
271
+
272
+ **작업 항목**:
273
+ - [ ] CLI에서 마커 강조
274
+ - [ ] 통계 표시 옵션
275
+
276
+ ### 4.2 Web Viewer
277
+
278
+ **파일**: `src/ui/components/EventContent.ts` 수정
279
+
280
+ ```typescript
281
+ function EventContent({ content }) {
282
+ // [PRIVATE] 마커를 컴포넌트로 변환
283
+ const parts = content.split(/(\[PRIVATE\])/g);
284
+
285
+ return h('div', { class: 'event-content' },
286
+ parts.map(part =>
287
+ part === '[PRIVATE]'
288
+ ? h('span', { class: 'private-marker' }, '🔒 Private content')
289
+ : h('span', {}, part)
290
+ )
291
+ );
292
+ }
293
+ ```
294
+
295
+ **작업 항목**:
296
+ - [ ] 마커를 시각적 컴포넌트로 변환
297
+ - [ ] 툴팁 추가
298
+
299
+ ## Phase 5: 통계 및 모니터링 (P1)
300
+
301
+ ### 5.1 통계 수집
302
+
303
+ **파일**: `src/services/memory-service.ts` 수정
304
+
305
+ ```typescript
306
+ export class MemoryService {
307
+ async getPrivacyStats(): Promise<PrivacyStats> {
308
+ const events = await this.eventStore.query({
309
+ filter: { 'payload.privacy.hasPrivateTags': true }
310
+ });
311
+
312
+ return {
313
+ totalPrivateSections: events.reduce(
314
+ (sum, e) => sum + (e.payload.privacy?.privateTagCount || 0),
315
+ 0
316
+ ),
317
+ totalCharactersFiltered: events.reduce(
318
+ (sum, e) => sum + (
319
+ (e.payload.privacy?.originalLength || 0) -
320
+ (e.payload.privacy?.filteredLength || 0)
321
+ ),
322
+ 0
323
+ ),
324
+ sessionsWithPrivate: new Set(events.map(e => e.sessionId)).size
325
+ };
326
+ }
327
+ }
328
+ ```
329
+
330
+ **작업 항목**:
331
+ - [ ] 프라이버시 통계 수집
332
+ - [ ] Stats API에 추가
333
+ - [ ] 대시보드 표시
334
+
335
+ ## 파일 목록
336
+
337
+ ### 신규 파일
338
+ ```
339
+ src/core/privacy/tag-parser.ts # 태그 파서
340
+ src/core/privacy/filter.ts # 통합 필터 (기존 확장 가능)
341
+ ```
342
+
343
+ ### 수정 파일
344
+ ```
345
+ src/core/types.ts # 설정 스키마
346
+ src/hooks/stop.ts # 응답 필터링
347
+ src/hooks/user-prompt-submit.ts # 프롬프트 필터링
348
+ src/cli/commands/history.ts # CLI 표시
349
+ src/ui/components/EventContent.ts # Web 표시
350
+ src/services/memory-service.ts # 통계
351
+ ```
352
+
353
+ ## 테스트
354
+
355
+ ### 필수 테스트 케이스
356
+
357
+ 1. **기본 태그 파싱**
358
+ ```typescript
359
+ test('should remove private tag content', () => {
360
+ const result = parsePrivateTags(
361
+ 'before <private>secret</private> after',
362
+ { formats: ['xml'], marker: '[PRIVATE]' }
363
+ );
364
+ expect(result.filtered).toBe('before [PRIVATE] after');
365
+ });
366
+ ```
367
+
368
+ 2. **코드 블록 보호**
369
+ ```typescript
370
+ test('should not parse tags inside code blocks', () => {
371
+ const result = parsePrivateTagsSafe(
372
+ '```\n<private>code</private>\n```',
373
+ { formats: ['xml'], marker: '[PRIVATE]' }
374
+ );
375
+ expect(result.filtered).toContain('<private>code</private>');
376
+ });
377
+ ```
378
+
379
+ 3. **불완전한 태그**
380
+ ```typescript
381
+ test('should ignore incomplete tags', () => {
382
+ const result = parsePrivateTags(
383
+ '<private>no closing tag',
384
+ { formats: ['xml'], marker: '[PRIVATE]' }
385
+ );
386
+ expect(result.filtered).toBe('<private>no closing tag');
387
+ });
388
+ ```
389
+
390
+ 4. **빈 태그**
391
+ ```typescript
392
+ test('should remove empty tags completely', () => {
393
+ const result = parsePrivateTags(
394
+ 'text <private></private> more',
395
+ { formats: ['xml'], marker: '[PRIVATE]' }
396
+ );
397
+ expect(result.filtered).toBe('text more');
398
+ });
399
+ ```
400
+
401
+ ## 마일스톤
402
+
403
+ | 단계 | 완료 기준 |
404
+ |------|----------|
405
+ | M1 | 태그 파서 구현 |
406
+ | M2 | 코드 블록 보호 |
407
+ | M3 | 설정 통합 |
408
+ | M4 | 훅 연동 |
409
+ | M5 | CLI 표시 |
410
+ | M6 | Web 표시 |
411
+ | M7 | 통계 수집 |
412
+ | M8 | 테스트 통과 |