From 6a8302881daf12bb2de5c9296e085bc8e2aafdaf Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 4 Aug 2026 19:28:55 -0400 Subject: [PATCH] feat(jetbrains): add jetbrains-cli-pin skill Add a skill that pins the JetBrains plugin to the latest released CLI, unpins to the local repo CLI, or fresh-regenerates the local CLI. Every command first cleans all leftover CLI binaries and build artifacts in the current worktree so each run starts from a fresh, artifact-free state. Reuses the release-jetbrains set-pin/pin-common helpers for validated version bumps and cross-links the skill from the JetBrains AGENTS.md. --- .kilo/skills/jetbrains-cli-pin/SKILL.md | 79 +++++++++++++++ .../skills/jetbrains-cli-pin/script/clean.ts | 23 +++++ .../jetbrains-cli-pin/script/cli-pin.ts | 95 +++++++++++++++++++ packages/kilo-jetbrains/AGENTS.md | 2 + 4 files changed, 199 insertions(+) create mode 100644 .kilo/skills/jetbrains-cli-pin/SKILL.md create mode 100644 .kilo/skills/jetbrains-cli-pin/script/clean.ts create mode 100644 .kilo/skills/jetbrains-cli-pin/script/cli-pin.ts diff --git a/.kilo/skills/jetbrains-cli-pin/SKILL.md b/.kilo/skills/jetbrains-cli-pin/SKILL.md new file mode 100644 index 0000000000..ecd59d67da --- /dev/null +++ b/.kilo/skills/jetbrains-cli-pin/SKILL.md @@ -0,0 +1,79 @@ +--- +name: jetbrains-cli-pin +description: Use when pinning or unpinning the CLI version the Kilo JetBrains plugin uses, or fresh-regenerating the local repo CLI. Cleans all leftover CLI binaries and build artifacts in the current worktree so every operation starts from a fresh, artifact-free state. +--- + +# JetBrains CLI Pin + +Pin the Kilo JetBrains plugin to the latest released CLI, unpin it to use the local +repo CLI, or fresh-regenerate the local CLI while unpinned. Every command first cleans +all CLI/pin build artifacts and binaries in the current worktree so the result never +carries state from a previous run. + +Run all commands from the repository root of the worktree you want to affect. Paths are +relative, so they resolve to the current worktree, not the main checkout. + +## Two Controls + +The plugin's CLI behavior is governed by two independent values: + +| Control | Location | Meaning | +|---|---|---| +| Pin mode | `packages/kilo-jetbrains/gradle.properties` -> `kilo.cli.pinned` | `true` = download the released CLI at build/connect time. `false` = build and bundle the local repo CLI. | +| Pinned version | `packages/kilo-jetbrains/package.json` -> `version` | Which GitHub CLI release is downloaded and generated from when `pinned=true`. | + +"Pin to latest" means `kilo.cli.pinned=true` **and** `package.json` set to the latest +stable CLI release. "Unpin" means `kilo.cli.pinned=false` with a freshly built local CLI +bundled. + +## Commands + +```bash +bun .kilo/skills/jetbrains-cli-pin/script/cli-pin.ts [--no-verify] +``` + +| Command | Steps | +|---|---| +| `pin` | Clean -> set `kilo.cli.pinned=true` -> bump `package.json` to latest release (via `set-pin.ts --latest`, which validates release assets) -> verify with a cold `gradlew clean typecheck`. | +| `unpin` | Clean -> set `kilo.cli.pinned=false` -> `:backend:buildRepoCli` (fresh CLI) -> `:backend:stageRepoCli` -> assert staged `kilo-cli.zip` -> verify with `gradlew typecheck`. | +| `regen` | Fast dev loop while unpinned: `rm -rf dist` -> `buildRepoCli` -> `stageRepoCli`. Refuses to run unless `kilo.cli.pinned=false`. | +| `clean` | Run the shared artifact clean only. | + +`--no-verify` skips the gradle verification build (rewrites + clean only). Use it when +offline or without Java 21. + +## Cleaned Artifacts + +`clean()` runs `./gradlew clean` plus targeted deletes. All paths are gitignored, so +tracked files are never touched. The clean removes the stale artifacts that otherwise +leak across a pin/unpin flip: + +| Artifact | Path | +|---|---| +| Repo CLI binaries | `packages/opencode/dist/` | +| Staged CLI archive | `packages/kilo-jetbrains/backend/build/generated/kilo-cli-res/kilo-cli.zip` | +| Generated props / checksums / OpenAPI client | `packages/kilo-jetbrains/backend/build/generated/` | +| Compiled resources (bundled zip on classpath) | `packages/kilo-jetbrains/backend/build/resources/` | +| CLI download cache | `packages/kilo-jetbrains/backend/build/cli-cache/` | + +The staged `kilo-cli.zip` is the nastiest leak: once it lands in `backend/build/resources/main/` +from an unpinned build, runtime prefers the bundled zip over downloading. A full clean is +the only reliable reset. + +## Notes + +- Verification builds pass `--no-configuration-cache` so the changed `kilo.cli.pinned` + value is re-read instead of served from the on-disk Gradle configuration cache. +- The `pin` verification is a cold build: it downloads the pinned CLI release via + `generateOpenApiSpec` and needs network access plus Java 21. Use `--no-verify` offline. +- `kilo.cli.pinned=false` is dev-only and not releasable. Production Gradle builds, + `script/build-version.sh`, and the release scripts hard-fail on `false` -- run `pin` + before releasing. + +## Related + +- Version-bump and release-gating logic lives in the `release-jetbrains` skill + (`.kilo/skills/release-jetbrains/SKILL.md`); this skill reuses its `set-pin.ts` and + `pin-common.ts` helpers. +- Background on the build wiring: the "CLI Pinning, Unpinning, and Bumping" and "CLI + Integration" sections of `packages/kilo-jetbrains/AGENTS.md`. diff --git a/.kilo/skills/jetbrains-cli-pin/script/clean.ts b/.kilo/skills/jetbrains-cli-pin/script/clean.ts new file mode 100644 index 0000000000..a58ddcc532 --- /dev/null +++ b/.kilo/skills/jetbrains-cli-pin/script/clean.ts @@ -0,0 +1,23 @@ +import { $ } from "bun" + +// Single source of truth for every CLI/pin artifact that can leak across a mode +// flip in the current worktree. Everything here is gitignored (dist, backend/build, +// .gradle), so cleaning never touches tracked files. +// +// The build's conditional sourceSets/dependsOn wiring in backend/build.gradle.kts only +// produces a correct package from a clean build/ directory. Incremental builds are what +// let a stale kilo-cli.zip survive a pin<->unpin flip, and runtime prefers a bundled +// zip over downloading -- so a full gradle clean is the reliable reset. +export async function clean(jb = "packages/kilo-jetbrains") { + // gradle clean wipes each project's build directory (including backend/build). + await $`./gradlew clean --quiet`.cwd(jb).nothrow() + + // Stale per-platform CLI binaries. build.ts only rm -rf dist for the platforms it + // builds, so old platform dirs can survive; wipe the whole tree. + await $`rm -rf packages/opencode/dist` + + // Belt-and-suspenders in case gradle clean was skipped or ran offline. + await $`rm -rf ${jb}/backend/build/generated`.nothrow() + await $`rm -rf ${jb}/backend/build/resources`.nothrow() + await $`rm -rf ${jb}/backend/build/cli-cache`.nothrow() +} diff --git a/.kilo/skills/jetbrains-cli-pin/script/cli-pin.ts b/.kilo/skills/jetbrains-cli-pin/script/cli-pin.ts new file mode 100644 index 0000000000..ae293b6b1f --- /dev/null +++ b/.kilo/skills/jetbrains-cli-pin/script/cli-pin.ts @@ -0,0 +1,95 @@ +#!/usr/bin/env bun + +import { $ } from "bun" +import { parseArgs } from "util" +import { clean } from "./clean" + +const jb = "packages/kilo-jetbrains" +const props = `${jb}/gradle.properties` +const pkg = `${jb}/package.json` +const zip = `${jb}/backend/build/generated/kilo-cli-res/kilo-cli.zip` + +const arg = Bun.argv[2] +const cmd = arg && !arg.startsWith("-") ? arg : undefined +const { values } = parseArgs({ + args: cmd ? Bun.argv.slice(3) : Bun.argv.slice(2), + options: { + "no-verify": { type: "boolean", default: false }, + help: { type: "boolean", short: "h", default: false }, + }, +}) + +if (values.help || !cmd) { + console.log(` +Usage: bun .kilo/skills/jetbrains-cli-pin/script/cli-pin.ts [--no-verify] + +Commands: + pin Pin the JetBrains plugin to the latest released CLI. Cleans artifacts, + sets kilo.cli.pinned=true, bumps package.json to the latest release, + then verifies with a cold gradle build (needs network + Java 21). + unpin Use the local repo CLI. Cleans artifacts, sets kilo.cli.pinned=false, + fresh-builds and stages the repo CLI, then verifies with typecheck. + regen Fast dev loop: rebuild + restage the local repo CLI (requires unpinned). + clean Remove all CLI/pin build artifacts and binaries in the current worktree. + +Options: + --no-verify Skip the gradle verification build (rewrites + clean only). + +Run from the repository root of the worktree you want to affect. +`) + process.exit(values.help ? 0 : 1) +} + +async function pinned() { + const text = await Bun.file(props).text() + const line = text.split(/\r?\n/).find((l) => l.startsWith("kilo.cli.pinned=")) + return (line?.split("=", 2)[1]?.trim().toLowerCase() ?? "true") === "true" +} + +async function setPinned(value: boolean) { + const text = await Bun.file(props).text() + if (!/^kilo\.cli\.pinned=.*$/m.test(text)) throw new Error(`kilo.cli.pinned not found in ${props}`) + await Bun.write(props, text.replace(/^kilo\.cli\.pinned=.*$/m, `kilo.cli.pinned=${value}`)) +} + +async function report() { + const version = (await Bun.file(pkg).json()).version + console.log(`\nState: kilo.cli.pinned=${await pinned()}, package.json version=${version}`) +} + +if (cmd === "pin") { + await clean() + await setPinned(true) + // set-pin.ts bumps package.json to the latest release and refuses versions with + // missing runtime assets, so we do not reimplement release/asset validation. + await $`bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest` + if (!values["no-verify"]) { + // Cold pinned build downloads the pinned CLI release via generateOpenApiSpec. + await $`./gradlew clean typecheck --no-configuration-cache`.cwd(jb) + } + await report() +} else if (cmd === "unpin") { + await clean() + await setPinned(false) + // build.ts does rm -rf dist internally, producing a fresh single-platform binary. + await $`./gradlew :backend:buildRepoCli --no-configuration-cache`.cwd(jb) + // stageRepoCli has upToDateWhen{false}; force it so the staged zip matches this build. + await $`./gradlew :backend:stageRepoCli --no-configuration-cache`.cwd(jb) + if (!(await Bun.file(zip).exists())) throw new Error(`Expected staged CLI at ${zip} after unpin`) + if (!values["no-verify"]) { + await $`./gradlew typecheck --no-configuration-cache`.cwd(jb) + } + await report() +} else if (cmd === "regen") { + if (await pinned()) throw new Error("regen requires the unpinned state; run 'unpin' first") + await $`rm -rf packages/opencode/dist` + await $`./gradlew :backend:buildRepoCli --no-configuration-cache`.cwd(jb) + await $`./gradlew :backend:stageRepoCli --no-configuration-cache`.cwd(jb) + if (!(await Bun.file(zip).exists())) throw new Error(`Expected staged CLI at ${zip} after regen`) + await report() +} else if (cmd === "clean") { + await clean() + await report() +} else { + throw new Error(`Unknown command '${cmd}'. Run with --help for usage.`) +} diff --git a/packages/kilo-jetbrains/AGENTS.md b/packages/kilo-jetbrains/AGENTS.md index 2901241038..254d66698f 100644 --- a/packages/kilo-jetbrains/AGENTS.md +++ b/packages/kilo-jetbrains/AGENTS.md @@ -170,6 +170,8 @@ For blocking I/O in coroutines, move the dispatcher switch inside the callee usi The JetBrains plugin has two independent CLI controls. Use the commands below directly when asked to change either one; do not hand-edit versions by guesswork. +For a one-shot pin/unpin/regen that also cleans every leftover CLI binary and build artifact in the current worktree, use the `jetbrains-cli-pin` skill (`.kilo/skills/jetbrains-cli-pin/SKILL.md`): `bun .kilo/skills/jetbrains-cli-pin/script/cli-pin.ts `. + **Pin mode** (`kilo.cli.pinned` in `packages/kilo-jetbrains/gradle.properties`) controls release CLI vs local repo CLI. | Ask | Do |