blob: 4bff995b15fbcd8911ca176420bd5893cab5718b [file]
#!/bin/sh
# The Markdown the demo reads, checked as the demo reads it. scripts/docs-only.sh lets a
# change to docs/ or examples/decisions/ skip the build and the tests, but those files are
# the demo's record: this seeds a scratch record from them as `make demo-ready` does, and
# fails when what reads that record would break.
#
# - every question in the golden suites the demo scores (golden/docs.toml, and
# golden/platform.toml, which act 7 files) names an entry the record holds, and its
# keywords find that entry in the top k;
# - every question an act puts to a harness (golden/acts.toml) names an entry the
# record holds;
# - every task in evals/demo.toml names, in `answers`, an entry the record holds, and
# its `expect` matches the text of one of them, or, for a task with `counts`, the
# number of lines of one that match `counts`;
# - act 11 finds both of its lists at the top of README.md, and prints every item of
# each.
#
# scripts/corpus-check.sh target/debug/amem [directories to seed from]
set -eu
amem=$(cd "$(dirname "$1")" && pwd)/$(basename "$1")
shift
[ $# -gt 0 ] || set -- examples/decisions docs
here=$(cd "$(dirname "$0")/.." && pwd)
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
trap 'exit 1' INT TERM HUP
root=$work/record
cd "$here"
"$amem" --root "$root" init --owner human:demo:check --domain dom-demo >/dev/null
python3 scripts/seed-corpus.py --strict --root "$root" --memory "$amem" "$@" >"$work/seed.txt"
tail -n 1 "$work/seed.txt"
for suite in docs platform acts; do
"$amem" --root "$root" golden "golden/$suite.toml" --json >"$work/$suite.json"
done
python3 - evals/demo.toml "$work/evals.toml" <<'PY'
import json, sys, tomllib
tasks = tomllib.load(open(sys.argv[1], "rb"))["task"]
bad = [t["id"] for t in tasks if not t.get("answers")]
if bad:
sys.exit(f"evals/demo.toml: tasks without `answers`: {', '.join(bad)}")
with open(sys.argv[2], "w") as out:
out.write('domain = "dom-demo"\n')
for t in tasks:
out.write(f"\n[[question]]\nask = {json.dumps(t['id'], ensure_ascii=False)}\n")
out.write(f"answers = {json.dumps(t['answers'], ensure_ascii=False)}\n")
PY
"$amem" --root "$root" golden "$work/evals.toml" --json >"$work/evals.json"
python3 - "$work" "$root/record/dom-demo" evals/demo.toml <<'PY'
import json, pathlib, re, sys, tomllib
work, record, evals_toml = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2]), sys.argv[3]
fail = []
for suite in ("docs", "platform", "acts"):
g = json.loads((work / f"{suite}.json").read_text())
fail += [f"golden/{suite}.toml: no entry is titled as the answer to: {q}" for q in g["unresolved"]]
if suite != "acts":
fail += [
f"golden/{suite}.toml: its keywords do not find the answer in the top {g['k']}: {r['ask']}"
for r in g["rows"]
if r["strategy"] == "keywords" and r["rank"] is None
]
for s in g["scores"]:
print(f" golden/{suite}.toml {s['strategy']:<9} {s['hits']} of {s['asked']}, MRR {s['mrr']:.2f}")
else:
print(f" golden/acts.toml: {g['questions'] - len(g['unresolved'])} of {g['questions']} questions name an entry")
e = json.loads((work / "evals.json").read_text())
fail += [f"evals/demo.toml: no entry is titled as the answer to task {t}" for t in e["unresolved"]]
# the entries as golden matches them: an answer is a part of the title, in any case
entries = []
for p in record.glob("*.md"):
_, front, body = p.read_text().split("---\n", 2)
title = next((l[7:].strip() for l in front.splitlines() if l.startswith("title: ")), "")
if title[:1] == "'" and title[-1:] == "'":
title = title[1:-1].replace("''", "'")
elif title[:1] == '"' and title[-1:] == '"':
title = json.loads(title)
entries.append((title.lower(), body))
matched = 0
for t in tomllib.load(open(evals_toml, "rb"))["task"]:
bodies = [b for title, b in entries if any(a.strip().lower() in title for a in t["answers"])]
if "counts" in t:
bodies = [str(len(re.findall(t["counts"], b, re.M))) for b in bodies]
if bodies and not any(re.search(t["expect"], b, re.I | re.S) for b in bodies):
fail.append(f"evals/demo.toml: task {t['id']}: its `expect` matches no text of the entries its `answers` name")
elif bodies:
matched += 1
print(f" evals: {e['questions'] - len(e['unresolved'])} of {e['questions']} tasks name an entry, {matched} of them hold the answer")
if fail:
sys.exit("\n".join(fail))
PY
AMEM=$amem sh scripts/demo/11-limits.sh "$root" >"$work/act11.txt"
python3 - "$work/act11.txt" README.md <<'PY'
import re, sys
text, readme = open(sys.argv[1]).read(), open(sys.argv[2]).read()
parts = re.split(r"^11\.\d ", text, flags=re.M)[1:]
counts = [len(re.findall(r"^ - ", p, re.M)) for p in parts]
heads = ("What the paper asks for that is not built", "What is built but works only at demo scale")
# the items of the first list under each heading: it ends at the first line that is not
# an item, a line of one, or blank
wanted = []
for head in heads:
part = readme.split("### " + head, 1)[1].split("\n#", 1)[0] if "### " + head in readme else ""
items, started = 0, False
for line in part.splitlines():
if line.startswith("- "):
items, started = items + 1, True
elif started and line and not line.startswith(" "):
break
wanted.append(items)
print(f" act 11: {' and '.join(map(str, counts))} items, of {' and '.join(map(str, wanted))} in README.md")
if len(counts) != 2 or 0 in counts or counts != wanted:
sys.exit(f"act 11: it does not print every item of the two lists in README.md:\n{text}")
PY