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.
Files changed (2) hide show
  1. package/dist/index.js +113 -80
  2. 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 (v2 - independent toggles)"
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
- // Generic event handler for ALL events
213
- event: async ({ event }) => {
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] Event received: ${event.type}`
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
- }, 500);
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.6",
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
  }