This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Build all projects
dotnet build
# Build specific solution
dotnet build Keybinding.sln
dotnet build Keybinding.Examples.sln
# Format code
dotnet format# Run all tests
dotnet test
# Run tests with code coverage
dotnet test /p:CollectCoverage=true /p:CoverletOutput=./TestResults/ /p:CoverletOutputFormat=cobertura /p:Exclude="[*Test*]*"
# Run tests from test directory
cd Keybinding.Test
dotnet test /p:CollectCoverage=true /p:CoverletOutput=../TestResults/ /p:CoverletOutputFormat=cobertura /p:Exclude="[*Test*]*"# Run console app demo
dotnet run --project Keybinding.Demo
# Run Web API example
dotnet run --project Examples/Keybinding.WebAPI.ExampleThe library uses a musical metaphor for keybinding concepts:
-
Notes 🎵 → Individual keys (A, B, Escape, Ctrl, Alt, etc.)
- Represented by
Noteclass with aNoteNameproperty - Keys are stored uppercase internally
- Modifiers (Ctrl, Alt, Shift, Meta) are treated as notes
- Represented by
-
Chords 🎶 → Key combinations (Ctrl+C, Alt+Tab, Ctrl+Shift+N)
- Represented by
Chordclass containing multipleNoteobjects - Notes are deduplicated and ordered
- Display format shows modifiers first (Ctrl, Alt, Shift, Meta), then regular keys
- Parse from string:
Chord.Parse("Ctrl+Alt+S")
- Represented by
-
Phrases 🎼 → Sequences of chords (like Ctrl+R, R in Visual Studio)
- Represented by
Phraseclass containing multipleChordobjects - Can be single chord or multi-chord sequences
- Parse from string:
Phrase.Parse("Ctrl+R, R")
- Represented by
-
Profiles 📝 → Collections of command-to-phrase bindings
- Represented by
Profileclass with ID, name, description - Store mappings from
CommandIdtoPhrase - Support multiple profiles (e.g., "default", "vim", "gaming")
- Represented by
The codebase follows a contracts-models-services architecture with SOLID principles:
Contracts (Interfaces):
ICommandRegistry- Command registration and retrievalIProfileManager- Profile lifecycle managementIKeybindingService- Keybinding coordination between commands and profilesIKeybindingRepository- Persistence operationsIKeybindingConfiguration- Configuration abstractionIKeybindingManagerFactory- Factory for creating managers
Models:
- Musical types:
Note,Chord,Phrase - Domain types:
Command,Profile,CommandId,ProfileId, etc. - Semantic string types (using ktsu.SemanticString)
- All models are immutable where possible
Services:
CommandRegistry- Thread-safe command storage (usesConcurrentDictionary)ProfileManager- Profile management with lockingKeybindingService- Coordinates commands and profilesJsonKeybindingRepository- JSON-based persistence
Facade:
KeybindingManager- Main entry point coordinating all services
The library supports both standalone and dependency injection usage:
Standalone:
var manager = new KeybindingManager("./data");
await manager.InitializeAsync();Dependency Injection:
services.AddKeybinding("./data"); // Singleton with default repository
services.AddKeybindingScoped(); // Scoped lifetime
services.AddKeybinding<CustomRepository>(); // Custom repository implementationDefault JSON-based storage structure:
data-directory/
├── commands.json # All registered commands
├── active-profile.json # ID of currently active profile
└── profiles/
├── default.json # Default profile phrase bindings
└── custom.json # Custom profile phrase bindings
- Each class has single responsibility
- Interfaces enable extensibility without modification
- Helper classes eliminate repeated patterns:
ValidationHelper- Common validation operationsDisposalHelper- Consistent disposal checkingAsyncBatchHelper- Async iteration patternsOperationHelper- Error-handling loops
- Use semantic string types from ktsu.SemanticString with validation attributes
- Convert strings using
.As<SemanticString>()extensions (not casts) - One type per file, avoid nested types
- Use enums,
nameof, and consts to reduce hard-coded strings - Cache
CompositeFormatinstances for string formatting - Catch specific exception types (CA1031)
- Use
DisposalHelper.ThrowIfDisposed()for disposal checking
- Keys are stored in uppercase internally in
NoteName Chord.ToString()formats modifier keys with proper casing for display- Modifier keys ordered consistently: Ctrl, Alt, Shift, Meta
- Modifiers normalized: "Control" → "Ctrl", "Win"/"Windows"/"Cmd"/"Command" → "Meta"
- Thread-safe operations in all services
- All async operations use
.ConfigureAwait(false)
Keybinding.Core/ # Main library
├── Contracts/ # Interfaces
├── Models/ # Domain models and musical types
├── Services/ # Service implementations
├── Extensions/ # Extension methods (DI registration)
├── Helpers/ # Helper classes
└── Constants.cs # Shared constants
Keybinding.Test/ # Unit tests
Keybinding.Demo/ # Demo console application
Examples/ # Example applications
└── Keybinding.WebAPI.Example/ # Web API DI example
dotnet format
dotnet test- Uses .NET 9.0 (
global.json) - Central Package Management (
Directory.Packages.props) - Custom SDK:
ktsu.Sdk.Libfor library projects - SonarQube integration for code quality
- Code coverage via Coverlet
.runsettingsconfigured for coverage collection
- GitHub Actions workflow in
.github/workflows/dotnet.yml - Custom PowerShell build module:
scripts/PSBuild.psm1 - Automated versioning, building, testing, packaging, and releasing
- SonarQube analysis when
SONAR_TOKENis available - Winget manifest generation for releases
- Security scanning via component detection
- Console apps must set UTF-8 encoding at startup
- Use Spectre.Console's
AnsiConsolefor all console writing - Core library should not output to console (functionality belongs in Demo app)
- Use validation attributes on semantic strings (see ktsu.SemanticString examples)
- Follow patterns from
https://github.com/ktsu-dev/SemanticString/blob/main/SemanticString/SemanticStringValidationAttributes.cs
TAGS.mdandDESCRIPTION.mdshould havestatus: draftfrontmatter- Update README before releasing
CHANGELOG.mdtracked for version management