Skip to content

Commit d27400e

Browse files
Merge branch 'stage' into bugfix/ADFA-6199-kotlin-indexer-analysis-failures
2 parents c27b7b7 + 01a2938 commit d27400e

40 files changed

Lines changed: 5718 additions & 4 deletions
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Spotless apply
2+
3+
# For contributors without a shell (e.g. editing in the GitHub web UI): runs spotlessApply on a
4+
# branch and appends the result as a standalone commit. The push uses GITHUB_TOKEN, so it does not
5+
# start other workflows.
6+
7+
permissions:
8+
contents: write
9+
10+
on:
11+
workflow_dispatch:
12+
inputs:
13+
branch:
14+
description: 'Branch to format (not stage or main)'
15+
required: true
16+
type: string
17+
18+
concurrency:
19+
group: spotless-apply-${{ inputs.branch }}
20+
cancel-in-progress: false
21+
22+
jobs:
23+
spotless_apply:
24+
name: Run spotlessApply and commit
25+
runs-on: self-hosted
26+
timeout-minutes: 30
27+
env:
28+
BRANCH: ${{ inputs.branch }}
29+
30+
steps:
31+
- name: Refuse protected branches
32+
run: |
33+
case "$BRANCH" in
34+
stage|main)
35+
echo "::error::Refusing to push to '$BRANCH'."
36+
exit 1
37+
;;
38+
esac
39+
40+
# fetch-depth 0: the Spotless ratchet compares against origin/stage.
41+
- name: Checkout branch
42+
uses: actions/checkout@v4
43+
with:
44+
ref: ${{ inputs.branch }}
45+
fetch-depth: 0
46+
47+
- name: Check if Nix is installed
48+
run: |
49+
if command -v nix >/dev/null 2>&1; then
50+
echo "nix_installed=true" >> $GITHUB_ENV
51+
else
52+
echo "nix_installed=false" >> $GITHUB_ENV
53+
fi
54+
55+
- name: Install Flox
56+
if: env.nix_installed == 'false'
57+
uses: flox/install-flox-action@v2
58+
59+
- name: Run spotlessApply
60+
run: |
61+
flox activate -d flox/base -- ./gradlew spotlessApply --no-daemon
62+
63+
- name: Commit and push
64+
run: |
65+
if git diff --quiet; then
66+
echo "::notice::Already formatted. Nothing to commit."
67+
exit 0
68+
fi
69+
git config user.name "ADFA"
70+
git config user.email "dev-team@appdevforall.org"
71+
git commit -am "style: spotless reformat, no functional change"
72+
git push origin "HEAD:refs/heads/$BRANCH"
73+
echo "::notice::Pushed $(git rev-parse --short HEAD) to $BRANCH."

‎app/proguard-rules.pro‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@
256256
-keep class com.google.android.material.** { *; }
257257
-keep class io.noties.markwon.** { *; }
258258
-keep class com.google.gson.** { *; }
259+
-keep class org.snakeyaml.engine.** { *; }
259260

260261
-keep class com.google.firebase.** { *; }
261262
-keep class com.google.android.gms.** { *; }

‎docs/PLUGIN_API_CHANGELOG.md‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,41 @@ need a source change, a recompile, or both · `tooling` = API-stability
3535
milestone. **[verified]** = read from the checked-in ABI dump. **[reconstructed]**
3636
= diffed from `plugin-api/src` history (predates the dump; symbol-accurate).
3737

38-
### 26.40 — unreleased
38+
### 26.41 — unreleased
39+
- **added — The AI prompt config engine and settings-pane helpers** _(ADFA-6281)_ **[verified]**
40+
Every AI plugin carried its own copy of the code that reads and renders its prompt
41+
config, and the credential screens their own copy of the reveal toggle and pane
42+
styling, so a fix had to be repeated per plugin and a missed copy made the plugins
43+
drift. The host now ships one copy.
44+
`com.itsaky.androidide.plugins.ai.prompt`: `PromptTemplateEngine` and `PromptText`
45+
(the `{{NAME}}` / `{{#NAME}}` / `{{^NAME}}` renderer; names may be in any case and hold
46+
dots, e.g. `{{fileName}}` or `{{item.name}}`, `{{{{` writes a literal `{{`, and config text keeps its whitespace as YAML
47+
parsed it); `PromptConfigLoader.load(source,
48+
parser)`, which reads `agent.yml` and its `include` list off the main thread;
49+
`PromptConfigDocument` and `PromptConfigObject`, the strict key-by-key reader a parser
50+
maps the merged YAML through; `PromptConfigSource` / `AssetPromptConfigSource`;
51+
`PromptConfigException`; and `PromptConfigStore<T>`, the per-activation cache, behind
52+
`PromptConfigProvider<T>`. Loader and store are generic over the plugin's config type:
53+
a plugin supplies only a `PromptConfigParser<T>` and keeps one store, e.g.
54+
`val shared = PromptConfigStore(MyParser)`, and calls `shared.reload(source, onLoaded, onFailed)`
55+
from `activate()` and `shared.clear()` from `deactivate()`. The YAML library (snakeyaml-engine 2.10)
56+
is on the host side, so a plugin using the loader no longer bundles it.
57+
`com.itsaky.androidide.plugins.ai.ui`: `SecretRevealController`, whose two states are
58+
each a `RevealToggle` (icon and content description), and
59+
`View.applyPaneStyling(PaneStyle, outlinedButtonIds)`, with `PaneStyle` grouping a
60+
`ButtonColors` per emphasis and a `FieldColors`. Both take the plugin's own resource ids
61+
rather than shipping any: they resolve against the view's context, which carries the
62+
plugin's resources, not the host's.
63+
Additive to the ABI (185 added lines in the dump, none removed), but no longer unused:
64+
AI-Core and the Gemini, Local and OpenAI agents now load and render their prompt config
65+
through `ai.prompt` and drop their private copies, and the Gemini, OpenAI and MCP
66+
settings screens use `ai.ui`. `LlmInferenceService.WebSearchBackend` (`canSearchWeb()`)
67+
lets a backend say whether a `web_search` request would be searched now; ai-core forces
68+
and offers its `web_search` tool only when it does. The `extraParams` keys both sides
69+
read are defined once, as `WebSearchBackend.EXTRA_PARAM_WEB_SEARCH` and
70+
`ToolCallingBackend.EXTRA_PARAM_REQUIRED_TOOL`. Floor
71+
`plugin.min_ide_version` at `26.41` to use any of it; an older IDE has none of these
72+
classes, and the plugin fails with `NoClassDefFoundError` on first use.
3973
- **added — Read-only App Logs and IDE Logs** _(ADFA-6267)_ **[verified]**
4074
Plugins could read build output (`IdeBuildService.getBuildOutput()`) but not the App Logs
4175
or IDE Logs tabs, so an agent diagnosing a runtime crash had to ask the user to paste them.
@@ -50,7 +84,8 @@ milestone. **[verified]** = read from the checked-in ABI dump. **[reconstructed]
5084
`LogReadResult.EMPTY`, never a throw. The service has no clear or write method, and needs
5185
no permission: plugins run in-process under the IDE's uid, so a gate would disclose log
5286
access, not enforce it. Purely additive (the ABI dump diff is additions only). Floor
53-
`plugin.min_ide_version` at `26.40` to use it; an older IDE has no such service.
87+
`plugin.min_ide_version` at `26.41` to use it; an older IDE has no such service.
88+
### 26.40 — 2026-09-29
5489
- **added — An embedding capability a backend can declare** _(ADFA-6053)_ **[verified]**
5590
A backend that has an embedding model can now say so. The only embedding entry point
5691
before this was `LlmInferenceService.getEmbeddings(String, String)`, which addresses a

‎docs/adr/0002-on-device-builds-via-gradle-tooling-api.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Run builds with the **Gradle Tooling API in a separate JVM process**, and have t
2020

2121
The app streams progress/events back from this process and renders them (e.g. `BuildState`, build output). The process runs on a **full out-of-process JDK** — the `java` binary from our terminal bootstrap packages (`appdevforall/terminal-packages`), launched by `ToolingServerRunner` — **not** the composite-build toolchains from [ADR 0003](0003-vendored-forked-desktop-toolchain.md), which are a separate, in-IDE-runtime concern.
2222

23+
**Scope:** this covers every build that produces an installable artifact, including Quick Build's own proxy-app provisioning. Quick Build's incremental per-save step is the one exception — it compiles outside Gradle, and the trade-offs are recorded in [ADR 0016](0016-quick-build-compiles-outside-gradle.md).
24+
2325
## Consequences
2426

2527
**Positive**
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# 0016. Quick Build's live reload path compiles incrementally outside Gradle
2+
3+
- **Status:** Proposed
4+
- **Date:** 2026-08-12
5+
- **Deciders:** Code On The Go team
6+
7+
## Context
8+
9+
[ADR 0002](0002-on-device-builds-via-gradle-tooling-api.md) builds on device through real Gradle so results match a desktop build, and rejects a custom build engine. That still holds for anything a user installs or ships.
10+
11+
Quick Build (ADFA-4128) does a different job: fast live reload, so a developer can iterate while writing code. A standard incremental Gradle build of a single app-module edit medians 4.7 s on a Galaxy A56 and 18.4 s on an A06, against 1.1 s and 2.8 s for Quick Build `[measured on a56, a06]`.
12+
13+
Most of that time is not spent on the edit. A one-line Kotlin edit takes 7.8 s to build incrementally on an A06:
14+
15+
- launch and configuration, 3.9 s - paid whatever the edit touched
16+
- packaging and install, 1.1 s - to make an APK a running app does not need
17+
- dex and resource link, 1.4 s - on outputs the edit did not change
18+
- kotlinc, 1.2 s - the only stage the edit created
19+
20+
The first three cannot be sped up or skipped.
21+
22+
## Decision
23+
24+
**Quick Build's live reload path does not use Gradle.** `:quickbuild:daemon`, a JVM child process of CoGo, compiles Kotlin with the Kotlin Build Tools API and Java with javac, then dexes with d8 and relinks resources with aapt2, using the SDK already on the device. No AGP, no r8.
25+
26+
**Gradle handles what live reload cannot.** It still provisions the proxy app through the existing Tooling API path, and still builds every edit the classifier declines. Nothing a user installs or ships comes out of the daemon.
27+
28+
**One compiler, not two.** Quick Build needs Kotlin 2.3.x for faster, more robust incremental compilation. Until the rest of CoGo moves up, the APK carries two Kotlin compilers. The move is in review as ADFA-2602; unifying them is ADFA-4931.
29+
30+
## Consequences
31+
32+
**Positive**
33+
34+
- The edit loop is 4-6x faster, measured in the 2026-09-05 to 09-07 benchmarking run against the initial experimental Quick Build release.
35+
- The compiler stays warm between edits - the biggest single latency lever, and something Gradle cannot do.
36+
- A compiler crash kills the daemon, not the IDE, and the daemon can be shut down to give Gradle its memory back.
37+
38+
**Negative - inherent to the decision**
39+
40+
- Output is not identical to Gradle's. That is deliberate: close enough on the cases that matter beats full compatibility.
41+
- A second build pipeline to maintain. It will drift from AGP, and we cannot use Gradle as ground truth, so it needs its own ongoing testing - which is slow, because builds on low-spec devices are slow.
42+
43+
**Negative - solvable with more work**
44+
45+
- No annotation processing. kapt and KSP edits go to Gradle; KSP looks tractable, see [ksp-kapt-feasibility.md](../../quickbuild/docs/ksp-kapt-feasibility.md).
46+
- Live reload covers a narrow set of edits today; the rest fall back to Gradle. Conservative defaults, not hard limits.
47+
- Memory is not tuned. Gradle and Quick Build share it, and idle timeouts are all that keeps them out of each other's way.
48+
49+
## Alternatives considered
50+
51+
- **Gradle with fewer tasks** — rejected: the cost is mostly configuration and task-graph work, which fewer tasks do not remove, and it still builds an APK rather than a deployable payload.
52+
- **Compile in-process inside the IDE** — rejected for ADR 0002's own reason: a compiler OOM would take the editor with it.
53+
- **Replace the proxy-app build too** — rejected: it would drift from AGP on the one artifact where that is unacceptable.
54+
- **ART hot-swap (Apply Changes)** — rejected: needs an attached debugger and replaces only method bodies.
55+
- **Patch the android.jar** — rejected as infeasible; see [why not android.jar](../../quickbuild/docs/why-not-android-jar.md).
56+
57+
## Related
58+
59+
- [ADR 0002](0002-on-device-builds-via-gradle-tooling-api.md) — still governs full builds and Quick Build's provisioning.
60+
- [ADR 0004](0004-embedded-termux-runtime.md) — the daemon runs on the bundled JDK.
61+
- [`quickbuild/README.md`](../../quickbuild/README.md) — design and measured numbers.

‎docs/adr/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ Format is lightweight **MADR / Nygard**: Context → Decision → Consequences
2929
| [0013](0013-refactoring-ui-lives-in-the-owning-lsp-module.md) | Refactoring UI lives in the owning LSP module | Proposed |
3030
| [0014](0014-refactorings-decline-rather-than-rewrite.md) | Interactive refactorings decline rather than rewrite unselected code | Proposed |
3131
| [0015](0015-one-pinned-ktfile-per-analysis.md) | One pinned live KtFile per analysis, enforced by the type system | Proposed |
32+
| [0016](0016-quick-build-compiles-outside-gradle.md) | Quick Build's per-save path compiles incrementally outside Gradle | Proposed |

‎docs/plugin-api.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The surface a plugin binds to is broader than one module. All of the following a
1313
- Extension interfaces plugins **implement**: `UIExtension`, `EditorExtension`, `EditorTabExtension`, `DocumentationExtension`, `BuildActionExtension`, `SnippetExtension`, `ProjectExtension`, `FileOpenExtension`, `SettingsExtension`.
1414
- IDE service interfaces plugins **call** (via `ServiceRegistry.get(X::class.java)`): `IdeProjectService`, `IdeEditorService`, `IdeFileService`, `IdeEnvironmentService`, `IdeArchiveService`, `IdeBuildService`, `IdeLogService`, `IdeUIService`, `IdeEditorTabService`, `IdeTooltipService`, `IdeThemeService`, `IdeFeatureFlagService`, `IdeCommandService`, `IdeTemplateService`, `IdeSnippetService`, `IdeSidebarService`.
1515
- Cross-plugin service interfaces, where **one plugin implements what another calls** (via `SharedServices`): `LlmInferenceService` — implemented by ai-core, called by every AI plugin — together with the types nested in it that a *backend* plugin implements (`LlmBackend`, `HistoryCapableBackend`, `ToolCallingBackend`, `CancellableBackend`, `ConfigurableBackend`, `EmbeddingBackend`) and the value types either side constructs (`ChatMessage`, `LlmConfig`, `LlmResponse`, `SystemPromptRequest`, `ToolDefinition`, `ToolCallRequest`). Also `ToolSourceRegistry` — implemented by ai-core, called by any plugin contributing tools to the agent — with `ToolSource` and `ToolSpec`, which a *contributing* plugin implements, `ToolInvocation`, which ai-core constructs and passes to `ToolSource.invoke`, and `ToolOutcome`, which the source returns.
16-
- Utility classes plugins **instantiate**: `KeystoreSecretStore` (AES/GCM over the Android Keystore, alias supplied by the caller). Host-side implementation rather than an interface, so plugins share one copy in the process instead of compiling their own.
16+
- Utility classes plugins **instantiate**: `KeystoreSecretStore` (AES/GCM over the Android Keystore, alias supplied by the caller); the AI prompt config engine in `ai.prompt` (`PromptTemplateEngine`, `PromptConfigLoader`, `PromptConfigStore`, `PromptConfigDocument`, `PromptConfigObject`, `AssetPromptConfigSource`), generic over a plugin's own config type through `PromptConfigParser` and `PromptConfigProvider`, which a plugin implements; and the settings-pane helpers in `ai.ui` (`SecretRevealController` with `RevealToggle`, `applyPaneStyling` with `PaneStyle`, `ButtonColors` and `FieldColors`). Host-side implementations rather than interfaces, so plugins share one copy in the process instead of compiling their own.
1717
- Data classes plugins **construct** (e.g. `MenuItem`, `TabItem`, `EditorTabItem`, `NavigationItem`, `ToolbarAction`, `FabAction`, `PluginBuildAction`, `SnippetContribution`, `PluginTooltipEntry`, `PluginSettingsEntry`).
1818
- Enums / sealed types plugins **reference**: `PluginPermission`, `ShowAsAction`, `ArchiveFormat`, `BuildActionCategory`, `ToolbarActionIds`, `CommandSpec`, `CommandResult`, `ExtractResult`, `KeystoreSecretStore.Stored`. Sealed, so a plugin `when`s over the cases exhaustively — adding one is a **breaking** change, not an additive one.
1919
- **Wire/format contracts outside the module:**

‎gradle/libs.versions.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ tooling-agp = { module = "com.android.tools.build:gradle", version.ref = "agp-to
299299
tooling-builderModel = { module = "com.android.tools.build:builder-model", version.ref = "agp-tooling" }
300300
tooling-gradleApi = { module = "com.itsaky.androidide.gradle:gradle-tooling-api", version.ref = "gradle-tooling" }
301301
tooling-slf4j = { module = "org.slf4j:slf4j-api", version = "2.0.12" }
302+
# 2.x targets Java 8; 3.x needs 11. Match the AI plugins' copy, which the app shadows parent-first.
303+
snakeyaml-engine = { module = "org.snakeyaml:snakeyaml-engine", version = "2.10" }
302304

303305
# Classpaths
304306
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" }

0 commit comments

Comments
 (0)