agentmap 0.8.0 → 0.9.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 (83) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +24 -0
  3. package/dist/cli.js +37 -12
  4. package/dist/cli.js.map +1 -1
  5. package/dist/extract/definitions.js +12 -12
  6. package/dist/extract/definitions.js.map +1 -1
  7. package/dist/extract/definitions.test.js +30 -259
  8. package/dist/extract/definitions.test.js.map +1 -1
  9. package/dist/extract/git-status.d.ts +7 -2
  10. package/dist/extract/git-status.d.ts.map +1 -1
  11. package/dist/extract/git-status.js +12 -18
  12. package/dist/extract/git-status.js.map +1 -1
  13. package/dist/extract/markdown.js +1 -1
  14. package/dist/extract/markdown.test.js +3 -3
  15. package/dist/extract/markdown.test.js.map +1 -1
  16. package/dist/extract/marker.js +1 -1
  17. package/dist/extract/marker.test.js +4 -4
  18. package/dist/extract/marker.test.js.map +1 -1
  19. package/dist/index.d.ts +4 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5 -4
  22. package/dist/index.js.map +1 -1
  23. package/dist/logger.d.ts +10 -0
  24. package/dist/logger.d.ts.map +1 -0
  25. package/dist/logger.js +41 -0
  26. package/dist/logger.js.map +1 -0
  27. package/dist/map/builder.d.ts.map +1 -1
  28. package/dist/map/builder.js +23 -12
  29. package/dist/map/builder.js.map +1 -1
  30. package/dist/map/builder.test.d.ts +2 -0
  31. package/dist/map/builder.test.d.ts.map +1 -0
  32. package/dist/map/builder.test.js +66 -0
  33. package/dist/map/builder.test.js.map +1 -0
  34. package/dist/map/truncate.d.ts +7 -3
  35. package/dist/map/truncate.d.ts.map +1 -1
  36. package/dist/map/truncate.js +80 -11
  37. package/dist/map/truncate.js.map +1 -1
  38. package/dist/scanner.d.ts.map +1 -1
  39. package/dist/scanner.js +164 -65
  40. package/dist/scanner.js.map +1 -1
  41. package/dist/scanner.test.d.ts +2 -0
  42. package/dist/scanner.test.d.ts.map +1 -0
  43. package/dist/scanner.test.js +84 -0
  44. package/dist/scanner.test.js.map +1 -0
  45. package/dist/test-helpers/git-test-helpers.d.ts +13 -0
  46. package/dist/test-helpers/git-test-helpers.d.ts.map +1 -0
  47. package/dist/test-helpers/git-test-helpers.js +48 -0
  48. package/dist/test-helpers/git-test-helpers.js.map +1 -0
  49. package/dist/types.d.ts +15 -1
  50. package/dist/types.d.ts.map +1 -1
  51. package/package.json +15 -3
  52. package/src/cli.ts +164 -0
  53. package/src/extract/definitions.test.ts +2040 -0
  54. package/src/extract/definitions.ts +379 -0
  55. package/src/extract/git-status.test.ts +507 -0
  56. package/src/extract/git-status.ts +359 -0
  57. package/src/extract/markdown.test.ts +159 -0
  58. package/src/extract/markdown.ts +202 -0
  59. package/src/extract/marker.test.ts +566 -0
  60. package/src/extract/marker.ts +398 -0
  61. package/src/extract/submodules.test.ts +95 -0
  62. package/src/extract/submodules.ts +269 -0
  63. package/src/extract/utils.ts +27 -0
  64. package/src/index.ts +106 -0
  65. package/src/languages/cpp.ts +129 -0
  66. package/src/languages/go.ts +72 -0
  67. package/src/languages/index.ts +231 -0
  68. package/src/languages/javascript.ts +33 -0
  69. package/src/languages/python.ts +41 -0
  70. package/src/languages/rust.ts +72 -0
  71. package/src/languages/typescript.ts +74 -0
  72. package/src/languages/zig.ts +106 -0
  73. package/src/logger.ts +55 -0
  74. package/src/map/builder.test.ts +72 -0
  75. package/src/map/builder.ts +175 -0
  76. package/src/map/truncate.ts +188 -0
  77. package/src/map/yaml.ts +66 -0
  78. package/src/parser/index.ts +53 -0
  79. package/src/parser/languages.ts +64 -0
  80. package/src/scanner.test.ts +95 -0
  81. package/src/scanner.ts +364 -0
  82. package/src/test-helpers/git-test-helpers.ts +62 -0
  83. package/src/types.ts +191 -0
@@ -0,0 +1,269 @@
1
+ // Detect git submodules, their branches, and dirty state.
2
+ // Uses multiple git commands with safeExec for cross-platform reliability.
3
+
4
+ import { execSync } from 'child_process'
5
+ import { existsSync } from 'fs'
6
+ import { join } from 'path'
7
+ import type { SubmoduleInfo } from '../types.js'
8
+
9
+ /**
10
+ * Safely execute a git command, returning empty string on any error
11
+ */
12
+ function safeExec(cmd: string, dir: string): string {
13
+ try {
14
+ return execSync(cmd, {
15
+ cwd: dir,
16
+ encoding: 'utf8',
17
+ maxBuffer: 1024 * 1024 * 10, // 10MB
18
+ stdio: ['pipe', 'pipe', 'pipe'],
19
+ })
20
+ } catch {
21
+ return ''
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Detect submodule paths by looking for gitlink entries (mode 160000) in git ls-files --stage.
27
+ * This is the most reliable method - works even if submodules are not initialized.
28
+ */
29
+ function detectSubmodulePaths(dir: string): Set<string> {
30
+ const output = safeExec('git ls-files --stage', dir)
31
+ const paths = new Set<string>()
32
+
33
+ if (!output.trim()) return paths
34
+
35
+ for (const line of output.split('\n')) {
36
+ if (!line.trim()) continue
37
+ // Format: <mode> <hash> <stage>\t<path>
38
+ // Submodules have mode 160000
39
+ if (line.startsWith('160000 ')) {
40
+ const tabIdx = line.indexOf('\t')
41
+ if (tabIdx !== -1) {
42
+ paths.add(line.slice(tabIdx + 1).trim())
43
+ }
44
+ }
45
+ }
46
+
47
+ return paths
48
+ }
49
+
50
+ /**
51
+ * Parse `git submodule status --recursive` output.
52
+ * Format: " <sha> <path> (<describe>)" or "-<sha> <path>" (uninitialized) or "+<sha> <path> (<describe>)" (out of sync)
53
+ */
54
+ function parseSubmoduleStatus(dir: string): Map<string, { commit: string; initialized: boolean }> {
55
+ const output = safeExec('git submodule status --recursive', dir)
56
+ const result = new Map<string, { commit: string; initialized: boolean }>()
57
+
58
+ if (!output.trim()) return result
59
+
60
+ for (const line of output.split('\n')) {
61
+ if (!line.trim()) continue
62
+
63
+ // First char: ' ' = OK, '-' = uninitialized, '+' = out of sync, 'U' = merge conflict
64
+ const prefix = line[0]
65
+ const initialized = prefix !== '-'
66
+
67
+ // Rest: <sha> <path> [(<describe>)]
68
+ const rest = line.slice(1).trim()
69
+ const spaceIdx = rest.indexOf(' ')
70
+ if (spaceIdx === -1) continue
71
+
72
+ const commit = rest.slice(0, spaceIdx)
73
+ let path = rest.slice(spaceIdx + 1)
74
+
75
+ // Remove optional describe suffix: " (v1.2.3)" or " (heads/main)"
76
+ const parenIdx = path.lastIndexOf(' (')
77
+ if (parenIdx !== -1) {
78
+ path = path.slice(0, parenIdx)
79
+ }
80
+
81
+ result.set(path.trim(), {
82
+ commit: commit.slice(0, 7), // short SHA
83
+ initialized,
84
+ })
85
+ }
86
+
87
+ return result
88
+ }
89
+
90
+ /**
91
+ * Get the checked-out branch for each initialized submodule.
92
+ * Returns a map of submodule path -> branch name (or undefined for detached HEAD).
93
+ */
94
+ function getSubmoduleBranches(dir: string): Map<string, string | undefined> {
95
+ // Use foreach to run git symbolic-ref in each submodule
96
+ const output = safeExec(
97
+ 'git submodule foreach --quiet --recursive \'echo "$sm_path|$(git symbolic-ref --short -q HEAD 2>/dev/null || echo __detached__)"\'',
98
+ dir
99
+ )
100
+ const result = new Map<string, string | undefined>()
101
+
102
+ if (!output.trim()) return result
103
+
104
+ for (const line of output.split('\n')) {
105
+ if (!line.trim()) continue
106
+ const pipeIdx = line.indexOf('|')
107
+ if (pipeIdx === -1) continue
108
+
109
+ const path = line.slice(0, pipeIdx).trim()
110
+ const branch = line.slice(pipeIdx + 1).trim()
111
+
112
+ result.set(path, branch === '__detached__' || branch === '' ? undefined : branch)
113
+ }
114
+
115
+ return result
116
+ }
117
+
118
+ /**
119
+ * Get submodule URLs from .gitmodules config.
120
+ * Returns a map of submodule path -> URL.
121
+ */
122
+ function getSubmoduleUrls(dir: string): Map<string, string> {
123
+ // Check if .gitmodules exists first
124
+ if (!existsSync(join(dir, '.gitmodules'))) {
125
+ return new Map()
126
+ }
127
+
128
+ const output = safeExec(
129
+ 'git config -f .gitmodules --get-regexp "^submodule\\..*\\.url$"',
130
+ dir
131
+ )
132
+ const pathOutput = safeExec(
133
+ 'git config -f .gitmodules --get-regexp "^submodule\\..*\\.path$"',
134
+ dir
135
+ )
136
+
137
+ // Build name -> url map
138
+ const nameToUrl = new Map<string, string>()
139
+ for (const line of output.split('\n')) {
140
+ if (!line.trim()) continue
141
+ // Format: submodule.<name>.url <url>
142
+ const match = line.match(/^submodule\.(.+)\.url\s+(.+)$/)
143
+ if (match) {
144
+ nameToUrl.set(match[1], match[2].trim())
145
+ }
146
+ }
147
+
148
+ // Build path -> url map via name -> path mapping
149
+ const result = new Map<string, string>()
150
+ for (const line of pathOutput.split('\n')) {
151
+ if (!line.trim()) continue
152
+ // Format: submodule.<name>.path <path>
153
+ const match = line.match(/^submodule\.(.+)\.path\s+(.+)$/)
154
+ if (match) {
155
+ const name = match[1]
156
+ const path = match[2].trim()
157
+ const url = nameToUrl.get(name)
158
+ if (url) {
159
+ result.set(path, url)
160
+ }
161
+ }
162
+ }
163
+
164
+ return result
165
+ }
166
+
167
+ /**
168
+ * Check which submodules have dirty working trees.
169
+ * Uses git status --porcelain=2 to detect modified submodule content.
170
+ *
171
+ * Porcelain v2 "changed entry" format (type 1):
172
+ * 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
173
+ * Fields are space-separated but the path (field 9) may contain spaces,
174
+ * so we split on the first 8 spaces and take the remainder as the path.
175
+ */
176
+ function getDirtySubmodules(dir: string, submodulePaths: Set<string>): Set<string> {
177
+ if (submodulePaths.size === 0) return new Set()
178
+
179
+ const output = safeExec('git status --porcelain=2', dir)
180
+ const dirty = new Set<string>()
181
+
182
+ if (!output.trim()) return dirty
183
+
184
+ for (const line of output.split('\n')) {
185
+ if (!line.trim()) continue
186
+
187
+ if (line.startsWith('1 ') || line.startsWith('2 ')) {
188
+ // Split only the first 8 spaces to preserve path with spaces
189
+ const fields = splitNFields(line, 9)
190
+ if (fields.length < 9) continue
191
+
192
+ // fields[2] is the sub field (N... for non-submodule, S... for submodule)
193
+ const subField = fields[2]
194
+ // fields[8] is the full path (may contain spaces)
195
+ const path = fields[8]
196
+
197
+ if (subField && subField.startsWith('S') && subField !== 'S...' && submodulePaths.has(path)) {
198
+ dirty.add(path)
199
+ }
200
+ }
201
+ }
202
+
203
+ return dirty
204
+ }
205
+
206
+ /**
207
+ * Split a string into exactly N fields by spaces.
208
+ * The last field gets the remainder (preserving spaces in paths).
209
+ */
210
+ function splitNFields(str: string, n: number): string[] {
211
+ const fields: string[] = []
212
+ let pos = 0
213
+ for (let i = 0; i < n - 1 && pos < str.length; i++) {
214
+ const spaceIdx = str.indexOf(' ', pos)
215
+ if (spaceIdx === -1) break
216
+ fields.push(str.slice(pos, spaceIdx))
217
+ pos = spaceIdx + 1
218
+ }
219
+ if (pos < str.length) {
220
+ fields.push(str.slice(pos))
221
+ }
222
+ return fields
223
+ }
224
+
225
+ /**
226
+ * Get all submodule info for a repository.
227
+ * Combines detection, status, branches, URLs, and dirty state.
228
+ */
229
+ export function getSubmodules(dir: string): SubmoduleInfo[] {
230
+ // Step 1: Detect all submodule paths (works even if uninitialized)
231
+ const submodulePaths = detectSubmodulePaths(dir)
232
+ if (submodulePaths.size === 0) return []
233
+
234
+ // Step 2: Get status (commit SHAs and initialized state)
235
+ const statusMap = parseSubmoduleStatus(dir)
236
+
237
+ // Step 3: Get branches for initialized submodules
238
+ const branchMap = getSubmoduleBranches(dir)
239
+
240
+ // Step 4: Get URLs from .gitmodules
241
+ const urlMap = getSubmoduleUrls(dir)
242
+
243
+ // Step 5: Check dirty state
244
+ const dirtySet = getDirtySubmodules(dir, submodulePaths)
245
+
246
+ // Combine all info
247
+ const submodules: SubmoduleInfo[] = []
248
+ for (const path of submodulePaths) {
249
+ const status = statusMap.get(path)
250
+ submodules.push({
251
+ path,
252
+ commit: status?.commit ?? 'unknown',
253
+ branch: branchMap.get(path),
254
+ url: urlMap.get(path),
255
+ dirty: dirtySet.has(path),
256
+ initialized: status?.initialized ?? false,
257
+ })
258
+ }
259
+
260
+ return submodules
261
+ }
262
+
263
+ /**
264
+ * Get the set of submodule paths for filtering from diff output.
265
+ * Lightweight version that only detects paths without full info.
266
+ */
267
+ export function getSubmodulePaths(dir: string): Set<string> {
268
+ return detectSubmodulePaths(dir)
269
+ }
@@ -0,0 +1,27 @@
1
+ // Shared utilities for file extraction.
2
+
3
+ import { open } from 'fs/promises'
4
+
5
+ /**
6
+ * Read the first N lines of a file.
7
+ * Returns null if file cannot be read (ENOENT, permission denied, etc.)
8
+ */
9
+ export async function readFirstLines(filepath: string, maxLines: number): Promise<string | null> {
10
+ let handle
11
+ try {
12
+ handle = await open(filepath, 'r')
13
+ } catch {
14
+ // File doesn't exist or can't be opened - skip silently
15
+ return null
16
+ }
17
+ try {
18
+ // Read enough bytes for ~maxLines lines (generous estimate)
19
+ const buffer = Buffer.alloc(maxLines * 200)
20
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0)
21
+ const content = buffer.toString('utf8', 0, bytesRead)
22
+ const lines = content.split('\n').slice(0, maxLines)
23
+ return lines.join('\n')
24
+ } finally {
25
+ await handle.close()
26
+ }
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,106 @@
1
+ // Library exports for programmatic usage.
2
+
3
+ import { execSync } from 'child_process'
4
+ import { homedir } from 'os'
5
+ import { resolve } from 'path'
6
+ import { scanDirectory } from './scanner.js'
7
+ import { buildMap, getRootName } from './map/builder.js'
8
+ import { toYaml } from './map/yaml.js'
9
+ import { truncateMap } from './map/truncate.js'
10
+ import type { GenerateOptions, MapNode } from './types.js'
11
+
12
+ export { toYaml } from './map/yaml.js'
13
+ export { truncateMap, truncateDefs } from './map/truncate.js'
14
+ export type { TruncateOptions } from './map/truncate.js'
15
+ export { createConsoleLogger, createNoopLogger, formatLogMessage } from './logger.js'
16
+ export type { Logger } from './logger.js'
17
+
18
+ export type {
19
+ DefEntry,
20
+ Definition,
21
+ DefinitionDiff,
22
+ DefinitionStatus,
23
+ DiffHunk,
24
+ FileDiffStats,
25
+ FileEntry,
26
+ FileDiff,
27
+ FileResult,
28
+ GenerateOptions,
29
+ Language,
30
+ MapNode,
31
+ MarkerResult,
32
+ SubmoduleEntry,
33
+ SubmoduleInfo,
34
+ SubmoduleNode,
35
+ } from './types.js'
36
+
37
+ /**
38
+ * Check if directory is inside a git repository
39
+ */
40
+ export function isGitRepo(dir: string): boolean {
41
+ try {
42
+ execSync('git rev-parse --git-dir', { cwd: dir, stdio: 'ignore' })
43
+ return true
44
+ } catch {
45
+ return false
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Check if directory is the user's home directory
51
+ */
52
+ export function isHomeDirectory(dir: string): boolean {
53
+ const home = homedir()
54
+ const resolved = resolve(dir)
55
+ return resolved === home
56
+ }
57
+
58
+ const DEFAULT_MAX_DEFS = 25
59
+
60
+ /**
61
+ * Generate a map object from a directory
62
+ * Returns empty map if not in a git repo or if directory is home
63
+ */
64
+ export async function generateMap(options: GenerateOptions = {}): Promise<MapNode> {
65
+ const dir = resolve(options.dir ?? '.')
66
+ const rootName = getRootName(dir)
67
+
68
+ // Safety checks - return empty map
69
+ if (!isGitRepo(dir) || isHomeDirectory(dir)) {
70
+ return { [rootName]: {} }
71
+ }
72
+
73
+ const { files, submodules } = await scanDirectory({ ...options, dir })
74
+ const map = buildMap(files, rootName, submodules)
75
+
76
+ // Apply truncation
77
+ const maxDefs = options.maxDefs ?? DEFAULT_MAX_DEFS
78
+ return truncateMap(map, { maxDefs, maxDescChars: options.maxDescChars })
79
+ }
80
+
81
+ /**
82
+ * Generate a YAML string map from a directory
83
+ * Returns empty string if not in a git repo or if directory is home
84
+ */
85
+ export async function generateMapYaml(options: GenerateOptions = {}): Promise<string> {
86
+ const dir = resolve(options.dir ?? '.')
87
+
88
+ // Safety checks - return empty
89
+ if (!isGitRepo(dir) || isHomeDirectory(dir)) {
90
+ return ''
91
+ }
92
+
93
+ const { files, submodules } = await scanDirectory({ ...options, dir })
94
+
95
+ if (files.length === 0 && submodules.length === 0) {
96
+ return ''
97
+ }
98
+
99
+ const rootName = getRootName(dir)
100
+ const map = buildMap(files, rootName, submodules)
101
+
102
+ // Apply truncation
103
+ const maxDefs = options.maxDefs ?? DEFAULT_MAX_DEFS
104
+ const truncated = truncateMap(map, { maxDefs, maxDescChars: options.maxDescChars })
105
+ return toYaml(truncated)
106
+ }
@@ -0,0 +1,129 @@
1
+ // C/C++ language support for agentmap.
2
+
3
+ import type { SyntaxNode } from '../types.js'
4
+
5
+ export const id = 'cpp' as const
6
+ export const extensions = ['.c', '.h', '.cpp', '.cc', '.cxx', '.hpp', '.hxx']
7
+ export const grammar = 'tree-sitter-cpp/tree-sitter-cpp.wasm'
8
+
9
+ // AST node types
10
+ export const FUNCTION_TYPES = ['function_definition']
11
+ export const CLASS_TYPES = ['class_specifier']
12
+ export const STRUCT_TYPES = ['struct_specifier']
13
+ export const TRAIT_TYPES: string[] = []
14
+ export const INTERFACE_TYPES: string[] = []
15
+ export const TYPE_TYPES = ['type_definition', 'alias_declaration']
16
+ export const ENUM_TYPES = ['enum_specifier']
17
+ export const CONST_TYPES = ['declaration']
18
+
19
+ /**
20
+ * C++ doesn't have module exports in the traditional sense
21
+ */
22
+ export function isExported(_node: SyntaxNode): boolean {
23
+ return false
24
+ }
25
+
26
+ /**
27
+ * Check if a C++ node has extern storage class or is in linkage_specification
28
+ */
29
+ export function isExtern(node: SyntaxNode): boolean {
30
+ if (node.type === 'declaration' || node.type === 'function_definition') {
31
+ for (let i = 0; i < node.childCount; i++) {
32
+ const child = node.child(i)
33
+ if (child?.type === 'storage_class_specifier' && child.text === 'extern') {
34
+ return true
35
+ }
36
+ }
37
+ }
38
+ return false
39
+ }
40
+
41
+ /**
42
+ * Extract name from a C++ node
43
+ */
44
+ export function extractName(node: SyntaxNode): string | null {
45
+ if (node.type === 'function_definition') {
46
+ const declarator = node.childForFieldName('declarator')
47
+ if (declarator) {
48
+ const funcDecl = declarator.type === 'function_declarator'
49
+ ? declarator
50
+ : findChild(declarator, 'function_declarator')
51
+ if (funcDecl) {
52
+ const innerDecl = funcDecl.childForFieldName('declarator')
53
+ if (innerDecl?.type === 'identifier') {
54
+ return innerDecl.text
55
+ }
56
+ if (innerDecl?.type === 'qualified_identifier') {
57
+ const name = innerDecl.childForFieldName('name')
58
+ return name?.text ?? null
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ if (node.type === 'struct_specifier' || node.type === 'class_specifier' || node.type === 'enum_specifier') {
65
+ const nameNode = node.childForFieldName('name')
66
+ if (nameNode) {
67
+ return nameNode.text
68
+ }
69
+ for (let i = 0; i < node.childCount; i++) {
70
+ const child = node.child(i)
71
+ if (child?.type === 'type_identifier') {
72
+ return child.text
73
+ }
74
+ }
75
+ }
76
+
77
+ if (node.type === 'type_definition') {
78
+ const declarator = node.childForFieldName('declarator')
79
+ if (declarator?.type === 'type_identifier') {
80
+ return declarator.text
81
+ }
82
+ }
83
+
84
+ if (node.type === 'alias_declaration') {
85
+ const nameNode = node.childForFieldName('name')
86
+ return nameNode?.text ?? null
87
+ }
88
+
89
+ if (node.type === 'declaration') {
90
+ const declarator = node.childForFieldName('declarator')
91
+ if (declarator) {
92
+ if (declarator.type === 'identifier') {
93
+ return declarator.text
94
+ }
95
+ if (declarator.type === 'init_declarator') {
96
+ const innerDecl = declarator.childForFieldName('declarator')
97
+ if (innerDecl?.type === 'identifier') {
98
+ return innerDecl.text
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ for (let i = 0; i < node.childCount; i++) {
105
+ const child = node.child(i)
106
+ if (child?.type === 'identifier' || child?.type === 'type_identifier') {
107
+ return child.text
108
+ }
109
+ }
110
+ return null
111
+ }
112
+
113
+ /**
114
+ * Extract name from const declaration
115
+ */
116
+ export function extractConstName(node: SyntaxNode): string | null {
117
+ return extractName(node)
118
+ }
119
+
120
+ function findChild(node: SyntaxNode, type: string): SyntaxNode | null {
121
+ for (let i = 0; i < node.childCount; i++) {
122
+ const child = node.child(i)
123
+ if (child?.type === type) return child
124
+ }
125
+ return null
126
+ }
127
+
128
+ // Comment handling
129
+ export const commentPrefixes = ['//', '/*']
@@ -0,0 +1,72 @@
1
+ // Go language support for agentmap.
2
+
3
+ import type { SyntaxNode } from '../types.js'
4
+
5
+ export const id = 'go' as const
6
+ export const extensions = ['.go']
7
+ export const grammar = 'tree-sitter-go/tree-sitter-go.wasm'
8
+
9
+ // AST node types
10
+ export const FUNCTION_TYPES = ['function_declaration', 'method_declaration']
11
+ export const CLASS_TYPES: string[] = [] // Go has structs, not classes
12
+ export const STRUCT_TYPES: string[] = [] // Go structs handled via type_declaration
13
+ export const TRAIT_TYPES: string[] = []
14
+ export const INTERFACE_TYPES: string[] = [] // Go interfaces handled via type_declaration
15
+ export const TYPE_TYPES = ['type_declaration']
16
+ export const ENUM_TYPES: string[] = []
17
+ export const CONST_TYPES = ['const_declaration', 'var_declaration']
18
+
19
+ /**
20
+ * Check if a Go identifier is exported (starts with uppercase)
21
+ */
22
+ export function isExported(_node: SyntaxNode, name?: string): boolean {
23
+ if (!name || name.length === 0) return false
24
+ const firstChar = name.charAt(0)
25
+ return firstChar >= 'A' && firstChar <= 'Z'
26
+ }
27
+
28
+ /**
29
+ * Extract name from a Go node
30
+ */
31
+ export function extractName(node: SyntaxNode): string | null {
32
+ if (node.type === 'type_declaration') {
33
+ const spec = findChild(node, 'type_spec')
34
+ if (spec) {
35
+ const nameNode = spec.childForFieldName('name')
36
+ return nameNode?.text ?? null
37
+ }
38
+ }
39
+
40
+ for (let i = 0; i < node.childCount; i++) {
41
+ const child = node.child(i)
42
+ if (child?.type === 'identifier' || child?.type === 'field_identifier') {
43
+ return child.text
44
+ }
45
+ }
46
+ return null
47
+ }
48
+
49
+ /**
50
+ * Extract name from const/var declaration
51
+ */
52
+ export function extractConstName(node: SyntaxNode): string | null {
53
+ for (let i = 0; i < node.childCount; i++) {
54
+ const child = node.child(i)
55
+ if (child?.type === 'const_spec' || child?.type === 'var_spec') {
56
+ const nameNode = child.childForFieldName('name')
57
+ return nameNode?.text ?? null
58
+ }
59
+ }
60
+ return null
61
+ }
62
+
63
+ function findChild(node: SyntaxNode, type: string): SyntaxNode | null {
64
+ for (let i = 0; i < node.childCount; i++) {
65
+ const child = node.child(i)
66
+ if (child?.type === type) return child
67
+ }
68
+ return null
69
+ }
70
+
71
+ // Comment handling
72
+ export const commentPrefixes = ['//', '/*']