vibe-learning-opencode 0.2.6 → 0.2.10
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/dist/index.js +113 -80
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,63 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
1
9
|
// src/index.ts
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import * as os from "node:os";
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
var DB_DIR = ".vibe-learning";
|
|
14
|
+
var DB_FILENAME = "learning.db";
|
|
15
|
+
function getLearningStatus() {
|
|
16
|
+
try {
|
|
17
|
+
const dbPath = path.join(os.homedir(), DB_DIR, DB_FILENAME);
|
|
18
|
+
if (!fs.existsSync(dbPath)) {
|
|
19
|
+
return {
|
|
20
|
+
unknownUnknowns: { count: 0 },
|
|
21
|
+
dueReviews: { count: 0 }
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const Database = __require("better-sqlite3");
|
|
25
|
+
const db = new Database(dbPath, { readonly: true });
|
|
26
|
+
try {
|
|
27
|
+
const unknownsCount = db.prepare(
|
|
28
|
+
"SELECT COUNT(*) as count FROM unknown_unknowns WHERE explored = 0"
|
|
29
|
+
).get();
|
|
30
|
+
const firstUnknown = db.prepare(
|
|
31
|
+
"SELECT concept_id FROM unknown_unknowns WHERE explored = 0 ORDER BY appearances DESC, first_seen DESC LIMIT 1"
|
|
32
|
+
).get();
|
|
33
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
34
|
+
const reviewsCount = db.prepare(
|
|
35
|
+
"SELECT COUNT(*) as count FROM concept_progress WHERE next_review IS NOT NULL AND next_review <= ?"
|
|
36
|
+
).get(today);
|
|
37
|
+
const firstReview = db.prepare(
|
|
38
|
+
"SELECT concept_id FROM concept_progress WHERE next_review IS NOT NULL AND next_review <= ? ORDER BY next_review ASC LIMIT 1"
|
|
39
|
+
).get(today);
|
|
40
|
+
return {
|
|
41
|
+
unknownUnknowns: {
|
|
42
|
+
count: unknownsCount?.count ?? 0,
|
|
43
|
+
first: firstUnknown?.concept_id
|
|
44
|
+
},
|
|
45
|
+
dueReviews: {
|
|
46
|
+
count: reviewsCount?.count ?? 0,
|
|
47
|
+
first: firstReview?.concept_id
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
} finally {
|
|
51
|
+
db.close();
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
unknownUnknowns: { count: 0 },
|
|
56
|
+
dueReviews: { count: 0 },
|
|
57
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
2
61
|
var CONFIG = {
|
|
3
62
|
TOOL_THRESHOLD: 3,
|
|
4
63
|
COOLDOWN_MS: 15 * 60 * 1e3,
|
|
@@ -11,7 +70,6 @@ var recentConcepts = [];
|
|
|
11
70
|
var seniorEnabled = true;
|
|
12
71
|
var afterEnabled = true;
|
|
13
72
|
var lastSessionID = null;
|
|
14
|
-
var sessionContextShown = /* @__PURE__ */ new Set();
|
|
15
73
|
var FILE_CONCEPTS = [
|
|
16
74
|
{ pattern: /test|spec|__tests__/i, concept: "unit-testing" },
|
|
17
75
|
{ pattern: /auth|login|session|jwt|oauth/i, concept: "authentication" },
|
|
@@ -106,20 +164,6 @@ After final round, call BOTH tools:
|
|
|
106
164
|
THEN say "Good thinking! Implementing now."
|
|
107
165
|
|
|
108
166
|
ONE question per round. Be strict like a real senior.`;
|
|
109
|
-
var SESSION_START_PROMPT = `[VibeLearning - Session Start Context]
|
|
110
|
-
|
|
111
|
-
Execute these steps NOW to show session context:
|
|
112
|
-
|
|
113
|
-
1. Call mcp__vibe-learning__get_unknown_unknowns with period="month" and limit=5
|
|
114
|
-
2. Call mcp__vibe-learning__get_due_reviews with limit=5
|
|
115
|
-
|
|
116
|
-
Then if either has results, show a brief summary:
|
|
117
|
-
|
|
118
|
-
**[VibeLearning]** Session context:
|
|
119
|
-
- X unexplored concepts (use /learn unknowns)
|
|
120
|
-
- Y concepts due for review (use /learn review)
|
|
121
|
-
|
|
122
|
-
Keep it to 2-3 lines max. If nothing is due, don't show anything.`;
|
|
123
167
|
var AUTO_LEARNING_PROMPT = `[VibeLearning - Task Completion]
|
|
124
168
|
|
|
125
169
|
Task completed. Execute these steps NOW (in order):
|
|
@@ -191,11 +235,11 @@ function parseLearnCommand(text) {
|
|
|
191
235
|
return "focus";
|
|
192
236
|
return null;
|
|
193
237
|
}
|
|
194
|
-
var VibeLearningPlugin = (ctx) => {
|
|
238
|
+
var VibeLearningPlugin = async (ctx) => {
|
|
195
239
|
const { client } = ctx;
|
|
196
240
|
client.app.log({
|
|
197
241
|
level: "info",
|
|
198
|
-
message: "[VibeLearning] Plugin loaded (
|
|
242
|
+
message: "[VibeLearning] Plugin loaded (v3 - session toast)"
|
|
199
243
|
}).catch(() => {
|
|
200
244
|
});
|
|
201
245
|
const injectPrompt = (sessionID, prompt) => {
|
|
@@ -209,53 +253,60 @@ var VibeLearningPlugin = (ctx) => {
|
|
|
209
253
|
});
|
|
210
254
|
};
|
|
211
255
|
return {
|
|
212
|
-
//
|
|
213
|
-
|
|
256
|
+
// Session created - show toast with learning status
|
|
257
|
+
"session.created": async (input) => {
|
|
258
|
+
lastSessionID = input.id;
|
|
259
|
+
const status = getLearningStatus();
|
|
260
|
+
let message;
|
|
261
|
+
let variant = "info";
|
|
262
|
+
if (status.error) {
|
|
263
|
+
message = "Learning mode active. Use /learn to check status.";
|
|
264
|
+
} else if (status.unknownUnknowns.count === 0 && status.dueReviews.count === 0) {
|
|
265
|
+
message = "All caught up! No pending reviews.";
|
|
266
|
+
variant = "success";
|
|
267
|
+
} else {
|
|
268
|
+
const parts = [];
|
|
269
|
+
if (status.unknownUnknowns.count > 0) {
|
|
270
|
+
const { first, count } = status.unknownUnknowns;
|
|
271
|
+
if (first) {
|
|
272
|
+
if (count === 1) {
|
|
273
|
+
parts.push(`New concept: "${first}"`);
|
|
274
|
+
} else {
|
|
275
|
+
parts.push(`New concepts: "${first}" +${count - 1} more`);
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
parts.push(`${count} new concepts to learn`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (status.dueReviews.count > 0) {
|
|
282
|
+
const { first, count } = status.dueReviews;
|
|
283
|
+
if (first) {
|
|
284
|
+
if (count === 1) {
|
|
285
|
+
parts.push(`Due for review: "${first}"`);
|
|
286
|
+
} else {
|
|
287
|
+
parts.push(`Due for review: "${first}" +${count - 1} more`);
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
parts.push(`${count} concepts due for review`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
message = parts.join(" | ");
|
|
294
|
+
variant = "warning";
|
|
295
|
+
}
|
|
296
|
+
client.tui.showToast({
|
|
297
|
+
body: {
|
|
298
|
+
title: "\u{1F4DA} VibeLearning",
|
|
299
|
+
message,
|
|
300
|
+
variant,
|
|
301
|
+
duration: 5e3
|
|
302
|
+
}
|
|
303
|
+
}).catch(() => {
|
|
304
|
+
});
|
|
214
305
|
client.app.log({
|
|
215
306
|
level: "info",
|
|
216
|
-
message: `[VibeLearning]
|
|
307
|
+
message: `[VibeLearning] Session created: ${input.id} (unknowns: ${status.unknownUnknowns.count}, reviews: ${status.dueReviews.count})`
|
|
217
308
|
}).catch(() => {
|
|
218
309
|
});
|
|
219
|
-
if (event.type === "session.created") {
|
|
220
|
-
const props = event.properties;
|
|
221
|
-
const sessionID = props?.info?.id;
|
|
222
|
-
client.app.log({
|
|
223
|
-
level: "info",
|
|
224
|
-
message: `[VibeLearning] session.created - sessionID: ${sessionID}`
|
|
225
|
-
}).catch(() => {
|
|
226
|
-
});
|
|
227
|
-
if (!sessionID)
|
|
228
|
-
return;
|
|
229
|
-
lastSessionID = sessionID;
|
|
230
|
-
sessionContextShown.add(sessionID);
|
|
231
|
-
try {
|
|
232
|
-
client.tui.showToast({
|
|
233
|
-
body: {
|
|
234
|
-
title: "\u{1F4DA} VibeLearning",
|
|
235
|
-
message: "Session started!",
|
|
236
|
-
variant: "info",
|
|
237
|
-
duration: 3e3
|
|
238
|
-
}
|
|
239
|
-
}).catch(() => {
|
|
240
|
-
});
|
|
241
|
-
const contextPrompt = `[VibeLearning - Session Start]
|
|
242
|
-
|
|
243
|
-
Execute NOW and show results to user:
|
|
244
|
-
1. Call mcp__vibe-learning__get_unknown_unknowns with period="month", limit=5
|
|
245
|
-
2. Call mcp__vibe-learning__get_due_reviews with limit=5
|
|
246
|
-
|
|
247
|
-
Format as brief notification:
|
|
248
|
-
\u{1F4DA} **[VibeLearning]**
|
|
249
|
-
\u2022 X unexplored concepts
|
|
250
|
-
\u2022 Y due for review
|
|
251
|
-
|
|
252
|
-
If nothing to show, skip silently.`;
|
|
253
|
-
injectPrompt(sessionID, contextPrompt);
|
|
254
|
-
} catch (err) {
|
|
255
|
-
client.app.log({ level: "error", message: `[VibeLearning] Session start error: ${err}` }).catch(() => {
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
310
|
},
|
|
260
311
|
"tool.execute.after": async (input, output) => {
|
|
261
312
|
lastSessionID = input.sessionID;
|
|
@@ -293,21 +344,6 @@ If nothing to show, skip silently.`;
|
|
|
293
344
|
if (role !== "user" || !content)
|
|
294
345
|
return;
|
|
295
346
|
lastSessionID = input.sessionID;
|
|
296
|
-
if (!sessionContextShown.has(input.sessionID)) {
|
|
297
|
-
sessionContextShown.add(input.sessionID);
|
|
298
|
-
if (sessionContextShown.size > 10) {
|
|
299
|
-
const oldest = sessionContextShown.values().next().value;
|
|
300
|
-
if (oldest)
|
|
301
|
-
sessionContextShown.delete(oldest);
|
|
302
|
-
}
|
|
303
|
-
setTimeout(() => {
|
|
304
|
-
if (lastSessionID) {
|
|
305
|
-
injectPrompt(lastSessionID, SESSION_START_PROMPT);
|
|
306
|
-
}
|
|
307
|
-
}, 100);
|
|
308
|
-
client.app.log({ level: "info", message: `[VibeLearning] Injected session start context` }).catch(() => {
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
347
|
const cmd = parseLearnCommand(content);
|
|
312
348
|
if (cmd) {
|
|
313
349
|
if (cmd === "off") {
|
|
@@ -329,15 +365,11 @@ If nothing to show, skip silently.`;
|
|
|
329
365
|
}
|
|
330
366
|
const prompt = COMMAND_PROMPTS[cmd];
|
|
331
367
|
if (prompt) {
|
|
332
|
-
client.tui.showToast({
|
|
333
|
-
body: { title: "\u{1F393} VibeLearning", message: `Executing /learn ${cmd}...`, variant: "info", duration: 2e3 }
|
|
334
|
-
}).catch(() => {
|
|
335
|
-
});
|
|
336
368
|
setTimeout(() => {
|
|
337
369
|
if (lastSessionID) {
|
|
338
370
|
injectPrompt(lastSessionID, prompt);
|
|
339
371
|
}
|
|
340
|
-
},
|
|
372
|
+
}, 100);
|
|
341
373
|
}
|
|
342
374
|
client.app.log({ level: "info", message: `[VibeLearning] Command: ${cmd}` }).catch(() => {
|
|
343
375
|
});
|
|
@@ -360,5 +392,6 @@ If nothing to show, skip silently.`;
|
|
|
360
392
|
};
|
|
361
393
|
var src_default = VibeLearningPlugin;
|
|
362
394
|
export {
|
|
395
|
+
VibeLearningPlugin,
|
|
363
396
|
src_default as default
|
|
364
397
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibe-learning-opencode",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "VibeLearning plugin for OpenCode - spaced repetition learning while coding",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"dist"
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@opencode-ai/plugin --external:@opencode-ai/sdk",
|
|
16
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@opencode-ai/plugin --external:@opencode-ai/sdk --external:better-sqlite3",
|
|
17
17
|
"prepublishOnly": "npm run build"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
@@ -33,11 +33,14 @@
|
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@opencode-ai/plugin": "*",
|
|
36
|
-
"@opencode-ai/sdk": "*"
|
|
36
|
+
"@opencode-ai/sdk": "*",
|
|
37
|
+
"better-sqlite3": ">=9.0.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"@opencode-ai/plugin": "^1.1.4",
|
|
40
41
|
"@opencode-ai/sdk": "^1.0.0",
|
|
42
|
+
"@types/better-sqlite3": "^7.6.8",
|
|
43
|
+
"better-sqlite3": "^11.0.0",
|
|
41
44
|
"esbuild": "^0.20.0",
|
|
42
45
|
"typescript": "^5.0.0"
|
|
43
46
|
}
|