This document provides guidelines and instructions for contributing to the DeepLint project.
Getting Started
Thank you for your interest in contributing to DeepLint! This guide will help you get started with the development process.
Prerequisites
Before you begin, ensure you have the following installed:
Node.js : Version 22.0.0 or higher
pnpm : Version 10.x or higher
Setting Up the Development Environment
Fork the Repository
Start by forking the DeepLint repository to your GitHub account.
Clone Your Fork
Copy git clone https://github.com/YOUR_USERNAME/deeplint-cli.git
cd deeplint-cli
Set Up Environment Variables
Copy the sample environment file and modify as needed:
Edit the .env
file to include your OpenAI API key:
Copy OPENAI_API_KEY=your_api_key_here
Run the Development Server
This will start the development server, allowing you to run DeepLint with changes you make to the codebase.
Verify Your Setup
Run a simple command to verify that your development environment is set up correctly:
You should see the help output for DeepLint.
Development Workflow
Branch Naming Convention
Use the following naming convention for branches:
feature/short-description
: For new features
fix/short-description
: For bug fixes
docs/short-description
: For documentation changes
refactor/short-description
: For code refactoring
Examples:
Copy git checkout -b feature/add-rule-engine
git checkout -b fix/context-builder-token-limit
git checkout -b docs/improve-getting-started
git checkout -b refactor/command-system
Commit Message Guidelines
Follow these guidelines for commit messages:
Use the present tense ("Add feature" not "Added feature")
Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
Limit the first line to 72 characters or less
Reference issues and pull requests liberally after the first line
Example:
Copy Add context building functionality
- Implement file system scanner
- Add dependency analyzer
- Create token counter
- Fixes #123
Development Process
Create a Branch
Copy git checkout -b feature/your-feature-name
Make Your Changes
Implement your changes, following the coding standards .
Run Tests
Ensure your changes pass all tests:
Lint Your Code
Ensure your code follows the linting rules:
Format Your Code
Ensure your code is properly formatted:
Commit Your Changes
Copy git add .
git commit -m "Your descriptive commit message"
Push to Your Fork
Copy git push origin feature/your-feature-name
Pull Request Process
Fill in the Pull Request Template
Provide a clear description of the changes and reference any related issues.
Update Documentation
Ensure that any new features or changes are documented.
Pass CI Checks
Make sure your pull request passes all CI checks.
Code Review
Address any feedback from code reviewers.
Merge
Once approved, your pull request will be merged.
Code Review Guidelines
As a Contributor
Be responsive to feedback
Explain your design decisions
Break large changes into smaller, more manageable pull requests
Test your changes thoroughly
As a Reviewer
Be respectful and constructive
Focus on the code, not the person
Provide specific, actionable feedback
Consider the context and constraints
Documentation
Code Documentation
Use JSDoc comments for public APIs
Document parameters, return values, and exceptions
Include examples for complex functions
Example:
Copy /**
* Builds context for LLM analysis based on staged changes
*
* @param options - Options for the context builder
* @returns Promise resolving to the context build result
* @throws {GitError} If Git operations fail
* @throws {ContextBuildingError} If context building fails
*
* @example
* ```typescript
* const contextBuilder = new ContextBuilder();
* const result = await contextBuilder.buildContext();
* console.log(`Built context with ${result.stats.totalTokens} tokens`);
* ```
*/
async function buildContext(
options?: Partial<ContextBuilderOptions>,
): Promise<ContextBuildResult> {
// Implementation
}
Project Documentation
Update README.md with any new features or changes
Update the documentation in the docs/
directory
Create new documentation files as needed
Testing
Writing Tests
Write tests for all new features
Ensure tests are deterministic and isolated
Use mocks for external dependencies
Aim for high test coverage
Example:
Copy describe("ContextBuilder", () => {
let contextBuilder: ContextBuilder;
let mockGitOperations: MockGitOperations;
beforeEach(() => {
mockGitOperations = new MockGitOperations();
contextBuilder = new ContextBuilder({
gitOperations: mockGitOperations,
});
});
describe("buildContext", () => {
it("should return empty context when no changes are staged", async () => {
mockGitOperations.hasStagedChanges.mockResolvedValue(false);
const result = await contextBuilder.buildContext();
expect(result.context.changes.files).toHaveLength(0);
expect(result.stats.changedFiles).toBe(0);
});
it("should process staged changes", async () => {
mockGitOperations.hasStagedChanges.mockResolvedValue(true);
mockGitOperations.getStagedFiles.mockResolvedValue(["file1.ts", "file2.ts"]);
mockGitOperations.getStagedDiff.mockResolvedValue("diff content");
const result = await contextBuilder.buildContext();
expect(result.context.changes.files).toHaveLength(2);
expect(result.stats.changedFiles).toBe(2);
});
});
});
Running Tests
Copy # Run all tests
pnpm test
# Run specific tests
pnpm test -- --testPathPattern=context-builder
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage
pnpm test:coverage
Debugging
Development Server
Run the development server with debugging enabled:
This will start the server with the Node.js inspector enabled. You can then attach a debugger to it.
VS Code Debugging
A .vscode/launch.json
file is provided for VS Code users. You can use the "Debug DeepLint" configuration to debug the application.
To debug a specific command:
Open the file you want to debug
Set breakpoints by clicking in the gutter
Press F5 to start debugging
Select the "Debug DeepLint" configuration
Enter the command arguments when prompted
Example launch configuration:
Copy {
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug DeepLint",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/src/index.ts",
"args": ["--debug"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"sourceMaps": true,
"console": "integratedTerminal"
}
]
}
Common Development Tasks
Adding a New Command
Create a new file in src/commands/
with the naming convention command-name-command.ts
Extend the BaseCommand
class
Implement the required methods
The command will be automatically discovered and registered
Example:
Copy // src/commands/analyze-command.ts
import { Argv } from "yargs";
import { BaseCommand } from "./base-command";
import { logger } from "../providers/logger";
import { ContextBuilder } from "../context-builder/context/context-builder";
export class AnalyzeCommand extends BaseCommand {
get name(): string {
return "analyze";
}
get description(): string {
return "Analyze specific files";
}
get aliases(): string[] {
return ["a", "check"];
}
configure(yargs: Argv): Argv {
return yargs.command({
command: "analyze [files..]",
describe: this.description,
builder: (y) => {
return y
.positional("files", {
describe: "Files to analyze",
type: "string",
array: true,
})
.option("context", {
describe: "Context depth (light, deep)",
type: "string",
choices: ["light", "deep"],
default: "light",
});
},
handler: (argv) => this.run(argv),
});
}
async execute(args: any): Promise<void> {
const { files, context } = args;
logger.info(`Analyzing ${files.length} files with ${context} context...`);
const contextBuilder = new ContextBuilder({
contextType: context,
});
// Implementation details
logger.success("Analysis complete!");
}
}
Adding a New Configuration Option
Update the configuration types in src/config/types.ts
Update the default configuration in src/config/templates.ts
Update the configuration validation in src/config/validator.ts
Update the documentation in docs/getting-started/configuration.md
Example:
Copy // Step 1: Update types in src/config/types.ts
export interface ContextBuilderConfig {
// Existing options
contextType: "light" | "deep";
maxTokens: number;
tokensPerFile: number;
maxFileSize: number;
includeDependencies: boolean;
maxDependencyDepth: number;
includeStructure: boolean;
// New option
includeComments: boolean;
}
// Step 2: Update default configuration in src/config/templates.ts
export const getDefaultConfig = (): Config => ({
contextBuilder: {
contextType: "light",
maxTokens: 8000,
tokensPerFile: 1000,
maxFileSize: 500,
includeDependencies: false,
maxDependencyDepth: 1,
includeStructure: true,
includeComments: true, // New option with default value
},
// Other configuration sections
});
// Step 3: Update validation in src/config/validator.ts
private validateContextBuilderConfig(config: ContextBuilderConfig): ValidationResult {
const errors: ValidationError[] = [];
// Existing validation
// New validation
if (typeof config.includeComments !== "boolean") {
errors.push({
path: "contextBuilder.includeComments",
message: "Value must be a boolean.",
value: config.includeComments,
});
}
return { isValid: errors.length === 0, errors };
}
Modifying the Context Builder
Update the context builder implementation in src/context-builder/context/context-builder.ts
Update the context types in src/context-builder/context/context-types.ts
Update the tests in tests/context-builder/
Update the documentation in docs/concepts/context-building.md
Example:
Copy // src/context-builder/context/context-builder.ts
export class ContextBuilder {
private readonly options: ContextBuilderOptions;
private readonly gitOperations: GitOperations;
private readonly fileSystemScanner: FileSystemScanner;
private readonly tokenCounter: TokenCounter;
constructor(options: Partial<ContextBuilderOptions> = {}) {
this.options = { ...getDefaultOptions(), ...options };
this.gitOperations = options.gitOperations || new GitOperations();
this.fileSystemScanner = options.fileSystemScanner || new FileSystemScanner();
this.tokenCounter = options.tokenCounter || new TokenCounter();
}
async buildContext(): Promise<ContextBuildResult> {
const startTime = Date.now();
try {
// Check if there are staged changes
const hasStagedChanges = await this.gitOperations.hasStagedChanges();
if (!hasStagedChanges && !this.options.includeUnstaged) {
logger.warn("No staged changes found.");
return this.createEmptyContext(startTime);
}
// Get staged or unstaged files
const files = this.options.includeUnstaged
? await this.gitOperations.getUnstagedFiles()
: await this.gitOperations.getStagedFiles();
// Scan repository
const repoStructure = await this.fileSystemScanner.scan({
include: this.options.include,
exclude: this.options.exclude,
useGitignore: this.options.useGitignore,
});
// Build context
const context = {
repository: {
name: path.basename(process.cwd()),
root: process.cwd(),
structure: repoStructure,
},
changes: {
files: this.processFiles(files),
summary: this.generateSummary(files),
},
relatedFiles: [],
metadata: {
contextSize: {
totalTokens: 0,
changesTokens: 0,
relatedFilesTokens: 0,
structureTokens: 0,
},
generatedAt: new Date().toISOString(),
contextType: this.options.contextType,
},
};
// Count tokens
const tokenCounts = this.tokenCounter.countTokens(context);
context.metadata.contextSize = tokenCounts;
return {
context,
stats: {
totalFiles: files.length,
changedFiles: files.length,
relatedFiles: 0,
totalTokens: tokenCounts.totalTokens,
buildTime: Date.now() - startTime,
},
};
} catch (error) {
logger.error(`Error building context: ${error.message}`);
throw new ContextBuildingError(`Failed to build context: ${error.message}`, {
cause: error,
});
}
}
// Helper methods
private processFiles(files: string[]): ContextChange[] {
// Implementation
}
private generateSummary(files: string[]): string {
// Implementation
}
private createEmptyContext(startTime: number): ContextBuildResult {
// Implementation
}
}
Adding a New Feature to the CLI
Identify the component that needs to be modified
Make the necessary changes
Add tests for the new feature
Example: Adding a --format
option to the default command
Copy // src/commands/default-command.ts
configure(yargs: Argv): Argv {
return yargs
.usage("Usage: $0 [options]")
.option("context", {
describe: "Context depth (light, deep)",
type: "string",
choices: ["light", "deep"],
default: "light",
})
.option("debug", {
describe: "Enable debug output",
type: "boolean",
default: false,
})
.option("dump", {
describe: "Dump context to a file",
type: "string",
})
.option("unstaged", {
describe: "Include unstaged changes",
type: "boolean",
default: false,
})
// New option
.option("format", {
describe: "Output format (text, json)",
type: "string",
choices: ["text", "json"],
default: "text",
});
}
async execute(args: any): Promise<void> {
// Existing implementation
// Handle the new format option
const { format } = args;
if (format === "json") {
console.log(JSON.stringify(result, null, 2));
} else {
// Default text output
logger.info(`Context built with ${result.stats.changedFiles} changed files and ${result.stats.relatedFiles} related files`);
logger.info(`Context building statistics:`);
logger.info(`- Total files: ${result.stats.totalFiles}`);
logger.info(`- Changed files: ${result.stats.changedFiles}`);
logger.info(`- Related files: ${result.stats.relatedFiles}`);
logger.info(`- Total tokens: ${result.stats.totalTokens}`);
logger.info(`- Build time: ${result.stats.buildTime}ms`);
}
}
Code of Conduct
We expect all contributors to follow our Code of Conduct (Coming Soon). Please be respectful and constructive in all interactions.
Communication
GitHub Issues : For bug reports, feature requests, and discussions
Pull Requests : For code contributions
Discussions : For general questions and community discussions
Recognition
All contributors will be recognized in the CONTRIBUTORS.md file (Coming Soon).
Resources
Thank You!
Thank you for contributing to DeepLint! Your efforts help make this project better for everyone.