@@ -74,22 +74,124 @@ AI Context Convention TypeScript Linter (v${packageVersion})
74
74
}
75
75
76
76
private lintMarkdownFile ( content : string ) : void {
77
- // TODO: Implement markdown file linting
78
77
console . log ( ' - Validating Markdown structure' ) ;
79
78
console . log ( ' - Checking YAML frontmatter' ) ;
80
79
console . log ( ' - Verifying content against AI Context Convention Specification' ) ;
80
+
81
+ // Split content into frontmatter and markdown
82
+ const parts = content . split ( '---\n' ) ;
83
+ if ( parts . length < 3 ) {
84
+ console . error ( ' Error: Invalid markdown structure. YAML frontmatter is missing or incomplete.' ) ;
85
+ return ;
86
+ }
87
+
88
+ const frontmatter = parts [ 1 ] ;
89
+ const markdownContent = parts . slice ( 2 ) . join ( '---\n' ) . trim ( ) ;
90
+
91
+ // Parse and validate frontmatter
92
+ try {
93
+ const frontmatterData = yaml . load ( frontmatter ) as Record < string , unknown > ;
94
+ this . validateContextData ( frontmatterData ) ;
95
+ } catch ( error ) {
96
+ console . error ( ` Error parsing YAML frontmatter: ${ error } ` ) ;
97
+ }
98
+
99
+ // Validate markdown content
100
+ this . validateMarkdownContent ( markdownContent ) ;
101
+ }
102
+
103
+ private validateContextData ( data : Record < string , unknown > ) : void {
104
+ const requiredFields = [ 'directoryName' , 'description' ] ;
105
+ for ( const field of requiredFields ) {
106
+ if ( ! ( field in data ) ) {
107
+ console . error ( ` Error: Missing required field '${ field } '.` ) ;
108
+ }
109
+ }
110
+
111
+ if ( 'language' in data && typeof data . language !== 'string' ) {
112
+ console . error ( ' Error: Field \'language\' must be a string.' ) ;
113
+ }
114
+
115
+ if ( 'dependencies' in data && ! Array . isArray ( data . dependencies ) ) {
116
+ console . error ( ' Error: Field \'dependencies\' must be an array.' ) ;
117
+ }
118
+
119
+ // Add more specific checks as needed
120
+ }
121
+
122
+ private validateMarkdownContent ( content : string ) : void {
123
+ const tokens = this . md . parse ( content , { } ) ;
124
+ let hasTitle = false ;
125
+ let hasDescriptionSection = false ;
126
+ let hasFileStructureSection = false ;
127
+ let hasUsageSection = false ;
128
+ let currentSection = '' ;
129
+
130
+ for ( let i = 0 ; i < tokens . length ; i ++ ) {
131
+ const token = tokens [ i ] ;
132
+
133
+ if ( token . type === 'heading_open' ) {
134
+ if ( token . tag === 'h1' && ! hasTitle ) {
135
+ hasTitle = true ;
136
+ } else if ( token . tag === 'h2' ) {
137
+ currentSection = tokens [ i + 1 ] . content . toLowerCase ( ) ;
138
+ if ( currentSection === 'description' ) hasDescriptionSection = true ;
139
+ if ( currentSection === 'file structure' ) hasFileStructureSection = true ;
140
+ if ( currentSection === 'usage' ) hasUsageSection = true ;
141
+ }
142
+ }
143
+
144
+ if ( token . type === 'link_open' ) {
145
+ const hrefToken = tokens [ i + 1 ] ;
146
+ if ( hrefToken . type !== 'text' || ! hrefToken . content . startsWith ( 'http' ) ) {
147
+ console . error ( ' Warning: Link may be improperly formatted or using relative path.' ) ;
148
+ }
149
+ }
150
+
151
+ if ( token . type === 'fence' && ! token . info ) {
152
+ console . error ( ' Warning: Code block is missing language specification.' ) ;
153
+ }
154
+ }
155
+
156
+ if ( ! hasTitle ) {
157
+ console . error ( ' Error: Markdown content should start with a title (H1 heading).' ) ;
158
+ }
159
+
160
+ if ( ! hasDescriptionSection ) {
161
+ console . error ( ' Warning: Markdown content is missing a "Description" section.' ) ;
162
+ }
163
+
164
+ if ( ! hasFileStructureSection ) {
165
+ console . warn ( ' Note: Consider adding a "File Structure" section for better context.' ) ;
166
+ }
167
+
168
+ if ( ! hasUsageSection ) {
169
+ console . warn ( ' Note: Consider adding a "Usage" section for better understanding.' ) ;
170
+ }
81
171
}
82
172
83
173
private lintYamlFile ( content : string ) : void {
84
- // TODO: Implement YAML file linting
85
174
console . log ( ' - Validating YAML structure' ) ;
86
175
console . log ( ' - Verifying content against AI Context Convention Specification' ) ;
176
+
177
+ try {
178
+ const yamlData = yaml . load ( content ) as Record < string , unknown > ;
179
+ this . validateContextData ( yamlData ) ;
180
+ } catch ( error ) {
181
+ console . error ( ` Error parsing YAML file: ${ error } ` ) ;
182
+ }
87
183
}
88
184
89
185
private lintJsonFile ( content : string ) : void {
90
- // TODO: Implement JSON file linting
91
186
console . log ( ' - Validating JSON structure' ) ;
92
187
console . log ( ' - Verifying content against AI Context Convention Specification' ) ;
188
+
189
+ try {
190
+ const jsonData = JSON . parse ( content ) as Record < string , unknown > ;
191
+ this . validateContextData ( jsonData ) ;
192
+ } catch ( error ) {
193
+ console . error ( ` Error parsing JSON file: ${ error } ` ) ;
194
+ }
93
195
}
94
196
95
197
private lintContextdocsFile ( filePath : string ) : void {
@@ -100,10 +202,54 @@ AI Context Convention TypeScript Linter (v${packageVersion})
100
202
console . log ( ' - Checking for required sections' ) ;
101
203
console . log ( ' - Verifying content against .contextdocs.md specification' ) ;
102
204
103
- // TODO: Implement specific checks for .contextdocs.md
104
- // 1. Check if the file starts with a level 1 heading "AI Context Documentation"
105
- // 2. Verify that all required sections are present (e.g., "Overview", "File Structure", "Conventions")
106
- // 3. Ensure that the content follows the expected format for each section
205
+ const tokens = this . md . parse ( content , { } ) ;
206
+ let hasTitle = false ;
207
+ let hasOverview = false ;
208
+ let hasFileStructure = false ;
209
+ let hasConventions = false ;
210
+ let currentSection = '' ;
211
+
212
+ for ( let i = 0 ; i < tokens . length ; i ++ ) {
213
+ const token = tokens [ i ] ;
214
+
215
+ if ( token . type === 'heading_open' ) {
216
+ if ( token . tag === 'h1' && ! hasTitle ) {
217
+ hasTitle = tokens [ i + 1 ] . content === 'AI Context Documentation' ;
218
+ } else if ( token . tag === 'h2' ) {
219
+ currentSection = tokens [ i + 1 ] . content . toLowerCase ( ) ;
220
+ if ( currentSection === 'overview' ) hasOverview = true ;
221
+ if ( currentSection === 'file structure' ) hasFileStructure = true ;
222
+ if ( currentSection === 'conventions' ) hasConventions = true ;
223
+ }
224
+ }
225
+
226
+ if ( token . type === 'link_open' ) {
227
+ const hrefToken = tokens [ i + 1 ] ;
228
+ if ( hrefToken . type !== 'text' || ! hrefToken . content . startsWith ( 'http' ) ) {
229
+ console . error ( ' Warning: Link may be improperly formatted or using relative path.' ) ;
230
+ }
231
+ }
232
+
233
+ if ( token . type === 'fence' && ! token . info ) {
234
+ console . error ( ' Warning: Code block is missing language specification.' ) ;
235
+ }
236
+ }
237
+
238
+ if ( ! hasTitle ) {
239
+ console . error ( ' Error: .contextdocs.md should start with the title "AI Context Documentation".' ) ;
240
+ }
241
+
242
+ if ( ! hasOverview ) {
243
+ console . error ( ' Error: .contextdocs.md is missing the "Overview" section.' ) ;
244
+ }
245
+
246
+ if ( ! hasFileStructure ) {
247
+ console . error ( ' Error: .contextdocs.md is missing the "File Structure" section.' ) ;
248
+ }
249
+
250
+ if ( ! hasConventions ) {
251
+ console . error ( ' Error: .contextdocs.md is missing the "Conventions" section.' ) ;
252
+ }
107
253
}
108
254
109
255
private lintContextignoreFile ( filePath : string ) : void {
@@ -113,10 +259,42 @@ AI Context Convention TypeScript Linter (v${packageVersion})
113
259
console . log ( ' - Validating .contextignore format' ) ;
114
260
console . log ( ' - Checking for valid ignore patterns' ) ;
115
261
116
- // TODO: Implement specific checks for .contextignore
117
- // 1. Verify that each line is a valid ignore pattern (similar to .gitignore format)
118
- // 2. Check for any conflicting or redundant patterns
119
- // 3. Ensure that the file doesn't ignore critical context files (e.g., .context.md files)
262
+ const lines = content . split ( '\n' ) . map ( line => line . trim ( ) ) . filter ( line => line !== '' && ! line . startsWith ( '#' ) ) ;
263
+ const patterns : string [ ] = [ ] ;
264
+ const criticalPatterns = [ '.context.md' , '.context.yaml' , '.context.json' , '.contextdocs.md' , '.contextignore' ] ;
265
+
266
+ for ( let i = 0 ; i < lines . length ; i ++ ) {
267
+ const line = lines [ i ] ;
268
+
269
+ // Check for valid pattern format
270
+ if ( ! / ^ [ ! # ] ? [ \w \- . / \* \? ] + $ / . test ( line ) ) {
271
+ console . error ( ` Error: Invalid ignore pattern on line ${ i + 1 } : ${ line } ` ) ;
272
+ }
273
+
274
+ // Check for redundant patterns
275
+ if ( patterns . includes ( line ) ) {
276
+ console . warn ( ` Warning: Redundant pattern on line ${ i + 1 } : ${ line } ` ) ;
277
+ }
278
+
279
+ // Check for critical file ignores
280
+ for ( const criticalPattern of criticalPatterns ) {
281
+ if ( line . endsWith ( criticalPattern ) || line . includes ( `/${ criticalPattern } ` ) ) {
282
+ console . error ( ` Error: Ignoring critical context file on line ${ i + 1 } : ${ line } ` ) ;
283
+ }
284
+ }
285
+
286
+ patterns . push ( line ) ;
287
+ }
288
+
289
+ // Check for conflicting include/exclude patterns
290
+ for ( let i = 0 ; i < patterns . length ; i ++ ) {
291
+ for ( let j = i + 1 ; j < patterns . length ; j ++ ) {
292
+ if ( patterns [ i ] . startsWith ( '!' ) && patterns [ j ] === patterns [ i ] . slice ( 1 ) ||
293
+ patterns [ j ] . startsWith ( '!' ) && patterns [ i ] === patterns [ j ] . slice ( 1 ) ) {
294
+ console . warn ( ` Warning: Conflicting patterns: "${ patterns [ i ] } " and "${ patterns [ j ] } "` ) ;
295
+ }
296
+ }
297
+ }
120
298
}
121
299
}
122
300
0 commit comments