@kentwynn/kgraph 0.1.24 → 0.1.25

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.
@@ -1,2 +1,2 @@
1
- import type { Command } from "commander";
1
+ import type { Command } from 'commander';
2
2
  export declare function registerInitCommand(program: Command): void;
@@ -1,28 +1,43 @@
1
- import { writeDefaultConfig } from "../../config/config.js";
2
- import { normalizeIntegrationNames } from "../../integrations/integration-registry.js";
3
- import { addIntegrations } from "../../integrations/integration-store.js";
4
- import { ensureWorkspace } from "../../storage/kgraph-paths.js";
5
- import { KGraphError, runCommand } from "../errors.js";
1
+ import { loadConfig, writeDefaultConfig } from '../../config/config.js';
2
+ import { normalizeIntegrationNames } from '../../integrations/integration-registry.js';
3
+ import { addIntegrations } from '../../integrations/integration-store.js';
4
+ import { scanRepository } from '../../scanner/repo-scanner.js';
5
+ import { ensureWorkspace } from '../../storage/kgraph-paths.js';
6
+ import { readMaps, writeMaps } from '../../storage/map-store.js';
7
+ import { KGraphError, runCommand } from '../errors.js';
6
8
  export function registerInitCommand(program) {
7
9
  program
8
- .command("init")
9
- .description("Initialize a .kgraph workspace")
10
- .option("--integration <name>", "Configure an AI tool integration", collectOption, [])
11
- .option("--integrations <names>", "Configure comma-separated AI tool integrations")
12
- .option("--mode <mode>", "Integration mode: smart, always, manual, or off", "always")
10
+ .command('init')
11
+ .description('Initialize a .kgraph workspace')
12
+ .option('--integration <name>', 'Configure an AI tool integration', collectOption, [])
13
+ .option('--integrations <names>', 'Configure comma-separated AI tool integrations')
14
+ .option('--mode <mode>', 'Integration mode: always, smart, manual, or off', 'always')
13
15
  .action((options) => runCommand(async () => {
14
16
  const workspace = await ensureWorkspace(process.cwd());
15
17
  const wroteConfig = await writeDefaultConfig(workspace);
16
- console.log(wroteConfig ? "Initialized .kgraph workspace." : ".kgraph workspace already initialized.");
18
+ console.log(wroteConfig
19
+ ? 'Initialized .kgraph workspace.'
20
+ : '.kgraph workspace already initialized.');
17
21
  const names = normalizeIntegrationNames([
18
22
  ...(options.integration ?? []),
19
- ...(options.integrations ? [options.integrations] : [])
23
+ ...(options.integrations ? [options.integrations] : []),
20
24
  ]);
21
25
  if (names.length > 0) {
22
26
  const mode = normalizeIntegrationMode(options.mode);
23
27
  const changed = await addIntegrations(workspace, names, mode);
24
- console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(", ")}`);
28
+ console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(', ')}`);
25
29
  }
30
+ const config = await loadConfig(workspace);
31
+ const previousMaps = await readMaps(workspace);
32
+ const result = await scanRepository(workspace.rootPath, config, {
33
+ files: previousMaps.fileMap.files,
34
+ symbols: previousMaps.symbolMap.symbols,
35
+ dependencies: previousMaps.dependencyMap.dependencies,
36
+ relationships: previousMaps.relationshipMap.relationships,
37
+ warnings: [],
38
+ });
39
+ await writeMaps(workspace, result);
40
+ console.log(`Scanned ${result.files.length} files and ${result.symbols.length} symbols.`);
26
41
  }));
27
42
  }
28
43
  function collectOption(value, previous) {
@@ -30,8 +45,11 @@ function collectOption(value, previous) {
30
45
  return previous;
31
46
  }
32
47
  function normalizeIntegrationMode(value) {
33
- if (value === "smart" || value === "always" || value === "manual" || value === "off") {
48
+ if (value === 'smart' ||
49
+ value === 'always' ||
50
+ value === 'manual' ||
51
+ value === 'off') {
34
52
  return value;
35
53
  }
36
- throw new KGraphError("--mode must be smart, always, manual, or off.");
54
+ throw new KGraphError('--mode must be smart, always, manual, or off.');
37
55
  }
@@ -1,2 +1,2 @@
1
- import type { Command } from "commander";
1
+ import type { Command } from 'commander';
2
2
  export declare function registerIntegrateCommand(program: Command): void;
@@ -1,63 +1,75 @@
1
- import { addIntegrations, listIntegrations, removeIntegrations, setIntegrationMode } from "../../integrations/integration-store.js";
2
- import { normalizeIntegrationNames } from "../../integrations/integration-registry.js";
3
- import { assertWorkspace } from "../../storage/kgraph-paths.js";
4
- import { KGraphError, runCommand } from "../errors.js";
1
+ import { normalizeIntegrationNames } from '../../integrations/integration-registry.js';
2
+ import { addIntegrations, listIntegrations, removeIntegrations, setIntegrationMode, } from '../../integrations/integration-store.js';
3
+ import { assertWorkspace } from '../../storage/kgraph-paths.js';
4
+ import { KGraphError, runCommand } from '../errors.js';
5
5
  export function registerIntegrateCommand(program) {
6
- const integrate = program.command("integrate").description("Manage AI tool integrations");
7
- integrate.command("list").description("List configured integrations").action(() => runCommand(async () => {
6
+ const integrate = program
7
+ .command('integrate')
8
+ .description('Manage AI tool integrations');
9
+ integrate
10
+ .command('list')
11
+ .description('List configured integrations')
12
+ .action(() => runCommand(async () => {
8
13
  const workspace = await assertWorkspace(process.cwd());
9
14
  const integrations = await listIntegrations(workspace);
10
15
  if (integrations.length === 0) {
11
- console.log("No integrations configured.");
16
+ console.log('No integrations configured.');
12
17
  return;
13
18
  }
14
19
  for (const integration of integrations) {
15
- console.log(`${integration.name} ${integration.enabled ? "enabled" : "disabled"} ${integration.mode} ${integration.targetPath} ${integration.targetExists ? "present" : "missing"}`);
20
+ console.log(`${integration.name} ${integration.enabled ? 'enabled' : 'disabled'} ${integration.mode} ${integration.targetPath} ${integration.targetExists ? 'present' : 'missing'}`);
16
21
  }
17
22
  }));
18
23
  integrate
19
- .command("add")
20
- .description("Add AI tool integrations")
21
- .argument("<names...>")
22
- .option("--mode <mode>", "smart, always, manual, or off", "always")
24
+ .command('add')
25
+ .description('Add AI tool integrations')
26
+ .argument('<names...>')
27
+ .option('--mode <mode>', 'always, smart, manual, or off', 'always')
23
28
  .action((names, options) => runCommand(async () => {
24
29
  const workspace = await assertWorkspace(process.cwd());
25
30
  const normalized = normalizeIntegrationNames(names);
26
31
  if (normalized.length === 0) {
27
- throw new KGraphError("Provide at least one integration name.");
32
+ throw new KGraphError('Provide at least one integration name.');
28
33
  }
29
34
  const mode = normalizeIntegrationMode(options.mode);
30
35
  const changed = await addIntegrations(workspace, normalized, mode);
31
- console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(", ")}`);
36
+ console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(', ')}`);
32
37
  }));
33
38
  integrate
34
- .command("set")
35
- .description("Set AI tool integration mode")
36
- .argument("<names...>")
37
- .requiredOption("--mode <mode>", "smart, always, manual, or off")
39
+ .command('set')
40
+ .description('Set AI tool integration mode')
41
+ .argument('<names...>')
42
+ .requiredOption('--mode <mode>', 'smart, always, manual, or off')
38
43
  .action((names, options) => runCommand(async () => {
39
44
  const workspace = await assertWorkspace(process.cwd());
40
45
  const normalized = normalizeIntegrationNames(names);
41
46
  if (normalized.length === 0) {
42
- throw new KGraphError("Provide at least one integration name.");
47
+ throw new KGraphError('Provide at least one integration name.');
43
48
  }
44
49
  const mode = normalizeIntegrationMode(options.mode);
45
50
  const changed = await setIntegrationMode(workspace, normalized, mode);
46
- console.log(`Updated integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(", ")}`);
51
+ console.log(`Updated integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(', ')}`);
47
52
  }));
48
- integrate.command("remove").description("Remove AI tool integrations").argument("<names...>").action((names) => runCommand(async () => {
53
+ integrate
54
+ .command('remove')
55
+ .description('Remove AI tool integrations')
56
+ .argument('<names...>')
57
+ .action((names) => runCommand(async () => {
49
58
  const workspace = await assertWorkspace(process.cwd());
50
59
  const normalized = normalizeIntegrationNames(names);
51
60
  if (normalized.length === 0) {
52
- throw new KGraphError("Provide at least one integration name.");
61
+ throw new KGraphError('Provide at least one integration name.');
53
62
  }
54
63
  const removed = await removeIntegrations(workspace, normalized);
55
- console.log(`Removed integrations: ${removed.join(", ")}`);
64
+ console.log(`Removed integrations: ${removed.join(', ')}`);
56
65
  }));
57
66
  }
58
67
  function normalizeIntegrationMode(value) {
59
- if (value === "smart" || value === "always" || value === "manual" || value === "off") {
68
+ if (value === 'smart' ||
69
+ value === 'always' ||
70
+ value === 'manual' ||
71
+ value === 'off') {
60
72
  return value;
61
73
  }
62
- throw new KGraphError("--mode must be smart, always, manual, or off.");
74
+ throw new KGraphError('--mode must be smart, always, manual, or off.');
63
75
  }
@@ -1,8 +1,9 @@
1
1
  import YAML from 'yaml';
2
2
  const PATH_REF = /(?:^|\s|`?)([\w./-]+\.(?:ts|tsx|js|jsx|json|md|yaml|yml))(?:\s|$|[),.;`])/g;
3
3
  export function parseMarkdownNote(markdown) {
4
+ const normalized = markdown.replace(/\r\n/g, '\n');
4
5
  const warnings = [];
5
- const { frontmatter, body } = splitFrontmatter(markdown, warnings);
6
+ const { frontmatter, body } = splitFrontmatter(normalized, warnings);
6
7
  const sections = parseSections(body);
7
8
  const frontmatterTitle = typeof frontmatter.title === 'string' ? frontmatter.title : undefined;
8
9
  const title = extractTitle(body) ?? frontmatterTitle ?? 'Untitled Cognition Note';
@@ -1,4 +1,4 @@
1
- import type { IntegrationMode } from "../types/config.js";
1
+ import type { IntegrationMode } from '../types/config.js';
2
2
  export declare const KGRAPH_CONTEXT_POLICY_PLACEHOLDER = "{{KGRAPH_CONTEXT_POLICY}}";
3
3
  export declare const KGRAPH_CAPTURE_POLICY_PLACEHOLDER = "{{KGRAPH_CAPTURE_POLICY}}";
4
4
  export declare function upsertManagedBlock(content: string, integrationName: string, instructions: string): string;
@@ -1,7 +1,7 @@
1
- const MARKER_PREFIX = "<!--";
2
- const MARKER_SUFFIX = "-->";
3
- export const KGRAPH_CONTEXT_POLICY_PLACEHOLDER = "{{KGRAPH_CONTEXT_POLICY}}";
4
- export const KGRAPH_CAPTURE_POLICY_PLACEHOLDER = "{{KGRAPH_CAPTURE_POLICY}}";
1
+ const MARKER_PREFIX = '<!--';
2
+ const MARKER_SUFFIX = '-->';
3
+ export const KGRAPH_CONTEXT_POLICY_PLACEHOLDER = '{{KGRAPH_CONTEXT_POLICY}}';
4
+ export const KGRAPH_CAPTURE_POLICY_PLACEHOLDER = '{{KGRAPH_CAPTURE_POLICY}}';
5
5
  export function upsertManagedBlock(content, integrationName, instructions) {
6
6
  const normalized = content.trimEnd();
7
7
  const block = renderManagedBlock(integrationName, instructions);
@@ -9,23 +9,26 @@ export function upsertManagedBlock(content, integrationName, instructions) {
9
9
  if (pattern.test(content)) {
10
10
  return content.replace(pattern, block);
11
11
  }
12
- return `${normalized}${normalized ? "\n\n" : ""}${block}\n`;
12
+ return normalized ? `${block}\n\n${normalized}\n` : `${block}\n`;
13
13
  }
14
14
  export function removeManagedBlock(content, integrationName) {
15
- return content.replace(managedBlockPattern(integrationName), "").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
15
+ return (content
16
+ .replace(managedBlockPattern(integrationName), '')
17
+ .replace(/\n{3,}/g, '\n\n')
18
+ .trim() + '\n');
16
19
  }
17
20
  function renderManagedBlock(integrationName, instructions) {
18
21
  return [
19
22
  `${MARKER_PREFIX} BEGIN KGRAPH ${integrationName} ${MARKER_SUFFIX}`,
20
23
  instructions.trim(),
21
- `${MARKER_PREFIX} END KGRAPH ${integrationName} ${MARKER_SUFFIX}`
22
- ].join("\n");
24
+ `${MARKER_PREFIX} END KGRAPH ${integrationName} ${MARKER_SUFFIX}`,
25
+ ].join('\n');
23
26
  }
24
27
  function managedBlockPattern(integrationName) {
25
- return new RegExp(`${escapeRegExp(MARKER_PREFIX)} BEGIN KGRAPH ${escapeRegExp(integrationName)} ${escapeRegExp(MARKER_SUFFIX)}[\\s\\S]*?${escapeRegExp(MARKER_PREFIX)} END KGRAPH ${escapeRegExp(integrationName)} ${escapeRegExp(MARKER_SUFFIX)}\\n?`, "m");
28
+ return new RegExp(`${escapeRegExp(MARKER_PREFIX)} BEGIN KGRAPH ${escapeRegExp(integrationName)} ${escapeRegExp(MARKER_SUFFIX)}[\\s\\S]*?${escapeRegExp(MARKER_PREFIX)} END KGRAPH ${escapeRegExp(integrationName)} ${escapeRegExp(MARKER_SUFFIX)}\\n?`, 'm');
26
29
  }
27
30
  function escapeRegExp(value) {
28
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
29
32
  }
30
33
  export function applyContextPolicy(content, mode) {
31
34
  return content
@@ -34,13 +37,13 @@ export function applyContextPolicy(content, mode) {
34
37
  }
35
38
  export function renderContextPolicy(mode) {
36
39
  switch (mode) {
37
- case "always":
40
+ case 'always':
38
41
  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.';
39
- case "manual":
42
+ case 'manual':
40
43
  return 'Do not run KGraph automatically. Run `kgraph "<topic>"` only when the user explicitly asks for KGraph context or invokes the KGraph command.';
41
- case "off":
42
- return "KGraph is disabled for this integration.";
43
- case "smart":
44
+ case 'off':
45
+ return 'KGraph is disabled for this integration.';
46
+ case 'smart':
44
47
  default:
45
48
  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.';
46
49
  }
@@ -1,4 +1,4 @@
1
- import type { IntegrationName } from "../types/config.js";
1
+ import type { IntegrationName } from '../types/config.js';
2
2
  export interface IntegrationAdapter {
3
3
  name: IntegrationName;
4
4
  label: string;
@@ -1,10 +1,10 @@
1
- import { claudeCodeAdapter } from "./adapters/claude-code.js";
2
- import { clineAdapter } from "./adapters/cline.js";
3
- import { codexAdapter } from "./adapters/codex.js";
4
- import { copilotAdapter } from "./adapters/copilot.js";
5
- import { cursorAdapter } from "./adapters/cursor.js";
6
- import { geminiAdapter } from "./adapters/gemini.js";
7
- import { windsurfAdapter } from "./adapters/windsurf.js";
1
+ import { claudeCodeAdapter } from './adapters/claude-code.js';
2
+ import { clineAdapter } from './adapters/cline.js';
3
+ import { codexAdapter } from './adapters/codex.js';
4
+ import { copilotAdapter } from './adapters/copilot.js';
5
+ import { cursorAdapter } from './adapters/cursor.js';
6
+ import { geminiAdapter } from './adapters/gemini.js';
7
+ import { windsurfAdapter } from './adapters/windsurf.js';
8
8
  const ADAPTERS = [
9
9
  claudeCodeAdapter,
10
10
  clineAdapter,
@@ -12,7 +12,7 @@ const ADAPTERS = [
12
12
  copilotAdapter,
13
13
  cursorAdapter,
14
14
  geminiAdapter,
15
- windsurfAdapter
15
+ windsurfAdapter,
16
16
  ].sort((left, right) => left.name.localeCompare(right.name));
17
17
  export function listIntegrationAdapters() {
18
18
  return ADAPTERS;
@@ -20,7 +20,7 @@ export function listIntegrationAdapters() {
20
20
  export function getIntegrationAdapter(name) {
21
21
  const adapter = ADAPTERS.find((item) => item.name === name);
22
22
  if (!adapter) {
23
- throw new Error(`Unsupported integration "${name}". Supported integrations: ${ADAPTERS.map((item) => item.name).join(", ")}`);
23
+ throw new Error(`Unsupported integration "${name}". Supported integrations: ${ADAPTERS.map((item) => item.name).join(', ')}`);
24
24
  }
25
25
  return adapter;
26
26
  }
@@ -31,7 +31,7 @@ export function normalizeIntegrationNames(values) {
31
31
  const names = [];
32
32
  const seen = new Set();
33
33
  for (const value of values) {
34
- for (const raw of value.split(",")) {
34
+ for (const raw of value.split(/[\s,]+/)) {
35
35
  const name = raw.trim();
36
36
  if (!name || seen.has(name)) {
37
37
  continue;
@@ -1,4 +1,4 @@
1
- import type { IntegrationConfig, IntegrationMode, 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;
@@ -1,9 +1,9 @@
1
- import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import { loadConfig, saveConfig } from "../config/config.js";
4
- import { pathExists } from "../storage/kgraph-paths.js";
5
- import { getIntegrationAdapter } from "./integration-registry.js";
6
- import { applyContextPolicy, removeManagedBlock, upsertManagedBlock } from "./instruction-blocks.js";
1
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { loadConfig, saveConfig } from '../config/config.js';
4
+ import { pathExists } from '../storage/kgraph-paths.js';
5
+ import { applyContextPolicy, removeManagedBlock, upsertManagedBlock, } from './instruction-blocks.js';
6
+ import { getIntegrationAdapter } from './integration-registry.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) => ({
@@ -11,11 +11,11 @@ export async function listIntegrations(workspace) {
11
11
  enabled: integration.enabled,
12
12
  mode: integration.mode,
13
13
  targetPath: integration.targetPath,
14
- targetExists: await pathExists(path.join(workspace.rootPath, integration.targetPath))
14
+ targetExists: await pathExists(path.join(workspace.rootPath, integration.targetPath)),
15
15
  })));
16
16
  return statuses.sort((left, right) => left.name.localeCompare(right.name));
17
17
  }
18
- export async function addIntegrations(workspace, names, mode = "always") {
18
+ export async function addIntegrations(workspace, names, mode = 'always') {
19
19
  const config = await loadConfig(workspace);
20
20
  const byName = new Map(config.integrations.map((integration) => [integration.name, integration]));
21
21
  const changed = [];
@@ -23,12 +23,12 @@ export async function addIntegrations(workspace, names, mode = "always") {
23
23
  const adapter = getIntegrationAdapter(name);
24
24
  const next = {
25
25
  name: adapter.name,
26
- enabled: mode !== "off",
26
+ enabled: mode !== 'off',
27
27
  mode,
28
- targetPath: adapter.targetPath
28
+ targetPath: adapter.targetPath,
29
29
  };
30
30
  byName.set(adapter.name, next);
31
- if (mode === "off") {
31
+ if (mode === 'off') {
32
32
  await removeIntegrationInstructions(workspace.rootPath, adapter.targetPath, adapter.name);
33
33
  await removeIntegrationCommandFiles(workspace.rootPath, adapter.commandFiles ?? []);
34
34
  }
@@ -36,7 +36,7 @@ export async function addIntegrations(workspace, names, mode = "always") {
36
36
  await writeIntegrationInstructions(workspace.rootPath, adapter.targetPath, adapter.name, applyContextPolicy(adapter.instructions, mode));
37
37
  await writeIntegrationCommandFiles(workspace.rootPath, (adapter.commandFiles ?? []).map((file) => ({
38
38
  ...file,
39
- content: applyContextPolicy(file.content, mode)
39
+ content: applyContextPolicy(file.content, mode),
40
40
  })));
41
41
  }
42
42
  await removeIntegrationCommandFiles(workspace.rootPath, adapter.obsoleteCommandFiles ?? []);
@@ -66,34 +66,36 @@ export async function removeIntegrations(workspace, names) {
66
66
  }
67
67
  async function writeIntegrationInstructions(rootPath, targetPath, integrationName, instructions) {
68
68
  const fullPath = path.join(rootPath, targetPath);
69
- const existing = (await pathExists(fullPath)) ? await readFile(fullPath, "utf8") : "";
69
+ const existing = (await pathExists(fullPath))
70
+ ? await readFile(fullPath, 'utf8')
71
+ : '';
70
72
  const next = upsertManagedBlock(existing, integrationName, instructions);
71
73
  await mkdir(path.dirname(fullPath), { recursive: true });
72
- await writeFile(fullPath, next, "utf8");
74
+ await writeFile(fullPath, next, 'utf8');
73
75
  }
74
76
  async function removeIntegrationInstructions(rootPath, targetPath, integrationName) {
75
77
  const fullPath = path.join(rootPath, targetPath);
76
78
  if (!(await pathExists(fullPath))) {
77
79
  return;
78
80
  }
79
- const existing = await readFile(fullPath, "utf8");
81
+ const existing = await readFile(fullPath, 'utf8');
80
82
  const next = removeManagedBlock(existing, integrationName);
81
83
  if (next.trim().length === 0) {
82
84
  await rm(fullPath, { force: true });
83
85
  return;
84
86
  }
85
- await writeFile(fullPath, next, "utf8");
87
+ await writeFile(fullPath, next, 'utf8');
86
88
  }
87
89
  async function writeIntegrationCommandFiles(rootPath, files) {
88
90
  for (const file of files) {
89
91
  const fullPath = path.join(rootPath, file.path);
90
92
  await mkdir(path.dirname(fullPath), { recursive: true });
91
- await writeFile(fullPath, file.content.trimEnd() + "\n", "utf8");
93
+ await writeFile(fullPath, file.content.trimEnd() + '\n', 'utf8');
92
94
  }
93
95
  }
94
96
  async function removeIntegrationCommandFiles(rootPath, files) {
95
97
  for (const file of files) {
96
- const filePath = typeof file === "string" ? file : file.path;
98
+ const filePath = typeof file === 'string' ? file : file.path;
97
99
  await rm(path.join(rootPath, filePath), { force: true, recursive: true });
98
100
  }
99
101
  }
@@ -53,6 +53,7 @@ select:hover,button:hover{background:#475569}
53
53
  <span id="t-title">\u29e1 KGraph \u00b7 ${repoName}</span>
54
54
  <span id="t-stats">${meta.fileCount} files &middot; ${meta.symbolCount} symbols &middot; ${meta.cognitionCount} notes &middot; ~${meta.tokenEstimate} tokens</span>
55
55
  <div id="t-controls">
56
+ <label class="clabel"><input type="checkbox" id="tog-lbl" checked> Labels</label>
56
57
  <label class="clabel"><input type="checkbox" id="tog-cog" checked> Cognition</label>
57
58
  <select id="sel-layout" title="Graph layout algorithm">
58
59
  <option value="dagre">Hierarchical</option>
@@ -108,6 +109,27 @@ select:hover,button:hover{background:#475569}
108
109
  cytoscape.use(cytoscapeDagre);
109
110
  }
110
111
 
112
+ // Separate symbol data from graph elements — symbols are shown in sidebar only.
113
+ var SYMBOL_TYPES = { symbol: 1, contains: 1, 'symbol-contains': 1, calls: 1 };
114
+ var coreElements = [];
115
+ var symbolsByFile = {};
116
+ GRAPH_DATA.elements.forEach(function (el) {
117
+ if (el.data.type === 'symbol') {
118
+ var fp = el.data.path;
119
+ if (!symbolsByFile[fp]) symbolsByFile[fp] = [];
120
+ symbolsByFile[fp].push(el.data);
121
+ } else if (!SYMBOL_TYPES[el.data.type]) {
122
+ coreElements.push(el);
123
+ }
124
+ });
125
+
126
+ var LARGE_THRESHOLD = 200;
127
+ var isLarge = coreElements.length > LARGE_THRESHOLD;
128
+
129
+ if (isLarge) {
130
+ document.getElementById('tog-lbl').checked = false;
131
+ }
132
+
111
133
  function esc(v) {
112
134
  return String(v == null ? '' : v)
113
135
  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
@@ -122,13 +144,13 @@ select:hover,button:hover{background:#475569}
122
144
 
123
145
  var cy = cytoscape({
124
146
  container: document.getElementById('cy'),
125
- elements: GRAPH_DATA.elements,
147
+ elements: coreElements,
126
148
  style: [
127
149
  {
128
150
  selector: 'node',
129
151
  style: {
130
152
  'background-color': 'data(color)',
131
- label: 'data(label)',
153
+ label: isLarge ? '' : 'data(label)',
132
154
  color: '#94a3b8',
133
155
  'font-size': '10px',
134
156
  'text-valign': 'bottom',
@@ -136,8 +158,8 @@ select:hover,button:hover{background:#475569}
136
158
  'text-margin-y': '5px',
137
159
  'border-width': 1,
138
160
  'border-color': '#1e293b',
139
- width: 30,
140
- height: 30,
161
+ width: isLarge ? 20 : 30,
162
+ height: isLarge ? 20 : 30,
141
163
  'text-wrap': 'ellipsis',
142
164
  'text-max-width': '80px',
143
165
  'overlay-opacity': 0
@@ -204,37 +226,45 @@ select:hover,button:hover{background:#475569}
204
226
  },
205
227
  { selector: '.hidden', style: { display: 'none' } }
206
228
  ],
207
- layout: {
208
- name: 'dagre',
209
- rankDir: 'LR',
210
- nodeSep: 60,
211
- rankSep: 120,
212
- padding: 40,
213
- animate: true,
214
- animationDuration: 400
215
- }
229
+ layout: isLarge
230
+ ? { name: 'cose', animate: false, padding: 40, nodeOverlap: 20, idealEdgeLength: 80, numIter: 100 }
231
+ : { name: 'dagre', rankDir: 'LR', nodeSep: 60, rankSep: 120, padding: 40, animate: true, animationDuration: 400 }
216
232
  });
217
233
 
234
+ var anim = !isLarge;
218
235
  var LAYOUTS = {
219
- dagre: { name: 'dagre', rankDir: 'LR', nodeSep: 60, rankSep: 120, animate: true, animationDuration: 400, padding: 40 },
220
- cose: { name: 'cose', animate: true, animationDuration: 600, padding: 40 },
221
- grid: { name: 'grid', animate: true, animationDuration: 400, padding: 40 },
236
+ dagre: { name: 'dagre', rankDir: 'LR', nodeSep: 60, rankSep: 120, animate: anim, animationDuration: 400, padding: 40 },
237
+ cose: { name: 'cose', animate: anim, animationDuration: 600, padding: 40 },
238
+ grid: { name: 'grid', animate: anim, animationDuration: 400, padding: 40 },
222
239
  concentric: {
223
240
  name: 'concentric',
224
241
  concentric: function (n) { return n.degree(); },
225
242
  levelWidth: function () { return 2; },
226
- animate: true,
243
+ animate: anim,
227
244
  animationDuration: 400,
228
245
  padding: 40
229
246
  }
230
247
  };
231
248
 
249
+ var SYMBOL_KIND_COLORS = { 'function': '#22c55e', 'class': '#a855f7', method: '#14b8a6', 'export': '#f97316', 'import': '#64748b' };
250
+
232
251
  function renderFilePanel(d) {
252
+ var syms = symbolsByFile[d.path] || [];
253
+ var symHtml = '';
254
+ if (syms.length) {
255
+ symHtml = '<div class="sb-sect"><div class="sb-lbl">Symbols (' + syms.length + ')</div><ul class="sb-list">' +
256
+ syms.map(function (s) {
257
+ var c = SYMBOL_KIND_COLORS[s.kind] || '#94a3b8';
258
+ return '<li><span style="color:' + c + ';font-weight:600">' + esc(s.kind) + '</span> <span class="sb-code">' + esc(s.label) + '</span>' +
259
+ (s.parentName ? ' <span style="color:#475569">in ' + esc(s.parentName) + '</span>' : '') + '</li>';
260
+ }).join('') + '</ul></div>';
261
+ }
233
262
  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>' +
234
263
  '<div class="sb-title">' + esc(d.path) + '</div>' +
235
264
  '<div class="sb-sect"><div class="sb-lbl">Scan Status</div><div class="sb-val">' + esc(d.scanStatus) + '</div></div>' +
236
265
  '<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>';
266
+ '<div class="sb-sect"><div class="sb-lbl">Estimated Tokens</div><div class="sb-val">~' + esc(d.tokenEstimate || 0) + ' tokens</div></div>' +
267
+ symHtml;
238
268
  }
239
269
 
240
270
  function renderCognitionPanel(d) {
@@ -269,6 +299,10 @@ select:hover,button:hover{background:#475569}
269
299
  document.getElementById('sidebar').classList.remove('open');
270
300
  });
271
301
 
302
+ document.getElementById('tog-lbl').addEventListener('change', function (e) {
303
+ cy.style().selector('node').style('label', e.target.checked ? 'data(label)' : '').update();
304
+ });
305
+
272
306
  document.getElementById('tog-cog').addEventListener('change', function (e) {
273
307
  if (e.target.checked) {
274
308
  cy.nodes('.cognition').removeClass('hidden');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kentwynn/kgraph",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Persistent repo intelligence for AI coding assistants.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  "scripts": {
15
15
  "clean": "node scripts/clean-dist.mjs",
16
16
  "build": "npm run clean && tsc -p tsconfig.json",
17
- "postbuild": "chmod +x dist/cli/index.js",
17
+ "postbuild": "node -e \"try{require('child_process').execSync('chmod +x dist/cli/index.js')}catch{}\"",
18
18
  "test": "vitest run",
19
19
  "kgraph": "tsx src/cli/index.ts",
20
20
  "check:artifacts": "node scripts/check-clean-artifacts.mjs",