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.
- package/.claude-plugin/commands/memory-forget.md +42 -0
- package/.claude-plugin/commands/memory-history.md +34 -0
- package/.claude-plugin/commands/memory-import.md +56 -0
- package/.claude-plugin/commands/memory-list.md +37 -0
- package/.claude-plugin/commands/memory-search.md +36 -0
- package/.claude-plugin/commands/memory-stats.md +34 -0
- package/.claude-plugin/hooks.json +59 -0
- package/.claude-plugin/plugin.json +24 -0
- package/.history/package_20260201112328.json +45 -0
- package/.history/package_20260201113602.json +45 -0
- package/.history/package_20260201113713.json +45 -0
- package/.history/package_20260201114110.json +45 -0
- package/Memo.txt +558 -0
- package/README.md +520 -0
- package/context.md +636 -0
- package/dist/.claude-plugin/commands/memory-forget.md +42 -0
- package/dist/.claude-plugin/commands/memory-history.md +34 -0
- package/dist/.claude-plugin/commands/memory-import.md +56 -0
- package/dist/.claude-plugin/commands/memory-list.md +37 -0
- package/dist/.claude-plugin/commands/memory-search.md +36 -0
- package/dist/.claude-plugin/commands/memory-stats.md +34 -0
- package/dist/.claude-plugin/hooks.json +59 -0
- package/dist/.claude-plugin/plugin.json +24 -0
- package/dist/cli/index.js +3539 -0
- package/dist/cli/index.js.map +7 -0
- package/dist/core/index.js +4408 -0
- package/dist/core/index.js.map +7 -0
- package/dist/hooks/session-end.js +2971 -0
- package/dist/hooks/session-end.js.map +7 -0
- package/dist/hooks/session-start.js +2969 -0
- package/dist/hooks/session-start.js.map +7 -0
- package/dist/hooks/stop.js +3123 -0
- package/dist/hooks/stop.js.map +7 -0
- package/dist/hooks/user-prompt-submit.js +2960 -0
- package/dist/hooks/user-prompt-submit.js.map +7 -0
- package/dist/services/memory-service.js +2931 -0
- package/dist/services/memory-service.js.map +7 -0
- package/package.json +45 -0
- package/plan.md +1642 -0
- package/scripts/build.ts +102 -0
- package/spec.md +624 -0
- package/specs/citations-system/context.md +243 -0
- package/specs/citations-system/plan.md +495 -0
- package/specs/citations-system/spec.md +371 -0
- package/specs/endless-mode/context.md +305 -0
- package/specs/endless-mode/plan.md +620 -0
- package/specs/endless-mode/spec.md +455 -0
- package/specs/entity-edge-model/context.md +401 -0
- package/specs/entity-edge-model/plan.md +459 -0
- package/specs/entity-edge-model/spec.md +391 -0
- package/specs/evidence-aligner-v2/context.md +401 -0
- package/specs/evidence-aligner-v2/plan.md +303 -0
- package/specs/evidence-aligner-v2/spec.md +312 -0
- package/specs/mcp-desktop-integration/context.md +278 -0
- package/specs/mcp-desktop-integration/plan.md +550 -0
- package/specs/mcp-desktop-integration/spec.md +494 -0
- package/specs/post-tool-use-hook/context.md +319 -0
- package/specs/post-tool-use-hook/plan.md +469 -0
- package/specs/post-tool-use-hook/spec.md +364 -0
- package/specs/private-tags/context.md +288 -0
- package/specs/private-tags/plan.md +412 -0
- package/specs/private-tags/spec.md +345 -0
- package/specs/progressive-disclosure/context.md +346 -0
- package/specs/progressive-disclosure/plan.md +663 -0
- package/specs/progressive-disclosure/spec.md +415 -0
- package/specs/task-entity-system/context.md +297 -0
- package/specs/task-entity-system/plan.md +301 -0
- package/specs/task-entity-system/spec.md +314 -0
- package/specs/vector-outbox-v2/context.md +470 -0
- package/specs/vector-outbox-v2/plan.md +562 -0
- package/specs/vector-outbox-v2/spec.md +466 -0
- package/specs/web-viewer-ui/context.md +384 -0
- package/specs/web-viewer-ui/plan.md +797 -0
- package/specs/web-viewer-ui/spec.md +516 -0
- package/src/cli/index.ts +570 -0
- package/src/core/canonical-key.ts +186 -0
- package/src/core/citation-generator.ts +63 -0
- package/src/core/consolidated-store.ts +279 -0
- package/src/core/consolidation-worker.ts +384 -0
- package/src/core/context-formatter.ts +276 -0
- package/src/core/continuity-manager.ts +336 -0
- package/src/core/edge-repo.ts +324 -0
- package/src/core/embedder.ts +124 -0
- package/src/core/entity-repo.ts +342 -0
- package/src/core/event-store.ts +672 -0
- package/src/core/evidence-aligner.ts +635 -0
- package/src/core/graduation.ts +365 -0
- package/src/core/index.ts +32 -0
- package/src/core/matcher.ts +210 -0
- package/src/core/metadata-extractor.ts +203 -0
- package/src/core/privacy/filter.ts +179 -0
- package/src/core/privacy/index.ts +20 -0
- package/src/core/privacy/tag-parser.ts +145 -0
- package/src/core/progressive-retriever.ts +415 -0
- package/src/core/retriever.ts +235 -0
- package/src/core/task/blocker-resolver.ts +325 -0
- package/src/core/task/index.ts +9 -0
- package/src/core/task/task-matcher.ts +238 -0
- package/src/core/task/task-projector.ts +345 -0
- package/src/core/task/task-resolver.ts +414 -0
- package/src/core/types.ts +841 -0
- package/src/core/vector-outbox.ts +295 -0
- package/src/core/vector-store.ts +182 -0
- package/src/core/vector-worker.ts +488 -0
- package/src/core/working-set-store.ts +244 -0
- package/src/hooks/post-tool-use.ts +127 -0
- package/src/hooks/session-end.ts +78 -0
- package/src/hooks/session-start.ts +57 -0
- package/src/hooks/stop.ts +78 -0
- package/src/hooks/user-prompt-submit.ts +54 -0
- package/src/mcp/handlers.ts +212 -0
- package/src/mcp/index.ts +47 -0
- package/src/mcp/tools.ts +78 -0
- package/src/server/api/citations.ts +101 -0
- package/src/server/api/events.ts +101 -0
- package/src/server/api/index.ts +18 -0
- package/src/server/api/search.ts +98 -0
- package/src/server/api/sessions.ts +111 -0
- package/src/server/api/stats.ts +97 -0
- package/src/server/index.ts +91 -0
- package/src/services/memory-service.ts +626 -0
- package/src/services/session-history-importer.ts +367 -0
- package/tests/canonical-key.test.ts +101 -0
- package/tests/evidence-aligner.test.ts +152 -0
- package/tests/matcher.test.ts +112 -0
- package/tsconfig.json +24 -0
- 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
|