agentmap 0.7.1 → 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 (96) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +24 -0
  3. package/dist/cli.js +44 -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 +11 -4
  10. package/dist/extract/git-status.d.ts.map +1 -1
  11. package/dist/extract/git-status.js +21 -16
  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/extract/submodules.d.ts +12 -0
  20. package/dist/extract/submodules.d.ts.map +1 -0
  21. package/dist/extract/submodules.js +234 -0
  22. package/dist/extract/submodules.js.map +1 -0
  23. package/dist/extract/submodules.test.d.ts +2 -0
  24. package/dist/extract/submodules.test.d.ts.map +1 -0
  25. package/dist/extract/submodules.test.js +84 -0
  26. package/dist/extract/submodules.test.js.map +1 -0
  27. package/dist/index.d.ts +4 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +10 -9
  30. package/dist/index.js.map +1 -1
  31. package/dist/logger.d.ts +10 -0
  32. package/dist/logger.d.ts.map +1 -0
  33. package/dist/logger.js +41 -0
  34. package/dist/logger.js.map +1 -0
  35. package/dist/map/builder.d.ts +3 -3
  36. package/dist/map/builder.d.ts.map +1 -1
  37. package/dist/map/builder.js +59 -9
  38. package/dist/map/builder.js.map +1 -1
  39. package/dist/map/builder.test.d.ts +2 -0
  40. package/dist/map/builder.test.d.ts.map +1 -0
  41. package/dist/map/builder.test.js +66 -0
  42. package/dist/map/builder.test.js.map +1 -0
  43. package/dist/map/truncate.d.ts +7 -3
  44. package/dist/map/truncate.d.ts.map +1 -1
  45. package/dist/map/truncate.js +90 -9
  46. package/dist/map/truncate.js.map +1 -1
  47. package/dist/map/yaml.d.ts.map +1 -1
  48. package/dist/map/yaml.js +13 -3
  49. package/dist/map/yaml.js.map +1 -1
  50. package/dist/scanner.d.ts +9 -2
  51. package/dist/scanner.d.ts.map +1 -1
  52. package/dist/scanner.js +172 -49
  53. package/dist/scanner.js.map +1 -1
  54. package/dist/scanner.test.d.ts +2 -0
  55. package/dist/scanner.test.d.ts.map +1 -0
  56. package/dist/scanner.test.js +84 -0
  57. package/dist/scanner.test.js.map +1 -0
  58. package/dist/test-helpers/git-test-helpers.d.ts +13 -0
  59. package/dist/test-helpers/git-test-helpers.d.ts.map +1 -0
  60. package/dist/test-helpers/git-test-helpers.js +48 -0
  61. package/dist/test-helpers/git-test-helpers.js.map +1 -0
  62. package/dist/types.d.ts +42 -2
  63. package/dist/types.d.ts.map +1 -1
  64. package/package.json +15 -3
  65. package/src/cli.ts +164 -0
  66. package/src/extract/definitions.test.ts +2040 -0
  67. package/src/extract/definitions.ts +379 -0
  68. package/src/extract/git-status.test.ts +507 -0
  69. package/src/extract/git-status.ts +359 -0
  70. package/src/extract/markdown.test.ts +159 -0
  71. package/src/extract/markdown.ts +202 -0
  72. package/src/extract/marker.test.ts +566 -0
  73. package/src/extract/marker.ts +398 -0
  74. package/src/extract/submodules.test.ts +95 -0
  75. package/src/extract/submodules.ts +269 -0
  76. package/src/extract/utils.ts +27 -0
  77. package/src/index.ts +106 -0
  78. package/src/languages/cpp.ts +129 -0
  79. package/src/languages/go.ts +72 -0
  80. package/src/languages/index.ts +231 -0
  81. package/src/languages/javascript.ts +33 -0
  82. package/src/languages/python.ts +41 -0
  83. package/src/languages/rust.ts +72 -0
  84. package/src/languages/typescript.ts +74 -0
  85. package/src/languages/zig.ts +106 -0
  86. package/src/logger.ts +55 -0
  87. package/src/map/builder.test.ts +72 -0
  88. package/src/map/builder.ts +175 -0
  89. package/src/map/truncate.ts +188 -0
  90. package/src/map/yaml.ts +66 -0
  91. package/src/parser/index.ts +53 -0
  92. package/src/parser/languages.ts +64 -0
  93. package/src/scanner.test.ts +95 -0
  94. package/src/scanner.ts +364 -0
  95. package/src/test-helpers/git-test-helpers.ts +62 -0
  96. package/src/types.ts +191 -0
@@ -0,0 +1,231 @@
1
+ // Language registry for agentmap.
2
+ // Aggregates all language-specific implementations.
3
+
4
+ import type { Language, SyntaxNode, DefinitionType } from '../types.js'
5
+
6
+ import * as typescript from './typescript.js'
7
+ import * as javascript from './javascript.js'
8
+ import * as python from './python.js'
9
+ import * as rust from './rust.js'
10
+ import * as go from './go.js'
11
+ import * as zig from './zig.js'
12
+ import * as cpp from './cpp.js'
13
+
14
+ // Re-export language modules for direct access
15
+ export { typescript, javascript, python, rust, go, zig, cpp }
16
+
17
+ /**
18
+ * All registered languages
19
+ */
20
+ export const languages = {
21
+ typescript,
22
+ javascript,
23
+ python,
24
+ rust,
25
+ go,
26
+ zig,
27
+ cpp,
28
+ } as const
29
+
30
+ /**
31
+ * File extension to language mapping
32
+ */
33
+ export const LANGUAGE_EXTENSIONS: Record<string, Language> = {
34
+ ...Object.fromEntries(typescript.extensions.map(e => [e, 'typescript' as const])),
35
+ ...Object.fromEntries(javascript.extensions.map(e => [e, 'javascript' as const])),
36
+ ...Object.fromEntries(python.extensions.map(e => [e, 'python' as const])),
37
+ ...Object.fromEntries(rust.extensions.map(e => [e, 'rust' as const])),
38
+ ...Object.fromEntries(go.extensions.map(e => [e, 'go' as const])),
39
+ ...Object.fromEntries(zig.extensions.map(e => [e, 'zig' as const])),
40
+ ...Object.fromEntries(cpp.extensions.map(e => [e, 'cpp' as const])),
41
+ }
42
+
43
+ /**
44
+ * Grammar paths per language
45
+ */
46
+ export const GRAMMAR_PATHS: Record<Language, string> = {
47
+ typescript: typescript.grammar,
48
+ javascript: javascript.grammar,
49
+ python: python.grammar,
50
+ rust: rust.grammar,
51
+ go: go.grammar,
52
+ zig: zig.grammar,
53
+ cpp: cpp.grammar,
54
+ }
55
+
56
+ /**
57
+ * Node types that represent functions per language
58
+ */
59
+ export const FUNCTION_TYPES: Record<Language, string[]> = {
60
+ typescript: typescript.FUNCTION_TYPES,
61
+ javascript: javascript.FUNCTION_TYPES,
62
+ python: python.FUNCTION_TYPES,
63
+ rust: rust.FUNCTION_TYPES,
64
+ go: go.FUNCTION_TYPES,
65
+ zig: zig.FUNCTION_TYPES,
66
+ cpp: cpp.FUNCTION_TYPES,
67
+ }
68
+
69
+ /**
70
+ * Node types that represent classes per language
71
+ */
72
+ export const CLASS_TYPES: Record<Language, string[]> = {
73
+ typescript: typescript.CLASS_TYPES,
74
+ javascript: javascript.CLASS_TYPES,
75
+ python: python.CLASS_TYPES,
76
+ rust: rust.CLASS_TYPES,
77
+ go: go.CLASS_TYPES,
78
+ zig: zig.CLASS_TYPES,
79
+ cpp: cpp.CLASS_TYPES,
80
+ }
81
+
82
+ /**
83
+ * Node types that represent structs per language
84
+ */
85
+ export const STRUCT_TYPES: Record<Language, string[]> = {
86
+ typescript: typescript.STRUCT_TYPES,
87
+ javascript: javascript.STRUCT_TYPES,
88
+ python: python.STRUCT_TYPES,
89
+ rust: rust.STRUCT_TYPES,
90
+ go: go.STRUCT_TYPES,
91
+ zig: zig.STRUCT_TYPES,
92
+ cpp: cpp.STRUCT_TYPES,
93
+ }
94
+
95
+ /**
96
+ * Node types that represent traits per language
97
+ */
98
+ export const TRAIT_TYPES: Record<Language, string[]> = {
99
+ typescript: typescript.TRAIT_TYPES,
100
+ javascript: javascript.TRAIT_TYPES,
101
+ python: python.TRAIT_TYPES,
102
+ rust: rust.TRAIT_TYPES,
103
+ go: go.TRAIT_TYPES,
104
+ zig: zig.TRAIT_TYPES,
105
+ cpp: cpp.TRAIT_TYPES,
106
+ }
107
+
108
+ /**
109
+ * Node types that represent interfaces per language
110
+ */
111
+ export const INTERFACE_TYPES: Record<Language, string[]> = {
112
+ typescript: typescript.INTERFACE_TYPES,
113
+ javascript: javascript.INTERFACE_TYPES,
114
+ python: python.INTERFACE_TYPES,
115
+ rust: rust.INTERFACE_TYPES,
116
+ go: go.INTERFACE_TYPES,
117
+ zig: zig.INTERFACE_TYPES,
118
+ cpp: cpp.INTERFACE_TYPES,
119
+ }
120
+
121
+ /**
122
+ * Node types that represent type aliases per language
123
+ */
124
+ export const TYPE_TYPES: Record<Language, string[]> = {
125
+ typescript: typescript.TYPE_TYPES,
126
+ javascript: javascript.TYPE_TYPES,
127
+ python: python.TYPE_TYPES,
128
+ rust: rust.TYPE_TYPES,
129
+ go: go.TYPE_TYPES,
130
+ zig: zig.TYPE_TYPES,
131
+ cpp: cpp.TYPE_TYPES,
132
+ }
133
+
134
+ /**
135
+ * Node types that represent enums per language
136
+ */
137
+ export const ENUM_TYPES: Record<Language, string[]> = {
138
+ typescript: typescript.ENUM_TYPES,
139
+ javascript: javascript.ENUM_TYPES,
140
+ python: python.ENUM_TYPES,
141
+ rust: rust.ENUM_TYPES,
142
+ go: go.ENUM_TYPES,
143
+ zig: zig.ENUM_TYPES,
144
+ cpp: cpp.ENUM_TYPES,
145
+ }
146
+
147
+ /**
148
+ * Node types that represent constants per language
149
+ */
150
+ export const CONST_TYPES: Record<Language, string[]> = {
151
+ typescript: typescript.CONST_TYPES,
152
+ javascript: javascript.CONST_TYPES,
153
+ python: python.CONST_TYPES,
154
+ rust: rust.CONST_TYPES,
155
+ go: go.CONST_TYPES,
156
+ zig: zig.CONST_TYPES,
157
+ cpp: cpp.CONST_TYPES,
158
+ }
159
+
160
+ /**
161
+ * Extract name from a node using language-specific logic
162
+ */
163
+ export function extractName(node: SyntaxNode, language: Language): string | null {
164
+ // Try 'name' field first (common across languages)
165
+ const nameNode = node.childForFieldName('name')
166
+ if (nameNode) {
167
+ return nameNode.text
168
+ }
169
+
170
+ return languages[language].extractName(node)
171
+ }
172
+
173
+ /**
174
+ * Extract name from a const/let/var declaration
175
+ */
176
+ export function extractConstName(node: SyntaxNode, language: Language): string | null {
177
+ const lang = languages[language]
178
+ if ('extractConstName' in lang && typeof lang.extractConstName === 'function') {
179
+ return lang.extractConstName(node)
180
+ }
181
+ return extractName(node, language)
182
+ }
183
+
184
+ /**
185
+ * Check if a node is exported (language-specific)
186
+ */
187
+ export function isExported(node: SyntaxNode, language: Language, name?: string): boolean {
188
+ const lang = languages[language]
189
+ // Go uses name-based exports
190
+ if (language === 'go') {
191
+ return (lang as typeof go).isExported(node, name)
192
+ }
193
+ return lang.isExported(node)
194
+ }
195
+
196
+ /**
197
+ * Unwrap export statement to get the actual declaration (TS/JS only)
198
+ */
199
+ export function unwrapExport(node: SyntaxNode, language: Language): SyntaxNode {
200
+ if (language === 'typescript' || language === 'javascript') {
201
+ return typescript.unwrapExport(node)
202
+ }
203
+ return node
204
+ }
205
+
206
+ /**
207
+ * Check if a node has extern modifier (Zig/C++ only)
208
+ */
209
+ export function isExtern(node: SyntaxNode, language: Language): boolean {
210
+ if (language === 'zig') {
211
+ return zig.isExtern(node)
212
+ }
213
+ if (language === 'cpp') {
214
+ return cpp.isExtern(node)
215
+ }
216
+ return false
217
+ }
218
+
219
+ /**
220
+ * Zig-specific: check if variable_declaration uses 'const'
221
+ */
222
+ export function isZigConst(node: SyntaxNode): boolean {
223
+ return zig.isConst(node)
224
+ }
225
+
226
+ /**
227
+ * Zig-specific: get type declaration (struct/enum/union) from variable_declaration
228
+ */
229
+ export function getZigTypeDeclaration(node: SyntaxNode): DefinitionType | null {
230
+ return zig.getTypeDeclaration(node)
231
+ }
@@ -0,0 +1,33 @@
1
+ // JavaScript language support for agentmap.
2
+ // Shares most implementation with TypeScript.
3
+
4
+ import type { SyntaxNode } from '../types.js'
5
+ import {
6
+ extractName as tsExtractName,
7
+ extractConstName as tsExtractConstName,
8
+ isExported as tsIsExported,
9
+ unwrapExport as tsUnwrapExport,
10
+ } from './typescript.js'
11
+
12
+ export const id = 'javascript' as const
13
+ export const extensions = ['.js', '.jsx', '.mjs', '.cjs']
14
+ export const grammar = 'tree-sitter-javascript/tree-sitter-javascript.wasm'
15
+
16
+ // AST node types
17
+ export const FUNCTION_TYPES = ['function_declaration', 'method_definition']
18
+ export const CLASS_TYPES = ['class_declaration']
19
+ export const STRUCT_TYPES: string[] = []
20
+ export const TRAIT_TYPES: string[] = []
21
+ export const INTERFACE_TYPES: string[] = []
22
+ export const TYPE_TYPES: string[] = []
23
+ export const ENUM_TYPES: string[] = []
24
+ export const CONST_TYPES = ['lexical_declaration']
25
+
26
+ // Reuse TypeScript implementations
27
+ export const isExported = tsIsExported
28
+ export const unwrapExport = tsUnwrapExport
29
+ export const extractName = tsExtractName
30
+ export const extractConstName = tsExtractConstName
31
+
32
+ // Comment handling
33
+ export const commentPrefixes = ['//', '/*']
@@ -0,0 +1,41 @@
1
+ // Python language support for agentmap.
2
+
3
+ import type { SyntaxNode } from '../types.js'
4
+
5
+ export const id = 'python' as const
6
+ export const extensions = ['.py', '.pyi']
7
+ export const grammar = 'tree-sitter-python/tree-sitter-python.wasm'
8
+
9
+ // AST node types
10
+ export const FUNCTION_TYPES = ['function_definition']
11
+ export const CLASS_TYPES = ['class_definition']
12
+ export const STRUCT_TYPES: string[] = []
13
+ export const TRAIT_TYPES: string[] = []
14
+ export const INTERFACE_TYPES: string[] = []
15
+ export const TYPE_TYPES: string[] = []
16
+ export const ENUM_TYPES: string[] = []
17
+ export const CONST_TYPES: string[] = [] // Python constants handled separately
18
+
19
+ /**
20
+ * Python doesn't have explicit exports - everything is importable
21
+ */
22
+ export function isExported(_node: SyntaxNode): boolean {
23
+ return false
24
+ }
25
+
26
+ /**
27
+ * Extract name from a Python node
28
+ */
29
+ export function extractName(node: SyntaxNode): string | null {
30
+ for (let i = 0; i < node.childCount; i++) {
31
+ const child = node.child(i)
32
+ if (child?.type === 'identifier') {
33
+ return child.text
34
+ }
35
+ }
36
+ return null
37
+ }
38
+
39
+ // Comment handling
40
+ export const commentPrefixes = ['#']
41
+ export const docstringType = 'string' // Python uses docstrings
@@ -0,0 +1,72 @@
1
+ // Rust language support for agentmap.
2
+
3
+ import type { SyntaxNode } from '../types.js'
4
+
5
+ export const id = 'rust' as const
6
+ export const extensions = ['.rs']
7
+ export const grammar = 'tree-sitter-rust/tree-sitter-rust.wasm'
8
+
9
+ // AST node types
10
+ export const FUNCTION_TYPES = ['function_item']
11
+ export const CLASS_TYPES: string[] = [] // Rust has structs/traits, not classes
12
+ export const STRUCT_TYPES = ['struct_item']
13
+ export const TRAIT_TYPES = ['trait_item']
14
+ export const INTERFACE_TYPES: string[] = [] // Rust traits handled in TRAIT_TYPES
15
+ export const TYPE_TYPES = ['type_item']
16
+ export const ENUM_TYPES = ['enum_item']
17
+ export const CONST_TYPES = ['const_item', 'static_item']
18
+
19
+ /**
20
+ * Check if a Rust node has 'pub' visibility modifier
21
+ */
22
+ export function isExported(node: SyntaxNode): boolean {
23
+ for (let i = 0; i < node.childCount; i++) {
24
+ const child = node.child(i)
25
+ if (child?.type === 'visibility_modifier') {
26
+ return child.text.startsWith('pub')
27
+ }
28
+ if (child?.type === 'identifier' || child?.type === 'type_identifier') break
29
+ }
30
+ return false
31
+ }
32
+
33
+ /**
34
+ * Extract name from a Rust node
35
+ */
36
+ export function extractName(node: SyntaxNode): string | null {
37
+ if (node.type === 'impl_item') {
38
+ const typeNode = node.childForFieldName('type')
39
+ if (typeNode) {
40
+ const ident = typeNode.type === 'type_identifier'
41
+ ? typeNode
42
+ : findChild(typeNode, 'type_identifier')
43
+ return ident?.text ?? null
44
+ }
45
+ }
46
+
47
+ for (let i = 0; i < node.childCount; i++) {
48
+ const child = node.child(i)
49
+ if (child?.type === 'identifier' || child?.type === 'type_identifier') {
50
+ return child.text
51
+ }
52
+ }
53
+ return null
54
+ }
55
+
56
+ /**
57
+ * Extract name from const/static declaration
58
+ */
59
+ export function extractConstName(node: SyntaxNode): string | null {
60
+ return extractName(node)
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 = ['//', '/*', '//!', '///']
@@ -0,0 +1,74 @@
1
+ // TypeScript language support for agentmap.
2
+
3
+ import type { SyntaxNode } from '../types.js'
4
+
5
+ export const id = 'typescript' as const
6
+ export const extensions = ['.ts', '.tsx', '.mts', '.cts']
7
+ export const grammar = 'tree-sitter-typescript/tree-sitter-tsx.wasm'
8
+
9
+ // AST node types
10
+ export const FUNCTION_TYPES = ['function_declaration', 'method_definition']
11
+ export const CLASS_TYPES = ['class_declaration', 'abstract_class_declaration']
12
+ export const STRUCT_TYPES: string[] = []
13
+ export const TRAIT_TYPES: string[] = []
14
+ export const INTERFACE_TYPES = ['interface_declaration']
15
+ export const TYPE_TYPES = ['type_alias_declaration']
16
+ export const ENUM_TYPES = ['enum_declaration']
17
+ export const CONST_TYPES = ['lexical_declaration']
18
+
19
+ /**
20
+ * Check if a node is an export statement
21
+ */
22
+ export function isExported(node: SyntaxNode): boolean {
23
+ return node.type === 'export_statement'
24
+ }
25
+
26
+ /**
27
+ * Unwrap export statement to get the actual declaration
28
+ */
29
+ export function unwrapExport(node: SyntaxNode): SyntaxNode {
30
+ if (node.type === 'export_statement') {
31
+ for (let i = 0; i < node.childCount; i++) {
32
+ const child = node.child(i)
33
+ if (!child) continue
34
+ if (child.type !== 'export' && !child.type.includes('comment') && child.type !== 'default') {
35
+ return child
36
+ }
37
+ }
38
+ }
39
+ return node
40
+ }
41
+
42
+ /**
43
+ * Extract name from a TypeScript/JavaScript node
44
+ */
45
+ export function extractName(node: SyntaxNode): string | null {
46
+ for (let i = 0; i < node.childCount; i++) {
47
+ const child = node.child(i)
48
+ if (!child) continue
49
+ if (child.type === 'identifier' || child.type === 'type_identifier') {
50
+ return child.text
51
+ }
52
+ if (child.type === 'property_identifier') {
53
+ return child.text
54
+ }
55
+ }
56
+ return null
57
+ }
58
+
59
+ /**
60
+ * Extract name from const/let declaration
61
+ */
62
+ export function extractConstName(node: SyntaxNode): string | null {
63
+ for (let i = 0; i < node.childCount; i++) {
64
+ const child = node.child(i)
65
+ if (child?.type === 'variable_declarator') {
66
+ const nameNode = child.childForFieldName('name')
67
+ return nameNode?.text ?? null
68
+ }
69
+ }
70
+ return null
71
+ }
72
+
73
+ // Comment handling
74
+ export const commentPrefixes = ['//', '/*']
@@ -0,0 +1,106 @@
1
+ // Zig language support for agentmap.
2
+
3
+ import type { SyntaxNode, DefinitionType } from '../types.js'
4
+
5
+ export const id = 'zig' as const
6
+ export const extensions = ['.zig']
7
+ export const grammar = '@tree-sitter-grammars/tree-sitter-zig/tree-sitter-zig.wasm'
8
+
9
+ // AST node types
10
+ export const FUNCTION_TYPES = ['function_declaration', 'test_declaration']
11
+ export const CLASS_TYPES: string[] = [] // Zig has structs/unions, not classes
12
+ export const STRUCT_TYPES: string[] = [] // Handled via variable_declaration with struct value
13
+ export const TRAIT_TYPES: string[] = []
14
+ export const INTERFACE_TYPES: string[] = []
15
+ export const TYPE_TYPES: string[] = []
16
+ export const ENUM_TYPES: string[] = [] // Handled via variable_declaration with enum value
17
+ export const CONST_TYPES = ['variable_declaration']
18
+
19
+ /**
20
+ * Check if a Zig node has 'pub' modifier
21
+ */
22
+ export function isExported(node: SyntaxNode): boolean {
23
+ for (let i = 0; i < node.childCount; i++) {
24
+ const child = node.child(i)
25
+ if (child?.type === 'pub') return true
26
+ if (child?.type === 'identifier' || child?.type === 'block') break
27
+ }
28
+ return false
29
+ }
30
+
31
+ /**
32
+ * Check if a Zig variable_declaration uses 'const' (not 'var')
33
+ */
34
+ export function isConst(node: SyntaxNode): boolean {
35
+ for (let i = 0; i < node.childCount; i++) {
36
+ const child = node.child(i)
37
+ if (child?.type === 'const') return true
38
+ if (child?.type === 'var') return false
39
+ }
40
+ return false
41
+ }
42
+
43
+ /**
44
+ * Check if a Zig variable_declaration contains a struct/enum/union declaration
45
+ */
46
+ export function getTypeDeclaration(node: SyntaxNode): DefinitionType | null {
47
+ for (let i = 0; i < node.childCount; i++) {
48
+ const child = node.child(i)
49
+ if (child?.type === 'struct_declaration') return 'struct'
50
+ if (child?.type === 'union_declaration') return 'union'
51
+ if (child?.type === 'enum_declaration') return 'enum'
52
+ }
53
+ return null
54
+ }
55
+
56
+ /**
57
+ * Check if a Zig function has 'extern' modifier
58
+ */
59
+ export function isExtern(node: SyntaxNode): boolean {
60
+ for (let i = 0; i < node.childCount; i++) {
61
+ const child = node.child(i)
62
+ if (child?.type === 'extern') return true
63
+ if (child?.type === 'block' || child?.type === 'fn') break
64
+ }
65
+ return false
66
+ }
67
+
68
+ /**
69
+ * Extract name from a Zig node
70
+ */
71
+ export function extractName(node: SyntaxNode): string | null {
72
+ if (node.type === 'test_declaration') {
73
+ for (let i = 0; i < node.childCount; i++) {
74
+ const child = node.child(i)
75
+ if (child?.type === 'string') {
76
+ const text = child.text
77
+ if (text.startsWith('"') && text.endsWith('"')) {
78
+ return text.slice(1, -1)
79
+ }
80
+ return text
81
+ }
82
+ if (child?.type === 'identifier') {
83
+ return child.text
84
+ }
85
+ }
86
+ return null
87
+ }
88
+
89
+ for (let i = 0; i < node.childCount; i++) {
90
+ const child = node.child(i)
91
+ if (child?.type === 'identifier') {
92
+ return child.text
93
+ }
94
+ }
95
+ return null
96
+ }
97
+
98
+ /**
99
+ * Extract name from const/var declaration
100
+ */
101
+ export function extractConstName(node: SyntaxNode): string | null {
102
+ return extractName(node)
103
+ }
104
+
105
+ // Comment handling
106
+ export const commentPrefixes = ['//', '/*']
package/src/logger.ts ADDED
@@ -0,0 +1,55 @@
1
+ // Shared logger abstraction used by CLI and integrations.
2
+
3
+ export interface Logger {
4
+ debug: (...args: unknown[]) => void
5
+ info: (...args: unknown[]) => void
6
+ warn: (...args: unknown[]) => void
7
+ error: (...args: unknown[]) => void
8
+ }
9
+
10
+ const NOOP = () => {}
11
+
12
+ const NOOP_LOGGER: Logger = {
13
+ debug: NOOP,
14
+ info: NOOP,
15
+ warn: NOOP,
16
+ error: NOOP,
17
+ }
18
+
19
+ export function createNoopLogger(): Logger {
20
+ return NOOP_LOGGER
21
+ }
22
+
23
+ function formatArg(value: unknown): string {
24
+ if (typeof value === 'string') {
25
+ return value
26
+ }
27
+
28
+ if (value instanceof Error) {
29
+ return value.stack ?? value.message
30
+ }
31
+
32
+ try {
33
+ return JSON.stringify(value)
34
+ } catch {
35
+ return String(value)
36
+ }
37
+ }
38
+
39
+ export function formatLogMessage(args: unknown[]): string {
40
+ return args.map(formatArg).join(' ')
41
+ }
42
+
43
+ function writeLine(stream: Pick<NodeJS.WriteStream, 'write'>, args: unknown[]): void {
44
+ const message = formatLogMessage(args)
45
+ stream.write(message.endsWith('\n') ? message : `${message}\n`)
46
+ }
47
+
48
+ export function createConsoleLogger(): Logger {
49
+ return {
50
+ debug: (...args) => writeLine(process.stderr, args),
51
+ info: (...args) => writeLine(process.stderr, args),
52
+ warn: (...args) => writeLine(process.stderr, args),
53
+ error: (...args) => writeLine(process.stderr, args),
54
+ }
55
+ }
@@ -0,0 +1,72 @@
1
+ // Tests for building nested map nodes for recursive submodule trees.
2
+
3
+ import { describe, expect, test } from 'bun:test'
4
+ import { buildMap } from './builder.js'
5
+
6
+ describe('buildMap with recursive submodules', () => {
7
+ test('renders submodule metadata and nested files under the same node', () => {
8
+ const map = buildMap(
9
+ [
10
+ {
11
+ relativePath: 'README.md',
12
+ description: 'Root repo\nRoot README.',
13
+ definitions: [],
14
+ },
15
+ {
16
+ relativePath: 'vendor/child-lib/README.md',
17
+ description: 'Child repo\nChild README.',
18
+ definitions: [],
19
+ },
20
+ {
21
+ relativePath: 'vendor/child-lib/deps/nested-lib/README.md',
22
+ description: 'Nested repo\nNested README.',
23
+ definitions: [],
24
+ },
25
+ ],
26
+ 'repo',
27
+ [
28
+ {
29
+ path: 'vendor/child-lib',
30
+ commit: 'abc1234',
31
+ branch: undefined,
32
+ dirty: false,
33
+ initialized: true,
34
+ url: undefined,
35
+ },
36
+ {
37
+ path: 'vendor/child-lib/deps/nested-lib',
38
+ commit: 'def5678',
39
+ branch: undefined,
40
+ dirty: true,
41
+ initialized: true,
42
+ url: undefined,
43
+ },
44
+ ]
45
+ )
46
+
47
+ expect(map).toEqual({
48
+ repo: {
49
+ 'README.md': {
50
+ description: 'Root repo\nRoot README.',
51
+ },
52
+ vendor: {
53
+ 'child-lib': {
54
+ 'README.md': {
55
+ description: 'Child repo\nChild README.',
56
+ },
57
+ deps: {
58
+ 'nested-lib': {
59
+ 'README.md': {
60
+ description: 'Nested repo\nNested README.',
61
+ },
62
+ dirty: 'modified',
63
+ submodule: 'detached @ def5678',
64
+ },
65
+ },
66
+ submodule: 'detached @ abc1234',
67
+ },
68
+ },
69
+ },
70
+ })
71
+ })
72
+ })