# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project

`@yaegassy/coc-ansible` is a [coc.nvim](https://github.com/neoclide/coc.nvim) extension that wraps the official [`@ansible/ansible-language-server`](https://github.com/ansible/ansible-language-server) (bundled as a runtime dependency under `node_modules/@ansible/ansible-language-server/dist/cli.cjs`). It is a thin client/launcher: most language features come from the upstream server; this repo's job is to spawn it, mediate configuration, install Python tooling, and add a few coc-specific commands and code actions.

## Commands

Package manager is yarn (see `packageManager` field). The extension is built with esbuild, not `tsc`.

- `yarn build` — bundle `src/index.ts` → `lib/index.js` via `esbuild.js`
- `yarn watch` — same, in watch mode
- `yarn lint` — `eslint src --ext ts`
- `yarn clean` — `rimraf lib`
- `yarn prepare` — runs `node esbuild.js` (so `yarn install` produces a usable `lib/`)

There is no test suite. `tsconfig.json` is for editor/type-checking only — esbuild does the actual build, externalizing `coc.nvim` and targeting `node14.14`.

To install/test locally against a coc.nvim setup, point `Plug` at the working tree (the README shows `Plug 'yaegassy/coc-ansible', {'do': 'yarn install --frozen-lockfile'}` — the `prepare` script handles the build).

## Architecture

### Entry point: `src/index.ts`

`activate()` does four things in order:

1. **Resolve Python + Ansible tooling.** Calls `getCurrentPythonPath` (custom interpreter → `python3` → `python`) and probes for `ansible`, `ansible-lint`, `ansible-doc` via `existsCmdWithHelpOpt` (literally `<cmd> -h`). If `python.interpreterPath` is set, it instead checks for the `ansible` / `ansiblelint` Python modules with `existsPythonImportModule`.
2. **Fall back to a builtin venv** under `context.storagePath/ansible/venv/` when system tools are missing or `ansible.builtin.force` is set. If still missing, prompts the user to run the installer (`installer.ts`), which `pip install`s `ansible`, `ansible-lint`, and optionally `yamllint` into that venv.
3. **Spawn the language server** as a Node IPC child process. Server module path comes from `ansible.dev.serverPath` if set (for hacking on the upstream server locally), otherwise from the bundled `node_modules/@ansible/ansible-language-server/dist/cli.cjs`.
4. **Register feature modules** — each command/action lives in its own file under `src/commands/` or `src/actions/` and exports an `activate(context, ...)` function.

### Configuration middleware (the load-bearing trick)

The `configuration` middleware in `src/index.ts` intercepts every `workspace/configuration` request the language server sends and rewrites the `ansible` section before returning it. This is how the extension transparently redirects the server to use the builtin venv or disables linting:

- `ansible.path` and `ansible.validation.lint.path` are forced to the bare names `ansible` / `ansible-lint` (the server resolves them through the venv's PATH, not from user config).
- When no system `ansible` is present and no custom interpreter is set, `python.interpreterPath` is rewritten to point at the builtin venv's `python`.
- When `ansible-lint` isn't found (either as a command or as a Python module, depending on which path was taken), `validation.lint.enabled` is forced to `false` so the server doesn't try to invoke a missing binary.

If you change tool-resolution logic, the middleware almost certainly needs the corresponding adjustment — the server only sees what this function returns.

### Tool resolution: `src/tool.ts`

Two parallel APIs exist: `getCurrentPythonPath` returns a `{env, real}` `PythonPaths` object (used by the activation flow), and the older `getCurrentPythonPath2` returns a string. The `real` path (resolved via `fs.realpathSync`) is what gets passed to `python -m venv` so the venv is built against the underlying interpreter, not a shim. `getBuiltinToolPath` is the canonical place that knows the venv layout (`bin/` on POSIX, `Scripts/.exe` on Windows) — reuse it rather than reconstructing paths.

### Commands and actions

Each file under `src/commands/` registers one `CocCommand`; each under `src/actions/` registers one `CodeActionProvider` for the `ansible` / `yaml.ansible` document selector. Server-driven commands (`serverShowMetaData`, `serverResyncAnsibleInventory`) communicate with the language server over custom `NotificationType`s like `update/ansible-metadata` and `resync/ansible-inventory` — these names must match the server side.

## Key behavior notes

- The extension activates on language IDs `ansible` and `yaml.ansible`. Per the README, users are expected to set `g:coc_filetype_map = {'yaml.ansible': 'ansible'}` in their vim config; without that the document selector won't match.
- `ansibleServer.trace.server` (`off`/`messages`/`verbose`) toggles LSP tracing — useful when debugging server interactions.
- The bundled language server version is pinned in `package.json` (`@ansible/ansible-language-server`); bumping it is the typical "update the upstream features" change.
