Merge branch 'main' into feat/allow-everything

This commit is contained in:
Marian Alexandru Alecu
2026-04-08 19:19:33 +03:00
committed by GitHub
38 changed files with 1190 additions and 58 deletions
+15
View File
@@ -220,6 +220,21 @@ const bar = 2
// kilocode_change - new file
```
<!-- prettier-ignore -->
**JSX/TSX (inside JSX templates):**
<!-- prettier-ignore -->
```tsx
{/* kilocode_change */}
```
<!-- prettier-ignore -->
```tsx
{/* kilocode_change start */}
<MyComponent />
{/* kilocode_change end */}
```
#### When markers are NOT needed
Code in these paths is Kilo Code-specific and does NOT need `kilocode_change` markers:
-2
View File
@@ -72,12 +72,10 @@ During development, `bun dev` is the local equivalent of the built `kilo` comman
# Development (from project root)
bun dev --help # Show all available commands
bun dev serve # Start headless API server
bun dev web # Start server + open web interface
# Production
kilo --help # Show all available commands
kilo serve # Start headless API server
kilo web # Start server + open web interface
```
### Testing with a local backend
@@ -31,6 +31,17 @@ The panel opens as an editor tab and stays active across focus changes.
Each Agent Manager session runs in an isolated git worktree on a separate branch, keeping your main branch clean.
### PR Status Badges
Worktree items in the sidebar display a **PR status badge** when the branch has an associated pull request:
- **Open** — badge indicating the PR is open (its color can also reflect review and check status)
- **Merged** — purple badge indicating the PR has been merged
- **Closed** — red badge indicating the PR was closed without merging
- **Draft** — gray badge indicating the PR is in draft state
The badge appears on the right side of each worktree item and updates automatically via polling. If the worktree's branch doesn't have a PR yet, no badge is shown.
### Creating a New Worktree Session
1. Click **New Worktree** or press `Cmd+N` (macOS) / `Ctrl+N` (Windows/Linux) to create a new worktree
@@ -37,7 +37,13 @@ See [Auto-Approving Actions](/docs/getting-started/settings/auto-approving-actio
### Is the context progress graph still available?
The context progress graph will be [added soon](https://github.com/Kilo-Org/kilocode/issues/8210) for users who like to see it.
Yes — the context progress graph (also known as the task timeline) is now available. It appears at the top of the chat panel and shows:
- **Timeline bars** — colored bars representing session activity (different colors for read, write, tool, error, and text parts)
- **Context window progress** — a three-segment bar showing used, reserved, and available tokens, with a visual indicator when usage exceeds 50%
- **Token breakdown** — input, output, cache writes, and cache reads display
You can expand or collapse the graph — your preference is saved in the `kilo-code.new.showTaskTimeline` setting.
### I like to closely monitor and approve the behavior of the agent. How can I do that better in the new version?
+1 -1
View File
@@ -34,7 +34,7 @@ export default [
{
files: ["src/KiloProvider.ts"],
rules: {
"max-lines": ["error", 3200],
"max-lines": ["error", 3300],
},
},
eslintConfigPrettier,
+16 -9
View File
@@ -90,12 +90,26 @@ import {
saveCustomProvider as saveCustomProviderAction,
} from "./provider-actions"
import { fetchOpenAIModels, FetchModelsError } from "./shared/fetch-models"
import type { Agent } from "@kilocode/sdk/v2/client"
type KiloProviderOptions = {
projectDirectory?: string | null
slimEditMetadata?: boolean
}
// Helper to map agent data to the subset of fields sent to the webview
const mapAgent = (a: Agent) => ({
name: a.name,
displayName: a.displayName,
description: a.description,
mode: a.mode,
native: a.native,
hidden: a.hidden,
color: a.color,
deprecated: a.deprecated,
permission: a.permission,
})
export class KiloProvider implements vscode.WebviewViewProvider, TelemetryPropertiesProvider {
public static readonly viewType = "kilo-code.SidebarProvider"
@@ -1621,15 +1635,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
const message = {
type: "agentsLoaded",
agents: visible.map((a) => ({
name: a.name,
displayName: a.displayName,
description: a.description,
mode: a.mode,
native: a.native,
color: a.color,
deprecated: a.deprecated,
})),
agents: visible.map(mapAgent),
allAgents: agents.map(mapAgent),
defaultAgent,
}
this.cachedAgentsMessage = message
@@ -218,12 +218,12 @@ export class WorktreeStateManager {
const removed = this.worktrees.delete(id)
if (!removed) return []
// Dissociate all sessions from this worktree (set worktreeId to null)
// Collect and remove all sessions belonging to this worktree
const orphaned: ManagedSession[] = []
for (const s of this.sessions.values()) {
if (s.worktreeId === id) {
s.worktreeId = null
orphaned.push(s)
orphaned.push({ ...s })
this.sessions.delete(s.id)
}
}
@@ -234,7 +234,7 @@ export class WorktreeStateManager {
const idx = this.worktreeOrder.indexOf(id)
if (idx !== -1) this.worktreeOrder.splice(idx, 1)
this.log(`Removed worktree ${id}, orphaned ${orphaned.length} sessions`)
this.log(`Removed worktree ${id}, removed ${orphaned.length} sessions`)
void this.save()
return orphaned
}
@@ -491,7 +491,13 @@ export class WorktreeStateManager {
}) ?? wt.path
this.worktrees.set(id, { id, ...wt, path: fixed })
}
let pruned = 0
for (const [id, s] of Object.entries(data.sessions ?? {})) {
// Skip orphaned sessions (null worktreeId or referencing a deleted worktree)
if (!s.worktreeId || !this.worktrees.has(s.worktreeId)) {
pruned++
continue
}
this.sessions.set(id, { id, ...s })
}
for (const [id, sec] of Object.entries(data.sections ?? {})) {
@@ -517,6 +523,10 @@ export class WorktreeStateManager {
}
this.defaultBase = data.defaultBaseBranch
this.log(`Loaded state: ${this.worktrees.size} worktrees, ${this.sessions.size} sessions`)
if (pruned > 0) {
this.log(`Pruned ${pruned} orphaned sessions`)
void this.save()
}
} catch (error) {
const code = (error as NodeJS.ErrnoException).code
if (code !== "ENOENT") {
@@ -526,7 +536,7 @@ export class WorktreeStateManager {
return migration
}
/** Remove worktrees whose directories no longer exist on disk. */
/** Remove worktrees whose directories no longer exist on disk and prune orphaned sessions. */
async validate(root: string): Promise<void> {
let changed = false
for (const wt of [...this.worktrees.values()]) {
@@ -537,7 +547,17 @@ export class WorktreeStateManager {
changed = true
}
}
if (changed) await this.save()
// Prune orphaned sessions (worktreeId is null or references a deleted worktree)
for (const s of [...this.sessions.values()]) {
if (!s.worktreeId || !this.worktrees.has(s.worktreeId)) {
this.sessions.delete(s.id)
changed = true
}
}
if (changed) {
this.log(`Pruned orphaned sessions during validation`)
await this.save()
}
}
/** Wait for any in-flight save to complete without triggering a new one. */
@@ -41,7 +41,7 @@ describe("WorktreeStateManager", () => {
expect(manager.findWorktreeByPath("/tmp/c")).toBeUndefined()
})
it("removes worktree and orphans sessions", () => {
it("removes worktree and deletes its sessions", () => {
const wt = manager.addWorktree({ branch: "fix", path: "/tmp/fix", parentBranch: "main" })
manager.addSession("s1", wt.id)
manager.addSession("s2", wt.id)
@@ -49,9 +49,10 @@ describe("WorktreeStateManager", () => {
const orphaned = manager.removeWorktree(wt.id)
expect(orphaned).toHaveLength(2)
expect(manager.getWorktrees()).toHaveLength(0)
// Sessions still exist but with null worktreeId
expect(manager.getSession("s1")?.worktreeId).toBeNull()
expect(manager.getSession("s2")?.worktreeId).toBeNull()
// Sessions are removed from state
expect(manager.getSession("s1")).toBeUndefined()
expect(manager.getSession("s2")).toBeUndefined()
expect(manager.getSessions()).toHaveLength(0)
})
it("returns empty array when removing nonexistent worktree", () => {
@@ -140,7 +141,7 @@ describe("WorktreeStateManager", () => {
})
describe("persistence", () => {
it("saves and loads state", async () => {
it("saves and loads state, pruning orphaned sessions", async () => {
const wt = manager.addWorktree({ branch: "fix", path: "/tmp/fix", parentBranch: "main" })
manager.addSession("s1", wt.id)
manager.addSession("s2", null)
@@ -153,9 +154,10 @@ describe("WorktreeStateManager", () => {
expect(loaded.getWorktrees()).toHaveLength(1)
expect(loaded.getWorktrees()[0].branch).toBe("fix")
expect(loaded.getSessions()).toHaveLength(2)
// s2 had null worktreeId so it gets pruned on load
expect(loaded.getSessions()).toHaveLength(1)
expect(loaded.getSession("s1")?.worktreeId).toBe(wt.id)
expect(loaded.getSession("s2")?.worktreeId).toBeNull()
expect(loaded.getSession("s2")).toBeUndefined()
})
it("load is a no-op when file does not exist", async () => {
@@ -285,20 +287,35 @@ describe("WorktreeStateManager", () => {
})
describe("validate", () => {
it("removes worktrees whose directories do not exist", async () => {
it("removes worktrees whose directories do not exist and prunes their sessions", async () => {
const existing = path.join(root, "wt-exists")
fs.mkdirSync(existing, { recursive: true })
manager.addWorktree({ branch: "exists", path: existing, parentBranch: "main" })
manager.addWorktree({ branch: "gone", path: path.join(root, "wt-gone"), parentBranch: "main" })
manager.addSession("s1", manager.getWorktrees()[1].id)
const gone = manager.addWorktree({ branch: "gone", path: path.join(root, "wt-gone"), parentBranch: "main" })
manager.addSession("s1", gone.id)
await manager.validate(root)
expect(manager.getWorktrees()).toHaveLength(1)
expect(manager.getWorktrees()[0].branch).toBe("exists")
// Session orphaned (worktreeId set to null)
expect(manager.getSession("s1")?.worktreeId).toBeNull()
// Session removed along with its worktree
expect(manager.getSession("s1")).toBeUndefined()
})
it("prunes orphaned sessions with null worktreeId on validate", async () => {
const existing = path.join(root, "wt-exists")
fs.mkdirSync(existing, { recursive: true })
const wt = manager.addWorktree({ branch: "exists", path: existing, parentBranch: "main" })
manager.addSession("s1", wt.id)
manager.addSession("s2", null)
await manager.validate(root)
// s1 stays (its worktree exists), s2 is pruned (null worktreeId)
expect(manager.getSession("s1")).toBeTruthy()
expect(manager.getSession("s2")).toBeUndefined()
})
it("resolves relative paths against root", async () => {
@@ -319,8 +336,9 @@ describe("WorktreeStateManager", () => {
for (let i = 0; i < 20; i++) {
manager.addWorktree({ branch: `b-${i}`, path: `/tmp/b-${i}`, parentBranch: "main" })
}
const wts = manager.getWorktrees()
for (let i = 0; i < 20; i++) {
manager.addSession(`s-${i}`, null)
manager.addSession(`s-${i}`, wts[i]!.id)
}
// Wait for all fire-and-forget saves to settle
@@ -355,9 +373,9 @@ describe("WorktreeStateManager", () => {
expect(loaded.getWorktrees()).toHaveLength(1)
expect(loaded.getWorktrees()[0].branch).toBe("keep")
// s2 was orphaned when wt2 was removed, s1 and s3 belong to wt1
// s2 was removed when wt2 was removed, s1 and s3 belong to wt1
expect(loaded.getSession("s1")?.worktreeId).toBe(wt1.id)
expect(loaded.getSession("s2")?.worktreeId).toBeNull()
expect(loaded.getSession("s2")).toBeUndefined()
expect(loaded.getSession("s3")?.worktreeId).toBe(wt1.id)
})
@@ -419,7 +437,7 @@ describe("WorktreeStateManager", () => {
expect(manager.getSessions()).toHaveLength(0)
})
it("handles partial data with missing worktrees key", async () => {
it("handles partial data with missing worktrees key and prunes orphaned sessions", async () => {
const file = path.join(root, ".kilo", "agent-manager.json")
fs.writeFileSync(
file,
@@ -430,7 +448,8 @@ describe("WorktreeStateManager", () => {
await manager.load()
expect(manager.getWorktrees()).toHaveLength(0)
expect(manager.getSessions()).toHaveLength(1)
// Orphaned session with null worktreeId is pruned on load
expect(manager.getSessions()).toHaveLength(0)
})
})
@@ -82,7 +82,12 @@ const AgentBehaviourTab: Component = () => {
})
const agentNames = createMemo(() => {
const names = session.agents().map((a) => a.name)
// Exclude server-side hidden internal modes (compaction, title, summary)
// from the list. Config-only agents are still added below.
const names = session
.allAgents()
.filter((a) => !a.hidden)
.map((a) => a.name)
// Also include any agents from config that might not be in the agent list
const agents = Object.keys(config().agent ?? {})
for (const name of agents) {
@@ -93,10 +98,15 @@ const AgentBehaviourTab: Component = () => {
return names.sort()
})
const defaultAgentOptions = createMemo<SelectOption[]>(() => [
{ value: "", label: language.t("common.default") },
...agentNames().map((name) => ({ value: name, label: name })),
])
// Default-agent picker must only show visible primary agents (not subagents
// or hidden modes) since the CLI rejects those as default_agent values.
const defaultAgentOptions = createMemo<SelectOption[]>(() => {
const visible = session.agents().map((a) => a.name)
return [
{ value: "", label: language.t("common.default") },
...visible.map((name) => ({ value: name, label: name })),
]
})
const instructions = () => config().instructions ?? []
@@ -185,7 +195,7 @@ const AgentBehaviourTab: Component = () => {
))
}
const removableModes = createMemo(() => session.agents().filter((a) => !a.native))
const removableModes = createMemo(() => session.allAgents().filter((a) => !a.native))
const confirmRemoveMode = (agent: AgentInfo) => {
dialog.show(() => (
@@ -354,7 +364,7 @@ const AgentBehaviourTab: Component = () => {
<Card style={{ "margin-bottom": "12px" }}>
<For each={agentNames()}>
{(name, index) => {
const agent = () => session.agents().find((a) => a.name === name)
const agent = () => session.allAgents().find((a) => a.name === name)
const isCustom = () => !agent()?.native
const agentCfg = () => config().agent?.[name] ?? {}
const disabled = () => agentCfg().disable ?? false
@@ -396,6 +406,19 @@ const AgentBehaviourTab: Component = () => {
custom
</span>
</Show>
<Show when={agent()?.mode === "subagent"}>
<span
style={{
"font-size": "10px",
padding: "1px 5px",
"border-radius": "3px",
background: "var(--bg-subtle-base, var(--vscode-badge-background))",
color: "var(--text-weak-base, var(--vscode-badge-foreground))",
}}
>
{language.t("settings.agentBehaviour.badge.subagent")}
</span>
</Show>
<Show when={hidden()}>
<span
style={{
@@ -1,4 +1,4 @@
import { Component, Show, createMemo } from "solid-js"
import { Component, Show, For, createMemo, createSignal } from "solid-js"
import { TextField } from "@kilocode/kilo-ui/text-field"
import { Switch } from "@kilocode/kilo-ui/switch"
import { Card } from "@kilocode/kilo-ui/card"
@@ -8,7 +8,7 @@ import { IconButton } from "@kilocode/kilo-ui/icon-button"
import { useConfig } from "../../context/config"
import { useSession } from "../../context/session"
import { useLanguage } from "../../context/language"
import type { AgentConfig, AgentInfo } from "../../types/messages"
import type { AgentConfig, AgentInfo, PermissionRuleItem } from "../../types/messages"
import SettingsRow from "./SettingsRow"
import { buildExport } from "./mode-io"
@@ -26,8 +26,9 @@ const ModeEditView: Component<Props> = (props) => {
// agent() may be undefined for modes that only exist in the config draft (just
// created, not yet saved). This is fine — native defaults to false (correct for
// custom modes) and all fields read from cfg() which comes from config context.
const agent = () => session.agents().find((a) => a.name === props.name)
const agent = () => session.allAgents().find((a) => a.name === props.name)
const native = () => agent()?.native ?? false
const [expanded, setExpanded] = createSignal(false)
const cfg = createMemo<AgentConfig>(() => config().agent?.[props.name] ?? {})
@@ -230,6 +231,18 @@ const ModeEditView: Component<Props> = (props) => {
</SettingsRow>
</Card>
{/* Calculated permissions (read-only, collapsible) */}
<Show when={agent()?.permission} keyed>
{(rules) => (
<PermissionRuleset
agent={props.name}
rules={rules}
expanded={expanded()}
onToggle={() => setExpanded((v) => !v)}
/>
)}
</Show>
<div style={{ display: "flex", "justify-content": "flex-end" }}>
<Button variant="ghost" onClick={props.onBack}>
{language.t("settings.agentBehaviour.editMode.back")}
@@ -239,4 +252,201 @@ const ModeEditView: Component<Props> = (props) => {
)
}
// ---------------------------------------------------------------------------
// Collapsible permissions ruleset display
// ---------------------------------------------------------------------------
const ACTION_COLORS: Record<string, { bg: string; fg: string }> = {
allow: { bg: "var(--vscode-terminal-ansiGreen, #3fb950)", fg: "var(--vscode-editor-background, #1e1e1e)" },
ask: { bg: "var(--vscode-editorWarning-foreground, #cca700)", fg: "var(--vscode-editor-background, #1e1e1e)" },
deny: { bg: "var(--vscode-errorForeground, #f85149)", fg: "var(--vscode-editor-background, #fff)" },
unknown: { bg: "var(--vscode-descriptionForeground, #8b949e)", fg: "var(--vscode-editor-background, #1e1e1e)" },
}
interface RulesetProps {
agent: string
rules: PermissionRuleItem[]
expanded: boolean
onToggle: () => void
}
const PermissionRuleset: Component<RulesetProps> = (props) => {
const language = useLanguage()
const [copied, setCopied] = createSignal(false)
// Compute effective action per unique tool by finding the last rule with pattern "*"
// NOTE: This assumes the CLI uses "*" as the wildcard pattern for catch-all rules.
// If the CLI convention changes (e.g. to "**" or another pattern), this will need updating.
const summary = createMemo(() => {
const tools = new Map<string, PermissionRuleItem["action"]>()
for (const rule of props.rules) {
if (rule.pattern === "*") {
tools.set(rule.permission, rule.action)
}
}
return [...tools.entries()].sort((a, b) => a[0].localeCompare(b[0]))
})
const copy = (e: MouseEvent) => {
e.stopPropagation()
const data = { agent: props.agent, rules: props.rules }
navigator.clipboard.writeText(JSON.stringify(data, null, 2))
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<Card style={{ "margin-bottom": "12px" }}>
<div
style={{ display: "flex", "align-items": "center", cursor: "pointer", "user-select": "none" }}
onClick={props.onToggle}
>
<IconButton
size="small"
variant="ghost"
icon={props.expanded ? "chevron-down" : "chevron-right"}
onClick={(e: MouseEvent) => {
e.stopPropagation()
props.onToggle()
}}
/>
<span data-slot="settings-row-label-title" style={{ "margin-left": "4px" }}>
{language.t("settings.agentBehaviour.permissions.title")}
</span>
<span
style={{
"margin-left": "8px",
"font-size": "11px",
color: "var(--text-weak-base, var(--vscode-descriptionForeground))",
}}
>
{language.t("settings.agentBehaviour.permissions.count", { count: String(props.rules.length) })}
</span>
<div style={{ "margin-left": "auto" }}>
<IconButton
size="small"
variant="ghost"
icon={copied() ? "check" : "copy"}
title={language.t("settings.agentBehaviour.permissions.copy")}
onClick={copy}
/>
</div>
</div>
<Show when={props.expanded}>
{/* Summary: effective action per tool for wildcard pattern */}
<Show when={summary().length > 0}>
<div style={{ "margin-top": "8px", "margin-bottom": "8px" }}>
<div
style={{
"font-size": "11px",
color: "var(--text-weak-base, var(--vscode-descriptionForeground))",
"margin-bottom": "4px",
}}
>
{language.t("settings.agentBehaviour.permissions.effective")}
</div>
<div style={{ display: "flex", "flex-wrap": "wrap", gap: "4px" }}>
<For each={summary()}>
{([tool, action]) => {
const colors = ACTION_COLORS[action] ?? ACTION_COLORS.unknown
return (
<span
style={{
"font-size": "11px",
padding: "2px 6px",
"border-radius": "3px",
background: colors.bg,
color: colors.fg,
"font-family": "var(--vscode-editor-font-family, monospace)",
}}
>
{tool}: {action}
</span>
)
}}
</For>
</div>
</div>
</Show>
{/* Full ruleset table */}
<div
style={{
"margin-top": "8px",
"font-size": "11px",
"font-family": "var(--vscode-editor-font-family, monospace)",
"max-height": "300px",
"overflow-y": "auto",
border: "1px solid var(--border-weak-base, var(--vscode-panel-border))",
"border-radius": "4px",
}}
>
<table style={{ width: "100%", "border-collapse": "collapse" }}>
<thead>
<tr
style={{
background: "var(--bg-subtle-base, var(--vscode-editorWidget-background))",
position: "sticky",
top: "0",
}}
>
<th style={{ padding: "4px 8px", "text-align": "left", "font-weight": "600" }}>
{language.t("settings.agentBehaviour.permissions.col.tool")}
</th>
<th style={{ padding: "4px 8px", "text-align": "left", "font-weight": "600" }}>
{language.t("settings.agentBehaviour.permissions.col.pattern")}
</th>
<th style={{ padding: "4px 8px", "text-align": "left", "font-weight": "600" }}>
{language.t("settings.agentBehaviour.permissions.col.action")}
</th>
</tr>
</thead>
<tbody>
<For each={props.rules}>
{(rule, idx) => {
const colors = ACTION_COLORS[rule.action] ?? ACTION_COLORS.unknown
return (
<tr
style={{
"border-top":
idx() > 0 ? "1px solid var(--border-weak-base, var(--vscode-panel-border))" : "none",
}}
>
<td style={{ padding: "3px 8px" }}>{rule.permission}</td>
<td style={{ padding: "3px 8px", color: "var(--text-weak-base)" }}>{rule.pattern}</td>
<td style={{ padding: "3px 8px" }}>
<span
style={{
padding: "1px 4px",
"border-radius": "2px",
background: colors.bg,
color: colors.fg,
}}
>
{rule.action}
</span>
</td>
</tr>
)
}}
</For>
</tbody>
</table>
</div>
<div
style={{
"margin-top": "6px",
"font-size": "10px",
color: "var(--text-weak-base, var(--vscode-descriptionForeground))",
}}
>
{language.t("settings.agentBehaviour.permissions.hint")}
</div>
</Show>
</Card>
)
}
export default ModeEditView
@@ -134,6 +134,7 @@ interface SessionContextValue {
// Agent/mode selection (per-session)
agents: Accessor<AgentInfo[]>
allAgents: Accessor<AgentInfo[]>
removeMode: (name: string) => void
removeMcp: (name: string) => void
@@ -255,6 +256,7 @@ export const SessionProvider: ParentComponent = (props) => {
// Agents (modes) loaded from the CLI backend
const [agents, setAgents] = createSignal<AgentInfo[]>([])
const [allAgents, setAllAgents] = createSignal<AgentInfo[]>([])
const [defaultAgent, setDefaultAgent] = createSignal("code")
// Skills loaded from the CLI backend
@@ -468,6 +470,7 @@ export const SessionProvider: ParentComponent = (props) => {
return
}
setAgents(message.agents)
setAllAgents(message.allAgents ?? message.agents)
setDefaultAgent(message.defaultAgent)
const names = new Set(message.agents.map((a) => a.name))
@@ -1808,6 +1811,7 @@ export const SessionProvider: ParentComponent = (props) => {
costBreakdown,
contextUsage,
agents,
allAgents,
skills,
refreshSkills,
removeSkill,
+10
View File
@@ -1117,6 +1117,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"هذا وضع مضمن. لا يمكن تغيير تعريفه الأساسي، ولكن يمكنك تكوين التجاوزات أدناه.",
"settings.agentBehaviour.editMode.promptOverride": "تجاوز موجه مخصص لهذا الوضع المدمج",
"settings.agentBehaviour.badge.subagent": "وكيل فرعي",
"settings.agentBehaviour.permissions.title": "الأذونات المحسوبة",
"settings.agentBehaviour.permissions.count": "{{count}} قواعد",
"settings.agentBehaviour.permissions.effective": "الفعال (حرف بدل):",
"settings.agentBehaviour.permissions.col.tool": "الأداة",
"settings.agentBehaviour.permissions.col.pattern": "النمط",
"settings.agentBehaviour.permissions.col.action": "الإجراء",
"settings.agentBehaviour.permissions.copy": "نسخ الأذونات كـ JSON",
"settings.agentBehaviour.permissions.hint":
"يتم تقييم القواعد بالترتيب — القاعدة المطابقة الأخيرة هي التي تُطبق. هذه هي مجموعة القواعد المحلولة من خلفية CLI.",
"settings.agentBehaviour.removeMode.title": "إزالة الوضع",
"settings.agentBehaviour.removeMode.confirm":
'هل تريد إزالة الوضع "{{name}}"؟ سيؤدي هذا إلى تعطيل الوضع عن طريق تحديث الإعدادات.',
+10
View File
@@ -1136,6 +1136,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"Este é um modo embutido. Sua definição base não pode ser alterada, mas você pode configurar as sobrescritas abaixo.",
"settings.agentBehaviour.editMode.promptOverride": "Substituição de prompt personalizado para este modo integrado",
"settings.agentBehaviour.badge.subagent": "subagente",
"settings.agentBehaviour.permissions.title": "Permissões Calculadas",
"settings.agentBehaviour.permissions.count": "{{count}} regras",
"settings.agentBehaviour.permissions.effective": "Efetiva (curinga):",
"settings.agentBehaviour.permissions.col.tool": "Ferramenta",
"settings.agentBehaviour.permissions.col.pattern": "Padrão",
"settings.agentBehaviour.permissions.col.action": "Ação",
"settings.agentBehaviour.permissions.copy": "Copiar permissões como JSON",
"settings.agentBehaviour.permissions.hint":
"As regras são avaliadas em ordem — a última regra correspondente vence. Este é o conjunto de regras resolvido do backend da CLI.",
"settings.agentBehaviour.removeMode.title": "Remover modo",
"settings.agentBehaviour.removeMode.confirm":
'Remover o modo "{{name}}"? Isso desativará o modo atualizando sua configuração.',
+10
View File
@@ -1134,6 +1134,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"Ovo je ugrađeni način rada. Njegova osnovna definicija ne može se mijenjati, ali ispod možete konfigurirati nadjačavanja.",
"settings.agentBehaviour.editMode.promptOverride": "Prilagođeno nadjačavanje prompta za ovaj ugrađeni mod",
"settings.agentBehaviour.badge.subagent": "podagent",
"settings.agentBehaviour.permissions.title": "Izračunate dozvole",
"settings.agentBehaviour.permissions.count": "{{count}} pravila",
"settings.agentBehaviour.permissions.effective": "Efektivno (wildcard):",
"settings.agentBehaviour.permissions.col.tool": "Alat",
"settings.agentBehaviour.permissions.col.pattern": "Uzorak",
"settings.agentBehaviour.permissions.col.action": "Akcija",
"settings.agentBehaviour.permissions.copy": "Kopiraj dozvole kao JSON",
"settings.agentBehaviour.permissions.hint":
"Pravila se evaluiraju po redoslijedu — zadnje pravilo koje se podudara pobjeđuje. Ovo je riješeni skup pravila iz CLI backenda.",
"settings.agentBehaviour.removeMode.title": "Ukloni mod",
"settings.agentBehaviour.removeMode.confirm":
'Ukloniti mod "{{name}}"? Ovo će onemogućiti mod ažuriranjem vaše konfiguracije.',
+10
View File
@@ -1127,6 +1127,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"Dette er en indbygget tilstand. Dens grundlæggende definition kan ikke ændres, men du kan konfigurere tilsidesættelser nedenfor.",
"settings.agentBehaviour.editMode.promptOverride": "Tilpasset prompt-tilsidesættelse for denne indbyggede tilstand",
"settings.agentBehaviour.badge.subagent": "subagent",
"settings.agentBehaviour.permissions.title": "Beregnede tilladelser",
"settings.agentBehaviour.permissions.count": "{{count}} regler",
"settings.agentBehaviour.permissions.effective": "Gældende (wildcard):",
"settings.agentBehaviour.permissions.col.tool": "Værktøj",
"settings.agentBehaviour.permissions.col.pattern": "Mønster",
"settings.agentBehaviour.permissions.col.action": "Handling",
"settings.agentBehaviour.permissions.copy": "Kopiér tilladelser som JSON",
"settings.agentBehaviour.permissions.hint":
"Reglerne evalueres i rækkefølge — den sidst matchende regel vinder. Dette er det endelige regelsæt fra CLI-backenden.",
"settings.agentBehaviour.removeMode.title": "Fjern tilstand",
"settings.agentBehaviour.removeMode.confirm":
'Vil du fjerne tilstanden "{{name}}"? Dette vil deaktivere tilstanden ved at opdatere din konfiguration.',
+10
View File
@@ -1149,6 +1149,16 @@ export const dict = {
"Dies ist ein integrierter Modus. Seine Basisdefinition kann nicht geändert werden, aber Sie können unten Überschreibungen konfigurieren.",
"settings.agentBehaviour.editMode.promptOverride":
"Benutzerdefinierte Prompt-Überschreibung für diesen eingebauten Modus",
"settings.agentBehaviour.badge.subagent": "Subagent",
"settings.agentBehaviour.permissions.title": "Berechnete Berechtigungen",
"settings.agentBehaviour.permissions.count": "{{count}} Regeln",
"settings.agentBehaviour.permissions.effective": "Effektiv (Platzhalter):",
"settings.agentBehaviour.permissions.col.tool": "Werkzeug",
"settings.agentBehaviour.permissions.col.pattern": "Muster",
"settings.agentBehaviour.permissions.col.action": "Aktion",
"settings.agentBehaviour.permissions.copy": "Berechtigungen als JSON kopieren",
"settings.agentBehaviour.permissions.hint":
"Regeln werden der Reihe nach ausgewertet — die letzte übereinstimmende Regel gewinnt. Dies ist das aufgelöste Regelwerk vom CLI-Backend.",
"settings.agentBehaviour.removeMode.title": "Modus entfernen",
"settings.agentBehaviour.removeMode.confirm":
'Modus "{{name}}" entfernen? Dadurch wird der Modus durch Aktualisierung Ihrer Konfiguration deaktiviert.',
@@ -1178,6 +1178,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"This is a built-in mode. Its base definition cannot be changed, but you can configure overrides below.",
"settings.agentBehaviour.editMode.promptOverride": "Custom prompt override for this built-in mode",
"settings.agentBehaviour.badge.subagent": "subagent",
"settings.agentBehaviour.permissions.title": "Calculated Permissions",
"settings.agentBehaviour.permissions.count": "{{count}} rules",
"settings.agentBehaviour.permissions.effective": "Effective (wildcard):",
"settings.agentBehaviour.permissions.col.tool": "Tool",
"settings.agentBehaviour.permissions.col.pattern": "Pattern",
"settings.agentBehaviour.permissions.col.action": "Action",
"settings.agentBehaviour.permissions.copy": "Copy permissions as JSON",
"settings.agentBehaviour.permissions.hint":
"Rules are evaluated in order — last matching rule wins. This is the resolved ruleset from the CLI backend.",
"settings.autoApprove.description":
"Define how tools are allowed to run. Most tools default to Allow. doom_loop and external_directory default to Ask.",
+10
View File
@@ -1141,6 +1141,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"Este es un modo integrado. Su definición base no se puede cambiar, pero puedes configurar sobreescrituras a continuación.",
"settings.agentBehaviour.editMode.promptOverride": "Anulación de prompt personalizado para este modo integrado",
"settings.agentBehaviour.badge.subagent": "subagente",
"settings.agentBehaviour.permissions.title": "Permisos calculados",
"settings.agentBehaviour.permissions.count": "{{count}} reglas",
"settings.agentBehaviour.permissions.effective": "Efectivo (comodín):",
"settings.agentBehaviour.permissions.col.tool": "Herramienta",
"settings.agentBehaviour.permissions.col.pattern": "Patrón",
"settings.agentBehaviour.permissions.col.action": "Acción",
"settings.agentBehaviour.permissions.copy": "Copiar permisos como JSON",
"settings.agentBehaviour.permissions.hint":
"Las reglas se evalúan en orden — gana la última regla coincidente. Este es el conjunto de reglas resuelto desde el backend de la CLI.",
"settings.agentBehaviour.removeMode.title": "Eliminar modo",
"settings.agentBehaviour.removeMode.confirm":
'¿Eliminar el modo "{{name}}"? Esto desactivará el modo actualizando su configuración.',
+10
View File
@@ -1153,6 +1153,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"Il s'agit d'un mode intégré. Sa définition de base ne peut pas être modifiée, mais vous pouvez configurer des surcharges ci-dessous.",
"settings.agentBehaviour.editMode.promptOverride": "Remplacement de prompt personnalisé pour ce mode intégré",
"settings.agentBehaviour.badge.subagent": "sous-agent",
"settings.agentBehaviour.permissions.title": "Permissions calculées",
"settings.agentBehaviour.permissions.count": "{{count}} règles",
"settings.agentBehaviour.permissions.effective": "Effectif (wildcard) :",
"settings.agentBehaviour.permissions.col.tool": "Outil",
"settings.agentBehaviour.permissions.col.pattern": "Motif",
"settings.agentBehaviour.permissions.col.action": "Action",
"settings.agentBehaviour.permissions.copy": "Copier les permissions en JSON",
"settings.agentBehaviour.permissions.hint":
"Les règles sont évaluées dans l'ordre — la dernière règle correspondante l'emporte. Il s'agit de l'ensemble de règles résolu depuis le backend CLI.",
"settings.agentBehaviour.removeMode.title": "Supprimer le mode",
"settings.agentBehaviour.removeMode.confirm":
'Supprimer le mode "{{name}}" ? Cela désactivera le mode en mettant à jour votre configuration.',
+10
View File
@@ -1129,6 +1129,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"これは組み込みモードです。基本定義は変更できませんが、以下でオーバーライドを設定できます。",
"settings.agentBehaviour.editMode.promptOverride": "この組み込みモードのカスタムプロンプト上書き",
"settings.agentBehaviour.badge.subagent": "サブエージェント",
"settings.agentBehaviour.permissions.title": "計算された権限",
"settings.agentBehaviour.permissions.count": "{{count}} 個のルール",
"settings.agentBehaviour.permissions.effective": "有効(ワイルドカード):",
"settings.agentBehaviour.permissions.col.tool": "ツール",
"settings.agentBehaviour.permissions.col.pattern": "パターン",
"settings.agentBehaviour.permissions.col.action": "アクション",
"settings.agentBehaviour.permissions.copy": "権限をJSONとしてコピー",
"settings.agentBehaviour.permissions.hint":
"ルールは順番に評価され、最後に一致したルールが適用されます。これはCLIバックエンドから解決されたルールセットです。",
"settings.agentBehaviour.removeMode.title": "モードを削除",
"settings.agentBehaviour.removeMode.confirm":
'モード "{{name}}" を削除しますか?設定を更新してモードを無効にします。',
+10
View File
@@ -1123,6 +1123,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"이것은 내장 모드입니다. 기본 정의는 변경할 수 없지만, 아래에서 재정의를 구성할 수 있습니다.",
"settings.agentBehaviour.editMode.promptOverride": "이 내장 모드에 대한 사용자 지정 프롬프트 재정의",
"settings.agentBehaviour.badge.subagent": "서브에이전트",
"settings.agentBehaviour.permissions.title": "계산된 권한",
"settings.agentBehaviour.permissions.count": "{{count}}개 규칙",
"settings.agentBehaviour.permissions.effective": "유효 (와일드카드):",
"settings.agentBehaviour.permissions.col.tool": "도구",
"settings.agentBehaviour.permissions.col.pattern": "패턴",
"settings.agentBehaviour.permissions.col.action": "작업",
"settings.agentBehaviour.permissions.copy": "권한을 JSON으로 복사",
"settings.agentBehaviour.permissions.hint":
"규칙은 순서대로 평가되며, 마지막에 일치하는 규칙이 적용됩니다. 이것은 CLI 백엔드에서 확인된 규칙 세트입니다.",
"settings.agentBehaviour.removeMode.title": "모드 제거",
"settings.agentBehaviour.removeMode.confirm":
'모드 "{{name}}"을(를) 제거하시겠습니까? 구성을 업데이트하여 모드를 비활성화합니다.',
+10
View File
@@ -1382,5 +1382,15 @@ export const dict = {
"Dit is een ingebouwde modus. De basisdefinitie kan niet worden gewijzigd, maar u kunt hieronder overrides configureren.",
"settings.agentBehaviour.editMode.prompt": "Systeemprompt",
"settings.agentBehaviour.editMode.promptOverride": "Aangepaste systeemprompt voor deze ingebouwde modus",
"settings.agentBehaviour.badge.subagent": "subagent",
"settings.agentBehaviour.permissions.title": "Berekende machtigingen",
"settings.agentBehaviour.permissions.count": "{{count}} regels",
"settings.agentBehaviour.permissions.effective": "Effectief (wildcard):",
"settings.agentBehaviour.permissions.col.tool": "Tool",
"settings.agentBehaviour.permissions.col.pattern": "Patroon",
"settings.agentBehaviour.permissions.col.action": "Actie",
"settings.agentBehaviour.permissions.copy": "Machtigingen kopiëren als JSON",
"settings.agentBehaviour.permissions.hint":
"Regels worden op volgorde geëvalueerd — de laatst overeenkomende regel wint. Dit is de opgeloste regelset van de CLI backend.",
"settings.agentBehaviour.editMode.save": "Klaar",
}
+10
View File
@@ -1130,6 +1130,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"Dette er en innebygd modus. Grunndefinisjonen kan ikke endres, men du kan konfigurere overstyringer nedenfor.",
"settings.agentBehaviour.editMode.promptOverride": "Tilpasset prompt-overstyring for denne innebygde modusen",
"settings.agentBehaviour.badge.subagent": "subagent",
"settings.agentBehaviour.permissions.title": "Beregnede tillatelser",
"settings.agentBehaviour.permissions.count": "{{count}} regler",
"settings.agentBehaviour.permissions.effective": "Gjeldende (wildcard):",
"settings.agentBehaviour.permissions.col.tool": "Verktøy",
"settings.agentBehaviour.permissions.col.pattern": "Mønster",
"settings.agentBehaviour.permissions.col.action": "Handling",
"settings.agentBehaviour.permissions.copy": "Kopier tillatelser som JSON",
"settings.agentBehaviour.permissions.hint":
"Reglene evalueres i rekkefølge — siste matchende regel vinner. Dette er det gjeldende regelsettet fra CLI-backenden.",
"settings.agentBehaviour.removeMode.title": "Fjern modus",
"settings.agentBehaviour.removeMode.confirm":
'Vil du fjerne modusen "{{name}}"? Dette vil deaktivere modusen ved å oppdatere konfigurasjonen din.',
+10
View File
@@ -1133,6 +1133,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"To jest wbudowany tryb. Jego podstawowa definicja nie może zostać zmieniona, ale poniżej możesz skonfigurować nadpisania.",
"settings.agentBehaviour.editMode.promptOverride": "Niestandardowe nadpisanie promptu dla tego wbudowanego trybu",
"settings.agentBehaviour.badge.subagent": "podagent",
"settings.agentBehaviour.permissions.title": "Obliczone uprawnienia",
"settings.agentBehaviour.permissions.count": "{{count}} reguł",
"settings.agentBehaviour.permissions.effective": "Efektywne (wieloznacznik):",
"settings.agentBehaviour.permissions.col.tool": "Narzędzie",
"settings.agentBehaviour.permissions.col.pattern": "Wzorzec",
"settings.agentBehaviour.permissions.col.action": "Akcja",
"settings.agentBehaviour.permissions.copy": "Kopiuj uprawnienia jako JSON",
"settings.agentBehaviour.permissions.hint":
"Reguły są sprawdzane po kolei — ostatnia pasująca reguła wygrywa. To jest wynikowy zestaw reguł z backendu CLI.",
"settings.agentBehaviour.removeMode.title": "Usuń tryb",
"settings.agentBehaviour.removeMode.confirm":
'Usunąć tryb "{{name}}"? Spowoduje to wyłączenie trybu poprzez aktualizację konfiguracji.',
+10
View File
@@ -1136,6 +1136,16 @@ export const dict = {
"Это встроенный режим. Его базовое определение нельзя изменить, но вы можете настроить переопределения ниже.",
"settings.agentBehaviour.editMode.promptOverride":
"Пользовательское переопределение промпта для этого встроенного режима",
"settings.agentBehaviour.badge.subagent": "субагент",
"settings.agentBehaviour.permissions.title": "Вычисленные разрешения",
"settings.agentBehaviour.permissions.count": "{{count}} правил",
"settings.agentBehaviour.permissions.effective": "Действующие (wildcard):",
"settings.agentBehaviour.permissions.col.tool": "Инструмент",
"settings.agentBehaviour.permissions.col.pattern": "Шаблон",
"settings.agentBehaviour.permissions.col.action": "Действие",
"settings.agentBehaviour.permissions.copy": "Скопировать разрешения как JSON",
"settings.agentBehaviour.permissions.hint":
"Правила оцениваются по порядку — побеждает последнее совпавшее правило. Это разрешенный набор правил из бэкенда CLI.",
"settings.agentBehaviour.removeMode.title": "Удалить режим",
"settings.agentBehaviour.removeMode.confirm":
'Удалить режим "{{name}}"? Это отключит режим, обновив вашу конфигурацию.',
+10
View File
@@ -1118,6 +1118,16 @@ export const dict = {
"settings.agentBehaviour.editMode.native":
"นี่คือโหมดในตัว ไม่สามารถเปลี่ยนคำจำกัดความพื้นฐานได้ แต่คุณสามารถกำหนดค่าการแทนที่ได้ที่ด้านล่าง",
"settings.agentBehaviour.editMode.promptOverride": "การแทนที่ prompt แบบกำหนดเองสำหรับโหมดในตัวนี้",
"settings.agentBehaviour.badge.subagent": "เอเจนต์ย่อย",
"settings.agentBehaviour.permissions.title": "สิทธิ์ที่คำนวณแล้ว",
"settings.agentBehaviour.permissions.count": "{{count}} กฎ",
"settings.agentBehaviour.permissions.effective": "มีผล (ไวลด์การ์ด):",
"settings.agentBehaviour.permissions.col.tool": "เครื่องมือ",
"settings.agentBehaviour.permissions.col.pattern": "รูปแบบ",
"settings.agentBehaviour.permissions.col.action": "การดำเนินการ",
"settings.agentBehaviour.permissions.copy": "คัดลอกสิทธิ์เป็น JSON",
"settings.agentBehaviour.permissions.hint":
"กฎจะถูกประเมินตามลำดับ — กฎที่ตรงกันล่าสุดจะมีผล นี่คือชุดกฎที่ประมวลผลแล้วจากแบ็กเอนด์ CLI",
"settings.agentBehaviour.removeMode.title": "ลบโหมด",
"settings.agentBehaviour.removeMode.confirm":
'ต้องการลบโหมด "{{name}}" หรือไม่? การดำเนินการนี้จะปิดใช้งานโหมดโดยอัปเดตการกำหนดค่าของคุณ',
+10
View File
@@ -1375,5 +1375,15 @@ export const dict = {
"Bu yerleşik bir moddur. Temel tanımı değiştirilemez, ancak aşağıdan geçersiz kılmaları yapılandırabilirsiniz.",
"settings.agentBehaviour.editMode.prompt": "Sistem İstemi",
"settings.agentBehaviour.editMode.promptOverride": "Bu yerleşik mod için özel sistem istemi geçersiz kılma",
"settings.agentBehaviour.badge.subagent": "alt ajan",
"settings.agentBehaviour.permissions.title": "Hesaplanan İzinler",
"settings.agentBehaviour.permissions.count": "{{count}} kural",
"settings.agentBehaviour.permissions.effective": "Geçerli (joker karakter):",
"settings.agentBehaviour.permissions.col.tool": "Araç",
"settings.agentBehaviour.permissions.col.pattern": "Desen",
"settings.agentBehaviour.permissions.col.action": "Eylem",
"settings.agentBehaviour.permissions.copy": "İzinleri JSON olarak kopyala",
"settings.agentBehaviour.permissions.hint":
"Kurallar sırayla değerlendirilir — son eşleşen kural kazanır. Bu, CLI arka ucundan çözümlenen kural kümesidir.",
"settings.agentBehaviour.editMode.save": "Tamam",
}
+10
View File
@@ -1377,5 +1377,15 @@ export const dict = {
"settings.agentBehaviour.editMode.prompt": "Системний запит",
"settings.agentBehaviour.editMode.promptOverride":
"Власне перевизначення системного запиту для цього вбудованого режиму",
"settings.agentBehaviour.badge.subagent": "субагент",
"settings.agentBehaviour.permissions.title": "Обчислені дозволи",
"settings.agentBehaviour.permissions.count": "{{count}} правил",
"settings.agentBehaviour.permissions.effective": "Ефективні (шаблон):",
"settings.agentBehaviour.permissions.col.tool": "Інструмент",
"settings.agentBehaviour.permissions.col.pattern": "Шаблон",
"settings.agentBehaviour.permissions.col.action": "Дія",
"settings.agentBehaviour.permissions.copy": "Копіювати дозволи як JSON",
"settings.agentBehaviour.permissions.hint":
"Правила оцінюються по порядку — останнє відповідне правило має пріоритет. Це розрахований набір правил з CLI бекенду.",
"settings.agentBehaviour.editMode.save": "Готово",
}
+9
View File
@@ -1105,6 +1105,15 @@ export const dict = {
"settings.agentBehaviour.editMode.back": "返回列表",
"settings.agentBehaviour.editMode.native": "这是一个内置模式。它的基础定义无法更改,但您可以在下方配置覆盖项。",
"settings.agentBehaviour.editMode.promptOverride": "此内置模式的自定义提示覆盖",
"settings.agentBehaviour.badge.subagent": "子代理",
"settings.agentBehaviour.permissions.title": "计算出的权限",
"settings.agentBehaviour.permissions.count": "{{count}} 条规则",
"settings.agentBehaviour.permissions.effective": "生效 (通配符):",
"settings.agentBehaviour.permissions.col.tool": "工具",
"settings.agentBehaviour.permissions.col.pattern": "模式",
"settings.agentBehaviour.permissions.col.action": "操作",
"settings.agentBehaviour.permissions.copy": "复制权限为 JSON",
"settings.agentBehaviour.permissions.hint": "规则按顺序评估 — 最后匹配的规则生效。这是从 CLI 后端解析出的规则集。",
"settings.agentBehaviour.removeMode.title": "移除模式",
"settings.agentBehaviour.removeMode.confirm": '移除模式 "{{name}}" 吗?这将通过更新配置来禁用该模式。',
"settings.agentBehaviour.removeMode.button": "移除",
+10
View File
@@ -1108,6 +1108,16 @@ export const dict = {
"settings.agentBehaviour.editMode.back": "返回列表",
"settings.agentBehaviour.editMode.native": "這是一個內建模式。它的基礎定義無法更改,但您可以在下方設定覆寫項。",
"settings.agentBehaviour.editMode.promptOverride": "此內建模式的自訂提示覆寫",
"settings.agentBehaviour.badge.subagent": "子代理",
"settings.agentBehaviour.permissions.title": "已計算的權限",
"settings.agentBehaviour.permissions.count": "{{count}} 條規則",
"settings.agentBehaviour.permissions.effective": "生效(萬用字元):",
"settings.agentBehaviour.permissions.col.tool": "工具",
"settings.agentBehaviour.permissions.col.pattern": "模式",
"settings.agentBehaviour.permissions.col.action": "動作",
"settings.agentBehaviour.permissions.copy": "複製權限為 JSON",
"settings.agentBehaviour.permissions.hint":
"規則會按順序評估 — 最後符合的規則為準。這是來自 CLI 後端的已解析規則集。",
"settings.agentBehaviour.removeMode.title": "移除模式",
"settings.agentBehaviour.removeMode.confirm": '要移除模式 "{{name}}" 嗎?這將透過更新設定來停用該模式。',
"settings.agentBehaviour.removeMode.button": "移除",
@@ -163,6 +163,7 @@ export function mockSessionValue(overrides?: {
costBreakdown: () => [],
contextUsage: () => undefined,
agents: () => [{ name: "code", description: "Code mode", mode: "primary" as const }],
allAgents: () => [{ name: "code", description: "Code mode", mode: "primary" as const }],
skills: () => [],
refreshSkills: noop,
removeSkill: noop,
@@ -63,6 +63,7 @@ export const AgentBehaviourAgents: Story = {
const session = {
...mockSessionValue({ id: "agents-story", status: "idle" }),
agents: () => MOCK_AGENTS,
allAgents: () => MOCK_AGENTS,
removeMode: noop,
removeMcp: noop,
skills: () => [],
@@ -87,6 +88,7 @@ export const AgentBehaviourEditCustomMode: Story = {
const session = {
...mockSessionValue({ id: "edit-mode-story", status: "idle" }),
agents: () => MOCK_AGENTS,
allAgents: () => MOCK_AGENTS,
removeMode: noop,
removeMcp: noop,
skills: () => [],
@@ -179,6 +181,7 @@ export const AgentBehaviourWorkflows: Story = {
const session = {
...mockSessionValue({ id: "workflows-story", status: "idle" }),
agents: () => MOCK_AGENTS,
allAgents: () => MOCK_AGENTS,
removeMode: noop,
removeMcp: noop,
skills: () => [],
@@ -201,6 +204,7 @@ export const AgentBehaviourWorkflowsEmpty: Story = {
const session = {
...mockSessionValue({ id: "workflows-empty-story", status: "idle" }),
agents: () => MOCK_AGENTS,
allAgents: () => MOCK_AGENTS,
removeMode: noop,
removeMcp: noop,
skills: () => [],
@@ -298,6 +302,7 @@ export const ModeEditExport: Story = {
const session = {
...mockSessionValue({ id: "export-story", status: "idle" }),
agents: () => MOCK_AGENTS,
allAgents: () => MOCK_AGENTS,
removeMode: noop,
removeMcp: noop,
skills: () => [],
@@ -213,6 +213,13 @@ export interface SlashCommandInfo {
hints: string[]
}
// A single resolved permission rule from the CLI backend (matches PermissionNext.Rule)
export interface PermissionRuleItem {
permission: string
pattern: string
action: PermissionLevel
}
// Agent/mode info from CLI backend
export interface AgentInfo {
name: string
@@ -223,6 +230,7 @@ export interface AgentInfo {
hidden?: boolean
deprecated?: boolean
color?: string
permission?: PermissionRuleItem[]
}
// Server info
@@ -656,6 +664,7 @@ export interface ProvidersLoadedMessage {
export interface AgentsLoadedMessage {
type: "agentsLoaded"
agents: AgentInfo[]
allAgents: AgentInfo[]
defaultAgent: string
}
@@ -1044,6 +1044,7 @@ export function Prompt(props: PromptProps) {
</box>
<box
height={1}
flexShrink={0} // kilocode_change - prevent border box from shrinking in narrow terminals (#6309)
border={["left"]}
borderColor={highlight()}
customBorderChars={{
+2 -2
View File
@@ -152,7 +152,7 @@ export namespace ToolRegistry {
const usePatch =
model.modelID.includes("gpt-") && !model.modelID.includes("oss") && !model.modelID.includes("gpt-4")
if (t.id === "apply_patch") return usePatch
if (t.id === "edit" || t.id === "write") return !usePatch
if (t.id === "edit") return !usePatch
return true
})
@@ -174,4 +174,4 @@ export namespace ToolRegistry {
)
return result
}
}
}
+3
View File
@@ -9,6 +9,9 @@
".": "./src/index.ts"
},
"version": "7.2.0",
"scripts": {
"test": "bun test"
},
"dependencies": {},
"peerDependencies": {}
}
@@ -0,0 +1,596 @@
import { describe, expect, test } from "bun:test"
import path from "node:path"
const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx"])
function isExempt(file: string) {
const norm = file.replaceAll("\\", "/").toLowerCase()
return norm.split("/").some((part) => part.includes("kilocode"))
}
function isSource(file: string) {
return SOURCE_EXTS.has(path.extname(file))
}
const MARKER_PREFIX = /(?:\/\/|\{?\s*\/\*)\s*kilocode_change\b/
function hasMarker(line: string) {
return MARKER_PREFIX.test(line)
}
function coveredLines(text: string): Set<number> {
const lines = text.split(/\r?\n/)
const covered = new Set<number>()
const first = lines.find((x) => x.trim() !== "")
if (first?.match(/(?:\/\/|\{?\s*\/\*)\s*kilocode_change\s*-\s*new\s*file\b/)) {
for (let i = 1; i <= lines.length; i++) covered.add(i)
return covered
}
let block = false
for (let i = 0; i < lines.length; i++) {
const n = i + 1
const line = lines[i] ?? ""
if (line.match(/(?:\/\/|\{?\s*\/\*)\s*kilocode_change\s+start\b/)) {
block = true
covered.add(n)
continue
}
if (line.match(/(?:\/\/|\{?\s*\/\*)\s*kilocode_change\s+end\b/)) {
covered.add(n)
block = false
continue
}
if (block) {
covered.add(n)
continue
}
if (hasMarker(line)) covered.add(n)
}
return covered
}
function checkLine(line: string, covered: Set<number>, n: number): boolean {
const trim = line.trim()
if (!trim) return true
if (hasMarker(trim)) return true
return covered.has(n)
}
// ─── hasMarker tests ──────────────────────────────────────────────────────────
describe("hasMarker", () => {
const cases: Array<[string, boolean]> = [
// JS-style inline
["// kilocode_change", true],
[" // kilocode_change", true],
["const x = 1 // kilocode_change", true],
["// kilocode_change start", true],
["// kilocode_change end", true],
["// kilocode_change - new file", true],
["// kilocode_change", true],
["// kilocode_change ", true],
// JSX-style inline
["{/* kilocode_change */}", true],
[" {/* kilocode_change */}", true],
["{/* kilocode_change start */}", true],
["{/* kilocode_change end */}", true],
["{/* kilocode_change - new file */}", true],
["{/* kilocode_change - KiloNews added */}", true],
["{/* kilocode_change */}", true],
["{/* kilocode_change */}", true],
// bare /* */ style
["/* kilocode_change */", true],
[" /* kilocode_change */", true],
["/* kilocode_change start */", true],
["/* kilocode_change end */", true],
// Non-markers
["const x = 1", false],
["<text fg={color}>{label}</text>", false],
["// some other comment", false],
["{/* just a comment */}", false],
["/* something else */", false],
// typo variants — should NOT match (missing word boundary)
["// kilocode_changes", false],
["// kilocode_changelog", false],
["/* kilocode_change_log */", false],
["{/* kilocode_changes */}", false],
["// kilocode_changeable", false],
["", false],
[" ", false],
]
test.each(cases)("input %j → %j", (input, expected) => {
expect(hasMarker(input)).toBe(expected)
})
})
// ─── isExempt tests ───────────────────────────────────────────────────────────
describe("isExempt", () => {
const cases: Array<[string, boolean]> = [
// exempt — "kilocode" in path
["packages/opencode/src/kilocode/foo.ts", true],
["packages/opencode/test/kilocode/bar.test.ts", true],
["packages/opencode/src/some/kilocode/deep/path.ts", true],
["packages/opencode/src/kilocode/deep/nested/file.tsx", true],
// exempt — "kilocode" in filename
["packages/opencode/src/foo/kilocode.ts", true],
["packages/opencode/src/bar/kilocode.test.ts", true],
["packages/opencode/src/file.kilocode.ts", true],
// exempt — case-insensitive
["packages/opencode/src/KiloCode/foo.ts", true],
["packages/opencode/src/KILOCODE/bar.ts", true],
// NOT exempt
["packages/opencode/src/index.ts", false],
["packages/opencode/src/cli/cmd/tui/routes/home.tsx", false],
["packages/opencode/src/cli/cmd/tui/routes/session/index.tsx", false],
["packages/opencode/src/tool/registry.ts", false],
["packages/opencode/src/config/config.ts", false],
["packages/opencode/src/indexing/search-service.ts", false],
// kilocode_change is not the same as kilocode
["packages/opencode/src/check-opencode-annotations.ts", false],
]
test.each(cases)("%j → exempt=%j", (file, expected) => {
expect(isExempt(file)).toBe(expected)
})
})
// ─── isSource tests ───────────────────────────────────────────────────────────
describe("isSource", () => {
const cases: Array<[string, boolean]> = [
["foo.ts", true],
["foo.tsx", true],
["foo/bar.tsx", true],
["foo.js", true],
["foo.jsx", true],
[".json", false],
[".md", false],
[".txt", false],
["Makefile", false],
["foo.go", false],
["foo.rs", false],
]
test.each(cases)("%j → isSource=%j", (file, expected) => {
expect(isSource(file)).toBe(expected)
})
})
// ─── coveredLines tests ───────────────────────────────────────────────────────
describe("coveredLines", () => {
test("empty file", () => {
const covered = coveredLines("")
expect(covered.size).toBe(0)
})
test("file with only whitespace", () => {
const covered = coveredLines(" \n\n \n")
expect(covered.size).toBe(0)
})
test("whole-file JS annotation", () => {
const covered = coveredLines("// kilocode_change - new file\nexport const x = 1\nexport const y = 2")
expect(covered).toEqual(new Set([1, 2, 3]))
})
test("whole-file JSX annotation", () => {
const covered = coveredLines("{/* kilocode_change - new file */}\nexport const x = 1\nexport const y = 2")
expect(covered).toEqual(new Set([1, 2, 3]))
})
test("JS block markers", () => {
const text = [
"const a = 1",
"// kilocode_change start",
"const b = 2",
"const c = 3",
"// kilocode_change end",
"const d = 4",
].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([2, 3, 4, 5])) // block markers + content
})
test("JSX block markers", () => {
const text = [
"const a = 1",
"{/* kilocode_change start */}",
"const b = 2",
"const c = 3",
"{/* kilocode_change end */}",
"const d = 4",
].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([2, 3, 4, 5]))
})
test("mixed JS and JSX block markers (nested)", () => {
const text = [
"// kilocode_change start",
"{/* kilocode_change start */}",
"const b = 2",
"{/* kilocode_change end */}",
"// kilocode_change end",
].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([1, 2, 3, 4, 5]))
})
test("bare /* */ block markers", () => {
const text = ["/* kilocode_change start */", "const b = 2", "/* 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)
expect(covered).toEqual(new Set([2]))
})
test("inline JSX marker covers only that line", () => {
const text = ["const a = 1", "{/* kilocode_change */}", "const c = 3"].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([2]))
})
test("inline JS marker with code on same line", () => {
const text = "const url = Flag.KILO_MODELS_URL || 'https://models.dev' // kilocode_change\n"
const covered = coveredLines(text)
expect(covered).toEqual(new Set([1]))
})
test("JSX block marker with descriptive suffix", () => {
const text = [
"{/* kilocode_change start - Kilo-specific error display */}",
"<ErrorDisplay />",
"{/* kilocode_change end */}",
].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([1, 2, 3]))
})
test("multiple independent blocks", () => {
const text = [
"// kilocode_change start",
"const a = 1",
"// kilocode_change end",
"const b = 2",
"{/* kilocode_change start */}",
"const c = 3",
"{/* kilocode_change end */}",
"const d = 4",
].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([1, 2, 3, 5, 6, 7]))
})
test("marker line with extra text after marker is still covered", () => {
const text = [
"const a = 1",
"// kilocode_change start - this is kilo specific",
"const b = 2",
"// kilocode_change end",
].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([2, 3, 4]))
})
test("nested block — inner block ends, outer continues", () => {
const text = [
"// kilocode_change start",
"{/* kilocode_change start */}",
"const b = 2",
"{/* kilocode_change end */}",
"const c = 3",
"// kilocode_change end",
].join("\n")
const covered = coveredLines(text)
// Line 1: start, block=true
// Line 2: inner start, block=true (covered by block)
// Line 3: covered by block
// Line 4: inner end, block=false, covered by end marker
// Line 5: NOT covered (block is false, no inline marker)
// Line 6: outer end, block already false, covered by end marker
expect(covered).toEqual(new Set([1, 2, 3, 4, 6]))
})
test("whitespace before marker is handled", () => {
const text = [" {/* kilocode_change start */}", " const b = 2", " {/* kilocode_change end */}"].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([1, 2, 3]))
})
})
// ─── checkLine integration tests ──────────────────────────────────────────────
// Simulates what the main loop does for each added line
describe("checkLine (main loop simulation)", () => {
function check(text: string, addedLines: number[]): string[] {
const covered = coveredLines(text)
const lines = text.split(/\r?\n/)
const violations: string[] = []
for (const n of addedLines) {
const line = lines[n - 1] ?? ""
const trim = line.trim()
if (!trim) continue
if (hasMarker(trim)) continue
if (!covered.has(n)) violations.push(`line ${n}: ${trim}`)
}
return violations
}
test("covered line reports no violation", () => {
const text = ["// kilocode_change start", "const kilo = 1", "// kilocode_change end"].join("\n")
expect(check(text, [2])).toEqual([])
})
test("uncovered line reports violation", () => {
const text = ["const uncovered = 1", "const also_uncovered = 2"].join("\n")
expect(check(text, [1, 2])).toEqual(["line 1: const uncovered = 1", "line 2: const also_uncovered = 2"])
})
test("empty lines are skipped", () => {
const text = ["const x = 1", "", " ", "", "const y = 2"].join("\n")
expect(check(text, [1, 2, 3, 4, 5])).toEqual(["line 1: const x = 1", "line 5: const y = 2"])
})
test("marker lines are skipped even if uncovered", () => {
// This shouldn't normally happen, but the loop should skip it
const text = ["{/* kilocode_change */}", "{/* kilocode_change start */}"].join("\n")
expect(check(text, [1, 2])).toEqual([])
})
test("real-world TSX home.tsx pattern", () => {
const text = [
'<box width="100%" maxWidth={75}>',
" {/* kilocode_change start */}",
" <Show when={indexingOn()}>",
" <text fg={indexingColor()}>{indexingLabel()}</text>",
" </Show>",
" {/* kilocode_change end */}",
"</box>",
].join("\n")
// Only the first and last lines (opening/closing box) should be uncovered
expect(check(text, [1, 7])).toEqual([`line 1: <box width="100%" maxWidth={75}>`, `line 7: </box>`])
// Middle lines are covered
expect(check(text, [2, 3, 4, 5, 6])).toEqual([])
})
test("real-world TSX session index.tsx pattern", () => {
const text = [
"const foo = 1",
"{/* kilocode_change start */}",
'<Match when={props.part.tool === "semantic_search"}>',
"<SemanticSearch {...toolprops} />",
"</Match>",
"{/* kilocode_change end */}",
"const bar = 2",
].join("\n")
// Lines 1 and 7 are uncovered (not in any block)
expect(check(text, [1, 7])).toEqual(["line 1: const foo = 1", "line 7: const bar = 2"])
// Lines 2-6 are covered
expect(check(text, [2, 3, 4, 5, 6])).toEqual([])
})
test("real-world TSX sidebar.tsx pattern", () => {
const text = [
"<box>",
" {/* kilocode_change start */}",
" <SessionTree />",
" {/* kilocode_change end */}",
"</box>",
" {/* kilocode_change start */}",
" <div>other content</div>",
" {/* kilocode_change end */}",
].join("\n")
expect(check(text, [1, 5])).toEqual(["line 1: <box>", "line 5: </box>"])
expect(check(text, [2, 3, 4, 6, 7, 8])).toEqual([])
})
test("real-world TSX permission.tsx inline pattern", () => {
const text = [
"{/* kilocode_change */}",
"<PermissionDeniedCard />",
"{/* kilocode_change */}",
"<AnotherKiloComponent />",
].join("\n")
expect(check(text, [2, 4])).toEqual(["line 2: <PermissionDeniedCard />", "line 4: <AnotherKiloComponent />"])
expect(check(text, [1, 3])).toEqual([])
})
test("JS-style session/index.tsx pattern (from existing codebase)", () => {
const text = ["const foo = 1", "<Toast />", "{/* kilocode_change */}", "<Footer />", "</box>"].join("\n")
// Line 2 (<Toast />) is NOT covered — it's between <Toast /> and the marker
expect(check(text, [2, 4])).toEqual(["line 2: <Toast />", "line 4: <Footer />"])
expect(check(text, [3])).toEqual([])
})
test("whole-file annotated file — no violations even for unmarked lines", () => {
const text = [
"// kilocode_change - new file",
"export const kiloFeature = true",
"export const alsoKilo = 123",
"export const notMarked = 'oops'",
].join("\n")
expect(check(text, [2, 3, 4])).toEqual([])
})
})
// ─── Regex edge cases ─────────────────────────────────────────────────────────
describe("MARKER_PREFIX regex edge cases", () => {
test("handles { followed immediately by /*", () => {
expect(hasMarker("{/* kilocode_change */}")).toBe(true)
})
test("handles { followed by whitespace then /*", () => {
expect(hasMarker("{ /* kilocode_change */}")).toBe(true)
})
test("handles just /* with no brace", () => {
expect(hasMarker("/* kilocode_change */")).toBe(true)
})
test("handles // with no spaces", () => {
expect(hasMarker("//kilocode_change")).toBe(true)
})
test("handles // with lots of spaces", () => {
expect(hasMarker("// kilocode_change")).toBe(true)
})
test("does not match {/* without kilocode_change", () => {
expect(hasMarker("{/* some other comment */}")).toBe(false)
})
test("does not match /* without kilocode_change", () => {
expect(hasMarker("/* just a comment */")).toBe(false)
})
test("does not match kilocode_changes (word boundary)", () => {
expect(hasMarker("// kilocode_changes")).toBe(false)
expect(hasMarker("// kilocode_changelog")).toBe(false)
expect(hasMarker("{/* kilocode_changes */}")).toBe(false)
expect(hasMarker("// kilocode_changeable")).toBe(false)
})
})
// ─── isExempt — Windows paths ─────────────────────────────────────────────────
describe("isExempt — Windows backslash paths", () => {
test("Windows paths with backslashes", () => {
expect(isExempt("packages\\opencode\\src\\kilocode\\foo.ts")).toBe(true)
expect(isExempt("packages\\opencode\\test\\kilocode\\bar.test.ts")).toBe(true)
expect(isExempt("packages\\opencode\\src\\index.ts")).toBe(false)
})
})
// ─── coveredLines — additional patterns ───────────────────────────────────────
describe("coveredLines — additional patterns", () => {
test("block with descriptive suffix is still recognized", () => {
const text = [
"{/* kilocode_change start - Kilo-specific indexing display */}",
"<IndexingStatus />",
"{/* kilocode_change end */}",
].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([1, 2, 3]))
})
test("empty file content", () => {
const covered = coveredLines("// kilocode_change start\n \n// kilocode_change end")
expect(covered).toEqual(new Set([1, 2, 3]))
})
test("multiple separate JS inline markers", () => {
const text = [
"const a = 1 // kilocode_change",
"const b = 2",
"const c = 3 // kilocode_change",
"const d = 4",
].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([1, 3]))
})
test("consecutive block markers (no content)", () => {
const text = ["// kilocode_change start", "// kilocode_change end"].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([1, 2]))
})
test("block immediately followed by another start", () => {
const text = [
"// kilocode_change start",
"const a = 1",
"// kilocode_change end",
"{/* kilocode_change start */}",
"const b = 2",
"{/* kilocode_change end */}",
].join("\n")
const covered = coveredLines(text)
expect(covered).toEqual(new Set([1, 2, 3, 4, 5, 6]))
})
test("trailing empty line after block end is not covered", () => {
const text = "// kilocode_change start\nconst a = 1\n// kilocode_change end\n\n"
const covered = coveredLines(text)
// Block ends at line 3; trailing empty line 4 is outside the block
expect(covered).toEqual(new Set([1, 2, 3]))
})
})
// ─── checkLine — additional patterns ─────────────────────────────────────────
describe("checkLine — additional patterns", () => {
function check(text: string, addedLines: number[]): string[] {
const covered = coveredLines(text)
const lines = text.split(/\r?\n/)
const violations: string[] = []
for (const n of addedLines) {
const line = lines[n - 1] ?? ""
const trim = line.trim()
if (!trim) continue
if (hasMarker(trim)) continue
if (!covered.has(n)) violations.push(`line ${n}: ${trim}`)
}
return violations
}
test("real-world dialog-status.tsx pattern — multiple inline blocks", () => {
// Based on actual file: packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx
const text = [
"{/* kilocode_change start */}",
"<KiloDialog>",
"{/* kilocode_change end */}",
"const normal = 1",
" {/* kilocode_change start */}",
" <KiloDialog />",
" {/* kilocode_change end */}",
].join("\n")
// Lines 4 is uncovered
expect(check(text, [4])).toEqual(["line 4: const normal = 1"])
// Lines 1-3 and 5-7 are covered
expect(check(text, [1, 2, 3, 5, 6, 7])).toEqual([])
})
test("real-world TUI routes — line between marker and code should be uncovered", () => {
// A common mistake: putting code on a different line from the marker
const text = ["{/* kilocode_change start */}", "", "<KiloIndexing />", "", "{/* kilocode_change end */}"].join("\n")
// Empty lines (2, 4) are skipped
expect(check(text, [3])).toEqual([])
// All non-empty lines (1, 3, 5) are covered
expect(check(text, [1, 3, 5])).toEqual([])
})
test("end marker on same line as content is covered", () => {
const text = "const a = 1\n{/* kilocode_change end */} // block already closed, still covered\n"
const covered = coveredLines(text)
expect(covered).toEqual(new Set([2]))
})
test("end marker closes block correctly", () => {
const text = [
"// kilocode_change start",
"const a = 1",
"// kilocode_change end",
"const b = 2", // uncovered
].join("\n")
expect(check(text, [1, 2, 3, 4])).toEqual(["line 4: const b = 2"])
})
})
+25 -10
View File
@@ -9,12 +9,14 @@
* bun run script/check-opencode-annotations.ts --base <ref> # diff against <ref>
*
* A line is "covered" if it:
* - contains // kilocode_change (inline annotation)
* - falls inside a // kilocode_change start/end block (block annotation)
* - is in a file whose first non-empty line is (whole-file annotation)
* - contains a kilocode_change marker comment (inline annotation)
* - falls inside a kilocode_change start/end block (block annotation)
* - is in a file whose first non-empty line is (whole-file annotation)
* // kilocode_change - new file
* - is empty / whitespace-only (skipped)
* - is itself a marker line (auto-covered)
* - is empty / whitespace-only (skipped)
* - is itself a marker line (auto-covered)
*
* Both JS (//) and JSX ({/ * ... * /}) comment styles are recognized.
*
* Exempt paths (no markers needed — entirely Kilo-specific):
* - packages/opencode/src/kilocode/**
@@ -65,13 +67,20 @@ function addedLines(file: string): Set<number> {
return out
}
// Matches the start of a kilocode_change marker in both JS (//) and JSX ({/* */}) comments
const MARKER_PREFIX = /(?:\/\/|\{?\s*\/\*)\s*kilocode_change\b/
function hasMarker(line: string) {
return MARKER_PREFIX.test(line)
}
function coveredLines(text: string): { lines: string[]; covered: Set<number> } {
const lines = text.split(/\r?\n/)
const covered = new Set<number>()
// Whole-file annotation: first non-empty line is "// kilocode_change - new file"
const first = lines.find((x) => x.trim() !== "")
if (first?.match(/\/\/\s*kilocode_change\s*-\s*new\s*file\b/)) {
if (first?.match(/(?:\/\/|\{?\s*\/\*)\s*kilocode_change\s*-\s*new\s*file\b/)) {
for (let i = 1; i <= lines.length; i++) covered.add(i)
return { lines, covered }
}
@@ -81,13 +90,13 @@ function coveredLines(text: string): { lines: string[]; covered: Set<number> } {
const n = i + 1
const line = lines[i] ?? ""
if (line.match(/\/\/\s*kilocode_change\s+start\b/)) {
if (line.match(/(?:\/\/|\{?\s*\/\*)\s*kilocode_change\s+start\b/)) {
block = true
covered.add(n)
continue
}
if (line.match(/\/\/\s*kilocode_change\s+end\b/)) {
if (line.match(/(?:\/\/|\{?\s*\/\*)\s*kilocode_change\s+end\b/)) {
covered.add(n)
block = false
continue
@@ -98,7 +107,7 @@ function coveredLines(text: string): { lines: string[]; covered: Set<number> } {
continue
}
if (line.match(/\/\/\s*kilocode_change\b/)) covered.add(n)
if (hasMarker(line)) covered.add(n)
}
return { lines, covered }
@@ -127,7 +136,7 @@ for (const file of files) {
const line = lines[n - 1] ?? ""
const trim = line.trim()
if (!trim) continue
if (trim.match(/\/\/\s*kilocode_change\b/)) continue
if (hasMarker(trim)) continue
if (!covered.has(n)) violations.push(` ${file}:${n}: ${trim}`)
}
}
@@ -153,6 +162,12 @@ console.error(
" ...",
" // kilocode_change end",
"",
"JSX/TSX (inside JSX templates):",
" {/* kilocode_change */}",
" {/* kilocode_change start */}",
" ...",
" {/* kilocode_change end */}",
"",
"New file:",
" // kilocode_change - new file",
"",