From 3b88d2a5cec4a8743ce2fa4a499d53f3fbffdfec Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 28 Apr 2026 12:33:40 -0400 Subject: [PATCH] fix(cli): include shell scripts in annotation check --- .../tests/check-opencode-annotations.test.ts | 34 +++++++++++++++++-- script/check-opencode-annotations.ts | 14 +++++--- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/packages/script/tests/check-opencode-annotations.test.ts b/packages/script/tests/check-opencode-annotations.test.ts index 18cb540959d..8ed042e755f 100644 --- a/packages/script/tests/check-opencode-annotations.test.ts +++ b/packages/script/tests/check-opencode-annotations.test.ts @@ -1,7 +1,8 @@ import { describe, expect, test } from "bun:test" import path from "node:path" -const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".yml", ".yaml", ".toml"]) +const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".yml", ".yaml", ".toml", ".sh", ".bash", ".zsh"]) +const FILES = new Map() const SCOPES = [ "sdks/vscode", "packages/opencode", @@ -36,7 +37,10 @@ function isExempt(file: string) { } function isSource(file: string) { - return SOURCE_EXTS.has(path.extname(file)) + const ext = path.extname(file) + if (SOURCE_EXTS.has(ext)) return true + if (ext) return false + return FILES.get(file)?.startsWith("#!") ?? false } const MARKER_PREFIX = /(?:\/\/|\{?\s*\/\*|#)\s*kilocode_change\b/ @@ -113,11 +117,12 @@ describe("hasMarker", () => { ["/* kilocode_change start */", true], ["/* kilocode_change end */", true], - // YAML/TOML-style inline + // YAML/TOML/shell-style inline ["# kilocode_change", true], [" # kilocode_change", true], ["name: test # kilocode_change", true], ['name = "zed" # kilocode_change', true], + ['export FOO="bar" # kilocode_change', true], ["# kilocode_change start", true], ["# kilocode_change end", true], @@ -178,6 +183,8 @@ describe("isExempt", () => { ["packages/desktop-electron/src/main/index.ts", false], ["sdks/vscode/src/extension.ts", false], ["packages/extensions/zed/extension.toml", false], + ["github/script/release", false], + ["github/script/publish", false], ["script/changelog.ts", false], // kilocode_change is not the same as kilocode ["packages/opencode/src/check-opencode-annotations.ts", false], @@ -203,6 +210,8 @@ describe("isChecked", () => { ["script/check-opencode-annotations.ts", true], [".github/workflows/test.yml", true], ["github/action.yml", true], + ["github/script/release", true], + ["github/script/publish", true], ["packages/kilo-ui/src/components/icon.tsx", false], ["packages/kilo-vscode/src/extension.ts", false], ["packages/sdk/js/src/index.ts", false], @@ -227,15 +236,23 @@ describe("isSource", () => { ["workflow.yml", true], ["workflow.yaml", true], ["extension.toml", true], + ["script.sh", true], + ["script.bash", true], + ["script.zsh", true], [".md", false], [".txt", false], ["Makefile", false], + ["github/script/release", true], + ["github/script/plain", false], ["foo.go", false], ["foo.rs", false], ] test.each(cases)("%j → isSource=%j", (file, expected) => { + FILES.set("github/script/release", "#!/usr/bin/env bash\n") + FILES.set("github/script/plain", "set -euo pipefail\n") expect(isSource(file)).toBe(expected) + FILES.clear() }) }) @@ -277,6 +294,11 @@ describe("coveredLines", () => { expect(covered).toEqual(new Set([1, 2, 3])) }) + test("whole-file shell annotation after shebang", () => { + const covered = coveredLines('#!/usr/bin/env bash\n# kilocode_change - new file\nset -euo pipefail') + expect(covered).toEqual(new Set([1, 2, 3])) + }) + test("JS block markers", () => { const text = [ "const a = 1", @@ -333,6 +355,12 @@ describe("coveredLines", () => { expect(covered).toEqual(new Set([1, 2, 3])) }) + test("shell block markers", () => { + const text = ["# kilocode_change start", "set -euo pipefail", "# kilocode_change end"].join("\n") + const covered = coveredLines(text) + expect(covered).toEqual(new Set([1, 2, 3])) + }) + test("inline JS marker covers only that line", () => { const text = ["const a = 1", "const b = 2 // kilocode_change", "const c = 3"].join("\n") const covered = coveredLines(text) diff --git a/script/check-opencode-annotations.ts b/script/check-opencode-annotations.ts index ba54be93a49..ec5d5b46d24 100644 --- a/script/check-opencode-annotations.ts +++ b/script/check-opencode-annotations.ts @@ -16,7 +16,8 @@ * - is empty / whitespace-only (skipped) * - is itself a marker line (auto-covered) * - * JS (//), JSX ({/ * ... * /}), YAML (#), and TOML (#) comment styles are recognized. + * JS (//), JSX ({/ * ... * /}), YAML (#), TOML (#), and shell (#) comment styles are recognized. + * Extensionless files with shebangs are treated as source files. * * Exempt paths (no markers needed — entirely Kilo-specific): * - packages/opencode/src/kilocode/** @@ -32,7 +33,7 @@ import { readFileSync } from "node:fs" import path from "node:path" const ROOT = path.resolve(import.meta.dir, "..") -const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".yml", ".yaml", ".toml"]) +const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".yml", ".yaml", ".toml", ".sh", ".bash", ".zsh"]) const SCOPES = [ "sdks/vscode", "packages/opencode", @@ -96,7 +97,10 @@ function isChecked(file: string) { } function isSource(file: string) { - return SOURCE_EXTS.has(path.extname(file)) + const ext = path.extname(file) + if (SOURCE_EXTS.has(ext)) return true + if (ext) return false + return readFileSync(path.join(ROOT, file), "utf8").startsWith("#!") } function addedLines(file: string): Set { @@ -112,7 +116,7 @@ function addedLines(file: string): Set { return out } -// Matches the start of a kilocode_change marker in JS, JSX, YAML, and TOML comments. +// Matches the start of a kilocode_change marker in JS, JSX, YAML, TOML, and shell comments. const MARKER_PREFIX = /(?:\/\/|\{?\s*\/\*|#)\s*kilocode_change\b/ function hasMarker(line: string) { @@ -221,7 +225,7 @@ console.error( " ...", " {/* kilocode_change end */}", "", - "YAML:", + "YAML/TOML/shell:", " # kilocode_change", " # kilocode_change start", " ...",