Skip to content

Latest commit

 

History

History
250 lines (214 loc) · 11.4 KB

AGENTS.md

File metadata and controls

250 lines (214 loc) · 11.4 KB

AGENTS.md

Guidance for AI agents and automated tooling working in this repository. Complements CONTRIBUTING.md (human contributors) with the concrete commands, conventions, and guardrails an agent needs for safe, consistent changes.

Project summary

Vim Plugin Manager is a lightweight Vim plugin manager built on Git submodules and Vim 8's native package system (pack/plugins/{start,opt}). The codebase is pure VimScript, organized into small single-responsibility modules.

Build, run, and test

There is no compilation step. The project is loaded directly by Vim.

  • Run the test suite (Vader):
    make test      # Interactive TUI
    make test-ci   # Headless (same output as CI)
    vader.vim is cloned automatically at the pinned SHA on first run. To use an existing clone: make test-ci VADER_DIR=./vader.vim.
  • The test target generates a throwaway .vaderrc.vim and runs vim -Nu .vaderrc.vim -c 'Vader! tests/*.vader' (interactive TUI).
  • The test-ci target runs the same suite via vim -es (headless/ex mode) producing clean plain-text output with no ANSI sequences.
  • Clean test artifacts:
    make clean
  • VimScript linting: optional, via vim-vint (not run in CI). Config: .vintrc.yaml (correctness=error, style=off).
  • CI runs test-ci: .github/workflows/test.yml (GitHub Actions) and .gitlab-ci.yml (GitLab). Keep both green.
  • CI matrix covers AlmaLinux 9/10, Debian Bookworm/Trixie, Ubuntu 24.04/26.04, Arch Linux. Gentoo runs non-blocking (allow_failure: true). The AlmaLinux 9 job runs on Vim 8.2 and proves the 8.2 floor is respected.
  • Real async CI runs via make test-async on both GitHub Actions and GitLab CI. Headless vim -es does not run the event loop; the async job uses script -qec "vim -N -u tests/async_smoke.vim" (util-linux pty) so Vim's event loop runs and real job callbacks fire. The smoke test exercises start_job with opts.callback, async#git, and the concurrency queue. Blocking on both CI systems.

Requirements: Vim 8.2+ (with +job and +channel), Git 2.39+. Neovim is not supported (it has lazy.nvim, packer.nvim and vim-plug). Windows and macOS are not supported: the project targets Linux only (Debian, Ubuntu, Arch, Gentoo, RHEL/AlmaLinux/Rocky).

The minimum Vim version floor is 8.2 and must not be raised without a concrete reason. It is set by RHEL 9 / AlmaLinux 9 / Rocky 9, which ship Vim 8.2.2637. RHEL 9 is supported until 2032. The codebase uses no Vim 9.0+ features; a vim9script migration is explicitly deferred (dominant cost is git/network I/O, not script execution). The guard if v:version < 802 in plugin/plugin_manager.vim is intentional - do not raise it to 900.

Coding conventions

For VimScript:

  • 2-space indentation. Keep lines under 100 characters where practical.
  • snake_case for functions and variables.
  • Internal (script-local) functions and variables are prefixed with s:.
  • Public functions follow plugin_manager#<module>#<function>().
  • Each file starts with a header comment:
    " path/to/file.vim - Short description
    " Maintainer: G.K.E. <gke@6admin.io>
  • Document functions with a short comment above them.
  • Prefer Vim script native idioms over shelling out when feasible.
  • Follow Semantic Versioning 2.0.0 for tags: PATCH = bug fix retrocompatible only. MINOR = addition d'API publique retrocompatible ou deprecation. MAJOR = breaking change.
  • The public API surface is: api.vim functions, :PluginManager* / :Plugin / :PluginBegin / :PluginEnd commands, and g:plugin_manager_* config variables. Everything else (core/*, git.vim, async.vim, ui.vim internals, cmd/*.vim) is internal and may change without deprecation.
  • Directory scoping: git commands run through git#execute, which injects git -C <dir> when a directory is provided (available since git 1.8.5; project floor is 2.39). Arbitrary shell commands (exec hooks, cp, rsync) run through core#util#run_in_dir, which uses cd <dir> && <cmd> (POSIX subshell, never touches Vim's process cwd). Never use :cd / :lcd in plugin code. Command strings must be single invocations (no shell ||, &&, ;).

Architecture

plugin/plugin_manager.vim     Entry point: config defaults + command definitions
autoload/plugin_manager/
  core.vim          Error handling (throw, handle_error, parse_error)
  core/log.vim      Log management (debug, trace, view, clear, rotation)
  core/cache.vim    Update check cache (read, write, TTL)
  core/util.vim     Paths, config, URL parsing, filesystem, plugin options
  git.vim           Git operations, .gitmodules cache, submodule status
  async.vim         Non-blocking async jobs (Vim job/channel) with a concurrency queue
  ui.vim            Sidebar rendering, spinners, operation tracking
  api.vim           Public API facade
  cmd.vim           Command dispatcher + legacy-format adapters
  cmd/*.vim         add, remove, update, status, list, helptags, reload,
                    backup, restore, remote, declare, check
ftdetect/ ftplugin/ syntax/   Sidebar buffer (filetype=pluginmanager)
doc/                          :help documentation
tests/                        Vader tests

Control flow: :PluginManager <cmd> -> plugin_manager#cmd#dispatch() -> plugin_manager#api#* -> plugin_manager#cmd#<cmd>#execute(). UI feedback goes through ui.vim; Git through git.vim; background work through async.vim.

Error handling

Internal errors use a structured, 4-field format:

PM_ERROR:<component>:<CODE>:<message>
  • Raise errors with plugin_manager#core#throw(component, code, message), not a bare throw 'PM_ERROR:...'. The valid codes per component live in s:error_types in core.vim.
  • Catch and present errors with plugin_manager#core#handle_error(v:exception, component).
  • Logging is handled centrally; do not write to the log file directly.

UI conventions

  • Use the operation API: plugin_manager#ui#start_operation(), #update_operation(), #complete_operation().
  • Use plugin_manager#ui#get_symbol() for glyphs (never reference the script-local s:symbols from outside ui.vim).
  • Do not reintroduce the removed legacy UI helpers (start_task, update_task, complete_task, box, themed_header, show_message, format_message).

Guardrails for agents

  • Never add network access (e.g. git fetch) on startup or on a timer unless it is strictly opt-in behind a g:plugin_manager_* flag defaulting to off.
  • Do not auto-commit or auto-push without honoring the relevant config flags (g:plugin_manager_auto_commit_on_update).
  • Keep changes minimal and within the most relevant module; create a new module only when needed.
  • When adding a config option, declare it in plugin/plugin_manager.vim with a sensible default and read it via plugin_manager#core#util#get_config().
  • Add or update Vader tests for new logic - test-first whenever practical: write the failing test before the implementation, and before the fix for bug fixes (regression test). When not practical (docs, CI plumbing, smoke fixtures), the PR must justify it. Prefer tests that do not require network access (mock with local fixtures).
  • Update documentation (README.md, doc/plugin_manager.txt, CHANGELOG.md) when behavior changes.

Commit conventions

Use Conventional Commits: type(scope): subject.

Valid types:

  • feat: new features (prefer over historical feature:)
  • fix: bug fixes
  • docs: documentation changes
  • test: test additions or changes
  • refactor: code refactoring
  • style: formatting changes
  • chore: routine maintenance
  • ci: CI/CD workflow changes
  • build: build system or dependency changes

Scopes (optional, recommended when relevant): core, async, ui, git, cmd, api, github, gitlab, deps.

Examples:

feat(git): add non-blocking status via async fetch
fix(core): implement missing s:check_log_rotation function
ci(github): bump checkout to v6 and cache to v5 for Node 24
test: add headless test-ci target with clean Vader output
docs: document GitFlow branching and Conventional Commits

Only commit when explicitly requested.

Branching model

GitHub Flow + tags. One protected branch (main) and release tags; no develop branch, no vX.Y maintenance branches. The ruleset "main-protection" requires the ci-green check, resolved conversations, and forbids force pushes and deletions; PRs are squash-merged (one commit per PR). Maintainers bypass the ruleset.

  • main -- stable code, tagged vX.Y.Z for releases. Releases are tags on main; hotfixes are prioritized fix/* branches targeting main.
  • External contributors: fork + feature/*, fix/*, docs/*, chore/* branch + pull request (template + Copilot review + maintainer review + squash merge). One branch per topic, rebased on main when asked.
  • Maintainers: local feature/* / fix/* / chore/* branches merged with --no-ff (preserves branch topology), pushed directly (bypass).

Multi-session coordination

Multiple agent sessions may work on this repository. These rules exist because two sessions mutating one working tree caused: commits landing on a foreign branch (the checkout changed under the first session), release tags created before the merge strategy was known (a squash merge orphaned them), and a PR merged while the other session was still planning its own push.

  • Worktree by default: any session whose work will produce commits starts in its own worktree:
    git worktree add ~/projets/wt/vim-plugin-manager/<topic> -b <topic>
    (run from the main clone; branch naming per CONTRIBUTING: fix/*, feat/*, chore/*). All work, commits and pushes happen there. The main clone stays on main and is never mutated by sessions. One worktree = one session.
  • Worktree lifecycle: push and PR integration happen from the worktree; when the work is merged, clean up with git worktree remove ~/projets/wt/vim-plugin-manager/<topic> and delete the branch.
  • Verify before acting: before any commit, run git branch --show-current, git status and git reflog -5. Re-verify after every pause.
  • Tags only after merge: release tags are created on the exact main commit AFTER the merge lands, never before. A squash merge rewrites history: tags created on pre-merge commits become orphans.
  • Dependabot PRs: dependabot prepares (grouped PRs, rebases, cooldown), the maintainer validates: gh pr review --approve then merge. No auto-merge: the ruleset require_extra_approval_for_unattributed_changes is the guardrail, the human approval is the policy.

Releases

Releases are automated via .github/workflows/release.yml:

  • Commit all changes and ensure the working tree is clean before tagging.
  • Update CHANGELOG.md for the new version (the single source of truth).
  • Per-file Version: headers are not maintained - the canonical version is the Git tag and CHANGELOG entry.
  • Tag and push: make tag VERSION=vX.Y.Z (creates an annotated tag that git push --follow-tags will carry to all three remotes). Manual equivalent: git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin main --follow-tags. Push one tag at a time: pushing multiple tags simultaneously can cause an older tag's release workflow to finish last, making GitHub mark it as "Latest" instead of the newest version.
  • Pushing a vX.Y.Z tag to GitHub triggers a job that runs make archive and publishes a GitHub Release with the .tar.gz asset and auto-generated notes.