Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,101 @@ program
.option('--auto', 'Automatically apply recommended changes')
.action(optimizeCommand);

program
.command('observations')
.description('View or manage the persistent observation log')
.argument('[path]', 'Path to the repository', '.')
.option('--purge', 'Drop all observations and start fresh')
.action(async (repoPath: string, options: { purge?: boolean }) => {
const path = await import('path');
const resolved = path.resolve(repoPath);

// Dynamically import the observation log from the UI package server
// module. This works because the CLI and UI share a workspace and
// the module has no Svelte/browser deps.
const { openObservationLog } = await import('../../ui/src/lib/server/observation-log.js');
const log = openObservationLog(resolved);

if (options.purge) {
log.purge();
console.log('Observations purged.');
return;
}

const s = log.stats();
console.log(`Observation log: ${log.filePath}`);
console.log(`Total events: ${s.totalEvents}`);
console.log(`File size: ${(s.fileSizeBytes / 1024).toFixed(1)} KB`);

if (Object.keys(s.byTool).length > 0) {
console.log('\nBy tool:');
for (const [tool, count] of Object.entries(s.byTool).sort((a, b) => b[1] - a[1])) {
console.log(` ${tool}: ${count}`);
}
}

if (Object.keys(s.bySession).length > 0) {
console.log(`\nSessions: ${Object.keys(s.bySession).length}`);
}

if (Object.keys(s.byTopFolder).length > 0) {
console.log('\nBy top folder:');
for (const [folder, count] of Object.entries(s.byTopFolder).sort((a, b) => b[1] - a[1]).slice(0, 10)) {
console.log(` ${folder}: ${count}`);
}
}
});

program
.command('learn')
.description('Compute learning scores from observation log')
.argument('[path]', 'Path to the repository', '.')
.option('--json', 'Output raw JSON instead of formatted text')
.action(async (repoPath: string, options: { json?: boolean }) => {
const path = await import('path');
const resolved = path.resolve(repoPath);

const { openObservationLog } = await import('../../ui/src/lib/server/observation-log.js');
const { computeLearningState, saveLearningState } = await import('../../ui/src/lib/server/learning.js');

const log = openObservationLog(resolved);
const observations = log.readAll();

if (observations.length === 0) {
console.log('No observations recorded yet. Use Klonode with the live watcher to accumulate data.');
return;
}

// Compute learning state from observations (emotion events from JSONL
// parsing is a separate, heavier step — for now we compute repetition only).
const state = computeLearningState(observations);
saveLearningState(resolved, state);

if (options.json) {
console.log(JSON.stringify(state, null, 2));
return;
}

console.log(`Learning state computed from ${state.observationCount} observations across ${state.sessionCount} session(s).\n`);

// Sort by confidence descending.
const sorted = Object.values(state.nodes).sort((a, b) => b.confidence - a.confidence);

if (sorted.length === 0) {
console.log('No folder-level data yet.');
return;
}

console.log('Top folders by confidence:\n');
for (const node of sorted.slice(0, 15)) {
const bar = '█'.repeat(Math.round(node.confidence * 20));
const urgencyStr = node.urgency > 0 ? ` urgency: ${node.urgency.toFixed(1)}` : '';
console.log(` ${node.path.padEnd(45)} ${bar} ${(node.confidence * 100).toFixed(0)}% (${node.signals.readCount}R ${node.signals.writeCount}W ${node.signals.sessionsCount}S)${urgencyStr}`);
}

console.log(`\nSaved to .klonode/learning.json`);
});

program
.command('update')
.description('Regenerate routing for changed directories')
Expand Down
267 changes: 0 additions & 267 deletions packages/core/src/agents/agent-registry.ts

This file was deleted.

Loading
Loading