@kentwynn/kgraph 0.1.22 → 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.
Files changed (38) hide show
  1. package/README.md +36 -0
  2. package/dist/cli/commands/doctor.js +4 -0
  3. package/dist/cli/commands/init.js +11 -3
  4. package/dist/cli/commands/integrate.js +32 -5
  5. package/dist/cli/commands/session.d.ts +4 -0
  6. package/dist/cli/commands/session.js +112 -0
  7. package/dist/cli/commands/workflow.js +5 -0
  8. package/dist/cli/help.d.ts +6 -0
  9. package/dist/cli/help.js +15 -1
  10. package/dist/cli/index.js +2 -0
  11. package/dist/cognition/cognition-quality.d.ts +4 -0
  12. package/dist/cognition/cognition-quality.js +14 -0
  13. package/dist/config/config.js +6 -0
  14. package/dist/integrations/adapters/claude-code.js +62 -10
  15. package/dist/integrations/adapters/cline.js +6 -5
  16. package/dist/integrations/adapters/codex.js +12 -11
  17. package/dist/integrations/adapters/copilot.js +22 -10
  18. package/dist/integrations/adapters/cursor.js +6 -5
  19. package/dist/integrations/adapters/gemini.js +6 -5
  20. package/dist/integrations/adapters/windsurf.js +6 -5
  21. package/dist/integrations/instruction-blocks.d.ts +4 -0
  22. package/dist/integrations/instruction-blocks.js +17 -0
  23. package/dist/integrations/integration-store.d.ts +4 -2
  24. package/dist/integrations/integration-store.js +19 -5
  25. package/dist/scanner/repo-scanner.js +2 -0
  26. package/dist/session/session-store.d.ts +15 -0
  27. package/dist/session/session-store.js +170 -0
  28. package/dist/session/token-estimator.d.ts +1 -0
  29. package/dist/session/token-estimator.js +17 -0
  30. package/dist/storage/kgraph-paths.js +4 -2
  31. package/dist/types/config.d.ts +3 -0
  32. package/dist/types/maps.d.ts +1 -0
  33. package/dist/types/session.d.ts +51 -0
  34. package/dist/types/session.js +1 -0
  35. package/dist/visualization/graph-builder.d.ts +1 -0
  36. package/dist/visualization/graph-builder.js +14 -1
  37. package/dist/visualization/html-template.js +19 -2
  38. package/package.json +1 -1
@@ -4,14 +4,14 @@ export const codexAdapter = {
4
4
  targetPath: 'AGENTS.md',
5
5
  instructions: `## KGraph Workflow
6
6
 
7
- Before exploring the repository, run \`kgraph "<topic>"\` to refresh repo intelligence and load focused context. The /kgraph skill handles the full automated workflow. Run \`kgraph doctor\` when setup or generated maps look wrong. Run \`kgraph scan\`, \`kgraph update\`, and \`kgraph context\` manually only when you need one specific step.
7
+ {{KGRAPH_CONTEXT_POLICY}} The /kgraph skill handles the full automated workflow. Run \`kgraph doctor\` when setup or generated maps look wrong. Run \`kgraph scan\`, \`kgraph update\`, and \`kgraph context\` manually only when you need one specific step.
8
8
  `,
9
9
  commandFiles: [
10
10
  {
11
11
  path: '.agents/skills/kgraph/SKILL.md',
12
12
  content: `---
13
13
  name: kgraph
14
- description: Use KGraph persistent repo intelligence before broad repository exploration. Use when asked about repo structure, debugging context, architecture decisions, or to avoid rediscovering what is already known.
14
+ description: Use KGraph persistent repo intelligence according to the configured integration mode. Use when asked about repo structure, debugging context, architecture decisions, or to avoid rediscovering what is already known.
15
15
  ---
16
16
 
17
17
  # KGraph Skill
@@ -19,17 +19,18 @@ description: Use KGraph persistent repo intelligence before broad repository exp
19
19
  Workflow:
20
20
 
21
21
  1. Infer the current topic from the user request.
22
- 2. Run \`kgraph "<topic>"\` before broad repo exploration. This refreshes maps, processes pending inbox notes, and returns focused context in one command.
22
+ 2. {{KGRAPH_CONTEXT_POLICY}}
23
23
  3. Use KGraph's returned files, symbols, relationships, and cognition as navigation hints.
24
24
  4. Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
25
- 5. Run \`kgraph impact "<file-or-symbol>"\` when the user asks what a change may affect. Run \`kgraph history "<topic>"\` when prior work or decisions matter.
26
- 6. At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
27
- 7. If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
28
- 8. Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
29
- 9. Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
30
- 10. Run \`kgraph repair --dry-run\` before cleanup when stale/noisy cognition needs fixing. Run \`kgraph repair\` only when the user asks to apply that cleanup.
31
- 11. Run \`kgraph visualize\` when the user wants to inspect the dependency graph opens an interactive graph at http://localhost:4242 with PNG export.
32
- 12. Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
25
+ 5. Track meaningful session activity with \`kgraph session start --agent codex\`, \`kgraph session read <path> --agent codex\`, \`kgraph session write <path> --agent codex\`, and \`kgraph session end --agent codex\`.
26
+ 6. Run \`kgraph impact "<file-or-symbol>"\` when the user asks what a change may affect. Run \`kgraph history "<topic>"\` when prior work or decisions matter.
27
+ 7. At the end of any session that changed repository files, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\` before finishing.
28
+ 8. Do not skip capture for UI text, button, link, route, styling, or small file edits. Skip capture only when no repository files changed.
29
+ 9. Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write the inbox note first, then run \`kgraph\` once at the end.
30
+ 10. After the final \`kgraph\` run, mention whether the inbox note was processed.
31
+ 11. Run \`kgraph repair --dry-run\` before cleanup when stale/noisy cognition needs fixing. Run \`kgraph repair\` only when the user asks to apply that cleanup.
32
+ 12. Run \`kgraph visualize\` when the user wants to inspect the dependency graph — opens an interactive graph at http://localhost:4242 with PNG export.
33
+ 13. Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
33
34
 
34
35
  The inbox note must use this structure:
35
36
  \`\`\`markdown
@@ -4,14 +4,15 @@ export const copilotAdapter = {
4
4
  targetPath: '.github/copilot-instructions.md',
5
5
  instructions: `## KGraph Workflow
6
6
 
7
- 1. **Before exploring the repository**, run \`kgraph "<topic>"\` to refresh maps, process pending inbox notes, and load focused repo intelligence. Use the returned files, symbols, relationships, and cognition before any broad exploration.
7
+ 1. {{KGRAPH_CONTEXT_POLICY}}
8
8
  2. Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
9
- 3. Run \`kgraph impact "<file-or-symbol>"\` when the user asks what a change may affect. Run \`kgraph history "<topic>"\` when prior work or decisions matter.
10
- 4. At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
11
- 5. If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
12
- 6. Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
13
- 7. Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
14
- 8. Run \`kgraph repair --dry-run\` before cleanup when stale/noisy cognition needs fixing. Run \`kgraph repair\` only when the user asks to apply that cleanup.
9
+ 3. Track meaningful session activity with \`kgraph session start --agent copilot\`, \`kgraph session read <path> --agent copilot\`, \`kgraph session write <path> --agent copilot\`, and \`kgraph session end --agent copilot\`.
10
+ 4. Run \`kgraph impact "<file-or-symbol>"\` when the user asks what a change may affect. Run \`kgraph history "<topic>"\` when prior work or decisions matter.
11
+ 5. At the end of any session that changed repository files, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\` before finishing.
12
+ 6. Do not skip capture for UI text, button, link, route, styling, or small file edits. Skip capture only when no repository files changed.
13
+ 7. Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write the inbox note first, then run \`kgraph\` once at the end.
14
+ 8. After the final \`kgraph\` run, mention whether the inbox note was processed.
15
+ 9. Run \`kgraph repair --dry-run\` before cleanup when stale/noisy cognition needs fixing. Run \`kgraph repair\` only when the user asks to apply that cleanup.
15
16
 
16
17
  The inbox note must use this structure:
17
18
  \`\`\`markdown
@@ -92,9 +93,9 @@ argument-hint: "Brief description of what was done"
92
93
 
93
94
  Capture this session into KGraph cognition.
94
95
 
95
- 1. For any completed code or repo-file change, check whether it has future value before finishing.
96
- 2. Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
97
- 3. Write one Markdown note to \`.kgraph/inbox/<slug>.md\` using the structure below. Use the user's message as context, but keep the note factual and concise.
96
+ 1. For any completed code or repo-file change, write one Markdown note to \`.kgraph/inbox/<slug>.md\` using the structure below.
97
+ 2. Do not skip capture for UI text, button, link, route, styling, or small file edits. Skip capture only when no repository files changed.
98
+ 3. Use the user's message as context, but keep the note factual and concise.
98
99
  4. Run \`kgraph\` once to process the note and refresh maps. Use \`kgraph update\` only when you intentionally want inbox processing without a scan.
99
100
 
100
101
  Note structure:
@@ -124,6 +125,17 @@ argument-hint: "File, symbol, or topic"
124
125
  ---
125
126
 
126
127
  Run \`kgraph impact "$ARGUMENTS"\` to show matched files/symbols, import users, callers, callees, related cognition, and risk hints.
128
+ `,
129
+ },
130
+ {
131
+ path: '.github/prompts/kgraph-session.prompt.md',
132
+ content: `---
133
+ description: Track KGraph session read/write activity and token estimates
134
+ agent: agent
135
+ argument-hint: "start, read <path>, write <path>, end, or status"
136
+ ---
137
+
138
+ Use \`kgraph session\` to inspect current session activity. Record meaningful events with \`kgraph session start --agent copilot\`, \`kgraph session read <path> --agent copilot\`, \`kgraph session write <path> --agent copilot\`, and \`kgraph session end --agent copilot\`.
127
139
  `,
128
140
  },
129
141
  {
@@ -9,13 +9,14 @@ alwaysApply: true
9
9
 
10
10
  ## KGraph Workflow
11
11
 
12
- - **Before exploring the repository**, run \`kgraph "<topic>"\` to refresh maps, process pending inbox notes, and load focused repo intelligence. Use the returned files, symbols, relationships, and cognition before any broad exploration.
12
+ - {{KGRAPH_CONTEXT_POLICY}}
13
13
  - Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
14
+ - Track meaningful session activity with \`kgraph session start --agent cursor\`, \`kgraph session read <path> --agent cursor\`, \`kgraph session write <path> --agent cursor\`, and \`kgraph session end --agent cursor\`.
14
15
  - Run \`kgraph impact "<file-or-symbol>"\` when the user asks what a change may affect. Run \`kgraph history "<topic>"\` when prior work or decisions matter.
15
- - At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
16
- - If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
17
- - Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
18
- - Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
16
+ - At the end of any session that changed repository files, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\` before finishing.
17
+ - Do not skip capture for UI text, button, link, route, styling, or small file edits. Skip capture only when no repository files changed.
18
+ - Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write the inbox note first, then run \`kgraph\` once at the end.
19
+ - After the final \`kgraph\` run, mention whether the inbox note was processed.
19
20
  - Run \`kgraph repair --dry-run\` before cleanup when stale/noisy cognition needs fixing. Run \`kgraph repair\` only when the user asks to apply that cleanup.
20
21
  - Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
21
22
  - Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
@@ -4,13 +4,14 @@ export const geminiAdapter = {
4
4
  targetPath: 'GEMINI.md',
5
5
  instructions: `## KGraph Workflow
6
6
 
7
- - **Before exploring the repository**, run \`kgraph "<topic>"\` to refresh maps, process pending inbox notes, and load focused repo intelligence. Use the returned files, symbols, relationships, and cognition before any broad exploration.
7
+ - {{KGRAPH_CONTEXT_POLICY}}
8
8
  - Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
9
+ - Track meaningful session activity with \`kgraph session start --agent gemini\`, \`kgraph session read <path> --agent gemini\`, \`kgraph session write <path> --agent gemini\`, and \`kgraph session end --agent gemini\`.
9
10
  - Run \`kgraph impact "<file-or-symbol>"\` when the user asks what a change may affect. Run \`kgraph history "<topic>"\` when prior work or decisions matter.
10
- - At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
11
- - If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
12
- - Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
13
- - Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
11
+ - At the end of any session that changed repository files, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\` before finishing.
12
+ - Do not skip capture for UI text, button, link, route, styling, or small file edits. Skip capture only when no repository files changed.
13
+ - Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write the inbox note first, then run \`kgraph\` once at the end.
14
+ - After the final \`kgraph\` run, mention whether the inbox note was processed.
14
15
  - Run \`kgraph repair --dry-run\` before cleanup when stale/noisy cognition needs fixing. Run \`kgraph repair\` only when the user asks to apply that cleanup.
15
16
  - Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
16
17
  - Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
@@ -4,13 +4,14 @@ export const windsurfAdapter = {
4
4
  targetPath: '.windsurf/rules/kgraph.md',
5
5
  instructions: `# KGraph Workflow
6
6
 
7
- - **Before exploring the repository**, run \`kgraph "<topic>"\` to refresh maps, process pending inbox notes, and load focused repo intelligence. Use the returned files, symbols, relationships, and cognition before any broad exploration.
7
+ - {{KGRAPH_CONTEXT_POLICY}}
8
8
  - Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
9
+ - Track meaningful session activity with \`kgraph session start --agent windsurf\`, \`kgraph session read <path> --agent windsurf\`, \`kgraph session write <path> --agent windsurf\`, and \`kgraph session end --agent windsurf\`.
9
10
  - Run \`kgraph impact "<file-or-symbol>"\` when the user asks what a change may affect. Run \`kgraph history "<topic>"\` when prior work or decisions matter.
10
- - At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
11
- - If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
12
- - Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
13
- - Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
11
+ - At the end of any session that changed repository files, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\` before finishing.
12
+ - Do not skip capture for UI text, button, link, route, styling, or small file edits. Skip capture only when no repository files changed.
13
+ - Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write the inbox note first, then run \`kgraph\` once at the end.
14
+ - After the final \`kgraph\` run, mention whether the inbox note was processed.
14
15
  - Run \`kgraph repair --dry-run\` before cleanup when stale/noisy cognition needs fixing. Run \`kgraph repair\` only when the user asks to apply that cleanup.
15
16
  - Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
16
17
  - Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
@@ -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: true,
26
+ enabled: mode !== "off",
27
+ mode,
26
28
  targetPath: adapter.targetPath
27
29
  };
28
30
  byName.set(adapter.name, next);
29
- await writeIntegrationInstructions(workspace.rootPath, adapter.targetPath, adapter.name, adapter.instructions);
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) {
@@ -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
  }
@@ -10,6 +10,7 @@ export interface RepositoryFile {
10
10
  sizeBytes: number;
11
11
  modifiedAt?: string;
12
12
  contentHash: string;
13
+ tokenEstimate?: number;
13
14
  scanStatus: ScanStatus;
14
15
  warnings: string[];
15
16
  }
@@ -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 {};
@@ -10,6 +10,7 @@ export interface GraphData {
10
10
  fileCount: number;
11
11
  symbolCount: number;
12
12
  cognitionCount: number;
13
+ tokenEstimate: number;
13
14
  generatedAt: string;
14
15
  };
15
16
  }