# Contributing to Lintro

Thank you for your interest in contributing to Lintro! This document provides guidelines
and information for contributors.

## Conventional Commits (required)

We use Conventional Commits (Angular style) to drive automated versioning and releases.

- Format: `type(scope): subject` (scope optional)
- Types: feat, fix, docs, refactor, perf, test, chore, ci, style, revert
- Use imperative mood (e.g., "add", not "added").

Examples:

```text
feat: add new configuration option

- support for custom tool paths
- update documentation
- add integration tests
```

PR titles must also follow Conventional Commits. A PR check enforces this and will
comment with guidance if invalid.

### PR titles and version bumps

Semantic versioning is determined from the PR title (we squash-merge so the PR title
becomes the merge commit):

- **minor**: `feat(...)`
- **patch**: `fix(...)`, `perf(...)`
- **major**: add `!` after type or include a `BREAKING CHANGE:` footer
- **no bump**: `docs`, `chore`, `refactor`, `style`, `test`, `ci`, `build` (unless
  marked breaking)

Examples:

```text
feat(cli): add --group-by option            # minor
fix(parser): handle empty config            # patch
perf(ruff): speed up large file handling    # patch
refactor(core)!: rewrite execution model    # major (breaking)
chore: update dependencies                  # no bump

# Footer form for breaking change
refactor(core): unify plugin interfaces

BREAKING CHANGE: plugins must implement run() and report()
```

Notes:

- Use imperative mood (e.g., "add", not "added").
- If work is ambiguous (e.g., a large refactor), explicitly signal with `!` or a
  `BREAKING CHANGE:` footer.
- The PR title validator (`.github/workflows/semantic-pr-title.yml`) enforces the format
  before merge.

## Developer Certificate of Origin (required)

All contributions must be signed off under the Developer Certificate of Origin (DCO).
Use `git commit -s` (or `--signoff`) so your commit message contains a line like:

```text
Signed-off-by: Your Name <your.email@example.com>
```

See `DCO.md` for details. Pull requests without DCO sign-offs will be asked to amend
commits before merge.

## Quick Start

1. Clone the repository:

   ```bash
   git clone https://github.com/lgtm-hq/py-lintro.git
   cd py-lintro
   ```

2. Install dependencies:

   ```bash
   uv sync --dev
   ```

3. Run tests:

   ```bash
   ./scripts/local/run-tests.sh
   ```

4. Run Lintro on the codebase:

   ```bash
   ./scripts/local/local-lintro.sh check --output-format grid
   ```

## More Information

Release automation:

- Merges to `main` run `release-version-pr.yml` to open a version bump PR; after merge,
  `release-auto-tag.yml` creates the tag.
- Tag push publishes to PyPI (OIDC) and creates a GitHub Release with artifacts.

For detailed contribution guidelines, see the project documentation or contact a
maintainer.

---

## Tool Integration Guidelines

This section describes how to add a new external tool to Lintro while keeping a
consistent UX and maintainable implementation.

### Principles

- Minimal defaults: invoke the native CLI without forcing special formats.
- Predictable discovery: put file-discovery rules into the tool implementation (e.g.,
  Actionlint filters to `/.github/workflows/`).
- Wrapper, not replacement: rely on tool defaults and parse its standard output; only
  add switches when strictly necessary for parsing.

### Implementation Steps

<!-- markdownlint-disable MD029 -- numbering restarts after nested blocks -->

1. Core code

- Create a tool plugin in `lintro/tools/definitions/` (subclass `BaseToolPlugin`, use
  `@register_tool`).
- Implement `definition` property returning `ToolDefinition`.
- Implement `check()` (and `fix()` only if the tool supports auto-fixes).
- Add a parser module in `lintro/parsers/<tool>/` for the tool's default output.
- Use unified formatter in `lintro/formatters/unified` (no tool-specific formatter
  needed).

2. Tests

- Put minimal violation samples in `test_samples/`.
- Add unit tests for the parser.
- Add integration tests that:
  - Run the tool directly (CLI) on the sample
  - Run the tool via Lintro and compare parity (issue counts, status)
  - Use realistic paths (e.g., `.github/workflows/` for Actionlint)
- Allow local skip when a system binary is missing (CI/Docker installs tools).

3. Installer support

- Update `scripts/utils/install-tools.sh` to install the binary.
- Prefer upstream installers when available (uv/pip, npm, official script, Homebrew).
  For OS/arch tarballs, encapsulate download/extract in a helper.
- Verify checksums when possible.

### Tool Version Sources (Including Companion Packages)

- `lintro/tools/manifest.json` is the canonical source of external tool versions used by
  Docker/CI. Add new tool binaries here.
- npm-managed tool versions come from `package.json` via `_NPM_PACKAGE_TO_TOOL` in
  `lintro/_tool_versions.py`.
- If a tool requires companion npm packages (for example, a checker plugin that ships
  alongside the main tool), add those packages to `package.json` and to
  `_COMPANION_NPM_PACKAGES` in `lintro/_tool_versions.py`.
- Use `get_tool_version("<npm-package-name>")` in `scripts/utils/install-tools.sh` to
  resolve companion package versions.
- Do not add companion packages to the manifest unless they provide a standalone binary
  tool.
- Each manifest tool entry has `version` (recommended/tested) and optional `min_version`
  (hard parser compatibility floor). If you change a parser, verify whether
  `min_version` must be bumped. Renovate updates `version` only.

4. Docs checklist

- Update `README.md` Supported Tools table and CLI examples.
- Update `docs/getting-started.md` with a short usage example.
- Update `docs/configuration.md` with discovery notes and usage tips.

5. CI and Docker

- The Docker image installs all supported tools; integration tests should run without
  skipping inside Docker/CI.

### Pass-through Options (optional)

Expose native flags via `--tool-options tool:key=value` only after the core behavior is
stable. Keep defaults minimal to avoid surprising users and to maintain parity with
direct CLI behavior.

---

## Code Review Process

All contributions go through code review:

1. **Automated checks**: CI runs tests, linting, and security audits
2. **Manual review**: A maintainer reviews code quality and design
3. **Approval required**: At least one approval before merge
4. **Squash merge**: PRs are squash-merged with the PR title as commit message

### What reviewers look for

- Code follows the [style guide](style-guide.md)
- Tests cover new functionality
- Documentation is updated if needed
- No security vulnerabilities introduced
- Commit messages follow Conventional Commits

---

## Merge Discipline

Passing CI and clean reviews are enforced, not merely encouraged. The following norms
are mandatory for every PR merged to `main`.

### All required checks must be green

- Every status check marked as required by branch protection or repository rulesets must
  pass before merge. Use the exact check-run names reported by GitHub when configuring
  or auditing the required-check list.
- **Never merge red.** A failing or in-progress required check blocks the merge. Do not
  merge past a failure "to fix it in a follow-up" — fix it first, or revert.
- Admin bypass of a failing check is not a normal workflow. If an override is ever
  unavoidable, it must be explicit and logged, with the reason recorded on the PR.

### All review threads must be resolved

- Every review conversation must be **resolved** before merge, not just replied to.
  Replying to a comment is not the same as resolving it.
- Resolve a thread only when the underlying concern is actually addressed (code changed,
  or the reviewer agrees no change is needed). Leave the resolution to reflect real
  agreement, not to clear the merge button.
- If a comment surfaces follow-up work that is genuinely out of scope, open a tracked
  issue and link it in the thread before resolving.

### No papering over linters

Do not weaken a gate to make a linter pass. Specifically, **without a written
justification in the diff** (a code comment or the PR description explaining why), do
not:

- Add `.lintro-ignore` entries (or equivalent per-tool ignore/suppression rules).
- Raise or add module-size baselines to absorb a new violation.
- Downgrade a quality gate — lower a severity threshold, disable a check, or loosen a
  failure condition — to get a green run.

The default is to fix the underlying issue. Suppressions and baseline bumps are a last
resort that must be visible and explained in the same change that introduces them, so a
reviewer can weigh the trade-off deliberately.

---

## Local Development Commands

> See [Quick Start](#quick-start) above for initial setup. This section covers
> additional development workflows.

### Run the test suite

```bash
# Run all tests
./scripts/local/run-tests.sh

# Run with coverage
uv run pytest --cov=lintro --cov-report=term-missing

# Run specific tests
uv run pytest tests/unit/tools/ -v
```

### Run linting locally

```bash
# Full lintro check on the codebase
./scripts/local/local-lintro.sh check --output-format grid

# Format code
./scripts/local/local-lintro.sh format

# Run specific tools
uv run ruff check .
uv run mypy lintro/
```

### Build and test locally

```bash
# Install in development mode
uv sync --dev

# Build package
uv run python -m build

# Test the built package
uv pip install dist/*.whl
lintro --version
```

---

## Additional Resources

- [Style Guide](style-guide.md) - Code style and conventions
- [Architecture](architecture/ARCHITECTURE.md) - System design and decisions
- [Troubleshooting](troubleshooting.md) - Common issues and solutions
