GO ?= go
PY ?= python3

# 版本号的唯一来源是仓库根的 CHANGELOG.md（正本 §6.1：版本号只有一个来源）。
# 刻意**不**在构建时跑 `git describe --tags`：日常检出是浅克隆、拿不到 tag，
# 那会让版本号在本地正常、在 CI 上静默退化（见 docs/agents/traps.md trap-5）。
# **正式 tag** 由人批准后创建（注释 tag `vX.Y.Z`），CI 只核验「tag 版本 == 该提交里的版本记录」。
VERSION         ?= $(shell $(PY) scripts/version.py 2>/dev/null || echo dev)
RELEASE_VERSION ?= $(shell $(PY) scripts/version.py 2>/dev/null || echo)
BUILD_KIND      ?= development
# 自 v<正式版本> 以来的提交数（开发号 = <主>.<次>.<它>；取不到就空 ⇒ 构建侧退回 -dev+g<短SHA>）
COMMITS_SINCE_RELEASE ?= $(shell $(PY) -c "import json,subprocess;print(json.loads(subprocess.run(['$(PY)','scripts/version.py','--identity'],capture_output=True,text=True).stdout).get('commits_since_release') or '')" 2>/dev/null)
COMMIT_SHA      ?= $(shell git rev-parse --short=12 HEAD 2>/dev/null || echo unknown)
FULL_SHA        ?= $(shell git rev-parse HEAD 2>/dev/null || echo unknown)
BUILD_TIME      ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)

.PHONY: verify checks list \
        format static-go static-web deps-install build-go build-web \
        tests-go tests-web contracts security manifests-locked \
        test vet build web-test web-typecheck web-build \
        version changelog-check version-contract image

# ── 唯一权威门禁入口（正本 §3.1）────────────────────────────────────────
# 一条命令跑完全部必需检查、**串行**、失败即停，并产出 ci-report.json。
# CI 的 build 作业跑的就是这一条（`.gitea/workflows/build.yml` 里的 `make verify`）——
# 本地与 CI 同一份逻辑，不存在"两边各写一套，迟早只改一边"的问题。
verify:
	$(PY) scripts/ci_checks.py

# 清单：哪些是必需项、在哪个档案（local / ci）下必需
list:
	$(PY) scripts/ci_checks.py --list

# 门禁自身的反向自检（汇总逻辑：必需项未运行/被跳过时**不得**报全绿）
checks:
	$(PY) scripts/ci_checks.py --self-test

# ── 单项（调试用）──────────────────────────────────────────────────────
# ⚠️ `make <单项>` 只跑子集，**不算全绿**：单项命令会自己在输出里说明这一点。
#    写这一组是为了"红了要知道红在哪一项"，不是为了跳过整个门禁。
format:
	$(PY) scripts/ci_checks.py --only format
static-go:
	$(PY) scripts/ci_checks.py --only static-go
static-web:
	$(PY) scripts/ci_checks.py --only static-web
deps-install:
	$(PY) scripts/ci_checks.py --only deps-install
build-go:
	$(PY) scripts/ci_checks.py --only build-go
build-web:
	$(PY) scripts/ci_checks.py --only build-web
tests-go:
	$(PY) scripts/ci_checks.py --only tests-go
tests-web:
	$(PY) scripts/ci_checks.py --only tests-web
contracts:
	$(PY) scripts/ci_checks.py --only contracts
security:
	$(PY) scripts/ci_checks.py --only security-secrets,security-deps
manifests-locked:
	$(PY) scripts/ci_checks.py --only manifests-locked

# ── 旧名字保留为别名：文档和习惯都还在引用它们 ─────────────────────────
test: tests-go
vet: static-go
build: build-go
web-test: tests-web
web-typecheck: static-web
web-build: build-web

# 构建身份（JSON）：release_version / build_kind / commit_sha / display_version / built_at
# 开发构建也能用：没有正式版本时是 dev+g<短SHA>，不会退化成"上一正式版本号"。
version:
	@$(PY) scripts/version.py --identity

# 变更记录结构自检：小节只认 Added/Changed/Fixed/Security（分类可省略、不许空栏目），
# 破坏性变化必须在 Changed 里带发布文档链接。加 --tags 再核「tag 版本 == 该提交里的版本记录」。
changelog-check:
	@$(PY) scripts/check_changelog.py

# 版本契约：注入进二进制的身份必须与 CHANGELOG 对账，且**开发构建不许自称正式版**。
# ⚠️ 不注入时这两条用例会 SKIP —— 那是"本地不想被迫先构建"的便利，但**CI 必须断言它们真的 PASS**
#    （门禁的 contracts 项就是这么做的，并额外跑一条"自称正式版 ⇒ 必须红"的负例）。
version-contract:
	$(GO) test -ldflags "-X knowledge-governance/internal/version.Number=$(VERSION) \
	  -X knowledge-governance/internal/version.ReleaseVersion=$(RELEASE_VERSION) \
	  -X knowledge-governance/internal/version.BuildKind=$(BUILD_KIND) \
	  -X knowledge-governance/internal/version.CommitsSinceRelease=$(COMMITS_SINCE_RELEASE) \
	  -X knowledge-governance/internal/version.CommitSHA=$(FULL_SHA)" \
	  -count=1 -run Test -v ./internal/version/...

# 构建镜像。build-arg 必须显式传入 —— 不传就落成 dev/unknown（故意的：
# 一个显眼的 dev 好过一个看起来正常的假版本号）。compose.yaml 的 args 透传见 PR TODO。
image:
	docker build --build-arg VERSION=$(VERSION) --build-arg RELEASE_VERSION=$(RELEASE_VERSION) \
	  --build-arg BUILD_KIND=$(BUILD_KIND) --build-arg COMMITS_SINCE_RELEASE=$(COMMITS_SINCE_RELEASE) \
	  --build-arg COMMIT_SHA=$(COMMIT_SHA) \
	  --build-arg BUILD_TIME=$(BUILD_TIME) -t knowledge-governance-p1:$(VERSION)-$(COMMIT_SHA) .