arborist/Makefile
russell@unturf.com abe5988bef
fan-out: 5 π* graduations close the registry chapter
tabular-pinned@v1 + calculus-limit@v1 + calculus-series@v1 +
linear-algebra@v1 + function-sampled@v1 — all reserved stubs
graduated; the π* registry is now 15 concrete kernels with no
remaining reserved-stub entries.

#000030 Phase 4 — calculus-limit@v1
====================================

sp.limit with thread-timeout. One-sided dir support (+/-/+-).
Pinned spelling for infinity cases: b"+oo" / b"-oo" / b"zoo"
(complex infinity) — bypasses sp.expand since Infinity isn't
algebraic. Finite results re-canonicalize through algebra-symbolic
recipe (sp.expand + sp.srepr). Unevaluated cases / timeouts emit
b"unevaluated:" + sp.srepr(<Limit>) sentinel, mirroring
calculus-integral's pattern.

#000030 Phase 5 — calculus-series@v1
=====================================

sp.series(f, x, x0, n).removeO() → sp.expand → sp.srepr. Drops
O(x**n) remainder explicitly so the canonical form is finite-byte.
Sentinel format mirrors limit/integral: b"unevaluated:Series(...)"
on timeout. n must be a positive int; 0 / float / negative rejected.

#000030 Phase 6 — linear-algebra@v1
====================================

Single π* covers the whole linear-algebra surface via {op, matrix}
JSON. Ops: rref / det / eigenvalues / inverse. Matrix cells go
through Fraction(Decimal(str(...))) for floats so 1, 1.0, "1.0"
all collapse to Rational(1, 1) — matching arithmetic@v1's
discipline. Without this fold, sp.sympify keeps floats as Float
(separate type) and downstream det/inverse return Float-shaped
bytes. Eigenvalues are sorted by srepr for determinism.

Output formats:
  rref / inverse:  rows/cols header + cells joined by | (rows by ||)
  det:             det:<num/den-or-srepr>
  eigenvalues:     eigenvalues:<value-1>x<mult-1>|...

#000030 Phase 7 — function-sampled@v1
======================================

Bridge to time-series-quantized@v1. SymPy expression + linspace
grid → quantized integer-vector signature in time-series's exact
output format (dt=...;dv=...;n=...;t0=0:v0|v1|...). Two functions
that render identically (within sample-grid tolerance) collapse
to the same canonical bytes. This is what plotting CAN become
in π* terms — the PNG render is a downstream view of the same
canonical evidence.

Math-only sampler (no numpy in the dep surface); Python's round()
is banker's-rounding so the bytes are interchangeable with
time-series-quantized@v1's output. Complex / non-finite samples
raise PiStarError rather than silently dropping imaginary parts.

tabular-pinned@v1 — last reserved stub graduates
=================================================

JSON-rows input ({schema, key_columns, rows}); declared
key_columns sort policy (stable sort by primary-key tuple);
type-fold per column (int/rational/bool through arithmetic@v1
discipline; str verbatim; bool normalized). Header case is
PINNED EXACT — Excel and PostgreSQL both care about case;
defaulting to lowercase-fold would break operator expectations.

Output: header (schema + key + n) + rows joined by \n + cells by |.

The π* registry has no remaining reserved stubs. Every modality
the substrate paper reserved is now real.

Test suite: 1568 passed (was 1467; +101). New closure-criterion
test (test_no_stub_pi_stars_remain) replaces the old reserved-stub
parametrize — adding a future stub re-opens this list.

110/110 fixtures pass across the 5 new bench-5s-* targets.
PHASE_1_CARRIERS gained calculus / linear-algebra / function-sampled
/ tabular.
2026-05-09 13:04:43 -04:00

663 lines
35 KiB
Makefile
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# arborist — Makefile entry points
# Every workflow lives behind a `make` target. Bare python commands are not
# the user interface.
# Tools and config
PYTHON ?= python3
VENV ?= .venv
PIP := $(VENV)/bin/pip
PY := $(VENV)/bin/python
ARBORIST := $(VENV)/bin/arborist
# Data + DB
DATA_DIR ?= data
WP_BASE_URL ?= https://dumps.wikimedia.org/archive/2003/2003-05-16/en
WP_CUR := $(DATA_DIR)/20030516_cur_tablesql.bz2
WP_OLD_1 := $(DATA_DIR)/old_tablesqlbz2.1
WP_OLD_2 := $(DATA_DIR)/old_tablesqlbz2.2
WP_OLD := $(DATA_DIR)/20030516_old_tablesql.bz2
# Back-compat alias (older callers used WP_DUMP for the cur snapshot).
WP_DUMP := $(WP_CUR)
DB ?= $(HOME)/.arborist/arborist.db
# Smoke-test caps so make all stays fast
INGEST_LIMIT ?= 500
VERIFY_N ?= 10
SEARCH_Q ?= computer
.PHONY: all bootstrap fetch fetch-cur fetch-old fetch-xml fetch-abstract \
ingest ingest-cur ingest-old ingest-xml ingest-xml-history \
ingest-xml-attached ingest-abstract \
ingest-self ingest-self-providence ingest-git ingest-hg \
verify search stats test test-live docs docs-api docs-api-clean \
chain-check chain-check-shards \
falsify burn burn-kindergarten inspect bootstrap-crawler test-crawler crawl-ingest \
recrawl-check bench-qa bootstrap-math clean clean-db clean-data help
all: bootstrap fetch-cur ingest-cur verify stats ## bootstrap → fetch cur → ingest cur → verify → stats
help: ## show this help
@awk 'BEGIN{FS=":.*##"} /^[a-zA-Z0-9_-]+:.*##/{printf " %-16s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
$(VENV)/bin/activate: pyproject.toml
$(PYTHON) -m venv $(VENV)
$(PIP) install --upgrade pip wheel
$(PIP) install -e '.[dev]'
@touch $(VENV)/bin/activate
bootstrap: $(VENV)/bin/activate ## create venv and install editable package
$(DATA_DIR):
mkdir -p $(DATA_DIR)
$(WP_CUR): | $(DATA_DIR)
@echo ">> fetching $(WP_BASE_URL)/$$(basename $@)"
curl -fL --retry 3 -o $@ "$(WP_BASE_URL)/$$(basename $@)"
$(WP_OLD_1): | $(DATA_DIR)
@echo ">> fetching $(WP_BASE_URL)/$$(basename $@)"
curl -fL --retry 3 -o $@ "$(WP_BASE_URL)/$$(basename $@)"
$(WP_OLD_2): | $(DATA_DIR)
@echo ">> fetching $(WP_BASE_URL)/$$(basename $@)"
curl -fL --retry 3 -o $@ "$(WP_BASE_URL)/$$(basename $@)"
# The two old_tablesqlbz2.{1,2} parts are split halves of a single bzip2
# stream (.1 is exactly 640 MiB). Concatenate to get a working bz2 file.
$(WP_OLD): $(WP_OLD_1) $(WP_OLD_2)
@echo ">> concatenating old dump parts"
cat $(WP_OLD_1) $(WP_OLD_2) > $@
fetch-cur: $(WP_CUR) ## download cur table dump (~82 MB)
fetch-old: $(WP_OLD) ## download old (revision history) parts and concatenate (~893 MB)
fetch: fetch-cur fetch-old ## download all 3 files (cur + old.1 + old.2 + concat)
ingest-cur: bootstrap fetch-cur ## ingest INGEST_LIMIT cur articles
$(ARBORIST) --db $(DB) ingest --source wikipedia_cur --path $(WP_CUR) --limit $(INGEST_LIMIT)
ingest-old: bootstrap fetch-old ## ingest INGEST_LIMIT old (history) revisions
$(ARBORIST) --db $(DB) ingest --source wikipedia_old --path $(WP_OLD) --limit $(INGEST_LIMIT)
# Phase 2: attach-forever sharding. Each shard owns its own SQLite file —
# no WAL contention. Reads via `arborist --shards-dir <dir> <cmd>` attach
# all shards as UNION views. "Merge cost" = 0.
SHARDS ?= 4
SHARDS_DIR ?= $(HOME)/.arborist/shards
ingest-cur-attached: bootstrap fetch-cur ## sharded ingest, no WAL contention (Phase 2)
@mkdir -p $(SHARDS_DIR)
@for i in $$(seq 0 $$(($(SHARDS) - 1))); do \
$(ARBORIST) ingest --source wikipedia_cur --path $(WP_CUR) \
--shards-dir $(SHARDS_DIR) --shard $$i/$(SHARDS) & \
done; wait
ingest-old-attached: bootstrap fetch-old ## sharded ingest of old history (Phase 2)
@mkdir -p $(SHARDS_DIR)
@for i in $$(seq 0 $$(($(SHARDS) - 1))); do \
$(ARBORIST) ingest --source wikipedia_old --path $(WP_OLD) \
--shards-dir $(SHARDS_DIR) --shard $$i/$(SHARDS) & \
done; wait
stats-shards: bootstrap ## cross-shard stats via UNION views over $(SHARDS_DIR)
$(ARBORIST) --shards-dir $(SHARDS_DIR) stats
ACTIVITY_LIMIT ?= 10
activity: bootstrap ## recent Q&A + freshly cached docs (agent timeline)
$(ARBORIST) --shards-dir $(SHARDS_DIR) activity --limit $(ACTIVITY_LIMIT)
inspect: bootstrap ## sidecar diagnose unverified spans for a cache_key: make inspect KEY=hex [JSON=1]
@if [ -z "$(KEY)" ]; then echo "usage: make inspect KEY=<cache_key> [JSON=1]" >&2; exit 2; fi
$(ARBORIST) --shards-dir $(SHARDS_DIR) inspect --cache-key $(KEY) $(if $(JSON),--json,)
falsify: bootstrap ## mark a cached answer wrong: make falsify KEY=hex REASON='why'
@if [ -z "$(KEY)" ]; then echo "usage: make falsify KEY=<cache_key> REASON='why'" >&2; exit 2; fi
$(ARBORIST) --shards-dir $(SHARDS_DIR) providence --falsify $(KEY) --reason "$(REASON)"
burn: bootstrap ## delete a leaf with no children. providence: KEY=<cache_key>; document/core: KIND=document|core ROOT=<hex>. REASON='why' [FORCE=1]
@kind="$${KIND:-providence}"; \
if [ "$$kind" = "providence" ]; then \
if [ -z "$(KEY)" ]; then echo "usage: make burn KEY=<cache_key> REASON='why' [FORCE=1]" >&2; exit 2; fi; \
$(ARBORIST) --shards-dir $(SHARDS_DIR) burn --kind providence --cache-key $(KEY) --reason "$(REASON)" $(if $(FORCE),--force,); \
elif [ "$$kind" = "document" ] || [ "$$kind" = "core" ]; then \
if [ -z "$(ROOT)" ]; then echo "usage: make burn KIND=$$kind ROOT=<document_root> REASON='why' [FORCE=1]" >&2; exit 2; fi; \
$(ARBORIST) --shards-dir $(SHARDS_DIR) burn --kind $$kind --root $(ROOT) --reason "$(REASON)" $(if $(FORCE),--force,); \
else \
echo "unknown KIND: $$kind (expected: providence|document|core)" >&2; exit 2; \
fi
# Mass-burn providence_cache rows younger than the kindergarten window.
# Mirrors mesh sync's kindergarten so what's still un-broadcast is what's
# safe to bust without confusing peers. Useful while iterating on
# retrieval/verifier tunings — wipe recent test runs in one shot.
KG_SECONDS ?= 3600
burn-kindergarten: bootstrap ## bust providence rows < SECONDS old [SECONDS=3600 FORCE=1 DRY_RUN=1 REASON='why']
$(ARBORIST) --shards-dir $(SHARDS_DIR) burn-kindergarten \
--kindergarten-seconds $(KG_SECONDS) \
$(if $(REASON),--reason "$(REASON)",) \
$(if $(FORCE),--force,) \
$(if $(DRY_RUN),--dry-run,)
# Multi-source RAG query against the shard cluster.
# Usage: make query Q="What is anarcho-capitalism?"
QUERY_TOP_K ?= 8
# G0 / CTI — pointer-mode is the testing default. Library DEFAULT_POLICY
# stays "quote" so Python callers aren't surprised; the Makefile
# harness ships pointer-mode-on so `make query` exercises the new path
# end-to-end. ANSWER_MODE default flipped to claim_lattice (JSON) on
# 2026-04-30 after the post-retry bench showed it leading on
# strict-rate (50%) and grounded count (54) with 0 errors. Override
# with ANSWER_MODE=claim_lattice_pointer for prose-distribution path
# (Hermes-3 8B reflexive output without grammar guidance), or
# ANSWER_MODE=quote for the legacy substring verifier. ANSWER_MODE=
# (empty) defers to DEFAULT_QUERY_POLICY.
ANSWER_MODE ?= claim_lattice
# BROAD=1 → flip on the Ticket #000008 quantifier-cap apply-gate for
# this call. Default-off per §10.11.3 dry-run discipline; operator
# opts in here for broad-quantifier shapes (winners-of-all,
# tell-me-everything-about-X). Pairs cleanly with the broad-
# quantifier reminder which is default-on for lattice modes.
# Bench (#000008 §12.10): cap-on JSON wins +14pp on STRICT-rate.
# REJECT_BROAD=1 → strict reject for ALL/COMPREHENSIVE/OPEN_REQUEST
# unbounded shapes; returns UNGROUNDED before the LLM call.
# ALLOW_BROAD=1 → emergent search; classifier on, caps off.
query: bootstrap ## ask the corpus a question [JSON=1 BURN=1 REPAIR=1 REPROMPTS=N K="extra retrieval keywords" ANSWER_MODE=claim_lattice|claim_lattice_pointer|quote BROAD=1 REJECT_BROAD=1 ALLOW_BROAD=1 WITNESS=1]; JSON by default
@if [ -z "$$Q" ] && [ -z "$(Q)" ]; then \
echo "usage: make query Q=\"your question\" [JSON=1 BURN=1 REPAIR=1 REPROMPTS=N K=\"extra retrieval keywords\" ANSWER_MODE=claim_lattice|claim_lattice_pointer|quote BROAD=1 REJECT_BROAD=1 ALLOW_BROAD=1 WITNESS=1]"; exit 2; \
fi
$(ARBORIST) --shards-dir $(SHARDS_DIR) query --top-k $(QUERY_TOP_K) $(if $(JSON),--json,) $(if $(BURN),--burn,) $(if $(REPAIR),--repair,) $(if $(REPROMPTS),--repair-reprompts $(REPROMPTS),) $(if $(ANSWER_MODE),--answer-mode $(ANSWER_MODE),) $(if $(K),--retrieval-keywords "$(K)",) $(if $(BROAD),--apply-quantifier-caps,) $(if $(REJECT_BROAD),--reject-broad,) $(if $(ALLOW_BROAD),--allow-broad,) $(if $(WITNESS),--witness,) "$(Q)"
query-dry: bootstrap ## like 'make query' but skip the LLM call (dry-run) [JSON=1 BURN=1 ANSWER_MODE=... BROAD=1 REJECT_BROAD=1 ALLOW_BROAD=1]
@if [ -z "$$Q" ] && [ -z "$(Q)" ]; then \
echo "usage: make query-dry Q=\"your question\" [JSON=1 BURN=1 ANSWER_MODE=claim_lattice|claim_lattice_pointer|quote BROAD=1 REJECT_BROAD=1 ALLOW_BROAD=1]"; exit 2; \
fi
$(ARBORIST) --shards-dir $(SHARDS_DIR) query --top-k $(QUERY_TOP_K) --dry-run $(if $(JSON),--json,) $(if $(BURN),--burn,) $(if $(ANSWER_MODE),--answer-mode $(ANSWER_MODE),) $(if $(BROAD),--apply-quantifier-caps,) $(if $(REJECT_BROAD),--reject-broad,) $(if $(ALLOW_BROAD),--allow-broad,) "$(Q)"
BENCH_QA_QUESTIONS ?= bench/qa_questions.txt
BENCH_QA_OUT ?= bench/qa_results
BENCH_QA_MODES ?= quote,claim_lattice_pointer,claim_lattice
BENCH_QA_LIMIT ?= 0
BENCH_QA_N ?= 3
BENCH_QA_CONCURRENCY ?= 4
bench-qa: bootstrap ## QA-quality sweep: questions × modes × N samples [BENCH_QA_N=3 BENCH_QA_LIMIT=N BENCH_QA_MODES=... BENCH_QA_CONCURRENCY=4]
PYTHONUNBUFFERED=1 $(PY) bench/qa_sweep.py \
--questions $(BENCH_QA_QUESTIONS) \
--shards-dir $(SHARDS_DIR) \
--out-dir $(BENCH_QA_OUT) \
--top-k $(QUERY_TOP_K) \
--modes $(BENCH_QA_MODES) \
--limit $(BENCH_QA_LIMIT) \
--n $(BENCH_QA_N) \
--concurrency $(BENCH_QA_CONCURRENCY)
# Smoke fixture: 5 questions, all anchor classes, all currently failing
# pointer mode 100% while JSON aces 100%. Inner loop for prompt iteration.
# ~30s wall-clock at concurrency=4. Use this between full sweeps.
bench-qa-smoke: bootstrap ## quick 5-question smoke (all anchor classes; ~30s)
PYTHONUNBUFFERED=1 $(PY) bench/qa_sweep.py \
--questions bench/qa_questions_smoke.txt \
--shards-dir $(SHARDS_DIR) \
--out-dir $(BENCH_QA_OUT) \
--top-k $(QUERY_TOP_K) \
--modes $(BENCH_QA_MODES) \
--n 1 \
--concurrency $(BENCH_QA_CONCURRENCY)
test-live: bootstrap ## live QA quality tests against Hermes (gated; -n auto parallel)
ARBORIST_LIVE_TESTS=1 ARBORIST_LIVE_SHARDS_DIR=$(SHARDS_DIR) \
.venv/bin/pytest tests/test_qa_quality_live.py -v -n auto
# Concept-layer backfill targets. Each runs an extractor across every
# wiki shard; per-shard work is independent so we use GNU-parallel-
# style concurrency with `xargs -P` to overlap the slow paths
# (link_reciprocity ~50s/shard, token_idf ~12s/shard, documents_fts
# ~3s/shard). Total wall-clock with -P 4 vs serial:
# serial: link 200s + idf 50s + fts 10s = 260s
# parallel: link 50s + idf 12s + fts 3s ≈ 65s (~4× speedup)
CONCEPTS_WORKERS ?= 4
backfill-concepts: bootstrap ## backfill all concept extractors in parallel across shards
PYTHONUNBUFFERED=1 $(PY) scripts/backfill_concepts.py \
--shards-dir $(SHARDS_DIR) \
--workers $(CONCEPTS_WORKERS)
# Quick bench mode — 1 sample, smoke fixture, all 3 modes. ~10s.
# For pure smoke after a code change before the longer bench-qa-smoke.
# Emergent stress-test: random word triangulation. Pick 3 words from
# /usr/share/dict/words, ask Hermes @ temp=0.8 to weave them into a
# creative question, send to arborist, append the journey to
# bench/emergent_log.jsonl. Designed for blue-moon cadence — surfaces
# combinatoric failure modes the curated bench-qa fixture set can't.
# Teacher review (Opus) runs separately via `--print-pending`; fox
# brings entries here & gets back guidance to append to the log.
EMERGENT_N ?= 10
EMERGENT_SEED ?=
bench-emergent: bootstrap ## blue-moon emergent stress test (3-word triangulation; N=10)
PYTHONUNBUFFERED=1 $(PY) scripts/bench_emergent.py \
--n $(EMERGENT_N) \
$(if $(EMERGENT_SEED),--seed $(EMERGENT_SEED),) \
--shards-dir $(SHARDS_DIR) \
--qa-db $(SHARDS_DIR)/qa.db
bench-emergent-pending: bootstrap ## print log entries awaiting teacher review
$(PY) scripts/bench_emergent.py --print-pending
bench-5s: bootstrap ## 5S battery (Syntax+Semantics+Syllogism+Synthesis+Semiotics)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax --fixtures bench/fixtures/5s/syntax-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics --fixtures bench/fixtures/5s/semantics-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syllogism --fixtures bench/fixtures/5s/syllogism-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub synthesis --fixtures bench/fixtures/5s/synthesis-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semiotics --fixtures bench/fixtures/5s/semiotics-v1.jsonl
bench-5t: bootstrap ## 5T battery (Transfer Learning+Triangulation+Truthtables+Transitivity+Time)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5t --sub transfer-learning --fixtures bench/fixtures/5t/transfer-learning-v2.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5t --sub triangulation --fixtures bench/fixtures/5t/triangulation-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5t --sub truthtables --fixtures bench/fixtures/5t/truthtables-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5t --sub transitivity --fixtures bench/fixtures/5t/transitivity-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5t --sub time --fixtures bench/fixtures/5t/time-v1.jsonl
bench-5t-legacy: bootstrap ## 5T legacy SQD-name (transfer-v1) for Phase-1a digest stability
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5t --sub transfer --fixtures bench/fixtures/5t/transfer-v1.jsonl
bench-5f: bootstrap ## 5F battery (Function+Finetuning+Falsification+Formulate+Feedback Loop)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5f --sub function --fixtures bench/fixtures/5f/function-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5f --sub finetuning --fixtures bench/fixtures/5f/finetuning-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5f --sub falsification --fixtures bench/fixtures/5f/falsification-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5f --sub formulate --fixtures bench/fixtures/5f/formulate-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5f --sub feedback-loop --fixtures bench/fixtures/5f/feedback-loop-v1.jsonl
bench-5s-code: bootstrap ## 5S code-carrier (Syntax + Semantics through code-py-ast@v1)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-code-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-code-v1.jsonl
bench-5s-arithmetic: bootstrap ## 5S arithmetic π* (SQD §14.1; rational arithmetic canonicalizer)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-arithmetic-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-arithmetic-v1.jsonl
bench-5s-logic-kernel: bootstrap ## 5S logic-kernel π* (SQD §14.3; CNF canonicalizer)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-logic-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-logic-v1.jsonl
bench-5s-math: bench-5s-arithmetic bench-5s-logic-kernel ## complete math π* surface (arithmetic + logic-kernel)
bench-5s-algebra: bootstrap-math ## 5S algebra-symbolic π* (ticket #000030 Phase 1; SymPy expand+srepr canonicalizer)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-algebra-symbolic-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-algebra-symbolic-v1.jsonl
bench-5s-time-series: bootstrap ## 5S time-series-quantized π* (SQD §13.5; quantized integer-vector canonicalizer)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-time-series-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-time-series-v1.jsonl
bench-5s-tabular: bootstrap ## 5S tabular-pinned π* (#000030/Phase tabular; declared-schema 2D structured-data canonicalizer)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-tabular-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-tabular-v1.jsonl
bench-5s-calculus-limit: bootstrap ## 5S calculus-limit π* (#000030 Phase 4)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-calculus-limit-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-calculus-limit-v1.jsonl
bench-5s-calculus-series: bootstrap ## 5S calculus-series π* (#000030 Phase 5)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-calculus-series-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-calculus-series-v1.jsonl
bench-5s-linear-algebra: bootstrap ## 5S linear-algebra π* (#000030 Phase 6)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-linear-algebra-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-linear-algebra-v1.jsonl
bench-5s-function-sampled: bootstrap ## 5S function-sampled π* (#000030 Phase 7; SymPy → time-series bridge)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-function-sampled-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-function-sampled-v1.jsonl
bench-real-shard: bootstrap ## #000026 Phase 2 — real-shard workload baseline (latency, audit, primary-source use)
PYTHONUNBUFFERED=1 $(PY) -m bench.scripts.real_shard_baseline \
--shards-dir $${ARBORIST_SHARDS_DIR:-$$HOME/.arborist/shards} \
--burn
# v8 ForkScore — runs the full bench-suite, then scores the fresh
# child output against a previously-pinned PARENT artifact. Default
# parent is bench/results/baseline-suite.json (operator pins this
# once via `make bench-fork-baseline`); override per-call with
# PARENT=path. Fox's iteration loop:
#
# make bench-fork-baseline # one-shot — pins current state
# <hack hack hack>
# make bench-fork-score # compare hacked branch to baseline
# # exit 1 on REJECT (CI-gateable)
FORK_PARENT ?= bench/results/baseline-suite.json
FORK_CHILD ?= bench/results/current-suite.json
FORK_REPORT ?= bench/results/fork_score_report.json
bench-fork-baseline: bootstrap ## pin current bench-suite output as ForkScore parent
@mkdir -p bench/results
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --all \
--out $(FORK_PARENT)
@echo ">> baseline pinned: $(FORK_PARENT)"
bench-fork-score: bootstrap ## #000012 Phase 1b — score current bench output vs $(FORK_PARENT)
@mkdir -p bench/results
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --all \
--out $(FORK_CHILD)
$(ARBORIST) v8 score \
--parent $(FORK_PARENT) \
--child $(FORK_CHILD) \
--out $(FORK_REPORT)
bench-5f-formulate-live: bootstrap ## 5F Formulate via live arborist.qa.parse_claims (Phase 1b.2)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5f --sub formulate \
--fixtures bench/fixtures/5f/formulate-live-v1.jsonl
bench-5f-feedback-loop-live: bootstrap ## 5F Feedback Loop via live arborist memory + audit chain (Phase 1b.2)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5f --sub feedback-loop \
--fixtures bench/fixtures/5f/feedback-loop-live-v1.jsonl
bench-5f-function-live: bootstrap ## 5F Function via live arborist.qa.parse_claims (Phase 1b.2)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5f --sub function \
--fixtures bench/fixtures/5f/function-live-v1.jsonl
bench-5f-finetuning-live: bootstrap ## 5F Finetuning via real selfmodel store/claims_for round-trip (Phase 1b.2)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5f --sub finetuning \
--fixtures bench/fixtures/5f/finetuning-live-v1.jsonl
bench-5f-falsification-live: bootstrap ## 5F Falsification via real arborist.qa.verify.verify_quotes (Phase 1b.2)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5f --sub falsification \
--fixtures bench/fixtures/5f/falsification-live-v1.jsonl
bench-5f-live: bench-5f-formulate-live bench-5f-feedback-loop-live bench-5f-function-live bench-5f-finetuning-live bench-5f-falsification-live ## all 5F Phase-1b.2 live wire-ups
bench-5r: bootstrap ## 5R battery (React+Rearrange+Restore+Replicate+Resonate)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5r --sub react --fixtures bench/fixtures/5r/react-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5r --sub rearrange --fixtures bench/fixtures/5r/rearrange-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5r --sub restore --fixtures bench/fixtures/5r/restore-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5r --sub replicate --fixtures bench/fixtures/5r/replicate-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5r --sub resonate --fixtures bench/fixtures/5r/resonate-v1.jsonl
bench-5r-react-live: bootstrap ## 5R React via real audit_events on a temp shard (Phase 1b.2)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5r --sub react \
--fixtures bench/fixtures/5r/react-live-v1.jsonl
bench-5r-restore-live: bootstrap ## 5R Restore via real audit chain query (Phase 1b.2)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5r --sub restore \
--fixtures bench/fixtures/5r/restore-live-v1.jsonl
bench-5r-live: bench-5r-react-live bench-5r-restore-live ## all 5R Phase-1b.2 live wire-ups
bench-5s5t: bench-5s bench-5t ## 5S + 5T (Phase-1b vocabulary)
bench-5s5t5f: bench-5s bench-5t bench-5f ## 5S + 5T + 5F (operational triad)
bench-suite: bench-5s bench-5t bench-5f bench-5r ## complete Dav1DPrometheus suite (5S + 5T + 5F + 5R)
verify-shards: bootstrap ## cross-shard Merkle round-trip on a random sample
$(ARBORIST) --shards-dir $(SHARDS_DIR) verify -n $(VERIFY_N)
analyze-shards: bootstrap ## cross-shard compression spectrum + audit integrity
$(ARBORIST) --shards-dir $(SHARDS_DIR) analyze
# Audit-chain integrity probe: counts dangling prev_event_hash references.
# Faster than `analyze` and trivially scriptable. 0 = chain intact.
define CHAIN_CHECK_SQL
SELECT COUNT(*) AS chain_breaks FROM audit_events a1
LEFT JOIN audit_events a2 ON a2.event_hash = a1.prev_event_hash
WHERE a1.prev_event_hash IS NOT NULL AND a2.event_hash IS NULL
endef
export CHAIN_CHECK_SQL
chain-check: ## audit-chain break count for $(DB) (0 = intact)
@printf '%s ' "$(DB)"; sqlite3 $(DB) "$$CHAIN_CHECK_SQL"
chain-check-shards: ## audit-chain break count for every *.db in $(SHARDS_DIR)
@for db in $(SHARDS_DIR)/*.db; do \
printf '%s ' "$$db"; sqlite3 "$$db" "$$CHAIN_CHECK_SQL"; \
done
# Parallel per-shard distill: one process per shard. No DB contention
# because each shard is its own file.
distill-shards-parallel: bootstrap ## one distill process per shard (parallel)
@for shard in $(SHARDS_DIR)/*.db; do \
$(ARBORIST) --db $$shard distill --process first-sentence-v1 --kind surface & \
done; wait
# TF-IDF cores serve as enriched titles for retrieval — distinctive
# low-frequency terms that surface in body text get promoted into
# something queryable without a real title match.
distill-shards-tfidf-parallel: bootstrap ## TF-IDF cores per shard, in parallel
@for shard in $(SHARDS_DIR)/*.db; do \
$(ARBORIST) --db $$shard distill --process tfidf-keywords-v1 --kind surface & \
done; wait
ingest: ingest-cur ## default ingest = cur (use ingest-old or *-parallel for full)
# Grok account-export ETL.
# Point GROK_EXPORT at the directory xAI delivered (the one containing
# `ttl/30d/export_data/<user-id>/prod-grok-backend.json`). The source class
# auto-walks down to find the JSON.
GROK_EXPORT ?= $(HOME)/Downloads/ab8ef1f0-0d08-4f87-89c2-d4509e18115b
# Grok lives in its own shard inside the attach-forever cluster so cross-
# shard queries (`make query`) see it alongside the Wikipedia 2003 corpus.
# Single shard (rank 0/1) — Grok conversations are private and small.
GROK_SHARD := $(SHARDS_DIR)/grok.db
ingest-grok-attached: bootstrap ## ingest Grok conversations into $(GROK_SHARD)
@mkdir -p $(SHARDS_DIR)
$(ARBORIST) --db $(GROK_SHARD) ingest --source grok_export --path $(GROK_EXPORT) --resume
ingest-grok-media-attached: bootstrap ## ingest Grok media prompts into $(GROK_SHARD)
@mkdir -p $(SHARDS_DIR)
$(ARBORIST) --db $(GROK_SHARD) ingest --source grok_media --path $(GROK_EXPORT) --resume
# ----------------------------------------------------------------------------
# Phase IV (2006+) Wikipedia XML dumps. Drop-in for any dated snapshot in
# the dumps.wikimedia.org/archive tree by overriding WP_XML_YEAR/MONTH/DATE
# (and WP_XML_LANG for non-English wikis).
#
# Defaults point at enwiki 20101011 (2010-11 archive), the largest snapshot
# in the archive — 6.2 GB compressed, ~3.4M articles.
# Other useful snapshots:
# make fetch-xml WP_XML_YEAR=2006 WP_XML_MONTH=2006-07 WP_XML_DATE=20061104
# make fetch-xml WP_XML_YEAR=2006 WP_XML_MONTH=2006-12 WP_XML_DATE=20061130
# ----------------------------------------------------------------------------
WP_XML_LANG ?= en
WP_XML_YEAR ?= 2010
WP_XML_MONTH ?= 2010-11
WP_XML_DATE ?= 20101011
WP_XML_BASE_URL ?= https://dumps.wikimedia.org/archive/$(WP_XML_YEAR)/$(WP_XML_MONTH)/$(WP_XML_LANG)wiki/$(WP_XML_DATE)
WP_XML_FILE ?= $(WP_XML_LANG)wiki-$(WP_XML_DATE)-pages-articles.xml.bz2
WP_XML := $(DATA_DIR)/$(WP_XML_FILE)
WP_ABSTRACT_FILE ?= $(WP_XML_LANG)wiki-$(WP_XML_DATE)-abstract.xml
WP_ABSTRACT := $(DATA_DIR)/$(WP_ABSTRACT_FILE)
$(WP_XML): | $(DATA_DIR)
@echo ">> fetching $(WP_XML_BASE_URL)/$(WP_XML_FILE)"
curl -fL --retry 3 -o $@ "$(WP_XML_BASE_URL)/$(WP_XML_FILE)"
$(WP_ABSTRACT): | $(DATA_DIR)
@echo ">> fetching $(WP_XML_BASE_URL)/$(WP_ABSTRACT_FILE)"
curl -fL --retry 3 -o $@ "$(WP_XML_BASE_URL)/$(WP_ABSTRACT_FILE)"
fetch-xml: $(WP_XML) ## download Phase IV XML cur dump (default: enwiki 20101011, 6.2 GB)
fetch-abstract: $(WP_ABSTRACT) ## download Phase IV abstract.xml (default: enwiki 20101011, ~3 GB)
ingest-xml: bootstrap fetch-xml ## ingest INGEST_LIMIT pages from $(WP_XML)
$(ARBORIST) --db $(DB) ingest --source wikipedia_xml --path $(WP_XML) --limit $(INGEST_LIMIT)
ingest-xml-history: bootstrap ## ingest every revision (multi-revision mode); set WP_XML to a pages-meta-history file
$(ARBORIST) --db $(DB) ingest --source wikipedia_xml_history --path $(WP_XML) --limit $(INGEST_LIMIT)
# Sharded XML ingest into the attach-forever cluster — same pattern as
# ingest-cur-attached. One process per shard, one SQLite file per shard,
# zero WAL contention.
ingest-xml-attached: bootstrap fetch-xml ## sharded XML ingest, one process per shard
@mkdir -p $(SHARDS_DIR)
@for i in $$(seq 0 $$(($(SHARDS) - 1))); do \
$(ARBORIST) ingest --source wikipedia_xml --path $(WP_XML) \
--shards-dir $(SHARDS_DIR) --shard $$i/$(SHARDS) & \
done; wait
ingest-abstract: bootstrap fetch-abstract ## ingest INGEST_LIMIT abstract docs from $(WP_ABSTRACT)
$(ARBORIST) --db $(DB) ingest --source wikipedia_abstract --path $(WP_ABSTRACT) --limit $(INGEST_LIMIT)
# ----------------------------------------------------------------------------
# Self-ingest: arborist consults its own source code as a queryable corpus.
# Re-running picks up new commits — same path + new content gets a fresh
# document_root chained to the prior version via a `supersedes` edge, so the
# audit chain grows as the repo grows. Lands in a dedicated shard file so it
# doesn't compete with the wikipedia/grok shards' WAL writer lock.
#
# Override SELF_REPO to ingest a different repo's tree.
# ----------------------------------------------------------------------------
SELF_REPO ?= $(CURDIR)
SELF_SHARD := $(SHARDS_DIR)/arborist-self.db
ingest-self: bootstrap ## ingest this repo's HEAD into a dedicated shard
@mkdir -p $(SHARDS_DIR)
$(ARBORIST) --db $(SELF_SHARD) ingest --source git_repo --path $(SELF_REPO)
# Self-reference: promote STRICT live providence records past the
# kindergarten window into each shard's documents table. Each shard
# self-promotes only its own records; cross-shard sharing happens
# via the existing shards-dir UNION at retrieval time. Run on a cron
# (e.g. hourly) to keep the substrate fresh.
# See docs/self-reference-thought-chains-design.md.
KG_SECONDS ?= 3600
ingest-self-providence: bootstrap ## promote STRICT live providence records into the document corpus [KG_SECONDS=3600]
@mkdir -p $(SHARDS_DIR)
@for db in $(SHARDS_DIR)/*.db; do \
echo ">> promoting providence records: $$db"; \
$(ARBORIST) --db $$db ingest --source providence --kindergarten-seconds $(KG_SECONDS); \
done
# Generic git-repo ingest: aim it at any local clone via GIT_REPO=...
GIT_REPO ?= $(CURDIR)
GIT_SHARD := $(SHARDS_DIR)/$(notdir $(GIT_REPO))-git.db
ingest-git: bootstrap ## ingest GIT_REPO=<path> into its own shard
@mkdir -p $(SHARDS_DIR)
$(ARBORIST) --db $(GIT_SHARD) ingest --source git_repo --path $(GIT_REPO)
# Generic hg-repo ingest. HG_REPO=<path>.
HG_REPO ?=
HG_SHARD := $(SHARDS_DIR)/$(notdir $(HG_REPO))-hg.db
ingest-hg: bootstrap ## ingest HG_REPO=<path> (mercurial) into its own shard
@if [ -z "$(HG_REPO)" ]; then echo "usage: make ingest-hg HG_REPO=/path/to/repo" >&2; exit 2; fi
@mkdir -p $(SHARDS_DIR)
$(ARBORIST) --db $(HG_SHARD) ingest --source hg_repo --path $(HG_REPO)
verify: bootstrap ## round-trip Merkle proofs for VERIFY_N random documents
$(ARBORIST) --db $(DB) verify -n $(VERIFY_N)
search: bootstrap ## keyword search; override SEARCH_Q (or pass Q=...)
$(ARBORIST) --db $(DB) search '$(if $(Q),$(Q),$(SEARCH_Q))'
stats: bootstrap ## counts: documents, chunks, edges, audit chain
$(ARBORIST) --db $(DB) stats
test: bootstrap ## run pytest suite (excludes opt-in crawler tests)
$(VENV)/bin/pytest -q --ignore=tests/crawler -n auto
# Crawler tests are off-by-default — they hit the network in many cases
# and require the heavy [crawler] extras (aiohttp, bs4, lxml, etc.).
# Bootstrap installs the extras into the existing venv idempotently.
bootstrap-crawler: bootstrap ## install [crawler] extras into the venv
$(PIP) install -e '.[crawler]'
# SymPy substrate for algebra/calculus π* canonicalizers (ticket #000030).
# Already pulled in transitively by `make bootstrap` via the [dev] extras;
# this target is the explicit opt-in for minimal-install users.
bootstrap-math: bootstrap ## install [math] extras (sympy) into the venv
$(PIP) install -e '.[math]'
test-crawler: bootstrap-crawler ## run only the lifted crawler tests
$(VENV)/bin/pytest -q tests/crawler
# Bridge target: BFS a seed URL, ingest discovered pages into a shard,
# capture ETag/Last-Modified for each so recrawl-check can do conditional
# HEAD requests later. URL is required; DEPTH and MAX have safe defaults.
CRAWL_DEPTH ?= 2
CRAWL_MAX ?= 0
# Per-domain shard so `make query` (which reads $(SHARDS_DIR)) sees the
# crawled content. Shard filename derived from the seed URL's hostname:
# https://russell.ballestrini.net -> $(SHARDS_DIR)/crawl_russell_ballestrini_net.db
# Override with CRAWL_SHARD=... when you want a custom path.
crawl-ingest: bootstrap-crawler ## crawl URL=https://x.com [DEPTH=2 MAX=0 FAST=1] into $(SHARDS_DIR)/crawl_<domain>.db
@if [ -z "$(URL)" ]; then echo "usage: make crawl-ingest URL=https://example.com [DEPTH=2 MAX=0 FAST=1 CRAWL_SHARD=path]" >&2; exit 2; fi
@mkdir -p $(SHARDS_DIR)
@shard="$(CRAWL_SHARD)"; \
if [ -z "$$shard" ]; then \
domain=$$(echo "$(URL)" | sed -E 's,^https?://([^/]+).*$$,\1,' | tr '.' '_'); \
shard="$(SHARDS_DIR)/crawl_$${domain}.db"; \
fi; \
echo " shard: $$shard" >&2; \
$(ARBORIST) --db "$$shard" crawl --seed-url "$(URL)" --depth $(CRAWL_DEPTH) --max-pages $(CRAWL_MAX) $(if $(FAST),--fast,) --ingest
# Fast freshness probe: conditional HEAD per doc, classify fresh/stale/gone.
# Send only If-None-Match + If-Modified-Since headers — server returns 304
# with no body when content unchanged.
RECRAWL_LIMIT ?= 100
# By default check every shard under $(SHARDS_DIR). Pass CRAWL_SHARD=path
# to scope to one. DOMAIN= filters to URLs containing the substring.
recrawl-check: bootstrap-crawler ## conditional HEAD per ingested doc [DOMAIN=x.com LIMIT=100 CRAWL_SHARD=path]
@if [ -n "$(CRAWL_SHARD)" ]; then \
$(ARBORIST) --db $(CRAWL_SHARD) crawler recrawl-check $(if $(DOMAIN),--domain $(DOMAIN),) --limit $(RECRAWL_LIMIT); \
else \
for db in $(SHARDS_DIR)/*.db; do \
case "$$(basename $$db)" in qa.db|snapshots.db) continue;; esac; \
echo " shard: $$db" >&2; \
$(ARBORIST) --db "$$db" crawler recrawl-check $(if $(DOMAIN),--domain $(DOMAIN),) --limit $(RECRAWL_LIMIT); \
done; \
fi
DOT_SRCS := $(wildcard docs/diagrams/*.dot)
DOT_PNGS := $(DOT_SRCS:.dot=.png)
DOT_SVGS := $(DOT_SRCS:.dot=.svg)
docs/diagrams/%.png: docs/diagrams/%.dot
dot -Tpng $< -o $@
docs/diagrams/%.svg: docs/diagrams/%.dot
dot -Tsvg $< -o $@
docs: $(DOT_PNGS) $(DOT_SVGS) ## render docs/diagrams/*.dot -> .png + .svg via graphviz
docs-api: ## generate Sphinx API reference from docstrings (output: docs/_source/_build/html/)
$(PY) -m sphinx.cmd.build -b html docs/_source docs/_source/_build/html
docs-api-clean: ## remove Sphinx build artifacts
rm -rf docs/_source/_build/
# Reproducible micro-benchmark over a fixed slice of cur. Lets you compare
# ETL throughput across configs and catches regressions on optimization
# work. Override BENCH_DOCS=N (default 5000).
BENCH_DOCS ?= 5000
BENCH_DIR := /tmp/arborist-bench
bench: bootstrap fetch-cur ## benchmark serial vs parallel-shared vs attached at $(BENCH_DOCS) docs
@bash bench/run.sh $(BENCH_DOCS)
clean: ## remove venv + caches (keeps fetched data and db)
rm -rf $(VENV) .pytest_cache **/__pycache__ arborist.egg-info
find . -type d -name __pycache__ -prune -exec rm -rf {} +
clean-db: ## drop the arborist db (keeps fetched data and venv)
rm -f $(DB) $(DB)-journal $(DB)-wal $(DB)-shm
clean-data: ## remove fetched dumps
rm -rf $(DATA_DIR)