diff --git a/.changeset/fresh-rules-list.md b/.changeset/fresh-rules-list.md
new file mode 100644
index 00000000000..0266967dcc1
--- /dev/null
+++ b/.changeset/fresh-rules-list.md
@@ -0,0 +1,5 @@
+---
+"@kilocode/cli": patch
+---
+
+Add `/rules` in the CLI to list rule files loaded for the current session.
diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/app.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/app.tsx
index 92873540006..56d90f74619 100644
--- a/packages/opencode/src/kilocode/cli/cmd/tui/app.tsx
+++ b/packages/opencode/src/kilocode/cli/cmd/tui/app.tsx
@@ -19,6 +19,7 @@ import { useTheme } from "@tui/context/theme"
import { DialogAlert } from "@tui/ui/dialog-alert"
import { DialogSelect } from "@tui/ui/dialog-select"
import { Link } from "@tui/ui/link"
+import { DialogRules } from "./component/dialog-rules"
import { isKiloError, showKiloErrorToast } from "@/kilocode/kilo-errors"
import { registerKiloCommands } from "@/kilocode/kilo-commands"
import { initializeTUIDependencies } from "@kilocode/kilo-gateway/tui"
@@ -154,6 +155,17 @@ export function init() {
// Register auto-approve toggle
command.register(() => [
+ {
+ title: "Rules",
+ value: "rules.list",
+ category: "System",
+ slash: {
+ name: "rules",
+ },
+ onSelect: (dialog) => {
+ dialog.replace(() => )
+ },
+ },
{
get title() {
return isAllowEverything(sync.data.config.permission) ? "Disable auto-approve mode" : "Enable auto-approve mode"
diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-rules.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-rules.tsx
new file mode 100644
index 00000000000..e309e2b6da9
--- /dev/null
+++ b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-rules.tsx
@@ -0,0 +1,29 @@
+// kilocode_change - new file
+import { createMemo, createResource } from "solid-js"
+import { useProject } from "@tui/context/project"
+import { useSDK } from "@tui/context/sdk"
+import { useDialog } from "@tui/ui/dialog"
+import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
+
+export function DialogRules() {
+ const project = useProject()
+ const sdk = useSDK()
+ const dialog = useDialog()
+ dialog.setSize("large")
+
+ const [rules] = createResource(async () => {
+ const result = await sdk.client.kilocode.rules({ workspace: project.workspace.current() }, { throwOnError: true })
+ return result.data ?? []
+ })
+
+ const options = createMemo[]>(() =>
+ (rules() ?? []).map((rule) => ({
+ title: rule.name,
+ description: rule.path,
+ value: rule.path,
+ category: "Rules",
+ })),
+ )
+
+ return
+}
diff --git a/packages/opencode/src/kilocode/rules.ts b/packages/opencode/src/kilocode/rules.ts
new file mode 100644
index 00000000000..97533c23ef5
--- /dev/null
+++ b/packages/opencode/src/kilocode/rules.ts
@@ -0,0 +1,24 @@
+// kilocode_change - new file
+import path from "path"
+import { Effect } from "effect"
+import z from "zod"
+import { Instruction } from "@/session/instruction"
+
+export namespace KiloRules {
+ export const Info = z.object({
+ path: z.string(),
+ name: z.string(),
+ })
+ export type Info = z.infer
+
+ export const list = Effect.fn("KiloRules.list")(function* () {
+ const instruction = yield* Instruction.Service
+ const paths = yield* instruction.systemPaths()
+ return Array.from(paths)
+ .map((file) => ({
+ path: file,
+ name: path.basename(file),
+ }))
+ .toSorted((a, b) => a.path.localeCompare(b.path))
+ })
+}
diff --git a/packages/opencode/src/server/routes/instance/kilocode.ts b/packages/opencode/src/server/routes/instance/kilocode.ts
index ebfacf8980a..c7b99774e31 100644
--- a/packages/opencode/src/server/routes/instance/kilocode.ts
+++ b/packages/opencode/src/server/routes/instance/kilocode.ts
@@ -6,14 +6,38 @@ import { describeRoute, validator, resolver } from "hono-openapi"
import z from "zod"
import { Skill } from "@/skill"
import { Agent } from "@/agent/agent"
+import { KiloRules } from "@/kilocode/rules"
import { lazy } from "@/util/lazy"
import { errors } from "../../error"
+import { jsonRequest } from "./trace"
import { SessionImportRoutes } from "@/kilocode/session-import/routes"
import { HeapSnapshot } from "@/kilocode/cli/heap-snapshot"
export const KilocodeRoutes = lazy(() =>
new Hono()
.route("/session-import", SessionImportRoutes())
+ .get(
+ "/rules",
+ describeRoute({
+ summary: "List loaded rules",
+ description: "List local rule and instruction files currently loaded for this session.",
+ operationId: "kilocode.rules",
+ responses: {
+ 200: {
+ description: "Loaded rules",
+ content: {
+ "application/json": {
+ schema: resolver(KiloRules.Info.array()),
+ },
+ },
+ },
+ },
+ }),
+ async (c) =>
+ jsonRequest("KilocodeRoutes.rules", c, function* () {
+ return yield* KiloRules.list()
+ }),
+ )
.post(
"/heap/snapshot",
describeRoute({
diff --git a/packages/opencode/test/kilocode/rules.test.ts b/packages/opencode/test/kilocode/rules.test.ts
new file mode 100644
index 00000000000..b70136661c3
--- /dev/null
+++ b/packages/opencode/test/kilocode/rules.test.ts
@@ -0,0 +1,74 @@
+import { afterEach, describe, expect, test } from "bun:test"
+import * as fs from "fs/promises"
+import path from "path"
+import { Effect } from "effect"
+import { Global } from "../../src/global"
+import { KiloRules } from "../../src/kilocode/rules"
+import { Instance } from "../../src/project/instance"
+import { Instruction } from "../../src/session/instruction"
+import { tmpdir } from "../fixture/fixture"
+
+const run = (effect: Effect.Effect) =>
+ Effect.runPromise(effect.pipe(Effect.provide(Instruction.defaultLayer)))
+
+afterEach(async () => {
+ await Instance.disposeAll()
+})
+
+describe("KiloRules.list", () => {
+ test("lists loaded project, global, and configured rule files", async () => {
+ const originalConfigDir = process.env["KILO_CONFIG_DIR"]
+ delete process.env["KILO_CONFIG_DIR"]
+
+ await using globalTmp = await tmpdir({
+ init: async (dir) => {
+ await Bun.write(path.join(dir, "AGENTS.md"), "# Global Instructions")
+ },
+ })
+ await using projectTmp = await tmpdir({
+ config: { instructions: [".kilo/rules/style.md"] },
+ init: async (dir) => {
+ await Bun.write(path.join(dir, "AGENTS.md"), "# Project Instructions")
+ await fs.mkdir(path.join(dir, ".kilo", "rules"), { recursive: true })
+ await Bun.write(path.join(dir, ".kilo", "rules", "style.md"), "# Style Rules")
+ },
+ })
+
+ const originalGlobalConfig = Global.Path.config
+ ;(Global.Path as { config: string }).config = globalTmp.path
+
+ try {
+ await Instance.provide({
+ directory: projectTmp.path,
+ fn: () =>
+ run(
+ Effect.gen(function* () {
+ const rules = yield* KiloRules.list()
+ const paths = rules.map((rule) => rule.path)
+
+ expect(rules).toContainEqual({
+ path: path.join(projectTmp.path, ".kilo", "rules", "style.md"),
+ name: "style.md",
+ })
+ expect(rules).toContainEqual({
+ path: path.join(projectTmp.path, "AGENTS.md"),
+ name: "AGENTS.md",
+ })
+ expect(rules).toContainEqual({
+ path: path.join(globalTmp.path, "AGENTS.md"),
+ name: "AGENTS.md",
+ })
+ expect(paths).toEqual(paths.toSorted())
+ }),
+ ),
+ })
+ } finally {
+ ;(Global.Path as { config: string }).config = originalGlobalConfig
+ if (originalConfigDir === undefined) {
+ delete process.env["KILO_CONFIG_DIR"]
+ } else {
+ process.env["KILO_CONFIG_DIR"] = originalConfigDir
+ }
+ }
+ })
+})
diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts
index d9d3bac64e4..3fa764bd350 100644
--- a/packages/sdk/js/src/v2/gen/sdk.gen.ts
+++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts
@@ -75,6 +75,7 @@ import type {
KilocodeRemoveAgentResponses,
KilocodeRemoveSkillErrors,
KilocodeRemoveSkillResponses,
+ KilocodeRulesResponses,
KilocodeSessionImportMessageErrors,
KilocodeSessionImportMessageResponses,
KilocodeSessionImportPartErrors,
@@ -5506,6 +5507,36 @@ export class Heap extends HeyApiClient {
}
export class Kilocode extends HeyApiClient {
+ /**
+ * List loaded rules
+ *
+ * List local rule and instruction files currently loaded for this session.
+ */
+ public rules(
+ parameters?: {
+ directory?: string
+ workspace?: string
+ },
+ options?: Options,
+ ) {
+ const params = buildClientParams(
+ [parameters],
+ [
+ {
+ args: [
+ { in: "query", key: "directory" },
+ { in: "query", key: "workspace" },
+ ],
+ },
+ ],
+ )
+ return (options?.client ?? this.client).get({
+ url: "/kilocode/rules",
+ ...options,
+ ...params,
+ })
+ }
+
/**
* Remove a skill
*
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index b9d92659382..ef3c1ff5a6a 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -6839,6 +6839,28 @@ export type KilocodeSessionImportPartResponses = {
export type KilocodeSessionImportPartResponse =
KilocodeSessionImportPartResponses[keyof KilocodeSessionImportPartResponses]
+export type KilocodeRulesData = {
+ body?: never
+ path?: never
+ query?: {
+ directory?: string
+ workspace?: string
+ }
+ url: "/kilocode/rules"
+}
+
+export type KilocodeRulesResponses = {
+ /**
+ * Loaded rules
+ */
+ 200: Array<{
+ path: string
+ name: string
+ }>
+}
+
+export type KilocodeRulesResponse = KilocodeRulesResponses[keyof KilocodeRulesResponses]
+
export type KilocodeHeapSnapshotData = {
body?: never
path?: never
diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json
index 75779e397fa..1f4c10cfe11 100644
--- a/packages/sdk/openapi.json
+++ b/packages/sdk/openapi.json
@@ -9649,6 +9649,59 @@
]
}
},
+ "/kilocode/rules": {
+ "get": {
+ "operationId": "kilocode.rules",
+ "parameters": [
+ {
+ "in": "query",
+ "name": "directory",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "in": "query",
+ "name": "workspace",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "summary": "List loaded rules",
+ "description": "List local rule and instruction files currently loaded for this session.",
+ "responses": {
+ "200": {
+ "description": "Loaded rules",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": ["path", "name"]
+ }
+ }
+ }
+ }
+ }
+ },
+ "x-codeSamples": [
+ {
+ "lang": "js",
+ "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.kilocode.rules({\n ...\n})"
+ }
+ ]
+ }
+ },
"/kilocode/heap/snapshot": {
"post": {
"operationId": "kilocode.heap.snapshot",