Agents meta-library-dev
Develop reusable library code across languages. Use when designing public APIs, organizing modules, managing versioning, or creating utility libraries. Provides foundational patterns that language-specific *-lib-* skills extend.
git clone https://github.com/aRustyDev/agents
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/meta-library-dev" ~/.claude/skills/arustydev-agents-meta-library-dev && rm -rf "$T"
content/skills/meta-library-dev/SKILL.mdLibrary Development Patterns
Foundational patterns for developing reusable library code across programming languages. This meta-skill provides guidance that language-specific library skills (e.g.,
rust-lib-dev, python-lib-dev) extend.
When to Use This Skill
- Designing library public APIs
- Organizing modules and package structure
- Managing semantic versioning and compatibility
- Creating utility libraries, frameworks, or toolkits
- Establishing documentation and testing standards for libraries
- Publishing and distributing packages
This Skill Does NOT Cover
- SDK development (wrapping APIs/services) - see
meta-sdk-patterns-eng - Application architecture - see
architecture-patterns - Language-specific implementation details - see
skills*-lib-* - CLI tool development - see
skills*-cli-*
Library vs SDK: Key Distinctions
| Aspect | Library | SDK |
|---|---|---|
| Purpose | Reusable functionality | API/service wrapper |
| Dependencies | Minimal, self-contained | Tied to external service |
| Versioning | Independent semver | Often tracks API version |
| Examples | lodash, requests, serde | AWS SDK, Stripe SDK |
| Testing | Unit/integration tests | Mocks external service |
Public API Design
Principle: Minimal Surface Area
Expose only what users need. Everything public becomes a maintenance commitment.
Good: 3-5 primary exports that compose Bad: 50 exports that users must navigate
Export Patterns
Layered Exports:
library/ ├── mod.rs (or index.ts, __init__.py) # Primary public API ├── types.rs # Public types/interfaces ├── errors.rs # Error types └── internal/ # Private implementation └── ...
Explicit vs Glob Exports:
# Preferred: Explicit exports from .core import parse, validate, transform __all__ = ["parse", "validate", "transform"] # Avoid: Glob re-exports (hides what's public) from .core import *
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Functions | Verb-first, descriptive | , |
| Types/Classes | Noun, singular | , |
| Constants | SCREAMING_SNAKE | , |
| Modules | Lowercase, descriptive | , |
Function Signatures
Prefer Specific Over Generic:
// Good: Clear intent fn parse_config(path: &Path) -> Result<Config, ParseError> // Avoid: Too generic, unclear fn parse(input: &str) -> Result<Value, Error>
Parameter Guidelines:
- 0-3 parameters: Use positional
- 4+ parameters: Use options object/struct
- Required first, optional last
- Sensible defaults for optional parameters
// Good: Options object for many parameters interface ParseOptions { encoding?: string; strict?: boolean; maxDepth?: number; } function parse(input: string, options?: ParseOptions): Result // Avoid: Too many positional parameters function parse(input: string, encoding: string, strict: boolean, maxDepth: number): Result
Module Organization
Standard Structure
library/ ├── src/ │ ├── lib.rs # Rust: Library root │ ├── index.ts # TS: Package entry │ ├── __init__.py # Python: Package init │ │ │ ├── core/ # Core functionality │ │ ├── mod.rs │ │ ├── parser.rs │ │ └── validator.rs │ │ │ ├── types/ # Type definitions │ │ ├── mod.rs │ │ └── config.rs │ │ │ ├── errors/ # Error types │ │ ├── mod.rs │ │ └── parse_error.rs │ │ │ └── utils/ # Internal utilities │ └── ... │ ├── tests/ # Integration tests ├── examples/ # Usage examples ├── benches/ # Benchmarks (if applicable) └── docs/ # Additional documentation
Module Cohesion Principles
High Cohesion:
- Modules should do one thing well
- Related functionality stays together
- Clear boundaries between modules
Low Coupling:
- Modules should be independently testable
- Minimize cross-module dependencies
- Use dependency injection for flexibility
Re-export Patterns
Facade Pattern for Complex Libraries:
// lib.rs - Clean public interface pub use crate::core::{parse, validate}; pub use crate::types::{Config, Result}; pub use crate::errors::ParseError; // Internal modules hidden mod core; mod types; mod errors; mod utils; // Not re-exported
Versioning Strategy
Semantic Versioning (SemVer)
MAJOR.MINOR.PATCH MAJOR: Breaking changes MINOR: New features (backward compatible) PATCH: Bug fixes (backward compatible)
What Constitutes Breaking Changes?
| Change Type | Breaking? | Version Bump |
|---|---|---|
| Remove public function | Yes | MAJOR |
| Change function signature | Yes | MAJOR |
| Add required parameter | Yes | MAJOR |
| Add optional parameter | No | MINOR |
| Add new function | No | MINOR |
| Fix bug in existing behavior | No | PATCH |
| Performance improvement | No | PATCH |
Pre-release Versions
0.x.y - Initial development (breaking changes allowed in minor) 1.0.0-alpha.1 - Alpha release 1.0.0-beta.1 - Beta release 1.0.0-rc.1 - Release candidate 1.0.0 - Stable release
Deprecation Process
- Announce deprecation with
annotation@deprecated - Document migration path in deprecation notice
- Maintain deprecated API for at least one minor version
- Remove in next major version
import warnings def old_function(): """ .. deprecated:: 2.0.0 Use :func:`new_function` instead. """ warnings.warn( "old_function is deprecated, use new_function instead", DeprecationWarning, stacklevel=2 ) return new_function()
Error Handling
Error Type Design
Hierarchical Error Types:
#[derive(Debug, thiserror::Error)] pub enum LibraryError { #[error("Parse error: {0}")] Parse(#[from] ParseError), #[error("Validation error: {0}")] Validation(#[from] ValidationError), #[error("IO error: {0}")] Io(#[from] std::io::Error), } #[derive(Debug, thiserror::Error)] pub enum ParseError { #[error("Invalid syntax at line {line}: {message}")] InvalidSyntax { line: usize, message: String }, #[error("Unexpected token: {0}")] UnexpectedToken(String), }
Error Messages
Good Error Messages Include:
- What happened
- Where it happened (context)
- How to fix it (when possible)
# Good ParseError: Invalid JSON at line 42: expected ',' or '}' after object property # Bad Error: Parse failed
Result Types
Prefer Result Types Over Exceptions (where language supports):
// Rust: Result type pub fn parse(input: &str) -> Result<Document, ParseError> // TypeScript: Discriminated union type ParseResult = | { success: true; data: Document } | { success: false; error: ParseError };
Configuration Patterns
Configuration Hierarchy
1. Defaults (in code) 2. Config file 3. Environment variables 4. Explicit parameters
Builder Pattern for Configuration
let config = ConfigBuilder::new() .timeout(Duration::from_secs(30)) .max_retries(3) .strict_mode(true) .build()?;
Validation at Construction
@dataclass class Config: timeout: int max_retries: int def __post_init__(self): if self.timeout <= 0: raise ValueError("timeout must be positive") if self.max_retries < 0: raise ValueError("max_retries cannot be negative")
Testing Strategies
Test Pyramid for Libraries
/\ / \ E2E (examples work) /----\ / \ Integration (modules work together) /--------\ / \ Unit (individual functions work) --------------
Testing Patterns
Property-Based Testing:
#[quickcheck] fn parse_roundtrip(input: ValidJson) -> bool { let parsed = parse(&input.0)?; let serialized = serialize(&parsed); input.0 == serialized }
Snapshot Testing:
test('parser output', () => { const result = parse(complexInput); expect(result).toMatchSnapshot(); });
Fuzz Testing:
#[fuzz] fn fuzz_parser(data: &[u8]) { // Should not panic on any input let _ = parse(data); }
Example-Driven Testing
def parse_date(s: str) -> date: """Parse a date string. Examples: >>> parse_date("2024-01-15") date(2024, 1, 15) >>> parse_date("invalid") Traceback (most recent call last): ... ValueError: Invalid date format """
Documentation Standards
Documentation Hierarchy
README.md # Quick start, installation docs/ ├── guide/ # Tutorials, how-tos ├── reference/ # API documentation └── examples/ # Runnable examples
API Documentation Requirements
Every public item needs:
- Summary - One-line description
- Description - Detailed explanation (if needed)
- Parameters - Each parameter documented
- Returns - Return value documented
- Errors - Possible errors documented
- Examples - At least one usage example
/// Parses a configuration file into a Config struct. /// /// Reads the file at the given path and parses it as TOML format. /// The file must exist and be valid UTF-8. /// /// # Arguments /// /// * `path` - Path to the configuration file /// /// # Returns /// /// A `Config` struct containing the parsed configuration. /// /// # Errors /// /// Returns `ParseError::Io` if the file cannot be read. /// Returns `ParseError::InvalidSyntax` if the TOML is malformed. /// /// # Examples /// /// ``` /// let config = parse_config("config.toml")?; /// assert_eq!(config.timeout, 30); /// ``` pub fn parse_config(path: &Path) -> Result<Config, ParseError> { // ... }
README Template
# Library Name Brief description of what the library does. ## Installation \`\`\`bash # Package manager command \`\`\` ## Quick Start \`\`\`language // Minimal working example \`\`\` ## Features - Feature 1 - Feature 2 - Feature 3 ## Documentation - [Guide](docs/guide/) - [API Reference](docs/reference/) - [Examples](examples/) ## License [License type]
Compatibility Considerations
Minimum Supported Version
Document and test against minimum versions:
# Cargo.toml rust-version = "1.70" # pyproject.toml requires-python = ">=3.9" # package.json "engines": { "node": ">=18.0.0" }
Feature Flags
Use feature flags for optional functionality:
# Cargo.toml [features] default = ["json"] json = ["serde_json"] yaml = ["serde_yaml"] async = ["tokio"]
Backward Compatibility Strategies
- Additive changes only in minor versions
- Feature flags for optional new features
- Type aliases to rename types without breaking
- Default parameters instead of signature changes
Publishing and Distribution
Pre-publish Checklist
- Version bumped appropriately
- CHANGELOG updated
- All tests passing
- Documentation updated
- Examples work with new version
- No accidental breaking changes
- License file present
- README accurate
Registry-Specific Guidelines
| Registry | Key Requirements |
|---|---|
| crates.io | Cargo.toml metadata complete, license |
| npm | package.json fields, TypeScript types |
| PyPI | pyproject.toml, classifiers, requires-python |
| Maven | pom.xml, GPG signing, Javadoc |
Changelog Format
Follow Keep a Changelog:
# Changelog ## [Unreleased] ## [2.1.0] - 2024-01-15 ### Added - New `parse_strict` function for strict parsing ### Changed - Improved error messages for parse failures ### Deprecated - `parse_loose` is deprecated, use `parse` with `strict: false` ## [2.0.0] - 2024-01-01 ### Changed - **BREAKING**: Renamed `Config` to `Settings` - **BREAKING**: `parse` now returns `Result` instead of panicking
Performance Considerations
Avoid Premature Optimization
- Correctness first - Make it work
- Clarity second - Make it readable
- Performance third - Make it fast (if needed)
Common Performance Patterns
Zero-Copy Where Possible:
// Good: Borrow instead of clone fn process(data: &str) -> Result<&str, Error> // Avoid: Unnecessary allocation fn process(data: &str) -> Result<String, Error>
Lazy Evaluation:
// Good: Iterator (lazy) fn items(&self) -> impl Iterator<Item = &Item> // Avoid: Collect everything (eager) fn items(&self) -> Vec<Item>
Benchmark Before Optimizing:
#[bench] fn bench_parse(b: &mut Bencher) { let input = include_str!("../fixtures/large.json"); b.iter(|| parse(input)); }
Extension Points
Plugin Architecture
pub trait Plugin { fn name(&self) -> &str; fn process(&self, input: &Document) -> Result<Document, Error>; } pub struct Library { plugins: Vec<Box<dyn Plugin>>, } impl Library { pub fn register_plugin(&mut self, plugin: impl Plugin + 'static) { self.plugins.push(Box::new(plugin)); } }
Callback Hooks
interface Hooks { beforeParse?: (input: string) => string; afterParse?: (result: Document) => Document; onError?: (error: Error) => void; } function parse(input: string, hooks?: Hooks): Document { const processed = hooks?.beforeParse?.(input) ?? input; // ... }
Anti-Patterns to Avoid
1. God Module
Problem: Single module with too many responsibilities Solution: Split by domain/functionality
2. Leaky Abstractions
Problem: Internal implementation details exposed in public API Solution: Clear public/private boundaries
3. Unstable Dependencies
Problem: Depending on unstable/pre-1.0 libraries in public API Solution: Wrap or re-export with stable types
4. Breaking Changes in Patches
Problem: Changing behavior in patch releases Solution: Strict semver adherence
5. Missing Error Context
Problem: Generic errors that don't help debugging Solution: Rich error types with context
6. Documentation Drift
Problem: Docs out of sync with code Solution: Doc tests, examples in tests
References
- Semantic Versioning
- Keep a Changelog
- Rust API Guidelines
- The Little Manual of API Design
- Language-specific:
skills*-lib-* - SDK development:
meta-sdk-patterns-eng