@lovenyberg/ove 0.2.2 → 0.4.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.
@@ -0,0 +1,272 @@
1
+ # Conversation-Aware Repo Resolution
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** When a user sends a follow-up message without specifying a repo, Ove should remember which repo they were talking about from recent conversation context.
6
+
7
+ **Architecture:** Two-layer fallback added to the repo resolution chain in `handleTaskMessage`: (1) check the user's most recent task for its repo (cheap DB query), (2) feed conversation history to the LLM resolver prompt so it can infer the repo from context. No new tables or schema changes.
8
+
9
+ **Tech Stack:** Bun, bun:sqlite, bun:test, TypeScript
10
+
11
+ ---
12
+
13
+ ### Task 1: Add `lastRepoForUser` helper to handlers
14
+
15
+ **Files:**
16
+ - Modify: `src/handlers.ts:396-446`
17
+
18
+ **Step 1: Write the failing test**
19
+
20
+ Add to `src/flows.test.ts`:
21
+
22
+ ```typescript
23
+ describe("Conversation-aware repo resolution", () => {
24
+ it("derives lastRepo from recent task history", async () => {
25
+ const db = new Database(":memory:");
26
+ db.run("PRAGMA journal_mode = WAL");
27
+ const queue = new TaskQueue(db);
28
+
29
+ // Simulate a completed task on "iris"
30
+ const taskId = queue.enqueue({
31
+ userId: "telegram:U1",
32
+ repo: "iris",
33
+ prompt: "check the roadmap",
34
+ });
35
+ queue.dequeue();
36
+ queue.complete(taskId, "Here's the roadmap...");
37
+
38
+ // The user's last task repo should be "iris"
39
+ const recent = queue.listByUser("telegram:U1", 1);
40
+ expect(recent.length).toBe(1);
41
+ expect(recent[0].repo).toBe("iris");
42
+ });
43
+ });
44
+ ```
45
+
46
+ **Step 2: Run test to verify it passes**
47
+
48
+ Run: `bun test src/flows.test.ts --test-name-pattern "derives lastRepo"`
49
+ Expected: PASS (this is testing existing queue behavior — sanity check)
50
+
51
+ **Step 3: Add lastRepo fallback in handleTaskMessage**
52
+
53
+ In `src/handlers.ts`, inside `handleTaskMessage`, after the single-repo check (line ~411) and before the LLM resolver (line ~418), add:
54
+
55
+ ```typescript
56
+ // Check last task's repo as context fallback
57
+ const recentTasks = deps.queue.listByUser(msg.userId, 1);
58
+ const lastRepo = recentTasks[0]?.repo;
59
+ if (lastRepo && repoNames.includes(lastRepo)) {
60
+ parsed.repo = lastRepo;
61
+ logger.info("repo resolved from recent task", { resolved: lastRepo, userText: parsed.rawText.slice(0, 80) });
62
+ } else {
63
+ ```
64
+
65
+ This goes inside the `else if (repoNames.length > 1)` block, between the single-repo check and the LLM resolver. The full block becomes:
66
+
67
+ ```typescript
68
+ if (repoNames.length === 1) {
69
+ parsed.repo = repoNames[0];
70
+ } else if (repoNames.length === 0) {
71
+ const reply = "No repos discovered yet. Set one up with `init repo <name> <git-url>` or configure GitHub sync.";
72
+ await msg.reply(reply);
73
+ return;
74
+ } else {
75
+ // Try last task's repo first (cheap)
76
+ const recentTasks = deps.queue.listByUser(msg.userId, 1);
77
+ const lastRepo = recentTasks[0]?.repo;
78
+ if (lastRepo && repoNames.includes(lastRepo)) {
79
+ parsed.repo = lastRepo;
80
+ logger.info("repo resolved from recent task", { resolved: lastRepo, userText: parsed.rawText.slice(0, 80) });
81
+ } else {
82
+ // Resolve repo via LLM call (existing code, moved into else branch)
83
+ const repoList = repoNames.join(", ");
84
+ // ... existing LLM resolver code ...
85
+ }
86
+ }
87
+ ```
88
+
89
+ **Step 4: Run all tests**
90
+
91
+ Run: `bun test src/flows.test.ts`
92
+ Expected: PASS
93
+
94
+ **Step 5: Commit**
95
+
96
+ ```bash
97
+ git add src/handlers.ts src/flows.test.ts
98
+ git commit -m "feat: resolve repo from recent task history for follow-up messages"
99
+ ```
100
+
101
+ ---
102
+
103
+ ### Task 2: Enhance LLM resolver with conversation history
104
+
105
+ **Files:**
106
+ - Modify: `src/handlers.ts:418-433`
107
+
108
+ **Step 1: Write the failing test**
109
+
110
+ Add to `src/flows.test.ts`:
111
+
112
+ ```typescript
113
+ describe("LLM resolver with conversation history", () => {
114
+ it("buildResolverPrompt includes conversation history", () => {
115
+ const history = [
116
+ { role: "user", content: "check the roadmap on iris", timestamp: "" },
117
+ { role: "assistant", content: "Here's the iris roadmap...", timestamp: "" },
118
+ { role: "user", content: "what about tomorrow's plan", timestamp: "" },
119
+ ];
120
+ const currentText = "what about tomorrow's plan";
121
+ const repoList = "iris, docs, my-app";
122
+
123
+ // Test the prompt format includes history
124
+ const historyContext = history.length > 0
125
+ ? "Recent conversation:\n" + history.map(m => `${m.role}: ${m.content}`).join("\n") + "\n\n"
126
+ : "";
127
+ const prompt = `You are a repo-name resolver. ${historyContext}The user's latest message:\n"${currentText}"\n\nAvailable repos: ${repoList}\n\nRespond with ONLY the repo name that best matches their request. Nothing else — just the exact repo name from the list. If you cannot determine which repo, respond with "UNKNOWN".`;
128
+
129
+ expect(prompt).toContain("Recent conversation:");
130
+ expect(prompt).toContain("check the roadmap on iris");
131
+ expect(prompt).toContain("Here's the iris roadmap");
132
+ expect(prompt).toContain("what about tomorrow's plan");
133
+ expect(prompt).toContain("iris, docs, my-app");
134
+ });
135
+ });
136
+ ```
137
+
138
+ **Step 2: Run test to verify it passes**
139
+
140
+ Run: `bun test src/flows.test.ts --test-name-pattern "buildResolverPrompt"`
141
+ Expected: PASS (testing the prompt format)
142
+
143
+ **Step 3: Update LLM resolver in handleTaskMessage to include history**
144
+
145
+ In `src/handlers.ts`, modify the LLM resolver block. Change the resolver prompt from:
146
+
147
+ ```typescript
148
+ const resolvePrompt = `You are a repo-name resolver. The user said:\n"${parsed.rawText}"\n\nAvailable repos: ${repoList}\n\nRespond with ONLY the repo name that best matches their request. Nothing else — just the exact repo name from the list. If you cannot determine which repo, respond with "UNKNOWN".`;
149
+ ```
150
+
151
+ To:
152
+
153
+ ```typescript
154
+ const history = deps.sessions.getHistory(msg.userId, 6);
155
+ const historyContext = history.length > 1
156
+ ? "Recent conversation:\n" + history.slice(0, -1).map(m => `${m.role}: ${m.content}`).join("\n") + "\n\n"
157
+ : "";
158
+ const resolvePrompt = `You are a repo-name resolver. ${historyContext}The user's latest message:\n"${parsed.rawText}"\n\nAvailable repos: ${repoList}\n\nRespond with ONLY the repo name that best matches their request. Consider the conversation context if the current message doesn't mention a specific repo. Nothing else — just the exact repo name from the list. If you cannot determine which repo, respond with "UNKNOWN".`;
159
+ ```
160
+
161
+ **Step 4: Run all tests**
162
+
163
+ Run: `bun test src/flows.test.ts`
164
+ Expected: PASS
165
+
166
+ **Step 5: Commit**
167
+
168
+ ```bash
169
+ git add src/handlers.ts src/flows.test.ts
170
+ git commit -m "feat: feed conversation history to LLM repo resolver"
171
+ ```
172
+
173
+ ---
174
+
175
+ ### Task 3: Integration test for the full follow-up flow
176
+
177
+ **Files:**
178
+ - Modify: `src/flows.test.ts`
179
+
180
+ **Step 1: Write the integration test**
181
+
182
+ ```typescript
183
+ describe("Full follow-up conversation flow", () => {
184
+ it("follow-up message without repo uses last task's repo", () => {
185
+ const db = new Database(":memory:");
186
+ db.run("PRAGMA journal_mode = WAL");
187
+ const queue = new TaskQueue(db);
188
+ const sessions = new SessionStore(db);
189
+
190
+ // Simulate conversation: user talked about iris
191
+ sessions.addMessage("telegram:U1", "user", "check the roadmap on iris");
192
+ sessions.addMessage("telegram:U1", "assistant", "Here's the iris roadmap...");
193
+ sessions.addMessage("telegram:U1", "user", "what about tomorrow's plan");
194
+
195
+ // Simulate a completed task on iris
196
+ const taskId = queue.enqueue({
197
+ userId: "telegram:U1",
198
+ repo: "iris",
199
+ prompt: "check the roadmap",
200
+ });
201
+ queue.dequeue();
202
+ queue.complete(taskId, "Here's the roadmap...");
203
+
204
+ // Now a follow-up: "what about tomorrow" — no repo mentioned
205
+ const parsed = parseMessage("what about tomorrow's plan");
206
+ expect(parsed.type).toBe("free-form");
207
+ expect(parsed.repo).toBeUndefined(); // Router can't find repo in text
208
+
209
+ // But the last task was on iris
210
+ const recentTasks = queue.listByUser("telegram:U1", 1);
211
+ expect(recentTasks[0].repo).toBe("iris");
212
+
213
+ // And the conversation history mentions iris
214
+ const history = sessions.getHistory("telegram:U1", 6);
215
+ expect(history.some(m => m.content.includes("iris"))).toBe(true);
216
+ });
217
+
218
+ it("explicit repo in message overrides last task repo", () => {
219
+ const db = new Database(":memory:");
220
+ db.run("PRAGMA journal_mode = WAL");
221
+ const queue = new TaskQueue(db);
222
+
223
+ // Last task was on iris
224
+ const taskId = queue.enqueue({
225
+ userId: "telegram:U1",
226
+ repo: "iris",
227
+ prompt: "check roadmap",
228
+ });
229
+ queue.dequeue();
230
+ queue.complete(taskId, "done");
231
+
232
+ // But new message explicitly says "on docs"
233
+ const parsed = parseMessage("check the tests on docs");
234
+ expect(parsed.repo).toBe("docs"); // Regex hint takes priority
235
+ });
236
+ });
237
+ ```
238
+
239
+ **Step 2: Run test**
240
+
241
+ Run: `bun test src/flows.test.ts --test-name-pattern "follow-up"`
242
+ Expected: PASS
243
+
244
+ **Step 3: Commit**
245
+
246
+ ```bash
247
+ git add src/flows.test.ts
248
+ git commit -m "test: add integration tests for conversation-aware repo resolution"
249
+ ```
250
+
251
+ ---
252
+
253
+ ### Task 4: Verify full test suite passes
254
+
255
+ **Step 1: Run all tests**
256
+
257
+ Run: `bun test`
258
+ Expected: All tests pass
259
+
260
+ **Step 2: Manual verification (optional)**
261
+
262
+ If running locally, send a message to Ove via Telegram:
263
+ 1. "check the roadmap on iris" → should resolve to iris
264
+ 2. "what about tomorrow's plan" → should resolve to iris (from recent task)
265
+ 3. "check tests on docs" → should resolve to docs (explicit override)
266
+
267
+ **Step 3: Final commit if needed**
268
+
269
+ ```bash
270
+ git add -A
271
+ git commit -m "feat: conversation-aware repo resolution for follow-up messages"
272
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovenyberg/ove",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "description": "Your grumpy but meticulous dev companion. AI coding agent for Slack, WhatsApp, Telegram, Discord, GitHub, HTTP API, and CLI.",
5
5
  "type": "module",
6
6
  "bin": {
Binary file