session 2026-08-24: the heap got a dynamo — thread-query.py + daybook-cue.py, 11 user-memories harvested to the vault
- thread-query.py: the turning. Queries both corpora with the pulling thread, excluding the recency slice (/wake-up reaches 0.71% of an 859,803-word corpus) and favouring age. Wired into /wake-up §2.b.3, replacing a described-not-invoked grep step. Trial pre-registered, graded 2026-10-05 from --log. Caught PASS-BUT-FALSELY on its first live run at 14/14 green; rescored on windowed co-occurrence, length-bias control added. - daybook-cue.py: PostToolUse cue for the daily note. Diagnosed from the record — the hook only ever CREATED and nothing ever prompted filling. 16/16, fail-open, never blocks. - All 11 user-* memories harvested into the vault (12 notes, 10 into 09. Atlas of Roots, empty since 2025-09-29); each memory file back-pointed, vault note canonical for the idea. - N-now corrected in MEMORY.md: 49/84, down 11 — the counter is a rolling window. Steward-authorized. Trials: thread-query + Smart Connections, both graded 2026-10-05. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JQKeKY9T9d95KpvHwwok8T
This commit is contained in:
co-authored by
Claude Opus 5
parent
e3da9abad1
commit
edf71fb173
Executable
+169
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""daybook-cue.py — PostToolUse cue (Write|Edit): the daily note has no trigger.
|
||||
|
||||
Diagnosed 2026-08-24 from the record, not from impression. Daily notes for 19-22
|
||||
August do not exist; 2026-08-23 exists at 33,242 bytes across eight commits — written
|
||||
incrementally, as the convention specifies. So the practice has produced exactly one
|
||||
note, on the day the convention was authored and live in working memory.
|
||||
|
||||
The failure is structural. `daybook-ensure.py` fires at SessionStart and ONLY EVER
|
||||
CREATES. The next cue is /wrap-up §7.5, hours later. Between them — the entire working
|
||||
session, which is exactly when the convention says to write — there is no cue at all.
|
||||
The vault's own notes diagnosed the two dead predecessors correctly ("neither had a
|
||||
trigger. The hook is the trigger") and then built the trigger for the half that never
|
||||
needed one. Whether the file exists was never the failure mode.
|
||||
|
||||
The convention's own trigger word is "as it lands", and work lands when a file is
|
||||
written. So this fires there.
|
||||
|
||||
REMINDER, NOT BLOCK — unlike verify-before-compose, whose failure is unrecoverable.
|
||||
This one's is recoverable, and a gate that interrupts every write would be removed
|
||||
within a day. Fail-open everywhere: any unexpected condition exits 0.
|
||||
"""
|
||||
import datetime, json, os, sys, time
|
||||
|
||||
HOME = os.path.expanduser("~")
|
||||
VAULT = os.path.join(HOME, "Library/Mobile Documents/iCloud~md~obsidian/Documents",
|
||||
"David, root-and-branch")
|
||||
DAILY_DIR = os.path.join(VAULT, "01. Daily")
|
||||
STAMP = os.path.join(HOME, ".claude/state/daybook-cue.stamp")
|
||||
|
||||
SKELETON_CEILING = 1200 # the generated skeleton is ~558 bytes
|
||||
QUIET_SECONDS = 900 # cue at most once per 15 minutes; a nag gets disabled
|
||||
|
||||
# Writes that are not "work landing".
|
||||
IGNORED_FRAGMENTS = ("/scratchpad/", "/private/tmp/", "/tmp/", "/.git/",
|
||||
"/node_modules/", "/.obsidian/")
|
||||
IGNORED_SUFFIXES = (".bak", ".stamp", ".lock", ".log", ".jsonl", ".pyc")
|
||||
|
||||
|
||||
def is_substantive(path):
|
||||
if not path:
|
||||
return False
|
||||
if any(f in path for f in IGNORED_FRAGMENTS):
|
||||
return False
|
||||
if path.endswith(IGNORED_SUFFIXES):
|
||||
return False
|
||||
# writing the daily note itself is the thing being asked for, not a trigger for it
|
||||
if os.path.normpath(DAILY_DIR) in os.path.normpath(path):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def note_path(today=None):
|
||||
d = today or datetime.date.today()
|
||||
return os.path.join(DAILY_DIR, f"{d.isoformat()}.md")
|
||||
|
||||
|
||||
def should_cue(path, note_size, note_exists, seconds_since_last, today=None):
|
||||
"""Pure decision, so it can be tested without a filesystem or a clock."""
|
||||
if not is_substantive(path):
|
||||
return False
|
||||
if not note_exists: # daybook-ensure owns creation, not us
|
||||
return False
|
||||
if note_size >= SKELETON_CEILING: # it is being written; say nothing
|
||||
return False
|
||||
if seconds_since_last < QUIET_SECONDS:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
MESSAGE = """DAILY NOTE — still a skeleton, and work is landing elsewhere.
|
||||
|
||||
The convention is FILL IT AS THE WORK HAPPENS, not at the wrap:
|
||||
01. Daily/{date}.md (currently {size} bytes)
|
||||
|
||||
Give what just landed its own `##` heading now — plain language, for the steward on a
|
||||
day he wants to know what happened without reading git log. /wrap-up §7.5 finalises;
|
||||
it never begins the file, and a skeleton at wrap is a failure that step exists to catch.
|
||||
|
||||
(Cue at most once per 15 min. Reminder, not a block — the write already succeeded.)"""
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
raw = sys.stdin.read()
|
||||
data = json.loads(raw) if raw.strip() else {}
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
path = (data.get("tool_input") or {}).get("file_path") or ""
|
||||
np = note_path()
|
||||
try:
|
||||
exists = os.path.exists(np)
|
||||
size = os.path.getsize(np) if exists else 0
|
||||
except OSError:
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
last = os.path.getmtime(STAMP) if os.path.exists(STAMP) else 0
|
||||
except OSError:
|
||||
last = 0
|
||||
since = time.time() - last
|
||||
|
||||
if not should_cue(path, size, exists, since):
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.dirname(STAMP), exist_ok=True)
|
||||
open(STAMP, "w").write(str(time.time()))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
print(MESSAGE.format(date=datetime.date.today().isoformat(), size=size), file=sys.stderr)
|
||||
sys.exit(2) # PostToolUse: tool already ran; stderr is surfaced as feedback
|
||||
|
||||
|
||||
def selftest():
|
||||
ok = fail = 0
|
||||
|
||||
def check(name, cond):
|
||||
nonlocal ok, fail
|
||||
if cond:
|
||||
ok += 1; print(f" PASS {name}")
|
||||
else:
|
||||
fail += 1; print(f" FAIL {name}")
|
||||
|
||||
vault_md = os.path.join(VAULT, "08. Notes/Something.md")
|
||||
daily_md = os.path.join(DAILY_DIR, "2026-08-24.md")
|
||||
|
||||
# positive control — it fires when it should
|
||||
check("cues on a real vault write while the note is a skeleton",
|
||||
should_cue(vault_md, 558, True, 9999) is True)
|
||||
check("cues on a repo write too",
|
||||
should_cue(os.path.join(HOME, "_Dev/x/y.py"), 558, True, 9999) is True)
|
||||
|
||||
# negative controls — each suppression works in isolation
|
||||
check("silent once the note is being written", should_cue(vault_md, 5000, True, 9999) is False)
|
||||
check("silent when the note does not exist", should_cue(vault_md, 0, False, 9999) is False)
|
||||
check("silent inside the quiet window", should_cue(vault_md, 558, True, 10) is False)
|
||||
check("silent on the daily note itself", should_cue(daily_md, 558, True, 9999) is False)
|
||||
check("silent on scratchpad", should_cue("/private/tmp/claude-501/x/scratchpad/a.md", 558, True, 9999) is False)
|
||||
check("silent on /tmp", should_cue("/tmp/a.md", 558, True, 9999) is False)
|
||||
check("silent on .obsidian config", should_cue(os.path.join(VAULT, ".obsidian/x.json"), 558, True, 9999) is False)
|
||||
check("silent on backups", should_cue(vault_md + ".bak", 558, True, 9999) is False)
|
||||
check("silent on jsonl logs", should_cue(os.path.join(HOME, "a/b.jsonl"), 558, True, 9999) is False)
|
||||
check("silent on empty path", should_cue("", 558, True, 9999) is False)
|
||||
|
||||
# boundary
|
||||
check("threshold is exclusive at the ceiling",
|
||||
should_cue(vault_md, SKELETON_CEILING, True, 9999) is False)
|
||||
check("just under the ceiling still cues",
|
||||
should_cue(vault_md, SKELETON_CEILING - 1, True, 9999) is True)
|
||||
|
||||
# fail-open on malformed input
|
||||
import subprocess
|
||||
r = subprocess.run([sys.executable, __file__], input="not json",
|
||||
capture_output=True, text=True)
|
||||
check("fail-open on malformed stdin", r.returncode == 0)
|
||||
r = subprocess.run([sys.executable, __file__], input="", capture_output=True, text=True)
|
||||
check("fail-open on empty stdin", r.returncode == 0)
|
||||
|
||||
print(f"\nSELFTEST {'PASS' if not fail else 'FAIL'} — {ok} ok, {fail} failed")
|
||||
return 0 if not fail else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "--selftest" in sys.argv:
|
||||
sys.exit(selftest())
|
||||
main()
|
||||
Reference in New Issue
Block a user