# Policy checks: root semgrep.yml is copied from the parser repo (sync with # install -m0644 "$(PARSER_ROOT)/semgrep.yml" "$(CURDIR)/semgrep.yml" # or refresh when parser policy changes). Cursor hooks/rules: scripts/install_cursor_from_parser.sh PARSER_ROOT ?= $(HOME)/PycharmProjects/parser PYTHON ?= $(shell if test -x "$(CURDIR)/.venv/bin/python"; then echo "$(CURDIR)/.venv/bin/python"; else command -v python3; fi) SEMGREP_BIN ?= $(CURDIR)/.venv/bin/semgrep SEMGREP_CONFIG ?= $(CURDIR)/semgrep.yml MUTMUT_STATS ?= $(CURDIR)/mutants/mutmut-cicd-stats.json MUTMUT_MIN_SCORE ?= 100 MUTMUT_MAX_SURVIVED ?= 0 MUTMUT_MAX_TIMEOUT ?= 0 MUTMUT_MAX_NO_TESTS ?= 0 # Broad-scan excludes: parser-parity rules that are not yet adopted repo-wide (trim over time). SEMGREP_KINK_EXCLUDE_RULES ?= \ python-enforce-readable-line-length \ python-no-single-leading-underscore-identifiers \ python-no-any-annotation \ python-no-default-parameters \ python-no-bare-collection-annotations \ python-no-keyword-only-separator \ python-no-os-environ \ python-no-nested-functions \ python-no-try-except-anywhere \ python-no-importlib-import-module \ python-no-import-aliases \ python-no-defensive-dict-list-isinstance \ python-no-basemodel-outside-models-py \ python-no-dataclasses \ python-no-defensive-get-with-or-default \ python-no-getattr-non-dunder SEMGREP_EXCLUDE_FLAGS := $(foreach r,$(SEMGREP_KINK_EXCLUDE_RULES),--exclude-rule $(r)) .PHONY: semgrep-kink semgrep-recsys-core semgrep-recsys-strict ruff-kink dead-kink policy-kink test mutation-python recsys-benchmark adv-probes-http prove-hf-media-placeholder publish-hf-verify verify-hf-live # Python sources only (exclude data/, audit/node_modules, deploy artifacts). semgrep-kink: "$(SEMGREP_BIN)" --config "$(SEMGREP_CONFIG)" $(SEMGREP_EXCLUDE_FLAGS) \ "$(CURDIR)/backend" \ "$(CURDIR)/scripts" \ "$(CURDIR)/tests" \ "$(CURDIR)/api.py" \ "$(CURDIR)/app.py" \ "$(CURDIR)/models.py" \ "$(CURDIR)/ingest.py" \ "$(CURDIR)/fetlife_collect.py" \ "$(CURDIR)/fetlife_auth.py" \ "$(CURDIR)/cdp_fetlife.py" \ "$(CURDIR)/import_fetlife_sample.py" \ "$(CURDIR)/import_fetlife_kinktionary.py" \ "$(CURDIR)/inspect_fetlife.py" \ "$(CURDIR)/download_kinktionary.py" # Recommender-related modules (expect findings until debt is fixed; use for local triage). semgrep-recsys-core: "$(SEMGREP_BIN)" --config "$(SEMGREP_CONFIG)" $(SEMGREP_EXCLUDE_FLAGS) \ "$(CURDIR)/backend/models.py" \ "$(CURDIR)/backend/recsys_data.py" \ "$(CURDIR)/backend/recsys_ots.py" \ "$(CURDIR)/backend/recsys_metrics.py" \ "$(CURDIR)/backend/recsys_benchmark.py" \ "$(CURDIR)/backend/recsys_graph.py" \ "$(CURDIR)/backend/recsys_mmr.py" \ "$(CURDIR)/backend/recommendations.py" \ "$(CURDIR)/backend/recsys_env_overrides.py" # Phase 0-A strict gate: typed recsys settings in models.py (no broad excludes). semgrep-recsys-strict: "$(SEMGREP_BIN)" --config "$(SEMGREP_CONFIG)" "$(CURDIR)/backend/models.py" # Phase 0-A: strict paths only (full-tree Ruff is still very noisy). RUFF_STAGED := $(CURDIR)/backend/models.py $(CURDIR)/scripts/recsys_benchmark.py $(CURDIR)/tests/test_recsys_ots.py ruff-kink: "$(PYTHON)" -m ruff check $(RUFF_STAGED) # Requires a git checkout (`dead` uses `git ls-files`). Uses `.dead-allowlist` at repo root. dead-kink: test -d "$(CURDIR)/.git" cd "$(CURDIR)" && "$(PYTHON)" -m dead --exclude '^(\.venv|audit|data|frontend|deploy|node_modules)/' --symbol-allowlist "$(CURDIR)/.dead-allowlist" policy-kink: ruff-kink semgrep-recsys-strict test: "$(PYTHON)" -m pytest -q mutation-python: "$(PYTHON)" -m mutmut run "$(PYTHON)" -m mutmut export-cicd-stats "$(PYTHON)" "$(CURDIR)/scripts/check_mutmut_stats.py" "$(MUTMUT_STATS)" \ --min-score "$(MUTMUT_MIN_SCORE)" \ --max-survived "$(MUTMUT_MAX_SURVIVED)" \ --max-timeout "$(MUTMUT_MAX_TIMEOUT)" \ --max-no-tests "$(MUTMUT_MAX_NO_TESTS)" # Build HF Docker image and assert missing ``/media/fetlife_fetishes/...`` returns JPEG fallback # (``KINK_HF_SPACE_IMAGE`` + strict env ignored). Requires Docker. prove-hf-media-placeholder: docker build -t kink-cli-prove-media:local "$(CURDIR)" "$(PYTHON)" "$(CURDIR)/scripts/prove_hf_media_placeholder.py" kink-cli-prove-media:local # Push this repo to the Docker Space, wait for RUNNING, then run scripts/verify_hf_stack.py on the app URL. # Needs HF_TOKEN (or HUGGING_FACE_HUB_TOKEN / hf auth / parser secrets.toml) and HF_SPACE_REPO or secrets [huggingface]. publish-hf-verify: "$(PYTHON)" "$(CURDIR)/scripts/publish_hf_space.py" --verify # Live Space smoke only (no upload). Example: make verify-hf-live HF_VERIFY_BASE_URL=https://owner-slug.hf.space verify-hf-live: @test -n "$(HF_VERIFY_BASE_URL)" || (echo >&2 "Set HF_VERIFY_BASE_URL=https://-.hf.space"; exit 2) HF_VERIFY_BASE_URL="$(HF_VERIFY_BASE_URL)" "$(PYTHON)" "$(CURDIR)/scripts/verify_hf_stack.py" --smoke --base-url "$(HF_VERIFY_BASE_URL)" # Partner/group HTTP probes (same as CI test_adversarial_partner_probes_script). adv-probes-http: KINK_PROBE_HTTP=1 KINK_SKIP_HEAVY_WARM=1 "$(PYTHON)" scripts/adversarial_partner_probes.py # Train Cornac + write candidates/benchmark (override --db / --user as needed). recsys-benchmark: "$(PYTHON)" scripts/recsys_benchmark.py --db data/store_slim.db