mizchi/ailab

repository·main·Indexed 20 days ago

https://github.com/mizchi/ailab

An experimental playground for AI-driven code generation using Deno, focused on structured coding rules for AI agents like CLINE and Roo. It includes several modules: @mizchi/npm-summary for extracting TypeScript type definitions and generating AI summaries via Google Gemini, ts-callgraph for static analysis and visualization of TypeScript call graphs, TypePredictor for generating Zod schemas from JSON data, and a Todo CLI for task management.

Tokens
70.9K
Snippets
210
Records
268
Agent score
72%

What's inside ailab

  1. Lighthouse Documentation and Recipes

    main

    Lighthouse provides various guides and recipes for advanced usage. Key topics include:

    • Dealing with variance: Understanding how results change between runs.
    • Programmatic usage: Using Lighthouse within your own Node.js applications.
    • Authenticated pages: Testing sites that require login.
    • Plugins & Audits: How to develop new plugins or create custom audits to extend Lighthouse functionality.
    • Mobile testing: Running audits specifically for mobile devices.
  2. Compare Traditional DDD vs Lightweight DDD approaches

    main

    The repository demonstrates two ways to implement Domain-Driven Design:

    Traditional DDD (ddd-sample)

    • Structure: Clear layered architecture (domain, application, infrastructure).
    • Complexity: Higher, follows strict Evans-style DDD terminology (Entities, Value Objects, Repositories).
    • Best for: Complex domains requiring strict modeling.

    Lightweight DDD (ddd-sample-light)

    • Structure: Flatter structure (e.g., src/domain, src/adapters).
    • Logic: Uses pure functions and immutable data structures instead of heavy classes.
    • Best for: Small to medium-sized projects where simplicity and speed are prioritized.
  3. What is Functional Domain Modeling (FDM)?

    main

    Functional Domain Modeling (FDM) is an approach to Domain-Driven Design (DDD) using the functional programming (FP) paradigm. It focuses on representing business processes as workflows of pure functions and data structures as immutable types.

    Key principles include:

    • Type-Driven Development: Designing types first to "make illegal states unrepresentable."
    • Immutability: Representing state transitions as functions that return new objects rather than mutating existing ones.
    • Algebraic Data Types (ADT): Using types to model complex domain logic and state transitions.
    • Separation of Concerns: Explicitly separating types from functions and pushing side effects to the boundaries of the system.
  4. What is the Sampler Pattern and how does it work?

    main

    The Sampler Pattern is a design pattern used to extract representative samples from large datasets. It is particularly useful for:

    • Reducing log noise by displaying only representative samples instead of every entry.
    • Extracting statistically significant samples from massive data streams.
    • Debugging by isolating specific data points that meet certain criteria.

    This pattern leverages the ECMAScript Symbol.dispose and the using syntax. By using using sampler = createSimpleSampler(...), the sampler automatically triggers a display function (via [Symbol.dispose]()) as soon as the execution leaves the current scope. This ensures that results are always reported without requiring manual cleanup calls.

    {
      using resource = createResource();
      // resource is used here
      // When the scope ends, resource[Symbol.dispose]() is automatically called
    }
  5. Implement the Adapter pattern in TypeScript

    main

    The Adapter pattern in TypeScript is used to abstract external dependencies, making your code more testable and decoupled. The core principles are:

    1. Abstract via Interfaces: Define the shape of the dependency using an interface.
    2. Provide Implementations: Use either functions or classes to fulfill the interface.
    3. Use Mock Implementations for Testing: Swap real implementations with mocks during tests.
    4. Use Result types for Error Handling: Instead of throwing exceptions, return a Result type (e.g., from neverthrow) to represent success or failure explicitly.
  6. Implement Domain Events

    main

    Domain Events represent something significant that has happened within the domain. They are used to notify other parts of the system about state changes.

    Rules for Domain Events:

    • Naming: Use past tense (e.g., OrderPlaced, OrderPaid).
    • Immutability: Events represent facts that occurred in the past and cannot be changed.
    • Integration with Aggregates: An AggregateRoot can collect events during its lifecycle and provide a mechanism to clearEvents() after they are published.

    Core Components:

    • DomainEvent: Base interface with occurredAt: Date.
    • DomainEventPublisher: Interface for publishing and subscribing to events.
    • AggregateRoot: Abstract class to manage the collection of events within an aggregate.
    interface DomainEvent {
      readonly occurredAt: Date;
    }
    
    class OrderPlaced implements DomainEvent {
      readonly occurredAt: Date;
      constructor(
        readonly orderId: OrderId,
        readonly customerId: CustomerId,
        readonly orderTotal: number,
      ) {
        this.occurredAt = new Date();
      }
    }
    
    abstract class AggregateRoot {
      private _domainEvents: DomainEvent[] = [];
    
      protected addDomainEvent(event: DomainEvent): void {
        this._domainEvents.push(event);
      }
    
      clearEvents(): DomainEvent[] {
        const events = [...this._domainEvents];
        this._domainEvents = [];
        return events;
      }
    }
    
    class OrderWithEvents extends AggregateRoot {
      // ... implementation ...
      markAsPaid(): void {
        // ... logic ...
        this._status = "paid";
        this.addDomainEvent(new OrderPaid(this.id, this.total));
      }
    }
  7. Use Lighthouse Gather and Audit modes (Lifecycle control)

    main

    You can run specific parts of the Lighthouse lifecycle using the --gather-mode (-G) and --audit-mode (-A) flags. This is useful for separating the browser interaction/artifact collection phase from the report generation phase.

    • Gather Mode (-G): Launches the browser, collects artifacts (like traces and screenshots), and saves them to disk (defaulting to ./latest-run/), then quits.
    • Audit Mode (-A): Skips browser interaction, loads artifacts from disk, runs audits on them, and generates a report.
    • Gather + Audit (-GA): Performs both steps and saves artifacts to disk.

    You can provide a custom directory for artifacts by appending a path to the flag.

    Examples:

    # Collect artifacts and quit
    lighthouse http://example.com -G
    
    # Run audits using previously collected artifacts
    lighthouse http://example.com -A
    
    # Collect and save to a specific directory
    lighthouse -GA=./my-artifacts https://example.com
    lighthouse http://example.com -GA
  8. Implement Railway Oriented Programming with Result types

    main

    Since TypeScript lacks a built-in Result type, use libraries like neverthrow, fp-ts, or effect to handle errors without exceptions. This enables Railway Oriented Programming, where workflows are constructed by composing small functions using .andThen() (or similar methods) to create a pipeline that handles success and failure paths automatically.

    import { err, ok, Result } from "neverthrow";
    
    // Example of a workflow using Railway Oriented Programming
    const placeOrderWorkflow = (
      command: PlaceOrderCommand,
    ): Result<OrderId, Error> => {
      return validateOrder(command)
        .andThen(reserveInventory)
        .andThen(processPayment)
        .andThen(createOrder)
        .andThen(notifyCustomer);
    };
  9. Implement the Infrastructure Layer in DDD

    main

    The Infrastructure Layer handles technical details required by the application, such as persistence, messaging, and external integrations. This layer implements the interfaces defined in the domain or application layers.

    Common implementations include:

    • Repositories: Concrete implementations for database access (e.g., InMemoryCustomerRepository, SqlOrderRepository).
    • Event Publishers: Implementations for broadcasting domain events (e.g., SimpleDomainEventPublisher).
    • External Clients: API clients for third-party services.
    // Example: In-memory Repository implementation
    class InMemoryCustomerRepository implements CustomerRepository {
      private customers: Map<string, Customer> = new Map();
    
      async findById(id: CustomerId): Promise<Customer | null> {
        const customer = this.customers.get(id.toString());
        return customer || null;
      }
    
      async save(customer: Customer): Promise<void> {
        this.customers.set(customer.customerId.toString(), customer);
      }
    }
    
    // Example: Simple Domain Event Publisher
    class SimpleDomainEventPublisher implements DomainEventPublisher {
      private handlers: Map<string, Array<(event: DomainEvent) => void>> = new Map();
    
      publish<T extends DomainEvent>(event: T): void {
        const eventType = event.constructor.name;
        const eventHandlers = this.handlers.get(eventType) || [];
        for (const handler of eventHandlers) {
          try {
            handler(event);
          } catch (error) {
            console.error(`Error handling event ${eventType}:`, error);
          }
        }
      }
    
      subscribe<T extends DomainEvent>(
        eventType: new (...args: any[]) => T,
        handler: (event: T) => void,
      ): void {
        const eventName = eventType.name;
        if (!this.handlers.has(eventName)) {
          this.handlers.set(eventName, []);
        }
        this.handlers.get(eventName)!.push(handler as any);
      }
    }
  10. Understand the PathInfo and PathSegment data structures

    main

    The system relies on two core interfaces to represent the structure and metadata of JSON data during analysis:

    PathSegment

    Represents a single step in a JSON path (e.g., a key in an object or an index in an array).

    • type: Either "key", "index", or "wildcard".
    • value: The actual key name or index.
    • arrayAccess: Boolean indicating if this segment is part of an array access.
    • arrayInfo: Contains isTuple (boolean) and itemTypes (an array of PathInfo) if the segment is an array.

    PathInfo

    Represents a complete path to a value, including its type and metadata.

    • segments: An array of PathSegment objects.
    • value: The actual value found at this path.
    • type: The primitive type ("string", "number", "boolean", "null", "object", or "array").
    • isNullable: Boolean indicating if the value can be null.
    • metadata: Advanced information used for prediction, such as occurrences (count), patterns (for enum prediction), or recordPattern (for Record<string, T> detection).
    interface PathSegment {
      type: "key" | "index" | "wildcard";
      value: string;
      arrayAccess?: boolean;
      arrayInfo?: {
        isTuple: boolean;
        itemTypes: PathInfo[];
      };
    }
    
    interface PathInfo {
      segments: PathSegment[];
      value: unknown;
      type: "string" | "number" | "boolean" | "null" | "object" | "array";
      isNullable: boolean;
      metadata?: {
        occurrences: number;
        patterns?: string[];
        recordPattern?: {
          keyPattern: string;
          valueType: PathInfo;
        };
      };
    }
  11. Switch implementation modes in AI coding assistants

    main

    Once .clinerules and .roomodes are generated, you can instruct your AI coding assistant to switch between predefined modes.

    Available Modes:

    • deno-script (Deno:ScriptMode): Script mode for single-file implementations.
    • deno-module (Deno:Module): Module mode for multi-file implementations.
    • deno-tdd (Deno:TestFirstMode): Test-first mode (writing types and tests before implementation).

    How to switch:

    1. Natural Language: Tell the assistant: "モードを deno-script に切り替えてください。" (Switch to deno-script mode).
    2. File Markers: Include specific markers at the top of your file:
      • @script for Script mode
      • @tdd for Test-first mode

    Example:

    // @script @tdd
    // This file is implemented using both script and TDD modes
  12. Understand Domain-Driven Design (DDD) principles

    main

    Domain-Driven Design (DDD) is a software development methodology that focuses on modeling the business domain accurately. Instead of focusing on UI or functions first, DDD prioritizes understanding the core business logic (the domain) and creating a shared model between developers and domain experts.

    Core Principles:

    1. Iterative Modeling: Build the domain model incrementally by accumulating domain knowledge.
    2. Ubiquitous Language: Use the domain model as a common language between developers and domain experts (users, business stakeholders).
    3. Code Alignment: Ensure the domain model and the implementation code are consistently mapped to one another.