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,415 @@
1
+ # Progressive Disclosure Specification
2
+
3
+ > **Version**: 1.0.0
4
+ > **Status**: Draft
5
+ > **Created**: 2026-02-01
6
+ > **Reference**: claude-mem (thedotmack/claude-mem)
7
+
8
+ ## 1. 개요
9
+
10
+ ### 1.1 문제 정의
11
+
12
+ 현재 시스템에서 메모리 검색 시 토큰 비효율 발생:
13
+
14
+ 1. **전체 로드 문제**: 검색 결과를 한 번에 모든 내용을 가져옴
15
+ 2. **토큰 낭비**: 관련 없는 내용도 컨텍스트에 포함
16
+ 3. **컨텍스트 한계**: 대용량 메모리 사용 시 토큰 초과
17
+
18
+ ### 1.2 해결 방향
19
+
20
+ **3-Layer Progressive Disclosure**:
21
+ - Layer 1: 검색 인덱스 (ID + 요약) - 최소 토큰
22
+ - Layer 2: 타임라인 컨텍스트 - 시간순 맥락
23
+ - Layer 3: 상세 정보 - 선택된 항목만 전체 로드
24
+
25
+ ## 2. 핵심 개념
26
+
27
+ ### 2.1 3-Layer 아키텍처
28
+
29
+ ```
30
+ ┌─────────────────────────────────────────────────────────────┐
31
+ │ User Query │
32
+ └─────────────────────────────────────────────────────────────┘
33
+
34
+
35
+ ┌─────────────────────────────────────────────────────────────┐
36
+ │ Layer 1: Search Index (~50-100 tokens per result) │
37
+ │ ┌──────────────────────────────────────────────────────┐ │
38
+ │ │ { id: "mem_1", summary: "파일 구조 설명", score: 0.95 } │ │
39
+ │ │ { id: "mem_2", summary: "타입 정의 논의", score: 0.87 } │ │
40
+ │ │ { id: "mem_3", summary: "버그 수정 방법", score: 0.82 } │ │
41
+ │ └──────────────────────────────────────────────────────┘ │
42
+ └─────────────────────────────────────────────────────────────┘
43
+
44
+ (선택적 확장)
45
+
46
+ ┌─────────────────────────────────────────────────────────────┐
47
+ │ Layer 2: Timeline Context (~200 tokens) │
48
+ │ ┌──────────────────────────────────────────────────────┐ │
49
+ │ │ 2026-01-30 14:00: "파일 구조 변경 결정" │ │
50
+ │ │ 2026-01-30 14:15: "types.ts 분리" ← mem_1 │ │
51
+ │ │ 2026-01-30 14:30: "테스트 작성" │ │
52
+ │ └──────────────────────────────────────────────────────┘ │
53
+ └─────────────────────────────────────────────────────────────┘
54
+
55
+ (필요 시만)
56
+
57
+ ┌─────────────────────────────────────────────────────────────┐
58
+ │ Layer 3: Full Details (~500-1000 tokens per result) │
59
+ │ ┌──────────────────────────────────────────────────────┐ │
60
+ │ │ mem_1: { │ │
61
+ │ │ content: "전체 대화 내용...", │ │
62
+ │ │ metadata: {...}, │ │
63
+ │ │ evidence: [...] │ │
64
+ │ │ } │ │
65
+ │ └──────────────────────────────────────────────────────┘ │
66
+ └─────────────────────────────────────────────────────────────┘
67
+ ```
68
+
69
+ ### 2.2 토큰 효율성
70
+
71
+ | 방식 | 5개 결과 토큰 | 20개 결과 토큰 |
72
+ |------|--------------|---------------|
73
+ | 기존 (전체 로드) | ~5,000 | ~20,000 |
74
+ | Progressive L1 | ~500 | ~2,000 |
75
+ | Progressive L1+L2 | ~700 | ~2,200 |
76
+ | Progressive L1+L2+L3 (2개) | ~1,700 | ~2,200 |
77
+
78
+ **예상 토큰 절약: ~10배**
79
+
80
+ ### 2.3 확장 트리거
81
+
82
+ ```typescript
83
+ type ExpansionTrigger =
84
+ | 'high_confidence' // score ≥ 0.92 → 자동 L3 확장
85
+ | 'user_request' // "자세히 알려줘" → L3 확장
86
+ | 'temporal_proximity' // 시간적 근접 → L2 확장
87
+ | 'explicit_id' // "mem_1 보여줘" → L3 확장
88
+ | 'ambiguity'; // 여러 유사 결과 → L2 확장
89
+ ```
90
+
91
+ ## 3. 데이터 스키마
92
+
93
+ ### 3.1 Layer 1: SearchIndex
94
+
95
+ ```typescript
96
+ const SearchIndexItemSchema = z.object({
97
+ id: z.string(), // 이벤트/메모리 ID
98
+ summary: z.string().max(100), // 한 줄 요약
99
+ score: z.number(), // 유사도 점수
100
+ type: z.enum(['prompt', 'response', 'tool', 'insight']),
101
+ timestamp: z.date(),
102
+ sessionId: z.string()
103
+ });
104
+
105
+ type SearchIndexItem = z.infer<typeof SearchIndexItemSchema>;
106
+
107
+ // 반환 예시
108
+ {
109
+ id: "evt_abc123",
110
+ summary: "DuckDB 스키마 설계 논의",
111
+ score: 0.94,
112
+ type: "response",
113
+ timestamp: "2026-01-30T14:00:00Z",
114
+ sessionId: "session_xyz"
115
+ }
116
+ ```
117
+
118
+ ### 3.2 Layer 2: TimelineContext
119
+
120
+ ```typescript
121
+ const TimelineItemSchema = z.object({
122
+ id: z.string(),
123
+ timestamp: z.date(),
124
+ type: z.enum(['prompt', 'response', 'tool', 'insight']),
125
+ preview: z.string().max(200), // 2-3문장 미리보기
126
+ isTarget: z.boolean() // 검색 결과에 해당하는지
127
+ });
128
+
129
+ type TimelineItem = z.infer<typeof TimelineItemSchema>;
130
+
131
+ // 반환 예시 (target ID 주변 ±3개)
132
+ [
133
+ { id: "evt_1", preview: "이전 대화...", isTarget: false },
134
+ { id: "evt_2", preview: "관련 질문...", isTarget: false },
135
+ { id: "evt_abc123", preview: "DuckDB 스키마...", isTarget: true }, // 타겟
136
+ { id: "evt_3", preview: "후속 논의...", isTarget: false }
137
+ ]
138
+ ```
139
+
140
+ ### 3.3 Layer 3: FullDetail
141
+
142
+ ```typescript
143
+ const FullDetailSchema = z.object({
144
+ id: z.string(),
145
+ content: z.string(), // 전체 내용
146
+ type: z.enum(['prompt', 'response', 'tool', 'insight']),
147
+ timestamp: z.date(),
148
+ sessionId: z.string(),
149
+
150
+ // 메타데이터
151
+ metadata: z.object({
152
+ tokenCount: z.number(),
153
+ hasCode: z.boolean(),
154
+ files: z.array(z.string()).optional(),
155
+ tools: z.array(z.string()).optional()
156
+ }),
157
+
158
+ // 관계 정보
159
+ relations: z.object({
160
+ parentId: z.string().optional(),
161
+ childIds: z.array(z.string()),
162
+ relatedIds: z.array(z.string())
163
+ }).optional()
164
+ });
165
+
166
+ type FullDetail = z.infer<typeof FullDetailSchema>;
167
+ ```
168
+
169
+ ## 4. API 인터페이스
170
+
171
+ ### 4.1 ProgressiveRetriever
172
+
173
+ ```typescript
174
+ interface ProgressiveRetriever {
175
+ // Layer 1: 검색 인덱스 반환
176
+ searchIndex(
177
+ query: string,
178
+ options?: {
179
+ topK?: number;
180
+ filter?: SearchFilter;
181
+ }
182
+ ): Promise<SearchIndexItem[]>;
183
+
184
+ // Layer 2: 타임라인 컨텍스트 반환
185
+ getTimeline(
186
+ targetIds: string[],
187
+ options?: {
188
+ windowSize?: number; // 앞뒤로 몇 개씩
189
+ }
190
+ ): Promise<TimelineItem[]>;
191
+
192
+ // Layer 3: 상세 정보 반환
193
+ getDetails(ids: string[]): Promise<FullDetail[]>;
194
+
195
+ // 편의 메서드: 자동 확장
196
+ smartSearch(
197
+ query: string,
198
+ options?: SmartSearchOptions
199
+ ): Promise<ProgressiveSearchResult>;
200
+ }
201
+ ```
202
+
203
+ ### 4.2 SmartSearch 옵션
204
+
205
+ ```typescript
206
+ interface SmartSearchOptions {
207
+ // Layer 1 설정
208
+ topK: number; // 기본: 10
209
+ minScore: number; // 기본: 0.7
210
+
211
+ // 자동 확장 설정
212
+ autoExpandTimeline: boolean; // 기본: true (score gap 클 때)
213
+ autoExpandDetails: boolean; // 기본: true (score ≥ 0.92)
214
+ maxAutoExpandCount: number; // 기본: 3
215
+
216
+ // 토큰 제한
217
+ maxTotalTokens: number; // 기본: 2000
218
+ }
219
+ ```
220
+
221
+ ### 4.3 ProgressiveSearchResult
222
+
223
+ ```typescript
224
+ interface ProgressiveSearchResult {
225
+ // Layer 1 (항상 포함)
226
+ index: SearchIndexItem[];
227
+
228
+ // Layer 2 (선택적)
229
+ timeline?: TimelineItem[];
230
+
231
+ // Layer 3 (선택적)
232
+ details?: FullDetail[];
233
+
234
+ // 메타정보
235
+ meta: {
236
+ totalMatches: number;
237
+ expandedCount: number;
238
+ estimatedTokens: number;
239
+ expansionReason?: string;
240
+ };
241
+ }
242
+ ```
243
+
244
+ ## 5. 확장 규칙
245
+
246
+ ### 5.1 자동 확장 조건
247
+
248
+ ```typescript
249
+ function shouldAutoExpand(results: SearchIndexItem[]): ExpansionDecision {
250
+ // Rule 1: 높은 신뢰도 단일 결과
251
+ if (results[0]?.score >= 0.92 && results.length === 1) {
252
+ return { expand: true, ids: [results[0].id], reason: 'high_confidence' };
253
+ }
254
+
255
+ // Rule 2: 명확한 1등 (2등과 gap이 큼)
256
+ if (results.length >= 2) {
257
+ const gap = results[0].score - results[1].score;
258
+ if (results[0].score >= 0.85 && gap >= 0.1) {
259
+ return { expand: true, ids: [results[0].id], reason: 'clear_winner' };
260
+ }
261
+ }
262
+
263
+ // Rule 3: 모호한 결과 → 타임라인만 확장
264
+ if (results.length >= 3 && results[2].score >= 0.8) {
265
+ return {
266
+ expand: true,
267
+ expandTimeline: true,
268
+ expandDetails: false,
269
+ ids: results.slice(0, 3).map(r => r.id),
270
+ reason: 'ambiguous_results'
271
+ };
272
+ }
273
+
274
+ // Rule 4: 낮은 점수 → 확장 안 함
275
+ return { expand: false, reason: 'low_confidence' };
276
+ }
277
+ ```
278
+
279
+ ### 5.2 토큰 예산 관리
280
+
281
+ ```typescript
282
+ function expandWithinBudget(
283
+ index: SearchIndexItem[],
284
+ budget: number
285
+ ): ProgressiveSearchResult {
286
+ let usedTokens = estimateTokens(index); // ~50-100 per item
287
+ const result: ProgressiveSearchResult = { index, meta: { ... } };
288
+
289
+ // 예산 내에서 확장
290
+ const sortedByScore = [...index].sort((a, b) => b.score - a.score);
291
+
292
+ for (const item of sortedByScore) {
293
+ if (usedTokens >= budget) break;
294
+
295
+ // 타임라인 추가 (~200 tokens)
296
+ if (usedTokens + 200 <= budget && !result.timeline) {
297
+ result.timeline = await getTimeline([item.id]);
298
+ usedTokens += estimateTokens(result.timeline);
299
+ }
300
+
301
+ // 상세 추가 (~500-1000 tokens)
302
+ if (item.score >= 0.85 && usedTokens + 800 <= budget) {
303
+ const detail = await getDetails([item.id]);
304
+ result.details = [...(result.details || []), ...detail];
305
+ usedTokens += estimateTokens(detail);
306
+ }
307
+ }
308
+
309
+ result.meta.estimatedTokens = usedTokens;
310
+ return result;
311
+ }
312
+ ```
313
+
314
+ ## 6. 컨텍스트 포맷
315
+
316
+ ### 6.1 Layer 1 포맷 (최소)
317
+
318
+ ```markdown
319
+ ## Related Memories (5 matches)
320
+
321
+ | ID | Summary | Score |
322
+ |----|---------|-------|
323
+ | mem_1 | DuckDB 스키마 설계 논의 | 0.94 |
324
+ | mem_2 | 타입 시스템 리팩토링 | 0.87 |
325
+ | mem_3 | 벡터 저장소 설정 | 0.82 |
326
+ | mem_4 | 테스트 코드 작성 | 0.78 |
327
+ | mem_5 | CI/CD 파이프라인 | 0.75 |
328
+
329
+ *Use "show mem_1" for details*
330
+ ```
331
+
332
+ ### 6.2 Layer 2 포맷 (타임라인)
333
+
334
+ ```markdown
335
+ ## Related Memories with Timeline
336
+
337
+ ### Context around mem_1 (2026-01-30)
338
+
339
+ 14:00 - User: "DB 스키마를 어떻게 설계할까?"
340
+ 14:05 - **[mem_1]** Assistant: "DuckDB를 사용하여 이벤트 소싱 패턴..."
341
+ 14:15 - User: "인덱스는 어떻게?"
342
+ 14:20 - Assistant: "다음 인덱스들을 추천..."
343
+ ```
344
+
345
+ ### 6.3 Layer 3 포맷 (상세)
346
+
347
+ ```markdown
348
+ ## Memory Detail: mem_1
349
+
350
+ **Session**: session_xyz | **Date**: 2026-01-30 14:05
351
+
352
+ ### Content
353
+ DuckDB를 사용하여 이벤트 소싱 패턴을 구현하는 방법을 설명드립니다.
354
+
355
+ 1. events 테이블 생성:
356
+ \`\`\`sql
357
+ CREATE TABLE events (
358
+ event_id VARCHAR PRIMARY KEY,
359
+ ...
360
+ );
361
+ \`\`\`
362
+
363
+ 2. 인덱스 설계:
364
+ - event_type별 인덱스
365
+ - session_id별 인덱스
366
+ ...
367
+
368
+ **Related Files**: src/core/event-store.ts, src/core/types.ts
369
+ **Tools Used**: Read, Write
370
+ ```
371
+
372
+ ## 7. 캐싱 전략
373
+
374
+ ### 7.1 Layer별 캐싱
375
+
376
+ ```typescript
377
+ interface CacheConfig {
378
+ layer1: {
379
+ ttl: 60 * 1000, // 1분 (검색 결과)
380
+ maxSize: 100 // 최근 100개 쿼리
381
+ };
382
+ layer2: {
383
+ ttl: 5 * 60 * 1000, // 5분 (타임라인)
384
+ maxSize: 500
385
+ };
386
+ layer3: {
387
+ ttl: 30 * 60 * 1000, // 30분 (상세 정보)
388
+ maxSize: 200
389
+ };
390
+ }
391
+ ```
392
+
393
+ ### 7.2 캐시 키
394
+
395
+ ```typescript
396
+ function getCacheKey(layer: number, params: unknown): string {
397
+ switch (layer) {
398
+ case 1:
399
+ return `l1:${hash(params.query)}:${params.topK}`;
400
+ case 2:
401
+ return `l2:${params.targetIds.sort().join(',')}`;
402
+ case 3:
403
+ return `l3:${params.id}`;
404
+ }
405
+ }
406
+ ```
407
+
408
+ ## 8. 성공 기준
409
+
410
+ - [ ] Layer 1 검색이 100ms 이내 반환
411
+ - [ ] Layer 2 타임라인이 200ms 이내 반환
412
+ - [ ] Layer 3 상세가 500ms 이내 반환
413
+ - [ ] 평균 토큰 사용량이 기존 대비 50% 이상 감소
414
+ - [ ] 자동 확장이 적절한 경우에만 동작
415
+ - [ ] 토큰 예산 내에서 최적의 결과 제공
@@ -0,0 +1,297 @@
1
+ # Task Entity System Context
2
+
3
+ > **Version**: 1.0.0
4
+ > **Created**: 2026-01-31
5
+
6
+ ## 1. 배경
7
+
8
+ ### 1.1 기존 설계의 한계
9
+
10
+ 현재 code-memory 시스템에서 Task는 `entries` 테이블에 저장되는 불변 기록으로 관리됩니다:
11
+
12
+ ```typescript
13
+ // 기존: entries 테이블에 Task를 저장
14
+ {
15
+ entry_id: "ent_abc123",
16
+ entry_type: "task",
17
+ title: "벡터 검색 구현",
18
+ content_json: { status: "in_progress", ... }
19
+ }
20
+ ```
21
+
22
+ **문제점**:
23
+
24
+ 1. **세션 A**에서 "벡터 검색 구현" Task 생성 → `ent_abc123`
25
+ 2. **세션 B**에서 같은 Task 언급 → `ent_def456` (새 entry 생성!)
26
+ 3. **세션 C**에서 "완료" 언급 → `ent_ghi789` (또 새 entry!)
27
+
28
+ 결과: 하나의 Task가 3개의 분리된 entry로 존재하며, 상태 추적 불가
29
+
30
+ ### 1.2 해결 방향
31
+
32
+ **Entry와 Entity 분리**:
33
+
34
+ | 구분 | Entry | Entity |
35
+ |------|-------|--------|
36
+ | 특성 | 불변 기록 | 상태 변화 개체 |
37
+ | 예시 | Fact, Decision, Insight | Task, Condition, Artifact |
38
+ | 생명주기 | 생성 후 변경 없음 | 이벤트로 상태 변화 |
39
+ | 식별 | UUID | canonical_key |
40
+
41
+ ## 2. Memo.txt 참고 사항
42
+
43
+ ### 2.1 핵심 원칙 (섹션 2)
44
+
45
+ > **5. Task는 entity**
46
+ > - Task 상태(status/priority/blockers)는 이벤트 fold 결과로 계산
47
+ > - 세션마다 Task entry를 새로 만들지 말고, 기존 task entity를 찾아 업데이트
48
+
49
+ ### 2.2 DB 스키마 (섹션 4.3)
50
+
51
+ ```sql
52
+ CREATE TABLE entities (
53
+ entity_id VARCHAR PRIMARY KEY,
54
+ entity_type VARCHAR NOT NULL, -- task|condition|artifact
55
+ canonical_key VARCHAR NOT NULL,
56
+ title VARCHAR NOT NULL,
57
+ stage VARCHAR NOT NULL,
58
+ status VARCHAR NOT NULL,
59
+ current_json JSON NOT NULL,
60
+ ...
61
+ );
62
+ ```
63
+
64
+ ### 2.3 Task 이벤트 타입 (섹션 7.2)
65
+
66
+ - `task_created`
67
+ - `task_status_changed`
68
+ - `task_priority_changed`
69
+ - `task_blockers_set` (mode=replace|suggest)
70
+ - `task_transition_rejected`
71
+
72
+ ### 2.4 BlockerResolver 규칙 (섹션 7.3)
73
+
74
+ 1. 강한 ID/URL/키 패턴 → artifact로 get-or-create
75
+ 2. 명시 task_id → task로 연결
76
+ 3. Task 제목 매칭 실패 → **condition으로 fallback** (스텁 Task 생성 금지)
77
+
78
+ ## 3. Idris2 영감 적용
79
+
80
+ ### 3.1 의존적 타입 개념
81
+
82
+ **Idris2의 Vector 타입**:
83
+ ```idris
84
+ -- 길이가 타입에 인코딩됨
85
+ data Vect : Nat -> Type -> Type where
86
+ Nil : Vect 0 a
87
+ (::) : a -> Vect n a -> Vect (S n) a
88
+ ```
89
+
90
+ **TypeScript 적용**:
91
+ ```typescript
92
+ // 상태에 따라 blockers 필드 타입이 달라짐
93
+ type TaskState =
94
+ | { status: 'blocked'; blockers: BlockerRef[] } // 필수, 1개 이상
95
+ | { status: 'done'; blockers?: never }; // 없어야 함
96
+ ```
97
+
98
+ ### 3.2 불변식 (Invariants)
99
+
100
+ **Idris2에서**:
101
+ ```idris
102
+ -- 타입 시스템이 강제
103
+ nonEmptyBlockers : (t : Task) -> t.status = Blocked -> NonEmpty t.blockers
104
+ ```
105
+
106
+ **TypeScript + Zod에서**:
107
+ ```typescript
108
+ // 런타임 검증
109
+ const BlockedTaskSchema = z.object({
110
+ status: z.literal('blocked'),
111
+ blockers: z.array(BlockerRefSchema).min(1) // 최소 1개 강제
112
+ });
113
+ ```
114
+
115
+ ### 3.3 왜 실제 Idris2를 사용하지 않는가?
116
+
117
+ **Memo.txt 섹션 11**:
118
+ > "지금은 Python 쪽 구현이 핵심이므로, Idris는 Candidate/Verified 래퍼 기반으로만 최소 수정"
119
+
120
+ **실용적 이유**:
121
+
122
+ 1. **학습 곡선**: 팀원 모두가 Idris2를 학습해야 함
123
+ 2. **도구 체인**: idris2 컴파일러 설치/관리 필요
124
+ 3. **통합 복잡도**: TypeScript ↔ Idris2 FFI 오버헤드
125
+ 4. **디버깅**: 두 언어 간 스택 트레이스 추적 어려움
126
+
127
+ **TypeScript로 충분한 이유**:
128
+
129
+ 1. **Discriminated Union**: 상태별 타입 분리 가능
130
+ 2. **Zod**: 런타임 검증으로 불변식 강제
131
+ 3. **타입 가드**: 조건부 타입 narrowing
132
+ 4. **생태계**: 풍부한 라이브러리와 도구
133
+
134
+ ## 4. 기존 코드와의 관계
135
+
136
+ ### 4.1 types.ts
137
+
138
+ 현재 정의된 타입 활용:
139
+
140
+ ```typescript
141
+ // 기존
142
+ export type MatchConfidence = 'high' | 'suggested' | 'none';
143
+ export const MATCH_THRESHOLDS = {
144
+ minCombinedScore: 0.92,
145
+ minGap: 0.03,
146
+ suggestionThreshold: 0.75
147
+ };
148
+
149
+ // 확장
150
+ export type EntityType = 'task' | 'condition' | 'artifact';
151
+ export type TaskStatus = 'pending' | 'in_progress' | 'blocked' | 'done' | 'cancelled';
152
+ ```
153
+
154
+ ### 4.2 canonical-key.ts
155
+
156
+ 현재 함수 확장:
157
+
158
+ ```typescript
159
+ // 기존: 이벤트용 canonical key
160
+ export function makeCanonicalKey(title: string, context?: {...}): string;
161
+
162
+ // 확장: 엔티티 타입별 canonical key
163
+ export function makeEntityCanonicalKey(
164
+ entityType: EntityType,
165
+ identifier: string,
166
+ context?: { project?: string }
167
+ ): string {
168
+ switch (entityType) {
169
+ case 'task':
170
+ return `task:${context?.project ?? 'default'}:${normalize(identifier)}`;
171
+ case 'condition':
172
+ return `cond:${context?.project ?? 'default'}:${normalize(identifier)}`;
173
+ case 'artifact':
174
+ return makeArtifactKey(identifier);
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### 4.3 event-store.ts
180
+
181
+ 현재 EventStore 활용:
182
+
183
+ ```typescript
184
+ // 기존 append 메서드 재활용
185
+ // Task 이벤트도 동일하게 append-only로 저장
186
+ const event = {
187
+ eventType: 'task_created',
188
+ sessionId,
189
+ content: JSON.stringify(payload),
190
+ ...
191
+ };
192
+ await eventStore.append(event);
193
+ ```
194
+
195
+ ### 4.4 matcher.ts
196
+
197
+ 현재 Matcher 로직 확장:
198
+
199
+ ```typescript
200
+ // 기존: 이벤트 매칭
201
+ export class Matcher {
202
+ matchSearchResults(results: SearchResult[]): MatchResult;
203
+ }
204
+
205
+ // 확장: Task 매칭에도 동일 로직 적용
206
+ export class TaskMatcher {
207
+ // MATCH_THRESHOLDS 재활용
208
+ findSimilar(title: string, project: string): MatchResult;
209
+ }
210
+ ```
211
+
212
+ ## 5. 경계 조건
213
+
214
+ ### 5.1 Unknown Blocker 처리
215
+
216
+ ```typescript
217
+ // Task가 blocked인데 blockedBy가 비어있으면
218
+ if (task.status === 'blocked' && blockedByTexts.length === 0) {
219
+ // 자동으로 placeholder condition 생성
220
+ const placeholder = await createCondition({
221
+ text: `Unknown blocker for ${task.title}`,
222
+ meta: { auto_placeholder: true }
223
+ });
224
+ blockers.push({ kind: 'condition', entity_id: placeholder.id });
225
+ }
226
+ ```
227
+
228
+ ### 5.2 상태 전이 거부
229
+
230
+ ```typescript
231
+ // pending → done 직접 전이 시
232
+ if (from === 'pending' && to === 'done') {
233
+ // task_transition_rejected 이벤트 발행
234
+ await eventStore.append({
235
+ eventType: 'task_transition_rejected',
236
+ content: JSON.stringify({
237
+ task_id: task.id,
238
+ from_status: 'pending',
239
+ to_status: 'done',
240
+ reason: 'Direct transition from pending to done is not allowed'
241
+ })
242
+ });
243
+
244
+ // in_progress로 보정
245
+ return 'in_progress';
246
+ }
247
+ ```
248
+
249
+ ### 5.3 Condition → Task 해결
250
+
251
+ ```typescript
252
+ // 나중에 "API 키 설정됨" condition이 실제 Task로 식별되면
253
+ await eventStore.append({
254
+ eventType: 'condition_resolved_to',
255
+ content: JSON.stringify({
256
+ condition_id: 'cond_xyz',
257
+ resolved_to: { kind: 'task', entity_id: 'task_abc' }
258
+ })
259
+ });
260
+ ```
261
+
262
+ ## 6. 성능 고려사항
263
+
264
+ ### 6.1 캐싱
265
+
266
+ ```typescript
267
+ // Entity 조회 캐시 (LRU)
268
+ const entityCache = new LRUCache<string, Entity>({
269
+ max: 1000,
270
+ ttl: 1000 * 60 * 5 // 5분
271
+ });
272
+ ```
273
+
274
+ ### 6.2 배치 처리
275
+
276
+ ```typescript
277
+ // Projector는 배치로 이벤트 처리
278
+ const BATCH_SIZE = 100;
279
+ const events = await eventStore.fetchSince(offset, { limit: BATCH_SIZE });
280
+ ```
281
+
282
+ ### 6.3 인덱스 활용
283
+
284
+ ```sql
285
+ -- FTS 검색용
286
+ CREATE INDEX idx_entities_search ON entities USING GIN(to_tsvector('english', search_text));
287
+
288
+ -- canonical_key 조회용
289
+ CREATE INDEX idx_entities_type_key ON entities(entity_type, canonical_key);
290
+ ```
291
+
292
+ ## 7. 참고 자료
293
+
294
+ - **Memo.txt**: AxiomMind Memory Graduation Pipeline 지시서
295
+ - **spec.md**: `src/core/types.ts` - 기존 타입 정의
296
+ - **AXIOMMIND 원칙**: Principle 5 - Task는 Entity
297
+ - **Idris2 개념**: Dependent types, Linear types