# How to Configure Custom SKILL.md Rules for AI Context in 99

> Learn to configure custom SKILL.md rules for AI context in 99. Add directories to custom_rules, structure each skill, and use # to trigger AI completion easily.

- Repository: [ThePrimeagen/99](https://github.com/theprimeagen/99)
- Tags: how-to-guide
- Published: 2026-02-16

---

**To configure custom SKILL.md rules for AI context in 99, add your rule directories to the `custom_rules` list in the setup configuration, ensure each skill lives in its own subdirectory with a [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) file, and trigger completion with the `#` character in any 99 prompt buffer.**

ThePrimeagen's **99** is a Neovim plugin that enriches AI prompts with project-specific context through custom skill files. By configuring custom SKILL.md rules for AI context in 99, you can inject domain knowledge, coding standards, or framework-specific instructions directly into your AI workflow without leaving your editor.

## Understanding the SKILL.md Workflow in 99

99 implements a three-stage pipeline for managing custom AI context:

1. **Discovery** – During setup and refresh cycles, the plugin scans configured directories for `*/SKILL.md` patterns using `helpers.ls` in [`lua/99/extensions/agents/helpers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/helpers.lua).
2. **Completion** – When you type `#` in a 99 prompt buffer, the completion provider queries the cached rule table and displays available skills with previews generated by `helpers.head`.
3. **Resolution** – Upon sending the prompt, `get_rule_content` reads the full skill file, wraps it in XML-style tags (`<skill_name>…</skill_name>`), and injects it into the AI context.

All state management happens through `_99_state.completion.custom_rules`, initialized in [`lua/99/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/init.lua) and refreshed via `_99.State:refresh_rules`.

## Step-by-Step: Configure Custom SKILL.md Rules

### 1. Define Rule Directories in Setup

Configure the `custom_rules` option during plugin initialization in your Neovim configuration. In [`lua/99/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/init.lua), the `_99.setup` function expands paths and validates them before storing in global state.

```lua
require("99").setup({
  completion = {
    -- Directories containing skill subdirectories
    custom_rules = {
      "scratch/custom_rules/",      -- Resolves to cwd/scratch/custom_rules/
      vim.fn.expand("~/ai-skills/"), -- Absolute path with ~ expansion
    },
    source = "cmp",   -- Use nvim-cmp for # completion

  },
})

```

Paths support relative references, absolute paths, and `~` expansion for home directories.

### 2. Create the Directory Structure

Each skill requires its own subdirectory containing a [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) file. The `helpers.ls` function in [`lua/99/extensions/agents/helpers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/helpers.lua) searches for both direct [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) files and `*/SKILL.md` patterns within configured directories.

```

scratch/custom_rules/
├─ vim/
│  └─ SKILL.md          # Skill name: "vim"

├─ front-end/
│  └─ SKILL.md          # Skill name: "front-end"

└─ backend/
   └─ SKILL.md          # Skill name: "backend"

```

Example [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) content:

```markdown

# vim

When generating code, prefer Vim motions for text manipulation.
Use registers for complex yank/paste operations.
Avoid arrow keys; use hjkl navigation.

```

### 3. Refresh and Verify Rules

After configuration changes, rules populate automatically during the next refresh cycle. The `Agents.rules` function in [`lua/99/extensions/agents/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/init.lua) builds the rule table by aggregating results from all configured directories.

To verify your configuration programmatically:

```lua
local M = require("99.extensions.agents")
local rules = M.rules(_99_state)
print(vim.inspect(rules.custom))
-- Output: { "vim", "front-end", "backend" }

```

## How 99 Discovers and Loads SKILL.md Files

The discovery mechanism relies on two core components in `lua/99/extensions/agents/`:

**`helpers.ls`** (lines 18-28): Expands directory globs to locate skill files. It handles both flat structures ([`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) in the root of the custom_rules folder) and nested structures (`<skill>/SKILL.md`).

**`Agents.rules`** (lines 29-38): Iterates through `_99_state.completion.custom_rules`, calls `helpers.ls` for each directory, and constructs a table containing `name`, `path`, and `absolute_path` for every discovered skill.

The system caches these results in the global state object, refreshing automatically when Neovim's working directory changes or when explicitly triggered via `_99.State:refresh_rules`.

## Using Custom Rules in AI Prompts

### Triggering Rule Completion

When editing a 99 prompt buffer, type `#` to trigger the completion menu. The completion provider in [`lua/99/extensions/agents/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/init.lua) (lines 31-53) generates items by calling `helpers.head` on each rule file to extract the first few lines for the preview window.

- **Label**: Displays the skill name (directory name)
- **Insert text**: Inserts `#<relative_path_to_SKILL.md>`
- **Documentation**: Shows the header content of the skill file

### Rule Resolution and Context Injection

When you submit the prompt, the `get_rule_content` function (lines 12-27 in [`lua/99/extensions/agents/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/init.lua)) processes the inserted rule references:

1. Parses the `#<path>` pattern from the prompt text
2. Locates the corresponding [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) file using the cached rule table
3. Reads the entire file contents
4. Wraps the content in XML-style tags: `<skill_name>…file contents…</skill_name>`
5. Injects the wrapped content into the AI context before sending the request

This ensures the AI receives the full skill context without cluttering your visible prompt buffer with large text blocks.

## Advanced Configuration Tips

**Adding Rules at Runtime**

You can dynamically extend your rule set without restarting Neovim:

```lua
-- Add a new directory to the configuration
table.insert(_99_state.completion.custom_rules, vim.fn.expand("~/new_skills/"))
-- Refresh the rule cache
_99_state:refresh_rules()

```

**Multiple Directory Support**

The `custom_rules` array accepts multiple paths, allowing you to organize skills by domain or share common rules across projects:

```lua
custom_rules = {
  "project_specific/skills/",
  vim.fn.expand("~/global_ai_skills/"),
  "/opt/company_wide_rules/",
}

```

## Summary

- **Configure directories** in `setup()` using the `completion.custom_rules` option to declare where 99 should look for skill files.
- **Structure skills** as `<skill_name>/SKILL.md` within those directories; 99 discovers them via `helpers.ls` in [`lua/99/extensions/agents/helpers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/helpers.lua).
- **Trigger completion** by typing `#` in any 99 prompt buffer; the completion provider in [`lua/99/extensions/agents/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/init.lua) surfaces available skills.
- **Inject context** automatically when sending prompts; `get_rule_content` wraps skill files in XML tags and inserts them into the AI context.
- **Refresh dynamically** by calling `_99_state:refresh_rules()` after modifying `custom_rules` at runtime.

## Frequently Asked Questions

### Where does 99 store the custom rule configuration?

99 stores your custom rule directories in the global state object `_99_state.completion.custom_rules`, which is initialized during `_99.setup()` in [`lua/99/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/init.lua) (lines 34-56). The actual skill files remain in your specified directories; 99 only caches references to their paths and metadata, not the file contents themselves.

### Can I add new SKILL.md rules without restarting Neovim?

Yes. You can append new directories to `_99_state.completion.custom_rules` at runtime using `table.insert()`, then call `_99_state:refresh_rules()` to rebuild the rule cache. The `Agents.rules` function in [`lua/99/extensions/agents/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/init.lua) will immediately pick up any new [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) files in the added directories.

### What format should the SKILL.md files follow?

[`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) files should follow standard Markdown syntax. The first few lines should contain a clear header or description, as `helpers.head` in [`lua/99/extensions/agents/helpers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/helpers.lua) extracts the beginning of the file for the completion preview. The full contents are injected verbatim into the AI context wrapped in `<skill_name>` XML tags, so structure the content as instructions or reference material for the AI.

### How does 99 handle multiple custom_rules directories?

99 iterates through every path in the `custom_rules` array using `Agents.rules` in [`lua/99/extensions/agents/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/init.lua). For each directory, it calls `helpers.ls` to discover [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) files. Rules from all directories are aggregated into a single table, with each skill identified by its directory name. You can organize skills by project, domain, or team by separating them into different directories in the configuration.