@kentwynn/kgraph 0.1.26 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +3 -18
  2. package/dist/cli/commands/context.d.ts +2 -2
  3. package/dist/cli/commands/context.js +82 -23
  4. package/dist/cli/commands/init.js +2 -25
  5. package/dist/cli/commands/workflow.js +2 -2
  6. package/dist/cli/help.d.ts +1 -0
  7. package/dist/cli/help.js +4 -6
  8. package/dist/cli/index.js +0 -2
  9. package/dist/cli/init-prompt.d.ts +2 -7
  10. package/dist/cli/init-prompt.js +0 -63
  11. package/dist/cli/init-recommendations.d.ts +1 -12
  12. package/dist/cli/init-recommendations.js +0 -23
  13. package/dist/cli/init-summary.d.ts +2 -4
  14. package/dist/cli/init-summary.js +10 -35
  15. package/dist/config/config.js +0 -33
  16. package/dist/context/context-query.js +23 -0
  17. package/dist/scanner/c-symbol-extractor.d.ts +1 -1
  18. package/dist/scanner/c-symbol-extractor.js +108 -65
  19. package/dist/scanner/csharp-symbol-extractor.d.ts +1 -1
  20. package/dist/scanner/csharp-symbol-extractor.js +93 -67
  21. package/dist/scanner/go-symbol-extractor.d.ts +1 -1
  22. package/dist/scanner/go-symbol-extractor.js +75 -60
  23. package/dist/scanner/jvm-symbol-extractor.d.ts +1 -1
  24. package/dist/scanner/jvm-symbol-extractor.js +139 -71
  25. package/dist/scanner/python-symbol-extractor.d.ts +1 -1
  26. package/dist/scanner/python-symbol-extractor.js +92 -71
  27. package/dist/scanner/repo-scanner.js +65 -8
  28. package/dist/scanner/rust-symbol-extractor.d.ts +1 -1
  29. package/dist/scanner/rust-symbol-extractor.js +94 -89
  30. package/dist/scanner/tree-sitter-parser.d.ts +5 -0
  31. package/dist/scanner/tree-sitter-parser.js +55 -0
  32. package/dist/types/cognition.d.ts +3 -2
  33. package/dist/types/config.d.ts +0 -7
  34. package/dist/types/maps.d.ts +6 -5
  35. package/package.json +10 -1
  36. package/dist/cli/commands/extractor.d.ts +0 -2
  37. package/dist/cli/commands/extractor.js +0 -50
  38. package/dist/extractors/extractor-registry.d.ts +0 -11
  39. package/dist/extractors/extractor-registry.js +0 -70
  40. package/dist/extractors/extractor-store.d.ts +0 -10
  41. package/dist/extractors/extractor-store.js +0 -58
package/README.md CHANGED
@@ -100,17 +100,14 @@ kgraph init
100
100
  # 2. Optional: connect AI tools so they know the KGraph workflow
101
101
  kgraph integrate add codex copilot cursor claude-code gemini windsurf cline
102
102
 
103
- # 3. Optional: configure deep language extractors for non-TS repos
104
- kgraph extractor add jvm python
105
-
106
- # 4. Run the normal workflow for a topic
103
+ # 3. Run the normal workflow for a topic
107
104
  kgraph "auth token refresh"
108
105
 
109
- # 5. Check health if something feels off
106
+ # 4. Check health if something feels off
110
107
  kgraph doctor
111
108
  ```
112
109
 
113
- `kgraph init` now scans once, then prints relevant next steps. When KGraph can detect likely AI tools on the machine, it recommends matching integrations. When the repository contains languages that only have basic built-in extraction today, it recommends optional deep extractors and prints the exact install/configure commands.
110
+ `kgraph init` now scans once, then prints relevant next steps. When KGraph can detect likely AI tools on the machine, it recommends matching integrations.
114
111
 
115
112
  After useful AI work, assistants save durable runtime-capture notes into `.kgraph/inbox/`. These notes are not project documentation; they are KGraph input files that the next `kgraph` run processes automatically. You can also process them directly with `kgraph update`.
116
113
 
@@ -263,18 +260,6 @@ New integrations default to `always` mode because coding agents often under-clas
263
260
  | Codex | `AGENTS.md`, `.agents/skills/kgraph/SKILL.md` |
264
261
  | GitHub Copilot | `.github/copilot-instructions.md`, `.github/prompts/*` |
265
262
  | Cursor | `.cursor/rules/kgraph.mdc` |
266
-
267
- ## Optional Deep Extractors
268
-
269
- KGraph ships with built-in extractors for several languages, but TypeScript and JavaScript still have the deepest built-in analysis today. For languages such as Java, Kotlin, Python, Go, Rust, C/C++, and C#, `kgraph init` can recommend optional deep extractors when those languages are detected in the repository.
270
-
271
- ```bash
272
- kgraph extractor list
273
- kgraph extractor add jvm python
274
- kgraph extractor remove jvm
275
- ```
276
-
277
- `extractor add` writes extractor configuration into `.kgraph/config.yaml` and prints the exact `npm install -D ...` command for the matching optional packages. This is explicit on purpose: KGraph recommends install commands by default rather than silently changing package-manager state.
278
263
  | Claude Code | `CLAUDE.md`, `.claude/commands/*` |
279
264
  | Gemini CLI | `GEMINI.md` |
280
265
  | Windsurf | `.windsurf/rules/kgraph.md` |
@@ -1,4 +1,4 @@
1
- import type { Command } from "commander";
2
- import type { ContextResponse } from "../../types/cognition.js";
1
+ import type { Command } from 'commander';
2
+ import type { ContextResponse } from '../../types/cognition.js';
3
3
  export declare function registerContextCommand(program: Command): void;
4
4
  export declare function renderContextMarkdown(response: ContextResponse): string;
@@ -1,43 +1,102 @@
1
- import { loadConfig } from "../../config/config.js";
2
- import { queryContext } from "../../context/context-query.js";
3
- import { assertWorkspace } from "../../storage/kgraph-paths.js";
4
- import { mapsExist, readMaps } from "../../storage/map-store.js";
5
- import { KGraphError, runCommand } from "../errors.js";
1
+ import { loadConfig } from '../../config/config.js';
2
+ import { queryContext } from '../../context/context-query.js';
3
+ import { assertWorkspace } from '../../storage/kgraph-paths.js';
4
+ import { mapsExist, readMaps } from '../../storage/map-store.js';
5
+ import { KGraphError, runCommand } from '../errors.js';
6
6
  export function registerContextCommand(program) {
7
7
  program
8
- .command("context <query>")
9
- .description("Return compact repo context for a query")
10
- .option("--json", "Print JSON output")
8
+ .command('context <query>')
9
+ .description('Return compact repo context for a query')
10
+ .option('--json', 'Print JSON output')
11
11
  .action((query, options) => runCommand(async () => {
12
12
  if (!query.trim()) {
13
- throw new KGraphError("Query cannot be empty.");
13
+ throw new KGraphError('Query cannot be empty.');
14
14
  }
15
15
  const workspace = await assertWorkspace(process.cwd());
16
16
  if (!(await mapsExist(workspace))) {
17
- throw new KGraphError("KGraph maps are missing. Run `kgraph scan` first.");
17
+ throw new KGraphError('KGraph maps are missing. Run `kgraph scan` first.');
18
18
  }
19
19
  const config = await loadConfig(workspace);
20
20
  const maps = await readMaps(workspace);
21
21
  const response = await queryContext(workspace, config, maps, query);
22
- console.log(options.json ? JSON.stringify(response, null, 2) : renderContextMarkdown(response));
22
+ console.log(options.json
23
+ ? JSON.stringify(response, null, 2)
24
+ : renderContextMarkdown(response));
23
25
  }));
24
26
  }
25
27
  export function renderContextMarkdown(response) {
26
28
  const lines = [`# KGraph Context`, ``, `Query: ${response.query}`, ``];
27
- lines.push("## Matched Domains", "");
28
- lines.push(...formatList(response.matchedDomains.map((item) => `- ${item.item.name} (${item.reasons.join(", ")})`)));
29
- lines.push("", "## Relevant Files", "");
30
- lines.push(...formatList(response.relevantFiles.map((item) => `- ${item.item.path} (${item.reasons.join(", ")})`)));
31
- lines.push("", "## Relevant Symbols", "");
32
- lines.push(...formatList(response.relevantSymbols.map((item) => `- ${item.item.name} in ${item.item.filePath}`)));
33
- lines.push("", "## Relevant Cognition", "");
29
+ lines.push('## Matched Domains', '');
30
+ lines.push(...formatList(response.matchedDomains.map((item) => `- ${item.item.name} (${item.reasons.join(', ')})`)));
31
+ lines.push('', '## Relevant Files', '');
32
+ lines.push(...formatList(response.relevantFiles.map((item) => {
33
+ const f = item.item;
34
+ const meta = [
35
+ f.language,
36
+ f.tokenEstimate ? `~${f.tokenEstimate} tokens` : '',
37
+ ]
38
+ .filter(Boolean)
39
+ .join(', ');
40
+ return `- ${f.path}${meta ? ` [${meta}]` : ''}`;
41
+ })));
42
+ lines.push('', '## Relevant Symbols', '');
43
+ lines.push(...formatList(response.relevantSymbols.map((item) => {
44
+ const s = item.item;
45
+ const kindInfo = [s.kind, s.parentName].filter(Boolean).join(', ');
46
+ const lineRange = s.startLine != null && s.endLine != null
47
+ ? `:${s.startLine}-${s.endLine}`
48
+ : '';
49
+ return `- ${s.name} (${kindInfo}) in ${s.filePath}${lineRange}`;
50
+ })));
51
+ lines.push('', '## Relevant Cognition', '');
34
52
  lines.push(...formatList(response.relevantCognition.map((item) => `- ${item.item.title} [${item.item.referencesStatus}]`)));
35
- lines.push("", "## Relationships", "");
36
- lines.push(...formatList(response.relationships.map((relationship) => `- ${relationship.sourceId} ${relationship.relationshipType} ${relationship.targetId} (${relationship.confidence})`)));
37
- lines.push("", "## Stale References", "");
53
+ lines.push('', '## Relationships', '');
54
+ lines.push(...formatGroupedRelationships(response.relationships));
55
+ lines.push('', '## Nearby Symbols (1-hop imports)', '');
56
+ lines.push(...formatList((response.nearbySymbols ?? []).map((s) => {
57
+ const kindInfo = [s.kind, s.parentName].filter(Boolean).join(', ');
58
+ const lineRange = s.startLine != null && s.endLine != null
59
+ ? `:${s.startLine}-${s.endLine}`
60
+ : '';
61
+ return `- ${s.name} (${kindInfo}) in ${s.filePath}${lineRange}`;
62
+ })));
63
+ lines.push('', '## Stale References', '');
38
64
  lines.push(...formatList(response.staleReferences.map((ref) => `- ${ref}`)));
39
- return lines.join("\n");
65
+ return lines.join('\n');
66
+ }
67
+ function formatGroupedRelationships(relationships) {
68
+ const imports = relationships.filter((r) => r.relationshipType === 'import');
69
+ const calls = relationships.filter((r) => r.relationshipType === 'calls');
70
+ const contains = relationships.filter((r) => r.relationshipType === 'symbol-contains');
71
+ const other = relationships.filter((r) => r.relationshipType !== 'import' &&
72
+ r.relationshipType !== 'calls' &&
73
+ r.relationshipType !== 'symbol-contains' &&
74
+ r.relationshipType !== 'mentions' &&
75
+ r.relationshipType !== 'belongs-to-domain' &&
76
+ r.relationshipType !== 'stale-reference');
77
+ const lines = [];
78
+ if (imports.length > 0) {
79
+ lines.push('Imports:');
80
+ for (const r of imports)
81
+ lines.push(` ${r.sourceId} → ${r.targetId}`);
82
+ }
83
+ if (calls.length > 0) {
84
+ lines.push('Calls:');
85
+ for (const r of calls)
86
+ lines.push(` ${r.sourceId} → ${r.targetId}`);
87
+ }
88
+ if (contains.length > 0) {
89
+ lines.push('Contains:');
90
+ for (const r of contains)
91
+ lines.push(` ${r.sourceId} contains ${r.targetId}`);
92
+ }
93
+ if (other.length > 0) {
94
+ lines.push('Other:');
95
+ for (const r of other)
96
+ lines.push(` ${r.sourceId} ${r.relationshipType} ${r.targetId}`);
97
+ }
98
+ return lines.length > 0 ? lines : ['- None'];
40
99
  }
41
100
  function formatList(items) {
42
- return items.length > 0 ? items : ["- None"];
101
+ return items.length > 0 ? items : ['- None'];
43
102
  }
@@ -1,14 +1,12 @@
1
1
  import { loadConfig, writeDefaultConfig } from '../../config/config.js';
2
- import { installCommandForExtractors } from '../../extractors/extractor-registry.js';
3
- import { addExtractors } from '../../extractors/extractor-store.js';
4
2
  import { normalizeIntegrationNames } from '../../integrations/integration-registry.js';
5
3
  import { addIntegrations } from '../../integrations/integration-store.js';
6
4
  import { scanRepository } from '../../scanner/repo-scanner.js';
7
5
  import { ensureWorkspace } from '../../storage/kgraph-paths.js';
8
6
  import { readMaps, writeMaps } from '../../storage/map-store.js';
9
7
  import { KGraphError, runCommand } from '../errors.js';
10
- import { promptForInitExtractors, promptForInitIntegrations, shouldPromptForInitExtractors, shouldPromptForInitIntegrations, } from '../init-prompt.js';
11
- import { detectMachineIntegrationRecommendations, recommendedExtractorsForInit, recommendedIntegrationsForInit, } from '../init-recommendations.js';
8
+ import { promptForInitIntegrations, shouldPromptForInitIntegrations, } from '../init-prompt.js';
9
+ import { detectMachineIntegrationRecommendations, recommendedIntegrationsForInit, } from '../init-recommendations.js';
12
10
  import { renderInitSummary } from '../init-summary.js';
13
11
  export function registerInitCommand(program) {
14
12
  program
@@ -48,10 +46,6 @@ export function registerInitCommand(program) {
48
46
  configuredIntegrations: config.integrations,
49
47
  detectedIntegrations: detectedMachineIntegrations,
50
48
  });
51
- let recommendedExtractors = recommendedExtractorsForInit({
52
- files: result.files,
53
- configuredExtractors: config.extractors,
54
- });
55
49
  if (shouldPromptForInitIntegrations({
56
50
  explicitIntegrationsRequested: names.length > 0,
57
51
  configuredIntegrations: config.integrations,
@@ -67,28 +61,11 @@ export function registerInitCommand(program) {
67
61
  });
68
62
  }
69
63
  }
70
- if (shouldPromptForInitExtractors({
71
- configuredExtractors: config.extractors,
72
- })) {
73
- const selected = await promptForInitExtractors(recommendedExtractors);
74
- if (selected.length > 0) {
75
- const changed = await addExtractors(workspace, selected);
76
- console.log(`Configured extractors: ${changed.map((item) => item.name).join(', ')}`);
77
- console.log(`Install packages: ${installCommandForExtractors(changed.map((item) => item.packageName))}`);
78
- config = await loadConfig(workspace);
79
- recommendedExtractors = recommendedExtractorsForInit({
80
- files: result.files,
81
- configuredExtractors: config.extractors,
82
- });
83
- }
84
- }
85
64
  console.log('');
86
65
  console.log(renderInitSummary({
87
66
  files: result.files,
88
67
  integrations: config.integrations,
89
68
  recommendedIntegrations,
90
- extractors: config.extractors,
91
- recommendedExtractors,
92
69
  }));
93
70
  }));
94
71
  }
@@ -1,5 +1,4 @@
1
- import { updateCognition } from '../../cognition/cognition-updater.js';
2
- import { refreshCognitionReferenceStatuses } from '../../cognition/cognition-updater.js';
1
+ import { refreshCognitionReferenceStatuses, updateCognition, } from '../../cognition/cognition-updater.js';
3
2
  import { loadConfig } from '../../config/config.js';
4
3
  import { queryContext } from '../../context/context-query.js';
5
4
  import { scanRepository } from '../../scanner/repo-scanner.js';
@@ -35,6 +34,7 @@ export async function runDefaultWorkflow(query) {
35
34
  console.log(renderWorkflowBanner({
36
35
  files: scan.files.length,
37
36
  symbols: scan.symbols.length,
37
+ skippedFiles: scan.skippedFiles,
38
38
  cognitionNotes: update.processed.length,
39
39
  integrations: config.integrations.map((integration) => ({
40
40
  name: integration.name,
@@ -3,6 +3,7 @@ interface WorkflowBannerStats {
3
3
  files: number;
4
4
  symbols: number;
5
5
  cognitionNotes: number;
6
+ skippedFiles?: number;
6
7
  integrations?: WorkflowBannerIntegration[];
7
8
  }
8
9
  interface WorkflowBannerIntegration {
package/dist/cli/help.js CHANGED
@@ -47,11 +47,6 @@ export function renderRootHelp(useColor = supportsColor()) {
47
47
  command('integrate remove cursor', 'Remove KGraph-managed instruction blocks'),
48
48
  command('--mode smart|always|manual|off', 'Control automatic KGraph involvement per integration'),
49
49
  '',
50
- theme.bold('Extractors'),
51
- command('extractor list', 'Show configured optional deep extractors'),
52
- command('extractor add jvm python', 'Configure optional deep extractors and print install commands'),
53
- command('extractor remove jvm', 'Remove extractor configuration'),
54
- '',
55
50
  theme.bold('Options'),
56
51
  command('-V, --version', 'Show version'),
57
52
  command('-h, --help', 'Show this help'),
@@ -82,7 +77,10 @@ export function renderWorkflowBanner(stats, useColor = supportsColor()) {
82
77
  ` ${theme.bold('KGraph')} ${theme.dim('repo intelligence refreshed')}`,
83
78
  '',
84
79
  theme.bold('Refresh Complete'),
85
- command('files', String(stats.files)),
80
+ command('files', String(stats.files) +
81
+ (stats.skippedFiles
82
+ ? ` (${stats.skippedFiles} unchanged, skipped)`
83
+ : '')),
86
84
  command('symbols', String(stats.symbols)),
87
85
  command('cognition notes processed', String(stats.cognitionNotes)),
88
86
  command('integration modes', integrationLine),
package/dist/cli/index.js CHANGED
@@ -5,7 +5,6 @@ import { createRequire } from 'node:module';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { registerContextCommand } from './commands/context.js';
7
7
  import { registerDoctorCommand } from './commands/doctor.js';
8
- import { registerExtractorCommand } from './commands/extractor.js';
9
8
  import { registerHistoryCommand } from './commands/history.js';
10
9
  import { registerImpactCommand } from './commands/impact.js';
11
10
  import { registerInitCommand } from './commands/init.js';
@@ -44,7 +43,6 @@ export function createProgram() {
44
43
  registerUpdateCommand(program);
45
44
  registerContextCommand(program);
46
45
  registerImpactCommand(program);
47
- registerExtractorCommand(program);
48
46
  registerIntegrateCommand(program);
49
47
  registerVisualizeCommand(program);
50
48
  registerHistoryCommand(program);
@@ -1,13 +1,8 @@
1
- import type { ExtractorConfig, ExtractorName, IntegrationConfig, IntegrationName } from '../types/config.js';
2
- import type { InitExtractorRecommendation, InitIntegrationRecommendation } from './init-recommendations.js';
1
+ import type { IntegrationConfig, IntegrationName } from '../types/config.js';
2
+ import type { InitIntegrationRecommendation } from './init-recommendations.js';
3
3
  export declare function shouldPromptForInitIntegrations(options: {
4
4
  explicitIntegrationsRequested: boolean;
5
5
  configuredIntegrations: Pick<IntegrationConfig, 'name'>[];
6
6
  interactive?: boolean;
7
7
  }): boolean;
8
8
  export declare function promptForInitIntegrations(recommendations: InitIntegrationRecommendation[]): Promise<IntegrationName[]>;
9
- export declare function shouldPromptForInitExtractors(options: {
10
- configuredExtractors: Pick<ExtractorConfig, 'name'>[];
11
- interactive?: boolean;
12
- }): boolean;
13
- export declare function promptForInitExtractors(recommendations: InitExtractorRecommendation[]): Promise<ExtractorName[]>;
@@ -1,5 +1,4 @@
1
1
  import * as clack from '@clack/prompts';
2
- import { listExtractorAdapters } from '../extractors/extractor-registry.js';
3
2
  import { listIntegrationAdapters } from '../integrations/integration-registry.js';
4
3
  // --- Integration prompt ---
5
4
  export function shouldPromptForInitIntegrations(options) {
@@ -57,68 +56,6 @@ export async function promptForInitIntegrations(recommendations) {
57
56
  }
58
57
  return selected;
59
58
  }
60
- // --- Extractor prompt ---
61
- export function shouldPromptForInitExtractors(options) {
62
- const interactive = options.interactive ?? isInteractiveTerminal();
63
- return interactive && options.configuredExtractors.length === 0;
64
- }
65
- export async function promptForInitExtractors(recommendations) {
66
- const recNames = recommendations.map((item) => item.name);
67
- const hasRecommendations = recommendations.length > 0;
68
- const action = await clack.select({
69
- message: 'Optional deep language extractors',
70
- options: [
71
- ...(hasRecommendations
72
- ? [
73
- {
74
- value: 'recommended',
75
- label: `Use recommended (${recNames.join(', ')})`,
76
- hint: recommendations
77
- .map((item) => `${item.name} for ${item.languages.join(', ')}`)
78
- .join('; '),
79
- },
80
- ]
81
- : []),
82
- {
83
- value: 'custom',
84
- label: 'Custom selection',
85
- hint: hasRecommendations
86
- ? undefined
87
- : 'no language-specific extractors detected; pick manually',
88
- },
89
- { value: 'skip', label: 'Skip' },
90
- ],
91
- });
92
- if (clack.isCancel(action) || action === 'skip') {
93
- return [];
94
- }
95
- if (action === 'recommended') {
96
- return recNames;
97
- }
98
- const recommendedNames = new Set(recNames);
99
- const otherAdapters = listExtractorAdapters().filter((adapter) => !recommendedNames.has(adapter.name));
100
- const allOptions = [
101
- ...recommendations.map((rec) => ({
102
- value: rec.name,
103
- label: rec.name,
104
- hint: `${rec.languages.join(', ')} — ${rec.packageName} (recommended)`,
105
- })),
106
- ...otherAdapters.map((adapter) => ({
107
- value: adapter.name,
108
- label: adapter.name,
109
- hint: `${adapter.languages.join(', ')} — ${adapter.packageName}`,
110
- })),
111
- ];
112
- const selected = await clack.multiselect({
113
- message: 'Select extractors (space to toggle, enter to confirm)',
114
- options: allOptions,
115
- required: false,
116
- });
117
- if (clack.isCancel(selected)) {
118
- return [];
119
- }
120
- return selected;
121
- }
122
59
  function isInteractiveTerminal() {
123
60
  return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
124
61
  }
@@ -1,14 +1,8 @@
1
- import type { ExtractorConfig, ExtractorName, IntegrationConfig, IntegrationName } from '../types/config.js';
2
- import type { RepositoryFile } from '../types/maps.js';
1
+ import type { IntegrationConfig, IntegrationName } from '../types/config.js';
3
2
  export interface InitIntegrationRecommendation {
4
3
  name: IntegrationName;
5
4
  reason: string;
6
5
  }
7
- export interface InitExtractorRecommendation {
8
- name: ExtractorName;
9
- packageName: string;
10
- languages: string[];
11
- }
12
6
  interface MachineDetectionContext {
13
7
  env?: NodeJS.ProcessEnv;
14
8
  homeDir?: string;
@@ -22,10 +16,5 @@ export declare function recommendedIntegrationsForInit(options: {
22
16
  configuredIntegrations: Pick<IntegrationConfig, 'name'>[];
23
17
  detectedIntegrations: InitIntegrationRecommendation[];
24
18
  }): InitIntegrationRecommendation[];
25
- export declare function recommendedExtractorsForInit(options: {
26
- files: RepositoryFile[];
27
- configuredExtractors: Pick<ExtractorConfig, 'name'>[];
28
- }): InitExtractorRecommendation[];
29
19
  export declare function integrationSetupCommand(recommendations: InitIntegrationRecommendation[]): string | undefined;
30
- export declare function extractorSetupCommands(recommendations: InitExtractorRecommendation[]): string[];
31
20
  export {};
@@ -1,7 +1,6 @@
1
1
  import { readdir } from 'node:fs/promises';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
- import { installCommandForExtractors, listExtractorAdapters, } from '../extractors/extractor-registry.js';
5
4
  import { pathExists } from '../storage/kgraph-paths.js';
6
5
  export async function detectMachineIntegrationRecommendations(context = {}) {
7
6
  const env = context.env ?? process.env;
@@ -40,34 +39,12 @@ export function recommendedIntegrationsForInit(options) {
40
39
  const configured = new Set(options.configuredIntegrations.map((item) => item.name));
41
40
  return options.detectedIntegrations.filter((item) => !configured.has(item.name));
42
41
  }
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
42
  export function integrationSetupCommand(recommendations) {
57
43
  if (recommendations.length === 0) {
58
44
  return undefined;
59
45
  }
60
46
  return `kgraph integrate add ${recommendations.map((item) => item.name).join(' ')}`;
61
47
  }
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
48
  async function hasVsCodeExtension(prefixes, context) {
72
49
  const exists = context.exists ?? pathExists;
73
50
  const readDir = context.readDir ?? defaultReadDir;
@@ -1,6 +1,6 @@
1
- import type { ExtractorConfig, IntegrationConfig } from '../types/config.js';
1
+ import type { IntegrationConfig } from '../types/config.js';
2
2
  import type { RepositoryFile } from '../types/maps.js';
3
- import { type InitExtractorRecommendation, type InitIntegrationRecommendation } from './init-recommendations.js';
3
+ import { type InitIntegrationRecommendation } from './init-recommendations.js';
4
4
  type CoverageLevel = 'deep' | 'basic' | 'generic';
5
5
  export interface InitLanguageSummary {
6
6
  language: string;
@@ -13,7 +13,5 @@ export declare function renderInitSummary(options: {
13
13
  files: RepositoryFile[];
14
14
  integrations: Pick<IntegrationConfig, 'name' | 'enabled' | 'mode'>[];
15
15
  recommendedIntegrations: InitIntegrationRecommendation[];
16
- extractors: Pick<ExtractorConfig, 'name' | 'enabled' | 'packageName'>[];
17
- recommendedExtractors: InitExtractorRecommendation[];
18
16
  }): string;
19
17
  export {};
@@ -1,17 +1,17 @@
1
- import { extractorSetupCommands, integrationSetupCommand, } from './init-recommendations.js';
1
+ import { integrationSetupCommand, } from './init-recommendations.js';
2
2
  const LANGUAGE_PRESENTATION = {
3
3
  javascript: { label: 'JavaScript', coverage: 'deep' },
4
4
  javascriptreact: { label: 'JavaScript', coverage: 'deep' },
5
5
  typescript: { label: 'TypeScript', coverage: 'deep' },
6
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' },
7
+ python: { label: 'Python', coverage: 'deep' },
8
+ go: { label: 'Go', coverage: 'deep' },
9
+ rust: { label: 'Rust', coverage: 'deep' },
10
+ java: { label: 'Java', coverage: 'deep' },
11
+ kotlin: { label: 'Kotlin', coverage: 'deep' },
12
+ c: { label: 'C', coverage: 'deep' },
13
+ cpp: { label: 'C++', coverage: 'deep' },
14
+ csharp: { label: 'C#', coverage: 'deep' },
15
15
  yaml: { label: 'YAML', coverage: 'generic' },
16
16
  json: { label: 'JSON', coverage: 'generic' },
17
17
  toml: { label: 'TOML', coverage: 'generic' },
@@ -50,12 +50,6 @@ export function summarizeInitLanguages(files) {
50
50
  }
51
51
  export function renderInitSummary(options) {
52
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
53
  const lines = ['KGraph Init Summary', ''];
60
54
  lines.push('AI integrations');
61
55
  if (options.recommendedIntegrations.length > 0) {
@@ -76,23 +70,7 @@ export function renderInitSummary(options) {
76
70
  }
77
71
  else {
78
72
  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})`);
73
+ lines.push(` ${language.label}: ${formatFileCount(language.fileCount)}, ${coverageDescription(language.coverage)}`);
96
74
  }
97
75
  }
98
76
  lines.push('');
@@ -105,9 +83,6 @@ export function renderInitSummary(options) {
105
83
  else if (options.integrations.length === 0) {
106
84
  lines.push(' kgraph integrate add <agent> Optional: connect an AI tool');
107
85
  }
108
- for (const command of extractorSetupCommands(options.recommendedExtractors)) {
109
- lines.push(` ${command}`);
110
- }
111
86
  lines.push(' kgraph doctor Check workspace health');
112
87
  return lines.join('\n');
113
88
  }
@@ -71,7 +71,6 @@ export const DEFAULT_CONFIG = {
71
71
  maxContextItems: 8,
72
72
  domainHints: {},
73
73
  integrations: [],
74
- extractors: [],
75
74
  };
76
75
  export async function writeDefaultConfig(workspace) {
77
76
  if (await pathExists(workspace.configPath)) {
@@ -115,7 +114,6 @@ export function normalizeConfig(config) {
115
114
  ? config.domainHints
116
115
  : {},
117
116
  integrations: normalizeIntegrations(config.integrations),
118
- extractors: normalizeExtractors(config.extractors),
119
117
  };
120
118
  }
121
119
  function mergeUnique(base, extra) {
@@ -163,34 +161,3 @@ function normalizeIntegrationMode(value) {
163
161
  ? value
164
162
  : 'smart';
165
163
  }
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
- }
@@ -73,6 +73,28 @@ export async function queryContext(workspace, config, maps, query) {
73
73
  .filter((s) => !symbolNames.has(s))
74
74
  .map((ref) => `${note.title}: ${ref}`),
75
75
  ]);
76
+ // Collect nearby symbols: exported symbols from files 1-hop imported by matched files
77
+ const matchedFilePaths = new Set([
78
+ ...relevantFiles.map((f) => f.item.path),
79
+ ...relevantSymbols.map((s) => s.item.filePath),
80
+ ]);
81
+ const matchedSymbolIds = new Set(relevantSymbols.map((s) => s.item.id));
82
+ const importedFilePaths = new Set();
83
+ for (const dep of maps.dependencyMap.dependencies) {
84
+ if (dep.kind === 'local' &&
85
+ dep.resolvedFile &&
86
+ matchedFilePaths.has(dep.fromFile)) {
87
+ importedFilePaths.add(dep.resolvedFile);
88
+ }
89
+ }
90
+ // Remove files already in the matched set
91
+ for (const p of matchedFilePaths)
92
+ importedFilePaths.delete(p);
93
+ const nearbySymbols = maps.symbolMap.symbols
94
+ .filter((s) => s.exported &&
95
+ importedFilePaths.has(s.filePath) &&
96
+ !matchedSymbolIds.has(s.id))
97
+ .slice(0, max);
76
98
  return {
77
99
  query,
78
100
  matchedDomains,
@@ -80,6 +102,7 @@ export async function queryContext(workspace, config, maps, query) {
80
102
  relevantSymbols,
81
103
  relevantCognition,
82
104
  relationships: relationships.slice(0, max),
105
+ nearbySymbols,
83
106
  staleReferences,
84
107
  warnings: [],
85
108
  };
@@ -1,2 +1,2 @@
1
1
  import type { SymbolExtractionResult } from './ts-symbol-extractor.js';
2
- export declare function extractCSymbols(sourceText: string, filePath: string): SymbolExtractionResult;
2
+ export declare function extractCSymbols(sourceText: string, filePath: string): Promise<SymbolExtractionResult>;