Commit af18355
Add a Go indexer service with a gRPC streaming contract
Extracts the walk-and-parse stage behind rpc IndexRepo(IndexRequest) returns (stream
IndexEvent). Opt-in: IndexingService still uses the Python parser, and nothing changes
until indexer_grpc_enabled is set.
WHY, STATED HONESTLY: NOT SPEED
The obvious claim is false and was measured before writing any Go. On 900 files:
sequential 1115ms, ThreadPool(4) 1092ms (1.02x), ProcessPool(4) 393ms (2.84x).
py-tree-sitter never releases the GIL, so threads gain nothing -- which also means the
obvious anyio.to_thread fix would not have worked. Only ~41% of the parse phase is C, so a
perfect native rewrite ceilings near 2.4x, BELOW what ProcessPoolExecutor on the existing
Python already delivers for half a day of work.
The actual reason is a process and failure boundary: repos.py runs indexing by spinning a
new event loop inside an anyio threadpool thread and blocking it for minutes, in the same
process that serves chat. Server streaming is why gRPC rather than one big response --
progress becomes part of the contract instead of shared mutable state, the same bug class
that made the SSE progress bar show only 0% or 100%.
BINDING CHOICE
Official tree-sitter/go-tree-sitter, not smacker/go-tree-sitter -- which has roughly twice
the stars (562 vs 288) and outranks it in search results but was last pushed 2024-08-27
with 42 open issues and no deprecation notice. Verified against the GitHub API rather than
assumed, the same way the Terraform provider choice was checked.
TWO BUGS NOT REPEATED
Both were fixed in the Python parser earlier; this gets them right from the start.
- .tsx uses LanguageTSX(), not LanguageTypescript(). The plain TS grammar cannot parse JSX.
- Chunk text comes from node.Utf8Text(source) over the source BYTES, because tree-sitter
offsets are byte offsets and slicing a decoded string with them corrupts every chunk
after the first multi-byte character.
had_parse_error is on the wire because tree-sitter returns a partial tree rather than
failing, so without it the Python caller cannot decide to fall back to raw indexing.
ONE BUG FIXED IN THE PORT
Python's _find_files max-files cap never worked: its `break` left only the inner filename
loop, so os.walk continued into the next directory and kept appending. The Go walker uses
fs.SkipAll and reports Truncated, so a partial index is not mistaken for full coverage.
VERIFIED END TO END, Go server streaming to the Python client over real code
(apps/api/src): 5 progress events, 3 chunk batches, 509 chunks from 59 files in 99ms, 0
parse errors; kinds {module 17, class 121, function 109, method 262}; spot-check
class 'Level' in api/graphql/schema.py L47-53 matches the file. Correctness cases: a .tsx
component parses with had_parse_error=False under the tsx grammar, and after a line
containing "héllo — wörld 日本語 🎉" the chunks still slice as 'class Café:' and
'def método(self):' rather than misaligned fragments.
Suite: 168 passed, ruff clean. Generated *_pb2*.py are excluded from ruff, since any fix
there is overwritten by the next protoc run.
NOT DONE, deliberately: the service is not wired into the pipeline. That swap needs a
placement decision this commit does not make -- the service must be co-located with the API
on the shared volume, because clones live under ./data/repos/<owner>/<name> and that volume
attaches to exactly one machine. There is also no Go CI job yet (ci.yml has Python 3.11 and
Node 20 only, no cgo toolchain, no grammar-compile caching, no cross-language contract
test), and this build links 5 grammars against the Python side's 9 -- Health() reports which,
so the caller never assumes parity.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>1 parent 906849d commit af18355
15 files changed
Lines changed: 2403 additions & 0 deletions
File tree
- apps/api
- src/core/indexer
- services/indexer
- gen
- internal
- parse
- walk
- proto
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
20 | 23 | | |
21 | 24 | | |
22 | 25 | | |
| |||
Whitespace-only changes.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments