amber-lsp on Crystal 1.21: pin ameba past the lexer drift, dogfood the FSDD rules, add a live-fire stdio smoke test - #31
Draft
crimson-knight wants to merge 6 commits into
Draft
Conversation
…manager structure, story grammar) Four diagnostics rules enforcing the FSDD/Amber V2 standards: full method type interfaces, mandatory doc blocks, process-manager structure (typed initialize + perform), and feature-story grammar validity. Build 0/0, 44 specs pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parses crystal docs JSON (real signatures + FSDD doc-comment sections), applies a version ramp, renders a self-contained static HTML site to FSDD_docs/html/. The shared JSON layer the LSP also consumes. Build 0/0, 36 specs. (also: Time.monotonic→instant warning fix) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The spec/amber_lsp/ suite drives AmberLSP::Server in-process over an IO::Memory pair. That proves the code but not the artifact: it cannot catch a stale bin/amber-lsp, a broken build, or a server that goes silent instead of answering. scripts/lsp_smoke.rb spawns the real executable, builds a throwaway Amber-shaped fixture project (shard.yml WITH an `amber` dependency -- ProjectContext.detect keeps the server silent without one), runs a real framed initialize/initialized/didOpen session, and asserts on the textDocument/publishDiagnostics notifications that come back: >= 1 diagnostic for the violating controller, exactly 0 for the clean one. Exit codes are deliberately three-valued: 0 pass, 1 expectations not met, 2 could-not-measure (no binary, handshake failure, timeout). A timeout is never reported as a pass -- a silent server is not a clean server. Ruby 2.6 / stdlib only so it runs under macOS system ruby with no setup. Verified against crystal-alpha 1.21.0 (Crystal 1.21.0 [9c1e8ec64]).
…rystal 1.21
Crystal 1.21 removed Crystal::Lexer#next_string_array_token. Every released
ameba up to 1.6.4 calls it unguarded from src/ameba/tokenizer.cr:88, so on
1.21 `shards install` dies inside ameba's own postinstall -- before a single
line of this project is compiled. The symptom looks like "amber-lsp does not
build on 1.21"; it is not. amber-lsp compiles clean and always did. The
failing target was ameba, a development dependency.
Error: undefined method 'next_string_array_token' for Crystal::Lexer
in lib/ameba/src/ameba/tokenizer.cr:88
ameba fixed it in 4057e0c5e "Support Crystal v1.21.0+" (2026-05-04), guarding
the call with `lexer.responds_to?(:next_string_array_token)`. That commit is
reachable only from the untagged 1.7 line, so `~> 1.6.4` cannot get there.
Pinned by SHA rather than by the `v1.7.0-dev` tag it currently points at,
because a `-dev` tag is a moving pointer. Switch to `~> 1.7` when 1.7.0 is
tagged.
Before: CRYSTAL=crystal-alpha shards build amber-lsp -> exit 1
After: CRYSTAL=crystal-alpha shards build amber-lsp -> exit 0
I: Installing ameba (1.7.0-dev at 34a3de4)
KNOWN SIDE EFFECT: ameba 1.7.0-dev dropped both `postinstall` and its
`executables:` list, so `shards install` no longer produces bin/ameba. Any
tooling that shells out to <root>/bin/ameba (our fleet gate does, and skips
silently when it is absent) goes quiet until it is rebuilt. `shards build
ameba` does NOT do it -- ameba is not a target of this shard.yml. Use:
crystal-alpha build lib/ameba/src/cli.cr -o bin/ameba
…d ruling) agent_e2e_spec asserted that after the agent fixes the file, diagnostics are empty. They were not: the fixture drew 2x fsdd/doc-block-required and 1x fsdd/method-type-signature. This was never Crystal 1.21 drift -- the spec last changed in 06cdee5 and the FSDD rules landed later in 8781d5b, so the new rules correctly fire on a fixture written before they existed. Two ways to make it green: turn the FSDD rules off by default, or fix the fixture. Owner's standing directive decides it -- we need to be eating our own dog food. The rules STAY ON BY DEFAULT; the fixture was wrong, not the rules. So the corrected code now carries a doc comment on the class and on the action and an explicit return type, and "clean" in this spec means clean by our own conventions rather than merely free of the two violations the fixture was originally written to demonstrate. Anything less and this spec would quietly assert that our own conventions are optional. 253 examples, 0 failures, 0 errors, 0 pending
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft. Makes
amber-lspbuild and its specs pass on Crystal 1.21, and adds alive-fire proof that the built binary really speaks LSP.
Finding 1 —
amber-lspwas never the thing that was brokenThe reported symptom was that
shards build amber-lspfails on Crystal 1.21with:
That call site is in ameba 1.6.4, our
development_dependenciesentry —not in
src/amber_lsp/.grep -rn next_string_array_token src/ spec/returnsnothing. The failure happens during
shards install's postinstall(
shards build -Dpreview_mtinsidelib/ameba), before any of our source iscompiled. Skipping dev dependencies, the target built clean with zero source
changes, which is how we know amber-lsp itself never drifted.
Crystal::Lexer#next_string_array_tokenexists in Crystal 1.20 and is gone in1.21 — in both upstream
crystal 1.21.0and ouragent-crystalbuild(identical lexer sources); only
next_string_array_token_noescaperemains. Soit is genuine upstream Crystal drift, and it was ameba's to fix.
Fixed in
7761452. ameba fixed it in4057e0c5e"Support Crystal v1.21.0+"(2026-05-04), which guards the call:
That commit is reachable only from the untagged 1.7 line, so
~> 1.6.4cannotget there. Pinned by SHA (
34a3de4, whatv1.7.0-devpoints at today) ratherthan by that tag, because a
-devtag is a moving pointer.Two consequences worth knowing before merging.
postinstalland itsexecutables:list, soshards installno longer producesbin/ameba. Tooling that shells out to<root>/bin/amebagoes quiet until it is rebuilt, andshards build amebadoes not do it (ameba is not a target of this shard.yml). Use:
crystal-alpha build lib/ameba/src/cli.cr -o bin/ameba.this repo (there is no
.ameba.yml, so it runs the full default rule set).How much of that is new in 1.7 versus pre-existing under 1.6.4 is not
measurable on this toolchain — 1.6.4 cannot compile on Crystal 1.21 at all,
which is the very bug being fixed here. A lint pass wants to be its own PR,
with a
.ameba.ymldecision attached.Finding 2 — the e2e fixture, and the dogfood ruling
agent_e2e_specasserted that after the agent "fixes" the file, diagnostics areempty. They were not: the fixed fixture drew 2x
fsdd/doc-block-requiredand1x
fsdd/method-type-signature.This was never Crystal 1.21 drift.
git logputs it exactly: the spec lastchanged in
06cdee5; the FSDD rules landed later in8781d5b. The new rulescorrectly fire on a fixture written before they existed.
That left a real product question — should the FSDD rules be on by default for
every Amber project? — with two possible answers: relax the rules, or fix the
fixture. Resolved in
553c6bbby the dogfood ruling: the rules stay on bydefault; the fixture was wrong, not the rules. The corrected code now carries
a doc comment on the class and on the action plus an explicit return type, so
"clean" in this spec means clean by our own conventions rather than merely free
of the two violations the fixture was written to demonstrate. Anything less and
the spec would quietly assert that our own conventions are optional.
What this adds —
scripts/lsp_smoke.rbA live-fire proof that the built binary speaks LSP.
The
spec/amber_lsp/suite drivesAmberLSP::Serverin-process over anIO::Memorypair. That proves the code, not the artifact — it cannot catch astale
bin/amber-lsp, a broken build, or a server that goes silent instead ofanswering. The script spawns the real executable against a throwaway
Amber-shaped fixture project and asserts on the
publishDiagnosticsnotifications that actually come back.
Three-valued exit on purpose:
0pass,1expectations not met,2could-not-measure (no binary / handshake failure / timeout). A timeout is
never a pass — a silent server is not a clean server.
One trap the script documents in code, because it is a false-green generator:
ProjectContext.detectonly enables diagnostics whenshard.ymlhas anamberdependency. Without one the server publishes nothing — and"nothing" is indistinguishable from "clean" to any client that treats silence
as success.
Verified on
Crystal 1.21.0 [9c1e8ec64] (2026-07-20)viacrystal-alpha.Note on the commit range
This branch is cut from local
HEAD(9f170cc), which was 3 commits ahead oforigin/main(a0ff687,8781d5b,9f170cc) — unpushed local work carriedalong by the branch point. This PR's own changes are
549dd82,7761452and553c6bb.