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
18 changes: 12 additions & 6 deletions src/cli/commands/pack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,25 @@ export function registerPackCommand(program: Command): void {
options.agent ??
(command.getOptionValue('agent') as string | undefined) ??
findCommandOption(command, 'agent');
const [config, maps] = await Promise.all([
loadConfig(workspace),
readMaps(workspace),
]);
const response = await queryContext(workspace, config, maps, task);
const pack = buildContextPack(response, budget, workspace.rootPath);
if (agent) {
const omittedTokens = pack.omitted.reduce(
(sum, item) => sum + item.tokenEstimate,
0,
);
await recordSessionEvent(workspace, {
agent: assertSessionAgent(agent),
type: 'context',
captureSource: 'automatic',
packUsedTokens: pack.usedTokens,
packOmittedTokens: omittedTokens,
});
}
const [config, maps] = await Promise.all([
loadConfig(workspace),
readMaps(workspace),
]);
const response = await queryContext(workspace, config, maps, task);
const pack = buildContextPack(response, budget, workspace.rootPath);
const pendingInboxFiles = (await listInboxNotes(workspace)).map(
(file) =>
path.relative(workspace.rootPath, file).split(path.sep).join('/'),
Expand Down
112 changes: 95 additions & 17 deletions src/cli/commands/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
recordSessionEvent,
resetSession,
} from '../../session/session-store.js';
import type { SessionCaptureSource } from '../../types/session.js';
import { assertWorkspace } from '../../storage/kgraph-paths.js';
import { readMaps } from '../../storage/map-store.js';
import type { SessionCaptureSource } from '../../types/session.js';
import { KGraphError, runCommand } from '../errors.js';
import { normalizeConfidence, normalizeKind } from './conclude.js';

Expand All @@ -37,14 +37,22 @@ export function registerSessionCommand(program: Command): void {
runCommand(async () => {
const workspace = await assertWorkspace(process.cwd());
const report = await buildSessionReport(workspace);
console.log(options.json ? JSON.stringify(report, null, 2) : renderSessionReport(report));
console.log(
options.json
? JSON.stringify(report, null, 2)
: renderSessionReport(report),
);
}),
);

session
.command('start')
.option('--agent <name>', 'KGraph integration agent name')
.option('--source <source>', 'automatic, agent-reported, or manual', 'manual')
.option(
'--source <source>',
'automatic, agent-reported, or manual',
'manual',
)
.action((options: SessionOptions, command: Command) =>
runCommand(async () => {
const workspace = await assertWorkspace(process.cwd());
Expand All @@ -60,7 +68,11 @@ export function registerSessionCommand(program: Command): void {
session
.command('read <path>')
.option('--agent <name>', 'KGraph integration agent name')
.option('--source <source>', 'automatic, agent-reported, or manual', 'manual')
.option(
'--source <source>',
'automatic, agent-reported, or manual',
'manual',
)
.action((filePath: string, options: SessionOptions, command: Command) =>
runCommand(async () => {
const workspace = await assertWorkspace(process.cwd());
Expand All @@ -81,7 +93,11 @@ export function registerSessionCommand(program: Command): void {
session
.command('write <path>')
.option('--agent <name>', 'KGraph integration agent name')
.option('--source <source>', 'automatic, agent-reported, or manual', 'manual')
.option(
'--source <source>',
'automatic, agent-reported, or manual',
'manual',
)
.action((filePath: string, options: SessionOptions, command: Command) =>
runCommand(async () => {
const workspace = await assertWorkspace(process.cwd());
Expand All @@ -93,17 +109,27 @@ export function registerSessionCommand(program: Command): void {
captureSource: normalizeSource(options.source),
fileMap: maps.fileMap,
});
console.log(`KGraph recorded write: ${event.path}${event.tokenEstimate !== undefined ? ` ~${event.tokenEstimate} tokens` : ''}.`);
console.log(
`KGraph recorded write: ${event.path}${event.tokenEstimate !== undefined ? ` ~${event.tokenEstimate} tokens` : ''}.`,
);
}),
);

session
.command('end')
.option('--agent <name>', 'KGraph integration agent name')
.option('--source <source>', 'automatic, agent-reported, or manual', 'manual')
.option(
'--source <source>',
'automatic, agent-reported, or manual',
'manual',
)
.option('--conclude', 'Store a durable typed summary for this session')
.option('--topic <topic>', 'Conclusion topic when using --conclude')
.option('--type <type>', 'finding, decision, gotcha, summary, or relationship', 'summary')
.option(
'--type <type>',
'finding, decision, gotcha, summary, or relationship',
'summary',
)
.option('--confidence <level>', 'high, medium, or low', 'medium')
.option('--note <text>', 'Concise durable conclusion text')
.action((options: SessionOptions, command: Command) =>
Expand Down Expand Up @@ -151,20 +177,62 @@ export function registerSessionCommand(program: Command): void {
);
}

export function renderSessionReport(report: Awaited<ReturnType<typeof buildSessionReport>>): string {
export function renderSessionReport(
report: Awaited<ReturnType<typeof buildSessionReport>>,
): string {
const lines = ['', 'KGraph Session', ''];
lines.push(`Active agents: ${report.activeAgents.length === 0 ? 'none' : report.activeAgents.map((agent) => agent.agent).join(', ')}`);
lines.push(
`Active agents: ${report.activeAgents.length === 0 ? 'none' : report.activeAgents.map((agent) => agent.agent).join(', ')}`,
);
lines.push(`Reads: ${report.readCount}`);
lines.push(`Writes: ${report.writeCount}`);
lines.push(`Repeated reads: ${report.repeatedReadCount}`);
lines.push(`Estimated read tokens: ${report.estimatedReadTokens}`);
lines.push(`Estimated repeated-read tokens: ${report.estimatedRepeatedReadTokens}`);
lines.push(
`Estimated repeated-read tokens: ${report.estimatedRepeatedReadTokens}`,
);
lines.push('');
lines.push('Pack Usage');
lines.push(` Pack calls: ${report.packCallCount}`);
lines.push(` Tokens used: ${report.totalPackUsedTokens}`);
lines.push(` Tokens filtered: ${report.totalPackOmittedTokens}`);
if (report.packCallCount > 0 && report.totalPackOmittedTokens > 0) {
const total = report.totalPackUsedTokens + report.totalPackOmittedTokens;
const pct = Math.round((report.totalPackOmittedTokens / total) * 100);
lines.push(
` Filter rate: ${pct}% of candidate tokens excluded from context`,
);
}
lines.push('', 'Top Repeated Reads');
lines.push(...formatList(report.topRepeatedReads.map((item) => `- ${item.path} read ${item.count} times (~${item.estimatedTokens} tokens)`)));
lines.push(
...formatList(
report.topRepeatedReads.map(
(item) =>
`- ${item.path} read ${item.count} times (~${item.estimatedTokens} tokens)`,
),
),
);
lines.push('', 'Recent Events');
lines.push(...formatList(report.recentEvents.map((event) => `- ${event.agent} ${event.type}${event.path ? ` ${event.path}` : ''} [${event.captureSource}]`)));
lines.push(
...formatList(
report.recentEvents.map((event) => {
const packInfo =
event.packUsedTokens !== undefined
? ` [used:${event.packUsedTokens} filtered:${event.packOmittedTokens ?? 0}]`
: '';
return `- ${event.agent} ${event.type}${event.path ? ` ${event.path}` : ''}${packInfo} [${event.captureSource}]`;
}),
),
);
lines.push('', 'Recent Ledger');
lines.push(...formatList(report.ledger.map((entry) => `- ${entry.agent} ${entry.readCount} reads, ${entry.writeCount} writes, ${entry.repeatedReadCount} repeated`)));
lines.push(
...formatList(
report.ledger.map(
(entry) =>
`- ${entry.agent} ${entry.readCount} reads, ${entry.writeCount} writes, ${entry.repeatedReadCount} repeated`,
),
),
);
lines.push('', 'Next');
lines.push(...sessionNextActions(report));
return lines.join('\n');
Expand Down Expand Up @@ -199,10 +267,16 @@ function findCommandOption(
}

function normalizeSource(value: string | undefined): SessionCaptureSource {
if (value === 'automatic' || value === 'agent-reported' || value === 'manual') {
if (
value === 'automatic' ||
value === 'agent-reported' ||
value === 'manual'
) {
return value;
}
throw new KGraphError('--source must be automatic, agent-reported, or manual.');
throw new KGraphError(
'--source must be automatic, agent-reported, or manual.',
);
}

function formatList(items: string[]): string[] {
Expand All @@ -212,7 +286,11 @@ function formatList(items: string[]): string[] {
function sessionNextActions(
report: Awaited<ReturnType<typeof buildSessionReport>>,
): string[] {
if (report.readCount === 0 && report.writeCount === 0) {
if (
report.packCallCount === 0 &&
report.readCount === 0 &&
report.writeCount === 0
) {
return [
'- Start tracking with `kgraph session start --agent <name>`.',
'- Record meaningful reads/writes with `kgraph session read <path> --agent <name>` and `kgraph session write <path> --agent <name>`.',
Expand Down
45 changes: 42 additions & 3 deletions src/cli/commands/visualize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { exec } from 'node:child_process';
import { createServer } from 'node:http';
import { loadConfig } from '../../config/config.js';
import { refreshKnowledgeAtomStatuses } from '../../knowledge/atom-store.js';
import { readSessionState } from '../../session/session-store.js';
import { assertWorkspace } from '../../storage/kgraph-paths.js';
import { mapsExist, readMaps } from '../../storage/map-store.js';
import { buildGraph } from '../../visualization/graph-builder.js';
import { renderHtml } from '../../visualization/html-template.js';
import {
renderHtml,
type SessionVizData,
} from '../../visualization/html-template.js';
import { KGraphError, runCommand } from '../errors.js';

export function registerVisualizeCommand(program: Command): void {
Expand All @@ -33,21 +37,56 @@ export function registerVisualizeCommand(program: Command): void {
);
}

const maps = await readMaps(workspace);
const [maps, sessionState] = await Promise.all([
readMaps(workspace),
readSessionState(workspace),
]);
const { atoms } = await refreshKnowledgeAtomStatuses(workspace, {
fileMap: maps.fileMap,
symbolMap: maps.symbolMap,
});

await loadConfig(workspace); // ensure workspace is valid
const contextEvents = sessionState.events.filter(
(e) => e.type === 'context',
);
const sessionVizData: SessionVizData | undefined =
sessionState.events.length > 0
? {
activeAgents: Object.values(sessionState.active).map(
(a) => a.agent,
),
packCallCount: contextEvents.length,
totalPackUsedTokens: contextEvents.reduce(
(sum, e) => sum + (e.packUsedTokens ?? 0),
0,
),
totalPackOmittedTokens: contextEvents.reduce(
(sum, e) => sum + (e.packOmittedTokens ?? 0),
0,
),
readCount: sessionState.events.filter((e) => e.type === 'read')
.length,
writeCount: sessionState.events.filter(
(e) => e.type === 'write',
).length,
contextEvents: contextEvents.map((e) => ({
agent: e.agent,
packUsedTokens: e.packUsedTokens ?? 0,
packOmittedTokens: e.packOmittedTokens ?? 0,
timestamp: e.timestamp,
captureSource: e.captureSource,
})),
}
: undefined;
const graphData = buildGraph(
maps.fileMap,
maps.symbolMap,
maps.dependencyMap,
maps.relationshipMap,
atoms,
);
const html = renderHtml(graphData, workspace.rootPath);
const html = renderHtml(graphData, workspace.rootPath, sessionVizData);

await serveGraph(html, port, options.open);
}),
Expand Down
Loading