File size: 5,286 Bytes
6ff91d6
 
 
 
 
 
 
2c11dc3
 
 
 
 
6ff91d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c11dc3
6ff91d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c11dc3
 
 
 
 
 
 
 
 
58ef86c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ff91d6
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# 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://<owner>-<slug>.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