-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
424 lines (372 loc) · 18.5 KB
/
Makefile
File metadata and controls
424 lines (372 loc) · 18.5 KB
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# AICX Build System
# Local developer flow + release/readiness helpers
# --- Cargo PATH discovery ---------------------------------------------------
# Surface cargo from ~/.cargo/bin when the calling shell (uv-run, sh -c,
# CI runners that strip PATH) didn't source ~/.cargo/env. Idempotent —
# no-op when cargo is already reachable, silent when rustup isn't installed.
ifeq (,$(shell command -v cargo 2>/dev/null))
ifneq (,$(wildcard $(HOME)/.cargo/bin/cargo))
export PATH := $(HOME)/.cargo/bin:$(PATH)
endif
endif
.PHONY: all build build-native release-binaries install install-bin install-config install-cargo git-hooks
.PHONY: precheck precheck-native test test-native check fmt fmt-check clippy clippy-native semgrep ci clean help manifest-check
.PHONY: embeddings-check embeddings-test embeddings-clippy embeddings-hydrate embeddings-info
.PHONY: version version-show version-check version-bump version-patch bump-patch changelog-close release-notes release-plan release-prepare release-check release-tag release-push package-check release-bundle release-bundle-only-binaries test-e2e
all: build
PACKAGE_NAME := $(shell grep '^name = ' Cargo.toml | head -n 1 | cut -d '"' -f 2)
VERSION := $(shell grep '^version = ' Cargo.toml | head -n 1 | cut -d '"' -f 2)
TAG := v$(VERSION)
# --- Python discovery (release tooling needs stdlib tomllib, i.e. 3.11+) ----
# macOS system python3 is 3.9 (no tomllib). Pick the newest available 3.11+
# from PATH; fall back to plain python3 (scripts will fail-fast with a clear
# message if it's < 3.11).
PYTHON := $(shell command -v python3.14 2>/dev/null || command -v python3.13 2>/dev/null || command -v python3.12 2>/dev/null || command -v python3.11 2>/dev/null || command -v python3)
KEYS ?= $(if $(AICX_KEYS_DIR),$(AICX_KEYS_DIR),$(HOME)/.keys)
NOTARY_PROFILE ?= $(AICX_NOTARY_PROFILE)
CLEAN ?= 1
EMBEDDER_PROFILE ?= base
NATIVE ?= 0
FEATURES ?=
TARGET ?= $(shell rustc -vV | sed -n 's/^host: //p')
CODESIGN ?= auto
CARGO_BUILD ?= cargo build
DIST_DIR ?= $(CURDIR)/dist
DRY_RUN ?= 0
RELEASE_BINARIES := aicx aicx-mcp
build:
cargo build --locked --release --bin aicx --bin aicx-mcp
build-native:
cargo build --locked --release --features native-embedder --bin aicx --bin aicx-mcp
release-binaries:
@if [ -z "$(STAGING_DIR)" ]; then \
echo "STAGING_DIR is required. Usage: make release-binaries STAGING_DIR=/tmp/stage TARGET=$(TARGET)" >&2; \
exit 1; \
fi
$(CARGO_BUILD) --locked --release --target "$(TARGET)" --bin aicx --bin aicx-mcp
@mkdir -p "$(STAGING_DIR)/bin" "$(STAGING_DIR)/components"
@for bin in $(RELEASE_BINARIES); do \
install -m 0755 "target/$(TARGET)/release/$$bin" "$(STAGING_DIR)/bin/$$bin"; \
printf ' %s -> %s\n' "$$bin" "$(STAGING_DIR)/bin/$$bin"; \
done
@case "$(TARGET)" in \
*apple-darwin) \
if [ "$(CODESIGN)" = "0" ]; then \
echo " codesign skipped (CODESIGN=0)"; \
elif [ -n "$${MACOS_DEVELOPER_ID_APPLICATION:-}" ]; then \
for bin in $(RELEASE_BINARIES); do \
codesign --force --timestamp --options runtime --sign "$$MACOS_DEVELOPER_ID_APPLICATION" "$(STAGING_DIR)/bin/$$bin"; \
codesign --verify --verbose=2 "$(STAGING_DIR)/bin/$$bin" >/dev/null; \
printf ' codesigned %s\n' "$$bin"; \
done; \
elif [ "$(CODESIGN)" = "1" ]; then \
echo "MACOS_DEVELOPER_ID_APPLICATION is required for CODESIGN=1" >&2; \
exit 1; \
else \
echo " codesign skipped (set CODESIGN=1 and MACOS_DEVELOPER_ID_APPLICATION for release)"; \
fi ;; \
esac
@$(PYTHON) -c 'import json, pathlib, sys; staging=pathlib.Path(sys.argv[1]); version=sys.argv[2]; commit=sys.argv[3]; data={"source":"loctree-aicx","commit":commit,"components":[{"name":"aicx","version":version,"source":"loctree-aicx"},{"name":"aicx-mcp","version":version,"source":"loctree-aicx"}]}; path=staging/"components"/"loctree-aicx.json"; path.write_text(json.dumps(data, indent=2)+"\n", encoding="utf-8"); print(f" metadata -> {path}")' "$(STAGING_DIR)" "$(VERSION)" "$$(git rev-parse --short=12 HEAD)"
release-binaries-linux:
@for target in x86_64-unknown-linux-gnu aarch64-unknown-linux-musl; do \
echo "==> Building $$target"; \
cross build --release --target $$target --bin aicx --bin aicx-mcp || exit 1; \
mkdir -p dist/aicx-v$(VERSION)-$$target-slim-unsigned; \
cp target/$$target/release/aicx target/$$target/release/aicx-mcp dist/aicx-v$(VERSION)-$$target-slim-unsigned/; \
(cd dist && tar -czf aicx-v$(VERSION)-$$target-slim-unsigned.tar.gz aicx-v$(VERSION)-$$target-slim-unsigned/); \
done
install:
./install.sh
@$(MAKE) git-hooks
install-bin:
AICX_INSTALL_MODE=local ./install.sh --shadow-check-only
cargo install --path . --locked --force --bin aicx --bin aicx-mcp
AICX_INSTALL_MODE=local ./install.sh --verify-path-only
install-config:
./install.sh --skip-install
install-cargo:
@echo "crates.io install is not the active AICX distribution path."
@echo "Use GitHub Release bundles, npm, Homebrew tap, or a local checkout install."
@echo "For this checkout, run: make install-bin"
git-hooks:
@echo "Installing git hooks..."
@bash ./tools/install-githooks.sh
@echo "✓ pre-commit + pre-push hooks installed"
precheck:
cargo check --locked -p aicx --all-targets
cargo check --locked -p aicx-embeddings
precheck-native:
cargo check --locked -p aicx-embeddings --features gguf
cargo check --locked -p aicx --features native-embedder --all-targets
manifest-check:
@$(PYTHON) -c 'import sys, re; text = open("Cargo.toml", "r").read(); bad = [m.group(1) for m in re.finditer(r"^([\w-]+)\s*=.*path\s*=", text, re.MULTILINE) if m.group(1) not in ("rmcp-memex", "aicx-embeddings", "aicx-retrieve", "aicx-parser", "aicx-monitor", "aicx-progress-contracts", "path")]; sys.exit("Manifest policy check failed:\n - Unexpected local path dependency: " + ", ".join(bad)) if bad else print("Manifest policy: ok (approved local product deps only)")'
test:
cargo test --locked -p aicx --all-targets
cargo test --locked -p aicx-embeddings
test-native:
cargo test --locked -p aicx-embeddings --features gguf
cargo test --locked -p aicx --features native-embedder --test native_embedder
# End-to-end pipeline test against operator's canonical ~/.aicx/config.toml.
# Fail-fast when preconditions missing (no config, empty corpus, embedder
# unreachable) per operator doctrine — distinct from a CI test that
# silently skips on missing infra.
test-e2e:
cargo test --locked -p aicx --features e2e-aicx --test e2e_pipeline -- --nocapture
cargo test --locked -p aicx --features e2e-aicx --test e2e_context_pack_ingest -- --nocapture
test-retrieval-eval:
cargo test --test retrieval_eval_harness
test-retrieval-eval-live:
cargo test --test retrieval_eval_harness --features e2e-aicx -- --nocapture
test-retrieval-eval-rebaseline:
AICX_RETRIEVAL_EVAL_WRITE_BASELINE=1 cargo test --test retrieval_eval_harness --features e2e-aicx -- --nocapture
check:
@echo "=== AICX Quality Gate ==="
@echo "[1/10] Checking manifest portability..."
@$(MAKE) manifest-check
@echo "[2/10] Checking formatting..."
@cargo fmt --all --check || (echo "Run 'make fmt' to fix formatting." && exit 1)
@echo "[3/10] Running default cargo check..."
@$(MAKE) precheck
@echo "[4/10] Running native GGUF cargo check..."
@$(MAKE) precheck-native
@echo "[5/10] Running default clippy..."
@$(MAKE) clippy
@echo "[6/10] Running native GGUF clippy..."
@$(MAKE) clippy-native
@echo "[7/10] Running default tests..."
@$(MAKE) test
@echo "[8/10] Running native GGUF tests..."
@$(MAKE) test-native
@echo "[9/10] Building slim release binaries..."
@cargo build --locked --release --bin aicx --bin aicx-mcp
@echo "[10/10] Running Semgrep (if available)..."
@if command -v semgrep >/dev/null 2>&1 || command -v pipx >/dev/null 2>&1; then \
SEMGREP=$$(command -v semgrep || echo "pipx run semgrep"); \
$$SEMGREP --config auto --error --quiet . --exclude target; \
else \
echo "[!] Semgrep not available, skipping (install: pipx install semgrep)"; \
fi
@echo "=== All checks passed ==="
fmt:
cargo fmt --all
fmt-check:
cargo fmt --all --check
clippy:
cargo clippy --locked -p aicx --all-targets -- -D warnings
cargo clippy --locked -p aicx-embeddings -- -D warnings
clippy-native:
cargo clippy --locked -p aicx-embeddings --features gguf -- -D warnings
cargo clippy --locked -p aicx --features native-embedder --all-targets -- -D warnings
embeddings-info:
@case "$(EMBEDDER_PROFILE)" in \
base) repo="mradermacher/F2LLM-v2-0.6B-GGUF"; file="F2LLM-v2-0.6B.Q4_K_M.gguf"; size="~397 MB"; dim="1024";; \
dev) repo="mradermacher/F2LLM-v2-1.7B-GGUF"; file="F2LLM-v2-1.7B.Q4_K_M.gguf"; size="~1.1 GB"; dim="2048";; \
premium) repo="mradermacher/F2LLM-v2-1.7B-GGUF"; file="F2LLM-v2-1.7B.Q6_K.gguf"; size="~1.4 GB"; dim="2048";; \
*) echo "Unsupported EMBEDDER_PROFILE=$(EMBEDDER_PROFILE). Use base, dev, or premium." >&2; exit 1;; \
esac; \
printf "profile: %s\nrepo: %s\nfile: %s\nsize: %s\ndim: %s\n" "$(EMBEDDER_PROFILE)" "$$repo" "$$file" "$$size" "$$dim"
embeddings-hydrate:
@if ! command -v hf >/dev/null 2>&1; then \
echo "Missing 'hf' CLI. Install HuggingFace CLI first, then retry."; \
exit 1; \
fi
@case "$(EMBEDDER_PROFILE)" in \
base) repo="mradermacher/F2LLM-v2-0.6B-GGUF"; file="F2LLM-v2-0.6B.Q4_K_M.gguf";; \
dev) repo="mradermacher/F2LLM-v2-1.7B-GGUF"; file="F2LLM-v2-1.7B.Q4_K_M.gguf";; \
premium) repo="mradermacher/F2LLM-v2-1.7B-GGUF"; file="F2LLM-v2-1.7B.Q6_K.gguf";; \
*) echo "Unsupported EMBEDDER_PROFILE=$(EMBEDDER_PROFILE). Use base, dev, or premium." >&2; exit 1;; \
esac; \
echo "Hydrating AICX native embedder: $$repo $$file"; \
hf download "$$repo" "$$file"
embeddings-check:
cargo check --locked -p aicx-embeddings --features gguf
embeddings-test:
cargo test --locked -p aicx-embeddings --features gguf
embeddings-clippy:
cargo clippy --locked -p aicx-embeddings --features gguf -- -D warnings
semgrep:
@if command -v semgrep >/dev/null 2>&1 || command -v pipx >/dev/null 2>&1; then \
SEMGREP=$$(command -v semgrep || echo "pipx run semgrep"); \
$$SEMGREP --config auto --error --quiet . --exclude target; \
else \
echo "[!] Semgrep not available, skipping (install: pipx install semgrep)"; \
fi
ci: check
@echo "CI-equivalent local checks passed."
version: version-show
version-show:
@printf "package: %s\n" "$(PACKAGE_NAME)"
@printf "version: %s\n" "$(VERSION)"
@printf "tag: %s\n" "$(TAG)"
@if git rev-parse --verify "refs/tags/$(TAG)" >/dev/null 2>&1; then \
echo "tag-state: exists"; \
else \
echo "tag-state: missing"; \
fi
version-check:
@$(PYTHON) tools/release_sync.py check
@bash tools/release-channel-check.sh
version-bump:
ifeq ($(origin VERSION),command line)
@$(PYTHON) tools/release_sync.py bump "$(VERSION)"
@echo ""
@echo "Versioned release surfaces synced from Cargo.toml into docs + distribution/npm."
@echo "Cargo.lock is intentionally not touched by version-bump."
@echo "To sync the lockfile for this package only (no network):"
@echo " cargo update --package $(PACKAGE_NAME) --offline"
@echo "Or rely on 'make release-prepare' to sync it for you."
else
@echo "VERSION is required. Usage: make version-bump VERSION={patch|minor|major|x.y.z}" >&2 && exit 1
endif
version-patch bump-patch:
@$(MAKE) version-bump VERSION=patch
changelog-close:
@$(PYTHON) tools/changelog_close.py $(if $(CHANGELOG_GENERATE),--generate-if-empty)
release-notes:
@$(PYTHON) tools/release_sync.py notes $(if $(origin VERSION),$(VERSION),) $(if $(OUTPUT),--output $(OUTPUT),)
release-plan:
@echo "AICX release flow"
@echo ""
@echo "1. Ensure branch is merged and green."
@echo "2. Prepare the release bundle:"
@echo " make release-prepare VERSION={patch|minor|major|x.y.z}"
@echo " (runs version-bump + changelog-close + release-notes preview + precheck)"
@echo "3. Review diff, commit Cargo.toml + Cargo.lock + CHANGELOG.md + any synced docs/package manifests."
@echo "4. Run: make release-check"
@echo "5. Create annotated tag: make release-tag"
@echo "6. Push tag: make release-push"
@echo "7. Wait for GitHub Actions to build signed release archives from the pushed tag."
@echo "8. Publish npm after GitHub Release assets exist:"
@echo " gh workflow run npm-publish.yml -f version=$(VERSION)"
@echo "9. Optional local macOS signed bundle:"
@echo " make release-bundle KEYS=$(HOME)/.keys"
@echo " make release-bundle KEYS=$(HOME)/.keys NATIVE=1"
@echo " make release-bundle KEYS=$(HOME)/.keys NOTARY_PROFILE=my-notary-profile"
@echo " make release-bundle KEYS=$(HOME)/.keys CLEAN=0 # keep local target artifacts"
@echo "10. Optional native embedder sanity:"
@echo " make embeddings-info EMBEDDER_PROFILE=base"
@echo " make embeddings-hydrate EMBEDDER_PROFILE=base"
@echo " make test-native"
@echo "11. GitHub Actions release workflow builds archives and derives GitHub release notes from CHANGELOG.md."
@echo ""
@echo "Reference docs:"
@echo " - docs/RELEASES.md"
@echo " - docs/COMMANDS.md"
release-prepare:
ifeq ($(origin VERSION),command line)
@$(MAKE) version-bump VERSION=$(VERSION)
@$(MAKE) changelog-close CHANGELOG_GENERATE=1
@cargo update --package $(PACKAGE_NAME) --offline
@$(MAKE) version-check
@$(PYTHON) tools/release_sync.py notes --output dist/release-notes.md
@$(MAKE) precheck
else
@echo "VERSION is required. Usage: make release-prepare VERSION={patch|minor|major|x.y.z}" >&2 && exit 1
endif
@echo ""
@echo "=== Release prepared ==="
@echo "Next: review diff, commit, then:"
@echo " make release-check"
@echo " make release-tag"
@echo " make release-push"
@echo " cat dist/release-notes.md # preview GitHub release body"
@echo " make release-bundle KEYS=$(HOME)/.keys [CLEAN=0]"
release-check:
@bash tools/release-channel-check.sh
@$(PYTHON) tools/release_sync.py check --require-version-section
@$(MAKE) check
@echo "Release readiness passed."
release-tag:
@if git rev-parse --verify "refs/tags/$(TAG)" >/dev/null 2>&1; then \
echo "Tag $(TAG) already exists."; \
exit 1; \
fi
@key="$${LOCTREE_GPG_KEY_ID:-}"; \
if [ -z "$$key" ]; then \
echo "LOCTREE_GPG_KEY_ID is not set — refusing to create unsigned release tag." >&2; \
echo "Export the org's GPG key id in your shell (zshrc) or pass LOCTREE_GPG_KEY_ID=... inline." >&2; \
exit 1; \
fi; \
pp="$${LOCTREE_GPG_PASSPHRASE_FILE:-$$HOME/.keys/.gpg.passphrase}"; \
if [ -r "$$pp" ]; then \
echo "warmup" | gpg --batch --pinentry-mode loopback --passphrase-file "$$pp" --local-user "$$key" --detach-sign -o /dev/null 2>/dev/null || true; \
fi; \
git tag -as -u "$$key" "$(TAG)" -m "Release $(TAG)"
@echo "Created GPG-signed annotated tag $(TAG)"
release-push:
git push origin "$(TAG)"
package-check:
@echo "crates.io packaging is intentionally disabled for aicx."
@echo "Use GitHub Release archives + npm platform packages instead."
@echo "Run: make release-check"
release-bundle:
@KEYS="$(KEYS)" \
NOTARY_PROFILE="$(NOTARY_PROFILE)" \
TARGET="$(TARGET)" \
DIST_DIR="$(DIST_DIR)" \
DRY_RUN="$(DRY_RUN)" \
AICX_CLEAN_AFTER_BUILD="$(CLEAN)" \
NATIVE="$(NATIVE)" \
FEATURES="$(FEATURES)" \
PACKAGE_NAME="$(PACKAGE_NAME)" \
./tools/release_bundle.sh
release-bundle-only-binaries:
@AICX_RELEASE_BUNDLE_ONLY_BINARIES=1 \
TARGET="$(TARGET)" \
DIST_DIR="$(DIST_DIR)" \
DRY_RUN="$(DRY_RUN)" \
AICX_CLEAN_AFTER_BUILD="$(CLEAN)" \
NATIVE="$(NATIVE)" \
FEATURES="$(FEATURES)" \
PACKAGE_NAME="$(PACKAGE_NAME)" \
./tools/release_bundle.sh
clean:
cargo clean
help:
@echo "AICX Build System"
@echo ""
@echo "Core Commands:"
@echo " make build - Build release binaries (aicx + aicx-mcp)"
@echo " make build-native - Build release binaries with native GGUF embedder support"
@echo " make install - Install binaries + configure local MCP clients via install.sh"
@echo " make install-bin - Install only aicx + aicx-mcp from the current checkout"
@echo " make install-config - Configure local MCP clients without reinstalling binaries"
@echo " make install-cargo - Explain why crates.io install is not the active path"
@echo " make git-hooks - Install repo-local pre-commit + pre-push hooks"
@echo " make precheck - Quick default cargo check"
@echo " make precheck-native - Quick native GGUF cargo check"
@echo " make manifest-check - Fail if Cargo.toml uses local path dependencies"
@echo " make check - Full local gate (fmt, check, clippy, test, build, semgrep)"
@echo " make test - Run all tests"
@echo " make test-native - Run native GGUF embedder tests"
@echo " make fmt - Format all Rust code"
@echo " make clean - Clean build artifacts"
@echo ""
@echo "Native Embeddings:"
@echo " make embeddings-info EMBEDDER_PROFILE=base|dev|premium - Show GGUF profile details"
@echo " make embeddings-hydrate EMBEDDER_PROFILE=base|dev|premium - Download selected GGUF into HF cache"
@echo " make embeddings-check - Check aicx-embeddings with GGUF backend"
@echo " make embeddings-test - Test aicx-embeddings with GGUF backend"
@echo " make embeddings-clippy - Clippy aicx-embeddings with GGUF backend"
@echo ""
@echo "Release / Version:"
@echo " make version - Alias for version-show"
@echo " make version-show - Show package version and tag state"
@echo " make version-check - Validate synced release surfaces (Cargo/docs/npm/changelog basics)"
@echo " make version-bump VERSION=X - Bump version and sync docs/npm surfaces. X={patch|minor|major|x.y.z}"
@echo " make version-patch - Alias for version-bump VERSION=patch"
@echo " make bump-patch - Alias for version-bump VERSION=patch"
@echo " make changelog-close - Close CHANGELOG '## [Unreleased]' to current version + date"
@echo " make release-notes - Print release notes body derived from CHANGELOG current version section"
@echo " make release-plan - Print the full post-merge release flow"
@echo " make release-prepare VERSION=X - version-bump + changelog-close + notes preview + precheck. X={patch|minor|major|x.y.z}"
@echo " make release-check - Strict release readiness gate"
@echo " make release-tag - Create annotated tag from Cargo.toml version"
@echo " make release-push - Push the current release tag to origin"
@echo " make package-check - Explain binary-release packaging track"
@echo " make release-bundle - Local macOS bundle + codesign + notarize using KEYS/NOTARY_PROFILE (NATIVE=1 for GGUF backend)"
@echo ""
@echo "Quick start:"
@echo " make install - Contributor/local operator setup"
@echo " make check - Full local verification"
@echo " make release-plan - Review release flow before tagging"