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.
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +207 -0
- package/package.json +42 -0
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|