# Makefile for Blogex library

MISE_BIN ?= mise
MISE_EXEC = $(MISE_BIN) exec --

.PHONY: check precommit deps deps.check compile test test.coverage coverage crap format format.check verify

# Cheap static gate for this library (no database, no network, no coverage).
# The deps.check guard fails loudly with a pointer at `make setup` when Hex
# deps are missing. Runs from the root `make check`, which loops app then
# blogex. `format.check` only reports:
# a gate that rewrites the files it judges can never fail. Fix formatting with
# `make format`.
#
# Sensor slot: add `credo` once the "credo strict, tuned config (both apps)"
# yak lands blogex's credo dep + .credo.exs. Do not list it before then — a
# gate that claims a check it did not run is worse than no gate.
check: deps.check compile format.check

# Per-app precommit loop: same target name and alias shape as app/, so both
# apps share one habit (compile --warning-as-errors, format, test).
precommit:
	$(MISE_EXEC) mix precommit

# Sync dependencies and compile them: NimblePublisher pulls in mdex_native,
# whose precompiled NIF downloads from GitHub at first compile. Keeping that
# in `deps` (called by root `make setup`) keeps check/test/verify offline on
# a cold clone.
deps:
	$(MISE_EXEC) mix deps.get
	$(MISE_EXEC) mix deps.compile

# Local, network-free guard: every package pinned in mix.lock must be present
# in deps/. check, test and test.coverage depend on this instead of `deps`, so
# the everyday loops never touch the network: `mix deps.get` contacts hex.pm
# even when everything is already installed (the advisory check), which fails
# in offline/sandboxed environments. Fetching is `make setup`'s explicit job,
# run from the repo root.
deps.check:
	@test -f mix.lock || { echo "deps.check: no mix.lock - run 'make setup' at the repo root" >&2; exit 1; }
	@missing=""; \
	for dep in $$(sed -n 's/^[[:space:]]*"\([^"]*\)".*/\1/p' mix.lock); do \
		[ -d "deps/$$dep" ] || missing="$$missing $$dep"; \
	done; \
	test -z "$$missing" || { echo "deps.check: missing:$$missing - run 'make setup' at the repo root" >&2; exit 1; }

# Compile the project (warnings are fatal)
compile:
	$(MISE_EXEC) mix compile --warnings-as-errors

# Run the test suite
test: deps.check
	$(MISE_EXEC) mix test

# Run the suite once with coverage exported, so the coverage floor check (and,
# later, CRAP) reads the export instead of running the suite a second time.
# Stale exports are removed first: the floor must never report an older run.
test.coverage: deps.check
	rm -f cover/*.coverdata
	$(MISE_EXEC) mix test --cover --export-coverage default

# Enforce the coverage floor (mix.exs -> test_coverage) from that export. No
# test run. `mix test.coverage` exits 0 when it finds no export at all, so the
# guard below is load-bearing: a floor check that did not run must not pass.
coverage:
	@test -f cover/default.coverdata || { echo "coverage: no exported coverage - run 'make test.coverage' first" >&2; exit 1; }
	$(MISE_EXEC) mix test.coverage

# Enforce CRAP scores (complexity x coverage) from the same export. No test run.
# Runs in MIX_ENV=test because that is the environment the export came from:
# running in dev would compile a second build and score against a different set
# of modules. The guard is load-bearing for the same reason as `coverage`:
# ex_crap falls back to 0% coverage when the export is missing, which would
# report scores that are pessimistic instead of absent - a false failure is
# less bad than a false pass, but neither is acceptable in the Lens pass.
#
# Threshold is ex_crap's default (30), kept deliberately: decision 2026-10-28
# (human, yak "ratchets, deferred decisions, handoff practice") is not to
# ratchet --max-score down yet - below 30 is a policy change, not a cleanup.
# Baseline at that threshold, same date: 0 offenders above 30, worst functions
# Blogex.LinkValidator.validate_link/1 and validate_links/1 at 6.00
# ("Summary: files=12 functions=61 scored=61 worst_score=6.00"). A failing run
# prints "High scores: N" plus the offending functions; on a passing run read
# N=0 off the worst score rather than inventing a count.
crap:
	@test -f cover/default.coverdata || { echo "crap: no exported coverage - run 'make test.coverage' first" >&2; exit 1; }
	MIX_ENV=test $(MISE_EXEC) mix crap

# Format code. MUTATES the tree; deliberately not part of `check` (nor of the
# Lens pass), which must never write: a gate that fixes what it judges can never
# fail, and would dirty the tree a push is about to send. `make check` uses
# `format.check` below instead.
format:
	$(MISE_EXEC) mix format

# Verify formatting without writing. Fails with the offending files, so the fix
# is explicit: run `make format` (or `make -C blogex format`).
format.check:
	$(MISE_EXEC) mix format --check-formatted

# =============================================================================
# Lens pass (heavy checks; root `make verify` runs this before every push)
# =============================================================================
#
# Sensor slots, cheapest first. Add a sensor as its yak lands, both as a target
# and to VERIFY_SENSORS — that variable is the single source of truth for what
# this target reports as wired.
#   - coverage   (yak: coverage floors + framework-glue exclusions policy)
#                  WIRED below (test.coverage + coverage).
#   - crap       (yak: CRAP scores with ex_crap (both apps))
#                  WIRED below (crap); reuses the coverage export.
#   - mutation   (yak: mutation testing with muex — a sensor, not a gate)
#
# The suite runs FIRST: it is what produces the coverage export that the
# sensors below read. (Sampling and reporting are separate steps because
# `mix test --cover --export-coverage` exports instead of printing a summary,
# and reporting through `mix test.coverage` keeps it to one suite run.) Sensors
# still run cheapest-first among themselves.
VERIFY_SENSORS := coverage crap

verify:
	@echo "==> verify(blogex): suite (coverage exported), then sensors [$(VERIFY_SENSORS)]"
	@$(MAKE) test.coverage
	@for sensor in $(VERIFY_SENSORS); do echo "==> verify(blogex): sensor $$sensor"; $(MAKE) $$sensor || exit 1; done
