@kentwynn/kgraph 0.1.24 → 0.1.26

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,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
  }
@@ -7,19 +7,26 @@ export interface KGraphConfig {
7
7
  maxContextItems: number;
8
8
  domainHints: Record<string, DomainHint>;
9
9
  integrations: IntegrationConfig[];
10
+ extractors: ExtractorConfig[];
10
11
  }
11
12
  export interface DomainHint {
12
13
  paths?: string[];
13
14
  tags?: string[];
14
15
  }
15
- export type IntegrationName = "claude-code" | "cline" | "codex" | "copilot" | "cursor" | "gemini" | "windsurf";
16
- export type IntegrationMode = "smart" | "always" | "manual" | "off";
16
+ export type IntegrationName = 'claude-code' | 'cline' | 'codex' | 'copilot' | 'cursor' | 'gemini' | 'windsurf';
17
+ export type IntegrationMode = 'smart' | 'always' | 'manual' | 'off';
18
+ export type ExtractorName = 'c-family' | 'csharp' | 'go' | 'jvm' | 'python' | 'rust';
17
19
  export interface IntegrationConfig {
18
20
  name: IntegrationName;
19
21
  enabled: boolean;
20
22
  mode: IntegrationMode;
21
23
  targetPath: string;
22
24
  }
25
+ export interface ExtractorConfig {
26
+ name: ExtractorName;
27
+ enabled: boolean;
28
+ packageName: string;
29
+ }
23
30
  export interface KGraphWorkspace {
24
31
  rootPath: string;
25
32
  kgraphPath: string;
@@ -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.26",
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",
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "homepage": "https://github.com/kentwynn/KGraph#readme",
41
41
  "dependencies": {
42
+ "@clack/prompts": "^1.3.0",
42
43
  "chalk": "^5.6.2",
43
44
  "commander": "^12.1.0",
44
45
  "fast-glob": "^3.3.2",