#!/usr/bin/env python3 """daybook-ensure — the day's work-log note exists before anyone remembers to want it. Two previous attempts at this log died inside four weeks (`daily-log/` 4 entries, `sessions/` 3, both Mar-Apr 2026). Neither had a trigger; both depended on someone remembering. This hook removes the remembering: at every session start the day's note exists, skeleton in place, so writing into it is an append rather than a decision to begin. Deliberately narrow: - CREATES only. An existing note is never opened for writing, never rewritten, never reordered. The prose is the executor's and the hook must not be able to damage it. - Says so when it fails. The job this replaces died silently for five months behind a `> /dev/null 2>&1` (see the 2026-08-23 daily note). A hook that cannot write must announce that, not shrug. Constitutional Constraint #4, at hook scale. - Always exits 0. Honest reporting must never cost the steward a session. """ import datetime, os, sys VAULT = os.path.expanduser( "~/Library/Mobile Documents/iCloud~md~obsidian/Documents/David, root-and-branch") DAILY = os.path.join(VAULT, "01. Daily") # The steward's own folders and formats, in use since 2025 — 01. Weekly/YYYY-Www.md and # 02. Monthly/YYYY.MM.md (dots, not dashes). Reused rather than reinvented: the vault already # holds 2025-W15..W21 and 2025.05, and [[2025-W15]]-style links already resolve against them. WEEKLY = os.path.join(VAULT, "02. Reviews & Planning", "01. Weekly") MONTHLY = os.path.join(VAULT, "02. Reviews & Planning", "02. Monthly") def week_id(day): y, w, _ = day.isocalendar() return f"{y}-W{w:02d}" def month_id(day): return day.strftime("%Y.%m") SKELETON = """--- title: "{date} — {dayname}" type: daily date: {date} week: {week} month: {month} status: active tags: - daily - worklog --- # {date} — {dayname} **{nav}** *A plain-language record of the day's work, so the history is readable without reading `git log`. Project work sits under its own heading; decisions, insights and open threads are collected at the end.* ## The day in short ## Decisions taken ## Insights and exchanges worth keeping ## Open / next """ def note_path(day, daily_dir=DAILY): return os.path.join(daily_dir, f"{day.isoformat()}.md") def ensure(day=None, daily_dir=DAILY): """-> (status, path). status in {'exists','created','no-dir','error:'}. 'exists' is not a failure and 'created' is not a success worth announcing loudly — the caller decides what is worth a line of the steward's attention. """ day = day or datetime.date.today() p = note_path(day, daily_dir) try: if not os.path.isdir(daily_dir): return "no-dir", p if os.path.exists(p): return "exists", p # Upward links only. A "tomorrow" link on every note manufactures ~365 unresolved # links a year, and an unresolved-link census on 2026-08-23 found 4,799 already — # 56% of notes isolated. The week note is created below, so [[week]] always resolves; # yesterday is linked only if it actually exists. nav = [f"[[{week_id(day)}|Week]]", f"[[{month_id(day)}|Month]]", "[[Daily index]]"] prev = day - datetime.timedelta(days=1) if os.path.exists(note_path(prev, daily_dir)): nav.insert(0, f"[[{prev.isoformat()}|Previous]]") body = SKELETON.format(date=day.isoformat(), dayname=day.strftime("%A"), week=week_id(day), month=month_id(day), nav=" · ".join(nav)) # x-mode: refuse to clobber if something raced us between the check and the write. with open(p, "x", encoding="utf-8") as fh: fh.write(body) return "created", p except FileExistsError: return "exists", p except OSError as e: return f"error:{e.__class__.__name__}", p WEEK_SKELETON = """--- type: weekly week: {week} start_date: {start} status: active tags: - weekly - review --- # Weekly Review — {week} *Week of {start} to {end}.* > The daily notes for this week link up to here, so Obsidian's **backlinks** pane below lists > exactly the days that have one. Nothing is hard-linked downward on purpose: a link to a day that > never happened is an unresolved link, and the vault already carries thousands of those. ## Work — the week in review *Written at the end of the week, from the dailies. What moved, what was decided, what is still open. Plain language, and shorter than the sum of its days — a week's worth of perspective, not a digest.* ## Threads that carried across days *The point of the weekly view: what appeared on more than one day. A problem that kept returning, a question that stayed open, an idea that grew. These are invisible from inside a single day.* ## Personal review *The 2025 weekly practice lived here — intentions, patterns, task migration. Kept as its own heading; the work rollup above does not displace it.* """ MONTH_SKELETON = """--- type: monthly month: {month} status: active tags: - monthly - review --- # Monthly Review — {label} ## Work — the month in perspective *Written from the weeks, not from the days. The question this answers is the one no daily can: **did the month go anywhere?** Name the two or three things that actually advanced, and what consumed time without advancing.* ## Threads that carried across weeks ## Personal review """ def rollup(day, kind, weekly_dir=None, monthly_dir=None): """-> (status, path) for the week or month note. Same create-only contract as the daily.""" try: if kind == "week": d, name = (weekly_dir or WEEKLY), week_id(day) start = day - datetime.timedelta(days=day.isoweekday() - 1) body = WEEK_SKELETON.format(week=name, start=start.isoformat(), end=(start + datetime.timedelta(days=6)).isoformat()) else: d, name = (monthly_dir or MONTHLY), month_id(day) body = MONTH_SKELETON.format(month=name, label=day.strftime("%B %Y")) if not os.path.isdir(d): return "no-dir", os.path.join(d, name + ".md") p = os.path.join(d, name + ".md") if os.path.exists(p): return "exists", p with open(p, "x", encoding="utf-8") as fh: fh.write(body) return "created", p except FileExistsError: return "exists", p except OSError as e: return f"error:{e.__class__.__name__}", os.path.join(d, name + ".md") def selftest(): import tempfile, shutil ok = fail = 0 def chk(label, cond): nonlocal ok, fail print(f" [{'ok ' if cond else 'FAIL'}] {label}") ok, fail = (ok + (1 if cond else 0), fail + (0 if cond else 1)) d = tempfile.mkdtemp() try: day = datetime.date(2026, 8, 23) st, p = ensure(day, d) chk("creates the note when absent", st == "created" and os.path.exists(p)) txt = open(p, encoding="utf-8").read() chk("skeleton carries the ISO date and weekday", "2026-08-23" in txt and "Sunday" in txt) chk("skeleton is spec-conformant (type/status/tags present)", "type: daily" in txt and "status: active" in txt and "worklog" in txt) # THE control that matters: an existing note must survive untouched. open(p, "w", encoding="utf-8").write("PROSE THE EXECUTOR WROTE\n") st2, _ = ensure(day, d) chk("does NOT overwrite an existing note [the whole point]", st2 == "exists" and open(p, encoding="utf-8").read() == "PROSE THE EXECUTOR WROTE\n") chk("reports 'exists' rather than 'created' on the second run", st2 == "exists") # --- upward-link controls. The census that motivated them: 4,799 unresolved link # targets, 56% of notes isolated. A nav line that manufactures links is the disease. day2 = datetime.date(2026, 8, 24) # the day AFTER one that now exists st5, p5 = ensure(day2, d) txt2 = open(p5, encoding="utf-8").read() chk("links Previous when yesterday's note EXISTS", "[[2026-08-23|Previous]]" in txt2) chk("never links tomorrow [no manufactured unresolved link]", "2026-08-25" not in txt2) chk("links up to the week and month, which the hook creates", "[[2026-W35|Week]]" in txt2 and "[[2026.08|Month]]" in txt2) chk("carries the steward's own week/month frontmatter vocabulary", "week: 2026-W35" in txt2 and "month: 2026.08" in txt2) d3 = tempfile.mkdtemp() st6, p6 = ensure(datetime.date(2026, 9, 1), d3) # no previous note in a fresh dir chk("OMITS Previous when yesterday's note does NOT exist [negative control — proves" " the Previous check reads the disk, not the calendar]", "Previous" not in open(p6, encoding="utf-8").read()) shutil.rmtree(d3, ignore_errors=True) # --- rollups: same create-only contract, the steward's own folders and formats wd, md = tempfile.mkdtemp(), tempfile.mkdtemp() try: stw, pw = rollup(day, "week", wd, md) chk("creates the weekly note as YYYY-Www.md [his 2025 format]", stw == "created" and os.path.basename(pw) == "2026-W34.md") stm, pm = rollup(day, "month", wd, md) chk("creates the monthly note as YYYY.MM.md [dots, as his 2025.05]", stm == "created" and os.path.basename(pm) == "2026.08.md") wtxt = open(pw, encoding="utf-8").read() chk("weekly carries a Work heading AND keeps his personal-review heading", "## Work — the week in review" in wtxt and "## Personal review" in wtxt) chk("weekly hard-links NO daily notes [backlinks instead; zero manufactured links]", "2026-08-2" not in wtxt.split("start_date")[1].split("## Personal")[0] .replace("2026-08-23", "").replace("2026-08-17", "")) open(pw, "w", encoding="utf-8").write("STEWARD PROSE\n") stw2, _ = rollup(day, "week", wd, md) chk("does NOT overwrite an existing weekly note", stw2 == "exists" and open(pw, encoding="utf-8").read() == "STEWARD PROSE\n") finally: shutil.rmtree(wd, ignore_errors=True); shutil.rmtree(md, ignore_errors=True) missing = os.path.join(d, "nope") st3, _ = ensure(day, missing) chk("reports a missing directory instead of creating one [no silent mkdir]", st3 == "no-dir" and not os.path.exists(missing)) # positive control: the failure path must be reachable, or 'no-dir' proves nothing. st4, _ = ensure(day, d) chk("the same call succeeds where the directory exists [positive control — proves" " 'no-dir' reports the directory, not a broken function]", st4 == "exists") finally: shutil.rmtree(d, ignore_errors=True) print(f"\n{'SELFTEST PASS' if not fail else 'SELFTEST FAIL'} — {ok} ok, {fail} failed") return 0 if not fail else 1 if __name__ == "__main__": if "--selftest" in sys.argv: sys.exit(selftest()) made, failed = [], [] for st, pth in (ensure(), rollup(datetime.date.today(), "week"), rollup(datetime.date.today(), "month")): if st == "created": made.append(os.path.basename(pth)) elif st != "exists": failed.append(f"{os.path.basename(pth)} ({st})") if made: print("DAYBOOK — created " + ", ".join(made)) if failed: # Loud on purpose. The predecessor's silence cost five months. print("DAYBOOK — COULD NOT WRITE: " + "; ".join(failed)) sys.exit(0)