add-skill-kit 3.2.3 → 3.2.4
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/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/auto_preview.py +148 -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/checklist.py +222 -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/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/learn.js +255 -0
- package/lib/agent-cli/lib/learn.test.js +70 -0
- package/lib/agent-cli/lib/migrate-to-v4.js +322 -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 +820 -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/session_manager.py +120 -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 +123 -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 +169 -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/verify_all.py +327 -0
- package/lib/agent-cli/lib/watcher.js +181 -0
- package/lib/agent-cli/lib/watcher.test.js +85 -0
- package/lib/agent-cli/package.json +51 -0
- package/lib/agentskillskit-cli/README.md +21 -0
- package/lib/agentskillskit-cli/ag-smart.js +158 -0
- package/lib/agentskillskit-cli/package.json +51 -0
- package/package.json +10 -6
- /package/{node_modules/agentskillskit-cli → lib/agent-cli}/README.md +0 -0
- /package/{node_modules/agentskillskit-cli → lib/agent-cli}/bin/ag-smart.js +0 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Skill Learn - Auto-update SKILL.md from Hot Patterns
|
|
4
|
+
*
|
|
5
|
+
* When patterns reach threshold hits, automatically add them
|
|
6
|
+
* to the relevant SKILL.md file as learned rules.
|
|
7
|
+
*
|
|
8
|
+
* This makes the Self-Learning Engine truly "self-learning"!
|
|
9
|
+
*
|
|
10
|
+
* Usage: ag-smart sync-skills
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import { loadKnowledge, saveKnowledge } from "./recall.js";
|
|
16
|
+
import { AGENT_DIR, VERSION } from "./config.js";
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// CONFIGURATION
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
const SYNC_THRESHOLD = 10; // Hits required before syncing to SKILL.md
|
|
23
|
+
const SKILLS_DIR = path.join(AGENT_DIR, "skills");
|
|
24
|
+
|
|
25
|
+
// Category to skill folder mapping
|
|
26
|
+
const CATEGORY_MAP = {
|
|
27
|
+
"general": "clean-code",
|
|
28
|
+
"security": "vulnerability-scanner",
|
|
29
|
+
"code-quality": "clean-code",
|
|
30
|
+
"performance": "performance-profiling",
|
|
31
|
+
"testing": "testing-patterns",
|
|
32
|
+
"react": "react-patterns",
|
|
33
|
+
"api": "api-patterns",
|
|
34
|
+
"database": "database-design"
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// SKILL UPDATER
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Find the SKILL.md file for a category
|
|
43
|
+
* @param {string} category
|
|
44
|
+
* @returns {string|null}
|
|
45
|
+
*/
|
|
46
|
+
function findSkillFile(category) {
|
|
47
|
+
const skillFolder = CATEGORY_MAP[category] || CATEGORY_MAP["general"];
|
|
48
|
+
const skillPath = path.join(SKILLS_DIR, skillFolder, "SKILL.md");
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(skillPath)) {
|
|
51
|
+
return skillPath;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Fallback to clean-code
|
|
55
|
+
const fallback = path.join(SKILLS_DIR, "clean-code", "SKILL.md");
|
|
56
|
+
return fs.existsSync(fallback) ? fallback : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse SKILL.md structure to find best insertion point
|
|
61
|
+
* @param {string} skillPath
|
|
62
|
+
* @returns {{ sections: Array<{name: string, start: number, end: number}>, content: string }}
|
|
63
|
+
*/
|
|
64
|
+
function parseSkillStructure(skillPath) {
|
|
65
|
+
const content = fs.readFileSync(skillPath, "utf8");
|
|
66
|
+
const lines = content.split("\n");
|
|
67
|
+
const sections = [];
|
|
68
|
+
|
|
69
|
+
let currentSection = null;
|
|
70
|
+
|
|
71
|
+
lines.forEach((line, idx) => {
|
|
72
|
+
// Detect section headers (## or ###)
|
|
73
|
+
const headerMatch = line.match(/^(#{2,3})\s+(.+)/);
|
|
74
|
+
if (headerMatch) {
|
|
75
|
+
if (currentSection) {
|
|
76
|
+
currentSection.end = idx - 1;
|
|
77
|
+
sections.push(currentSection);
|
|
78
|
+
}
|
|
79
|
+
currentSection = {
|
|
80
|
+
name: headerMatch[2].trim(),
|
|
81
|
+
level: headerMatch[1].length,
|
|
82
|
+
start: idx,
|
|
83
|
+
end: lines.length - 1
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (currentSection) {
|
|
89
|
+
sections.push(currentSection);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { sections, content, lines };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Find best section to insert pattern based on category
|
|
97
|
+
* @param {Array} sections
|
|
98
|
+
* @param {string} category
|
|
99
|
+
* @returns {number} - Line number to insert at
|
|
100
|
+
*/
|
|
101
|
+
function findInsertionPoint(sections, category) {
|
|
102
|
+
// Look for "Auto-learned" section first
|
|
103
|
+
const autoSection = sections.find(s =>
|
|
104
|
+
s.name.toLowerCase().includes("auto-learned") ||
|
|
105
|
+
s.name.toLowerCase().includes("learned patterns")
|
|
106
|
+
);
|
|
107
|
+
if (autoSection) {
|
|
108
|
+
return autoSection.end + 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Look for relevant category section
|
|
112
|
+
const categoryMap = {
|
|
113
|
+
"security": ["security", "vulnerab"],
|
|
114
|
+
"performance": ["performance", "optim"],
|
|
115
|
+
"code-quality": ["quality", "best practice", "rule"],
|
|
116
|
+
"testing": ["test", "spec"]
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const keywords = categoryMap[category] || ["rule", "pattern"];
|
|
120
|
+
const matchSection = sections.find(s =>
|
|
121
|
+
keywords.some(kw => s.name.toLowerCase().includes(kw))
|
|
122
|
+
);
|
|
123
|
+
if (matchSection) {
|
|
124
|
+
return matchSection.end + 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Default: append at end
|
|
128
|
+
return -1; // -1 means append
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generate rule entry for SKILL.md
|
|
133
|
+
* @param {object} lesson
|
|
134
|
+
* @returns {string}
|
|
135
|
+
*/
|
|
136
|
+
function generateRuleEntry(lesson) {
|
|
137
|
+
const date = new Date().toISOString().split("T")[0];
|
|
138
|
+
return `
|
|
139
|
+
### 🤖 Auto-learned: ${lesson.id}
|
|
140
|
+
|
|
141
|
+
> **Pattern:** \`${lesson.pattern}\`
|
|
142
|
+
>
|
|
143
|
+
> ${lesson.message}
|
|
144
|
+
|
|
145
|
+
- **Severity:** ${lesson.severity}
|
|
146
|
+
- **Detected:** ${lesson.hitCount} times
|
|
147
|
+
- **Added:** ${date}
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if pattern already exists in SKILL.md
|
|
155
|
+
*/
|
|
156
|
+
function patternExistsInSkill(skillPath, pattern) {
|
|
157
|
+
const content = fs.readFileSync(skillPath, "utf8");
|
|
158
|
+
// Check multiple formats
|
|
159
|
+
return content.includes(`\`${pattern}\``) ||
|
|
160
|
+
content.includes(`Pattern:** \`${pattern}\``) ||
|
|
161
|
+
content.includes(`"${pattern}"`) ||
|
|
162
|
+
content.includes(`'${pattern}'`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Insert rule at specific line or append
|
|
167
|
+
* @param {string} skillPath
|
|
168
|
+
* @param {object} lesson
|
|
169
|
+
* @param {string} category
|
|
170
|
+
*/
|
|
171
|
+
function insertRule(skillPath, lesson, category) {
|
|
172
|
+
const { sections, content, lines } = parseSkillStructure(skillPath);
|
|
173
|
+
const insertPoint = findInsertionPoint(sections, category);
|
|
174
|
+
const entry = generateRuleEntry(lesson);
|
|
175
|
+
|
|
176
|
+
if (insertPoint === -1 || insertPoint >= lines.length) {
|
|
177
|
+
// Append at end
|
|
178
|
+
fs.appendFileSync(skillPath, entry, "utf8");
|
|
179
|
+
} else {
|
|
180
|
+
// Insert at specific line
|
|
181
|
+
lines.splice(insertPoint, 0, entry);
|
|
182
|
+
fs.writeFileSync(skillPath, lines.join("\n"), "utf8");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Sync hot patterns to SKILL.md files
|
|
188
|
+
*/
|
|
189
|
+
function syncSkills() {
|
|
190
|
+
console.log(`\n🔄 Skill Sync v${VERSION}`);
|
|
191
|
+
console.log(`📊 Threshold: ${SYNC_THRESHOLD} hits\n`);
|
|
192
|
+
console.log("─".repeat(50));
|
|
193
|
+
|
|
194
|
+
const db = loadKnowledge();
|
|
195
|
+
|
|
196
|
+
if (!db.lessons || db.lessons.length === 0) {
|
|
197
|
+
console.log("\nℹ️ No lessons learned yet.");
|
|
198
|
+
return { synced: 0, skipped: 0 };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Find hot patterns
|
|
202
|
+
const hotPatterns = db.lessons.filter(l => (l.hitCount || 0) >= SYNC_THRESHOLD && !l.syncedToSkill);
|
|
203
|
+
|
|
204
|
+
if (hotPatterns.length === 0) {
|
|
205
|
+
console.log("\nℹ️ No patterns have reached the sync threshold yet.");
|
|
206
|
+
console.log(` Patterns need ${SYNC_THRESHOLD}+ hits to be synced to SKILL.md\n`);
|
|
207
|
+
|
|
208
|
+
// Show progress
|
|
209
|
+
const inProgress = db.lessons.filter(l => l.hitCount > 0 && l.hitCount < SYNC_THRESHOLD);
|
|
210
|
+
if (inProgress.length > 0) {
|
|
211
|
+
console.log("📈 Patterns in progress:\n");
|
|
212
|
+
inProgress.forEach(l => {
|
|
213
|
+
const progress = Math.round((l.hitCount / SYNC_THRESHOLD) * 100);
|
|
214
|
+
const bar = "█".repeat(Math.floor(progress / 10)) + "░".repeat(10 - Math.floor(progress / 10));
|
|
215
|
+
console.log(` [${l.id}] ${l.hitCount}/${SYNC_THRESHOLD} hits (${progress}%)`);
|
|
216
|
+
console.log(` ${bar}`);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return { synced: 0, skipped: 0 };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log(`\n🔥 Found ${hotPatterns.length} hot pattern(s) ready to sync:\n`);
|
|
223
|
+
|
|
224
|
+
let synced = 0;
|
|
225
|
+
let skipped = 0;
|
|
226
|
+
|
|
227
|
+
hotPatterns.forEach(lesson => {
|
|
228
|
+
const category = lesson.category || "general";
|
|
229
|
+
const skillPath = findSkillFile(category);
|
|
230
|
+
|
|
231
|
+
if (!skillPath) {
|
|
232
|
+
console.log(`⚠️ [${lesson.id}] No SKILL.md found for category: ${category}`);
|
|
233
|
+
skipped++;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Check if already synced
|
|
238
|
+
if (patternExistsInSkill(skillPath, lesson.pattern)) {
|
|
239
|
+
console.log(`⏭️ [${lesson.id}] Already in ${path.basename(path.dirname(skillPath))}/SKILL.md`);
|
|
240
|
+
lesson.syncedToSkill = true;
|
|
241
|
+
skipped++;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Append to SKILL.md
|
|
246
|
+
const entry = generateRuleEntry(lesson);
|
|
247
|
+
fs.appendFileSync(skillPath, entry, "utf8");
|
|
248
|
+
|
|
249
|
+
// Mark as synced
|
|
250
|
+
lesson.syncedToSkill = true;
|
|
251
|
+
lesson.syncedAt = new Date().toISOString();
|
|
252
|
+
|
|
253
|
+
console.log(`✅ [${lesson.id}] → ${path.basename(path.dirname(skillPath))}/SKILL.md`);
|
|
254
|
+
console.log(` Pattern: /${lesson.pattern}/`);
|
|
255
|
+
console.log(` Hits: ${lesson.hitCount}`);
|
|
256
|
+
synced++;
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Save updated knowledge base
|
|
260
|
+
saveKnowledge(db);
|
|
261
|
+
|
|
262
|
+
console.log(`\n${"─".repeat(50)}`);
|
|
263
|
+
console.log(`📊 Summary: ${synced} synced, ${skipped} skipped`);
|
|
264
|
+
|
|
265
|
+
if (synced > 0) {
|
|
266
|
+
console.log(`\n🎉 SKILL.md files updated! The agent will now remember these patterns.`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { synced, skipped };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// CLI
|
|
274
|
+
// ============================================================================
|
|
275
|
+
|
|
276
|
+
const args = process.argv.slice(2);
|
|
277
|
+
|
|
278
|
+
if (args.includes("--help")) {
|
|
279
|
+
console.log(`
|
|
280
|
+
🔄 Skill Sync - Auto-update SKILL.md
|
|
281
|
+
|
|
282
|
+
Usage:
|
|
283
|
+
ag-smart sync-skills
|
|
284
|
+
|
|
285
|
+
When patterns reach ${SYNC_THRESHOLD}+ violations, they are automatically
|
|
286
|
+
added to the relevant SKILL.md file as learned rules.
|
|
287
|
+
|
|
288
|
+
This makes the Self-Learning Engine truly "self-learning"!
|
|
289
|
+
|
|
290
|
+
Options:
|
|
291
|
+
--help Show this help
|
|
292
|
+
`);
|
|
293
|
+
process.exit(0);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
syncSkills();
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Smart Stats - Knowledge Base Statistics
|
|
4
|
+
*
|
|
5
|
+
* Display statistics about the knowledge base:
|
|
6
|
+
* - Total lessons learned
|
|
7
|
+
* - Most triggered patterns
|
|
8
|
+
* - Violation trends
|
|
9
|
+
*
|
|
10
|
+
* Usage: ag-smart stats
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { loadKnowledge } from "./recall.js";
|
|
14
|
+
import { VERSION } from "./config.js";
|
|
15
|
+
import * as p from "@clack/prompts";
|
|
16
|
+
import pc from "picocolors";
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// STATS DISPLAY
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
function displayStats() {
|
|
23
|
+
const db = loadKnowledge();
|
|
24
|
+
|
|
25
|
+
p.intro(pc.cyan(`📊 Smart Agent Knowledge Base v${VERSION}`));
|
|
26
|
+
|
|
27
|
+
if (!db.lessons || db.lessons.length === 0) {
|
|
28
|
+
p.note("No lessons learned yet.\nUse 'ag-smart learn' to add patterns.", pc.dim("Empty"));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Summary
|
|
33
|
+
const totalHits = db.lessons.reduce((sum, l) => sum + (l.hitCount || 0), 0);
|
|
34
|
+
const errorCount = db.lessons.filter(l => l.severity === "ERROR").length;
|
|
35
|
+
const warningCount = db.lessons.filter(l => l.severity === "WARNING").length;
|
|
36
|
+
|
|
37
|
+
const summaryLines = [
|
|
38
|
+
`📚 Total Lessons: ${pc.bold(db.lessons.length)}`,
|
|
39
|
+
`🎯 Total Violations: ${pc.bold(totalHits)}`,
|
|
40
|
+
`${pc.red("❌")} ERROR patterns: ${errorCount}`,
|
|
41
|
+
`${pc.yellow("⚠️")} WARNING patterns: ${warningCount}`
|
|
42
|
+
];
|
|
43
|
+
p.note(summaryLines.join("\n"), pc.dim("Summary"));
|
|
44
|
+
|
|
45
|
+
// Most triggered
|
|
46
|
+
const sorted = [...db.lessons]
|
|
47
|
+
.filter(l => l.hitCount > 0)
|
|
48
|
+
.sort((a, b) => (b.hitCount || 0) - (a.hitCount || 0))
|
|
49
|
+
.slice(0, 5);
|
|
50
|
+
|
|
51
|
+
if (sorted.length > 0) {
|
|
52
|
+
const triggeredLines = sorted.map((lesson, idx) => {
|
|
53
|
+
const bar = "█".repeat(Math.min(15, Math.ceil((lesson.hitCount / (sorted[0].hitCount || 1)) * 15)));
|
|
54
|
+
return `${idx + 1}. [${lesson.id}] ${lesson.hitCount} hits\n ${pc.green(bar)}\n ${pc.dim(lesson.message)}`;
|
|
55
|
+
});
|
|
56
|
+
p.note(triggeredLines.join("\n\n"), pc.dim("🔥 Most Triggered"));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Auto-escalated
|
|
60
|
+
const escalated = db.lessons.filter(l => l.autoEscalated);
|
|
61
|
+
if (escalated.length > 0) {
|
|
62
|
+
const escalatedLines = escalated.map(l =>
|
|
63
|
+
`[${l.id}] → Escalated after ${l.hitCount} violations`
|
|
64
|
+
);
|
|
65
|
+
p.note(escalatedLines.join("\n"), pc.dim("⚡ Auto-Escalated"));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Recent activity
|
|
69
|
+
const withHits = db.lessons.filter(l => l.lastHit);
|
|
70
|
+
if (withHits.length > 0) {
|
|
71
|
+
const recent = [...withHits]
|
|
72
|
+
.sort((a, b) => new Date(b.lastHit) - new Date(a.lastHit))
|
|
73
|
+
.slice(0, 3);
|
|
74
|
+
|
|
75
|
+
const recentLines = recent.map(l => {
|
|
76
|
+
const timeAgo = getTimeAgo(new Date(l.lastHit));
|
|
77
|
+
return `[${l.id}] Last hit: ${timeAgo}`;
|
|
78
|
+
});
|
|
79
|
+
p.note(recentLines.join("\n"), pc.dim("🕐 Recent Activity"));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Sources breakdown
|
|
83
|
+
const sources = {};
|
|
84
|
+
db.lessons.forEach(l => {
|
|
85
|
+
const src = l.source || "manual";
|
|
86
|
+
sources[src] = (sources[src] || 0) + 1;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const sourceLines = Object.entries(sources).map(([src, count]) => {
|
|
90
|
+
const icon = src === "manual" ? "✍️" : src === "eslint" ? "🔧" : "🧪";
|
|
91
|
+
return `${icon} ${src}: ${count}`;
|
|
92
|
+
});
|
|
93
|
+
p.note(sourceLines.join("\n"), pc.dim("📥 Lesson Sources"));
|
|
94
|
+
|
|
95
|
+
p.outro(pc.green("Stats complete"));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get human-readable time ago string
|
|
100
|
+
*/
|
|
101
|
+
function getTimeAgo(date) {
|
|
102
|
+
const seconds = Math.floor((Date.now() - date) / 1000);
|
|
103
|
+
|
|
104
|
+
if (seconds < 60) return "just now";
|
|
105
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`;
|
|
106
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
|
|
107
|
+
return `${Math.floor(seconds / 86400)} days ago`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// CLI
|
|
112
|
+
// ============================================================================
|
|
113
|
+
|
|
114
|
+
const args = process.argv.slice(2);
|
|
115
|
+
|
|
116
|
+
if (args.includes("--help")) {
|
|
117
|
+
console.log(`
|
|
118
|
+
📊 Smart Stats - Knowledge Base Statistics
|
|
119
|
+
|
|
120
|
+
Usage:
|
|
121
|
+
ag-smart stats
|
|
122
|
+
|
|
123
|
+
Shows:
|
|
124
|
+
- Total lessons and violations
|
|
125
|
+
- Most triggered patterns
|
|
126
|
+
- Auto-escalated patterns
|
|
127
|
+
- Recent activity
|
|
128
|
+
`);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
displayStats();
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for stats.js functionality
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
|
|
6
|
+
describe("Stats - Statistics Calculation", () => {
|
|
7
|
+
it("calculates total hits from lessons", () => {
|
|
8
|
+
const lessons = [
|
|
9
|
+
{ id: "LEARN-001", hitCount: 5 },
|
|
10
|
+
{ id: "LEARN-002", hitCount: 3 },
|
|
11
|
+
{ id: "LEARN-003", hitCount: 0 }
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const totalHits = lessons.reduce((sum, l) => sum + (l.hitCount || 0), 0);
|
|
15
|
+
expect(totalHits).toBe(8);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("counts severity types correctly", () => {
|
|
19
|
+
const lessons = [
|
|
20
|
+
{ id: "LEARN-001", severity: "ERROR" },
|
|
21
|
+
{ id: "LEARN-002", severity: "WARNING" },
|
|
22
|
+
{ id: "LEARN-003", severity: "ERROR" },
|
|
23
|
+
{ id: "LEARN-004", severity: "WARNING" }
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const errorCount = lessons.filter(l => l.severity === "ERROR").length;
|
|
27
|
+
const warningCount = lessons.filter(l => l.severity === "WARNING").length;
|
|
28
|
+
|
|
29
|
+
expect(errorCount).toBe(2);
|
|
30
|
+
expect(warningCount).toBe(2);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("sorts by hit count descending", () => {
|
|
34
|
+
const lessons = [
|
|
35
|
+
{ id: "LEARN-001", hitCount: 3 },
|
|
36
|
+
{ id: "LEARN-002", hitCount: 10 },
|
|
37
|
+
{ id: "LEARN-003", hitCount: 5 }
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const sorted = [...lessons].sort((a, b) => (b.hitCount || 0) - (a.hitCount || 0));
|
|
41
|
+
|
|
42
|
+
expect(sorted[0].id).toBe("LEARN-002");
|
|
43
|
+
expect(sorted[1].id).toBe("LEARN-003");
|
|
44
|
+
expect(sorted[2].id).toBe("LEARN-001");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("filters auto-escalated patterns", () => {
|
|
48
|
+
const lessons = [
|
|
49
|
+
{ id: "LEARN-001", autoEscalated: true },
|
|
50
|
+
{ id: "LEARN-002", autoEscalated: false },
|
|
51
|
+
{ id: "LEARN-003", autoEscalated: true }
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const escalated = lessons.filter(l => l.autoEscalated);
|
|
55
|
+
expect(escalated.length).toBe(2);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("groups by source correctly", () => {
|
|
59
|
+
const lessons = [
|
|
60
|
+
{ id: "LEARN-001", source: "manual" },
|
|
61
|
+
{ id: "LEARN-002", source: "eslint" },
|
|
62
|
+
{ id: "LEARN-003", source: "manual" },
|
|
63
|
+
{ id: "LEARN-004", source: "test-failure" }
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const sources = {};
|
|
67
|
+
lessons.forEach(l => {
|
|
68
|
+
const src = l.source || "manual";
|
|
69
|
+
sources[src] = (sources[src] || 0) + 1;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(sources["manual"]).toBe(2);
|
|
73
|
+
expect(sources["eslint"]).toBe(1);
|
|
74
|
+
expect(sources["test-failure"]).toBe(1);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("Stats - Time Formatting", () => {
|
|
79
|
+
it("formats time ago correctly", () => {
|
|
80
|
+
const getTimeAgo = (date) => {
|
|
81
|
+
const seconds = Math.floor((Date.now() - date) / 1000);
|
|
82
|
+
if (seconds < 60) return "just now";
|
|
83
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`;
|
|
84
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
|
|
85
|
+
return `${Math.floor(seconds / 86400)} days ago`;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
expect(getTimeAgo(now - 30000)).toBe("just now");
|
|
90
|
+
expect(getTimeAgo(now - 120000)).toBe("2 minutes ago");
|
|
91
|
+
expect(getTimeAgo(now - 7200000)).toBe("2 hours ago");
|
|
92
|
+
expect(getTimeAgo(now - 172800000)).toBe("2 days ago");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Type Definitions for agent-skill-kit CLI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} Lesson
|
|
7
|
+
* @property {string} id - Lesson ID (e.g., LEARN-001)
|
|
8
|
+
* @property {string} pattern - Regex pattern to match
|
|
9
|
+
* @property {string} message - Explanation message
|
|
10
|
+
* @property {'WARNING'|'ERROR'} severity - Severity level
|
|
11
|
+
* @property {string} addedAt - ISO timestamp
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} KnowledgeBase
|
|
16
|
+
* @property {Lesson[]} lessons - Array of learned lessons
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} AuditResult
|
|
21
|
+
* @property {string} file - File path
|
|
22
|
+
* @property {string[]} violations - List of violations found
|
|
23
|
+
* @property {boolean} passed - Whether file passed audit
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} CLIConfig
|
|
28
|
+
* @property {string} agentDir - Path to .agent directory
|
|
29
|
+
* @property {string} knowledgePath - Path to lessons file
|
|
30
|
+
* @property {string} rulesPath - Path to rules directory
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
export { };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit UI - Compliance check with Clack UI
|
|
3
|
+
*
|
|
4
|
+
* Wrapper for the audit functionality that integrates
|
|
5
|
+
* with the main menu instead of spawning a separate process.
|
|
6
|
+
*/
|
|
7
|
+
import * as p from "@clack/prompts";
|
|
8
|
+
import pc from "picocolors";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { scanDirectory, loadKnowledge, saveKnowledge } from "../recall.js";
|
|
12
|
+
import { VERSION, cwd } from "../config.js";
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// CONFIGURATION
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
const SCAN_EXTENSIONS = [".js", ".ts", ".tsx", ".jsx", ".mjs"];
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// GOVERNANCE CHECKS
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if governance files exist
|
|
26
|
+
* @param {string} projectRoot
|
|
27
|
+
* @returns {{ passed: boolean, details: string[] }}
|
|
28
|
+
*/
|
|
29
|
+
function checkGovernance(projectRoot) {
|
|
30
|
+
const details = [];
|
|
31
|
+
let passed = true;
|
|
32
|
+
|
|
33
|
+
const governanceFiles = [
|
|
34
|
+
{ path: path.join(projectRoot, ".agent", "GEMINI.md"), name: "GEMINI.md" },
|
|
35
|
+
{ path: path.join(projectRoot, ".agent", "ARCHITECTURE.md"), name: "ARCHITECTURE.md" }
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
governanceFiles.forEach(file => {
|
|
39
|
+
if (fs.existsSync(file.path)) {
|
|
40
|
+
details.push(`${pc.green("✓")} ${file.name} found`);
|
|
41
|
+
} else {
|
|
42
|
+
details.push(`${pc.yellow("⚠")} ${file.name} not found (optional)`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Check for skills
|
|
47
|
+
const skillsDir = path.join(projectRoot, ".agent", "skills");
|
|
48
|
+
if (fs.existsSync(skillsDir)) {
|
|
49
|
+
try {
|
|
50
|
+
const skills = fs.readdirSync(skillsDir).filter(f =>
|
|
51
|
+
fs.statSync(path.join(skillsDir, f)).isDirectory()
|
|
52
|
+
);
|
|
53
|
+
details.push(`${pc.green("✓")} ${skills.length} skill(s) loaded`);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
details.push(`${pc.dim("○")} Skills directory not accessible`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { passed, details };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// AUDIT UI
|
|
64
|
+
// ============================================================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Run audit with Clack UI
|
|
68
|
+
*/
|
|
69
|
+
export async function runAuditUI() {
|
|
70
|
+
const projectRoot = cwd;
|
|
71
|
+
|
|
72
|
+
p.intro(pc.cyan(`Smart Audit v${VERSION}`));
|
|
73
|
+
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
let hasErrors = false;
|
|
76
|
+
|
|
77
|
+
// Phase 1: Memory Recall
|
|
78
|
+
const s1 = p.spinner();
|
|
79
|
+
s1.start("Phase 1: Memory Recall");
|
|
80
|
+
|
|
81
|
+
const db = loadKnowledge();
|
|
82
|
+
|
|
83
|
+
if (db.lessons.length === 0) {
|
|
84
|
+
s1.stop(pc.dim("Phase 1: No lessons learned yet"));
|
|
85
|
+
} else {
|
|
86
|
+
const { results } = scanDirectory(projectRoot, db);
|
|
87
|
+
|
|
88
|
+
// Count violations
|
|
89
|
+
let totalViolations = 0;
|
|
90
|
+
let errorCount = 0;
|
|
91
|
+
let warningCount = 0;
|
|
92
|
+
|
|
93
|
+
results.forEach(result => {
|
|
94
|
+
result.violations.forEach(({ lesson, matches }) => {
|
|
95
|
+
totalViolations += matches.length;
|
|
96
|
+
if (lesson.severity === "ERROR") {
|
|
97
|
+
errorCount += matches.length;
|
|
98
|
+
hasErrors = true;
|
|
99
|
+
} else {
|
|
100
|
+
warningCount += matches.length;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (totalViolations > 0) {
|
|
106
|
+
s1.stop(`Phase 1: Found ${totalViolations} violation(s)`);
|
|
107
|
+
|
|
108
|
+
// Show violations summary
|
|
109
|
+
if (errorCount > 0 || warningCount > 0) {
|
|
110
|
+
p.log.warn(`${pc.red("✖")} ${errorCount} error(s) ${pc.yellow("⚠")} ${warningCount} warning(s)`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
saveKnowledge(db);
|
|
114
|
+
} else {
|
|
115
|
+
s1.stop(pc.green("Phase 1: No violations found ✓"));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Phase 2: Governance
|
|
120
|
+
const s2 = p.spinner();
|
|
121
|
+
s2.start("Phase 2: Governance Check");
|
|
122
|
+
|
|
123
|
+
const govResult = checkGovernance(projectRoot);
|
|
124
|
+
s2.stop("Phase 2: Governance checked");
|
|
125
|
+
|
|
126
|
+
p.note(govResult.details.join("\n"), "Governance");
|
|
127
|
+
|
|
128
|
+
// Phase 3: Summary
|
|
129
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
130
|
+
const totalHits = db.lessons.reduce((sum, l) => sum + (l.hitCount || 0), 0);
|
|
131
|
+
|
|
132
|
+
p.note(
|
|
133
|
+
`${pc.dim("Duration:")} ${duration}s\n` +
|
|
134
|
+
`${pc.dim("Lessons:")} ${db.lessons.length} in memory\n` +
|
|
135
|
+
`${pc.dim("Hits:")} ${totalHits} total`,
|
|
136
|
+
"Summary"
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (hasErrors) {
|
|
140
|
+
p.outro(pc.red("AUDIT FAILED: Please fix ERROR violations"));
|
|
141
|
+
} else {
|
|
142
|
+
p.outro(pc.green("AUDIT PASSED: Code is compliant"));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export default runAuditUI;
|