Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# bb plugins

Four bb plugins I use for product design work, kept together with the few build and repository tools they share. [![CI](https://github.com/brsbl/bb-plugins/actions/workflows/ci.yml/badge.svg)](https://github.com/brsbl/bb-plugins/actions/workflows/ci.yml)
Five bb plugins I use for product design work, kept together with the few build and repository tools they share. [![CI](https://github.com/brsbl/bb-plugins/actions/workflows/ci.yml/badge.svg)](https://github.com/brsbl/bb-plugins/actions/workflows/ci.yml)

[bb](https://getbb.app) is an agentic IDE for running coding agents across projects, threads, and environments. Its plugins can add UI, commands, skills, and server capabilities; this repository is where I build and maintain mine.

Expand Down Expand Up @@ -38,6 +38,16 @@ Lets you peek at a thread's status and repository context without leaving the si

Install: `bb plugin install git:https://github.com/brsbl/bb-plugins.git@plugin/thread-hover-cards --yes`

### Thread Organizer

Files new threads into the right existing work section while preserving native titles and every manual override.

![Thread Organizer settings in bb](plugins/thread-organizer/docs/screenshot.png)

[Source](plugins/thread-organizer) · [README](plugins/thread-organizer/README.md)

Install: `bb plugin install git:https://github.com/brsbl/bb-plugins.git@plugin/thread-organizer --yes`

### UI Patterns

Puts proven UI components and interaction guidance within reach of both designers and agents.
Expand Down
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions plugins/thread-organizer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Thread Organizer

Thread Organizer files new bb threads into existing work sections while leaving manual organization in control. It starts in observe mode and never creates sections, backfills old threads, or runs a hidden classification agent.

![Thread Organizer settings in bb](docs/screenshot.png)

## Install

```bash
bb plugin install git:https://github.com/brsbl/bb-plugins.git@plugin/thread-organizer --yes
```

## Use

Leave the plugin in its default `observe` mode first and inspect its decisions:

```bash
bb plugin logs thread-organizer -f
```

When the proposals look right, enable changes:

```bash
bb plugin config thread-organizer set mode apply
```

The setting takes effect immediately.

| Existing section | Automatic use |
| --- | --- |
| `bb` or `bb quick fixes` | bb engineering work |
| `Extensions` | Plugins, skills, automations, and agent tooling |
| `Design` | Design systems, UI patterns, information architecture, and API-surface work |
| `Writing` | Articles, positioning, copy, and editorial work |
| `QA` | Never; reserved for manual phase management |

Threads that do not clear the confidence threshold stay unsectioned. Pinning remains independent priority state, and archiving remains completion state.

## Behavior

| Moment | Evaluation |
| --- | --- |
| Creation | Uses project identity and the prompt-derived title fallback for an early section proposal |
| Activation | Retries prompt history briefly when the initial prompt was not yet available |
| First completed turn | Refines the section and repairs a still-missing title |
| Later turns | Re-evaluates at turns 5, 15, 25, and every ten completed turns thereafter |

New placements require at least `0.85` confidence with a `0.20` lead over the next rule. Moving a plugin-managed thread requires `0.92` confidence, a `0.25` lead, and the same result at two consecutive scheduled evaluations.

Only visible, ordinary user root threads created while the plugin is loaded are tracked. Hidden workers, children, forks, side chats, plugin-originated threads, archived threads, deleted threads, and terminal error states are excluded.

An explicit creation-time title or section is locked. After the plugin writes a field, any different value is treated as a manual or external override and locks that field permanently. Native bb titles therefore win, and section changes are never cleared automatically.

## Develop

From the monorepo root:

```bash
npm ci
npm run check --workspace=bb-plugin-thread-organizer
bb plugin install "path:$PWD/plugins/thread-organizer" --yes
```
206 changes: 206 additions & 0 deletions plugins/thread-organizer/core.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { describe, expect, it } from "vitest";

import {
advanceEvaluationMilestone,
classifySection,
deriveTaskTitle,
isEligibleThread,
resolveSectionId,
} from "./core.js";

describe("thread eligibility", () => {
const eligible = {
archivedAt: null,
childOrigin: null,
deletedAt: null,
originKind: null,
originPluginId: null,
parentThreadId: null,
sourceThreadId: null,
status: "idle" as const,
visibility: "visible" as const,
};

it("accepts ordinary root threads, including pinned threads by implication", () => {
expect(isEligibleThread(eligible)).toBe(true);
});

it.each([
{ ...eligible, archivedAt: 1 },
{ ...eligible, deletedAt: 1 },
{ ...eligible, parentThreadId: "thr_parent" },
{ ...eligible, sourceThreadId: "thr_source" },
{ ...eligible, originKind: "side-chat" as const },
{ ...eligible, childOrigin: "fork" as const },
{ ...eligible, originPluginId: "automations" },
{ ...eligible, status: "error" as const },
{ ...eligible, visibility: "hidden" as const },
])("rejects protected thread shape %#", (thread) => {
expect(isEligibleThread(thread)).toBe(false);
});
});

describe("section classification", () => {
it.each([
{
expected: "bb",
projectName: "bb",
texts: ["Review the recent plugin API changes and harden them."],
title: "Plugin API review and hardening",
},
{
expected: "bb",
projectName: "bb",
texts: ["can you make a prod build from latest origin/main?"],
title: "bb integration branch",
},
{
expected: "extensions",
projectName: "Personal",
texts: [
"Create a bb plugin that automatically renames and reorganizes threads.",
],
title: "Auto Rename and Reorganize",
},
{
expected: "extensions",
projectName: "bb plugins",
texts: ["Design and implement the Omegacode plugin workflow overview."],
title: "Omegacode workflow overview design pass",
},
{
expected: "extensions",
projectName: "Design Doctrine",
texts: ["Rewrite the doctrine docs."],
title: "Rewrite doctrine docs",
},
{
expected: "design",
projectName: "UI Pattern Atlas",
texts: ["Take over the UI Patterns work."],
title: "atlas",
},
{
expected: "design",
projectName: "moss",
texts: ["Continue the design↔code system work."],
title: "design↔code",
},
{
expected: "design",
projectName: "moss",
texts: ["Spec the right API surface for the Moss agent."],
title: "Moss Agent API Surface",
},
{
expected: "writing",
projectName: "moss",
texts: ["Help me work on positioning for Moss."],
title: "moss positioning",
},
{
expected: "writing",
projectName: "Personal",
texts: ["Write a blog post about creating loops."],
title: "Creating Loops Blog Post",
},
{
expected: "bb",
projectName: "bb",
texts: ["https://github.com/ymichael/bb/issues/603"],
title: "Issue 603",
},
])("classifies $title as $expected", ({ expected, projectName, texts }) => {
expect(classifySection({ projectName, texts })?.target).toBe(expected);
});

it("abstains from weak personal-workspace intent", () => {
expect(
classifySection({
projectName: "Personal",
texts: ["clean up disk space and CPU"],
}),
).toBeNull();
});

it("surfaces a low margin instead of hiding mixed bb design intent", () => {
const decision = classifySection({
projectName: "bb",
texts: ["Design a durable UI pattern system."],
});
expect(decision).toMatchObject({
runnerUp: "bb",
target: "design",
});
expect(decision?.margin).toBeCloseTo(0.05);
});
});

describe("existing section resolution", () => {
it("supports the bb quick fixes migration alias", () => {
expect(
resolveSectionId(
[
{ id: "sec_bb", name: "bb quick fixes" },
{ id: "sec_design", name: "Design" },
],
"bb",
),
).toBe("sec_bb");
});

it("fails closed when both bb aliases exist", () => {
expect(
resolveSectionId(
[
{ id: "sec_bb", name: "bb" },
{ id: "sec_old", name: "bb quick fixes" },
],
"bb",
),
).toBeNull();
});

it("never treats QA as a target alias", () => {
expect(
resolveSectionId([{ id: "sec_qa", name: "QA" }], "design"),
).toBeNull();
});
});

describe("conservative title repair", () => {
it.each([
["can you optimize my CI?", "Optimize CI"],
["Please fix the external file nav.", "Fix External File Nav"],
["take over tools hub refactor PR", "Take Over Tools Hub Refactor"],
["clean up disk space and CPU", "Clean Up Disk Space"],
])("derives %s", (prompt, expected) => {
expect(deriveTaskTitle(prompt)?.title).toBe(expected);
});

it.each([
"continue",
"root cause this",
"https://github.com/ymichael/bb/issues/603",
"create a bb plugin",
"what should we do next?",
])("abstains from %s", (prompt) => {
expect(deriveTaskTitle(prompt)).toBeNull();
});
});

describe("turn milestones", () => {
it.each([
[1, 1, 5],
[1, 4, 5],
[1, 5, 15],
[5, 14, 15],
[5, 15, 25],
[15, 37, 45],
])(
"advances current %i after %i completed turns to %i",
(current, turns, expected) => {
expect(advanceEvaluationMilestone(current, turns)).toBe(expected);
},
);
});
Loading