@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.
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.js +33 -15
- package/dist/cli/commands/integrate.d.ts +1 -1
- package/dist/cli/commands/integrate.js +37 -25
- package/dist/cognition/markdown-note-parser.js +2 -1
- package/dist/integrations/instruction-blocks.d.ts +1 -1
- package/dist/integrations/instruction-blocks.js +18 -15
- package/dist/integrations/integration-registry.d.ts +1 -1
- package/dist/integrations/integration-registry.js +10 -10
- package/dist/integrations/integration-store.d.ts +1 -1
- package/dist/integrations/integration-store.js +20 -18
- package/dist/visualization/html-template.js +52 -18
- package/package.json +2 -2
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { Command } from
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
2
|
export declare function registerInitCommand(program: Command): void;
|
|
@@ -1,28 +1,43 @@
|
|
|
1
|
-
import { writeDefaultConfig } from
|
|
2
|
-
import { normalizeIntegrationNames } from
|
|
3
|
-
import { addIntegrations } from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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(
|
|
9
|
-
.description(
|
|
10
|
-
.option(
|
|
11
|
-
.option(
|
|
12
|
-
.option(
|
|
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
|
|
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 ===
|
|
48
|
+
if (value === 'smart' ||
|
|
49
|
+
value === 'always' ||
|
|
50
|
+
value === 'manual' ||
|
|
51
|
+
value === 'off') {
|
|
34
52
|
return value;
|
|
35
53
|
}
|
|
36
|
-
throw new KGraphError(
|
|
54
|
+
throw new KGraphError('--mode must be smart, always, manual, or off.');
|
|
37
55
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { Command } from
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
2
|
export declare function registerIntegrateCommand(program: Command): void;
|
|
@@ -1,63 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { assertWorkspace } from
|
|
4
|
-
import { KGraphError, runCommand } from
|
|
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
|
|
7
|
-
|
|
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(
|
|
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 ?
|
|
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(
|
|
20
|
-
.description(
|
|
21
|
-
.argument(
|
|
22
|
-
.option(
|
|
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(
|
|
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(
|
|
35
|
-
.description(
|
|
36
|
-
.argument(
|
|
37
|
-
.requiredOption(
|
|
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(
|
|
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
|
|
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(
|
|
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 ===
|
|
68
|
+
if (value === 'smart' ||
|
|
69
|
+
value === 'always' ||
|
|
70
|
+
value === 'manual' ||
|
|
71
|
+
value === 'off') {
|
|
60
72
|
return value;
|
|
61
73
|
}
|
|
62
|
-
throw new KGraphError(
|
|
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(
|
|
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
|
|
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 =
|
|
4
|
-
export const KGRAPH_CAPTURE_POLICY_PLACEHOLDER =
|
|
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 `${
|
|
12
|
+
return normalized ? `${block}\n\n${normalized}\n` : `${block}\n`;
|
|
13
13
|
}
|
|
14
14
|
export function removeManagedBlock(content, integrationName) {
|
|
15
|
-
return content
|
|
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(
|
|
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?`,
|
|
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
|
|
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
|
|
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
|
|
42
|
-
return
|
|
43
|
-
case
|
|
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,10 +1,10 @@
|
|
|
1
|
-
import { claudeCodeAdapter } from
|
|
2
|
-
import { clineAdapter } from
|
|
3
|
-
import { codexAdapter } from
|
|
4
|
-
import { copilotAdapter } from
|
|
5
|
-
import { cursorAdapter } from
|
|
6
|
-
import { geminiAdapter } from
|
|
7
|
-
import { windsurfAdapter } from
|
|
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
|
|
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
|
|
2
|
-
import path from
|
|
3
|
-
import { loadConfig, saveConfig } from
|
|
4
|
-
import { pathExists } from
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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 =
|
|
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 !==
|
|
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 ===
|
|
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))
|
|
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,
|
|
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,
|
|
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,
|
|
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() +
|
|
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 ===
|
|
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 · ${meta.symbolCount} symbols · ${meta.cognitionCount} notes · ~${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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
@@ -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:
|
|
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: '
|
|
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:
|
|
220
|
-
cose: { name: 'cose', animate:
|
|
221
|
-
grid: { name: 'grid', animate:
|
|
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:
|
|
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.
|
|
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",
|