dexter
AGENTS.md421 starsChanged 7 months agoMITView on GitHub
For a detailed reference of the LSP code flows, use-chain resolution, variable scoping, and rename logic, see docs/architecture.md. Install golangci-lint if needed: - Our guiding principles for all code written are: We want maximum performance and speed, but without sacrificing correctness of results. It's okay to sacrifice some readability in the name of performance, but be mindful of the tradeoffs. - When fixing reported bugs, write the regression test first, verify the failure, and then write the fix. - When…
- Installs packages
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,700 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repositoryOne other file it gives its agents.
- CLAUDE.mdCLAUDE.md
The file
We split this AGENTS.md into the rules it gives an agent
5 rules. About 0 are common, 2 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 23
When fixing reported bugs, write the regression test first, verify the failure, and then write the fix.
- Very specific: line 24
When given real code causing bugs, use fake/generic module and function names in tests (e.g.
- Distinctive: line 26
When writing code that affects the in-document runtime parsing that is similar to the index parsing (or vice versa), see if the code can be …
- Very specific: line 39
Do not change Version or IndexVersion in a feature PR.
- Very specific: line 63
Use parser.TokenWalker for new code that iterates over tokens.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Agent guidelines
For a detailed reference of the LSP code flows, use-chain resolution, variable scoping, and rename logic, see docs/architecture.md.
Building and testing
make build # build dexter binary
make test # run all tests
make lint # run after completing a set of changesInstall golangci-lint if needed:
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4Conventions
- Our guiding principles for all code written are: We want maximum performance and speed, but without sacrificing correctness of results. It's okay to sacrifice some readability in the name of performance, but be mindful of the tradeoffs.
- When fixing reported bugs, write the regression test first, verify the failure, and then write the fix.
- When given real code causing bugs, use fake/generic module and function names in tests (e.g.
MyApp.Accounts,SharedLib.Worker) — never use real names from the user's codebase, even for regression tests - When writing code that affects the in-document runtime parsing that is similar to the index parsing (or vice versa), see if the code can be unified to reduce deduplication. But we should never do this at the cost of performance. Indexing must be kept fast no matter what.
- Keep the CLI commands (
init,reindex,lookup,references,stop) working with no editor open. They are frontends to the workspace daemon, not clients of the LSP;initis the only one that takes exclusive workspace ownership. The daemon is the only process that opens the index for normal operation, and it belongs to the workspace rather than to any frontend:dexter lspis also a frontend, a stdio proxy, and nothing may serve an editor in-process - Parser tests should cover real-world Elixir patterns from large codebases
- Version strings (
Version,IndexVersion, anddaemon.ContractVersion) live ininternal/version/version.goandinternal/daemon/endpoint.go. Do not bump them in feature PRs; version bumps are made together in a separate release PR. WheneverIndexVersionis bumped, bumpdaemon.ContractVersionwith it: a running daemon from the older build would otherwise keep serving the new frontend from the stale index.
When a release PR should bump IndexVersion
Do not change Version or IndexVersion in a feature PR. Note when a parser or schema change makes existing indexes
produce wrong results so the separate release PR can bump IndexVersion alongside Version. The LSP server checks this
on startup and, on a mismatch, triggers a forced rebuild of a populated index. An empty index is left to the
background full build, which stamps the current version itself — so a bump is not a way to force anything to happen on
a store that has no rows.
What to check when making changes
| Area changed | Also check |
parseUsingBody / use-chain logic | lookupInUsingEntry, resolveModuleViaUseChainWithOpts, findModulesWhoseUsingImports |
resolveBareFunctionModule | All callers pass lineNum — used by LookupFunctionInFile to prefer the enclosing module |
Rename edits (buildTextEdits) | findFunctionTokenColumns (skips keyword keys), includeKeyword flag on import sites |
Variable scoping (variables.go) | Pin operator handling, with multi-clause, stab body rebinds |
| Tokenizer string/heredoc/sigil scanning | TokenResult.Interp (the #{} token stream), flushInterpRefs, TokenizedFile.ExpressionAtCursor |
ExtractAliases | LSP handlers use ExtractAliasesInScope(text, lineNum) — scope-aware. Only Completion and CodeAction use the unscoped ExtractAliases intentionally |
| Any new store query | Add an index if the query will run on hot paths (definition, hover, references) |
internal/beam ETF tag handling | The ERTS external term format spec. One wrong field width desynchronises every later term in the chunk (EXPORT_EXT carries its arity as an integer term, NEW_FUN_EXT as a raw byte) |
internal/workspace/runtime.go (mutation queue, readiness, subscribers) | internal/lsp write coordination (IndexCoordinator), watch.go event filtering, failed-watch retries and one-shot coverage reconciliation, workspace/watch subscribers, and the Close ordering: watchers stop before the queue drains, and the queue drains before the store checkpoints |
| A new failure or degraded state | Report it through the workspace notify.Reporter (IndexCoordinator.Reporter()): a condition key with Set, and Clear with a message when it stops. A log line alone is not seen in an editor. Report only on state changes, never per file or per request. See "Telling the user" in docs/architecture.md |
internal/daemon (protocol, registries, endpoint) | ContractVersion, the reserved method and kind names in registry.go, client.go multiplexing (one reader, ids matched to callers, notifications interleaved), and docs/daemon.md. Socket paths must stay short: sockaddr_un is capped near 104 bytes |
Token walking
Use parser.TokenWalker for new code that iterates over tokens. It provides:
- Consistent depth tracking with automatic clamping (never goes negative)
- Forward progress guarantees via
EnsureProgress() - Module/function detection helpers
- Statement boundary handling
See internal/parser/token_walker.go for the API and token_walk_test.go for examples.
The consistency tests in elixir_test.go (TestModuleScopeConsistency, TestDepthTrackingConsistency) verify that similar functions handle edge cases the same way. Add new edge cases there when fixing scope-related bugs.
Common gotchas
- Nested modules:
defmodule Inner doinsideOutercreatesOuter.Innerin the store but the raw line only saysInner. UseLookupModulesInFile/LookupEnclosingModulerather than regex-scanning lines for module names. as:aliases:alias Foo.Bar, as: Baz—Baz≠ last segment ofFoo.Bar. Detected inPrepareRenameby comparing the alias short name tomoduleLastSegment(resolved).- Import-only sites in rename: Lines like
import Module, only: [fn: 1]have the function name as a keyword key. They're flaggedincludeKeyword: truesobuildTextEditsusesfindAllTokenColumnsinstead offindFunctionTokenColumns. - Dynamic use-chain imports:
import unquote(mod)goes intooptBindings, notimports. StaticfindModulesWhoseUsingImportswon't find these — the fast opt-binding path in the References handler handles them instead. - ExUnit.CaseTemplate: Uses
using opts doinstead ofdefmacro __using__. Only recognised when the module hasuse ExUnit.CaseTemplate. The body may delegate to a helper function (using_block(opts)) —parseHelperQuoteBlockfollows this. - Performance:
findModulesWhoseUsingImportsis expensive (~30-65ms). The References handler skips it when the fast opt-binding check already found injectors. - A session is not a process: one daemon serves every editor and CLI client on a workspace, so nothing on a path shared with daemon-backed sessions may call
os.Exit.Server.Exitcloses the stream instead whenmanageWorkspaceis false (every daemon session);Shutdownand disconnects go throughCloseSession, which issync.Once-guarded. The embeddedmanageWorkspacepath exists for tests and library use only — the CLI never selects it, because one workspace has one owner - Session state stays per-session, index state stays shared: open documents, client capabilities, and position encoding belong to one
Server; the store,IndexCoordinator, watchers, and caches belong to theworkspace.Runtime. A new field onServeris a new per-editor copy unless it deliberately goes on the coordinator. - Daemon-backed sessions do not manage the workspace: with
ManageWorkspace: false,Initializestarts no reindex and no.git/HEADpoll — the runtime does both. It still registersdidChangeWatchedFiles, because the editor's glob coversdeps/and path dependencies that the native watcher deliberately skips; duplicate events from both sources coalesce by path. Editor notifications are routed throughWorkspaceEventsinto the one mutation queue rather than written to the index directly. - A workspace has to look like one:
init,reindex,lookup, andreferencesrefuse a directory with nomix.exs,.git, or Dexter database unless-y/--yesis passed, so a mistyped command cannot build an index over the home directory.lspwarns and serves anyway (the editor decides), andstopnever checks, so a stray daemon can always be cleaned up
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

