AI Agent Development Instructions
January 4, 2026 · View on GitHub
Overview
sibling-jump.nvim is a Neovim plugin for context-aware code navigation using Tree-sitter.
Primary languages: TypeScript, JavaScript, JSX, TSX, Lua
Partial support: Java, C, C++, C#, Python
Read ARCHITECTURE.md first to understand the two-mode navigation system (sibling navigation vs block-loop).
Critical Rules
1. Testing is Non-Negotiable
# ALWAYS run tests after ANY change
bash tests/test_runner.sh
# All tests MUST pass - no exceptions
Why: Complex Tree-sitter AST interactions mean small changes can break navigation in unexpected ways.
2. Never Break Backward Compatibility
Public API in init.lua must remain stable:
M.setup(opts)- Same signatureM.jump_to_sibling(opts)- Same signature- User commands:
SiblingJumpBuffer*- Same names - Configuration options: Same names and types
Internal modules can change freely, but public API is sacred.
3. Don't Use Plenary for Tests
❌ DO NOT add plenary.nvim as a test dependency
✅ DO use the existing direct test runner (tests/run_tests.lua)
Why: Plenary's test isolation breaks Tree-sitter parser access.
4. Module Naming Convention
- Repository:
sibling-jump.nvim(hyphen) - Lua modules:
sibling_jump(underscore) - Always use:
require("sibling_jump.module")
5. Always Ask Before Committing
NEVER auto-commit changes. Always:
- Show summary of changes
- Explain what was fixed/changed and why
- Wait for explicit user approval
- Only commit after receiving approval
Development Workflow
Making Changes
# 1. Create feature branch (if not on one)
git checkout -b feature/your-feature
# 2. Make changes & run tests continuously
bash tests/test_runner.sh
# 3. Ask user before committing (ALWAYS!)
# 4. Commit with user approval
git add -A
git commit -m "feat: description"
# 5. Push when tests pass
git push origin feature/your-feature
Test-Driven Development
IMPORTANT: Write failing test BEFORE fixing bugs or adding features.
- Add fixture (if needed):
tests/fixtures/your_test.ts - Write failing test: In
tests/run_tests.luaortests/run_block_loop_tests.luatest("Description", function() vim.cmd("edit " .. fixtures_dir .. "your_test.ts") vim.api.nvim_win_set_cursor(0, {5, 0}) sibling_jump.jump_to_sibling({ forward = true }) local pos = vim.api.nvim_win_get_cursor(0) assert_eq(7, pos[1], "Should jump to line 7") end) - Run test - Verify it fails:
bash tests/test_runner.sh - Implement fix/feature - Make test pass
- Run all tests - Ensure no regressions
Why test-first?
- Proves the bug exists
- Specifies correct behavior
- Prevents future regressions
- Validates the fix works
Common Tasks
Debugging Navigation Issues
- Check Tree-sitter structure: Use
:InspectTree(Neovim 0.9+) or:TSPlaygroundToggle - Add temporary debug prints:
print("Node type:", node:type()) print("Parent:", parent and parent:type() or "nil") - Check if node is meaningful:
:lua local utils = require("sibling_jump.utils") :lua print("Meaningful?", utils.is_meaningful_node(vim.treesitter.get_node())) - Remove debug prints after fixing
Adding Block-Loop Handler
See ARCHITECTURE.md → Extension Points. Basic structure:
-
Create
lua/sibling_jump/block_loop/your_handler.lua:local M = {} function M.detect(node, cursor_pos) -- Walk AST to detect if cursor is in this construct -- Return: detected (bool), context (table with positions) end function M.navigate(context, cursor_pos, mode) -- Find next/prev position based on current cursor -- Return: {row, col} or nil at boundaries end return M -
Add to
block_loop.luahandlers list in priority order (most specific first) -
Add tests to
tests/run_block_loop_tests.lua -
Run tests:
bash tests/test_runner.sh
Adding Language Support
- Use
:InspectTreeto find node type names - Add to
config.luaMEANINGFUL_TYPES:-- Python "function_definition", "class_definition", "for_statement", - Test thoroughly with fixtures
Code Style
- Indentation: 2 spaces
- Line length: ~100 chars (no hard limit)
- Comments: Explain why, not what
- Functions:
snake_casefor local,M.snake_casefor exports - Early returns: Prefer guard clauses over deep nesting
- Nil checks: Always check nodes before calling methods
Avoid Deep Nesting
❌ Bad:
if condition1 then
if condition2 then
if condition3 then
-- work
end
end
end
✅ Good:
if not condition1 then return end
if not condition2 then return end
if not condition3 then return end
-- work
Tree-sitter Common Patterns
Walking up the tree:
local current = node
local depth = 0
while current and depth < MAX_DEPTH do
if current:type() == "target_type" then
return current
end
current = current:parent()
depth = depth + 1
end
Getting position:
local start_row, start_col, end_row, end_col = node:range()
-- Note: Tree-sitter is 0-indexed, Neovim cursor API is 1-indexed
Common Pitfalls
- 0-indexed vs 1-indexed: Tree-sitter is 0-indexed, Neovim cursor API is 1-indexed
- Nil nodes: Always check
if not node then return nil end - Node types vary by language: Check actual names in
:InspectTree - Exclusive end_col: Tree-sitter's
end_colpoints after last char, subtract 1 to land on it - Priority matters: Wrong handler order = wrong navigation behavior
Known Limitations
Lua Member Calls in Declarations
For declarations with member calls like local result = handlers.method():
- First jump navigates the declaration (local → closing paren)
- Second jump may switch context to method navigation
- Why: Both
call_expressionsanddeclarationshandlers detect it,call_expressionshas priority - Workaround: Start cursor precisely on
localkeyword for pure declaration navigation
This is an edge case that would require stateful context tracking to fix.
Git Practices
Commit Messages
Follow conventional commits:
feat: add feature description
fix: fix bug description
refactor: refactor description
docs: documentation changes
test: add or fix tests
Branch Naming
feature/description- New featuresfix/description- Bug fixesrefactor/description- Code refactoringdocs/description- Documentation
Performance Notes
✅ Do:
- Use early returns for cheap checks
- Bounded loops with max depth (prevent infinite loops)
- Lazy module loading (Lua caches requires)
❌ Don't:
- Cache Tree-sitter nodes (Neovim handles this)
- Use async operations (navigation must be instant)
- Unbounded recursion (use iteration with depth bounds)
Success Checklist
Before considering any change complete:
- All tests pass (
bash tests/test_runner.sh) - No new warnings or errors
- Public API unchanged (or breaking changes documented)
- Tests added for new functionality
- Code follows style guidelines
- Comments explain "why" not "what"
- No debug prints left in code
- User approved commit (if committing)
Resources
- Architecture:
ARCHITECTURE.md- Module structure and design decisions - Main README:
README.md- User documentation - Changelog:
CHANGELOG.md- Version history - Neovim help:
:help treesitter-core,:help lua-guide,:help api
Questions?
When unsure:
- ✅ Check
ARCHITECTURE.mdfor design decisions - ✅ Check test suite for expected behavior examples
- ✅ Use
:InspectTreeto inspect Tree-sitter structure - ✅ Run tests frequently to catch regressions
- ✅ Ask the user!
Remember: This plugin helps developers navigate code efficiently. Every change should make navigation more intuitive, never more confusing.