Metadata-Version: 2.3
Name: kigit
Version: 1.0.0
Summary: KiCad-semantics aware git diff and git status CLI tool
Author: RUB-SELAB-2026 group 1
Requires-Dist: typer>=0.25.1
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# kigit

**A KiCad-aware `git diff` for your schematics and boards.**

## Acknowledgements

`kigit` is developed as a Software Engineering course project for and with help from
Auto-Intern GmbH, whose hardware team uses KiCad and Git for industrial monitoring
systems. The goal is to help engineers make better commit decisions and to keep project
history readable, with the intention of maintaining it as an open-source tool for the
wider KiCad community. This tool is being developed by group 1 of this course.

---

[![PyPI version](https://img.shields.io/pypi/v/kigit.svg)](https://pypi.org/project/kigit/)
[![Python versions](https://img.shields.io/pypi/pyversions/kigit.svg)](https://pypi.org/project/kigit/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

`kigit` is a command-line tool that makes Git output for [KiCad](https://www.kicad.org/)
projects readable. Raw `git diff` on a `.kicad_sch` file is dominated by formatting,
re-ordering, and tool-generated metadata, so it tells you *that* a file changed but not
*what* meaningfully changed. `kigit` cuts through that noise and reports the change in
electrical terms instead: which components changed, and which nets were added,
removed, renamed, or rewired.

> **Status: stable (1.0).** `kigit` covers schematic components, the netlist, board
> placement, and a board-level statistics summary, and intentionally does a small set
> of things well rather than a large set adequately. The command-line interface and
> the meaning of the output are now stable: within 1.x they will only be extended, and
> anything that would change what an existing invocation does waits for 2.0. PCB
> routing is deliberately still out of scope — see
> [What it does not do](#what-it-does-not-do). Bug reports and feedback are very
> welcome.

---

## What it does

`kigit` answers questions a plain text diff struggles with, such as *"Did the wiring
actually change, or is this just KiCad reshuffling the file?"*

For the schematic in your repository it:

- locates the schematic via your project (`.kicad_pro`) file,
- exports the **netlist** for two project states using KiCad's own `kicad-cli`,
- compares the two states semantically - components and netlists - ignoring
  text-level noise, and
- prints a summary that opens with change counts and one impact line, followed (with
  `-v` or `-vv`) by as much detail as you asked for.

For **components** it reports parts that were **added**, **removed**, **changed**
(value, footprint, sourcing fields, documentation, ...), and **renamed**. A pure
re-annotation - `R42` becoming `R14` with everything else identical - is reported as
one rename, and is *also* fed into the net comparison, so a renamed part does not
show up as fake rewiring on every net it touches. A rename is only claimed when it
can be substantiated; see [When a change counts as a rename](#when-a-change-counts-as-a-rename).

For **nets** it reports nets that were **added**, **removed**, **changed** (pins
added, removed, or modified; net-class changes), and **renamed** (a net whose
connections are unchanged but whose name differs). When several pins move between
nets together, `kigit` additionally recognizes the pattern - a net **split**, **merge**,
or **move** - and reports it as one event; see
[Reading the output](#reading-the-output). Each entry is annotated with semantic tags
such as `power-net`, `ground-net`, `critical-net`, `high-speed-net`, connectivity
level, and impact tags like `connectivity-increased`, `node-added`, or
`pin-function-changed`. Purely cosmetic differences and unconnected
(`unconnected-*`) nets are filtered out.

When the project also has a board file (`.kicad_pcb`), `kigit diff` additionally reports
**placement** changes by exporting each version's position (pick-and-place) file and
comparing components by reference designator. It flags parts that were **moved** past a
small threshold (`component-moved`), **rotated** (`component-rotated`), or **flipped** to
the other side of the board (`side-changed`) — manual edits that are easy to miss in a
text diff but matter for assembly and clearance. Sub-threshold jitter from rounding is
ignored, and `-vv` shows the exact position, rotation, and side deltas. Projects
without a board file are unaffected.

When the project also has a PCB (`.kicad_pcb`), `kigit diff` additionally exports a
**board-level statistics summary** for both states via `kicad-cli` and reports the
numeric deltas: component count, board area, front/back copper area, and drill hole
count. Only fields that actually changed are shown (zero-difference fields are
suppressed), and a field that's missing from one of the two exports is treated as
unchanged rather than reported as a spurious change.

### What it does not do

`kigit` reports on the schematic, the netlist and the board's placement and summary
statistics. It does **not** analyze:

- **PCB routing** — copper pours, traces and zones. Component *placement* (position,
  rotation, side) and the board *statistics summary* are covered; the routing itself
  is not, so a re-route with no placement change shows up only as a shift in the
  board summary numbers.
- **Project settings** files (`.kicad_pro`, `.kicad_prl`) as changes in their own
  right. The `.kicad_pro` is read, because net classes live there and they affect the
  netlist, but an edit to it is reported through its effect on the netlist rather than
  as a settings diff.
- **Hierarchical-sheet-aware grouping.** Changes on sub-sheets are reported, but flat,
  not grouped by the sheet they came from.

These are candidates for later 1.x releases. None of them is a silent gap: a change
`kigit` cannot interpret still shows up in `git status`, and the board summary moves
when the copper does.

---

## Requirements

`kigit` orchestrates the tools you already use; it does not reimplement them.

- **Python** 3.10 or newer.
- **Git**, available on your `PATH`. 2.25 or newer lets `kigit` check out only the
  project directory rather than the whole repository; older versions work, just more
  slowly.
- **KiCad 10.x**, with the bundled **`kicad-cli`** available on your `PATH`.
  (`kicad-cli` ships with the standard KiCad installation on Windows, Linux, and macOS.)

`kigit` is cross-platform and runs on **Windows and Linux** (and other platforms where
Python, Git, and KiCad are available). Run `kigit check-health` at any time to confirm
your environment is ready.

---

## Installation

From PyPI with `pip`:

```bash
pip install kigit
```

Or as an isolated tool with [uv](https://docs.astral.sh/uv/):

```bash
uv tool install kigit
```

---

## Quick start

> **Prerequisite:** your schematic must be committed to Git at least once. `kigit`
> retrieves historical versions with `git show`, so an untracked schematic cannot be
> diffed.

```bash
# Compare your working changes against the last commit (the "status" use case):
kigit diff

# Compare two commits, branches, or tags:
kigit diff -c1 main -c2 my-feature-branch

# See which components, nets and fields changed:
kigit diff -v

# ... and the pin-by-pin detail underneath:
kigit diff -vv

# Confirm Git and KiCad are installed and reachable:
kigit check-health
```

---

## Commands

Run `kigit` or `kigit --help` for general help, and `kigit COMMAND --help` for details on
a specific command.

### `kigit diff`

Summarizes the differences between two KiCad project states, filtering out noise from
formatting, ordering, and tool-generated metadata.

| Option | Alias | Default | Description |
| --- | --- | --- | --- |
| `--c1-ref` | `-c1` | `HEAD` | Git reference (commit, branch, or tag) used as the **first** state for comparison. Validated up front: must resolve in the repository. |
| `--c2-ref` | `-c2` | *none* | Git reference used as the **second** state. When omitted, the first ref is compared against the **working-directory** schematic. When given, it must resolve in the repository. |
| `--search-depth` | `-p` | `2` | How many directory levels to descend when locating the project, schematic, and board files. Must be a non-negative integer. |
| `--search-directory` | `-d` | `.` | Root directory for the project, schematic, and board file lookup. Must be a path tracked in the repository. |
| `--jobs` | `-j` | `4` | How many `kicad-cli` exports to run at once. `-j 1` runs them one at a time. |
| `--no-cache` | | *(off)* | Re-export everything instead of reusing identical exports from earlier runs. |
| `--no-sparse` | | *(off)* | Check out the whole repository for each compared state instead of just the project directory. |
| `--timings` | | *(off)* | Print where the wall clock went, to stderr. |
| `--verbose` | `-v` | *(off)* | Repeatable. `-v` names the components, nets and fields that changed, with each field's old and new value; `-vv` adds the per-pin detail. See [Detail levels](#detail-levels). |

Behavior of the default invocation: with no `-c2`, `kigit diff` compares the first
reference (default `HEAD`) against the current schematic on disk — i.e. "what have I
changed since my last commit?". There is deliberately no separate `kigit status`
command: plain `kigit diff` *is* that view.

**Input validation.** `kigit diff` checks its arguments before any schematic
is exported or compared, so mistakes fail fast with a clear message instead of a confusing
downstream error:

- a `-c1` / `-c2` reference that does not resolve in the repository is rejected with
  *"Commit <ref> doesn't exist in this repository."*;
- a `-d` search directory that is not tracked in the repository is rejected with
  *"Path <dir> doesn't exist in this repository."*;
- a negative `-p` search depth is rejected as an invalid depth.

### `kigit check-health`

Verifies that all required dependencies (Git and `kicad-cli`) are present on the system
and reports any that are missing.

### `kigit cache clear`

Deletes every cached `kicad-cli` export for this repository. Cached exports are
addressed by content and cannot go stale on their own, so this is only needed to
measure a cold run or to reclaim the space. See [Speed](#speed).

### `kigit version`

Prints the installed `kigit` version.

---

## Reading the output

> Output is color-coded in your terminal; the examples in this section show the plain
> text.

Here is `kigit diff -vv` for a small, realistic edit: the feedback divider of a
3.3V regulator was rewired onto its own net, `R42` was renamed to `R14`, `R4` was
retuned, and a filter capacitor `C7` was added.

```text
Comparing 'HEAD~3' (9f31c2a, 2026-07-08) → 'working tree'

+ 1 component added   ~ 1 component changed   ~ 1 component renamed
⇄ 1 net split   + 2 nets added   - 1 net removed
! impact: critical-net-involved, ground-net-involved, value-change

Added Components:
	+ C7 (100n) [Device:C] Capacitor_SMD:C_0402
		* properties: passive

Changed Components:
	R4 (4.7k):
		* impact: value-change
		* value: 10k → 4.7k

Renamed Components:
	R42 -> R14 (32k)

Net Structure Changes:
	Net split: '+3.3V' (removed) → 'GND' (3 pins, new), 'Net-(U1-ADJ)' (3 pins, new)
		* impact: critical-net-involved, ground-net-involved
		'+3.3V' → 'GND':
			• U1 pin 4 (GND, power_in)
			• U1 pin 5 (~{SHDN}, input)
			• U2 pin 1 (GND, power_in)
		'+3.3V' → 'Net-(U1-ADJ)':
			• R14 pin 2 (passive)
			• R4 pin 2 (passive)
			• U1 pin 2 (ADJ, input)

Added Nets:
	Net 'Net-(U1-ADJ)':
		+ C7 pin 1 (passive)
		(3 nodes arrived from other nets — see 'Net Structure Changes')
```

### The first line: what is compared with what

The header names both states of the comparison. The left side is the base (here
`HEAD~3`, with its commit id and date); the arrow points at what it is compared to
(here the uncommitted working tree). If you ask for a comparison that runs backwards
in history — for example `-c1 HEAD -c2 HEAD~5` — `kigit` adds a note that the arrows
read newer → older, so you don't misread it: `+ added` in such an output means the
*older* state had it and the newer one does not.

### The summary block

The first block answers, at a glance, how much changed and how much it matters:

- one counter line per area — components, nets, and (when the project has a board
  file) placement and board-level stats;
- one `! impact:` line collecting **every** impact tag found anywhere in the diff,
  ordered most-serious-first: changes involving ground, supply, or otherwise critical
  nets lead; paperwork like documentation edits comes last.

The header plus this block is the entire default output — that is the intended
"can I commit this?" view. If the impact line worries you, rerun with `-v`.

### Detail levels

`-v` is repeatable, and each level is a superset of the one below it. The section
structure is identical at `-v` and `-vv`; the deeper level only fills those same
sections in with per-pin detail, so nothing has to be relearned when you go deeper.

| Level | Shows | Answers |
| --- | --- | --- |
| *(default)* | counters and the `! impact:` line | *how much changed, and how serious is it?* |
| `-v` | **which** components, nets and placements changed, plus every changed field with its old and new value | *what did I actually touch?* |
| `-vv` | the pins on each net, the pads in each flow, the millimetre and degree deltas | *exactly how is it wired now?* |

Every level below the deepest ends with a dimmed one-line pointer to the next one, so
the detail view is never something you have to already know about to find.

`-v` is where the field-level answers live. A change to a sourcing field is reported
with both values, which is the usual reason to go looking:

```text
Changed Components:
	R104 (100k):
		* impact: sourcing-change
		* LCSC: C25744 → C999999
```

Any field is reported this way, whether it is one KiCad knows about (`value`,
`footprint`, `datasheet`) or one your team invented (`LCSC`, `MPN`, `Tolerance`,
`dnp`). A field that was added or cleared shows `∅` on the side where it was absent.

> **If you used 0.0.7,** `--verbose` used to mean what `-vv` now means. If you have
> `kigit diff --verbose` in a script and want the pin-level output, make it `-vv`.
> This was the last such change; from 1.0 the flags are stable.

### Net structure changes: splits, merges, and moves

When several pins leave one net for another together, that is one editing action —
splitting a net, merging nets, or moving a group of pins — not a coincidence of
unrelated additions and removals. `kigit` correlates the movements and reports the
event as one story under **Net Structure Changes**: which net fed which, and exactly
which pins went where. In the example, six pins leaving `'+3.3V'` for two
destinations is one split, not a dozen separate line items scattered across the
Added and Removed Nets listings.

Reshuffles spanning more than six nets are deliberately not narrated — at that size
a summary would obscure more than it explains — and the plain added / removed /
changed listings then report everything unabridged.

### Every change is reported exactly once

The listing sections after the narrative do not repeat what it already told:

- A pin movement told under **Net Structure Changes** is not listed again under
  Added / Removed / Changed Nets.
- A net whose entire content the narrative explains is not listed again at all. Its
  name in the narrative carries the fact instead: `'GND' (3 pins, new)` says
  everything an Added Nets entry would have said, and `'+3.3V' (removed)` marks the
  emptied source. That is why the example has no Removed Nets section at all, and
  why `'GND'` does not appear under Added Nets.
- A net with anything the narrative does *not* explain stays listed — showing only
  that part. `'Net-(U1-ADJ)'` keeps its entry because `C7 pin 1` is genuinely new
  (the capacitor was just added, it came from no other net); the dimmed pointer line
  below it — "3 nodes arrived from other nets" — accounts for the three pins already
  told above. (A *node* is netlist vocabulary for one component pin on one net.)
- The counters in the summary block always count every net, listed or folded:
  `+ 2 nets added` includes `'GND'`.

So if a section seems to be "missing", nothing was dropped — the whole story of
those nets is told in the narrative, and repeating it would only make the output
longer, not clearer.

### When a change counts as a rename

Calling something a rename is a claim about identity, so `kigit` only makes it when
it can back it up. There are two ways it can.

**The part kept its KiCad identity.** KiCad preserves a symbol's UUID across
re-annotation, so `R42` becoming `R14` with the same UUID *is* the same part. This is
reported as a rename regardless of anything else that changed. If the part also moved
onto different nets in the same commit, the rename stands and carries a caveat:

```text
Renamed Components:
    R42 -> R14 (32k)  [also rewired]
```

The alternative would be to report it as one removal plus one addition, which would
throw away the one thing known for certain. The `rewired` tag also appears on the
summary's `! impact:` line, so the caveat is visible without `-v`.

**The part was deleted and recreated, but is demonstrably the same one.** Here there
is no shared identity, so a rename is an inference and has to be earned. All of the
following must hold:

- exactly one candidate on each side shares the same value, footprint, library,
  symbol and sheet, so two interchangeable parts are never mis-paired; **and**
- both sit on the same nets, on the same pins.

A part deleted in one corner of a sheet and a same-valued part created in another, on
different nets, is *not* a rename. It is one removal and one addition, and that is how
it is reported. On a board with a dozen identical 100n capacitors, "same part number"
is far too weak a signal on its own; being on the same nets is what makes it the same
capacitor rather than merely a similar one.

Two details worth knowing:

- Nets that KiCad names automatically embed a reference designator, so the same wire
  is `Net-(R42-Pad2)` before a re-annotation and `Net-(R14-Pad2)` after it. `kigit`
  accounts for this, so a genuine rename is not rejected just because its own net
  names followed it.
- A part with no pads (a mounting hole, a fiducial) has no connectivity to compare,
  so the unambiguous-match rule decides on its own.

**Known limitation.** Components are compared before nets, so a rename cannot see
*net* renames. If a part is re-annotated in the same commit that a net it sits on is
renamed, the connectivity check fails and the part is reported as removed and added.
This errs toward under-claiming, which is the safer direction.

### Properties and impact tags

Two kinds of annotations appear on entries, answering different questions:

- `* properties:` describes what a net or component **is** — for example
  `power-net`, `ground-net`, `critical-net`, `high-speed-net`, or its connectivity
  level. Context, not change.
- `* impact:` describes what the **change did** or touched. The most common tags:

| Tag | Meaning |
| --- | --- |
| `critical-net-involved`, `ground-net-involved`, `power-net-involved` | the change touches a ground or supply net (`GND`, `VCC`, `3V3`, `5V`, ...) |
| `connectivity-increased` / `connectivity-decreased` | the net gained or lost pins |
| `node-added` / `node-removed` | specific pins appeared on / disappeared from the net |
| `pin-function-changed` / `pin-type-changed` | a pin is now reported with a different function or electrical type |
| `value-change` | a component's value changed (e.g. 10k → 4.7k) |
| `footprint-change` | a component's footprint changed |
| `rewired` | a renamed component also changed which nets it sits on |
| `component-moved`, `component-rotated`, `side-changed` | board placement changed |
| `sourcing-change` | a purchasing field moved: `LCSC`, `MPN`, `Manufacturer`, a distributor part number, ... |
| `electrical-spec-change` | a rated field moved: `Tolerance`, `Voltage`, `Power`, `Current`, ... |
| `fitment-change` | the part's population changed: `dnp`, `dni`, `exclude_from_bom`, ... |
| `documentation-change` | datasheet or description edited |
| `metadata-change` | some *other* custom field changed; this is the fallback bucket |

The summary block's `! impact:` line is the union of these across the whole diff, so
scanning one line tells you the most serious thing the change does.

Note that the last five rows are five different tags, not one. Custom fields are
sorted by what they mean, so a change to `LCSC` or `MPN` is reported as
`sourcing-change` rather than `metadata-change` — `metadata-change` only ever covers
fields `kigit` does not recognise. If your house style uses a name that is not in the
lists above and you would like it classified, that mapping lives in
`semantic_categorization.py` and is a one-line addition.

### Board-level summary

Shown when the project has a `.kicad_pcb`, listing only the fields that changed:

```text
Board:
 ~ components: 42 → 45 (+3)
 ~ board area: 1200.0 mm² → 1180.0 mm² (-20 mm²)
```

### The board summary

When both states have a board file, the detail view ends with a compact board-level
section — the numbers a fab house asks about:

```text
Board:
	~ components: 42 → 43 (+1)
	~ front copper area: 812.5 mm² → 818.75 mm² (+6.25 mm²)
	~ drill holes: 96 → 102 (+6)
```

Only fields that actually changed are shown, each with its signed delta — board area
and back copper area were identical here, so they are suppressed. A field missing
from one of the two exports is not comparable and is left out rather than reported
as a change. In the summary block all of this appears as one counter
(`~ 3 board stats changed`), and these numbers are context, not verdicts: they raise
no impact tags.

### When nothing meaningful changed

```text
No semantically meaningful changes to show.

 (Hint: only added, removed, changed and renamed items are shown (i.e. code changes are discarded.))
```

This is the tool's whole point: KiCad rewriting timestamps, reordering entries, or
reshuffling a file is *not* a design change, and `kigit` says so explicitly.

---

## How it works

Before the steps below, `kigit diff` validates its arguments (commit refs, search path,
search depth) and stops early with a clear message if any are invalid.

1. **Locate** — find the project's `.kicad_pro` and the matching `.kicad_sch` (and
   `.kicad_pcb`, if present) within the configured search directory and depth.
2. **Retrieve** — for each requested Git reference, extract that version of the project
   with `git show <ref>:<path>` (schematic) or a detached worktree (PCB) into a
   temporary working area.
3. **Export** — run `kicad-cli sch export netlist --format kicadxml` on each schematic
   version to produce a netlist in KiCad XML. When the project has a board file, also run
   `kicad-cli pcb export pos --format csv --units mm` to produce the position
   (pick-and-place) CSV, and `kicad-cli pcb export stats --format json` to produce the
   board-level statistics summary.
4. **Parse** — read the `<components>` and `<nets>` sections into an immutable model
   of components, nets, and their nodes (component reference, pin, pin function, pin
   type); parse the position CSV into placements (reference, value, package, X, Y,
   rotation, side); and parse the board stats JSON into a matching model of
   board/pad/via/component counts and measurements.
5. **Diff** — compare components first (added / removed / changed, plus renames: the
   same symbol with only its reference changed, or a deleted-and-recreated part that
   is identical *and* identically wired — see
   [When a change counts as a rename](#when-a-change-counts-as-a-rename)). The rename
   map feeds the net comparison, so a renamed part is not read as rewiring. Then compute added / removed / changed / renamed
   nets, ignoring net codes and `unconnected-*` nets, and correlate pins that moved
   between nets into splits, merges, and moves. When a board file is present, also
   diff placements by reference designator, flagging moves past the position
   threshold, rotations, and side flips, and compute numeric deltas for the
   board-level summary fields.
6. **Classify & report** — tag nets, components, and changes with semantic
   categories, roll every impact tag up into the summary line, and print the report
   described in [Reading the output](#reading-the-output), including the board-level
   summary when applicable.

---

## Speed

`kigit` spends nearly all of its time waiting for `kicad-cli`, which loads a large part
of KiCad before it prints anything. A 0.0.7 run started it up to seven times. Most of
those are now avoided rather than made faster.

**Exports that cannot matter are not run.** Before anything is checked out, `kigit`
hashes every file in the project directory in both states, using git's own content
hashes so a commit and your uncommitted working tree are directly comparable. If they
match, there is no diff to compute and `kicad-cli` is never started at all — which, in
a repository where the hardware is one corner of a larger tree, is the most common
outcome. If only the board changed, the netlist export is skipped; if only the
schematic changed, the position and statistics exports are.

The schematic side is gated on the whole project directory rather than on the
`.kicad_sch` alone, because a netlist also depends on child sheets and on the net
classes stored in the `.kicad_pro`. Untracked files count too: a sub-sheet you have
drawn but not yet committed is something `kicad-cli` will read, so it has to be
something `kigit` notices.

**The exports that are needed run at the same time.** They are independent, so up to
four run concurrently. `-j 1` restores one-at-a-time behaviour.

**Identical exports are reused between runs.** Results are cached under your
repository's git directory, addressed by content, so an entry is only ever reused for
the exact bytes that produced it. The key also covers the `kicad-cli` version, so
upgrading KiCad invalidates everything cached before it. Running `kigit diff`
repeatedly while you edit therefore re-exports only the side you are actually
changing. `--no-cache` bypasses it; `kigit cache clear` empties it.

The cache lives inside `.git/`, so it never appears in `git status` and needs no
`.gitignore` entry. Deleting the clone deletes the cache.

**Checkouts are restricted to the project directory** where git supports it (2.25 and
newer), rather than materialising the whole repository twice per run. Repository-root
library tables are included, so this cannot change what KiCad resolves. `--no-sparse`
turns it off.

To see where a particular run went:

```text
$ kigit diff -c1 HEAD~1 --timings
timings (wall clock):
  verify                   0.1 ms
  locate                   1.1 ms
  snapshot                 3.2 ms
  checkout:HEAD~1         10.9 ms
  export:netlist:c1       56.4 ms
  export:netlist:c2       56.2 ms
  exports                 57.3 ms
  ...
  skipped exports: board unchanged
  export cache: 2 hit(s), 0 miss(es)
```

The `exports` phase is wall-clock time for all exports together, so it being shorter
than the sum of the individual ones is the concurrency showing up. `scripts/bench.py`
runs a set of scenarios repeatedly if you want to compare two builds; measure on the
machine that actually feels slow, since `kicad-cli` startup costs considerably more on
Windows than on Linux.

---

## Logs and temporary files

- Diagnostic logs are written to a `kigit-log/` directory in the current working
  directory. If a run fails unexpectedly, this is the first place to look.
- Temporary working data (`.kigit_temp/`, `.kigit_netlist_temp/`, `.kigit_stats_temp/`)
  is created during a run and cleaned up automatically afterwards.

You may wish to add `kigit-log/`, `.kigit_temp/`, `.kigit_netlist_temp/`, and
`.kigit_stats_temp/` to your `.gitignore`.

Cached exports are kept separately, under your repository's `.git/` directory, so they
never appear in `git status` and need no `.gitignore` entry. `kigit cache clear`
empties them.

---

## Development

The project uses [uv](https://docs.astral.sh/uv/) for packaging, [pytest](https://docs.pytest.org/)
and [Hypothesis](https://hypothesis.readthedocs.io/) for testing,
[Typer](https://typer.tiangolo.com/) for the command-line interface, and
[ruff](https://docs.astral.sh/ruff/) for linting and formatting.

```bash
uv run pytest                                  # everything
uv run pytest --hypothesis-profile=dev         # fast inner loop
uv run pytest -m integration                   # end-to-end, with a stubbed kicad-cli
uv run ruff check                              # after a one-off `uv lock`
```

Integration tests run the real CLI against a throwaway git repository. Only
`kicad-cli` is faked, by a stub that serves fixtures and records every invocation, so
they need neither KiCad nor a network. Tests marked `requires_kicad` run against a
genuine install when there is one and skip otherwise.

Output is covered by golden files under `tests/fixtures/output/`. When you change the
format deliberately, regenerate them with `python -m tests.regenerate_output_goldens`
and review the diff of the `.txt` files as part of the change.

Changes are recorded in [CHANGELOG.md](CHANGELOG.md); design decisions with
consequences live in `docs/adr/`.

---

## License

`kigit` is released under the [MIT License](LICENSE).
