From 073ef8a4823e2be342d8074d733a985562e4f5d1 Mon Sep 17 00:00:00 2001 From: David F Glidden Date: Sun, 23 Aug 2026 10:52:35 +0200 Subject: [PATCH] The daily work log, with a trigger this time (PENDING-155 filed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The steward asked for a daily note in Obsidian explaining in plain terms what we did, decisions taken, and commit references — plus significant insights and exchanges between the parties. This is Obsidian tracker item 5, the actual goal, unstarted since 2026-08-19 while four items of frontmatter hygiene stood in front of it. Two prior attempts at this exact genre were read before anything was designed, honouring the tracker's own flag: daily-log/ (4 entries, Mar-Apr 2026) and sessions/ (3 long-form narratives). Both died inside four weeks. Neither had a trigger — they were written when someone remembered. So the design question was never what the note should say. It was what makes it survive. Answer, steward-chosen at each fork: the note lives at 01. Daily/YYYY-MM-DD.md with project work under its own H2 inside it rather than in project folders; a SessionStart hook creates it so existence stops depending on memory; the executor writes during the session because a wrap-only design inherits the wrap's failure mode (2026-08-19 died unwrapped); and the wrap finalises rather than begins. daybook-ensure.py creates only. It will not touch an existing note — that is the control the design turns on, and it is tested, along with a positive control proving the no-dir report distinguishes a missing directory from a broken function. It also reports failure loudly, because the job it sits beside spent five months failing behind a > /dev/null 2>&1. Not revived: the 2025 analogue template — sleep, supplements, homeopathy. Pen and paper beat it and should keep it. That it failed is a finding, not a gap, and this note is a different genre rather than its replacement. PENDING-155 files the steward's idea for the jurist's side: an append-only MCP surface writing a scratch file the executor folds in. Deliberately NOT a tool on governance-mcp.py, whose read-only guarantee is a live AST control with a positive control rather than a comment. A separate one-tool server leaves that intact, and the vault note stays single-writer — the same lesson the thinking-mirror taught this morning. Correction carried into the record: the executor told the steward mid-session that Claude.app has no filesystem access. False since 2026-08-08. Third instance today of a constraint asserted from recall where the substrate was one file away. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JQKeKY9T9d95KpvHwwok8T --- PENDING.md | 26 ++++ .../memory/project-obsidian-vault-practice.md | 51 ++++++- claude/skills/wrap-up/SKILL.md | 39 ++++++ scripts/daybook-ensure.py | 128 ++++++++++++++++++ 4 files changed, 237 insertions(+), 7 deletions(-) create mode 100755 scripts/daybook-ensure.py diff --git a/PENDING.md b/PENDING.md index 68c8a03..e6cf66b 100644 --- a/PENDING.md +++ b/PENDING.md @@ -4582,3 +4582,29 @@ If the visible work today came from **positional difference between two same-for **Files affected:** none. Cross-filed to PENDING-89; ladder entry queued to PENDING-141. **Awaiting:** PENDING-89 to record which of (a)'s two claims the data supports. + +## PENDING-155 — A daybook append surface for the jurist: one tool, one file, and not on the governance server +**Date:** 2026-08-23 +**Tag:** [PROPOSAL] — a new write interface between two governing parties. Not built. Installing it edits the steward's desktop-app config. +**Summary:** The steward asked whether an MCP surface could let Claude.app append its side of an exchange to a temporary file the executor then folds into the daily work log. It can. It must not be a tool on `governance-mcp.py`. + +**Origin.** The daily work log (`01. Daily/YYYY-MM-DD.md`, built 2026-08-23) records what we did, decisions taken, commit references, and — the steward's distinctive ask — *"any significant insights or exchanges that happened between us."* The executor's side is covered: `/wrap-up` §7.5 records it, and jurist material relayed through the session is recorded there too. What is not covered is a jurist-only exchange the executor never sees. + +**The refusal this runs into, stated before the workaround.** `governance-mcp.py` (PENDING-82, installed 2026-08-08) is read-only, and its first designed-in refusal reads: *"No tool writes. Audited by AST, not by text search... A write path would collapse three parties into one."* Verified live this session, not taken from the document: the selftest prints `no filesystem-mutating call in this file` **and** a positive control proving the checker detects writes when present. Adding a write tool there would break a control that currently passes, and that control is what makes "read-only" checkable rather than merely claimed. + +⚠ **The executor also told the steward, earlier in the same session, that Claude.app "has no filesystem access."** That was wrong — it has mediated read access and has since 2026-08-08. Corrected in place. The error is the one this register keeps finding: a constraint asserted from recall where the substrate was checkable. + +**Proposed shape — a separate server, so the invariant above survives untouched:** +- New `~/dotfiles/scripts/daybook-mcp.py`. **One tool:** `daybook_append(text)`. +- **No path argument.** The target derives from today's date, as `governance-mcp.py`'s refusal 4 does: there is no traversal to defend because no path is accepted. +- **Append-only.** Never truncates, never deletes, never reads back. A jurist that could read the file back could audit the executor's folding of it, which is a different and unrequested capability. +- **Target is a scratch file, not the vault** — e.g. `~/dotfiles/claude/daybook/YYYY-MM-DD.inbox.md`. The executor folds entries into the daily note and the scratch file is a handoff, not a record. +- **The vault note stays single-writer.** This is the same lesson the thinking-mirror taught today: two writers to one destination is how a failure goes unnoticed for five months. + +**Why a scratch file rather than the note directly.** The steward's own phrasing — *"amend to some kind of temporary file you could read"* — is the better design, and not only for tidiness. A jurist writing the note directly would make the note a shared artifact with no single party accountable for its register; the plain-language discipline the steward asked for would have no owner. + +**What this does NOT establish.** It does not give the jurist a way to *modify* governance state, and must not grow one. If a future need looks like "the jurist should be able to write X," that is a new item, not an extension of this one. + +**Files affected:** new `~/dotfiles/scripts/daybook-mcp.py`; `/wrap-up` §7.5 (fold step); awaiting steward hand: `claude_desktop_config.json` merge + app restart. +**Recommendation:** Build it, with a selftest carrying the same paired positive/negative controls as its sibling, and hold installation until the daily log has run long enough to show it survives — a jurist inbox for a practice that lapsed would be the fourth abandoned attempt rather than the first durable one. +**Awaiting:** Steward authorization. diff --git a/claude/memory/project-obsidian-vault-practice.md b/claude/memory/project-obsidian-vault-practice.md index c40de62..4181122 100644 --- a/claude/memory/project-obsidian-vault-practice.md +++ b/claude/memory/project-obsidian-vault-practice.md @@ -138,6 +138,45 @@ old, and the Daily-Notes plugin is not configured. There is no dormant capture h practice would be built from nothing. `00a. _inbox` holds 11 notes. + +### 2026-08-23 (later) — the capture practice is started, and it is a different genre from the one that failed + +**The steward specified it, and the specification excludes the old daily note.** The 2025 template +(sleep, supplements, homeopathy, skin, family, reflection) was an attempt to reconcile an analogue +practice with a digital one; pen and paper won, and **it stays retired — that is a finding, not a +gap.** What is wanted instead: *"a daily note in obsidian which I can refer to, which explains in +relatively simple terms what it is that we have done, decisions taken, and references to any +commits"*, plus *"any significant insights or exchanges that happened between us."* + +**Two prior attempts at exactly this genre were found before designing anything** (the tracker's own +"read `daily-log/` and `sessions/` first" flag, honoured): `daily-log/` — 4 entries, Mar 10/12/20 + +Apr 5, sections Summary / What we did / Status / Blocked / Next, already recording an exchange +(*"Thistleweld caught the meta-problem at line 183"*); and `sessions/` — 3 long-form narratives, +linked as children of the daily note. **Both died inside four weeks. Neither had a trigger.** So the +design question was not what the note should say but what makes it survive. + +**Built, steward-chosen at each fork:** +- **Location:** `01. Daily/YYYY-MM-DD.md` — its normal home. **Project work under its own H2/H3 + inside the note, NOT in project folders** (steward, explicitly). +- **Trigger:** `daybook-ensure.py`, wired into the `SessionStart` hook chain. **Creates only** — + it will not touch an existing note, which is the control the design turns on. 7 checks, incl. a + positive control proving the `no-dir` report is real. Registered via the `update-config` skill; + `jq -e` validates, and a diff against a backup shows only the 8 added lines. +- **Accretion:** the executor writes during the session; `/wrap-up` §7.5 finalises rather than + begins. A wrap-only design inherits the wrap's failure mode — 2026-08-19 died unwrapped. +- **Register:** plain language, explicitly. The hardest part, and named as such in §7.5, because the + executor's default is dense and written for itself. +- **Jurist:** recorded by the executor as exchanges pass through it. **PENDING-155** filed for the + steward's better idea — an append-only MCP surface writing a scratch file the executor folds in. + ⚠ Deliberately **not** a tool on `governance-mcp.py`, whose read-only guarantee is enforced by a + live AST control with a positive control, not by a comment. + +⚠ **Correction:** the executor told the steward mid-session that Claude.app "has no filesystem +access." False since 2026-08-08 — `governance-mcp.py` gives it mediated read access (PENDING-82, +closed). Same failure class as the two earlier corrections today: asserted from recall where the +substrate was one file away. + + --- ## Current state — OPEN @@ -177,10 +216,8 @@ Items 1–4 are the **frontmatter tail**: real, bounded, and *not the goal*. Ite - **2, 3** are steward-judgement work, not executor work: 73 pointer-form `canonical` values and 213 `related` targets need an eye that knows what the notes *mean*. - **4** (16 YAML parse errors) has never been in scope and is a session-sized bite. -- **5 — the capture practice — has still not been started**, and the 2026-08-23 check made it - sharper, not smaller: `01. Daily/` holds 11 notes, newest **2025-09-04**, and the Daily Notes - plugin is unconfigured. There is no dormant habit to revive. - ⚠ Note for whoever designs it: `00. Compass/00b. Constellations/CapableMind/` already contains - `daily-log/` and `sessions/` folders. **Read those before designing anything** — a capture - practice may already have been attempted here, and re-inventing over it would be the third - duplicate mirror in spirit. +- **5 — the capture practice — STARTED 2026-08-23**, after 4 days as the unstarted goal. Hook + + in-session accretion + wrap finalisation + a first real note. **It is not proven yet:** two + predecessors reached 3 and 4 entries. The measure of this one is whether it is still being written + in four weeks — and §7.5 now requires the wrap to SAY SO if it lapses, rather than letting it fade + a third time in silence. diff --git a/claude/skills/wrap-up/SKILL.md b/claude/skills/wrap-up/SKILL.md index 4dcfd95..65ce136 100644 --- a/claude/skills/wrap-up/SKILL.md +++ b/claude/skills/wrap-up/SKILL.md @@ -239,6 +239,45 @@ that had nothing to do, and that is how five months of silence went unnoticed. path unreachable from a restricted context), say that too; the diagnosis is in `session-ledger-2026-04-16.md` and it is macOS Full Disk Access, not git and not rsync. +### 7.5. The daily note — finalise the day's work log + +`01. Daily/YYYY-MM-DD.md` in the vault. **A session-start hook (`daybook-ensure.py`) has already +created it**, so this step never begins a file — it finishes one. If the note is still only the +skeleton, that is the failure this step exists to catch: the day's work was never written down as it +happened, and writing it all now from a compacted context is exactly the reconstruction the log is +meant to replace. + +**Who it is for, and therefore how it reads.** The steward, on a day when he wants to know what we +did without reading `git log`. **Plain language.** Explain the jargon or drop it. Short sentences. +A reader who was not in the session must be able to follow it. This is the register the steward +asked for on 2026-08-23 — *"relatively simple terms"* — and it is the hardest part of this step, +because the executor's default register is dense, hedged, and written for itself. + +**Shape** (steward-specified 2026-08-23): +- `## The day in short` — one paragraph. What actually happened, and why it mattered. +- `## ` — **one H2 per project touched**, H3 beneath if it needs it. Project work goes + under its own heading, *not* into project folders. Put commit hashes here, inline, next to what + they did: `→ f1c91d9`. +- `## Decisions taken` — what was decided and *by whom*, briefly. Cross-project, so it stays global. +- `## Insights and exchanges worth keeping` — the distinctive section, and the one most likely to be + skipped under time pressure. A sentence that changed how we saw the problem; a correction that + landed; a phrase worth keeping. **Record the jurist's contributions here too** — rulings, design + gates and reasoning relayed by the steward reach this session and belong in the record the same + way the executor's do. Claude.app cannot write to the vault itself (its MCP surface, + `governance-mcp.py`, is structurally read-only and audited to stay that way), so this step is + where its side of the exchange gets kept. +- `## Open / next` — what is unfinished, with dates where they exist. + +**Accrete DURING the session; do not save it all for the wrap.** A session that dies unwrapped loses +whatever only lived here — 2026-08-19 died exactly that way. The hook guarantees the file; only +writing into it as the day goes guarantees the content. + +**Never rewrite a previous day's note.** It is a record, not a document under maintenance. + +⚠ **Two previous attempts at this log died inside four weeks** — `daily-log/` (4 entries) and +`sessions/` (3), both Mar–Apr 2026, both abandoned without a note saying so. Neither had a trigger. +If this one lapses, say so in the wrap rather than letting it fade a third time. + ### 8. Output Produce a brief summary for the steward: diff --git a/scripts/daybook-ensure.py b/scripts/daybook-ensure.py new file mode 100755 index 0000000..4842f9e --- /dev/null +++ b/scripts/daybook-ensure.py @@ -0,0 +1,128 @@ +#!/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") + +SKELETON = """--- +title: "{date} — {dayname}" +type: daily +date: {date} +status: active +tags: + - daily + - worklog +--- + +# {date} — {dayname} + +*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 + body = SKELETON.format(date=day.isoformat(), dayname=day.strftime("%A")) + # 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 + + +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") + + 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()) + status, path = ensure() + if status == "created": + print(f"DAYBOOK — created {os.path.basename(path)}") + elif status.startswith("error") or status == "no-dir": + # Loud on purpose. The predecessor's silence cost five months. + print(f"DAYBOOK — COULD NOT WRITE the day's note ({status}). " + f"Expected at: {path}") + sys.exit(0)