-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
361 lines (309 loc) · 17.7 KB
/
Copy pathMakefile
File metadata and controls
361 lines (309 loc) · 17.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
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
# m-stdlib Makefile.
#
# Toolchain: the **Go `m`** (github.com/vista-cloud-dev/m-cli) — one binary for
# fmt / lint / test / coverage. The Go `m` needs the engine transport named
# EXPLICITLY (it does not auto-detect like the legacy Python m-cli): engine-bound
# targets (test, coverage) pass `$(M_ENGINE_FLAGS)`, which defaults to the
# `m-test-engine` Docker container in byte mode. Engine-free targets (manifest,
# manifest-check, skill-check, doctest-check, fmt, lint) work on a fresh clone
# with no engine configured.
SHELL := /bin/bash
# m — the Go `m` binary (fmt / lint / test / coverage). Resolved from $PATH by
# default. Override per-shell or per-invocation when the binary lives elsewhere
# (e.g. a sibling m-cli build), e.g.
# make check M=$HOME/vista-cloud-dev/m-cli/dist/m
M ?= m
# Engine transport for the Go `m`'s engine-bound commands (test, coverage).
# Default = the m-test-engine Docker container, byte (M) mode — m-stdlib is
# byte-oriented (STDB64/STDHEX/STDCSPRNG need `--chset m` on YDB; see AGENTS.md
# "Engine charset"). Override for a host-YDB dev box or a different transport,
# e.g. make test M_ENGINE_FLAGS='--engine ydb --chset m' (LocalEngine)
# make test M_ENGINE_FLAGS='--engine ydb --docker my-ydb --chset m'
M_ENGINE_FLAGS ?= --engine ydb --docker m-test-engine --chset m
# m-test-engine — local checkout for `make engine-up` / `engine-down`.
# Override if you cloned it elsewhere.
M_TEST_ENGINE ?= $(HOME)/projects/m-test-engine
# v-pkg — the host tool that builds the MSL KIDS base from
# kids/std.build.json (VSL T0b.2). Defaults to the sibling checkout's
# built binary under ~/vista-cloud-dev/ (one-session-one-repo layout);
# override with `make kids VPKG=/path/to/v-pkg`.
VPKG ?= $(HOME)/vista-cloud-dev/v-pkg/dist/v-pkg
.PHONY: all fmt fmt-check lint test test-optional safe-test coverage check ci ci-json clean print-env seed unseed manifest manifest-check check-manifest frontmatter skill skill-check skill-install doctest doctest-check doctest-run engine-up engine-down engine-status check-docs-prose kids check-kids seams check-seams check-icr check-citations namespaces check-namespaces
# vista-meta connection contract — silently included if present.
# Preserves the maintainer's existing workflow but no longer hard-errors
# on a fresh clone. Engine-bound targets fail with m-cli's
# EngineNotConfigured guidance instead — fresh-clone-friendly.
VISTA_CONN := $(HOME)/data/vista-meta/conn.env
ifneq ($(wildcard $(VISTA_CONN)),)
include $(VISTA_CONN)
export VISTA_HOST VISTA_SSH_PORT VISTA_SSH_USER
endif
all: check
print-env:
@echo "M = $(M)"
@echo "VISTA_HOST = $(VISTA_HOST)"
@echo "VISTA_PORT = $(VISTA_SSH_PORT)"
# ── Source-only ─────────────────────────────────────────────────────
fmt:
$(M) fmt src/ tests/
fmt-check:
$(M) fmt --check src/ tests/
# House lint gate = zero ERROR-severity findings (style/warning are advisory).
# The Go `m` has no `--error-on=<severity>` flag (its `--check` fails on ANY
# finding), so the severity filter lives in scripts/m-lint-gate.sh, which reads
# `m lint -o json` and exits non-zero only on error-severity findings.
lint:
@M='$(M)' scripts/m-lint-gate.sh src/ tests/
# ── Engine lifecycle (m-test-engine container) ──────────────────────
#
# `engine-up` starts the lightweight YDB container so DockerEngine can
# target it. m-cli's detect_engine() picks up the running container
# automatically. Force the choice with `M_CLI_ENGINE=docker`.
#
# `engine-status` shows which transport detect_engine() resolves to.
engine-up:
@if [ -d "$(M_TEST_ENGINE)" ]; then \
$(MAKE) -C $(M_TEST_ENGINE) up; \
else \
echo "m-test-engine not found at $(M_TEST_ENGINE)."; \
echo "Clone it: git clone https://github.com/m-dev-tools/m-test-engine $(M_TEST_ENGINE)"; \
echo "Or override: make engine-up M_TEST_ENGINE=/elsewhere"; \
exit 1; \
fi
engine-down:
@$(MAKE) -C $(M_TEST_ENGINE) down
engine-status:
@$(M) -c "from m_cli.engine import detect_engine; e = detect_engine(); print(f'transport: {type(e).__name__}'); print(f'detail: {e!r}')" 2>/dev/null \
|| python3 -c "import sys; sys.path.insert(0, '$(HOME)/projects/m-cli/src'); from m_cli.engine import detect_engine; e = detect_engine(); print(f'transport: {type(e).__name__}'); print(f'detail: {e!r}')" \
|| echo "(no engine resolved — install YDB, run 'make engine-up', or set up vista-meta)"
# ── Engine-bound — m test / m coverage dispatch via m-cli's resolver ──
seed:
@./scripts/seed-vista.sh
unseed:
@./scripts/unseed-vista.sh
# Optional modules either need compiled callout libs (libz/libzstd/libcrypto)
# or are engine-sensitive with a partial-engine implementation (STDNET: real
# sockets, YottaDB-only today, soft-skips on IRIS). Both their SUITES and their
# SRC are excluded from the default `make test`/`make coverage` and run
# explicitly via `make test-optional`. The same split is recorded per-module in
# dist/stdlib-manifest.json ("tier": "optional") via the `@tier optional` tag.
OPTIONAL_MODULES := STDCOMPRESS STDCRYPTO STDHTTP STDNET
OPTIONAL_SUITES := STDCOMPRESSTST STDCRYPTOTST STDCRYPTODOCTST STDHTTPTST STDNETTST
OPTIONAL_PATHS := $(addprefix tests/,$(addsuffix .m,$(OPTIONAL_SUITES)))
OPTIONAL_SRC := $(addprefix src/,$(addsuffix .m,$(OPTIONAL_MODULES)))
CORE_SUITES := $(filter-out $(OPTIONAL_PATHS),$(wildcard tests/*TST.m))
# CORE_SRC = the measured-coverage set. The Go `m coverage --min-percent` gate is
# AGGREGATE (not per-module), so measuring an optional module that has no core
# suite running would tank the aggregate — exclude them here and cover them under
# `make coverage-optional` once the callout libs are deployed.
CORE_SRC := $(filter-out $(OPTIONAL_SRC),$(wildcard src/*.m))
# `make test` runs the dependency-free core (no compiled callout libs). The Go
# `m` stages all of src/ via `--routines src` so ^STDASSERT + the modules under
# test resolve on the engine's routine path.
test:
$(M) test $(M_ENGINE_FLAGS) --routines src $(CORE_SUITES)
# `make test-optional` runs the callout-backed suites; needs the .so libs
# (libz/libzstd/libcrypto) and their ydb_xc_* descriptors deployed.
test-optional:
$(M) test $(M_ENGINE_FLAGS) --routines src $(OPTIONAL_PATHS)
# `safe-test` runs the same suites through scripts/safe-test.sh, which
# auto-recovers from the documented vista-meta container failure modes
# (stuck `mumps` processes, SSH MaxSessions exhaustion). Use this when
# a previous run crashed and `make test` is now hanging on the leak.
# Logs every attempt + recovery step to ~/data/m-stdlib/test-runs.log.
safe-test:
@./scripts/safe-test.sh tests/
# `make coverage` measures CORE_SRC while running CORE_SUITES and writes LCOV.
# The Go `m` splits the positional paths into measured-routines (src/*.m) vs
# suites (*TST.m); `--lcov <path>` replaces the legacy `--format=lcov`. The
# `--min-percent=85` gate is AGGREGATE line coverage across CORE_SRC (the legacy
# Python m-cli gated per-module — see CORE_SRC note + AGENTS.md "CI / toolchain").
coverage:
$(M) coverage $(M_ENGINE_FLAGS) --min-percent=85 --lcov coverage.lcov $(CORE_SRC) $(CORE_SUITES)
# `check` is the fast dev loop (fmt-check + lint + test). Coverage is gated
# (aggregate ≥85%) — run it as `make coverage` or via `make ci`.
check: fmt-check lint test
@echo "OK"
# `make ci` is the CI-shaped invocation: the `check` gate, the coverage gate
# (aggregate ≥85% + coverage.lcov), and the machine-readable JSON artifacts.
ci: check coverage ci-json
# `make ci-json` writes the machine-readable artifacts without re-running the
# gates — CI calls it after the discrete fmt/lint/test/coverage steps pass. The
# Go `m` has no TAP writer; `-o json` emits the structured result envelope
# (test-results.json) and the coverage JSON report (coverage.json).
ci-json:
$(M) test $(M_ENGINE_FLAGS) --routines src -o json $(CORE_SUITES) > test-results.json
$(M) coverage $(M_ENGINE_FLAGS) -o json $(CORE_SRC) $(CORE_SUITES) > coverage.json
# ── Manifest generation (WA4: discoverability + tooling plan) ─────────
#
# `make manifest` regenerates dist/stdlib-manifest.json + dist/errors.json
# from src/STD*.m via the doc-comment grammar in
# docs/guides/m-doc-grammar.md. The manifest is the canonical
# machine-readable surface consumed by m-cli `m doc`, the VS Code
# extension, and the AI skill (Wave A → B/C/D).
#
# `make manifest-check` (WA5) re-runs the generator and fails on diff
# against the committed dist/ files — same model as `m fmt --check`.
manifest:
python3 tools/gen-manifest.py
manifest-check: manifest
@# Diff each generated artefact separately. The previous form
@# (`git diff --exit-code -- A B`) tried to use the `--` pathspec
@# separator to disambiguate, but git silently falls back to
@# blob-vs-blob mode (diffing A against B instead of each against
@# the index) on some git versions even with the `--` — observed
@# under GitHub Actions' bundled git as of 2026-05-09. The single-
@# file form is unambiguous on every git version.
@git diff --exit-code -- dist/stdlib-manifest.json \
|| { echo "ERROR: dist/stdlib-manifest.json out of date — run 'make manifest' and commit."; exit 1; }
@git diff --exit-code -- dist/errors.json \
|| { echo "ERROR: dist/errors.json out of date — run 'make manifest' and commit."; exit 1; }
@echo "manifest: clean"
# `check-manifest` is the tier-1 drift gate. It composes the existing
# manifest-check (generated payloads) with an assertion that the hand-edited
# repo.meta.json is tracked and clean. The meta lives at the repo ROOT (the
# standardized "one meta artifact, one location"); `m arch check` reads it
# root-first and validates its shape (id/layer/language/verification_commands).
check-manifest: manifest-check
@git ls-files --error-unmatch repo.meta.json >/dev/null 2>&1 \
|| { echo "ERROR: repo.meta.json is not tracked — 'git add repo.meta.json' and commit."; exit 1; }
@git diff --exit-code -- repo.meta.json \
|| { echo "ERROR: repo.meta.json has uncommitted changes — review and commit."; exit 1; }
@echo "check-manifest: clean"
# `make frontmatter` re-syncs YAML frontmatter on every docs/modules/std*.md
# from the manifest + index.md (WA6). Idempotent — files that already carry
# frontmatter are skipped. Use `--force` to overwrite (e.g. after WA2 backfill
# adds @raises tags and the errors / labels lists need refreshing).
frontmatter:
python3 tools/write-module-frontmatter.py
# ── AI skill generation (WD1: discoverability + tooling plan §6.1) ────
#
# `make skill` regenerates dist/skill/*.md from the manifest +
# tools/skill-patterns.md (the hand-curated idiom
# input copied through verbatim into patterns.md).
# `make skill-check` --check mode: drift between freshly-generated
# output and the committed dist/skill/ files
# exits non-zero. Mirrors `make manifest-check`.
# `make skill-install` copies dist/skill/*.md to ~/claude/skills/m-stdlib/
# so Claude can load it as a knowledge skill.
# One-shot — not part of CI; the developer runs
# this when they want the local skill refreshed.
skill:
python3 tools/gen-skill.py
skill-check:
@python3 tools/gen-skill.py --check \
|| { echo "ERROR: dist/skill/ is out of date — run 'make skill' and commit."; exit 1; }
@echo "skill: clean"
skill-install: skill
@mkdir -p $(HOME)/claude/skills/m-stdlib
cp -f dist/skill/SKILL.md $(HOME)/claude/skills/m-stdlib/SKILL.md
cp -f dist/skill/manifest-index.md $(HOME)/claude/skills/m-stdlib/manifest-index.md
cp -f dist/skill/patterns.md $(HOME)/claude/skills/m-stdlib/patterns.md
cp -f dist/skill/error-codes.md $(HOME)/claude/skills/m-stdlib/error-codes.md
@echo "skill installed at $(HOME)/claude/skills/m-stdlib/"
# ── Doctest generation (WD2: discoverability + tooling plan §6.1) ─────
#
# `make doctest` regenerates tests/STD<MOD>DOCTST.m for every
# module that carries at least one self-contained
# Pattern-A @example (write expr ; "expected" or
# write expr ; <number>). Examples that reference
# free variables, illustrative-only outputs (current
# time, hostname, …), or non-write shapes are
# skipped — see tools/gen-doctests.py for the rules
# and the ILLUSTRATIVE_LABELS skiplist.
# `make doctest-check` drift gate: regenerate + diff. Fails if any
# DOCTST file would change. Run by CI.
# `make doctest-run` execute the generated suites against the engine
# (vista-meta). Separate from `make check` because
# it requires a live engine connection — wire into
# `make ci` once the doctest invariant stabilises.
doctest:
python3 tools/gen-doctests.py
doctest-check:
@python3 tools/gen-doctests.py --check \
|| { echo "ERROR: tests/STD*DOCTST.m is out of date — run 'make doctest' and commit."; exit 1; }
@echo "doctest: clean"
doctest-run:
$(M) test tests/STD*DOCTST.m
clean:
rm -rf coverage.lcov test-results.json coverage.json
# ── MSL KIDS base (VSL T0b.2) ───────────────────────────────────────
# kids/std.build.json declares the STD* base that m-stdlib ships as a
# KIDS package (the ≤8-char pure modules — routine-only, no FileMan
# components). `make kids` builds the deterministic, normalized .KID via
# v-pkg; `make check-kids` re-gates it: a fresh rebuild must be
# byte-identical (the deterministic-build invariant) AND match the
# committed dist/kids/MSL.kids (drift gate, same discipline as
# check-manifest). Engine-free — needs only the v-pkg binary, no engine.
kids:
$(VPKG) build kids/std.build.json --src src --out dist/kids/MSL.kids
check-kids:
@if [ ! -x "$(VPKG)" ]; then \
echo "check-kids: v-pkg not found at $(VPKG) — build it (make build in v-pkg) or set VPKG=…" >&2; \
exit 1; \
fi
@tmp=$$(mktemp); \
$(VPKG) build kids/std.build.json --src src --out $$tmp >/dev/null; \
if diff -q $$tmp dist/kids/MSL.kids >/dev/null 2>&1; then \
echo "check-kids: dist/kids/MSL.kids matches a fresh deterministic build ✓"; \
rm -f $$tmp; \
else \
echo "ERROR: dist/kids/MSL.kids drifted from kids/std.build.json + src/ — run 'make kids' and commit" >&2; \
rm -f $$tmp; exit 1; \
fi
# ── Drift gate #1: seam contract + bump-forcer (VSL T0b.3) ──────────
# The `seams` block in dist/stdlib-manifest.json is generated from `@seam`
# doc-tags (tools/gen-manifest.py). `make seams` writes the dist/seam-snapshot.json
# projection; `make check-seams` is the gate: it asserts the snapshot is current
# (drift) AND that any existing seam whose signature changed vs git HEAD also
# bumped its contract_version (the bump-forcer, §9 of the coordination plan).
# Engine-free (pure Python over src/). Empty seams are a valid green.
seams: manifest
python3 tools/seam_contract.py --write
check-seams:
@python3 tools/seam_contract.py --check
# ── Drift gate #2: ICR / DBIA conformance (VSL T0b.3) ───────────────
# `@icr` doc-tags → dist/icr-registry.json (tools/gen-icr.py). `make check-icr`
# asserts the registry is current AND that every external-reference call site in
# src/ is declared with a non-retired Supported/Controlled ICR, with no direct
# VistA-file global access (coordination plan §5.4). In m-stdlib (layer m, no
# VistA L4 calls) the registry is empty and the gate is trivially green; the
# gate still runs everywhere so a leaked below-the-line call cannot merge.
icr:
python3 tools/gen-icr.py --write
check-icr:
@python3 tools/gen-icr.py --check
# ── Drift gate #3: citation provenance (VSL T0b.3) ──────────────────
# Every `@source <doc_key>#anchor` cited in src/ doc-tags must still resolve in
# the vdocs gold corpus, still be is_latest=1, and have an unchanged body_sha
# (coordination plan §5.5). The corpus (~/data/vdocs) is a shared mutating lake
# absent in CI → the gate SKIPS green when the corpus is unavailable and reds
# only on real drift when it is present (it runs on a cadence, not every commit).
check-citations:
@python3 tools/check_citations.py --check
# ── Drift gate #4: namespace registry (VSL T0b.3) ───────────────────
# dist/namespace-registry.json records the routine/global prefixes this repo
# owns (from repo.meta.json). `make check-namespaces` asserts the registry is
# current AND that every src/ routine + global subscript lives under a declared,
# collision-free namespace (no unregistered or colliding prefix).
namespaces:
python3 tools/gen_namespace_registry.py --write
check-namespaces:
@python3 tools/gen_namespace_registry.py --check
# Guardrail: docs/ holds only human-readable prose. Non-prose artifacts
# (generated data, JSON/TSV output, copy-paste examples, scaffolding
# templates) belong under dist/, examples/, templates/, or a top-level
# domain-specific directory — not docs/.
check-docs-prose:
@if [ ! -d docs ]; then echo "check-docs-prose: no docs/ directory ✓"; exit 0; fi; \
violations=$$(find docs -type f \
! -name '*.md' ! -name '*.markdown' \
! -name '*.png' ! -name '*.jpg' ! -name '*.jpeg' \
! -name '*.gif' ! -name '*.svg' ! -name '*.webp' \
! -name '.gitkeep'); \
if [ -n "$$violations" ]; then \
echo "ERROR: non-prose files under docs/ — move to dist/, examples/, templates/, or a top-level domain dir:" >&2; \
echo "$$violations" >&2; \
exit 1; \
fi; \
echo "check-docs-prose: docs/ is prose-only ✓"