claude-memory-layer 1.0.11 → 1.0.12

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 (99) hide show
  1. package/AGENTS.md +60 -0
  2. package/README.md +166 -2
  3. package/bootstrap-kb/decisions/decisions.md +244 -0
  4. package/bootstrap-kb/glossary/glossary.md +46 -0
  5. package/bootstrap-kb/modules/.claude-plugin.md +22 -0
  6. package/bootstrap-kb/modules/agents.md.md +15 -0
  7. package/bootstrap-kb/modules/claude.md.md +15 -0
  8. package/bootstrap-kb/modules/context.md.md +15 -0
  9. package/bootstrap-kb/modules/docs.md +18 -0
  10. package/bootstrap-kb/modules/handoff.md.md +15 -0
  11. package/bootstrap-kb/modules/package-lock.json.md +15 -0
  12. package/bootstrap-kb/modules/package.json.md +15 -0
  13. package/bootstrap-kb/modules/plan.md.md +15 -0
  14. package/bootstrap-kb/modules/readme.md.md +15 -0
  15. package/bootstrap-kb/modules/scripts.md +26 -0
  16. package/bootstrap-kb/modules/spec.md.md +15 -0
  17. package/bootstrap-kb/modules/specs.md +20 -0
  18. package/bootstrap-kb/modules/src.md +51 -0
  19. package/bootstrap-kb/modules/tests.md +42 -0
  20. package/bootstrap-kb/modules/tsconfig.json.md +15 -0
  21. package/bootstrap-kb/modules/vitest.config.ts.md +15 -0
  22. package/bootstrap-kb/overview/overview.md +40 -0
  23. package/bootstrap-kb/sources/manifest.json +950 -0
  24. package/bootstrap-kb/sources/manifest.md +227 -0
  25. package/bootstrap-kb/timeline/timeline.md +57 -0
  26. package/d.sh +3 -0
  27. package/deploy.sh +3 -0
  28. package/dist/cli/index.js +2389 -286
  29. package/dist/cli/index.js.map +4 -4
  30. package/dist/core/index.js +1017 -132
  31. package/dist/core/index.js.map +4 -4
  32. package/dist/hooks/post-tool-use.js +1347 -202
  33. package/dist/hooks/post-tool-use.js.map +4 -4
  34. package/dist/hooks/session-end.js +1339 -194
  35. package/dist/hooks/session-end.js.map +4 -4
  36. package/dist/hooks/session-start.js +1343 -198
  37. package/dist/hooks/session-start.js.map +4 -4
  38. package/dist/hooks/stop.js +1351 -206
  39. package/dist/hooks/stop.js.map +4 -4
  40. package/dist/hooks/user-prompt-submit.js +1347 -202
  41. package/dist/hooks/user-prompt-submit.js.map +4 -4
  42. package/dist/server/api/index.js +1436 -211
  43. package/dist/server/api/index.js.map +4 -4
  44. package/dist/server/index.js +1445 -220
  45. package/dist/server/index.js.map +4 -4
  46. package/dist/services/memory-service.js +1345 -199
  47. package/dist/services/memory-service.js.map +4 -4
  48. package/dist/ui/app.js +69 -2
  49. package/dist/ui/index.html +8 -0
  50. package/docs/MCP_MEMORY_SERVICE_COMPARATIVE_REVIEW.md +271 -0
  51. package/docs/MEMU_ADOPTION.md +40 -0
  52. package/memory/.claude-plugin/commands/2026-02-25.md +263 -0
  53. package/memory/_index.md +405 -0
  54. package/memory/default/uncategorized/2026-02-25.md +4839 -0
  55. package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +142 -0
  56. package/memory/specs/citations-system/2026-02-25.md +1121 -0
  57. package/memory/specs/endless-mode/2026-02-25.md +1392 -0
  58. package/memory/specs/entity-edge-model/2026-02-25.md +1263 -0
  59. package/memory/specs/evidence-aligner-v2/2026-02-25.md +1028 -0
  60. package/memory/specs/mcp-desktop-integration/2026-02-25.md +1334 -0
  61. package/memory/specs/post-tool-use-hook/2026-02-25.md +1164 -0
  62. package/memory/specs/private-tags/2026-02-25.md +1057 -0
  63. package/memory/specs/progressive-disclosure/2026-02-25.md +1436 -0
  64. package/memory/specs/task-entity-system/2026-02-25.md +924 -0
  65. package/memory/specs/vector-outbox-v2/2026-02-25.md +1510 -0
  66. package/memory/specs/web-viewer-ui/2026-02-25.md +1709 -0
  67. package/package.json +2 -1
  68. package/scripts/build.ts +6 -0
  69. package/src/cli/index.ts +281 -2
  70. package/src/core/consolidated-store.ts +63 -1
  71. package/src/core/consolidation-worker.ts +115 -6
  72. package/src/core/event-store.ts +14 -0
  73. package/src/core/index.ts +1 -0
  74. package/src/core/ingest-interceptor.ts +80 -0
  75. package/src/core/markdown-mirror.ts +70 -0
  76. package/src/core/md-mirror.ts +92 -0
  77. package/src/core/mongo-sync-config.ts +165 -0
  78. package/src/core/mongo-sync-worker.ts +381 -0
  79. package/src/core/retriever.ts +540 -150
  80. package/src/core/sqlite-event-store.ts +350 -1
  81. package/src/core/tag-taxonomy.ts +51 -0
  82. package/src/core/types.ts +28 -0
  83. package/src/server/api/health.ts +53 -0
  84. package/src/server/api/index.ts +3 -1
  85. package/src/server/api/stats.ts +46 -1
  86. package/src/services/bootstrap-organizer.ts +443 -0
  87. package/src/services/codex-session-history-importer.ts +474 -0
  88. package/src/services/memory-service.ts +373 -68
  89. package/src/ui/app.js +69 -2
  90. package/src/ui/index.html +8 -0
  91. package/tests/bootstrap-organizer.test.ts +111 -0
  92. package/tests/consolidation-worker.test.ts +75 -0
  93. package/tests/ingest-interceptor.test.ts +38 -0
  94. package/tests/markdown-mirror.test.ts +85 -0
  95. package/tests/md-mirror.test.ts +50 -0
  96. package/tests/retriever-fallback-chain.test.ts +223 -0
  97. package/tests/retriever-strategy-scope.test.ts +97 -0
  98. package/tests/retriever.memu-adoption.test.ts +122 -0
  99. package/tests/sqlite-event-store-replication.test.ts +92 -0
@@ -0,0 +1,924 @@
1
+
2
+ ## 2026-02-25T12:31:26.421Z | a9796b08-6c4a-480f-8c45-e0faae66a868
3
+ - type: session_summary
4
+ - session: import:organized
5
+ # Task Entity System Context
6
+
7
+ > **Version**: 1.0.0
8
+ > **Created**: 2026-01-31
9
+
10
+ ## 1. 배경
11
+
12
+ ### 1.1 기존 설계의 한계
13
+
14
+ 현재 code-memory 시스템에서 Task는 `entries` 테이블에 저장되는 불변 기록으로 관리됩니다:
15
+
16
+ ```typescript
17
+ // 기존: entries 테이블에 Task를 저장
18
+ {
19
+ entry_id: "ent_abc123",
20
+ entry_type: "task",
21
+ title: "벡터 검색 구현",
22
+ content_json: { status: "in_progress", ... }
23
+ }
24
+ ```
25
+
26
+ **문제점**:
27
+
28
+ 1. **세션 A**에서 "벡터 검색 구현" Task 생성 → `ent_abc123`
29
+ 2. **세션 B**에서 같은 Task 언급 → `ent_def456` (새 entry 생성!)
30
+ 3. **세션 C**에서 "완료" 언급 → `ent_ghi789` (또 새 entry!)
31
+
32
+ 결과: 하나의 Task가 3개의 분리된 entry로 존재하며, 상태 추적 불가
33
+
34
+ ### 1.2 해결 방향
35
+
36
+ **Entry와 Entity 분리**:
37
+
38
+ | 구분 | Entry | Entity |
39
+ |------|-------|--------|
40
+ | 특성 | 불변 기록 | 상태 변화 개체 |
41
+ | 예시 | Fact, Decision, Insight | Task, Condition, Artifact |
42
+ | 생명주기 | 생성 후 변경 없음 | 이벤트로 상태 변화 |
43
+ | 식별 | UUID | canonical_key |
44
+
45
+ ## 2. Memo.txt 참고 사항
46
+
47
+ ### 2.1 핵심 원칙 (섹션 2)
48
+
49
+ > **5. Task는 entity**
50
+ > - Task 상태(status/priority/blockers)는 이벤트 fold 결과로 계산
51
+ > - 세션마다 Task entry를 새로 만들지 말고, 기존 task entity를 찾아 업데이트
52
+
53
+ ### 2.2 DB 스키마 (섹션 4.3)
54
+
55
+ ```sql
56
+ CREATE TABLE entities (
57
+ entity_id VARCHAR PRIMARY KEY,
58
+ entity_type VARCHAR NOT NULL, -- task|condition|artifact
59
+ canonical_key VARCHAR NOT NULL,
60
+ title VARCHAR NOT NULL,
61
+ stage VARCHAR NOT NULL,
62
+ status VARCHAR NOT NULL,
63
+ current_json JSON NOT NULL,
64
+ ...
65
+ );
66
+ ```
67
+
68
+ ### 2.3 Task 이벤트 타입 (섹션 7.2)
69
+
70
+ - `task_created`
71
+ - `task_status_changed`
72
+ - `task_priority_changed`
73
+ - `task_blockers_set` (mode=replace|suggest)
74
+ - `task_transition_rejected`
75
+
76
+ ### 2.4 BlockerResolver 규칙 (섹션 7.3)
77
+
78
+ 1. 강한 ID/URL/키 패턴 → artifact로 get-or-create
79
+ 2. 명시 task_id → task로 연결
80
+ 3. Task 제목 매칭 실패 → **condition으로 fallback** (스텁 Task 생성 금지)
81
+
82
+ ## 3. Idris2 영감 적용
83
+
84
+ ### 3.1 의존적 타입 개념
85
+
86
+ **Idris2의 Vector 타입**:
87
+ ```idris
88
+ -- 길이가 타입에 인코딩됨
89
+ data Vect : Nat -> Type -> Type where
90
+ Nil : Vect 0 a
91
+ (::) : a -> Vect n a -> Vect (S n) a
92
+ ```
93
+
94
+ **TypeScript 적용**:
95
+ ```typescript
96
+ // 상태에 따라 blockers 필드 타입이 달라짐
97
+ type TaskState =
98
+ | { status: 'blocked'; blockers: BlockerRef[] } // 필수, 1개 이상
99
+ | { status: 'done'; blockers?: never }; // 없어야 함
100
+ ```
101
+
102
+ ### 3.2 불변식 (Invariants)
103
+
104
+ **Idris2에서**:
105
+ ```idris
106
+ -- 타입 시스템이 강제
107
+ nonEmptyBlockers : (t : Task) -> t.status = Blocked -> NonEmpty t.blockers
108
+ ```
109
+
110
+ **TypeScript + Zod에서**:
111
+ ```typescript
112
+ // 런타임 검증
113
+ const BlockedTaskSchema = z.object({
114
+ status: z.literal('blocked'),
115
+ blockers: z.array(BlockerRefSchema).min(1) // 최소 1개 강제
116
+ });
117
+ ```
118
+
119
+ ### 3.3 왜 실제 Idris2를 사용하지 않는가?
120
+
121
+ **Memo.txt 섹션 11**:
122
+ > "지금은 Python 쪽 구현이 핵심이므로, Idris는 Candidate/Verified 래퍼 기반으로만 최소 수정"
123
+
124
+ **실용적 이유**:
125
+
126
+ 1. **학습 곡선**: 팀원 모두가 Idris2를 학습해야 함
127
+ 2. **도구 체인**: idris2 컴파일러 설치/관리 필요
128
+ 3. **통합 복잡도**: TypeScript ↔ Idris2 FFI 오버헤드
129
+ 4. **디버깅**: 두 언어 간 스택 트레이스 추적 어려움
130
+
131
+ **TypeScript로 충분한 이유**:
132
+
133
+ 1. **Discriminated Union**: 상태별 타입 분리 가능
134
+ 2. **Zod**: 런타임 검증으로 불변식 강제
135
+ 3. **타입 가드**: 조건부 타입 narrowing
136
+ 4. **생태계**: 풍부한 라이브러리와 도구
137
+
138
+ ## 4. 기존 코드와의 관계
139
+
140
+ ### 4.1 types.ts
141
+
142
+ 현재 정의된 타입 활용:
143
+
144
+ ```typescript
145
+ // 기존
146
+ export type MatchConfidence = 'high' | 'suggested' | 'none';
147
+ export const MATCH_THRESHOLDS = {
148
+ minCombinedScore: 0.92,
149
+ minGap: 0.03,
150
+ suggestionThreshold: 0.75
151
+ };
152
+
153
+ // 확장
154
+ export type EntityType = 'task' | 'condition' | 'artifact';
155
+ export type TaskStatus = 'pending' | 'in_progress' | 'blocked' | 'done' | 'cancelled';
156
+ ```
157
+
158
+ ### 4.2 canonical-key.ts
159
+
160
+ 현재 함수 확장:
161
+
162
+ ```typescript
163
+ // 기존: 이벤트용 canonical key
164
+ export function makeCanonicalKey(title: string, context?: {...}): string;
165
+
166
+ // 확장: 엔티티 타입별 canonical key
167
+ export function makeEntityCanonicalKey(
168
+ entityType: EntityType,
169
+ identifier: string,
170
+ context?: { project?: string }
171
+ ): string {
172
+ switch (entityType) {
173
+ case 'task':
174
+ return `task:${context?.project ?? 'default'}:${normalize(identifier)}`;
175
+ case 'condition':
176
+ return `cond:${context?.project ?? 'default'}:${normalize(identifier)}`;
177
+ case 'artifact':
178
+ return makeArtifactKey(identifier);
179
+ }
180
+ }
181
+ ```
182
+
183
+ ### 4.3 event-store.ts
184
+
185
+ 현재 EventStore 활용:
186
+
187
+ ```typescript
188
+ // 기존 append 메서드 재활용
189
+ // Task 이벤트도 동일하게 append-only로 저장
190
+ const event = {
191
+ eventType: 'task_created',
192
+ sessionId,
193
+ content: JSON.stringify(payload),
194
+ ...
195
+ };
196
+ await eventStore.append(event);
197
+ ```
198
+
199
+ ### 4.4 matcher.ts
200
+
201
+ 현재 Matcher 로직 확장:
202
+
203
+ ```typescript
204
+ // 기존: 이벤트 매칭
205
+ export class Matcher {
206
+ matchSearchResults(results: SearchResult[]): MatchResult;
207
+ }
208
+
209
+ // 확장: Task 매칭에도 동일 로직 적용
210
+ export class TaskMatcher {
211
+ // MATCH_THRESHOLDS 재활용
212
+ findSimilar(title: string, project: string): MatchResult;
213
+ }
214
+ ```
215
+
216
+ ## 5. 경계 조건
217
+
218
+ ### 5.1 Unknown Blocker 처리
219
+
220
+ ```typescript
221
+ // Task가 blocked인데 blockedBy가 비어있으면
222
+ if (task.status === 'blocked' && blockedByTexts.length === 0) {
223
+ // 자동으로 placeholder condition 생성
224
+ const placeholder = await createCondition({
225
+ text: `Unknown blocker for ${task.title}`,
226
+ meta: { auto_placeholder: true }
227
+ });
228
+ blockers.push({ kind: 'condition', entity_id: placeholder.id });
229
+ }
230
+ ```
231
+
232
+ ### 5.2 상태 전이 거부
233
+
234
+ ```typescript
235
+ // pending → done 직접 전이 시
236
+ if (from === 'pending' && to === 'done') {
237
+ // task_transition_rejected 이벤트 발행
238
+ await eventStore.append({
239
+ eventType: 'task_transition_rejected',
240
+ content: JSON.stringify({
241
+ task_id: task.id,
242
+ from_status: 'pending',
243
+ to_status: 'done',
244
+ reason: 'Direct transition from pending to done is not allowed'
245
+ })
246
+ });
247
+
248
+ // in_progress로 보정
249
+ return 'in_progress';
250
+ }
251
+ ```
252
+
253
+ ### 5.3 Condition → Task 해결
254
+
255
+ ```typescript
256
+ // 나중에 "API 키 설정됨" condition이 실제 Task로 식별되면
257
+ await eventStore.append({
258
+ eventType: 'condition_resolved_to',
259
+ content: JSON.stringify({
260
+ condition_id: 'cond_xyz',
261
+ resolved_to: { kind: 'task', entity_id: 'task_abc' }
262
+ })
263
+ });
264
+ ```
265
+
266
+ ## 6. 성능 고려사항
267
+
268
+ ### 6.1 캐싱
269
+
270
+ ```typescript
271
+ // Entity 조회 캐시 (LRU)
272
+ const entityCache = new LRUCache<string, Entity>({
273
+ max: 1000,
274
+ ttl: 1000 * 60 * 5 // 5분
275
+ });
276
+ ```
277
+
278
+ ### 6.2 배치 처리
279
+
280
+ ```typescript
281
+ // Projector는 배치로 이벤트 처리
282
+ const BATCH_SIZE = 100;
283
+ const events = await eventStore.fetchSince(offset, { limit: BATCH_SIZE });
284
+ ```
285
+
286
+ ### 6.3 인덱스 활용
287
+
288
+ ```sql
289
+ -- FTS 검색용
290
+ CREATE INDEX idx_entities_search ON entities USING GIN(to_tsvector('english', search_text));
291
+
292
+ -- canonical_key 조회용
293
+ CREATE INDEX idx_entities_type_key ON entities(entity_type, canonical_key);
294
+ ```
295
+
296
+ ## 7. 참고 자료
297
+
298
+ - **Memo.txt**: AxiomMind Memory Graduation Pipeline 지시서
299
+ - **spec.md**: `src/core/types.ts` - 기존 타입 정의
300
+ - **AXIOMMIND 원칙**: Principle 5 - Task는 Entity
301
+ - **Idris2 개념**: Dependent types, Linear types
302
+
303
+ ## 2026-02-25T12:31:26.428Z | fb683857-09e2-4b9c-b8c4-d6aa9b77f614
304
+ - type: session_summary
305
+ - session: import:organized
306
+ # Task Entity System Implementation Plan
307
+
308
+ > **Version**: 1.0.0
309
+ > **Status**: Draft
310
+ > **Created**: 2026-01-31
311
+
312
+ ## Phase 1: 기반 구조 (P0)
313
+
314
+ ### 1.1 타입 정의
315
+
316
+ **파일**: `src/core/types.ts` 수정
317
+
318
+ ```typescript
319
+ // 추가할 타입들
320
+ export const EntityTypeSchema = z.enum(['task', 'condition', 'artifact']);
321
+ export type EntityType = z.infer<typeof EntityTypeSchema>;
322
+
323
+ export const TaskStatusSchema = z.enum(['pending', 'in_progress', 'blocked', 'done', 'cancelled']);
324
+ export type TaskStatus = z.infer<typeof TaskStatusSchema>;
325
+
326
+ export const TaskEventTypeSchema = z.enum([
327
+ 'task_created',
328
+ 'task_status_changed',
329
+ 'task_priority_changed',
330
+ 'task_blockers_set',
331
+ 'task_transition_rejected'
332
+ ]);
333
+ ```
334
+
335
+ **작업 항목**:
336
+ - [ ] EntityType, TaskStatus 스키마 추가
337
+ - [ ] TaskEvent 타입 정의
338
+ - [ ] BlockerRef 타입 정의
339
+ - [ ] TaskState Discriminated Union 정의
340
+
341
+ ### 1.2 DB 스키마 추가
342
+
343
+ **파일**: `src/core/schema.sql` 수정
344
+
345
+ **작업 항목**:
346
+ - [ ] entities 테이블 생성
347
+ - [ ] entity_aliases 테이블 생성
348
+ - [ ] edges 테이블 생성
349
+ - [ ] 인덱스 추가
350
+
351
+ ### 1.3 Canonical Key 확장
352
+
353
+ **파일**: `src/core/canonical-key.ts` 수정
354
+
355
+ ```typescript
356
+ // 추가할 함수
357
+ export function makeEntityCanonicalKey(
358
+ entityType: EntityType,
359
+ identifier: string,
360
+ context?: { project?: string }
361
+ ): string;
362
+ ```
363
+
364
+ **작업 항목**:
365
+ - [ ] Task canonical key 함수
366
+ - [ ] Condition canonical key 함수
367
+ - [ ] Artifact canonical key 함수 (URL, JIRA, GitHub 패턴)
368
+
369
+ ## Phase 2: 핵심 컴포넌트 (P0)
370
+
371
+ ### 2.1 EntityRepository
372
+
373
+ **파일**: `src/core/entity-repo.ts` (신규)
374
+
375
+ ```typescript
376
+ export class EntityRepository {
377
+ constructor(private db: Database);
378
+
379
+ // CRUD (Create, Read만 - append-only 철학)
380
+ async create(entity: EntityInput): Promise<Entity>;
381
+ async findById(entityId: string): Promise<Entity | null>;
382
+ async findByCanonicalKey(type: EntityType, key: string): Promise<Entity | null>;
383
+
384
+ // 검색
385
+ async findSimilar(type: EntityType, searchText: string): Promise<Entity[]>;
386
+
387
+ // Alias 관리
388
+ async addAlias(entityId: string, alias: string): Promise<void>;
389
+ async resolveAlias(type: EntityType, alias: string): Promise<string | null>;
390
+
391
+ // 상태 업데이트 (이벤트 발행 후 projector가 호출)
392
+ async updateCurrentState(entityId: string, state: EntityState): Promise<void>;
393
+ }
394
+ ```
395
+
396
+ **작업 항목**:
397
+ - [ ] EntityRepository 클래스 구현
398
+ - [ ] findByCanonicalKey 구현
399
+ - [ ] findSimilar 구현 (FTS 활용)
400
+ - [ ] alias 관리 메서드 구현
401
+
402
+ ### 2.2 TaskMatcher
403
+
404
+ **파일**: `src/core/task-matcher.ts` (신규)
405
+
406
+ ```typescript
407
+ export class TaskMatcher {
408
+ constructor(
409
+ private entityRepo: EntityRepository,
410
+ private vectorStore: VectorStore
411
+ );
412
+
413
+ async findExact(canonicalKey: string): Promise<Task | null>;
414
+ async findSimilar(title: string, project: string): Promise<MatchResult>;
415
+ async suggestCandidates(title: string, project: string, limit?: number): Promise<Task[]>;
416
+ }
417
+ ```
418
+
419
+ **작업 항목**:
420
+ - [ ] 정확 매칭 (entity_aliases 활용)
421
+ - [ ] FTS 기반 유사 매칭
422
+ - [ ] Vector 기반 semantic 매칭
423
+ - [ ] 점수 계산 (stage weight, status weight, recency)
424
+ - [ ] strict 확정 로직 (score >= 0.92, gap >= 0.03)
425
+
426
+ ### 2.3 BlockerResolver
427
+
428
+ **파일**: `src/core/blocker-resolver.ts` (신규)
429
+
430
+ **작업 항목**:
431
+ - [ ] URL/JIRA/GitHub 패턴 감지
432
+ - [ ] Artifact get-or-create 로직
433
+ - [ ] Task 제목 매칭 시도
434
+ - [ ] Condition fallback 생성
435
+ - [ ] candidates 저장 로직
436
+
437
+ ### 2.4 TaskResolver
438
+
439
+ **파일**: `src/core/task-resolver.ts` (신규)
440
+
441
+ **작업 항목**:
442
+ - [ ] Task entry 처리 로직
443
+ - [ ] 기존 Task 찾기 (TaskMatcher 활용)
444
+ - [ ] 이벤트 발행 로직
445
+ - [ ] 상태 전이 검증
446
+ - [ ] blockers 정규화 (BlockerResolver 활용)
447
+
448
+ ## Phase 3: Projection (P0)
449
+
450
+ ### 3.1 TaskProjector
451
+
452
+ **파일**: `src/core/task-projector.ts` (신규)
453
+
454
+ ```typescript
455
+ export class TaskProjector {
456
+ constructor(
457
+ private eventStore: EventStore,
458
+ private entityRepo: EntityRepository,
459
+ private edgeRepo: EdgeRepository
460
+ );
461
+
462
+ async projectIncremental(): Promise<ProjectionResult>;
463
+ async rebuild(): Promise<void>;
464
+
465
+ private async handleTaskCreated(event: TaskEvent): Promise<void>;
466
+ private async handleStatusChanged(event: TaskEvent): Promise<void>;
467
+ private async handleBlockersSet(event: TaskEvent): Promise<void>;
468
+ }
469
+ ```
470
+
471
+ **작업 항목**:
472
+ - [ ] projection_offsets 관리
473
+ - [ ] 증분 이벤트 읽기
474
+ - [ ] 이벤트 타입별 핸들러
475
+ - [ ] mode=replace 처리 (기존 edge 삭제 + 새 edge 삽입)
476
+ - [ ] mode=suggest 처리 (suggested edge만)
477
+ - [ ] current_json 갱신
478
+
479
+ ### 3.2 EdgeRepository
480
+
481
+ **파일**: `src/core/edge-repo.ts` (신규)
482
+
483
+ ```typescript
484
+ export class EdgeRepository {
485
+ async createEdge(edge: EdgeInput): Promise<Edge>;
486
+ async findEdges(srcId: string, relType?: string): Promise<Edge[]>;
487
+ async deleteEdges(srcId: string, relType: string): Promise<number>;
488
+ async replaceEdges(srcId: string, relType: string, newEdges: EdgeInput[]): Promise<void>;
489
+ }
490
+ ```
491
+
492
+ **작업 항목**:
493
+ - [ ] Edge CRUD 구현
494
+ - [ ] 관계 타입별 조회
495
+ - [ ] replace 로직 (트랜잭션)
496
+
497
+ ## Phase 4: 통합 (P0)
498
+
499
+ ### 4.1 Graduation Pipeline 연동
500
+
501
+ **파일**: `src/core/graduation.ts` 수정
502
+
503
+ **작업 항목**:
504
+ - [ ] Task entry 처리 시 TaskResolver 호출
505
+ - [ ] 이벤트 발행 후 TaskProjector 호출
506
+ - [ ] evidence edge 생성 로직
507
+
508
+ ### 4.2 EventStore 확장
509
+
510
+ **파일**: `src/core/event-store.ts` 수정
511
+
512
+ **작업 항목**:
513
+ - [ ] Task 이벤트 타입 지원
514
+ - [ ] fetch_since 메서드 추가 (projector용)
515
+ - [ ] replay 메서드 추가 (rebuild용)
516
+
517
+ ## Phase 5: CLI 및 조회 (P1)
518
+
519
+ ### 5.1 조회 API
520
+
521
+ **작업 항목**:
522
+ - [ ] list_blocked_tasks()
523
+ - [ ] list_tasks_with_only_suggested_blockers()
524
+ - [ ] get_task_detail(task_id)
525
+ - [ ] v_task_blockers_effective 뷰
526
+
527
+ ### 5.2 CLI 커맨드
528
+
529
+ **파일**: `src/cli/index.ts` 수정
530
+
531
+ **작업 항목**:
532
+ - [ ] `cli blocked` - blocked task 목록
533
+ - [ ] `cli task show <task_id>` - task 상세
534
+ - [ ] `cli tasks --status <status>` - 상태별 목록
535
+
536
+ ## 파일 목록
537
+
538
+ ### 신규 파일
539
+ ```
540
+ src/core/entity-repo.ts
541
+ src/core/edge-repo.ts
542
+ src/core/task-matcher.ts
543
+ src/core/blocker-resolver.ts
544
+ src/core/task-resolver.ts
545
+ src/core/task-projector.ts
546
+ ```
547
+
548
+ ### 수정 파일
549
+ ```
550
+ src/core/types.ts
551
+ src/core/canonical-key.ts
552
+ src/core/event-store.ts
553
+ src/core/graduation.ts
554
+ src/cli/index.ts
555
+ ```
556
+
557
+ ## 테스트
558
+
559
+ ### 필수 테스트 케이스
560
+
561
+ 1. **Task 동일성**
562
+ - 같은 제목의 Task가 여러 세션에서 언급되어도 하나의 Entity로 관리
563
+
564
+ 2. **상태 전이**
565
+ - pending → done 직접 전이 시 에러
566
+ - blocked 상태에서 blockers가 비어있으면 에러
567
+
568
+ 3. **Idempotency**
569
+ - 동일 세션 재처리 시 중복 이벤트 없음
570
+ - 동일 edge 중복 생성 없음
571
+
572
+ 4. **Blocker 해결**
573
+ - URL → Artifact 정상 생성
574
+ - Task 제목 매칭 실패 → Condition 생성 + candidates 저장
575
+
576
+ ## 의존성 그래프
577
+
578
+ ```
579
+ types.ts
580
+
581
+ ├── entity-repo.ts
582
+ │ │
583
+ │ ├── task-matcher.ts
584
+ │ │
585
+ │ └── edge-repo.ts
586
+ │ │
587
+ │ └── task-projector.ts
588
+
589
+ ├── blocker-resolver.ts ─────────┐
590
+ │ │
591
+ └── task-resolver.ts ◀───────────┘
592
+
593
+ └── graduation.ts
594
+ ```
595
+
596
+ ## 마일스톤
597
+
598
+ | 단계 | 완료 기준 |
599
+ |------|----------|
600
+ | M1 | 타입 정의 + DB 스키마 |
601
+ | M2 | EntityRepo + EdgeRepo 동작 |
602
+ | M3 | TaskMatcher 동작 (정확 매칭만) |
603
+ | M4 | BlockerResolver + TaskResolver 동작 |
604
+ | M5 | TaskProjector 동작 (증분 처리) |
605
+ | M6 | Graduation 연동 완료 |
606
+ | M7 | CLI 조회 커맨드 완료 |
607
+
608
+ ## 2026-02-25T12:31:26.435Z | fc140be7-1e67-4a23-822b-e83e23b295c0
609
+ - type: session_summary
610
+ - session: import:organized
611
+ # Task Entity System Specification
612
+
613
+ > **Version**: 1.0.0
614
+ > **Status**: Draft
615
+ > **Created**: 2026-01-31
616
+
617
+ ## 1. 개요
618
+
619
+ ### 1.1 문제 정의
620
+
621
+ 현재 시스템에서 Task는 세션별로 entry(기록)로 저장되어 다음 문제가 발생:
622
+
623
+ 1. **중복 생성**: 같은 Task가 여러 세션에서 언급될 때마다 새 entry 생성
624
+ 2. **상태 단절**: Task 상태 변경 이력이 세션 단위로 분리되어 추적 불가
625
+ 3. **관계 손실**: Task 간 blockedBy 관계가 세션 경계에서 단절
626
+
627
+ ### 1.2 해결 방향
628
+
629
+ **Task를 Entity로 승격**:
630
+ - Task는 고유한 Entity로 관리 (entries와 분리된 entities 테이블)
631
+ - 상태 변경은 이벤트로 기록 (event-sourced)
632
+ - fold 연산으로 현재 상태 계산
633
+
634
+ ## 2. 핵심 개념
635
+
636
+ ### 2.1 Entity vs Entry
637
+
638
+ | 구분 | Entry (기존) | Entity (신규) |
639
+ |------|-------------|--------------|
640
+ | 정의 | 세션에서 추출된 불변 기록 | 시간에 따라 상태가 변화하는 개체 |
641
+ | 예시 | Fact, Decision, Insight | Task, Condition, Artifact |
642
+ | 식별 | entry_id (UUID) | entity_id + canonical_key |
643
+ | 상태 | 불변 (created once) | 이벤트 fold로 계산 |
644
+
645
+ ### 2.2 Canonical Key
646
+
647
+ Entity의 동일성을 판단하는 정규화된 키:
648
+
649
+ ```typescript
650
+ // Task
651
+ task:{project}:{normalize(title)}
652
+ // 예: task:code-memory:implement-vector-search
653
+
654
+ // Condition
655
+ cond:{project}:{normalize(text)}
656
+ // 예: cond:code-memory:api-key-configured
657
+
658
+ // Artifact
659
+ art:url:{sha1(url)} // URL
660
+ art:jira:{key} // JIRA
661
+ art:gh_issue:{repo}:{num} // GitHub Issue
662
+ ```
663
+
664
+ ### 2.3 Task 상태 머신
665
+
666
+ ```
667
+ ┌─────────────────────────────────────┐
668
+ │ │
669
+ ▼ │
670
+ ┌─────────┐ ┌───────────┐ ┌────────┴───┐
671
+ │ pending │────▶│in_progress│────▶│ done │
672
+ └────┬────┘ └─────┬─────┘ └────────────┘
673
+ │ │
674
+ │ ▼
675
+ │ ┌──────────┐
676
+ └─────────▶│ blocked │
677
+ └──────────┘
678
+
679
+ │ (blockers 해결 시)
680
+
681
+ ┌──────────┐
682
+ │in_progress│
683
+ └──────────┘
684
+ ```
685
+
686
+ **불변식 (Invariants)**:
687
+ - `blocked` 상태면 `blockers[]` 비어있으면 안 됨
688
+ - `done` 상태면 `blockers[]` 비어있어야 함
689
+ - `pending` → `done` 직접 전이 금지 (in_progress 거쳐야 함)
690
+
691
+ ## 3. 이벤트 타입
692
+
693
+ ### 3.1 Task 이벤트
694
+
695
+ ```typescript
696
+ type TaskEventType =
697
+ | 'task_created' // 신규 생성
698
+ | 'task_status_changed' // 상태 변경
699
+ | 'task_priority_changed' // 우선순위 변경
700
+ | 'task_blockers_set' // blockers 설정/변경
701
+ | 'task_transition_rejected'; // 전이 거부 (디버깅용)
702
+ ```
703
+
704
+ ### 3.2 이벤트 페이로드 스키마
705
+
706
+ ```typescript
707
+ // task_created
708
+ interface TaskCreatedPayload {
709
+ task_id: string;
710
+ canonical_key: string;
711
+ title: string;
712
+ initial_status: 'pending' | 'in_progress'; // done 금지
713
+ priority?: 'low' | 'medium' | 'high' | 'critical';
714
+ source_entry_id: string;
715
+ }
716
+
717
+ // task_status_changed
718
+ interface TaskStatusChangedPayload {
719
+ task_id: string;
720
+ from_status: TaskStatus;
721
+ to_status: TaskStatus;
722
+ reason?: string;
723
+ }
724
+
725
+ // task_blockers_set
726
+ interface TaskBlockersSetPayload {
727
+ task_id: string;
728
+ mode: 'replace' | 'suggest';
729
+ blockers: BlockerRef[];
730
+ source_entry_id?: string;
731
+ }
732
+ ```
733
+
734
+ ## 4. 컴포넌트 설계
735
+
736
+ ### 4.1 TaskMatcher
737
+
738
+ 기존 Task 찾기:
739
+
740
+ ```typescript
741
+ interface TaskMatcher {
742
+ // 정확한 매칭
743
+ findExact(canonicalKey: string): Task | null;
744
+
745
+ // 유사도 기반 매칭
746
+ findSimilar(title: string, project: string): MatchResult;
747
+
748
+ // 후보 목록 반환
749
+ suggestCandidates(title: string, project: string, limit?: number): Task[];
750
+ }
751
+
752
+ // 매칭 조건 (strict 확정)
753
+ const STRICT_MATCH = {
754
+ minScore: 0.92,
755
+ minGap: 0.03, // top1 - top2
756
+ status: ['active'], // cancelled 제외
757
+ taskStatus: ['pending', 'in_progress', 'blocked'] // done 제외
758
+ };
759
+ ```
760
+
761
+ ### 4.2 BlockerResolver
762
+
763
+ blockedBy 텍스트를 Entity 참조로 변환:
764
+
765
+ ```typescript
766
+ interface BlockerRef {
767
+ kind: 'task' | 'condition' | 'artifact';
768
+ entity_id: string;
769
+ raw_text: string;
770
+ confidence: 'high' | 'suggested' | 'none';
771
+ candidates?: EntityRef[]; // confidence='none'일 때
772
+ }
773
+
774
+ interface BlockerResolver {
775
+ resolve(
776
+ blockedByTexts: string[],
777
+ project: string,
778
+ sourceEntryId: string
779
+ ): Promise<BlockerRef[]>;
780
+ }
781
+ ```
782
+
783
+ **해결 규칙**:
784
+ 1. 강한 패턴 (URL, JIRA key) → Artifact로 get-or-create
785
+ 2. 명시 task_id → Task 연결 (없으면 Condition으로 fallback)
786
+ 3. Task 제목 매칭 실패 → Condition으로 생성 + candidates 저장
787
+ 4. **스텁 Task 생성 금지** (중복 지옥 방지)
788
+
789
+ ### 4.3 TaskResolver
790
+
791
+ 세션에서 추출된 Task entry 처리:
792
+
793
+ ```typescript
794
+ interface TaskResolver {
795
+ processTaskEntry(entry: TaskEntry): Promise<{
796
+ task_id: string;
797
+ events: TaskEvent[];
798
+ isNew: boolean;
799
+ }>;
800
+ }
801
+ ```
802
+
803
+ **처리 흐름**:
804
+ 1. canonical_key로 기존 Task 찾기
805
+ 2. 없으면 `task_created` 이벤트 발행
806
+ 3. priority/status 변경 필요 시 이벤트 발행
807
+ 4. blockers가 있으면 BlockerResolver로 정규화
808
+ 5. evidenceAligned → `mode=replace`, 아니면 `mode=suggest`
809
+ 6. `task_blockers_set` 이벤트 발행
810
+
811
+ ### 4.4 TaskProjector
812
+
813
+ 이벤트를 entities/edges에 반영:
814
+
815
+ ```typescript
816
+ interface TaskProjector {
817
+ // 증분 처리
818
+ projectIncremental(): Promise<ProjectionResult>;
819
+
820
+ // 전체 rebuild
821
+ rebuild(): Promise<void>;
822
+ }
823
+ ```
824
+
825
+ **mode=replace**:
826
+ - 기존 blocked_by edges 삭제
827
+ - 새 edges 삽입
828
+ - entities.current_json.blockers 갱신
829
+
830
+ **mode=suggest**:
831
+ - blocked_by_suggested edges만 insert/replace
832
+ - entities.current_json.blocker_suggestions 누적
833
+
834
+ ## 5. DB 스키마
835
+
836
+ ### 5.1 entities 테이블
837
+
838
+ ```sql
839
+ CREATE TABLE entities (
840
+ entity_id VARCHAR PRIMARY KEY,
841
+ entity_type VARCHAR NOT NULL, -- task|condition|artifact
842
+ canonical_key VARCHAR NOT NULL,
843
+ title VARCHAR NOT NULL,
844
+ stage VARCHAR NOT NULL, -- raw|working|candidate|verified|certified
845
+ status VARCHAR NOT NULL, -- active|contested|deprecated|superseded
846
+ current_json JSON NOT NULL, -- fold된 현재 상태
847
+ title_norm VARCHAR, -- 정규화된 제목 (검색용)
848
+ search_text VARCHAR, -- FTS용 텍스트
849
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
850
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
851
+ );
852
+
853
+ CREATE INDEX idx_entities_type_key ON entities(entity_type, canonical_key);
854
+ CREATE INDEX idx_entities_status ON entities(status);
855
+ ```
856
+
857
+ ### 5.2 entity_aliases 테이블
858
+
859
+ 동일 Entity의 여러 이름:
860
+
861
+ ```sql
862
+ CREATE TABLE entity_aliases (
863
+ entity_type VARCHAR NOT NULL,
864
+ canonical_key VARCHAR NOT NULL,
865
+ entity_id VARCHAR NOT NULL,
866
+ is_primary BOOLEAN DEFAULT FALSE,
867
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
868
+ PRIMARY KEY(entity_type, canonical_key)
869
+ );
870
+ ```
871
+
872
+ ## 6. Idris2 영감 적용
873
+
874
+ ### 6.1 타입 레벨 보장 (TypeScript)
875
+
876
+ ```typescript
877
+ // Discriminated Union으로 상태별 타입 분리
878
+ type TaskState =
879
+ | { status: 'pending'; blockers?: never }
880
+ | { status: 'in_progress'; blockers?: never }
881
+ | { status: 'blocked'; blockers: BlockerRef[] } // 비어있으면 안 됨
882
+ | { status: 'done'; blockers?: never };
883
+
884
+ // 타입 가드
885
+ function isBlocked(task: TaskState): task is { status: 'blocked'; blockers: BlockerRef[] } {
886
+ return task.status === 'blocked' && task.blockers.length > 0;
887
+ }
888
+ ```
889
+
890
+ ### 6.2 불변식 검증
891
+
892
+ ```typescript
893
+ // Zod 스키마로 런타임 검증
894
+ const TaskInvariantSchema = z.discriminatedUnion('status', [
895
+ z.object({
896
+ status: z.literal('blocked'),
897
+ blockers: z.array(BlockerRefSchema).min(1) // 최소 1개
898
+ }),
899
+ z.object({
900
+ status: z.literal('done'),
901
+ blockers: z.array(BlockerRefSchema).max(0) // 0개
902
+ }),
903
+ // ...
904
+ ]);
905
+ ```
906
+
907
+ ### 6.3 왜 실제 Idris2를 사용하지 않는가?
908
+
909
+ **Memo.txt 지침** (섹션 11):
910
+ > "지금은 Python 쪽 구현이 핵심이므로, Idris는 Candidate/Verified 래퍼 기반으로만 최소 수정"
911
+
912
+ **실용적 이유**:
913
+ 1. 팀 학습 곡선 최소화
914
+ 2. 도구 체인 단순화 (idris2 설치/관리 불필요)
915
+ 3. TypeScript + Zod로 충분한 타입 안전성 확보
916
+ 4. 런타임 검증이 실제 버그 방지에 더 효과적
917
+
918
+ ## 7. 성공 기준
919
+
920
+ - [ ] Task가 세션 간 동일성 유지 (canonical_key 기반)
921
+ - [ ] 상태 변경 이력이 이벤트로 추적 가능
922
+ - [ ] blockers가 Entity 참조로 정규화됨
923
+ - [ ] 불변식 위반 시 에러 발생 (blocked인데 blockers 비어있음 등)
924
+ - [ ] 기존 entry 시스템과 호환 유지