Skip to main content

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

Built by Powered by

PyPI Python License SDK Zero Dependencies SARIF

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 wants Duration, not String
  • cache.name โ€” returns Optional<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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sdkspindle-0.1.1.tar.gz (89.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sdkspindle-0.1.1-py3-none-any.whl (84.5 kB view details)

Uploaded Python 3

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

Hashes for sdkspindle-0.1.1.tar.gz
Algorithm Hash digest
SHA256 a0fb05064e0d39a69ad320a785a3fae9ddfea4227ea54ab431970daedb4db692
MD5 169527e80bbc27addec40ef4f1f44dcc
BLAKE2b-256 ed6b73adc56200e987e7cc2b9728309035d8b1dfbacff25090e13822e3e17b44

See more details on using hashes here.

Provenance

The following attestation bundles were made for sdkspindle-0.1.1.tar.gz:

Publisher: build.yml on elb-pr/sdkspindle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for sdkspindle-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 694d4b9607d2458cee277ba443ae363056fd425bf0ba17b9f36833816c459440
MD5 a99cc5db41cd38623080aa470456cb81
BLAKE2b-256 09ec76318071373a4975eb22fee0e73c872295da73fbf91e85aa2983cd8a31a7

See more details on using hashes here.

Provenance

The following attestation bundles were made for sdkspindle-0.1.1-py3-none-any.whl:

Publisher: build.yml on elb-pr/sdkspindle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page