- Shows offset, duration, role, meta for each event - --json for structured output (feeds report workflow) - --content for content preview
This commit is contained in:
@@ -103,7 +103,9 @@ async function reviewerFn(chain: WorkflowMessage[]) {
|
||||
console.log(` 🔍 reviewer reviewing "${title}"...`);
|
||||
const t = Date.now();
|
||||
const result = await reviewerRole(chain, 'live', null as any);
|
||||
console.log(` 🔍 reviewer verdict=${result.meta?.verdict} (${((Date.now() - t) / 1000).toFixed(1)}s)`);
|
||||
console.log(
|
||||
` 🔍 reviewer verdict=${result.meta?.verdict} (${((Date.now() - t) / 1000).toFixed(1)}s)`,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,85 @@ export function registerWorkflowCommand(program: Command): void {
|
||||
scopedStore.close();
|
||||
});
|
||||
|
||||
topic
|
||||
.command('timeline <key>')
|
||||
.description('Show workflow event timeline')
|
||||
.option('--json', 'Output as JSON')
|
||||
.option('--content', 'Include content preview')
|
||||
.action((key: string, opts: { json?: boolean; content?: boolean }) => {
|
||||
const config = loadConfig(resolveDir(program.opts().dir));
|
||||
migrateToScoped(config);
|
||||
const scopedStore = openScopedStore(
|
||||
config.store.scopesDir,
|
||||
config.store.objectsDir,
|
||||
);
|
||||
if (!scopedStore) {
|
||||
console.error('No store found.');
|
||||
process.exit(1);
|
||||
}
|
||||
const store = scopedStore.scope('workflows');
|
||||
const events = store
|
||||
.getAfter(0)
|
||||
.filter((e) => e.kind.startsWith('coding.') && e.key === key);
|
||||
|
||||
if (events.length === 0) {
|
||||
console.error(`No events found for key: ${key}`);
|
||||
scopedStore.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const t0 = events[0].occurredAt;
|
||||
const entries = events.map((e, i) => {
|
||||
const role = e.kind.replace('coding.', '');
|
||||
const prevTime = i > 0 ? events[i - 1].occurredAt : t0;
|
||||
const durationMs = e.occurredAt - prevTime;
|
||||
const meta = e.meta ? JSON.parse(e.meta) : null;
|
||||
let contentPreview: string | undefined;
|
||||
if (opts.content && e.hash) {
|
||||
try {
|
||||
const obj = store.getObject(e.hash);
|
||||
const text = typeof obj === 'string' ? obj : JSON.stringify(obj);
|
||||
contentPreview =
|
||||
text.slice(0, 200) + (text.length > 200 ? '...' : '');
|
||||
} catch {}
|
||||
}
|
||||
return {
|
||||
id: e.id,
|
||||
role,
|
||||
offsetMs: e.occurredAt - t0,
|
||||
durationMs: i === 0 ? 0 : durationMs,
|
||||
meta,
|
||||
...(contentPreview ? { contentPreview } : {}),
|
||||
};
|
||||
});
|
||||
|
||||
const totalMs = events[events.length - 1].occurredAt - t0;
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify({ key, totalMs, events: entries }, null, 2));
|
||||
} else {
|
||||
console.log(`\n📊 Workflow Timeline: ${key}`);
|
||||
console.log(` Total: ${(totalMs / 1000).toFixed(1)}s\n`);
|
||||
for (const entry of entries) {
|
||||
const offset = `+${(entry.offsetMs / 1000).toFixed(1)}s`;
|
||||
const dur =
|
||||
entry.durationMs > 0
|
||||
? ` (${(entry.durationMs / 1000).toFixed(1)}s)`
|
||||
: '';
|
||||
const metaStr = entry.meta ? ` ${JSON.stringify(entry.meta)}` : '';
|
||||
console.log(
|
||||
` [${offset.padStart(8)}] #${String(entry.id).padStart(2)} ${entry.role.padEnd(12)}${dur}${metaStr}`,
|
||||
);
|
||||
if (entry.contentPreview) {
|
||||
console.log(` ${entry.contentPreview.split('\n')[0]}`);
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
scopedStore.close();
|
||||
});
|
||||
|
||||
topic
|
||||
.command('list')
|
||||
.description('List coding topics')
|
||||
|
||||
Reference in New Issue
Block a user