refactor(types): single-source-of-truth contract types (WS1)#498
Merged
Conversation
WS1 unifies the plugin contract types that exist as 2-6 drifting copies, then splits the two type god-files. Crux decision resolved by hard constraint: the SDK types module is documented self-contained and the publish guard bans @bakin/core refs in its output, so the SDK is the canonical home and core re-exports — the report's 'core is home for Task' direction is impossible for types plugins reach through ctx. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unreachable from any import surface (verified at HEAD by basename stem + every named export across src/packages/plugins/cli/scripts/tests/dev/ docs/.github): new-task-dialog (live new-task UI is TaskDetailDrawer; the docs screenshot captures by route, not this file), curated-browser, calendar-view + its sole-consumer parser parsers/calendar, plugin-slot (the SDK barrel explicitly does NOT re-export it), cli/ui/panel, and the host skeleton-loader. Audit finding: arch-coupling-deadcode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HealthCheckResult, the HealthRepair* family, and PluginHealthCheckInput were declared verbatim in both packages/sdk/src/types and packages/core/src/plugin-types. The SDK (the plugin-author surface) is now the sole declaration; core re-exports it. Core's richer doc comments were ported up to the canonical SDK declaration. Core-internal HealthCheckDef stays in core, now extending the SDK type. Audit finding: sdk-gaps (hand-maintained SDK/core type fork). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ExecToolResult was declared identically in core and the SDK. Core now re-exports it from @makinbakin/sdk/types. PluginToolContext and ExecToolDefinition reference the imported type but stay in core for now — they carry the task-service/runtime-adapter surface, which is unified together with PluginContext in a later commit. Audit finding: sdk-gaps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 14-type search cluster (SearchSchemaField, SearchIndexDefinition,
SearchContentTypeDefinition, SearchQueryParams, SearchResult,
SearchResponse, SearchHealth{Index,Table,Snapshot}, SearchTransformOp,
FilePatternMapper, FileBackedContentTypeDefinition, SearchAPI,
SearchMaintenanceAPI) was duplicated across core and the SDK. Two real
drifts where core was the accurate superset, now reconciled UP into the
canonical SDK declaration: the SDK lacked SearchMaintenanceAPI +
SearchAPI.maintenance (used by the memory plugin via ctx.search) and its
SearchHealthSnapshot inlined a narrower per-table shape without
indexHealth/SearchHealthIndex. Core now re-exports the cluster. Audit
finding: sdk-gaps.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nifest contract Core's PluginManifest had drifted stale — missing runtimeCapabilities, contributes, and devWatch — while core's own manifest parser already imports the current PluginManifest from @makinbakin/sdk/types. So the @bakin/core barrel served one shape and the parser used another. Core now re-exports PluginManifest, PluginManifestSignature, SecretDeclaration, PluginEntry, and BakinConfig from the SDK; the stale declarations are deleted. Consumers gain the missing optional fields. Audit findings: sdk-gaps, arch-coupling-deadcode (stale duplicate PluginManifest). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o-tier split The audit's "single-home PluginContext/BakinPlugin" recommendation was unsafe: core and the SDK are an intentional two-tier contract, not a fork. core's PluginContext.runtime is the FULL AgentRuntimeAdapter that 6 core plugins depend on (memory.*, agents.writeWorkspaceFile, config.replace, images.*, cron.getRaw — ~15 full-only methods); the SDK exposes a curated subset. ctx.tasks returns PluginTask (core) vs Task (SDK); BakinPlugin/StorageAdapter/NavItem/APIRoute/HookAPI/SkillDefinition are intentionally fuller in core. Collapsing the boundary is WS2 work. Both type files now carry a header documenting the two-tier design so the divergence isn't "fixed" later. The genuinely-identical zero-dependency leaf primitives EventBus, ActivityAPI, and PluginLogger are single-homed in the SDK and re-exported. Audit finding: sdk-gaps (with a correction to its blanket dedup recommendation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TaskLogEntry was byte-identical in 6 places. It's part of the published TaskService contract (appendLog) and identical everywhere, so the SDK is the canonical home; packages/core/src/tasks/store.ts re-exports it, src/core/task-store.ts re-exports through @bakin/core/tasks/store (preserving the facade's single import source), and plugin-types + plugins/tasks/types import from it. Task itself is NOT merged: the SDK's published Task (column: ColumnId, string timestamps) and the internal storage Task/BakinTask (numeric updatedAt + version optimistic-concurrency counter) are intentionally different projections per the two-tier split — version is an internal detail external plugins must not see. The internal Task/BakinTask duplication is a separate core-internal concern, out of scope for the SDK contract unification. Audit finding: arch-coupling-deadcode (TaskLogEntry copy-pasted across 4-6 layers). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AvailableModel was declared twice and drifted: the models plugin's copy had name/tier/provider required + the curated-catalog enrichment fields; the SDK's had them optional + an unused `source?`. The models plugin builds the /available payload and its cache zod schema requires name/tier/provider, so the SDK is reconciled UP to that accurate wire shape (required fields, enrichment docs, `source?` dropped — read nowhere). The models plugin now re-exports from the SDK; the team plugin (already importing from the SDK) gets the accurate non-optional fields. AvailableModelsResponse stays plugin-local. Audit finding: sdk-gaps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nceId) The SDK's published WorkflowInstance keyed on `id`, but the real wire shape the workflows plugin emits uses `instanceId` — a consumer reading `.id` off the published type would get undefined. Corrected to the accurate permissive view (instanceId + the commonly-read optional fields, index signature retained); the full typed shape with stepStates/history stays internal to the workflows plugin. Migrating the tasks plugin's hand-rolled workflow types + raw fetches onto this is WS3 (sdk-gaps) consumer work. Audit finding: sdk-gaps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ir-escaping import AgentUsage was declared in src/core/agent-usage.ts, copied verbatim into the health plugin's health-page.tsx, and imported by the team plugin's overview-tab via a relative path that escaped the plugin directory into src/core (the only client file in any plugin reaching outside its own tree). It's now a type-only export from the SDK (the usage-panel wire shape); src/core/agent-usage.ts re-exports it for server callers, and both client components import it from @makinbakin/sdk/types. The verbatim copy and the escaping import are gone. Audit finding: sdk-gaps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e types src/types/index.ts carried 15 exports; only Heartbeat, ActivityEvent, and ContentState are imported (by the SSE content store, use-sse, agent-status, map-audit-message, and the host activity route). The other 12 — a 4th stale Task model plus TaskLogEntry/TaskColumns/TaskBoard/ColumnId and the Calendar*/Memory*/ProjectMeta/OfficeData leftovers — had zero importers and are deleted. Audit finding: arch-coupling-deadcode (Next.js-era residue). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 1,524-line SDK types file is split into six cohesive modules behind the same barrel: primitives, manifest, runtime, services, registration, context. index.ts stays the @makinbakin/sdk/types entrypoint (scripts/build-sdk-package.ts SDK_EXPORTS + the vendor bundle) and re-exports the full surface via `export *`. Cross-module references use type-only sibling imports; no module reaches @bakin/core or repo source, so the published self-containment holds. The 8 dead misc types (Calendar*, Memory*, ProjectMeta, the SDK's orphaned Heartbeat and WorkflowStep — zero importers) are dropped in the move. Exported surface is otherwise identical (125 → 117 = the 8 deletions); pure types, zero runtime. Verified: typecheck, full suite, build:vendors, name-set diff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…contract repo-architecture.md and plugin-system.md described @makinbakin/sdk/types as "full type re-exports"; it's now the canonical, self-contained source of truth (split into focused modules behind a barrel) that core re-exports the identical leaf types from, while core keeps its fuller internal tier. Added the two-tier rationale so the intentional divergence isn't "fixed" later. Closes the WS1 doc sweep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WS1 of the audit refactor (
.claude/specs/audit-2026-06/REPORT.md). Eliminates the plugin-contract type duplication that the audit found drifting across 2–6 layers, establishing the SDK as the single source of truth, then splits the oversized SDK types file. 14 commits, one revertable checkpoint each; tests + typecheck green on every commit.The crux decision (resolved by hard constraint)
The audit report was internally contradictory on source-of-truth direction. It's settled by the SDK types module's documented self-containment + the publish guard (
assertNoForbiddenImportsbans@bakin/core//src/in SDK output): the SDK is the canonical home;packages/core/src/plugin-types.tsre-exports from it. The "core is home for Task" direction is physically impossible for anything plugins reach throughctx.Phase A — unify (kills the drift)
ExecToolResult, the 14-type search cluster (reconciledSearchMaintenanceAPI+ richer health snapshot UP into the SDK), manifest contract (dropped core's stalePluginManifest)PluginContext/BakinPluginare not duplicates — core and the SDK are an intentional two-tier contract (core'sctx.runtimeis the fullAgentRuntimeAdapterthat 6 plugins depend on for ~15 methods; the SDK exposes a curated subset). Documented the split in both type files; single-homed only the truly-identical primitives (EventBus/ActivityAPI/PluginLogger).TaskLogEntrysingle-homed (6 identical copies → 1); internal storageTaskleft distinct from the SDK's published projection (version/updatedAt are internal)AvailableModelreconciled to the accurate wire shape; A8 fixed the SDKWorkflowInstance(id→instanceId); A9AgentUsagesingle-homed + dropped a plugin-dir-escaping import; A10 stripped the Next.js-erasrc/typesresidue (15 → 3 live types)Phase B — split
sdk/types/index.tsinto 6 focused modules (primitives/manifest/runtime/services/registration/context) behind the same barrel; dropped 8 more dead misc types. Exported surface identical minus the dead types (125 → 117).core/plugin-types.ts1,129 → 738 lines, under the split threshold.Verification
bun run test(4,996 pass / 0 fail) +bun run typecheckgreen on every commitbun run lint, fullbun run build(3 binaries),build:vendorsgreenBAKIN_HOME: 200, all 10 plugins loadrepo-architecture.md,plugin-system.md); two-tier rationale recordedRefs the audit report; next workstream is WS2 (core-extractions / adapter-boundary).
🤖 Generated with Claude Code