Skip to content
This repository was archived by the owner on Oct 10, 2025. It is now read-only.

Commit e47ee1e

Browse files
committed
fix: recursion and diagram / related modules checks
1 parent 2b715b4 commit e47ee1e

File tree

4 files changed

+60
-11
lines changed

4 files changed

+60
-11
lines changed

.context.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
module-name: Codebase Context Specification
33
version: 1.0.0
44
description: A specification for providing explicit context information about a codebase
5-
related-modules: []
5+
related-modules:
6+
- TypeScript Linter: ./linters/typescript
7+
- Codebase Context Editor: ./examples/context-editor
8+
- Three: ./invalid
69
technologies:
710
- Markdown
811
- YAML
@@ -16,7 +19,7 @@ directives:
1619
- Maintain backward compatibility
1720
- Keep documentation up-to-date
1821
diagrams:
19-
- context-hierarchy.png
22+
- Dagram : ./img/codebase-diagram.mermaid
2023
architecture:
2124
style: Documentation-driven
2225
components:

linters/typescript/src/context_linter.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ export class ContextLinter {
3434
public async lintDirectory(directoryPath: string, packageVersion: string): Promise<boolean> {
3535
try {
3636
printHeader(packageVersion, directoryPath);
37-
console.log(`Linting directory: ${this.normalizePath(directoryPath)}\n`);
3837
let isValid = true;
3938

4039
// Initialize ignore patterns
@@ -104,31 +103,41 @@ export class ContextLinter {
104103

105104
for (const filePath of contextFiles) {
106105
if (!this.contextignoreLinter.isIgnored(filePath, directoryPath)) {
107-
const fileContent = await fs.promises.readFile(filePath, 'utf-8');
108-
const fileExtension = path.extname(filePath);
106+
const fullPath = path.join(directoryPath, filePath);
107+
const fileContent = await fs.promises.readFile(fullPath, 'utf-8');
108+
const fileExtension = path.extname(fullPath);
109109
let result: ValidationResult;
110110

111111
switch (fileExtension) {
112112
case '.md':
113-
result = await this.lintMarkdownFile(fileContent, filePath);
113+
result = await this.lintMarkdownFile(fileContent, fullPath);
114114
break;
115115
case '.yaml':
116116
case '.yml':
117-
result = await this.lintYamlFile(fileContent, filePath);
117+
result = await this.lintYamlFile(fileContent, fullPath);
118118
break;
119119
case '.json':
120-
result = await this.lintJsonFile(fileContent, filePath);
120+
result = await this.lintJsonFile(fileContent, fullPath);
121121
break;
122122
default:
123123
console.warn(`Unsupported file extension: ${fileExtension}`);
124124
continue;
125125
}
126126

127-
this.printValidationResult(result, filePath);
127+
this.printValidationResult(result, fullPath);
128128
isValid = isValid && result.isValid;
129129
}
130130
}
131131

132+
// Recursively process subdirectories
133+
const subdirectories = await fs.promises.readdir(directoryPath, { withFileTypes: true });
134+
for (const dirent of subdirectories) {
135+
if (dirent.isDirectory() && !this.contextignoreLinter.isIgnored(dirent.name, directoryPath)) {
136+
const subdirectoryPath = path.join(directoryPath, dirent.name);
137+
isValid = await this.handleContextFilesRecursively(subdirectoryPath) && isValid;
138+
}
139+
}
140+
132141
return isValid;
133142
}
134143

linters/typescript/src/utils/context_structure.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,6 @@ export const stringTypes: Set<string> = new Set([
5353
]);
5454

5555
export const directoryTypes: Set<string> = new Set([
56-
'related-modules'
56+
'related-modules',
57+
'diagrams'
5758
]);

linters/typescript/src/utils/validator.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
13
import { kebabToCamelCase } from './string_utils';
24
import { allowedTopLevelFields, sectionChecks, listTypes, stringTypes, directoryTypes } from './context_structure';
35

@@ -76,11 +78,45 @@ export class ContextValidator {
7678
console.error(` Error: Field '${field}' should be a string.`);
7779
isValid = false;
7880
} else if (directoryTypes.has(field)) {
79-
// Additional validation for directory types can be added here
81+
if (field === 'related-modules' && Array.isArray(value)) {
82+
for (const modulePath of value) {
83+
if (typeof modulePath === 'string' && !this.isValidRelatedModule(modulePath)) {
84+
console.error(` Error: Related module '${modulePath}' is not a valid directory containing a .context file.`);
85+
isValid = false;
86+
}
87+
}
88+
} else if (field === 'diagrams' && Array.isArray(value)) {
89+
for (const diagramPath of value) {
90+
if (typeof diagramPath === 'string' && !this.isValidDiagram(diagramPath)) {
91+
console.error(` Error: Diagram '${diagramPath}' is not a valid file or URL.`);
92+
isValid = false;
93+
}
94+
}
95+
}
8096
}
8197
return isValid;
8298
}
8399

100+
private isValidRelatedModule(modulePath: string): boolean {
101+
if (!fs.existsSync(modulePath) || !fs.statSync(modulePath).isDirectory()) {
102+
return false;
103+
}
104+
const contextFiles = ['.context.md', '.context.json', '.context.yaml', '.context.yml'];
105+
return contextFiles.some(file => fs.existsSync(path.join(modulePath, file)));
106+
}
107+
108+
private isValidDiagram(diagramPath: string): boolean {
109+
if (diagramPath.startsWith('http://') || diagramPath.startsWith('https://')) {
110+
// Assume URLs are valid diagrams
111+
return true;
112+
}
113+
if (!fs.existsSync(diagramPath)) {
114+
return false;
115+
}
116+
const allowedExtensions = ['.mermaid', '.mmd', '.pdf', '.png', '.jpeg', '.jpg'];
117+
return allowedExtensions.includes(path.extname(diagramPath).toLowerCase());
118+
}
119+
84120
private validateSectionFields(sectionName: string, data: Record<string, unknown>, isJson: boolean): SectionValidationResult {
85121
const checks = sectionChecks[sectionName];
86122
const coveredFields = new Set<string>();

0 commit comments

Comments
 (0)