diff --git a/docs/configuration.md b/docs/configuration.md index 2c7d83de432..8a85f80c1a4 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -565,10 +565,13 @@ runs. When both files exist, `/AGENTS.md` is appended first and `/.bb/AGENTS.md` second. An empty or whitespace-only file is treated as absent. -No agent loads `.bb/AGENTS.md` natively, and provider-native instruction files -(`CLAUDE.md` for Claude Code, a repo-root `AGENTS.md` for Codex) remain -provider-specific. bb reads the files above itself and injects them, so use them -for guidance you want every bb thread to receive regardless of provider. +No agent loads `.bb/AGENTS.md` natively. Provider-native instruction files +remain separate. Codex reads a repo-root `AGENTS.md`. Claude Code 2.1.277 and +later also reads `AGENTS.md` when no project or ancestor `CLAUDE.md` or +`CLAUDE.local.md` takes precedence. Older Claude Code versions and sessions +without its built-in `AGENTS.md` support still require `CLAUDE.md`. bb reads +the files above itself and injects them, so use them for guidance you want every +bb thread to receive regardless of provider. ## Skills diff --git a/packages/templates/src/templates/bb-guide-agent-configuration.md b/packages/templates/src/templates/bb-guide-agent-configuration.md index 88b2109490d..5fa192c67f0 100644 --- a/packages/templates/src/templates/bb-guide-agent-configuration.md +++ b/packages/templates/src/templates/bb-guide-agent-configuration.md @@ -28,8 +28,11 @@ Workspace instructions (.bb/AGENTS.md): Only the plural AGENTS.md is read, only from the exact data-dir and workspace-root .bb/ locations above (bb does not walk parent directories), and an empty file is ignored. This is bb's own provider-agnostic instruction - injection, separate from provider-native files such as CLAUDE.md or a - repo-root AGENTS.md. + injection, separate from provider-native instruction files. Codex reads a + repo-root AGENTS.md. Claude Code 2.1.277 and later also reads AGENTS.md when + no project or ancestor CLAUDE.md or CLAUDE.local.md takes precedence. Older + Claude Code versions and sessions without its built-in AGENTS.md support + still require CLAUDE.md. Skills (.bb/skills/): diff --git a/plugins/provider-claude-code/PLUGIN_OVERVIEW.md b/plugins/provider-claude-code/PLUGIN_OVERVIEW.md index 47fef10382b..b3618e00b63 100644 --- a/plugins/provider-claude-code/PLUGIN_OVERVIEW.md +++ b/plugins/provider-claude-code/PLUGIN_OVERVIEW.md @@ -5,7 +5,8 @@ Start a thread, pick Claude Code, and let it work in your repository from bb. Th - Permission modes `accept-edits`, `auto`, and `full`, plus a plan action in the composer. - Reasoning levels from Low to Max, plus Ultracode, which turns on multi-agent workflow orchestration. - Checkpoint forks, manual compaction, and native questions from the agent. -- Claude Code skills and CLAUDE.md files from your home directory and project. +- Claude Code skills and provider-native CLAUDE.md or supported AGENTS.md files + from your home directory and project. - Health, usage, and install status for Claude Code on each host, with an install or update action. ## Settings diff --git a/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts b/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts index 7a635befa00..9084135f7aa 100644 --- a/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts +++ b/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts @@ -1788,6 +1788,58 @@ describe("bridge", () => { } }); + it("translates tagged dollar skill mentions without changing plain dollar text", async () => { + const bridge = createBridgeJsonRpcTestHarness(handleLine); + const queries: ControlledClaudeQuery[] = []; + queryMock.mockImplementation(() => { + const query = createControlledClaudeQuery(); + queries.push(query); + return query; + }); + + try { + const threadId = "thread-dollar-skill"; + await startBridgeThread({ bridge, threadId }); + const call = getLatestQueryCall(); + bridge.sendRequest( + 2, + "turn/start", + canonicalTurnParams({ + threadId, + input: [ + { + type: "text", + text: "Use $review but keep $PATH and $review", + mentions: [ + { + start: 4, + end: 11, + resource: { + kind: "command", + trigger: "$", + name: "review", + source: "skill", + origin: "user", + label: "review", + argumentHint: null, + }, + }, + ], + }, + ], + }), + ); + + expect(await readNextPromptText(call)).toBe( + "Use /review but keep $PATH and $review", + ); + await bridge.waitForResponse(2); + await stopBridgeThread({ bridge, queries, threadId }); + } finally { + bridge.restore(); + } + }); + it("switches a live session into Plan mode when a later turn carries /plan", async () => { const bridge = createBridgeJsonRpcTestHarness(handleLine); const queries: ControlledClaudeQuery[] = []; diff --git a/plugins/provider-claude-code/src/bridge/bridge.ts b/plugins/provider-claude-code/src/bridge/bridge.ts index 6763f322782..4dfa610bd1b 100644 --- a/plugins/provider-claude-code/src/bridge/bridge.ts +++ b/plugins/provider-claude-code/src/bridge/bridge.ts @@ -116,6 +116,22 @@ const promptInputItemSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal("text"), text: z.string(), + mentions: z + .array( + z.object({ + start: z.number().int().nonnegative(), + end: z.number().int().nonnegative(), + resource: z + .object({ + kind: z.string(), + trigger: z.string().optional(), + name: z.string().optional(), + source: z.string().optional(), + }) + .passthrough(), + }), + ) + .default([]), }), z.object({ type: z.literal("image"), @@ -2359,6 +2375,31 @@ function localAttachmentMarker(args: { return `[Attached ${args.kind}${namePart}${suffix}. It is on disk at ${args.path} — use the Read tool to view it.]`; } +function normalizeClaudeSkillMentions( + entry: Extract, { type: "text" }>, +): string { + const replacements = new Set(); + for (const mention of entry.mentions) { + const resource = mention.resource; + if ( + resource.kind === "command" && + resource.source === "skill" && + resource.trigger === "$" && + typeof resource.name === "string" && + mention.end <= entry.text.length && + entry.text.slice(mention.start, mention.end) === `$${resource.name}` + ) { + replacements.add(mention.start); + } + } + + let normalized = entry.text; + for (const start of replacements) { + normalized = `${normalized.slice(0, start)}/${normalized.slice(start + 1)}`; + } + return normalized; +} + function buildPromptText(input: unknown): string | undefined { if (typeof input === "string") { return input.length > 0 ? input : undefined; @@ -2372,7 +2413,8 @@ function buildPromptText(input: unknown): string | undefined { const entry = parsed.data; switch (entry.type) { case "text": - if (entry.text.length > 0) chunks.push(entry.text); + if (entry.text.length > 0) + chunks.push(normalizeClaudeSkillMentions(entry)); break; case "image": chunks.push(`[Attached image: ${entry.url}]`);