How the 99 Completion System Handles #rules and @file References

The 99 completion system processes #rules and @file references through a provider-based architecture where trigger characters (# and @) activate specialized completion providers that validate tokens against project rules or filesystem paths and resolve them into markdown content before prompt submission.

The 99 plugin by ThePrimeagen implements a sophisticated completion framework that transforms shorthand references into full contextual content. Understanding how the 99 completion system resolves these tokens is essential for customizing AI interactions and extending the plugin's functionality.

Core Completion Engine Architecture

The central completion engine in lua/99/extensions/completions.lua orchestrates all token resolution. It maintains a registry of providers and exposes a unified parsing interface that the rest of the application consumes.

Provider Registration and Trigger Detection

The engine uses a registration pattern to support extensible token types. Each provider registers itself via M.register(provider), which stores the provider in a local table indexed by trigger character.

-- From completions.lua
M.register = function(provider)
  providers[provider.trigger] = provider
end

The M.get_trigger_characters() function exposes active triggers (# and @) to the completion UI, while M.get_keyword_pattern() returns regex patterns like #%S+ and @%S+ for fuzzy matching.

Parsing and Token Resolution

When processing a prompt, M.parse(prompt_text) iterates through registered providers to identify and resolve tokens:

  1. Pattern matching – Builds a regex from the provider's trigger character
  2. Token validation – Calls provider.is_valid(token) to verify existence
  3. Content resolution – Invokes provider.resolve(token) to fetch markdown content

Source: completions.lua – parse function

Rule Provider Implementation (#rules)

The #rules provider, defined in lua/99/extensions/agents/init.lua, handles references to custom rule files that guide AI behavior.

Provider Structure and Validation

The M.completion_provider(_99) function returns a provider table configured for rule resolution:

return {
  trigger = "#",
  name = "rules",
  get_items = function() ... end,
  is_valid = function(token) 
    return M.is_rule(_99.rules, token) 
  end,
  resolve = function(token) ... end,
}

The is_rule helper scans _99.rules.custom – the list of directories containing user-defined rule files – to verify that the referenced rule exists.

Content Resolution

When a rule token is validated, the resolve function:

  1. Locates the rule file in the custom rules directories
  2. Reads the file content
  3. Wraps it in a markdown code block
  4. Returns the formatted string for prompt insertion

Source: agents init – completion_provider

File Provider Implementation (@file)

The @file provider in lua/99/extensions/files/init.lua enables inline references to source code files, allowing the AI to see implementation details without manual copy-pasting.

File Discovery and Caching

The provider maintains a cached index of project files through M.discover_files():

  • Walks the project root directory
  • Respects exclusion patterns (e.g., node_modules, .git)
  • Enforces file size limits to prevent token overflow
  • Caches results for performance

Token Validation and Resolution

The completion provider structure mirrors the rules provider but uses file-specific validation:

return {
  trigger = "@",
  name = "files",
  get_items = function() ... end,
  is_valid = function(token) 
    return M.is_project_file(token) 
  end,
  resolve = function(token) ... end,
}

The is_project_file function checks the discovered file cache for exact path matches or filename matches. Upon resolution, resolve reads the file content, detects the language from the file extension, and returns a fenced code block.

Source: files init – completion_provider

Neovim CMP Integration

The 99 completion system bridges to Neovim's native completion UI through lua/99/extensions/cmp.lua, which implements the nvim-cmp source interface.

The CMP source delegates trigger character detection to the core engine:

function CmpSource.get_trigger_characters()
  return Completions.get_trigger_characters()
end

This ensures that typing # or @ automatically invokes the completion menu without hardcoding characters in the CMP layer. The get_completions method similarly delegates to the engine's parsing logic, converting 99 completion items into the format expected by nvim-cmp.

Source: cmp.lua – trigger characters

End-to-End Workflow Example

Consider a user working with the 99 plugin who types the following prompt:

-- User input:
local prompt = "Explain the core idea of #my_rule and show its implementation in @src/main.lua"

The completion system processes this through the following pipeline:

  1. Trigger Detection: The UI identifies # and @ as trigger characters via Completions.get_trigger_characters()

  2. Token Parsing: Completions.parse(prompt) extracts #my_rule and @src/main.lua

  3. Validation:

    • The agents provider confirms #my_rule exists in _99.rules.custom
    • The files provider verifies @src/main.lua exists in the project cache
  4. Resolution:

    • Rule content is wrapped in markdown
    • File content is read and fenced with ```lua
  5. Final Prompt Assembly:

local final_prompt = [[
Explain the core idea of <content of my_rule>
and show its implementation in 

```lua
-- src/main.lua
local function foo() … end

]]


## Summary

- The **99 completion system** uses a provider-based architecture in [`lua/99/extensions/completions.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/completions.lua) to handle special token references.
- **`#rules`** are resolved by the agents provider ([`lua/99/extensions/agents/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/init.lua)), which validates tokens against custom rule directories and returns markdown-wrapped content.
- **`@file`** references are handled by the files provider ([`lua/99/extensions/files/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/files/init.lua)), which maintains a cached project file index, validates paths, and returns fenced code blocks with language detection.
- The system integrates with **nvim-cmp** through [`lua/99/extensions/cmp.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/cmp.lua), exposing trigger characters dynamically based on registered providers.
- New token types can be added by implementing the provider interface and registering with `Completions.register()`.

## Frequently Asked Questions

### How does the 99 completion system detect when to show completion suggestions?

The system exposes trigger characters through `Completions.get_trigger_characters()`, which aggregates triggers from all registered providers. When the user types `#` or `@`, the nvim-cmp integration ([`lua/99/extensions/cmp.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/cmp.lua)) detects these characters and invokes the completion menu automatically.

### What happens if I reference a rule or file that doesn't exist?

The provider's `is_valid` function returns `false` for non-existent tokens. During the parsing phase in `Completions.parse()`, invalid tokens are skipped and not included in the final resolved prompt. The UI typically filters these out during completion suggestion display.

### Can I add custom trigger characters beyond # and @?

Yes. The provider architecture supports arbitrary trigger characters. You can create a new provider module implementing the `trigger`, `is_valid`, and `resolve` interface methods, then register it via `Completions.register(provider)` in [`lua/99/extensions/completions.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/completions.lua). The CMP integration will automatically pick up the new trigger.

### How does the file provider handle large files?

The files provider ([`lua/99/extensions/files/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/files/init.lua)) enforces size limits during the discovery phase in `M.discover_files()`. When resolving a token, `M.resolve()` checks the file size against configured limits before reading. Files exceeding the limit are either truncated or excluded from resolution to prevent context window overflow.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →