feat: DI container + event bus foundation (Phase 1)#20
Conversation
Adds awilix as a production dependency for the upcoming DI container implementation. Awilix is decorator-free, CommonJS-native, and lightweight (~15KB). Closes GIT-39 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Define the domain-layer contracts for the hook event system: - IEventBus, IEventHandler, IEventResult interfaces - HookEvent types: ISessionStartEvent, ISessionStopEvent, IPromptSubmitEvent - Zero dependencies (pure domain types) Closes GIT-40 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add infrastructure EventBus implementing IEventBus. Key features: - Sequential handler dispatch with try/catch per handler - Failed handlers produce IEventResult with success:false, don't block others - Optional ILogger for failure warnings - registeredEvents() for introspection Closes GIT-41 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add createContainer() factory that wires all services via awilix: - ICradle interface types all registrations against domain interfaces - CLASSIC injection mode with explicit mapping for GitTriageService (constructor uses `git` param, not `gitClient`) - Logger scoping via options.scope with child() pattern - Conditional LLM client creation via options.enrich - All registrations are singletons within container scope Closes GIT-42 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CLI commands and MCP tools now use createContainer() instead of manually constructing NotesService → MemoryRepository → Service chains. Removes 72 lines of duplicated wiring code across 8 entry points. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds an Awilix-based DI composition root and types, replaces manual service construction in CLI commands and MCP tools with container resolution, introduces HookEvent types plus a typed EventBus and related interfaces, adds unit tests for DI and EventBus, and adds Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 Comment |
The disable comment targeted the deprecated no-var-requires rule instead of the current no-require-imports rule, causing CI lint failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/commands/recall.ts (1)
5-19:⚠️ Potential issue | 🟠 MajorReplace DI container usage with manual wiring in command handlers.
The command now usescreateContainer(Line 17), but command handlers must construct their dependency chain directly per invocation (NotesService → MemoryRepository → MemoryService). Please revert to manual wiring here.As per coding guidelines:
src/commands/**/*.ts: Use manual dependency construction instead of a DI container. Each command/tool handler must construct its dependency chain fresh per invocation: NotesService → MemoryRepository → MemoryService.src/commands/context.ts (1)
5-17:⚠️ Potential issue | 🟠 MajorReplace DI container usage with manual wiring in command handlers.
The command now usescreateContainer(Line 15), but command handlers must construct their dependency chain directly per invocation (NotesService → MemoryRepository → MemoryService). Please revert to manual wiring here.As per coding guidelines:
src/commands/**/*.ts: Use manual dependency construction instead of a DI container. Each command/tool handler must construct its dependency chain fresh per invocation: NotesService → MemoryRepository → MemoryService.src/commands/remember.ts (1)
5-22:⚠️ Potential issue | 🟠 MajorCommands must keep manual wiring (no DI container).
This now usescreateContainer, but command handlers are required to construct dependencies per invocation (NotesService → MemoryRepository → MemoryService). Please revert to explicit wiring in this file.As per coding guidelines: “src/commands/**/*.ts: Use manual dependency construction instead of a DI container. Each command/tool handler must construct its dependency chain fresh per invocation: NotesService → MemoryRepository → MemoryService”.
🤖 Fix all issues with AI agents
In `@src/commands/liberate.ts`:
- Around line 17-25: Replace the DI container usage (createContainer and
subsequent container.cradle destructuring) with manual construction of the
command's dependency chain: instantiate each dependency in order (e.g., create
MemoryRepository, then MemoryService, then NotesService, then LiberateService),
wire the llm client/anthropic client similarly so options.enrich checks
reference that explicit llmClient instance, and keep the existing logger usage
by passing the logger into the constructed services (replace references to
container.cradle.liberateService, container.cradle.llmClient, and
container.cradle.logger/log with the newly created instances). Ensure the logic
that logs the warning when enrich is enabled still checks the explicit llmClient
and that log.info is called on the passed logger instance after construction.
In `@src/infrastructure/di/types.ts`:
- Around line 1-7: Update the file-level docblock to mention both exported
interfaces — ICradle and IContainerOptions — rather than only ICradle; describe
that this file defines the typed shape of the DI container (ICradle) and the
configuration/options shape (IContainerOptions), note that all types reference
interfaces not concrete implementations, and keep wording concise so readers
immediately understand the purpose of both exports.
In `@src/infrastructure/events/EventBus.ts`:
- Around line 37-44: The handler name is obtained via handler.constructor.name
which can be mangled or empty in production; change the API to require/accept an
explicit stable name instead. Add a required name property to the IEventHandler
interface (or accept a name when registering handlers), update EventBus.emit and
any registration logic to use handler.name (or the supplied registration name)
when populating IEventResult.handler, and update all call sites that
construct/register handlers so they provide a meaningful name instead of relying
on handler.constructor.name.
- Around line 20-24: Change on() to return an unsubscribe function so callers
can remove handlers: update the IEventBus.on() signature to return () => void,
have EventBus.on<T extends HookEvent>(eventType: T['type'], handler:
IEventHandler<T>) add the handler to this.handlers (as it currently does) but
then return a function that removes that specific handler from the handlers map
for eventType (cleaning up the array and deleting the map entry if empty).
Ensure you reference and remove the same handler instance (IEventHandler<T> /
IEventHandler<HookEvent> casting as needed) and keep generics intact so typings
remain correct.
EventBus (12 tests): handler registration, event emission, error isolation, non-Error wrapping, event type filtering, registeredEvents. DI Container (9 tests): service resolution, singleton scoping, logger options with scope, LLM client conditional creation, eventBus wiring, end-to-end service wiring with real git repo. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9448f26a0d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Use CLASSIC-compatible plain parameters in asFunction registrations instead of destructuring (codex-connector feedback) - Update types.ts docblock to document both ICradle and IContainerOptions - Update CLAUDE.md bootstrapping docs to reflect new DI container pattern Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addressing Review Comments1. coderabbitai — Commands should manually wire dependencies (liberate.ts, recall.ts, context.ts, remember.ts) 2. coderabbitai — Docblock only describes ICradle (types.ts) 3. coderabbitai — Return unsubscribe from on() (EventBus.ts:24) 4. coderabbitai — constructor.name unreliable (EventBus.ts:44) 5. codex-connector — CLASSIC mode with destructuring (container.ts:63) |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@CLAUDE.md`:
- Around line 45-50: Add a blank line before the opening fenced code block and a
blank line after the closing ``` so the block is separated from surrounding
text; update the section that shows the createContainer example (the lines
containing "const container = createContainer({ logger, scope: 'remember' });"
and "const { memoryService } = container.cradle;") to have an empty line above
the triple-backtick and an empty line below the closing triple-backtick.
In `@src/infrastructure/di/container.ts`:
- Around line 68-70: The container is using asClass with InjectionMode.CLASSIC
which requires constructor parameter names to match registration keys (ICradle)
— this is fragile; update src/infrastructure/di/container.ts by either adding a
clear comment above the registrations (near memoryService, contextService,
liberateService) noting the CLASSIC-name coupling, or switch the container to
InjectionMode.PROXY / use a different registration strategy, or add a small
runtime/assertion test that verifies constructor parameter names match the
ICradle keys; reference asClass, InjectionMode.CLASSIC, ICradle, and the
registration names memoryService, contextService, liberateService when making
the change.
- Add blank lines around fenced code block in CLAUDE.md (MD031) - Add comment noting CLASSIC mode name-coupling for asClass services Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
createContainer()factory replaces manualNotesService → MemoryRepository → Servicechains across all 8 entry points (4 CLI commands + 4 MCP tools), removing 72 lines of duplicated wiringISessionStartEvent,ISessionStopEvent,IPromptSubmitEventwith discriminated unionHookEventfor future Claude Code hooks integrationIEventBus,IEventHandler,IEventResultinterfacesChanges
chore: install awilixfeat: domain eventsfeat: EventBusfeat: DI containercreateContainer()with ICradle type, IContainerOptions, CLASSIC injection moderefactor: entry pointscreateContainer()instead of manual instantiationTest plan
npm run type-checkpassesnpm run lintpasses (pre-existingno-require-importsin cli.ts only)git mem remember "test" && git mem recall🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores
Tests
Documentation