# DevLog v0.1.14 — Fix `connect` Source Memories

**Ngày:** 2026-02-28
**Version:** 0.1.13 → 0.1.14
**Author:** Claude Code (Opus 4.5)
**Branch:** main

---

## Tóm tắt

Sửa hai bug liên quan khiến lệnh `kioku-lite connect` luôn trả về `source_memories: []` — không bao giờ hydrate được text của memory gốc từ edges trong knowledge graph.

---

## Root Cause Analysis

Bug là **two-part failure** trong pipeline `connect → find_path → explain_connection`:

### Part 1 — `find_path()` không lấy `source_hash` từ DB

**File:** `src/kioku_lite/pipeline/graph_store.py`
**Method:** `find_path()`

SQL query không include `source_hash` trong SELECT:

```python
# BEFORE (broken):
cur.execute("SELECT source, target, rel_type, evidence FROM kg_edges")
adj: dict[str, list[tuple[str, str, str]]] = {}
for row in cur.fetchall():
    s, t, rel, ev = row[0], row[1], row[2] or "", row[3] or ""
    adj.setdefault(s.lower(), []).append((t, rel, ev))
    adj.setdefault(t.lower(), []).append((s, rel, ev))
```

Vì không có `source_hash` trong adjacency list, `GraphEdge` được tạo ra với `source_hash=""`.
Do đó `hashes = list({e.source_hash for e in result.edges if e.source_hash})` trả về `[]`.
`hydrated = {}` → `source_memories = []`.

### Part 2 — `explain_connection()` iterate `.values()` thay vì `.items()`

**File:** `src/kioku_lite/service.py`
**Method:** `explain_connection()`

Kể cả khi Part 1 được sửa (edges có `source_hash`), `source_memories` vẫn thiếu `content_hash` vì:

```python
# BEFORE (broken):
"source_memories": [{"content": v["text"], "date": v.get("date", "")} for v in hydrated.values()],
```

`hydrated` là `dict[hash → {text, date, mood, ...}]`.
Dùng `.values()` → không lấy được key (hash).
Pattern đúng đã có ở `recall_entity()` nhưng không được apply ở đây.

---

## Fix

### Fix 1 — `graph_store.py`: Thêm `source_hash` vào SQL và adjacency tuples

```python
# AFTER (fixed):
cur.execute("SELECT source, target, rel_type, evidence, source_hash FROM kg_edges")
adj: dict[str, list[tuple[str, str, str, str]]] = {}
for row in cur.fetchall():
    s, t, rel, ev, sh = row[0], row[1], row[2] or "", row[3] or "", row[4] or ""
    adj.setdefault(s.lower(), []).append((t, rel, ev, sh))
    adj.setdefault(t.lower(), []).append((s, rel, ev, sh))

# Path reconstruction — pass source_hash to GraphEdge:
for nb, rel, ev, sh in adj.get(a, []):
    if nb.lower() == b:
        edges.append(GraphEdge(
            source=path[i], target=path[i + 1],
            rel_type=rel, evidence=ev, source_hash=sh
        ))
        break

# BFS loop:
for neighbor, _, _, _ in adj.get(current, []):
```

### Fix 2 — `service.py`: Đổi `.values()` → `.items()` + thêm `content_hash` và `mood`

```python
# AFTER (fixed) — consistent với recall_entity() pattern:
"source_memories": [
    {"content": v["text"], "date": v.get("date", ""), "mood": v.get("mood", ""), "content_hash": k}
    for k, v in hydrated.items()
],
```

---

## Files Changed

| File | Change |
|------|--------|
| `src/kioku_lite/pipeline/graph_store.py` | `find_path()`: SELECT thêm `source_hash`, adj tuples 4-element, GraphEdge truyền `source_hash` |
| `src/kioku_lite/service.py` | `explain_connection()`: `.values()` → `.items()`, thêm `content_hash` + `mood` vào output |
| `pyproject.toml` | version 0.1.13 → 0.1.14 |

---

## Verification

### Unit Tests

```
149 passed in 21.27s — 0 regressions
```

### E2E Test

```bash
# Setup
kioku-lite users --use connect-verify-test
kioku-lite save "Phúc builds kioku-lite using Python and SQLite as the database." --mood work
# → hash: d1a470e7...

kioku-lite kg-index d1a470e7... \
  --entities '[{"name":"Phúc","type":"PERSON"},{"name":"kioku-lite","type":"PROJECT"},{"name":"SQLite","type":"TOOL"}]' \
  --relationships '[
    {"source":"Phúc","rel_type":"WORKS_ON","target":"kioku-lite","evidence":"Phúc builds kioku-lite"},
    {"source":"kioku-lite","rel_type":"USED_BY","target":"SQLite","evidence":"kioku-lite using SQLite as database"}
  ]'

kioku-lite connect "Phúc" "SQLite"
```

**Output (v0.1.14):**
```json
{
    "from": "Phúc",
    "to": "SQLite",
    "connected": true,
    "paths": [["Phúc", "kioku-lite", "SQLite"]],
    "nodes": [
        {"name": "Phúc", "type": ""},
        {"name": "kioku-lite", "type": ""},
        {"name": "SQLite", "type": ""}
    ],
    "source_memories": [
        {
            "content": "Phúc builds kioku-lite using Python and SQLite as the database.",
            "date": "2026-02-28",
            "mood": "work",
            "content_hash": "d1a470e7cd3cf9205292142e9c0115c2e93f63ea4e56a3571499ac74cb8e67b5"
        }
    ]
}
```

✅ `connected: true`
✅ `paths` có multi-hop path đúng
✅ `source_memories` có `content`, `date`, `mood`, `content_hash`
✅ `content_hash` match với hash lúc `save`

---

## Impact

| Feature | Before v0.1.14 | After v0.1.14 |
|---------|----------------|----------------|
| `connect` path finding | ✅ Đúng | ✅ Đúng |
| `connect` source_memories content | ❌ Luôn `[]` | ✅ Hydrated từ DB |
| `connect` source_memories content_hash | ❌ Không có | ✅ Có đủ |
| `connect` source_memories mood | ❌ Không có | ✅ Có đủ |

---

*DevLog by Claude Code — 2026-02-28*
