Skip to content

Keep plugin SDK declarations current so agents stop reading bundles - #1107

Merged
SawyerHood merged 4 commits into
mainfrom
bb/evaluate-plugin-authoring-skill-thr_s6pnjdrhy3
Aug 7, 2026
Merged

Keep plugin SDK declarations current so agents stop reading bundles#1107
SawyerHood merged 4 commits into
mainfrom
bb/evaluate-plugin-authoring-skill-thr_s6pnjdrhy3

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

The problem

bb plugin new seeds types/*.d.ts once, and nothing ever refreshes them. The SDK surface grows every release, so a plugin scaffolded against an older bb typechecks green against an API that has since moved. Local copies on one machine:

plugin lines current
bb-plugin-posthog-dashboard 10,966 12,868
omegacode 11,736 12,868
bb-slop-cop 12,409 12,868
bb-plugin-strudel 12,738 12,868

The bb-plugin-authoring skill points agents at types/ for an exact signature, but could not promise the directory existed or was current. With no trustworthy local source of truth, an agent falls back to grepping minified build output — which costs far more context and answers worse.

The fix

bb plugin types [path] writes the running bb's @bb/plugin-sdk declarations into the plugin's types/, creating the directory when absent. Needs no server. --check reports staleness and exits non-zero without writing, for CI.

Auto-refresh: bb plugin build and bb plugin dev run the same sync. dev refreshes before its watcher starts, so the write cannot feed the loop its own change event. A failure warns and continues — an unwritable types/ must not fail a build.

syncPluginTypes lives in packages/templates/src/plugin-scaffold.ts, next to the scaffold that seeds the same files. It compares before writing, so a current plugin reports unchanged and keeps its mtime, and a headless plugin never gets a frontend declaration it did not ask for.

The skill gains a ## Looking up the exact API section with a three-step ladder — run bb plugin types, read the .d.ts, clone the repo with a copy-pasteable git clone --depth 1 — plus an explicit rule: never answer an API question from a built bundle, and if you catch yourself grepping minified JavaScript, go back to step 1. A Gotchas bullet points back at it.

Guide chapter (regenerated), bb-cli skill, and scaffold README updated to match.

Verification

Against the real stale posthog-dashboard plugin: --check reported both files stale and exited 1; bb plugin types took the root declaration 10,966 → 12,868 lines and the app declaration 507 → 1,443; a re-check reported unchanged. A headless plugin with no types/ got only the root declaration. bb plugin build printed Refreshed SDK declarations before compiling.

pnpm exec turbo run typecheck test --filter=@bb/cli --filter=@bb/templates passes (387 tests). Server skill and plugin-authoring-doc tests pass (75 tests). Five new tests in packages/templates/test/plugin-sync-types.test.ts cover stale replacement, unchanged-keeps-mtime, the headless case, refreshing an existing app declaration, and check-mode writing nothing.

One call to confirm

bb plugin build now rewrites types/ in place, so a plugin that commits those files and builds in CI will show a dirty tree. That is deliberate — --check is the non-writing alternative — but happy to make build warn instead.

🤖 Generated with Claude Code

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

I am SlopCop. I am reviewing this change for security, code quality, performance, architecture, and end-to-end behavior.

The stack contains only #1107. I did not skip a pull request.

continue;
}
await mkdir(typesDir, { recursive: true });
await writeFile(filePath, candidate.content);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — High: Refuse symbolic links before declaration writes.

writeFile follows this path. A plugin repository can commit types/bb-plugin-sdk.d.ts as a symbolic link to any writable file. Then bb plugin types, bb plugin build, or bb plugin dev replaces that external file. A linked types/ directory has the same effect.

I confirmed this behavior with the pull request CLI. The command replaced a file outside types/ through a declaration link.

Reject linked directories and destination files. Verify that the real destination remains below the real plugin root. Then write through a safe temporary regular file and rename it. Add tests for both link forms.

@SawyerHood
SawyerHood force-pushed the bb/evaluate-plugin-authoring-skill-thr_s6pnjdrhy3 branch from 10abb40 to 60fe4a4 Compare August 7, 2026 14:22

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

I am SlopCop. I am reviewing this pull request for security, code quality, performance, architecture, and end-to-end behavior.

Stack: #1107 only.

No pull request was skipped.

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

Performance phase complete for #1107.

No performance findings exist. The agent confirmed unchanged declarations keep their modification time. Synchronization runs once before the development watcher starts.

The CLI and template tests passed. The type checks passed. One external scaffold test could not run npm pack; it showed no performance regression.

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

Security phase complete for #1107.

One high-severity finding exists. The new automatic declaration refresh follows symbolic links. A hostile plugin can replace a user-writable file outside its plugin directory.

The agent confirmed the exact head SHA. The focused tests and type checks passed.

continue;
}
await mkdir(typesDir, { recursive: true });
await writeFile(filePath, candidate.content);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — Reject symbolic links before the automatic declaration write.

writeFile follows a symbolic link at the declaration file or its types directory. A hostile plugin can link types/bb-plugin-sdk.d.ts to another user-writable file. The new bb plugin build and bb plugin dev paths then replace that file automatically.

I reproduced this behavior with a temporary plugin. The declaration write replaced a file outside the plugin root.

Use lstat to reject links. Resolve the real destination and verify that it stays inside the real plugin root. Use a temporary file and an atomic rename.

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

End-to-end phase complete for #1107.

I built the repository CLI. I created a real plugin scaffold. The initial bb plugin types --check passed.

I changed the declaration. The check then failed with the expected stale result. The refresh wrote the current declaration. The next check passed.

After dependency installation, bb plugin build passed. A browser test does not apply because this change adds no HTTP route or user interface.

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

Code quality and architecture phase complete for #1107.

One low-severity finding exists. The new synchronization function duplicates the declaration writes that remain in scaffoldPlugin.

No other correctness, contract, CLI behavior, edge-case, or stale-name findings exist.

The CLI and template type checks passed. My full template suite passed all 19 tests.

* Files are compared before writing, so an already-current plugin reports
* `unchanged` and keeps its mtime.
*/
export async function syncPluginTypes(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — Use the synchronization function for the initial scaffold writes.

syncPluginTypes duplicates the direct declaration writes that remain later in scaffoldPlugin. A future declaration change can make new plugins and refreshed plugins receive different contracts.

Call syncPluginTypes({ rootDir: targetDir, app }) from scaffoldPlugin. Remove the direct declaration writes.

Comment thread apps/cli/src/commands/plugin.ts Outdated
// Keep the local declarations tracking the bb doing the build, so a
// plugin scaffolded against an older SDK never typechecks green
// against an API this bb no longer has.
if (manifest) await refreshPluginTypes(rootDir, hasApp);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — Validate the plugin before the declaration refresh changes its files.

A readable package.json without bb.server reaches this write. buildPluginServer rejects the package only after the refresh.

I reproduced this with an empty manifest. bb plugin build failed as expected, but it first created types/bb-plugin-sdk.d.ts.

Require a string bb.server before this call, or perform all manifest validation before any write.

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

Final review for stack #1107.

One high-severity security finding blocks a safe merge.

  • The declaration write follows symbolic links.
  • A hostile plugin can replace a writable file outside its root.
  • bb plugin build and bb plugin dev now start this write automatically.

Two low-severity quality findings also exist.

  • A failed build writes declarations before it validates bb.server.
  • The scaffold and refresh paths duplicate the declaration write policy.

I found no performance issue.

Validation passed:

  • Turbo type checks for @bb/cli and @bb/templates.
  • All 19 template tests.
  • The CLI build.
  • The complete bb plugin types stale and refresh flow.
  • The plugin build after dependency installation.
  • All GitHub checks.

A browser test does not apply because this change adds no route or user interface.

I posted three inline findings. This review uses comment status only. It does not approve or request changes on GitHub.

SawyerHood and others added 4 commits August 7, 2026 14:43
`bb plugin new` seeds types/*.d.ts once and nothing ever refreshes them, so
every plugin scaffolded against an older bb typechecks green against an API
that has since moved. Local copies here ranged from 130 to 1,900 lines behind
the current 12,868-line surface. With no trustworthy local source of truth, an
agent looking for an exact signature falls back to grepping minified build
output.

Add `bb plugin types [path]`: it writes the running bb's @bb/plugin-sdk
declarations into the plugin's types/, creating the directory when absent, and
needs no server. `--check` reports staleness and exits non-zero without
writing, for CI. `bb plugin build` and `bb plugin dev` run the same sync
automatically; dev refreshes before its watcher starts so the write cannot feed
the loop its own change event, and a failure warns rather than failing a build.

The shared syncPluginTypes sits next to the scaffold that seeds the same files.
It compares before writing, so a current plugin reports `unchanged` and keeps
its mtime, and a headless plugin never gets a frontend declaration it did not
ask for.

The bb-plugin-authoring skill gains a "Looking up the exact API" section: run
`bb plugin types`, read the .d.ts, clone the repo — with an explicit rule never
to answer an API question from a built bundle. Guide chapter, bb-cli skill, and
scaffold README updated to match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same ladder and the same prohibition, roughly half the words.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three fixes from the SlopCop review of this PR.

`writeFile` follows links, so a plugin that shipped `types/` — or a
declaration inside it — as a symbolic link redirected the write onto whatever
it pointed at. That was already reachable through `bb plugin types`, and the
new automatic refresh made `bb plugin build` and `bb plugin dev` do it without
the author asking. Building a plugin never runs its code, so cloning an
untrusted plugin and building it must not write anywhere but that plugin. I
reproduced the clobber before fixing it.

syncPluginTypes now lstats without following, refuses either link form,
verifies the resolved `types/` still sits inside the resolved plugin root, and
writes through a temporary regular file it renames into place. Tests cover
both link forms and assert the target survives.

Also: scaffoldPlugin now seeds declarations through syncPluginTypes instead of
duplicating the writes, so a scaffolded plugin and a refreshed one cannot
diverge; and `bb plugin build` gates the refresh on a string `bb.server`, so a
directory it is about to reject is never written to first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SawyerHood
SawyerHood force-pushed the bb/evaluate-plugin-authoring-skill-thr_s6pnjdrhy3 branch from 471bfc1 to d2d96a4 Compare August 7, 2026 14:44
@SawyerHood
SawyerHood merged commit d54acb3 into main Aug 7, 2026
10 checks passed
@SawyerHood
SawyerHood deleted the bb/evaluate-plugin-authoring-skill-thr_s6pnjdrhy3 branch August 7, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant