vibe-learning-opencode 0.1.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,32 @@
1
+ /**
2
+ * VibeLearning OpenCode Plugin
3
+ *
4
+ * Handles /learn commands and auto-learning triggers.
5
+ */
6
+ import type { PluginInput } from "@opencode-ai/plugin";
7
+ declare const VibeLearningPlugin: (ctx: PluginInput) => {
8
+ "tool.execute.after": (input: {
9
+ tool: string;
10
+ sessionID: string;
11
+ callID: string;
12
+ }, output: {
13
+ title: string;
14
+ output: string;
15
+ metadata: any;
16
+ }) => Promise<void>;
17
+ "chat.message": (input: {
18
+ sessionID: string;
19
+ agent?: string;
20
+ model?: unknown;
21
+ messageID?: string;
22
+ variant?: string;
23
+ }, output: {
24
+ message: {
25
+ role: string;
26
+ content?: string;
27
+ };
28
+ parts: unknown[];
29
+ }) => Promise<void>;
30
+ };
31
+ export default VibeLearningPlugin;
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAsJvD,QAAA,MAAM,kBAAkB,GAAI,KAAK,WAAW;kCAoB/B;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,UAClD;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE,KACvD,OAAO,CAAC,IAAI,CAAC;4BAiCP;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,UAC3F;QAAE,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,KAAK,EAAE,OAAO,EAAE,CAAA;KAAE,KACxE,OAAO,CAAC,IAAI,CAAC;CAmCnB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,207 @@
1
+ // src/index.ts
2
+ var CONFIG = {
3
+ TOOL_THRESHOLD: 3,
4
+ COOLDOWN_MS: 15 * 60 * 1e3,
5
+ MAX_CONCEPTS: 10,
6
+ SIGNIFICANT_TOOLS: ["edit", "write", "bash"]
7
+ // 소문자로 변경
8
+ };
9
+ var toolCount = 0;
10
+ var lastLearningPrompt = 0;
11
+ var recentConcepts = [];
12
+ var currentMode = "after";
13
+ var lastSessionID = null;
14
+ var FILE_CONCEPTS = [
15
+ { pattern: /test|spec|__tests__/i, concept: "unit-testing" },
16
+ { pattern: /auth|login|session|jwt|oauth/i, concept: "authentication" },
17
+ { pattern: /api|endpoint|route/i, concept: "api-design" },
18
+ { pattern: /cache|redis|memcache/i, concept: "caching" },
19
+ { pattern: /hook|use[A-Z]/i, concept: "react-hooks" },
20
+ { pattern: /\.tsx$|\.jsx$/i, concept: "react-components" },
21
+ { pattern: /db|database|prisma|sequelize|typeorm/i, concept: "database" }
22
+ ];
23
+ function addConcept(concept) {
24
+ if (!recentConcepts.includes(concept)) {
25
+ recentConcepts.push(concept);
26
+ if (recentConcepts.length > CONFIG.MAX_CONCEPTS)
27
+ recentConcepts.shift();
28
+ }
29
+ }
30
+ function extractConceptFromPath(filePath) {
31
+ const lower = filePath.toLowerCase();
32
+ for (const { pattern, concept } of FILE_CONCEPTS) {
33
+ if (pattern instanceof RegExp && pattern.test(lower))
34
+ return concept;
35
+ }
36
+ return null;
37
+ }
38
+ var COMMAND_PROMPTS = {
39
+ status: `Execute NOW: Call mcp__vibe-learning__get_mode and mcp__vibe-learning__should_ask_question, then show:
40
+ - Current learning mode
41
+ - Daily questions remaining
42
+ - Cooldown status`,
43
+ stats: `Execute NOW: Call mcp__vibe-learning__get_stats with period="month", then format as a dashboard showing:
44
+ - Total concepts learned
45
+ - Correct rate percentage
46
+ - Average level
47
+ - Per-concept breakdown`,
48
+ report: `Execute NOW: Call mcp__vibe-learning__get_report_data with period="week", then format as a report with:
49
+ - Summary
50
+ - Weak areas needing reinforcement
51
+ - Strong areas
52
+ - Skipped concepts
53
+ - Growth trends`,
54
+ "report-month": `Execute NOW: Call mcp__vibe-learning__get_report_data with period="month", then format as monthly report.`,
55
+ "report-save": `Execute NOW: Call mcp__vibe-learning__save_report with period="week". Confirm file saved.`,
56
+ unknowns: `Execute NOW: Call mcp__vibe-learning__get_unknown_unknowns with period="month" and limit=10, then show:
57
+ - Unknown concepts by priority
58
+ - Why each is important
59
+ - Related concepts`,
60
+ "unknowns-save": `Execute NOW: Call mcp__vibe-learning__save_unknowns with period="month" and limit=20. Confirm file saved.`,
61
+ review: `Execute NOW: Call mcp__vibe-learning__get_due_reviews with limit=5. For each due concept, ask a level-appropriate question and record results.`,
62
+ interview: `Execute NOW: Call mcp__vibe-learning__get_stats with period="month". Then conduct interview-style Level 3-5 questions on the learned concepts.`,
63
+ pause: `Execute NOW: Call mcp__vibe-learning__set_mode with mode="after" and paused_until set to 1 hour from now (ISO format). Confirm paused.`,
64
+ off: `Execute NOW: Call mcp__vibe-learning__set_mode with mode="off". Confirm learning is off (recording continues).`,
65
+ after: `Execute NOW: Call mcp__vibe-learning__set_mode with mode="after". Confirm mode changed.`,
66
+ before: `Execute NOW: Call mcp__vibe-learning__set_mode with mode="before". Confirm mode changed.`,
67
+ senior: `Execute NOW: Call mcp__vibe-learning__set_mode with mode="senior". Confirm senior mode enabled.`,
68
+ "senior-light": `Execute NOW: Call mcp__vibe-learning__set_mode with mode="senior_light". Confirm mode changed.`
69
+ };
70
+ var AUTO_LEARNING_PROMPT = `[VibeLearning - Learning Time]
71
+
72
+ Task completed. Execute these steps NOW:
73
+
74
+ 1. Call mcp__vibe-learning__should_ask_question
75
+ 2. If shouldAsk is true:
76
+ - Call mcp__vibe-learning__get_concept_level with a relevant concept from this task
77
+ - Ask a level-appropriate question (L1: recognition, L2: understanding, L3: comparison, L4: edge cases, L5: design)
78
+ - After user answers, call mcp__vibe-learning__record_learning
79
+ 3. If shouldAsk is false, briefly mention cooldown
80
+
81
+ Ask naturally, like conversation.`;
82
+ function parseLearnCommand(text) {
83
+ const lower = text.toLowerCase().trim();
84
+ if (lower === "/learn" || lower === "/learn status")
85
+ return "status";
86
+ if (lower === "/learn stats")
87
+ return "stats";
88
+ if (lower === "/learn report --save" || lower === "/learn report -s")
89
+ return "report-save";
90
+ if (lower === "/learn report month")
91
+ return "report-month";
92
+ if (lower === "/learn report")
93
+ return "report";
94
+ if (lower === "/learn unknowns --save" || lower === "/learn unknowns -s")
95
+ return "unknowns-save";
96
+ if (lower === "/learn unknowns")
97
+ return "unknowns";
98
+ if (lower === "/learn review")
99
+ return "review";
100
+ if (lower === "/learn interview")
101
+ return "interview";
102
+ if (lower === "/learn pause")
103
+ return "pause";
104
+ if (lower === "/learn off")
105
+ return "off";
106
+ if (lower === "/learn after")
107
+ return "after";
108
+ if (lower === "/learn before")
109
+ return "before";
110
+ if (lower === "/learn senior light")
111
+ return "senior-light";
112
+ if (lower === "/learn senior")
113
+ return "senior";
114
+ if (lower.startsWith("/learn focus "))
115
+ return "focus";
116
+ return null;
117
+ }
118
+ var VibeLearningPlugin = (ctx) => {
119
+ const { client } = ctx;
120
+ client.app.log({
121
+ level: "info",
122
+ message: "[VibeLearning] Plugin loaded"
123
+ }).catch(() => {
124
+ });
125
+ const injectPrompt = (sessionID, prompt) => {
126
+ client.session.prompt({
127
+ path: { id: sessionID },
128
+ body: { parts: [{ type: "text", text: prompt }] },
129
+ query: { directory: ctx.directory }
130
+ }).catch((err) => {
131
+ client.app.log({ level: "error", message: `[VibeLearning] Inject failed: ${err}` }).catch(() => {
132
+ });
133
+ });
134
+ };
135
+ return {
136
+ "tool.execute.after": async (input, output) => {
137
+ lastSessionID = input.sessionID;
138
+ const toolLower = input.tool.toLowerCase();
139
+ if (!CONFIG.SIGNIFICANT_TOOLS.includes(toolLower))
140
+ return;
141
+ toolCount++;
142
+ const filePath = output.metadata?.file_path || output.title || "";
143
+ const concept = extractConceptFromPath(filePath);
144
+ if (concept)
145
+ addConcept(concept);
146
+ if (toolCount >= CONFIG.TOOL_THRESHOLD && currentMode !== "off") {
147
+ const now = Date.now();
148
+ if (now - lastLearningPrompt >= CONFIG.COOLDOWN_MS) {
149
+ toolCount = 0;
150
+ lastLearningPrompt = now;
151
+ const sessionID = lastSessionID;
152
+ setTimeout(() => {
153
+ if (sessionID) {
154
+ client.tui.showToast({
155
+ body: { title: "\u{1F393} VibeLearning", message: "Learning time!", variant: "info", duration: 3e3 }
156
+ }).catch(() => {
157
+ });
158
+ injectPrompt(sessionID, AUTO_LEARNING_PROMPT);
159
+ }
160
+ }, 2e3);
161
+ }
162
+ }
163
+ },
164
+ "chat.message": async (input, output) => {
165
+ const { message } = output;
166
+ if (!message)
167
+ return;
168
+ const { role, content } = message;
169
+ if (role !== "user" || !content)
170
+ return;
171
+ lastSessionID = input.sessionID;
172
+ const cmd = parseLearnCommand(content);
173
+ if (cmd) {
174
+ if (cmd === "off")
175
+ currentMode = "off";
176
+ else if (cmd === "after")
177
+ currentMode = "after";
178
+ else if (cmd === "before")
179
+ currentMode = "before";
180
+ else if (cmd === "senior")
181
+ currentMode = "senior";
182
+ else if (cmd === "senior-light")
183
+ currentMode = "senior_light";
184
+ else if (cmd === "pause")
185
+ lastLearningPrompt = Date.now();
186
+ const prompt = COMMAND_PROMPTS[cmd];
187
+ if (prompt) {
188
+ client.tui.showToast({
189
+ body: { title: "\u{1F393} VibeLearning", message: `Executing /learn ${cmd}...`, variant: "info", duration: 2e3 }
190
+ }).catch(() => {
191
+ });
192
+ setTimeout(() => {
193
+ if (lastSessionID) {
194
+ injectPrompt(lastSessionID, prompt);
195
+ }
196
+ }, 500);
197
+ }
198
+ client.app.log({ level: "info", message: `[VibeLearning] Command: ${cmd}` }).catch(() => {
199
+ });
200
+ }
201
+ }
202
+ };
203
+ };
204
+ var src_default = VibeLearningPlugin;
205
+ export {
206
+ src_default as default
207
+ };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "vibe-learning-opencode",
3
+ "version": "0.1.0",
4
+ "description": "VibeLearning plugin for OpenCode - spaced repetition learning while coding",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "scripts": {
16
+ "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@opencode-ai/plugin",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "opencode",
21
+ "plugin",
22
+ "learning",
23
+ "spaced-repetition",
24
+ "ai",
25
+ "coding"
26
+ ],
27
+ "author": "jeongjeong-il",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/jeongjeong-il/vibe-learning.git",
32
+ "directory": "packages/opencode-plugin"
33
+ },
34
+ "peerDependencies": {
35
+ "@opencode-ai/plugin": "*"
36
+ },
37
+ "devDependencies": {
38
+ "@opencode-ai/plugin": "^1.1.4",
39
+ "esbuild": "^0.20.0",
40
+ "typescript": "^5.0.0"
41
+ }
42
+ }