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,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Knowledge Index Generator
|
|
4
|
+
*
|
|
5
|
+
* Builds inverted index for O(1) pattern lookup in knowledge base.
|
|
6
|
+
* Regenerates on-demand when knowledge files change.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* agent index --rebuild # Force rebuild index
|
|
10
|
+
* agent index --status # Check index freshness
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import yaml from 'js-yaml';
|
|
16
|
+
import { KNOWLEDGE_DIR } from './config.js';
|
|
17
|
+
|
|
18
|
+
const INDEX_PATH = path.join(KNOWLEDGE_DIR, 'index.json');
|
|
19
|
+
const INDEX_VERSION = 1;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load all lessons from knowledge base
|
|
23
|
+
* @returns {{ mistakes: Array, improvements: Array }}
|
|
24
|
+
*/
|
|
25
|
+
function loadAllLessons() {
|
|
26
|
+
const mistakes = [];
|
|
27
|
+
const improvements = [];
|
|
28
|
+
|
|
29
|
+
// Load mistakes.yaml
|
|
30
|
+
const mistakesPath = path.join(KNOWLEDGE_DIR, 'mistakes.yaml');
|
|
31
|
+
if (fs.existsSync(mistakesPath)) {
|
|
32
|
+
const data = yaml.load(fs.readFileSync(mistakesPath, 'utf8'));
|
|
33
|
+
if (data?.mistakes) {
|
|
34
|
+
mistakes.push(...data.mistakes.map(m => ({ ...m, type: 'mistake' })));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Load improvements.yaml
|
|
39
|
+
const improvementsPath = path.join(KNOWLEDGE_DIR, 'improvements.yaml');
|
|
40
|
+
if (fs.existsSync(improvementsPath)) {
|
|
41
|
+
const data = yaml.load(fs.readFileSync(improvementsPath, 'utf8'));
|
|
42
|
+
if (data?.improvements) {
|
|
43
|
+
improvements.push(...data.improvements.map(i => ({ ...i, type: 'improvement' })));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { mistakes, improvements };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build inverted index from lessons
|
|
52
|
+
* @param {Array} lessons - All lessons combined
|
|
53
|
+
* @returns {Object} Index structure
|
|
54
|
+
*/
|
|
55
|
+
function buildIndex(lessons) {
|
|
56
|
+
const patternIndex = {}; // pattern word -> lesson IDs
|
|
57
|
+
const tagIndex = {}; // tag -> lesson IDs
|
|
58
|
+
const idIndex = {}; // id -> lesson (for direct lookup)
|
|
59
|
+
const severityIndex = { // severity -> lesson IDs
|
|
60
|
+
ERROR: [],
|
|
61
|
+
WARNING: [],
|
|
62
|
+
INFO: []
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
for (const lesson of lessons) {
|
|
66
|
+
const id = lesson.id;
|
|
67
|
+
|
|
68
|
+
// ID index (direct lookup)
|
|
69
|
+
idIndex[id] = {
|
|
70
|
+
pattern: lesson.pattern,
|
|
71
|
+
message: lesson.message,
|
|
72
|
+
severity: lesson.severity || 'WARNING',
|
|
73
|
+
type: lesson.type,
|
|
74
|
+
hitCount: lesson.hitCount || 0,
|
|
75
|
+
confidence: lesson.cognitive?.confidence || 0.3
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Pattern index (tokenize pattern for partial matching)
|
|
79
|
+
if (lesson.pattern) {
|
|
80
|
+
const tokens = tokenizePattern(lesson.pattern);
|
|
81
|
+
for (const token of tokens) {
|
|
82
|
+
if (!patternIndex[token]) {
|
|
83
|
+
patternIndex[token] = [];
|
|
84
|
+
}
|
|
85
|
+
if (!patternIndex[token].includes(id)) {
|
|
86
|
+
patternIndex[token].push(id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Tag index
|
|
92
|
+
if (lesson.tags && Array.isArray(lesson.tags)) {
|
|
93
|
+
for (const tag of lesson.tags) {
|
|
94
|
+
if (!tagIndex[tag]) {
|
|
95
|
+
tagIndex[tag] = [];
|
|
96
|
+
}
|
|
97
|
+
if (!tagIndex[tag].includes(id)) {
|
|
98
|
+
tagIndex[tag].push(id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Severity index
|
|
104
|
+
const severity = lesson.severity || 'WARNING';
|
|
105
|
+
if (severityIndex[severity]) {
|
|
106
|
+
severityIndex[severity].push(id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
version: INDEX_VERSION,
|
|
112
|
+
generatedAt: new Date().toISOString(),
|
|
113
|
+
stats: {
|
|
114
|
+
totalLessons: lessons.length,
|
|
115
|
+
totalMistakes: lessons.filter(l => l.type === 'mistake').length,
|
|
116
|
+
totalImprovements: lessons.filter(l => l.type === 'improvement').length,
|
|
117
|
+
totalPatterns: Object.keys(patternIndex).length,
|
|
118
|
+
totalTags: Object.keys(tagIndex).length
|
|
119
|
+
},
|
|
120
|
+
patternIndex,
|
|
121
|
+
tagIndex,
|
|
122
|
+
severityIndex,
|
|
123
|
+
idIndex
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Tokenize a regex pattern into searchable words
|
|
129
|
+
* @param {string} pattern - Regex pattern
|
|
130
|
+
* @returns {string[]} Tokens
|
|
131
|
+
*/
|
|
132
|
+
function tokenizePattern(pattern) {
|
|
133
|
+
// Extract alphanumeric words, ignoring regex special chars
|
|
134
|
+
const words = pattern
|
|
135
|
+
.replace(/[\[\]\(\)\{\}\.\*\+\?\^\$\\|]/g, ' ')
|
|
136
|
+
.split(/\s+/)
|
|
137
|
+
.map(w => w.toLowerCase().trim())
|
|
138
|
+
.filter(w => w.length >= 2);
|
|
139
|
+
|
|
140
|
+
return [...new Set(words)];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get modification time of knowledge files
|
|
145
|
+
* @returns {number} Most recent mtime
|
|
146
|
+
*/
|
|
147
|
+
function getKnowledgeMtime() {
|
|
148
|
+
const files = ['mistakes.yaml', 'improvements.yaml', 'lessons-learned.yaml'];
|
|
149
|
+
let maxMtime = 0;
|
|
150
|
+
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
const filepath = path.join(KNOWLEDGE_DIR, file);
|
|
153
|
+
if (fs.existsSync(filepath)) {
|
|
154
|
+
const stat = fs.statSync(filepath);
|
|
155
|
+
if (stat.mtime.getTime() > maxMtime) {
|
|
156
|
+
maxMtime = stat.mtime.getTime();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return maxMtime;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check if index is stale
|
|
166
|
+
* @returns {{ stale: boolean, reason?: string }}
|
|
167
|
+
*/
|
|
168
|
+
export function checkIndexFreshness() {
|
|
169
|
+
if (!fs.existsSync(INDEX_PATH)) {
|
|
170
|
+
return { stale: true, reason: 'Index does not exist' };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const index = JSON.parse(fs.readFileSync(INDEX_PATH, 'utf8'));
|
|
175
|
+
const indexTime = new Date(index.generatedAt).getTime();
|
|
176
|
+
const knowledgeTime = getKnowledgeMtime();
|
|
177
|
+
|
|
178
|
+
if (knowledgeTime > indexTime) {
|
|
179
|
+
return { stale: true, reason: 'Knowledge files modified after index' };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (index.version !== INDEX_VERSION) {
|
|
183
|
+
return { stale: true, reason: `Index version mismatch (${index.version} vs ${INDEX_VERSION})` };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { stale: false };
|
|
187
|
+
} catch (e) {
|
|
188
|
+
return { stale: true, reason: `Index corrupted: ${e.message}` };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Load or rebuild index
|
|
194
|
+
* @param {boolean} forceRebuild - Force rebuild even if fresh
|
|
195
|
+
* @returns {Object} Index
|
|
196
|
+
*/
|
|
197
|
+
export function loadIndex(forceRebuild = false) {
|
|
198
|
+
const freshness = checkIndexFreshness();
|
|
199
|
+
|
|
200
|
+
if (!forceRebuild && !freshness.stale) {
|
|
201
|
+
// Load existing index
|
|
202
|
+
return JSON.parse(fs.readFileSync(INDEX_PATH, 'utf8'));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Rebuild index
|
|
206
|
+
return rebuildIndex();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Force rebuild index
|
|
211
|
+
* @returns {Object} New index
|
|
212
|
+
*/
|
|
213
|
+
export function rebuildIndex() {
|
|
214
|
+
const { mistakes, improvements } = loadAllLessons();
|
|
215
|
+
const allLessons = [...mistakes, ...improvements];
|
|
216
|
+
|
|
217
|
+
const index = buildIndex(allLessons);
|
|
218
|
+
|
|
219
|
+
// Save index
|
|
220
|
+
fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
|
|
221
|
+
fs.writeFileSync(INDEX_PATH, JSON.stringify(index, null, 2), 'utf8');
|
|
222
|
+
|
|
223
|
+
return index;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Search index by pattern
|
|
228
|
+
* @param {string} query - Search query
|
|
229
|
+
* @param {Object} index - Loaded index
|
|
230
|
+
* @returns {string[]} Matching lesson IDs
|
|
231
|
+
*/
|
|
232
|
+
export function searchByPattern(query, index) {
|
|
233
|
+
const tokens = tokenizePattern(query);
|
|
234
|
+
const matchingIds = new Set();
|
|
235
|
+
|
|
236
|
+
for (const token of tokens) {
|
|
237
|
+
if (index.patternIndex[token]) {
|
|
238
|
+
for (const id of index.patternIndex[token]) {
|
|
239
|
+
matchingIds.add(id);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return [...matchingIds];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Search index by tag
|
|
249
|
+
* @param {string} tag - Tag to search
|
|
250
|
+
* @param {Object} index - Loaded index
|
|
251
|
+
* @returns {string[]} Matching lesson IDs
|
|
252
|
+
*/
|
|
253
|
+
export function searchByTag(tag, index) {
|
|
254
|
+
return index.tagIndex[tag] || [];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get lesson by ID
|
|
259
|
+
* @param {string} id - Lesson ID
|
|
260
|
+
* @param {Object} index - Loaded index
|
|
261
|
+
* @returns {Object|null} Lesson info
|
|
262
|
+
*/
|
|
263
|
+
export function getById(id, index) {
|
|
264
|
+
return index.idIndex[id] || null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* CLI entry point
|
|
269
|
+
*/
|
|
270
|
+
function main() {
|
|
271
|
+
const args = process.argv.slice(2);
|
|
272
|
+
|
|
273
|
+
if (args.includes('--status')) {
|
|
274
|
+
const freshness = checkIndexFreshness();
|
|
275
|
+
if (freshness.stale) {
|
|
276
|
+
console.log(`⚠️ Index is STALE: ${freshness.reason}`);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
} else {
|
|
279
|
+
const index = loadIndex();
|
|
280
|
+
console.log(`✅ Index is FRESH`);
|
|
281
|
+
console.log(` Generated: ${index.generatedAt}`);
|
|
282
|
+
console.log(` Lessons: ${index.stats.totalLessons}`);
|
|
283
|
+
console.log(` Patterns: ${index.stats.totalPatterns}`);
|
|
284
|
+
console.log(` Tags: ${index.stats.totalTags}`);
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (args.includes('--rebuild')) {
|
|
290
|
+
console.log('🔄 Rebuilding knowledge index...');
|
|
291
|
+
const index = rebuildIndex();
|
|
292
|
+
console.log(`✅ Index rebuilt successfully`);
|
|
293
|
+
console.log(` Lessons: ${index.stats.totalLessons}`);
|
|
294
|
+
console.log(` Patterns: ${index.stats.totalPatterns}`);
|
|
295
|
+
console.log(` Tags: ${index.stats.totalTags}`);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
console.log(`
|
|
300
|
+
📇 Knowledge Index Manager
|
|
301
|
+
|
|
302
|
+
Usage:
|
|
303
|
+
agent index --rebuild Rebuild index from knowledge files
|
|
304
|
+
agent index --status Check if index is fresh or stale
|
|
305
|
+
|
|
306
|
+
The index provides O(1) lookup for:
|
|
307
|
+
- Pattern matching (by keyword)
|
|
308
|
+
- Tag filtering
|
|
309
|
+
- Severity grouping
|
|
310
|
+
- Direct ID lookup
|
|
311
|
+
`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Run if called directly
|
|
315
|
+
if (process.argv[1]?.includes('knowledge-index')) {
|
|
316
|
+
main();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export default {
|
|
320
|
+
loadIndex,
|
|
321
|
+
rebuildIndex,
|
|
322
|
+
checkIndexFreshness,
|
|
323
|
+
searchByPattern,
|
|
324
|
+
searchByTag,
|
|
325
|
+
getById
|
|
326
|
+
};
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Knowledge Metrics Collector
|
|
4
|
+
*
|
|
5
|
+
* Aggregates KPIs from knowledge base for observability.
|
|
6
|
+
* Metrics are collected on-demand or on scan completion.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* agent metrics # Show dashboard
|
|
10
|
+
* agent metrics --json # JSON output for CI
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import yaml from 'js-yaml';
|
|
16
|
+
import { KNOWLEDGE_DIR } from './config.js';
|
|
17
|
+
import { loadIndex } from './knowledge-index.js';
|
|
18
|
+
|
|
19
|
+
const METRICS_PATH = path.join(KNOWLEDGE_DIR, 'metrics.json');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load mistakes and improvements
|
|
23
|
+
* @returns {{ mistakes: Array, improvements: Array }}
|
|
24
|
+
*/
|
|
25
|
+
function loadKnowledge() {
|
|
26
|
+
const mistakes = [];
|
|
27
|
+
const improvements = [];
|
|
28
|
+
|
|
29
|
+
const mistakesPath = path.join(KNOWLEDGE_DIR, 'mistakes.yaml');
|
|
30
|
+
if (fs.existsSync(mistakesPath)) {
|
|
31
|
+
const data = yaml.load(fs.readFileSync(mistakesPath, 'utf8'));
|
|
32
|
+
if (data?.mistakes) mistakes.push(...data.mistakes);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const improvementsPath = path.join(KNOWLEDGE_DIR, 'improvements.yaml');
|
|
36
|
+
if (fs.existsSync(improvementsPath)) {
|
|
37
|
+
const data = yaml.load(fs.readFileSync(improvementsPath, 'utf8'));
|
|
38
|
+
if (data?.improvements) improvements.push(...data.improvements);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { mistakes, improvements };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Calculate average confidence score
|
|
46
|
+
* @param {Array} lessons
|
|
47
|
+
* @returns {number}
|
|
48
|
+
*/
|
|
49
|
+
function calculateAvgConfidence(lessons) {
|
|
50
|
+
if (lessons.length === 0) return 0;
|
|
51
|
+
|
|
52
|
+
const total = lessons.reduce((sum, l) => {
|
|
53
|
+
return sum + (l.cognitive?.confidence || 0.3);
|
|
54
|
+
}, 0);
|
|
55
|
+
|
|
56
|
+
return total / lessons.length;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Calculate escalation rate
|
|
61
|
+
* @param {Array} mistakes
|
|
62
|
+
* @returns {number}
|
|
63
|
+
*/
|
|
64
|
+
function calculateEscalationRate(mistakes) {
|
|
65
|
+
if (mistakes.length === 0) return 0;
|
|
66
|
+
|
|
67
|
+
const escalated = mistakes.filter(m => m.autoEscalated === true).length;
|
|
68
|
+
return escalated / mistakes.length;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Calculate fix rate
|
|
73
|
+
* @param {Array} improvements
|
|
74
|
+
* @returns {number}
|
|
75
|
+
*/
|
|
76
|
+
function calculateFixRate(improvements) {
|
|
77
|
+
if (improvements.length === 0) return 0;
|
|
78
|
+
|
|
79
|
+
const applied = improvements.filter(i => (i.appliedCount || 0) > 0).length;
|
|
80
|
+
return applied / improvements.length;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get 7-day hit trend
|
|
85
|
+
* @param {Array} lessons
|
|
86
|
+
* @returns {number[]}
|
|
87
|
+
*/
|
|
88
|
+
function get7DayTrend(lessons) {
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
91
|
+
const trend = new Array(7).fill(0);
|
|
92
|
+
|
|
93
|
+
for (const lesson of lessons) {
|
|
94
|
+
if (lesson.lastHit) {
|
|
95
|
+
const hitTime = new Date(lesson.lastHit).getTime();
|
|
96
|
+
const daysAgo = Math.floor((now - hitTime) / dayMs);
|
|
97
|
+
|
|
98
|
+
if (daysAgo >= 0 && daysAgo < 7) {
|
|
99
|
+
trend[6 - daysAgo] += lesson.hitCount || 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return trend;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get new lessons in last 30 days
|
|
109
|
+
* @param {Array} lessons
|
|
110
|
+
* @returns {number}
|
|
111
|
+
*/
|
|
112
|
+
function getNewLessonsLast30Days(lessons) {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
|
|
115
|
+
|
|
116
|
+
return lessons.filter(l => {
|
|
117
|
+
if (l.addedAt) {
|
|
118
|
+
const addedTime = new Date(l.addedAt).getTime();
|
|
119
|
+
return (now - addedTime) < thirtyDaysMs;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}).length;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get top violations
|
|
127
|
+
* @param {Array} mistakes
|
|
128
|
+
* @param {number} limit
|
|
129
|
+
* @returns {Array}
|
|
130
|
+
*/
|
|
131
|
+
function getTopViolations(mistakes, limit = 5) {
|
|
132
|
+
return mistakes
|
|
133
|
+
.map(m => ({
|
|
134
|
+
id: m.id,
|
|
135
|
+
pattern: m.pattern,
|
|
136
|
+
hitCount: m.hitCount || 0,
|
|
137
|
+
severity: m.severity || 'WARNING'
|
|
138
|
+
}))
|
|
139
|
+
.sort((a, b) => b.hitCount - a.hitCount)
|
|
140
|
+
.slice(0, limit);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get stale lessons (no hits, old)
|
|
145
|
+
* @param {Array} lessons
|
|
146
|
+
* @returns {number}
|
|
147
|
+
*/
|
|
148
|
+
function getStaleLessonsCount(lessons) {
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const ninetyDaysMs = 90 * 24 * 60 * 60 * 1000;
|
|
151
|
+
|
|
152
|
+
return lessons.filter(l => {
|
|
153
|
+
const noHits = !l.hitCount || l.hitCount === 0;
|
|
154
|
+
const isOld = l.addedAt && (now - new Date(l.addedAt).getTime()) > ninetyDaysMs;
|
|
155
|
+
return noHits && isOld;
|
|
156
|
+
}).length;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Collect all metrics
|
|
161
|
+
* @returns {Object}
|
|
162
|
+
*/
|
|
163
|
+
export function collectMetrics() {
|
|
164
|
+
const { mistakes, improvements } = loadKnowledge();
|
|
165
|
+
const allLessons = [...mistakes, ...improvements];
|
|
166
|
+
|
|
167
|
+
// Load index for additional stats
|
|
168
|
+
let indexStats = { totalPatterns: 0, totalTags: 0 };
|
|
169
|
+
try {
|
|
170
|
+
const index = loadIndex();
|
|
171
|
+
indexStats = index.stats || indexStats;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
// Index may not exist yet
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const metrics = {
|
|
177
|
+
version: 1,
|
|
178
|
+
lastUpdated: new Date().toISOString(),
|
|
179
|
+
|
|
180
|
+
kpis: {
|
|
181
|
+
totalLessons: allLessons.length,
|
|
182
|
+
totalMistakes: mistakes.length,
|
|
183
|
+
totalImprovements: improvements.length,
|
|
184
|
+
activePatterns: allLessons.filter(l => (l.hitCount || 0) > 0).length,
|
|
185
|
+
totalHits: allLessons.reduce((sum, l) => sum + (l.hitCount || 0), 0),
|
|
186
|
+
avgConfidence: parseFloat(calculateAvgConfidence(allLessons).toFixed(2)),
|
|
187
|
+
escalationRate: parseFloat(calculateEscalationRate(mistakes).toFixed(2)),
|
|
188
|
+
fixRate: parseFloat(calculateFixRate(improvements).toFixed(2)),
|
|
189
|
+
staleLessons: getStaleLessonsCount(allLessons)
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
trends: {
|
|
193
|
+
hitsLast7Days: get7DayTrend(allLessons),
|
|
194
|
+
newLessonsLast30Days: getNewLessonsLast30Days(allLessons)
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
topViolations: getTopViolations(mistakes, 5),
|
|
198
|
+
|
|
199
|
+
breakdown: {
|
|
200
|
+
bySeverity: {
|
|
201
|
+
ERROR: mistakes.filter(m => m.severity === 'ERROR').length,
|
|
202
|
+
WARNING: mistakes.filter(m => m.severity === 'WARNING').length,
|
|
203
|
+
INFO: improvements.length
|
|
204
|
+
},
|
|
205
|
+
byMaturity: {
|
|
206
|
+
stable: allLessons.filter(l => l.cognitive?.maturity === 'stable').length,
|
|
207
|
+
learning: allLessons.filter(l => l.cognitive?.maturity === 'learning').length,
|
|
208
|
+
deprecated: allLessons.filter(l => l.cognitive?.maturity === 'deprecated').length
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
index: indexStats
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
return metrics;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Save metrics to file
|
|
220
|
+
* @param {Object} metrics
|
|
221
|
+
*/
|
|
222
|
+
export function saveMetrics(metrics) {
|
|
223
|
+
fs.writeFileSync(METRICS_PATH, JSON.stringify(metrics, null, 2), 'utf8');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Load cached metrics
|
|
228
|
+
* @returns {Object|null}
|
|
229
|
+
*/
|
|
230
|
+
export function loadMetrics() {
|
|
231
|
+
if (!fs.existsSync(METRICS_PATH)) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
return JSON.parse(fs.readFileSync(METRICS_PATH, 'utf8'));
|
|
237
|
+
} catch (e) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Display metrics dashboard
|
|
244
|
+
* @param {Object} metrics
|
|
245
|
+
*/
|
|
246
|
+
function displayDashboard(metrics) {
|
|
247
|
+
console.log(`
|
|
248
|
+
📊 Knowledge Metrics Dashboard
|
|
249
|
+
${'─'.repeat(50)}
|
|
250
|
+
|
|
251
|
+
📈 KPIs:
|
|
252
|
+
Total Lessons: ${metrics.kpis.totalLessons}
|
|
253
|
+
Active Patterns: ${metrics.kpis.activePatterns} (${Math.round((metrics.kpis.activePatterns / metrics.kpis.totalLessons) * 100)}%)
|
|
254
|
+
Total Hits: ${metrics.kpis.totalHits}
|
|
255
|
+
Avg Confidence: ${metrics.kpis.avgConfidence}
|
|
256
|
+
Escalation Rate: ${Math.round(metrics.kpis.escalationRate * 100)}%
|
|
257
|
+
Fix Rate: ${Math.round(metrics.kpis.fixRate * 100)}%
|
|
258
|
+
Stale Lessons: ${metrics.kpis.staleLessons}
|
|
259
|
+
|
|
260
|
+
📊 Breakdown:
|
|
261
|
+
Mistakes: ${metrics.kpis.totalMistakes}
|
|
262
|
+
ERROR: ${metrics.breakdown.bySeverity.ERROR}
|
|
263
|
+
WARNING: ${metrics.breakdown.bySeverity.WARNING}
|
|
264
|
+
Improvements: ${metrics.kpis.totalImprovements}
|
|
265
|
+
|
|
266
|
+
🧠 Maturity:
|
|
267
|
+
Stable: ${metrics.breakdown.byMaturity.stable}
|
|
268
|
+
Learning: ${metrics.breakdown.byMaturity.learning}
|
|
269
|
+
Deprecated: ${metrics.breakdown.byMaturity.deprecated}
|
|
270
|
+
|
|
271
|
+
📈 Trends:
|
|
272
|
+
7-Day Hits: ${sparkline(metrics.trends.hitsLast7Days)}
|
|
273
|
+
New (30d): ${metrics.trends.newLessonsLast30Days}
|
|
274
|
+
|
|
275
|
+
🔥 Top Violations:`);
|
|
276
|
+
|
|
277
|
+
metrics.topViolations.forEach((v, i) => {
|
|
278
|
+
console.log(` ${i + 1}. ${v.id}: ${v.hitCount} hits (${v.severity})`);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
console.log(`
|
|
282
|
+
🔎 Index:
|
|
283
|
+
Patterns: ${metrics.index.totalPatterns}
|
|
284
|
+
Tags: ${metrics.index.totalTags}
|
|
285
|
+
|
|
286
|
+
Last Updated: ${new Date(metrics.lastUpdated).toLocaleString()}
|
|
287
|
+
`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Generate ASCII sparkline
|
|
292
|
+
* @param {number[]} data
|
|
293
|
+
* @returns {string}
|
|
294
|
+
*/
|
|
295
|
+
function sparkline(data) {
|
|
296
|
+
const chars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
|
|
297
|
+
const max = Math.max(...data, 1);
|
|
298
|
+
|
|
299
|
+
return data.map(val => {
|
|
300
|
+
const index = Math.floor((val / max) * (chars.length - 1));
|
|
301
|
+
return chars[index];
|
|
302
|
+
}).join('');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* CLI entry point
|
|
307
|
+
*/
|
|
308
|
+
function main() {
|
|
309
|
+
const args = process.argv.slice(2);
|
|
310
|
+
const jsonMode = args.includes('--json');
|
|
311
|
+
|
|
312
|
+
console.log('🔄 Collecting metrics...');
|
|
313
|
+
const metrics = collectMetrics();
|
|
314
|
+
|
|
315
|
+
// Save for caching
|
|
316
|
+
saveMetrics(metrics);
|
|
317
|
+
|
|
318
|
+
if (jsonMode) {
|
|
319
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
displayDashboard(metrics);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Run if called directly
|
|
327
|
+
if (process.argv[1]?.includes('knowledge-metrics')) {
|
|
328
|
+
main();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export default {
|
|
332
|
+
collectMetrics,
|
|
333
|
+
saveMetrics,
|
|
334
|
+
loadMetrics
|
|
335
|
+
};
|