mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 11:05:31 +08:00
refactor: kilo compat for v1.17.9
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
# Changesets
|
||||
|
||||
This directory contains changeset files used to track changes for the next release.
|
||||
|
||||
## Adding a changeset
|
||||
|
||||
When making a user-facing change, prefer one concise changeset per PR, grouping related changes when possible. Run:
|
||||
|
||||
```sh
|
||||
bunx changeset add
|
||||
```
|
||||
|
||||
Or manually create a file `.changeset/<slug>.md`:
|
||||
|
||||
```md
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Short description of the change for the changelog.
|
||||
```
|
||||
|
||||
Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes.
|
||||
|
||||
Changeset files are consumed at release time when the `publish.yml` workflow runs, generating changelog entries for the GitHub release notes.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Allow agents to stop and remove a targeted Agent Manager session.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Improve CLI sidebar usage sections with cent-formatted costs, collapsible details, and aligned model totals.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Respect the Terminal Command Blocks display setting for background process tool cards.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Display here-document content as plain text in terminal approval prompts.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Support bidirectional text in chat messages so right-to-left and mixed-language conversations render in the correct direction.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"@kilocode/kilo-gateway": patch
|
||||
---
|
||||
|
||||
Fix Cloud Agent session imports in installed CLI builds and prevent malformed exports or write failures from leaving partial imports.
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json",
|
||||
"changelog": ["../script/changelog-github.cjs", { "repo": "Kilo-Org/kilocode" }],
|
||||
"commit": false,
|
||||
"fixed": [["kilo-code", "@kilocode/cli"]],
|
||||
"linked": [],
|
||||
"access": "restricted",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Open the Agent Manager new-worktree dialog from primary actions and keyboard shortcuts, with an explicit quick-create action in its dropdown.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep sessions usable after deprecated local review commands complete without a session status transition.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
"@kilocode/kilo-indexing": minor
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Configure a custom file extension allowlist for codebase indexing to limit scans to relevant project files and support additional text formats.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-indexing": patch
|
||||
---
|
||||
|
||||
Allow remote embedder validation timeouts to retry before indexing reports a connection failure.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
---
|
||||
|
||||
Remote CLI: expose slash command discovery, execution, and `/new` session creation over the relay. `list_commands` and `send_command` (including the built-in `compact` flow) are scoped to the current session's directory. `create_session` creates a root session in that directory, attaches it to the relay heartbeat set, and returns the new session id only after the heartbeat completes so the mobile client can navigate immediately. Failures are sanitized; the command is not auto-retried and the user may retry manually after a transient relay failure.
|
||||
@@ -7,8 +7,3 @@ end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
max_line_length = 80
|
||||
|
||||
[*.{kt,kts}]
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Check if nix command is available before trying to use flake
|
||||
if command -v nix >/dev/null 2>&1; then
|
||||
use flake
|
||||
fi
|
||||
@@ -1,41 +1,2 @@
|
||||
# Enforce LF line endings for all text files
|
||||
* text=auto eol=lf
|
||||
|
||||
# Git LFS tracking for binary/media files
|
||||
*.gif filter=lfs diff=lfs merge=lfs -text
|
||||
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# Visual regression baseline snapshots
|
||||
packages/kilo-ui/tests/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
packages/kilo-vscode/tests/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
packages/kilo-docs/public/img/screenshot-tests/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# Hide most localization files by default in GitHub PR diffs. Show English as the source
|
||||
# language and German so reviewers can catch untranslated copy-pasted English strings.
|
||||
**/i18n/*.ts linguist-generated=true
|
||||
**/i18n/**/*.ts linguist-generated=true
|
||||
**/i18n/en.ts linguist-generated=false
|
||||
**/i18n/en*.ts linguist-generated=false
|
||||
**/i18n/**/en.ts linguist-generated=false
|
||||
**/i18n/**/en*.ts linguist-generated=false
|
||||
**/i18n/de.ts linguist-generated=false
|
||||
**/i18n/de*.ts linguist-generated=false
|
||||
**/i18n/**/de.ts linguist-generated=false
|
||||
**/i18n/**/de*.ts linguist-generated=false
|
||||
**/i18n/package-nls-en.ts linguist-generated=false
|
||||
**/i18n/package-nls-de.ts linguist-generated=false
|
||||
**/i18n/index.ts linguist-generated=false
|
||||
**/i18n/**/index.ts linguist-generated=false
|
||||
**/i18n/parity.test.ts linguist-generated=false
|
||||
**/i18n/**/parity.test.ts linguist-generated=false
|
||||
packages/kilo-i18n/src/*.ts linguist-generated=true
|
||||
packages/kilo-i18n/src/en.ts linguist-generated=false
|
||||
packages/kilo-i18n/src/de.ts linguist-generated=false
|
||||
|
||||
# Auto-generated CLI reference docs
|
||||
packages/kilo-docs/markdoc/partials/cli-commands-table.md linguist-generated=true
|
||||
packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md linguist-generated=true
|
||||
|
||||
# Upstream generated migration artifacts
|
||||
packages/core/migration/**/snapshot.json linguist-generated
|
||||
packages/core/src/database/migration.gen.ts linguist-generated
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
name: Bug report
|
||||
description: Report an issue that should be fixed
|
||||
labels: ["bug"]
|
||||
description: Report an issue that should be fixed (avoid pasting giant AI generated summaries or your issue may be closed/ignored)
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
@@ -20,10 +19,10 @@ body:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: kilo-version
|
||||
id: opencode-version
|
||||
attributes:
|
||||
label: Kilo version
|
||||
description: What version of Kilo are you using?
|
||||
label: OpenCode version
|
||||
description: What version of OpenCode are you using?
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💬 Join our Discord
|
||||
url: https://kilo.ai/discord
|
||||
- name: 💬 Discord Community
|
||||
url: https://discord.gg/opencode
|
||||
about: For quick questions or real-time discussion. Note that issues are searchable and help others with the same question.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
name: 🚀 Feature Request
|
||||
description: Suggest an idea, feature, or enhancement
|
||||
labels: [discussion]
|
||||
title: "[FEATURE]:"
|
||||
|
||||
body:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
name: Question
|
||||
description: Ask a question
|
||||
labels: ["question"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: question
|
||||
|
||||
@@ -33,10 +33,7 @@ runs:
|
||||
shell: bash
|
||||
run: echo "dir=$(bun pm cache)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Restoring and extracting the ~1 GB cache took 2m23s on Windows, while a # kilocode_change
|
||||
# fresh install took 1m27s. Keep Windows off this cache until that reverses. # kilocode_change
|
||||
- name: Restore Bun dependencies
|
||||
if: runner.os != 'Windows' # kilocode_change
|
||||
id: bun-cache
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
@@ -45,66 +42,24 @@ runs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
# kilocode_change start
|
||||
- name: Setup Node for native dependency builds
|
||||
if: runner.os != 'Windows'
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Configure node-gyp Node headers
|
||||
if: runner.os != 'Windows'
|
||||
id: node-gyp
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const fs = require("node:fs")
|
||||
const path = require("node:path")
|
||||
|
||||
const exe = path.dirname(fs.realpathSync(process.execPath))
|
||||
const dirs = [exe, path.dirname(exe)]
|
||||
const root = dirs.find((dir) => fs.existsSync(path.join(dir, "include", "node", "node.h")))
|
||||
if (!root) {
|
||||
throw new Error(`Node headers not found under ${dirs.join(", ")}`)
|
||||
}
|
||||
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `nodedir=${root}\n`)
|
||||
NODE
|
||||
shell: bash
|
||||
# kilocode_change end
|
||||
|
||||
- name: Install setuptools for distutils compatibility
|
||||
run: python3 -m pip install setuptools || pip install setuptools || true
|
||||
shell: bash
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
# kilocode_change start
|
||||
if [ "$RUNNER_OS" != "Windows" ]; then
|
||||
export npm_config_nodedir="${{ steps.node-gyp.outputs.nodedir }}"
|
||||
fi
|
||||
# kilocode_change end
|
||||
|
||||
# Workaround for patched peer variants
|
||||
# e.g. ./patches/ for standard-openapi
|
||||
# https://github.com/oven-sh/bun/issues/28147 # kilocode_change
|
||||
# https://github.com/oven-sh/bun/issues/28147
|
||||
if [ "$RUNNER_OS" = "Windows" ]; then
|
||||
# kilocode_change start
|
||||
if ! bun install --frozen-lockfile --linker hoisted ${{ inputs.install-flags }}; then
|
||||
echo "::warning::Bun install failed on Windows; retrying with conservative extraction"
|
||||
sleep 5
|
||||
BUN_FEATURE_FLAG_DISABLE_STREAMING_INSTALL=1 \
|
||||
bun install --frozen-lockfile --linker hoisted --network-concurrency 16 ${{ inputs.install-flags }}
|
||||
fi
|
||||
# kilocode_change end
|
||||
bun install --linker hoisted ${{ inputs.install-flags }}
|
||||
else
|
||||
bun install --frozen-lockfile ${{ inputs.install-flags }} # kilocode_change
|
||||
bun install ${{ inputs.install-flags }}
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
# Do not upload a Windows cache that Windows jobs intentionally never restore. # kilocode_change
|
||||
- name: Save Bun dependencies
|
||||
if: runner.os != 'Windows' && steps.bun-cache.outputs.cache-hit != 'true' && github.event_name != 'pull_request' && github.event_name != 'pull_request_target' # kilocode_change
|
||||
if: steps.bun-cache.outputs.cache-hit != 'true' && github.event_name != 'pull_request' && github.event_name != 'pull_request_target'
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ steps.cache.outputs.dir }}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
name: "Setup Git Committer"
|
||||
description: "Create app token and configure git user"
|
||||
inputs:
|
||||
# kilocode_change start
|
||||
kilo-maintainer-app-id:
|
||||
description: "Kilo Maintainer GitHub App ID"
|
||||
opencode-app-id:
|
||||
description: "OpenCode GitHub App ID"
|
||||
required: true
|
||||
kilo-maintainer-app-secret:
|
||||
description: "Kilo Maintainer GitHub App private key"
|
||||
opencode-app-secret:
|
||||
description: "OpenCode GitHub App private key"
|
||||
required: true
|
||||
# kilocode_change end
|
||||
outputs:
|
||||
token:
|
||||
description: "GitHub App token"
|
||||
@@ -23,10 +21,8 @@ runs:
|
||||
id: apptoken
|
||||
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2
|
||||
with:
|
||||
# kilocode_change start
|
||||
app-id: ${{ inputs['kilo-maintainer-app-id'] }}
|
||||
private-key: ${{ inputs['kilo-maintainer-app-secret'] }}
|
||||
# kilocode_change end
|
||||
app-id: ${{ inputs.opencode-app-id }}
|
||||
private-key: ${{ inputs.opencode-app-secret }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Configure git user
|
||||
@@ -39,15 +35,6 @@ runs:
|
||||
- name: Clear checkout auth
|
||||
run: |
|
||||
git config --local --unset-all http.https://github.com/.extraheader || true
|
||||
# kilocode_change start
|
||||
keys="$(git config --local --name-only --get-regexp '^includeIf\.gitdir:' || true)"
|
||||
for key in $keys; do
|
||||
path="$(git config --local --get "$key" || true)"
|
||||
case "$path" in
|
||||
*git-credentials-*.config) git config --local --unset-all "$key" || true ;;
|
||||
esac
|
||||
done
|
||||
# kilocode_change end
|
||||
shell: bash
|
||||
|
||||
- name: Configure git remote
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: "Setup Kilo"
|
||||
description: "Install Kilo CLI globally"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install Kilo CLI
|
||||
run: curl -fsSL https://kilo.ai/cli/install | bash
|
||||
shell: bash
|
||||
|
||||
- name: Add Kilo to PATH
|
||||
run: echo "$HOME/.kilo/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
@@ -1,21 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: "Setup Linux Sandbox"
|
||||
description: "Build the Linux bubblewrap helper"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup Zig
|
||||
run: |
|
||||
curl --fail --location --retry 3 \
|
||||
https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz \
|
||||
--output "$RUNNER_TEMP/zig.tar.xz"
|
||||
echo "473ec26806133cf4d1918caf1a410f8403a13d979726a9045b421b685031a982 $RUNNER_TEMP/zig.tar.xz" | sha256sum --check --status
|
||||
tar -xJf "$RUNNER_TEMP/zig.tar.xz" -C "$RUNNER_TEMP"
|
||||
echo "$RUNNER_TEMP/zig-linux-x86_64-0.14.0" >> "$GITHUB_PATH"
|
||||
shell: bash
|
||||
|
||||
- name: Build bubblewrap helper
|
||||
run: |
|
||||
bun packages/opencode/script/kilocode/bubblewrap.ts --arch x64 --output "$RUNNER_TEMP/bwrap"
|
||||
echo "KILO_BWRAP_PATH=$RUNNER_TEMP/bwrap" >> "$GITHUB_ENV"
|
||||
shell: bash
|
||||
@@ -1,12 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: Critical-only CodeQL config
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
kind:
|
||||
- problem
|
||||
- path-problem
|
||||
- alert
|
||||
- path-alert
|
||||
tags contain: security
|
||||
security-severity: /^(9(\.[0-9])?|10(\.0)?)$/
|
||||
@@ -1,74 +1,29 @@
|
||||
## Issue
|
||||
### Issue for this PR
|
||||
|
||||
<!-- Reference an existing issue with `Fixes #123`, `Closes #123`, or equivalent linked issue wording. -->
|
||||
Closes #
|
||||
|
||||
Fixes #
|
||||
### Type of change
|
||||
|
||||
## Context
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Refactor / code improvement
|
||||
- [ ] Documentation
|
||||
|
||||
<!-- Brief description of WHAT you’re doing and WHY. -->
|
||||
### What does this PR do?
|
||||
|
||||
## Implementation
|
||||
Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.
|
||||
|
||||
<!--
|
||||
**If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!**
|
||||
|
||||
Some description of HOW you achieved it. Perhaps give a high level description of the program flow. Did you need to refactor something? What tradeoffs did you take? Are there things in here which you’d particularly like people to pay close attention to? Keep this focused on context reviewers cannot infer from the diff; skip file-by-file summaries, placeholders, and other filler.
|
||||
### How did you verify your code works?
|
||||
|
||||
-->
|
||||
### Screenshots / recordings
|
||||
|
||||
## Screenshots / Video
|
||||
_If this is a UI change, please include a screenshot or recording._
|
||||
|
||||
<!-- Required for visual changes. Include the relevant before/after or resulting state. -->
|
||||
### Checklist
|
||||
|
||||
| before | after |
|
||||
|---|---|
|
||||
| | |
|
||||
- [ ] I have tested my changes locally
|
||||
- [ ] I have not included unrelated changes in this PR
|
||||
|
||||
## How to Test
|
||||
|
||||
### Manual/local verification
|
||||
|
||||
<!--
|
||||
Describe local CLI, extension, or docs verification. Example:
|
||||
- Opened the updated settings page and confirmed the new copy appears
|
||||
-->
|
||||
|
||||
-
|
||||
|
||||
### Reviewer test steps
|
||||
|
||||
<!--
|
||||
Provide steps reviewers can retrace. Example:
|
||||
1. Sign in with a user with tracks
|
||||
2. Activate `show_awesome_cat_gifs` feature by adding `?feature.show_awesome_cat_gifs=1` to your URL
|
||||
3. Confirm you see a GIF with cats dancing
|
||||
-->
|
||||
|
||||
-
|
||||
|
||||
### Blocked checks and substitute verification
|
||||
|
||||
<!--
|
||||
If a relevant command could not be completed, name the command, explain the blocker or failure, and describe the substitute verification performed. Example:
|
||||
- `bun run test:integration` could not complete because the required API key is unavailable; substitute verification was testing the affected flow manually in the development extension
|
||||
-->
|
||||
|
||||
-
|
||||
|
||||
<!--
|
||||
Every PR marked ready for review must include testing evidence. A bare "Not tested" or "N/A" answer is not sufficient. For each piece of evidence make clear if the test was executed by the agent or by a human
|
||||
|
||||
Docs-only, config-only, and similar changes still need concrete evidence. For examples, see [Testing Evidence for Pull Requests](https://kilo.ai/docs/contributing/development-environment#testing-evidence-for-pull-requests). Draft PRs may be incomplete until marked ready for review.
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Issue linked above, or exception explained
|
||||
- [ ] Tests/verification described
|
||||
- [ ] Screenshots/video included for visual changes, or marked N/A
|
||||
- [ ] Changeset considered for user-facing changes
|
||||
- [ ] I personally reviewed the diff and can explain the changes, including any AI-assisted work.
|
||||
|
||||
## Get in Touch
|
||||
|
||||
<!-- We'd love to have a way to chat with you about your changes if necessary. If you're in the [Kilo Code Discord](https://kilo.ai/discord), please share your handle here. -->
|
||||
_If you do not follow this template your PR will be automatically rejected._
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
name: Auto-docs dry-run intake
|
||||
|
||||
# Trigger on merged feature PRs, or manually with a specific PR number.
|
||||
# IMPORTANT: This workflow intentionally does NOT check out PR code and does NOT
|
||||
# execute any code from the PR branch. It only reads PR metadata and forwards it
|
||||
# to a webhook. This is required for safe use of pull_request_target on a public repo.
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: "PR number to test"
|
||||
required: true
|
||||
|
||||
# Minimal permissions: read repo contents and PR metadata only.
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
# One run per PR at a time. Does not cancel in progress — let the current run
|
||||
# finish so we don't drop events when a PR is quickly closed/reopened/closed.
|
||||
concurrency:
|
||||
group: auto-docs-${{ github.event.pull_request.number || inputs.pr_number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
call-valtown:
|
||||
# kilocode_change start
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.repository == 'Kilo-Org/kilocode' &&
|
||||
github.event.pull_request.merged == true &&
|
||||
(
|
||||
startsWith(github.event.pull_request.title, 'feat:') ||
|
||||
startsWith(github.event.pull_request.title, 'feat(')
|
||||
)
|
||||
)
|
||||
# kilocode_change end
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Build webhook payload
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
REPO: ${{ github.repository }}
|
||||
PR_NUMBER_FROM_EVENT: ${{ github.event.pull_request.number }}
|
||||
PR_NUMBER_FROM_INPUT: ${{ inputs.pr_number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
PR_NUMBER="${PR_NUMBER_FROM_EVENT:-$PR_NUMBER_FROM_INPUT}"
|
||||
|
||||
if [ -z "$PR_NUMBER" ]; then
|
||||
echo "Missing PR number"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gh api "repos/$REPO/pulls/$PR_NUMBER" > pr.json
|
||||
|
||||
jq -n \
|
||||
--arg event_name "$EVENT_NAME" \
|
||||
--arg repo "$REPO" \
|
||||
--arg pr_number "$PR_NUMBER" \
|
||||
--slurpfile pr pr.json \
|
||||
'{
|
||||
event_name: $event_name,
|
||||
repo: $repo,
|
||||
pr_number: $pr_number,
|
||||
title: $pr[0].title,
|
||||
description: ($pr[0].body // ""),
|
||||
author: $pr[0].user.login,
|
||||
merged_at: ($pr[0].merged_at // ""),
|
||||
pr_url: $pr[0].html_url,
|
||||
base_branch: $pr[0].base.ref,
|
||||
head_branch: $pr[0].head.ref
|
||||
}' > payload.json
|
||||
|
||||
echo "Payload created:"
|
||||
jq '{
|
||||
event_name,
|
||||
repo,
|
||||
pr_number,
|
||||
title,
|
||||
author,
|
||||
merged_at,
|
||||
pr_url,
|
||||
base_branch,
|
||||
head_branch
|
||||
}' payload.json
|
||||
|
||||
- name: Send webhook to Val Town
|
||||
env:
|
||||
DOC_WEBHOOK_URL: ${{ secrets.DOC_WEBHOOK_URL }}
|
||||
DOC_WEBHOOK_SECRET: ${{ secrets.DOC_WEBHOOK_SECRET }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${DOC_WEBHOOK_URL:-}" ]; then
|
||||
echo "DOC_WEBHOOK_URL secret is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${DOC_WEBHOOK_SECRET:-}" ]; then
|
||||
echo "DOC_WEBHOOK_SECRET secret is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl --fail-with-body -sS -X POST "$DOC_WEBHOOK_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Docs-Webhook-Secret: $DOC_WEBHOOK_SECRET" \
|
||||
--data-binary @payload.json
|
||||
@@ -7,31 +7,31 @@ on:
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6 # kilocode_change
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
persist-credentials: false # kilocode_change
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup Git Committer
|
||||
id: committer
|
||||
id: setup-git-committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
# kilocode_change start
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
# kilocode_change end
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Install OpenCode
|
||||
run: bun i -g opencode-ai
|
||||
|
||||
- name: Sync beta branch
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
run: bun script/beta.ts
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: Check forbidden strings
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check forbidden strings
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Run check
|
||||
run: bun run script/check-forbidden-strings.ts
|
||||
@@ -1,27 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: Check Kilo generated artifacts
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".gitignore"
|
||||
- ".github/workflows/check-kilo-generated-artifacts.yml"
|
||||
- ".kilo/**"
|
||||
- ".kilocode/**"
|
||||
- "script/check-kilo-generated-artifacts.ts"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check Kilo generated artifacts
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Run check
|
||||
run: bun run script/check-kilo-generated-artifacts.ts
|
||||
@@ -1,24 +0,0 @@
|
||||
name: Check markdown table padding
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "**/*.md"
|
||||
- "script/check-md-table-padding.ts"
|
||||
- ".github/workflows/check-md-table-padding.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check markdown table padding
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Run check
|
||||
run: bun run script/check-md-table-padding.ts
|
||||
@@ -1,49 +0,0 @@
|
||||
name: Check shared upstream annotations
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/**"
|
||||
- "github/**"
|
||||
- "packages/extensions/**"
|
||||
- "packages/opencode/**"
|
||||
- "packages/script/**"
|
||||
- "packages/shared/**"
|
||||
- "packages/storybook/**"
|
||||
- "packages/ui/**"
|
||||
- "script/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-annotations:
|
||||
name: Check kilocode_change annotations
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Check kilocode_change annotations in shared upstream files
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
run: |
|
||||
if [ -n "$BASE_SHA" ]; then
|
||||
bun run script/check-opencode-annotations.ts --base "$BASE_SHA"
|
||||
else
|
||||
echo "No PR base SHA available (workflow_dispatch without PR context) — skipping."
|
||||
fi
|
||||
|
||||
# kilocode_change start
|
||||
- name: Check Effect Promise facade allowlist
|
||||
run: bun run script/check-opencode-promise-facades.ts
|
||||
|
||||
- name: Check model tool network boundary
|
||||
run: bun run script/check-model-tool-network.ts
|
||||
|
||||
- name: Check workflow allowlist
|
||||
run: bun run script/check-workflows.ts
|
||||
# kilocode_change end
|
||||
@@ -1,48 +0,0 @@
|
||||
name: check-org-member
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
username:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
kilo-maintainer-app-id:
|
||||
required: true
|
||||
kilo-maintainer-app-secret:
|
||||
required: true
|
||||
outputs:
|
||||
is-member:
|
||||
description: Whether the user is an org member or kilo-maintainer bot
|
||||
value: ${{ jobs.check.outputs['is-member'] }}
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404 # kilocode_change
|
||||
outputs:
|
||||
is-member: ${{ steps.check.outputs['is-member'] }}
|
||||
steps:
|
||||
- name: Create app token
|
||||
id: apptoken
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets['kilo-maintainer-app-id'] }}
|
||||
private-key: ${{ secrets['kilo-maintainer-app-secret'] }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Check if author is org member or kilo bot
|
||||
id: check
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.apptoken.outputs.token }}
|
||||
USERNAME: ${{ inputs.username }}
|
||||
run: |
|
||||
if [[ "$USERNAME" == "kilo-maintainer[bot]" || "$USERNAME" == "kiloconnect[bot]" || "$USERNAME" == "kiloconnect-lite[bot]" ]]; then
|
||||
echo "is-member=true" >> "$GITHUB_OUTPUT"
|
||||
echo "User is a kilo bot"
|
||||
elif gh api orgs/Kilo-Org/members/"$USERNAME" --silent 2>/dev/null; then
|
||||
echo "is-member=true" >> "$GITHUB_OUTPUT"
|
||||
echo "User is an org member"
|
||||
else
|
||||
echo "is-member=false" >> "$GITHUB_OUTPUT"
|
||||
echo "User is not an org member"
|
||||
fi
|
||||
@@ -1,69 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: "CodeQL Kotlin"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- "packages/kilo-jetbrains/**"
|
||||
- ".github/workflows/codeql-kotlin.yml"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- "packages/kilo-jetbrains/**"
|
||||
- ".github/workflows/codeql-kotlin.yml"
|
||||
schedule:
|
||||
- cron: "29 10 * * 5"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (java-kotlin)
|
||||
if: github.event_name == 'workflow_dispatch' || github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
packages: read
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
|
||||
- name: Free disk space for CodeQL
|
||||
shell: bash
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/boost
|
||||
docker system prune --all --force || true
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: java-kotlin
|
||||
build-mode: manual
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
|
||||
- name: Build Java/Kotlin
|
||||
shell: bash
|
||||
run: ./gradlew typecheck --rerun-tasks --no-build-cache
|
||||
working-directory: packages/kilo-jetbrains
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:java-kotlin"
|
||||
@@ -1,82 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
- cron: "29 10 * * 5"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
if: github.event_name == 'workflow_dispatch' || github.repository == 'Kilo-Org/kilocode'
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -3,7 +3,7 @@ name: containers
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main # kilocode_change
|
||||
- dev
|
||||
paths:
|
||||
- packages/containers/**
|
||||
- .github/workflows/containers.yml
|
||||
@@ -16,18 +16,12 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'workflow_dispatch' || github.repository == 'Kilo-Org/kilocode' # kilocode_change
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
env:
|
||||
REGISTRY: ghcr.io/${{ github.repository_owner }}
|
||||
TAG: "24.04"
|
||||
steps:
|
||||
# kilocode_change start
|
||||
- name: Set lowercase repository owner
|
||||
id: repo
|
||||
run: echo "owner_lc=${OWNER,,}" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
OWNER: ${{ github.repository_owner }}
|
||||
# kilocode_change end
|
||||
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
@@ -47,7 +41,5 @@ jobs:
|
||||
- name: Build and push containers
|
||||
run: bun ./packages/containers/script/build.ts --push
|
||||
env:
|
||||
# kilocode_change start
|
||||
REGISTRY: ghcr.io/${{ steps.repo.outputs.owner_lc }}
|
||||
TAG: "24.04"
|
||||
# kilocode_change end
|
||||
REGISTRY: ${{ env.REGISTRY }}
|
||||
TAG: ${{ env.TAG }}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
name: kilo
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
kilo:
|
||||
if: |
|
||||
(github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') &&
|
||||
(
|
||||
contains(github.event.comment.body, ' /kc') ||
|
||||
startsWith(github.event.comment.body, '/kc') ||
|
||||
contains(github.event.comment.body, ' /kilo') ||
|
||||
startsWith(github.event.comment.body, '/kilo')
|
||||
)
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Run opencode
|
||||
uses: Kilo-Org/kilocode/github@latest
|
||||
env:
|
||||
KILO_API_KEY: ${{ secrets.KILO_API_KEY }}
|
||||
KILO_ORG_ID: ${{ secrets.KILO_ORG_ID }}
|
||||
KILO_PERMISSION: '{"bash": "deny"}'
|
||||
with:
|
||||
model: kilo/kilo-auto/frontier
|
||||
@@ -1,46 +0,0 @@
|
||||
name: nix-desktop
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
- "packages/app/**"
|
||||
- "packages/desktop/**"
|
||||
- ".github/workflows/nix-desktop.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
- "packages/app/**"
|
||||
- "packages/desktop/**"
|
||||
- ".github/workflows/nix-desktop.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
nix-desktop:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- blacksmith-4vcpu-ubuntu-2404
|
||||
- blacksmith-4vcpu-ubuntu-2404-arm
|
||||
- macos-15-intel
|
||||
- macos-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Nix
|
||||
uses: nixbuild/nix-quick-install-action@v34
|
||||
|
||||
- name: Build desktop via flake
|
||||
run: |
|
||||
set -euo pipefail
|
||||
nix --version
|
||||
nix build .#desktop -L
|
||||
@@ -1,35 +0,0 @@
|
||||
name: "sync-zed-extension"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
zed:
|
||||
name: Release Zed Extension
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Get version tag
|
||||
id: get_tag
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "release" ]; then
|
||||
TAG="${{ github.event.release.tag_name }}"
|
||||
else
|
||||
TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1)
|
||||
fi
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "Using tag: ${TAG}"
|
||||
|
||||
- name: Sync Zed extension
|
||||
run: |
|
||||
./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }}
|
||||
env:
|
||||
ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }}
|
||||
ZED_PR_PAT: ${{ secrets.ZED_PR_PAT }}
|
||||
@@ -1,30 +0,0 @@
|
||||
name: docs-build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "packages/kilo-docs/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "packages/kilo-docs/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build docs site
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404 # kilocode_change
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6 # kilocode_change
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Build site
|
||||
run: bun run --filter @kilocode/kilo-docs build
|
||||
env:
|
||||
NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.POSTHOG_API_KEY }}
|
||||
|
||||
- name: Run tests
|
||||
run: bun run --filter @kilocode/kilo-docs test
|
||||
@@ -1,29 +0,0 @@
|
||||
name: Check Links
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
paths:
|
||||
- "packages/kilo-docs/**"
|
||||
- ".github/workflows/docs-check-links.yml"
|
||||
schedule:
|
||||
# Run daily at 9am UTC
|
||||
- cron: "0 9 * * *"
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
jobs:
|
||||
link-checker:
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
|
||||
- name: Link Checker
|
||||
uses: Kilo-Org/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411
|
||||
with:
|
||||
args: "**/*.md"
|
||||
workingDirectory: packages/kilo-docs
|
||||
fail: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
+5
-5
@@ -20,13 +20,13 @@ jobs:
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install opencode
|
||||
run: curl -fsSL https://kilo.ai/install | bash
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Check duplicates and compliance
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
KILO_PERMISSION: |
|
||||
OPENCODE_PERMISSION: |
|
||||
{
|
||||
"bash": {
|
||||
"*": "deny",
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
Remember: post at most ONE comment combining all findings. If everything is fine, post nothing."
|
||||
|
||||
recheck-compliance:
|
||||
if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance') # kilocode_change
|
||||
if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance')
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -132,13 +132,13 @@ jobs:
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install opencode
|
||||
run: curl -fsSL https://kilo.ai/install | bash
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Recheck compliance
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
KILO_PERMISSION: |
|
||||
OPENCODE_PERMISSION: |
|
||||
{
|
||||
"bash": {
|
||||
"*": "deny",
|
||||
@@ -7,30 +7,23 @@ on:
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6 # kilocode_change
|
||||
# kilocode_change start
|
||||
with:
|
||||
persist-credentials: false
|
||||
# kilocode_change end
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup Git Committer
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
# kilocode_change start
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
# kilocode_change end
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Generate
|
||||
run: ./script/generate.ts
|
||||
|
||||
@@ -1,366 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: kilo-auto-close
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dryRun:
|
||||
description: "Log actions without closing items"
|
||||
type: boolean
|
||||
default: true
|
||||
schedule:
|
||||
- cron: "0 6 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
close:
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Close inactive PRs and issues
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
KILO_AUTO_CLOSE_ENABLED: ${{ vars.KILO_AUTO_CLOSE_ENABLED }}
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const PR_DAYS_INACTIVE = 30
|
||||
const ISSUE_DAYS_INACTIVE = 60
|
||||
const MAX_RETRIES = 3
|
||||
|
||||
// Adaptive delay: fast for small batches, slower for large to respect
|
||||
// GitHub's 80 content-generating requests/minute limit
|
||||
const SMALL_BATCH_THRESHOLD = 10
|
||||
const SMALL_BATCH_DELAY_MS = 1000 // 1s for daily operations (≤10 items)
|
||||
const LARGE_BATCH_DELAY_MS = 2000 // 2s for backlog (>10 items) = ~30 ops/min, well under 80 limit
|
||||
|
||||
const startTime = Date.now()
|
||||
const prCutoff = new Date(Date.now() - PR_DAYS_INACTIVE * 24 * 60 * 60 * 1000)
|
||||
const issueCutoff = new Date(Date.now() - ISSUE_DAYS_INACTIVE * 24 * 60 * 60 * 1000)
|
||||
const { owner, repo } = context.repo
|
||||
const dryRunInput = context.payload.inputs?.dryRun
|
||||
const enabled = process.env.KILO_AUTO_CLOSE_ENABLED === "true"
|
||||
const dryRun = dryRunInput === undefined
|
||||
? !enabled
|
||||
: dryRunInput !== "false"
|
||||
|
||||
core.info(`Dry run mode: ${dryRun}`)
|
||||
core.info(`PR cutoff date: ${prCutoff.toISOString()}`)
|
||||
core.info(`Issue cutoff date: ${issueCutoff.toISOString()}`)
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
async function withRetry(fn, description = 'API call') {
|
||||
let lastError
|
||||
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const result = await fn()
|
||||
return result
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
const isRateLimited = error.status === 403 &&
|
||||
(error.message?.includes('rate limit') || error.message?.includes('secondary'))
|
||||
|
||||
if (!isRateLimited) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (attempt === MAX_RETRIES - 1) {
|
||||
break
|
||||
}
|
||||
|
||||
// Parse retry-after header, default to 60 seconds
|
||||
const retryAfter = error.response?.headers?.['retry-after']
|
||||
? parseInt(error.response.headers['retry-after'])
|
||||
: 60
|
||||
|
||||
// Exponential backoff: retryAfter * 2^attempt
|
||||
const backoffMs = retryAfter * 1000 * Math.pow(2, attempt)
|
||||
|
||||
core.warning(`${description}: Rate limited (attempt ${attempt + 1}/${MAX_RETRIES}). Waiting ${backoffMs / 1000}s before retry...`)
|
||||
|
||||
await sleep(backoffMs)
|
||||
}
|
||||
}
|
||||
core.error(`${description}: Max retries (${MAX_RETRIES}) exceeded`)
|
||||
throw lastError
|
||||
}
|
||||
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!, $cursor: String) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequests(first: 100, states: OPEN, after: $cursor) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
number
|
||||
title
|
||||
author {
|
||||
login
|
||||
}
|
||||
createdAt
|
||||
commits(last: 1) {
|
||||
nodes {
|
||||
commit {
|
||||
committedDate
|
||||
}
|
||||
}
|
||||
}
|
||||
comments(last: 1) {
|
||||
nodes {
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
reviews(last: 1) {
|
||||
nodes {
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
reviewThreads(last: 100) {
|
||||
nodes {
|
||||
comments(last: 1) {
|
||||
nodes {
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const allPrs = []
|
||||
let cursor = null
|
||||
let hasNextPage = true
|
||||
let pageCount = 0
|
||||
|
||||
while (hasNextPage) {
|
||||
pageCount++
|
||||
core.info(`Fetching page ${pageCount} of open PRs...`)
|
||||
|
||||
const result = await withRetry(
|
||||
() => github.graphql(query, { owner, repo, cursor }),
|
||||
`GraphQL page ${pageCount}`
|
||||
)
|
||||
|
||||
allPrs.push(...result.repository.pullRequests.nodes)
|
||||
hasNextPage = result.repository.pullRequests.pageInfo.hasNextPage
|
||||
cursor = result.repository.pullRequests.pageInfo.endCursor
|
||||
|
||||
core.info(`Page ${pageCount}: fetched ${result.repository.pullRequests.nodes.length} PRs (total: ${allPrs.length})`)
|
||||
|
||||
// Delay between pagination requests (use small batch delay for reads)
|
||||
if (hasNextPage) {
|
||||
await sleep(SMALL_BATCH_DELAY_MS)
|
||||
}
|
||||
}
|
||||
|
||||
core.info(`Found ${allPrs.length} open pull requests`)
|
||||
|
||||
function entry(source, date) {
|
||||
if (!date) return null
|
||||
|
||||
return { source, date: new Date(date) }
|
||||
}
|
||||
|
||||
function latest(items) {
|
||||
return items.filter(Boolean).sort((a, b) => b.date.getTime() - a.date.getTime())[0]
|
||||
}
|
||||
|
||||
const stalePrs = allPrs.flatMap((pr) => {
|
||||
const activity = latest([
|
||||
entry("created", pr.createdAt),
|
||||
entry("commit", pr.commits.nodes[0]?.commit.committedDate),
|
||||
entry("comment", pr.comments.nodes[0]?.createdAt),
|
||||
entry("review", pr.reviews.nodes[0]?.createdAt),
|
||||
...pr.reviewThreads.nodes.map((t) => entry("review comment", t.comments.nodes[0]?.createdAt)),
|
||||
])
|
||||
const text = activity
|
||||
? `${activity.date.toISOString()} via ${activity.source}`
|
||||
: "unknown"
|
||||
|
||||
if (!activity || activity.date > prCutoff) {
|
||||
core.info(`PR #${pr.number} is fresh (last activity: ${text})`)
|
||||
return []
|
||||
}
|
||||
|
||||
core.info(`PR #${pr.number} is STALE (last activity: ${text})`)
|
||||
return [{ ...pr, activity }]
|
||||
})
|
||||
|
||||
core.info(`Found ${stalePrs.length} stale pull requests`)
|
||||
|
||||
const prDelayMs = stalePrs.length > SMALL_BATCH_THRESHOLD
|
||||
? LARGE_BATCH_DELAY_MS
|
||||
: SMALL_BATCH_DELAY_MS
|
||||
|
||||
core.info(`Using ${prDelayMs}ms delay between PR operations (${stalePrs.length > SMALL_BATCH_THRESHOLD ? 'large' : 'small'} batch mode)`)
|
||||
|
||||
let closedPrCount = 0
|
||||
let skippedPrCount = 0
|
||||
|
||||
for (const pr of stalePrs) {
|
||||
const issue_number = pr.number
|
||||
const closeComment = `To stay organized pull requests are automatically closed after ${PR_DAYS_INACTIVE} days of inactivity. If the pull request is still relevant please reopen it or create a fresh new one.`
|
||||
|
||||
if (dryRun) {
|
||||
core.info(`[dry-run] Would close PR #${issue_number} from ${pr.author?.login || 'unknown'} (last activity: ${pr.activity.date.toISOString()} via ${pr.activity.source}): ${pr.title}`)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
// Add comment
|
||||
await withRetry(
|
||||
() => github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
body: closeComment,
|
||||
}),
|
||||
`Comment on PR #${issue_number}`
|
||||
)
|
||||
|
||||
// Close PR
|
||||
await withRetry(
|
||||
() => github.rest.pulls.update({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: issue_number,
|
||||
state: "closed",
|
||||
}),
|
||||
`Close PR #${issue_number}`
|
||||
)
|
||||
|
||||
closedPrCount++
|
||||
core.info(`Closed PR #${issue_number} from ${pr.author?.login || 'unknown'} (last activity: ${pr.activity.date.toISOString()} via ${pr.activity.source}): ${pr.title}`)
|
||||
|
||||
// Delay before processing next PR
|
||||
await sleep(prDelayMs)
|
||||
} catch (error) {
|
||||
skippedPrCount++
|
||||
core.error(`Failed to close PR #${issue_number}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
let page = 1
|
||||
let stop = false
|
||||
const staleIssues = []
|
||||
|
||||
while (!stop) {
|
||||
core.info(`Fetching page ${page} of open issues...`)
|
||||
|
||||
const result = await withRetry(
|
||||
() => github.rest.issues.listForRepo({
|
||||
owner,
|
||||
repo,
|
||||
state: "open",
|
||||
sort: "updated",
|
||||
direction: "asc",
|
||||
per_page: 100,
|
||||
page,
|
||||
}),
|
||||
`Issues page ${page}`
|
||||
)
|
||||
|
||||
if (!result.data.length) {
|
||||
break
|
||||
}
|
||||
|
||||
core.info(`Page ${page}: fetched ${result.data.length} issues`)
|
||||
|
||||
for (const issue of result.data) {
|
||||
const updated = new Date(issue.updated_at)
|
||||
|
||||
if (updated >= issueCutoff) {
|
||||
core.info(`Found fresh issue/PR #${issue.number} (${updated.toISOString()}), stopping issue scan`)
|
||||
stop = true
|
||||
break
|
||||
}
|
||||
|
||||
if (issue.pull_request) {
|
||||
core.info(`Skipping PR #${issue.number} in issue scan`)
|
||||
continue
|
||||
}
|
||||
|
||||
staleIssues.push({ ...issue, activity: updated })
|
||||
}
|
||||
|
||||
if (!stop) {
|
||||
page++
|
||||
await sleep(SMALL_BATCH_DELAY_MS)
|
||||
}
|
||||
}
|
||||
|
||||
core.info(`Found ${staleIssues.length} stale issues`)
|
||||
|
||||
const issueDelayMs = staleIssues.length > SMALL_BATCH_THRESHOLD
|
||||
? LARGE_BATCH_DELAY_MS
|
||||
: SMALL_BATCH_DELAY_MS
|
||||
|
||||
core.info(`Using ${issueDelayMs}ms delay between issue operations (${staleIssues.length > SMALL_BATCH_THRESHOLD ? 'large' : 'small'} batch mode)`)
|
||||
|
||||
let closedIssueCount = 0
|
||||
let skippedIssueCount = 0
|
||||
|
||||
for (const issue of staleIssues) {
|
||||
const closeComment = `To stay organized issues are automatically closed after ${ISSUE_DAYS_INACTIVE} days of no activity. If the issue is still relevant please reopen it or create a fresh new one.`
|
||||
|
||||
if (dryRun) {
|
||||
core.info(`[dry-run] Would close issue #${issue.number} (last activity: ${issue.activity.toISOString()} via updated): ${issue.title}`)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
await withRetry(
|
||||
() => github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue.number,
|
||||
body: closeComment,
|
||||
}),
|
||||
`Comment on issue #${issue.number}`
|
||||
)
|
||||
|
||||
await withRetry(
|
||||
() => github.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue.number,
|
||||
state: "closed",
|
||||
state_reason: "not_planned",
|
||||
}),
|
||||
`Close issue #${issue.number}`
|
||||
)
|
||||
|
||||
closedIssueCount++
|
||||
core.info(`Closed issue #${issue.number} (last activity: ${issue.activity.toISOString()} via updated): ${issue.title}`)
|
||||
|
||||
await sleep(issueDelayMs)
|
||||
} catch (error) {
|
||||
skippedIssueCount++
|
||||
core.error(`Failed to close issue #${issue.number}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
const elapsed = Math.round((Date.now() - startTime) / 1000)
|
||||
core.info(`\n========== Summary ==========`)
|
||||
core.info(`Total open PRs found: ${allPrs.length}`)
|
||||
core.info(`Stale PRs identified: ${stalePrs.length}`)
|
||||
core.info(`PRs closed: ${closedPrCount}`)
|
||||
core.info(`PRs skipped (errors): ${skippedPrCount}`)
|
||||
core.info(`Stale issues identified: ${staleIssues.length}`)
|
||||
core.info(`Issues closed: ${closedIssueCount}`)
|
||||
core.info(`Issues skipped (errors): ${skippedIssueCount}`)
|
||||
core.info(`Elapsed time: ${elapsed}s`)
|
||||
core.info(`=============================`)
|
||||
@@ -16,7 +16,6 @@ permissions:
|
||||
|
||||
jobs:
|
||||
nix-eval:
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
@@ -39,7 +38,9 @@ jobs:
|
||||
nix flake show --all-systems
|
||||
|
||||
SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin"
|
||||
PACKAGES="kilo"
|
||||
PACKAGES="opencode"
|
||||
# TODO: move 'desktop' to PACKAGES when #11755 is fixed
|
||||
OPTIONAL_PACKAGES="desktop"
|
||||
|
||||
echo ""
|
||||
echo "=== Evaluating packages for all systems ==="
|
||||
@@ -59,6 +60,23 @@ jobs:
|
||||
done
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Evaluating optional packages ==="
|
||||
for system in $SYSTEMS; do
|
||||
echo ""
|
||||
echo "--- $system ---"
|
||||
for pkg in $OPTIONAL_PACKAGES; do
|
||||
printf " %s: " "$pkg"
|
||||
if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then
|
||||
echo "✓"
|
||||
else
|
||||
echo "✗"
|
||||
echo "::warning::Evaluation failed for packages.$system.$pkg"
|
||||
echo "$output"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Evaluating devShells for all systems ==="
|
||||
for system in $SYSTEMS; do
|
||||
|
||||
@@ -6,7 +6,7 @@ permissions:
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [dev, beta]
|
||||
paths:
|
||||
- "bun.lock"
|
||||
- "package.json"
|
||||
@@ -25,7 +25,6 @@ jobs:
|
||||
# Native runners required: bun install cross-compilation flags (--os/--cpu)
|
||||
# do not produce byte-identical node_modules as native installs.
|
||||
compute-hash:
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -83,7 +82,7 @@ jobs:
|
||||
echo "Computed hash for ${SYSTEM}: $HASH"
|
||||
|
||||
- name: Upload hash
|
||||
uses: actions/upload-artifact@v7 # kilocode_change
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: hash-${{ matrix.system }}
|
||||
path: hash.txt
|
||||
@@ -91,32 +90,29 @@ jobs:
|
||||
|
||||
update-hashes:
|
||||
needs: compute-hash
|
||||
if: github.repository == 'Kilo-Org/kilocode' && github.event_name != 'pull_request'
|
||||
if: github.event_name != 'pull_request'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6 # kilocode_change
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref_name }}
|
||||
|
||||
- name: Setup Git Committer
|
||||
id: committer
|
||||
- name: Setup git committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
# kilocode_change start
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
# kilocode_change end
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Pull latest changes
|
||||
run: |
|
||||
git pull --rebase --autostash origin "$GITHUB_REF_NAME"
|
||||
|
||||
- name: Download hash artifacts
|
||||
uses: actions/download-artifact@v8 # kilocode_change
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
path: hashes
|
||||
pattern: hash-*
|
||||
|
||||
+1
-4
@@ -6,10 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
# kilocode_change start - disabled for kilo-cli fork (OpenCode Discord notifications)
|
||||
if: false
|
||||
# kilocode_change end
|
||||
runs-on: depot-ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Send nicely-formatted embed to Discord
|
||||
uses: SethCohen/github-releases-to-discord@24d166886aee4646d448c8a389ff9e1ebcab3682 # v1.20.0
|
||||
+1
-1
@@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
- name: Install opencode
|
||||
if: steps.team-check.outputs.is_team != 'true'
|
||||
run: curl -fsSL https://kilo.ai/install | bash
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Build prompt
|
||||
if: steps.team-check.outputs.is_team != 'true'
|
||||
@@ -1,64 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: prepare-jetbrains-release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
kind:
|
||||
description: "Release kind"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- rc
|
||||
- stable
|
||||
version:
|
||||
description: "Version, e.g. 7.3.13-rc.1 or 7.3.13"
|
||||
required: true
|
||||
type: string
|
||||
from_tag:
|
||||
description: "Optional previous tag for changelog range"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: prepare-jetbrains-release-${{ inputs.version }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup Git Committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
|
||||
- name: Create release tag and PR
|
||||
run: |
|
||||
args=(--kind "$KIND" --version "$VERSION")
|
||||
if [[ -n "$FROM_TAG" ]]; then
|
||||
args+=(--from-tag "$FROM_TAG")
|
||||
fi
|
||||
bun script/jetbrains-release-pr.ts "${args[@]}"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
KIND: ${{ inputs.kind }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
FROM_TAG: ${{ inputs.from_tag }}
|
||||
+4
-8
@@ -22,13 +22,9 @@ jobs:
|
||||
|
||||
- run: git fetch --force --tags
|
||||
|
||||
- name: Setup Git Committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
|
||||
- name: Publish
|
||||
run: ./script/publish
|
||||
run: |
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
./script/publish
|
||||
working-directory: ./github
|
||||
@@ -1,208 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: publish-jetbrains
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr:
|
||||
description: Merged JetBrains release PR number to publish
|
||||
required: true
|
||||
type: string
|
||||
merge_commit:
|
||||
description: Merge commit SHA from the reviewed release PR
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: publish-jetbrains-pr-${{ github.event.pull_request.number || inputs.pr }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: >-
|
||||
github.repository == 'Kilo-Org/kilocode' &&
|
||||
(
|
||||
(
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.merged == true &&
|
||||
startsWith(github.event.pull_request.head.ref, 'jetbrains/release/') &&
|
||||
contains(github.event.pull_request.labels.*.name, 'jetbrains-release') &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
) ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
)
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout merged release PR
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha || inputs.merge_commit }}
|
||||
|
||||
- name: Setup Bun for validation
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Validate release PR and tag
|
||||
id: release
|
||||
run: bun script/jetbrains-release-validate.ts --pr "$PR_NUMBER"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr }}
|
||||
|
||||
- name: Save reviewed release metadata
|
||||
run: |
|
||||
cp packages/kilo-jetbrains/CHANGELOG.md "$RUNNER_TEMP/jetbrains-CHANGELOG.md"
|
||||
cp packages/kilo-jetbrains/gradle.properties "$RUNNER_TEMP/jetbrains-gradle.properties"
|
||||
|
||||
- name: Checkout release tag
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.release.outputs.tag }}
|
||||
|
||||
- name: Restore reviewed release metadata
|
||||
run: |
|
||||
cp "$RUNNER_TEMP/jetbrains-CHANGELOG.md" packages/kilo-jetbrains/CHANGELOG.md
|
||||
cp "$RUNNER_TEMP/jetbrains-gradle.properties" packages/kilo-jetbrains/gradle.properties
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Install build tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y patchelf zip
|
||||
curl --fail --location \
|
||||
https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz \
|
||||
--output "$RUNNER_TEMP/zig.tar.xz"
|
||||
echo "473ec26806133cf4d1918caf1a410f8403a13d979726a9045b421b685031a982 $RUNNER_TEMP/zig.tar.xz" | sha256sum --check --status
|
||||
tar -xJf "$RUNNER_TEMP/zig.tar.xz" -C "$RUNNER_TEMP"
|
||||
echo "$RUNNER_TEMP/zig-linux-x86_64-0.14.0" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Validate publishing secrets
|
||||
run: |
|
||||
missing=0
|
||||
for name in JETBRAINS_MARKETPLACE_TOKEN JETBRAINS_CERTIFICATE_CHAIN JETBRAINS_PRIVATE_KEY JETBRAINS_PRIVATE_KEY_PASSWORD; do
|
||||
if [[ -z "${!name}" ]]; then
|
||||
echo "Missing required secret: $name" >&2
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
exit "$missing"
|
||||
env:
|
||||
JETBRAINS_MARKETPLACE_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_TOKEN }}
|
||||
JETBRAINS_CERTIFICATE_CHAIN: ${{ secrets.JETBRAINS_CERTIFICATE_CHAIN }}
|
||||
JETBRAINS_PRIVATE_KEY: ${{ secrets.JETBRAINS_PRIVATE_KEY }}
|
||||
JETBRAINS_PRIVATE_KEY_PASSWORD: ${{ secrets.JETBRAINS_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
- name: Verify plugin
|
||||
working-directory: packages/kilo-jetbrains
|
||||
run: ./gradlew verifyPlugin -Pproduction=true -Pkilo.channel="$CHANNEL"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
CHANNEL: ${{ steps.release.outputs.marketplace_channel }}
|
||||
|
||||
- name: Render release notes
|
||||
working-directory: packages/kilo-jetbrains
|
||||
run: |
|
||||
if ! grep -Fq "## [$VERSION]" CHANGELOG.md; then
|
||||
echo "Missing packages/kilo-jetbrains/CHANGELOG.md entry for $VERSION. Review and merge a release PR before publishing." >&2
|
||||
exit 1
|
||||
fi
|
||||
./gradlew getChangelog --project-version "$VERSION" --no-header --no-empty-sections --output-file=build/release-notes.md
|
||||
env:
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
|
||||
- name: Publish to JetBrains Marketplace
|
||||
working-directory: packages/kilo-jetbrains
|
||||
run: ./gradlew publishPlugin -Pproduction=true -Pkilo.channel="$CHANNEL"
|
||||
env:
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
CHANNEL: ${{ steps.release.outputs.marketplace_channel }}
|
||||
JETBRAINS_MARKETPLACE_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_TOKEN }}
|
||||
JETBRAINS_CERTIFICATE_CHAIN: ${{ secrets.JETBRAINS_CERTIFICATE_CHAIN }}
|
||||
JETBRAINS_PRIVATE_KEY: ${{ secrets.JETBRAINS_PRIVATE_KEY }}
|
||||
JETBRAINS_PRIVATE_KEY_PASSWORD: ${{ secrets.JETBRAINS_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
- name: Resolve plugin archive
|
||||
id: archive
|
||||
run: |
|
||||
mapfile -t signed < <(compgen -G "packages/kilo-jetbrains/build/distributions/*-signed.zip")
|
||||
if [[ "${#signed[@]}" -eq 1 ]]; then
|
||||
echo "path=${signed[0]}" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${#signed[@]}" -gt 1 ]]; then
|
||||
echo "Expected exactly one signed JetBrains plugin ZIP, found ${#signed[@]}." >&2
|
||||
printf '%s\n' "${signed[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t files < <(compgen -G "packages/kilo-jetbrains/build/distributions/*.zip")
|
||||
if [[ "${#files[@]}" -ne 1 ]]; then
|
||||
echo "Expected exactly one JetBrains plugin ZIP, found ${#files[@]}." >&2
|
||||
printf '%s\n' "${files[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "path=${files[0]}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload to GitHub Release
|
||||
run: |
|
||||
tag="$TAG"
|
||||
title="JetBrains $VERSION"
|
||||
flags=(--title "$title" --notes-file "$NOTES")
|
||||
if [[ "$KIND" == "rc" ]]; then
|
||||
flags+=(--prerelease)
|
||||
fi
|
||||
if gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release upload "$tag" "$ARCHIVE" --clobber --repo "$GITHUB_REPOSITORY"
|
||||
gh release edit "$tag" "${flags[@]}" --repo "$GITHUB_REPOSITORY"
|
||||
exit 0
|
||||
fi
|
||||
gh release create "$tag" "$ARCHIVE" "${flags[@]}" --repo "$GITHUB_REPOSITORY"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
KIND: ${{ steps.release.outputs.kind }}
|
||||
ARCHIVE: ${{ steps.archive.outputs.path }}
|
||||
NOTES: packages/kilo-jetbrains/build/release-notes.md
|
||||
|
||||
- name: Upload workflow artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kilo-jetbrains-${{ steps.release.outputs.version }}
|
||||
path: packages/kilo-jetbrains/build/distributions/*.zip
|
||||
if-no-files-found: ignore
|
||||
+394
-353
@@ -1,13 +1,14 @@
|
||||
name: publish
|
||||
run-name: "${{ format('{0} {1}', inputs.pre_release && 'pre-release' || 'release', inputs.bump) }}"
|
||||
run-name: "${{ format('release {0}', inputs.bump) }}"
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches:
|
||||
# - ci
|
||||
# - dev
|
||||
# - beta
|
||||
# - snapshot-*
|
||||
push:
|
||||
branches:
|
||||
- ci
|
||||
- dev
|
||||
- beta
|
||||
- fix/npm-native-binary-install
|
||||
- snapshot-*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
bump:
|
||||
@@ -15,440 +16,467 @@ on:
|
||||
required: false
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
- minor
|
||||
- patch
|
||||
version:
|
||||
description: "Override version (optional)"
|
||||
required: false
|
||||
type: string
|
||||
# kilocode_change start
|
||||
pre_release:
|
||||
description: "Publish as pre-release (VS Code marketplace + npm rc channel)"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
# kilocode_change end
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inputs.bump }}
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
issues: write # kilocode_change - label automated JetBrains CLI pin bump PRs
|
||||
packages: write
|
||||
pull-requests: write # kilocode_change - create automated JetBrains CLI pin bump PRs
|
||||
|
||||
jobs:
|
||||
version:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
steps:
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
# kilocode_change start - install deps for version script workspace resolution
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Install Kilo
|
||||
- name: Install OpenCode
|
||||
if: inputs.bump || inputs.version
|
||||
run: bun i -g @kilocode/cli
|
||||
# kilocode_change end
|
||||
run: bun i -g opencode-ai
|
||||
|
||||
- id: version
|
||||
run: |
|
||||
./script/version.ts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
KILO_BUMP: ${{ inputs.bump }}
|
||||
KILO_VERSION: ${{ inputs.version }}
|
||||
KILO_PRE_RELEASE: ${{ inputs.pre_release }}
|
||||
KILO_API_KEY: ${{ secrets.KILO_API_KEY }}
|
||||
KILO_ORG_ID: ${{ secrets.KILO_ORG_ID }}
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||
OPENCODE_VERSION: ${{ inputs.version }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GH_REPO: ${{ (github.ref_name == 'beta' && 'anomalyco/opencode-beta') || github.repository }}
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
release: ${{ steps.version.outputs.release }}
|
||||
tag: ${{ steps.version.outputs.tag }}
|
||||
repo: ${{ steps.version.outputs.repo }}
|
||||
|
||||
build-cli:
|
||||
needs: version
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
steps:
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
with:
|
||||
fetch-tags: true
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
# kilocode_change start
|
||||
- name: Setup Zig for Linux sandbox helpers
|
||||
run: |
|
||||
curl --fail --location --retry 3 \
|
||||
https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz \
|
||||
--output "$RUNNER_TEMP/zig.tar.xz"
|
||||
echo "473ec26806133cf4d1918caf1a410f8403a13d979726a9045b421b685031a982 $RUNNER_TEMP/zig.tar.xz" | sha256sum --check --status
|
||||
tar -xJf "$RUNNER_TEMP/zig.tar.xz" -C "$RUNNER_TEMP"
|
||||
echo "$RUNNER_TEMP/zig-linux-x86_64-0.14.0" >> "$GITHUB_PATH"
|
||||
# kilocode_change end
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
./packages/opencode/script/build.ts
|
||||
./packages/opencode/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
|
||||
./packages/cli/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
|
||||
env:
|
||||
KILO_VERSION: ${{ needs.version.outputs.version }}
|
||||
KILO_RELEASE: ${{ needs.version.outputs.release }}
|
||||
KILO_PRE_RELEASE: ${{ inputs.pre_release }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
|
||||
# kilocode_change start - pack dist tree into a single zstd-compressed tar before upload.
|
||||
# download-artifact@v4's streaming unzip is CPU-bound and processes entries one by one;
|
||||
# collapsing ~480 files into one tar cuts build-vscode download from >30m to seconds.
|
||||
# zstd compresses the wasms (~4x) and leaves the already-compact binaries alone, for
|
||||
# ~1 GB artifact. upload-artifact@v7's `archive: false` skips the zip wrapper entirely
|
||||
# so the tarball travels as-is (download-artifact@v8 auto-detects via Content-Type).
|
||||
# With `archive: false` the `name:` input is ignored; the artifact is named after the
|
||||
# file (kilo-cli.tar.zst). zstd + GNU tar are preinstalled on both github-hosted and
|
||||
# blacksmith ubuntu-2404. See actions/upload-artifact#36, actions/toolkit#1533.
|
||||
- name: Pack CLI dist into tar.zst
|
||||
run: |
|
||||
find packages/opencode/dist -name '*.map' -delete
|
||||
tar --zstd -cf /tmp/kilo-cli.tar.zst -C packages/opencode/dist .
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
path: /tmp/kilo-cli.tar.zst
|
||||
archive: false
|
||||
# kilocode_change end
|
||||
name: opencode-cli
|
||||
path: |
|
||||
packages/opencode/dist/opencode-darwin*
|
||||
packages/opencode/dist/opencode-linux*
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: opencode-cli-windows
|
||||
path: packages/opencode/dist/opencode-windows*
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: opencode-preview-cli
|
||||
path: packages/cli/dist/cli-*
|
||||
|
||||
outputs:
|
||||
version: ${{ needs.version.outputs.version }}
|
||||
|
||||
# kilocode_change start - execute supported Unix CLI binaries before packaging VSIX artifacts
|
||||
validate-cli-unix:
|
||||
name: Validate CLI (${{ matrix.target }})
|
||||
needs: build-cli
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: darwin-arm64
|
||||
runner: macos-15
|
||||
uname: arm64
|
||||
package: "@kilocode/cli-darwin-arm64"
|
||||
mode: host
|
||||
- target: darwin-x64
|
||||
runner: macos-15-intel
|
||||
uname: x86_64
|
||||
package: "@kilocode/cli-darwin-x64"
|
||||
mode: host
|
||||
- target: linux-arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
uname: aarch64
|
||||
package: "@kilocode/cli-linux-arm64"
|
||||
mode: host
|
||||
- target: linux-x64
|
||||
runner: ubuntu-24.04
|
||||
uname: x86_64
|
||||
package: "@kilocode/cli-linux-x64"
|
||||
mode: host
|
||||
- target: alpine-arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
uname: aarch64
|
||||
package: "@kilocode/cli-linux-arm64-musl"
|
||||
mode: alpine
|
||||
platform: linux/arm64
|
||||
- target: alpine-x64
|
||||
runner: ubuntu-24.04
|
||||
uname: x86_64
|
||||
package: "@kilocode/cli-linux-x64-musl"
|
||||
mode: alpine
|
||||
platform: linux/amd64
|
||||
steps:
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: kilo-cli.tar.zst
|
||||
path: /tmp
|
||||
skip-decompress: true
|
||||
|
||||
- name: Unpack CLI dist
|
||||
run: |
|
||||
mkdir -p dist
|
||||
tar --zstd -xf /tmp/kilo-cli.tar.zst -C dist
|
||||
|
||||
- name: Run CLI smoke test
|
||||
run: |
|
||||
test "$(uname -m)" = "${{ matrix.uname }}"
|
||||
|
||||
smoke_host() {
|
||||
binary="$1"
|
||||
"$binary" --version
|
||||
helper="$(dirname "$binary")/bwrap"
|
||||
if [[ "${{ matrix.target }}" == linux-* ]]; then
|
||||
test -x "$helper"
|
||||
grep -q '^SPDX-License-Identifier: LGPL-2.0-or-later$' "$(dirname "$binary")/licenses/bubblewrap/NOTICE"
|
||||
"$helper" --version
|
||||
# The live user-namespace bootstrap depends on the runner's kernel/AppArmor policy
|
||||
# (GitHub-hosted Ubuntu 24.04 sets kernel.apparmor_restrict_unprivileged_userns=1), not on
|
||||
# the shipped artifact. The runtime probe degrades gracefully, so keep this check non-fatal.
|
||||
"$helper" --unshare-user --disable-userns --unshare-pid --die-with-parent --new-session \
|
||||
--ro-bind / / --dev /dev --proc /proc -- "$helper" --version \
|
||||
|| echo "unprivileged user namespaces unavailable on this runner; skipping live sandbox check"
|
||||
fi
|
||||
root="$(mktemp -d)"
|
||||
trap 'rm -rf "$root"' RETURN
|
||||
(
|
||||
unset KILO_MODELS_PATH KILO_MODELS_URL KILO_CONFIG KILO_CONFIG_DIR
|
||||
export XDG_DATA_HOME="$root/data"
|
||||
export XDG_CACHE_HOME="$root/cache"
|
||||
export XDG_CONFIG_HOME="$root/config"
|
||||
export XDG_STATE_HOME="$root/state"
|
||||
export KILO_DISABLE_MODELS_FETCH=1
|
||||
export KILO_DISABLE_PROJECT_CONFIG=1
|
||||
export KILO_CONFIG_CONTENT='{"enabled_providers":["anthropic"]}'
|
||||
export ANTHROPIC_API_KEY=dummy
|
||||
"$binary" --pure models anthropic | grep -q '^anthropic/'
|
||||
)
|
||||
}
|
||||
|
||||
if [ "${{ matrix.mode }}" = "host" ]; then
|
||||
smoke_host "./dist/${{ matrix.package }}/bin/kilo"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
docker run --rm \
|
||||
--platform "${{ matrix.platform }}" \
|
||||
-v "$PWD/dist:/dist:ro" \
|
||||
-e PACKAGE="${{ matrix.package }}" \
|
||||
alpine:3.22 \
|
||||
sh -c '
|
||||
set -eu
|
||||
# kilocode_change start - Bun musl binaries link against libstdc++ and libgcc_s
|
||||
# (GCC C++ runtime). Alpine does not ship these by default; they are available
|
||||
# as optional packages and must be installed for any Bun-compiled musl binary to run.
|
||||
apk add --no-cache libstdc++ libgcc
|
||||
# kilocode_change end
|
||||
binary="/dist/$PACKAGE/bin/kilo" # kilocode_change
|
||||
"$binary" --version # kilocode_change
|
||||
"/dist/$PACKAGE/bin/bwrap" --version # kilocode_change
|
||||
grep -q '\''^SPDX-License-Identifier: LGPL-2.0-or-later$'\'' "/dist/$PACKAGE/bin/licenses/bubblewrap/NOTICE" # kilocode_change
|
||||
root="$(mktemp -d)"
|
||||
trap '\''rm -rf "$root"'\'' EXIT
|
||||
unset KILO_MODELS_PATH KILO_MODELS_URL KILO_CONFIG KILO_CONFIG_DIR
|
||||
export XDG_DATA_HOME="$root/data"
|
||||
export XDG_CACHE_HOME="$root/cache"
|
||||
export XDG_CONFIG_HOME="$root/config"
|
||||
export XDG_STATE_HOME="$root/state"
|
||||
export KILO_DISABLE_MODELS_FETCH=1
|
||||
export KILO_DISABLE_PROJECT_CONFIG=1
|
||||
export KILO_CONFIG_CONTENT='\''{"enabled_providers":["anthropic"]}'\''
|
||||
export ANTHROPIC_API_KEY=dummy
|
||||
"$binary" --pure models anthropic | grep -q "^anthropic/"
|
||||
'
|
||||
|
||||
validate-cli-windows:
|
||||
name: Validate CLI (windows-${{ matrix.arch }})
|
||||
needs: build-cli
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 20 # kilocode_change
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: arm64
|
||||
runner: windows-11-arm
|
||||
package: "@kilocode/cli-windows-arm64"
|
||||
- arch: x64
|
||||
runner: windows-2025
|
||||
package: "@kilocode/cli-windows-x64"
|
||||
steps:
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: kilo-cli.tar.zst
|
||||
path: ${{ runner.temp }}
|
||||
skip-decompress: true
|
||||
|
||||
- name: Install zstd
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (-not (Get-Command zstd -ErrorAction SilentlyContinue)) {
|
||||
choco install zstandard -y --no-progress
|
||||
}
|
||||
|
||||
- name: Unpack CLI dist
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path dist | Out-Null
|
||||
zstd -d --stdout "$env:RUNNER_TEMP\kilo-cli.tar.zst" | tar -xf - -C dist
|
||||
|
||||
- name: Run CLI smoke test
|
||||
shell: pwsh
|
||||
run: |
|
||||
$package = "${{ matrix.package }}".Replace("/", "\")
|
||||
$binary = ".\dist\$package\bin\kilo.exe"
|
||||
& $binary --version
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
$root = Join-Path $env:RUNNER_TEMP ([guid]::NewGuid().ToString())
|
||||
New-Item -ItemType Directory -Force -Path $root | Out-Null
|
||||
try {
|
||||
foreach ($name in "KILO_MODELS_PATH", "KILO_MODELS_URL", "KILO_CONFIG", "KILO_CONFIG_DIR") {
|
||||
Remove-Item "Env:$name" -ErrorAction SilentlyContinue
|
||||
}
|
||||
$env:XDG_DATA_HOME = Join-Path $root "data"
|
||||
$env:XDG_CACHE_HOME = Join-Path $root "cache"
|
||||
$env:XDG_CONFIG_HOME = Join-Path $root "config"
|
||||
$env:XDG_STATE_HOME = Join-Path $root "state"
|
||||
$env:KILO_DISABLE_MODELS_FETCH = "1"
|
||||
$env:KILO_DISABLE_PROJECT_CONFIG = "1"
|
||||
$env:KILO_CONFIG_CONTENT = '{"enabled_providers":["anthropic"]}'
|
||||
$env:ANTHROPIC_API_KEY = "dummy"
|
||||
$output = & $binary --pure models anthropic
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
if (-not ($output -match "(?m)^anthropic/")) {
|
||||
throw "Compiled Windows binary did not list Anthropic models from the embedded snapshot"
|
||||
}
|
||||
} finally {
|
||||
Remove-Item -Recurse -Force $root -ErrorAction SilentlyContinue
|
||||
}
|
||||
# kilocode_change end
|
||||
build-vscode:
|
||||
sign-cli-windows:
|
||||
needs:
|
||||
- build-cli
|
||||
- validate-cli-unix
|
||||
- validate-cli-windows
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
- version
|
||||
runs-on: blacksmith-4vcpu-windows-2025
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
env:
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
|
||||
AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: opencode-cli-windows
|
||||
path: packages/opencode/dist
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Azure login
|
||||
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
client-id: ${{ env.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ env.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- uses: azure/artifact-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1.2.0
|
||||
with:
|
||||
endpoint: ${{ env.AZURE_TRUSTED_SIGNING_ENDPOINT }}
|
||||
signing-account-name: ${{ env.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
certificate-profile-name: ${{ env.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
|
||||
files: |
|
||||
${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64\bin\opencode.exe
|
||||
${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64\bin\opencode.exe
|
||||
${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline\bin\opencode.exe
|
||||
exclude-environment-credential: true
|
||||
exclude-workload-identity-credential: true
|
||||
exclude-managed-identity-credential: true
|
||||
exclude-shared-token-cache-credential: true
|
||||
exclude-visual-studio-credential: true
|
||||
exclude-visual-studio-code-credential: true
|
||||
exclude-azure-cli-credential: false
|
||||
exclude-azure-powershell-credential: true
|
||||
exclude-azure-developer-cli-credential: true
|
||||
exclude-interactive-browser-credential: true
|
||||
|
||||
- name: Verify Windows CLI signatures
|
||||
shell: pwsh
|
||||
run: |
|
||||
$files = @(
|
||||
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64\bin\opencode.exe",
|
||||
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64\bin\opencode.exe",
|
||||
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline\bin\opencode.exe"
|
||||
)
|
||||
|
||||
foreach ($file in $files) {
|
||||
$sig = Get-AuthenticodeSignature $file
|
||||
if ($sig.Status -ne "Valid") {
|
||||
throw "Invalid signature for ${file}: $($sig.Status)"
|
||||
}
|
||||
}
|
||||
|
||||
- name: Repack Windows CLI archives
|
||||
working-directory: packages/opencode/dist
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path "opencode-windows-arm64\bin\*" -DestinationPath "opencode-windows-arm64.zip" -Force
|
||||
Compress-Archive -Path "opencode-windows-x64\bin\*" -DestinationPath "opencode-windows-x64.zip" -Force
|
||||
Compress-Archive -Path "opencode-windows-x64-baseline\bin\*" -DestinationPath "opencode-windows-x64-baseline.zip" -Force
|
||||
|
||||
- name: Upload signed Windows CLI release assets
|
||||
if: needs.version.outputs.release != ''
|
||||
shell: pwsh
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
run: |
|
||||
gh release upload "v${{ needs.version.outputs.version }}" `
|
||||
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64.zip" `
|
||||
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64.zip" `
|
||||
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline.zip" `
|
||||
--clobber `
|
||||
--repo "${{ needs.version.outputs.repo }}"
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: opencode-cli-signed-windows
|
||||
path: |
|
||||
packages/opencode/dist/opencode-windows-arm64
|
||||
packages/opencode/dist/opencode-windows-x64
|
||||
packages/opencode/dist/opencode-windows-x64-baseline
|
||||
|
||||
build-electron:
|
||||
needs:
|
||||
- build-cli
|
||||
- version
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
continue-on-error: false
|
||||
env:
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
|
||||
AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- host: macos-26-intel
|
||||
target: x86_64-apple-darwin
|
||||
platform_flag: --mac --x64
|
||||
bun_install_flags: --os=darwin --cpu=x64
|
||||
- host: macos-26
|
||||
target: aarch64-apple-darwin
|
||||
platform_flag: --mac --arm64
|
||||
bun_install_flags: --os=darwin --cpu=arm64
|
||||
# github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain
|
||||
- host: "windows-2025"
|
||||
target: aarch64-pc-windows-msvc
|
||||
platform_flag: --win --arm64
|
||||
- host: "blacksmith-4vcpu-windows-2025"
|
||||
target: x86_64-pc-windows-msvc
|
||||
platform_flag: --win
|
||||
- host: "blacksmith-4vcpu-ubuntu-2404"
|
||||
target: x86_64-unknown-linux-gnu
|
||||
platform_flag: --linux
|
||||
- host: "blacksmith-4vcpu-ubuntu-2404-arm"
|
||||
target: aarch64-unknown-linux-gnu
|
||||
platform_flag: --linux --arm64
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0
|
||||
if: runner.os == 'macOS'
|
||||
with:
|
||||
keychain: build
|
||||
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Setup Apple API Key
|
||||
if: runner.os == 'macOS'
|
||||
run: echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
with:
|
||||
install-flags: ${{ matrix.settings.bun_install_flags }}
|
||||
|
||||
- uses: actions/setup-node@v6 # kilocode_change
|
||||
- name: Azure login
|
||||
if: runner.os == 'Windows'
|
||||
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
client-id: ${{ env.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ env.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: "24"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Install @vscode/vsce
|
||||
run: bun install -g @vscode/vsce
|
||||
|
||||
# kilocode_change start - download into /tmp and extract the tar.zst packed by build-cli
|
||||
- uses: actions/download-artifact@v8
|
||||
- name: Cache apt packages
|
||||
if: contains(matrix.settings.host, 'ubuntu')
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
name: kilo-cli.tar.zst
|
||||
path: /tmp
|
||||
skip-decompress: true
|
||||
path: ~/apt-cache
|
||||
key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-${{ hashFiles('.github/workflows/publish.yml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-
|
||||
|
||||
- name: Unpack CLI dist
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: contains(matrix.settings.host, 'ubuntu')
|
||||
run: |
|
||||
mkdir -p packages/opencode/dist
|
||||
tar --zstd -xf /tmp/kilo-cli.tar.zst -C packages/opencode/dist
|
||||
# kilocode_change end
|
||||
- name: Build VSIX packages
|
||||
run: bun script/build.ts
|
||||
working-directory: ./packages/kilo-vscode
|
||||
env:
|
||||
CLI_DIST_DIR: ../../packages/opencode/dist
|
||||
KILO_VERSION: ${{ needs.build-cli.outputs.version }}
|
||||
KILO_PRE_RELEASE: ${{ inputs.pre_release }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" rpm
|
||||
sudo chmod -R a+rw ~/apt-cache
|
||||
|
||||
- uses: actions/upload-artifact@v7 # kilocode_change
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
name: kilo-vscode
|
||||
path: packages/kilo-vscode/out
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
# kilocode_change start
|
||||
# Run smoke tests against CLI assets uploaded to the draft GitHub release
|
||||
# before publishing the release and package artifacts.
|
||||
smoke-test:
|
||||
name: Smoke Test (pre-publish gate)
|
||||
needs:
|
||||
- version
|
||||
- build-cli
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
uses: ./.github/workflows/smoke-test.yml
|
||||
with:
|
||||
cli_version: ${{ needs.version.outputs.version }}
|
||||
secrets: inherit
|
||||
# kilocode_change end
|
||||
- name: Prepare
|
||||
run: bun ./scripts/prepare.ts
|
||||
working-directory: packages/desktop
|
||||
env:
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||
OPENCODE_CLI_ARTIFACT: ${{ (runner.os == 'Windows' && 'opencode-cli-windows') || 'opencode-cli' }}
|
||||
RUST_TARGET: ${{ matrix.settings.target }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
|
||||
- name: Build
|
||||
run: bun run build
|
||||
working-directory: packages/desktop
|
||||
env:
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
|
||||
SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
|
||||
VITE_SENTRY_ENVIRONMENT: ${{ (github.ref_name == 'beta' && 'beta') || 'production' }}
|
||||
VITE_SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||
|
||||
- name: Package
|
||||
if: needs.version.outputs.release
|
||||
run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts
|
||||
working-directory: packages/desktop
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
|
||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
|
||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||
|
||||
- name: Package (no publish)
|
||||
if: ${{ !needs.version.outputs.release }}
|
||||
run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts
|
||||
working-directory: packages/desktop
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||
|
||||
- name: Create macOS .app.tar.gz
|
||||
if: runner.os == 'macOS' && needs.version.outputs.release
|
||||
working-directory: packages/desktop/dist
|
||||
run: |
|
||||
if [[ "${{ matrix.settings.target }}" == "x86_64-apple-darwin" ]]; then
|
||||
APP_DIR="mac"
|
||||
OUT_NAME="opencode-desktop-mac-x64.app.tar.gz"
|
||||
elif [[ "${{ matrix.settings.target }}" == "aarch64-apple-darwin" ]]; then
|
||||
APP_DIR="mac-arm64"
|
||||
OUT_NAME="opencode-desktop-mac-arm64.app.tar.gz"
|
||||
else
|
||||
echo "Unknown macOS target: ${{ matrix.settings.target }}"
|
||||
exit 1
|
||||
fi
|
||||
APP_PATH=$(find "$APP_DIR" -maxdepth 1 -name "*.app" -type d | head -1)
|
||||
if [ -z "$APP_PATH" ]; then
|
||||
echo "No .app bundle found in $APP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
tar -czf "$OUT_NAME" -C "$(dirname "$APP_PATH")" "$(basename "$APP_PATH")"
|
||||
|
||||
- name: Verify signed Windows Electron artifacts
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$files = @()
|
||||
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*.exe" | Select-Object -ExpandProperty FullName
|
||||
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*unpacked\*.exe" | Select-Object -ExpandProperty FullName
|
||||
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*unpacked\resources\opencode-cli.exe" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
|
||||
|
||||
foreach ($file in $files | Select-Object -Unique) {
|
||||
$sig = Get-AuthenticodeSignature $file
|
||||
if ($sig.Status -ne "Valid") {
|
||||
throw "Invalid signature for ${file}: $($sig.Status)"
|
||||
}
|
||||
}
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: opencode-desktop-${{ matrix.settings.target }}
|
||||
path: packages/desktop/dist/*
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: needs.version.outputs.release
|
||||
with:
|
||||
name: latest-yml-${{ matrix.settings.target }}
|
||||
path: packages/desktop/dist/latest*.yml
|
||||
|
||||
publish:
|
||||
needs:
|
||||
- version
|
||||
- build-cli
|
||||
- build-vscode
|
||||
- validate-cli-unix # kilocode_change
|
||||
- validate-cli-windows # kilocode_change
|
||||
- smoke-test
|
||||
runs-on: ubuntu-24.04
|
||||
- sign-cli-windows
|
||||
- build-electron
|
||||
if: always() && !failure() && !cancelled()
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
# kilocode_change start
|
||||
with:
|
||||
persist-credentials: false
|
||||
# kilocode_change end
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- uses: actions/setup-node@v6 # kilocode_change
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: "24"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
package-manager-cache: false # kilocode_change
|
||||
|
||||
- name: Install @vscode/vsce
|
||||
run: bun install -g @vscode/vsce
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: opencode-cli
|
||||
path: packages/opencode/dist
|
||||
|
||||
- name: Setup Git Committer
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: opencode-cli-windows
|
||||
path: packages/opencode/dist
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: opencode-cli-signed-windows
|
||||
path: packages/opencode/dist
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: opencode-preview-cli
|
||||
path: packages/cli/dist
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
if: needs.version.outputs.release
|
||||
with:
|
||||
pattern: latest-yml-*
|
||||
path: /tmp/latest-yml
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
if: needs.version.outputs.release
|
||||
with:
|
||||
pattern: opencode-desktop-*
|
||||
path: /tmp/desktop
|
||||
merge-multiple: true
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
# kilocode_change start
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
# kilocode_change end
|
||||
|
||||
# kilocode_change start - download into /tmp and extract the tar.zst packed by build-cli
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: kilo-cli.tar.zst
|
||||
path: /tmp
|
||||
skip-decompress: true
|
||||
|
||||
- name: Unpack CLI dist
|
||||
run: |
|
||||
mkdir -p packages/opencode/dist
|
||||
tar --zstd -xf /tmp/kilo-cli.tar.zst -C packages/opencode/dist
|
||||
# kilocode_change end
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: kilo-vscode
|
||||
path: packages/kilo-vscode/out
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Cache apt packages (AUR)
|
||||
uses: actions/cache@v5 # kilocode_change
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: ${{ runner.os }}-apt-aur-${{ hashFiles('.github/workflows/publish.yml') }}
|
||||
@@ -462,18 +490,31 @@ jobs:
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
git config --global user.email "kilo-maintainer[bot]@users.noreply.github.com"
|
||||
git config --global user.name "kilo-maintainer[bot]"
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
|
||||
|
||||
- name: Upload desktop release assets
|
||||
if: needs.version.outputs.release
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
files=(/tmp/desktop/*.{exe,blockmap,dmg,zip,AppImage,deb,rpm} /tmp/desktop/*.app.tar.gz)
|
||||
if (( ${#files[@]} == 0 )); then
|
||||
echo "No desktop release assets found"
|
||||
exit 1
|
||||
fi
|
||||
gh release upload "v${{ needs.version.outputs.version }}" "${files[@]}" --clobber --repo "${{ needs.version.outputs.repo }}"
|
||||
|
||||
- run: ./script/publish.ts
|
||||
env:
|
||||
KILO_VERSION: ${{ needs.version.outputs.version }}
|
||||
KILO_RELEASE: ${{ needs.version.outputs.release }}
|
||||
KILO_PRE_RELEASE: ${{ inputs.pre_release }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
VSCE_PAT: ${{ secrets.VSCE_TOKEN }}
|
||||
OPENVSX_TOKEN: ${{ secrets.OVSX_TOKEN }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
NPM_CONFIG_PROVENANCE: false
|
||||
LATEST_YML_DIR: /tmp/latest-yml
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
+2
-7
@@ -22,13 +22,8 @@ jobs:
|
||||
|
||||
- run: git fetch --force --tags
|
||||
|
||||
- name: Setup Git Committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
|
||||
- name: Release
|
||||
run: |
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
./github/script/release
|
||||
@@ -6,14 +6,10 @@ on:
|
||||
|
||||
jobs:
|
||||
check-guidelines:
|
||||
# kilocode_change start - disabled for kilo-cli fork (requires OpenCode install and API keys)
|
||||
if: false
|
||||
# kilocode_change end
|
||||
# Original condition:
|
||||
# if: |
|
||||
# github.event.issue.pull_request &&
|
||||
# startsWith(github.event.comment.body, '/review') &&
|
||||
# contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association)
|
||||
if: |
|
||||
github.event.issue.pull_request &&
|
||||
startsWith(github.event.comment.body, '/review') &&
|
||||
contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association)
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -51,7 +47,7 @@ jobs:
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
KILO_PERMISSION: '{ "bash": { "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }'
|
||||
OPENCODE_PERMISSION: '{ "bash": { "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }'
|
||||
PR_TITLE: ${{ steps.pr-details.outputs.title }}
|
||||
run: |
|
||||
PR_BODY=$(jq -r .body pr_data.json)
|
||||
@@ -1,151 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
# Standalone smoke test — checks out kilo-bench and runs a small set of
|
||||
# Harbor eval tasks against the latest published CLI (or a specific version).
|
||||
#
|
||||
# Tasks: hello-world (~$0.01), log-summary-date-ranges (~$0.13)
|
||||
# Expected total: < $0.50, < 15 min wall clock
|
||||
#
|
||||
# Triggers:
|
||||
# - workflow_dispatch: manually from Actions tab (optionally pass a CLI version)
|
||||
# - workflow_call: from publish.yml after draft release assets are uploaded
|
||||
#
|
||||
# Required secrets:
|
||||
# KILO_API_KEY — Kilo Gateway key
|
||||
# KILO_ORG_ID — Kilo org ID
|
||||
# BENCH_GITHUB_TOKEN — PAT with contents:read on Kilo-Org/kilo-bench (private repo)
|
||||
|
||||
name: smoke-test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
cli_version:
|
||||
description: "CLI version to test (e.g. 7.0.36). Leave blank for latest npm release."
|
||||
required: false
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
cli_version:
|
||||
description: "CLI version to test from draft release assets."
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: smoke-test
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write # needed to read draft release assets when called from publish.yml
|
||||
|
||||
jobs:
|
||||
smoke-test:
|
||||
name: Smoke Test
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
KILO_API_KEY: ${{ secrets.KILO_API_KEY }}
|
||||
KILO_ORG_ID: ${{ secrets.KILO_ORG_ID }}
|
||||
steps:
|
||||
- name: Checkout kilo-bench
|
||||
uses: actions/checkout@v6 # kilocode_change
|
||||
with:
|
||||
repository: Kilo-Org/kilo-bench
|
||||
token: ${{ secrets.BENCH_GITHUB_TOKEN }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install 3.13
|
||||
|
||||
- name: Install dependencies
|
||||
run: uv sync --no-dev
|
||||
|
||||
- name: Validate API key
|
||||
run: |
|
||||
if [[ -z "$KILO_API_KEY" ]]; then
|
||||
echo "::error::KILO_API_KEY secret is not set."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Resolve CLI asset URL
|
||||
id: cli
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
VERSION="${{ inputs.cli_version }}"
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo "cli_url=" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice::Testing latest CLI from npm"
|
||||
exit 0
|
||||
fi
|
||||
# Resolve the release asset via the API so this works for draft
|
||||
# releases too (browser /releases/download/... URLs 404 on drafts).
|
||||
# The installer passes Accept: application/octet-stream + bearer
|
||||
# auth, which is what the API asset endpoint expects.
|
||||
# The tag_name filter yields at most one release and one asset,
|
||||
# so --jq returns a single URL without needing head/pipefail games.
|
||||
URL=$(gh api "repos/${{ github.repository }}/releases" \
|
||||
--jq ".[] | select(.tag_name == \"v${VERSION}\") | .assets[] | select(.name == \"kilo-linux-x64.tar.gz\") | .url")
|
||||
if [[ -z "$URL" ]]; then
|
||||
echo "::error::asset kilo-linux-x64.tar.gz not found for v${VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
echo "cli_url=$URL" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice::Testing CLI v${VERSION} via asset API: $URL"
|
||||
|
||||
# Harbor's default agent-setup timeout is 360s. The hello-world container
|
||||
# (FROM ubuntu:24.04) needs apt-get update + apt-get install + a NodeSource
|
||||
# curl|bash + apt install nodejs before the CLI even downloads, and on
|
||||
# the Blacksmith runners that occasionally pushes past 6 min when an apt
|
||||
# mirror or NodeSource cdn is slow, killing the run with
|
||||
# AgentSetupTimeoutError. Doubling the multiplier to 2 gives enough
|
||||
# headroom for transient mirror/cdn slowness while still finishing well
|
||||
# under timeout-minutes. Note: --timeout-multiplier scales every timeout
|
||||
# uniformly (setup, agent, verifier, env build), but they're all upper
|
||||
# limits so this is harmless.
|
||||
- name: Run smoke test — hello-world
|
||||
env:
|
||||
KILO_CLI_URL: ${{ steps.cli.outputs.cli_url }}
|
||||
KILO_CLI_GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
./scripts/run_eval.sh \
|
||||
-m kilo/anthropic/claude-sonnet-4.6 \
|
||||
-d hello-world \
|
||||
--job-name smoke-test-hello-world \
|
||||
--timeout-multiplier 2
|
||||
|
||||
- name: Run smoke test — log-summary-date-ranges
|
||||
env:
|
||||
KILO_CLI_URL: ${{ steps.cli.outputs.cli_url }}
|
||||
KILO_CLI_GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
./scripts/run_eval.sh \
|
||||
-m kilo/anthropic/claude-sonnet-4.6 \
|
||||
-d terminal-bench-sample \
|
||||
--include-task-name "log-summary-date-ranges" \
|
||||
--job-name smoke-test-log-summary \
|
||||
--timeout-multiplier 2
|
||||
|
||||
- name: Validate results
|
||||
run: python3 scripts/validate_smoke_test.py jobs/smoke-test-*/
|
||||
|
||||
# Also upload the agent setup logs (stdout/stderr/return-code from the
|
||||
# CLI install script) so we can see exactly which step stalls when the
|
||||
# next AgentSetupTimeoutError happens. These come from the install
|
||||
# script in kilo-bench, which uses `set -euo pipefail` (no `set -x`)
|
||||
# and never echoes auth tokens or API keys, so the captured output is
|
||||
# safe to upload.
|
||||
- name: Upload results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7 # kilocode_change
|
||||
with:
|
||||
name: smoke-test-results
|
||||
path: |
|
||||
jobs/smoke-test-*/**/result.json
|
||||
jobs/smoke-test-*/**/trajectory.json
|
||||
jobs/smoke-test-*/**/agent/setup/*.txt
|
||||
retention-days: 30
|
||||
if-no-files-found: warn
|
||||
@@ -1,24 +0,0 @@
|
||||
name: Check Source Links
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "packages/kilo-vscode/src/**"
|
||||
- "packages/kilo-vscode/webview-ui/**"
|
||||
- "packages/opencode/src/**"
|
||||
- "packages/kilo-docs/source-links.md"
|
||||
- "script/extract-source-links.ts"
|
||||
- ".github/workflows/source-check-links.yml"
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
jobs:
|
||||
source-links-freshness:
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6 # kilocode_change
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Check source-links.md is up to date
|
||||
run: bun run script/extract-source-links.ts --check
|
||||
@@ -9,6 +9,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
stats:
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -1,115 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: test-jetbrains
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
pull-requests: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
name: detect JetBrains changes
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
outputs:
|
||||
jetbrains: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.filter.outputs.jetbrains }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Detect JetBrains changes
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
id: filter
|
||||
uses: Kilo-Org/paths-filter@668c092af3649c4b664c54e4b704aa46782f6f7c # v3
|
||||
with:
|
||||
predicate-quantifier: every
|
||||
filters: |
|
||||
jetbrains:
|
||||
- '**'
|
||||
- '!.changeset/**'
|
||||
- '!packages/kilo-vscode/**'
|
||||
- '!packages/kilo-docs/**'
|
||||
|
||||
unit:
|
||||
name: jetbrains
|
||||
needs: changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.jetbrains == 'true'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Mark workspace as git-safe
|
||||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
bun-version-file: package.json
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
- name: Run JetBrains unit tests
|
||||
run: bun script/test-ci.ts
|
||||
working-directory: packages/kilo-jetbrains
|
||||
|
||||
- name: Publish JetBrains unit reports
|
||||
if: always()
|
||||
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6.4.0
|
||||
with:
|
||||
report_paths: packages/kilo-jetbrains/.artifacts/unit/junit.xml
|
||||
check_name: "unit results (jetbrains)"
|
||||
detailed_summary: true
|
||||
include_time_in_summary: true
|
||||
fail_on_failure: false
|
||||
|
||||
- name: Upload JetBrains unit artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: unit-jetbrains-${{ github.run_attempt }}
|
||||
include-hidden-files: true
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
path: packages/kilo-jetbrains/.artifacts/unit/junit.xml
|
||||
|
||||
required:
|
||||
name: result
|
||||
needs:
|
||||
- changes
|
||||
- unit
|
||||
if: always()
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Verify JetBrains jobs passed
|
||||
run: |
|
||||
echo "changes=${{ needs.changes.result }}"
|
||||
echo "jetbrains=${{ needs.unit.result }}"
|
||||
test "${{ needs.changes.result }}" = "success"
|
||||
if [ "${{ needs.changes.outputs.jetbrains }}" = "true" ]; then
|
||||
test "${{ needs.unit.result }}" = "success"
|
||||
else
|
||||
test "${{ needs.changes.outputs.jetbrains }}" = "false"
|
||||
test "${{ needs.unit.result }}" = "skipped"
|
||||
fi
|
||||
@@ -1,55 +0,0 @@
|
||||
name: test-vscode
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths:
|
||||
- "packages/kilo-vscode/**"
|
||||
- "packages/ui/**"
|
||||
- "packages/kilo-ui/**"
|
||||
- ".github/workflows/test-vscode.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "packages/kilo-vscode/**"
|
||||
- "packages/ui/**"
|
||||
- "packages/kilo-ui/**"
|
||||
- ".github/workflows/test-vscode.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
unit:
|
||||
name: unit tests
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404 # kilocode_change
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6 # kilocode_change
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Run unit tests
|
||||
working-directory: packages/kilo-vscode
|
||||
run: bun run test:unit
|
||||
|
||||
- name: Check lint (ESLint)
|
||||
working-directory: packages/kilo-vscode
|
||||
run: bun run lint
|
||||
|
||||
- name: Check formatting (prettier)
|
||||
working-directory: packages/kilo-vscode
|
||||
run: bun run format:check
|
||||
|
||||
- name: Check for dead code (knip)
|
||||
working-directory: packages/kilo-vscode
|
||||
run: bun run knip
|
||||
|
||||
- name: Check for kilocode_change markers
|
||||
working-directory: packages/kilo-vscode
|
||||
run: bun run check-kilocode-change
|
||||
+78
-209
@@ -3,14 +3,14 @@ name: test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main # kilocode_change
|
||||
- dev
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
# Keep every run on main so cancelled checks do not pollute the default branch # kilocode_change
|
||||
# Keep every run on dev so cancelled checks do not pollute the default branch
|
||||
# commit history. PRs and other branches still share a group and cancel stale runs.
|
||||
group: ${{ case(github.ref == 'refs/heads/main', format('{0}-{1}', github.workflow, github.run_id), format('{0}-{1}', github.workflow, github.event.pull_request.number || github.ref)) }} # kilocode_change
|
||||
group: ${{ case(github.ref == 'refs/heads/dev', format('{0}-{1}', github.workflow, github.run_id), format('{0}-{1}', github.workflow, github.event.pull_request.number || github.ref)) }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
@@ -21,256 +21,125 @@ env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
# kilocode_change start
|
||||
changes:
|
||||
name: detect general unit test changes
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
settings: ${{ steps.matrix.outputs.settings }}
|
||||
general: ${{ steps.matrix.outputs.general }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Detect general unit test changes
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
id: filter
|
||||
uses: Kilo-Org/paths-filter@668c092af3649c4b664c54e4b704aa46782f6f7c # v3
|
||||
with:
|
||||
predicate-quantifier: every
|
||||
filters: |
|
||||
general:
|
||||
- '**'
|
||||
- '!.changeset/**'
|
||||
- '!packages/kilo-jetbrains/**'
|
||||
- '!packages/kilo-vscode/**'
|
||||
- '!packages/kilo-docs/**'
|
||||
- '!packages/extensions/zed/**'
|
||||
- '!specs/**'
|
||||
- '!perf/**'
|
||||
|
||||
- name: Configure unit matrix
|
||||
id: matrix
|
||||
env:
|
||||
GENERAL: ${{ github.event_name != 'pull_request' || steps.filter.outputs.general == 'true' }}
|
||||
run: |
|
||||
if [ "$GENERAL" != "true" ]; then
|
||||
echo 'general=false' >> "$GITHUB_OUTPUT"
|
||||
echo 'settings=[{"os":"linux","index":1,"total":1,"host":"blacksmith-4vcpu-ubuntu-2404","run":false,"packages":false}]' >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo 'general=true' >> "$GITHUB_OUTPUT"
|
||||
echo 'settings=[{"os":"linux","index":1,"total":2,"host":"blacksmith-4vcpu-ubuntu-2404","run":true,"packages":true},{"os":"linux","index":2,"total":2,"host":"blacksmith-4vcpu-ubuntu-2404","run":true,"packages":false},{"os":"macos","index":1,"total":1,"host":"macos-15","run":true,"packages":true},{"os":"windows","index":1,"total":4,"host":"blacksmith-4vcpu-windows-2025","run":true,"packages":true},{"os":"windows","index":2,"total":4,"host":"blacksmith-4vcpu-windows-2025","run":true,"packages":false},{"os":"windows","index":3,"total":4,"host":"blacksmith-4vcpu-windows-2025","run":true,"packages":false},{"os":"windows","index":4,"total":4,"host":"blacksmith-4vcpu-windows-2025","run":true,"packages":false}]' >> "$GITHUB_OUTPUT"
|
||||
# kilocode_change end
|
||||
|
||||
unit:
|
||||
# kilocode_change start
|
||||
name: ${{ !matrix.settings.run && 'unit (unchanged)' || matrix.settings.total > 1 && format('unit ({0}, {1}/{2})', matrix.settings.os, matrix.settings.index, matrix.settings.total) || format('unit ({0})', matrix.settings.os) }}
|
||||
needs: changes
|
||||
# kilocode_change end
|
||||
name: unit (${{ matrix.settings.name }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings: ${{ fromJSON(needs.changes.outputs.settings) }} # kilocode_change
|
||||
settings:
|
||||
- name: linux
|
||||
host: blacksmith-4vcpu-ubuntu-2404
|
||||
- name: windows
|
||||
host: blacksmith-4vcpu-windows-2025
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
timeout-minutes: 45 # kilocode_change
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
# kilocode_change start
|
||||
- name: Skip unchanged general unit tests
|
||||
if: ${{ !matrix.settings.run }}
|
||||
run: echo "Only isolated product, documentation, or metadata files changed; general unit tests are unchanged."
|
||||
# kilocode_change end
|
||||
|
||||
- name: Checkout repository
|
||||
if: matrix.settings.run # kilocode_change
|
||||
uses: actions/checkout@v6 # kilocode_change
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# kilocode_change start
|
||||
- name: Setup Node
|
||||
if: matrix.settings.run
|
||||
id: setup-node
|
||||
continue-on-error: ${{ runner.os == 'Windows' }}
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: "24"
|
||||
# kilocode_change end
|
||||
|
||||
# kilocode_change start
|
||||
- name: Retry Setup Node on Windows
|
||||
if: matrix.settings.run && runner.os == 'Windows' && steps.setup-node.outcome == 'failure'
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
# kilocode_change end
|
||||
|
||||
- name: Setup Bun
|
||||
if: matrix.settings.run # kilocode_change
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
# kilocode_change start
|
||||
- name: Setup Linux sandbox helper
|
||||
if: matrix.settings.run && runner.os == 'Linux'
|
||||
uses: ./.github/actions/setup-linux-sandbox
|
||||
# kilocode_change end
|
||||
- name: Configure git identity
|
||||
if: matrix.settings.run # kilocode_change
|
||||
# kilocode_change start
|
||||
run: |
|
||||
git config --global user.email "kilo-maintainer[bot]@users.noreply.github.com"
|
||||
git config --global user.name "kilo-maintainer[bot]"
|
||||
# kilocode_change end
|
||||
git config --global user.email "bot@opencode.ai"
|
||||
git config --global user.name "opencode"
|
||||
|
||||
- name: Cache Turbo
|
||||
if: matrix.settings.run # kilocode_change
|
||||
uses: actions/cache@v5 # kilocode_change
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: .turbo/cache # kilocode_change
|
||||
key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}-${{ matrix.settings.os }}-${{ matrix.settings.index }}-${{ github.sha }} # kilocode_change
|
||||
# kilocode_change start
|
||||
path: node_modules/.cache/turbo
|
||||
key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}-${{ matrix.settings.os }}-${{ matrix.settings.index }}-
|
||||
turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}-
|
||||
turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}-
|
||||
turbo-${{ runner.os }}-
|
||||
# kilocode_change end
|
||||
|
||||
# kilocode_change start - test non-CLI packages separately from sharded CLI tests
|
||||
- name: Run non-CLI unit tests
|
||||
if: matrix.settings.run && matrix.settings.packages
|
||||
run: bun turbo test:ci --output-logs=errors-only --log-order=grouped --log-prefix=task --filter='!@kilocode/cli' --filter='!@kilocode/kilo-jetbrains'
|
||||
# kilocode_change end
|
||||
|
||||
# kilocode_change start - ensure the Darwin profile cannot suppress its own validation
|
||||
- name: Validate Darwin CLI test profile
|
||||
if: matrix.settings.run && matrix.settings.os == 'macos'
|
||||
working-directory: packages/opencode
|
||||
run: bun test test/kilocode/test-profile.test.ts
|
||||
# kilocode_change end
|
||||
|
||||
# kilocode_change start - run Kilo CLI tests through platform-specific shards
|
||||
- name: Run CLI unit tests
|
||||
if: matrix.settings.run
|
||||
run: bun turbo test:ci --output-logs=errors-only --log-order=grouped --log-prefix=task --filter='@kilocode/cli'
|
||||
- name: Run unit tests
|
||||
timeout-minutes: 20
|
||||
run: bun turbo test --output-logs=errors-only --log-order=grouped --log-prefix=task
|
||||
env:
|
||||
KILO_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
|
||||
KILO_TEST_PROFILE: ${{ matrix.settings.os == 'macos' && 'darwin' || '' }}
|
||||
KILO_TEST_SHARD: ${{ format('{0}/{1}', matrix.settings.index, matrix.settings.total) }}
|
||||
# kilocode_change end
|
||||
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
|
||||
|
||||
# kilocode_change start
|
||||
- name: Publish unit reports
|
||||
if: always() && matrix.settings.run
|
||||
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6.4.0
|
||||
with:
|
||||
report_paths: packages/*/.artifacts/unit/junit.xml
|
||||
annotate_only: true
|
||||
detailed_summary: true
|
||||
include_time_in_summary: true
|
||||
fail_on_failure: false
|
||||
- name: Run HttpApi exerciser gates
|
||||
if: runner.os == 'Linux'
|
||||
working-directory: packages/opencode
|
||||
run: bun run test:httpapi
|
||||
|
||||
- name: Upload unit artifacts
|
||||
if: always() && matrix.settings.run
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: unit-${{ matrix.settings.os }}-${{ matrix.settings.index }}-${{ github.run_attempt }}
|
||||
include-hidden-files: true
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
path: packages/*/.artifacts/unit/junit.xml
|
||||
# kilocode_change end
|
||||
|
||||
# kilocode_change start
|
||||
httpapi:
|
||||
name: HttpApi exerciser
|
||||
needs: changes
|
||||
if: needs.changes.outputs.general == 'true'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 15
|
||||
e2e:
|
||||
name: e2e (${{ matrix.settings.name }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- name: linux
|
||||
host: blacksmith-4vcpu-ubuntu-2404
|
||||
- name: windows
|
||||
host: blacksmith-4vcpu-windows-2025
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
env:
|
||||
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup Linux sandbox helper
|
||||
uses: ./.github/actions/setup-linux-sandbox
|
||||
|
||||
- name: Configure git identity
|
||||
- name: Read Playwright version
|
||||
id: playwright-version
|
||||
run: |
|
||||
git config --global user.email "kilo-maintainer[bot]@users.noreply.github.com"
|
||||
git config --global user.name "kilo-maintainer[bot]"
|
||||
version=$(node -e 'console.log(require("./package.json").workspaces.catalog["@playwright/test"])')
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Turbo
|
||||
uses: actions/cache@v5
|
||||
- name: Cache Playwright browsers
|
||||
id: playwright-cache
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: .turbo/cache
|
||||
key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}-httpapi-${{ github.sha }}
|
||||
restore-keys: |
|
||||
turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}-httpapi-
|
||||
turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}-
|
||||
turbo-${{ runner.os }}-
|
||||
path: ${{ github.workspace }}/.playwright-browsers
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright-version.outputs.version }}-chromium
|
||||
|
||||
- name: Run HttpApi exerciser gates
|
||||
run: bun turbo test:httpapi --filter='@kilocode/cli'
|
||||
# kilocode_change end
|
||||
- name: Install Playwright system dependencies
|
||||
if: runner.os == 'Linux'
|
||||
working-directory: packages/app
|
||||
run: bunx playwright install-deps chromium
|
||||
|
||||
# kilocode_change start
|
||||
jetbrains:
|
||||
name: jetbrains
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
pull-requests: read
|
||||
uses: ./.github/workflows/test-jetbrains.yml
|
||||
# kilocode_change end
|
||||
- name: Install Playwright browsers
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
working-directory: packages/app
|
||||
run: bunx playwright install chromium
|
||||
|
||||
# kilocode_change start
|
||||
unit-required:
|
||||
name: unit (linux)
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
needs:
|
||||
- unit
|
||||
if: always()
|
||||
steps:
|
||||
- name: Verify unit matrix passed
|
||||
run: |
|
||||
echo "unit=${{ needs.unit.result }}"
|
||||
test "${{ needs.unit.result }}" = "success"
|
||||
# kilocode_change end
|
||||
- name: Run app e2e tests
|
||||
run: bun --cwd packages/app test:e2e:local
|
||||
env:
|
||||
CI: true
|
||||
timeout-minutes: 30
|
||||
|
||||
# kilocode_change start
|
||||
required:
|
||||
name: test (linux)
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
needs:
|
||||
- changes
|
||||
- unit
|
||||
- httpapi
|
||||
- jetbrains
|
||||
if: always()
|
||||
steps:
|
||||
- name: Verify upstream test jobs passed # kilocode_change
|
||||
run: |
|
||||
echo "unit=${{ needs.unit.result }}"
|
||||
echo "httpapi=${{ needs.httpapi.result }}"
|
||||
echo "jetbrains=${{ needs.jetbrains.result }}"
|
||||
test "${{ needs.unit.result }}" = "success"
|
||||
if [ "${{ needs.changes.outputs.general }}" = "true" ]; then
|
||||
test "${{ needs.httpapi.result }}" = "success"
|
||||
else
|
||||
test "${{ needs.httpapi.result }}" = "skipped"
|
||||
fi
|
||||
test "${{ needs.jetbrains.result }}" = "success"
|
||||
# kilocode_change end
|
||||
- name: Upload Playwright artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: playwright-${{ matrix.settings.name }}-${{ github.run_attempt }}
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
path: |
|
||||
packages/app/e2e/test-results
|
||||
packages/app/e2e/playwright-report
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install opencode
|
||||
run: curl -fsSL https://kilo.ai/install | bash
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Triage issue
|
||||
env:
|
||||
@@ -2,101 +2,20 @@ name: typecheck
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [dev]
|
||||
pull_request:
|
||||
branches: [dev]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
typecheck-js:
|
||||
name: typecheck-js
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404 # kilocode_change
|
||||
typecheck:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6 # kilocode_change
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
# kilocode_change start
|
||||
- name: Run TypeScript typecheck
|
||||
run: bun turbo typecheck --filter='!@kilocode/kilo-jetbrains'
|
||||
|
||||
- name: Build Kilo Console
|
||||
run: bun turbo build --filter=@kilocode/kilo-console
|
||||
# kilocode_change end
|
||||
|
||||
# kilocode_change start
|
||||
jetbrains-changes:
|
||||
name: detect JetBrains changes
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
outputs:
|
||||
jetbrains: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.filter.outputs.jetbrains }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Detect JetBrains changes
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
id: filter
|
||||
uses: Kilo-Org/paths-filter@668c092af3649c4b664c54e4b704aa46782f6f7c # v3
|
||||
with:
|
||||
predicate-quantifier: every
|
||||
filters: |
|
||||
jetbrains:
|
||||
- '**'
|
||||
- '!.changeset/**'
|
||||
- '!packages/kilo-vscode/**'
|
||||
- '!packages/kilo-docs/**'
|
||||
|
||||
typecheck-jetbrains:
|
||||
name: typecheck-jetbrains
|
||||
needs: jetbrains-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.jetbrains-changes.outputs.jetbrains == 'true'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
- name: Run JetBrains typecheck
|
||||
run: ./gradlew typecheck
|
||||
working-directory: packages/kilo-jetbrains
|
||||
|
||||
required:
|
||||
name: typecheck
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
needs:
|
||||
- typecheck-js
|
||||
- jetbrains-changes
|
||||
- typecheck-jetbrains
|
||||
if: always()
|
||||
steps:
|
||||
- name: Verify typecheck jobs passed
|
||||
run: |
|
||||
echo "typecheck-js=${{ needs.typecheck-js.result }}"
|
||||
echo "jetbrains-changes=${{ needs.jetbrains-changes.result }}"
|
||||
echo "typecheck-jetbrains=${{ needs.typecheck-jetbrains.result }}"
|
||||
test "${{ needs.typecheck-js.result }}" = "success"
|
||||
test "${{ needs.jetbrains-changes.result }}" = "success"
|
||||
if [ "${{ needs.jetbrains-changes.outputs.jetbrains }}" = "true" ]; then
|
||||
test "${{ needs.typecheck-jetbrains.result }}" = "success"
|
||||
else
|
||||
test "${{ needs.jetbrains-changes.outputs.jetbrains }}" = "false"
|
||||
test "${{ needs.typecheck-jetbrains.result }}" = "skipped"
|
||||
fi
|
||||
# kilocode_change end
|
||||
- name: Run typecheck
|
||||
run: bun typecheck
|
||||
|
||||
@@ -1,412 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: Visual Regression Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check-paths:
|
||||
name: Check changed paths
|
||||
runs-on: ubuntu-latest # kilocode_change
|
||||
outputs:
|
||||
matched: ${{ steps.filter.outputs.matched }}
|
||||
can_autocommit: ${{ steps.autocommit-check.outputs.can_autocommit }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Check changed files
|
||||
id: filter
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
matched=false
|
||||
while IFS= read -r file; do
|
||||
case "$file" in
|
||||
packages/kilo-ui/*|packages/ui/*|packages/util/*|packages/sdk/js/*|packages/kilo-vscode/webview-ui/*|packages/kilo-vscode/.storybook/*|packages/kilo-vscode/tests/visual-regression*|packages/kilo-vscode/tests/permission-dock-dropdown*|packages/kilo-vscode/tests/accessibility*|packages/kilo-docs/public/img/screenshot-tests/*|.github/actions/setup-bun/*|.github/workflows/visual-regression.yml)
|
||||
matched=true
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done < <(gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls/${PR}/files" --jq '.[].filename')
|
||||
echo "matched=$matched" >> "$GITHUB_OUTPUT"
|
||||
echo "matched=$matched"
|
||||
- name: Check if PR is from a fork
|
||||
id: fork-check
|
||||
run: |
|
||||
if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
|
||||
echo "is_fork=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_fork=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Check baseline auto-commit permissions
|
||||
id: autocommit-check
|
||||
env:
|
||||
MAINTAINER_APP_ID: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
MAINTAINER_APP_SECRET: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
run: |
|
||||
if [ "${{ steps.fork-check.outputs.is_fork }}" != "true" ] && [ -n "$MAINTAINER_APP_ID" ] && [ -n "$MAINTAINER_APP_SECRET" ]; then
|
||||
echo "can_autocommit=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "can_autocommit=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
visual-regression:
|
||||
needs: check-paths
|
||||
if: needs.check-paths.outputs.matched == 'true'
|
||||
name: Visual Regression (kilo-ui) # kilocode_change
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout (internal)
|
||||
if: needs.check-paths.outputs.can_autocommit == 'true'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
lfs: true
|
||||
# Use github.token for LFS access. The maintainer app is used only for generated commits.
|
||||
token: ${{ github.token }}
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Checkout (read-only)
|
||||
if: needs.check-paths.outputs.can_autocommit != 'true'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Check if HEAD is a baseline update commit
|
||||
if: needs.check-paths.outputs.can_autocommit == 'true'
|
||||
id: check-baseline-commit
|
||||
run: |
|
||||
COMMIT_MSG=$(git log -1 --format=%s)
|
||||
if [ "$COMMIT_MSG" = "chore: update visual regression baselines" ] || [ "$COMMIT_MSG" = "chore: update kilo-vscode visual regression baselines" ]; then
|
||||
echo "is_baseline_update=true" >> "$GITHUB_OUTPUT"
|
||||
echo "HEAD commit is a baseline update commit — will not auto-commit again."
|
||||
else
|
||||
echo "is_baseline_update=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun # kilocode_change
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
id: playwright-cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-${{ hashFiles('packages/kilo-ui/package.json') }}
|
||||
|
||||
- name: Install Playwright browsers
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: bunx playwright install chromium
|
||||
working-directory: packages/kilo-ui
|
||||
|
||||
- name: Install Playwright system deps
|
||||
run: bunx playwright install-deps chromium
|
||||
working-directory: packages/kilo-ui
|
||||
|
||||
- name: Cache Storybook build
|
||||
id: storybook-cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: packages/kilo-ui/storybook-static
|
||||
key: storybook-${{ hashFiles('packages/kilo-ui/src/**', 'packages/kilo-ui/.storybook/**', 'packages/ui/src/**', 'packages/kilo-ui/package.json') }}
|
||||
|
||||
- name: Build Storybook
|
||||
if: steps.storybook-cache.outputs.cache-hit != 'true'
|
||||
run: bun run build-storybook
|
||||
working-directory: packages/kilo-ui
|
||||
|
||||
- name: Generate baselines for new/missing stories
|
||||
run: bun run test:visual:update
|
||||
working-directory: packages/kilo-ui
|
||||
env:
|
||||
CI: true
|
||||
PLAYWRIGHT_WORKERS: "4"
|
||||
|
||||
- name: Remove stale baselines for deleted stories
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs'), path = require('path');
|
||||
const idx = JSON.parse(fs.readFileSync('storybook-static/index.json', 'utf8'));
|
||||
const stories = Object.values(idx.entries || idx.stories || {}).filter(s => s.id && !s.id.endsWith('--docs'));
|
||||
const expected = new Set(stories.map(s => { const [c,v] = s.id.split('--'); return c + '/' + v + '-chromium-linux.png'; }));
|
||||
const dir = '../kilo-docs/public/img/screenshot-tests/kilo-ui/visual-regression';
|
||||
if (!fs.existsSync(dir)) process.exit(0);
|
||||
for (const component of fs.readdirSync(dir)) {
|
||||
const cdir = path.join(dir, component);
|
||||
if (!fs.statSync(cdir).isDirectory()) continue;
|
||||
for (const file of fs.readdirSync(cdir)) {
|
||||
const rel = component + '/' + file;
|
||||
if (file.endsWith('.png') && !expected.has(rel)) { console.log('Removing stale baseline:', rel); fs.unlinkSync(path.join(cdir, file)); }
|
||||
}
|
||||
}
|
||||
"
|
||||
working-directory: packages/kilo-ui
|
||||
|
||||
- name: Check for baseline changes (read-only PRs)
|
||||
if: needs.check-paths.outputs.can_autocommit != 'true'
|
||||
run: |
|
||||
git add packages/kilo-docs/public/img/screenshot-tests/kilo-ui/
|
||||
if git diff --cached --quiet; then
|
||||
echo "No visual regression detected."
|
||||
else
|
||||
echo "::error::Visual regression detected. Screenshot baselines have changed."
|
||||
echo "::error::Since this PR cannot receive baseline commits automatically, updated screenshots cannot be committed."
|
||||
echo "::error::Please ask a Kilo developer for help updating the screenshots."
|
||||
git diff --cached --stat
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Fail if baselines still changing after auto-update
|
||||
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.check-baseline-commit.outputs.is_baseline_update == 'true'
|
||||
run: |
|
||||
git add packages/kilo-docs/public/img/screenshot-tests/kilo-ui/
|
||||
if git diff --cached --quiet; then
|
||||
echo "Baselines are stable after auto-update."
|
||||
else
|
||||
echo "::error::Visual regression baselines changed again after a previous auto-update commit."
|
||||
echo "::error::This indicates non-deterministic screenshots that would cause an infinite update loop."
|
||||
echo "::error::Please investigate the flaky screenshots and add non-deterministic stories to the SKIP set."
|
||||
git diff --cached --stat
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check for baseline updates
|
||||
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.check-baseline-commit.outputs.is_baseline_update != 'true'
|
||||
id: baseline-changes
|
||||
run: |
|
||||
git add packages/kilo-docs/public/img/screenshot-tests/kilo-ui/
|
||||
if git diff --cached --quiet; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup Git Committer
|
||||
if: steps.baseline-changes.outputs.changed == 'true'
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
|
||||
- name: Commit and push new baselines (if any)
|
||||
if: steps.baseline-changes.outputs.changed == 'true'
|
||||
id: commit-baselines
|
||||
run: |
|
||||
git commit -m "chore: update visual regression baselines"
|
||||
git lfs push --all origin
|
||||
git push --no-verify origin "HEAD:${GITHUB_HEAD_REF}"
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Fail if baselines changed
|
||||
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.commit-baselines.outputs.changed == 'true'
|
||||
run: |
|
||||
echo "::error::Visual regression baselines changed. New baselines have been committed to the branch. Please pull and review."
|
||||
exit 1
|
||||
|
||||
- name: Upload test results on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: visual-regression-results
|
||||
path: packages/kilo-ui/test-results/
|
||||
retention-days: 7
|
||||
|
||||
visual-regression-vscode:
|
||||
needs: check-paths
|
||||
if: needs.check-paths.outputs.matched == 'true'
|
||||
name: Visual Regression (kilo-vscode webview) # kilocode_change
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
steps:
|
||||
- name: Checkout (internal)
|
||||
if: needs.check-paths.outputs.can_autocommit == 'true'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
lfs: true
|
||||
# Use github.token for LFS access. The maintainer app is used only for generated commits.
|
||||
token: ${{ github.token }}
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Checkout (read-only)
|
||||
if: needs.check-paths.outputs.can_autocommit != 'true'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Check if HEAD is a baseline update commit
|
||||
if: needs.check-paths.outputs.can_autocommit == 'true'
|
||||
id: check-baseline-commit-vscode
|
||||
run: |
|
||||
COMMIT_MSG=$(git log -1 --format=%s)
|
||||
if [ "$COMMIT_MSG" = "chore: update visual regression baselines" ] || [ "$COMMIT_MSG" = "chore: update kilo-vscode visual regression baselines" ]; then
|
||||
echo "is_baseline_update=true" >> "$GITHUB_OUTPUT"
|
||||
echo "HEAD commit is a baseline update commit — will not auto-commit again."
|
||||
else
|
||||
echo "is_baseline_update=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun # kilocode_change
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
id: playwright-cache-vscode
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-vscode-${{ hashFiles('packages/kilo-vscode/package.json') }}
|
||||
|
||||
- name: Install Playwright browsers
|
||||
if: steps.playwright-cache-vscode.outputs.cache-hit != 'true'
|
||||
run: bunx playwright install chromium
|
||||
working-directory: packages/kilo-vscode
|
||||
|
||||
- name: Install Playwright system deps
|
||||
run: bunx playwright install-deps chromium
|
||||
working-directory: packages/kilo-vscode
|
||||
|
||||
- name: Cache Storybook build
|
||||
id: storybook-cache-vscode
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: packages/kilo-vscode/storybook-static
|
||||
key: storybook-vscode-${{ hashFiles('packages/kilo-vscode/webview-ui/src/**', 'packages/kilo-vscode/.storybook/**', 'packages/kilo-ui/src/**', 'packages/ui/src/**', 'packages/kilo-vscode/package.json') }}
|
||||
|
||||
- name: Build Storybook
|
||||
if: steps.storybook-cache-vscode.outputs.cache-hit != 'true'
|
||||
run: bun run build-storybook
|
||||
working-directory: packages/kilo-vscode
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
- name: Generate baselines and enforce webview accessibility checks
|
||||
run: bun run test:visual:update
|
||||
working-directory: packages/kilo-vscode
|
||||
env:
|
||||
CI: true
|
||||
PLAYWRIGHT_WORKERS: "4"
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
- name: Remove stale baselines for deleted stories
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs'), path = require('path');
|
||||
const idx = JSON.parse(fs.readFileSync('storybook-static/index.json', 'utf8'));
|
||||
const stories = Object.values(idx.entries || idx.stories || {}).filter(s => s.id && !s.id.endsWith('--docs'));
|
||||
const expected = new Set(stories.map(s => { const [c,v] = s.id.split('--'); return c + '/' + v + '-chromium-linux.png'; }));
|
||||
const dir = '../kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression';
|
||||
if (!fs.existsSync(dir)) process.exit(0);
|
||||
for (const component of fs.readdirSync(dir)) {
|
||||
const cdir = path.join(dir, component);
|
||||
if (!fs.statSync(cdir).isDirectory()) continue;
|
||||
for (const file of fs.readdirSync(cdir)) {
|
||||
const rel = component + '/' + file;
|
||||
if (file.endsWith('.png') && !expected.has(rel)) { console.log('Removing stale baseline:', rel); fs.unlinkSync(path.join(cdir, file)); }
|
||||
}
|
||||
}
|
||||
"
|
||||
working-directory: packages/kilo-vscode
|
||||
|
||||
- name: Remove stale baselines for permission-dock-dropdown tests
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const specPath = 'tests/permission-dock-dropdown.spec.ts';
|
||||
const expected = new Set();
|
||||
if (fs.existsSync(specPath)) {
|
||||
const spec = fs.readFileSync(specPath, 'utf8');
|
||||
const re = /toHaveScreenshot\(\[([^\]]+)\]\)/g;
|
||||
let m;
|
||||
while ((m = re.exec(spec)) !== null) {
|
||||
const parts = m[1].split(',').map(s => s.trim().replace(/[\"']/g, ''));
|
||||
const file = parts[parts.length - 1];
|
||||
if (file) expected.add(file.replace(/\.png$/, '-chromium-linux.png'));
|
||||
}
|
||||
}
|
||||
const dir = '../kilo-docs/public/img/screenshot-tests/kilo-vscode/permission-dock-dropdown';
|
||||
if (!fs.existsSync(dir)) process.exit(0);
|
||||
for (const file of fs.readdirSync(dir)) {
|
||||
if (file.endsWith('.png') && !expected.has(file)) {
|
||||
console.log('Removing stale baseline:', file);
|
||||
fs.unlinkSync(dir + '/' + file);
|
||||
}
|
||||
}
|
||||
"
|
||||
working-directory: packages/kilo-vscode
|
||||
|
||||
- name: Check for baseline changes (read-only PRs)
|
||||
if: needs.check-paths.outputs.can_autocommit != 'true'
|
||||
run: |
|
||||
git add packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/
|
||||
if git diff --cached --quiet; then
|
||||
echo "No visual regression detected."
|
||||
else
|
||||
echo "::error::Visual regression detected. Screenshot baselines have changed."
|
||||
echo "::error::Since this PR cannot receive baseline commits automatically, updated screenshots cannot be committed."
|
||||
echo "::error::Please ask a Kilo developer for help updating the screenshots."
|
||||
git diff --cached --stat
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Fail if baselines still changing after auto-update
|
||||
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.check-baseline-commit-vscode.outputs.is_baseline_update == 'true'
|
||||
run: |
|
||||
git add packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/
|
||||
if git diff --cached --quiet; then
|
||||
echo "Baselines are stable after auto-update."
|
||||
else
|
||||
echo "::error::Visual regression baselines changed again after a previous auto-update commit."
|
||||
echo "::error::This indicates non-deterministic screenshots that would cause an infinite update loop."
|
||||
echo "::error::Please investigate the flaky screenshots and add non-deterministic stories to the SKIP set."
|
||||
git diff --cached --stat
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check for baseline updates
|
||||
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.check-baseline-commit-vscode.outputs.is_baseline_update != 'true'
|
||||
id: baseline-changes-vscode
|
||||
run: |
|
||||
git add packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/
|
||||
if git diff --cached --quiet; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup Git Committer
|
||||
if: steps.baseline-changes-vscode.outputs.changed == 'true'
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
|
||||
- name: Commit and push new baselines (if any)
|
||||
if: steps.baseline-changes-vscode.outputs.changed == 'true'
|
||||
id: commit-baselines-vscode
|
||||
run: |
|
||||
git commit -m "chore: update kilo-vscode visual regression baselines"
|
||||
git lfs push --all origin
|
||||
git push --no-verify origin "HEAD:${GITHUB_HEAD_REF}"
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Fail if baselines changed
|
||||
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.commit-baselines-vscode.outputs.changed == 'true'
|
||||
run: |
|
||||
echo "::error::Visual regression baselines changed. New baselines have been committed to the branch. Please pull and review."
|
||||
exit 1
|
||||
|
||||
- name: Upload test results on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: visual-regression-vscode-results
|
||||
path: packages/kilo-vscode/test-results/
|
||||
retention-days: 7
|
||||
@@ -1,71 +0,0 @@
|
||||
on:
|
||||
schedule:
|
||||
- cron: "7,37 * * * *" # every 30 min at :07 and :37
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: opencode-release-watch
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-release:
|
||||
if: vars.OPENCODE_WATCH_ENABLED != 'false' && (github.event_name == 'workflow_dispatch' || github.repository == 'Kilo-Org/kilocode') # kilocode_change
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- id: fetch
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
data=$(gh release view --repo anomalyco/opencode --json tagName,name,url 2>/dev/null || echo '{}')
|
||||
tag=$(echo "$data" | jq -r '.tagName // empty')
|
||||
[ -z "$tag" ] && { echo "tag=" >> "$GITHUB_OUTPUT"; exit 0; }
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
echo "name=$(echo "$data" | jq -r '.name // .tagName')" >> "$GITHUB_OUTPUT"
|
||||
echo "url=$(echo "$data" | jq -r '.url')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- if: steps.fetch.outputs.tag != ''
|
||||
id: cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: .cache/last-seen
|
||||
key: last-seen-${{ steps.fetch.outputs.tag }}
|
||||
restore-keys: last-seen-
|
||||
|
||||
- if: steps.fetch.outputs.tag != '' && steps.cache.outputs.cache-hit != 'true'
|
||||
name: Trigger opencode sync
|
||||
env:
|
||||
URL: ${{ secrets.OPENCODE_WATCH_SYNC_URL }}
|
||||
SECRET: ${{ secrets.OPENCODE_WATCH_SYNC_SECRET }}
|
||||
VERCEL: ${{ secrets.OPENCODE_WATCH_VERCEL_SECRET }}
|
||||
run: |
|
||||
if [ -n "$URL" ] && [ -n "$SECRET" ]; then
|
||||
headers=(-H "x-sync-secret: $SECRET" -H "Content-Type: application/json")
|
||||
[ -n "$VERCEL" ] && headers+=(-H "x-vercel-protection-bypass: $VERCEL")
|
||||
curl -sf -X POST "$URL" "${headers[@]}" -d '{}' || true
|
||||
fi
|
||||
|
||||
- if: steps.fetch.outputs.tag != '' && steps.cache.outputs.cache-hit != 'true'
|
||||
name: Notify Slack
|
||||
uses: slackapi/slack-github-action@v2.0.0
|
||||
with:
|
||||
webhook: ${{ secrets.OPENCODE_WATCH_SLACK_WEBHOOK }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: |
|
||||
name: "${{ steps.fetch.outputs.name }}"
|
||||
tag: "${{ steps.fetch.outputs.tag }}"
|
||||
url: "${{ steps.fetch.outputs.url }}"
|
||||
|
||||
- if: steps.fetch.outputs.tag != '' && steps.cache.outputs.cache-hit != 'true'
|
||||
name: Mark tag as seen
|
||||
run: mkdir -p .cache/last-seen && echo "${{ steps.fetch.outputs.tag }}" > .cache/last-seen/tag.txt
|
||||
|
||||
- if: success() && steps.fetch.outputs.tag != '' && steps.cache.outputs.cache-hit != 'true'
|
||||
name: Save seen-tag cache
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: .cache/last-seen
|
||||
key: last-seen-${{ steps.fetch.outputs.tag }}
|
||||
+2
-40
@@ -1,29 +1,17 @@
|
||||
.husky/_
|
||||
.DS_Store
|
||||
.secrets/
|
||||
node_modules
|
||||
.worktrees
|
||||
.sst
|
||||
.env
|
||||
.env.local
|
||||
.idea/*
|
||||
!.idea/gradle.xml
|
||||
!.idea/.run
|
||||
.idea/*.iml
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
# use vscode workspaces with peacock to have many vscodes with the same repo:
|
||||
*.code-workspace
|
||||
.idea
|
||||
.vscode
|
||||
.codex
|
||||
*~
|
||||
playground
|
||||
tmp
|
||||
dist
|
||||
ts-dist
|
||||
storybook-static
|
||||
.turbo
|
||||
**/.serena
|
||||
.serena/
|
||||
@@ -37,36 +25,10 @@ a.out
|
||||
target
|
||||
.scripts
|
||||
.direnv/
|
||||
packages/kilo-jetbrains/build-tasks/.gradle/
|
||||
|
||||
# Local dev files
|
||||
opencode-dev
|
||||
.kilo-dev/
|
||||
UPCOMING_CHANGELOG.md
|
||||
logs/
|
||||
*.bun-build
|
||||
|
||||
# Telemetry ID
|
||||
telemetry-id
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
# Kilo
|
||||
.kilo/.gitignore
|
||||
.kilo/package.json
|
||||
.kilo/package-lock.json
|
||||
.kilo/pnpm-lock.yaml
|
||||
.kilo/bun.lock
|
||||
.kilo/yarn.lock
|
||||
.kilo/node_modules
|
||||
.kilo/plans/*upstream-merge-report-*.md
|
||||
.kilocode/.gitignore
|
||||
.kilocode/package.json
|
||||
.kilocode/package-lock.json
|
||||
.kilocode/pnpm-lock.yaml
|
||||
.kilocode/bun.lock
|
||||
.kilocode/yarn.lock
|
||||
.kilocode/node_modules
|
||||
|
||||
# Test Artifacts
|
||||
packages/app/.artifacts/
|
||||
packages/opencode/.artifacts/
|
||||
|
||||
+1
-31
@@ -17,34 +17,4 @@ if (process.versions.bun !== expectedBunVersion) {
|
||||
console.warn(`Warning: Bun version ${process.versions.bun} differs from expected ${expectedBunVersion}`);
|
||||
}
|
||||
'
|
||||
|
||||
# kilocode_change start
|
||||
jetbrains=0
|
||||
zero=0000000000000000000000000000000000000000
|
||||
|
||||
while read -r local_ref local_oid remote_ref remote_oid; do
|
||||
if [ "$local_oid" = "$zero" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
base="$remote_oid"
|
||||
if [ "$remote_oid" = "$zero" ]; then
|
||||
base=$(git merge-base "$local_oid" "$1/main" 2>/dev/null || git hash-object -t tree /dev/null)
|
||||
fi
|
||||
|
||||
if ! git diff --quiet "$base" "$local_oid" -- . \
|
||||
':(exclude).changeset/**' \
|
||||
':(exclude)packages/kilo-vscode/**' \
|
||||
':(exclude)packages/kilo-docs/**'; then
|
||||
jetbrains=1
|
||||
fi
|
||||
done
|
||||
|
||||
bun typecheck --filter='!@kilocode/kilo-jetbrains'
|
||||
|
||||
if [ "$jetbrains" -eq 1 ]; then
|
||||
bun typecheck --filter='@kilocode/kilo-jetbrains'
|
||||
else
|
||||
echo "Skipping JetBrains typecheck (no relevant changes)"
|
||||
fi
|
||||
# kilocode_change end
|
||||
bun typecheck
|
||||
|
||||
Generated
-32
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<compositeConfiguration>
|
||||
<compositeBuild compositeDefinitionSource="SCRIPT">
|
||||
<builds>
|
||||
<build path="$PROJECT_DIR$/packages/kilo-jetbrains/build-tasks" name="build-tasks">
|
||||
<projects>
|
||||
<project path="$PROJECT_DIR$/packages/kilo-jetbrains/build-tasks" />
|
||||
</projects>
|
||||
</build>
|
||||
</builds>
|
||||
</compositeBuild>
|
||||
</compositeConfiguration>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$/packages/kilo-jetbrains" />
|
||||
<option name="gradleJvm" value="#JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$/packages/kilo-jetbrains" />
|
||||
<option value="$PROJECT_DIR$/packages/kilo-jetbrains/backend" />
|
||||
<option value="$PROJECT_DIR$/packages/kilo-jetbrains/build-tasks" />
|
||||
<option value="$PROJECT_DIR$/packages/kilo-jetbrains/frontend" />
|
||||
<option value="$PROJECT_DIR$/packages/kilo-jetbrains/shared" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,421 +0,0 @@
|
||||
---
|
||||
description: Resolve upstream opencode merge conflicts interactively
|
||||
mode: primary
|
||||
permission:
|
||||
read: ask
|
||||
edit: ask
|
||||
webfetch: ask
|
||||
bash:
|
||||
"*": ask
|
||||
"git status *": allow
|
||||
"git log *": allow
|
||||
"git diff *": allow
|
||||
"git show *": allow
|
||||
"git ls-files *": allow
|
||||
"git ls-tree *": allow
|
||||
"git grep *": allow
|
||||
"git hash-object *": allow
|
||||
"git remote -v *": allow
|
||||
"git rev-parse *": allow
|
||||
"git merge-base *": allow
|
||||
"git show-ref *": allow
|
||||
"git worktree list": allow
|
||||
"git branch --show-current": allow
|
||||
"grep *": allow
|
||||
"rg *": allow
|
||||
"head *": allow
|
||||
"tail *": allow
|
||||
"cat *": allow
|
||||
"wc *": allow
|
||||
"ls *": allow
|
||||
"pwd *": allow
|
||||
"diff *": allow
|
||||
"gh pr view *": allow
|
||||
"gh run view *": allow
|
||||
"gh api \"repos/sst/opencode/commits/dev\" *": allow
|
||||
"axiom *": allow
|
||||
"bun test *": allow
|
||||
"bun run typecheck *": allow
|
||||
"bun run lint *": allow
|
||||
"bun run script/check-opencode-annotations.ts *": allow
|
||||
"script/upstream/find-conflict-markers.sh *": allow
|
||||
"./script/upstream/find-conflict-markers.sh *": allow
|
||||
---
|
||||
|
||||
Resolve the manual part of an upstream merge.
|
||||
|
||||
**Do not load the `kilocode-merge-minimizer` skill.** That skill is for
|
||||
authoring new Kilo changes against shared upstream files; during an upstream
|
||||
merge it gives the wrong guidance (it nudges toward extracting Kilo logic out
|
||||
of conflict regions, which is exactly the opposite of what merge resolution
|
||||
needs). Follow the rules in this agent file instead.
|
||||
|
||||
The user will provide the upstream version (for example `v1.1.50` or `1.1.50`)
|
||||
in their first message. If they don't, infer it from the current branch name,
|
||||
from `upstream-merge-report-<version>.md`, or from the newest relevant report
|
||||
file.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Inspect the current merge state
|
||||
|
||||
- `git status --short`
|
||||
- `git diff --name-only --diff-filter=U`
|
||||
- `upstream-merge-report-<version>.md` when present
|
||||
- `.worktrees/opencode-merge/auto-merge` for the automated merge snapshot when present
|
||||
|
||||
### 2. Read every conflicted file end-to-end before planning
|
||||
|
||||
Use `script/upstream/find-conflict-markers.sh <file>` to jump to each region,
|
||||
then read enough surrounding lines to understand the code — not just the
|
||||
conflict hunk. Specifically check:
|
||||
|
||||
- is this a plain 3-way on a single expression, or a structural refactor?
|
||||
- does upstream rename/move something that invalidates a HEAD-only declaration?
|
||||
- does a `kilocode_change` marker in HEAD encode a bug fix, a feature, or a
|
||||
defensive check?
|
||||
- is the conflicted block referenced by *non-conflicted* code elsewhere in the
|
||||
same file (imports, signatures, call sites) that will break if we drop it?
|
||||
|
||||
When HEAD includes a non-obvious Kilo-specific wrapper (e.g. a helper in
|
||||
`packages/opencode/src/kilocode/`), find out why it exists before deciding to
|
||||
keep or bypass it:
|
||||
|
||||
```bash
|
||||
git log --all --oneline -S "<symbol>" -- packages/opencode/src/kilocode/
|
||||
git log --all --oneline -- <kilo-file>
|
||||
```
|
||||
|
||||
Look at the commit message and any PR reference. "We wrote our own because of
|
||||
PR #NNNN" is a real constraint; "we wrote our own because of a typo" is not.
|
||||
|
||||
When upstream narrows an externally-visible compatibility list (models,
|
||||
providers, routes, config keys, file formats), verify the intent from upstream
|
||||
PRs, issues, release notes, or current docs before dropping entries. Treat
|
||||
silent list shrinkage during a refactor as suspicious until proven intentional.
|
||||
|
||||
### 3. Write a plan in chat and get approval
|
||||
|
||||
For every conflicted file (and any adjacent file the resolution forces you to
|
||||
touch — see §6) include:
|
||||
|
||||
- expected resolution kind: `hybrid`, `take-ours`, `take-theirs`, `regenerated`,
|
||||
`removed`, `renamed`, or `other`
|
||||
- risk level: `low`, `medium`, or `high`
|
||||
- one-sentence rationale (what Kilo behaviour is preserved, what upstream
|
||||
feature is adopted, what is dropped)
|
||||
- verification commands you expect to run (targeted tests, typecheck)
|
||||
|
||||
Group files by risk level. Ask the user which batch to start with. You can
|
||||
resolve an entire `low` batch in one pass if the user approves the batch, but
|
||||
resolve `medium` and `high` files one at a time.
|
||||
|
||||
**Do not resolve a file until the user has approved that file's (or batch's)
|
||||
strategy.**
|
||||
|
||||
### 4. Before every edit, explain reasoning before showing the diff
|
||||
|
||||
The user needs to review intent, not just the raw change. For each file, in
|
||||
order:
|
||||
|
||||
1. Show the conflict's surrounding context (10–30 lines around each conflict
|
||||
region, in chat).
|
||||
2. Explain what each of the three sides (HEAD, merge-base, upstream) is doing.
|
||||
3. State which Kilo behaviour must survive and why (reference PR numbers /
|
||||
`kilocode_change` comments when possible).
|
||||
4. State the resolution and why it is better than the alternatives.
|
||||
5. Then apply the edit. The tool will display the diff — the user only has to
|
||||
verify the diff matches the reasoning.
|
||||
|
||||
Do not lead with the diff. A diff without reasoning forces the user to
|
||||
reverse-engineer the decision.
|
||||
|
||||
### 5. Apply resolution rules
|
||||
|
||||
Reference worktrees when present:
|
||||
|
||||
- `.worktrees/opencode-merge/opencode` — pristine upstream tree
|
||||
- `.worktrees/opencode-merge/kilo-main` — Kilo base snapshot
|
||||
- `.worktrees/opencode-merge/auto-merge` — automated merge snapshot (original
|
||||
conflict reference)
|
||||
|
||||
Apply in order:
|
||||
|
||||
- prefer upstream code and architecture whenever compatible with Kilo behaviour
|
||||
- preserve Kilo-specific behaviour marked with `kilocode_change`
|
||||
- keep `kilocode_change` markers around Kilo-specific code in shared opencode
|
||||
files
|
||||
- when upstream refactors a region that HEAD had annotated with
|
||||
`kilocode_change`, **check whether the marker encodes a bug fix or a feature
|
||||
delta**. Bug fixes (missing `await`, defensive null-check, error capture)
|
||||
usually need to be re-applied on top of the upstream refactor. Example from
|
||||
v1.14.30: `Workspace.isSyncing` was missing an `await` — upstream's Effect
|
||||
refactor reintroduced the same bug, so we had to port the fix into the new
|
||||
`Effect.gen` block.
|
||||
- when a `take-theirs` drops a line that was the target of a Kilo pre-filter,
|
||||
the upstream line may be actively wrong for Kilo — e.g. an inner `continue`
|
||||
filter whose condition collides with an outer filter Kilo added. Re-read the
|
||||
surrounding 20 lines before committing to `take-theirs`.
|
||||
- if Kilo-specific code must be refactored to fit new upstream architecture,
|
||||
explain the refactor in the final summary
|
||||
- if upstream moved the relevant logic to another file, port the Kilo behaviour
|
||||
there and list both paths in the final summary. Verify the new file already
|
||||
carries the Kilo-renamed symbols (e.g. `x-kilo-directory`) by diffing against
|
||||
pristine upstream.
|
||||
- if upstream extracts shared policy into a helper, move Kilo-specific additions
|
||||
into the helper when possible instead of keeping a pre-check at the old call
|
||||
site. The extracted helper should stay the source of truth for all callers.
|
||||
- if upstream deleted a file, analyse whether the Kilo behaviour should be
|
||||
ported elsewhere or removed rather than restoring the deleted file
|
||||
- if tests fail only because upstream intentionally removed behaviour, remove
|
||||
or update the obsolete tests rather than adding the old file back
|
||||
- do not modify unrelated files
|
||||
|
||||
When removing code that existed in one side of a conflict, prefer
|
||||
**commenting it out with `kilocode_change` markers** over deletion when the
|
||||
surrounding structure (an `if`, a loop) still makes sense. That keeps the
|
||||
intent visible to the next merger. Example:
|
||||
|
||||
```ts
|
||||
} else if (input?.scope !== "project" && !Flag.KILO_EXPERIMENTAL_WORKSPACES) {
|
||||
// kilocode_change start - directory filtering handled by KiloSession.filters above
|
||||
// if (input?.directory) {
|
||||
// conditions.push(eq(SessionTable.directory, input.directory))
|
||||
// }
|
||||
// kilocode_change end
|
||||
}
|
||||
```
|
||||
|
||||
Use `TODO:` not `NOTE:` for follow-ups. `TODO` is searchable and implies an
|
||||
owner will act on it; `NOTE` reads as permanent commentary.
|
||||
|
||||
### 6. Look for adjacent files the conflict forces you to touch
|
||||
|
||||
Upstream restructures sometimes split one file into several (e.g. `permission.ts`
|
||||
→ `groups/permission.ts` + `handlers/permission.ts`). Only the *renamed* file
|
||||
shows up in `git diff --diff-filter=U`; the new sibling may need a Kilo feature
|
||||
ported in too. After resolving the flagged file, check:
|
||||
|
||||
- files that import from the resolved file — do they compile?
|
||||
- files at paths implied by new imports (e.g. `../middleware/*`, `./handlers/*`)
|
||||
- `kilocode_change` comments in the *auto-merge* snapshot that didn't end up in
|
||||
the working tree because the hosting file was renamed
|
||||
|
||||
Add any such files to the plan as `hybrid` or `take-ours` with the same
|
||||
approval flow.
|
||||
|
||||
### 6.5. Scan auto-merged files for latent bugs
|
||||
|
||||
Files not in `--diff-filter=U` merged without conflict markers but may still
|
||||
be broken. Check every auto-merged file for:
|
||||
|
||||
- **Duplicate declarations in the same scope.** If both sides added equivalent
|
||||
code independently, auto-merge keeps both. Grep touched functions for
|
||||
repeated identifiers before trusting the merge.
|
||||
- **Duplicate keys in config/manifest files.** If both sides added the same
|
||||
entry to a shared manifest (dependencies, scripts, workflow lists), the
|
||||
merged file may have the key twice. This often breaks install/setup before
|
||||
any test runs — a cheap early win to scan for.
|
||||
- **Orphaned imports and references.** A rename upstream may leave a Kilo
|
||||
callsite pointing at a now-missing export. Run full typecheck from the repo
|
||||
root; references that silently survived the merge surface there.
|
||||
- **Partial auto-merges.** Upstream may have refactored a region Kilo
|
||||
deliberately stubbed out (commented blocks, removed fallbacks). If the
|
||||
auto-merge pulled in references to names that only exist in the removed
|
||||
path, the file compiles upstream but breaks on Kilo.
|
||||
|
||||
### 7. Verify each resolution before moving on
|
||||
|
||||
- confirm `script/upstream/find-conflict-markers.sh <file>` prints nothing
|
||||
- read the final file region (the new shape after edit) and sanity-check imports
|
||||
- for apparently-unused symbols upstream introduced, `grep` the file and the
|
||||
rest of the package before deleting — they may be called from non-conflicted
|
||||
code elsewhere. Example: `isTheme` in `theme.tsx` looked unused at the
|
||||
resolution site but was called twice further down.
|
||||
- run the smallest relevant check (single `bun test` file, or `bun run
|
||||
typecheck` in the touched package)
|
||||
- summarise the exact resolution, tradeoff, and verification result in chat
|
||||
- ask the user to approve the resolved file before staging it or resolving the
|
||||
next one (for `medium` / `high`; `low` batches can be staged together)
|
||||
|
||||
### 8. Run the full checks once everything is resolved
|
||||
|
||||
- `git diff --name-only --diff-filter=U` returns empty
|
||||
- `bun run typecheck` from `packages/opencode/` (targeted) and from repo root
|
||||
(catches non-conflicted call-site breakage)
|
||||
- relevant targeted tests. Tests that hang or time out in an unrelated part of
|
||||
the graph may be pre-existing — note them, don't block the merge on them
|
||||
- `bun run script/check-opencode-annotations.ts` if `packages/opencode/` shared
|
||||
files changed. Note that this tool compares against the merge base via `HEAD`
|
||||
and will be silent until the merge commit lands
|
||||
- other CI guards that touched files imply (knip for `kilo-vscode/`,
|
||||
`check-kilocode-change`, source-links, visual regression,
|
||||
`script/check-forbidden-strings.ts`)
|
||||
- if you encounter a hardcoded upstream URL, repo path, or attribution string
|
||||
during conflict resolution that obviously shouldn't ship in Kilo (e.g. another
|
||||
`https://opencode.ai/...` link, an `anomalyco/opencode` reference, an
|
||||
attribution header naming "opencode"), suggest adding a literal pattern for
|
||||
it to `script/check-forbidden-strings.ts` in the merge summary so future
|
||||
merges catch it automatically. Don't add it silently mid-merge — flag it for
|
||||
the user.
|
||||
|
||||
### 9. Commit with the standard message
|
||||
|
||||
Per `script/upstream/README.md`:
|
||||
|
||||
```bash
|
||||
git commit -m "resolve merge conflicts"
|
||||
```
|
||||
|
||||
The default `git merge` auto-message (`Merge branch '…' into …`) is also fine,
|
||||
but `resolve merge conflicts` is the convention for these PRs.
|
||||
|
||||
### 9.5. Handle downstream API renames as separate commits
|
||||
|
||||
Upstream often renames exported APIs. The rename itself auto-merges cleanly in
|
||||
shared code, but the change cascades into Kilo-only files (kilocode tests,
|
||||
kilo-specific source, plugins) that still reference the old symbol. Those
|
||||
files don't appear in `--diff-filter=U` because their own content didn't
|
||||
conflict.
|
||||
|
||||
Keep the behavioural merge commit focused on resolution decisions. Land the
|
||||
cascade in one or more follow-up commits:
|
||||
|
||||
- after the merge commit, run full repo typecheck and collect every "cannot
|
||||
find name" / "property does not exist" error
|
||||
- bulk-rename with a mechanical transform when the rename is one-to-one
|
||||
- restructure or parameter-thread when upstream changed semantics, not just
|
||||
the name (e.g. moved a helper behind a dependency-injected surface, so
|
||||
callers now need the injected handle)
|
||||
- split large downstream refactors into their own commits with messages that
|
||||
name the rename
|
||||
|
||||
Reviewers can then skim the behavioural commit without untangling mechanical
|
||||
rename noise from merge decisions.
|
||||
|
||||
### 9.6. Handle upstream-added tests that diverge from Kilo
|
||||
|
||||
Upstream sometimes adds tests that encode design contracts Kilo intentionally
|
||||
breaks. These auto-merge cleanly and then fail. Three resolution patterns:
|
||||
|
||||
- **Rewrite the test** when the test is a contract assertion and Kilo has a
|
||||
different but equally valid contract. Invert or adjust the assertion with a
|
||||
`kilocode_change` marker explaining the divergence.
|
||||
- **Skip the test** when the test relies on patterns that Kilo has replaced
|
||||
(interception seams that are bypassed by dependency injection, fixture
|
||||
helpers bound to a removed API, assumptions about serialization shape that
|
||||
Kilo's extensions break). Mark with `kilocode_change` and a rationale
|
||||
explaining what would need to change for the test to run.
|
||||
- **Delete the test** when it covers functionality Kilo deliberately removed
|
||||
(fallback paths, deprecated endpoints, products Kilo doesn't ship). Note
|
||||
the deletion in the PR body.
|
||||
|
||||
Never silently delete; always leave a breadcrumb. A future reviewer should be
|
||||
able to understand why this one upstream test is treated differently.
|
||||
|
||||
### 10. Resync version strings in a separate commit
|
||||
|
||||
Upstream stamps its own version into shared files — notably
|
||||
`packages/extensions/zed/extension.toml` (version field + 5 Kilo-Org download
|
||||
URLs), and any `package.json` that upstream bumped in the same release window.
|
||||
After the merge this leaves parts of the tree pointing at upstream's version
|
||||
(e.g. `1.14.30`), whose release tag does not exist on Kilo's pipeline, so the
|
||||
Zed download URLs silently 404.
|
||||
|
||||
Fix this in a dedicated commit *after* `resolve merge conflicts`:
|
||||
|
||||
```bash
|
||||
bun run script/sync-versions.ts # uses root package.json version
|
||||
# or, to target an explicit version:
|
||||
bun run script/sync-versions.ts 7.2.41
|
||||
git add -A
|
||||
git commit -m "chore: resync versions after upstream merge"
|
||||
```
|
||||
|
||||
The script rewrites every top-level `"version"` in `package.json` files
|
||||
(excluding `node_modules`, hidden dirs, and `packages/kilo-jetbrains/` which
|
||||
tracks its own cadence), plus the Zed extension toml. It is idempotent — rerun
|
||||
it any time to rebase the version back onto Kilo main (useful during
|
||||
long-running upstream merges where `main` releases in the meantime).
|
||||
|
||||
Keeping this in its own commit makes reviewers' job easier: the merge commit
|
||||
only contains behavioural resolutions, and the version resync is a trivial
|
||||
diff they can skim in one glance.
|
||||
|
||||
### 11. Write the PR body
|
||||
|
||||
Structure the description so reviewers can skim:
|
||||
|
||||
- **Non-trivial merge decisions**: a short section per file (or group of
|
||||
related files) that required more than a mechanical `take-ours`/`take-theirs`.
|
||||
Focus on *what Kilo behaviour survived* and *what upstream features were
|
||||
adopted*. Link to Kilo PRs when a `kilocode_change` encodes a specific fix.
|
||||
- **Notable auto-merged changes**: new columns, new helper files, renamed
|
||||
middleware — anything reviewers should eyeball even though git didn't flag
|
||||
it.
|
||||
- **What to test**: explicit, scenario-level test steps for each non-trivial
|
||||
change. Don't list tests; list *user-visible behaviour* so a tester who
|
||||
doesn't read the diff can exercise it.
|
||||
- **CI guards to watch**: typecheck, knip, annotation check, visual regression.
|
||||
- **Follow-ups**: any `TODO:` you left in code, as a bullet list with links.
|
||||
|
||||
## User-approval checkpoints
|
||||
|
||||
Every manual merge decision requires explicit user approval **before applying**
|
||||
and **again after verification**. Be especially cautious when a decision is
|
||||
destructive, changes auth, billing, data deletion, public API compatibility,
|
||||
config schema behaviour, migrations, provider routing, or security posture.
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- Auto-merged code can reference declarations that still live inside conflict
|
||||
blocks.
|
||||
- Related sibling files can need edits even when they are not listed as
|
||||
unmerged — especially after upstream structural splits.
|
||||
- `renamed` should be used only when behaviour moves to a different file.
|
||||
- Function signatures can drift across conflict boundaries (args added, return
|
||||
types widened). Grep for every call site before finalising.
|
||||
- Full-repo typecheck is the catch-all for non-conflicted call-site breakage.
|
||||
- Upstream can reintroduce bugs a Kilo `kilocode_change` had already fixed —
|
||||
during big refactors check every `kilocode_change` the refactor touched.
|
||||
- "Take-theirs" on an inner conditional is often wrong when Kilo added an outer
|
||||
pre-filter whose whole point was to widen what makes it to the inner block.
|
||||
- Apparently-unused upstream-added declarations may be called from
|
||||
non-conflicted code elsewhere. Grep before deleting.
|
||||
- Stricter DOM lib types (upstream TS upgrade) can surface latent casting
|
||||
issues around `WebSocket.send`, `Headers`, etc. — prefer narrowing the Kilo
|
||||
type over adding `any` casts.
|
||||
- Auto-merge can duplicate the same declaration twice in one scope when both
|
||||
sides added equivalent code independently. Silent for git, caught by
|
||||
typecheck. Same hazard for duplicated object keys in config/manifest files —
|
||||
those can break install before any test runs.
|
||||
- Kilo code may rely on ambient context (async-local storage, globally-set
|
||||
flags, process env) being populated at a lifecycle moment that upstream
|
||||
refactors away. If Kilo behaviour reads ambient state during init, forked
|
||||
work, or event handlers, check the refactor still establishes that state at
|
||||
the right time. Fix by restoring the ambient state, or by threading the
|
||||
needed value through explicitly.
|
||||
- Tests that intercept via process-global or module-global spies can become
|
||||
no-ops after upstream moves the intercepted code path through dependency
|
||||
injection. The production code no longer touches the spied symbol. Fixing
|
||||
the test usually means injecting a mock at the new seam rather than tweaking
|
||||
the spy.
|
||||
- When Kilo extends a shared data shape with extra optional fields, different
|
||||
serialization paths for that shape can diverge on whether missing values are
|
||||
omitted or emitted as null. Parity tests between two such paths break on
|
||||
every Kilo addition — audit the encoding assumption before adding fields.
|
||||
- Rule ordering in allowlist/permission evaluation is usually last-match-wins.
|
||||
Re-declaring a catch-all rule "for safety" in a later ruleset silently
|
||||
overrides more specific allow rules from an earlier ruleset. Treat a
|
||||
redundant catch-all as destructive, not defensive.
|
||||
- Upstream-added tests can encode a design contract Kilo deliberately breaks.
|
||||
The test passes upstream because upstream doesn't share Kilo's requirement.
|
||||
Decide between refactoring Kilo to match the upstream contract or rewriting
|
||||
the test to assert Kilo's divergent contract — with a `kilocode_change`
|
||||
marker explaining the divergence.
|
||||
- CI and local can show different test failures. Tests that read user-local
|
||||
state (home dir, global config, auth tokens) pass in one environment but
|
||||
fail in the other. A green local run does not imply green CI.
|
||||
- Dependency manifests and lockfiles move together. When the merge edits one,
|
||||
regenerate and commit the other in the same change — otherwise CI breaks on
|
||||
the follow-up setup step.
|
||||
@@ -1,81 +0,0 @@
|
||||
---
|
||||
description: Review an upstream merge PR and write reports
|
||||
---
|
||||
|
||||
Review $1.
|
||||
|
||||
Branch off so we have all the code for the reviewed PR. Then run the following reviews in parallel, using one subagent per review, and save each result in the named markdown file at the repository root. A human will read those files, so if in doubt add a finding for human verification.
|
||||
|
||||
When all subagents are done, commit the report files and create a draft PR with the reviewed PR branch as base.
|
||||
|
||||
Do not include exhaustive per-file checklists in the report files. Summarize the scope and methodology, then list only findings, notable non-findings, command outputs, and limitations.
|
||||
|
||||
## KILOCODE_CHANGE_MARKERS.md
|
||||
|
||||
Review carefully, file by file, whether we accidentally removed any `kilocode_change` marker.
|
||||
|
||||
Get the full list of files changed in the reviewed PR. Check each changed file. For each file, compare both our `main` branch and the upstream-merged version in the reviewed PR. Determine whether any marker removal, marker move, or Kilo-specific change makes sense, and comment on that.
|
||||
|
||||
Do not include a full "Files Checked" section. Mention the number of changed files checked, but only list files that have findings or need human verification.
|
||||
|
||||
## INFRASTRUCTURE_CHANGE.md
|
||||
|
||||
Review whether this PR adds, removes, or changes any infrastructure, such as GitHub Actions, CI config, release/deploy scripts, Docker/build infrastructure, package manager/workspace infrastructure, repository automation, issue templates, changelog automation, or generated SDK/build automation.
|
||||
|
||||
We want to merge upstream code but keep our own infrastructure, so flag anything infrastructure-related. When in doubt, add a finding and say a human should check manually.
|
||||
|
||||
Do not include a full "Files Checked" section. Only list infrastructure-relevant files and findings.
|
||||
|
||||
## OPENCODE_MENTIONS.md
|
||||
|
||||
Check whether the merge now mentions OpenCode somewhere user-facing instead of Kilo, or links to OpenCode web properties.
|
||||
|
||||
Focus on UI strings, docs, help text, package metadata shown to users, URLs, CLI output, config docs, generated SDK/OpenAPI descriptions, and error messages.
|
||||
|
||||
## UNNECESSARY_MARKERS.md
|
||||
|
||||
Check whether any merged files now use `kilocode_change` markers without any actual difference to upstream.
|
||||
|
||||
Use `script/upstream/find-reset-candidates.ts --dry-run` to see whether any files changed in the PR are actually now identical to upstream. If you find candidates, verify them with `script/upstream/reset-to-upstream.ts --dry-run`.
|
||||
|
||||
## BROKEN_PIPELINE_CHAINS.md
|
||||
|
||||
Review this PR for broken end-to-end chains where our custom functionality requires changes across multiple files or layers, but the merge may have removed or altered an intermediate step. The code may still compile, so these issues can be silent.
|
||||
|
||||
Look for cases like:
|
||||
|
||||
- A parameter that is set but never read.
|
||||
- A field that is populated but never passed through.
|
||||
- An event that is emitted but no longer handled.
|
||||
- A config option that is defined but never propagated to where it is used.
|
||||
- A type definition extended on one side but not consumed on the other.
|
||||
|
||||
For each `kilocode_change` in this PR, trace the full chain: where the value or behavior is introduced, where it needs to flow through, and where it is ultimately consumed. Verify every link in the chain still exists after the merge.
|
||||
|
||||
Pay special attention to:
|
||||
|
||||
- Props or parameters passed through multiple component or function layers.
|
||||
- Values written to state, context, or storage that are read elsewhere.
|
||||
- Message types, events, or IPC handlers where sender and receiver must match.
|
||||
- Configuration or feature flags defined in one place and checked in another.
|
||||
- Type definitions extended on one side but consumed on the other.
|
||||
|
||||
When in doubt, add a finding. A human will verify it. Compiling code is not proof the chain is intact.
|
||||
|
||||
## CONFIG_REGRESSION.md
|
||||
|
||||
Check whether this PR introduces or re-introduces fallback logic for `opencode` config files, or accidentally breaks code that now correctly expects only `.kilo`-based configuration.
|
||||
|
||||
Kilo removed fallback support for `opencode` config directories. Look for:
|
||||
|
||||
- Any new or restored code that reads from `opencode` config paths.
|
||||
- Upstream additions to config discovery, loading, or path resolution that add `opencode` fallback candidates we stripped.
|
||||
- Changes that break `.kilo`-only config lookup by removing or reordering it in a multi-path search.
|
||||
|
||||
When in doubt, add a finding. A human should verify config path changes manually.
|
||||
|
||||
## TESTS.md
|
||||
|
||||
Check whether this PR removed any Kilo-specific tests.
|
||||
|
||||
Kilo-specific tests may live in paths containing `kilo` or `kilocode`, or may include Kilo-specific assertions, fixtures, or `kilocode_change` markers.
|
||||
@@ -1,37 +0,0 @@
|
||||
# Sample Plan With Todos
|
||||
|
||||
## Goal
|
||||
Create a clear sample plan that breaks work into actionable todos.
|
||||
|
||||
## Todos
|
||||
|
||||
1. Define the objective
|
||||
- Clarify the desired outcome.
|
||||
- Identify the target audience.
|
||||
- List success criteria.
|
||||
|
||||
2. Gather requirements
|
||||
- Collect relevant inputs.
|
||||
- Note constraints and assumptions.
|
||||
- Identify dependencies.
|
||||
|
||||
3. Break down the work
|
||||
- Split the goal into smaller tasks.
|
||||
- Prioritize tasks by importance.
|
||||
- Estimate effort for each task.
|
||||
|
||||
4. Execute the tasks
|
||||
- Complete the highest-priority items first.
|
||||
- Track progress as todos are finished.
|
||||
- Adjust the plan if new information appears.
|
||||
|
||||
5. Review and validate
|
||||
- Check that all todos are complete.
|
||||
- Verify the result matches the goal.
|
||||
- Document any follow-up work.
|
||||
|
||||
## Acceptance Criteria
|
||||
- The plan has a clear goal.
|
||||
- The plan includes a list of actionable todos.
|
||||
- The todos are ordered logically.
|
||||
- The plan includes a way to confirm completion.
|
||||
@@ -1,38 +0,0 @@
|
||||
# Sample Plan With Todos
|
||||
|
||||
## Goal
|
||||
Create a simple sample plan that demonstrates how to organize work into clear, actionable todos.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. Define the objective
|
||||
- State the desired outcome in one sentence.
|
||||
- Identify who the plan is for.
|
||||
- List the criteria that indicate success.
|
||||
|
||||
2. Gather requirements
|
||||
- Capture relevant inputs and context.
|
||||
- Note constraints, assumptions, and dependencies.
|
||||
- Identify any missing information that may affect execution.
|
||||
|
||||
3. Break down the work
|
||||
- Split the objective into smaller tasks.
|
||||
- Order tasks by dependency and priority.
|
||||
- Keep each todo specific enough to complete independently.
|
||||
|
||||
4. Execute the todos
|
||||
- Work through the highest-priority tasks first.
|
||||
- Mark todos complete as progress is made.
|
||||
- Update the plan if new information changes the scope.
|
||||
|
||||
5. Review the result
|
||||
- Confirm every todo has been addressed.
|
||||
- Verify the result matches the original objective.
|
||||
- Record any follow-up work separately.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- The plan has a clear goal.
|
||||
- The plan includes actionable todos.
|
||||
- The todos are ordered logically.
|
||||
- The plan includes a review step to confirm completion.
|
||||
@@ -1,37 +0,0 @@
|
||||
# Sample Plan: Add Todo-Based Workflow
|
||||
|
||||
## Goal
|
||||
Create a small, traceable workflow for implementing a feature with clear todos, validation steps, and completion criteria.
|
||||
|
||||
## Scope
|
||||
- Identify the affected package or feature area.
|
||||
- Make the smallest correct code change.
|
||||
- Add or update tests only where they verify behavior.
|
||||
- Run the smallest relevant checks before completion.
|
||||
|
||||
## Todos
|
||||
- [ ] Inspect the relevant files and existing patterns.
|
||||
- [ ] Confirm whether the change belongs in Kilo-owned code or shared upstream code.
|
||||
- [ ] Implement the minimal code change.
|
||||
- [ ] Add or update targeted tests if behavior changes.
|
||||
- [ ] Run formatting or linting if the touched package requires it.
|
||||
- [ ] Run the smallest relevant typecheck or test command.
|
||||
- [ ] Fix any failures introduced by the change.
|
||||
- [ ] Summarize changed files and verification results.
|
||||
|
||||
## Implementation Notes
|
||||
- Prefer Kilo-owned directories for Kilo-specific behavior.
|
||||
- If shared upstream files must be edited, keep the change narrow and add `kilocode_change` markers where required.
|
||||
- Avoid broad refactors unless they are necessary for the requested behavior.
|
||||
- Preserve existing style, naming, and package conventions.
|
||||
|
||||
## Verification
|
||||
- Run the targeted test for the changed behavior when available.
|
||||
- Run the package typecheck if TypeScript code changes.
|
||||
- Run any repo-specific guard required by touched files, such as annotation or source-link checks.
|
||||
|
||||
## Completion Criteria
|
||||
- The requested behavior is implemented.
|
||||
- Tests or checks relevant to the change pass, or any inability to run them is documented.
|
||||
- No unrelated files are modified.
|
||||
- The final response includes a concise summary and verification status.
|
||||
@@ -1,35 +0,0 @@
|
||||
# Sample Plan
|
||||
|
||||
## Goal
|
||||
Create a small example feature or change using a clear implementation workflow and tracked todo list.
|
||||
|
||||
## Scope
|
||||
- Add or update a minimal code path.
|
||||
- Add focused tests for the changed behavior.
|
||||
- Run the smallest relevant verification command.
|
||||
- Summarize the result and any follow-up work.
|
||||
|
||||
## Todo
|
||||
- [ ] Inspect the relevant files and existing patterns.
|
||||
- [ ] Identify the smallest safe implementation approach.
|
||||
- [ ] Make the code change.
|
||||
- [ ] Add or update tests for the behavior.
|
||||
- [ ] Run targeted tests or typecheck.
|
||||
- [ ] Fix any failures introduced by the change.
|
||||
- [ ] Provide a concise final summary with verification results.
|
||||
|
||||
## Implementation Notes
|
||||
- Prefer the smallest correct change over broad refactors.
|
||||
- Preserve existing style and naming conventions.
|
||||
- Avoid touching unrelated files.
|
||||
- If shared upstream-owned files are involved, use the repository’s `kilocode_change` marker guidance.
|
||||
|
||||
## Verification
|
||||
Run the most specific applicable check for the touched area, such as:
|
||||
- CLI: `bun test ./path/to/test.ts` from `packages/opencode/`
|
||||
- VS Code extension: `bun run test:unit` from `packages/kilo-vscode/`
|
||||
- Cross-package changes: `bun run typecheck` from the repo root
|
||||
|
||||
## Risks
|
||||
- The sample scope may need adjustment once the real target files are known.
|
||||
- Verification commands depend on which package is touched.
|
||||
@@ -1,69 +0,0 @@
|
||||
# Plan: Replace Internal SVGLoader Usage
|
||||
|
||||
## Context
|
||||
|
||||
The JetBrains plugin currently imports `com.intellij.util.SVGLoader` in `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt` and calls `SVGLoader.load(ByteArrayInputStream(patch()), scale.toFloat())` from `SvgIcon.image(Graphics)`.
|
||||
|
||||
Inspection reports this as an internal API usage because `SVGLoader` and its `load(InputStream, float)` method are marked `@ApiStatus.Internal`.
|
||||
|
||||
I checked the local IntelliJ source reference via `$INTELLIJ_REPO=/Users/kirillk/products/intellij-community`:
|
||||
|
||||
- `platform/util/ui/src/com/intellij/util/SVGLoader.kt`
|
||||
- `platform/util/ui/src/com/intellij/ui/svg/svg.kt`
|
||||
- `platform/util/ui/src/com/intellij/ui/svg/jsvg.kt`
|
||||
- `libraries/jsvg/intellij.libraries.jsvg.iml`
|
||||
|
||||
Important findings:
|
||||
|
||||
- IntelliJ `SVGLoader.load(stream, scale)` delegates to internal `loadSvg(...)` in `com.intellij.ui.svg`.
|
||||
- The actual renderer uses `com.github.weisj:jsvg:2.1.0` and renders into a `BufferedImage` with antialiasing, bicubic interpolation, and pure stroke control.
|
||||
- Copying `SVGLoader.kt` directly would keep references to other internal IntelliJ APIs (`IconLoader`, `ScaleContext`, `ImageUtil`, `SvgAttributePatcher`, `createJSvgDocument`, `renderSvgWithSize`, cache classes, etc.) and would be broader than needed.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
1. Add Kilo-owned SVG rendering code under the frontend UI package, for example:
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgLoader.kt`
|
||||
- Package: `ai.kilocode.client.ui`
|
||||
- Suggested internal object name: `SvgLoader`
|
||||
|
||||
2. Keep the copied implementation minimal and purpose-specific:
|
||||
- Accept `InputStream` and `scale: Float`.
|
||||
- Parse the SVG with `com.github.weisj.jsvg.parser.SVGLoader().load(stream)` or the equivalent public jsvg API available in version `2.1.0`.
|
||||
- Determine image dimensions from the root SVG `width` and `height` already parsed by existing `SvgIconColorizer.size(...)`, or by passing dimensions into the loader if the public jsvg API does not expose reliable intrinsic size.
|
||||
- Render into `BufferedImage(width * scale, height * scale, BufferedImage.TYPE_INT_ARGB)` using `Graphics2D` rendering hints matching IntelliJ’s `jsvg.kt`:
|
||||
- `KEY_ANTIALIASING = VALUE_ANTIALIAS_ON`
|
||||
- `KEY_INTERPOLATION = VALUE_INTERPOLATION_BICUBIC`
|
||||
- `KEY_STROKE_CONTROL = VALUE_STROKE_PURE`
|
||||
- Return `Image`/`BufferedImage`.
|
||||
|
||||
3. If jsvg does not expose intrinsic size cleanly from public API, adjust the API to avoid duplicating SVG parsing:
|
||||
- Add `width` and `height` parameters to the Kilo loader.
|
||||
- In `SvgIcon.image(Graphics)`, call the loader with the already-computed `size.first` and `size.second`.
|
||||
- This is the likely smallest stable solution because current icons already require explicit `width` and `height` for `SvgIconColorizer.size(...)`.
|
||||
|
||||
4. Add the jsvg dependency explicitly to the plugin frontend module instead of relying on the IntelliJ-bundled library:
|
||||
- In `packages/kilo-jetbrains/gradle/libs.versions.toml`, add `jsvg = "2.1.0"` and `jsvg = { module = "com.github.weisj:jsvg", version.ref = "jsvg" }`.
|
||||
- In `packages/kilo-jetbrains/frontend/build.gradle.kts`, add `implementation(libs.jsvg)`.
|
||||
- This follows the package instruction to bundle third-party libraries with the plugin rather than relying on IDE-bundled versions.
|
||||
|
||||
5. Update `SvgIconColorizer.kt`:
|
||||
- Remove `import com.intellij.util.SVGLoader`.
|
||||
- Replace `SVGLoader.load(ByteArrayInputStream(patch()), scale.toFloat())` with the Kilo-owned loader call.
|
||||
- Keep the existing color patching, cache key, scale calculation, and size parsing intact.
|
||||
|
||||
6. Add a focused frontend unit test:
|
||||
- New test file: `frontend/src/test/kotlin/ai/kilocode/client/ui/SvgIconColorizerTest.kt` or `SvgLoaderTest.kt`.
|
||||
- Exercise the actual loader/colorizer with a small SVG resource or inline SVG bytes.
|
||||
- Assert that rendering returns a non-empty image at scale `1f` and `2f`, and that scaled dimensions are correct.
|
||||
- If testing the colorizer, paint the icon into a `BufferedImage` and verify at least one expected patched pixel color appears.
|
||||
|
||||
7. Run verification from `packages/kilo-jetbrains/`:
|
||||
- `./gradlew typecheck`
|
||||
- A targeted frontend test if Gradle supports one cleanly, otherwise `./gradlew test`.
|
||||
- The requested DevKit internal API inspection is IDE-based; note in final response that the code no longer references `com.intellij.util.SVGLoader`, and typecheck/tests were run.
|
||||
|
||||
## Notes And Risks
|
||||
|
||||
- The user said “copy SaveLoaer”; I interpret this as “copy `SVGLoader`”. The implementation should copy the relevant rendering behavior, not IntelliJ’s full internal `SVGLoader` object, because the full source depends on multiple other internal APIs and cache infrastructure.
|
||||
- This change is in `packages/kilo-jetbrains/`, a Kilo-owned package, so `kilocode_change` markers are not needed.
|
||||
- jsvg package names and constructors should be confirmed during implementation against the downloaded dependency/API available to Gradle. If the API differs from IntelliJ source usage, inspect the source jar or compile error and adapt the minimal wrapper accordingly.
|
||||
@@ -1,60 +0,0 @@
|
||||
# Remove JetBrains SVG Runtime Colorization
|
||||
|
||||
## Goal
|
||||
Stop runtime SVG recoloring in the JetBrains plugin and rely on IntelliJ's standard light/dark icon asset resolution. Ensure every SVG asset used by the plugin has a light/dark pair.
|
||||
|
||||
## Findings
|
||||
- Runtime colorization exists only in `frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt`.
|
||||
- The only production caller is `frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt`.
|
||||
- `ScrollButtonIcon` colorizes two icons at runtime:
|
||||
- `/icons/scroll-bottom.svg`
|
||||
- `/icons/scroll-question.svg`
|
||||
- Both already have dark variants:
|
||||
- `scroll-bottom_dark.svg`
|
||||
- `scroll-question_dark.svg`
|
||||
- Other used frontend icons already have light/dark pairs:
|
||||
- `send.svg` / `send_dark.svg`
|
||||
- `stop.svg` / `stop_dark.svg`
|
||||
- `shield.svg` / `shield_dark.svg`
|
||||
- `shield-filled.svg` / `shield-filled_dark.svg`
|
||||
- `compress.svg` / `compress_dark.svg`
|
||||
- `chevron-down.svg` / `chevron-down_dark.svg`
|
||||
- `arrow-up.svg` / `arrow-up_dark.svg`
|
||||
- `arrow-down-to-line.svg` / `arrow-down-to-line_dark.svg`
|
||||
- `kilo.svg` / `kilo_dark.svg`
|
||||
- `kilo-content.svg` / `kilo-content_dark.svg`
|
||||
- `plus.svg` / `plus_dark.svg` exists, though no current direct source reference was found.
|
||||
- `kilo@20x20.svg` / `kilo@20x20_dark.svg` exists, though no current direct source reference was found.
|
||||
- `src/main/resources/META-INF/pluginIcon.svg` is present and has no `pluginIcon_dark.svg`. It is not referenced in source/XML, but JetBrains treats this filename conventionally as plugin metadata/marketplace icon, so it should be paired for completeness.
|
||||
- The SVG assets use literal colors and no `currentColor`, `<style>`, CSS classes, or CSS variables were found in `frontend/src/main/resources/icons/*.svg`.
|
||||
|
||||
## Implementation Plan
|
||||
1. Replace `ScrollButtonIcon` runtime colorization with standard `IconLoader.getIcon(...)` loading.
|
||||
- Remove imports for `colorizedSvgIcon`, `SessionUiStyle`, `UiStyle`, `JBUI`, and `Color` from `ScrollButtonIcon.kt`.
|
||||
- Add `IconLoader` import.
|
||||
- Load `bottom` with `IconLoader.getIcon("/icons/scroll-bottom.svg", ScrollButtonIcon::class.java)`.
|
||||
- Load `prompt` with `IconLoader.getIcon("/icons/scroll-question.svg", ScrollButtonIcon::class.java)`.
|
||||
- Keep `create(question: Boolean = false)` unchanged so callers continue to receive the appropriate icon.
|
||||
- IntelliJ should automatically resolve `_dark.svg` variants when the look and feel is dark.
|
||||
2. Delete `frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt` after confirming no callers remain.
|
||||
3. Remove now-unused scroll icon source palette constants from `SessionUiStyle.ScrollIcon`.
|
||||
- Delete the `ScrollIcon` object if no references remain.
|
||||
- This also removes stale comments about runtime colorization.
|
||||
4. Add `src/main/resources/META-INF/pluginIcon_dark.svg` as the dark-mode pair for `pluginIcon.svg`.
|
||||
- Use the same artwork/colors as `pluginIcon.svg` unless there is a desired brand-specific dark variant; the current icon already has a black background and yellow mark, so an identical dark pair is acceptable and satisfies IntelliJ's expected pair convention.
|
||||
5. Re-scan the JetBrains package after edits.
|
||||
- Confirm there are no `colorizedSvgIcon` or `SvgIconColorizer` references.
|
||||
- Confirm every used SVG path has a sibling `_dark.svg` where IntelliJ expects one.
|
||||
- Confirm no frontend SVG uses `currentColor`, `<style>`, CSS classes, or CSS variables.
|
||||
6. Run the smallest relevant verification from `packages/kilo-jetbrains/`:
|
||||
- `bun run typecheck` or `./gradlew typecheck`.
|
||||
|
||||
## Files Expected To Change
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt`
|
||||
- Delete `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt`
|
||||
- Add `packages/kilo-jetbrains/src/main/resources/META-INF/pluginIcon_dark.svg`
|
||||
|
||||
## Risks
|
||||
- The scroll icons will no longer follow arbitrary runtime theme colors; they will use the static light/dark SVG asset colors. This matches the requested behavior.
|
||||
- If a non-default theme expects palette overrides, static literal SVG colors still follow normal IntelliJ SVG handling better than custom byte-patching, but the exact custom-color behavior may differ from the old colorizer.
|
||||
@@ -1,49 +0,0 @@
|
||||
# Fix JetBrains Session Question Scroll
|
||||
|
||||
## Goal
|
||||
When a question-like active view first appears in the JetBrains session UI, do not auto-scroll the transcript if the user is already away from the tail and the scroll-to-bottom button is visible. This applies to permission prompts and question-based views. Existing behavior should remain: if the transcript is already at the bottom, new questions/permissions keep the view at the bottom; explicit question navigation can still jump to the active card.
|
||||
|
||||
## Current Findings
|
||||
- `SessionUi` wires `QuestionView` with:
|
||||
- `follow = { scroll.following() }`
|
||||
- `scroll = { scroll.followBottom(it) }`
|
||||
- `QuestionView.show()` runs on first display and calls:
|
||||
- `val tail = follow()` before rendering the card
|
||||
- `scroll(tail)` after rendering
|
||||
- `SessionScroll.following()` currently returns `component.viewport.view === messages && tail`.
|
||||
- This can use stale internal follow state instead of the live scrollbar state. The user-visible condition is better represented by `SessionScroll.atBottom()`, which is also what `SessionController.beforeUpdate` uses for normal transcript updates.
|
||||
- Existing `SessionScrollTest` already covers question and login-required middle-scroll preservation, but it does not cover permission first appearance and likely does not cover a stale-tail/live-not-at-bottom edge.
|
||||
|
||||
## Implementation Plan
|
||||
1. Update question first-show follow sampling to use live bottom state.
|
||||
- In `SessionUi.buildUi()`, change the `QuestionView` `follow` lambda from `scroll.following()` to `scroll.atBottom()`.
|
||||
- Alternatively, if a single semantic API is preferred, change `SessionScroll.following()` to return `component.viewport.view === messages && atBottom()` and keep the `SessionUi` call site unchanged. Prefer the smaller, clearer call-site change unless tests show other `following()` behavior should change too.
|
||||
|
||||
2. Add permission scroll regression tests in `SessionScrollTest`.
|
||||
- Add `test permission appearing at bottom keeps scroll at bottom`.
|
||||
- Add `test permission appearing while user is in middle preserves scroll position`.
|
||||
- Emit `ChatEventDto.PermissionAsked("ses_test", PermissionRequestDto(...))` using the same DTO style as the existing recovered permission test.
|
||||
- Assert the scrollbar value is preserved and `jumpButton().isVisible` stays true when initially away from bottom.
|
||||
|
||||
3. Strengthen question regression coverage if needed.
|
||||
- Keep the existing `test question appearing while user is in middle preserves scroll position`.
|
||||
- If the bug reproduces only when the jump button is visible but `tail` can be stale, add a targeted regression that creates the visible jump state before `QuestionAsked` and asserts first display does not jump.
|
||||
- Avoid adding production-only test hooks; use existing `setValue`, `setValuePassive`, `drainScroll`, and event helpers.
|
||||
|
||||
4. Preserve intentional explicit navigation behavior.
|
||||
- Do not change `QuestionView.goForward()`, `goBack()`, or `goReview()`, which intentionally call `scroll(true)` and are covered by existing tests that expect navigation to jump to the active question card.
|
||||
- Do not change prompt growth or normal streaming follow behavior unless a failing test shows the same stale-state issue applies there.
|
||||
|
||||
5. Verification.
|
||||
- Run the targeted JetBrains frontend session scroll tests from `packages/kilo-jetbrains/` using Gradle test filtering for `SessionScrollTest` if supported.
|
||||
- Run `./gradlew typecheck` from `packages/kilo-jetbrains/` if the targeted tests pass or if Gradle filtering is unavailable.
|
||||
- If Java 21 is unavailable, first check/report that blocker as instructed by the repo docs.
|
||||
|
||||
## Expected Files To Touch
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionScrollTest.kt`
|
||||
|
||||
## Non-Goals
|
||||
- No backend or RPC protocol changes.
|
||||
- No redesign of the session scroll algorithm.
|
||||
- No changes to explicit question navigation auto-scroll behavior.
|
||||
@@ -1,81 +0,0 @@
|
||||
# Plan: JetBrains Question-Style Scroll Button
|
||||
|
||||
## Goal
|
||||
|
||||
Fix JetBrains session scrolling so that when any inline prompt card appears while the scroll/jump button is already visible, the transcript scroll position is preserved and only the scroll button state changes. This applies to all current `BaseQuestionView`-backed session cards: questions, permissions, and login-required prompts. Preserve existing behavior that question navigation, review/back/next, and bottom-following content changes auto-scroll when they are supposed to.
|
||||
|
||||
## Findings
|
||||
|
||||
- The relevant files are `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt`, `.../scroll/SessionScroll.kt`, `.../views/question/QuestionView.kt`, `.../views/permission/PermissionView.kt`, `.../views/LoginRequiredView.kt`, `.../views/base/BaseQuestionView.kt`, and `frontend/src/test/kotlin/ai/kilocode/client/session/SessionScrollTest.kt`.
|
||||
- `QuestionView`, `PermissionView`, and `LoginRequiredView` do not inherit from `BaseQuestionView`; they compose it as the shared card shell. Scroll behavior should stay outside `BaseQuestionView` because it is a pure visual component with no session scroll context.
|
||||
- The current uncommitted `SessionUi.kt` attempt changes `QuestionView` from `follow = { scroll.following() }` to `follow = { scroll.atBottom() }`.
|
||||
- That fixes initial question appearance when the user is visibly away from bottom, but it is too broad because `QuestionView.follow()` is also used after custom editor growth, selection refresh, reply/reject, and other internal updates.
|
||||
- `QuestionView` already intentionally calls `scroll(true)` in `goBack`, `goForward`, and `goReview`; those forced calls are the behavior that preserves auto-scroll when switching question pages.
|
||||
- `PermissionView.show()` and `LoginRequiredView.show()` do not call scroll directly. They rely on `SessionController.beforeUpdate` / `afterUpdate`, which samples `scroll.atBottom()` before the state change and calls `scroll.followBottom(follow)` afterward. With the stale-tail fix, this path can preserve scroll for all non-question inline prompt cards.
|
||||
- The uncommitted `SessionScroll.followBottom(false)` change that clears stale `tail` when the transcript is visible and not actually at bottom is directionally correct. It prevents later `following()` calls from resurrecting auto-follow after a non-follow decision.
|
||||
- The current scroll button pending state is named `question` / `setQuestionPending` and is only enabled for non-plan `AwaitingQuestion`. To cover all `BaseQuestionView`-backed prompts, this should become a generic pending-input/attention state and include `AwaitingPermission` and `LoginRequired`.
|
||||
- Existing behavior intentionally keeps plan follow-up questions using the normal scroll icon (`test plan followup question keeps scroll icon`), so preserve that exception unless product intent changes separately.
|
||||
- The uncommitted `build.gradle.kts` and `script/build-sign-check.sh` changes are unrelated to this bug and should not be touched as part of this fix.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
1. In `QuestionView.kt`, split the scroll-follow decision into two callbacks.
|
||||
|
||||
- Keep the existing `follow` callback for question-internal updates.
|
||||
- Add a second callback for initial question appearance, with a default that delegates to `follow` so existing tests/call sites remain compatible.
|
||||
- In `show(q)`, sample the new initial callback before making the question visible or rebuilding the card, then call `scroll(tail)` after `syncPage()` as today.
|
||||
- Leave `scroll(true)` in `goBack`, `goForward`, and `goReview` unchanged.
|
||||
- Leave existing `scroll(follow())` calls in custom editor changes, option selection, reply, and reject paths unchanged.
|
||||
|
||||
2. In `SessionUi.kt`, wire the callbacks with distinct meanings.
|
||||
|
||||
- Use `follow = { scroll.following() }` for question-internal updates.
|
||||
- Use the new initial callback as `{ scroll.atBottom() }` for initial question appearance.
|
||||
- Keep `scroll = { scroll.followBottom(it) }`.
|
||||
|
||||
3. Generalize the scroll button pending state for all inline prompt cards.
|
||||
|
||||
- Rename `SessionScroll.setQuestionPending` and the backing `question` boolean to a generic name such as `setInputPending` / `pending` or `setAttentionPending` / `pending`.
|
||||
- Rename `questionPending(state)` in `SessionUi.kt` to match the broader meaning.
|
||||
- Return `true` for non-plan `SessionState.AwaitingQuestion`, `SessionState.AwaitingPermission`, and `SessionState.LoginRequired`.
|
||||
- Keep returning `false` for plan follow-up questions so the existing plan-specific icon behavior is preserved.
|
||||
- Reuse the existing question-style icon and tooltip for now, unless a separate product decision asks for a different permission/login-specific icon or string.
|
||||
|
||||
4. In `SessionScroll.kt`, keep the stale-tail reconciliation in `followBottom(false)`.
|
||||
|
||||
- When `follow` is false, increment `seq`, and if the active view is the transcript and `!atBottom()`, set `tail = false` before `updateJump()`.
|
||||
- Do not change `following()` to use `atBottom()`, because `tail` is needed to preserve bottom-follow behavior across deferred layout/height growth.
|
||||
|
||||
5. Do not add scroll callbacks to `PermissionView`, `LoginRequiredView`, or `BaseQuestionView` unless tests prove the controller-level path is insufficient.
|
||||
|
||||
- `PermissionView` and `LoginRequiredView` should remain retained visual views that show/hide themselves and refresh their parent.
|
||||
- `BaseQuestionView` should stay a scroll-unaware card shell.
|
||||
- The shared behavior for those cards should come from the controller-level state-change follow decision plus the generalized pending-button state.
|
||||
|
||||
6. In `SessionScrollTest.kt`, update tests around question-style prompt appearance and preserved behavior.
|
||||
|
||||
- Keep or add an assertion that the jump button is visible before emitting a question in the middle-scroll test.
|
||||
- Keep a regression for stale `tail` plus visible jump button if no public-only scenario can reproduce the failed state; keep the reflective helper local to this test file and avoid adding production test hooks.
|
||||
- Keep existing behavior tests for question navigation from the middle, forced navigation resuming follow, custom question answer growth at bottom, custom question answer growth in the middle, and option selection not resuming follow.
|
||||
- Add or keep permission bottom/middle tests to prove `AwaitingPermission` preserves scroll when away from bottom and follows when at bottom.
|
||||
- Add or keep login-required bottom/middle tests to prove `LoginRequired` preserves scroll when away from bottom and follows when at bottom.
|
||||
- Add explicit button-state assertions for permission and login-required while away from bottom: the button remains visible, switches to the pending-input icon/tooltip, and clicking it still scrolls to bottom.
|
||||
- Keep the plan follow-up test proving plan prompts do not switch to the pending-input icon.
|
||||
|
||||
7. Add a patch changeset for the JetBrains plugin unless an existing branch changeset is intentionally covering this exact fix.
|
||||
|
||||
- Suggested release note: `Preserve JetBrains chat scroll position when inline prompts appear while the scroll button is visible.`
|
||||
|
||||
## Verification
|
||||
|
||||
- From `packages/kilo-jetbrains/`, run `./gradlew test --tests "ai.kilocode.client.session.SessionScrollTest"`.
|
||||
- From `packages/kilo-jetbrains/`, run `./gradlew typecheck`.
|
||||
- Do not run root `bun test`.
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
- If the transcript is away from bottom and the jump button is visible, a newly appearing question, permission prompt, or login-required prompt changes the button state without moving `bar.value`.
|
||||
- If the transcript is at bottom, a newly appearing question, permission prompt, or login-required prompt follows to the new bottom.
|
||||
- Clicking the visible pending-input scroll button still jumps to the bottom for every inline prompt card.
|
||||
- Question carousel next/back/review still jumps to the bottom even if the transcript was previously in the middle.
|
||||
- Custom question editor growth still follows when the transcript was following and preserves position when it was not.
|
||||
@@ -1,109 +0,0 @@
|
||||
# Plan: JetBrains Feedback & Support Button
|
||||
|
||||
## Goal
|
||||
Add the VS Code empty-state `Feedback & Support` affordance to the JetBrains plugin, including a non-modal popup with the same three destinations:
|
||||
|
||||
- GitHub issues: `https://github.com/Kilo-Org/kilocode/issues/new/choose`
|
||||
- Discord: `https://kilo.ai/discord`
|
||||
- Customer support: `https://kilo.ai/support`
|
||||
|
||||
The popup must hide when clicking outside or pressing Escape.
|
||||
|
||||
## Findings
|
||||
|
||||
- The JetBrains empty state is `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/EmptySessionPanel.kt`.
|
||||
- It already renders the logo, welcome copy, recents, and `Show History`; this is the right place to add the button.
|
||||
- The VS Code implementation is:
|
||||
- Button: `packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx`
|
||||
- Popup content: `packages/kilo-vscode/webview-ui/src/components/chat/FeedbackDialog.tsx`
|
||||
- Styling: `packages/kilo-vscode/webview-ui/src/styles/welcome.css`
|
||||
- Local IntelliJ API source is available at `$INTELLIJ_REPO=/Users/kirillk/products/intellij-community`.
|
||||
- IntelliJ popup API confirmed in local source:
|
||||
- `JBPopupFactory.getInstance().createComponentPopupBuilder(content, focusComponent)`
|
||||
- `setModalContext(false)` for non-modal context
|
||||
- `setRequestFocus(false)` / `setFocusable(false)` if we want the popup not to steal focus
|
||||
- `setCancelOnClickOutside(true)` for outside-click dismissal
|
||||
- `setCancelKeyEnabled(true)` for Escape dismissal
|
||||
- `setCancelOnWindowDeactivation(true)` and `setCancelOnOtherWindowOpen(true)` are available and appropriate for cleanup
|
||||
- IntelliJ platform icons found in `AllIcons`:
|
||||
- Feedback button: `AllIcons.Ide.Feedback`
|
||||
- GitHub action: `AllIcons.Vcs.Vendors.Github`
|
||||
- Support/help action: `AllIcons.Actions.Help` or `AllIcons.General.ContextHelp`
|
||||
- Discord: no platform icon found. Add a Discord icon by borrowing the existing VS Code/kilo-ui Discord SVG artwork and adapting it into the JetBrains plugin resources with IntelliJ-compatible SVG colors and dark variant if needed.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. Add localized JetBrains bundle keys.
|
||||
- Add base English strings to `frontend/src/main/resources/messages/KiloBundle.properties`:
|
||||
- `feedback.button=Feedback & Support`
|
||||
- `feedback.dialog.message=We'd love to hear your feedback or help with any issues you're experiencing.`
|
||||
- `feedback.dialog.github=Report an issue on GitHub`
|
||||
- `feedback.dialog.discord=Join our Discord community`
|
||||
- `feedback.dialog.support=Customer Support`
|
||||
- Reuse existing cancel text if present; otherwise add `common.cancel=Cancel` only if needed.
|
||||
- Add matching keys to localized `KiloBundle_*.properties` files. If accurate translations are not already available from the VS Code i18n files, copy the VS Code translations for matching locales where practical and leave English fallback only if the JetBrains bundle mechanism supports it cleanly.
|
||||
|
||||
2. Extend `EmptySessionPanel` UI.
|
||||
- Add a new retained `FeedbackButton`, styled to match VS Code conceptually:
|
||||
- feedback icon + `Feedback & Support` text
|
||||
- dashed link-colored border
|
||||
- transparent background
|
||||
- hand cursor
|
||||
- hover state fills with link color and flips foreground/background for contrast
|
||||
- Use IntelliJ theme APIs instead of raw colors:
|
||||
- link color: `JBUI.CurrentTheme.Link.Foreground.ENABLED`
|
||||
- hover link color: `JBUI.CurrentTheme.Link.Foreground.HOVERED` if useful
|
||||
- editor/panel background from existing session style or `UiStyle.Colors.editorBackground()` / component background
|
||||
- spacing via `UiStyle.Gap` / `JBUI.Borders`
|
||||
- rounded arc via `UiStyle.Arc.component()` or `JBUI.getInt("Button.arc", 6)` at paint time
|
||||
- Add the feedback button below the existing `Show History` button in the empty-state south area, keeping the current centered layout.
|
||||
- Keep Swing mutations on EDT and preserve retained-component behavior.
|
||||
|
||||
3. Implement the feedback popup in frontend Swing code.
|
||||
- Add a small popup builder method/class inside `EmptySessionPanel.kt` or a new nearby UI file if the file would become too large.
|
||||
- Content should mirror VS Code:
|
||||
- Kilo logo at top using existing `/icons/kilo-content.svg`
|
||||
- message text
|
||||
- three action buttons/rows for GitHub, Discord, and Customer Support
|
||||
- every action should have an icon: `AllIcons.Vcs.Vendors.Github` for GitHub, the borrowed Discord SVG for Discord, and `AllIcons.Actions.Help` or `AllIcons.General.ContextHelp` for Customer Support
|
||||
- optional Cancel button if it fits the JetBrains UX; Escape/outside click already dismisses
|
||||
- Add the Discord icon asset under `frontend/src/main/resources/icons/` using the plugin's existing icon naming pattern, for example `discord.svg` and `discord_dark.svg` if the borrowed asset needs separate theme variants.
|
||||
- Load the Discord icon with `IconLoader.getIcon("/icons/discord.svg", EmptySessionPanel::class.java)` or a small Kilo-owned icon object if multiple call sites need it.
|
||||
- Use `BrowserUtil.browse(url)` to open links.
|
||||
- Close the popup after opening a URL.
|
||||
- Use `JBPopupFactory.createComponentPopupBuilder(content, null)` and configure:
|
||||
- `.setModalContext(false)`
|
||||
- `.setRequestFocus(false)`
|
||||
- `.setFocusable(false)` unless keyboard tabbing inside popup is desired; if buttons need keyboard focus, use `.setFocusable(true)` with `.setRequestFocus(false)`
|
||||
- `.setCancelOnClickOutside(true)`
|
||||
- `.setCancelKeyEnabled(true)`
|
||||
- `.setCancelOnWindowDeactivation(true)`
|
||||
- `.setCancelOnOtherWindowOpen(true)`
|
||||
- `.setResizable(false)`
|
||||
- `.setMovable(false)`
|
||||
- Show with `popup.showUnderneathOf(feedbackButton)`.
|
||||
|
||||
4. Add test coverage in `EmptySessionPanelTest.kt`.
|
||||
- Assert feedback button text uses `KiloBundle.message("feedback.button")`.
|
||||
- Assert feedback button has hand cursor and visible border semantics.
|
||||
- Add an injectable browse callback or popup action callback only if needed for testing without launching browsers; keep it minimal and avoid production-only test hooks if component traversal can exercise enough behavior.
|
||||
- Add a test that clicking the feedback button creates/shows popup content, if feasible in `BasePlatformTestCase`; otherwise test the retained popup content builder/action rows directly through package-internal methods.
|
||||
- Assert the three action labels exist and URL callbacks map to the same URLs as VS Code.
|
||||
- Assert the Discord action has an icon, so the plan cannot regress to a text-only Discord row.
|
||||
|
||||
5. Add release note.
|
||||
- This is user-facing for the JetBrains plugin, so add a patch changeset under `.changeset/` unless the repo has a JetBrains-specific release-note mechanism that supersedes changesets.
|
||||
- Suggested text: `Add Feedback & Support to the JetBrains empty session screen.`
|
||||
|
||||
6. Verify.
|
||||
- Run the focused frontend/JetBrains tests first, preferably from `packages/kilo-jetbrains/`:
|
||||
- `./gradlew :frontend:test --tests "ai.kilocode.client.session.ui.EmptySessionPanelTest"`
|
||||
- Run JetBrains typecheck from `packages/kilo-jetbrains/`:
|
||||
- `bun run typecheck` or `./gradlew typecheck`
|
||||
- If touching only frontend UI and tests pass, no backend/SDK generation is needed.
|
||||
|
||||
## Notes
|
||||
|
||||
- Do not add Kotlin UI DSL, Compose, or JCEF.
|
||||
- Do not modify shared opencode files; this work is entirely under `packages/kilo-jetbrains/` plus a changeset.
|
||||
- Prefer platform icons where available, but explicitly borrow/add the Discord icon because `AllIcons` does not provide one and the popup should match the VS Code button set visually.
|
||||
@@ -1,84 +0,0 @@
|
||||
# JetBrains Model Picker Details Plan
|
||||
|
||||
## Goal
|
||||
Add a JetBrains model picker maximize/minimize affordance that expands the popup to roughly double width and shows model details comparable to VS Code's `ModelPreview`. Persist the last expanded/collapsed state in IntelliJ `PropertiesComponent`. First-time default is collapsed.
|
||||
|
||||
## Decisions
|
||||
- Scope is full VS Code parity where JetBrains can receive the same model metadata: pricing, release date, descriptions, modalities/capabilities, terminal-bench, auto-routing details, context limits, badges, and provider/model identity.
|
||||
- Default state is collapsed for users without a saved property.
|
||||
- Expanded state is app-level UI state stored in `PropertiesComponent`, not backend/session state.
|
||||
- Use Swing/IntelliJ platform components only. Do not introduce Compose, JCEF, or UI DSL.
|
||||
- Keep existing compact list behavior when collapsed. In expanded mode, show a retained details panel beside the list.
|
||||
|
||||
## Affected Areas
|
||||
- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ProviderDto.kt`
|
||||
- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloWorkspaceState.kt`
|
||||
- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt`
|
||||
- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceDtoMapper.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/model/SessionModel.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/models/ModelsSettingsUi.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/model/ModelPicker*.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle*.properties`
|
||||
- Existing tests under `packages/kilo-jetbrains/frontend/src/test/...` and `backend/src/test/...`
|
||||
|
||||
## Implementation Steps
|
||||
1. Extend model metadata DTOs.
|
||||
- Add serializable nested DTOs to `ProviderDto.kt` for preview metadata matching VS Code provider types where practical: cost/cache cost, capabilities/input modalities, options/description, auto-routing model ids, terminal-bench score/cost.
|
||||
- Extend `ModelDto` with nullable fields such as `inputPrice`, `outputPrice`, `contextLength`, `releaseDate`, `latest`, `cost`, `capabilities`, `options`, `autoRouting`, and `terminalBench`.
|
||||
- Preserve existing fields (`limit`, `variants`, `free`, `byok`, `mayTrainOnYourPrompts`) to avoid breaking callers.
|
||||
|
||||
2. Parse and map the extra metadata in the JetBrains backend.
|
||||
- Extend `ModelInfo` in `KiloWorkspaceState.kt` with matching nullable metadata fields.
|
||||
- Update `KiloCliDataParser.parseModel` and `parseModelDto` to read the fields currently used by VS Code: `inputPrice`, `outputPrice`, `contextLength`, `releaseDate`, `latest`, `cost`, `capabilities.input`, `options.description`, `autoRouting.models`, and `terminalBench`.
|
||||
- Keep permissive parsing: missing or malformed optional fields should result in `null` or empty data, not provider load failure.
|
||||
- Update `KiloWorkspaceDtoMapper.model` to copy all new fields to `ModelDto`.
|
||||
|
||||
3. Carry metadata through the frontend session/settings model flow.
|
||||
- Extend session `ModelItem` and picker `ModelPicker.Item` to hold the preview metadata.
|
||||
- Update `SessionController` -> `SessionModel`, `SessionUi`, and `ModelsSettingsUi.items()` mappings so metadata survives into the picker.
|
||||
- Pass `limit` through to `ModelPicker.Item`; it is already available in `ModelItem` but currently dropped in `SessionUi`.
|
||||
|
||||
4. Add a retained Swing details panel.
|
||||
- Create a small `ModelDetailsPanel` near `session/ui/model/` or inside `ModelPicker.kt` if compact enough.
|
||||
- Use `JBLabel`, `SimpleColoredComponent`, `JBHtmlPane` or safe HTML helpers, `Stack`, and `JBUI` spacing.
|
||||
- Render VS Code-equivalent sections where data exists: header name/provider/star, free/BYOK/training badges, pricing, context window, release date, capabilities/modalities, terminal-bench, description, auto-routing candidates, and ids.
|
||||
- Sanitize/escape model descriptions. Use `HtmlChunk`/`HtmlBuilder` or platform escaping helpers; do not concatenate unescaped HTML.
|
||||
- Details panel should update from the currently previewed row in expanded mode and fall back to the active selected model when no preview row is set.
|
||||
|
||||
5. Rework popup layout and size behavior.
|
||||
- Add an expand/minimize button to the search/header row in `ModelPicker.showPopup()`.
|
||||
- Use pointer cursor on the button and localized tooltips/accessibility names: `Maximize model details` and `Minimize model details` or equivalent.
|
||||
- Persist state under a key such as `kilo.model.picker.expanded` using `PropertiesComponent.getInstance().getBoolean(key, false)` and `setValue(key, value.toString())`.
|
||||
- Collapsed mode keeps the current list-only width behavior.
|
||||
- Expanded mode lays out list and details side-by-side with a splitter/separator and sets preferred width to approximately double the collapsed width, clamped to available screen bounds. Keep `setLocateWithinScreenBounds(true)`.
|
||||
- Make `JBPopup` resizable only if the implementation can keep the retained layout stable; otherwise keep non-resizable and compute deterministic sizes.
|
||||
|
||||
6. Match VS Code interaction semantics where useful.
|
||||
- Collapsed mode: clicking a model selects it immediately, preserving current behavior.
|
||||
- Expanded mode: clicking/hovering or keyboard navigation updates the preview; Enter selects; double-click selects; Escape closes.
|
||||
- Favorite star behavior remains unchanged and updates the details panel favorite state when toggled.
|
||||
- When collapsing from expanded, clear transient preview state and show selected/active model on next expansion.
|
||||
|
||||
7. Add localization strings.
|
||||
- Add base `KiloBundle.properties` entries for maximize/minimize tooltips, detail labels, pricing labels, context, capabilities, terminal-bench, description, auto-routing choices, and unavailable values.
|
||||
- If existing localized files require key completeness, add English fallback values there too; otherwise rely on base bundle fallback consistent with project practice.
|
||||
|
||||
8. Add tests.
|
||||
- Backend parser tests: ensure all new metadata fields parse from representative provider JSON and missing optional fields are tolerated.
|
||||
- Mapper/serialization tests: ensure new DTO fields survive backend -> shared DTO serialization.
|
||||
- `ModelPickerTest`: verify expanded property default is collapsed, toggle persists true/false, tooltip/icon state switches maximize/minimize, expanded width is greater than collapsed width, preview panel displays selected/hovered model metadata, and pointer cursor is set on the expand control.
|
||||
- Renderer/details tests should exercise actual Swing components on the EDT using existing `BasePlatformTestCase` patterns.
|
||||
- Update existing tests/helpers that construct `ModelPicker.Item`, `ModelItem`, or `ModelDto` as needed using default values to keep changes minimal.
|
||||
|
||||
## Risks And Constraints
|
||||
- Full parity depends on metadata already present in CLI provider JSON. If a field is absent from the backend response for a provider, the details panel should omit that row rather than showing placeholders everywhere.
|
||||
- Adding fields to shared serializable DTOs affects split-mode RPC compatibility. Keep new fields nullable/defaulted for safe deserialization.
|
||||
- Avoid making provider loading fragile: optional preview metadata parsing must not throw on unexpected types.
|
||||
- The popup is a lightweight `JBPopup`; changing size after showing may require recreating/repacking carefully. Prefer computing expanded/collapsed preferred size before show and updating the content with `revalidate()`/`repaint()` on toggle.
|
||||
- Keep all Swing mutation on the EDT.
|
||||
|
||||
## Validation
|
||||
- From `packages/kilo-jetbrains/`: run `./gradlew typecheck` or `bun run typecheck`.
|
||||
- From `packages/kilo-jetbrains/`: run targeted tests for backend parser/serialization and frontend model picker tests, or `./gradlew test` if targeted Gradle filters are not straightforward.
|
||||
- Manually verify in sandbox if feasible: `./gradlew runIde`, open model picker, confirm first open is collapsed, maximize doubles width and shows details, minimize returns to compact width, tooltip/cursor are correct, and the last state persists after closing/reopening the popup.
|
||||
@@ -1,111 +0,0 @@
|
||||
# Prompt Rollback + Redo — JetBrains Parity
|
||||
|
||||
Bring the VS Code "rollback / redo" (CLI `revert` / `unrevert`) feature to the JetBrains plugin, and fix the misleading VS Code hint string. Implementation-capable agent required — this plan touches source and tests.
|
||||
|
||||
## Context (verified)
|
||||
|
||||
- CLI already exposes everything needed; no CLI release or SDK regen required. Pinned CLI version is `7.4.1` (`packages/kilo-jetbrains/package.json`).
|
||||
- `POST /session/{id}/revert` body `{ messageID, partID? }`, `POST /session/{id}/unrevert`.
|
||||
- `Session.revert?: { messageID, partID?, snapshot?, diff? }` — `packages/sdk/js/src/gen/types.gen.ts:554`. Pushed via `session.updated` SSE.
|
||||
- VS Code entry points: `RevertBanner.tsx`, `VscodeSessionTurn.tsx`, `session.tsx` context. Per-user-message rollback restores reverted prompt text into the input; redo banner shows count + per-file diff stats + Redo / Redo All + hint; reverted messages/parts hidden (`id >= revert.messageID`, partID partial-turn boundary).
|
||||
- JetBrains split-mode packages differ from AGENTS.md's stated `ai.kilocode.jetbrains`; actual source roots are:
|
||||
- shared → `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/`
|
||||
- backend → `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/`
|
||||
- frontend → `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/`
|
||||
- All target files confirmed to exist: `SessionDto.kt`, `KiloSessionRpcApi.kt`, `KiloBackendChatManager.kt`, `KiloSessionRpcApiImpl.kt`, `KiloSessionService.kt`, `SessionController.kt`, `SessionModel.kt`, `SessionModelEvent.kt`, `SessionView.kt`, `MessageView.kt`, `MessageToolbar.kt`, `PromptPanel.kt`, `PromptEditorTextField.kt`, plus reuse targets `DiffStatBadge.kt`, `HoverIcon.kt`, `PermissionDiffView.kt`.
|
||||
|
||||
## Decisions
|
||||
|
||||
- **Reuse `DiffStatBadge`** (already the shared per-file +/- renderer used by `PermissionDiffView`) for the banner's per-file diff rows. No new diff-stat component.
|
||||
- **Reuse the existing user-prompt hover toolbar (`MessageToolbar`)** for the per-message rollback control.
|
||||
- **Reuse the existing `session.updated → model.setSession(...)` pipeline** for the revert boundary; no new SSE handling.
|
||||
- **Stale non-English hint strings: reset all to the new English value** and flag for re-translation (applies to VS Code locales; JetBrains base bundle only, others fall back).
|
||||
|
||||
## Part A — VS Code string fix
|
||||
|
||||
File: `packages/kilo-vscode/webview-ui/src/i18n/en.ts:138`
|
||||
|
||||
- Change `revert.banner.hint` from `"Send a new message to make this permanent"` to `"You can redo these changes until you send a new message"`.
|
||||
- In the 19 non-English locale files under `packages/kilo-vscode/webview-ui/src/i18n/` (ar, br, bs, da, de, es, fr, it, ja, ko, nl, no, pl, ru, th, tr, uk, zh, zht), overwrite `revert.banner.hint` with the identical new English string; leave a note that these need re-translation.
|
||||
- Do not touch the other revert keys (`revert.banner.count_one`, `revert.banner.count_other`, `revert.banner.redo`, `revert.banner.redo.all`, `revert.disabled.agentBusy`) — only the hint changes meaning.
|
||||
|
||||
## Part B — JetBrains implementation
|
||||
|
||||
### 1. Shared (DTO + RPC contract)
|
||||
|
||||
- `shared/.../rpc/dto/SessionDto.kt`: add `val revert: SessionRevertDto? = null`; add `@Serializable data class SessionRevertDto(val messageID: String, val partID: String? = null, val snapshot: String? = null, val diff: String? = null)`. Field names must match CLI JSON for kotlinx auto-mapping through `session.updated`.
|
||||
- `shared/.../rpc/KiloSessionRpcApi.kt`: add
|
||||
- `suspend fun revert(id: String, directory: String, messageID: String, partID: String?)`
|
||||
- `suspend fun unrevert(id: String, directory: String)`
|
||||
|
||||
### 2. Backend (RPC impl + HTTP)
|
||||
|
||||
- `backend/.../app/KiloBackendChatManager.kt`: add `revert(id, dir, messageID, partID?)` → `POST /session/$id/revert?directory=…` with `{messageID, partID?}` body, and `unrevert(id, dir)` → `POST /session/$id/unrevert?directory=…`. Mirror the existing `compact()` OkHttp pattern; add JSON body builders to `KiloCliDataParser`.
|
||||
- `backend/.../rpc/KiloSessionRpcApiImpl.kt`: implement both, resolve worktree dir via `sessions.getDirectory(id, directory)`, wrap in `ready { }` like `compact`/`abort`.
|
||||
|
||||
### 3. Frontend service
|
||||
|
||||
- `frontend/.../app/KiloSessionService.kt`: add `suspend revert(...)` / `unrevert(...)` wrappers over the RPC, using `durable {}` per split-mode rules.
|
||||
|
||||
### 4. Frontend controller (`session/controller/SessionController.kt`)
|
||||
|
||||
- Add EDT-entry actions dispatched to the coroutine scope:
|
||||
- `revert(messageID, partID?)`
|
||||
- `unrevert()`
|
||||
- `redo()` — mirror `RevertBanner.handleRedo`: find the next user message after the boundary and revert to it; `unrevert()` if none.
|
||||
- `redoAll()` → `unrevert()`.
|
||||
- Revert boundary already arrives via `is ChatEventDto.SessionUpdated -> model.setSession(...)` — no new SSE handling.
|
||||
- On revert, restore the reverted user prompt text into the input (`session/ui/prompt/PromptPanel.kt` / `PromptEditorTextField`); on full redo (`redoAll` / final `unrevert`), clear it — matching VS Code and TUI.
|
||||
|
||||
### 5. Frontend model (`session/model/SessionModel.kt`, `SessionModelEvent.kt`)
|
||||
|
||||
- Add `SessionModelEvent.RevertChanged(revert: SessionRevertDto?)` with a stable `toString()`, fired from `setSession()` when the revert value changes. Reset in `loadHistory()` and `clear()`.
|
||||
- Add derived helpers: `revert(): SessionRevertDto?` and `revertedCount()` (count of user messages with `id >= boundary`, mirroring `session.tsx`).
|
||||
- Transcript hiding: expose whether a message id is hidden by the boundary (`id >= revert.messageID`, with partID partial-turn handling), applied both to newly-arriving messages and on `RevertChanged`.
|
||||
- Follow the AGENTS.md "Adding a New Event" checklist (event subclass → model field + mutation + resets → controller handling → `-> Unit` stubs in exhaustive `when` blocks).
|
||||
|
||||
### 6. Frontend views
|
||||
|
||||
- New `session/ui/RevertBanner.kt` Swing view, built once and mutated via `update()` per retained-Swing rules. Listens to `RevertChanged` + `DiffUpdated`. Contents:
|
||||
- arrow-left icon + count label (localized count.one/other),
|
||||
- per-file diff rows reusing `ai.kilocode.client.ui.DiffStatBadge` fed from `model.diff` (`DiffFileDto.additions/deletions`),
|
||||
- Redo button → `controller.redo()`, Redo All (only when count > 1) → `controller.redoAll()`,
|
||||
- hint label (same new wording as Part A).
|
||||
- Placed in `session/ui/SessionView.kt` at the bottom of the transcript, above the prompt input; shown only when `revert != null`.
|
||||
- Per-user-message rollback control in `session/views/MessageView.kt`: user prompt bubbles already carry a hover `promptToolbar` (`MessageToolbar` with copy) + `promptHover` state. Add a rollback `HoverIcon` (arrow-left) alongside copy for user messages, wired through `SessionView → controller.revert(msgId)`. Disable/hide while the agent is busy or when there are no assistant messages after it, with the `revert.disabled.agentBusy` tooltip — mirroring `VscodeSessionTurn` `onRevert` + `data-revert-disabled`. Extend `MessageToolbar.kt` to host the extra action (or add a sibling control).
|
||||
- Apply transcript hiding: `SessionView`/`TurnView` toggle `MessageView` visibility on `RevertChanged` (prefer visibility toggle over rebuild, per Swing lifecycle guidance).
|
||||
|
||||
### 7. i18n (`frontend/src/main/resources/messages/KiloBundle*.properties`)
|
||||
|
||||
- Add to base `KiloBundle.properties`: `revert.banner.count.one`, `revert.banner.count.other`, `revert.banner.redo`, `revert.banner.redo.all`, `revert.banner.hint` (new wording), `revert.disabled.agentBusy`.
|
||||
- Other locale bundles fall back to base until translated; do not add stale translations.
|
||||
|
||||
### 8. Tests
|
||||
|
||||
- Backend: extend `KiloSessionRpcApiImplTest` / a `KiloBackendChatManager` test against `MockCliServer` asserting the exact HTTP path + JSON body for `revert`/`unrevert`, and that a session carrying `revert` deserializes into `SessionRevertDto`.
|
||||
- Frontend controller (`SessionControllerTestBase` + `FakeSessionRpcApi`, add revert/unrevert call tracking): `revert()`/`unrevert()`/`redo()`/`redoAll()` hit the right RPC; `RevertChanged` fires; count logic; reverted messages hidden and restored; prompt-text restore on revert / clear on full redo.
|
||||
- Frontend views (`BasePlatformTestCase`): `RevertBanner` renders count + per-file `DiffStatBadge` + redo buttons (Redo All only when count > 1); `MessageView` shows the rollback control on user messages and disables it while busy; retained-component assertions (`assertSame`, bounded count) per Swing lifecycle rules.
|
||||
|
||||
## Deliverables
|
||||
|
||||
| Area | Files |
|
||||
|---|---|
|
||||
| VS Code string | `webview-ui/src/i18n/en.ts` + 19 non-English locale hints reset |
|
||||
| Shared | `SessionDto.kt` (+`SessionRevertDto`), `KiloSessionRpcApi.kt` |
|
||||
| Backend | `KiloBackendChatManager.kt`, `KiloCliDataParser`, `KiloSessionRpcApiImpl.kt` |
|
||||
| Frontend service/controller/model | `KiloSessionService.kt`, `SessionController.kt`, `SessionModel.kt`, `SessionModelEvent.kt` |
|
||||
| Frontend views | new `RevertBanner.kt` (reusing `DiffStatBadge`), `MessageView.kt`, `MessageToolbar.kt`, `SessionView.kt`, `PromptPanel.kt` |
|
||||
| i18n | `KiloBundle.properties` |
|
||||
| Tests | backend RPC/chat-manager, frontend controller + view tests, `FakeSessionRpcApi` |
|
||||
|
||||
## Validation
|
||||
|
||||
- VS Code string change: `bun run typecheck` from repo root (or from `packages/kilo-vscode/`).
|
||||
- JetBrains: `./gradlew typecheck` and `./gradlew test` from `packages/kilo-jetbrains/` (Java 21).
|
||||
- Manual: `./gradlew runIde` (or split mode) to exercise rollback → redo → redo-all and reverted-transcript hiding.
|
||||
|
||||
## Risks / Notes
|
||||
|
||||
- Confirm no `kilocode_change` markers are needed for the VS Code i18n edits (Kilo-owned webview path — none required).
|
||||
- Reset (not delete) is chosen for VS Code locale hints, so the fallback path is not relied upon; flag the 19 files for re-translation.
|
||||
- Keep new Kotlin identifiers single-word per repo style; annotate EDT/model-touching methods with `@RequiresEdt`; wrap frontend RPC in `durable {}`.
|
||||
@@ -1,199 +0,0 @@
|
||||
# JetBrains Rollback Feature — Hardening Plan
|
||||
|
||||
Harden the new JetBrains rollback/redo feature: (1) communicate when only messages were
|
||||
reverted because snapshots are disabled, and (2–4) close the code-duplication, testing,
|
||||
end-to-end, and AGENTS-conformance gaps.
|
||||
|
||||
All work is inside `packages/kilo-jetbrains/`. No shared `packages/opencode/` files change,
|
||||
so no `kilocode_change` markers are needed.
|
||||
|
||||
## Context an implementer needs
|
||||
|
||||
- Revert flow: user clicks the per-message Rollback button (`MessageToolbar`/`MessageView`)
|
||||
→ `SessionController.revert()` aborts a busy turn then POSTs `/session/:id/revert`
|
||||
→ CLI sets a revert marker → `SessionUpdated` event → `SessionModel.setRevert()` →
|
||||
`RevertBanner` shows + reverted messages hidden. `redo()`/`redoAll()` drive
|
||||
`/session/:id/revert` (next user msg) and `/session/:id/unrevert`.
|
||||
- Snapshot signal: `SessionRevertDto.snapshot` (`shared/.../rpc/dto/SessionDto.kt:22`) is
|
||||
`null` exactly when the server did NOT restore files (snapshots disabled: `config.snapshot
|
||||
=== false`, non-git worktree, or ACP). It is already parsed and reaches the frontend via
|
||||
`SessionModel.revert()`. When `snapshot == null` the diff is also empty, so the banner shows
|
||||
no file rows today. This is the reliable, no-new-API signal for "history only".
|
||||
- Test harnesses already exist: frontend `SessionControllerTestBase` + `FakeSessionRpcApi`
|
||||
(already tracks `rpc.reverts` and `rpc.unreverts`); backend `MockCliServer` +
|
||||
`KiloBackendChatManagerTest`. Banner tests instantiate `RevertBanner(model, redo, redoAll)`
|
||||
and read components via `components(banner)`.
|
||||
|
||||
## Key files
|
||||
|
||||
- `frontend/.../session/ui/RevertBanner.kt`
|
||||
- `frontend/.../session/controller/SessionController.kt` (`revert`, `unrevert`, `redo`, `redoAll`, `capture`, `sessionProps`)
|
||||
- `frontend/.../session/model/SessionModel.kt` (`revert()`, `revertedCount()`, `isRevertedMessage()`)
|
||||
- `frontend/src/main/resources/messages/KiloBundle.properties` (+ translated `KiloBundle_*.properties`)
|
||||
- `backend/.../app/KiloBackendChatManager.kt` (`revert`/`unrevert`), `backend/.../cli/KiloCliDataParser.kt` (`buildRevertJson`)
|
||||
- `backend/.../app/KiloBackendSessionManager.kt` (`revertDto` duplication)
|
||||
- Tests: `frontend/.../session/controller/TurnLifecycleTest.kt` (or new `RevertFlowTest.kt`),
|
||||
`frontend/.../session/ui/SessionMessageListPanelTest.kt`,
|
||||
`frontend/.../session/model/SessionModelTest.kt`,
|
||||
`backend/.../testing/MockCliServer.kt`, `backend/.../app/KiloBackendChatManagerTest.kt`,
|
||||
`backend/.../cli/KiloCliDataParserTest.kt`
|
||||
|
||||
## Resolved decisions
|
||||
|
||||
- **Do NOT hide/disable the Rollback button when snapshots are off.** This matches VS Code and
|
||||
keeps history-only revert usable. We only add messaging.
|
||||
- **Detect "files not restored" via `model.revert()?.snapshot == null`.** No new config/API,
|
||||
no shared-code change.
|
||||
- **Message it in the `RevertBanner`** with a distinct localized line, shown only when
|
||||
`snapshot == null`.
|
||||
- Proactively warning on the button before a revert (needs `config.snapshot`/`vcs` exposed to
|
||||
the frontend) is **out of scope**.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Communicate history-only revert (snapshots disabled)
|
||||
|
||||
1. Add bundle key in `KiloBundle.properties` (place near the other `revert.banner.*` keys):
|
||||
- `revert.banner.filesNotRestored=Snapshots are off — only the conversation was reverted; your files were not changed.`
|
||||
- Add the same key to every translated `KiloBundle_*.properties` file. If translations are
|
||||
not available, copy the English value (mirror how the other `revert.banner.*` keys were
|
||||
seeded across bundles) so no key is missing.
|
||||
2. In `RevertBanner.kt`:
|
||||
- Add a retained `JBLabel` field `notice` styled like `hint` (`JBFont.small()`), created
|
||||
once in `init` and added to `body` after `hint`.
|
||||
- In `update()`, after computing `revert`, set
|
||||
`notice.isVisible = revert.snapshot == null` and its text to
|
||||
`KiloBundle.message("revert.banner.filesNotRestored")`.
|
||||
- Color it via a theme API in `applyStyle` (use `UIUtil.getContextHelpForeground()` to match
|
||||
the secondary `hint`; do not hardcode a color). Re-evaluate in `applyStyle` alongside
|
||||
`hint.foreground`.
|
||||
- Keep the existing empty-diff behavior (no file rows) — the notice replaces the missing
|
||||
information rather than adding chrome.
|
||||
3. Remove the redundant `isOpaque = false` on the `body` and `files` `Stack`s (see Phase 4);
|
||||
do this while editing the file.
|
||||
|
||||
## Phase 2 — Close code-duplication gaps
|
||||
|
||||
4. In `KiloBackendSessionManager.kt`, collapse the two identical `revertDto(...)` overloads
|
||||
(`SessionRevert` and `GlobalSessionRevert`) into a single private helper that maps the four
|
||||
fields (`messageID`, `partID`, `snapshot`, `diff`) from a small shared shape. Simplest
|
||||
approach: one private `fun revertDto(messageID: String?, partID: String?, snapshot: String?,
|
||||
diff: String?)` returning `SessionRevertDto?` (null when `messageID` is null), and have both
|
||||
call sites extract fields from their respective model type. Keep behavior identical; this is
|
||||
a pure refactor covered by existing `KiloBackendSessionManagerTest` parsing assertions.
|
||||
|
||||
## Phase 3 — Fill testing + end-to-end gaps
|
||||
|
||||
### Frontend controller/model logic
|
||||
|
||||
5. `SessionModelTest.kt` — add direct model tests:
|
||||
- `revertedCount()` returns the number of user messages from the marker to the end
|
||||
(build entries `u1,a1,u2,a2`; marker at `u1` → 2; marker at `u2` → 1; marker id absent → 0).
|
||||
- `isRevertedMessage()` true for the marker message and everything after, false before.
|
||||
- `snapshot`-null marker still yields a non-zero `revertedCount()` (history-only case).
|
||||
6. `TurnLifecycleTest.kt` (or new `RevertFlowTest.kt`) — add controller tests using `prompted()`:
|
||||
- `redo()` reverts to the NEXT user message: seed `u1,a1,u2,a2`, apply marker at `u1`,
|
||||
`edt { m.redo() }`, flush → assert `rpc.reverts` last call targets `u2`.
|
||||
- `redo()` at the last user message calls unrevert: marker at `u2` → `redo()` → assert
|
||||
`rpc.unreverts` recorded and no new revert call.
|
||||
- `redoAll()` calls unrevert: assert `rpc.unreverts` contains `("ses_test","/test")`.
|
||||
- `unrevert()` clears via RPC: assert `rpc.unreverts` populated.
|
||||
|
||||
### Frontend banner → controller wiring (end-to-end within the UI layer)
|
||||
|
||||
7. `SessionMessageListPanelTest.kt` — add wiring + history-only tests:
|
||||
- Redo button click invokes the controller: build `RevertBanner(model, redo = { redo++ },
|
||||
redoAll = { all++ })`, set a marker, `banner...first { text == redo }.doClick()` → assert
|
||||
the redo lambda fired; same for Redo All when `revertedCount > 1`.
|
||||
- History-only notice: marker with `SessionRevertDto("u1", snapshot = null)` → assert the
|
||||
`revert.banner.filesNotRestored` label is visible and no file rows are present.
|
||||
- Snapshot-present: marker with `snapshot = "snap1"` → assert the notice label is hidden.
|
||||
|
||||
### Full round-trip end-to-end (frontend)
|
||||
|
||||
8. Add one `RevertFlowTest` case that exercises the whole loop through `SessionControllerTestBase`:
|
||||
- Seed a multi-turn session, `edt { m.revert(userMsgId) }`, flush → assert busy-abort
|
||||
ordering + `rpc.reverts`.
|
||||
- `emit(SessionUpdated(..., revert = SessionRevertDto(userMsgId, snapshot = "s1")))` →
|
||||
assert `model.isRevertedMessage(userMsgId)` and reverted views hidden.
|
||||
- Drive Redo All via `m.redoAll()`; `emit(SessionUpdated(..., revert = null))` → assert
|
||||
messages restored (`isRevertedMessage` false) and marker cleared.
|
||||
Use existing `emit`/`flush` helpers; no timeouts.
|
||||
|
||||
### Backend HTTP contract
|
||||
|
||||
9. `MockCliServer.kt` — add routing + capture fields (the current generic `/session/ses_*`
|
||||
regexes are full-match and won't catch the `/revert` suffix, so new cases are safe):
|
||||
- Fields: `lastRevertPath`, `lastRevertBody`, `lastUnrevertPath`, `lastUnrevertBody`
|
||||
(`@Volatile var ... : String? = null`).
|
||||
- Cases (place beside the `summarize`/`prompt_async` cases):
|
||||
- `bare.matches(Regex("/session/ses_[^/]+/revert")) && method == "POST"` → store path+body,
|
||||
`respond(output, 200, sessionCreate)`.
|
||||
- `bare.matches(Regex("/session/ses_[^/]+/unrevert")) && method == "POST"` → store path+body,
|
||||
`respond(output, 200, sessionCreate)`.
|
||||
10. `KiloBackendChatManagerTest.kt` — mirror the existing `compact posts summarize request` test:
|
||||
- `revert posts messageID and partID to revert endpoint`: call
|
||||
`chat.revert("ses_abc", "/test/project", "msg1", "prt1")`; assert
|
||||
`requestCount("/session/ses_abc/revert") == 1`, path starts with
|
||||
`/session/ses_abc/revert?directory=`, body == `{"messageID":"msg1","partID":"prt1"}`.
|
||||
- `revert omits partID when null`: body == `{"messageID":"msg1"}`.
|
||||
- `unrevert posts empty body to unrevert endpoint`: body == `{}` and path prefix asserted.
|
||||
11. `KiloCliDataParserTest.kt` — add `buildRevertJson` unit tests:
|
||||
- `buildRevertJson("m1", null) == {"messageID":"m1"}`.
|
||||
- `buildRevertJson("m1", "p1") == {"messageID":"m1","partID":"p1"}`.
|
||||
- Escaping: an id containing a quote/backslash is JSON-escaped (reuse the parser's `escape`).
|
||||
|
||||
## Phase 4 — AGENTS conformance cleanup
|
||||
|
||||
12. Remove the unused bundle key `revert.disabled.agentBusy` from `KiloBundle.properties` and all
|
||||
translated bundles (grep confirms it is referenced nowhere in Kotlin). Alternatively, if the
|
||||
team wants VS Code parity, wire it as a disabled-state tooltip on the Rollback button — but
|
||||
the recommended, lower-risk action is removal, since JetBrains intentionally aborts the busy
|
||||
turn instead of disabling the button.
|
||||
13. Remove redundant `isOpaque = false` on the transparent `Stack`s in `RevertBanner.kt`
|
||||
(`body`, `files`) — `Stack` is already a no-color panel. Keep `isOpaque = false` on the
|
||||
`BorderLayoutPanel` root only if it visibly overlays the session surface; otherwise remove it
|
||||
too and rely on the default.
|
||||
14. Add usage metrics for the new user-facing actions in `SessionController.kt`, using the
|
||||
existing `capture(event, props)` + `sessionProps(id)` helpers:
|
||||
- In `revert()` (on the success path, after `sessions.revert`): `capture("Session Rollback",
|
||||
sessionProps(id))`.
|
||||
- In `redo()`/`redoAll()`/`unrevert()`: `capture("Session Redo", sessionProps(id))` /
|
||||
`capture("Session Redo All", ...)` as appropriate. Keep names consistent with existing
|
||||
event naming; do not add PII.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Exposing `config.snapshot`/`vcs` to the frontend to proactively warn before a revert.
|
||||
- Changing the retained-vs-rebuild strategy of `RevertBanner.files` (low-frequency update;
|
||||
acceptable as-is).
|
||||
- Replacing the full-project `ACTION_SYNCHRONIZE` in `synchronizeFromDisk` with a scoped VFS
|
||||
refresh (only revisit if it shows up as a perf problem).
|
||||
- Any VS Code webview parity changes beyond the shared i18n strings already present.
|
||||
|
||||
## Validation
|
||||
|
||||
Run from `packages/kilo-jetbrains/`:
|
||||
|
||||
- `./gradlew typecheck` — compiles all Kotlin including generated client.
|
||||
- `./gradlew test` — runs frontend + backend suites, including the new tests. Requires Java 21;
|
||||
only check Java if Gradle reports a Java error.
|
||||
- Targeted while iterating: run the touched test classes (`TurnLifecycleTest`/`RevertFlowTest`,
|
||||
`SessionMessageListPanelTest`, `SessionModelTest`, `KiloBackendChatManagerTest`,
|
||||
`KiloCliDataParserTest`).
|
||||
|
||||
Manual smoke (optional, `./gradlew runIde`): trigger a rollback in a non-git directory (or with
|
||||
`snapshot: false`) and confirm the banner shows the "files not changed" notice with no file rows;
|
||||
trigger one in a git repo and confirm the notice is hidden and file rows appear.
|
||||
|
||||
## Changeset
|
||||
|
||||
Add `.changeset/<slug>.md` (`"@kilocode/kilo-jetbrains": patch`) describing the user-facing
|
||||
change, e.g. "Clarify in JetBrains rollback that only the conversation was reverted when
|
||||
snapshots are disabled." Test-only and refactor tasks do not need their own changeset.
|
||||
|
||||
## Suggested order
|
||||
|
||||
Phase 3 backend tests (9–11) and model tests (5) are independent and can be done first to lock
|
||||
behavior. Then Phase 1 (user-facing messaging) with its banner tests (7). Then Phase 2 refactor,
|
||||
Phase 4 cleanups, controller tests (6, 8), and the changeset last.
|
||||
@@ -1,341 +0,0 @@
|
||||
# JetBrains custom provider: close on OK, select, edit, edit/disconnect buttons
|
||||
|
||||
## Goal
|
||||
|
||||
Fix the JetBrains "Add custom provider" flow and add edit support:
|
||||
|
||||
1. Confirming the dialog closes it (OK label "Add" for add, "Save" for edit).
|
||||
2. The added/edited provider becomes the selected row in the list.
|
||||
3. Double-clicking an editable custom provider opens it for editing instead of disconnecting it.
|
||||
4. Editable custom providers show both **Edit** and **Disconnect** action buttons.
|
||||
|
||||
All work is in `packages/kilo-jetbrains/frontend/`. No backend, RPC, or DTO changes. `saveCustom` already upserts, and `ProviderSettingsDto.config[id]` already carries the fields needed to prefill an edit. No `kilocode_change` markers are needed (all files are under Kilo-owned `packages/kilo-jetbrains/`).
|
||||
|
||||
## Root causes (verified in code)
|
||||
|
||||
- **Dialog does not close**: `CustomProviderDialog.syncActions()` sets `isOKActionEnabled = !saving && !fetching && modelIds().isNotEmpty()` (ProvidersSettingsUi.kt:957). On save success `doOKAction()` sets `outcome` and calls `closeOk()` → `super.doOKAction()` (line 966), which only closes when `getOKAction().isEnabled()` is true. `saving` stays `true`, so OK is disabled and nothing closes even though the log prints "closing dialog".
|
||||
- **Double-click deletes**: `SettingsListView.mouseClicked` fires `primary(item)` on non-button double-clicks (SettingsListView.kt:65-72), and `primary` runs the first enabled cell. A configured custom provider only exposes `DISCONNECT` (ProviderListRows.kt:90), so double-click disconnects.
|
||||
- **Edit feasibility**: `KiloBackendProviderSettingsManager.saveCustom` patches config as an upsert and only touches auth when a key/env is supplied (lines 154-166); a blank key on edit preserves the stored secret. Prefill source is `state.config[id]`: `name`, `options["baseURL"]`, `env` (first entry), `models` (model ids). The API key is a secret and is never returned, so the key field starts blank on edit.
|
||||
- OK label is already `settings.providers.customAdd=Add`; edit mode needs a new "Save" label.
|
||||
|
||||
## Exact edits
|
||||
|
||||
### 1. `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties`
|
||||
|
||||
Add `settings.providers.edit=Edit` right after line 456 (`settings.providers.disconnect=Disconnect`):
|
||||
|
||||
```
|
||||
settings.providers.edit=Edit
|
||||
```
|
||||
|
||||
Add two keys in the custom block (after line 470 `customTitle` / line 471 `customAdd`):
|
||||
|
||||
```
|
||||
settings.providers.customEditTitle=Edit OpenAI-Compatible Provider
|
||||
settings.providers.customSave=Save
|
||||
```
|
||||
|
||||
Only edit the base `KiloBundle.properties`; other locale files fall back to base for missing keys.
|
||||
|
||||
### 2. `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderCatalog.kt`
|
||||
|
||||
Add a helper at the end of the file (after `configured(...)`, line 99):
|
||||
|
||||
```kotlin
|
||||
internal fun customEditable(provider: ProviderSettingsProviderDto, state: ProviderSettingsDto) =
|
||||
state.config[provider.id]?.npm == CUSTOM_PROVIDER_PACKAGE
|
||||
```
|
||||
|
||||
`CUSTOM_PROVIDER_PACKAGE` (`"@ai-sdk/openai-compatible"`) is already defined at line 19 and is the same signal the backend disconnect path uses.
|
||||
|
||||
### 3. `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderListRows.kt`
|
||||
|
||||
Add `EDIT` to the enum (line 12-17):
|
||||
|
||||
```kotlin
|
||||
internal enum class ProviderListAction {
|
||||
CONNECT,
|
||||
OAUTH,
|
||||
EDIT,
|
||||
DISCONNECT,
|
||||
ENABLE,
|
||||
}
|
||||
```
|
||||
|
||||
Update `cells` (lines 35-43) to mark EDIT primary and keep both EDIT and DISCONNECT visible when connected:
|
||||
|
||||
```kotlin
|
||||
override val cells: List<SettingsListCell>
|
||||
get() = actions.map { action ->
|
||||
SettingsListCell(
|
||||
action.name,
|
||||
providerListActionText(action),
|
||||
enabled(action),
|
||||
alwaysVisible = (action == ProviderListAction.DISCONNECT || action == ProviderListAction.EDIT) && connected,
|
||||
primary = action == ProviderListAction.EDIT,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`enabled(action)` (line 45) already returns true for EDIT (`action != DISCONNECT`), no change.
|
||||
|
||||
Add the EDIT label to `providerListActionText` (lines 48-53):
|
||||
|
||||
```kotlin
|
||||
ProviderListAction.EDIT -> KiloBundle.message("settings.providers.edit")
|
||||
```
|
||||
|
||||
Update `providerActions` (lines 83-96) so configured editable custom providers expose EDIT + DISCONNECT:
|
||||
|
||||
```kotlin
|
||||
internal fun providerActions(
|
||||
provider: ProviderSettingsProviderDto,
|
||||
state: ProviderSettingsDto,
|
||||
disabled: Set<String> = state.disabled.toSet(),
|
||||
): List<ProviderListAction> {
|
||||
if (provider.id in disabled) return listOf(ProviderListAction.ENABLE)
|
||||
if (provider.id == KILO_PROVIDER_ID && configured(provider, state, state.connected.toSet())) return emptyList()
|
||||
if (configured(provider, state, state.connected.toSet())) {
|
||||
return if (customEditable(provider, state)) {
|
||||
listOf(ProviderListAction.EDIT, ProviderListAction.DISCONNECT)
|
||||
} else {
|
||||
listOf(ProviderListAction.DISCONNECT)
|
||||
}
|
||||
}
|
||||
val methods = providerMethods(provider, state)
|
||||
return buildList {
|
||||
if (methods.any { it.type == "oauth" }) add(ProviderListAction.OAUTH)
|
||||
if (methods.any { it.type == "api" }) add(ProviderListAction.CONNECT)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUi.kt`
|
||||
|
||||
**4a. Import.** Add to the settings.base imports (near line 7):
|
||||
|
||||
```kotlin
|
||||
import ai.kilocode.client.settings.base.SettingsListSelection
|
||||
```
|
||||
|
||||
**4b. `view` field (line 196).** Pass the new edit callback:
|
||||
|
||||
```kotlin
|
||||
private val view = ProvidersContent(::connect, ::oauth, ::disconnect, ::enable, ::edit)
|
||||
```
|
||||
|
||||
**4c. Replace `custom()` (lines 314-330)** with add + edit + shared open:
|
||||
|
||||
```kotlin
|
||||
@RequiresEdt
|
||||
private fun custom() {
|
||||
checkEdt()
|
||||
openCustomDialog(null)
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
private fun edit(provider: ProviderSettingsProviderDto) {
|
||||
checkEdt()
|
||||
val cfg = state.config[provider.id] ?: return
|
||||
openCustomDialog(
|
||||
CustomProviderEdit(
|
||||
id = provider.id,
|
||||
name = cfg.name ?: provider.name,
|
||||
baseUrl = cfg.options["baseURL"].orEmpty(),
|
||||
envVar = cfg.env.firstOrNull(),
|
||||
models = cfg.models.values.map { it.id },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// The dialog performs the save itself so failures can be shown inline and the user can
|
||||
// correct their input without re-typing. It only closes on a verified success.
|
||||
@RequiresEdt
|
||||
private fun openCustomDialog(existing: CustomProviderEdit?) {
|
||||
checkEdt()
|
||||
val dialog = CustomProviderDialog(
|
||||
cs,
|
||||
directory,
|
||||
{ service<KiloProviderService>().fetchCustomModels(it) },
|
||||
{ service<KiloProviderService>().saveCustom(it) },
|
||||
existing,
|
||||
)
|
||||
if (!dialog.showAndGet()) return
|
||||
val next = dialog.outcome ?: return
|
||||
state = next
|
||||
view.update(next, dialog.savedId)
|
||||
clearProgress()
|
||||
}
|
||||
```
|
||||
|
||||
**4d. `ProvidersContent` (lines 520-591).** Add the `edit` callback and handle EDIT + selection.
|
||||
|
||||
Constructor (lines 520-525):
|
||||
|
||||
```kotlin
|
||||
internal class ProvidersContent(
|
||||
private val connect: (ProviderSettingsProviderDto) -> Unit,
|
||||
private val oauth: (ProviderSettingsProviderDto) -> Unit,
|
||||
private val disconnect: (ProviderSettingsProviderDto) -> Unit,
|
||||
private val enable: (ProviderSettingsProviderDto) -> Unit,
|
||||
private val edit: (ProviderSettingsProviderDto) -> Unit,
|
||||
) : BaseContentPanel() {
|
||||
```
|
||||
|
||||
`update` (lines 536-545) — add optional `select`:
|
||||
|
||||
```kotlin
|
||||
@RequiresEdt
|
||||
fun update(state: ProviderSettingsDto, select: String? = null) {
|
||||
checkEdt()
|
||||
val notes = state.providers.count { providerDescription(it).isNotBlank() }
|
||||
ProvidersSettingsUi.LOG.info("provider settings content update: start providers=${state.providers.size} connected=${state.connected.size} disabled=${state.disabled.size} descriptions=$notes")
|
||||
this.state = state
|
||||
val rows = providerListRows(state, "", disabledRows = busy)
|
||||
if (select != null) view.update(rows, SettingsListSelection.Key(select)) else view.update(rows)
|
||||
ProvidersSettingsUi.LOG.info("provider settings content update: completed rows=${rows.size}")
|
||||
}
|
||||
```
|
||||
|
||||
`activate` `when` block (lines 580-585) — add EDIT:
|
||||
|
||||
```kotlin
|
||||
when (action) {
|
||||
ProviderListAction.CONNECT -> connect(row.provider)
|
||||
ProviderListAction.OAUTH -> oauth(row.provider)
|
||||
ProviderListAction.DISCONNECT -> disconnect(row.provider)
|
||||
ProviderListAction.ENABLE -> enable(row.provider)
|
||||
ProviderListAction.EDIT -> edit(row.provider)
|
||||
}
|
||||
```
|
||||
|
||||
**4e. `CustomProviderDialog`.** Add the edit-prefill data class, constructor param, prefill, `savedId`, edit-aware title/button, and the close fix.
|
||||
|
||||
Add near the dialog (e.g. above `internal class CustomProviderDialog`):
|
||||
|
||||
```kotlin
|
||||
internal data class CustomProviderEdit(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val baseUrl: String,
|
||||
val envVar: String?,
|
||||
val models: List<String>,
|
||||
)
|
||||
```
|
||||
|
||||
Constructor (lines 645-650) — add `existing` as the last param (keeps existing positional test calls valid):
|
||||
|
||||
```kotlin
|
||||
internal class CustomProviderDialog(
|
||||
private val cs: CoroutineScope,
|
||||
private val directory: String,
|
||||
private val fetch: suspend (CustomModelFetchDto) -> CustomModelFetchResultDto,
|
||||
private val save: suspend (CustomProviderSaveDto) -> ProviderActionResultDto,
|
||||
private val existing: CustomProviderEdit? = null,
|
||||
) : DialogWrapper(true) {
|
||||
```
|
||||
|
||||
Add the `savedId` property next to `outcome` (lines 667-669):
|
||||
|
||||
```kotlin
|
||||
// Set once the save succeeds; the panel reads it after the dialog closes to update the list.
|
||||
var outcome: ProviderSettingsDto? = null
|
||||
private set
|
||||
|
||||
// Id of the provider the save persisted; used to select the row after the dialog closes.
|
||||
var savedId: String? = null
|
||||
private set
|
||||
```
|
||||
|
||||
`init` (lines 671-686) — edit-aware title/button and prefill:
|
||||
|
||||
```kotlin
|
||||
init {
|
||||
title = if (existing != null) {
|
||||
KiloBundle.message("settings.providers.customEditTitle")
|
||||
} else {
|
||||
KiloBundle.message("settings.providers.customTitle")
|
||||
}
|
||||
setOKButtonText(
|
||||
if (existing != null) KiloBundle.message("settings.providers.customSave")
|
||||
else KiloBundle.message("settings.providers.customAdd"),
|
||||
)
|
||||
init()
|
||||
initValidation()
|
||||
existing?.let { prefill(it) }
|
||||
models.document.addDocumentListener(object : DocumentAdapter() {
|
||||
override fun textChanged(e: DocumentEvent) {
|
||||
syncActions()
|
||||
}
|
||||
})
|
||||
pick.addActionListener {
|
||||
if (fetching) cancelFetch()
|
||||
else selectModels()
|
||||
}
|
||||
syncActions()
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
private fun prefill(edit: CustomProviderEdit) {
|
||||
id.text = edit.id
|
||||
id.isEditable = false
|
||||
name.text = edit.name
|
||||
url.text = edit.baseUrl
|
||||
env.text = edit.envVar.orEmpty()
|
||||
models.text = edit.models.joinToString(", ")
|
||||
}
|
||||
```
|
||||
|
||||
Save-success branch in `doOKAction` (lines 767-769) — reset `saving`, re-enable OK, record id, close unconditionally:
|
||||
|
||||
```kotlin
|
||||
ProvidersSettingsUi.LOG.info("custom provider add: save succeeded id='${input.id}', closing dialog")
|
||||
outcome = result.state
|
||||
savedId = input.id
|
||||
saving = false
|
||||
syncActions()
|
||||
close(OK_EXIT_CODE)
|
||||
```
|
||||
|
||||
Remove the now-unused `closeOk()` helper (line 966), or replace its body with `close(OK_EXIT_CODE)`; do not leave `super.doOKAction()` in the success path. `OK_EXIT_CODE` is a `DialogWrapper` constant available to the subclass without an import.
|
||||
|
||||
## Tests — `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUiTest.kt`
|
||||
|
||||
Run against the real EDT/component tree with `FakeProviderRpcApi` (no mocks).
|
||||
|
||||
**Update existing tests broken by the new EDIT action / callback:**
|
||||
|
||||
- `content()` helper (line 965): change to `edt { ProvidersContent({}, {}, {}, {}, {}) }` (5 lambdas).
|
||||
- `test configured custom provider exposes only disconnect` (lines 306-324): rename to reflect edit + disconnect and assert `assertEquals(listOf(ProviderListAction.EDIT, ProviderListAction.DISCONNECT), row.actions)`.
|
||||
- `test source custom catalog providers remain visible while configured custom providers are connected` (line 412): change the `rows[0].actions` assertion to `listOf(ProviderListAction.EDIT, ProviderListAction.DISCONNECT)`.
|
||||
|
||||
**Add new tests:**
|
||||
|
||||
- Dialog closes on save success: reuse the `submit` + `flushUntil { edt { dialog.outcome != null } }` pattern; after success assert `dialog.savedId == "my-openai"` and `edt { dialog.isOKActionEnabled }` is `true` (proves the disabled-OK regression is fixed).
|
||||
- Edit prefill: build `CustomProviderDialog(cs, "/tmp", { CustomModelFetchResultDto(listOf("gpt-4o")) }, { ProviderActionResultDto(...) }, CustomProviderEdit("my-openai", "My OpenAI", "https://example.com/v1", null, listOf("gpt-4o")))`. Assert the text fields (indices 0/1/2/4/5 for id/name/url/env/models) are populated, the key field (index 3) is blank, `id` field `isEditable == false`, and the OK button text equals `KiloBundle.message("settings.providers.customSave")`. (Field order via `filterIsInstance<JTextField>()`: 0=id, 1=name, 2=url, 3=key, 4=env, 5=models — `JBPasswordField` is a `JTextField`.)
|
||||
- `providerActions` editable: given a provider with `config[id].npm == "@ai-sdk/openai-compatible"` and configured, assert `[EDIT, DISCONNECT]`, and that the EDIT cell has `primary == true` (via `row.cells.first { it.id == "EDIT" }.primary`).
|
||||
- Non-editable connected provider unchanged: a connected non-custom provider (e.g. `anthropic`, connected) still yields `[DISCONNECT]`.
|
||||
- Edit callback fires instead of disconnect on primary: construct `ProvidersContent` with recording lambdas (capture which provider each callback got), update with a configured editable provider, trigger the primary path, and assert the edit lambda ran and disconnect did not.
|
||||
- Selection after update: `content.update(state, select = "local-openai")` then assert the list's selected row key is `"local-openai"`.
|
||||
|
||||
## Validation
|
||||
|
||||
Run from `packages/kilo-jetbrains/`:
|
||||
|
||||
- `bun run typecheck` (or `./gradlew typecheck`).
|
||||
- `./gradlew test` (target `ProvidersSettingsUiTest` first if iterating).
|
||||
- Manual smoke via `./gradlew runIde`: add a custom provider → dialog closes and the new row is selected; double-click the row → edit dialog opens prefilled with the id field locked and "Save" button; the connected custom row shows both Edit and Disconnect.
|
||||
|
||||
Java 21 is required for Gradle; only check `java -version` if a Gradle command fails with a Java error.
|
||||
|
||||
## Notes / risks
|
||||
|
||||
- API key is intentionally not prefilled on edit (secret). Blank key preserves the stored key; a new value updates it — matches `saveCustom` behavior.
|
||||
- The id field is locked on edit because the id is the config key; renaming would create a second provider.
|
||||
- Double-click on a visible action button (Edit/Disconnect) still routes through the single-cell click path, not `primary`; only double-clicks on the non-button row area trigger the primary (EDIT) action.
|
||||
- Keep new identifiers single-word where clear (`existing`, `cfg`, `select`, `edit`, `prefill`, `savedId`).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Editing headers, per-model capabilities (reasoning), or config scope (global vs workspace) beyond the current add dialog.
|
||||
- Any changes to shared `packages/opencode/` code, RPC contracts, or DTOs.
|
||||
@@ -1,198 +0,0 @@
|
||||
# Consolidate JetBrains model pickers into a shared PickerPopup base
|
||||
|
||||
## Goal
|
||||
|
||||
Remove duplication between the two list-picker popups in the JetBrains frontend by
|
||||
extracting a generic base, and make both consume it:
|
||||
|
||||
1. Prompt model picker — `session/ui/model/ModelPicker.kt` (`showPopup`) — single-select
|
||||
commit, search field, expand/collapse details panel, sections, per-row favorite (star)
|
||||
toggle.
|
||||
2. Custom provider model picker — `settings/providers/ProvidersSettingsUi.kt`
|
||||
(`showModelPopup` inside `CustomProviderDialog`) — multi-select toggle, no search, no
|
||||
details, selection stored in the `models` text field.
|
||||
|
||||
The base must expose configurable options; the provider picker enables: multi-select, no
|
||||
expanded/details view, and extra header toolbar buttons ("Select All" / "Deselect All").
|
||||
|
||||
All work is under `packages/kilo-jetbrains/frontend/`. No backend/RPC/DTO changes. Files are
|
||||
Kilo-owned (no `kilocode_change` markers).
|
||||
|
||||
## Resolved decisions
|
||||
|
||||
- **Approach**: Extract a generic `PickerPopup<T>` base; both pickers become thin configs.
|
||||
- **Selection state**: Caller owns it. Base is selection-stateless: it calls `checked(row)`
|
||||
to render check state and `onPrimary(row)` on activation. Provider keeps the `models` text
|
||||
field as source of truth; prompt keeps its favorites callbacks.
|
||||
- **Select All / Deselect All**: Two caller-supplied header toolbar buttons. Remove the
|
||||
in-list `CustomModelRow.selectAll` row entirely.
|
||||
- **Renderer**: Extract a base renderer skeleton too; `ModelPickerRenderer` and the provider
|
||||
renderer extend it. Generalize the favorite hit-zone into a trailing click-zone helper.
|
||||
- **Location**: New package `ai/kilocode/client/ui/picker/` with `PickerPopup<T>` and
|
||||
`PickerListRenderer<T>`. `PickerRow` and `ModelSearch` stay where they are and are reused.
|
||||
|
||||
## Base design
|
||||
|
||||
### `ui/picker/PickerPopup.kt` — generic popup
|
||||
|
||||
Owns: `JBList<T>` + `CollectionListModel<T>`, `createComponentPopupBuilder` with the shared
|
||||
flags (requestFocus/focusable/cancelOnClickOutside/cancelKeyEnabled/cancelOnWindowDeactivation/
|
||||
locateWithinScreenBounds, non-resizable, non-movable), `popupBackground` helper (move the
|
||||
duplicated `NewUI -> Popup.BACKGROUND else getListBackground()` here), header assembly,
|
||||
mouse hit-testing, keyboard bindings, auto-select-on-move + scrolling install, and size
|
||||
computation.
|
||||
|
||||
Config (constructor params / vars, defaults match current behavior):
|
||||
|
||||
- `anchor: JComponent`, `placement: Placement` (`ABOVE` / `BELOW` / `UNDERNEATH`). Prompt uses
|
||||
`PopupShowOptions.aboveComponent` / `showUnderneathOf`; provider uses `showUnderneathOf(pick)`.
|
||||
- `rows: (query: String) -> List<T>` — rebuilt when the search text changes. Provider passes a
|
||||
function that ignores `query`.
|
||||
- `renderer: PickerListRenderer<T>`.
|
||||
- `checked: (T) -> Boolean` — drives the row check icon (single: `row.key == active`; multi:
|
||||
`isSelected(row)`).
|
||||
- `sectionTitle: (rows: List<T>, index: Int) -> String?` — optional; prompt supplies
|
||||
`modelPickerSectionTitle`, provider passes `{ _, _ -> null }`.
|
||||
- `mode: Mode` = `Single` | `Multi`.
|
||||
- Single: on primary click/ENTER call `onPrimary(row)` then `popup.closeOk(null)`.
|
||||
- Multi: on primary click/SPACE/ENTER call `onPrimary(row)`, repaint, stay open.
|
||||
- `onPrimary: (T) -> Unit`. (Prompt decides activate-vs-clear inside based on `row.item == null`;
|
||||
provider toggles membership in the text field.)
|
||||
- Optional trailing toggle: `trailingHit: (list, bounds, point) -> Boolean` and
|
||||
`onTrailing: (T) -> Unit`. Prompt supplies favorite hit-zone + favorite toggle; provider omits.
|
||||
Keyboard: in Single mode `Shift+SPACE` triggers trailing when present.
|
||||
- `search: Boolean` — show `SearchTextField` in header CENTER. Prompt true; provider false.
|
||||
- `toolbar: List<JComponent>` — extra header buttons placed in header WEST. Provider passes
|
||||
Select All / Deselect All; prompt passes empty.
|
||||
- Optional details/expand: `details: JComponent?` + `onPreview: (T?) -> Unit` +
|
||||
`expandStateKey: String?`. When non-null, base shows the expand `HoverIcon`, EAST details
|
||||
panel, persists expanded state via `PropertiesComponent`, reserves details width when
|
||||
expanded, and `Disposer.register(popup, details)` if `details is Disposable`. Provider passes
|
||||
null (no expand toggle, no details).
|
||||
- Sizing: `minWidth`, `maxWidth`, `maxVisibleRows`, `emptyListHeight` (defaults = current
|
||||
ModelPicker constants 420/760/10/120). Provider passes `minWidth = 320` and a `maxWidth`
|
||||
that preserves today's look. Reuse the existing `computeInitialPopupSize` /
|
||||
`computeListPreferredWidth` / `computeListPreferredHeight` logic, moved into the base and
|
||||
parameterized by these constants + optional details width.
|
||||
|
||||
Header layout: `BorderLayout` with `toolbar` row WEST, `search` CENTER (empty when
|
||||
`search=false`), expand `HoverIcon` EAST (only when details present). Preserve
|
||||
`AbstractPopup.customizeSearchFieldLook` and background wiring.
|
||||
|
||||
Keyboard (registered on both the list and, when present, the search editor):
|
||||
|
||||
- `UP`/`DOWN` → move selection (search field only; list uses `ScrollingUtil`).
|
||||
- `ENTER` → primary on selected row.
|
||||
- `ESC` → `popup.cancel()`.
|
||||
- Single + trailing present → `Shift+SPACE` → trailing on selected row.
|
||||
- Multi → `SPACE` → primary (toggle) on selected row.
|
||||
|
||||
Mouse (`mouseReleased`, `UIUtil.isActionClick`): resolve row via `locationToIndex` +
|
||||
`getCellBounds`/`contains`; if trailing present and `trailingHit` → `onTrailing` + consume;
|
||||
else `onPrimary` (Single closes, Multi consumes + repaints).
|
||||
|
||||
### `ui/picker/PickerListRenderer.kt` — renderer skeleton
|
||||
|
||||
Abstract `ListCellRenderer<T>` base: `PickerRow` wrap + optional top `GroupHeaderSeparator`
|
||||
(driven by `sectionTitle`) + `[check icon | content | trailing]` layout with the shared
|
||||
transparent row + `UiStyle.Gap.md/lg/md/pad` insets. Provides:
|
||||
|
||||
- `check` icon column (`AllIcons.Actions.Checked` / `EmptyIcon`), set from `checked(row)`.
|
||||
- protected `content: JComponent` slot and optional `trailing: JComponent` slot.
|
||||
- section separator top panel wiring (`top`/`sep` from `ModelPickerRenderer`).
|
||||
- a companion `trailingClickZone(list, bounds, point, width)` generalizing
|
||||
`ModelPickerRenderer.isFavoriteClick` (keep `FAVORITE_CLICK_AREA_WIDTH` behavior).
|
||||
|
||||
## Ordered implementation tasks
|
||||
|
||||
1. **Create `ui/picker/PickerListRenderer.kt`**: base renderer skeleton with check column,
|
||||
`PickerRow` wrap, top section separator, content/trailing slots, transparent row + insets,
|
||||
and the `trailingClickZone` helper (moved/generalized from `ModelPickerRenderer.isFavoriteClick`
|
||||
/ `favoriteInset`).
|
||||
2. **Create `ui/picker/PickerPopup.kt`**: generic popup per the design above, including the
|
||||
moved `popupBackground` helper and the sizing helpers (parameterized).
|
||||
3. **Refactor `ModelPickerRenderer`** to extend `PickerListRenderer`: content = title
|
||||
(`SimpleColoredComponent`) + warn + free/BYOK badges + provider label; trailing = favorite
|
||||
star; check via `checked`. Keep existing internal test accessors (`starIcon`,
|
||||
`badgeVisible`, `badgeText`, `byokVisible`, `warningVisible`, `warningTooltip`) and the
|
||||
`DATA_COLLECTED`/`checked`/`empty` companion members so `ModelPickerTest` compiles unchanged.
|
||||
4. **Refactor `ModelPicker.showPopup`** to build a `PickerPopup<ModelPickerRow>` configured
|
||||
as: Single mode; `rows = { q -> modelPickerRows(items, favorites(), q, allowEmpty, emptyText, includeSmall) }`;
|
||||
`checked = { it.key == selected?.key }`; `sectionTitle = ::modelPickerSectionTitle`;
|
||||
`onPrimary = { row -> row.item?.let(::activate) ?: clear() }`; trailing = favorite hit-zone +
|
||||
`onFavoriteToggle`; `search = true`; details = `ModelDetailsPanel` with `expandStateKey =
|
||||
MODEL_PICKER_EXPANDED_KEY`; placement from `placement`; sizing constants = current values.
|
||||
Keep `ModelPicker`'s public API (`setItems`, `select`, `clearSelection`, `open`, callbacks,
|
||||
`Placement`, `Item`) and test hooks (`selectedForTest`, `selectionKeyForTest`,
|
||||
`expandedForTest`) unchanged so `PromptPanel`, `settings/models`, and `settings/agents`
|
||||
consumers are unaffected.
|
||||
5. **Add provider picker renderer** (small subclass of `PickerListRenderer<String>` in
|
||||
`settings/providers/`, or a shared simple text renderer): content = a `JBLabel` showing the
|
||||
model id; no trailing; check via `checked`.
|
||||
6. **Rewrite `CustomProviderDialog.showModelPopup(ids)`** to build a `PickerPopup<String>`:
|
||||
Multi mode; `rows = { _ -> ids }` (raw model ids, no select-all row); `checked = { it in modelIds().toSet() }`;
|
||||
`onPrimary = { toggleModel(it) }`; `sectionTitle = { _, _ -> null }`; `search = false`;
|
||||
`toolbar = listOf(selectAllButton, deselectAllButton)`; details = null; `minWidth = 320`;
|
||||
anchor = `pick`, placement `UNDERNEATH`.
|
||||
7. **Extract provider selection mutations into testable helpers** on/near `CustomProviderDialog`
|
||||
operating on the `models` text field: `toggleModel(id)`, `selectAllModels(ids)`,
|
||||
`clearModels()` (reuse existing `modelIds()` / `setModelIds()`). Wire the two toolbar buttons
|
||||
to `selectAllModels(ids)` and `clearModels()`.
|
||||
8. **Remove dead code**: `CustomModelRow`, `customModelRows`, `CustomModelRowRenderer`,
|
||||
`CUSTOM_MODEL_POPUP_MIN_WIDTH`/`CUSTOM_MODEL_POPUP_MAX_ROWS` (fold into base config), and the
|
||||
now-duplicated inline `popupBackground` in `ProvidersSettingsUi.kt`.
|
||||
9. **Strings**: add a "Deselect All" key to `KiloBundle.properties` (e.g.
|
||||
`settings.providers.customModelsDeselectAll=Deselect All`); reuse existing
|
||||
`settings.providers.customModelsSelectAll` for "Select All".
|
||||
|
||||
## Tests
|
||||
|
||||
- **Keep passing unchanged**: `ModelPickerTest.kt` (row builder + renderer + `ModelSearch` +
|
||||
favorite hit-zone). Because `ModelPickerRenderer` keeps its accessors and companion members and
|
||||
`modelPickerRows`/`ModelPickerRow` are untouched, these should compile and pass as-is. Verify
|
||||
the favorite hit-zone tests still reference a working symbol (either keep
|
||||
`ModelPickerRenderer.isFavoriteClick` delegating to `PickerListRenderer.trailingClickZone`, or
|
||||
update the 3 call sites in `ModelPickerTest.kt` to the new helper).
|
||||
- **Update** `ProvidersSettingsUiTest.kt`:
|
||||
- Replace `test custom model rows start with select all` (there is no select-all row anymore)
|
||||
with tests for the extracted helpers: `toggleModel` adds/removes an id in the text field;
|
||||
`selectAllModels(ids)` sets all; `clearModels()` empties it.
|
||||
- Keep existing add/edit/delete and dialog-close tests working.
|
||||
- **New**: a small `PickerListRenderer`/`PickerPopup` unit test where feasible without a live
|
||||
popup — e.g. `trailingClickZone` geometry (mirroring the current `isFavoriteClick` tests) and
|
||||
the provider text renderer check-state. Avoid asserting live-popup internals; follow the
|
||||
existing pattern of testing renderers/row-builders/pure helpers directly (per package AGENTS
|
||||
"test the real implementation, no mocks").
|
||||
|
||||
## Validation
|
||||
|
||||
From `packages/kilo-jetbrains/`:
|
||||
|
||||
- `./gradlew typecheck`
|
||||
- `./gradlew test` (iterate with `--tests ai.kilocode.client.session.ui.model.ModelPickerTest`
|
||||
and `--tests ai.kilocode.client.settings.providers.ProvidersSettingsUiTest`).
|
||||
- Manual smoke via `./gradlew runIde`:
|
||||
- Prompt model picker: search, arrow/enter select, favorite star toggle, expand/collapse
|
||||
details, placement — all unchanged.
|
||||
- Add custom provider → fetch models → popup shows model list with "Select All"/"Deselect All"
|
||||
toolbar buttons, per-row multi-select toggles, no details/expand, selection reflected in the
|
||||
Model IDs field; Save persists selected models.
|
||||
|
||||
## Risks / notes
|
||||
|
||||
- `ModelPicker`'s public API and test hooks must stay stable — several settings screens depend on
|
||||
it. Only the popup internals move.
|
||||
- The details/expand panel is `Disposable`; the base must register it with the popup exactly as
|
||||
today to avoid leaks.
|
||||
- Sizing differs (prompt 420/760 dynamic + details; provider fixed 320). Parameterize rather than
|
||||
hard-code; keep provider width visually equivalent to current.
|
||||
- Popups are hard to unit test; keep coverage on pure helpers/renderers, not live-popup behavior.
|
||||
- Keyboard/mouse semantics differ by mode (single-commit-close vs multi-toggle-stay-open) and by
|
||||
presence of a trailing toggle — encode these in the base mode, don't special-case callers.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- The other picker popups (`ModePicker`, `ReasoningPicker`, `SessionAccountOverlay`) — not part
|
||||
of this consolidation.
|
||||
- Adding search to the provider picker (kept off to match current behavior; base supports it).
|
||||
- Any backend/RPC/DTO or CLI changes.
|
||||
@@ -1,67 +0,0 @@
|
||||
# JetBrains MdView Editor Theme Colors Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Keep JetBrains markdown layout compact while making markdown colors and backgrounds come from the active editor color scheme, and ensure existing theme/editor-setting listeners refresh all existing `MdView` instances after changes.
|
||||
|
||||
## Findings
|
||||
|
||||
- `SessionUi` already subscribes to `EditorColorsManager.TOPIC` and `LafManagerListener.TOPIC`, then calls `applyStyle(SessionEditorStyle.current())` on the session tree.
|
||||
- Most markdown consumers already propagate that style through `MdView.applyStyle(style)` via `TextView`, `ReasoningView`, message lists, and session panels.
|
||||
- `PlanExitView.applyStyle()` sets only font/code font/foreground and does not call `md.applyStyle(style)`, so internal markdown role colors can stay tied to the initial style.
|
||||
- `MdViewHybrid.applyStyle()` restyles retained HTML panes and code block containers, but retained `CodeField` editors need explicit reapplication of `SessionEditorStyle.applyToEditor()` after creation so syntax highlighting and editor colors follow scheme changes.
|
||||
- `MdCommon.defaults()` currently mixes editor colors, UI theme colors, and one literal inline-code color fallback. The literal fallback should be removed, and markdown roles should derive from `EditorColorsScheme`/syntax attributes wherever possible.
|
||||
- `TextView` currently overrides markdown background with `SessionUiStyle.Transcript.bgColor()` (`UiStyle.Colors.bg()`), while prompt markdown already uses `style.editorBackground`. If markdown surfaces should consistently use editor background, normal text views need to use `style.editorBackground` too.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
1. Keep compact CSS unchanged.
|
||||
- Do not reintroduce line-height, margin, padding, heading sizing, or other geometry rules.
|
||||
- Keep the current role color/background selectors only.
|
||||
|
||||
2. Derive markdown role defaults from editor settings.
|
||||
- Update `MdCommon.defaults(style)` to use `style.editorForeground` and `style.editorBackground` for primary text/background.
|
||||
- Add small local helper functions for editor attributes, for example foreground/background from `style.editorScheme.getAttributes(key)` and color keys from `style.editorScheme.getColor(key)`.
|
||||
- Use public IntelliJ editor keys for role colors:
|
||||
- Links: `CodeInsightColors.HYPERLINK_ATTRIBUTES.foregroundColor`, fallback to platform link color if absent.
|
||||
- Inline code foreground/background: `DefaultLanguageHighlighterColors.DOC_CODE_INLINE`, fallback to `STRING`, then editor foreground/background.
|
||||
- Code block foreground/background: `DefaultLanguageHighlighterColors.DOC_CODE_BLOCK`, fallback to editor foreground/background.
|
||||
- Quote/emphasis/list weak text: comment/doc-comment attributes, fallback to editor foreground or `UIUtil.getContextHelpForeground()` only when the scheme has no useful value.
|
||||
- Borders/HR/table/code border: editor preview/border color keys such as `EditorColors.PREVIEW_BORDER_COLOR`, fallback to `UiStyle.Colors.contentBorder()`.
|
||||
- Remove `JBColor(0x...)` or other literal runtime color fallbacks from `MdCommon`.
|
||||
- Keep public `MdView` API unchanged; role colors stay internal unless a concrete external override need appears.
|
||||
|
||||
3. Ensure style propagation reaches every markdown instance.
|
||||
- Update `PlanExitView.applyStyle(style)` to call `md.applyStyle(style)` before applying its explicit font/code-font/foreground overrides.
|
||||
- Audit existing markdown callers after the change; keep using the existing `SessionEditorStyleTarget` propagation path rather than adding per-`MdView` theme listeners.
|
||||
- Keep explicit foreground overrides in `TextView`, `ReasoningView`, and `PlanExitView` where they intentionally set body text role, but let internal markdown role colors refresh from the new style snapshot.
|
||||
|
||||
4. Ensure retained hybrid code blocks update after editor setting changes.
|
||||
- In `MdViewHybrid.CodeView.style(opts)`, for retained `CodeField` blocks, reapply `style.applyToEditor(editor)` to the underlying editor if it exists.
|
||||
- Reapply code editor background/scroll pane/viewport backgrounds from `opts.preBg` in the same path.
|
||||
- Preserve retained component/editor reuse: do not rebuild code block panes just to update style.
|
||||
|
||||
5. Align markdown backgrounds with editor settings.
|
||||
- Keep `MdCommon` default background as `style.editorBackground`.
|
||||
- Change normal `TextView` markdown background to `style.editorBackground` if the intent is that all markdown surfaces use editor background, matching `PromptView` and session root behavior.
|
||||
- Preserve `transparent` handling: when `md.opaque = false`, background should still be the editor-derived value for child/code surfaces, but the Swing component should remain non-opaque.
|
||||
|
||||
6. Update tests.
|
||||
- Extend `MdViewTest` to verify markdown role CSS changes when applying a `SessionEditorStyle` backed by a customized editor scheme, especially inline code, code block, link, and border colors.
|
||||
- Extend `MdViewHybridTest` to assert `applyStyle()` updates retained HTML panes and retained code editors without replacing them, including editor scheme/background changes.
|
||||
- Add or extend `PlanExitViewTest` so `applyStyle()` refreshes the nested markdown style, not just foreground/font overrides.
|
||||
- Keep existing compactness expectations: tests should not assert new spacing, padding, margin, line-height, or size rules.
|
||||
|
||||
## Verification
|
||||
|
||||
- From `packages/kilo-jetbrains/`, run `./gradlew frontend:test --tests '*MdView*'`.
|
||||
- From `packages/kilo-jetbrains/`, run `./gradlew frontend:test --tests '*PlanExitViewTest*'` if the focused test is not covered by the MdView filter.
|
||||
- From `packages/kilo-jetbrains/`, run `bun run typecheck`.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not introduce JCEF, Compose, or Kotlin UI DSL.
|
||||
- Do not add new theme/editor listeners in `MdView`; use the existing `SessionUi` listener and `SessionEditorStyleTarget` propagation path.
|
||||
- Avoid hardcoded runtime colors in markdown styling; prefer editor scheme attributes/color keys, then platform/theme APIs as non-literal fallbacks.
|
||||
- Preserve retained Swing behavior and `MdViewHybrid.sync()` component reuse.
|
||||
- Keep the public `MdView` override API stable unless implementation proves a new external override is required.
|
||||
@@ -1,69 +0,0 @@
|
||||
# JetBrains MdView VS Code Styling Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Improve JetBrains markdown output so assistant/user transcript markdown visually matches the VS Code webview markdown style while keeping the existing Swing/JBHtmlPane + editor-backed code block architecture.
|
||||
|
||||
## Findings
|
||||
|
||||
- VS Code markdown styling is split across `packages/ui/src/components/markdown.css`, `packages/kilo-ui/src/components/markdown.css`, `packages/kilo-ui/src/styles/vscode-bridge.css`, and message-part overrides.
|
||||
- Base VS Code markdown uses 14px sans text, 160% line height, tight first/last margins, same-size medium headings, 12px paragraph spacing, link-colored anchors, compact lists, weak list markers, weak blockquotes with a 2px left border, invisible HR spacing, bordered/padded code blocks, green inline code, and lightly bordered tables.
|
||||
- The VS Code theme bridge maps markdown roles to editor/theme tokens: heading/link/list/image use `textLinkForeground`, text/strong/code-block use editor foreground, inline code uses charts/syntax green, quote/emphasis use description foreground, HR uses panel border.
|
||||
- JetBrains markdown is rendered by `MdViewHybrid` and `MdViewHtmlPane`, with shared CSS from `MdCommon.rules()` and defaults from `MdCommon.defaults()`.
|
||||
- JetBrains currently styles only broad tag font/color, links, code/pre colors, blockquote border/text color, and table border. It lacks VS Code-equivalent spacing, heading/strong/emphasis/list marker/table cell rules, inline-code foreground, blockquote geometry, HR spacing, and code block surface polish.
|
||||
- JetBrains fenced code blocks are already stronger than VS Code in one respect: they use `EditorTextField` with real IDE syntax highlighting and streaming retention. Preserve this instead of switching to web/JCEF rendering.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
1. Expand JetBrains markdown style tokens.
|
||||
- Add internal fields to `MdStyle` for heading, strong, emphasis, inline code foreground, list marker, HR, table/header, and code block border colors.
|
||||
- Keep the public `MdView` override API stable unless a new external override is clearly needed.
|
||||
- Compute defaults in `MdCommon.defaults(style)` from IntelliJ/editor theme sources and centralized Kilo semantic colors where no platform key matches.
|
||||
- Use `JBColor.namedColor("Kilo.Markdown.*", fallback)` for Kilo-specific markdown palette fallbacks, so themes can override them and runtime code avoids scattered hardcoded colors.
|
||||
|
||||
2. Mirror VS Code markdown CSS in `MdCommon.rules()`.
|
||||
- Add root/body wrapping rules: max width behavior, break-word wrapping, base line-height, and first/last-child margin trimming where supported by `JBHtmlPane` CSS.
|
||||
- Add heading rules: same base size, medium/bold weight, role-specific color, line height, and bottom spacing.
|
||||
- Add paragraph, list, list item, nested list, and marker rules. If Swing HTML does not support `::marker`, fall back to `li { color: ... }` plus child text color reset only if supported; otherwise keep list text normal and document the limitation in tests.
|
||||
- Add strong/emphasis colors matching VS Code token roles.
|
||||
- Add anchor styling matching VS Code: themed link color, no forced background, and underline behavior where `JBHtmlPane` supports it.
|
||||
- Add blockquote geometry: 2px left border, 24px vertical margin, 8px left padding, weak text, and normal style.
|
||||
- Add table layout rules: collapse borders, full width where possible, 24px vertical margin, 12px cell padding, weak row borders, stronger header text.
|
||||
- Keep HRs visually hidden but spaced consistently with VS Code if the renderer includes them. `MdViewHybrid` currently filters thematic breaks, so this mainly benefits `MdViewHtmlPane` and future reuse.
|
||||
- Add inline-code foreground and medium font weight. Avoid inline code backgrounds unless the current `JBHtmlPane` configuration already draws them acceptably.
|
||||
|
||||
3. Polish JetBrains code block containers without losing IDE highlighting.
|
||||
- Keep `EditorTextField` for fenced/indented blocks and `JBTextArea` fallback.
|
||||
- Style `JBScrollPane` code blocks to match VS Code’s `markdown-code` wrapper feel: subtle background, subtle border, rounded-ish platform arc if feasible, 12px-ish padding, and thin horizontal scrollbar behavior.
|
||||
- Use `SessionUiStyle.View.Code` for geometry constants. Add only minimal new constants there if current values cannot represent the VS Code spacing.
|
||||
- Separate code block border color from table border internally so table styling can change without affecting code boxes.
|
||||
- Continue applying `SessionEditorStyle.applyToEditor(ed)` so code blocks follow IDE syntax highlighting and editor font changes.
|
||||
|
||||
4. Add file/path affordance parity where safe.
|
||||
- For markdown links whose `href` looks like a relative file path, keep existing link dispatch so the current caller can open files/URLs appropriately.
|
||||
- Consider decorating inline code that looks like a path with a `file-link` class only when an `openFile` callback is available through the existing usage path. If the current `MdView` abstraction only has `openUrl`, do not widen it unless the call sites can pass file opening cleanly.
|
||||
- At minimum, make inline code/path-looking content visually closer to VS Code by using the inline-code foreground and dotted underline for explicit file links where generated HTML contains link/code classes.
|
||||
|
||||
5. Preserve retained Swing behavior.
|
||||
- Keep `MdViewHybrid.sync()` prefix reuse logic unchanged unless necessary.
|
||||
- Ensure style updates call `reloadCssStylesheets()` and reassign text only for retained `JBHtmlPane` blocks, not by rebuilding all blocks.
|
||||
- Keep streaming fenced-code fast path and editor disposal behavior intact.
|
||||
|
||||
6. Add focused tests.
|
||||
- Extend `MdViewTest` and/or `MdViewHybridTest` to assert `overrideSheet()` contains the new VS Code-equivalent rules for headings, strong/emphasis, links, inline code foreground, list/table/blockquote spacing, HR, and code block/table border separation.
|
||||
- Add component tests for code block pane styling: background, viewport background, border color, padding, scrollbar policy, and retained editor instance after `applyStyle()`.
|
||||
- Keep existing stress/leak tests green. Add a small stress assertion only if the implementation changes style application semantics.
|
||||
- Add a changeset: `@kilocode/kilo-jetbrains` patch with user-facing wording such as `Improve markdown readability in JetBrains chat transcripts.`
|
||||
|
||||
## Verification
|
||||
|
||||
- Run targeted JetBrains markdown tests first from `packages/kilo-jetbrains/`: `./gradlew frontend:test --tests '*MdView*'` if the Gradle module supports it; otherwise run the closest supported targeted Gradle test command.
|
||||
- Run `bun run typecheck` from `packages/kilo-jetbrains/`.
|
||||
- If targeted Gradle filtering is unreliable, run `./gradlew test` from `packages/kilo-jetbrains/`.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not introduce JCEF, Compose, or Kotlin UI DSL.
|
||||
- Keep changes inside `packages/kilo-jetbrains/` and `.changeset/` unless a shared Kilo UI source of truth is explicitly required.
|
||||
- No `kilocode_change` markers are needed for JetBrains or Kilo UI paths.
|
||||
- Prefer IntelliJ theme APIs and centralized semantic tokens over scattered literal colors.
|
||||
@@ -1,63 +0,0 @@
|
||||
# JetBrains Session UI Icons And Header Layout Plan
|
||||
|
||||
## Goal
|
||||
Improve JetBrains session UI icon consistency and reduce accidental header interactions:
|
||||
- Use Kilo/VS Code-aligned session icons in session views.
|
||||
- Fix the reasoning header icon.
|
||||
- Normalize session part collapse/expand chevrons so collapsed/expanded states do not jump between differently sized glyphs.
|
||||
- Move the session-details toggle away from compaction and place it before the session title.
|
||||
|
||||
## Findings
|
||||
- Session view icons are centralized in `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/SessionViewIcons.kt` and loaded from `frontend/src/main/resources/icons/views/*.svg`.
|
||||
- The JetBrains `views` SVGs already mirror the shared VS Code/UI icon paths from `packages/ui/src/components/icon.tsx` for the audited names, including `brain`, `chevron-down`, `chevron-right`, `checklist`, `console`, `warning`, etc.
|
||||
- The reasoning view currently renders `SessionViewIcons.eye` in `ReasoningView.kt`; VS Code/shared UI uses the `brain` icon for reasoning/thinking surfaces, and `SessionViewIcons.brain` already exists.
|
||||
- Standard collapsible session parts use `SessionViewIcons.chevronDown` when expanded and `SessionViewIcons.chevronRight` when collapsed in `AbstractSessionPartView.kt`; `QuestionResultView.kt` repeats this pattern manually. The down/right SVG paths have different visual extents.
|
||||
- The session header currently places the details toggle next to the compact button in the right-side controls in `SessionHeaderPanel.kt`, making the two actions easy to confuse.
|
||||
|
||||
## Implementation Steps
|
||||
1. **Keep icon sources aligned with VS Code/shared UI**
|
||||
- Treat `packages/ui/src/components/icon.tsx` as the source for Kilo web/session glyph shapes.
|
||||
- Re-check `SessionViewIcons.kt` entries against available JetBrains assets; only update or add SVGs if a session view uses a Kilo icon missing from `frontend/src/main/resources/icons/views/`.
|
||||
- Preserve JetBrains SVG theming rules: no `currentColor`; use literal palette colors and dark variants where assets are added or changed.
|
||||
|
||||
2. **Fix reasoning icon**
|
||||
- In `ReasoningView.kt`, change the reasoning header glyph from `SessionViewIcons.eye` to `SessionViewIcons.brain`.
|
||||
- Add or update test coverage in `ReasoningViewTest.kt` by inspecting the rendered Swing label tree and asserting the reasoning icon is `SessionViewIcons.brain`.
|
||||
|
||||
3. **Normalize collapse/expand chevrons for session parts**
|
||||
- Stop using the mixed `chevronRight`/`chevronDown` pair for collapsible session content.
|
||||
- Use a single base Kilo chevron glyph for both states, matching the current custom chevron used by the session header (`/icons/chevron-down.svg` / equivalent `SessionViewIcons.chevronDown`).
|
||||
- Add a shared rotated icon for the opposite state instead of switching to a differently sized right-facing asset. Prefer a small reusable helper or centralized icon field rather than importing header-specific UI into session views.
|
||||
- Update `AbstractSessionPartView.kt` and `QuestionResultView.kt` to use the normalized chevron pair.
|
||||
- Leave `QuestionView.kt` navigation chevrons alone unless auditing shows they are being used for collapse/expand; those are previous/next controls, not expand/collapse controls.
|
||||
|
||||
4. **Relocate and change header show/hide details toggle**
|
||||
- In `SessionHeaderPanel.kt`, replace the custom header details chevron with platform `AllIcons` arrows/chevrons, e.g. collapsed = `AllIcons.General.ArrowRight`, expanded = `AllIcons.General.ArrowDown`.
|
||||
- Move the details toggle out of the right-side controls and into `BorderLayout.WEST` of the header row.
|
||||
- Rebuild the top header as:
|
||||
- outer border layout
|
||||
- west: details toggle button
|
||||
- center: inner border layout
|
||||
- inner center: session title
|
||||
- inner east: horizontal stack/row with price/context and compact button
|
||||
- Remove the details toggle from the right-side row so compaction remains visually separate from show/hide details.
|
||||
- Keep existing tooltip/accessibility strings and expansion persistence behavior unchanged.
|
||||
|
||||
5. **Tests**
|
||||
- Update `SessionHeaderPanelTest.kt` to assert:
|
||||
- collapsed/expanded header details icons use the selected `AllIcons` constants;
|
||||
- the details toggle persists expansion state as before;
|
||||
- the details toggle is parented/laid out separately from the compact button.
|
||||
- Update `AbstractSessionPartViewTest.kt` to assert collapsible parts keep the same icon dimensions across collapsed/expanded states and no longer use the mismatched right/down pair.
|
||||
- Update `QuestionResultViewTest.kt` similarly because it has its own chevron implementation.
|
||||
- Update `ReasoningViewTest.kt` for the brain icon.
|
||||
|
||||
6. **Verification**
|
||||
- Run the smallest relevant JetBrains checks from `packages/kilo-jetbrains/`:
|
||||
- `./gradlew test --tests "ai.kilocode.client.session.views.ReasoningViewTest" --tests "ai.kilocode.client.session.views.base.AbstractSessionPartViewTest" --tests "ai.kilocode.client.session.views.QuestionResultViewTest" --tests "ai.kilocode.client.session.ui.header.SessionHeaderPanelTest"`
|
||||
- `./gradlew typecheck`
|
||||
- If the filtered Gradle test syntax is not accepted by the project, run `./gradlew test` from `packages/kilo-jetbrains/` instead.
|
||||
|
||||
## Notes
|
||||
- No shared upstream `opencode` files are involved; changes stay under `packages/kilo-jetbrains/`.
|
||||
- A changeset may be needed because this is user-facing JetBrains UI polish; confirm existing changeset policy for the private JetBrains package during implementation.
|
||||
@@ -1 +0,0 @@
|
||||
bun run extension
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
name: gh-issues
|
||||
description: Use when creating, triaging, or commenting on GitHub issues for the Kilo VS Code extension or JetBrains plugin via `gh`. Covers issue templates, project board assignment, title conventions, and required `gh` scopes.
|
||||
---
|
||||
|
||||
# GitHub Issues
|
||||
|
||||
Use this skill whenever you create or manage a GitHub issue with `gh` for either the VS Code extension or the JetBrains plugin.
|
||||
|
||||
## Templates
|
||||
|
||||
The repo defines issue templates in `.github/ISSUE_TEMPLATE/`. Pick the matching template instead of opening a blank issue:
|
||||
|
||||
| Template | When to use |
|
||||
|---|---|
|
||||
| `Bug report` (`bug-report.yml`) | Reproducible defects with steps, expected, and actual behavior |
|
||||
| `Feature Request` (`feature-request.yml`) | New capabilities, enhancements, or behavior changes |
|
||||
| `Question` (`question.yml`) | Usage or design questions that aren't obviously bugs or feature requests |
|
||||
|
||||
Pass the template title to `gh issue create --template`.
|
||||
|
||||
## Title Conventions
|
||||
|
||||
- Use a plain, descriptive title that reads cleanly as a standalone sentence.
|
||||
- Do not add platform-specific prefixes such as `[JetBrains]`, `[Jetbrains]`, `[JB]`, `[VS Code]`, `[VSCode]`, or similar. Routing happens through project boards, not the title.
|
||||
|
||||
## Project Boards
|
||||
|
||||
Every new issue must land on the correct project board:
|
||||
|
||||
| Surface | Project | URL |
|
||||
|---|---|---|
|
||||
| VS Code extension | `VS Code Extension` | https://github.com/orgs/Kilo-Org/projects/25 |
|
||||
| JetBrains plugin | `Jetbrains Plugin` | https://github.com/orgs/Kilo-Org/projects/39 |
|
||||
|
||||
Pass the project title to `gh issue create --project`.
|
||||
|
||||
## Recipes
|
||||
|
||||
Create a VS Code extension bug report and add it to the board:
|
||||
|
||||
```bash
|
||||
gh issue create \
|
||||
--template "Bug report" \
|
||||
--project "VS Code Extension" \
|
||||
--title "Sidebar chat fails to render after reload" \
|
||||
--body "..."
|
||||
```
|
||||
|
||||
Create a JetBrains feature request:
|
||||
|
||||
```bash
|
||||
gh issue create \
|
||||
--template "Feature Request" \
|
||||
--project "Jetbrains Plugin" \
|
||||
--title "Support Kotlin Multiplatform target detection" \
|
||||
--body "..."
|
||||
```
|
||||
|
||||
## Scope Errors
|
||||
|
||||
If `gh` reports a missing scope when assigning a project, refresh the auth token and retry:
|
||||
|
||||
```bash
|
||||
gh auth refresh -s project
|
||||
```
|
||||
|
||||
After the refresh succeeds, re-run the original `gh issue create` command. Do not fall back to creating the issue without the project — the board assignment is required.
|
||||
@@ -1,121 +0,0 @@
|
||||
---
|
||||
name: kilocode-merge-minimizer
|
||||
description: Use when changing shared upstream-owned files to add Kilo-specific behavior, editing `kilocode_change` markers in shared code, or moving additive behavior out of shared code to reduce upstream merge conflicts. Do not use for changes confined to Kilo-owned paths such as `packages/kilo-vscode/` or `packages/kilo-ui/`.
|
||||
---
|
||||
|
||||
# Kilo Merge Minimizer
|
||||
|
||||
Use this skill whenever a normal development task touches shared upstream-owned code and includes Kilo-specific behavior, especially for marker cleanup, extraction work, or `kilocode_change` annotations.
|
||||
|
||||
Do not use this skill when all changes are confined to Kilo-owned paths, including `packages/kilo-vscode/`, `packages/kilo-ui/`, and paths with `kilocode` in their name. Those files are not merged from upstream and do not need merge-minimization guidance. If a task also touches shared upstream-owned code, use this skill for the shared portion only.
|
||||
|
||||
Do not use this as the primary guide for upstream merge resolution. Upstream merges have their own instructions and should not duplicate that workflow here.
|
||||
|
||||
## Goal
|
||||
|
||||
Minimize Kilo's long-term diff against upstream OpenCode while preserving behavior.
|
||||
|
||||
Prefer this shape for Kilo-specific additions:
|
||||
|
||||
1. Shared upstream file contains only a minimal hook, import, call, registration, or config entry.
|
||||
2. Kilo-specific behavior lives in Kilo-owned code.
|
||||
3. Unavoidable shared-file changes have narrow `kilocode_change` markers.
|
||||
4. The annotation checker passes.
|
||||
|
||||
For changes to existing upstream behavior, prefer the smallest in-place shared-file diff with narrow markers. Do not move changed upstream logic into Kilo-owned code just to avoid textual conflicts, because that can create harder semantic merge conflicts.
|
||||
|
||||
## Core Rules
|
||||
|
||||
- Use `script/check-opencode-annotations.ts` as the source of truth for current shared scopes and exempt paths.
|
||||
- Use `script/upstream/fix-kilocode-markers.ts` for stale or broad markers, inspecting `--dry-run` output before applying changes.
|
||||
- Treat upstream-owned files as shared unless the checker or repo ownership rules exempt them.
|
||||
- Put Kilo-owned UI, CLI, runtime logic, and tests in Kilo-owned paths where practical.
|
||||
- Avoid adding Kilo business logic directly to shared files.
|
||||
- Keep shared-file edits as close as possible to upstream shape.
|
||||
- Do not change shared files unless the change is required for Kilo functionality, fixes a Kilo bug, or is a minimal targeted upstream-quality fix.
|
||||
- Do not create a large Kilo-only fork for a general upstream-quality improvement. Prefer a minimal targeted fix, or leave the broader change for upstream.
|
||||
- Do not duplicate upstream logic unless there is a concrete reason. If duplication is unavoidable, isolate the Kilo delta and keep the upstream dependency obvious.
|
||||
|
||||
## Shared File Structure
|
||||
|
||||
- Do not refactor, rename, split files, or extract helpers in shared files just to improve readability or make Kilo extraction cleaner.
|
||||
- Avoid structural changes that make upstream behavior harder to compare or hide semantic dependency on upstream code.
|
||||
|
||||
## Shared File Style
|
||||
|
||||
- Preserve upstream formatting and import style in shared files, even when it differs from Kilo style.
|
||||
- Put Kilo-only imports on separate marked lines instead of reorganizing upstream imports.
|
||||
|
||||
## Decision Rules
|
||||
|
||||
Extract Kilo logic when:
|
||||
|
||||
- The change is an additive Kilo feature or integration, not a modification of existing upstream behavior.
|
||||
- The shared-file change has meaningful Kilo-owned behavior, not just a tiny condition, import, registration, or field.
|
||||
- The code has loops, branching, error handling, async workflows, storage access, network calls, UI rendering, or telemetry.
|
||||
- The shared file can become a small orchestrator that calls Kilo helpers.
|
||||
- The Kilo code is independent enough that extraction will not hide future upstream fixes or behavior changes.
|
||||
|
||||
Keep the change inline when:
|
||||
|
||||
- The Kilo delta is a single field, import, call, simple condition, or small registry entry.
|
||||
- Extraction would reshape upstream code more than the Kilo change itself.
|
||||
- The change modifies an upstream algorithm, ordering, heuristic, control flow, or bug fix.
|
||||
- Extraction would duplicate upstream logic or hide semantic dependency on upstream behavior.
|
||||
- The Kilo helper closes over upstream-local state. Keep closure-scoped helpers inline and contiguous in one narrow marker block.
|
||||
- The shared file owns the only route table, enum, schema, switch, or registry where the hook must exist.
|
||||
- The change restores upstream shape or removes a stale Kilo divergence.
|
||||
|
||||
Always preserve upstream behavior order unless the Kilo behavior change is intentional and tested.
|
||||
|
||||
## Marker Rules
|
||||
|
||||
- Mark only Kilo-specific diff lines in shared upstream files.
|
||||
- Prefer inline markers for single-line changes: `const value = 42 // kilocode_change`.
|
||||
- Use block markers only for adjacent Kilo-specific lines:
|
||||
|
||||
```ts
|
||||
// kilocode_change start
|
||||
registerKiloFeature(app)
|
||||
// kilocode_change end
|
||||
```
|
||||
|
||||
- Use the file's native comment style, including JSX block comments inside JSX and `#` comments for YAML, TOML, and shell.
|
||||
- Do not add markers in checker-exempt Kilo-owned paths.
|
||||
- Remove stale markers when upstream already contains the behavior or when touching Kilo-owned files that still have old markers.
|
||||
- Use `// kilocode_change - new file` only for unavoidable new Kilo-specific files inside shared upstream paths.
|
||||
|
||||
## Tests
|
||||
|
||||
- Put Kilo-specific CLI/runtime tests in Kilo-owned test paths.
|
||||
- Move tests out of shared upstream test paths when the behavior under test is Kilo-specific.
|
||||
- Tests should cover the real failing path, not private or unstable APIs chosen only for convenience.
|
||||
- Do not add skip gates for required regression coverage.
|
||||
|
||||
## Verification
|
||||
|
||||
After editing shared files or marker comments, run:
|
||||
|
||||
```bash
|
||||
bun run script/check-opencode-annotations.ts
|
||||
```
|
||||
|
||||
If the PR uses a non-default comparison base, pass the correct base ref:
|
||||
|
||||
```bash
|
||||
bun run script/check-opencode-annotations.ts --base <base-ref>
|
||||
```
|
||||
|
||||
For stale or broad markers in one shared file, inspect the dry run before applying:
|
||||
|
||||
```bash
|
||||
bun run script/upstream/fix-kilocode-markers.ts <repo-relative-file> --dry-run
|
||||
```
|
||||
|
||||
Before finishing, confirm:
|
||||
|
||||
- Shared files contain minimal integration points only.
|
||||
- Kilo logic and tests live in Kilo-owned paths where practical.
|
||||
- Markers are narrow.
|
||||
- Stale markers are removed.
|
||||
- The annotation checker passed, or the reason it could not run is reported.
|
||||
@@ -1,237 +0,0 @@
|
||||
---
|
||||
name: release-jetbrains
|
||||
description: Use when releasing the Kilo JetBrains plugin -- resolve a version ("next rc" or explicit), run the prepare workflow, edit and commit a filtered human-readable changelog on the release PR, then watch publish to completion.
|
||||
---
|
||||
|
||||
# JetBrains Release
|
||||
|
||||
Use this skill when releasing the Kilo JetBrains plugin.
|
||||
|
||||
This skill drives the existing JetBrains release workflows. It must not move, delete, or recreate JetBrains release tags. It must always confirm the resolved version with the user before dispatching the prepare workflow because the prepare workflow creates an immutable `jetbrains/v<version>` tag.
|
||||
|
||||
## Preconditions
|
||||
|
||||
- Run from the repository root.
|
||||
- `gh` must be authenticated for `Kilo-Org/kilocode` with permission to dispatch workflows, read PRs, and write contents. Merge permission is only required if the user asks the skill to merge the release PR automatically.
|
||||
- Check auth with `gh auth status`. For GitHub CLI OAuth, refresh common release scopes with `gh auth refresh -s repo -s workflow`; `repo` covers private-repo contents and PR operations, and `workflow` allows workflow dispatch. If using a fine-grained token instead, grant repository permissions for Actions read/write, Contents read/write, and Pull requests read/write. Merging still requires normal repository collaborator permission or a token/user allowed by branch protection.
|
||||
- Reference `packages/kilo-jetbrains/RELEASING.md` for manual recovery rules.
|
||||
- Do not locally check out the generated release branch. The helper scripts update the release branch through GitHub to avoid disturbing the current worktree.
|
||||
|
||||
## Version Resolution
|
||||
|
||||
Resolve the user's version request:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/resolve-version.ts --spec "next rc"
|
||||
```
|
||||
|
||||
Accepted specs:
|
||||
|
||||
| Spec | Meaning |
|
||||
|---|---|
|
||||
| `next rc` | If the latest JetBrains tag is an RC, increment its `rc.n`; otherwise start the next patch RC at `rc.1`. |
|
||||
| `next stable` | If the latest JetBrains tag is an RC, use its base version; otherwise use the next patch stable. |
|
||||
| `x.y.z-rc.n` | Explicit RC release. |
|
||||
| `x.y.z` | Explicit stable release. |
|
||||
|
||||
Show the resolved `version`, `kind`, and default `fromTagDefault` to the user.
|
||||
|
||||
## CLI Pin Verification
|
||||
|
||||
Before dispatching prepare, verify the JetBrains plugin is pinned to the intended Kilo Core release. The plugin downloads the CLI version from `packages/kilo-jetbrains/package.json`, not from the JetBrains plugin version. Prepare tags `origin/main`, so the authoritative pin is the value on `origin/main`, not a local edit.
|
||||
|
||||
Run the pin preflight:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/check-pin.ts
|
||||
```
|
||||
|
||||
The script prints:
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| `pinMain` | CLI version that `origin/main` will lock into the release tag. |
|
||||
| `pinLocal` | CLI version in the current worktree, useful for catching stale local checkouts. |
|
||||
| `latestCli` | Latest stable `v*` Kilo CLI GitHub release. |
|
||||
| `prevJetbrainsCli` | CLI pin used by the latest `jetbrains/v*` release tag, for reviewing the jump. |
|
||||
| `pinnedMain` / `pinnedLocal` | Whether `kilo.cli.pinned=true`; `false` means repo CLI dev mode. |
|
||||
| `assetsOk` / `missingAssets` | Whether the pinned CLI release has every runtime asset. |
|
||||
| `drift` | `up-to-date`, `behind`, `worktree-behind-main`, `repo-mode-on-main`, `repo-mode-local`, or `assets-missing`. |
|
||||
|
||||
Interpretation:
|
||||
|
||||
| Drift | Action |
|
||||
|---|---|
|
||||
| `up-to-date` | Continue after user confirmation. |
|
||||
| `behind` | Stop and show `pinMain`, `latestCli`, and `prevJetbrainsCli`; ask whether to cancel, bump + test, or proceed anyway. |
|
||||
| `worktree-behind-main` | Explain that prepare tags `origin/main`; refresh the worktree or rely on `pinMain` in the confirmation. |
|
||||
| `repo-mode-on-main` | Stop. `kilo.cli.pinned=false` is dev-only and must be reset to `true` on `main` before release. |
|
||||
| `repo-mode-local` | Stop or reset local `kilo.cli.pinned=true`; release checks should run from a releasable local state. |
|
||||
| `assets-missing` | Stop. The pinned CLI release is incomplete and would fail runtime download. |
|
||||
|
||||
Show the resolved JetBrains plugin version, release kind, default `fromTagDefault`, `pinMain`, `latestCli`, `prevJetbrainsCli`, and `assetsOk` to the user, then ask for confirmation before continuing. If the user wants a different CLI pin, use the bump workflow below and do not dispatch prepare until the bump is merged to `main`.
|
||||
|
||||
## Bump the CLI Pin
|
||||
|
||||
Use this only when the user wants to test or release with a different CLI than `origin/main` currently pins. The helper refuses versions whose GitHub release or runtime assets are missing.
|
||||
|
||||
Local test edit only:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest
|
||||
# or
|
||||
bun .kilo/skills/release-jetbrains/script/set-pin.ts --version 7.4.1
|
||||
```
|
||||
|
||||
Then test from `packages/kilo-jetbrains/`:
|
||||
|
||||
```bash
|
||||
./gradlew typecheck
|
||||
./gradlew test
|
||||
```
|
||||
|
||||
If the user confirms the tested pin should be released, open or update a pin bump PR to `main`:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest --pr
|
||||
# or
|
||||
bun .kilo/skills/release-jetbrains/script/set-pin.ts --version 7.4.1 --pr
|
||||
```
|
||||
|
||||
After that PR merges to `main`, re-run `resolve-version.ts`, re-run `check-pin.ts`, confirm `drift=up-to-date`, then dispatch prepare. Do not dispatch prepare from a local-only pin edit; the prepare workflow tags `origin/main`.
|
||||
|
||||
## Prepare Workflow
|
||||
|
||||
After confirmation, dispatch and watch the prepare workflow:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/dispatch-prepare.ts --kind rc --version 7.0.1-rc.7
|
||||
```
|
||||
|
||||
Pass a generous Bash timeout, such as `1800000` ms, because the script blocks on `gh run watch --exit-status`. If the shell times out but the workflow is still running, re-attach with:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/dispatch-prepare.ts --kind rc --version 7.0.1-rc.7 --run-id <run-id>
|
||||
```
|
||||
|
||||
The script prints `prNumber`, `prUrl`, `runUrl`, and `branch` on success. Immediately show the `prUrl` to the user so they can open the release PR without asking for it later.
|
||||
|
||||
## Changelog Draft
|
||||
|
||||
Create a changelog draft after the prepare PR exists:
|
||||
|
||||
1. Read the PR body with `gh pr view <pr> --json body`.
|
||||
2. Extract `JetBrains-From-Tag`, `JetBrains-Tag`, and `## Generated Notes`.
|
||||
3. Fetch the release range tags if they are missing locally:
|
||||
|
||||
```bash
|
||||
git fetch origin refs/tags/<from-tag>:refs/tags/<from-tag> refs/tags/<tag>:refs/tags/<tag>
|
||||
```
|
||||
|
||||
4. Use the release range and path filter as the primary relevance signal:
|
||||
|
||||
```bash
|
||||
git log --oneline <from-tag>..<tag> -- packages/opencode packages/kilo-jetbrains
|
||||
```
|
||||
|
||||
Keep JetBrains and CLI/runtime changes. Drop unrelated VS Code, docs, gateway, telemetry, i18n, desktop, and webview-only changes unless they affect the CLI bundled into the JetBrains plugin.
|
||||
|
||||
Rewrite terse commit or PR titles into user-facing bullets grouped under `### Added`, `### Fixed`, and `### Changed`. Keep the exact generated header format:
|
||||
|
||||
```markdown
|
||||
## [<version>] - <date>
|
||||
```
|
||||
|
||||
Write the editable draft to:
|
||||
|
||||
```text
|
||||
packages/kilo-jetbrains/build/release/<version>-changelog.md
|
||||
```
|
||||
|
||||
Include source context in an HTML comment so it is easy to edit but not shipped:
|
||||
|
||||
```markdown
|
||||
<!-- CONTEXT - deleted automatically on commit. Source PRs in range:
|
||||
- #1234 feat(jetbrains): ... https://github.com/Kilo-Org/kilocode/pull/1234
|
||||
- #1235 fix(cli): ... https://github.com/Kilo-Org/kilocode/pull/1235
|
||||
-->
|
||||
```
|
||||
|
||||
Ask the user to edit the file and confirm when done.
|
||||
|
||||
## Commit Changelog
|
||||
|
||||
After the user confirms the draft is ready, strip the `<!-- CONTEXT ... -->` block into a temporary cleaned file, then commit the cleaned section to the release branch:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/update-changelog.ts --version 7.0.1-rc.7 --file /path/to/clean-section.md
|
||||
```
|
||||
|
||||
The script updates `packages/kilo-jetbrains/CHANGELOG.md` on `jetbrains/release/v<version>` through the GitHub contents API and commits with:
|
||||
|
||||
```text
|
||||
docs(jetbrains): edit changelog for v<version>
|
||||
```
|
||||
|
||||
If `update-changelog.ts` fails with `gh: Not Found (HTTP 404)`, verify the release branch and changelog path with:
|
||||
|
||||
```bash
|
||||
gh api "repos/Kilo-Org/kilocode/contents/packages/kilo-jetbrains/CHANGELOG.md?ref=jetbrains/release/v<version>"
|
||||
```
|
||||
|
||||
Then either fix and retry the helper, or perform the equivalent contents API update using `ref` in the query string.
|
||||
|
||||
After the changelog commit succeeds, show the release PR URL again and tell the user that the PR needs manual approval and merge before publishing can continue.
|
||||
|
||||
## Approve And Publish
|
||||
|
||||
Ask the user to approve the release changelog and metadata. Before merging or publishing, verify the PR approval and required checks are green:
|
||||
|
||||
```bash
|
||||
gh pr view <pr> --json mergeStateStatus,reviewDecision,statusCheckRollup
|
||||
gh pr checks <pr> --watch --interval 10
|
||||
```
|
||||
|
||||
Do not merge or publish while required checks are failing unless the user explicitly gives a maintainer override.
|
||||
|
||||
If a required check fails from an apparent flake, rerun only the failed jobs and wait for the run to finish:
|
||||
|
||||
```bash
|
||||
gh run rerun <run-id> --failed
|
||||
gh run watch <run-id> --exit-status
|
||||
```
|
||||
|
||||
By default, have the user merge the release PR manually in GitHub, then watch the publish workflow:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/watch-publish.ts --pr <number> --version 7.0.1-rc.7
|
||||
```
|
||||
|
||||
Only merge automatically when the user explicitly asks for it and `gh` has merge permission:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/watch-publish.ts --pr <number> --version 7.0.1-rc.7 --merge
|
||||
```
|
||||
|
||||
Pass a generous Bash timeout, such as `1800000` ms. If the shell times out, re-attach with:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/watch-publish.ts --pr <number> --version 7.0.1-rc.7 --run-id <run-id>
|
||||
```
|
||||
|
||||
If `watch-publish.ts --merge` reports that the PR is already merged, or a transient GitHub API `5xx` interrupts publish-run discovery, rerun without `--merge`:
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/release-jetbrains/script/watch-publish.ts --pr <number> --version <version>
|
||||
```
|
||||
|
||||
Report the Marketplace channel and GitHub Release URL. RC versions publish to the `eap` channel; stable versions publish to the default Marketplace channel.
|
||||
|
||||
## Recovery
|
||||
|
||||
- If prepare created the tag but failed before creating a PR, rerun prepare for the same version. The existing workflow reuses the tag if it points to the same commit.
|
||||
- If a tag points to an unexpected SHA, stop and inspect manually. Do not move or delete release tags casually.
|
||||
- If prepare tagged an unintended CLI pin, do not move the tag. Land the intended pin on `main`, resolve the next JetBrains version, and create a new release tag.
|
||||
- If release PR checks fail from an apparent flake, use `gh run rerun <run-id> --failed`, then `gh run watch <run-id> --exit-status` before publishing.
|
||||
- If publish fails after merge, rerun the failed workflow only if Marketplace did not already accept the version.
|
||||
- If Marketplace succeeds but GitHub Release upload fails, manually create or edit the GitHub Release for `jetbrains/v<version>` using the reviewed changelog.
|
||||
@@ -1,89 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
import semver from "semver"
|
||||
import { parseArgs } from "util"
|
||||
import { latest, missing } from "./pin-common"
|
||||
|
||||
const repo = process.env.GH_REPO ?? process.env.GITHUB_REPOSITORY ?? "Kilo-Org/kilocode"
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv.slice(2),
|
||||
options: {
|
||||
help: { type: "boolean", short: "h", default: false },
|
||||
},
|
||||
})
|
||||
|
||||
if (values.help) {
|
||||
console.log(`
|
||||
Usage: bun .kilo/skills/release-jetbrains/script/check-pin.ts
|
||||
|
||||
Checks the CLI pin that a JetBrains release would lock. Prepare tags origin/main,
|
||||
so this reads packages/kilo-jetbrains/package.json from origin/main and compares
|
||||
it with the latest published Kilo CLI release plus the local worktree pin.
|
||||
|
||||
Exit codes:
|
||||
0 Pin is release-ready.
|
||||
2 Pin drift, repo CLI mode, or missing CLI assets require maintainer review.
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
await $`git fetch origin main --tags`.quiet()
|
||||
|
||||
const pinMain = JSON.parse(await $`git show origin/main:packages/kilo-jetbrains/package.json`.text()).version as string
|
||||
const pinLocal = (await Bun.file("packages/kilo-jetbrains/package.json").json()).version as string
|
||||
const propsMain = await $`git show origin/main:packages/kilo-jetbrains/gradle.properties`.text()
|
||||
const propsLocal = await Bun.file("packages/kilo-jetbrains/gradle.properties").text()
|
||||
const pinnedMain = pinned(propsMain)
|
||||
const pinnedLocal = pinned(propsLocal)
|
||||
const latestCli = await latest(repo)
|
||||
const prevJetbrainsCli = await previous()
|
||||
const missingAssets = await missing(repo, pinMain)
|
||||
const assetsOk = missingAssets.length === 0
|
||||
const drift = (() => {
|
||||
if (!pinnedMain) return "repo-mode-on-main"
|
||||
if (!pinnedLocal) return "repo-mode-local"
|
||||
if (pinLocal !== pinMain) return "worktree-behind-main"
|
||||
if (!assetsOk) return "assets-missing"
|
||||
if (latestCli && semver.lt(pinMain, latestCli)) return "behind"
|
||||
return "up-to-date"
|
||||
})()
|
||||
|
||||
console.log(JSON.stringify({
|
||||
pinMain,
|
||||
pinLocal,
|
||||
latestCli,
|
||||
prevJetbrainsCli,
|
||||
pinnedMain,
|
||||
pinnedLocal,
|
||||
assetsOk,
|
||||
missingAssets,
|
||||
drift,
|
||||
}, null, 2))
|
||||
|
||||
if (drift !== "up-to-date") process.exit(2)
|
||||
|
||||
async function previous() {
|
||||
const text = await $`git tag --list ${"jetbrains/v*"}`.text()
|
||||
const tag = text
|
||||
.split(/\r?\n/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
.map((tag) => ({ tag, version: tag.replace(/^jetbrains\/v/, "") }))
|
||||
.filter((item) => semver.valid(item.version))
|
||||
.sort((a, b) => semver.rcompare(a.version, b.version))[0]?.tag
|
||||
if (!tag) return null
|
||||
const res = await $`git show ${tag}:packages/kilo-jetbrains/package.json`.nothrow().text()
|
||||
if (!res.trim()) return null
|
||||
return JSON.parse(res).version as string
|
||||
}
|
||||
|
||||
function pinned(text: string) {
|
||||
const value = text.split(/\r?\n/).flatMap((line) => {
|
||||
const [key, value] = line.split("=", 2)
|
||||
if (key.trim() !== "kilo.cli.pinned") return []
|
||||
return [value?.trim().toLowerCase()]
|
||||
})[0]
|
||||
return value == null || value === "true"
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
import { parseArgs } from "util"
|
||||
|
||||
const repo = process.env.GH_REPO ?? process.env.GITHUB_REPOSITORY ?? "Kilo-Org/kilocode"
|
||||
const workflow = "prepare-jetbrains-release.yml"
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv.slice(2),
|
||||
options: {
|
||||
kind: { type: "string" },
|
||||
version: { type: "string" },
|
||||
"from-tag": { type: "string" },
|
||||
"run-id": { type: "string" },
|
||||
help: { type: "boolean", short: "h", default: false },
|
||||
},
|
||||
})
|
||||
|
||||
if (values.help) {
|
||||
console.log(
|
||||
`Usage: bun .kilo/skills/release-jetbrains/script/dispatch-prepare.ts --kind <rc|stable> --version <version> [--from-tag <tag>] [--run-id <id>]`,
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const kind = values.kind
|
||||
const ver = values.version
|
||||
const branch = `jetbrains/release/v${ver}`
|
||||
|
||||
if (kind !== "rc" && kind !== "stable") throw new Error("--kind must be rc or stable")
|
||||
if (!ver) throw new Error("--version is required")
|
||||
|
||||
const id = values["run-id"] ?? (await dispatch())
|
||||
const url = `https://github.com/${repo}/actions/runs/${id}`
|
||||
|
||||
console.log(`prepareRunId=${id}`)
|
||||
console.log(`runUrl=${url}`)
|
||||
|
||||
await $`gh run watch ${id} --repo ${repo} --exit-status`
|
||||
|
||||
const pr = (await $`gh pr view ${branch} --repo ${repo} --json number,url`.json()) as { number: number; url: string }
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
prNumber: pr.number,
|
||||
prUrl: pr.url,
|
||||
runUrl: url,
|
||||
branch,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
)
|
||||
|
||||
async function dispatch() {
|
||||
const before = new Set((await runs()).map((run) => run.databaseId))
|
||||
const args = ["workflow", "run", workflow, "--repo", repo, "-f", `kind=${kind}`, "-f", `version=${ver}`]
|
||||
if (values["from-tag"]) args.push("-f", `from_tag=${values["from-tag"]}`)
|
||||
await $`gh ${args}`
|
||||
|
||||
for (const _ of Array.from({ length: 60 })) {
|
||||
const run = (await runs()).find((item) => !before.has(item.databaseId))
|
||||
if (run) return String(run.databaseId)
|
||||
await Bun.sleep(1000)
|
||||
}
|
||||
throw new Error(`No new ${workflow} run appeared after dispatch`)
|
||||
}
|
||||
|
||||
async function runs() {
|
||||
return (await $`gh run list --repo ${repo} --workflow ${workflow} --event workflow_dispatch --json databaseId,createdAt,status --limit 100`.json()) as {
|
||||
databaseId: number
|
||||
createdAt: string
|
||||
status: string
|
||||
}[]
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { $ } from "bun"
|
||||
import semver from "semver"
|
||||
|
||||
export const assets = [
|
||||
"kilo-darwin-arm64.zip",
|
||||
"kilo-darwin-x64.zip",
|
||||
"kilo-linux-arm64.tar.gz",
|
||||
"kilo-linux-x64.tar.gz",
|
||||
"kilo-windows-arm64.zip",
|
||||
"kilo-windows-x64.zip",
|
||||
]
|
||||
|
||||
export async function latest(repo: string) {
|
||||
const list = (await $`gh release list --repo ${repo} --limit 100 --json tagName,isDraft,isPrerelease`.json()) as {
|
||||
tagName: string
|
||||
isDraft: boolean
|
||||
isPrerelease: boolean
|
||||
}[]
|
||||
return list
|
||||
.filter((item) => /^v\d+\.\d+\.\d+$/.test(item.tagName) && !item.isDraft && !item.isPrerelease)
|
||||
.map((item) => item.tagName.slice(1))
|
||||
.sort(semver.rcompare)[0] ?? null
|
||||
}
|
||||
|
||||
export async function missing(repo: string, version: string) {
|
||||
const res = await $`gh release view ${`v${version}`} --repo ${repo} --json assets --jq ${".assets[].name"}`.quiet().nothrow()
|
||||
if (res.exitCode !== 0) return assets
|
||||
const names = res.stdout.toString().split(/\r?\n/).map((item) => item.trim()).filter(Boolean)
|
||||
return assets.filter((item) => !names.includes(item))
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
import semver from "semver"
|
||||
import { parseArgs } from "util"
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv.slice(2),
|
||||
options: {
|
||||
spec: { type: "string" },
|
||||
help: { type: "boolean", short: "h", default: false },
|
||||
},
|
||||
})
|
||||
|
||||
if (values.help) {
|
||||
console.log(
|
||||
`Usage: bun .kilo/skills/release-jetbrains/script/resolve-version.ts --spec <next rc|next stable|version>`,
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const spec = values.spec?.trim().toLowerCase()
|
||||
if (!spec) throw new Error("--spec is required")
|
||||
|
||||
await $`git fetch origin main --tags`.quiet()
|
||||
|
||||
const tags = await list()
|
||||
const hit = explicit(spec) ?? next(spec, tags)
|
||||
const from = base(hit.version, hit.kind, tags)
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
version: hit.version,
|
||||
kind: hit.kind,
|
||||
fromTagDefault: from,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
)
|
||||
|
||||
type Kind = "rc" | "stable"
|
||||
type Tag = { tag: string; version: string }
|
||||
|
||||
function explicit(spec: string) {
|
||||
if (/^\d+\.\d+\.\d+-rc\.\d+$/.test(spec)) return { version: spec, kind: "rc" as Kind }
|
||||
if (/^\d+\.\d+\.\d+$/.test(spec)) return { version: spec, kind: "stable" as Kind }
|
||||
return undefined
|
||||
}
|
||||
|
||||
function next(spec: string, tags: Tag[]) {
|
||||
const latest = [...tags].sort((a, b) => semver.rcompare(a.version, b.version))[0]
|
||||
if (!latest) throw new Error("No JetBrains release tags found; pass an explicit version")
|
||||
const parsed = semver.parse(latest.version)
|
||||
if (!parsed) throw new Error(`Invalid latest JetBrains tag: ${latest.tag}`)
|
||||
|
||||
if (spec === "next rc") return rc(parsed)
|
||||
if (spec === "next stable") return stable(parsed)
|
||||
throw new Error("--spec must be 'next rc', 'next stable', x.y.z-rc.n, or x.y.z")
|
||||
}
|
||||
|
||||
function rc(ver: semver.SemVer) {
|
||||
const pre = ver.prerelease
|
||||
if (pre[0] === "rc" && typeof pre[1] === "number") {
|
||||
return { version: `${ver.major}.${ver.minor}.${ver.patch}-rc.${pre[1] + 1}`, kind: "rc" as Kind }
|
||||
}
|
||||
return { version: `${ver.major}.${ver.minor}.${ver.patch + 1}-rc.1`, kind: "rc" as Kind }
|
||||
}
|
||||
|
||||
function stable(ver: semver.SemVer) {
|
||||
if (ver.prerelease.length) return { version: `${ver.major}.${ver.minor}.${ver.patch}`, kind: "stable" as Kind }
|
||||
return { version: `${ver.major}.${ver.minor}.${ver.patch + 1}`, kind: "stable" as Kind }
|
||||
}
|
||||
|
||||
function base(ver: string, kind: Kind, tags: Tag[]) {
|
||||
const want = semver.parse(ver)
|
||||
if (!want) throw new Error(`Invalid semver: ${ver}`)
|
||||
const prior = tags
|
||||
.filter((item) => !semver.prerelease(item.version) && semver.lt(item.version, ver))
|
||||
.sort((a, b) => semver.rcompare(a.version, b.version))
|
||||
|
||||
if (kind === "stable") {
|
||||
const hit = prior[0]
|
||||
if (!hit) return null
|
||||
return hit.tag
|
||||
}
|
||||
|
||||
const prerelease = tags
|
||||
.filter((item) => {
|
||||
const parsed = semver.parse(item.version)
|
||||
if (!parsed) return false
|
||||
if (parsed.major !== want.major || parsed.minor !== want.minor || parsed.patch !== want.patch) return false
|
||||
return Boolean(semver.prerelease(item.version)) && semver.lt(item.version, ver)
|
||||
})
|
||||
.sort((a, b) => semver.rcompare(a.version, b.version))
|
||||
|
||||
return (prerelease[0] ?? prior[0])?.tag ?? null
|
||||
}
|
||||
|
||||
async function list() {
|
||||
const text = await $`git tag --list ${"jetbrains/v*"}`.text()
|
||||
return text
|
||||
.split(/\r?\n/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
.map((tag) => ({ tag, version: tag.replace(/^jetbrains\/v/, "") }))
|
||||
.filter((item) => semver.valid(item.version))
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
import semver from "semver"
|
||||
import { parseArgs } from "util"
|
||||
import { latest, missing } from "./pin-common"
|
||||
|
||||
const repo = process.env.GH_REPO ?? process.env.GITHUB_REPOSITORY ?? "Kilo-Org/kilocode"
|
||||
const file = "packages/kilo-jetbrains/package.json"
|
||||
const label = "jetbrains-cli-pin-bump"
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv.slice(2),
|
||||
options: {
|
||||
version: { type: "string" },
|
||||
latest: { type: "boolean", default: false },
|
||||
pr: { type: "boolean", default: false },
|
||||
help: { type: "boolean", short: "h", default: false },
|
||||
},
|
||||
})
|
||||
|
||||
if (values.help) {
|
||||
console.log(`
|
||||
Usage: bun .kilo/skills/release-jetbrains/script/set-pin.ts (--latest | --version <x.y.z>) [--pr]
|
||||
|
||||
Without --pr, rewrites ${file} in the local worktree so you can test a CLI pin.
|
||||
With --pr, opens or updates a PR against main using the GitHub API; prepare tags
|
||||
origin/main, so the pin bump must merge there before a JetBrains release starts.
|
||||
|
||||
Examples:
|
||||
bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest
|
||||
bun .kilo/skills/release-jetbrains/script/set-pin.ts --version 7.4.1
|
||||
bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest --pr
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (values.latest && values.version) throw new Error("Pass either --latest or --version, not both")
|
||||
const version = values.latest ? await latest(repo) : values.version?.replace(/^v/, "")
|
||||
if (!version || !semver.valid(version) || semver.prerelease(version)) {
|
||||
throw new Error("Pass a stable CLI version with --version x.y.z or use --latest")
|
||||
}
|
||||
|
||||
const miss = await missing(repo, version)
|
||||
if (miss.length > 0) {
|
||||
throw new Error(`CLI release v${version} is missing required assets: ${miss.join(", ")}`)
|
||||
}
|
||||
|
||||
if (values.pr) {
|
||||
await pr(version)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const pkg = await Bun.file(file).json()
|
||||
const previous = pkg.version as string
|
||||
pkg.version = version
|
||||
await Bun.write(file, `${JSON.stringify(pkg, null, 2)}\n`)
|
||||
if (previous === version) {
|
||||
console.log(`${file} already pins CLI v${version}`)
|
||||
} else {
|
||||
console.log(`Pinned JetBrains CLI ${previous} -> ${version} in ${file}`)
|
||||
}
|
||||
console.log("Test locally with: cd packages/kilo-jetbrains && ./gradlew typecheck && ./gradlew test")
|
||||
console.log("When satisfied, run this script again with --pr so the bump lands on main before prepare tags it.")
|
||||
|
||||
async function pr(version: string) {
|
||||
await $`git fetch origin main`.quiet()
|
||||
const branch = `chore/jetbrains-cli-pin-v${version}`
|
||||
const main = (await $`git rev-parse origin/main`.text()).trim()
|
||||
const text = await $`git show origin/main:${file}`.text()
|
||||
const pkg = JSON.parse(text)
|
||||
const previous = pkg.version as string
|
||||
if (previous === version) {
|
||||
console.log(`origin/main already pins CLI v${version}; no PR needed.`)
|
||||
return
|
||||
}
|
||||
pkg.version = version
|
||||
const body = `${JSON.stringify(pkg, null, 2)}\n`
|
||||
await ensure(branch, main)
|
||||
const current = (await $`gh api ${`repos/${repo}/contents/${file}?ref=${branch}`}`.json()) as { sha: string }
|
||||
await $`gh api --method PUT ${`repos/${repo}/contents/${file}`} -f message=${`chore(jetbrains): bump CLI pin to v${version}`} -f content=${Buffer.from(body).toString("base64")} -f branch=${branch} -f sha=${current.sha}`.quiet()
|
||||
|
||||
const title = `chore(jetbrains): bump CLI pin to v${version}`
|
||||
const desc = [
|
||||
`Bumps the JetBrains CLI pin from v${previous} to v${version}.`,
|
||||
"",
|
||||
"Prepare tags origin/main, so this PR must merge before dispatching a JetBrains release that should lock this CLI.",
|
||||
"",
|
||||
"After merging, re-run:",
|
||||
"",
|
||||
"```bash",
|
||||
"bun .kilo/skills/release-jetbrains/script/check-pin.ts",
|
||||
"```",
|
||||
].join("\n")
|
||||
const view = await $`gh pr view ${branch} --repo ${repo} --json url --jq .url`.quiet().nothrow()
|
||||
if (view.exitCode === 0 && view.stdout.toString().trim()) {
|
||||
await $`gh pr edit ${branch} --repo ${repo} --title ${title} --body ${desc}`
|
||||
await tag(branch)
|
||||
console.log(view.stdout.toString().trim())
|
||||
return
|
||||
}
|
||||
const url = await $`gh pr create --repo ${repo} --base main --head ${branch} --title ${title} --body ${desc}`.text()
|
||||
await tag(branch)
|
||||
console.log(url.trim())
|
||||
}
|
||||
|
||||
async function ensure(branch: string, sha: string) {
|
||||
const ref = `repos/${repo}/git/refs/heads/${branch}`
|
||||
const exists = await $`gh api ${ref}`.nothrow().quiet()
|
||||
if (exists.exitCode === 0) {
|
||||
await $`gh api --method PATCH ${ref} -f sha=${sha} -F force=true`.quiet()
|
||||
return
|
||||
}
|
||||
await $`gh api --method POST ${`repos/${repo}/git/refs`} -f ref=${`refs/heads/${branch}`} -f sha=${sha}`.quiet()
|
||||
}
|
||||
|
||||
async function tag(branch: string) {
|
||||
await $`gh label create ${label} --repo ${repo} --color 1D76DB --description ${"JetBrains pinned CLI version bump"}`.quiet().nothrow()
|
||||
const result = await $`gh pr edit ${branch} --repo ${repo} --add-label ${label}`.quiet().nothrow()
|
||||
if (result.exitCode !== 0) {
|
||||
console.warn(`Warning: failed to add ${label} label to ${branch}`)
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
import { parseArgs } from "util"
|
||||
|
||||
const repo = process.env.GH_REPO ?? process.env.GITHUB_REPOSITORY ?? "Kilo-Org/kilocode"
|
||||
const path = "packages/kilo-jetbrains/CHANGELOG.md"
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv.slice(2),
|
||||
options: {
|
||||
version: { type: "string" },
|
||||
file: { type: "string" },
|
||||
help: { type: "boolean", short: "h", default: false },
|
||||
},
|
||||
})
|
||||
|
||||
if (values.help) {
|
||||
console.log(
|
||||
`Usage: bun .kilo/skills/release-jetbrains/script/update-changelog.ts --version <version> --file <section.md>`,
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const ver = values.version
|
||||
const file = values.file
|
||||
if (!ver) throw new Error("--version is required")
|
||||
if (!file) throw new Error("--file is required")
|
||||
|
||||
const branch = `jetbrains/release/v${ver}`
|
||||
const section = strip((await Bun.file(file).text()).trim())
|
||||
validate(section, ver)
|
||||
|
||||
const current = (await $`gh api ${`repos/${repo}/contents/${path}?ref=${encodeURIComponent(branch)}`}`.json()) as {
|
||||
content: string
|
||||
encoding: string
|
||||
sha: string
|
||||
}
|
||||
if (current.encoding !== "base64") throw new Error(`Unexpected content encoding: ${current.encoding}`)
|
||||
|
||||
const text = Buffer.from(current.content.replace(/\s/g, ""), "base64").toString("utf8")
|
||||
const pattern = regex(ver)
|
||||
if (!pattern.test(text)) throw new Error(`${path} is missing a section for ${ver} on ${branch}`)
|
||||
|
||||
const next =
|
||||
text
|
||||
.replace(pattern, `\n${section}\n`)
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trimEnd() + "\n"
|
||||
if (next === text) {
|
||||
console.log("CHANGELOG.md already contains the provided section")
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const body = {
|
||||
message: `docs(jetbrains): edit changelog for v${ver}`,
|
||||
content: Buffer.from(next).toString("base64"),
|
||||
sha: current.sha,
|
||||
branch,
|
||||
}
|
||||
const tmp = await temp(body)
|
||||
await $`gh api ${`repos/${repo}/contents/${path}`} --method PUT --input ${tmp}`
|
||||
console.log(`Committed changelog update to ${branch}`)
|
||||
|
||||
function strip(text: string) {
|
||||
return text.replace(/<!--\s*CONTEXT[\s\S]*?-->/g, "").trim()
|
||||
}
|
||||
|
||||
function validate(section: string, ver: string) {
|
||||
if (!section.startsWith(`## [${ver}]`)) throw new Error(`Section must start with ## [${ver}]`)
|
||||
if (!/^### (Added|Fixed|Changed)$/m.test(section)) {
|
||||
throw new Error("Section must contain at least one Added, Fixed, or Changed heading")
|
||||
}
|
||||
}
|
||||
|
||||
function regex(ver: string) {
|
||||
const safe = ver.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
return new RegExp(`\n?## \\[${safe}\\][\\s\\S]*?(?=\n## \\[|$)`)
|
||||
}
|
||||
|
||||
async function temp(body: object) {
|
||||
const file = `/tmp/kilo-jetbrains-changelog-${Date.now()}.json`
|
||||
await Bun.write(file, JSON.stringify(body))
|
||||
return file
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user