Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/buildcheck/tests/test_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import io
import os
import tempfile
import unittest
from unittest import mock

Expand Down Expand Up @@ -62,6 +63,33 @@ def test_only_coordinates_both_use_and_only_differences(self):
["s:p is 2 here and 9 in r1: bump it in every repository"])


class GradleKotlinTest(unittest.TestCase):

def test_plugins_are_read_from_every_kts_file_outside_build_output(self):
with tempfile.TemporaryDirectory() as root:
os.makedirs(os.path.join(root, "app", "build"))
with open(os.path.join(root, "app", "build.gradle.kts"), "w", encoding="utf-8") as f:
f.write('plugins {\n id("com.android.application") version "9.4.0"\n'
' id("org.jetbrains.kotlin.plugin.compose") version "2.4.20"\n'
' kotlin("jvm") version "2.4.21"\n}\n')
with open(os.path.join(root, "app", "build", "stale.gradle.kts"), "w", encoding="utf-8") as f:
f.write('kotlin("jvm") version "1.0.0"\n')
self.assertEqual(versions.gradle_kotlin_plugins(root), [
("app/build.gradle.kts", "org.jetbrains.kotlin.plugin.compose", "2.4.20"),
("app/build.gradle.kts", "org.jetbrains.kotlin.jvm", "2.4.21"),
])

def test_a_plugin_behind_the_poms_kotlin_is_a_warning_and_nothing_else_is(self):
own = {"org.jetbrains.kotlin:kotlin-stdlib": {"2.4.21"}, "org.jetbrains.kotlinx:kotlinx-coroutines-core": {"1.11.0"}}
plugins = [("app/build.gradle.kts", "org.jetbrains.kotlin.plugin.compose", "2.4.20"),
("app/build.gradle.kts", "org.jetbrains.kotlin.jvm", "2.4.21")]
self.assertEqual(versions.compare_gradle_kotlin(own, plugins), [
"org.jetbrains.kotlin.plugin.compose is 2.4.20 in app/build.gradle.kts and the POMs use Kotlin 2.4.21: "
"Dependabot bumps only the POMs, bump the Gradle plugin with them"])
self.assertEqual(versions.compare_gradle_kotlin({"a:b": {"1"}}, plugins), [])
self.assertEqual(versions.compare_gradle_kotlin(own, []), [])


class MainTest(unittest.TestCase):

def run_main(self, siblings):
Expand All @@ -87,6 +115,19 @@ def test_differences_are_warnings_and_never_fail(self):
self.assertNotIn("BitcoinAddressFinder:", err)
self.assertIn("1 version differences", out)

def test_a_gradle_kotlin_plugin_behind_the_poms_is_reported_with_the_other_warnings(self):
kotlin = pom('<dependencies><dependency><groupId>org.jetbrains.kotlin</groupId>'
'<artifactId>kotlin-stdlib</artifactId><version>2.4.21</version></dependency></dependencies>')
out, err = io.StringIO(), io.StringIO()
with mock.patch.object(versions, "current_repo", return_value="java-llama.cpp"), \
mock.patch.object(versions, "read_local", return_value=[kotlin]), \
mock.patch.object(versions, "gradle_kotlin_plugins", return_value=[
("android-llmservice/app/build.gradle.kts", "org.jetbrains.kotlin.plugin.compose", "2.4.20")]):
self.assertEqual(versions.main(["x"], "/nowhere", lambda repo, path: kotlin, out, err), 0)
self.assertIn("::warning::org.jetbrains.kotlin.plugin.compose is 2.4.20 in android-llmservice/app/build.gradle.kts "
"and the POMs use Kotlin 2.4.21", err.getvalue())
self.assertIn("1 version differences", out.getvalue())

def test_a_missing_pom_skips_that_file_not_the_repository(self):
out, err = io.StringIO(), io.StringIO()
bumped = pom('<dependencies><dependency><groupId>org.junit.jupiter</groupId>'
Expand Down
46 changes: 44 additions & 2 deletions .github/buildcheck/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@
`${property}` references resolved -- not property
names, which differ between repositories and include values that must differ (the Java release).

It also compares the Kotlin the POMs use with every Kotlin Gradle plugin a `*.gradle.kts` of the
repository pins: Dependabot's maven ecosystem bumps `kotlin.version` and never sees the Gradle files,
which is how a Compose compiler plugin stayed one patch release behind the POMs.

Warnings, never failures: a bump lands in four pull requests, and the first one merged would
otherwise turn the other three repositories red. The repository's own artifacts
(`net.ladenthin:*`) are left out: a sibling depends on the last RELEASE of java-llama.cpp while
java-llama.cpp itself is at the next snapshot.
"""

import os
import re
import sys
import urllib.request
Expand All @@ -28,7 +33,7 @@
NS = "{http://maven.apache.org/POM/4.0.0}"
POMS = {
"java-llama.cpp": ("pom.xml", "llama/pom.xml", "llama-langchain4j/pom.xml", "llama-kotlin/pom.xml",
"llama-platform/pom.xml", "llama-atmosphere-agent/pom.xml"),
"llama-platform/pom.xml", "llama-atmosphere-agent/pom.xml", "examples/server/pom.xml"),
"BitcoinAddressFinder": ("pom.xml",),
"srcmorph": ("pom.xml", "srcmorph/pom.xml", "srcmorph-cli/pom.xml", "srcmorph-maven-plugin/pom.xml"),
"streambuffer": ("pom.xml",),
Expand All @@ -42,6 +47,11 @@
CONFIGURED = {"palantirJavaFormat": "com.palantir.javaformat:palantir-java-format"}
RAW_URL = "https://raw.githubusercontent.com/{owner}/{repo}/HEAD/{path}"
PROPERTY = re.compile(r"\$\{([^}]+)\}")
KOTLIN_GROUP = "org.jetbrains.kotlin"
# `id("org.jetbrains.kotlin.plugin.compose") version "2.4.21"` and `kotlin("jvm") version "2.4.21"`
GRADLE_KOTLIN = re.compile(
r'(?:id\("(org\.jetbrains\.kotlin(?:\.[A-Za-z.]+)?)"\)|kotlin\("([A-Za-z.]+)"\))\s+version\s+"([^"]+)"')
SKIPPED_DIRS = {".git", ".gradle", ".idea", "build", "node_modules", "target"}


def coordinates(pom_texts):
Expand Down Expand Up @@ -94,6 +104,38 @@ def compare(own, others):
return warnings


def gradle_kotlin_plugins(root):
"""(path, plugin id, version) for every Kotlin Gradle plugin a `*.gradle.kts` below root pins, in
path order; build output and the tools' own directories are not searched."""
found = []
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = sorted(d for d in dirnames if d not in SKIPPED_DIRS)
for name in sorted(filenames):
if not name.endswith(".gradle.kts"):
continue
path = os.path.join(dirpath, name)
with open(path, encoding="utf-8") as f:
text = f.read()
for match in GRADLE_KOTLIN.finditer(text):
plugin = match.group(1) or f"{KOTLIN_GROUP}.{match.group(2)}"
found.append((os.path.relpath(path, root).replace(os.sep, "/"), plugin, match.group(3)))
return found


def compare_gradle_kotlin(own, plugins):
"""Warnings for every Kotlin Gradle plugin whose version is not one the POMs use for
`org.jetbrains.kotlin:*`; nothing when the POMs use no Kotlin or no Gradle file pins a plugin."""
maven = set()
for coordinate, used in own.items():
if coordinate.startswith(KOTLIN_GROUP + ":"):
maven |= used
if not maven:
return []
return [f"{plugin} is {version} in {path} and the POMs use Kotlin {', '.join(sorted(maven))}: "
f"Dependabot bumps only the POMs, bump the Gradle plugin with them"
for path, plugin, version in plugins if version not in maven]


def fetch(repo, path):
with urllib.request.urlopen(RAW_URL.format(owner=OWNER, repo=repo, path=path), timeout=20) as response:
return response.read().decode("utf-8")
Expand Down Expand Up @@ -129,7 +171,7 @@ def main(argv, root, fetcher=fetch, out=sys.stdout, err=sys.stderr):
print(f"::warning::could not read {sibling}'s {path}: {e}", file=err)
if texts:
others[sibling] = coordinates(texts)
warnings = compare(own, others)
warnings = compare(own, others) + compare_gradle_kotlin(own, gradle_kotlin_plugins(root))
for warning in warnings:
print(f"::warning::{warning}", file=err)
shared = set(own).intersection(*others.values()) if others else set()
Expand Down
5 changes: 3 additions & 2 deletions .github/shared-files.sha256
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ cbe504ad081ba31ec16e11c11f879c64179ca7741bf0b1d1318a7fc969ed67dd .github/buildc
fe36042fbd5444f0b454ad1304cdafde901dd8cec57cfaba7c7086d996dfee27 .github/buildcheck/tests/test_sharedfiles.py
7fe208dd33184b4543328f97b339c95e23c408b120e6bfa6095de4182aeb9a19 .github/check-release-gate.py
1604ccdea83998814a3c1f98f746272222039867b0c7d692355e61495c1c648b .github/check-shared-files.py
e5f1679dc7151b2676b2f587860bbaa17e1b89b44a5a4e9703eb3499248c4a06 .github/buildcheck/versions.py
d454d33b61089b5f7bd6d439e3e5b58e01e7b01eeba6b4c2f7ed3360bbb158f0 .github/buildcheck/tests/test_versions.py
74b4b18af6c5be2af54048de408ec6e138f37f85b9f9ed2fdef258e270a7c90c .github/buildcheck/versions.py
de437064302f9314028afff00e17fc995e48869e07ee402206d23708c7ed355a .github/buildcheck/tests/test_versions.py
0c78f43dd181f079d35edde7cbf25bb22eac92c907845d8dc8eec78e812aa097 .github/check-versions.py
33c70847b6568604e8725d167a9b1d5910cc2a1a5b01fec7362be31b488ddb02 .github/buildcheck/runscripts.py
c4967e3011d474ec43dbc797d113685c0a8a01b7cc52e910a1356273e6a67dc9 .github/buildcheck/tests/test_runscripts.py
7ad8f7258c983c91e67db695e5ab6ea0f9279af007a6aa815f92f0d8a74c2ae4 .github/check-run-scripts.py
7da53fdb9537ccf7d3f3740be31f295bacb914fe186304b9ed8a65fd3d3d413e .github/print-crash-logs.sh
fa1469664cb632e210ff3aa0669f15219d160ef13b486abeb978efade55f330b .github/print-host-info.sh
c8c02dc39aeacc154fad74071443c6513666aa94ca9f9c5fab49c5f39dea7ee6 .github/verify-signing-key.sh
e0b2a4f331eae326b61c71f78d6272d06d99140a85dedb7710f93402de862466 .github/verify-bytecode-version.sh
ab45f5c102b47dd16c325d4d9c283d158ba90c05f484eac45b2767885c4462f9 .github/signing-selftest/build.gradle.kts
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,23 @@ from version 5.0.0 onward. Pre-fork releases (`1.x`–`4.2.0`) were authored by
core's; `check-natives.py` fails when they differ.

### Changed
- **The local agent speaks ACP through SDK 0.80.0** (`com.agentclientprotocol:acp-core` +
`acp-json-jackson2`, from 0.18.0 -- upstream's binary-incompatible release of 2026-10-08). The wire
format is unchanged, so `AcpServerTest`, which drives the server with plain JSON, is unchanged too; the
port is mechanical: session updates go out through `sendSessionUpdate` without the discriminator
argument the records now write themselves, `session/request_permission` is a call on the prompt
context's `client()`, `awaitTermination()` replaces `await()`, and the constructors that gained the
protocol's new tool-call `name` carry the agent's tool name there (`read_file`, `shell`, ...), so an
editor can show it next to the title.
- **Build tooling in step with the sibling repositories:** Spotless 3.10.4, Checker Framework 4.3.1,
`exec-maven-plugin` 3.6.4 in `examples/server/pom.xml` -- which `check-versions.py` reads now, so the
drift it had (3.6.2 against the reactor's 3.6.4) can no longer go unseen -- and the Compose compiler
plugin 2.4.21, the Kotlin the POMs already used. `check-versions.py` compares the Kotlin Gradle
plugins of every `*.gradle.kts` with the POMs' Kotlin from now on: Dependabot bumps `kotlin.version`
and never sees the Gradle files.
- **`print-host-info.sh` is a shared file** (`.github/shared-files.sha256`, in all four repositories):
the runner's CPU at the top of every test job, which is how a chosen CPU module or a timing is read
against the hardware the job drew.
- **Upgraded the pinned llama.cpp from b11538 to b11556**, one reviewed step (18 commits, 96 files, no
`tools/ui` change, so the WebUI is unchanged). All twelve patches apply unchanged and every drop-check
still finds its defect. In the range: a request pinned to a **busy** slot (`id_slot`,
Expand Down
10 changes: 8 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,12 @@ resolved) with the siblings' default branches and **warns** per difference. Warn
bump lands in four pull requests and the first one merged must not turn the other three red; the
repos' own artifacts (`net.ladenthin:*`) are left out. It compares what is used, not property names:
those differ between repos and include values that must differ (`maven.compiler.release`). The POM
list per repo is `versions.POMS` -- a new module's POM goes there, in all four copies.
list per repo is `versions.POMS` -- a new module's POM goes there, in all four copies (here also
`examples/server/pom.xml`, whose `exec-maven-plugin` sat two patch releases behind the reactor's unseen
until it was listed). It also compares the Kotlin Gradle plugins of every `*.gradle.kts` with the Kotlin
the POMs use (`org.jetbrains.kotlin:*`): Dependabot's maven ecosystem bumps `kotlin.version` and never
sees the Gradle files, which is how the Compose compiler plugin stayed at 2.4.20 while the POMs went to
2.4.21.

**Run scripts parse (`check-run-scripts.py`).** A cleanup once lost the trailing ` \` of six continued
lines in `publish.yml` -- each left a line starting with `||`/`&&`, a syntax error in a step only a
Expand All @@ -357,7 +362,8 @@ file line), folded and plain values as YAML folds them, `${{ ... }}` becomes a w
`workflow.py` it reads the two-space layout these workflows use, not general YAML.

Shared today: the `buildcheck` modules above marked shared in
`__init__.py` (+ their tests and CLIs), `print-crash-logs.sh`, `verify-signing-key.sh`,
`__init__.py` (+ their tests and CLIs), `print-crash-logs.sh`, `print-host-info.sh` (the runner's CPU at the top of every
test job, so a timing or a chosen CPU module can be read against the hardware), `verify-signing-key.sh`,
`verify-bytecode-version.sh`, `smoke-fatjar-cli.sh` (BAF + srcmorph; `sign-fatjars.sh` is srcmorph's
alone since jllama ships no fat jar), the signing self-test, `lombok.config`, the issue/PR templates, `CODE_OF_CONDUCT.md`,
`claude.yml`, `claude-code-review.yml`, `codeql.yml`, `scorecard.yml`, `reuse.yml`, `osv-scanner.yml`,
Expand Down
4 changes: 2 additions & 2 deletions android-llmservice/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ domain is the only string that must be globally unique.

Structure (mirrors the consumer-test's plumbing):
- **`settings.gradle.kts`** — `rootProject.name = "android-llmservice"`; pins AGP `9.4.0` + the
Compose compiler plugin (`2.4.20`); `mavenLocal()` first so the freshly-built AAR + façade
Compose compiler plugin (`2.4.21`); `mavenLocal()` first so the freshly-built AAR + façade
resolve there in CI (Maven Central for real users). Stay at `>= 9.2.1`: `9.2.1` (not `9.2.0`)
first fixed a real R8 regression (`ClassNotFoundException` on `com.android.tools.r8.RecordTag`
after upgrading Gradle to 9.x with AGP 9.2.0) that hits this project directly since
Expand All @@ -34,7 +34,7 @@ Structure (mirrors the consumer-test's plumbing):
Kotlin Gradle plugin 2.2.10+), so the standalone `org.jetbrains.kotlin.android` plugin is no longer
applied — applying it now fails the build with "no longer required for Kotlin support since
AGP 9.0" (`app/build.gradle.kts` line 7). The Compose compiler plugin still applies
separately and its 2.4.20 pin exceeds AGP's 2.2.10 floor, so no other version changed.
separately and its 2.4.21 pin exceeds AGP's 2.2.10 floor, so no other version changed.
- **`app/build.gradle.kts`** — `namespace`/`applicationId` `net.ladenthin.android.llmservice`,
`minSdk 28` (AAR floor), `compileSdk 37` (raised from 35 — Compose/lifecycle/activity AAR
metadata now requires it), `targetSdk 35`, Jetpack Compose, `androidx.appcompat` (only for
Expand Down
2 changes: 1 addition & 1 deletion android-llmservice/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ gradle -p android-llmservice connectedDebugAndroidTest -PjllamaVersion="$VERSION

```
android-llmservice/
├── settings.gradle.kts # AGP 9.4.0 (built-in Kotlin) + Compose plugin 2.4.20, mavenLocal first
├── settings.gradle.kts # AGP 9.4.0 (built-in Kotlin) + Compose plugin 2.4.21, mavenLocal first
├── gradle.properties
├── requirements.md # spec of record: every feature the app implements today (no unit tests)
├── TODO.md # roadmap: not-yet-built features
Expand Down
4 changes: 2 additions & 2 deletions android-llmservice/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ pluginManagement {
// AGP 9.0+ has built-in Kotlin support (runtime dependency on Kotlin Gradle plugin
// 2.2.10+), so the standalone org.jetbrains.kotlin.android plugin is no longer
// applied/needed — see https://developer.android.com/build/migrate-to-built-in-kotlin.
// The Compose compiler plugin (2.4.20) still applies separately and exceeds AGP's
// The Compose compiler plugin (2.4.21) still applies separately and exceeds AGP's
// 2.2.10 floor.
plugins {
id("com.android.application") version "9.4.0"
id("org.jetbrains.kotlin.plugin.compose") version "2.4.20"
id("org.jetbrains.kotlin.plugin.compose") version "2.4.21"
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ SPDX-License-Identifier: MIT
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.2</version>
<version>3.6.4</version>
<configuration>
<mainClass>${llama.main}</mainClass>
<!-- a server never returns; let the embedded server own the JVM -->
Expand Down
5 changes: 4 additions & 1 deletion llama-atmosphere-agent/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,10 @@ object before `buildAsync`**, or the handshake message races the listener into a
`request(1)` is never called — a flaky test that looked like a server bug.

**`--acp` (`AcpServer`, `AcpFrontend`).** The ACP Java SDK (`com.agentclientprotocol:acp-core` +
`acp-json-jackson2`, 0.18.0) over `StdioAcpAgentTransport`. Its sync handlers run on the SDK's own pool,
`acp-json-jackson2`, 0.80.0 -- binary-incompatible with 0.18.0, ported 2026-10-11: session updates go out
through `sendSessionUpdate` without the discriminator argument the records write themselves,
`session/request_permission` is a call on `context.client()`, `awaitTermination()` replaces `await()`, and
the tool-call `name` the protocol added carries the agent's tool name) over `StdioAcpAgentTransport`. Its sync handlers run on the SDK's own pool,
so a blocking `session/prompt` does not block `session/cancel` or the answer to a permission request.
One `AgentSession` per ACP session, built for the editor's `cwd` (`AgentOptions.withWorkspace`); the
model endpoint is opened once per process. **stdout is the protocol**: `run()` keeps the real stdout for
Expand Down
4 changes: 2 additions & 2 deletions llama-atmosphere-agent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ SPDX-License-Identifier: MIT
<atmosphere.version>4.0.72</atmosphere.version>
<jline.version>4.4.7</jline.version>
<jetty.version>12.1.14</jetty.version>
<acp.version>0.18.0</acp.version>
<acp.version>0.80.0</acp.version>
<slf4j.version>2.0.20</slf4j.version>
<jspecify.version>1.0.1</jspecify.version>
<junit.version>6.1.3</junit.version>
Expand All @@ -84,7 +84,7 @@ SPDX-License-Identifier: MIT
<gpg.plugin.version>3.2.8</gpg.plugin.version>
<central.publishing.version>0.11.0</central.publishing.version>
<exec.plugin.version>3.6.4</exec.plugin.version>
<spotless.version>3.10.3</spotless.version>
<spotless.version>3.10.4</spotless.version>
<palantir-java-format.version>2.102.0</palantir-java-format.version>
<agent.main>net.ladenthin.llama.atmosphere.LocalAgent</agent.main>
</properties>
Expand Down
Loading
Loading