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,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 시스템과 호환 유지
|