Documentation
¶
Index ¶
- Constants
- Variables
- func AnchorHint(source string, anchor string) string
- func LanguageOf(path string) (string, bool)
- func ParserFor(path string, mode string) protocol.ParserInfo
- func ReferenceProviderIDs() []string
- func RenameProviderIDs() []string
- type Index
- func (i *Index) BuildRetrievalIndex() (protocol.RetrievalIndex, error)
- func (i *Index) BuildSymbolGraph() (protocol.SymbolGraph, error)
- func (i *Index) CandidatesForID(path string, symbolID string) []Symbol
- func (i *Index) CreateFile(path string, content string) error
- func (i *Index) DeleteFile(path string) (int, error)
- func (i *Index) DeleteSymbolSource(path string, symbolID string) (Symbol, []string, error)
- func (i *Index) FindSymbols(query string, kind string, limit int, maxLines int) (protocol.FindResponse, error)
- func (i *Index) Grep(req protocol.GrepRequest) (protocol.GrepResponse, error)
- func (i *Index) InsertSource(path string, anchor string, position string, text string) (int, error)
- func (i *Index) LanguageServerDiagnostics(path string) (diagnostics []protocol.Diagnostic, checker string, unchecked string, ...)
- func (i *Index) LanguageServerStatus(language string) lsp.ServerState
- func (i *Index) NotFoundResolution(path string, symbolID string) ([]protocol.OutlineItem, protocol.SymbolResolution)
- func (i *Index) Outline(path string) ([]Symbol, error)
- func (i *Index) OutlineStructured(path string) (OutlineSections, []Symbol, protocol.ParserInfo, error)
- func (i *Index) ReadRange(path string, start int, end int) (string, error)
- func (i *Index) ReadRangeInfo(path string, start int, end int) (RangeRead, error)
- func (i *Index) ReadRangePage(path string, start int, end int, maxBytes int) (RangeRead, error)
- func (i *Index) ReadSymbol(path string, symbolID string, maxLines int) (Symbol, string, error)
- func (i *Index) References(path string, symbolID string) (protocol.ReferencesResponse, error)
- func (i *Index) RenameSymbol(path string, symbolID string, newName string) ([]string, error)
- func (i *Index) ReplaceFileSource(path string, content string) (int, error)
- func (i *Index) ReplaceRangeSource(path string, start int, end int, newCode string) ([]string, error)
- func (i *Index) ReplaceSymbolSource(symbolID string, newCode string) (Symbol, []string, error)
- func (i *Index) ReplaceTextSource(path string, oldText string, newText string) (int, error)
- func (i *Index) RepositoryMap(query string, maxTokens int) (protocol.RepositoryMapResponse, error)
- func (i *Index) Retrieve(query string, maxTokens int) (protocol.RetrievalResponse, error)
- func (i *Index) Search(query string, mode string, limit int) (protocol.SearchResponse, error)
- func (i *Index) SearchNudge(command string, outputLength int, firstInSession bool) (footer string, ok bool)
- func (i *Index) SymbolDelta(relPath string, oldSource []byte, newSource []byte) []protocol.SymbolChange
- func (i *Index) SymbolsByName(path string, symbolName string) ([]Symbol, error)
- func (i *Index) UseLanguageServers(manager *lsp.Manager)
- func (i *Index) WorkspaceTree(maxEntries int) (protocol.WorkspaceTreeResponse, error)
- type OutlineSections
- type RangeRead
- type Symbol
Constants ¶
const ( InsertEnd = "end" InsertStart = "start" InsertBefore = "before" InsertAfter = "after" )
InsertPosition says where text goes relative to an anchor.
Variables ¶
var ErrAnchorRequired = errors.New("anchor is required for before/after inserts")
ErrAnchorRequired is returned when a positional insert names no anchor.
var ErrInvalidIdentifier = errors.New("rename target is not a valid identifier")
ErrInvalidIdentifier is returned when newName could not be a valid identifier, caught before touching the workspace rather than after gopls has half-applied something.
ErrRenameUnavailable is returned when no language server can resolve the rename. Unlike References (7.1), Rename deliberately has NO approximate fallback: BuildSymbolGraph matches on names, and applying a rename from name matches would silently rewrite unrelated identifiers that happen to share a name and miss shadowed or dynamically dispatched ones. A reference list that is merely approximate is still useful to a reader; an edit that is merely approximate is corruption. Refusing is the correct answer — docs/scope.md Rule 2 says to use the compiler's answer, not to guess when the compiler is unavailable.
var ErrTextAmbiguous = errors.New("anchor text is ambiguous")
ErrTextAmbiguous is returned when the anchor text appears more than once. Editing the first match would be a coin flip on which one the caller meant, so jade refuses and says how many it found — the caller can then extend the anchor with surrounding context to disambiguate.
var ErrTextNotFound = errors.New("anchor text not found")
ErrTextNotFound is returned when the anchor text does not appear in the file at all.
Functions ¶
func AnchorHint ¶ added in v0.0.4
AnchorHint says how an anchor that matched nothing differs from the file.
"anchor text not found" alone left an agent in a cobra benchmark run retrying six rewritten anchors from memory: it had written `return c.displayName()` above a function where the file said `return c.Name()`, and nothing told it which line was wrong. The hint names that line, or says the text is there with different whitespace, so one corrected call replaces a run of guesses.
func LanguageOf ¶ added in v0.0.7
LanguageOf names path's language and whether Jade parses it with a grammar; "" for a file type Jade does not recognise.
func ParserFor ¶
func ParserFor(path string, mode string) protocol.ParserInfo
ParserFor describes how path's symbols were obtained. mode is the value symbolsForPath returned, so this reports what actually happened rather than what the extension suggests should have — a Go file whose tree-sitter parse failed and fell through to the heuristic is reported as heuristic.
func ReferenceProviderIDs ¶ added in v0.0.7
func ReferenceProviderIDs() []string
ReferenceProviderIDs lists the references providers, strongest first.
func RenameProviderIDs ¶ added in v0.0.7
func RenameProviderIDs() []string
RenameProviderIDs lists the rename providers in the order asked.
Types ¶
type Index ¶
type Index struct {
// contains filtered or unexported fields
}
Index provides lightweight structural inspection primitives. It intentionally prioritizes bounded context and deterministic results.
func (*Index) BuildRetrievalIndex ¶
func (i *Index) BuildRetrievalIndex() (protocol.RetrievalIndex, error)
func (*Index) BuildSymbolGraph ¶
func (i *Index) BuildSymbolGraph() (protocol.SymbolGraph, error)
func (*Index) CandidatesForID ¶
CandidatesForID returns the symbols in path whose name matches the one embedded in symbolID. It is the transport-facing half of symbolNotFoundError: read_symbol reports a failed exact lookup as a not_found *resolution* rather than an error, so the suggestion has to travel as candidates in the response rather than as error text.
Returns nil when the ID is malformed or nothing matches — a not_found with no candidates is the honest answer to a name that is genuinely absent.
func (*Index) CreateFile ¶
CreateFile writes a brand-new file. It refuses to overwrite an existing one — an agent that wants to modify existing content should use ReplaceSymbolSource/ReplaceRangeSource, which carry revision checks; CreateFile has none, so silently overwriting would be unsafe.
func (*Index) DeleteFile ¶
DeleteFile removes a file and evicts its cached symbols (if any), and returns the line count it had for the caller's diff accounting.
func (*Index) DeleteSymbolSource ¶
DeleteSymbolSource removes a symbol's declaration entirely.
jade had create_file, delete_file, replace_symbol, replace_range and replace_text but no way to delete a declaration, so removing one meant locating it by hand and scanning for its closing brace — which is exactly the `python3` heredoc that prompted this task. Tree-sitter already knows the symbol's exact range, so the brace matching never needed hand-rolling.
It also removes the blank-line gap the declaration left behind. Deleting lines 10-14 out of a file where line 15 is blank and line 9 is blank leaves a double blank line, which `gofmt` then rewrites — turning a delete into a spurious two-line diff somewhere the caller never touched.
func (*Index) FindSymbols ¶
func (i *Index) FindSymbols(query string, kind string, limit int, maxLines int) (protocol.FindResponse, error)
FindSymbols locates declarations by name and returns their bodies in one call.
This closes the most-cited reason to leave jade for the shell. Answering "show me the function I have not located yet" took `outline` then `read_symbol` — two round trips — while `grep -n "func X" -A 30` fuses search and read into one. The shell won that comparison every time, and a bash fallback means no telemetry, no guardrails and no revision tracking.
Matching is by symbol name, exact first and then substring, so a caller who knows the exact name never has partial matches crowd out the one they asked for.
func (*Index) Grep ¶
func (i *Index) Grep(req protocol.GrepRequest) (protocol.GrepResponse, error)
Grep is literal or regex text search across the workspace, returning matching lines with optional trailing context.
Why this exists, given Search. Search ranks *symbols* by name similarity. Asked for the exact string "ChangedPaths" it returns ChangedFile, CheckRequest and CommitInfo — declarations whose names merely look alike, none of which contain the string, while missing both the struct field actually called ChangedPaths and every file that reads it. That is not a weak answer to the question; it is a confident answer to a different one, which is worse, because a caller who tests it once learns to distrust it and goes back to grep permanently.
The questions Search structurally cannot answer, and which recurred in this project's own dogfooding log for three tasks running:
- "What reads ChangesResponse.Paths?" A struct field is not a symbol with a resolvable position, so neither Search nor References reaches it — and this is the question every field removal starts with.
- "Where is this string literal / error message / build tag?"
- "Show me every match, minus the ones under testdata." Negative filters.
The response is shaped like `grep -n -A`, because that is the shape the fallback had and the one that reads cheapest: path:line, the matching line, and only as much trailing context as was asked for.
func (*Index) InsertSource ¶
InsertSource adds text to a file without replacing anything.
jade could create files and replace text, but not append: `create_file` refuses to overwrite and `replace_text` needs something to replace, so adding a test to an existing file meant reading it first purely to learn what to anchor on. That round trip is why `cat >> file` kept winning.
Anchors follow replace_text's rule — exactly one match or refuse — because inserting next to an arbitrary one of several matches is the same coin flip, silently placing code somewhere the caller did not look.
func (*Index) LanguageServerDiagnostics ¶ added in v0.0.2
func (*Index) LanguageServerStatus ¶ added in v0.0.7
func (i *Index) LanguageServerStatus(language string) lsp.ServerState
LanguageServerStatus reports language's server without starting it.
func (*Index) NotFoundResolution ¶
func (i *Index) NotFoundResolution(path string, symbolID string) ([]protocol.OutlineItem, protocol.SymbolResolution)
NotFoundResolution builds the outline and resolution for a failed exact-ID lookup, carrying any same-named candidates so the caller can retry without a second call. Both transports share it so read_symbol cannot answer one way over MCP and another over the internal API.
func (*Index) OutlineStructured ¶
func (i *Index) OutlineStructured(path string) (OutlineSections, []Symbol, protocol.ParserInfo, error)
OutlineStructured returns the grouped outline, the flat symbol list, and a ParserInfo saying how those symbols were obtained — see ParserFor for why the last one is not optional.
func (*Index) ReadRange ¶
ReadRange reads lines start..end inclusive.
An unset start means "from the beginning" and an unset end means "to the end", so a call with neither reads the whole file. That case is the point: go.mod, a Makefile, .mcp.json and every JSON/YAML/TOML config have no symbols to address and cannot be reached by read_symbol or find — yet they are exactly the files an agent opens first in an unfamiliar repo. Requiring a line range to read them meant every one of them was a `cat`, which is how this showed up in the dogfooding log.
An end past the last line is clamped to the last line, and ReadRangeInfo reports that it was. This used to be an error, on the theory that a caller naming an end states a belief about the file's length worth correcting. In use it was the opposite: "from here to the end" was nearly always the intent, and the rejection cost a whole extra turn every time.
func (*Index) ReadRangeInfo ¶ added in v0.0.3
ReadRangeInfo reads a line range and reports the bounds it used.
An end line past the end of the file is clamped to the last line, not rejected: "from here to the end" is what that request nearly always means, and rejecting it cost the caller a whole extra turn each time. The clamp is reported so the caller knows it got less than it named. A start past the end is still an error — there is nothing there to read.
func (*Index) ReadRangePage ¶ added in v0.0.7
ReadRangePage reads start..end like ReadRangeInfo, but whole lines up to maxBytes, at least one, instead of cutting out the middle: a caller can ask for the rest from NextLine rather than lose it.
func (*Index) ReadSymbol ¶
func (*Index) References ¶
References finds every place symbolID is referenced.
It prefers gopls (compiler-resolved, exact) and falls back to the approximate name-matched call graph when gopls is unavailable or the file isn't Go. The migration path is to supersede the approximate graph per language as real tooling comes online, rather than deleting it. The response always says which source answered, since a name-matched edge and a compiler-verified one are not the same claim, and an approximate graph must never be presented as authoritative IDE-grade semantics.
func (*Index) RenameSymbol ¶
RenameSymbol renames symbolID to newName across the whole repository using `gopls rename`, the CLI surface of the LSP textDocument/rename action. It returns the workspace-relative paths that changed.
The rename runs in two passes: `-d` produces a diff without touching disk, which is how the changed-file set is learned, and only then does `-w` apply it. That ordering also means a rename gopls would reject (conflicting name, unresolvable position) fails before anything is written.
func (*Index) ReplaceFileSource ¶
ReplaceFileSource overwrites an existing file wholesale and returns the line count it had, for the caller's diff accounting.
This is the counterpart to CreateFile, and the two are deliberately symmetric: CreateFile refuses when the file exists, ReplaceFileSource refuses when it does not. Neither can do the other's job by accident, so no flag is needed to make the destructive case explicit — choosing the tool is the explicit act.
It exists because every anchored edit needs something to anchor on. create_file cannot overwrite, replace_text and apply need text that is already in the file, and none of them can express "this document now says something else" — a rewritten README, a regenerated fixture, a consolidated notes file. That gap is total rather than merely awkward: there is no partial workaround, so it fell to a raw shell write every single time.
func (*Index) ReplaceRangeSource ¶
func (i *Index) ReplaceRangeSource(path string, start int, end int, newCode string) ([]string, error)
ReplaceRangeSource splices newCode over lines [start,end] (inclusive, 1-indexed) of path and writes the file back to disk. It returns the source lines it displaced.
func (*Index) ReplaceSymbolSource ¶
ReplaceSymbolSource resolves symbolID to its defining file, splices newCode over the symbol's current line range, and writes the file back to disk. It returns the replaced symbol and the source lines it displaced.
func (*Index) ReplaceTextSource ¶
ReplaceTextSource replaces an exact, unique string in a file.
This exists because line numbers are the wrong address for a sequence of edits: every prior edit shifts them, so each follow-up edit needs a fresh read first. Dogfooding jade through its own development produced several off-by-N splices from exactly that — a case landing inside a struct literal, another replacing an import line instead of inserting above it. An anchor string does not move when the lines around it do.
Uniqueness is required rather than preferred. "Replace the first match" is the behavior that makes sed dangerous in a script, and an agent cannot see which match it got.
func (*Index) RepositoryMap ¶
func (*Index) SearchNudge ¶
func (i *Index) SearchNudge(command string, outputLength int, firstInSession bool) (footer string, ok bool)
SearchNudge decides whether a nudge is warranted for command (a raw shell command string) given how much output it produced and whether this is the first search of the session, and if so returns the footer text to append below that command's own output. It never replaces or filters that output — ok=false simply means: don't append anything.
func (*Index) SymbolDelta ¶
func (i *Index) SymbolDelta(relPath string, oldSource []byte, newSource []byte) []protocol.SymbolChange
SymbolDelta reports which symbols were added, removed or modified between two versions of one file.
docs/scope.md §13's own MVP example has changes() naming the symbol that moved ("SessionManager.refreshSession modified"), not just the file. 3.2's numstat summary answers "how much"; this answers "what", which is the question an agent returning to a file actually has.
Both sides are parsed with the same extractor the rest of jade uses, so a language jade cannot parse yields no symbol-level detail rather than a wrong one — the file-level counts still stand on their own.
A nil or empty oldSource means the file is new: every symbol in it is reported as added. An empty newSource means it was deleted.
func (*Index) SymbolsByName ¶
func (*Index) UseLanguageServers ¶ added in v0.0.2
UseLanguageServers attaches a language server manager. Separate from NewIndex so the three commands that build an Index can opt in individually: the benchmark deliberately does not, since a warm language server would measure something other than what it claims to.
func (*Index) WorkspaceTree ¶
func (i *Index) WorkspaceTree(maxEntries int) (protocol.WorkspaceTreeResponse, error)
WorkspaceTree returns a plain, bounded structural listing of the workspace — orientation ("what does this repo look like"), unlike RepositoryMap which ranks files against a query. Directories and files matching shouldSkipPath (vendor, node_modules, .git, build output, etc.) are pruned from traversal entirely rather than merely filtered, since a vendored tree can be large enough to matter even just to walk.
type OutlineSections ¶
type OutlineSections struct {
Imports []string
Types []Symbol
Classes []Symbol
Functions []Symbol
Methods []Symbol
Other []Symbol
}
OutlineSections groups declarations for progressive disclosure consumers.
type RangeRead ¶ added in v0.0.3
type RangeRead struct {
Source string
Start int
End int
Total int
ClampedEnd bool
// NextLine is the first line a paged read left out, or 0 when it reached
// Through, the end of the range that was asked for.
NextLine int
Through int
}
RangeRead is a line range as actually read: the bounds used, the file's length, and whether the end was clamped.
Source Files
¶
- anchor_hint.go
- delete_symbol.go
- find.go
- gitignore.go
- grep.go
- index.go
- insert.go
- lsp_bridge.go
- nudge.go
- providers.go
- references.go
- rename.go
- replace_text.go
- symbol_delta.go
- symbol_notfound.go
- syntax_fallback.go
- treesitter_go.go
- treesitter_java.go
- treesitter_javascript.go
- treesitter_python.go
- treesitter_ruby.go
- treesitter_rust.go
- treesitter_scala.go
- treesitter_typescript.go
- write.go