# The Compiler Stopped Talking About Go: GALA in August and September (0.72 → 0.79)

Until 0.75, this GALA program compiled, linked, and printed `v=1`:

```gala
val x = 1
Println(s"v=${x +}")
```

The trailing `+` just vanished. Invalid source produced a working binary.

The cause was plumbing, not anything deep. The string-interpolation re-parser
removed the default error listeners and never installed its own. On top of
that, the expression rule wasn't anchored to end-of-input, so `x +` parsed as
`x` and stopped there. Nobody noticed because nobody writes `${x +}` on purpose.
It still counts as the worst kind of compiler bug: the program was wrong and
nothing said so.

It stood out, but it wasn't unusual. Any language that compiles to another
language picks up a whole class of these bugs, where the error is real but
shows up in the wrong place, in the wrong vocabulary, or not at all.
Go is a very good backend and a mediocre narrator. If the transpiler lets a
mistake through, `go build` catches it and reports it against a generated file
you never wrote, with type names you never used.

GALA has shipped twelve releases since [the last of these posts](https://martianov.dev/gala-july-2026-data-race-safety),
0.73.0 through 0.79.0. Most of the work had one goal: **report the problem
in your file, in GALA's terms, to whatever is reading it.** These days that
might be you, your editor, or an AI agent editing the code for you.

---

## Errors in your file, not in `gen/`

Here's the before picture. A GALA package under `internal/` was always
private to its parent tree, because Go enforces that rule. But Go enforced it on
the *generated* code, so the error looked like this:

```
gen\main.gen.go:6:8: use of internal package gala-build-workspace/gen/sub/internal/deep not allowed
```

That names a file you didn't write and a path that only exists inside the build
workspace. In 0.73 the check moved into GALA and became `GALA-E0041`, reported at
your `import` line:

```
error[GALA-E0041]: package "example.com/myapp/sub/internal/deep" is internal to "example.com/myapp/sub" and cannot be imported from "example.com/myapp"
  --> main.gala:3:8
  |
3 | import "example.com/myapp/sub/internal/deep"
  |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ internal packages are private to their parent tree
```

The rule is Go's, exactly as `cmd/go` applies it. Only whole path elements count
(`internalize` stays public), the last `internal` element wins, and the
standard library gets no exemption.

0.75 applied the same idea to three mistakes that people coming from other
languages make all the time. All three used to fail badly.

**Unparenthesized lambda parameters.** `x => x * 2` used to produce up to four
cascading parser errors, and none of them mentioned parentheses. Now you get
one:

```
error[GALA-E0042]: lambda parameters must be parenthesized
  --> main.gala:5:20
  |
5 |     Println(xs.Map(x => x * 2))
  |                    ^ use `(x) => ...`
  |
  = hint: use `(x) => ...`; GALA always parenthesizes a lambda's parameter list, including a single parameter
```

Why not just accept the bare form? Because `case x => body` would become
ambiguous, and the parser would silently pick the wrong reading. I'd rather have
a clear error than a grammar that sometimes guesses wrong.

**A type name called like a constructor.** `Array(1, 2, 3)` used to build a
corrupt struct literal from three of `Array`'s four private fields, and the
failure came back in Go's words. Now:

```
error[GALA-E0043]: Array is a type, not a constructor
  --> main.gala:6:14
  |
6 |     val xs = Array(1, 2, 3)
  |              ^^^^^ use `ArrayOf(...)`
  |
  = hint: use `ArrayOf(...)`; GALA constructs values through named functions, so a type name is never callable
```

**A method the type doesn't have.** This one used to come from `go build`,
reported against the generated expression, including auto-unwrap `.Get()` calls
the user never wrote. Now GALA reports it and suggests the closest real method:

```
error[GALA-E0044]: Array has no method Sise
  --> main.gala:7:16
  |
7 |     Println(xs.Sise())
  |                ^^^^ did you mean `Size`?
  |
  = hint: did you mean `Size`?
```

When nothing is close, it lists what the type does declare.

Two fixes in the same release go with these. First, a diagnostic raised inside
an interpolation now points at the right line. A bare `len` inside `s"..."` on line
6 used to put its caret on `package main`, because the embedded expression was
re-parsed in a fresh stream where everything sat on line 1. Second, `gala build`
and `gala run` now print a file, line, and source frame for syntax errors. Before,
only `gala transpile` did.

---

## The same diagnostics, for a machine

A framed error with a caret is great for a person reading a terminal. For a
program it's text that has to be scraped. More and more of the programs reading
compiler output are coding agents, and scraping is where they make mistakes.

So 0.75 added a structured form of each diagnostic. With `--json` on `build`,
`run` and `transpile`, the `Sise` error above becomes:

```json
{
  "diagnostics": [
    {
      "severity": "error",
      "code": "GALA-E0044",
      "message": "Array has no method Sise",
      "hint": "did you mean `Size`?",
      "file": "main.gala",
      "line": 7,
      "column": 16,
      "endColumn": 20,
      "docsUrl": "https://gala.fyi/docs/errors/gala-e0044/"
    }
  ]
}
```

Two lookup commands came with it:

- **`gala explain <code>`** prints the full reference page for any diagnostic,
  offline. It takes `GALA-E0044`, `E0044` or just `44`, and `--list` enumerates
  every code.
- **`gala doc <package>[.<Type>]`** prints what a package exports. That turns
  "what can I call on this?" into a lookup instead of a guess.

The website also serves an `llms.txt` now. It covers the places where GALA looks
like Go or Scala but behaves differently, which is where generated code most
often goes wrong.

---

## Documentation reaches the editor

The standard library has over 1,500 doc comments. Until 0.76, the lexer threw
every one of them away, so no tool could show them.

Now they flow through. GALA uses Go's convention exactly: a `//` run directly
above a declaration documents it. So the comments you already write show up in
**hover**, **completion** and **signature help**:

```
func (Option) GetOrElse(defaultValue T) T

GetOrElse returns the option's value if the option is Some, otherwise returns
the result of evaluating defaultValue.
defaultValue: the default value to return if the option is empty.

*Package: std*
```

That last `name: description` line is a parameter doc. Signature help shows it
next to that parameter. It's only treated as a parameter doc when `name` matches
a real parameter, so `Note:` and `Example:` stay part of the prose.

Adding documentation exposed how much hover was missing. Before 0.76 it
returned nothing for methods, struct fields, local bindings, package aliases and
import paths. In practice, every method call in every GALA file hovered to
nothing. Hover now uses the same type resolution as completion, so the two can't
disagree about what an expression is.

The point releases after that were mostly about builder chains, the style every
GALA server, client and config API is written in:

```gala
gsrv.NewServer().
    WithName("gala-kv").
    WithShutdownTimeout(timeout).
    ServeTCPOn(addr, (c) => Serve(c, st)) match { ... }
```

In 0.76.0, hovering over any link in that chain showed nothing. The chain starts
with a package qualifier rather than a value, and the resolver looked that
qualifier up as if it were a receiver *type*. It found no methods and gave up on
every link after it. Fixing that turned up more problems:

- Go to definition looked for the dot on the cursor's own line. In a chain, that
  line has no dot; it's at the end of the line above. It only *seemed* to work
  because it fell back to a global name search, which returned whichever type
  happened to declare a method with the same name.
- A comment above a statement was being read as part of it. Ordinary sentences
  end in a period, and the line joiner treats a trailing `.` as a member access.
- `val shutdownTimeout = 15 * time.Second` hovered as `int`. Arithmetic took
  its type from the left operand. Go's rule is that an untyped constant takes the
  type of the typed operand. The generated Go was always correct, because Go
  re-typed it. GALA's own inference was wrong, and hover and inlay hints are
  built on it.

0.77 made completion write your code, not just list names. Completing a call
inserts its required parameters as named arguments, with a tab stop for each:

```gala
srv.NewServer().
    WithName(name = ▮)
    ServeTCPOn(addr = ▮, handler = ▮)
```

Parameters with defaults are left out, because the call can omit them. Inside
an argument list, completion offers the parameters you haven't passed yet
first.

This work also turned up a transpiler bug. Methods with their own type
parameters ignored named arguments and bound everything by position:

```gala
arr.FoldLeft(f = (acc, x) => acc + x, initial = 0)
// was: Array_FoldLeft(arr, func(acc any, x any) any {...}, 0) — swapped, untyped
```

That output swapped the arguments, and it used `any`, which GALA's codegen is
never allowed to emit unless the source asks for it. Generic methods now bind by
name, fill in defaults, and report unknown, duplicate and missing arguments like
every other call. The line above prints `10`.

---

## A plugin for Claude Code

The last two releases, 0.78 and 0.79, both came out today, and they connect all
of the above to an agent.

GALA now ships a [Claude Code](https://code.claude.com) plugin that runs
`gala lsp`. While Claude edits `.gala` files, it gets two things:

- **Diagnostics after every edit.** Parse errors, non-exhaustive matches,
  `GALA-E*` checks like the data-race check from the last post: all of them
  reach Claude on its next tool call, so it fixes them without waiting for a
  build.
- **Lookups instead of guesses.** Claude's LSP tool can ask the server for
  hover (inferred types and docs), go to definition, find references, and
  document or workspace symbols.

```
/plugin marketplace add martianoff/gala
/plugin install gala@gala
```

`gala new` projects are already set up for it. They include a
`.claude/settings.json` that declares the marketplace, so Claude Code offers the
plugin once you trust the folder.

Building it surfaced some LSP server gaps that no editor had exposed.
Programs split across files had no cross-file navigation. `workspace/symbol`
wasn't implemented. The outline found names by substring, which put `main` on
the `package main` line. And the `shutdown` response had neither `result` nor
`error`, which JSON-RPC 2.0 forbids. All four are fixed in 0.78.

0.79 fixed a less obvious problem: having tools doesn't mean Claude will use
them. With only the plugin, Claude reliably *received* diagnostics but rarely
*asked* the server anything. On a real task it guessed at APIs and grepped the
standard library cache. The plugin now includes a short skill (about 74 tokens
per session) that tells Claude to use its Edit tool rather than `cat >>` (only
editor edits trigger diagnostics), to look up types through the server, and to
finish with `gala build`.

I ran the same task in fresh sessions with and without the skill. Without it,
Claude made zero LSP calls. With it, Claude went from a type's definition to its
method list to a hover, edited through Edit, built, and did it for about half
the cost. That's one task, so treat it as an anecdote, not a benchmark. Still,
the difference was large enough to ship.

The limit is the same as in the terminal. Diagnostics only cover what GALA's
transpiler checks. An error that only the Go compiler finds still needs
`gala build`, and the skill says so.

---

## The rest

**Conventional Go project layouts build from the root.** For a project with
packages under `internal/` and the program under `cmd/<name>/`, `gala build`
at the root used to fail with `no Go files in <workspace-hash>/gen`. Now it finds
the main package itself. If there are several, it lists them and asks you to
pick one. `gala test` handles the same layout too.

**Standing guards against type problems.** Since 0.74, several guards run
continuously instead of relying on individual fixes. One checks that no
generated variable, field, map or slice element widens to `any` unless the source
asked for it. Another checks that every error code has a docs page, and every
docs page has a code. There's also a guard that transformer state gets restored
on error paths. And `GALA_WARN_TYPES=1` lists every expression the transpiler
couldn't type. That list works as a regression detector rather than a backlog.
Every entry today is in a program that compiles and runs, because Go's type
checker finishes the job, so an *increase* is the thing to act on.

**An inference bug that depended on a different file.** GALA's `strings`
package shares a name with Go's. Once *any* file in a package imported GALA's,
a sibling file's Go `strings.SplitN` was typed as returning `Array[string]`,
while the code generator still emitted Go's `[]string`. Adding a file could
break a different file you hadn't touched. Fixed in 0.73.1: a qualified call now
resolves through that file's own imports.

**The IntelliJ plugin works on 2025.3+ again.** On newer platforms, every
PSI-based feature crashed as soon as you opened a `.gala` file. The cause was a
final class in the platform that the ANTLR adaptor subclasses. That adaptor has
exactly one release on Maven Central, so upgrading it wasn't an option, and the
plugin no longer uses it. The plugin's unit tests also run in CI again.

**`SECURITY.md`.** Security reports now go through private GitHub advisories.
The scope includes soundness holes in the guarantees GALA advertises (the race
checker, immutability, exhaustiveness). Running untrusted GALA is explicitly out
of scope, because GALA is not a sandbox.

---

## Where this leaves things

With a transpiled language, it's easy to treat the target language's compiler as
the real checker, and the language's own checks as nice extras. That works right
up until a user has to debug your generated code to understand their own
mistake.

These twelve releases pushed the other way. Mistakes are now reported by GALA,
against your source, with a code you can look up and a fix you can apply. That
same diagnostic now reaches the terminal, the JSON output, the IDE, and an agent
halfway through an edit, without any of them scraping text.

Some rough edges remain. Anything Go catches that GALA doesn't still shows up
as a Go error after a full build. The type-resolution inventory isn't empty. And
the agent integration is new enough that its best evidence so far is one task.
Still, the direction is clear: by the time Go sees the code, it should have
nothing left to report.

The error reference is at [gala.fyi/docs/errors](https://gala.fyi/docs/errors/),
the Claude Code plugin setup is in
[ide/claude-code](https://github.com/martianoff/gala/tree/master/ide/claude-code),
and the [playground](https://gala-playground.fly.dev) runs GALA without
installing anything.
