loki-mode 5.8.8 → 5.9.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/.loki/dashboard/index.html +102 -1
- package/autonomy/api-server.js +135 -17
- package/autonomy/loki +258 -0
- package/autonomy/run.sh +91 -16
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with zero human intervention. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v5.
|
|
6
|
+
# Loki Mode v5.9.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -253,4 +253,4 @@ Auto-detected or force with `LOKI_COMPLEXITY`:
|
|
|
253
253
|
|
|
254
254
|
---
|
|
255
255
|
|
|
256
|
-
**v5.
|
|
256
|
+
**v5.9.0 | Cross-Project Learning, VS Code Chat and Logs views | ~250 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.
|
|
1
|
+
5.9.0
|
|
@@ -1648,6 +1648,46 @@
|
|
|
1648
1648
|
</div>
|
|
1649
1649
|
</div>
|
|
1650
1650
|
|
|
1651
|
+
<!-- Cross-Project Learnings -->
|
|
1652
|
+
<div class="system-card">
|
|
1653
|
+
<div class="system-card-header">
|
|
1654
|
+
<span class="system-card-title">
|
|
1655
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/></svg>
|
|
1656
|
+
Cross-Project Learnings
|
|
1657
|
+
</span>
|
|
1658
|
+
<span class="system-card-status" id="learnings-status">--</span>
|
|
1659
|
+
</div>
|
|
1660
|
+
<div class="memory-bars">
|
|
1661
|
+
<div class="memory-bar">
|
|
1662
|
+
<div class="memory-bar-header">
|
|
1663
|
+
<span class="memory-bar-label">Patterns</span>
|
|
1664
|
+
<span class="memory-bar-value" id="learnings-patterns">0</span>
|
|
1665
|
+
</div>
|
|
1666
|
+
<div class="memory-bar-track">
|
|
1667
|
+
<div class="memory-bar-fill semantic" id="learnings-patterns-bar" style="width: 0%;"></div>
|
|
1668
|
+
</div>
|
|
1669
|
+
</div>
|
|
1670
|
+
<div class="memory-bar">
|
|
1671
|
+
<div class="memory-bar-header">
|
|
1672
|
+
<span class="memory-bar-label">Mistakes</span>
|
|
1673
|
+
<span class="memory-bar-value" id="learnings-mistakes">0</span>
|
|
1674
|
+
</div>
|
|
1675
|
+
<div class="memory-bar-track">
|
|
1676
|
+
<div class="memory-bar-fill episodic" id="learnings-mistakes-bar" style="width: 0%;"></div>
|
|
1677
|
+
</div>
|
|
1678
|
+
</div>
|
|
1679
|
+
<div class="memory-bar">
|
|
1680
|
+
<div class="memory-bar-header">
|
|
1681
|
+
<span class="memory-bar-label">Successes</span>
|
|
1682
|
+
<span class="memory-bar-value" id="learnings-successes">0</span>
|
|
1683
|
+
</div>
|
|
1684
|
+
<div class="memory-bar-track">
|
|
1685
|
+
<div class="memory-bar-fill procedural" id="learnings-successes-bar" style="width: 0%;"></div>
|
|
1686
|
+
</div>
|
|
1687
|
+
</div>
|
|
1688
|
+
</div>
|
|
1689
|
+
</div>
|
|
1690
|
+
|
|
1651
1691
|
<!-- Quality Gates -->
|
|
1652
1692
|
<div class="system-card">
|
|
1653
1693
|
<div class="system-card-header">
|
|
@@ -1806,7 +1846,8 @@
|
|
|
1806
1846
|
const CONFIG = {
|
|
1807
1847
|
POLL_INTERVAL: 2000, // Poll every 2 seconds
|
|
1808
1848
|
STATE_FILE: '../dashboard-state.json',
|
|
1809
|
-
LOCAL_STORAGE_KEY: 'loki-dashboard-local'
|
|
1849
|
+
LOCAL_STORAGE_KEY: 'loki-dashboard-local',
|
|
1850
|
+
API_URL: 'http://localhost:9898' // Loki API server for cross-project learnings
|
|
1810
1851
|
};
|
|
1811
1852
|
|
|
1812
1853
|
// ============================================
|
|
@@ -1890,9 +1931,11 @@
|
|
|
1890
1931
|
function startPolling() {
|
|
1891
1932
|
fetchServerState();
|
|
1892
1933
|
fetchTerminalLogs();
|
|
1934
|
+
fetchLearnings();
|
|
1893
1935
|
pollInterval = setInterval(() => {
|
|
1894
1936
|
fetchServerState();
|
|
1895
1937
|
fetchTerminalLogs();
|
|
1938
|
+
fetchLearnings();
|
|
1896
1939
|
}, CONFIG.POLL_INTERVAL);
|
|
1897
1940
|
}
|
|
1898
1941
|
|
|
@@ -2418,6 +2461,64 @@
|
|
|
2418
2461
|
}
|
|
2419
2462
|
}
|
|
2420
2463
|
|
|
2464
|
+
// ============================================
|
|
2465
|
+
// Cross-Project Learnings (API)
|
|
2466
|
+
// ============================================
|
|
2467
|
+
async function fetchLearnings() {
|
|
2468
|
+
try {
|
|
2469
|
+
const response = await fetch(CONFIG.API_URL + '/memory');
|
|
2470
|
+
if (!response.ok) {
|
|
2471
|
+
updateLearningsUI(null);
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
const data = await response.json();
|
|
2475
|
+
updateLearningsUI(data);
|
|
2476
|
+
} catch (error) {
|
|
2477
|
+
// API server may not be running
|
|
2478
|
+
updateLearningsUI(null);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
function updateLearningsUI(data) {
|
|
2483
|
+
const statusEl = document.getElementById('learnings-status');
|
|
2484
|
+
const patternsEl = document.getElementById('learnings-patterns');
|
|
2485
|
+
const mistakesEl = document.getElementById('learnings-mistakes');
|
|
2486
|
+
const successesEl = document.getElementById('learnings-successes');
|
|
2487
|
+
const patternsBar = document.getElementById('learnings-patterns-bar');
|
|
2488
|
+
const mistakesBar = document.getElementById('learnings-mistakes-bar');
|
|
2489
|
+
const successesBar = document.getElementById('learnings-successes-bar');
|
|
2490
|
+
|
|
2491
|
+
if (!data) {
|
|
2492
|
+
statusEl.textContent = 'Offline';
|
|
2493
|
+
statusEl.className = 'system-card-status warning';
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
const patterns = data.patterns || 0;
|
|
2498
|
+
const mistakes = data.mistakes || 0;
|
|
2499
|
+
const successes = data.successes || 0;
|
|
2500
|
+
const total = patterns + mistakes + successes;
|
|
2501
|
+
|
|
2502
|
+
patternsEl.textContent = patterns;
|
|
2503
|
+
mistakesEl.textContent = mistakes;
|
|
2504
|
+
successesEl.textContent = successes;
|
|
2505
|
+
|
|
2506
|
+
// Update progress bars (max 100 for percentage calc)
|
|
2507
|
+
const maxLearnings = Math.max(100, total);
|
|
2508
|
+
patternsBar.style.width = Math.min((patterns / maxLearnings) * 100, 100) + '%';
|
|
2509
|
+
mistakesBar.style.width = Math.min((mistakes / maxLearnings) * 100, 100) + '%';
|
|
2510
|
+
successesBar.style.width = Math.min((successes / maxLearnings) * 100, 100) + '%';
|
|
2511
|
+
|
|
2512
|
+
// Update status
|
|
2513
|
+
if (total === 0) {
|
|
2514
|
+
statusEl.textContent = 'No data';
|
|
2515
|
+
statusEl.className = 'system-card-status';
|
|
2516
|
+
} else {
|
|
2517
|
+
statusEl.textContent = total + ' total';
|
|
2518
|
+
statusEl.className = 'system-card-status healthy';
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2421
2522
|
// ============================================
|
|
2422
2523
|
// GitHub Import
|
|
2423
2524
|
// ============================================
|
package/autonomy/api-server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Loki Mode HTTP API Server (v1.
|
|
3
|
+
* Loki Mode HTTP API Server (v1.1.0)
|
|
4
4
|
* Zero npm dependencies - uses only Node.js built-ins
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
@@ -8,14 +8,19 @@
|
|
|
8
8
|
* loki api start
|
|
9
9
|
*
|
|
10
10
|
* Endpoints:
|
|
11
|
-
* GET /health
|
|
12
|
-
* GET /status
|
|
13
|
-
* GET /events
|
|
14
|
-
* GET /logs
|
|
15
|
-
* POST /start
|
|
16
|
-
* POST /stop
|
|
17
|
-
* POST /pause
|
|
18
|
-
* POST /resume
|
|
11
|
+
* GET /health - Health check
|
|
12
|
+
* GET /status - Session status
|
|
13
|
+
* GET /events - SSE stream
|
|
14
|
+
* GET /logs - Recent log lines
|
|
15
|
+
* POST /start - Start session
|
|
16
|
+
* POST /stop - Stop session
|
|
17
|
+
* POST /pause - Pause session
|
|
18
|
+
* POST /resume - Resume session
|
|
19
|
+
* GET /memory - Cross-project learnings summary
|
|
20
|
+
* GET /memory/:type - Get learnings (patterns|mistakes|successes)
|
|
21
|
+
* GET /memory/search?q= - Search learnings
|
|
22
|
+
* GET /memory/stats - Statistics by project/category
|
|
23
|
+
* DELETE /memory/:type - Clear specific learning type
|
|
19
24
|
*/
|
|
20
25
|
|
|
21
26
|
const http = require('http');
|
|
@@ -334,6 +339,115 @@ async function handleRequest(req, res) {
|
|
|
334
339
|
return json({ resumed: true });
|
|
335
340
|
}
|
|
336
341
|
|
|
342
|
+
// === Memory/Learnings endpoints ===
|
|
343
|
+
const LEARNINGS_DIR = path.join(process.env.HOME || '', '.loki', 'learnings');
|
|
344
|
+
|
|
345
|
+
if (method === 'GET' && pathname === '/memory') {
|
|
346
|
+
// List summary of all learnings
|
|
347
|
+
const result = { patterns: 0, mistakes: 0, successes: 0, location: LEARNINGS_DIR };
|
|
348
|
+
|
|
349
|
+
for (const type of ['patterns', 'mistakes', 'successes']) {
|
|
350
|
+
const filepath = path.join(LEARNINGS_DIR, `${type}.jsonl`);
|
|
351
|
+
if (fs.existsSync(filepath)) {
|
|
352
|
+
const content = fs.readFileSync(filepath, 'utf8');
|
|
353
|
+
const count = (content.match(/"description"/g) || []).length;
|
|
354
|
+
result[type] = count;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return json(result);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (method === 'GET' && pathname.startsWith('/memory/search')) {
|
|
361
|
+
const query = url.searchParams.get('q') || '';
|
|
362
|
+
if (!query) {
|
|
363
|
+
return json({ error: 'Missing query parameter ?q=' }, 400);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const results = [];
|
|
367
|
+
const queryLower = query.toLowerCase();
|
|
368
|
+
|
|
369
|
+
for (const type of ['patterns', 'mistakes', 'successes']) {
|
|
370
|
+
const filepath = path.join(LEARNINGS_DIR, `${type}.jsonl`);
|
|
371
|
+
if (!fs.existsSync(filepath)) continue;
|
|
372
|
+
|
|
373
|
+
const lines = fs.readFileSync(filepath, 'utf8').split('\n');
|
|
374
|
+
for (const line of lines) {
|
|
375
|
+
if (!line.trim()) continue;
|
|
376
|
+
try {
|
|
377
|
+
const entry = JSON.parse(line);
|
|
378
|
+
if (entry.description && entry.description.toLowerCase().includes(queryLower)) {
|
|
379
|
+
results.push({ type, ...entry });
|
|
380
|
+
}
|
|
381
|
+
} catch {}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return json({ query, results, count: results.length });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (method === 'GET' && pathname.match(/^\/memory\/(patterns|mistakes|successes)$/)) {
|
|
388
|
+
const type = pathname.split('/')[2];
|
|
389
|
+
const filepath = path.join(LEARNINGS_DIR, `${type}.jsonl`);
|
|
390
|
+
const limit = parseInt(url.searchParams.get('limit')) || 50;
|
|
391
|
+
const offset = parseInt(url.searchParams.get('offset')) || 0;
|
|
392
|
+
|
|
393
|
+
if (!fs.existsSync(filepath)) {
|
|
394
|
+
return json({ type, entries: [], total: 0 });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const entries = [];
|
|
398
|
+
const lines = fs.readFileSync(filepath, 'utf8').split('\n');
|
|
399
|
+
for (const line of lines) {
|
|
400
|
+
if (!line.trim()) continue;
|
|
401
|
+
try {
|
|
402
|
+
const entry = JSON.parse(line);
|
|
403
|
+
if (entry.description) {
|
|
404
|
+
entries.push(entry);
|
|
405
|
+
}
|
|
406
|
+
} catch {}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const total = entries.length;
|
|
410
|
+
const paginated = entries.slice(offset, offset + limit);
|
|
411
|
+
|
|
412
|
+
return json({ type, entries: paginated, total, limit, offset });
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (method === 'GET' && pathname === '/memory/stats') {
|
|
416
|
+
const stats = { byCategory: {}, byProject: {} };
|
|
417
|
+
|
|
418
|
+
for (const type of ['patterns', 'mistakes', 'successes']) {
|
|
419
|
+
const filepath = path.join(LEARNINGS_DIR, `${type}.jsonl`);
|
|
420
|
+
if (!fs.existsSync(filepath)) continue;
|
|
421
|
+
|
|
422
|
+
const lines = fs.readFileSync(filepath, 'utf8').split('\n');
|
|
423
|
+
let count = 0;
|
|
424
|
+
for (const line of lines) {
|
|
425
|
+
if (!line.trim()) continue;
|
|
426
|
+
try {
|
|
427
|
+
const entry = JSON.parse(line);
|
|
428
|
+
if (entry.description) {
|
|
429
|
+
count++;
|
|
430
|
+
const proj = entry.project || 'unknown';
|
|
431
|
+
stats.byProject[proj] = (stats.byProject[proj] || 0) + 1;
|
|
432
|
+
}
|
|
433
|
+
} catch {}
|
|
434
|
+
}
|
|
435
|
+
stats.byCategory[type] = count;
|
|
436
|
+
}
|
|
437
|
+
return json(stats);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (method === 'DELETE' && pathname.match(/^\/memory\/(patterns|mistakes|successes)$/)) {
|
|
441
|
+
const type = pathname.split('/')[2];
|
|
442
|
+
const filepath = path.join(LEARNINGS_DIR, `${type}.jsonl`);
|
|
443
|
+
|
|
444
|
+
fs.mkdirSync(LEARNINGS_DIR, { recursive: true });
|
|
445
|
+
const header = JSON.stringify({ version: '1.0', created: new Date().toISOString() });
|
|
446
|
+
fs.writeFileSync(filepath, header + '\n');
|
|
447
|
+
|
|
448
|
+
return json({ cleared: type });
|
|
449
|
+
}
|
|
450
|
+
|
|
337
451
|
// 404 for unknown routes
|
|
338
452
|
json({ error: 'not found', path: pathname }, 404);
|
|
339
453
|
}
|
|
@@ -346,14 +460,18 @@ server.listen(PORT, () => {
|
|
|
346
460
|
console.log(`Listening on http://localhost:${PORT}`);
|
|
347
461
|
console.log('');
|
|
348
462
|
console.log('Endpoints:');
|
|
349
|
-
console.log(' GET /health
|
|
350
|
-
console.log(' GET /status
|
|
351
|
-
console.log(' GET /events
|
|
352
|
-
console.log(' GET /logs
|
|
353
|
-
console.log(' POST /start
|
|
354
|
-
console.log(' POST /stop
|
|
355
|
-
console.log(' POST /pause
|
|
356
|
-
console.log(' POST /resume
|
|
463
|
+
console.log(' GET /health - Health check');
|
|
464
|
+
console.log(' GET /status - Session status');
|
|
465
|
+
console.log(' GET /events - SSE stream (real-time updates)');
|
|
466
|
+
console.log(' GET /logs - Recent log lines (?lines=50)');
|
|
467
|
+
console.log(' POST /start - Start session');
|
|
468
|
+
console.log(' POST /stop - Stop session');
|
|
469
|
+
console.log(' POST /pause - Pause after current task');
|
|
470
|
+
console.log(' POST /resume - Resume paused session');
|
|
471
|
+
console.log(' GET /memory - Cross-project learnings summary');
|
|
472
|
+
console.log(' GET /memory/:type - Get learnings by type');
|
|
473
|
+
console.log(' GET /memory/search?q= - Search learnings');
|
|
474
|
+
console.log(' GET /memory/stats - Statistics');
|
|
357
475
|
console.log('');
|
|
358
476
|
console.log(`LOKI_DIR: ${LOKI_DIR}`);
|
|
359
477
|
console.log(`SKILL_DIR: ${SKILL_DIR}`);
|
package/autonomy/loki
CHANGED
|
@@ -137,6 +137,8 @@ show_help() {
|
|
|
137
137
|
echo " sandbox [cmd] Docker sandbox (start|stop|status|logs|shell|build)"
|
|
138
138
|
echo " import Import GitHub issues as tasks"
|
|
139
139
|
echo " config [cmd] Manage configuration (show|init|edit|path)"
|
|
140
|
+
echo " memory [cmd] Cross-project learnings (list|show|search|stats)"
|
|
141
|
+
echo " reset [target] Reset session state (all|retries|failed)"
|
|
140
142
|
echo " version Show version"
|
|
141
143
|
echo " help Show this help"
|
|
142
144
|
echo ""
|
|
@@ -1127,6 +1129,9 @@ main() {
|
|
|
1127
1129
|
reset)
|
|
1128
1130
|
cmd_reset "$@"
|
|
1129
1131
|
;;
|
|
1132
|
+
memory)
|
|
1133
|
+
cmd_memory "$@"
|
|
1134
|
+
;;
|
|
1130
1135
|
version|--version|-v)
|
|
1131
1136
|
cmd_version
|
|
1132
1137
|
;;
|
|
@@ -1210,4 +1215,257 @@ with open('$LOKI_DIR/autonomy-state.json', 'w') as f:
|
|
|
1210
1215
|
esac
|
|
1211
1216
|
}
|
|
1212
1217
|
|
|
1218
|
+
# Cross-project memory/learnings management
|
|
1219
|
+
cmd_memory() {
|
|
1220
|
+
local subcommand="${1:-list}"
|
|
1221
|
+
local learnings_dir="${HOME}/.loki/learnings"
|
|
1222
|
+
|
|
1223
|
+
# Ensure directory exists
|
|
1224
|
+
mkdir -p "$learnings_dir"
|
|
1225
|
+
|
|
1226
|
+
case "$subcommand" in
|
|
1227
|
+
list|ls)
|
|
1228
|
+
echo -e "${BOLD}Cross-Project Learnings${NC}"
|
|
1229
|
+
echo ""
|
|
1230
|
+
|
|
1231
|
+
local patterns=0 mistakes=0 successes=0
|
|
1232
|
+
[ -f "$learnings_dir/patterns.jsonl" ] && patterns=$(grep -c '"description"' "$learnings_dir/patterns.jsonl" 2>/dev/null) || patterns=0
|
|
1233
|
+
[ -f "$learnings_dir/mistakes.jsonl" ] && mistakes=$(grep -c '"description"' "$learnings_dir/mistakes.jsonl" 2>/dev/null) || mistakes=0
|
|
1234
|
+
[ -f "$learnings_dir/successes.jsonl" ] && successes=$(grep -c '"description"' "$learnings_dir/successes.jsonl" 2>/dev/null) || successes=0
|
|
1235
|
+
|
|
1236
|
+
echo -e " Patterns: ${GREEN}$patterns${NC}"
|
|
1237
|
+
echo -e " Mistakes: ${YELLOW}$mistakes${NC}"
|
|
1238
|
+
echo -e " Successes: ${CYAN}$successes${NC}"
|
|
1239
|
+
echo ""
|
|
1240
|
+
echo "Location: $learnings_dir"
|
|
1241
|
+
echo ""
|
|
1242
|
+
echo "Use 'loki memory show <type>' to view entries"
|
|
1243
|
+
;;
|
|
1244
|
+
|
|
1245
|
+
show)
|
|
1246
|
+
local type="${2:-all}"
|
|
1247
|
+
|
|
1248
|
+
case "$type" in
|
|
1249
|
+
patterns|pattern)
|
|
1250
|
+
echo -e "${BOLD}Patterns${NC}"
|
|
1251
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1252
|
+
if [ -f "$learnings_dir/patterns.jsonl" ]; then
|
|
1253
|
+
python3 -c "
|
|
1254
|
+
import json
|
|
1255
|
+
with open('$learnings_dir/patterns.jsonl', 'r') as f:
|
|
1256
|
+
for line in f:
|
|
1257
|
+
try:
|
|
1258
|
+
e = json.loads(line)
|
|
1259
|
+
if 'description' in e:
|
|
1260
|
+
print(f\"[{e.get('project', 'unknown')}] {e['description'][:100]}\")
|
|
1261
|
+
except: pass
|
|
1262
|
+
" 2>/dev/null || echo "No patterns found"
|
|
1263
|
+
fi
|
|
1264
|
+
;;
|
|
1265
|
+
|
|
1266
|
+
mistakes|mistake)
|
|
1267
|
+
echo -e "${BOLD}Mistakes${NC}"
|
|
1268
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1269
|
+
if [ -f "$learnings_dir/mistakes.jsonl" ]; then
|
|
1270
|
+
python3 -c "
|
|
1271
|
+
import json
|
|
1272
|
+
with open('$learnings_dir/mistakes.jsonl', 'r') as f:
|
|
1273
|
+
for line in f:
|
|
1274
|
+
try:
|
|
1275
|
+
e = json.loads(line)
|
|
1276
|
+
if 'description' in e:
|
|
1277
|
+
print(f\"[{e.get('project', 'unknown')}] {e['description'][:100]}\")
|
|
1278
|
+
except: pass
|
|
1279
|
+
" 2>/dev/null | tail -20 || echo "No mistakes found"
|
|
1280
|
+
fi
|
|
1281
|
+
;;
|
|
1282
|
+
|
|
1283
|
+
successes|success)
|
|
1284
|
+
echo -e "${BOLD}Successes${NC}"
|
|
1285
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1286
|
+
if [ -f "$learnings_dir/successes.jsonl" ]; then
|
|
1287
|
+
python3 -c "
|
|
1288
|
+
import json
|
|
1289
|
+
with open('$learnings_dir/successes.jsonl', 'r') as f:
|
|
1290
|
+
for line in f:
|
|
1291
|
+
try:
|
|
1292
|
+
e = json.loads(line)
|
|
1293
|
+
if 'description' in e:
|
|
1294
|
+
print(f\"[{e.get('project', 'unknown')}] {e['description'][:100]}\")
|
|
1295
|
+
except: pass
|
|
1296
|
+
" 2>/dev/null | tail -20 || echo "No successes found"
|
|
1297
|
+
fi
|
|
1298
|
+
;;
|
|
1299
|
+
|
|
1300
|
+
all|*)
|
|
1301
|
+
cmd_memory show patterns
|
|
1302
|
+
echo ""
|
|
1303
|
+
cmd_memory show mistakes
|
|
1304
|
+
echo ""
|
|
1305
|
+
cmd_memory show successes
|
|
1306
|
+
;;
|
|
1307
|
+
esac
|
|
1308
|
+
;;
|
|
1309
|
+
|
|
1310
|
+
search)
|
|
1311
|
+
local query="$2"
|
|
1312
|
+
if [ -z "$query" ]; then
|
|
1313
|
+
echo -e "${RED}Usage: loki memory search <query>${NC}"
|
|
1314
|
+
exit 1
|
|
1315
|
+
fi
|
|
1316
|
+
|
|
1317
|
+
echo -e "${BOLD}Search Results for: $query${NC}"
|
|
1318
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1319
|
+
|
|
1320
|
+
python3 -c "
|
|
1321
|
+
import json
|
|
1322
|
+
import os
|
|
1323
|
+
|
|
1324
|
+
learnings_dir = '$learnings_dir'
|
|
1325
|
+
query = '$query'.lower()
|
|
1326
|
+
|
|
1327
|
+
for filename in ['patterns.jsonl', 'mistakes.jsonl', 'successes.jsonl']:
|
|
1328
|
+
filepath = os.path.join(learnings_dir, filename)
|
|
1329
|
+
if not os.path.exists(filepath):
|
|
1330
|
+
continue
|
|
1331
|
+
|
|
1332
|
+
category = filename.replace('.jsonl', '')
|
|
1333
|
+
with open(filepath, 'r') as f:
|
|
1334
|
+
for line in f:
|
|
1335
|
+
try:
|
|
1336
|
+
e = json.loads(line)
|
|
1337
|
+
desc = e.get('description', '').lower()
|
|
1338
|
+
if query in desc:
|
|
1339
|
+
print(f\"[{category}] [{e.get('project', 'unknown')}] {e['description'][:80]}...\")
|
|
1340
|
+
except: pass
|
|
1341
|
+
" 2>/dev/null
|
|
1342
|
+
;;
|
|
1343
|
+
|
|
1344
|
+
clear)
|
|
1345
|
+
local type="${2:-}"
|
|
1346
|
+
|
|
1347
|
+
if [ -z "$type" ]; then
|
|
1348
|
+
echo -e "${YELLOW}This will delete ALL cross-project learnings.${NC}"
|
|
1349
|
+
echo -n "Are you sure? (yes/no): "
|
|
1350
|
+
read -r confirm
|
|
1351
|
+
if [ "$confirm" = "yes" ]; then
|
|
1352
|
+
rm -rf "$learnings_dir"
|
|
1353
|
+
mkdir -p "$learnings_dir"
|
|
1354
|
+
echo '{"version":"1.0","created":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}' > "$learnings_dir/patterns.jsonl"
|
|
1355
|
+
echo '{"version":"1.0","created":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}' > "$learnings_dir/mistakes.jsonl"
|
|
1356
|
+
echo '{"version":"1.0","created":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}' > "$learnings_dir/successes.jsonl"
|
|
1357
|
+
echo -e "${GREEN}All learnings cleared${NC}"
|
|
1358
|
+
else
|
|
1359
|
+
echo "Cancelled"
|
|
1360
|
+
fi
|
|
1361
|
+
else
|
|
1362
|
+
case "$type" in
|
|
1363
|
+
patterns|mistakes|successes)
|
|
1364
|
+
echo '{"version":"1.0","created":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}' > "$learnings_dir/${type}.jsonl"
|
|
1365
|
+
echo -e "${GREEN}Cleared $type${NC}"
|
|
1366
|
+
;;
|
|
1367
|
+
*)
|
|
1368
|
+
echo -e "${RED}Unknown type: $type${NC}"
|
|
1369
|
+
echo "Valid types: patterns, mistakes, successes"
|
|
1370
|
+
exit 1
|
|
1371
|
+
;;
|
|
1372
|
+
esac
|
|
1373
|
+
fi
|
|
1374
|
+
;;
|
|
1375
|
+
|
|
1376
|
+
export)
|
|
1377
|
+
local output="${2:-learnings-export.json}"
|
|
1378
|
+
|
|
1379
|
+
python3 -c "
|
|
1380
|
+
import json
|
|
1381
|
+
import os
|
|
1382
|
+
|
|
1383
|
+
learnings_dir = '$learnings_dir'
|
|
1384
|
+
result = {'patterns': [], 'mistakes': [], 'successes': []}
|
|
1385
|
+
|
|
1386
|
+
for category in ['patterns', 'mistakes', 'successes']:
|
|
1387
|
+
filepath = os.path.join(learnings_dir, f'{category}.jsonl')
|
|
1388
|
+
if os.path.exists(filepath):
|
|
1389
|
+
with open(filepath, 'r') as f:
|
|
1390
|
+
for line in f:
|
|
1391
|
+
try:
|
|
1392
|
+
e = json.loads(line)
|
|
1393
|
+
if 'description' in e:
|
|
1394
|
+
result[category].append(e)
|
|
1395
|
+
except: pass
|
|
1396
|
+
|
|
1397
|
+
with open('$output', 'w') as f:
|
|
1398
|
+
json.dump(result, f, indent=2)
|
|
1399
|
+
print(f'Exported to $output')
|
|
1400
|
+
" 2>/dev/null
|
|
1401
|
+
;;
|
|
1402
|
+
|
|
1403
|
+
stats)
|
|
1404
|
+
echo -e "${BOLD}Learning Statistics${NC}"
|
|
1405
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1406
|
+
|
|
1407
|
+
python3 -c "
|
|
1408
|
+
import json
|
|
1409
|
+
import os
|
|
1410
|
+
from collections import Counter
|
|
1411
|
+
|
|
1412
|
+
learnings_dir = '$learnings_dir'
|
|
1413
|
+
projects = Counter()
|
|
1414
|
+
categories = Counter()
|
|
1415
|
+
|
|
1416
|
+
for filename in ['patterns.jsonl', 'mistakes.jsonl', 'successes.jsonl']:
|
|
1417
|
+
filepath = os.path.join(learnings_dir, filename)
|
|
1418
|
+
if not os.path.exists(filepath):
|
|
1419
|
+
continue
|
|
1420
|
+
|
|
1421
|
+
category = filename.replace('.jsonl', '')
|
|
1422
|
+
with open(filepath, 'r') as f:
|
|
1423
|
+
for line in f:
|
|
1424
|
+
try:
|
|
1425
|
+
e = json.loads(line)
|
|
1426
|
+
if 'description' in e:
|
|
1427
|
+
projects[e.get('project', 'unknown')] += 1
|
|
1428
|
+
categories[category] += 1
|
|
1429
|
+
except: pass
|
|
1430
|
+
|
|
1431
|
+
print('By Category:')
|
|
1432
|
+
for cat, count in categories.most_common():
|
|
1433
|
+
print(f' {cat}: {count}')
|
|
1434
|
+
|
|
1435
|
+
print()
|
|
1436
|
+
print('By Project:')
|
|
1437
|
+
for proj, count in projects.most_common(10):
|
|
1438
|
+
print(f' {proj}: {count}')
|
|
1439
|
+
" 2>/dev/null
|
|
1440
|
+
;;
|
|
1441
|
+
|
|
1442
|
+
--help|-h|help)
|
|
1443
|
+
echo -e "${BOLD}loki memory${NC} - Manage cross-project learnings"
|
|
1444
|
+
echo ""
|
|
1445
|
+
echo "Usage: loki memory <command> [args]"
|
|
1446
|
+
echo ""
|
|
1447
|
+
echo "Commands:"
|
|
1448
|
+
echo " list Show summary of all learnings"
|
|
1449
|
+
echo " show [type] Show entries (patterns|mistakes|successes|all)"
|
|
1450
|
+
echo " search <query> Search across all learnings"
|
|
1451
|
+
echo " stats Show statistics by project and category"
|
|
1452
|
+
echo " export [file] Export to JSON file"
|
|
1453
|
+
echo " clear [type] Clear learnings (all or specific type)"
|
|
1454
|
+
echo ""
|
|
1455
|
+
echo "Examples:"
|
|
1456
|
+
echo " loki memory list"
|
|
1457
|
+
echo " loki memory show mistakes"
|
|
1458
|
+
echo " loki memory search 'rate limit'"
|
|
1459
|
+
echo " loki memory stats"
|
|
1460
|
+
echo " loki memory export backup.json"
|
|
1461
|
+
;;
|
|
1462
|
+
|
|
1463
|
+
*)
|
|
1464
|
+
echo -e "${RED}Unknown memory command: $subcommand${NC}"
|
|
1465
|
+
echo "Run 'loki memory help' for usage."
|
|
1466
|
+
exit 1
|
|
1467
|
+
;;
|
|
1468
|
+
esac
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1213
1471
|
main "$@"
|
package/autonomy/run.sh
CHANGED
|
@@ -3071,15 +3071,17 @@ extract_learnings_from_session() {
|
|
|
3071
3071
|
|
|
3072
3072
|
log_info "Extracting learnings from session..."
|
|
3073
3073
|
|
|
3074
|
-
# Parse CONTINUITY.md for
|
|
3075
|
-
python3 << EXTRACT_SCRIPT
|
|
3074
|
+
# Parse CONTINUITY.md for all learning types
|
|
3075
|
+
python3 << 'EXTRACT_SCRIPT'
|
|
3076
3076
|
import re
|
|
3077
3077
|
import json
|
|
3078
3078
|
import os
|
|
3079
|
+
import hashlib
|
|
3079
3080
|
from datetime import datetime, timezone
|
|
3080
3081
|
|
|
3081
3082
|
continuity_file = ".loki/CONTINUITY.md"
|
|
3082
3083
|
learnings_dir = os.path.expanduser("~/.loki/learnings")
|
|
3084
|
+
os.makedirs(learnings_dir, exist_ok=True)
|
|
3083
3085
|
|
|
3084
3086
|
if not os.path.exists(continuity_file):
|
|
3085
3087
|
exit(0)
|
|
@@ -3087,22 +3089,95 @@ if not os.path.exists(continuity_file):
|
|
|
3087
3089
|
with open(continuity_file, 'r') as f:
|
|
3088
3090
|
content = f.read()
|
|
3089
3091
|
|
|
3090
|
-
|
|
3092
|
+
project = os.path.basename(os.getcwd())
|
|
3093
|
+
timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
3094
|
+
|
|
3095
|
+
def get_existing_hashes(filepath):
|
|
3096
|
+
"""Get hashes of existing entries to avoid duplicates"""
|
|
3097
|
+
hashes = set()
|
|
3098
|
+
if os.path.exists(filepath):
|
|
3099
|
+
with open(filepath, 'r') as f:
|
|
3100
|
+
for line in f:
|
|
3101
|
+
try:
|
|
3102
|
+
entry = json.loads(line)
|
|
3103
|
+
if 'description' in entry:
|
|
3104
|
+
h = hashlib.md5(entry['description'].encode()).hexdigest()
|
|
3105
|
+
hashes.add(h)
|
|
3106
|
+
except:
|
|
3107
|
+
continue
|
|
3108
|
+
return hashes
|
|
3109
|
+
|
|
3110
|
+
def save_entries(filepath, entries, category):
|
|
3111
|
+
"""Save entries avoiding duplicates"""
|
|
3112
|
+
existing = get_existing_hashes(filepath)
|
|
3113
|
+
saved = 0
|
|
3114
|
+
with open(filepath, 'a') as f:
|
|
3115
|
+
for desc in entries:
|
|
3116
|
+
h = hashlib.md5(desc.encode()).hexdigest()
|
|
3117
|
+
if h not in existing:
|
|
3118
|
+
entry = {
|
|
3119
|
+
"timestamp": timestamp,
|
|
3120
|
+
"project": project,
|
|
3121
|
+
"category": category,
|
|
3122
|
+
"description": desc.strip()
|
|
3123
|
+
}
|
|
3124
|
+
f.write(json.dumps(entry) + "\n")
|
|
3125
|
+
existing.add(h)
|
|
3126
|
+
saved += 1
|
|
3127
|
+
return saved
|
|
3128
|
+
|
|
3129
|
+
def extract_bullets(text):
|
|
3130
|
+
"""Extract bullet points from text"""
|
|
3131
|
+
return [b.strip() for b in re.findall(r'[-*]\s+(.+)', text) if b.strip()]
|
|
3132
|
+
|
|
3133
|
+
# === Extract Mistakes & Learnings ===
|
|
3091
3134
|
mistakes_match = re.search(r'## Mistakes & Learnings\n(.*?)(?=\n## |\Z)', content, re.DOTALL)
|
|
3092
3135
|
if mistakes_match:
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3136
|
+
bullets = extract_bullets(mistakes_match.group(1))
|
|
3137
|
+
saved = save_entries(f"{learnings_dir}/mistakes.jsonl", bullets, "session")
|
|
3138
|
+
if saved > 0:
|
|
3139
|
+
print(f"Extracted {saved} new mistakes")
|
|
3140
|
+
|
|
3141
|
+
# === Extract Patterns (from various sections) ===
|
|
3142
|
+
pattern_sections = [
|
|
3143
|
+
r'## Patterns Used\n(.*?)(?=\n## |\Z)',
|
|
3144
|
+
r'## Solutions Applied\n(.*?)(?=\n## |\Z)',
|
|
3145
|
+
r'## Key Approaches\n(.*?)(?=\n## |\Z)',
|
|
3146
|
+
r'## What Worked\n(.*?)(?=\n## |\Z)',
|
|
3147
|
+
]
|
|
3148
|
+
patterns = []
|
|
3149
|
+
for pattern in pattern_sections:
|
|
3150
|
+
match = re.search(pattern, content, re.DOTALL)
|
|
3151
|
+
if match:
|
|
3152
|
+
patterns.extend(extract_bullets(match.group(1)))
|
|
3153
|
+
|
|
3154
|
+
# Also extract lines starting with "Pattern:" or "Solution:"
|
|
3155
|
+
patterns.extend(re.findall(r'(?:Pattern|Solution|Approach):\s*(.+)', content))
|
|
3156
|
+
|
|
3157
|
+
if patterns:
|
|
3158
|
+
saved = save_entries(f"{learnings_dir}/patterns.jsonl", patterns, "session")
|
|
3159
|
+
if saved > 0:
|
|
3160
|
+
print(f"Extracted {saved} new patterns")
|
|
3161
|
+
|
|
3162
|
+
# === Extract Successes (completed tasks) ===
|
|
3163
|
+
success_sections = [
|
|
3164
|
+
r'## Completed Tasks\n(.*?)(?=\n## |\Z)',
|
|
3165
|
+
r'## Achievements\n(.*?)(?=\n## |\Z)',
|
|
3166
|
+
r'## Done\n(.*?)(?=\n## |\Z)',
|
|
3167
|
+
]
|
|
3168
|
+
successes = []
|
|
3169
|
+
for pattern in success_sections:
|
|
3170
|
+
match = re.search(pattern, content, re.DOTALL)
|
|
3171
|
+
if match:
|
|
3172
|
+
successes.extend(extract_bullets(match.group(1)))
|
|
3173
|
+
|
|
3174
|
+
# Also extract [x] completed checkboxes
|
|
3175
|
+
successes.extend(re.findall(r'\[x\]\s+(.+)', content, re.IGNORECASE))
|
|
3176
|
+
|
|
3177
|
+
if successes:
|
|
3178
|
+
saved = save_entries(f"{learnings_dir}/successes.jsonl", successes, "session")
|
|
3179
|
+
if saved > 0:
|
|
3180
|
+
print(f"Extracted {saved} new successes")
|
|
3106
3181
|
|
|
3107
3182
|
print("Learning extraction complete")
|
|
3108
3183
|
EXTRACT_SCRIPT
|