mcollina/skills

repository·main·Indexed 23 days ago

https://github.com/mcollina/skills

A collection of specialized expertise (skills) designed to enhance AI-assisted development. It provides high-signal guidance across domains including Node.js internals, Fastify best practices, advanced TypeScript type systems, and the Diátaxis documentation framework. The repository includes a skill-optimizer for improving AI activation and a comprehensive benchmarking workflow to evaluate performance and prevent regressions across different AI models.

Tokens
194.2K
Snippets
562
Records
698
Agent score
84%

What's inside @matteo.collina/skills

  1. Overview of Matteo Collina's Skills

    main
    Matteo Collina's Skills is a collection of specialized skills designed for AI-assisted development. It provides high-signal expertise across various domains including Node.js internals, Fastify best practices, TypeScript advanced type systems, and documentation using the Diátaxis framework. Developers can use these skills to improve the quality of AI-generated code, documentation, and workflows.
  2. When to use the skill-optimizer

    main

    The skill-optimizer is designed to improve the effectiveness of AI skills. Use this skill when you need to:

    • Improve activation: Ensure a skill is actually applied by models.
    • Diagnose failures: Understand why certain criteria fail across different models.
    • Prevent regressions: Ensure a skill update does not make model outputs worse.
    • Refactor for context pressure: Improve skill text so it is retrieved and followed more effectively when context is limited.
    • Establish benchmarks: Build repeatable benchmark loops and release gates for skill deployment.
  3. What is N-API (Node-API)?

    main
    N-API is Node.js's ABI-stable API for building native addons. Unlike older V8-based addons that require recompilation when Node.js or V8 versions change, N-API code remains compatible across different Node.js versions because it interacts with a stable interface rather than internal engine structures.
  4. Understand the Node.js Child Process Architecture

    main

    The Node.js child process implementation operates across three distinct layers:

    1. JavaScript Layer (lib/child_process.js): Provides the high-level API including spawn(), fork(), exec(), and execFile().
    2. Internal JS (lib/internal/child_process.js): Manages the ChildProcess class, channel setup, and synchronous spawning.
    3. C++ Bindings (src/process_wrap.cc): Interfaces with the OS via the ProcessWrap class to handle Spawn() and Kill() operations.
    4. libuv: The underlying abstraction layer that handles actual process spawning (uv_spawn), killing (uv_process_kill), and standard I/O pipes (uv_pipe_t).
    JavaScript Layer (lib/child_process.js)
    ├── spawn()
    ├── fork()
    ├── exec()
    └── execFile()
            │
            ▼
    Internal JS (lib/internal/child_process.js)
    ├── ChildProcess class
    ├── setupChannel()
    └── spawnSync
            │
            ▼
    C++ Bindings (src/process_wrap.cc)
    ├── ProcessWrap
    ├── Spawn(), Kill()
            │
            ▼
    libuv
    ├── uv_spawn()
    ├── uv_process_kill()
    └── uv_pipe_t (for stdio)
  5. Use Async Notifications for Thread Signaling

    main

    In C/libuv, uv_async_t allows one thread (e.g., a worker thread) to wake up the main event loop thread. This is used for signaling between threads. In Node.js, this pattern is used internally for Worker thread communication and N-API async callbacks.

    #include <uv.h>
    uv_async_t async;
    
    void async_cb(uv_async_t* handle) {
      printf("Received async notification\n");
    }
    
    void worker_thread(void* arg) {
      // Do some work...
      uv_async_send(&async);  // Wake up main thread
    }
    
    int main() {
      uv_loop_t* loop = uv_default_loop();
      uv_async_init(loop, &async, async_cb);
      // Start worker thread...
      return uv_run(loop, UV_RUN_DEFAULT);
    }
  6. Resolve overloads using top-to-bottom ordering

    main

    TypeScript attempts to match function calls to overloads in the order they are defined, from top to bottom. To ensure correct type inference, always place more specific overloads before more general ones.

    // More specific overloads first
    function processValue(value: string): string;
    function processValue(value: number): number;
    // General implementation signature
    function processValue(value: string | number): string | number {
      if (typeof value === "string") {
        return value.toUpperCase();
      }
      return value * 2;
    }
    
    const str = processValue("hello"); // Type: string
    const num = processValue(42); // Type: number
    function processValue(value: string): string;
    function processValue(value: number): number;
    function processValue(value: string | number): string | number {
      if (typeof value === "string") {
        return value.toUpperCase();
      }
      return value * 2;
    }
    
    const str = processValue("hello");
    const num = processValue(42);
  7. Default Content Type Parsers in Fastify

    main

    Fastify includes built-in parsers for common content types. By default, it handles application/json (parsing the body into a JavaScript object) and text/plain (parsing the body into a string).

    import Fastify from 'fastify';
    
    const app = Fastify();
    
    // Built-in parsers:
    // - application/json
    // - text/plain
    
    app.post('/json', async (request) => {
      // request.body is parsed JSON object
      return { received: request.body };
    });
    
    app.post('/text', async (request) => {
      // request.body is string for text/plain
      return { text: request.body };
    });
  8. Pattern matching with `infer` in template literals

    main

    You can use the infer keyword within a template literal type to extract specific parts of a string type.

    Remove Prefix

    To remove a specific prefix, use infer to capture the remainder of the string:

    type RemoveMaps<T> = T extends `maps:${infer Rest}` ? Rest : T;
    
    type Test1 = RemoveMaps<"maps:longitude">; // "longitude"

    Remove Suffix

    To remove a suffix, capture the prefix before the suffix:

    type RemovePostSuffix<T> = T extends `${infer Prefix}:post` ? Prefix : T;
    
    type Test = RemovePostSuffix<"attribute:post">; // "attribute"

    Split on Delimiter

    You can recursively split a string into a tuple based on a delimiter:

    type Split<S extends string, D extends string> =
      S extends `${infer Head}${D}${infer Tail}`
        ? [Head, ...Split<Tail, D>]
        : S extends ""
        ? []
        : [S];
    
    type Parts = Split<"a-b-c", "-">; // ["a", "b", "c"]
  9. Understand the skill benchmarking workflow and failure modes

    main

    The benchmarking workflow is designed to catch two specific failure modes before shipping skill changes:

    1. Non-activation: This occurs when criteria stay at 0% even when the skill is enabled.
    2. Regression: This occurs when the score decreases when the skill is enabled compared to the baseline (without the skill).

    When to run benchmarks

    • When editing any skills/*/SKILL.md file.
    • When editing any skills/*/rules/*.md file.
    • When preparing a release of updated skills.
  10. Prefer named exports over default exports

    main

    Use named exports instead of export default. Named exports provide better support for refactoring, improved IDE autocompletion, and more efficient tree-shaking.

    // GOOD - named exports
    export function createServer(config: Config): Server {
      // ...
    }
    
    export function createClient(config: Config): Client {
      // ...
    }
    
    // AVOID - default exports
    export default function createServer(config: Config): Server {
      // ...
    }
  11. How the Writable Stream Write Flow works

    main

    The lifecycle of a write operation follows this path:

    1. JS: stream.write(chunk) is called.
    2. JS: stream._write(chunk, encoding, callback) is invoked (user implementation).
    3. C++: WriteString() or WriteBuffer() is called.
    4. C++: DoWrite() triggers uv_write().
    5. libuv: The write request is queued.
    6. libuv: write_cb is triggered upon completion.
    7. C++: WriteWrap::OnDone() is called.
    8. JS: afterWrite() triggers the user's callback().
    9. JS: The 'drain' event is emitted if the buffer was previously full.
    1. JavaScript: stream.write(chunk)
       ↓
    2. JavaScript: _write(chunk, encoding, callback) [user implements]
       ↓
    3. C++: WriteString() or WriteBuffer()
       ↓
    4. C++: DoWrite() → uv_write()
       ↓
    5. libuv: Queue write request
       ↓
    6. libuv: write_cb when complete
       ↓
    7. C++: WriteWrap::OnDone()
       ↓
    8. JavaScript: afterWrite() → callback()
       ↓
    9. JavaScript: 'drain' event if buffer was full
  12. TypeScript requirements for type stripping

    main

    Type stripping works by removing type annotations without transforming code. To ensure compatibility, your TypeScript code must follow these constraints:

    1. Use Type-Only Imports

    Always use the type keyword for imports that only involve types. This prevents runtime errors during stripping.

    2. No Enums

    Enums require code transformation. Instead, use a const object with a corresponding type.

    3. No Namespaces

    Namespaces require transformation. Use standard ES modules (export) instead.

    4. No Constructor Parameter Properties

    Avoid using access modifiers (e.g., public, private) directly in constructor parameters. Use explicit property declarations and assignments instead.

    5. No Legacy Decorators

    Use the TC39 (stage 3) decorator syntax. Compatibility for decorators depends on your specific Node.js version.

    // GOOD - type-only import
    import type { User, Config } from './types.ts';
    import { createUser, type User } from './user.ts';
    
    // GOOD - const object instead of enum
    const Status = {
      Active: 'active',
      Inactive: 'inactive',
    } as const;
    type Status = (typeof Status)[keyof typeof Status];
    
    // GOOD - explicit property declaration instead of parameter properties
    class User {
      name: string;
      private age: number;
    
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
    }