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,301 @@
1
+ # Task Entity System Implementation Plan
2
+
3
+ > **Version**: 1.0.0
4
+ > **Status**: Draft
5
+ > **Created**: 2026-01-31
6
+
7
+ ## Phase 1: 기반 구조 (P0)
8
+
9
+ ### 1.1 타입 정의
10
+
11
+ **파일**: `src/core/types.ts` 수정
12
+
13
+ ```typescript
14
+ // 추가할 타입들
15
+ export const EntityTypeSchema = z.enum(['task', 'condition', 'artifact']);
16
+ export type EntityType = z.infer<typeof EntityTypeSchema>;
17
+
18
+ export const TaskStatusSchema = z.enum(['pending', 'in_progress', 'blocked', 'done', 'cancelled']);
19
+ export type TaskStatus = z.infer<typeof TaskStatusSchema>;
20
+
21
+ export const TaskEventTypeSchema = z.enum([
22
+ 'task_created',
23
+ 'task_status_changed',
24
+ 'task_priority_changed',
25
+ 'task_blockers_set',
26
+ 'task_transition_rejected'
27
+ ]);
28
+ ```
29
+
30
+ **작업 항목**:
31
+ - [ ] EntityType, TaskStatus 스키마 추가
32
+ - [ ] TaskEvent 타입 정의
33
+ - [ ] BlockerRef 타입 정의
34
+ - [ ] TaskState Discriminated Union 정의
35
+
36
+ ### 1.2 DB 스키마 추가
37
+
38
+ **파일**: `src/core/schema.sql` 수정
39
+
40
+ **작업 항목**:
41
+ - [ ] entities 테이블 생성
42
+ - [ ] entity_aliases 테이블 생성
43
+ - [ ] edges 테이블 생성
44
+ - [ ] 인덱스 추가
45
+
46
+ ### 1.3 Canonical Key 확장
47
+
48
+ **파일**: `src/core/canonical-key.ts` 수정
49
+
50
+ ```typescript
51
+ // 추가할 함수
52
+ export function makeEntityCanonicalKey(
53
+ entityType: EntityType,
54
+ identifier: string,
55
+ context?: { project?: string }
56
+ ): string;
57
+ ```
58
+
59
+ **작업 항목**:
60
+ - [ ] Task canonical key 함수
61
+ - [ ] Condition canonical key 함수
62
+ - [ ] Artifact canonical key 함수 (URL, JIRA, GitHub 패턴)
63
+
64
+ ## Phase 2: 핵심 컴포넌트 (P0)
65
+
66
+ ### 2.1 EntityRepository
67
+
68
+ **파일**: `src/core/entity-repo.ts` (신규)
69
+
70
+ ```typescript
71
+ export class EntityRepository {
72
+ constructor(private db: Database);
73
+
74
+ // CRUD (Create, Read만 - append-only 철학)
75
+ async create(entity: EntityInput): Promise<Entity>;
76
+ async findById(entityId: string): Promise<Entity | null>;
77
+ async findByCanonicalKey(type: EntityType, key: string): Promise<Entity | null>;
78
+
79
+ // 검색
80
+ async findSimilar(type: EntityType, searchText: string): Promise<Entity[]>;
81
+
82
+ // Alias 관리
83
+ async addAlias(entityId: string, alias: string): Promise<void>;
84
+ async resolveAlias(type: EntityType, alias: string): Promise<string | null>;
85
+
86
+ // 상태 업데이트 (이벤트 발행 후 projector가 호출)
87
+ async updateCurrentState(entityId: string, state: EntityState): Promise<void>;
88
+ }
89
+ ```
90
+
91
+ **작업 항목**:
92
+ - [ ] EntityRepository 클래스 구현
93
+ - [ ] findByCanonicalKey 구현
94
+ - [ ] findSimilar 구현 (FTS 활용)
95
+ - [ ] alias 관리 메서드 구현
96
+
97
+ ### 2.2 TaskMatcher
98
+
99
+ **파일**: `src/core/task-matcher.ts` (신규)
100
+
101
+ ```typescript
102
+ export class TaskMatcher {
103
+ constructor(
104
+ private entityRepo: EntityRepository,
105
+ private vectorStore: VectorStore
106
+ );
107
+
108
+ async findExact(canonicalKey: string): Promise<Task | null>;
109
+ async findSimilar(title: string, project: string): Promise<MatchResult>;
110
+ async suggestCandidates(title: string, project: string, limit?: number): Promise<Task[]>;
111
+ }
112
+ ```
113
+
114
+ **작업 항목**:
115
+ - [ ] 정확 매칭 (entity_aliases 활용)
116
+ - [ ] FTS 기반 유사 매칭
117
+ - [ ] Vector 기반 semantic 매칭
118
+ - [ ] 점수 계산 (stage weight, status weight, recency)
119
+ - [ ] strict 확정 로직 (score >= 0.92, gap >= 0.03)
120
+
121
+ ### 2.3 BlockerResolver
122
+
123
+ **파일**: `src/core/blocker-resolver.ts` (신규)
124
+
125
+ **작업 항목**:
126
+ - [ ] URL/JIRA/GitHub 패턴 감지
127
+ - [ ] Artifact get-or-create 로직
128
+ - [ ] Task 제목 매칭 시도
129
+ - [ ] Condition fallback 생성
130
+ - [ ] candidates 저장 로직
131
+
132
+ ### 2.4 TaskResolver
133
+
134
+ **파일**: `src/core/task-resolver.ts` (신규)
135
+
136
+ **작업 항목**:
137
+ - [ ] Task entry 처리 로직
138
+ - [ ] 기존 Task 찾기 (TaskMatcher 활용)
139
+ - [ ] 이벤트 발행 로직
140
+ - [ ] 상태 전이 검증
141
+ - [ ] blockers 정규화 (BlockerResolver 활용)
142
+
143
+ ## Phase 3: Projection (P0)
144
+
145
+ ### 3.1 TaskProjector
146
+
147
+ **파일**: `src/core/task-projector.ts` (신규)
148
+
149
+ ```typescript
150
+ export class TaskProjector {
151
+ constructor(
152
+ private eventStore: EventStore,
153
+ private entityRepo: EntityRepository,
154
+ private edgeRepo: EdgeRepository
155
+ );
156
+
157
+ async projectIncremental(): Promise<ProjectionResult>;
158
+ async rebuild(): Promise<void>;
159
+
160
+ private async handleTaskCreated(event: TaskEvent): Promise<void>;
161
+ private async handleStatusChanged(event: TaskEvent): Promise<void>;
162
+ private async handleBlockersSet(event: TaskEvent): Promise<void>;
163
+ }
164
+ ```
165
+
166
+ **작업 항목**:
167
+ - [ ] projection_offsets 관리
168
+ - [ ] 증분 이벤트 읽기
169
+ - [ ] 이벤트 타입별 핸들러
170
+ - [ ] mode=replace 처리 (기존 edge 삭제 + 새 edge 삽입)
171
+ - [ ] mode=suggest 처리 (suggested edge만)
172
+ - [ ] current_json 갱신
173
+
174
+ ### 3.2 EdgeRepository
175
+
176
+ **파일**: `src/core/edge-repo.ts` (신규)
177
+
178
+ ```typescript
179
+ export class EdgeRepository {
180
+ async createEdge(edge: EdgeInput): Promise<Edge>;
181
+ async findEdges(srcId: string, relType?: string): Promise<Edge[]>;
182
+ async deleteEdges(srcId: string, relType: string): Promise<number>;
183
+ async replaceEdges(srcId: string, relType: string, newEdges: EdgeInput[]): Promise<void>;
184
+ }
185
+ ```
186
+
187
+ **작업 항목**:
188
+ - [ ] Edge CRUD 구현
189
+ - [ ] 관계 타입별 조회
190
+ - [ ] replace 로직 (트랜잭션)
191
+
192
+ ## Phase 4: 통합 (P0)
193
+
194
+ ### 4.1 Graduation Pipeline 연동
195
+
196
+ **파일**: `src/core/graduation.ts` 수정
197
+
198
+ **작업 항목**:
199
+ - [ ] Task entry 처리 시 TaskResolver 호출
200
+ - [ ] 이벤트 발행 후 TaskProjector 호출
201
+ - [ ] evidence edge 생성 로직
202
+
203
+ ### 4.2 EventStore 확장
204
+
205
+ **파일**: `src/core/event-store.ts` 수정
206
+
207
+ **작업 항목**:
208
+ - [ ] Task 이벤트 타입 지원
209
+ - [ ] fetch_since 메서드 추가 (projector용)
210
+ - [ ] replay 메서드 추가 (rebuild용)
211
+
212
+ ## Phase 5: CLI 및 조회 (P1)
213
+
214
+ ### 5.1 조회 API
215
+
216
+ **작업 항목**:
217
+ - [ ] list_blocked_tasks()
218
+ - [ ] list_tasks_with_only_suggested_blockers()
219
+ - [ ] get_task_detail(task_id)
220
+ - [ ] v_task_blockers_effective 뷰
221
+
222
+ ### 5.2 CLI 커맨드
223
+
224
+ **파일**: `src/cli/index.ts` 수정
225
+
226
+ **작업 항목**:
227
+ - [ ] `cli blocked` - blocked task 목록
228
+ - [ ] `cli task show <task_id>` - task 상세
229
+ - [ ] `cli tasks --status <status>` - 상태별 목록
230
+
231
+ ## 파일 목록
232
+
233
+ ### 신규 파일
234
+ ```
235
+ src/core/entity-repo.ts
236
+ src/core/edge-repo.ts
237
+ src/core/task-matcher.ts
238
+ src/core/blocker-resolver.ts
239
+ src/core/task-resolver.ts
240
+ src/core/task-projector.ts
241
+ ```
242
+
243
+ ### 수정 파일
244
+ ```
245
+ src/core/types.ts
246
+ src/core/canonical-key.ts
247
+ src/core/event-store.ts
248
+ src/core/graduation.ts
249
+ src/cli/index.ts
250
+ ```
251
+
252
+ ## 테스트
253
+
254
+ ### 필수 테스트 케이스
255
+
256
+ 1. **Task 동일성**
257
+ - 같은 제목의 Task가 여러 세션에서 언급되어도 하나의 Entity로 관리
258
+
259
+ 2. **상태 전이**
260
+ - pending → done 직접 전이 시 에러
261
+ - blocked 상태에서 blockers가 비어있으면 에러
262
+
263
+ 3. **Idempotency**
264
+ - 동일 세션 재처리 시 중복 이벤트 없음
265
+ - 동일 edge 중복 생성 없음
266
+
267
+ 4. **Blocker 해결**
268
+ - URL → Artifact 정상 생성
269
+ - Task 제목 매칭 실패 → Condition 생성 + candidates 저장
270
+
271
+ ## 의존성 그래프
272
+
273
+ ```
274
+ types.ts
275
+
276
+ ├── entity-repo.ts
277
+ │ │
278
+ │ ├── task-matcher.ts
279
+ │ │
280
+ │ └── edge-repo.ts
281
+ │ │
282
+ │ └── task-projector.ts
283
+
284
+ ├── blocker-resolver.ts ─────────┐
285
+ │ │
286
+ └── task-resolver.ts ◀───────────┘
287
+
288
+ └── graduation.ts
289
+ ```
290
+
291
+ ## 마일스톤
292
+
293
+ | 단계 | 완료 기준 |
294
+ |------|----------|
295
+ | M1 | 타입 정의 + DB 스키마 |
296
+ | M2 | EntityRepo + EdgeRepo 동작 |
297
+ | M3 | TaskMatcher 동작 (정확 매칭만) |
298
+ | M4 | BlockerResolver + TaskResolver 동작 |
299
+ | M5 | TaskProjector 동작 (증분 처리) |
300
+ | M6 | Graduation 연동 완료 |
301
+ | M7 | CLI 조회 커맨드 완료 |
@@ -0,0 +1,314 @@
1
+ # Task Entity System Specification
2
+
3
+ > **Version**: 1.0.0
4
+ > **Status**: Draft
5
+ > **Created**: 2026-01-31
6
+
7
+ ## 1. 개요
8
+
9
+ ### 1.1 문제 정의
10
+
11
+ 현재 시스템에서 Task는 세션별로 entry(기록)로 저장되어 다음 문제가 발생:
12
+
13
+ 1. **중복 생성**: 같은 Task가 여러 세션에서 언급될 때마다 새 entry 생성
14
+ 2. **상태 단절**: Task 상태 변경 이력이 세션 단위로 분리되어 추적 불가
15
+ 3. **관계 손실**: Task 간 blockedBy 관계가 세션 경계에서 단절
16
+
17
+ ### 1.2 해결 방향
18
+
19
+ **Task를 Entity로 승격**:
20
+ - Task는 고유한 Entity로 관리 (entries와 분리된 entities 테이블)
21
+ - 상태 변경은 이벤트로 기록 (event-sourced)
22
+ - fold 연산으로 현재 상태 계산
23
+
24
+ ## 2. 핵심 개념
25
+
26
+ ### 2.1 Entity vs Entry
27
+
28
+ | 구분 | Entry (기존) | Entity (신규) |
29
+ |------|-------------|--------------|
30
+ | 정의 | 세션에서 추출된 불변 기록 | 시간에 따라 상태가 변화하는 개체 |
31
+ | 예시 | Fact, Decision, Insight | Task, Condition, Artifact |
32
+ | 식별 | entry_id (UUID) | entity_id + canonical_key |
33
+ | 상태 | 불변 (created once) | 이벤트 fold로 계산 |
34
+
35
+ ### 2.2 Canonical Key
36
+
37
+ Entity의 동일성을 판단하는 정규화된 키:
38
+
39
+ ```typescript
40
+ // Task
41
+ task:{project}:{normalize(title)}
42
+ // 예: task:code-memory:implement-vector-search
43
+
44
+ // Condition
45
+ cond:{project}:{normalize(text)}
46
+ // 예: cond:code-memory:api-key-configured
47
+
48
+ // Artifact
49
+ art:url:{sha1(url)} // URL
50
+ art:jira:{key} // JIRA
51
+ art:gh_issue:{repo}:{num} // GitHub Issue
52
+ ```
53
+
54
+ ### 2.3 Task 상태 머신
55
+
56
+ ```
57
+ ┌─────────────────────────────────────┐
58
+ │ │
59
+ ▼ │
60
+ ┌─────────┐ ┌───────────┐ ┌────────┴───┐
61
+ │ pending │────▶│in_progress│────▶│ done │
62
+ └────┬────┘ └─────┬─────┘ └────────────┘
63
+ │ │
64
+ │ ▼
65
+ │ ┌──────────┐
66
+ └─────────▶│ blocked │
67
+ └──────────┘
68
+
69
+ │ (blockers 해결 시)
70
+
71
+ ┌──────────┐
72
+ │in_progress│
73
+ └──────────┘
74
+ ```
75
+
76
+ **불변식 (Invariants)**:
77
+ - `blocked` 상태면 `blockers[]` 비어있으면 안 됨
78
+ - `done` 상태면 `blockers[]` 비어있어야 함
79
+ - `pending` → `done` 직접 전이 금지 (in_progress 거쳐야 함)
80
+
81
+ ## 3. 이벤트 타입
82
+
83
+ ### 3.1 Task 이벤트
84
+
85
+ ```typescript
86
+ type TaskEventType =
87
+ | 'task_created' // 신규 생성
88
+ | 'task_status_changed' // 상태 변경
89
+ | 'task_priority_changed' // 우선순위 변경
90
+ | 'task_blockers_set' // blockers 설정/변경
91
+ | 'task_transition_rejected'; // 전이 거부 (디버깅용)
92
+ ```
93
+
94
+ ### 3.2 이벤트 페이로드 스키마
95
+
96
+ ```typescript
97
+ // task_created
98
+ interface TaskCreatedPayload {
99
+ task_id: string;
100
+ canonical_key: string;
101
+ title: string;
102
+ initial_status: 'pending' | 'in_progress'; // done 금지
103
+ priority?: 'low' | 'medium' | 'high' | 'critical';
104
+ source_entry_id: string;
105
+ }
106
+
107
+ // task_status_changed
108
+ interface TaskStatusChangedPayload {
109
+ task_id: string;
110
+ from_status: TaskStatus;
111
+ to_status: TaskStatus;
112
+ reason?: string;
113
+ }
114
+
115
+ // task_blockers_set
116
+ interface TaskBlockersSetPayload {
117
+ task_id: string;
118
+ mode: 'replace' | 'suggest';
119
+ blockers: BlockerRef[];
120
+ source_entry_id?: string;
121
+ }
122
+ ```
123
+
124
+ ## 4. 컴포넌트 설계
125
+
126
+ ### 4.1 TaskMatcher
127
+
128
+ 기존 Task 찾기:
129
+
130
+ ```typescript
131
+ interface TaskMatcher {
132
+ // 정확한 매칭
133
+ findExact(canonicalKey: string): Task | null;
134
+
135
+ // 유사도 기반 매칭
136
+ findSimilar(title: string, project: string): MatchResult;
137
+
138
+ // 후보 목록 반환
139
+ suggestCandidates(title: string, project: string, limit?: number): Task[];
140
+ }
141
+
142
+ // 매칭 조건 (strict 확정)
143
+ const STRICT_MATCH = {
144
+ minScore: 0.92,
145
+ minGap: 0.03, // top1 - top2
146
+ status: ['active'], // cancelled 제외
147
+ taskStatus: ['pending', 'in_progress', 'blocked'] // done 제외
148
+ };
149
+ ```
150
+
151
+ ### 4.2 BlockerResolver
152
+
153
+ blockedBy 텍스트를 Entity 참조로 변환:
154
+
155
+ ```typescript
156
+ interface BlockerRef {
157
+ kind: 'task' | 'condition' | 'artifact';
158
+ entity_id: string;
159
+ raw_text: string;
160
+ confidence: 'high' | 'suggested' | 'none';
161
+ candidates?: EntityRef[]; // confidence='none'일 때
162
+ }
163
+
164
+ interface BlockerResolver {
165
+ resolve(
166
+ blockedByTexts: string[],
167
+ project: string,
168
+ sourceEntryId: string
169
+ ): Promise<BlockerRef[]>;
170
+ }
171
+ ```
172
+
173
+ **해결 규칙**:
174
+ 1. 강한 패턴 (URL, JIRA key) → Artifact로 get-or-create
175
+ 2. 명시 task_id → Task 연결 (없으면 Condition으로 fallback)
176
+ 3. Task 제목 매칭 실패 → Condition으로 생성 + candidates 저장
177
+ 4. **스텁 Task 생성 금지** (중복 지옥 방지)
178
+
179
+ ### 4.3 TaskResolver
180
+
181
+ 세션에서 추출된 Task entry 처리:
182
+
183
+ ```typescript
184
+ interface TaskResolver {
185
+ processTaskEntry(entry: TaskEntry): Promise<{
186
+ task_id: string;
187
+ events: TaskEvent[];
188
+ isNew: boolean;
189
+ }>;
190
+ }
191
+ ```
192
+
193
+ **처리 흐름**:
194
+ 1. canonical_key로 기존 Task 찾기
195
+ 2. 없으면 `task_created` 이벤트 발행
196
+ 3. priority/status 변경 필요 시 이벤트 발행
197
+ 4. blockers가 있으면 BlockerResolver로 정규화
198
+ 5. evidenceAligned → `mode=replace`, 아니면 `mode=suggest`
199
+ 6. `task_blockers_set` 이벤트 발행
200
+
201
+ ### 4.4 TaskProjector
202
+
203
+ 이벤트를 entities/edges에 반영:
204
+
205
+ ```typescript
206
+ interface TaskProjector {
207
+ // 증분 처리
208
+ projectIncremental(): Promise<ProjectionResult>;
209
+
210
+ // 전체 rebuild
211
+ rebuild(): Promise<void>;
212
+ }
213
+ ```
214
+
215
+ **mode=replace**:
216
+ - 기존 blocked_by edges 삭제
217
+ - 새 edges 삽입
218
+ - entities.current_json.blockers 갱신
219
+
220
+ **mode=suggest**:
221
+ - blocked_by_suggested edges만 insert/replace
222
+ - entities.current_json.blocker_suggestions 누적
223
+
224
+ ## 5. DB 스키마
225
+
226
+ ### 5.1 entities 테이블
227
+
228
+ ```sql
229
+ CREATE TABLE entities (
230
+ entity_id VARCHAR PRIMARY KEY,
231
+ entity_type VARCHAR NOT NULL, -- task|condition|artifact
232
+ canonical_key VARCHAR NOT NULL,
233
+ title VARCHAR NOT NULL,
234
+ stage VARCHAR NOT NULL, -- raw|working|candidate|verified|certified
235
+ status VARCHAR NOT NULL, -- active|contested|deprecated|superseded
236
+ current_json JSON NOT NULL, -- fold된 현재 상태
237
+ title_norm VARCHAR, -- 정규화된 제목 (검색용)
238
+ search_text VARCHAR, -- FTS용 텍스트
239
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
240
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
241
+ );
242
+
243
+ CREATE INDEX idx_entities_type_key ON entities(entity_type, canonical_key);
244
+ CREATE INDEX idx_entities_status ON entities(status);
245
+ ```
246
+
247
+ ### 5.2 entity_aliases 테이블
248
+
249
+ 동일 Entity의 여러 이름:
250
+
251
+ ```sql
252
+ CREATE TABLE entity_aliases (
253
+ entity_type VARCHAR NOT NULL,
254
+ canonical_key VARCHAR NOT NULL,
255
+ entity_id VARCHAR NOT NULL,
256
+ is_primary BOOLEAN DEFAULT FALSE,
257
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
258
+ PRIMARY KEY(entity_type, canonical_key)
259
+ );
260
+ ```
261
+
262
+ ## 6. Idris2 영감 적용
263
+
264
+ ### 6.1 타입 레벨 보장 (TypeScript)
265
+
266
+ ```typescript
267
+ // Discriminated Union으로 상태별 타입 분리
268
+ type TaskState =
269
+ | { status: 'pending'; blockers?: never }
270
+ | { status: 'in_progress'; blockers?: never }
271
+ | { status: 'blocked'; blockers: BlockerRef[] } // 비어있으면 안 됨
272
+ | { status: 'done'; blockers?: never };
273
+
274
+ // 타입 가드
275
+ function isBlocked(task: TaskState): task is { status: 'blocked'; blockers: BlockerRef[] } {
276
+ return task.status === 'blocked' && task.blockers.length > 0;
277
+ }
278
+ ```
279
+
280
+ ### 6.2 불변식 검증
281
+
282
+ ```typescript
283
+ // Zod 스키마로 런타임 검증
284
+ const TaskInvariantSchema = z.discriminatedUnion('status', [
285
+ z.object({
286
+ status: z.literal('blocked'),
287
+ blockers: z.array(BlockerRefSchema).min(1) // 최소 1개
288
+ }),
289
+ z.object({
290
+ status: z.literal('done'),
291
+ blockers: z.array(BlockerRefSchema).max(0) // 0개
292
+ }),
293
+ // ...
294
+ ]);
295
+ ```
296
+
297
+ ### 6.3 왜 실제 Idris2를 사용하지 않는가?
298
+
299
+ **Memo.txt 지침** (섹션 11):
300
+ > "지금은 Python 쪽 구현이 핵심이므로, Idris는 Candidate/Verified 래퍼 기반으로만 최소 수정"
301
+
302
+ **실용적 이유**:
303
+ 1. 팀 학습 곡선 최소화
304
+ 2. 도구 체인 단순화 (idris2 설치/관리 불필요)
305
+ 3. TypeScript + Zod로 충분한 타입 안전성 확보
306
+ 4. 런타임 검증이 실제 버그 방지에 더 효과적
307
+
308
+ ## 7. 성공 기준
309
+
310
+ - [ ] Task가 세션 간 동일성 유지 (canonical_key 기반)
311
+ - [ ] 상태 변경 이력이 이벤트로 추적 가능
312
+ - [ ] blockers가 Entity 참조로 정규화됨
313
+ - [ ] 불변식 위반 시 에러 발생 (blocked인데 blockers 비어있음 등)
314
+ - [ ] 기존 entry 시스템과 호환 유지