@kentwynn/kgraph 0.1.26 → 0.1.27

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 (34) hide show
  1. package/README.md +3 -18
  2. package/dist/cli/commands/init.js +2 -25
  3. package/dist/cli/help.js +0 -5
  4. package/dist/cli/index.js +0 -2
  5. package/dist/cli/init-prompt.d.ts +2 -7
  6. package/dist/cli/init-prompt.js +0 -63
  7. package/dist/cli/init-recommendations.d.ts +1 -12
  8. package/dist/cli/init-recommendations.js +0 -23
  9. package/dist/cli/init-summary.d.ts +2 -4
  10. package/dist/cli/init-summary.js +10 -35
  11. package/dist/config/config.js +0 -33
  12. package/dist/scanner/c-symbol-extractor.d.ts +1 -1
  13. package/dist/scanner/c-symbol-extractor.js +108 -65
  14. package/dist/scanner/csharp-symbol-extractor.d.ts +1 -1
  15. package/dist/scanner/csharp-symbol-extractor.js +93 -67
  16. package/dist/scanner/go-symbol-extractor.d.ts +1 -1
  17. package/dist/scanner/go-symbol-extractor.js +75 -60
  18. package/dist/scanner/jvm-symbol-extractor.d.ts +1 -1
  19. package/dist/scanner/jvm-symbol-extractor.js +139 -71
  20. package/dist/scanner/python-symbol-extractor.d.ts +1 -1
  21. package/dist/scanner/python-symbol-extractor.js +92 -71
  22. package/dist/scanner/repo-scanner.js +3 -3
  23. package/dist/scanner/rust-symbol-extractor.d.ts +1 -1
  24. package/dist/scanner/rust-symbol-extractor.js +94 -89
  25. package/dist/scanner/tree-sitter-parser.d.ts +5 -0
  26. package/dist/scanner/tree-sitter-parser.js +55 -0
  27. package/dist/types/config.d.ts +0 -7
  28. package/package.json +10 -1
  29. package/dist/cli/commands/extractor.d.ts +0 -2
  30. package/dist/cli/commands/extractor.js +0 -50
  31. package/dist/extractors/extractor-registry.d.ts +0 -11
  32. package/dist/extractors/extractor-registry.js +0 -70
  33. package/dist/extractors/extractor-store.d.ts +0 -10
  34. 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,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
  }
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'),
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
- }
@@ -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>;
@@ -1,14 +1,19 @@
1
+ import { parseSource } from './tree-sitter-parser.js';
2
+ const CPP_EXTS = new Set(['.cpp', '.cc', '.cxx', '.hpp', '.hxx']);
1
3
  // Handles C (.c, .h) and C++ (.cpp, .cc, .cxx, .hpp, .hxx)
2
- export function extractCSymbols(sourceText, filePath) {
3
- const lines = sourceText.split('\n');
4
+ export async function extractCSymbols(sourceText, filePath) {
4
5
  const symbols = [];
5
6
  const dependencies = [];
6
7
  const relationships = [];
7
8
  const warnings = [];
8
- const typeStack = [];
9
- let braceDepth = 0;
10
- const addSymbol = (name, kind, lineNum, parentName) => {
11
- const id = [filePath, kind, parentName, name, lineNum]
9
+ if (!sourceText.trim()) {
10
+ return { symbols, dependencies, relationships, warnings };
11
+ }
12
+ const ext = filePath.substring(filePath.lastIndexOf('.'));
13
+ const grammar = CPP_EXTS.has(ext) ? 'cpp' : 'c';
14
+ const tree = await parseSource(sourceText, grammar);
15
+ const addSymbol = (name, kind, startLine, endLine, parentName) => {
16
+ const id = [filePath, kind, parentName, name, startLine]
12
17
  .filter(Boolean)
13
18
  .join('#');
14
19
  symbols.push({
@@ -16,8 +21,8 @@ export function extractCSymbols(sourceText, filePath) {
16
21
  name,
17
22
  kind,
18
23
  filePath,
19
- startLine: lineNum,
20
- endLine: lineNum,
24
+ startLine,
25
+ endLine,
21
26
  exported: false,
22
27
  parentName,
23
28
  });
@@ -30,67 +35,105 @@ export function extractCSymbols(sourceText, filePath) {
30
35
  confidence: 'high',
31
36
  });
32
37
  };
33
- for (let i = 0; i < lines.length; i++) {
34
- const line = lines[i];
35
- const lineNum = i + 1;
36
- const trimmed = line.trim();
37
- if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('*'))
38
- continue;
39
- braceDepth +=
40
- (line.match(/\{/g) ?? []).length - (line.match(/\}/g) ?? []).length;
41
- while (typeStack.length > 0 &&
42
- braceDepth < typeStack[typeStack.length - 1].braceDepth) {
43
- typeStack.pop();
44
- }
45
- // #include <...> or #include "..."
46
- const includeMatch = trimmed.match(/^#include\s+[<"]([^>"]+)[>"]/);
47
- if (includeMatch) {
48
- const specifier = includeMatch[1];
49
- const kind = trimmed.includes('"') ? 'local' : 'package';
50
- dependencies.push({ fromFile: filePath, specifier, kind });
51
- relationships.push({
52
- sourceType: 'file',
53
- sourceId: filePath,
54
- targetType: kind === 'local' ? 'file' : 'package',
55
- targetId: specifier,
56
- relationshipType: 'import',
57
- confidence: 'high',
58
- });
59
- continue;
38
+ function getFuncName(node) {
39
+ // function_definition has a function_declarator child which contains the identifier
40
+ const declarator = node.childForFieldName('declarator');
41
+ if (!declarator)
42
+ return null;
43
+ if (declarator.type === 'function_declarator') {
44
+ const nameNode = declarator.childForFieldName('declarator');
45
+ return nameNode?.text ?? null;
60
46
  }
61
- // class / struct (C++ with body — has name before {)
62
- const classMatch = trimmed.match(/\b(?:class|struct)\s+(\w+)\s*(?::[^{]*)?\s*\{/);
63
- if (classMatch) {
64
- addSymbol(classMatch[1], 'class', lineNum, typeStack[typeStack.length - 1]?.name);
65
- typeStack.push({ name: classMatch[1], braceDepth });
66
- continue;
47
+ // For pointer_declarator wrapping function_declarator
48
+ const funcDecl = declarator.descendantsOfType('function_declarator')[0];
49
+ if (funcDecl) {
50
+ const nameNode = funcDecl.childForFieldName('declarator');
51
+ return nameNode?.text ?? null;
67
52
  }
68
- // Function definition: returnType funcName( — must have ( and no ; on same line
69
- // Exclude preprocessor, declarations without body
70
- if (!trimmed.endsWith(';') && !trimmed.startsWith('#')) {
71
- const funcMatch = trimmed.match(/\b(\w+)\s*\((?:[^)]*)?\)\s*(?:const\s*)?(?:noexcept\s*)?(?:override\s*)?(?:final\s*)?\{?$/);
72
- // Filter out common false positives: if/for/while/switch/catch/else
73
- const CONTROL_FLOW = new Set([
74
- 'if',
75
- 'for',
76
- 'while',
77
- 'switch',
78
- 'catch',
79
- 'else',
80
- 'return',
81
- 'sizeof',
82
- 'typeof',
83
- ]);
84
- if (funcMatch &&
85
- !CONTROL_FLOW.has(funcMatch[1]) &&
86
- funcMatch[1] !== 'class' &&
87
- funcMatch[1] !== 'struct') {
88
- const parent = typeStack[typeStack.length - 1];
89
- const kind = parent ? 'method' : 'function';
90
- addSymbol(funcMatch[1], kind, lineNum, parent?.name);
91
- continue;
53
+ return null;
54
+ }
55
+ function walk(node, parentClassName) {
56
+ switch (node.type) {
57
+ case 'preproc_include': {
58
+ // #include <...> or #include "..."
59
+ const pathNode = node.namedChildren.find((c) => c.type === 'system_lib_string') ??
60
+ node.namedChildren.find((c) => c.type === 'string_literal');
61
+ if (pathNode) {
62
+ let specifier;
63
+ let kind;
64
+ if (pathNode.type === 'system_lib_string') {
65
+ // <iostream> — strip angle brackets
66
+ specifier = pathNode.text.replace(/^<|>$/g, '');
67
+ kind = 'package';
68
+ }
69
+ else {
70
+ // "myheader.h" — extract string content
71
+ const content = pathNode.namedChildren.find((c) => c.type === 'string_content');
72
+ specifier = content?.text ?? pathNode.text.replace(/^"|"$/g, '');
73
+ kind = 'local';
74
+ }
75
+ dependencies.push({ fromFile: filePath, specifier, kind });
76
+ relationships.push({
77
+ sourceType: 'file',
78
+ sourceId: filePath,
79
+ targetType: kind === 'local' ? 'file' : 'package',
80
+ targetId: specifier,
81
+ relationshipType: 'import',
82
+ confidence: 'high',
83
+ });
84
+ }
85
+ return;
86
+ }
87
+ case 'class_specifier':
88
+ case 'struct_specifier': {
89
+ const nameNode = node.childForFieldName('name');
90
+ if (nameNode) {
91
+ addSymbol(nameNode.text, 'class', node.startPosition.row + 1, node.endPosition.row + 1, parentClassName);
92
+ // Walk body for methods
93
+ const body = node.childForFieldName('body');
94
+ if (body) {
95
+ for (const child of body.namedChildren) {
96
+ walk(child, nameNode.text);
97
+ }
98
+ }
99
+ }
100
+ return;
92
101
  }
102
+ case 'function_definition': {
103
+ const name = getFuncName(node);
104
+ if (name) {
105
+ const kind = parentClassName
106
+ ? 'method'
107
+ : 'function';
108
+ addSymbol(name, kind, node.startPosition.row + 1, node.endPosition.row + 1, parentClassName);
109
+ }
110
+ return;
111
+ }
112
+ case 'declaration': {
113
+ // Could be a function declaration (prototype) inside a class
114
+ if (parentClassName) {
115
+ const declarator = node.childForFieldName('declarator');
116
+ if (declarator) {
117
+ const funcDecl = declarator.type === 'function_declarator'
118
+ ? declarator
119
+ : declarator.descendantsOfType('function_declarator')[0];
120
+ if (funcDecl) {
121
+ const nameNode = funcDecl.childForFieldName('declarator');
122
+ if (nameNode) {
123
+ addSymbol(nameNode.text, 'method', node.startPosition.row + 1, node.endPosition.row + 1, parentClassName);
124
+ }
125
+ return;
126
+ }
127
+ }
128
+ }
129
+ break;
130
+ }
131
+ }
132
+ for (const child of node.namedChildren) {
133
+ walk(child, parentClassName);
93
134
  }
94
135
  }
136
+ walk(tree.rootNode);
137
+ tree.delete();
95
138
  return { symbols, dependencies, relationships, warnings };
96
139
  }
@@ -1,2 +1,2 @@
1
1
  import type { SymbolExtractionResult } from './ts-symbol-extractor.js';
2
- export declare function extractCSharpSymbols(sourceText: string, filePath: string): SymbolExtractionResult;
2
+ export declare function extractCSharpSymbols(sourceText: string, filePath: string): Promise<SymbolExtractionResult>;