diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000000..453ae820fe8
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,7 @@
+# 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
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index b3a49424fbd..5c5ca6159a6 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -7,6 +7,7 @@ on:
jobs:
sync:
+ if: github.repository == 'Kilo-Org/kilocode'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
diff --git a/.github/workflows/close-stale-prs.yml b/.github/workflows/close-stale-prs.yml
index 34beac1596d..38d0d3627a5 100644
--- a/.github/workflows/close-stale-prs.yml
+++ b/.github/workflows/close-stale-prs.yml
@@ -17,6 +17,7 @@ permissions:
jobs:
close-stale-prs:
+ if: github.repository == 'Kilo-Org/kilocode'
runs-on: blacksmith-2vcpu-ubuntu-2404 # kilocode_change
timeout-minutes: 15
steps:
diff --git a/.github/workflows/compliance-close.yml b/.github/workflows/compliance-close.yml
index 6ce16d8e02b..d56d3b4d971 100644
--- a/.github/workflows/compliance-close.yml
+++ b/.github/workflows/compliance-close.yml
@@ -13,7 +13,7 @@ permissions:
jobs:
close-non-compliant:
- if: false # kilocode_change - disabled: not needed in kilocode repo
+ if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
runs-on: ubuntu-latest
steps:
- name: Close non-compliant issues and PRs after 2 hours
diff --git a/.github/workflows/disabled/kilo.yml.disabled b/.github/workflows/disabled/kilo.yml.disabled
index afedad0200c..7687af97072 100644
--- a/.github/workflows/disabled/kilo.yml.disabled
+++ b/.github/workflows/disabled/kilo.yml.disabled
@@ -35,4 +35,4 @@ jobs:
KILO_ORG_ID: ${{ secrets.KILO_ORG_ID }}
KILO_PERMISSION: '{"bash": "deny"}'
with:
- model: kilo/kilo/auto
+ model: kilo/kilo-auto/frontier
diff --git a/.github/workflows/docs-check-links.yml b/.github/workflows/docs-check-links.yml
index 4f5eb581cb6..75b47a67077 100644
--- a/.github/workflows/docs-check-links.yml
+++ b/.github/workflows/docs-check-links.yml
@@ -4,6 +4,9 @@ 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 * * *"
@@ -11,6 +14,7 @@ on:
jobs:
link-checker:
+ if: github.repository == 'Kilo-Org/kilocode'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml
index bbc06efec62..25b03a7bfc2 100644
--- a/.github/workflows/generate.yml
+++ b/.github/workflows/generate.yml
@@ -7,6 +7,7 @@ on:
jobs:
generate:
+ if: github.repository == 'Kilo-Org/kilocode'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
diff --git a/.github/workflows/nix-eval.yml b/.github/workflows/nix-eval.yml
index abce8d05ab0..dbe5f0bfd05 100644
--- a/.github/workflows/nix-eval.yml
+++ b/.github/workflows/nix-eval.yml
@@ -16,6 +16,7 @@ permissions:
jobs:
nix-eval:
+ if: github.repository == 'Kilo-Org/kilocode'
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
steps:
diff --git a/.github/workflows/nix-hashes.yml b/.github/workflows/nix-hashes.yml
index 65d1c151bc9..cd1c053433a 100644
--- a/.github/workflows/nix-hashes.yml
+++ b/.github/workflows/nix-hashes.yml
@@ -21,6 +21,7 @@ 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:
@@ -76,7 +77,7 @@ jobs:
update-hashes:
needs: compute-hash
- if: github.event_name != 'pull_request'
+ if: github.repository == 'Kilo-Org/kilocode' && github.event_name != 'pull_request'
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
diff --git a/.github/workflows/pr-management.yml b/.github/workflows/pr-management.yml
index 8e5dfe9b8ec..7abe0a0ac18 100644
--- a/.github/workflows/pr-management.yml
+++ b/.github/workflows/pr-management.yml
@@ -7,7 +7,7 @@ on:
jobs:
# kilocode_change start
check-author:
- if: false # kilocode_change - disabled: not needed in kilocode repo
+ if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
uses: ./.github/workflows/check-org-member.yml
with:
username: ${{ github.event.pull_request.user.login }}
@@ -17,7 +17,7 @@ jobs:
check-duplicates:
needs: check-author
- if: false # kilocode_change - disabled: not needed in kilocode repo
+ if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
# kilocode_change end
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
@@ -89,7 +89,7 @@ jobs:
fi
add-contributor-label:
- if: false # kilocode_change - disabled: not needed in kilocode repo
+ if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
runs-on: blacksmith-2vcpu-ubuntu-2404 # kilocode_change
permissions:
pull-requests: write
diff --git a/.github/workflows/pr-standards.yml b/.github/workflows/pr-standards.yml
index 98b0c32e57c..75fb650144d 100644
--- a/.github/workflows/pr-standards.yml
+++ b/.github/workflows/pr-standards.yml
@@ -7,7 +7,7 @@ on:
jobs:
# kilocode_change start
check-author:
- if: false # kilocode_change - disabled: not needed in kilocode repo
+ if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
uses: ./.github/workflows/check-org-member.yml
with:
username: ${{ github.event.pull_request.user.login }}
@@ -17,7 +17,7 @@ jobs:
check-standards:
needs: check-author
- if: false # kilocode_change - disabled: not needed in kilocode repo
+ if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
# kilocode_change end
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
@@ -141,7 +141,7 @@ jobs:
console.log('PR meets all standards');
check-compliance:
- if: false # kilocode_change - disabled: not needed in kilocode repo
+ if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
runs-on: ubuntu-latest
permissions:
contents: read
diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml
index d86ef0ad0bb..51c649d823c 100644
--- a/.github/workflows/stale-issues.yml
+++ b/.github/workflows/stale-issues.yml
@@ -11,6 +11,7 @@ env:
jobs:
stale:
+ if: github.repository == 'Kilo-Org/kilocode'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
issues: write
diff --git a/.github/workflows/test-vscode.yml b/.github/workflows/test-vscode.yml
index 899ef321767..9ec1df4f442 100644
--- a/.github/workflows/test-vscode.yml
+++ b/.github/workflows/test-vscode.yml
@@ -6,9 +6,13 @@ on:
- dev
paths:
- "packages/kilo-vscode/**"
+ - "packages/ui/**"
+ - "packages/kilo-ui/**"
pull_request:
paths:
- "packages/kilo-vscode/**"
+ - "packages/ui/**"
+ - "packages/kilo-ui/**"
workflow_dispatch:
jobs:
@@ -30,3 +34,11 @@ jobs:
- name: Run unit tests
working-directory: packages/kilo-vscode
run: bun run test:unit
+
+ - 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
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 344830138e1..86ec616de0e 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -39,6 +39,8 @@ jobs:
run: bun turbo test
e2e:
+ # kilocode_change - disabled: packages/app is not actively maintained
+ if: false
name: e2e (${{ matrix.settings.name }})
needs: unit
strategy:
@@ -98,12 +100,9 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
needs:
- unit
- - e2e
if: always()
steps:
- name: Verify upstream test jobs passed
run: |
echo "unit=${{ needs.unit.result }}"
- echo "e2e=${{ needs.e2e.result }}"
test "${{ needs.unit.result }}" = "success"
- test "${{ needs.e2e.result }}" = "success"
diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml
index 45be522c483..b767380365e 100644
--- a/.github/workflows/triage.yml
+++ b/.github/workflows/triage.yml
@@ -6,6 +6,7 @@ on:
jobs:
triage:
+ if: github.repository == 'Kilo-Org/kilocode'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml
new file mode 100644
index 00000000000..b295678f4c5
--- /dev/null
+++ b/.github/workflows/visual-regression.yml
@@ -0,0 +1,364 @@
+name: Visual Regression Tests
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+jobs:
+ check-paths:
+ name: Check changed paths
+ runs-on: ubuntu-latest
+ outputs:
+ matched: ${{ steps.filter.outputs.matched }}
+ is_fork: ${{ steps.fork-check.outputs.is_fork }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: Kilo-Org/paths-filter@master
+ id: filter
+ with:
+ filters: |
+ matched:
+ - "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*"
+ - ".github/workflows/visual-regression.yml"
+ - 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
+
+ visual-regression:
+ needs: check-paths
+ if: needs.check-paths.outputs.matched == 'true'
+ name: Visual Regression (kilo-ui)
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ timeout-minutes: 15
+
+ steps:
+ - name: Checkout (internal)
+ if: needs.check-paths.outputs.is_fork != 'true'
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+ token: ${{ secrets.BOT_PAT }}
+ ref: ${{ github.head_ref }}
+
+ - name: Checkout (fork)
+ if: needs.check-paths.outputs.is_fork == 'true'
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Check if HEAD is a baseline update commit
+ if: needs.check-paths.outputs.is_fork != '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: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+
+ - name: Cache Bun modules
+ uses: actions/cache@v4
+ with:
+ path: ~/.bun/install/cache
+ key: bun-${{ hashFiles('bun.lock') }}
+
+ - name: Install dependencies
+ run: bun install
+
+ - name: Cache Playwright browsers
+ id: playwright-cache
+ uses: actions/cache@v4
+ 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@v4
+ 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 = 'tests/visual-regression.spec.ts-snapshots';
+ 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 (fork PRs)
+ if: needs.check-paths.outputs.is_fork == 'true'
+ run: |
+ git add packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/
+ 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 is from a fork, updated screenshots cannot be committed automatically."
+ 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.is_fork != 'true' && steps.check-baseline-commit.outputs.is_baseline_update == 'true'
+ run: |
+ git add packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/
+ 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: Commit and push new baselines (if any)
+ if: needs.check-paths.outputs.is_fork != 'true' && steps.check-baseline-commit.outputs.is_baseline_update != 'true'
+ id: commit-baselines
+ env:
+ GH_TOKEN: ${{ secrets.BOT_PAT }}
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/
+ if git diff --cached --quiet; then
+ echo "No new baselines — nothing to commit."
+ echo "changed=false" >> "$GITHUB_OUTPUT"
+ else
+ git commit -m "chore: update visual regression baselines"
+ git lfs push --all origin
+ git push
+ echo "changed=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Fail if baselines changed
+ if: needs.check-paths.outputs.is_fork != '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@v4
+ 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)
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ timeout-minutes: 15
+
+ steps:
+ - name: Checkout (internal)
+ if: needs.check-paths.outputs.is_fork != 'true'
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+ token: ${{ secrets.BOT_PAT }}
+ ref: ${{ github.head_ref }}
+
+ - name: Checkout (fork)
+ if: needs.check-paths.outputs.is_fork == 'true'
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Check if HEAD is a baseline update commit
+ if: needs.check-paths.outputs.is_fork != '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: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+
+ - name: Cache Bun modules
+ uses: actions/cache@v4
+ with:
+ path: ~/.bun/install/cache
+ key: bun-${{ hashFiles('bun.lock') }}
+
+ - name: Install dependencies
+ run: bun install
+
+ - name: Cache Playwright browsers
+ id: playwright-cache-vscode
+ uses: actions/cache@v4
+ 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@v4
+ 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
+
+ - name: Generate baselines for new/missing stories
+ run: bun run test:visual:update
+ working-directory: packages/kilo-vscode
+ 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 = 'tests/visual-regression.spec.ts-snapshots';
+ 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: Check for baseline changes (fork PRs)
+ if: needs.check-paths.outputs.is_fork == 'true'
+ run: |
+ git add packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/
+ 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 is from a fork, updated screenshots cannot be committed automatically."
+ 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.is_fork != 'true' && steps.check-baseline-commit-vscode.outputs.is_baseline_update == 'true'
+ run: |
+ git add packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/
+ 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: Commit and push new baselines (if any)
+ if: needs.check-paths.outputs.is_fork != 'true' && steps.check-baseline-commit-vscode.outputs.is_baseline_update != 'true'
+ id: commit-baselines-vscode
+ env:
+ GH_TOKEN: ${{ secrets.BOT_PAT }}
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/
+ if git diff --cached --quiet; then
+ echo "No new baselines — nothing to commit."
+ echo "changed=false" >> "$GITHUB_OUTPUT"
+ else
+ git commit -m "chore: update kilo-vscode visual regression baselines"
+ git lfs push --all origin
+ git push
+ echo "changed=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Fail if baselines changed
+ if: needs.check-paths.outputs.is_fork != '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@v4
+ with:
+ name: visual-regression-vscode-results
+ path: packages/kilo-vscode/test-results/
+ retention-days: 7
diff --git a/.gitignore b/.gitignore
index b678f695639..6bd9245c08b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.husky/_
.DS_Store
node_modules
.worktrees
diff --git a/.kilocode/skills/vscode-visual-regression/SKILL.md b/.kilocode/skills/vscode-visual-regression/SKILL.md
new file mode 100644
index 00000000000..7b2f3bc0460
--- /dev/null
+++ b/.kilocode/skills/vscode-visual-regression/SKILL.md
@@ -0,0 +1,326 @@
+---
+name: vscode-visual-regression
+description: Write Storybook stories and visual regression tests for the Kilo VS Code extension webview UI
+---
+
+Use this skill when the user asks you to add visual regression tests, screenshot tests, or Storybook stories for components in `packages/kilo-vscode/`.
+
+# Architecture
+
+The VS Code extension uses **Storybook + Playwright** for visual regression testing:
+
+1. **Storybook stories** define UI scenarios using SolidJS components with mock contexts
+2. **Playwright** auto-discovers all stories, renders each in headless Chromium, and compares screenshots against baseline PNGs using `toHaveScreenshot()`
+3. **Baselines** are Linux-only Chromium PNGs stored in `tests/visual-regression.spec.ts-snapshots/` (tracked via Git LFS)
+
+The test runner at `tests/visual-regression.spec.ts` is fully automatic — it fetches ALL stories from the Storybook index and creates one Playwright test per story. You do NOT write Playwright test code. You only write stories.
+
+# How to add a visual regression test
+
+## Step 1: Decide which story file to use
+
+Stories live in `packages/kilo-vscode/webview-ui/src/stories/`. Existing files and their scope:
+
+| File | Components covered |
+| --------------------------- | -------------------------------------------------------- |
+| `agent-manager.stories.tsx` | FileTree, DiffPanel, FullScreenDiffView, WorktreeItem |
+| `chat.stories.tsx` | ChatView, QuestionDock |
+| `composite.stories.tsx` | AssistantMessage with tool cards, permissions, questions |
+| `prompt-input.stories.tsx` | PromptInput (sidebar prompt bar) |
+| `settings.stories.tsx` | Settings panel, ProvidersTab |
+| `history.stories.tsx` | SessionList |
+| `shared.stories.tsx` | ModelSelector and shared controls |
+
+Add to an existing file if the component fits. Create a new file only for a genuinely new component area.
+
+## Step 2: Write the story
+
+Every story file follows this exact structure:
+
+```tsx
+/** @jsxImportSource solid-js */
+/**
+ * Stories for [ComponentName].
+ */
+
+import type { Meta, StoryObj } from "storybook-solidjs-vite"
+import { StoryProviders } from "./StoryProviders"
+// Import the component(s) under test
+import { MyComponent } from "../components/path/MyComponent"
+
+const meta: Meta = {
+ title: "MyCategory", // Becomes the snapshot subdirectory name (lowercased)
+ parameters: { layout: "padded" }, // or "fullscreen"
+}
+export default meta
+type Story = StoryObj
+
+export const MyStoryName: Story = {
+ name: "MyComponent — description of variant",
+ render: () => (
+
+
+
+
+
+ ),
+}
+```
+
+### Key rules
+
+- **Always start with `/** @jsxImportSource solid-js \*/`\*\* — required for SolidJS JSX compilation.
+- **Always wrap in ``** — provides all required contexts (VSCode, Server, Config, Provider, Session, I18n, Dialog, Marked, Data, Diff, Code). Without it, components that call `useVSCode()`, `useSession()`, etc. will throw.
+- **Set explicit dimensions** on a wrapper div so the screenshot has a deterministic size. Use `width: "420px"` for sidebar-width components (the standard VS Code sidebar). Use `width: "200px"` for narrow/collapsed states.
+- **Meta `title`** determines the snapshot subdirectory. Use PascalCase or slash-notation (e.g., `"Composite/Webview"`). Playwright transforms it: `"Composite/Webview"` becomes `composite-webview/` in the snapshots folder.
+- **Export name** determines the story ID. The Storybook ID is `{lowercase-title}--{kebab-export-name}`. For example, `title: "Chat"` + `export const ChatViewIdle` produces ID `chat--chat-view-idle`.
+- **Snapshot path** is derived automatically: `tests/visual-regression.spec.ts-snapshots/{title-slug}/{variant-slug}.png`. Example: `chat/chat-view-idle-chromium-linux.png`.
+
+### StoryProviders props
+
+```tsx
+interface StoryProvidersProps {
+ data?: any // Override mock data (messages, parts, permissions, etc.)
+ permissions?: PermissionRequest[] // Active permission requests
+ questions?: QuestionRequest[] // Active question requests
+ status?: string // Session status: "idle" | "busy"
+ sessionID?: string // Custom session ID
+ noPadding?: boolean // Skip the default 12px padding wrapper
+}
+```
+
+### Overriding session state
+
+For stories that need custom session behavior (messages, agents, model overrides), use `mockSessionValue()` and override the `SessionContext`:
+
+```tsx
+import { mockSessionValue } from "./StoryProviders"
+import { SessionContext } from "../context/session"
+
+export const MyCustomStory: Story = {
+ name: "Component — custom state",
+ render: () => {
+ const session = {
+ ...mockSessionValue({ id: "my-session", status: "idle" }),
+ messages: () => [{ id: "msg-001" }] as any[],
+ totalCost: () => 0.0023,
+ }
+ return (
+
+
+
+
+
+ )
+ },
+}
+```
+
+### Overriding data context (messages, parts, permissions)
+
+For stories showing assistant messages with tool parts or permissions, build a custom `data` object:
+
+```tsx
+import { defaultMockData } from "./StoryProviders"
+
+const SESSION_ID = "story-session-001"
+const ASST_MSG_ID = "asst-msg-001"
+
+// Build mock message + parts
+const baseMessage = {
+ id: ASST_MSG_ID,
+ sessionID: SESSION_ID,
+ role: "assistant",
+ // ... see composite.stories.tsx for full shape
+}
+
+const myPart = {
+ id: "part-001",
+ sessionID: SESSION_ID,
+ messageID: ASST_MSG_ID,
+ type: "tool",
+ tool: "read",
+ // ... see composite.stories.tsx for full ToolPart shape
+}
+
+function dataWith(parts: any[], permissions?: PermissionRequest[]) {
+ return {
+ ...defaultMockData,
+ message: { [SESSION_ID]: [baseMessage] },
+ part: { [ASST_MSG_ID]: parts },
+ permission: permissions ? { [SESSION_ID]: permissions } : {},
+ }
+}
+
+export const MyToolStory: Story = {
+ name: "Tool — with custom parts",
+ render: () => (
+
+
+
+ ),
+}
+```
+
+## Step 3: Handle narrow viewports
+
+For components that should be tested at multiple widths, create separate stories. Stories whose Storybook ID ends in `-200` are automatically rendered at 200px width by the test runner:
+
+```tsx
+export const Default420: Story = {
+ name: "Default — 420px",
+ render: () => (
+
+
+
+ ),
+}
+
+export const Default200: Story = {
+ name: "Default — 200px", // Storybook ID will end in -200
+ render: () => (
+
+
+
+ ),
+}
+```
+
+The naming convention with `-200` suffix on the **export name** (e.g., `Default200`) produces the ID `mycategory--default-200`, which the test runner detects and uses a 200px viewport for.
+
+## Step 4: Handle animations / non-deterministic content
+
+The test runner injects CSS to disable all animations and transitions. If a story still produces non-deterministic frames (e.g., a spinner captured at a random rotation), add the story ID to the `SKIP` set in `tests/visual-regression.spec.ts`:
+
+```ts
+const SKIP = new Set(["agentmanager--worktree-item-busy"])
+```
+
+Only skip stories as a last resort. Prefer making the story deterministic (e.g., use a static state instead of an animated one).
+
+## Step 5: Generate baseline images
+
+Baselines are generated on **Linux CI only** (font rendering differs on macOS). The CI workflow at `.github/workflows/visual-regression.yml` auto-runs `bun run test:visual:update` and commits new baselines to the PR branch.
+
+**You do NOT need to generate baseline PNGs locally.** Just write the story, push, and CI handles the rest.
+
+To preview stories locally:
+
+```bash
+# From packages/kilo-vscode/
+bun run storybook
+# Opens at http://localhost:6007
+```
+
+# Reference: snapshot directory structure
+
+Snapshots live at `packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/`:
+
+```
+tests/visual-regression.spec.ts-snapshots/
+ {title-slug}/
+ {variant-slug}-chromium-linux.png
+```
+
+The title-slug is derived from the meta `title` (lowercased, slashes become hyphens). The variant-slug is derived from the story export name (kebab-cased). Chromium and Linux suffixes are appended by Playwright.
+
+Example mapping:
+
+| Meta title | Export name | Snapshot path |
+| --------------------- | -------------------- | ----------------------------------------------------------- |
+| `"Chat"` | `ChatViewIdle` | `chat/chat-view-idle-chromium-linux.png` |
+| `"Composite/Webview"` | `GlobWithPermission` | `composite-webview/glob-with-permission-chromium-linux.png` |
+| `"Prompt Input"` | `Default200` | `prompt-input/default-200-chromium-linux.png` |
+| `"AgentManager"` | `WorktreeItemActive` | `agentmanager/worktree-item-active-chromium-linux.png` |
+
+# Reference: Playwright config
+
+Key settings in `packages/kilo-vscode/playwright.config.ts`:
+
+- Default viewport: **420x720** (VS Code sidebar dimensions)
+- Narrow stories (ID ending `-200`): **200x720**
+- Max pixel diff ratio: **0.01** (1% tolerance)
+- Browser: **Chromium only**
+- Storybook: built and served statically on port **6007**
+- Animations: forced off via `reducedMotion: "reduce"` + injected CSS
+
+# Reference: CI pipeline
+
+The `visual-regression.yml` workflow triggers on PRs when these paths change:
+
+- `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*`
+- `.github/workflows/visual-regression.yml`
+
+CI auto-commits new baselines via Git LFS and fails if screenshots changed, requiring developer review.
+
+# Reference: import paths
+
+Sidebar webview components live in `webview-ui/src/components/` and are imported relative to the stories directory:
+
+```tsx
+import { ChatView } from "../components/chat/ChatView"
+import { PromptInput } from "../components/chat/PromptInput"
+import { AssistantMessage } from "../components/chat/AssistantMessage"
+```
+
+Agent Manager components live in `webview-ui/agent-manager/` (one level up from the stories dir):
+
+```tsx
+import { FileTree } from "../../agent-manager/FileTree"
+import { DiffPanel } from "../../agent-manager/DiffPanel"
+import { WorktreeItem } from "../../agent-manager/WorktreeItem"
+import "../../agent-manager/agent-manager.css" // Required for AM component styles
+```
+
+kilo-ui components are imported via deep subpaths:
+
+```tsx
+import { Part } from "@kilocode/kilo-ui/message-part"
+import { BasicTool } from "@kilocode/kilo-ui/basic-tool"
+import { Button } from "@kilocode/kilo-ui/button"
+```
+
+SDK types for mock data:
+
+```tsx
+import type { AssistantMessage as SDKAssistantMessage, TextPart, ToolPart } from "@kilocode/sdk/v2"
+import type { PermissionRequest, QuestionRequest } from "../types/messages"
+```
+
+# Reference: Storybook theme globals
+
+The test runner renders every story with dark theme globals:
+
+```
+globals=colorScheme:dark;theme:kilo-vscode;vscodeTheme:dark-modern
+```
+
+The `.storybook/preview.tsx` applies these via a decorator that calls `applyVscodeTheme()` / `applyKiloTheme()` from kilo-ui. Stories do NOT need to handle theming — it happens automatically.
+
+# Reference: tool override registration
+
+If your story renders `AssistantMessage` with tool parts, you may need to register VS Code tool overrides at the top of the file (outside any story), as done in `composite.stories.tsx`:
+
+```tsx
+import { registerVscodeToolOverrides } from "../components/chat/VscodeToolOverrides"
+registerVscodeToolOverrides()
+```
+
+This ensures tool cards like `bash` render with their VS Code-specific expanded/collapsed behavior.
+
+# Checklist for adding a new visual regression test
+
+1. Identify or create the story file in `webview-ui/src/stories/`
+2. Import the component and `StoryProviders` (and optionally `mockSessionValue`, `defaultMockData`)
+3. Write the story with explicit dimensions and mock data
+4. Wrap in `` with appropriate props
+5. If testing multiple widths, create separate exports with the `-200` suffix convention
+6. If the component has animated states, prefer a static variant or add to the SKIP set
+7. Push to PR — CI generates baseline PNGs automatically
+8. Review the auto-committed baseline images in the PR diff
diff --git a/.opencode/agent/translator.md b/.opencode/agent/translator.md
index 7afaf5fd00b..9cad228a216 100644
--- a/.opencode/agent/translator.md
+++ b/.opencode/agent/translator.md
@@ -13,7 +13,7 @@ Requirements:
- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure).
- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks.
- Also preserve every term listed in the Do-Not-Translate glossary below.
-- Also apply locale-specific guidance from `.opencode/agent/glossary/.md` when available (for example, `zh-cn.md`).
+- Also apply locale-specific guidance from `.opencode/glossary/.md` when available (for example, `zh-cn.md`).
- Do not modify fenced code blocks.
- Output ONLY the translation (no commentary).
diff --git a/.opencode/agent/glossary/README.md b/.opencode/glossary/README.md
similarity index 100%
rename from .opencode/agent/glossary/README.md
rename to .opencode/glossary/README.md
diff --git a/.opencode/agent/glossary/ar.md b/.opencode/glossary/ar.md
similarity index 100%
rename from .opencode/agent/glossary/ar.md
rename to .opencode/glossary/ar.md
diff --git a/.opencode/agent/glossary/br.md b/.opencode/glossary/br.md
similarity index 100%
rename from .opencode/agent/glossary/br.md
rename to .opencode/glossary/br.md
diff --git a/.opencode/agent/glossary/bs.md b/.opencode/glossary/bs.md
similarity index 100%
rename from .opencode/agent/glossary/bs.md
rename to .opencode/glossary/bs.md
diff --git a/.opencode/agent/glossary/da.md b/.opencode/glossary/da.md
similarity index 100%
rename from .opencode/agent/glossary/da.md
rename to .opencode/glossary/da.md
diff --git a/.opencode/agent/glossary/de.md b/.opencode/glossary/de.md
similarity index 100%
rename from .opencode/agent/glossary/de.md
rename to .opencode/glossary/de.md
diff --git a/.opencode/agent/glossary/es.md b/.opencode/glossary/es.md
similarity index 100%
rename from .opencode/agent/glossary/es.md
rename to .opencode/glossary/es.md
diff --git a/.opencode/agent/glossary/fr.md b/.opencode/glossary/fr.md
similarity index 100%
rename from .opencode/agent/glossary/fr.md
rename to .opencode/glossary/fr.md
diff --git a/.opencode/agent/glossary/ja.md b/.opencode/glossary/ja.md
similarity index 100%
rename from .opencode/agent/glossary/ja.md
rename to .opencode/glossary/ja.md
diff --git a/.opencode/agent/glossary/ko.md b/.opencode/glossary/ko.md
similarity index 100%
rename from .opencode/agent/glossary/ko.md
rename to .opencode/glossary/ko.md
diff --git a/.opencode/agent/glossary/no.md b/.opencode/glossary/no.md
similarity index 100%
rename from .opencode/agent/glossary/no.md
rename to .opencode/glossary/no.md
diff --git a/.opencode/agent/glossary/pl.md b/.opencode/glossary/pl.md
similarity index 100%
rename from .opencode/agent/glossary/pl.md
rename to .opencode/glossary/pl.md
diff --git a/.opencode/agent/glossary/ru.md b/.opencode/glossary/ru.md
similarity index 100%
rename from .opencode/agent/glossary/ru.md
rename to .opencode/glossary/ru.md
diff --git a/.opencode/agent/glossary/th.md b/.opencode/glossary/th.md
similarity index 100%
rename from .opencode/agent/glossary/th.md
rename to .opencode/glossary/th.md
diff --git a/.opencode/glossary/tr.md b/.opencode/glossary/tr.md
new file mode 100644
index 00000000000..72b1cdfb40b
--- /dev/null
+++ b/.opencode/glossary/tr.md
@@ -0,0 +1,38 @@
+# tr Glossary
+
+## Sources
+
+- PR #15835: https://github.com/anomalyco/opencode/pull/15835
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose, docs, and UI copy)
+- Keep lowercase `opencode` in commands, package names, paths, URLs, and other exact identifiers
+- `` stays the literal key token in code blocks; use `Tab` for the nearby explanatory label in prose
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+These are PR-backed wording preferences and may evolve.
+
+| English / Context | Preferred | Notes |
+| ------------------------- | --------------------------------------- | ------------------------------------------------------------- |
+| available in beta | `beta olarak mevcut` | Prefer this over `beta olarak kullanılabilir` |
+| privacy-first | `Gizlilik öncelikli tasarlandı` | Prefer this over `Önce gizlilik için tasarlandı` |
+| connect your local models | `yerel modellerinizi bağlayabilirsiniz` | Use the fuller, more direct action phrase |
+| `` key label | `Tab` | Use `Tab` in prose; keep `` in literal UI or code blocks |
+| cross-platform | `cross-platform (tüm platformlarda)` | Keep the English term, add a short clarification when helpful |
+
+## Guidance
+
+- Prefer natural Turkish phrasing over literal translation
+- Merge broken sentence fragments into one clear sentence when the source is a single thought
+- Keep product naming consistent: `OpenCode` in prose, `opencode` only for exact technical identifiers
+- When an English technical term is intentionally kept, add a short Turkish clarification only if it improves readability
+
+## Avoid
+
+- Avoid `beta olarak kullanılabilir` when `beta olarak mevcut` fits
+- Avoid `Önce gizlilik için tasarlandı`; use the more natural reviewed wording instead
+- Avoid `Sekme` for the translated key label in prose when referring to ``
+- Avoid changing `opencode` to `OpenCode` inside commands, URLs, package names, or code literals
diff --git a/.opencode/agent/glossary/zh-cn.md b/.opencode/glossary/zh-cn.md
similarity index 100%
rename from .opencode/agent/glossary/zh-cn.md
rename to .opencode/glossary/zh-cn.md
diff --git a/.opencode/agent/glossary/zh-tw.md b/.opencode/glossary/zh-tw.md
similarity index 100%
rename from .opencode/agent/glossary/zh-tw.md
rename to .opencode/glossary/zh-tw.md
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 24190aad6e9..daa91bc53e5 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -114,7 +114,10 @@
"clear": false
},
"options": {
- "cwd": "${workspaceFolder}/packages/kilo-vscode"
+ "cwd": "${workspaceFolder}/packages/kilo-vscode",
+ "env": {
+ "VSCODE_EXEC_PATH": "${execPath}"
+ }
},
"problemMatcher": []
}
diff --git a/AGENTS.md b/AGENTS.md
index 18ae4708973..c0262af58b8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,6 +14,7 @@ Kilo CLI is an open source AI coding agent that generates code from natural lang
- **Test**: `bun test` from `packages/opencode/` (NOT from root -- root blocks tests)
- **Single test**: `bun test test/tool/tool.test.ts` from `packages/opencode/`
- **SDK regen**: After changing server endpoints in `packages/opencode/src/server/`, run `./script/generate.ts` from root to regenerate `packages/sdk/js/`
+- **Knip** (unused exports): `bun run knip` from `packages/kilo-vscode/`. CI runs this — all exported types/functions must be imported somewhere. Remove or unexport unused exports before pushing.
## Products
@@ -63,6 +64,17 @@ Prefer `const`.
Good:
+### Naming Enforcement (Read This)
+
+THIS RULE IS MANDATORY FOR AGENT WRITTEN CODE.
+
+- Use single word names by default for new locals, params, and helper functions.
+- Multi-word names are allowed only when a single word would be unclear or ambiguous.
+- Do not introduce new camelCase compounds when a short single-word alternative is clear.
+- Before finishing edits, review touched lines and shorten newly introduced identifiers where possible.
+- Good short names to prefer: `pid`, `cfg`, `err`, `opts`, `dir`, `root`, `child`, `state`, `timeout`.
+- Examples to avoid unless truly required: `inputPID`, `existingClient`, `connectTimeout`, `workerPath`.
+
```ts
const foo = condition ? 1 : 2
```
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b122ccdf82f..ed0cb83d8af 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -16,7 +16,7 @@ The Kilo Community is [on Discord](https://kilo.ai/discord).
## Developing Kilo CLI
-- **Requirements:** Bun 1.3+
+- **Requirements:** Bun 1.3.10+
- Install dependencies and start the dev server from the repo root:
```bash
@@ -26,7 +26,7 @@ The Kilo Community is [on Discord](https://kilo.ai/discord).
### Running against a different directory
-By default, `bun dev` runs Kilo CLI in the `packages/kilo-cli` directory. To run it against a different directory or repository:
+By default, `bun dev` runs Kilo CLI in the `packages/opencode` directory. To run it against a different directory or repository:
```bash
bun dev
@@ -43,13 +43,13 @@ bun dev .
To compile a standalone executable:
```bash
-./packages/kilo-cli/script/build.ts --single
+./packages/opencode/script/build.ts --single
```
Then run it with:
```bash
-./packages/kilo-cli/dist/kilo-cli-/bin/kilo
+./packages/opencode/dist/@kilocode/cli-/bin/kilo
```
Replace `` with your platform (e.g., `darwin-arm64`, `linux-x64`).
diff --git a/README.md b/README.md
index d393f1ff3c0..31d9e5cf348 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
# 🚀 Kilo
> Kilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.
-> #1 on OpenRouter. 1.5M+ Kilo Coders. 25T+ tokens processed
+> [#1 coding agent on OpenRouter](https://openrouter.ai/apps/category/coding). 1.5M+ Kilo Coders. 25T+ tokens processed
- ✨ Generate code from natural language
- ✅ Checks its own work
@@ -20,10 +20,6 @@
- 🎁 API keys optional
- 💡 **Get $20 in bonus credits when you top-up for the first time** Credits can be used with 500+ models like Gemini 3.1 Pro, Claude 4.6 Sonnet & Opus, and GPT-5.2
-