--- status: open created: 2026-05-18 last_updated: 2026-05-18 --- # Core Library Extraction Sync Review Review of the impact of extracting three core libraries — `@alkdev/operations`, `@alkdev/pubsub`, and `@alkdev/taskgraph` — on the alkhub_ts codebase and architecture documentation. These packages are now published on npm and replace in-repo code plus implement previously "not started" functionality. --- ## Summary Three packages were extracted from (or designed for) this codebase and are now platform-agnostic npm packages: | Package | Version | Replaces in `packages/core/` | New Capabilities | |---------|---------|-------------------------------|------------------| | `@alkdev/operations` | 0.1.0 | `operations/` (7 files) + `mcp/` (3 files) | Call protocol (PendingRequestMap), ResponseEnvelope, access control enforcement, CallError, SchemaAdapter, subscribe helper, SSE subscription handling | | `@alkdev/pubsub` | 0.1.0 | `pubsub/` (5 files) | EventEnvelope, WebSocket client+server+worker event targets, 13 operators (was 3), inlined Repeater, `prefix`/`close()` on Redis ET | | `@alkdev/taskgraph` | 0.0.2 | Nothing (new) | TaskGraph class, analysis (critical path, parallel groups, bottlenecks, risk, cost-benefit), frontmatter parsing | The decision has been made to **remove `packages/core/` as a package entirely**. Its remaining modules (config, logger, crypto) will be relocated — most likely into hub directly, since spokes that need config can import `@alkdev/operations` config types or we create a minimal `@alkhub/config` package. The first spokes won't need provider key storage; eventual "hub-like spokes" will be addressed as a federation concern later. --- ## 1. Code Changes ### 1.1 Delete from `packages/core/` All of these are replaced by npm packages: **`core/pubsub/`** — replaced by `@alkdev/pubsub`: - `create_pubsub.ts` - `typed_event_target.ts` - `redis_event_target.ts` - `operators.ts` - `mod.ts` **`core/operations/`** — replaced by `@alkdev/operations`: - `types.ts` - `registry.ts` - `env.ts` - `scanner.ts` - `validation.ts` - `from_schema.ts` - `from_openapi.ts` - `mod.ts` **`core/mcp/`** — replaced by `@alkdev/operations/from-mcp`: - `wrapper.ts` - `loader.ts` - `mod.ts` **Tests and fixtures** — for deleted modules: - `tests/operations/registry.test.ts` - `tests/operations/scanner.test.ts` - `tests/pubsub/redis_event_target.test.ts` - `tests/mcp/loader.test.ts` - `tests/fixtures/registry.ts` - `tests/fixtures/operations/demo/greet.ts` - `tests/fixtures/operations/other/calculate.ts` ### 1.2 Relocate from `packages/core/` These have no external replacement and need to be relocated: | Module | Lines | Destination | |--------|-------|-------------| | `core/config/types.ts` | 169 | Hub package (or a thin `@alkhub/config` if spokes need shared config types) | | `core/logger/mod.ts` | 27 | Hub package (logtape config is hub-specific anyway) | | `core/utils/crypto.ts` | 119 | Hub package (encryption key management is hub-only) | ### 1.3 Delete `packages/core/` as a package Once modules are relocated, remove: - `packages/core/deno.json` - `packages/core/mod.ts` - The `"core"` entry from root `deno.json` workspace array ### 1.4 Update dependency declarations **Root `deno.json`**: - Remove `"packages/core"` from workspace array - Add `@alkdev/operations`, `@alkdev/pubsub`, `@alkdev/taskgraph` to imports (if needed at root level) **New `packages/hub/deno.json`** (when created): - Add: `@alkdev/operations`, `@alkdev/pubsub`, `@alkdev/taskgraph`, `@alkdev/typebox`, `@alkdev/drizzlebox`, `hono`, `drizzle-orm`, `ioredis`, `logtape`, `@hono/mcp`, `ai`, `keypal` - Remove (no longer direct): `@repeaterjs/repeater` (inlined in @alkdev/pubsub), `@modelcontextprotocol/sdk` (optional peer in @alkdev/operations) **New `packages/spoke/deno.json`** (when created): - Add: `@alkdev/operations`, `@alkdev/pubsub` (client event target only), `@alkdev/typebox`, `logtape` ### 1.5 Breaking API Changes | Change | Impact | Migration | |--------|--------|-----------| | `registry.execute()` returns `ResponseEnvelope` not `T` | All callers must `unwrap()` or access `.data` | `import { unwrap } from "@alkdev/operations"` | | `OperationEnv` functions return `Promise` not `Promise` | All nested call sites | Same | | `OperationContext` drops `stream`/`pubsub` fields | Handlers using these (none exist yet) | Use `PendingRequestMap.subscribe()` for subscriptions | | `createPubSub` uses `PubSubEventMap` not `PubSubPublishArgsByKey` | Any pubsub usage | `createPubSub<{ eventType: PayloadType }>()` — publishes with `publish(type, id, payload)` | | `createRedisEventTarget` takes `prefix` and has `close()` | Redis setup code | Add `prefix: "alk:events:"`, call `close()` on shutdown | | Scanner uses `ScannerFS` interface, not `Deno.readDir` directly | Spoke scanner | Provide Deno adapter: `{ readdir: (p) => Deno.readDir(p), cwd: () => Deno.cwd() }` | | `AccessControl` drops `customAuth` field | No code uses it yet | N/A | | MCP adapter wraps results in `mcpEnvelope()` | MCP consumers | Use `unwrap()` or `isResponseEnvelope()` | | `assertIsSchema` throws `Error` instead of `AssertionError` | Test code | Already the correct behavior per @alkdev/operations | --- ## 2. Architecture Spec Updates ### 2.1 AGENTS.md — Major Update **Provenance table** — Replace all "Copied from predecessor project" and "Forked from graphql-yoga" entries: | Module | Current Status | New Status | |--------|---------------|------------| | Operations system | "Working, 7 tests passing" | **Extracted to `@alkdev/operations` v0.1.0** | | PubSub (createPubSub) | "Working" | **Extracted to `@alkdev/pubsub` v0.1.0** | | PubSub (operators) | "Working" | **Extracted to `@alkdev/pubsub` v0.1.0** | | TypedEventTarget | "Forked from graphql-yoga" | **Extracted to `@alkdev/pubsub` v0.1.0** | | Redis EventTarget | "Working, 5 tests passing" | **Extracted to `@alkdev/pubsub` v0.1.0** | | WebSocket EventTarget | "Not started" | **Implemented in `@alkdev/pubsub` v0.1.0** (client + server + worker) | | MCP client | "Working, 1 test passing" | **Extracted to `@alkdev/operations/from-mcp` v0.1.0** | | Call protocol | "Not started" | **Implemented in `@alkdev/operations` v0.1.0** | | Config types | "Needs hub config" | Remains (to relocate) | | Logger | "Needs proper config" | Remains (to relocate) | | Storage | "Not started" | Not started (unchanged) | **Key Patterns section** — Update: - Operations: Reference `@alkdev/operations` package, add ResponseEnvelope and call protocol - PubSub: Reference `@alkdev/pubsub` package, update from "graphql-yoga (MIT)" to standalone package with EventEnvelope pattern - New: Task graph operations via `@alkdev/taskgraph` **Reference Dependencies table** — Add: | `@alkdev/operations` | `npm:@alkdev/operations@^0.1.0` | Operations, call protocol, MCP adapter, ResponseEnvelope | | `@alkdev/pubsub` | `npm:@alkdev/pubsub@^0.1.0` | PubSub, EventEnvelope, event targets (Redis/WS/Worker) | | `@alkdev/taskgraph` | `npm:@alkdev/taskgraph@^0.0.2` | Task graph, analysis, frontmatter | Remove: - `graphql-yoga` row (source now in `@alkdev/pubsub`) Update: - `graphology` row: note it's now a transitive dep of `@alkdev/taskgraph`, no longer a direct dep of this project **Workspace Structure** — Remove `core/` package: ``` packages/ hub/ — Hono API server, storage (Drizzle+Postgres), auth, coordination, Redis events spoke/ — Self-registering runner: websocket connection, dispatch, operation provider ``` Add note about external dependencies: ``` External @alkdev packages (npm): @alkdev/operations — Operations registry, call protocol, MCP adapter, ResponseEnvelope @alkdev/pubsub — PubSub, event targets (Redis/WS/Worker), operators @alkdev/taskgraph — Task graph construction, analysis, frontmatter ``` **Constraints section** — Add: - `@alkdev/pubsub`, `@alkdev/operations`, `@alkdev/taskgraph` are the canonical implementations — do not duplicate their code in-repo ### 2.2 overview.md — Major Update **"What Exists" section** — Replace entirely: | Module | Location | Status | |--------|----------|--------| | Operations system | `@alkdev/operations` | Published v0.1.0 | | PubSub (createPubSub + operators) | `@alkdev/pubsub` | Published v0.1.0 | | TypedEventTarget | `@alkdev/pubsub` | Published v0.1.0 | | Redis EventTarget | `@alkdev/pubsub` | Published v0.1.0 | | WebSocket EventTarget (client+server) | `@alkdev/pubsub` | Published v0.1.0 | | Worker EventTarget | `@alkdev/pubsub` | Published v0.1.0 | | MCP client adapter | `@alkdev/operations/from-mcp` | Published v0.1.0 | | Call protocol (PendingRequestMap, CallHandler) | `@alkdev/operations` | Published v0.1.0 | | Access control (enforceAccess) | `@alkdev/operations` | Published v0.1.0 | | ResponseEnvelope | `@alkdev/operations` | Published v0.1.0 | | SchemaAdapter (Zod/Valibot) | `@alkdev/operations/from-typemap` | Published v0.1.0 | | SSE subscription handling | `@alkdev/operations/from-openapi` | Published v0.1.0 | | Task graph + analysis | `@alkdev/taskgraph` | Published v0.0.2 | | Config types | `packages/core/` | Stub — needs relocation | | Logger | `packages/core/` | Stub — needs relocation | **"What Needs Implementation"** — Remove completed items, keep remaining: | Component | Spec | Priority | |-----------|------|----------| | ~~WebSocket EventTarget~~ | ~~spoke-runner.md~~ | ~~High~~ — **Done: `@alkdev/pubsub`** | | ~~Call protocol (PendingRequestMap)~~ | ~~call-graph.md~~ | ~~High~~ — **Done: `@alkdev/operations`** | | Storage (Drizzle+Postgres tables, migrations) | storage/ | High | | Hub HTTP server (Hono) | hub-architecture.md | High | | OpenAI proxy (Hono) | agent-sessions.md | High | | Logger configuration | — | Medium | | Hub config system | hub-config.md | Medium | | MCP server (@hono/mcp) | mcp-server.md | Medium | | Agent sessions (AI SDK) | agent-sessions.md | Medium | | Coordination operations | coordination.md | Medium | | Call graph storage | call-graph.md, storage/ | Medium | | Operation graph | call-graph.md | Low | | Call templates | call-graph.md | Low | ### 2.3 packages.md — Major Rewrite **Remove `@alkhub/core` section entirely.** Add a new section for external `@alkdev/*` packages: ``` ### `@alkdev/operations` (npm package) Operations registry, call protocol, MCP adapter, ResponseEnvelope. Platform-agnostic. Exports: . — types, registry, call protocol (PendingRequestMap, buildCallHandler), subscribe, access control, error, env, scanner, validation, from_schema, response-envelope ./from-mcp — MCP tool adapter (ioredis optional peer) ./from-typemap — Zod/Valibot schema adapters (@alkdev/typemap optional peer) ./from-openapi — OpenAPI/SSE/HTTP service adapter ### `@alkdev/pubsub` (npm package) PubSub, event targets, operators. Platform-agnostic. Exports: . — createPubSub, types, operators, repeater ./event-target-redis — Redis adapter (ioredis optional peer) ./event-target-websocket-client — Spoke-side WebSocket adapter ./event-target-websocket-server — Hub-side WebSocket adapter ./event-target-worker — Web Worker adapter (host + thread) ### `@alkdev/taskgraph` (npm package) Task graph construction, analysis, frontmatter. Platform-agnostic. Exports: . — TaskGraph, analysis functions, schema, error types, frontmatter ``` **`@alkhub/hub` dependencies**: Add `@alkdev/operations`, `@alkdev/pubsub`, `@alkdev/taskgraph`. Remove `@repeaterjs/repeater` (inlined). Update: `ioredis` is optional (only if Redis ET is used directly; the package uses it). **`@alkhub/spoke` dependencies**: Add `@alkdev/operations`, `@alkdev/pubsub`. **Rules section** — Update rule 1: "core is transport-agnostic" becomes "packages should be transport-agnostic". Remove rule about core being persistence-agnostic (hub still is). Update dependency direction: ``` spoke → @alkdev/operations, @alkdev/pubsub hub → @alkdev/operations, @alkdev/pubsub, @alkdev/taskgraph hub ←/→ spoke (communicate via call protocol over WebSocket) ``` ### 2.4 call-graph.md — Significant Update **PendingRequestMap section** — Replace the schematic with actual `@alkdev/operations` API: ```ts // From @alkdev/operations import { PendingRequestMap } from "@alkdev/operations" const prm = new PendingRequestMap({ eventTarget }) await prm.call(operationId, input, { deadline, identity }) const stream = prm.subscribe(operationId, input, { idleTimeout, identity }) prm.respond(requestId, output) // output must be ResponseEnvelope prm.emitError(requestId, code, message, details?) prm.complete(requestId) prm.abort(requestId) ``` Key API differences from the doc: - `call()` returns `Promise` (not `Promise`) - `subscribe()` returns `AsyncIterable` - `respond()` requires output to be a `ResponseEnvelope` - Deadline and idle timeout are built in - Constructor takes optional `EventTarget` for pluggable transport **CallHandler section** — Reference `buildCallHandler` from `@alkdev/operations`: ```ts import { buildCallHandler } from "@alkdev/operations" const handler = buildCallHandler({ registry, eventTarget }) ``` **buildEnv section** — Remove `callMap` parameter. In `@alkdev/operations`, `buildEnv`: - No longer takes `callMap` — uses `PendingRequestMap` internally - Sets `trusted: true` on nested context - Returns env functions that return `Promise` **Dependencies section** — Replace graphology direct deps. Graphology is now a transitive dependency through `@alkdev/taskgraph`. Call graph storage still uses graphology for runtime operations but should prefer `@alkdev/taskgraph`'s `TaskGraph` class when applicable. ### 2.5 operations.md — Major Rewrite This doc needs significant restructuring since most of what it describes is now in `@alkdev/operations`. **Key changes**: - Remove "In-repo location: `packages/core/operations/`" — now external package - Component descriptions should reference `@alkdev/operations` exports - Schema Adapters section: Replace raw `@alkdev/typemap` dynamic import description with `SchemaAdapter` pattern - Remove SSE Subscription Handler Fix from open issues — fixed in `@alkdev/operations/from-openapi` - Update Call Protocol Integration section to reference `@alkdev/operations` API - Add ResponseEnvelope concept (universal result wrapper: local/http/mcp) - Add CallError/InfrastructureErrorCode concept - Update access control: `enforceAccess` is now in the package, with `trusted` bypass **New concepts to document**: - `ResponseEnvelope` with source discriminant (`"local"` | `"http"` | `"mcp"`) - `subscribe()` helper for subscription operations - `ScannerFS` interface (Deno runtime agnostic) - `OpenAPIServiceRegistry` class for managing HTTP services - `parseSSEFrames()` for SSE subscription handling ### 2.6 pubsub-redis.md — Major Rewrite This doc describes code that's now in `@alkdev/pubsub`. Key changes: - **Source location**: `@alkdev/pubsub` npm package, not `packages/core/pubsub/` - **createPubSub API**: Uses `PubSubEventMap` (simple `{ [eventType: string]: payload }`) not `PubSubPublishArgsByKey` - **EventEnvelope**: New concept — `{ type, id, payload }` is the cross-process message format. Reserved `__` prefix for control messages. - **Redis EventTarget**: Now accepts `prefix` option (e.g., `"alk:events:"`) and has `close()` method. No need for serializer workaround to add prefix. - **WebSocket EventTarget**: No longer "Not started" / "Deferred". Document both client and server adapters. - **Worker EventTarget**: New adapter for Web Workers (host + thread). - **Operators**: 13 operators, not 3. New: `take`, `reduce`, `toArray`, `batch`, `dedupe`, `window`, `flat`, `groupBy`, `chain`, `join`. - **Repeater**: Inlined, no longer depends on `@repeaterjs/repeater` externally. - **Prior Art section**: Update to reflect `@alkdev/pubsub` is a standalone package, not forked code in-repo. ### 2.7 storage/tasks.md — Update Graphology Section **"Graphology Integration" section** — Replace direct graphology usage with `@alkdev/taskgraph`: Instead of: ``` 1. Load all tasks + task_dependencies rows for a project from the DB 2. Build a graphology DirectedGraph in memory 3. Run graph algorithms as needed ``` Use: ``` 1. Load all tasks + task_dependencies rows for a project from the DB 2. Build a TaskGraph via TaskGraph.fromRecords(tasks, edges) 3. Run analysis functions as needed (criticalPath, parallelGroups, bottlenecks, riskPath, etc.) ``` **Frontmatter parsing** — Reference `@alkdev/taskgraph`'s `parseFrontmatter` and `serializeFrontmatter` functions instead of custom parsers. Note: `parseTaskFile` and `parseTaskDirectory` are Node.js only (use `node:fs/promises`). **References section** — Update graphology reference to point to `@alkdev/taskgraph` package. **NAPI note** — The doc says "Why not taskgraph NAPI for v1". This is now resolved: `@alkdev/taskgraph` is pure TypeScript (graphology-based), and the Rust CLI (`taskgraph`) is for offline analysis. The TS package handles runtime graph ops. ### 2.8 hub-architecture.md — Update Component Table - Operations row: `@alkdev/operations` not `core/operations/` - PubSub row: `@alkdev/pubsub` not `core/pubsub/` - Call protocol row: `@alkdev/operations` not `core/` (see call-graph.md) - WebSocket adapter: "pending" → "available in `@alkdev/pubsub`" ### 2.9 hub-config.md — Update Redis EventTarget Example Update `createRedisEventTarget` example to include `prefix`: ```ts createRedisEventTarget({ publishClient, subscribeClient, prefix: "alk:events:", }) ``` ### 2.10 hub-startup.md — Update References - PendingRequestMap + CallHandler: note these come from `@alkdev/operations` - PubSub setup: reference `@alkdev/pubsub` with `prefix` option ### 2.11 spoke-runner.md — Update References - WebSocketEventTarget: `@alkdev/pubsub/event-target-websocket-client` - PendingRequestMap: `@alkdev/operations` - Scanner: `@alkdev/operations` with `ScannerFS` Deno adapter - SchemaAdapters: `@alkdev/operations/from-typemap` - `FromSchema()` / `FromOpenAPI()`: `@alkdev/operations/from-schema` / `@alkdev/operations/from-openapi` ### 2.12 ADR-013 — Update Paths - Update `packages/core/operations/scanner.ts` references to `@alkdev/operations/scanner` - Update `packages/core/operations/from_schema.ts` references to `@alkdev/operations/from_schema` - Update `packages/core/operations/from_openapi.ts` references to `@alkdev/operations/from_openapi` - Update scanner enhancement task to reference `SchemaAdapter` pattern from `@alkdev/operations/from-typemap` ### 2.13 docs/research/migration/ — Update or Archive Both `operations.md` and `pubsub.md` in this directory describe planned extractions that are now **complete**. Options: - **Archive**: Move to `docs/research/migration/completed/` with a status note - **Update**: Rewrite as "completed migration" docs showing before/after Recommend: Archive both. They served their purpose and the current API surface is documented in the `@alkdev/*` package READMEs and this review. ### 2.14 docs/reviews/docs-consistency-review-2026-04-17.md — Superseded Entries Several findings from the previous review are now resolved by the extractions: | Finding | Original Issue | Resolution | |---------|---------------|------------| | C5 | PendingRequestMap is in core, not hub | **Resolved**: Now in `@alkdev/operations` | | I2 | `env.ts` has PendingRequestMap interface only | **Resolved**: Full implementation in `@alkdev/operations` | | I5 | `OperationContext.pubsub` typed as unknown | **Resolved**: `pubsub` field removed from context in `@alkdev/operations` | | I6 | `OperationContext.stream` never populated | **Resolved**: `stream` field removed from context in `@alkdev/operations` | | I7 | `@repeaterjs/repeater` version mismatch risk | **Resolved**: Inlined in `@alkdev/pubsub`, no external dep | --- ## 3. What's Now Unblocked | Component | Previous Status | Now Available In | |-----------|-----------------|------------------| | Call protocol (PendingRequestMap, CallHandler) | Not started | `@alkdev/operations` | | WebSocket transport (client + server) | Not started | `@alkdev/pubsub` | | WebSocket connection management (backpressure, SpokeEventTarget) | Not started | `@alkdev/pubsub` | | Access control enforcement (checkAccess, enforceAccess) | Not started | `@alkdev/operations` | | Task graph operations (topo sort, cycles, critical path, risk) | Not started | `@alkdev/taskgraph` | | ResponseEnvelope (source tracking) | Not started | `@alkdev/operations` | | Schema conversion (Zod/Valibot) | Not started | `@alkdev/operations/from-typemap` | | SSE subscription handling | Broken | `@alkdev/operations/from-openapi` | | Error model (CallError, InfrastructureErrorCode) | Not started | `@alkdev/operations` | | EventEnvelope (structured cross-process messages) | Not started | `@alkdev/pubsub` | ## 4. What Still Needs Implementation All of these are hub or spoke level concerns that can now be built on top of the extracted packages: | Component | Depends On | Spec | |-----------|------------|------| | Storage (Drizzle+Postgres tables, migrations) | `@alkdev/typebox`, `@alkdev/drizzlebox`, `drizzle-orm` | storage/ | | Hub HTTP server (Hono) | `@alkdev/operations`, `@alkdev/pubsub`, `hono` | hub-architecture.md | | Spoke WebSocket client | `@alkdev/operations`, `@alkdev/pubsub/event-target-websocket-client` | spoke-runner.md | | Hub WebSocket server (spoke management) | `@alkdev/operations`, `@alkdev/pubsub/event-target-websocket-server` | spoke-runner.md | | OpenAI proxy | `hono`, AI SDK | agent-sessions.md | | Auth (keypal) | Hono middleware | — | | MCP server (@hono/mcp) | `@alkdev/operations`, `@hono/mcp` | mcp-server.md | | Agent sessions (AI SDK) | `@alkdev/operations`, AI SDK, storage | agent-sessions.md | | Coordination operations | `@alkdev/operations`, storage | coordination.md | | Call graph storage | `@alkdev/operations`, storage | storage/call-graph.md | | Hub config loader | `@alkdev/operations` (config types) | hub-config.md | | Logger configuration | logtape | — | --- ## 5. Package Dependency Graph (New) ``` @alkdev/operations → @alkdev/typebox, @alkdev/pubsub, @logtape/logtape → (optional peers): @alkdev/typemap, @modelcontextprotocol/sdk @alkdev/pubsub → (no runtime deps) → (optional peer): ioredis (for ./event-target-redis) @alkdev/taskgraph → @alkdev/typebox, graphology (+plugins), yaml @alkhub/hub → @alkdev/operations, @alkdev/pubsub, @alkdev/taskgraph, @alkdev/typebox, @alkdev/drizzlebox, hono, drizzle-orm, ioredis, ai, keypal, logtape, @hono/mcp @alkhub/spoke → @alkdev/operations, @alkdev/pubsub, @alkdev/typebox, logtape ``` No `@alkhub/core` package. Config types, logger, and crypto utils live in `@alkhub/hub` (or a thin shared package if spokes need config types — this can be decided when implementing the spoke). --- ## 6. Open Decisions ### 6.1 Where do config types go? `core/config/types.ts` has `HubConfig`, `SpokeConfig`, `BaseConfig`, `PostgresConfig`, `RedisConfig`, `HttpConfig`, `AuthConfig`. These are used by both hub and spoke. Options: - **A**: Move to `@alkhub/hub`. Spokes that need config types import them from their own copy or a minimal `@alkhub/config` package. - **B**: Create `@alkdev/config` npm package. Platform-agnostic like the other `@alkdev/*` packages. - **C**: Put config types in `@alkdev/operations`. They're already TypeBox schemas and operations already depend on `@alkdev/typebox`. **Recommendation**: A for now. First spokes won't need hub config. Re-evaluate when a spoke actually needs shared config types. The spoke config types are already minimal (`SpokeConfig` has `hub.url` and `hub.auth.tokenFile`). ### 6.2 Logger and crypto? `core/logger/mod.ts` (27 lines) and `core/utils/crypto.ts` (119 lines) are hub-specific concerns. Move them into `@alkhub/hub` directly. ### 6.3 How to handle `ScannerFS` for Deno? `@alkdev/operations` uses an abstract `ScannerFS` interface. The spoke needs a Deno adapter: ```ts import { scanOperations } from "@alkdev/operations" const DenoFS: ScannerFS = { readdir: async (path) => Deno.readDir(path), cwd: () => Deno.cwd(), } const operations = await scanOperations("./operations", DenoFS) ``` This is minimal (~3 lines) and can live in the spoke package. ### 6.4 Research migration docs? `docs/research/migration/operations.md` and `docs/research/migration/pubsub.md` describe extraction plans that are now complete. They should be archived or removed — they're historical context, not current documentation. ### 6.5 Previous consistency review findings? The `docs-consistency-review-2026-04-17.md` has several findings that are now resolved by the extractions (C5, I2, I5, I6, I7 at minimum). These should be marked resolved in that document or superseded by this review. --- ## 7. Suggested Execution Order 1. **Delete replaced code** from `packages/core/` (operations, pubsub, mcp dirs + their tests) 2. **Update `packages/core/deno.json`** — remove deleted exports and dependencies 3. **Relocate remaining core modules** (config, logger, crypto) into `packages/hub/` 4. **Remove `packages/core/`** from workspace 5. **Update architecture docs** (overview, packages, call-graph, operations, pubsub-redis as priority) 6. **Update AGENTS.md** — provenance, key patterns, reference deps, workspace structure 7. **Update storage/tasks.md** — taskgraph references 8. **Update secondary docs** (hub-architecture, hub-config, hub-startup, spoke-runner, ADR-013) 9. **Archive research/migration docs** or mark as completed 10. **Update docs-consistency-review-2026-04-17.md** — mark superseded findings as resolved