@kentwynn/kgraph 0.1.21 → 0.1.23
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/README.md +56 -3
- package/dist/cli/commands/doctor.js +8 -0
- package/dist/cli/commands/history.d.ts +3 -1
- package/dist/cli/commands/history.js +22 -7
- package/dist/cli/commands/impact.d.ts +4 -0
- package/dist/cli/commands/impact.js +51 -0
- package/dist/cli/commands/init.js +11 -3
- package/dist/cli/commands/integrate.js +32 -5
- package/dist/cli/commands/session.d.ts +4 -0
- package/dist/cli/commands/session.js +112 -0
- package/dist/cli/commands/workflow.js +5 -0
- package/dist/cli/help.d.ts +6 -0
- package/dist/cli/help.js +17 -2
- package/dist/cli/index.js +4 -0
- package/dist/cognition/cognition-quality.d.ts +13 -1
- package/dist/cognition/cognition-quality.js +60 -0
- package/dist/config/config.js +6 -0
- package/dist/context/context-query.js +1 -0
- package/dist/context/impact.d.ts +20 -0
- package/dist/context/impact.js +72 -0
- package/dist/context/ranking.js +21 -6
- package/dist/integrations/adapters/claude-code.js +68 -10
- package/dist/integrations/adapters/cline.js +8 -6
- package/dist/integrations/adapters/codex.js +12 -10
- package/dist/integrations/adapters/copilot.js +35 -10
- package/dist/integrations/adapters/cursor.js +8 -6
- package/dist/integrations/adapters/gemini.js +8 -6
- package/dist/integrations/adapters/windsurf.js +8 -6
- package/dist/integrations/instruction-blocks.d.ts +4 -0
- package/dist/integrations/instruction-blocks.js +17 -0
- package/dist/integrations/integration-store.d.ts +4 -2
- package/dist/integrations/integration-store.js +19 -5
- package/dist/scanner/repo-scanner.js +2 -0
- package/dist/session/session-store.d.ts +15 -0
- package/dist/session/session-store.js +170 -0
- package/dist/session/token-estimator.d.ts +1 -0
- package/dist/session/token-estimator.js +17 -0
- package/dist/storage/kgraph-paths.js +4 -2
- package/dist/types/config.d.ts +3 -0
- package/dist/types/maps.d.ts +1 -0
- package/dist/types/session.d.ts +51 -0
- package/dist/types/session.js +1 -0
- package/dist/visualization/graph-builder.d.ts +1 -0
- package/dist/visualization/graph-builder.js +14 -1
- package/dist/visualization/html-template.js +19 -2
- package/package.json +1 -1
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
import type { IntegrationMode } from "../types/config.js";
|
|
2
|
+
export declare const KGRAPH_CONTEXT_POLICY_PLACEHOLDER = "{{KGRAPH_CONTEXT_POLICY}}";
|
|
1
3
|
export declare function upsertManagedBlock(content: string, integrationName: string, instructions: string): string;
|
|
2
4
|
export declare function removeManagedBlock(content: string, integrationName: string): string;
|
|
5
|
+
export declare function applyContextPolicy(content: string, mode: IntegrationMode): string;
|
|
6
|
+
export declare function renderContextPolicy(mode: IntegrationMode): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const MARKER_PREFIX = "<!--";
|
|
2
2
|
const MARKER_SUFFIX = "-->";
|
|
3
|
+
export const KGRAPH_CONTEXT_POLICY_PLACEHOLDER = "{{KGRAPH_CONTEXT_POLICY}}";
|
|
3
4
|
export function upsertManagedBlock(content, integrationName, instructions) {
|
|
4
5
|
const normalized = content.trimEnd();
|
|
5
6
|
const block = renderManagedBlock(integrationName, instructions);
|
|
@@ -25,3 +26,19 @@ function managedBlockPattern(integrationName) {
|
|
|
25
26
|
function escapeRegExp(value) {
|
|
26
27
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27
28
|
}
|
|
29
|
+
export function applyContextPolicy(content, mode) {
|
|
30
|
+
return content.replaceAll(KGRAPH_CONTEXT_POLICY_PLACEHOLDER, renderContextPolicy(mode));
|
|
31
|
+
}
|
|
32
|
+
export function renderContextPolicy(mode) {
|
|
33
|
+
switch (mode) {
|
|
34
|
+
case "always":
|
|
35
|
+
return 'Every chat in this repository must start by running `kgraph "<topic>"` before answering or exploring files. Infer the topic from the user\'s message; no exceptions, including simple or conversational requests.';
|
|
36
|
+
case "manual":
|
|
37
|
+
return 'Do not run KGraph automatically. Run `kgraph "<topic>"` only when the user explicitly asks for KGraph context or invokes the KGraph command.';
|
|
38
|
+
case "off":
|
|
39
|
+
return "KGraph is disabled for this integration.";
|
|
40
|
+
case "smart":
|
|
41
|
+
default:
|
|
42
|
+
return 'For repo-specific coding, debugging, architecture, refactor, review, or file-exploration requests, run `kgraph "<topic>"` before broad repository exploration. Infer the topic from the user\'s message. Skip KGraph for simple conversational requests that do not depend on repo knowledge.';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { IntegrationConfig, IntegrationName, KGraphWorkspace } from "../types/config.js";
|
|
1
|
+
import type { IntegrationConfig, IntegrationMode, IntegrationName, KGraphWorkspace } from "../types/config.js";
|
|
2
2
|
export interface IntegrationStatus {
|
|
3
3
|
name: IntegrationName;
|
|
4
4
|
enabled: boolean;
|
|
5
|
+
mode: IntegrationMode;
|
|
5
6
|
targetPath: string;
|
|
6
7
|
targetExists: boolean;
|
|
7
8
|
}
|
|
8
9
|
export declare function listIntegrations(workspace: KGraphWorkspace): Promise<IntegrationStatus[]>;
|
|
9
|
-
export declare function addIntegrations(workspace: KGraphWorkspace, names: IntegrationName[]): Promise<IntegrationConfig[]>;
|
|
10
|
+
export declare function addIntegrations(workspace: KGraphWorkspace, names: IntegrationName[], mode?: IntegrationMode): Promise<IntegrationConfig[]>;
|
|
11
|
+
export declare function setIntegrationMode(workspace: KGraphWorkspace, names: IntegrationName[], mode: IntegrationMode): Promise<IntegrationConfig[]>;
|
|
10
12
|
export declare function removeIntegrations(workspace: KGraphWorkspace, names: IntegrationName[]): Promise<IntegrationName[]>;
|
|
@@ -3,18 +3,19 @@ import path from "node:path";
|
|
|
3
3
|
import { loadConfig, saveConfig } from "../config/config.js";
|
|
4
4
|
import { pathExists } from "../storage/kgraph-paths.js";
|
|
5
5
|
import { getIntegrationAdapter } from "./integration-registry.js";
|
|
6
|
-
import { removeManagedBlock, upsertManagedBlock } from "./instruction-blocks.js";
|
|
6
|
+
import { applyContextPolicy, removeManagedBlock, upsertManagedBlock } from "./instruction-blocks.js";
|
|
7
7
|
export async function listIntegrations(workspace) {
|
|
8
8
|
const config = await loadConfig(workspace);
|
|
9
9
|
const statuses = await Promise.all(config.integrations.map(async (integration) => ({
|
|
10
10
|
name: integration.name,
|
|
11
11
|
enabled: integration.enabled,
|
|
12
|
+
mode: integration.mode,
|
|
12
13
|
targetPath: integration.targetPath,
|
|
13
14
|
targetExists: await pathExists(path.join(workspace.rootPath, integration.targetPath))
|
|
14
15
|
})));
|
|
15
16
|
return statuses.sort((left, right) => left.name.localeCompare(right.name));
|
|
16
17
|
}
|
|
17
|
-
export async function addIntegrations(workspace, names) {
|
|
18
|
+
export async function addIntegrations(workspace, names, mode = "always") {
|
|
18
19
|
const config = await loadConfig(workspace);
|
|
19
20
|
const byName = new Map(config.integrations.map((integration) => [integration.name, integration]));
|
|
20
21
|
const changed = [];
|
|
@@ -22,19 +23,32 @@ export async function addIntegrations(workspace, names) {
|
|
|
22
23
|
const adapter = getIntegrationAdapter(name);
|
|
23
24
|
const next = {
|
|
24
25
|
name: adapter.name,
|
|
25
|
-
enabled:
|
|
26
|
+
enabled: mode !== "off",
|
|
27
|
+
mode,
|
|
26
28
|
targetPath: adapter.targetPath
|
|
27
29
|
};
|
|
28
30
|
byName.set(adapter.name, next);
|
|
29
|
-
|
|
31
|
+
if (mode === "off") {
|
|
32
|
+
await removeIntegrationInstructions(workspace.rootPath, adapter.targetPath, adapter.name);
|
|
33
|
+
await removeIntegrationCommandFiles(workspace.rootPath, adapter.commandFiles ?? []);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
await writeIntegrationInstructions(workspace.rootPath, adapter.targetPath, adapter.name, applyContextPolicy(adapter.instructions, mode));
|
|
37
|
+
await writeIntegrationCommandFiles(workspace.rootPath, (adapter.commandFiles ?? []).map((file) => ({
|
|
38
|
+
...file,
|
|
39
|
+
content: applyContextPolicy(file.content, mode)
|
|
40
|
+
})));
|
|
41
|
+
}
|
|
30
42
|
await removeIntegrationCommandFiles(workspace.rootPath, adapter.obsoleteCommandFiles ?? []);
|
|
31
|
-
await writeIntegrationCommandFiles(workspace.rootPath, adapter.commandFiles ?? []);
|
|
32
43
|
changed.push(next);
|
|
33
44
|
}
|
|
34
45
|
config.integrations = [...byName.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
35
46
|
await saveConfig(workspace, config);
|
|
36
47
|
return changed;
|
|
37
48
|
}
|
|
49
|
+
export async function setIntegrationMode(workspace, names, mode) {
|
|
50
|
+
return addIntegrations(workspace, names, mode);
|
|
51
|
+
}
|
|
38
52
|
export async function removeIntegrations(workspace, names) {
|
|
39
53
|
const config = await loadConfig(workspace);
|
|
40
54
|
const removeNames = new Set(names);
|
|
@@ -10,6 +10,7 @@ import { extractJvmSymbols } from './jvm-symbol-extractor.js';
|
|
|
10
10
|
import { extractPythonSymbols } from './python-symbol-extractor.js';
|
|
11
11
|
import { extractRustSymbols } from './rust-symbol-extractor.js';
|
|
12
12
|
import { extractTsSymbols } from './ts-symbol-extractor.js';
|
|
13
|
+
import { estimateTokens } from '../session/token-estimator.js';
|
|
13
14
|
const C_EXTS = new Set(['.c', '.h', '.cpp', '.cc', '.cxx', '.hpp', '.hxx']);
|
|
14
15
|
const JVM_EXTS = new Set(['.java', '.kt', '.kts']);
|
|
15
16
|
function extractSymbols(text, repoPath) {
|
|
@@ -73,6 +74,7 @@ export async function scanRepository(rootPath, config, previous) {
|
|
|
73
74
|
sizeBytes: info.size,
|
|
74
75
|
modifiedAt: info.mtime.toISOString(),
|
|
75
76
|
contentHash,
|
|
77
|
+
tokenEstimate: estimateTokens(text, repoPath),
|
|
76
78
|
scanStatus: isPreciseLanguage(repoPath, config) ? 'mapped' : 'generic',
|
|
77
79
|
warnings: [],
|
|
78
80
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { KGraphWorkspace } from '../types/config.js';
|
|
2
|
+
import type { FileMap } from '../types/maps.js';
|
|
3
|
+
import type { SessionAgent, SessionCaptureSource, SessionEvent, SessionEventType, SessionLedgerEntry, SessionReport, SessionState } from '../types/session.js';
|
|
4
|
+
export declare function assertSessionAgent(value: string): SessionAgent;
|
|
5
|
+
export declare function readSessionState(workspace: KGraphWorkspace): Promise<SessionState>;
|
|
6
|
+
export declare function resetSession(workspace: KGraphWorkspace): Promise<void>;
|
|
7
|
+
export declare function readSessionLedger(workspace: KGraphWorkspace): Promise<SessionLedgerEntry[]>;
|
|
8
|
+
export declare function recordSessionEvent(workspace: KGraphWorkspace, input: {
|
|
9
|
+
agent: SessionAgent;
|
|
10
|
+
type: SessionEventType;
|
|
11
|
+
path?: string;
|
|
12
|
+
captureSource: SessionCaptureSource;
|
|
13
|
+
fileMap?: FileMap;
|
|
14
|
+
}): Promise<SessionEvent>;
|
|
15
|
+
export declare function buildSessionReport(workspace: KGraphWorkspace): Promise<SessionReport>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getIntegrationAdapter } from '../integrations/integration-registry.js';
|
|
4
|
+
import { pathExists } from '../storage/kgraph-paths.js';
|
|
5
|
+
import { KGraphError } from '../cli/errors.js';
|
|
6
|
+
import { estimateTokens } from './token-estimator.js';
|
|
7
|
+
const EMPTY_STATE = {
|
|
8
|
+
active: {},
|
|
9
|
+
events: [],
|
|
10
|
+
updatedAt: '',
|
|
11
|
+
};
|
|
12
|
+
export function assertSessionAgent(value) {
|
|
13
|
+
return getIntegrationAdapter(value).name;
|
|
14
|
+
}
|
|
15
|
+
export async function readSessionState(workspace) {
|
|
16
|
+
const filePath = currentPath(workspace);
|
|
17
|
+
if (!(await pathExists(filePath))) {
|
|
18
|
+
return { ...EMPTY_STATE, active: {}, events: [] };
|
|
19
|
+
}
|
|
20
|
+
return JSON.parse(await readFile(filePath, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
export async function resetSession(workspace) {
|
|
23
|
+
await rm(currentPath(workspace), { force: true });
|
|
24
|
+
}
|
|
25
|
+
export async function readSessionLedger(workspace) {
|
|
26
|
+
const filePath = ledgerPath(workspace);
|
|
27
|
+
if (!(await pathExists(filePath))) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
return JSON.parse(await readFile(filePath, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
export async function recordSessionEvent(workspace, input) {
|
|
33
|
+
const now = new Date().toISOString();
|
|
34
|
+
const state = await readSessionState(workspace);
|
|
35
|
+
const active = state.active[input.agent] ?? {
|
|
36
|
+
agent: input.agent,
|
|
37
|
+
sessionId: `${input.agent}-${now.replace(/[:.]/g, '-')}`,
|
|
38
|
+
startedAt: now,
|
|
39
|
+
lastEventAt: now,
|
|
40
|
+
};
|
|
41
|
+
const normalizedPath = input.path ? normalizeRepoPath(input.path) : undefined;
|
|
42
|
+
const tokenEstimate = normalizedPath && (input.type === 'read' || input.type === 'write')
|
|
43
|
+
? await estimatePathTokens(workspace, normalizedPath, input.fileMap)
|
|
44
|
+
: undefined;
|
|
45
|
+
const repeated = input.type === 'read' && normalizedPath
|
|
46
|
+
? state.events.some((event) => event.agent === input.agent &&
|
|
47
|
+
event.type === 'read' &&
|
|
48
|
+
event.path === normalizedPath)
|
|
49
|
+
: undefined;
|
|
50
|
+
const event = {
|
|
51
|
+
id: `${now}-${state.events.length + 1}`,
|
|
52
|
+
agent: input.agent,
|
|
53
|
+
type: input.type,
|
|
54
|
+
...(normalizedPath ? { path: normalizedPath } : {}),
|
|
55
|
+
...(tokenEstimate !== undefined ? { tokenEstimate } : {}),
|
|
56
|
+
...(repeated !== undefined ? { repeated } : {}),
|
|
57
|
+
captureSource: input.captureSource,
|
|
58
|
+
timestamp: now,
|
|
59
|
+
};
|
|
60
|
+
if (input.type === 'start') {
|
|
61
|
+
state.active[input.agent] = active;
|
|
62
|
+
}
|
|
63
|
+
else if (!state.active[input.agent]) {
|
|
64
|
+
state.active[input.agent] = active;
|
|
65
|
+
}
|
|
66
|
+
state.active[input.agent].lastEventAt = now;
|
|
67
|
+
state.events.push(event);
|
|
68
|
+
state.updatedAt = now;
|
|
69
|
+
if (input.type === 'end') {
|
|
70
|
+
await appendLedgerEntry(workspace, summarizeAgentSession(input.agent, state, now));
|
|
71
|
+
delete state.active[input.agent];
|
|
72
|
+
}
|
|
73
|
+
await writeSessionState(workspace, state);
|
|
74
|
+
return event;
|
|
75
|
+
}
|
|
76
|
+
export async function buildSessionReport(workspace) {
|
|
77
|
+
const [state, ledger] = await Promise.all([
|
|
78
|
+
readSessionState(workspace),
|
|
79
|
+
readSessionLedger(workspace),
|
|
80
|
+
]);
|
|
81
|
+
const readEvents = state.events.filter((event) => event.type === 'read');
|
|
82
|
+
const writeEvents = state.events.filter((event) => event.type === 'write');
|
|
83
|
+
const repeatedReads = readEvents.filter((event) => event.repeated);
|
|
84
|
+
return {
|
|
85
|
+
activeAgents: Object.values(state.active),
|
|
86
|
+
readCount: readEvents.length,
|
|
87
|
+
writeCount: writeEvents.length,
|
|
88
|
+
repeatedReadCount: repeatedReads.length,
|
|
89
|
+
estimatedReadTokens: sumTokens(readEvents),
|
|
90
|
+
estimatedRepeatedReadTokens: sumTokens(repeatedReads),
|
|
91
|
+
topRepeatedReads: topRepeatedReads(readEvents),
|
|
92
|
+
recentEvents: state.events.slice(-10),
|
|
93
|
+
ledger: ledger.slice(-10),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function estimatePathTokens(workspace, repoPath, fileMap) {
|
|
97
|
+
const mapped = fileMap?.files.find((file) => file.path === repoPath);
|
|
98
|
+
if (mapped?.tokenEstimate !== undefined) {
|
|
99
|
+
return mapped.tokenEstimate;
|
|
100
|
+
}
|
|
101
|
+
const fullPath = path.join(workspace.rootPath, repoPath);
|
|
102
|
+
try {
|
|
103
|
+
const info = await stat(fullPath);
|
|
104
|
+
if (!info.isFile()) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
const content = await readFile(fullPath, 'utf8');
|
|
108
|
+
return estimateTokens(content, repoPath);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function writeSessionState(workspace, state) {
|
|
115
|
+
await mkdir(workspace.sessionsPath, { recursive: true });
|
|
116
|
+
await writeFile(currentPath(workspace), `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
117
|
+
}
|
|
118
|
+
async function appendLedgerEntry(workspace, entry) {
|
|
119
|
+
const ledger = await readSessionLedger(workspace);
|
|
120
|
+
ledger.push(entry);
|
|
121
|
+
await mkdir(workspace.sessionsPath, { recursive: true });
|
|
122
|
+
await writeFile(ledgerPath(workspace), `${JSON.stringify(ledger, null, 2)}\n`, 'utf8');
|
|
123
|
+
}
|
|
124
|
+
function summarizeAgentSession(agent, state, endedAt) {
|
|
125
|
+
const active = state.active[agent];
|
|
126
|
+
if (!active) {
|
|
127
|
+
throw new KGraphError(`No active session for agent "${agent}".`);
|
|
128
|
+
}
|
|
129
|
+
const events = state.events.filter((event) => event.agent === agent && event.timestamp >= active.startedAt);
|
|
130
|
+
const reads = events.filter((event) => event.type === 'read');
|
|
131
|
+
const repeatedReads = reads.filter((event) => event.repeated);
|
|
132
|
+
return {
|
|
133
|
+
sessionId: active.sessionId,
|
|
134
|
+
agent,
|
|
135
|
+
startedAt: active.startedAt,
|
|
136
|
+
endedAt,
|
|
137
|
+
readCount: reads.length,
|
|
138
|
+
writeCount: events.filter((event) => event.type === 'write').length,
|
|
139
|
+
repeatedReadCount: repeatedReads.length,
|
|
140
|
+
estimatedReadTokens: sumTokens(reads),
|
|
141
|
+
estimatedRepeatedReadTokens: sumTokens(repeatedReads),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function topRepeatedReads(events) {
|
|
145
|
+
const byPath = new Map();
|
|
146
|
+
for (const event of events) {
|
|
147
|
+
if (!event.path)
|
|
148
|
+
continue;
|
|
149
|
+
const current = byPath.get(event.path) ?? { path: event.path, count: 0, estimatedTokens: 0 };
|
|
150
|
+
current.count += 1;
|
|
151
|
+
current.estimatedTokens += event.tokenEstimate ?? 0;
|
|
152
|
+
byPath.set(event.path, current);
|
|
153
|
+
}
|
|
154
|
+
return [...byPath.values()]
|
|
155
|
+
.filter((item) => item.count > 1)
|
|
156
|
+
.sort((left, right) => right.estimatedTokens - left.estimatedTokens)
|
|
157
|
+
.slice(0, 5);
|
|
158
|
+
}
|
|
159
|
+
function sumTokens(events) {
|
|
160
|
+
return events.reduce((total, event) => total + (event.tokenEstimate ?? 0), 0);
|
|
161
|
+
}
|
|
162
|
+
function normalizeRepoPath(value) {
|
|
163
|
+
return value.split(path.sep).join('/').replace(/^\.\//, '');
|
|
164
|
+
}
|
|
165
|
+
function currentPath(workspace) {
|
|
166
|
+
return path.join(workspace.sessionsPath, 'current.json');
|
|
167
|
+
}
|
|
168
|
+
function ledgerPath(workspace) {
|
|
169
|
+
return path.join(workspace.sessionsPath, 'ledger.json');
|
|
170
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function estimateTokens(content: string, filePath?: string): number;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function estimateTokens(content, filePath = '') {
|
|
2
|
+
if (content.length === 0) {
|
|
3
|
+
return 0;
|
|
4
|
+
}
|
|
5
|
+
const ratio = tokenRatio(filePath);
|
|
6
|
+
return Math.max(1, Math.ceil(content.length / ratio));
|
|
7
|
+
}
|
|
8
|
+
function tokenRatio(filePath) {
|
|
9
|
+
const lower = filePath.toLowerCase();
|
|
10
|
+
if (lower.endsWith('.md') || lower.endsWith('.txt'))
|
|
11
|
+
return 4.8;
|
|
12
|
+
if (lower.endsWith('.json') || lower.endsWith('.yaml') || lower.endsWith('.yml'))
|
|
13
|
+
return 3.8;
|
|
14
|
+
if (lower.endsWith('.html') || lower.endsWith('.css'))
|
|
15
|
+
return 3.6;
|
|
16
|
+
return 4.0;
|
|
17
|
+
}
|
|
@@ -7,7 +7,8 @@ const WORKSPACE_DIRS = [
|
|
|
7
7
|
"domains",
|
|
8
8
|
"inbox",
|
|
9
9
|
"interactions/processed",
|
|
10
|
-
"context"
|
|
10
|
+
"context",
|
|
11
|
+
"sessions"
|
|
11
12
|
];
|
|
12
13
|
export function resolveWorkspace(rootPath = process.cwd()) {
|
|
13
14
|
const kgraphPath = path.join(rootPath, ".kgraph");
|
|
@@ -20,7 +21,8 @@ export function resolveWorkspace(rootPath = process.cwd()) {
|
|
|
20
21
|
domainsPath: path.join(kgraphPath, "domains"),
|
|
21
22
|
inboxPath: path.join(kgraphPath, "inbox"),
|
|
22
23
|
processedInteractionsPath: path.join(kgraphPath, "interactions", "processed"),
|
|
23
|
-
contextPath: path.join(kgraphPath, "context")
|
|
24
|
+
contextPath: path.join(kgraphPath, "context"),
|
|
25
|
+
sessionsPath: path.join(kgraphPath, "sessions")
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
export async function pathExists(targetPath) {
|
package/dist/types/config.d.ts
CHANGED
|
@@ -13,9 +13,11 @@ export interface DomainHint {
|
|
|
13
13
|
tags?: string[];
|
|
14
14
|
}
|
|
15
15
|
export type IntegrationName = "claude-code" | "cline" | "codex" | "copilot" | "cursor" | "gemini" | "windsurf";
|
|
16
|
+
export type IntegrationMode = "smart" | "always" | "manual" | "off";
|
|
16
17
|
export interface IntegrationConfig {
|
|
17
18
|
name: IntegrationName;
|
|
18
19
|
enabled: boolean;
|
|
20
|
+
mode: IntegrationMode;
|
|
19
21
|
targetPath: string;
|
|
20
22
|
}
|
|
21
23
|
export interface KGraphWorkspace {
|
|
@@ -28,4 +30,5 @@ export interface KGraphWorkspace {
|
|
|
28
30
|
inboxPath: string;
|
|
29
31
|
processedInteractionsPath: string;
|
|
30
32
|
contextPath: string;
|
|
33
|
+
sessionsPath: string;
|
|
31
34
|
}
|
package/dist/types/maps.d.ts
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { IntegrationName } from './config.js';
|
|
2
|
+
export type SessionAgent = IntegrationName;
|
|
3
|
+
export type SessionEventType = 'start' | 'read' | 'write' | 'end';
|
|
4
|
+
export type SessionCaptureSource = 'automatic' | 'agent-reported' | 'manual';
|
|
5
|
+
export interface SessionEvent {
|
|
6
|
+
id: string;
|
|
7
|
+
agent: SessionAgent;
|
|
8
|
+
type: SessionEventType;
|
|
9
|
+
path?: string;
|
|
10
|
+
tokenEstimate?: number;
|
|
11
|
+
repeated?: boolean;
|
|
12
|
+
captureSource: SessionCaptureSource;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AgentSession {
|
|
16
|
+
agent: SessionAgent;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
startedAt: string;
|
|
19
|
+
lastEventAt: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SessionState {
|
|
22
|
+
active: Record<string, AgentSession>;
|
|
23
|
+
events: SessionEvent[];
|
|
24
|
+
updatedAt: string;
|
|
25
|
+
}
|
|
26
|
+
export interface SessionLedgerEntry {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
agent: SessionAgent;
|
|
29
|
+
startedAt: string;
|
|
30
|
+
endedAt: string;
|
|
31
|
+
readCount: number;
|
|
32
|
+
writeCount: number;
|
|
33
|
+
repeatedReadCount: number;
|
|
34
|
+
estimatedReadTokens: number;
|
|
35
|
+
estimatedRepeatedReadTokens: number;
|
|
36
|
+
}
|
|
37
|
+
export interface SessionReport {
|
|
38
|
+
activeAgents: AgentSession[];
|
|
39
|
+
readCount: number;
|
|
40
|
+
writeCount: number;
|
|
41
|
+
repeatedReadCount: number;
|
|
42
|
+
estimatedReadTokens: number;
|
|
43
|
+
estimatedRepeatedReadTokens: number;
|
|
44
|
+
topRepeatedReads: Array<{
|
|
45
|
+
path: string;
|
|
46
|
+
count: number;
|
|
47
|
+
estimatedTokens: number;
|
|
48
|
+
}>;
|
|
49
|
+
recentEvents: SessionEvent[];
|
|
50
|
+
ledger: SessionLedgerEntry[];
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -24,7 +24,9 @@ export function buildGraph(fileMap, symbolMap, dependencyMap, relationshipMap, c
|
|
|
24
24
|
const elements = [];
|
|
25
25
|
const edgeIds = new Set();
|
|
26
26
|
const nodeIds = new Set();
|
|
27
|
+
const tokenEstimate = fileMap.files.reduce((total, file) => total + (file.tokenEstimate ?? 0), 0);
|
|
27
28
|
for (const file of fileMap.files) {
|
|
29
|
+
const tokenBucket = getTokenBucket(file.tokenEstimate);
|
|
28
30
|
nodeIds.add(file.id);
|
|
29
31
|
elements.push({
|
|
30
32
|
data: {
|
|
@@ -35,9 +37,11 @@ export function buildGraph(fileMap, symbolMap, dependencyMap, relationshipMap, c
|
|
|
35
37
|
color: LANGUAGE_COLORS[file.language] ?? '#94a3b8',
|
|
36
38
|
type: 'file',
|
|
37
39
|
size: file.sizeBytes,
|
|
40
|
+
tokenEstimate: file.tokenEstimate ?? 0,
|
|
41
|
+
tokenBucket,
|
|
38
42
|
scanStatus: file.scanStatus,
|
|
39
43
|
},
|
|
40
|
-
classes: `file ${file.language}`,
|
|
44
|
+
classes: `file ${file.language} token-${tokenBucket}`,
|
|
41
45
|
});
|
|
42
46
|
}
|
|
43
47
|
for (const symbol of symbolMap.symbols) {
|
|
@@ -139,7 +143,16 @@ export function buildGraph(fileMap, symbolMap, dependencyMap, relationshipMap, c
|
|
|
139
143
|
fileCount: fileMap.files.length,
|
|
140
144
|
symbolCount: symbolMap.symbols.length,
|
|
141
145
|
cognitionCount: cognitionNotes.length,
|
|
146
|
+
tokenEstimate,
|
|
142
147
|
generatedAt: new Date().toISOString(),
|
|
143
148
|
},
|
|
144
149
|
};
|
|
145
150
|
}
|
|
151
|
+
function getTokenBucket(tokenEstimate) {
|
|
152
|
+
const tokens = tokenEstimate ?? 0;
|
|
153
|
+
if (tokens >= 1000)
|
|
154
|
+
return 'large';
|
|
155
|
+
if (tokens >= 200)
|
|
156
|
+
return 'medium';
|
|
157
|
+
return 'small';
|
|
158
|
+
}
|
|
@@ -51,7 +51,7 @@ select:hover,button:hover{background:#475569}
|
|
|
51
51
|
<body>
|
|
52
52
|
<div id="toolbar">
|
|
53
53
|
<span id="t-title">\u29e1 KGraph \u00b7 ${repoName}</span>
|
|
54
|
-
<span id="t-stats">${meta.fileCount} files · ${meta.symbolCount} symbols · ${meta.cognitionCount} notes</span>
|
|
54
|
+
<span id="t-stats">${meta.fileCount} files · ${meta.symbolCount} symbols · ${meta.cognitionCount} notes · ~${meta.tokenEstimate} tokens</span>
|
|
55
55
|
<div id="t-controls">
|
|
56
56
|
<label class="clabel"><input type="checkbox" id="tog-cog" checked> Cognition</label>
|
|
57
57
|
<select id="sel-layout" title="Graph layout algorithm">
|
|
@@ -81,6 +81,8 @@ select:hover,button:hover{background:#475569}
|
|
|
81
81
|
<span class="li"><span class="li-dot" style="background:#10b981"></span>Markdown</span>
|
|
82
82
|
<span class="li"><span class="li-dot" style="background:#8b5cf6"></span>YAML</span>
|
|
83
83
|
<span class="li"><span class="li-dot" style="background:#94a3b8"></span>Other</span>
|
|
84
|
+
<span class="li"><span class="li-dot" style="background:#475569"></span>200+ tok</span>
|
|
85
|
+
<span class="li"><span class="li-dot" style="background:#ef4444"></span>1000+ tok</span>
|
|
84
86
|
<span class="li-sep"></span>
|
|
85
87
|
<span class="li-head">Cognition</span>
|
|
86
88
|
<span class="li"><span class="li-dia" style="background:#10b981"></span>Current</span>
|
|
@@ -162,6 +164,20 @@ select:hover,button:hover{background:#475569}
|
|
|
162
164
|
'overlay-color': '#7dd3fc'
|
|
163
165
|
}
|
|
164
166
|
},
|
|
167
|
+
{
|
|
168
|
+
selector: 'node.token-medium',
|
|
169
|
+
style: {
|
|
170
|
+
'border-color': '#f59e0b',
|
|
171
|
+
'border-width': 2
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
selector: 'node.token-large',
|
|
176
|
+
style: {
|
|
177
|
+
'border-color': '#ef4444',
|
|
178
|
+
'border-width': 3
|
|
179
|
+
}
|
|
180
|
+
},
|
|
165
181
|
{
|
|
166
182
|
selector: 'edge.import',
|
|
167
183
|
style: {
|
|
@@ -217,7 +233,8 @@ select:hover,button:hover{background:#475569}
|
|
|
217
233
|
return '<div class="sb-badge" style="background:' + esc(d.color) + '22;color:' + esc(d.color) + ';border:1px solid ' + esc(d.color) + '44">' + esc(d.language) + '</div>' +
|
|
218
234
|
'<div class="sb-title">' + esc(d.path) + '</div>' +
|
|
219
235
|
'<div class="sb-sect"><div class="sb-lbl">Scan Status</div><div class="sb-val">' + esc(d.scanStatus) + '</div></div>' +
|
|
220
|
-
'<div class="sb-sect"><div class="sb-lbl">File Size</div><div class="sb-val">' + bytes(d.size) + '</div></div>'
|
|
236
|
+
'<div class="sb-sect"><div class="sb-lbl">File Size</div><div class="sb-val">' + bytes(d.size) + '</div></div>' +
|
|
237
|
+
'<div class="sb-sect"><div class="sb-lbl">Estimated Tokens</div><div class="sb-val">~' + esc(d.tokenEstimate || 0) + ' tokens</div></div>';
|
|
221
238
|
}
|
|
222
239
|
|
|
223
240
|
function renderCognitionPanel(d) {
|