SDK-aware linter, query engine & auto-fixer for com.google.genai
Project description
๐ฉ sdkspindle
SDK-aware linter, query engine & auto-fixer for com.google.genai
Born from a session where 7 SDK type mismatches made it to CI because there's no compiler in the loop when AI writes code.
What it does
sdkspindle extracts the entire public API surface of the Google GenAI Java SDK into a JSON reference file, then checks your Kotlin/Java source code against it. It catches the bugs that only show up at compile time โ before compile time.
$ sdkspindle check GeminiAdviceHelper.kt
SDK: 396 types, 37 services
โ GeminiAdviceHelper.kt:16 [MISSING_ARGS] caches.list() requires (ListCachedContentsConfig) โ called with no args
โ GeminiAdviceHelper.kt:20 [OPTIONAL_NO_CALL] 'displayName' on CachedContent returns Optional<String> โ use .displayName().orElse(...)
โ GeminiAdviceHelper.kt:30 [WRONG_TYPE] .ttl() expects Duration, got String literal
โ ๏ธ GeminiAdviceHelper.kt:31 [LISTOF_VARARGS] .contents() accepts varargs โ listOf() is unnecessary
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
3 error(s), 1 warning(s) across 1 file(s)
Install
pip install sdkspindle
For the Gemini-powered query engine:
pip install sdkspindle[query]
Commands
check โ Lint your code
# Lint specific files
sdkspindle check GeminiAdviceHelper.kt GeminiVisionHelper.kt
# Lint with auto-fix
sdkspindle check --fix **/*.kt
# Output SARIF for GitHub Actions
sdkspindle check --format sarif **/*.kt > results.sarif
# Output JSON
sdkspindle check --format json **/*.kt
extract โ Build SDK reference from source
# From a zip download
sdkspindle extract --sdk-zip java-genai-main.zip
# From a local directory
sdkspindle extract --sdk-dir path/to/java-genai/src/main/java/com/google/genai
# Custom output path
sdkspindle extract --sdk-zip java-genai-main.zip -o my_reference.json
query โ Ask the SDK (Gemini-powered)
# Natural language SDK lookup
sdkspindle query "how do I create a cached content with a 7-day TTL?"
sdkspindle query "what fields does CachedContent have?"
sdkspindle query "show me the correct way to call caches.list in Kotlin"
sdkspindle query "what's the difference between expireTime and ttl?"
# Use a specific model
sdkspindle query --model gemini-2.5-flash "how to set thinking level?"
Requires GOOGLE_API_KEY env var or --api-key flag.
stats โ SDK reference statistics
$ sdkspindle stats
sdkspindle SDK Reference Statistics
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Source: googleapis/java-genai
SDK: com.google.genai:google-genai
Total files: 518
Types: 396
Services: 37
Other classes: 70
Total fields: 1847
Optional<T>: 1623
Duration: 12
Instant: 28
Builder methods: 3291
Varargs: 186
Service methods: 94
Enum types: 42
Duration fields (common source of String/Duration bugs):
CreateCachedContentConfig.ttl
UpdateCachedContentConfig.ttl
...
diff โ Lint only changed files
# Lint files changed since last commit
sdkspindle diff
# Lint files changed since a specific ref
sdkspindle diff HEAD~5
# With auto-fix
sdkspindle diff --fix
init โ Generate config file
sdkspindle init
Configuration
sdkspindle looks for .sdkspindle.json walking up from the current directory (like .eslintrc):
{
"sdk_reference": "tools/sdkspindle/genai_sdk_reference.json",
"include": [
"android/app/src/main/java/**/*.kt"
],
"exclude": [
"**/test/**",
"**/build/**",
"**/generated/**"
],
"rules": {
"OPTIONAL_NO_CALL": "error",
"WRONG_TYPE": "error",
"MISSING_ARGS": "error",
"UNKNOWN_TYPE": "error",
"MISSING_IMPORT": "error",
"LISTOF_VARARGS": "warn",
"STRING_ENUM": "warn"
},
"gemini_model": "gemini-2.0-flash",
"suppress_inline": true
}
Set any rule to "off" to disable it.
Inline suppression
// Suppress a specific rule on the next line
// sdkspindle-ignore OPTIONAL_NO_CALL
val name = cache.name
// Suppress multiple rules
// sdkspindle-ignore OPTIONAL_NO_CALL,WRONG_TYPE
val ttl = config.ttl
// Suppress all rules on a line
val x = something.name // sdkspindle-ignore *
Checks
| Code | Default | What it catches | Auto-fix? |
|---|---|---|---|
OPTIONAL_NO_CALL |
error | cache.name instead of cache.name().orElse(...) |
โฌ |
WRONG_TYPE |
error | .ttl("604800s") instead of .ttl(Duration.ofDays(7)) |
โฌ |
MISSING_ARGS |
error | caches.list() instead of caches.list(config) |
โฌ |
UNKNOWN_TYPE |
error | import com.google.genai.types.DoesNotExist |
โฌ |
MISSING_IMPORT |
error | Using ListCachedContentsConfig without importing it |
โ |
LISTOF_VARARGS |
warn | .contents(listOf(x)) instead of .contents(x) |
โ |
STRING_ENUM |
warn | .thinkingLevel("LOW") instead of ThinkingLevel.Known.LOW |
โฌ |
CI Integration
GitHub Actions (SARIF)
name: SDK Lint
on: [push, pull_request]
jobs:
sdkspindle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install sdkspindle
- run: sdkspindle check --format sarif **/*.kt > results.sarif
continue-on-error: true
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
This gives you inline PR annotations on the exact lines with issues.
Reusable workflow
jobs:
sdk-lint:
uses: elb-pr/sdkspindle/.github/workflows/sdkspindle.yml@main
with:
files: 'android/app/src/main/java/**/*.kt'
Pre-commit hook
#!/bin/sh
# .git/hooks/pre-commit
sdkspindle diff HEAD --format text
JSON Reference Structure
The extracted SDK reference (genai_sdk_reference.json, ~936KB) contains:
{
"_meta": { "types": 396, "services": 37, "other": 70 },
"types": {
"CreateCachedContentConfig": {
"fields": [
{ "name": "ttl", "type": "Optional<Duration>", "optional": true },
{ "name": "contents", "type": "Optional<List<Content>>", "optional": true }
],
"builderMethods": [
{ "name": "ttl", "paramType": "Duration", "varargs": false },
{ "name": "contents", "paramType": "Content", "varargs": true }
]
}
},
"services": {
"Caches": {
"methods": [
{ "name": "list", "params": [{"type": "ListCachedContentsConfig"}],
"returnType": "Pager<CachedContent>" }
]
}
}
}
Origin story
During a session building the-adviser (an on-device poker advisor), Claude wrote Gemini SDK integration code with 7 type mismatches that all passed code review but failed CI:
.ttl("604800s")โ SDK wantsDuration, notStringcache.nameโ returnsOptional<String>, needs.orElse()client.caches.list()โ requires a config parameter.contents(listOf(...))โ method has varargs overload
The Kotlin compiler catches all of these instantly. The problem is there's no compiler in the loop when an AI writes code in a text editor. sdkspindle is the compiler-in-a-regex that fills that gap.
License
Apache-2.0 โ see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sdkspindle-0.1.1.tar.gz.
File metadata
- Download URL: sdkspindle-0.1.1.tar.gz
- Upload date:
- Size: 89.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0fb05064e0d39a69ad320a785a3fae9ddfea4227ea54ab431970daedb4db692
|
|
| MD5 |
169527e80bbc27addec40ef4f1f44dcc
|
|
| BLAKE2b-256 |
ed6b73adc56200e987e7cc2b9728309035d8b1dfbacff25090e13822e3e17b44
|
Provenance
The following attestation bundles were made for sdkspindle-0.1.1.tar.gz:
Publisher:
build.yml on elb-pr/sdkspindle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sdkspindle-0.1.1.tar.gz -
Subject digest:
a0fb05064e0d39a69ad320a785a3fae9ddfea4227ea54ab431970daedb4db692 - Sigstore transparency entry: 1108440988
- Sigstore integration time:
-
Permalink:
elb-pr/sdkspindle@ae43c6f00c0319070ee45b5b75b57669f663b5a2 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/elb-pr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@ae43c6f00c0319070ee45b5b75b57669f663b5a2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sdkspindle-0.1.1-py3-none-any.whl.
File metadata
- Download URL: sdkspindle-0.1.1-py3-none-any.whl
- Upload date:
- Size: 84.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
694d4b9607d2458cee277ba443ae363056fd425bf0ba17b9f36833816c459440
|
|
| MD5 |
a99cc5db41cd38623080aa470456cb81
|
|
| BLAKE2b-256 |
09ec76318071373a4975eb22fee0e73c872295da73fbf91e85aa2983cd8a31a7
|
Provenance
The following attestation bundles were made for sdkspindle-0.1.1-py3-none-any.whl:
Publisher:
build.yml on elb-pr/sdkspindle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sdkspindle-0.1.1-py3-none-any.whl -
Subject digest:
694d4b9607d2458cee277ba443ae363056fd425bf0ba17b9f36833816c459440 - Sigstore transparency entry: 1108441006
- Sigstore integration time:
-
Permalink:
elb-pr/sdkspindle@ae43c6f00c0319070ee45b5b75b57669f663b5a2 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/elb-pr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@ae43c6f00c0319070ee45b5b75b57669f663b5a2 -
Trigger Event:
push
-
Statement type: