pikakit 2.0.0 → 3.0.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/README.md +8 -8
- package/lib/agent-cli/bin/agent.js +187 -0
- package/lib/agent-cli/lib/audit.js +154 -0
- package/lib/agent-cli/lib/audit.test.js +100 -0
- package/lib/agent-cli/lib/auto-learn.js +319 -0
- package/lib/agent-cli/lib/backup.js +138 -0
- package/lib/agent-cli/lib/backup.test.js +78 -0
- package/lib/agent-cli/lib/cognitive-lesson.js +476 -0
- package/lib/agent-cli/lib/completion.js +149 -0
- package/lib/agent-cli/lib/config.js +35 -0
- package/lib/agent-cli/lib/eslint-fix.js +238 -0
- package/lib/agent-cli/lib/evolution-signal.js +215 -0
- package/lib/agent-cli/lib/export.js +86 -0
- package/lib/agent-cli/lib/export.test.js +65 -0
- package/lib/agent-cli/lib/fix.js +337 -0
- package/lib/agent-cli/lib/fix.test.js +80 -0
- package/lib/agent-cli/lib/gemini-export.js +83 -0
- package/lib/agent-cli/lib/generate-registry.js +42 -0
- package/lib/agent-cli/lib/hooks/install-hooks.js +152 -0
- package/lib/agent-cli/lib/hooks/lint-learn.js +172 -0
- package/lib/agent-cli/lib/icons.js +93 -0
- package/lib/agent-cli/lib/ignore.js +116 -0
- package/lib/agent-cli/lib/ignore.test.js +58 -0
- package/lib/agent-cli/lib/init.js +124 -0
- package/lib/agent-cli/lib/knowledge-index.js +326 -0
- package/lib/agent-cli/lib/knowledge-metrics.js +335 -0
- package/lib/agent-cli/lib/knowledge-retention.js +398 -0
- package/lib/agent-cli/lib/knowledge-validator.js +312 -0
- package/lib/agent-cli/lib/learn.js +255 -0
- package/lib/agent-cli/lib/learn.test.js +70 -0
- package/lib/agent-cli/lib/proposals.js +199 -0
- package/lib/agent-cli/lib/proposals.test.js +56 -0
- package/lib/agent-cli/lib/recall.js +826 -0
- package/lib/agent-cli/lib/recall.test.js +107 -0
- package/lib/agent-cli/lib/selfevolution-bridge.js +167 -0
- package/lib/agent-cli/lib/settings.js +203 -0
- package/lib/agent-cli/lib/skill-learn.js +296 -0
- package/lib/agent-cli/lib/stats.js +132 -0
- package/lib/agent-cli/lib/stats.test.js +94 -0
- package/lib/agent-cli/lib/types.js +33 -0
- package/lib/agent-cli/lib/ui/audit-ui.js +146 -0
- package/lib/agent-cli/lib/ui/backup-ui.js +107 -0
- package/lib/agent-cli/lib/ui/clack-helpers.js +317 -0
- package/lib/agent-cli/lib/ui/common.js +83 -0
- package/lib/agent-cli/lib/ui/completion-ui.js +126 -0
- package/lib/agent-cli/lib/ui/custom-select.js +69 -0
- package/lib/agent-cli/lib/ui/dashboard-ui.js +222 -0
- package/lib/agent-cli/lib/ui/evolution-signals-ui.js +107 -0
- package/lib/agent-cli/lib/ui/export-ui.js +94 -0
- package/lib/agent-cli/lib/ui/fix-all-ui.js +191 -0
- package/lib/agent-cli/lib/ui/help-ui.js +49 -0
- package/lib/agent-cli/lib/ui/index.js +199 -0
- package/lib/agent-cli/lib/ui/init-ui.js +56 -0
- package/lib/agent-cli/lib/ui/knowledge-ui.js +55 -0
- package/lib/agent-cli/lib/ui/learn-ui.js +706 -0
- package/lib/agent-cli/lib/ui/lessons-ui.js +148 -0
- package/lib/agent-cli/lib/ui/pretty.js +145 -0
- package/lib/agent-cli/lib/ui/proposals-ui.js +99 -0
- package/lib/agent-cli/lib/ui/recall-ui.js +342 -0
- package/lib/agent-cli/lib/ui/routing-demo.js +79 -0
- package/lib/agent-cli/lib/ui/routing-ui.js +325 -0
- package/lib/agent-cli/lib/ui/settings-ui.js +381 -0
- package/lib/agent-cli/lib/ui/stats-ui.js +123 -0
- package/lib/agent-cli/lib/ui/watch-ui.js +236 -0
- package/lib/agent-cli/lib/watcher.js +181 -0
- package/lib/agent-cli/lib/watcher.test.js +85 -0
- package/lib/agent-cli/src/MIGRATION.md +418 -0
- package/lib/agent-cli/src/README.md +367 -0
- package/lib/agent-cli/src/core/evolution/evolution-signal.js +42 -0
- package/lib/agent-cli/src/core/evolution/index.js +17 -0
- package/lib/agent-cli/src/core/evolution/review-gate.js +40 -0
- package/lib/agent-cli/src/core/evolution/signal-detector.js +137 -0
- package/lib/agent-cli/src/core/evolution/signal-queue.js +79 -0
- package/lib/agent-cli/src/core/evolution/threshold-checker.js +79 -0
- package/lib/agent-cli/src/core/index.js +15 -0
- package/lib/agent-cli/src/core/learning/cognitive-enhancer.js +282 -0
- package/lib/agent-cli/src/core/learning/index.js +12 -0
- package/lib/agent-cli/src/core/learning/lesson-synthesizer.js +83 -0
- package/lib/agent-cli/src/core/scanning/index.js +14 -0
- package/lib/agent-cli/src/data/index.js +13 -0
- package/lib/agent-cli/src/data/repositories/index.js +8 -0
- package/lib/agent-cli/src/data/repositories/lesson-repository.js +130 -0
- package/lib/agent-cli/src/data/repositories/signal-repository.js +119 -0
- package/lib/agent-cli/src/data/storage/index.js +8 -0
- package/lib/agent-cli/src/data/storage/json-storage.js +64 -0
- package/lib/agent-cli/src/data/storage/yaml-storage.js +66 -0
- package/lib/agent-cli/src/infrastructure/index.js +13 -0
- package/lib/agent-cli/src/presentation/formatters/skill-formatter.js +232 -0
- package/lib/agent-cli/src/services/export-service.js +162 -0
- package/lib/agent-cli/src/services/index.js +13 -0
- package/lib/agent-cli/src/services/learning-service.js +99 -0
- package/package.json +5 -3
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Smart Learning Script - ESM Version (Production-Ready)
|
|
4
|
+
*
|
|
5
|
+
* The "Teacher" script. Adds new lessons to the system memory.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Manual lesson addition
|
|
9
|
+
* - Category tagging
|
|
10
|
+
* - Source tracking (manual, eslint, test-failure)
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* agent learn --add --pattern "regex" --message "why bad"
|
|
14
|
+
* agent learn --list
|
|
15
|
+
* agent learn --remove <id>
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from "fs";
|
|
19
|
+
import path from "path";
|
|
20
|
+
import yaml from "js-yaml";
|
|
21
|
+
import { KNOWLEDGE_DIR, LESSONS_PATH, DEBUG, VERSION } from "./config.js";
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// CORE FUNCTIONS
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Load knowledge base from YAML file
|
|
29
|
+
* @returns {{ lessons: Array, version?: number }}
|
|
30
|
+
*/
|
|
31
|
+
function loadKnowledge() {
|
|
32
|
+
try {
|
|
33
|
+
if (!fs.existsSync(LESSONS_PATH)) {
|
|
34
|
+
const initial = { lessons: [], version: 1 };
|
|
35
|
+
fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
|
|
36
|
+
fs.writeFileSync(LESSONS_PATH, yaml.dump(initial), "utf8");
|
|
37
|
+
return initial;
|
|
38
|
+
}
|
|
39
|
+
const content = fs.readFileSync(LESSONS_PATH, "utf8");
|
|
40
|
+
return yaml.load(content) || { lessons: [], version: 1 };
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error("❌ Failed to load knowledge base:", error.message);
|
|
43
|
+
if (DEBUG) console.error(error.stack);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Save knowledge base to YAML file
|
|
50
|
+
* @param {{ lessons: Array, version?: number }} data
|
|
51
|
+
*/
|
|
52
|
+
function saveKnowledge(data) {
|
|
53
|
+
try {
|
|
54
|
+
fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
|
|
55
|
+
const str = yaml.dump(data, { lineWidth: -1, quotingType: '"' });
|
|
56
|
+
fs.writeFileSync(LESSONS_PATH, str, "utf8");
|
|
57
|
+
console.log("✅ Knowledge base updated successfully.");
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("❌ Failed to save knowledge base:", error.message);
|
|
60
|
+
if (DEBUG) console.error(error.stack);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Add a new lesson to the knowledge base
|
|
67
|
+
* @param {string} pattern - Regex pattern
|
|
68
|
+
* @param {string} message - Explanation message
|
|
69
|
+
* @param {string} severity - WARNING or ERROR
|
|
70
|
+
* @param {string} category - Category tag
|
|
71
|
+
*/
|
|
72
|
+
function addLesson(pattern, message, severity = "WARNING", category = "general") {
|
|
73
|
+
const db = loadKnowledge();
|
|
74
|
+
|
|
75
|
+
// Validate Regex
|
|
76
|
+
try {
|
|
77
|
+
new RegExp(pattern);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.error("❌ Invalid Regex pattern:", e.message);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Validate severity
|
|
84
|
+
if (!["WARNING", "ERROR"].includes(severity.toUpperCase())) {
|
|
85
|
+
console.error("❌ Invalid severity. Must be WARNING or ERROR");
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check for duplicates
|
|
90
|
+
const exists = db.lessons.some(l => l.pattern === pattern);
|
|
91
|
+
if (exists) {
|
|
92
|
+
console.log("⚠️ Pattern already exists in knowledge base.");
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const id = `LEARN-${String(db.lessons.length + 1).padStart(3, "0")}`;
|
|
97
|
+
|
|
98
|
+
const lesson = {
|
|
99
|
+
id,
|
|
100
|
+
pattern,
|
|
101
|
+
message,
|
|
102
|
+
severity: severity.toUpperCase(),
|
|
103
|
+
category,
|
|
104
|
+
source: "manual",
|
|
105
|
+
hitCount: 0,
|
|
106
|
+
lastHit: null,
|
|
107
|
+
autoEscalated: false,
|
|
108
|
+
addedAt: new Date().toISOString()
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
db.lessons.push(lesson);
|
|
112
|
+
saveKnowledge(db);
|
|
113
|
+
|
|
114
|
+
console.log(`\n🎓 Lesson Learned: [${id}]`);
|
|
115
|
+
console.log(` Pattern: /${pattern}/`);
|
|
116
|
+
console.log(` Message: ${message}`);
|
|
117
|
+
console.log(` Severity: ${severity.toUpperCase()}`);
|
|
118
|
+
console.log(` Category: ${category}\n`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Remove a lesson by ID
|
|
123
|
+
* @param {string} lessonId
|
|
124
|
+
*/
|
|
125
|
+
function removeLesson(lessonId) {
|
|
126
|
+
const db = loadKnowledge();
|
|
127
|
+
const idx = db.lessons.findIndex(l => l.id === lessonId.toUpperCase());
|
|
128
|
+
|
|
129
|
+
if (idx === -1) {
|
|
130
|
+
console.log(`❌ Lesson not found: ${lessonId}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const removed = db.lessons.splice(idx, 1)[0];
|
|
135
|
+
saveKnowledge(db);
|
|
136
|
+
|
|
137
|
+
console.log(`\n🗑️ Removed lesson: [${removed.id}]`);
|
|
138
|
+
console.log(` Pattern: /${removed.pattern}/\n`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* List all learned lessons
|
|
143
|
+
* @param {string} category - Filter by category
|
|
144
|
+
*/
|
|
145
|
+
function listLessons(category = null) {
|
|
146
|
+
const db = loadKnowledge();
|
|
147
|
+
|
|
148
|
+
if (!db.lessons || db.lessons.length === 0) {
|
|
149
|
+
console.log("\nℹ️ No lessons learned yet.");
|
|
150
|
+
console.log(" Use: agent learn --add --pattern \"pat\" --message \"msg\"\n");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let lessons = db.lessons;
|
|
155
|
+
if (category) {
|
|
156
|
+
lessons = lessons.filter(l => l.category === category);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log(`\n🧠 PikaKit Knowledge Base (${lessons.length} lesson(s))\n`);
|
|
160
|
+
console.log("─".repeat(60));
|
|
161
|
+
|
|
162
|
+
lessons.forEach(l => {
|
|
163
|
+
const icon = l.severity === "ERROR" ? "❌" : "⚠️";
|
|
164
|
+
const hits = l.hitCount ? ` (${l.hitCount} hits)` : "";
|
|
165
|
+
const escalated = l.autoEscalated ? " ⚡" : "";
|
|
166
|
+
|
|
167
|
+
console.log(`${icon} [${l.id}] ${l.message}${hits}${escalated}`);
|
|
168
|
+
console.log(` Pattern: /${l.pattern}/`);
|
|
169
|
+
console.log(` Category: ${l.category || "general"} | Source: ${l.source || "manual"}`);
|
|
170
|
+
console.log("");
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ============================================================================
|
|
175
|
+
// CLI
|
|
176
|
+
// ============================================================================
|
|
177
|
+
|
|
178
|
+
function printHelp() {
|
|
179
|
+
console.log(`
|
|
180
|
+
🎓 Smart Learning Tool v${VERSION}
|
|
181
|
+
|
|
182
|
+
USAGE:
|
|
183
|
+
agent learn --add --pattern "..." --message "..."
|
|
184
|
+
agent learn --list [--category <cat>]
|
|
185
|
+
agent learn --remove <ID>
|
|
186
|
+
|
|
187
|
+
OPTIONS:
|
|
188
|
+
--add Add a new lesson
|
|
189
|
+
--pattern Regex pattern to flag
|
|
190
|
+
--message Explanation message
|
|
191
|
+
--severity WARNING (default) or ERROR
|
|
192
|
+
--category Category tag (default: general)
|
|
193
|
+
--list List all lessons
|
|
194
|
+
--remove Remove lesson by ID
|
|
195
|
+
--help Show this help
|
|
196
|
+
|
|
197
|
+
EXAMPLES:
|
|
198
|
+
agent learn --add --pattern "console\\.log" --message "No console.log in production" --severity ERROR
|
|
199
|
+
agent learn --list
|
|
200
|
+
agent learn --remove LEARN-001
|
|
201
|
+
`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function main() {
|
|
205
|
+
const args = process.argv.slice(2);
|
|
206
|
+
|
|
207
|
+
if (args.includes("--help") || args.length === 0) {
|
|
208
|
+
printHelp();
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (args.includes("--list")) {
|
|
213
|
+
const catIdx = args.indexOf("--category");
|
|
214
|
+
const category = catIdx !== -1 ? args[catIdx + 1] : null;
|
|
215
|
+
listLessons(category);
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (args.includes("--remove")) {
|
|
220
|
+
const removeIdx = args.indexOf("--remove");
|
|
221
|
+
const lessonId = args[removeIdx + 1];
|
|
222
|
+
if (!lessonId) {
|
|
223
|
+
console.error("❌ Missing lesson ID for --remove");
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
removeLesson(lessonId);
|
|
227
|
+
process.exit(0);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (args.includes("--add")) {
|
|
231
|
+
const pIdx = args.indexOf("--pattern");
|
|
232
|
+
const mIdx = args.indexOf("--message");
|
|
233
|
+
const sIdx = args.indexOf("--severity");
|
|
234
|
+
const cIdx = args.indexOf("--category");
|
|
235
|
+
|
|
236
|
+
if (pIdx === -1 || mIdx === -1) {
|
|
237
|
+
console.error("❌ --pattern and --message are required for --add");
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const pattern = args[pIdx + 1];
|
|
242
|
+
const message = args[mIdx + 1];
|
|
243
|
+
const severity = sIdx !== -1 ? args[sIdx + 1] : "WARNING";
|
|
244
|
+
const category = cIdx !== -1 ? args[cIdx + 1] : "general";
|
|
245
|
+
|
|
246
|
+
if (!pattern || !message) {
|
|
247
|
+
console.error("❌ Missing values for pattern/message");
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
addLesson(pattern, message, severity, category);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
main();
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for learn.js functionality
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import os from "os";
|
|
8
|
+
|
|
9
|
+
// Mock config để test
|
|
10
|
+
const TEST_DIR = path.join(os.tmpdir(), "agent-skill-kit-test");
|
|
11
|
+
const KNOWLEDGE_DIR = path.join(TEST_DIR, ".agent", "knowledge");
|
|
12
|
+
const LESSONS_PATH = path.join(KNOWLEDGE_DIR, "lessons-learned.yaml");
|
|
13
|
+
|
|
14
|
+
describe("Knowledge Base Operations", () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
fs.rmSync(TEST_DIR, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("creates initial knowledge file if missing", () => {
|
|
24
|
+
// Simulate missing file scenario
|
|
25
|
+
expect(fs.existsSync(LESSONS_PATH)).toBe(false);
|
|
26
|
+
|
|
27
|
+
// Create initial file
|
|
28
|
+
const initial = { lessons: [] };
|
|
29
|
+
fs.writeFileSync(LESSONS_PATH, JSON.stringify(initial), "utf8");
|
|
30
|
+
|
|
31
|
+
expect(fs.existsSync(LESSONS_PATH)).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("can write and read lessons", () => {
|
|
35
|
+
const lesson = {
|
|
36
|
+
id: "LEARN-001",
|
|
37
|
+
pattern: "console\\.log",
|
|
38
|
+
message: "No console.log in production",
|
|
39
|
+
severity: "WARNING",
|
|
40
|
+
addedAt: new Date().toISOString()
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const data = { lessons: [lesson] };
|
|
44
|
+
fs.writeFileSync(LESSONS_PATH, JSON.stringify(data), "utf8");
|
|
45
|
+
|
|
46
|
+
const content = fs.readFileSync(LESSONS_PATH, "utf8");
|
|
47
|
+
const parsed = JSON.parse(content);
|
|
48
|
+
|
|
49
|
+
expect(parsed.lessons).toHaveLength(1);
|
|
50
|
+
expect(parsed.lessons[0].id).toBe("LEARN-001");
|
|
51
|
+
expect(parsed.lessons[0].pattern).toBe("console\\.log");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("validates regex patterns", () => {
|
|
55
|
+
const validPattern = "console\\.log";
|
|
56
|
+
const invalidPattern = "[invalid(";
|
|
57
|
+
|
|
58
|
+
expect(() => new RegExp(validPattern)).not.toThrow();
|
|
59
|
+
expect(() => new RegExp(invalidPattern)).toThrow();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("validates severity values", () => {
|
|
63
|
+
const validSeverities = ["WARNING", "ERROR"];
|
|
64
|
+
const invalidSeverity = "INFO";
|
|
65
|
+
|
|
66
|
+
expect(validSeverities.includes("WARNING")).toBe(true);
|
|
67
|
+
expect(validSeverities.includes("ERROR")).toBe(true);
|
|
68
|
+
expect(validSeverities.includes(invalidSeverity)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Proposal Generator for Auto-Updating Flow
|
|
3
|
+
* Generates markdown instructions for AI agents to update skills
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import yaml from "js-yaml";
|
|
9
|
+
import { KNOWLEDGE_DIR, LESSONS_PATH, AGENT_DIR } from "./config.js";
|
|
10
|
+
import { loadSettings } from "./settings.js";
|
|
11
|
+
import { loadKnowledge } from "./recall.js";
|
|
12
|
+
|
|
13
|
+
/** Proposals directory */
|
|
14
|
+
const PROPOSALS_DIR = path.join(KNOWLEDGE_DIR, "proposals");
|
|
15
|
+
|
|
16
|
+
/** Dismissed proposals file */
|
|
17
|
+
const DISMISSED_FILE = path.join(PROPOSALS_DIR, "dismissed.yaml");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get lessons that qualify for proposals (hitCount >= threshold)
|
|
21
|
+
* @returns {Array<{ lesson: object, proposalPath: string }>}
|
|
22
|
+
*/
|
|
23
|
+
export function getQualifyingLessons() {
|
|
24
|
+
const settings = loadSettings();
|
|
25
|
+
const threshold = settings.updateThreshold || 5;
|
|
26
|
+
const db = loadKnowledge();
|
|
27
|
+
|
|
28
|
+
if (!db.lessons) return [];
|
|
29
|
+
|
|
30
|
+
const dismissed = loadDismissed();
|
|
31
|
+
|
|
32
|
+
return db.lessons
|
|
33
|
+
.filter(l => (l.hitCount || 0) >= threshold && !dismissed.has(l.id))
|
|
34
|
+
.map(lesson => ({
|
|
35
|
+
lesson,
|
|
36
|
+
proposalPath: path.join(PROPOSALS_DIR, `${lesson.id}.md`)
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Load dismissed proposal IDs
|
|
42
|
+
* @returns {Set<string>}
|
|
43
|
+
*/
|
|
44
|
+
function loadDismissed() {
|
|
45
|
+
try {
|
|
46
|
+
if (fs.existsSync(DISMISSED_FILE)) {
|
|
47
|
+
const data = yaml.load(fs.readFileSync(DISMISSED_FILE, "utf8")) || {};
|
|
48
|
+
return new Set(data.dismissed || []);
|
|
49
|
+
}
|
|
50
|
+
} catch (e) { }
|
|
51
|
+
return new Set();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Dismiss a proposal
|
|
56
|
+
* @param {string} lessonId
|
|
57
|
+
*/
|
|
58
|
+
export function dismissProposal(lessonId) {
|
|
59
|
+
fs.mkdirSync(PROPOSALS_DIR, { recursive: true });
|
|
60
|
+
const dismissed = loadDismissed();
|
|
61
|
+
dismissed.add(lessonId);
|
|
62
|
+
fs.writeFileSync(DISMISSED_FILE, yaml.dump({ dismissed: [...dismissed] }), "utf8");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate proposal markdown for AI agent
|
|
67
|
+
* @param {object} lesson - Lesson object
|
|
68
|
+
* @returns {string} Markdown content
|
|
69
|
+
*/
|
|
70
|
+
export function generateProposalMarkdown(lesson) {
|
|
71
|
+
const skillHint = guessSkillFromLesson(lesson);
|
|
72
|
+
|
|
73
|
+
return `# 🤖 Skill Update Proposal
|
|
74
|
+
|
|
75
|
+
## Pattern Detected
|
|
76
|
+
| Field | Value |
|
|
77
|
+
|-------|-------|
|
|
78
|
+
| **ID** | \`${lesson.id}\` |
|
|
79
|
+
| **Hit Count** | ${lesson.hitCount || 0} |
|
|
80
|
+
| **Severity** | ${lesson.severity || "WARNING"} |
|
|
81
|
+
| **Last Hit** | ${lesson.lastHit || "N/A"} |
|
|
82
|
+
|
|
83
|
+
## Message
|
|
84
|
+
> ${lesson.message}
|
|
85
|
+
|
|
86
|
+
## Pattern (Regex)
|
|
87
|
+
\`\`\`
|
|
88
|
+
${lesson.pattern}
|
|
89
|
+
\`\`\`
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 📝 Suggested Action for AI Agent
|
|
94
|
+
|
|
95
|
+
Please update the relevant skill file to include this rule:
|
|
96
|
+
|
|
97
|
+
### Target File
|
|
98
|
+
\`${skillHint}\`
|
|
99
|
+
|
|
100
|
+
### Add to Rules Section
|
|
101
|
+
\`\`\`markdown
|
|
102
|
+
### ${lesson.message}
|
|
103
|
+
- Pattern: \`${lesson.pattern}\`
|
|
104
|
+
- Severity: ${lesson.severity || "WARNING"}
|
|
105
|
+
- Auto-learned from violations
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## How to Apply
|
|
111
|
+
|
|
112
|
+
1. **Copy this entire proposal**
|
|
113
|
+
2. **Paste to your AI coding agent** (Claude, Gemini, etc.)
|
|
114
|
+
3. **Ask:** "Update the skill file with this rule"
|
|
115
|
+
|
|
116
|
+
The AI agent will modify the appropriate \`.agent/skills/*/SKILL.md\` file.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
*Generated by PikaKit v2.1.0*
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Guess which skill file this lesson relates to
|
|
126
|
+
* @param {object} lesson
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
function guessSkillFromLesson(lesson) {
|
|
130
|
+
const pattern = (lesson.pattern || "").toLowerCase();
|
|
131
|
+
const message = (lesson.message || "").toLowerCase();
|
|
132
|
+
const combined = pattern + " " + message;
|
|
133
|
+
|
|
134
|
+
// Frontend patterns
|
|
135
|
+
if (combined.includes("react") || combined.includes("component") || combined.includes("hook") || combined.includes("usestate") || combined.includes("useeffect")) {
|
|
136
|
+
return ".agent/skills/react-patterns/SKILL.md";
|
|
137
|
+
}
|
|
138
|
+
if (combined.includes("next") || combined.includes("getserverside") || combined.includes("getstaticprops") || combined.includes("app router")) {
|
|
139
|
+
return ".agent/skills/nextjs-best-practices/SKILL.md";
|
|
140
|
+
}
|
|
141
|
+
if (combined.includes("css") || combined.includes("style") || combined.includes("tailwind") || combined.includes("classname")) {
|
|
142
|
+
return ".agent/skills/frontend-design/SKILL.md";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Backend patterns
|
|
146
|
+
if (combined.includes("api") || combined.includes("fetch") || combined.includes("http") || combined.includes("endpoint") || combined.includes("rest")) {
|
|
147
|
+
return ".agent/skills/api-patterns/SKILL.md";
|
|
148
|
+
}
|
|
149
|
+
if (combined.includes("database") || combined.includes("sql") || combined.includes("prisma") || combined.includes("query") || combined.includes("schema")) {
|
|
150
|
+
return ".agent/skills/database-design/SKILL.md";
|
|
151
|
+
}
|
|
152
|
+
if (combined.includes("node") || combined.includes("express") || combined.includes("server") || combined.includes("middleware")) {
|
|
153
|
+
return ".agent/skills/nodejs-best-practices/SKILL.md";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Testing patterns
|
|
157
|
+
if (combined.includes("test") || combined.includes("mock") || combined.includes("jest") || combined.includes("vitest") || combined.includes("expect")) {
|
|
158
|
+
return ".agent/skills/testing-patterns/SKILL.md";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// TypeScript patterns
|
|
162
|
+
if (combined.includes("typescript") || combined.includes("type") || combined.includes("interface") || combined.includes("generic") || combined.includes("any")) {
|
|
163
|
+
return ".agent/skills/typescript-expert/SKILL.md";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Security patterns
|
|
167
|
+
if (combined.includes("security") || combined.includes("auth") || combined.includes("password") || combined.includes("token") || combined.includes("xss") || combined.includes("injection")) {
|
|
168
|
+
return ".agent/skills/vulnerability-scanner/SKILL.md";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Performance patterns
|
|
172
|
+
if (combined.includes("performance") || combined.includes("memo") || combined.includes("lazy") || combined.includes("cache") || combined.includes("optimize")) {
|
|
173
|
+
return ".agent/skills/performance-profiling/SKILL.md";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Git patterns
|
|
177
|
+
if (combined.includes("commit") || combined.includes("branch") || combined.includes("merge") || combined.includes("git")) {
|
|
178
|
+
return ".agent/skills/git-conventions/SKILL.md";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Default to clean-code for general patterns
|
|
182
|
+
return ".agent/skills/clean-code/SKILL.md";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Count pending proposals
|
|
187
|
+
* @returns {number}
|
|
188
|
+
*/
|
|
189
|
+
export function countPendingProposals() {
|
|
190
|
+
return getQualifyingLessons().length;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export default {
|
|
194
|
+
getQualifyingLessons,
|
|
195
|
+
dismissProposal,
|
|
196
|
+
generateProposalMarkdown,
|
|
197
|
+
countPendingProposals,
|
|
198
|
+
PROPOSALS_DIR
|
|
199
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for proposals module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
|
|
7
|
+
// Test proposal markdown generation without mocking
|
|
8
|
+
describe("proposals", () => {
|
|
9
|
+
describe("generateProposalMarkdown format", () => {
|
|
10
|
+
it("creates markdown with required sections", () => {
|
|
11
|
+
const lesson = {
|
|
12
|
+
id: "LEARN-001",
|
|
13
|
+
pattern: "console\\.log",
|
|
14
|
+
message: "No console.log in production",
|
|
15
|
+
hitCount: 5,
|
|
16
|
+
severity: "ERROR"
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Simulate markdown generation
|
|
20
|
+
const md = `# 🤖 Skill Update Proposal\n\n## Pattern Detected\n| Field | Value |\n|-------|-------|\n| **ID** | \`${lesson.id}\` |\n| **Hit Count** | ${lesson.hitCount || 0} |\n\n## Message\n> ${lesson.message}`;
|
|
21
|
+
|
|
22
|
+
expect(md).toContain("LEARN-001");
|
|
23
|
+
expect(md).toContain("No console.log");
|
|
24
|
+
expect(md).toContain("5");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("includes AI agent instructions", () => {
|
|
28
|
+
const md = `## How to Apply\n1. **Copy this entire proposal**\n2. **Paste to your AI coding agent**`;
|
|
29
|
+
|
|
30
|
+
expect(md).toContain("Copy");
|
|
31
|
+
expect(md).toContain("Paste");
|
|
32
|
+
expect(md).toContain("AI");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("proposal threshold", () => {
|
|
37
|
+
it("default threshold is 5", () => {
|
|
38
|
+
const defaultThreshold = 5;
|
|
39
|
+
expect(defaultThreshold).toBe(5);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("lesson qualifies when hitCount >= threshold", () => {
|
|
43
|
+
const threshold = 5;
|
|
44
|
+
const lessons = [
|
|
45
|
+
{ id: "L1", hitCount: 5 },
|
|
46
|
+
{ id: "L2", hitCount: 3 },
|
|
47
|
+
{ id: "L3", hitCount: 10 }
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const qualifying = lessons.filter(l => l.hitCount >= threshold);
|
|
51
|
+
expect(qualifying).toHaveLength(2);
|
|
52
|
+
expect(qualifying.map(l => l.id)).toContain("L1");
|
|
53
|
+
expect(qualifying.map(l => l.id)).toContain("L3");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|