Contributing
Thanks for wanting to improve Differens. This guide covers cloning the repo, making changes, and shipping a release.Code of conduct
Be respectful. Differens is a small project maintained by people with day jobs, so:- Assume good intent in reviews and discussions
- Give constructive feedback. Say what is wrong and why, not just that it is wrong
- Be patient: maintainers review in their spare time
- Keep discussions on the topic of the project
Getting started
Clone the repo and install dependencies:bun --version to check) and Git (needed for the git integration tests).
Verify everything works:
No build step is needed to run the CLI from source. Bun runs TypeScript directly:
Making changes
- Pick or open an issue. Say what you want to work on so nobody else starts the same thing.
- Branch from
main. Give the branch a short descriptive name. - Write code + tests. Every behavior change gets a test.
- Run
bun run test. Must be all green. - Run
bun run lint. Must be clean (Biome). - Open a PR against
main. Reference the issue in the description.
Commit style
Human-readable, past-tense, lowercase after the first word. Write it as a sentence describing what the diff did. Good:Added Python extractor for async/await node typesFixed root-level insertion not emitted when parent is nullReplaced recursive tree walk with iterative stack to avoid stack overflow
fix: root insert. Too terse, does not say what or whyImplemented comprehensive solution for edge case handling in the tree matching subsystem. Too verbose to skim
Formatting
Biome handles formatting and linting. Runbun run format before committing, or let your editor do it on save.
Adding a language extractor
Extractors map tree-sitter node types to canonical concepts (Function, Class, Method, Import, …) so diffs read as “renamed method” instead of “renamed node method_definition”. Languages without an extractor still work. They fall back to structural diff with raw tree-sitter node labels.
- Create
packages/tiers/src/code/<language>.ts - Implement the
LanguageExtractorinterface fromextractor.ts - Map tree-sitter node types to canonical concepts (Function, Class, Method, Import, etc.)
- Register the extractor in
code/index.tsensureInitialized() - Add tests in
packages/tiers/src/index.test.ts - Run
bun test packages/tiers/src/
Adding a tier adapter
Adapters sit on the abstraction ladder: T0 is raw bytes, T5 is code. Each tier parses its content into aNode tree that the one diff core consumes. The core does not need to know where its input came from.
- Create the adapter in
packages/tiers/src/ - It must produce a
Nodetree (seepackages/core/src/index.tsfor the interface) - Register it in the
diffWithTier()switch statement inpackages/tiers/src/index.ts - Add to
classifyFile()if it has specific file extensions - Pick a tier number that reflects its position on the abstraction ladder (T0=bytes, T5=code)
- Add tests in
packages/tiers/src/index.test.ts
Graceful degradation is a core design rule: every tier can fall back to the one below it. A new adapter should not leave a file type un-diffable. If the adapter fails, the tier below takes over and the file still gets a diff.
Project structure
A textual map of the same layout:packages/core/src/index.ts for the matching algorithm; packages/tiers/src/index.ts for the content router and adapter pipeline.
Release process
Seven packages go out in dependency order: the five libraries, then the CLI under bothdifferens and @ossl-dev/differens-cli from one build.
From the repo root:
-- passes through to npm publish, so --otp=123456 is how a one-time code gets in when the account uses app-based 2FA. A passkey has no code. npm falls back to a browser challenge, which needs a real terminal. Run releases from your own shell, not from a CI step or a pipe.
The release is resumable: a version already on the registry is skipped rather than retried, and a scope the account cannot publish into skips only its own packages. scripts/publish.ts stages each publish into a temp directory and never rewrites the repo manifests, so an interrupted run cannot leave the workspace half-published.
Where to ask questions
- GitHub issues. Bugs, feature requests, and questions about behavior. Search first; your question may already be answered.
- Pull requests. Discuss code changes in the PR thread.
- The repository itself.
DEVELOPMENT_GUIDE.mdat the repo root is the source of truth for development workflow and architecture notes.