Deeplint is still in the MVP development phase and not yet available for use.

Contributing

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

  • Git: For version control

Setting Up the Development Environment

  1. Fork the Repository

    Start by forking the DeepLint repository to your GitHub account.

  2. Clone Your Fork

    git clone https://github.com/YOUR_USERNAME/deeplint-cli.git
    cd deeplint-cli
  3. Install Dependencies

    pnpm install
  4. Set Up Environment Variables

    Copy the sample environment file and modify as needed:

    cp .env.sample .env

    Edit the .env file to include your OpenAI API key:

    OPENAI_API_KEY=your_api_key_here
  5. Build the Project

    pnpm build
  6. Run the Development Server

    pnpm dev

    This will start the development server, allowing you to run DeepLint with changes you make to the codebase.

  7. Verify Your Setup

    Run a simple command to verify that your development environment is set up correctly:

    pnpm dev -- --help

    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:

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:

Add context building functionality

- Implement file system scanner
- Add dependency analyzer
- Create token counter
- Fixes #123

Development Process

  1. Create a Branch

    git checkout -b feature/your-feature-name
  2. Make Your Changes

    Implement your changes, following the coding standards.

  3. Run Tests

    Ensure your changes pass all tests:

    pnpm test
  4. Lint Your Code

    Ensure your code follows the linting rules:

    pnpm lint
  5. Format Your Code

    Ensure your code is properly formatted:

    pnpm format
  6. Commit Your Changes

    git add .
    git commit -m "Your descriptive commit message"
  7. Push to Your Fork

    git push origin feature/your-feature-name
  8. Create a Pull Request

    Go to the DeepLint repository and create a pull request from your fork.

Pull Request Process

  1. Fill in the Pull Request Template

    Provide a clear description of the changes and reference any related issues.

  2. Update Documentation

    Ensure that any new features or changes are documented.

  3. Pass CI Checks

    Make sure your pull request passes all CI checks.

  4. Code Review

    Address any feedback from code reviewers.

  5. 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:

/**
 * 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:

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

# 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:

pnpm dev:debug

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:

  1. Open the file you want to debug

  2. Set breakpoints by clicking in the gutter

  3. Press F5 to start debugging

  4. Select the "Debug DeepLint" configuration

  5. Enter the command arguments when prompted

Example launch configuration:

{
  "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

  1. Create a new file in src/commands/ with the naming convention command-name-command.ts

  2. Extend the BaseCommand class

  3. Implement the required methods

  4. The command will be automatically discovered and registered

Example:

// 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

  1. Update the configuration types in src/config/types.ts

  2. Update the default configuration in src/config/templates.ts

  3. Update the configuration validation in src/config/validator.ts

  4. Update the documentation in docs/getting-started/configuration.md

Example:

// 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

  1. Update the context builder implementation in src/context-builder/context/context-builder.ts

  2. Update the context types in src/context-builder/context/context-types.ts

  3. Update the tests in tests/context-builder/

  4. Update the documentation in docs/concepts/context-building.md

Example:

// 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

  1. Identify the component that needs to be modified

  2. Make the necessary changes

  3. Add tests for the new feature

  4. Update documentation

Example: Adding a --format option to the default command

// 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`);
  }
}

Community Guidelines

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.

Last updated