# Contributing
`creditlint` uses OpenSpec for specification-driven development.
## Before Coding
Read the active change:
```sh
pnpm dlx @fission-ai/openspec show bootstrap-creditlint-mvp
```
Validate the current OpenSpec state:
```sh
pnpm dlx @fission-ai/openspec validate --all
```
Implementation should follow:
```text
openspec/changes/bootstrap-creditlint-mvp/tasks.md
```
## Tooling
Use Cargo for Rust implementation work.
Use pnpm for OpenSpec commands and for the optional npm wrapper package under
`packages/creditlint`. Do not add Yarn or Bun runtime requirements for `creditlint`.
Do not make Node.js required for users who consume the native binary, crates.io
crate, or GitHub Release artifacts.
Expected Rust tooling:
- Rust stable from `rust-toolchain.toml`
- `rustfmt` and `clippy`
- `just` for project command shortcuts once available
- `cargo-nextest` for the preferred test workflow once configured
Optional local tools:
- `cargo-watch` for edit/test loops
- `bacon` if you personally prefer it
- `cargo-edit` for local dependency editing
Release-only tooling:
- `cross` or an equivalent cross-platform release builder
Recommended local setup:
```sh
cargo install just
cargo install cargo-nextest
cargo install cross
```
Common development commands:
```sh
just check
just fmt
just lint
just test
just test-npm
just ci
just release-build
just cross-build x86_64-unknown-linux-gnu
```
Optional npm wrapper commands:
```sh
pnpm install
pnpm --filter creditlint test
cargo build
CREDITLINT_BIN="$PWD/target/debug/creditlint" pnpm --filter creditlint run creditlint --help
```
The npm wrapper should remain usable by frontend and JavaScript-focused users
without a Rust toolchain. Publishable npm releases must provide native binaries
through platform optional packages under `packages/creditlint-*`; `CREDITLINT_BIN`
and Cargo build output fallback paths are for tests and repository development.
Use the repository script for manual npm publishing so platform packages are
published before the main wrapper package:
```sh
scripts/bootstrap-npm-trust-packages.sh --dry-run
scripts/bootstrap-npm-trust-packages.sh --execute
scripts/publish-npm-packages.sh --dry-run --stage-local
scripts/publish-npm-packages.sh --dry-run
scripts/publish-npm-packages.sh --execute
```
Use the bootstrap script only to create placeholder package records for npm
trusted publishing. It publishes `0.0.0-trust.0` with the `bootstrap` dist-tag
and should not be promoted to `latest`. The publish scripts default to
each package's `publishConfig.registry`, so local mirror config does not
accidentally catch publish commands.
The script stages native binaries from `dist/npm/` when that directory exists.
Use `--stage-local` only for current-platform dry runs; do not use it to publish
all platform packages from one local binary.
## Commit Metadata
This project is specifically about credit and authorship metadata. Contributors
should avoid adding tool-authorship markers such as:
```text
Co-authored-by: Codex <...>
Made with ...
Generated with ...
```
If process disclosure is needed, prefer explicit provenance metadata documented
by the project rather than authorship trailers.
## Threat Model Notes
The current MVP is intended to stop default or accidental credit/authorship
markers in normal Git workflows.
It is not yet intended to fully defeat deliberate evasion techniques such as:
- Unicode homoglyph spoofing
- Obfuscated or split markers
- Administrator bypass of repository rules
- Direct protected-branch writes
## Pull Requests
Pull requests should describe:
- The OpenSpec task IDs completed.
- Tests run.
- Any behavior that differs from the active spec.
Do not mark an OpenSpec task complete unless the implementation and tests for
that task are actually done.
## GitHub Actions Notes
When documenting or updating CI examples:
- Use Cargo or a native binary artifact path. Do not introduce Node-based
wrappers as the default integration path.
- Keep npm wrapper validation separate from Rust-native usage examples.
- Use full-history checkout for range checks:
```yaml
- uses: actions/checkout@v4
with:
fetch-depth: 0
```
- Keep the range check explicit, for example:
```sh
./target/release/creditlint check --range origin/${{ github.base_ref }}..HEAD
```
Shallow fetches can make `check --range` fail because the base commit is not
available locally.
## Delivery Notes
The repository is now in delivery preparation, not just bootstrap development.
Delivery expectations:
- CI validates workflow syntax, source builds, tests, OpenSpec artifacts, and
release-binary smoke execution.
- The release workflow builds native artifacts for Linux, macOS, and Windows.
- Tagged releases should publish those binaries and `SHA256SUMS` as GitHub
Release assets.
- Tagged releases should publish the crate to crates.io when
`CARGO_REGISTRY_TOKEN` is configured.
- Users should not need a Rust toolchain when consuming prebuilt binaries.
Required delivery configuration:
- job-scoped `permissions: contents: write` for GitHub Release asset upload
- repository secret `CARGO_REGISTRY_TOKEN` for `cargo publish`
## Local Hook Notes
Prefer the managed hook path for local testing:
```sh
creditlint init
creditlint install-hook
```
The managed installer is intentionally conservative:
- It writes a `commit-msg` hook that runs `creditlint check --message-file`.
- It replaces only hooks that already contain the stable `creditlint managed
hook` marker and version field.
- It refuses to overwrite unmanaged hooks.
When a repository already owns its `commit-msg` hook, document manual
integration by adding this command to the existing hook:
```sh
creditlint check --message-file "$1"
```
## Pull Request Text Notes
Document pull request title/body validation as a separate check surface from
commit ranges.
Recommended pattern:
```sh
printf '%s\n\n%s\n' "$PR_TITLE" "$PR_BODY" > /tmp/creditlint-pr-message.txt
creditlint check --message-file /tmp/creditlint-pr-message.txt
```
This is especially important for squash-merge workflows where platform-generated
final commit messages can inherit pull request text.
## Privacy Notes
Keep the default privacy boundary explicit in docs and reviews:
- No hosted service is required for normal CLI use.
- Commit messages, pull request text, and policy files stay local by default.
- Network access is not part of the default evaluation flow.
Do not casually add telemetry, background syncing, or remote policy lookups
without a separate OpenSpec change.
## GitHub Ruleset Notes
When documenting GitHub squash-merge protection, distinguish these layers:
- `creditlint check --range` validates pull request commit messages.
- `creditlint github ruleset-pattern` exports a conservative regex for GitHub
commit-message metadata restrictions.
- The GitHub ruleset is the platform-side control for the final squash message.
Do not describe a pull request range check as if it guarantees the final squash
commit message produced or edited in the GitHub UI.
## Merge Bot Notes
When a repository uses a controlled merge bot, document final-message
validation explicitly:
```sh
creditlint check --message-file final-merge-message.txt
```
The merge bot should run this against the exact message it is about to write,
then fail closed on exit code `1` or `2`.
## Ruleset Export Notes
Keep the documented GitHub ruleset export boundary aligned with the current
implementation.
Supported subset:
- exact forbidden trailer keys
- trailer value exact strings, unanchored regexes, or `Any`
- free-form rules expressed as one anchored line regex
- no overlap between allowed provenance keys and forbidden trailer keys
Unsupported subset:
- precedence-sensitive overlap on the same trailer key
- regex-matched trailer field names
- non-anchored free-form prose matching
- normalization or multi-pass logic
## Versioning And Changelog Notes
Until the first public release, keep release notes in `CHANGELOG.md` under
`Unreleased`.
When cutting a release:
- move shipped items into a dated version heading
- bump `Cargo.toml` version in the same change
- keep SemVer discipline for CLI behavior, config schema, and output contracts