From 7992aadf347874ae732dc99ad706ecbd5f35261d Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 10:47:28 +0000 Subject: [PATCH] chore(scripts): extract forbidden string check into dedicated script Replace the inline opncd.ai/s/ grep added to check-kilocode-change with a standalone script/check-forbidden-strings.ts. Wired into test-vscode.yml as a separate step alongside the existing marker check. --- .github/workflows/test-vscode.yml | 4 +++ AGENTS.md | 2 +- packages/kilo-vscode/package.json | 3 +- script/check-forbidden-strings.ts | 60 +++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 script/check-forbidden-strings.ts diff --git a/.github/workflows/test-vscode.yml b/.github/workflows/test-vscode.yml index 6056958584..2b5ed0dd6b 100644 --- a/.github/workflows/test-vscode.yml +++ b/.github/workflows/test-vscode.yml @@ -53,3 +53,7 @@ jobs: - name: Check for kilocode_change markers working-directory: packages/kilo-vscode run: bun run check-kilocode-change + + - name: Check for forbidden strings + working-directory: packages/kilo-vscode + run: bun run check-forbidden-strings diff --git a/AGENTS.md b/AGENTS.md index 5500574454..34fec96cc6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,7 @@ Kilo CLI is an open source AI coding agent that generates code from natural lang - **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. - **Source links**: After adding or changing URLs in `packages/kilo-vscode/`, `packages/kilo-vscode/webview-ui/`, or `packages/opencode/src/`, run `bun run script/extract-source-links.ts` from the repo root and commit the updated `packages/kilo-docs/source-links.md`. CI runs this check — the build fails if the file is stale. -- **kilocode_change check**: `bun run check-kilocode-change` from `packages/kilo-vscode/`. CI runs this — `kilocode_change` is a marker for upstream merge conflicts and must not appear in `packages/kilo-vscode/` or `packages/kilo-ui/` (these are entirely Kilo Code additions). Remove the markers before pushing. Also forbids the legacy upstream share URL pattern `opncd.ai/s/` anywhere in the repo. +- **kilocode_change check**: `bun run check-kilocode-change` from `packages/kilo-vscode/`. CI runs this — `kilocode_change` is a marker for upstream merge conflicts and must not appear in `packages/kilo-vscode/` or `packages/kilo-ui/` (these are entirely Kilo Code additions). Remove the markers before pushing. - **opencode annotation check**: `bun run script/check-opencode-annotations.ts` from repo root. CI runs this on PRs touching `packages/opencode/` — every Kilo-specific change in shared opencode files must be annotated with `kilocode_change` markers. Exempt paths (no markers needed): `packages/opencode/src/kilocode/`, `packages/opencode/test/kilocode/`, and any path containing `kilocode` in the name. - **workflow allowlist**: `bun run script/check-workflows.ts` from repo root. CI runs this as part of the annotations workflow — any `.yml` / `.yaml` file added to or removed from `.github/workflows/` must be reflected in the hardcoded list in `script/check-workflows.ts`. Prevents upstream-merged workflows from silently starting to run in our CI. - **Backend/SDK programmatic testing**: see [TESTING.md](./TESTING.md) for spawning the local main-branch backend (`bun dev serve`) and driving it via `curl` — use this instead of `kilo serve` (prod binary) when testing backend fixes. diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index 460d06d5e4..d55f141b14 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -895,7 +895,8 @@ "format": "prettier --write .", "format:check": "prettier --check .", "knip": "knip", - "check-kilocode-change": "! grep -rIn 'kilocode_change' . ../kilo-ui/ --exclude='package.json' --exclude='*.md' --exclude-dir='node_modules' --exclude-dir='dist' | grep -v '`kilocode_change`' && ! grep -rIn 'opncd\\.ai/s/' ../../ --exclude='package.json' --exclude-dir='node_modules' --exclude-dir='dist' --exclude-dir='.git'", + "check-kilocode-change": "! grep -rIn 'kilocode_change' . ../kilo-ui/ --exclude='package.json' --exclude='*.md' --exclude-dir='node_modules' --exclude-dir='dist' | grep -v '`kilocode_change`'", + "check-forbidden-strings": "bun ../../script/check-forbidden-strings.ts", "lint": "eslint src webview-ui", "test": "vscode-test", "test:unit": "bun test tests/unit/", diff --git a/script/check-forbidden-strings.ts b/script/check-forbidden-strings.ts new file mode 100644 index 0000000000..aecd66a8d1 --- /dev/null +++ b/script/check-forbidden-strings.ts @@ -0,0 +1,60 @@ +#!/usr/bin/env bun +// kilocode_change - new file + +/** + * Greps tracked files for forbidden strings that must not appear in the repo. + * + * Currently enforced: + * - opncd.ai/s/ -- legacy upstream OpenCode share URL pattern. Kilo shares + * go through a different host/path; this string sneaking + * back in usually means a hardcoded upstream URL. + */ + +import { spawnSync } from "node:child_process" +import path from "node:path" + +const ROOT = path.resolve(import.meta.dir, "..") +const SELF = path.relative(ROOT, import.meta.path).replaceAll("\\", "/") + +const forbidden = [{ pattern: "opncd.ai/s/", reason: "legacy upstream share URL pattern" }] + +const ls = spawnSync("git", ["ls-files", "-z"], { cwd: ROOT, encoding: "buffer" }) +if (ls.status !== 0) { + console.error(ls.stderr?.toString().trim() || "git ls-files failed") + process.exit(1) +} + +const files = ls.stdout + .toString("utf8") + .split("\0") + .filter(Boolean) + .filter((f) => f !== SELF) + +const hits: string[] = [] +for (const file of files) { + const buf = Bun.file(path.join(ROOT, file)) + if (!(await buf.exists())) continue + // Skip binary-ish files: read as text and skip if it contains a NUL byte. + const text = await buf.text().catch(() => null) + if (text === null) continue + if (text.includes("\0")) continue + for (const f of forbidden) { + let idx = 0 + while (true) { + const at = text.indexOf(f.pattern, idx) + if (at === -1) break + const line = text.slice(0, at).split("\n").length + hits.push(`${file}:${line}: ${f.pattern} (${f.reason})`) + idx = at + f.pattern.length + } + } +} + +if (hits.length === 0) { + console.log(`check-forbidden-strings: ${files.length} file(s) checked, no forbidden strings found.`) + process.exit(0) +} + +console.error("Found forbidden strings:") +for (const h of hits) console.error(` ${h}`) +process.exit(1)