Skip to content
Merged
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
21 changes: 21 additions & 0 deletions src/cli/commands/tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Command } from 'commander';
import { getAllTasksWithMetrics } from '../../core/taskService.js';
import { formatTasksOutput } from '../../lib/formatting.js';
import { TaskStatus } from '../../types/task.js';

export function registerTasks(program: Command): void {
program
.command('tasks')
.description('List tasks with metrics summary')
.option('--status <status>', 'Filter by status (active, paused, blocked, closed)')
.action(async (options) => {
try {
const status = options.status as TaskStatus | undefined;
const entries = getAllTasksWithMetrics(status);
console.log(formatTasksOutput(entries));
} catch (err) {
console.error('Error:', err instanceof Error ? err.message : String(err));
process.exit(1);
}
});
}
2 changes: 2 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { registerIngestArtifactUpdated } from './commands/ingestArtifactUpdated.
import { registerSkill } from './commands/skill.js';
import { registerLog } from './commands/log.js';
import { registerTopics } from './commands/topics.js';
import { registerTasks } from './commands/tasks.js';
import { registerShow } from './commands/show.js';

// Ensure ~/.worklog directory and DB exist on startup
Expand Down Expand Up @@ -47,6 +48,7 @@ registerIngestArtifactUpdated(program);
registerSkill(program);
registerLog(program);
registerTopics(program);
registerTasks(program);
registerShow(program);

program.parse();
17 changes: 17 additions & 0 deletions src/core/taskService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getDb } from '../db/client.js';
import { Task, TaskStatus } from '../types/task.js';
import { TaskMetrics } from '../types/metrics.js';
import { generateId } from '../lib/ids.js';
import { nowISO } from '../lib/time.js';

Expand Down Expand Up @@ -55,3 +56,19 @@ export function getActiveTasks(): Task[] {
"SELECT * FROM tasks WHERE status = 'active' ORDER BY importance DESC, updated_at DESC"
).all() as Task[];
}

export function getAllTasksWithMetrics(
status?: TaskStatus
): Array<{ task: Task; metrics: TaskMetrics | null }> {
const db = getDb();
const tasks = status
? (db.prepare('SELECT * FROM tasks WHERE status = ? ORDER BY importance DESC, updated_at DESC').all(status) as Task[])
: (db.prepare("SELECT * FROM tasks WHERE status != 'closed' ORDER BY importance DESC, updated_at DESC").all() as Task[]);

return tasks.map((task) => {
const metrics = (db.prepare(
'SELECT * FROM task_metrics WHERE task_id = ? ORDER BY calculated_at DESC LIMIT 1'
).get(task.id) as TaskMetrics) ?? null;
return { task, metrics };
});
}
36 changes: 36 additions & 0 deletions src/lib/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,42 @@ export function formatTopicsOutput(
return lines.join('\n');
}

const STATUS_LABELS: Record<string, string> = {
active: 'ACTIVE',
paused: 'PAUSED',
blocked: 'BLOCKED',
closed: 'CLOSED',
};

export function formatTasksOutput(
entries: Array<{ task: Task; metrics: import('../types/metrics.js').TaskMetrics | null }>
): string {
if (entries.length === 0) {
return 'No tasks found.';
}

const lines: string[] = [];
lines.push('='.repeat(72));
lines.push(`TASKS (${entries.length} total)`);
lines.push('='.repeat(72));
lines.push(
`${'TITLE'.padEnd(28)} ${'STATUS'.padEnd(7)} ${'IMP'.padStart(4)} ${'DRIFT'.padStart(5)} ${'LAST SEEN'.padEnd(16)}`
);
lines.push('-'.repeat(72));

for (const { task, metrics } of entries) {
const title = truncate(task.title, 28).padEnd(28);
const status = (STATUS_LABELS[task.status] ?? task.status).padEnd(7);
const imp = task.importance.toFixed(1).padStart(4);
const drift = metrics ? metrics.drift_score.toFixed(2).padStart(5) : ' N/A';
const lastSeen = metrics?.last_seen_at ? formatDate(metrics.last_seen_at) : 'never';
lines.push(`${title} ${status} ${imp} ${drift} ${lastSeen}`);
}

lines.push('='.repeat(72));
return lines.join('\n');
}

export function formatShowOutput(
topic: Topic,
events: Event[],
Expand Down
Loading