tools: recall.py prompt, the UserPromptSubmit hook that pushes nearest in-force rulings into context
Queries rulings, questions and follow-ups in force across every root, ranked by Huginn against the prompt, through mind.call, and prints one hook JSON within RECALL_BUDGET. Never blocks: any failure or the RECALL_DEADLINE_MS deadline (default 4000) exits 0 with empty stdout; one attempt; prompts under 3 words are not queried. test-tools.sh drives it against the fake binding and pins R1-R7; the fake gains a query branch. INDEX rows and a Precedent note added. The operator installs the hook; settings.json is not touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5e71d8e4ff
commit
f35410a8b2
5 changed files with 269 additions and 3 deletions
|
|
@ -286,6 +286,10 @@ reading its verdicts and findings, check that `admission.provenance.faculty` is
|
|||
`ruling`, `resolution` and `finding` with `root` = `<c>`, plus
|
||||
`"semantic": { "text": "<the question in plain words>", "top_k": 10 }`.
|
||||
|
||||
Once the operator installs the hook, `tools/recall.py` also puts the nearest in-force
|
||||
rulings, questions and follow-ups into every prompt; the recipe remains the
|
||||
deliberate check before raising a question.
|
||||
|
||||
## Substrate gaps
|
||||
|
||||
The known gaps in the mind's substrate are `follow_up`s in the mind, not a
|
||||
|
|
|
|||
|
|
@ -13,4 +13,6 @@ gets a Soul pass before merge.
|
|||
| `tools/mutation-table.sh` | One verify job that runs a list of hand mutants (file, before, after, test) against a revision and returns a kill table: CAUGHT with the killing test, SURVIVED, UNVIABLE, TIMEOUT, NOAPPLY, ERROR. Refuses a red or zero-test baseline and a red BUILD; rows print live. Isolation is Docker's: the baselines run once in a container that holds the revision, that container is committed to a throwaway image, and every mutant runs in a fresh `--rm` container from it, so nothing a run writes (tracked, untracked or ignored, such as `target/`) reaches the next mutant; the image is removed when the job ends, killed or not. The shared registry and sccache volumes and the network are not isolated. A file-level load failure (vitest `FAIL <file> [ <file> ]`, jest `FAIL <file>` plus `Test suite failed to run`) reads UNVIABLE. Replaces per-agent runmut/mutrun/strykrun scripts. | Soul, Hands (scoped run) | `references/briefs.md` (Hands verification, Soul reruns) |
|
||||
| `tools/testparse.sh` | Sourced library: failing test names and pass/fail/skip counts from cargo, dotnet, vitest, jest and pytest logs, and test files that failed to load (`LOADFAIL`). Shared by the summary and the kill table. | the two scripts above | not named by a brief; it lives and dies with them |
|
||||
| `tools/test-tools.sh` | Local fixture check of the parsers and the summary over `tools/fixtures/logs` (real cargo and mutation logs, plus hand-written dotnet, vitest, jest, pytest, exit-0-but-failed and no-verdict logs), and of the mutation runner's host driver and container half under a mock docker. Run it before merging a change to the scripts above. | whoever changes those scripts | `SKILL.md` (Shared tools) |
|
||||
| `tools/context-pack.py` | `py -3 tools/context-pack.py <cut_spec id> <worktree> [--since <sha>] [--only <k>[,<k>...]] [--spec-json <file>] [--max-lines <n>]`. Carries a cut_spec's `file_changes` anchors from the spec's base to the worktree by `git diff` hunks (the working tree included) and prints one header, a status table (SAME, MOVED, TOUCHED, RENAMED, NEW, GONE, OUT_OF_RANGE, UNRESOLVED) and windows of at most 100 lines numbered in worktree lines, within `--max-lines` (800). Exit 3 when an anchor cannot be carried, 4 when the spec is unreadable (fetched by id through the eureka-state binding, retried once on Unavailable; `--spec-json` takes a saved view). `--since <sha>` re-prints only the windows edited since that commit. Standard library plus git; writes nothing. | Hands, Soul, Imagination | `references/briefs.md` (Imagination map, Hands, Soul), `SKILL.md` (step 1) |
|
||||
| `tools/context-pack.py` | `py -3 tools/context-pack.py <cut_spec id> <worktree> [--since <sha>] [--only <k>[,<k>...]] [--spec-json <file>] [--max-lines <n>]`. Carries a cut_spec's `file_changes` anchors from the spec's base to the worktree by `git diff` hunks (the working tree included) and prints one header, a status table (SAME, MOVED, TOUCHED, RENAMED, NEW, GONE, OUT_OF_RANGE, UNRESOLVED) and windows of at most 100 lines numbered in worktree lines, within `--max-lines` (800). Exit 3 when an anchor cannot be carried, 4 when the spec is unreadable (fetched by id through the eureka-state binding (`tools/mind.py`), retried once on Unavailable; `--spec-json` takes a saved view). `--since <sha>` re-prints only the windows edited since that commit. Standard library plus git; writes nothing. | Hands, Soul, Imagination | `references/briefs.md` (Imagination map, Hands, Soul), `SKILL.md` (step 1) |
|
||||
| `tools/mind.py` | The one way a Eureka script reaches the mind: `mind.call(tool, arguments, deadline)` spawns the eureka-state binding (`EUREKA_STATE_EXE`, else `~/.claude.json` `mcpServers.eureka-state`), does the stdio handshake and one tool call within `deadline` seconds, returns the parsed JSON body, raises `MindError` (kind binding, transport, tool or answer; a detail, never an input value), and kills the child on every exit path. No retry inside `call`; `retry_once(fn)` is the caller's choice. Standard library only. | `tools/context-pack.py`, `tools/recall.py` | not named by a brief; it lives and dies with its consumers |
|
||||
| `tools/recall.py` | `py -3 tools/recall.py prompt`, a `UserPromptSubmit` command hook (hook JSON on stdin): queries the rulings, questions and follow-ups in force across every root, ranked by Huginn against the prompt (`RECALL_TOP_K`, 5), and prints one hook JSON whose `additionalContext` lists id, title and a short operator quote per item, within `RECALL_BUDGET` characters (4000), whole lines only. Prompts under 3 words are not queried. Never blocks: any failure exits 0 with empty stdout; its own deadline is `RECALL_DEADLINE_MS` (4000), so set the hook `timeout` above it. One attempt, no retry. Standard library only; writes nothing. | the operator's `UserPromptSubmit` hook in `~/.claude/settings.json` | `references/campaign-state.md` (Precedent) |
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@
|
|||
FAKE_SPEC: the view result to return. FAKE_MODE=unavailable answers isError
|
||||
Unavailable (naming HUGINN_ENDPOINT, as the real daemon's text might). FAKE_COUNT:
|
||||
a file that gains one line per view call. FAKE_MODE=needenv answers the spec only when
|
||||
EUREKA_INSTANCE is "t" and HUGINN_ENDPOINT equals FAKE_NEED, else isError Misconfigured."""
|
||||
import json, os, sys
|
||||
EUREKA_INSTANCE is "t" and HUGINN_ENDPOINT equals FAKE_NEED, else isError Misconfigured.
|
||||
tools/call query (recall.py): FAKE_COUNT gains a "query" line, FAKE_ARGS gains the call's arguments as one
|
||||
JSON line, FAKE_SLEEP seconds pass before the answer (then FAKE_MARK, if set, is written), and the answer is {"items": {"Documents": FAKE_ITEMS}}
|
||||
(a file holding the list); FAKE_MODE unavailable and garbage answer as they do for view."""
|
||||
import json, os, sys, time
|
||||
|
||||
inited = False
|
||||
for ln in sys.stdin:
|
||||
|
|
@ -19,6 +22,24 @@ for ln in sys.stdin:
|
|||
res = {"protocolVersion": "2024-11-05", "capabilities": {}, "serverInfo": {"name": "fake", "version": "0"}}
|
||||
else:
|
||||
p = msg.get("params", {})
|
||||
if p.get("name") == "query" and inited and msg["method"] == "tools/call" and msg["id"] == 2:
|
||||
with open(os.environ["FAKE_COUNT"], "a") as f:
|
||||
f.write("query\n")
|
||||
with open(os.environ["FAKE_ARGS"], "a") as f:
|
||||
f.write(json.dumps(p.get("arguments")) + "\n")
|
||||
time.sleep(float(os.environ.get("FAKE_SLEEP", "0")))
|
||||
if os.environ.get("FAKE_MARK"): # written only by a child that outlived its caller's deadline
|
||||
open(os.environ["FAKE_MARK"], "w").write("alive")
|
||||
if os.environ.get("FAKE_MODE") == "unavailable":
|
||||
res = {"isError": True, "content": [{"type": "text", "text": "Unavailable"}]}
|
||||
elif os.environ.get("FAKE_MODE") == "garbage":
|
||||
res = {"content": [{"type": "text", "text": "not json"}]}
|
||||
else:
|
||||
docs = json.load(open(os.environ["FAKE_ITEMS"], encoding="utf-8"))
|
||||
res = {"content": [{"type": "text", "text": json.dumps({"items": {"Documents": docs}})}]}
|
||||
sys.stdout.write(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": res}) + "\n")
|
||||
sys.stdout.flush()
|
||||
continue
|
||||
if (not inited or msg["method"] != "tools/call" or p.get("name") != "view" or msg["id"] != 2
|
||||
or p.get("arguments") != {"id": {"kind": "CutSpec", "id": os.environ["FAKE_WANT"]}}):
|
||||
sys.exit(1)
|
||||
|
|
|
|||
96
tools/recall.py
Normal file
96
tools/recall.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Push what the mind already holds into an agent's context.
|
||||
|
||||
Usage: recall.py prompt (a UserPromptSubmit command hook; the hook JSON is on stdin)
|
||||
|
||||
Queries the rulings, questions and follow-ups in force, across every root, ranked
|
||||
by Huginn's embeddings against the prompt, and prints one hook JSON whose
|
||||
additionalContext lists the nearest. Huginn's ranking is the only judge of
|
||||
relevance. It never blocks a prompt: every failure, the deadline included, exits 0
|
||||
with empty stdout. One attempt, no retry. Standard library only.
|
||||
Env: RECALL_TOP_K (5), RECALL_BUDGET characters (4000), RECALL_DEADLINE_MS (4000;
|
||||
set the hook's own timeout above it).
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import mind # noqa: E402
|
||||
|
||||
SCHEMAS = ["epiphany.pipeline.ruling.v2", "epiphany.pipeline.question.v2",
|
||||
"epiphany.pipeline.follow_up.v2"]
|
||||
QUERY_BYTES = 1000
|
||||
MIN_WORDS = 3
|
||||
ITEM_CHARS = 240
|
||||
QUOTE_CHARS = 300
|
||||
HEADER = ("The in-force rulings, questions and follow-ups in the Eureka mind nearest this "
|
||||
"prompt, ranked by Huginn and possibly irrelevant; read one in full with the "
|
||||
"eureka-state view tool by id:")
|
||||
|
||||
|
||||
def env_int(name, default):
|
||||
try:
|
||||
v = int(os.environ[name])
|
||||
except (KeyError, ValueError):
|
||||
return default
|
||||
return v if v > 0 else default
|
||||
|
||||
|
||||
def one_line(text, limit):
|
||||
return " ".join(text.split())[:limit]
|
||||
|
||||
|
||||
def line_of(item):
|
||||
ident = item["id"]["id"]
|
||||
value = item["document"]["value"]
|
||||
title = one_line(value["item"], ITEM_CHARS) if item["document"]["kind"] == "follow_up" else value["title"]
|
||||
out = "- %s: %s" % (ident, one_line(title, 1000))
|
||||
quote = value.get("operator_quote")
|
||||
if isinstance(quote, str) and quote and len(quote) <= QUOTE_CHARS:
|
||||
out += ' | operator: "%s"' % one_line(quote, QUOTE_CHARS)
|
||||
return out
|
||||
|
||||
|
||||
def render(items, budget):
|
||||
"""The header and as many whole item lines as fit the budget, in rank order."""
|
||||
kept = [HEADER]
|
||||
for ln in items:
|
||||
if len("\n".join(kept + [ln])) > budget:
|
||||
break
|
||||
kept.append(ln)
|
||||
return "\n".join(kept) if len(kept) > 1 else ""
|
||||
|
||||
|
||||
def prompt_mode(t0):
|
||||
prompt = json.loads(sys.stdin.buffer.read().decode("utf-8"))["prompt"]
|
||||
if not isinstance(prompt, str) or len(prompt.split()) < MIN_WORDS:
|
||||
return ""
|
||||
text = prompt.encode("utf-8")[:QUERY_BYTES].decode("utf-8", "ignore")
|
||||
k = env_int("RECALL_TOP_K", 5)
|
||||
selection = {"schemas": SCHEMAS, "projection": "document",
|
||||
"fields": [{"index": "in_force", "op": "any_of", "values": ["true"]}]}
|
||||
left = env_int("RECALL_DEADLINE_MS", 4000) / 1000.0 - (time.monotonic() - t0)
|
||||
answer = mind.call("query", {"selection": selection, "semantic": {"text": text, "top_k": k}}, left)
|
||||
lines = [line_of(i) for i in answer["items"]["Documents"]]
|
||||
return render(lines, env_int("RECALL_BUDGET", 4000))
|
||||
|
||||
|
||||
def main():
|
||||
t0 = time.monotonic()
|
||||
try:
|
||||
if sys.argv[1:] != ["prompt"]:
|
||||
return
|
||||
context = prompt_mode(t0)
|
||||
if context:
|
||||
out = {"hookSpecificOutput": {"hookEventName": "UserPromptSubmit",
|
||||
"additionalContext": context}}
|
||||
sys.stdout.write(json.dumps(out))
|
||||
sys.stdout.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -513,4 +513,147 @@ mcp env -u EUREKA_INSTANCE HUGINN_ENDPOINT=rudp://caller FAKE_MODE=needenv FAKE_
|
|||
cchk "the caller environment overrides the binding" 3 "$r"; cmp -s "$T/cp.out" "$T/cp.want" && echo "ok context-pack caller env wins over binding env" || { echo "FAIL context-pack caller env lost"; fail=1; }
|
||||
# standard library plus git only
|
||||
if grep -qE '^import (requests|yaml|tree_sitter)|^from (requests|yaml|tree_sitter)|ctags|tree-sitter|"rg"' "$CP"; then echo "FAIL context-pack: a dependency beyond git"; fail=1; else echo "ok context-pack: standard library and git only"; fi
|
||||
# recall.py: the UserPromptSubmit hook that pushes the nearest in-force rulings, questions and follow-ups
|
||||
# into a prompt. Driven against the fake binding; R1-R7 are the rules the cut_spec names.
|
||||
RC=$here/recall.py
|
||||
rcl() { mcp env FAKE_ARGS="$(wp "$T/rc.args")" FAKE_ITEMS="$(wp "$T/rc.items")" "$@"; }
|
||||
# rrun <stdin file> [VAR=val ...]: one hook run; sets rc.out (stdout), rr (exit status), rms (elapsed ms)
|
||||
rrun() {
|
||||
local in=$1; shift; : >"$T/cp.count"; : >"$T/rc.args"
|
||||
local a; a=$(date +%s%N)
|
||||
rcl env "$@" $PY "$RC" prompt <"$in" >"$T/rc.out" 2>"$T/rc.err"; rr=$?
|
||||
rms=$(( ($(date +%s%N) - a) / 1000000 ))
|
||||
}
|
||||
# rrun2: two runs, the faster one's elapsed time (a deadline bug shows in both; a busy host slows one)
|
||||
rrun2() { local a; rrun "$@"; a=$rms; rrun "$@"; [ $a -lt $rms ] && rms=$a; }
|
||||
rcalls() { grep -c '^query$' "$T/cp.count"; }
|
||||
rchk() { if [ "$2" = 0 ] && [ ! -s "$T/rc.out" ] && [ ! -s "$T/rc.err" ]; then echo "ok recall $1"; else echo "FAIL recall $1: exit $2, stdout $(wc -c <"$T/rc.out") bytes, stderr $(wc -c <"$T/rc.err") bytes"; fail=1; fi; }
|
||||
rctx() { $PY -c 'import json,sys;sys.stdout.write(json.load(open(sys.argv[1]))["hookSpecificOutput"]["additionalContext"])' "$(wp "$T/rc.out")"; }
|
||||
$PY - "$(wp "$T")" <<'PYE'
|
||||
import json, sys
|
||||
t = sys.argv[1]
|
||||
def doc(kind, label, value):
|
||||
return {"id": {"id": "t:%s:%s" % (kind, label), "kind": kind.title().replace("_", "")},
|
||||
"document": {"kind": kind, "value": value}, "status": "InForce"}
|
||||
items = [doc("ruling", "alpha", {"title": "Retrieval is pushed", "operator_quote": "pop into context"}),
|
||||
doc("question", "beta", {"title": "Which fork", "operator_quote": None}),
|
||||
doc("follow_up", "gamma", {"item": "f" * 500, "source": {}}),
|
||||
doc("ruling", "delta", {"title": "Edge in", "operator_quote": "q" * 300}),
|
||||
doc("ruling", "eps", {"title": "Edge out", "operator_quote": "q" * 301})]
|
||||
json.dump(items, open(t + "/rc.items", "w"))
|
||||
json.dump([items[0], doc("ruling", "big", {"title": "B" * 190, "operator_quote": None}), items[1]], open(t + "/rc.items2", "w"))
|
||||
json.dump([], open(t + "/rc.items0", "w"))
|
||||
def hook(name, prompt):
|
||||
json.dump({"hook_event_name": "UserPromptSubmit", "session_id": "s", "prompt": prompt}, open(t + "/" + name, "w"))
|
||||
hook("rc.in", "who is on the first batch")
|
||||
hook("rc.in2", "two words")
|
||||
hook("rc.in0", "")
|
||||
hook("rc.inmb", "a " + "日 " * 300)
|
||||
hook("rc.in3", "one two three")
|
||||
open(t + "/rc.inbad", "w").write("not json")
|
||||
json.dump({"hook_event_name": "UserPromptSubmit"}, open(t + "/rc.innoprompt", "w"))
|
||||
PYE
|
||||
RIN=$T/rc.in
|
||||
# the happy path: one hook JSON; a header, then one line per item in rank order
|
||||
rrun "$RIN"; [ $rr = 0 ] && [ ! -s "$T/rc.err" ] && echo "ok recall exits 0 with a hook JSON" || { echo "FAIL recall happy path: exit $rr"; fail=1; }
|
||||
$PY - "$(wp "$T/rc.out")" <<'PYE' && echo "ok recall output is one UserPromptSubmit hook JSON" || { echo "FAIL recall hook JSON shape"; fail=1; }
|
||||
import json, sys
|
||||
o = json.load(open(sys.argv[1]))
|
||||
assert list(o) == ["hookSpecificOutput"]
|
||||
assert o["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
|
||||
assert list(o["hookSpecificOutput"]) == ["hookEventName", "additionalContext"]
|
||||
PYE
|
||||
rctx >"$T/rc.full"
|
||||
$PY - "$(wp "$T/rc.full")" <<'PYE' && echo "ok recall lists id and title, a short operator quote, a follow_up cut to 240, no quote over 300" || { echo "FAIL recall line format"; fail=1; }
|
||||
import sys
|
||||
L = open(sys.argv[1], encoding="utf-8").read().split("\n")
|
||||
assert "possibly irrelevant" in L[0] and "view" in L[0] and "ranked" in L[0]
|
||||
assert L[1] == '- t:ruling:alpha: Retrieval is pushed | operator: "pop into context"', L[1]
|
||||
assert L[2] == "- t:question:beta: Which fork", L[2]
|
||||
assert L[3] == "- t:follow_up:gamma: " + "f" * 240, L[3]
|
||||
assert L[4] == '- t:ruling:delta: Edge in | operator: "%s"' % ("q" * 300), L[4]
|
||||
assert L[5] == "- t:ruling:eps: Edge out", L[5]
|
||||
assert len(L) == 6
|
||||
PYE
|
||||
# R4: the selection is exactly the three kinds, in force, across every root, as documents; the text is the prompt
|
||||
rrun "$RIN" RECALL_TOP_K=7
|
||||
$PY - "$(wp "$T/rc.args")" "$(wp "$T/rc.in")" <<'PYE' && echo "ok recall R4 selection is the three kinds, in force, document projection, top_k from RECALL_TOP_K" || { echo "FAIL recall R4 selection"; fail=1; }
|
||||
import json, sys
|
||||
a = [json.loads(l) for l in open(sys.argv[1])]
|
||||
assert len(a) == 1
|
||||
a = a[0]
|
||||
assert a["selection"] == {"schemas": ["epiphany.pipeline.ruling.v2", "epiphany.pipeline.question.v2", "epiphany.pipeline.follow_up.v2"],
|
||||
"projection": "document", "fields": [{"index": "in_force", "op": "any_of", "values": ["true"]}]}, a["selection"]
|
||||
assert a["semantic"] == {"text": json.load(open(sys.argv[2]))["prompt"], "top_k": 7}, a["semantic"]
|
||||
PYE
|
||||
rrun "$RIN"; [ "$($PY -c 'import json,sys;print(json.loads(open(sys.argv[1]).readline())["semantic"]["top_k"])' "$(wp "$T/rc.args")")" = 5 ] && echo "ok recall top_k defaults to 5" || { echo "FAIL recall default top_k"; fail=1; }
|
||||
# R3: the query text is the prompt cut to at most 1000 bytes at a character boundary
|
||||
rrun "$T/rc.inmb"
|
||||
$PY - "$(wp "$T/rc.args")" "$(wp "$T/rc.inmb")" <<'PYE' && echo "ok recall R3 a multibyte prompt is cut to a valid UTF-8 prefix of at most 1000 bytes" || { echo "FAIL recall R3 byte bound"; fail=1; }
|
||||
import json, sys
|
||||
sent = json.loads(open(sys.argv[1], encoding="utf-8").readline())["semantic"]["text"]
|
||||
full = json.load(open(sys.argv[2], encoding="utf-8"))["prompt"]
|
||||
n = len(sent.encode("utf-8"))
|
||||
assert len(full.encode("utf-8")) > 1200 and full.startswith(sent) and n == 998, n
|
||||
PYE
|
||||
$PY -c 'import json,sys;json.dump({"prompt": "ab " * 400}, open(sys.argv[1], "w"))' "$(wp "$T/rc.inasc")"
|
||||
rrun "$T/rc.inasc"; [ "$($PY -c 'import json,sys;print(len(json.loads(open(sys.argv[1]).readline())["semantic"]["text"].encode()))' "$(wp "$T/rc.args")")" = 1000 ] && echo "ok recall R3 an ASCII prompt is cut to exactly 1000 bytes" || { echo "FAIL recall R3 ASCII cut is not 1000 bytes"; fail=1; }
|
||||
# R6: fewer than 3 words is not queried; R7: one attempt
|
||||
rrun "$T/rc.in2"; rchk "R6 a two-word prompt prints nothing" $rr; [ "$(rcalls)" = 0 ] && echo "ok recall R6 a two-word prompt makes zero calls" || { echo "FAIL recall R6 queried a two-word prompt"; fail=1; }
|
||||
rrun "$T/rc.in0"; [ "$(rcalls)" = 0 ] && echo "ok recall R6 an empty prompt makes zero calls" || { echo "FAIL recall R6 queried an empty prompt"; fail=1; }
|
||||
rrun "$T/rc.in3"; [ "$(rcalls)" = 1 ] && echo "ok recall R6 a three-word prompt is queried" || { echo "FAIL recall R6 three words not queried"; fail=1; }
|
||||
rrun "$RIN" FAKE_MODE=unavailable; rchk "R1 Unavailable prints nothing" $rr; [ "$(rcalls)" = 1 ] && echo "ok recall R7 Unavailable is one attempt, no retry" || { echo "FAIL recall R7 attempts: $(rcalls)"; fail=1; }
|
||||
# R1: every failure is exit 0 and empty stdout
|
||||
rrun "$RIN" FAKE_MODE=garbage; rchk "R1 an unparsable answer" $rr
|
||||
rrun "$RIN" HOME="$(wp "$T")/nohome" USERPROFILE="$(wp "$T")/nohome"; rchk "R1 no binding" $rr
|
||||
rrun "$RIN" EUREKA_STATE_EXE="$T/CANARY-exe"; rchk "R1 an unspawnable binding" $rr
|
||||
rrun "$T/rc.inbad"; rchk "R1 malformed stdin" $rr; [ "$(rcalls)" = 0 ] && echo "ok recall malformed stdin makes no call" || { echo "FAIL recall called on malformed stdin"; fail=1; }
|
||||
rrun "$T/rc.innoprompt"; rchk "R1 stdin with no prompt" $rr
|
||||
: >"$T/cp.count"; rcl $PY "$RC" <"$RIN" >"$T/rc.out" 2>"$T/rc.err"; rchk "R1 no subcommand" $?
|
||||
# R5: the budget drops items whole from the tail and never cuts a line
|
||||
$PY - "$(wp "$T/rc.full")" "$(wp "$T")" <<'PYE'
|
||||
import sys
|
||||
full = open(sys.argv[1], encoding="utf-8").read()
|
||||
L = full.split("\n")
|
||||
one = len(L[0]) + 1 + len(L[1])
|
||||
open(sys.argv[2] + "/rc.b", "w").write(" ".join(str(n) for n in (len(full), len(full) - 1, one, one - 1, len(L[0]) + 4)))
|
||||
PYE
|
||||
set -- $(cat "$T/rc.b")
|
||||
rrun "$RIN" RECALL_BUDGET=$1; rctx >"$T/rc.ctx"; cmp -s "$T/rc.ctx" "$T/rc.full" && echo "ok recall R5 a budget equal to the output keeps every item" || { echo "FAIL recall R5 exact budget"; fail=1; }
|
||||
rrun "$RIN" RECALL_BUDGET=$2; rctx >"$T/rc.ctx"
|
||||
$PY - "$(wp "$T/rc.ctx")" "$(wp "$T/rc.full")" "$2" <<'PYE' && echo "ok recall R5 one character short drops the last item whole" || { echo "FAIL recall R5 budget minus one"; fail=1; }
|
||||
import sys
|
||||
got = open(sys.argv[1], encoding="utf-8").read()
|
||||
want = "\n".join(open(sys.argv[2], encoding="utf-8").read().split("\n")[:-1])
|
||||
assert got == want and len(got) <= int(sys.argv[3])
|
||||
PYE
|
||||
rrun "$RIN" RECALL_BUDGET=$3; rctx >"$T/rc.ctx"; [ "$(wc -l <"$T/rc.ctx" | tr -d ' ')" = 1 ] && echo "ok recall R5 the header and one item fit their exact budget" || { echo "FAIL recall R5 one-item budget"; fail=1; }
|
||||
rrun "$RIN" RECALL_BUDGET=$4; rchk "R5 a budget below the header and one item prints nothing" $rr
|
||||
rrun "$RIN" RECALL_BUDGET=$5; rchk "R5 a budget with no room for an item prints nothing" $rr
|
||||
# an item that does not fit ends the list, even when a later one would fit
|
||||
rcl env FAKE_ITEMS="$(wp "$T/rc.items2")" $PY "$RC" prompt <"$RIN" >"$T/rc.out" 2>"$T/rc.err"; rctx >"$T/rc.full2"
|
||||
$PY - "$(wp "$T/rc.full2")" "$(wp "$T")" <<'PYE'
|
||||
import sys
|
||||
L = open(sys.argv[1], encoding="utf-8").read().split("\n")
|
||||
assert len(L) == 4 and len(L[2]) > len(L[3]), L
|
||||
open(sys.argv[2] + "/rc.b2", "w").write(str(len(L[0]) + len(L[1]) + len(L[3]) + 2))
|
||||
PYE
|
||||
rcl env FAKE_ITEMS="$(wp "$T/rc.items2")" RECALL_BUDGET="$(cat "$T/rc.b2")" $PY "$RC" prompt <"$RIN" >"$T/rc.out" 2>"$T/rc.err"; rctx >"$T/rc.ctx"
|
||||
[ "$(wc -l <"$T/rc.ctx" | tr -d ' ')" = 1 ] && echo "ok recall R5 items drop from the tail: a big item ends the list" || { echo "FAIL recall R5 skipped over a big item"; fail=1; }
|
||||
rcl env FAKE_ITEMS="$(wp "$T/rc.items0")" $PY "$RC" prompt <"$RIN" >"$T/rc.out" 2>"$T/rc.err"; rchk "R5 zero items prints nothing" $?
|
||||
# R2: the deadline is a function of RECALL_DEADLINE_MS, and the child is killed
|
||||
rm -f "$T/rc.mark"
|
||||
rrun2 "$RIN" FAKE_SLEEP=3 FAKE_MARK="$(wp "$T/rc.mark")" RECALL_DEADLINE_MS=1000; rchk "R2 deadline 1000 prints nothing" $rr
|
||||
[ $rms -ge 900 ] && [ $rms -le 1600 ] && echo "ok recall R2 deadline 1000 ms returns in ${rms} ms" || { echo "FAIL recall R2 deadline 1000 took ${rms} ms"; fail=1; }
|
||||
rrun2 "$RIN" FAKE_SLEEP=3 RECALL_DEADLINE_MS=2000; rchk "R2 deadline 2000 prints nothing" $rr
|
||||
[ $rms -ge 1900 ] && [ $rms -le 2600 ] && echo "ok recall R2 deadline 2000 ms returns in ${rms} ms" || { echo "FAIL recall R2 deadline 2000 took ${rms} ms"; fail=1; }
|
||||
rrun2 "$RIN" FAKE_SLEEP=6; rchk "R2 default deadline prints nothing" $rr
|
||||
[ $rms -ge 3900 ] && [ $rms -le 4900 ] && echo "ok recall R2 the default deadline is 4000 ms (${rms} ms)" || { echo "FAIL recall R2 default deadline took ${rms} ms"; fail=1; }
|
||||
rrun "$RIN" FAKE_SLEEP=0.5 RECALL_DEADLINE_MS=1000; [ -s "$T/rc.out" ] && echo "ok recall R2 an answer inside the deadline is printed" || { echo "FAIL recall R2 answer inside the deadline lost"; fail=1; }
|
||||
$PY -c 'import time;time.sleep(3)'
|
||||
[ ! -e "$T/rc.mark" ] && echo "ok recall R2 the child is killed at the deadline" || { echo "FAIL recall R2 the child outlived the deadline"; fail=1; }
|
||||
# the mind client and the recall hook: standard library only
|
||||
for f in mind.py recall.py; do
|
||||
if grep -qE '^import (requests|yaml)|^from (requests|yaml)' "$here/$f"; then echo "FAIL $f: a dependency beyond the standard library"; fail=1; else echo "ok $f: standard library only"; fi
|
||||
done
|
||||
exit $fail
|
||||
|
|
|
|||
Loading…
Reference in a new issue