@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.
@@ -0,0 +1,163 @@
1
+ import { readdir } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { installCommandForExtractors, listExtractorAdapters, } from '../extractors/extractor-registry.js';
5
+ import { pathExists } from '../storage/kgraph-paths.js';
6
+ export async function detectMachineIntegrationRecommendations(context = {}) {
7
+ const env = context.env ?? process.env;
8
+ if (env.KGRAPH_DISABLE_MACHINE_DETECTION === '1') {
9
+ return [];
10
+ }
11
+ const recommendations = [];
12
+ if ((await hasVsCodeExtension(['github.copilot-', 'github.copilot-chat-'], context)) ||
13
+ (await hasVsCodeBundledCopilot(context))) {
14
+ recommendations.push({
15
+ name: 'copilot',
16
+ reason: 'VS Code Copilot detected',
17
+ });
18
+ }
19
+ if (await hasExecutable('codex', context)) {
20
+ recommendations.push({
21
+ name: 'codex',
22
+ reason: 'codex executable detected on PATH',
23
+ });
24
+ }
25
+ if (await hasExecutable('claude', context)) {
26
+ recommendations.push({
27
+ name: 'claude-code',
28
+ reason: 'claude executable detected on PATH',
29
+ });
30
+ }
31
+ if (await hasExecutable('gemini', context)) {
32
+ recommendations.push({
33
+ name: 'gemini',
34
+ reason: 'gemini executable detected on PATH',
35
+ });
36
+ }
37
+ return recommendations.sort((left, right) => left.name.localeCompare(right.name));
38
+ }
39
+ export function recommendedIntegrationsForInit(options) {
40
+ const configured = new Set(options.configuredIntegrations.map((item) => item.name));
41
+ return options.detectedIntegrations.filter((item) => !configured.has(item.name));
42
+ }
43
+ export function recommendedExtractorsForInit(options) {
44
+ const configured = new Set(options.configuredExtractors.map((item) => item.name));
45
+ const detectedLanguages = new Set(options.files.map((file) => file.language));
46
+ return listExtractorAdapters()
47
+ .filter((adapter) => !configured.has(adapter.name))
48
+ .map((adapter) => ({
49
+ name: adapter.name,
50
+ packageName: adapter.packageName,
51
+ languages: adapter.languages.filter((language) => detectedLanguages.has(language)),
52
+ }))
53
+ .filter((adapter) => adapter.languages.length > 0)
54
+ .sort((left, right) => left.name.localeCompare(right.name));
55
+ }
56
+ export function integrationSetupCommand(recommendations) {
57
+ if (recommendations.length === 0) {
58
+ return undefined;
59
+ }
60
+ return `kgraph integrate add ${recommendations.map((item) => item.name).join(' ')}`;
61
+ }
62
+ export function extractorSetupCommands(recommendations) {
63
+ if (recommendations.length === 0) {
64
+ return [];
65
+ }
66
+ return [
67
+ `kgraph extractor add ${recommendations.map((item) => item.name).join(' ')}`,
68
+ installCommandForExtractors(recommendations.map((item) => item.packageName)),
69
+ ];
70
+ }
71
+ async function hasVsCodeExtension(prefixes, context) {
72
+ const exists = context.exists ?? pathExists;
73
+ const readDir = context.readDir ?? defaultReadDir;
74
+ const homeDir = context.homeDir ?? os.homedir();
75
+ const candidates = [
76
+ path.join(homeDir, '.vscode', 'extensions'),
77
+ path.join(homeDir, '.vscode-insiders', 'extensions'),
78
+ ];
79
+ for (const candidate of candidates) {
80
+ if (!(await exists(candidate))) {
81
+ continue;
82
+ }
83
+ const entries = await readDir(candidate);
84
+ if (entries.some((entry) => prefixes.some((prefix) => entry.toLowerCase().startsWith(prefix)))) {
85
+ return true;
86
+ }
87
+ }
88
+ return false;
89
+ }
90
+ async function hasVsCodeBundledCopilot(context) {
91
+ const exists = context.exists ?? pathExists;
92
+ const readDir = context.readDir ?? defaultReadDir;
93
+ const platform = context.platform ?? process.platform;
94
+ const env = context.env ?? process.env;
95
+ const dirs = await resolveVsCodeBundledExtensionDirs(platform, env, context.localAppData, exists, readDir);
96
+ for (const dir of dirs) {
97
+ if (await exists(path.join(dir, 'copilot'))) {
98
+ return true;
99
+ }
100
+ }
101
+ return false;
102
+ }
103
+ async function resolveVsCodeBundledExtensionDirs(platform, env, overrideLocalAppData, exists, readDir) {
104
+ const dirs = [];
105
+ if (platform === 'win32') {
106
+ const localAppData = overrideLocalAppData ?? env.LOCALAPPDATA ?? '';
107
+ if (localAppData) {
108
+ const vsCodeRoot = path.join(localAppData, 'Programs', 'Microsoft VS Code');
109
+ if (await exists(vsCodeRoot)) {
110
+ const entries = await readDir(vsCodeRoot);
111
+ for (const entry of entries) {
112
+ dirs.push(path.join(vsCodeRoot, entry, 'resources', 'app', 'extensions'));
113
+ }
114
+ }
115
+ }
116
+ }
117
+ else if (platform === 'darwin') {
118
+ dirs.push('/Applications/Visual Studio Code.app/Contents/Resources/app/extensions');
119
+ dirs.push('/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/extensions');
120
+ }
121
+ else {
122
+ dirs.push('/usr/share/code/resources/app/extensions');
123
+ dirs.push('/usr/lib/code/resources/app/extensions');
124
+ }
125
+ return dirs;
126
+ }
127
+ async function hasExecutable(commandName, context) {
128
+ const env = context.env ?? process.env;
129
+ const exists = context.exists ?? pathExists;
130
+ const platform = context.platform ?? process.platform;
131
+ const pathValue = env.PATH ?? '';
132
+ const directories = pathValue
133
+ .split(path.delimiter)
134
+ .map((item) => item.trim())
135
+ .filter(Boolean);
136
+ if (directories.length === 0) {
137
+ return false;
138
+ }
139
+ const candidates = platform === 'win32'
140
+ ? buildWindowsExecutableCandidates(commandName, env.PATHEXT)
141
+ : [commandName];
142
+ for (const directory of directories) {
143
+ for (const candidate of candidates) {
144
+ if (await exists(path.join(directory, candidate))) {
145
+ return true;
146
+ }
147
+ }
148
+ }
149
+ return false;
150
+ }
151
+ function buildWindowsExecutableCandidates(commandName, pathExt) {
152
+ const extensions = (pathExt ?? '.EXE;.CMD;.BAT;.COM')
153
+ .split(';')
154
+ .map((item) => item.trim())
155
+ .filter(Boolean);
156
+ return [
157
+ commandName,
158
+ ...extensions.map((extension) => `${commandName}${extension}`),
159
+ ];
160
+ }
161
+ async function defaultReadDir(targetPath) {
162
+ return readdir(targetPath);
163
+ }
@@ -0,0 +1,19 @@
1
+ import type { ExtractorConfig, IntegrationConfig } from '../types/config.js';
2
+ import type { RepositoryFile } from '../types/maps.js';
3
+ import { type InitExtractorRecommendation, type InitIntegrationRecommendation } from './init-recommendations.js';
4
+ type CoverageLevel = 'deep' | 'basic' | 'generic';
5
+ export interface InitLanguageSummary {
6
+ language: string;
7
+ label: string;
8
+ fileCount: number;
9
+ coverage: CoverageLevel;
10
+ }
11
+ export declare function summarizeInitLanguages(files: RepositoryFile[]): InitLanguageSummary[];
12
+ export declare function renderInitSummary(options: {
13
+ files: RepositoryFile[];
14
+ integrations: Pick<IntegrationConfig, 'name' | 'enabled' | 'mode'>[];
15
+ recommendedIntegrations: InitIntegrationRecommendation[];
16
+ extractors: Pick<ExtractorConfig, 'name' | 'enabled' | 'packageName'>[];
17
+ recommendedExtractors: InitExtractorRecommendation[];
18
+ }): string;
19
+ export {};
@@ -0,0 +1,147 @@
1
+ import { extractorSetupCommands, integrationSetupCommand, } from './init-recommendations.js';
2
+ const LANGUAGE_PRESENTATION = {
3
+ javascript: { label: 'JavaScript', coverage: 'deep' },
4
+ javascriptreact: { label: 'JavaScript', coverage: 'deep' },
5
+ typescript: { label: 'TypeScript', coverage: 'deep' },
6
+ typescriptreact: { label: 'TypeScript', coverage: 'deep' },
7
+ python: { label: 'Python', coverage: 'basic' },
8
+ go: { label: 'Go', coverage: 'basic' },
9
+ rust: { label: 'Rust', coverage: 'basic' },
10
+ java: { label: 'Java', coverage: 'basic' },
11
+ kotlin: { label: 'Kotlin', coverage: 'basic' },
12
+ c: { label: 'C', coverage: 'basic' },
13
+ cpp: { label: 'C++', coverage: 'basic' },
14
+ csharp: { label: 'C#', coverage: 'basic' },
15
+ yaml: { label: 'YAML', coverage: 'generic' },
16
+ json: { label: 'JSON', coverage: 'generic' },
17
+ toml: { label: 'TOML', coverage: 'generic' },
18
+ xml: { label: 'XML', coverage: 'generic' },
19
+ graphql: { label: 'GraphQL', coverage: 'generic' },
20
+ sql: { label: 'SQL', coverage: 'generic' },
21
+ shell: { label: 'Shell', coverage: 'generic' },
22
+ };
23
+ const EXCLUDED_LANGUAGES = new Set(['unknown', 'markdown', 'restructuredtext']);
24
+ export function summarizeInitLanguages(files) {
25
+ const byLabel = new Map();
26
+ for (const file of files) {
27
+ if (EXCLUDED_LANGUAGES.has(file.language)) {
28
+ continue;
29
+ }
30
+ const descriptor = describeLanguage(file.language);
31
+ const existing = byLabel.get(descriptor.label);
32
+ if (existing) {
33
+ existing.fileCount += 1;
34
+ existing.coverage = moreDetailedCoverage(existing.coverage, descriptor.coverage);
35
+ continue;
36
+ }
37
+ byLabel.set(descriptor.label, {
38
+ language: file.language,
39
+ label: descriptor.label,
40
+ fileCount: 1,
41
+ coverage: descriptor.coverage,
42
+ });
43
+ }
44
+ return [...byLabel.values()].sort((left, right) => {
45
+ if (right.fileCount !== left.fileCount) {
46
+ return right.fileCount - left.fileCount;
47
+ }
48
+ return left.label.localeCompare(right.label);
49
+ });
50
+ }
51
+ export function renderInitSummary(options) {
52
+ const languages = summarizeInitLanguages(options.files);
53
+ const recommendedExtractorByLanguage = new Map();
54
+ for (const extractor of options.recommendedExtractors) {
55
+ for (const language of extractor.languages) {
56
+ recommendedExtractorByLanguage.set(language, extractor.name);
57
+ }
58
+ }
59
+ const lines = ['KGraph Init Summary', ''];
60
+ lines.push('AI integrations');
61
+ if (options.recommendedIntegrations.length > 0) {
62
+ lines.push(` recommended: ${options.recommendedIntegrations.map((item) => `${item.name} (${item.reason})`).join('; ')}`);
63
+ }
64
+ if (options.integrations.length === 0) {
65
+ lines.push(' configured: none');
66
+ }
67
+ else {
68
+ for (const integration of options.integrations) {
69
+ lines.push(` configured: ${integration.name}: ${integration.enabled ? integration.mode : 'off'}`);
70
+ }
71
+ }
72
+ lines.push('');
73
+ lines.push('Repo languages');
74
+ if (languages.length === 0) {
75
+ lines.push(' none detected yet');
76
+ }
77
+ else {
78
+ for (const language of languages) {
79
+ const recommendedExtractor = recommendedExtractorByLanguage.get(language.language);
80
+ lines.push(` ${language.label}: ${formatFileCount(language.fileCount)}, ${coverageDescription(language.coverage)}${recommendedExtractor ? `; recommended extractor: ${recommendedExtractor}` : ''}`);
81
+ }
82
+ }
83
+ lines.push('');
84
+ lines.push('Optional extractors');
85
+ if (options.extractors.length === 0) {
86
+ lines.push(' configured: none');
87
+ }
88
+ else {
89
+ for (const extractor of options.extractors) {
90
+ lines.push(` configured: ${extractor.name}: ${extractor.enabled ? extractor.packageName : 'off'}`);
91
+ }
92
+ }
93
+ if (options.recommendedExtractors.length > 0) {
94
+ for (const extractor of options.recommendedExtractors) {
95
+ lines.push(` recommended: ${extractor.name} for ${extractor.languages.map(humanizeLanguage).join(', ')} (${extractor.packageName})`);
96
+ }
97
+ }
98
+ lines.push('');
99
+ lines.push('Next');
100
+ lines.push(' kgraph "topic" Run the normal refresh and context workflow');
101
+ const integrationCommand = integrationSetupCommand(options.recommendedIntegrations);
102
+ if (integrationCommand) {
103
+ lines.push(` ${integrationCommand} Optional: connect detected AI tools`);
104
+ }
105
+ else if (options.integrations.length === 0) {
106
+ lines.push(' kgraph integrate add <agent> Optional: connect an AI tool');
107
+ }
108
+ for (const command of extractorSetupCommands(options.recommendedExtractors)) {
109
+ lines.push(` ${command}`);
110
+ }
111
+ lines.push(' kgraph doctor Check workspace health');
112
+ return lines.join('\n');
113
+ }
114
+ function describeLanguage(language) {
115
+ return (LANGUAGE_PRESENTATION[language] ?? {
116
+ label: humanizeLanguage(language),
117
+ coverage: 'generic',
118
+ });
119
+ }
120
+ function humanizeLanguage(language) {
121
+ return language
122
+ .split(/[-_\s]+/)
123
+ .filter(Boolean)
124
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
125
+ .join(' ');
126
+ }
127
+ function moreDetailedCoverage(left, right) {
128
+ const rank = {
129
+ deep: 3,
130
+ basic: 2,
131
+ generic: 1,
132
+ };
133
+ return rank[left] >= rank[right] ? left : right;
134
+ }
135
+ function coverageDescription(coverage) {
136
+ switch (coverage) {
137
+ case 'deep':
138
+ return 'deep built-in extraction';
139
+ case 'basic':
140
+ return 'basic built-in extraction';
141
+ default:
142
+ return 'generic file coverage';
143
+ }
144
+ }
145
+ function formatFileCount(fileCount) {
146
+ return fileCount === 1 ? '1 file' : `${fileCount} files`;
147
+ }
@@ -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';
@@ -71,6 +71,7 @@ export const DEFAULT_CONFIG = {
71
71
  maxContextItems: 8,
72
72
  domainHints: {},
73
73
  integrations: [],
74
+ extractors: [],
74
75
  };
75
76
  export async function writeDefaultConfig(workspace) {
76
77
  if (await pathExists(workspace.configPath)) {
@@ -114,6 +115,7 @@ export function normalizeConfig(config) {
114
115
  ? config.domainHints
115
116
  : {},
116
117
  integrations: normalizeIntegrations(config.integrations),
118
+ extractors: normalizeExtractors(config.extractors),
117
119
  };
118
120
  }
119
121
  function mergeUnique(base, extra) {
@@ -161,3 +163,34 @@ function normalizeIntegrationMode(value) {
161
163
  ? value
162
164
  : 'smart';
163
165
  }
166
+ function normalizeExtractors(value) {
167
+ if (!Array.isArray(value)) {
168
+ return [];
169
+ }
170
+ const seen = new Set();
171
+ const extractors = [];
172
+ for (const item of value) {
173
+ if (!item || typeof item !== 'object') {
174
+ continue;
175
+ }
176
+ const candidate = item;
177
+ if (typeof candidate.name !== 'string' ||
178
+ typeof candidate.packageName !== 'string' ||
179
+ seen.has(candidate.name)) {
180
+ continue;
181
+ }
182
+ if (!isExtractorName(candidate.name)) {
183
+ continue;
184
+ }
185
+ seen.add(candidate.name);
186
+ extractors.push({
187
+ name: candidate.name,
188
+ enabled: candidate.enabled !== false,
189
+ packageName: candidate.packageName,
190
+ });
191
+ }
192
+ return extractors;
193
+ }
194
+ function isExtractorName(value) {
195
+ return ['c-family', 'csharp', 'go', 'jvm', 'python', 'rust'].includes(value);
196
+ }
@@ -0,0 +1,11 @@
1
+ import type { ExtractorName } from '../types/config.js';
2
+ export interface ExtractorAdapter {
3
+ name: ExtractorName;
4
+ label: string;
5
+ packageName: string;
6
+ languages: string[];
7
+ }
8
+ export declare function listExtractorAdapters(): ExtractorAdapter[];
9
+ export declare function getExtractorAdapter(name: string): ExtractorAdapter;
10
+ export declare function normalizeExtractorNames(values: string[] | undefined): ExtractorName[];
11
+ export declare function installCommandForExtractors(packageNames: string[]): string;
@@ -0,0 +1,70 @@
1
+ const ADAPTERS = [
2
+ {
3
+ name: 'python',
4
+ label: 'Python deep extractor',
5
+ packageName: '@kentwynn/kgraph-extractor-python',
6
+ languages: ['python'],
7
+ },
8
+ {
9
+ name: 'jvm',
10
+ label: 'JVM deep extractor',
11
+ packageName: '@kentwynn/kgraph-extractor-jvm',
12
+ languages: ['java', 'kotlin'],
13
+ },
14
+ {
15
+ name: 'go',
16
+ label: 'Go deep extractor',
17
+ packageName: '@kentwynn/kgraph-extractor-go',
18
+ languages: ['go'],
19
+ },
20
+ {
21
+ name: 'rust',
22
+ label: 'Rust deep extractor',
23
+ packageName: '@kentwynn/kgraph-extractor-rust',
24
+ languages: ['rust'],
25
+ },
26
+ {
27
+ name: 'c-family',
28
+ label: 'C/C++ deep extractor',
29
+ packageName: '@kentwynn/kgraph-extractor-c-family',
30
+ languages: ['c', 'cpp'],
31
+ },
32
+ {
33
+ name: 'csharp',
34
+ label: 'C# deep extractor',
35
+ packageName: '@kentwynn/kgraph-extractor-csharp',
36
+ languages: ['csharp'],
37
+ },
38
+ ].sort((left, right) => left.name.localeCompare(right.name));
39
+ export function listExtractorAdapters() {
40
+ return ADAPTERS;
41
+ }
42
+ export function getExtractorAdapter(name) {
43
+ const adapter = ADAPTERS.find((item) => item.name === name);
44
+ if (!adapter) {
45
+ throw new Error(`Unsupported extractor "${name}". Supported extractors: ${ADAPTERS.map((item) => item.name).join(', ')}`);
46
+ }
47
+ return adapter;
48
+ }
49
+ export function normalizeExtractorNames(values) {
50
+ if (!values || values.length === 0) {
51
+ return [];
52
+ }
53
+ const names = [];
54
+ const seen = new Set();
55
+ for (const value of values) {
56
+ for (const raw of value.split(/[\s,]+/)) {
57
+ const name = raw.trim();
58
+ if (!name || seen.has(name)) {
59
+ continue;
60
+ }
61
+ const adapter = getExtractorAdapter(name);
62
+ seen.add(adapter.name);
63
+ names.push(adapter.name);
64
+ }
65
+ }
66
+ return names;
67
+ }
68
+ export function installCommandForExtractors(packageNames) {
69
+ return `npm install -D ${packageNames.join(' ')}`;
70
+ }
@@ -0,0 +1,10 @@
1
+ import type { ExtractorConfig, ExtractorName, KGraphWorkspace } from '../types/config.js';
2
+ export interface ExtractorStatus {
3
+ name: ExtractorName;
4
+ enabled: boolean;
5
+ packageName: string;
6
+ packageInstalled: boolean;
7
+ }
8
+ export declare function listExtractors(workspace: KGraphWorkspace): Promise<ExtractorStatus[]>;
9
+ export declare function addExtractors(workspace: KGraphWorkspace, names: ExtractorName[]): Promise<ExtractorConfig[]>;
10
+ export declare function removeExtractors(workspace: KGraphWorkspace, names: ExtractorName[]): Promise<ExtractorName[]>;
@@ -0,0 +1,58 @@
1
+ import { readFile } 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 { getExtractorAdapter } from './extractor-registry.js';
6
+ export async function listExtractors(workspace) {
7
+ const config = await loadConfig(workspace);
8
+ const statuses = await Promise.all(config.extractors.map(async (extractor) => ({
9
+ name: extractor.name,
10
+ enabled: extractor.enabled,
11
+ packageName: extractor.packageName,
12
+ packageInstalled: await isExtractorInstalled(workspace.rootPath, extractor.packageName),
13
+ })));
14
+ return statuses.sort((left, right) => left.name.localeCompare(right.name));
15
+ }
16
+ export async function addExtractors(workspace, names) {
17
+ const config = await loadConfig(workspace);
18
+ const byName = new Map(config.extractors.map((extractor) => [extractor.name, extractor]));
19
+ const changed = [];
20
+ for (const name of names) {
21
+ const adapter = getExtractorAdapter(name);
22
+ const next = {
23
+ name: adapter.name,
24
+ enabled: true,
25
+ packageName: adapter.packageName,
26
+ };
27
+ byName.set(adapter.name, next);
28
+ changed.push(next);
29
+ }
30
+ config.extractors = [...byName.values()].sort((left, right) => left.name.localeCompare(right.name));
31
+ await saveConfig(workspace, config);
32
+ return changed;
33
+ }
34
+ export async function removeExtractors(workspace, names) {
35
+ const config = await loadConfig(workspace);
36
+ const removeNames = new Set(names);
37
+ config.extractors = config.extractors.filter((extractor) => !removeNames.has(extractor.name));
38
+ await saveConfig(workspace, config);
39
+ return [...removeNames].sort((left, right) => left.localeCompare(right));
40
+ }
41
+ async function isExtractorInstalled(rootPath, packageName) {
42
+ const packageJsonPath = path.join(rootPath, 'package.json');
43
+ if (await pathExists(packageJsonPath)) {
44
+ try {
45
+ const raw = await readFile(packageJsonPath, 'utf8');
46
+ const pkg = JSON.parse(raw);
47
+ if (pkg.dependencies?.[packageName] ||
48
+ pkg.devDependencies?.[packageName] ||
49
+ pkg.optionalDependencies?.[packageName]) {
50
+ return true;
51
+ }
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ return pathExists(path.join(rootPath, 'node_modules', ...packageName.split('/'), 'package.json'));
58
+ }
@@ -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;