-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMakefile
More file actions
327 lines (306 loc) · 21.5 KB
/
Copy pathMakefile
File metadata and controls
327 lines (306 loc) · 21.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
# Front door for the harness — everything you need is a `make` target. No
# PYTHONPATH, no aliases, no remembering subcommands.
#
# make flow ID=13418 run the cycle for one issue
# make flow CSV="engine/….csv" batch: one Plan session may brief
# SEVERAL issues, all built unattended
# then signed off cheap-first via the queue
# Runs Plan → Do → Check → sign-off → publish (on an accept it
# opens a DRAFT PR; NO_PUBLISH=1 skips that). Plan/sign-off/publish
# open Claude in your terminal — use a real terminal (the Do gate
# runs the live gramps suite in Docker). Optional: ACT=1 also runs
# the cross-cycle Act review; BY=<name> overrides the §9 attribution
# (defaults to the project author).
# make batch IDS="123 456" [PLAN=1] [CSV=…]
# drive issues that are ALREADY briefed through the full cycle
# (Do → Check → sign-off → publish → Act) — no Plan beat. Skips
# ids with no brief or already complete. PLAN=1 (or CSV=…) adds a
# Plan pre-pass that briefs the UNPLANNED ids in one shared session
# first. NOACT=1 stops after sign-off; BY=<name> sets the §9 attribution.
# make rehearse ID=13418 [CSV=…]
# dry-run the SAME control flow with stub leaves + stub gates —
# no Claude, no Docker (publish dry-runs too), instant.
# make publish ID=123 [DRY=1]
# re-publish an already-accepted bundle as a draft PR (the flow
# does this on accept). DRY=1 prints the git/gh plan without pushing.
# make status list every bundle and its state.
# make cli ARGS="signoff 13418 --accept"
# run any `pdca` subcommand without the source-path boilerplate.
#
# make full self-test: offline guards + a real cycle that fires the
# live gramps gate (Docker, ~3-5 min; honestly red on gramps'
# own pre-existing failures — that is the engine working).
# make check fast: driver + engine guards only, offline, no Docker (~1s).
# make setup one-time: grant Claude read of the workspace + sibling repos
# so the interactive leaves don't prompt per file/dir.
# make install create .venv with a real `gramps-pdca` console script (optional —
# the targets above already work without it).
# make bootstrap one-command clean-machine setup: venv + permissions + sibling
# forks + worktrees + engine image + offline smoke test.
# MINIMAL=1 = core only (CI mode); APT=1 lets it sudo-install
# missing core tools; SSH=1 clones forks over SSH.
# make doctor report every prerequisite (OK/MISSING/UNAUTH/WARN + fix hint)
# without changing anything. STRICT=1 fails on any non-OK line.
PYTHON ?= python3
export PYTHONPATH := src
PDCA := $(PYTHON) -m pdca_harness.cli
.DEFAULT_GOAL := test
.PHONY: test check flow rehearse status revalidate cli install setup worktrees essential-worktrees fork-worktrees preflight batch publish gramps-requirements bootstrap doctor
# --- the cycle -------------------------------------------------------------
# Live, continuous, Claude-driven. Give ID for one issue, or just CSV for a batch
# Plan session that may brief several issues (built all, then signed off cheap-first).
# On an accept the flow publishes a draft PR; NO_PUBLISH=1 stops at COMPLETE.
flow:
@test -n "$(ID)$(CSV)" || { echo 'usage: make flow ID=<issue-id> [CSV="<path>"] [ACT=1] [NO_PUBLISH=1] [BY=<name>]'; echo ' or: make flow CSV="<path>" [...] (batch: Plan briefs several)'; exit 2; }
$(PDCA) flow $(ID) $(if $(CSV),--from-csv "$(CSV)") $(if $(NO_PUBLISH),--no-publish) $(if $(ACT),,--no-act) $(if $(BY),--by "$(BY)")
# Drive several ids through the full cycle (Do → Check → sign-off → publish → Act).
# v0.30 folded `batch` into `flow`: N ids fan out across lanes; unbriefed ids are
# auto-planned (one shared Plan session), so PLAN= is no longer needed. NOACT=1 stops
# after sign-off; BY=<name> sets §9.
batch:
@test -n "$(IDS)" || { echo 'usage: make batch IDS="<id> <id> ..." [CSV="<path>"] [NOACT=1] [BY=<name>]'; exit 2; }
$(PDCA) flow $(IDS) $(if $(CSV),--from-csv "$(CSV)") $(if $(NOACT),--no-act) $(if $(BY),--by "$(BY)")
# Re-publish an accepted bundle as a draft PR (the flow does this on accept).
publish:
@test -n "$(ID)" || { echo 'usage: make publish ID=<issue-id> [DRY=1] [BY=<name>]'; exit 2; }
$(PDCA) publish $(ID) $(if $(DRY),--dry-run) $(if $(BY),--by "$(BY)")
# Same control flow with stub leaves + stub gates (no Claude / Docker / TTY), in an
# ISOLATED throwaway bundle root so it never touches the real results/ a live run uses.
# --no-act: Act is default-on in the flow (v0.30) but writes to the real
# process/act-log.md, not the bundle root — so a rehearsal must skip it.
# Single-id rehearsals seed the filled toy brief first: under v0.31 (#113) the stub Plan
# copies the unfilled brief.md.tpl, which `is_placeholder()` routes to UNPLANNED — so a
# real brief is needed to drive Do→Check→publish→COMPLETE. A CSV batch plans from the CSV.
rehearse:
@test -n "$(ID)$(CSV)" || { echo 'usage: make rehearse ID=<issue-id> [CSV="<path>"]'; exit 2; }
@rm -rf .rehearse
@if [ -z "$(CSV)" ]; then \
PDCA_BUNDLE_ROOT=.rehearse PDCA_LEAVES_MODE=stub PDCA_GATES_MODE=stub $(PDCA) init-issue $(ID) --from-brief examples/toy/brief.md; \
fi
PDCA_BUNDLE_ROOT=.rehearse PDCA_LEAVES_MODE=stub PDCA_GATES_MODE=stub $(PDCA) flow $(ID) $(if $(CSV),--from-csv "$(CSV)") --no-act
@printf '(rehearsal bundles in ./.rehearse — throwaway; real runs use results/)\n'
status:
@$(PDCA) status
# Re-gate FROZEN bundles against the CURRENT engine (docs 09 / `pdca revalidate`). Each
# run writes an additive results/issue_<id>/revalidation-<date>.json (old→new per row)
# and NEVER mutates the frozen check-gates.json / SUMMARY.md §9 — Act surfaces the deltas
# (act-index "revalidation deltas"). ID=<id> revalidates one bundle, else every COMPLETE
# bundle. Docker-heavy (the full gate suite per bundle); a delta is advisory — a frozen
# PASS→FAIL is a regression to route, a FAIL→PASS a now-stale recorded red.
revalidate:
@if [ -n "$(ID)" ]; then ids="$(ID)"; \
else ids="$$($(PDCA) status | awk '/^COMPLETE /{sub(/^issue_/,"",$$2); print $$2}')"; fi; \
[ -n "$$ids" ] || { echo 'no COMPLETE bundles to revalidate'; exit 0; }; \
for id in $$ids; do echo "→ revalidate $$id"; $(PDCA) revalidate "$$id" || true; done; \
echo 'revalidate: stamps written — review revalidation-*.json deltas (Act surfaces them).'
# Escape hatch for any other subcommand: make cli ARGS="queue"
cli:
$(PDCA) $(ARGS)
# One-time permission setup so the interactive leaves don't prompt: grant Claude
# read of the whole workspace and the sibling repos (gramps, addons-source) it
# patches. MERGES into the machine-local .claude/settings.local.json (gitignored)
# so re-running setup (e.g. via `make bootstrap`) never clobbers permissions the
# user has accumulated since.
setup:
@$(PYTHON) -c "import json, os; p = '.claude/settings.local.json'; \
ws = os.path.dirname(os.getcwd()); \
cfg = json.load(open(p)) if os.path.exists(p) else {}; \
perm = cfg.setdefault('permissions', {}); \
allow = perm.setdefault('allow', []); rule = 'Read(/' + ws + '/**)'; \
rule in allow or allow.append(rule); \
extra = perm.setdefault('additionalDirectories', []); \
[extra.append(d) for d in (ws + '/gramps', ws + '/addons-source', '/tmp') if d not in extra]; \
json.dump(cfg, open(p, 'w'), indent=2)"
@echo "merged into .claude/settings.local.json — workspace read + siblings (gramps, addons-source) + /tmp"
@echo "NOTE: folder-TRUST is separate from permissions — it lives in the global"
@echo " ~/.claude.json, not these settings, so setup can't pre-set it. The very"
@echo " first interactive session asks once 'trust this folder?'; accept it and"
@echo " it persists for every later run."
# --- validation worktrees: per-version, based on UPSTREAM -------------------
# DEFAULT validation runs against clean upstream/maintenance/gramps6{0,1} — the real
# contribution target, NOT the fork's maintenance branches (which carry local CI/tooling
# commits) or the developer's working clone. Core C4-verify uses gramps-6.{0,1}; the
# addon matrix uses gramps-6.{0,1} + addons-source-6.{0,1}. Detached so they don't
# contend for the primary checkout's branch. Fetches upstream first, then creates
# missing worktrees AND realigns clean existing ones to the current upstream tip.
# LANES=N additionally creates per-lane copies (gramps-6.x-lane0 … -lane{N-1}) for the
# in-driver worker pool (docs 09): lane K's gates patch its own worktree so concurrent
# bundles never contend. The bare set is always (re)built — serial runs use it unchanged.
worktrees:
@ws=$$(cd .. && pwd); \
sfxs=""; if [ -n "$(LANES)" ]; then k=0; while [ "$$k" -lt "$(LANES)" ]; do sfxs="$$sfxs -lane$$k"; k=$$((k+1)); done; fi; \
for r in gramps addons-source; do \
git -C "$$ws/$$r" remote get-url upstream >/dev/null 2>&1 || { \
echo "error: $$r has no 'upstream' remote — worktrees are pinned to upstream/maintenance/gramps6{0,1}."; \
echo " fix: git -C $$ws/$$r remote add upstream https://github.com/gramps-project/$$r.git"; \
exit 1; }; \
done; \
for r in gramps addons-source; do git -C "$$ws/$$r" fetch upstream --prune --quiet || echo "warn: fetch upstream failed for $$r"; done; \
for sfx in "" $$sfxs; do \
for spec in "gramps gramps-6.0 upstream/maintenance/gramps60" \
"gramps gramps-6.1 upstream/maintenance/gramps61" \
"addons-source addons-source-6.0 upstream/maintenance/gramps60" \
"addons-source addons-source-6.1 upstream/maintenance/gramps61"; do \
set -- $$spec; repo="$$ws/$$1"; wt="$$ws/$$2$$sfx"; ref="$$3"; \
if [ -d "$$wt" ]; then \
if [ -n "$$(git -C "$$wt" status --porcelain 2>/dev/null)" ]; then echo "✔ $$wt (exists, dirty — left as-is)"; \
else git -C "$$wt" checkout --detach --quiet "$$ref" && echo "↻ $$wt → $$ref ($$(git -C "$$repo" rev-parse --short "$$ref"))"; fi; \
else echo "→ git -C $$1 worktree add --detach $$2$$sfx $$ref"; \
git -C "$$repo" worktree add --detach "$$wt" "$$ref" || exit 1; fi; \
done; \
done; \
echo "worktrees ready (upstream base)$${sfxs:+; lanes:$$sfxs} — core uses gramps-6.{0,1}; addon matrix adds addons-source-6.{0,1}."
# --- essential fallback line: upstream + harness-enabling fixes -------------
# Builds gramps-<ver>-essential = upstream/maintenance/gramps<ver> + the fixes listed in
# engine/essential-fixes.tsv (cherry-picked), on branch testbed/essential-gramps<ver>.
# run-verify.sh retries a CORE bundle here when it FAILS on upstream, and stamps the
# dependency. Idempotent — skips existing worktrees; pass REBUILD=1 to refresh from the
# manifest (e.g. after upstream moves or the manifest changes).
# The manifest is authoritative in both directions: the build loop below only visits the
# versions it declares, so dropping a row would otherwise strand that version's worktree
# on disk (never rebuilt, still a plausible-looking verification base). Prune those first.
# LANES=N also builds per-lane essential worktrees (gramps-<ver>-essential-lane{0..N-1})
# on lane-specific branches (a branch can live in only one worktree), mirroring the lane
# copies `make worktrees LANES=N` creates.
essential-worktrees:
@ws=$$(cd .. && pwd); manifest=engine/essential-fixes.tsv; \
[ -f "$$manifest" ] || { echo "no $$manifest"; exit 1; }; \
sfxs=""; if [ -n "$(LANES)" ]; then k=0; while [ "$$k" -lt "$(LANES)" ]; do sfxs="$$sfxs -lane$$k"; k=$$((k+1)); done; fi; \
git -C "$$ws/gramps" fetch upstream --prune --quiet || echo "warn: fetch upstream failed"; \
bash engine/scripts/lib/prune-essential-worktrees.sh "$$ws" "$$manifest" || exit 1; \
for sfx in "" $$sfxs; do \
for v in $$(awk -F'\t' '!/^#/ && NF>=3 {print $$1}' "$$manifest" | sort -u); do \
tag=$$(echo "$$v" | tr -d .); wt="$$ws/gramps-$$v-essential$$sfx"; br="testbed/essential-gramps$$tag$$sfx"; up="upstream/maintenance/gramps$$tag"; \
if [ -d "$$wt" ] && [ -z "$(REBUILD)" ]; then echo "✔ $$wt (exists; REBUILD=1 to refresh)"; continue; fi; \
[ -d "$$wt" ] && { git -C "$$ws/gramps" worktree remove --force "$$wt"; git -C "$$ws/gramps" branch -D "$$br" 2>/dev/null || true; }; \
git -C "$$ws/gramps" branch -f "$$br" "$$up"; \
git -C "$$ws/gramps" worktree add --quiet "$$wt" "$$br" || exit 1; \
awk -F'\t' -v v="$$v" '!/^#/ && $$1==v {print $$2}' "$$manifest" | while read -r c; do \
git -C "$$wt" cat-file -e "$$c^{commit}" 2>/dev/null || { echo "essential-fixes.tsv: $$v fix $$c is not a commit in $$ws/gramps — re-point or drop the row"; exit 1; }; \
git -C "$$wt" cherry-pick "$$c" >/dev/null 2>&1 || { echo "cherry-pick $$c FAILED in $$wt"; git -C "$$wt" cherry-pick --abort 2>/dev/null; exit 1; }; \
done || exit 1; \
echo "→ $$wt = $$up + $$(awk -F'\t' -v v="$$v" '!/^#/ && $$1==v {print $$3}' "$$manifest" | paste -sd, -)"; \
done; \
done; \
echo "essential worktrees ready — run-verify falls back here on an upstream failure."
# --- fork verification bases: a fork's open PR branch (issue #96) -----------
# Builds addons-source-<ver>-fork = a DEDICATED detached worktree checked out at a fork
# PR branch listed in engine/fork-bases.tsv (<ver>\t<remote>\t<branch>). An addon bundle
# whose brief declares `Verification base: <remote>/<branch>` verifies against this
# worktree instead of clean addons-source-<ver> (run-verify.sh / run-addon-unit.sh), so a
# change that lives on the fork (e.g. .github/ CI infra the clean base lacks) can apply +
# run locally. Idempotent: realigns an existing clean worktree to the branch tip; a dirty
# one is left as-is. <remote> is a remote on ../addons-source (origin = the fork).
# LANES=N also creates per-lane copies (addons-source-<ver>-fork-lane0 … -lane{N-1}), so a
# fork-based bundle running under the worker pool / a separate-terminal lane patches its OWN
# fork worktree (run-verify.sh derives addons-source-<ver>-fork$LANE_SFX). All lane copies
# detach at the same fork ref — no branch contention (issue #137). Without LANES only the
# bare worktree is built, unchanged.
fork-worktrees:
@ws=$$(cd .. && pwd); manifest=engine/fork-bases.tsv; \
[ -f "$$manifest" ] || { echo "no $$manifest"; exit 1; }; \
repo="$$ws/addons-source"; \
sfxs=""; if [ -n "$(LANES)" ]; then k=0; while [ "$$k" -lt "$(LANES)" ]; do sfxs="$$sfxs -lane$$k"; k=$$((k+1)); done; fi; \
for v in $$(awk -F'\t' '!/^#/ && NF>=3 {print $$1}' "$$manifest" | sort -u); do \
remote="$$(awk -F'\t' -v v="$$v" '!/^#/ && $$1==v {print $$2; exit}' "$$manifest")"; \
branch="$$(awk -F'\t' -v v="$$v" '!/^#/ && $$1==v {print $$3; exit}' "$$manifest")"; \
ref="$$remote/$$branch"; \
git -C "$$repo" fetch "$$remote" --prune --quiet || echo "warn: fetch $$remote failed"; \
for sfx in "" $$sfxs; do \
wt="$$ws/addons-source-$$v-fork$$sfx"; \
if [ -d "$$wt" ]; then \
if [ -n "$$(git -C "$$wt" status --porcelain 2>/dev/null)" ]; then echo "✔ $$wt (exists, dirty — left as-is)"; \
else git -C "$$wt" checkout --detach --quiet "$$ref" && echo "↻ $$wt → $$ref ($$(git -C "$$repo" rev-parse --short "$$ref"))"; fi; \
else echo "→ git -C addons-source worktree add --detach addons-source-$$v-fork$$sfx $$ref"; \
git -C "$$repo" worktree add --detach "$$wt" "$$ref" || exit 1; fi; \
done; \
done; \
echo "fork worktrees ready$${sfxs:+; lanes:$$sfxs} — an addon bundle declaring a fork Verification base verifies here."
# --- preflight: bring the verification substrate current before a session ----
# One explicit step to run ONCE before a flow/batch session (issue #79). It (1)
# realigns the per-version worktrees to current upstream, (2) refreshes the essential
# C4-fallback line, (3) checks the testbed checkout is current, (4) ensures the image
# is built, then (5) refreshes the T3 baselines against the freshly-realigned CLEAN
# tree (`t3_baseline --update`, driven from the pdca.toml T3 gate defs so it stays in
# sync) — so `known_failures` reflects the current upstream and the baseline_tree SHA
# (#89) stays valid. Idempotent; NEVER run mid-batch (realigning between bundles would
# verify later bundles against a different base). LANES=N for the worker pool;
# NO_CAPTURE=1 does the realign/image legs only (skips the slow suite captures).
# Regenerate the baked Docker deps layer from gramps' own pyproject.toml (issue #132).
# Single-sourced — run after a gramps dep bump; the drift test fails until you do.
# Then rebuild the image (make preflight / clean-build.sh) to pick up the new layer.
gramps-requirements:
@$(PYTHON) engine/scripts/lib/gramps_python_deps.py -o engine/docker/gramps-requirements.txt
preflight: gramps-requirements
@$(MAKE) --no-print-directory worktrees
@$(MAKE) --no-print-directory essential-worktrees REBUILD=1
@$(MAKE) --no-print-directory fork-worktrees
@echo "→ preflight: testbed checkout"; \
br="$$(git rev-parse --abbrev-ref HEAD)"; \
[ "$$br" = "main" ] || echo " warn: testbed is on '$$br', not main"; \
git fetch origin --quiet 2>/dev/null || echo " warn: git fetch origin failed"; \
behind="$$(git rev-list --count main..origin/main 2>/dev/null || echo 0)"; \
[ "$$behind" = "0" ] || echo " warn: main is $$behind commit(s) behind origin/main — pull before the session"
@gv="$$(python3 engine/scripts/lib/gramps_version.py ../gramps-6.1 2>/dev/null)"; \
img="gramps-testbed:ubuntu-$$gv"; \
if [ -z "$$gv" ]; then echo " warn: could not detect gramps version (is ../gramps-6.1 present?)"; \
elif docker image inspect "$$img" >/dev/null 2>&1; then echo "✔ image $$img present"; \
else echo "→ building $$img"; docker build -f engine/docker/Dockerfile.ubuntu -t "$$img" engine; fi
@if [ -n "$(NO_CAPTURE)" ]; then echo "→ skipping T3 baseline capture (NO_CAPTURE=1)"; else \
echo "→ refreshing T3 baselines on the realigned clean tree (t3_baseline --update)"; \
$(PYTHON) -c "import tomllib; d=tomllib.load(open('pdca.toml','rb')); [print(c['cmd']) for c in d['gates']['checks'] if c.get('tier')=='T3']" \
| while read -r cmd; do \
echo " → $$cmd --update"; \
bash -c "$$cmd --update" || echo " warn: capture failed for: $$cmd"; \
done; \
echo "→ T3 baselines refreshed — review & commit engine/baselines/*.json (a contribution's"; \
echo " patch-introduced red still surfaces as a delta at gate time; #89 warns on tree drift)"; \
fi
@echo "preflight ready."
# --- optional real install (venv console script) ---------------------------
# Name-agnostic: the console script is named per pyproject [project.scripts]
# (the cli_name copier choice), so depend on a sentinel, not a fixed script path.
# Also symlinks each script into ~/.local/bin so it works without activating the
# venv — the venv shebang keeps the right interpreter regardless of environment.
install: .venv/.installed
@mkdir -p "$$HOME/.local/bin"; \
for s in $$($(PYTHON) -c "import tomllib; print(' '.join(tomllib.load(open('pyproject.toml','rb'))['project']['scripts']))"); do \
ln -sf "$$(pwd)/.venv/bin/$$s" "$$HOME/.local/bin/$$s"; \
echo "→ $$HOME/.local/bin/$$s -> $$(pwd)/.venv/bin/$$s"; \
done; \
case ":$$PATH:" in *":$$HOME/.local/bin:"*) ;; \
*) echo "warn: ~/.local/bin is not on PATH — add it to use the command without the venv";; esac
@printf '\nInstalled. The console script (see pyproject [project.scripts]) is on .venv/bin/ and symlinked into ~/.local/bin; or keep using `make flow …`.\n'
.venv/.installed: pyproject.toml
@$(PYTHON) -c 'import ensurepip' 2>/dev/null || { \
echo 'error: $(PYTHON) cannot create a venv with pip (no ensurepip).'; \
echo ' fix: sudo apt-get install -y python3-venv — then re-run make install'; exit 1; }
$(PYTHON) -m venv .venv
.venv/bin/pip install -q -e '.[dev]'
@touch $@
# --- clean-machine entry points ---------------------------------------------
# `make bootstrap` = one-command setup (venv, permissions, forks, worktrees, image,
# scraper, offline smoke test); idempotent. MINIMAL=1 skips everything needing
# network/docker (CI mode). `make doctor` reports every prerequisite without
# changing anything; STRICT=1 fails on any non-OK line. doctor is the single-sourced
# `pdca doctor` (config + pdca.toml [[doctor.checks]]); scripts/doctor.sh is a shim
# over the same for the pre-venv bootstrap path.
bootstrap:
@bash scripts/bootstrap.sh $(if $(MINIMAL),--minimal) $(if $(APT),--apt) $(if $(SSH),--ssh)
doctor:
@$(PDCA) doctor $(if $(STRICT),--strict)
# --- self-test -------------------------------------------------------------
# Depends on `check` so the cheap guards fail fast before the slow live cycle.
test: check
@echo "== full cycle on a throwaway bundle (fires the live gramps engine) =="
@rm -rf results/issue_selftest
PDCA_LEAVES_MODE=stub $(PDCA) init-issue selftest --from-brief examples/toy/brief.md
PDCA_LEAVES_MODE=stub $(PDCA) run selftest
@printf '\n\xe2\x9c\x93 driver + engine OK. Inspect:\n'
@printf ' results/issue_selftest/check-gates.md (live gramps gate: real pass/fail)\n'
@printf ' results/issue_selftest/SUMMARY.md (assembled Check summary)\n'
# Prefer the venv's pytest when `make install` has run (a clean machine's system
# python has no pytest); fall back to $(PYTHON) for developers with pytest global.
check:
@if [ -x .venv/bin/pytest ]; then PYTHONPATH=src .venv/bin/pytest tests engine/tests -q; \
else $(PYTHON) -m pytest tests engine/tests -q; fi