Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
253 lines (221 loc) · 12.7 KB
/
Copy pathMakefile
File metadata and controls
253 lines (221 loc) · 12.7 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
# aloop — developer & release entrypoint.
# Run `make` (no target) for help.
SHELL := bash
.DEFAULT_GOAL := help
PLUGIN := aloop
MARKET := aloop-marketplace
PLUGIN_DIR := plugin
CTX := plugin/tools/ctx-symbols
MDDB := plugin/tools/md-db
BIN_DIR ?= $(HOME)/.local/bin
REPO := $(CURDIR)
VERSION := $(shell python3 -c "import json;print(json.load(open('plugin/.claude-plugin/plugin.json'))['version'])")
TAG := v$(VERSION)
PKG_DIR := package
PKG := $(PKG_DIR)/aloop-$(TAG).tar.gz
# Colors
C := \033[36m
B := \033[1m
R := \033[0m
.PHONY: help
help: ## Show this help
@printf "$(B)aloop$(R) v$(VERSION)\n\n"
@printf "Usage: make $(C)<target>$(R)\n\n"
@grep -hE '^[a-zA-Z0-9_-]+:.*?## ' $(MAKEFILE_LIST) \
| sort | awk 'BEGIN{FS=":.*?## "}{printf " $(C)%-16s$(R) %s\n",$$1,$$2}'
# ── Build / install ────────────────────────────────────────────────
.PHONY: build
build: ## Build ctx-symbols + md-db (release)
cd $(CTX) && cargo build --release
cd $(MDDB) && cargo build --release
.PHONY: install
install: ## Build + install ctx-symbols and md-db to BIN_DIR
./plugin/tools/install.sh "$(BIN_DIR)"
.PHONY: link
link: ## Register THIS repo as a local marketplace and install the plugin (dev)
@command -v claude >/dev/null || { echo "claude CLI not found — install: curl -fsSL https://claude.ai/install.sh | bash -s stable"; exit 1; }
claude plugin marketplace add "$(REPO)"
claude plugin install "$(PLUGIN)@$(MARKET)"
.PHONY: unlink
unlink: ## Uninstall the plugin and remove the local marketplace
-claude plugin uninstall "$(PLUGIN)@$(MARKET)"
-claude plugin marketplace remove "$(MARKET)"
# ── Format / lint / test ───────────────────────────────────────────
.PHONY: fmt
fmt: ## Format ctx-symbols
cd $(CTX) && cargo fmt
.PHONY: fmt-check
fmt-check: ## Check formatting (no writes)
cd $(CTX) && cargo fmt --all -- --check
.PHONY: clippy
clippy: ## Clippy with warnings-as-errors
cd $(CTX) && cargo clippy --all-targets -- -D warnings
.PHONY: shellcheck
shellcheck: ## Lint gate scripts (skipped with a warning if shellcheck is absent)
@if ! command -v shellcheck >/dev/null; then echo "WARN: shellcheck not installed; skipping (CI enforces it)"; else \
shellcheck -S error -e SC1091 plugin/bin/_gatelib.sh plugin/bin/_failclosed.sh plugin/bin/_finddoc.sh $$(grep -l "^#!/usr/bin/env bash" plugin/bin/gate-*) plugin/bin/log-execution \
plugin/bin/selfcheck plugin/bin/transcripts plugin/bin/worktree-create plugin/bin/worktree-remove \
plugin/bin/worktree-hygiene plugin/bin/ensure-tools plugin/bin/md-db plugin/bin/ctx-symbols \
plugin/bin/session-start plugin/bin/_paths.sh plugin/bin/aloop plugin/bin/aladdin plugin/bin/agentic plugin/bin/bind plugin/bin/task-worktree; fi
.PHONY: pycheck
pycheck: ## Byte-compile the Python hook scripts
@python3 -m py_compile plugin/bin/budget plugin/bin/stats plugin/bin/aloop-init plugin/bin/gate-commit-author plugin/bin/_paths.py \
plugin/bin/_usage.py plugin/bin/_prices.py plugin/bin/_events.py plugin/bin/events plugin/bin/intent plugin/bin/lessons plugin/bin/ids plugin/bin/targets plugin/bin/impact plugin/bin/timebox plugin/bin/sweep plugin/bin/capabilities plugin/bin/models plugin/bin/_routes.py plugin/bin/host plugin/bin/_host.py plugin/bin/config plugin/bin/_config.py plugin/bin/layout plugin/bin/_lsp.py scripts/test/fixtures/fake-lsp.py plugin/bin/_timebox.py plugin/bin/host-adapter plugin/bin/install \
plugin/bin/_approval.py plugin/bin/mission plugin/bin/_mission.py plugin/bin/_mission_roles.py plugin/bin/_shellread.py plugin/bin/gate-mission-scope plugin/bin/gate-mission-artifact $$(ls plugin/mission/*.py 2>/dev/null) \
&& rm -rf plugin/bin/__pycache__ plugin/mission/__pycache__ && echo "python ok"
.PHONY: test-hosts
test-hosts: ## Budget/worktree regression + Codex hooks emulation + OpenCode and dsh plugins end-to-end (no network, no tokens)
@command -v node >/dev/null && node --check plugin/hosts/opencode/aloop.js && node --check plugin/hosts/dsh/aloop.mjs
python3 scripts/test/budget-worktree.test.py
python3 scripts/test/budget-soft-telemetry.test.py
python3 scripts/test/budget-hard-stop.test.py
python3 scripts/test/budget-meter.test.py
python3 scripts/test/budget-roles.test.py
python3 scripts/test/budget-contract.test.py
python3 scripts/test/budget-classify.test.py
python3 scripts/test/sprint-admission.test.py
python3 scripts/test/planner-feedback.test.py
python3 scripts/test/events.test.py
python3 scripts/test/handoff-resume.test.py
python3 scripts/test/metrics.test.py
python3 scripts/test/intent-contract.test.py
python3 scripts/test/lesson-candidates.test.py
python3 scripts/test/ids-integrity.test.py
python3 scripts/test/semantic-targets.test.py
python3 scripts/test/supply-chain.test.py
python3 scripts/test/project-stop.test.py
python3 scripts/test/fetched-data.test.py
python3 scripts/test/commit-trace.test.py
python3 scripts/test/impact-provider.test.py
python3 scripts/test/timebox-auto.test.py
python3 scripts/test/orchestrator-sweep.test.py
python3 scripts/test/lsp-adapter.test.py
python3 scripts/test/context-boundary.test.py
python3 scripts/test/model-routes.test.py
python3 scripts/test/host-adapter.test.py
python3 scripts/test/dsh-install.test.py
python3 scripts/test/portable-config.test.py
python3 scripts/test/host-swap.test.py
python3 scripts/test/aladdin-cli.test.py
python3 scripts/test/site.test.py
python3 scripts/test/readme.test.py
python3 scripts/test/layout.test.py
plugin/bin/layout check
plugin/bin/host check
python3 scripts/test/targets-scope.test.py
python3 scripts/test/plan-render.test.py
python3 scripts/test/persona-shapes.test.py
python3 scripts/check-tracking.py
python3 scripts/test/codex-hooks.test.py
python3 scripts/test/rename-compat.test.py
python3 scripts/test/supervisor-scope.test.py
python3 scripts/test/planner-stuck.test.py
python3 scripts/test/hook-matchers.test.py
python3 scripts/test/dispatch.test.py
python3 scripts/test/plan-approval.test.py
python3 scripts/test/log-execution.test.py
python3 scripts/test/ensure-tools.test.py
python3 scripts/test/fail-closed.test.py
python3 scripts/test/bash-portable.test.py
python3 scripts/test/any-language.test.py
python3 scripts/test/intent-trigger.test.py
python3 scripts/test/evidence.test.py
python3 scripts/test/e2e-live-hosts.test.py
python3 scripts/test/suppression-contract.test.py
python3 scripts/test/skill-layout.test.py
@for t in scripts/test/mission-*.test.py; do [ -e "$$t" ] || continue; echo "python3 $$t"; python3 "$$t" || exit 1; done
@if command -v node >/dev/null; then node scripts/test/opencode-plugin.test.mjs; else echo "node not found — skipped OpenCode test (CI runs it)"; fi
@if command -v node >/dev/null; then node scripts/test/dsh-plugin.test.mjs && node scripts/test/dsh-runtime.test.mjs; else echo "node not found — skipped dsh tests (CI runs them)"; fi
.PHONY: conformance
conformance: ## ROADMAP §6 conformance fixtures on today's gates (needs cargo; no network, no tokens)
python3 scripts/conformance/claude-code.test.py
.PHONY: e2e-live
e2e-live: ## Live end-to-end run: one-line intent -> planned, tested, merged code (YES=1; spends dollars)
@test -n "$(YES)" || { echo "spends tokens: make e2e-live YES=1"; exit 2; }
scripts/smoke/e2e-live.sh --yes $(if $(INTENT),--intent $(INTENT)) $(if $(HOST),--host $(HOST)) $(if $(MODEL),--model $(MODEL))
.PHONY: smoke-live
smoke-live: ## Live Claude Code session: hooks fire, supervisor write blocked, transcripts + ledger (YES=1; spends ~$0.005)
@test -n "$(YES)" || { echo "spends tokens: make smoke-live YES=1"; exit 2; }
scripts/smoke/live-claude.sh --yes
.PHONY: json
json: ## Validate plugin.json + marketplace.json
@python3 -c "import json;json.load(open('plugin/.claude-plugin/plugin.json'));json.load(open('.claude-plugin/marketplace.json'));print('JSON ok')"
.PHONY: lint
lint: clippy shellcheck pycheck json ## Run all linters
.PHONY: test
test: ## Run ctx-symbols + md-db unit tests
cd $(CTX) && cargo test --all-targets
cd $(MDDB) && cargo test --release
.PHONY: validate
validate: json ## claude plugin validate --strict
@command -v claude >/dev/null || { echo "claude CLI not found — install: curl -fsSL https://claude.ai/install.sh | bash -s stable"; exit 1; }
claude plugin validate ./plugin --strict
.PHONY: ci
ci: fmt-check lint test eval-validate test-hosts conformance ## Run everything CI runs (except the live claude install)
# ── Evals ──────────────────────────────────────────────────────────
.PHONY: eval-validate
eval-validate: ## Validate every eval suite's JSON (no tokens)
@python3 scripts/eval/validate-suites.py
.PHONY: eval
eval: ## Run an eval suite: make eval SUITE=<evals.json> [YES=1] (YES spends tokens)
@test -n "$(SUITE)" || { echo "set SUITE=<path to evals.json> (see plugin/evals/ or the skill's evals/)"; exit 2; }
@./scripts/eval/run-evals.sh --suite "$(SUITE)" $(if $(YES),--yes,) $(if $(ITER),--iteration $(ITER),)
# ── Release / publish ──────────────────────────────────────────────
.PHONY: version
version: ## Print the current version
@echo "$(VERSION)"
.PHONY: version-check
version-check: ## Assert plugin.json, Cargo.toml, and CHANGELOG agree
@python3 scripts/version-check.py
.PHONY: release
release: ci validate version-check ## Verify, then create annotated git tag vX.Y.Z
@git rev-parse --git-dir >/dev/null 2>&1 || { echo "not a git repo — run: git init && git add -A && git commit -m init"; exit 1; }
@test -z "$$(git status --porcelain)" || { echo "working tree dirty — commit first"; exit 1; }
@git rev-parse "$(TAG)" >/dev/null 2>&1 && { echo "tag $(TAG) already exists"; exit 1; } || true
git tag -a "$(TAG)" -m "aloop $(TAG)"
@echo "Tagged $(TAG). Run 'make publish' to push."
.PHONY: publish
publish: ## Push the current branch + tags to origin (run after `make release`)
@git rev-parse --git-dir >/dev/null 2>&1 || { echo "not a git repo"; exit 1; }
@git remote get-url origin >/dev/null 2>&1 || { echo "no 'origin' remote — git remote add origin <url>"; exit 1; }
git push origin HEAD
git push origin --tags
@echo "Published. Users: /plugin marketplace add <repo-url> && /plugin install $(PLUGIN)@$(MARKET)"
.PHONY: package
package: ## Bundle the release into package/ and publish it as the GitHub release for $(TAG)
@command -v gh >/dev/null || { echo "gh CLI not found — https://cli.github.com"; exit 1; }
@git rev-parse "$(TAG)" >/dev/null 2>&1 || { echo "tag $(TAG) missing — run 'make release && make publish' first"; exit 1; }
@mkdir -p $(PKG_DIR)
@rm -f "$(PKG)" "$(PKG).sha256" "$(PKG_DIR)/notes-$(TAG).md"
tar --no-xattrs \
--exclude='.git' --exclude='target' --exclude='.agentic' --exclude='.transcripts' \
--exclude='.DS_Store' --exclude='node_modules' --exclude='$(PKG_DIR)' \
-czf "$(PKG)" .claude-plugin plugin README.md CHANGELOG.md LICENSE
@cd $(PKG_DIR) && shasum -a 256 "$(notdir $(PKG))" > "$(notdir $(PKG)).sha256"
@awk -v v='$(VERSION)' 'BEGIN{p=0} $$0 ~ "^## \\["v"\\]"{p=1;next} /^## \[/{if(p)exit} p' CHANGELOG.md > "$(PKG_DIR)/notes-$(TAG).md"
@test -s "$(PKG_DIR)/notes-$(TAG).md" || echo "aloop $(TAG)" > "$(PKG_DIR)/notes-$(TAG).md"
@echo "Built $(PKG)"
@if gh release view "$(TAG)" >/dev/null 2>&1; then \
gh release upload "$(TAG)" "$(PKG)" "$(PKG).sha256" --clobber; \
gh release edit "$(TAG)" --latest --notes-file "$(PKG_DIR)/notes-$(TAG).md"; \
else \
gh release create "$(TAG)" "$(PKG)" "$(PKG).sha256" --title "aloop $(TAG)" \
--notes-file "$(PKG_DIR)/notes-$(TAG).md" --latest; \
fi
@echo "Published GitHub release $(TAG) with $(notdir $(PKG))."
# ── Hooks / housekeeping ──────
4065
─────────────────────────────────────
.PHONY: hooks
hooks: ## Install the tracked git hooks (sets core.hooksPath)
git config core.hooksPath .githooks
chmod +x .githooks/* 2>/dev/null || true
@echo "git hooks installed (.githooks): pre-commit, pre-push"
.PHONY: smoke
smoke: build ## Quick offline gate smoke test (structural gate on a tmp tree)
@export PATH="$(BIN_DIR):$$PATH"; \
cp "$(CTX)/target/release/ctx-symbols" "$(BIN_DIR)/ctx-symbols" 2>/dev/null || true; \
T=$$(mktemp -d); printf 'pub fn a(){let x=1;}\npub fn caller(){a();}\n' > "$$T/a.rs"; \
REPO_DIR="$$T" CLAUDE_PLUGIN_ROOT="$(CURDIR)/plugin" plugin/bin/gate-structural-integrity; \
echo "structural gate exit=$$?"; rm -rf "$$T"
.PHONY: clean
clean: ## Remove build artifacts
cd $(CTX) && cargo clean