diff --git a/claude/memory/MEMORY-reference.md b/claude/memory/MEMORY-reference.md index e7d89ce..9aca172 100644 --- a/claude/memory/MEMORY-reference.md +++ b/claude/memory/MEMORY-reference.md @@ -25,7 +25,7 @@ metadata: - [Session 2026-08-01 — the Fool, and the boundary I manufactured](session-2026-08-01-the-fool-and-the-manufactured-boundary.md) — closed the reset thread (PENDING-85 eyeballed → **Arcades verdict WRONG**, a ClearScan scan; PENDING-84 triaged via the **§VII quarantine lane**, *dispositioned not repaired*), then found the stuckness was largely mine: **three of the day's biggest closures were ALREADY authorized** — register split (166K→44K, 177 open readable), ladder Stroke 2 (21→**71 instruments**), classifier fix — and once I justified inaction by invoking **PENDING-88's own unratified rule**. Landed **spec v2.9.1** (0-of-17→0-of-14; the prior figure is **irreproducible**, recorded in the constitution). **REVIEWED-85 ruled, NOT placed.** ESCALATE doctrine package filed (*differently biased checkers*). **Derrida: the independent witness WORKS** — ocrmac recovered what olmOCR silently dropped (5,590 words / 152 spans, invisible to every wired gate). **PULLING THREAD: stabilize the Fool method** (Qwen 3.6 35B on the M4, 2 trials, protocol unstable) — and **v1 Chamber's paired GPT/Claude raw outputs survive in ARC**, a ready-made dataset. Detail in the session file. - [MemPalace KG object 128-char cap](feedback-mempalace-kg-object-128-char-cap.md) — `kg_add` `object` hard-caps at 128 chars; write KG objects as short keyword phrases on the FIRST pass, detail goes in the drawer. Recurs at every /wrap-up §5 — stop re-deriving it. *(Relocation note: palace-memory wound down 2026-07-07; wrap §5 now appends JSONL — the cap now matters only for typography-palace kg_adds, which are rare.)* - [MemPalace wing-filter broken](feedback-mempalace-wing-filter-broken.md) — wing-scoped search → 'Error finding id' (upstream #1665, open at HEAD); search UNSCOPED + post-filter by wing. Don't run `repair` (#1589). Affects wake-up §2.b.3. *(Relocation note: §2.b.3 was rewired to the files layer 2026-07-07; relevant only to the typography palace CLI now.)* -- [Verify each post type after changes](feedback-verify-each-post-type-after-changes.md) — ARC: after any shared CSS/template/composition change, check EVERY content type on BOTH desktop and phone, not a sample on one viewport. Lesson from §II.d gate verified phone-only → desktop appearance surfaced 2 sessions late (no regression; just unseen). Per-type×per-viewport extension of [[trust-prior-pass-frame]]; belongs in the SCSS verification ladder. +- [Verify each post type after changes](feedback-verify-each-post-type-after-changes.md) — ARC: after any shared CSS/template/composition change, check EVERY content type on BOTH desktop and phone, not a sample on one viewport. Lesson from §II.d gate verified phone-only → desktop appearance surfaced 2 sessions late (no regression; just unseen). Per-type×per-viewport extension of [[feedback-trust-prior-pass-frame]]; belongs in the SCSS verification ladder. - [Reading Compass is SVG, not type](feedback-reading-compass-is-svg-not-type.md) — ARC Compass sigils are SVG `` artwork, NOT Plex Sans / nav typeface / Pattern 3. Recurring conflation; steward corrected 2026-06-03. Plex-Sans nav *text* = breadcrumb + version-stamp + post-type only. - [ARC chamber v1-legacy cluster](project-arc-chamber-v1-legacy-cluster.md) — ARC's `content/chamber/**` is intentional v1-Chamber legacy (not drift); deferred to-do = gather into a presentable cluster as a record of development. Out of §5 clause-1 audit scope. Confirmed 2026-05-29. @@ -280,7 +280,7 @@ Split out of [MEMORY.md](MEMORY.md) on 2026-07-06 to keep the wake-loaded index - [Session 2026-06-17 — the cul-de-lampe designed, rolled out & shipped; Levi base settled & paused](session-2026-06-17-cul-de-lampe-designed-rolled-out-shipped-levi-base-settled.md) — **A long ARC-dominated day, fully shipped.** Designed the **cul-de-lampe** (confronted EB Garamond hederae U+E001/E002, tips meeting, central heart; drawn from real glyph outlines via fontTools), **sized to the text-em** (`height:1em` — em-anchored NOT a constant; the steward's "is there a mathematically right proportion?" pushed to a doctrine-consistent answer per REVIEWED-42 silver-ratio retirement), **`var(--fg)` opacity 0.7** (NOT grey — steward caught it; grey retired Stage M; CSS mask, white-fill luminance-safe, hand-written -webkit-). **Close-only** ruling; **type-applicability**: Essay/Hommage/Tombeau carry it, **Fragment refused**, Photo-essay declined; placement (end of text proper, before Sources) + **publication mandate**. Caught + fixed my own **Sources-as-apparatus over-claim** (steward flagged). **Rolled out to 7 essays**; **Vespers quieted** (titles→§XII.b register-marks I–VIII; ornaments→silence; §VIII image-caption fixed) + **Mushi-Ken revised** (analysed against type-spec = it IS an essay; dropped the pre-typology appendix); **both reworks VERSIONED** (ADR-005 genesis snapshots + /v/ routes; steward: "respect the versioning"). Spec'd (§IV + Sub-table 6 reconciled). **Committed `d736df1` + `68001a4`, deployed live, pushed origin + github-backup.** Began on **Levi/Camus** Position-I conversion: **Camus DONE**; **Levi base settled = ocrmac fresh OCR** (4-pass; reading-order fidelity decides) BUT **drop-caps lost at OCR-source level** → source-verified restoration + ingest **PAUSED** (verbatim-trust > speed). Steward ruled **Levi = whole book**. **PULLING THREAD reverts to Levi → the pattern-finder's first TRUE pass.** Captured **B5** (versioning-links rethink) in the open-work register; A2 → DONE. ## Archived (2026-06-17 — cul-de-lampe shipped + Levi base settled) -- [Session 2026-06-16 (evening, post-/clear) — pattern-finder built + Station-I ingested + pass-1 contamination caught + ARC ornaments grounded](session-2026-06-16-evening-pattern-finder-build-station-i-contamination-arc-ornaments.md) — **The thread's first real build, and the engine catching its own error.** (1) **Station-I ingested** into studium-engine (5 authors/7 work-files; steward ruled FR-originals · Levi=Gray-Zone-only · both Weil; new manifest vocab `role:reading-source`+`station:I`; omnibus works sidecar-scoped; 15 sources/7282 drawers/verify HEALTHY). **[FIX] Mauss re-anchor** — the morning's own chamber-cleanup (`03de347`) broke the engine's pinned hash; gate caught it. (2) **Built `engine/pattern_finder.py`** — charter-L2 harness (three cognitions; generate-free/verify-tight; honest-silence; reasoner=Claude over FTS). (3) **PASS-1 CAUGHT ITS OWN FALSE GROUNDING** ([[feedback-tool-review-after-each-use]] PASS-BUT-FALSELY): "5/5 voices" partly false — **Camus la-chute span is mostly *Le Premier Homme*** (mislabelled mega-omnibus); **Musil/Arendt apparatus leak** (translator N.d.T. / Amos Elon intro / Bibliography-Index). The engine's thesis (§VII/§XV) *working*. Survivor: **P3 "complicity that depletes not damns"** (Weil↔Levi↔Arendt, 1947↔1986). **Return — frame-inheritance** ([[trust-prior-pass-frame]]): verified endpoints, not interior. (4) **Pass-2 fixes applied** (Musil/Arendt sidecars corrected, interior-verified; **Camus WITHDRAWN** pending clean re-source) — **NOT re-run/verified (Bash flaky); studium tree left inconsistent+uncommitted on purpose.** (5) **ARC ornaments grounded**: **cul-de-lampe** = confronted hederae, SVG from open EB Garamond, terminal-close, *ivy=lié=ligature binding dead-to-unborn-via-living* (steward's keystone); **triple point** = Penguin/Tschichold restraint citation at the opening threshold, "a touch more weight." Both → spec §IV + type-matrix (steward, tomorrow). **PULLING THREAD: the pattern-finder's first TRUE pass on Position I — gated now on a clean, complete source set the steward will assemble.** NEXT: re-source → re-run+verify pass-2 → clean grounding → marginalia → commit. +- [Session 2026-06-16 (evening, post-/clear) — pattern-finder built + Station-I ingested + pass-1 contamination caught + ARC ornaments grounded](session-2026-06-16-evening-pattern-finder-build-station-i-contamination-arc-ornaments.md) — **The thread's first real build, and the engine catching its own error.** (1) **Station-I ingested** into studium-engine (5 authors/7 work-files; steward ruled FR-originals · Levi=Gray-Zone-only · both Weil; new manifest vocab `role:reading-source`+`station:I`; omnibus works sidecar-scoped; 15 sources/7282 drawers/verify HEALTHY). **[FIX] Mauss re-anchor** — the morning's own chamber-cleanup (`03de347`) broke the engine's pinned hash; gate caught it. (2) **Built `engine/pattern_finder.py`** — charter-L2 harness (three cognitions; generate-free/verify-tight; honest-silence; reasoner=Claude over FTS). (3) **PASS-1 CAUGHT ITS OWN FALSE GROUNDING** ([[feedback-tool-review-after-each-use]] PASS-BUT-FALSELY): "5/5 voices" partly false — **Camus la-chute span is mostly *Le Premier Homme*** (mislabelled mega-omnibus); **Musil/Arendt apparatus leak** (translator N.d.T. / Amos Elon intro / Bibliography-Index). The engine's thesis (§VII/§XV) *working*. Survivor: **P3 "complicity that depletes not damns"** (Weil↔Levi↔Arendt, 1947↔1986). **Return — frame-inheritance** ([[feedback-trust-prior-pass-frame]]): verified endpoints, not interior. (4) **Pass-2 fixes applied** (Musil/Arendt sidecars corrected, interior-verified; **Camus WITHDRAWN** pending clean re-source) — **NOT re-run/verified (Bash flaky); studium tree left inconsistent+uncommitted on purpose.** (5) **ARC ornaments grounded**: **cul-de-lampe** = confronted hederae, SVG from open EB Garamond, terminal-close, *ivy=lié=ligature binding dead-to-unborn-via-living* (steward's keystone); **triple point** = Penguin/Tschichold restraint citation at the opening threshold, "a touch more weight." Both → spec §IV + type-matrix (steward, tomorrow). **PULLING THREAD: the pattern-finder's first TRUE pass on Position I — gated now on a clean, complete source set the steward will assemble.** NEXT: re-source → re-run+verify pass-2 → clean grounding → marginalia → commit. ## Archived (2026-06-16 morning — officina + conversion tooling trustworthy + Station-I corpus complete) - [Session 2026-06-16 — officina established + conversion tooling made trustworthy + Station-I corpus complete](session-2026-06-16-officina-conversion-tooling-station-i.md) — **Long, two movements, almost entirely the prerequisite for the thread.** (1) **Officina** = ARC's in-repo writing workshop (genetic trail seeds/notes/fragments/drafts; never published; **ADR-008**; gates = Hakyll allowlist + both-remotes-private; sub-canonical sources → chamber `antechamber/`). Seeded by The Making (sketch + *Magnifica Humanitas* note [steward deeply affected] + reading list). (2) **The conversion-tooling marathon** (steward's deepest ask: *review every tool after each use until reliable* → [[feedback-tool-review-after-each-use]]): hardened **audit_cruft** (gate was passing falsely-clean → corpus never "1280 clean": 160→215 dirty), built **verify_conversion.py** (composite verifier **+ prose-safety `prose_delta`**), fixed **strip_cruft** + **repair_epub_headings**, wrote **`conversion-runbook.yaml`** (the single operational place) + **`tool-evolution-log.md`**. Prose-gated cleanup: **180 cleaned, 35 reconvert → honest 1245/35**. **Station I corpus-complete** (Camus *La Chute* + Musil ×2 converted/graduated; joins Weil/Levi/Arendt). **Return: the false alarm** — cried "destroyed prose," disproved it (cruft tokens, not prose; recalibration: content-aware compare, not token counts). All pushed (chamber `d9c6755`, ARC `7a0d4b6`). NEXT: ingest the five into the engine → build/run the pattern-finder's first pass. diff --git a/claude/memory/reference-verification-ladder.md b/claude/memory/reference-verification-ladder.md index 9c48040..344b9ad 100644 --- a/claude/memory/reference-verification-ladder.md +++ b/claude/memory/reference-verification-ladder.md @@ -23,7 +23,7 @@ Proven gates, each earned from a real catch. Reach for the one the claim's shape - **Delayer-diff** (2026-06-08) — for an `@layer`/structural-wrap refactor where the compiled output gains uniform wrappers: strip the `@layer X{`/closing-`}` wrappers from the compiled CSS (brace-aware script), diff vs the true baseline; byte-identical proves nothing *moved, dropped, or changed* — only got wrapped. Proven 3× in W3R Stage 2a. **CAUTION — byte-identical ≠ rendered-identical under `@layer`:** `@layer` overrides specificity, so a structural-wrap byte-proof is *necessary but not sufficient*; render-review is the gate when the cascade *basis* changes (a 5-layer wrap was byte-true yet produced 3 render inversions the diff could not see — the steward's eye caught them). Rendered-identity is then argued *by construction* (only already-winning rules promoted) AND render-reviewed — not inferred from the byte-diff. - **Hold or exclude volatile build-stamps** — a build-date/commit stamp (colophon `_build_info`) differs between baseline and post builds and falsely flags a byte-diff. Hold it constant or exclude it, and say which (2026-06-10, Stage-G close). - **Two-hat commit separation** — name the hat each commit wears: a *refactor* commit's compiled output is byte-identical (or carries a pre-stated classified delta); a *feature* commit is where rendered values change. Makes the gate above applicable per-commit instead of per-session (2026-06-08, from Fowler). -- **Lossless-relocation gate** — restructuring an index or moving prose between files: line-range **slices, never retyping**, + md5-conservation per moved slice + a link-resolution canary over every `](file.md)` and `[[wikilink]]`. Proven on the MEMORY.md two-file split (2026-07-06) and again on the skill-harvest split (2026-08-01, archive byte-identical over 166,027 bytes). +- **Lossless-relocation gate** — restructuring an index or moving prose between files: line-range **slices, never retyping**, + md5-conservation per moved slice + a link-resolution canary over every `](file.md)` and `[[wikilink]]`. Proven on the MEMORY.md two-file split (2026-07-06) and again on the skill-harvest split (2026-08-01, archive byte-identical over 166,027 bytes). **The canary is an executable — `python3 ~/dotfiles/scripts/wake-digest.py` (both link forms, four outcomes each, breakage dated from git; `--selftest` proves it). Never hand-type it: the hand version cannot strip code spans, and on 2026-08-09 its one "finding" was the link pattern inside its own specification** (2026-08-10). ## Mechanical text transforms ("strip/replace token X across the file") - **Token-strip hits prose** (2026-06-08) — a whole-file `sed s/…!important…//` (or any token strip) also removes the token from comments, code-refs, and docstrings, silently mangling prose. Scope the transform to declaration lines, OR compare raw-before vs clean-declaration counts and verify comment integrity after (caught 4 mangled comments via the 56-vs-70 count mismatch; restored). Same family as *Count first, then look* — the word lives in prose too. diff --git a/claude/memory/session-ledger-2026-08-10.md b/claude/memory/session-ledger-2026-08-10.md new file mode 100644 index 0000000..b3cb838 --- /dev/null +++ b/claude/memory/session-ledger-2026-08-10.md @@ -0,0 +1,50 @@ +--- +name: session-ledger-2026-08-10 +description: "Practice-of-return ledger maintained by /symmetria — returns, open horizons, recalibrations, authorization moves, sub-agent dialogues, bypasses." +metadata: + node_type: memory + type: feedback + originSessionId: bc984a6d-e8ce-47f5-99da-062703403e7d + modified: 2026-08-10T13:56:12.136Z +--- + +# Session Ledger — 2026-08-10 + +## Returns + +- **09:xx — inherited premise checked before executing it (second consecutive wake).** The wrap's + steward-directed to-do asserted *"the gap is neither knowledge nor home but the absence of an + EXECUTABLE."* Substrate says otherwise: `~/dotfiles/scripts/wake-digest.py` already carries + `classify_pointers`/`sec_pointers` (built 2026-08-08, `19bddd5`), both indexes, four outcomes, + positive controls, wired to SessionStart. The task is therefore **completing an instrument**, + not building one. Named before starting work; the fix is scoped to the three real gaps. +- **Reached the ladder before deriving a gate.** `reference-verification-ladder.md` already + specifies the canary under *lossless-relocation gate* — "a link-resolution canary over every + `](file.md)` and `[[wikilink]]`". The wikilink half was specified on the ladder and absent from + the code; read, not re-derived. + +## What held + +- The wake's own instrument reported its lone "dead" pointer honestly enough to be diagnosable — + the four-outcome design (2026-07-28 provenance) did what it was built to do. + +## Open horizons + +- **`wrap_inside` false alarm.** The digest announced "PREVIOUS SESSION DID NOT WRAP" for a session + that wrapped at 19:48 and then kept working until 21:54. Same defect family as the pointer alarm: + a two-valued detector over a state that has three cases (wrapped · wrapped-then-continued · + never-wrapped). Named, not fixed — outside today's scope. +- REVIEWED-114 stays OPEN on 4b (steward's marked answer key). No mechanism can cover it. +- Skill-harvest: 41 `S2` rows authorized 2026-07-19, still unexecuted. + +## Confidence to recalibrate + +## Authorization moves + +- Canary completion treated as `[FIX]` — it resolves scoped bugs against an existing steward + specification (the 2026-08-09 to-do), on an instrument the steward directed be completed today. + No new architectural direction; no governance file touched. + +## Sub-agent dialogues + +## Bypasses diff --git a/claude/skills/wake-up/SKILL.md b/claude/skills/wake-up/SKILL.md index 0cea493..0e3e620 100644 --- a/claude/skills/wake-up/SKILL.md +++ b/claude/skills/wake-up/SKILL.md @@ -63,7 +63,7 @@ Run these in parallel to minimize latency: - Read `skill-harvest-register.md` directly — the canonical surface for open skill proposals (wrap §1.6 appends there); surface any awaiting steward authorization - Read `reference-verification-ladder.md` directly — the canonical surface for the named verification instruments; hold the one or two the session's work will actually need - Read `~/.claude/projects/-Users-davidglidden/memory/session-ledger-[previous-date].md` if it exists — **specifically read the "Returns" and "Confidence to recalibrate" sections** for mood signal -- **Link-resolution canary (seconds-cheap):** verify MEMORY.md's file pointers resolve — every `](file.md)` target exists in the memory dir. Flag dead pointers in the briefing, distinguishing pre-existing-broken from newly-broken (grep the previous git state if in doubt). A pointer to a missing file is the index lying about what memory holds. +- **Link-resolution canary — READ THE DIGEST'S `MEMORY POINTERS` LINE; do not re-implement it.** `~/dotfiles/scripts/wake-digest.py` runs at SessionStart and reports both indexes over both link forms — path pointers `](file.md)` and `[[wikilinks]]` — in four outcomes each (OK · MIS-AUTHORED, with the replacement string handed back · DEAD/UNWRITTEN · NON-PORTABLE), and dates each break as pre-existing or newly-broken from git. Surface any non-OK outcome in the briefing; a pointer to a missing file is the index lying about what memory holds. If the digest is absent, run it: `python3 ~/dotfiles/scripts/wake-digest.py`. ⚠ **Never hand-type this check.** It was typed inline on 2026-08-08 and again on 2026-08-09, and the hand version's one "finding" was the link pattern inside its own specification — the executable strips code spans and the hand version cannot. Prove the instrument before trusting a clean line: `wake-digest.py --selftest` (61 checks, positive and negative controls). - **Telos conditional:** if the pulling thread touches the studium engine / The Making / ARC-as-public-proof (the engine's reason-for-being), also read `project-studium-engine-telos-chamber-of-voices.md` and hold ONE line of the why in the briefing — the telos lane drawn first, never only the production lanes. Do NOT recite it on unrelated wakes (decorative). **b. The files layer — primary memory** `[palace-memory MemPalace wound down 2026-07-07]` diff --git a/scripts/wake-digest.py b/scripts/wake-digest.py index e3bf042..acfa86f 100755 --- a/scripts/wake-digest.py +++ b/scripts/wake-digest.py @@ -429,10 +429,52 @@ def brief_age_days(): # memory files' own prose (17 uses). Absolute still VALIDATES here — it is not # wrong, only unportable — but it is counted and reported so it cannot re-enter # silently. +# +# 2026-08-10 — three completions, all earned before this code existed. +# +# (1) CODE SPANS. The matcher read `](file.md)` inside backticks as a pointer, +# so on 2026-08-09 it reported exactly one DEAD pointer: the link pattern +# written inside MEMORY.md's own SPECIFICATION OF THIS CANARY. An +# instrument that flags its own documentation flags it every wake, forever, +# and the real signal drowns in a permanent known-false line — the same +# "known canary bug" dismissal the block above was written to end, arriving +# by a second route. Code spans and fenced blocks are blanked before +# matching, offsets preserved so every reported line number stays true. +# Inline spans may not cross a newline: a stray unmatched backtick must +# bound its damage to one line rather than blanking the rest of the file +# and HIDING dead pointers. An unterminated fence simply does not match, +# so the failure direction is over-reporting, never silence. +# +# (2) WIKILINKS. `reference-verification-ladder.md` has specified the canary as +# covering "every `](file.md)` and `[[wikilink]]`" since 2026-07-06; only +# the first half was ever built. Reported as UNWRITTEN, not DEAD, because +# ~/CLAUDE.md rules a wikilink with no file yet to be legitimate — it marks +# something worth writing. A permitted forward-reference and a broken index +# pointer are different findings and must not share a word. +# +# (3) PRE-EXISTING vs NEWLY BROKEN. The 2026-08-09 spec asked for it. Derived +# from git, NOT from a stored prior run: a state file would make this the +# one cached section in a digest whose governing property is that it is +# computed. Where git cannot answer — a target outside this repo — it says +# so rather than guessing. NONPORTABLE_RE = re.compile(r"^/(Users|home)/") MEM_INDEXES = ["MEMORY.md", "MEMORY-reference.md"] POINTER_RE = re.compile(r"\]\(([^)\s]+\.md)\)") +WIKILINK_RE = re.compile(r"\[\[([^\[\]|#\n]+?)(?:#[^\[\]|\n]*)?(?:\|[^\[\]\n]*)?\]\]") +FENCE_RE = re.compile(r"^(?P`{3,}|~{3,})[^\n]*\n.*?^(?P=f)[^\n]*$", re.M | re.S) +INLINE_CODE_RE = re.compile(r"`+[^`\n]*`+") + + +def blank_code(text): + """Blank code-span and fenced-block CONTENT, preserving offsets and newlines. + + Preserving offsets is the point: every line number this module reports is + computed from the blanked text, so it must still address the real line. + """ + def blank(m): + return "".join("\n" if c == "\n" else " " for c in m.group(0)) + return INLINE_CODE_RE.sub(blank, FENCE_RE.sub(blank, text)) def find_basename(name): @@ -458,6 +500,7 @@ def resolve(raw, base): def classify_pointers(text, base): """(n_checked, mis_authored, dead, nonportable) — outcomes, never one word.""" n, mis, dead, nonport = 0, [], [], [] + text = blank_code(text) for m in POINTER_RE.finditer(text): raw = m.group(1) if raw.startswith(("http://", "https://", "mailto:")): @@ -479,8 +522,93 @@ def classify_pointers(text, base): return n, mis, dead, nonport +def near_slugs(slug, memdir): + """Memory files this wikilink was plainly REACHING FOR. Prefix-family only. + + `[[trust-prior-pass-frame]]` wants `feedback-trust-prior-pass-frame.md`; the + slug is the tail of the real stem after a hyphen. Matching on that boundary + (never on a bare substring) keeps `[[arc]]` from claiming every file with + "arc" inside a word. + """ + out = [] + try: + stems = [f[:-3] for f in os.listdir(memdir) if f.endswith(".md")] + except OSError: + return out + for stem in stems: + if stem.endswith("-" + slug) or slug.endswith("-" + stem): + out.append(stem) + return sorted(out) + + +def classify_wikilinks(text, memdir): + """(n_checked, mis_authored, unwritten) — three outcomes, never one word. + + The same discipline as the path pointers above, and for the same reason: the + first build of this function reported only UNWRITTEN, and its two live hits + were both `[[trust-prior-pass-frame]]`, whose file EXISTS as + `feedback-trust-prior-pass-frame.md`. One word would have sent a fixable + typo to the wake every morning wearing the label "nothing to do here". + + UNWRITTEN, never DEAD, for the genuine misses: ~/CLAUDE.md rules that a + `[[name]]` matching no file yet "is fine — it marks something worth writing + later, not an error." A permitted forward-reference is not a defect. + """ + n, mis, unwritten = 0, [], [] + text = blank_code(text) + for m in WIKILINK_RE.finditer(text): + slug = m.group(1).strip() + if not slug: + continue + n += 1 + stem = slug[:-3] if slug.endswith(".md") else slug + if os.path.exists(os.path.join(memdir, stem + ".md")): + continue + line = text.count("\n", 0, m.start()) + 1 + near = near_slugs(stem, memdir) + if near: + mis.append((line, slug, " | ".join(near))) + else: + unwritten.append((line, slug)) + return n, mis, unwritten + + +# Breakage age is DERIVED from git, never from a stored prior run — a cache is +# the one thing this digest's governing property forbids. Two questions, both +# answerable at HEAD: was the pointer already written, and did its target +# already exist? Their four combinations are the four honest verdicts. +def breakage(raw, head_text, target_at_head): + """PRE-EXISTING vs NEWLY BROKEN. `None` inputs mean 'git cannot say'.""" + if head_text is None: + return "age unknown — no committed version of this index to compare" + if raw not in head_text: + return "NEW — this pointer was added since HEAD" + if target_at_head is True: + return "NEWLY BROKEN — target existed at HEAD and is gone now" + if target_at_head is False: + return "pre-existing — already broken at HEAD" + return "pre-existing at HEAD — target lives outside this repo, break date unknown" + + +def head_text_of(relpath): + """The index as committed. None => never committed, or git unavailable.""" + return sh(["git", "-C", D, "show", f"HEAD:{relpath}"]) + + +def target_at_head(raw, memrel): + """Did the pointer's target exist at HEAD? None where git cannot know.""" + base = os.path.basename(raw) + if os.path.normpath(resolve(raw, os.path.realpath(MEM))) != os.path.normpath( + os.path.join(os.path.realpath(MEM), base)): + return None # leaves the memory dir — outside git's reach here + return sh(["git", "-C", D, "cat-file", "-e", f"HEAD:{memrel}/{base}"]) is not None + + def sec_pointers(): total, mis, dead, nonport = 0, [], [], [] + wtotal, wmis, unwritten = 0, [], [] + memdir = os.path.realpath(MEM) + memrel = os.path.relpath(memdir, D) for name in MEM_INDEXES: path = os.path.realpath(os.path.join(MEM, name)) text = read(path) @@ -489,10 +617,16 @@ def sec_pointers(): continue n, m_, d_, np_ = classify_pointers(text, os.path.dirname(path)) total += n + head = head_text_of(f"{memrel}/{name}") mis += [(name,) + t for t in m_] - dead += [(name,) + t for t in d_] + dead += [(name, ln, raw, breakage(raw, head, target_at_head(raw, memrel))) + for ln, raw, _ in d_] nonport += [(name,) + t for t in np_] - return total, mis, dead, nonport + wn, wm, wu = classify_wikilinks(text, memdir) + wtotal += wn + wmis += [(name,) + t for t in wm] + unwritten += [(name,) + t for t in wu] + return total, mis, dead, nonport, wtotal, wmis, unwritten # ---------- stray maps ---------- @@ -609,6 +743,61 @@ def selftest(): classify_pointers("[x](https://a.md)", MEMDIR) == (0, [], [], [])) chk("classify_pointers returns clean on empty input", classify_pointers("", MEMDIR) == (0, [], [], [])) + NOWHERE = "no-such-file-anywhere-xyzzy-9931.md" + print("\ncode spans [2026-08-09: the canary flagged its own specification]:") + chk("a pointer INSIDE a code span is not a pointer", + classify_pointers(f"`[y]({NOWHERE})`", MEMDIR) == (0, [], [], [])) + chk("...and the SAME pointer outside one still fires [direction control —" + " blanking must not swallow everything]", + classify_pointers(f"[y]({NOWHERE})", MEMDIR)[0] == 1) + chk("MEMORY.md's own canary spec is silent [the exact 2026-08-09 false positive]", + (lambda t: classify_pointers(t, MEMDIR) == (0, [], [], []) + and classify_wikilinks(t, MEMDIR) == (0, [], []))( + "→ `~/dotfiles/scripts/`, invoked not described; `](file.md)` +" + " `[[wikilink]]` over both memory indexes")) + chk("blanking preserves LINE NUMBERS [offsets kept, not deleted]", + classify_pointers(f"`](x.md)`\n[y]({NOWHERE})\n", MEMDIR)[2][0][0] == 2) + chk("a fenced block is blanked", + classify_pointers(f"```\n[y]({NOWHERE})\n```\n", MEMDIR) == (0, [], [], [])) + chk("a STRAY unmatched backtick blanks nothing [damage bounded to one line;" + " a greedy matcher would HIDE dead pointers]", + classify_pointers(f"a ` b\n[y]({NOWHERE})\n", MEMDIR)[0] == 1) + chk("an UNTERMINATED fence blanks nothing [fails toward reporting, not silence]", + classify_pointers(f"```\n[y]({NOWHERE})\n", MEMDIR)[0] == 1) + print("\nwikilinks [the ladder specified them 2026-07-06; never built until now]:") + chk("a wikilink whose file exists resolves", + classify_wikilinks("see [[MEMORY]]", MEMDIR) == (1, [], [])) + chk("a wikilink with no file is UNWRITTEN, not dead [~/CLAUDE.md rules it legitimate]", + (lambda r: r[0] == 1 and not r[1] and len(r[2]) == 1)( + classify_wikilinks("see [[no-such-memory-xyzzy-9931]]", MEMDIR))) + chk("a NEAR-MISS slug is MIS-AUTHORED with the real file handed back" + " [live: the two hits the one-word version mislabelled]", + (lambda r: len(r[1]) == 1 and not r[2] + and r[1][0][2] == "feedback-trust-prior-pass-frame")( + classify_wikilinks("[[trust-prior-pass-frame]]", MEMDIR))) + chk("...and a genuine miss does NOT collapse into mis-authored [direction control]", + not classify_wikilinks("[[no-such-memory-xyzzy-9931]]", MEMDIR)[1]) + chk("near_slugs matches on the HYPHEN boundary, not bare substring" + " [else [[arc]] would claim every file with 'arc' in a word]", + all(s.endswith("-arc") or "arc".endswith("-" + s) for s in near_slugs("arc", MEMDIR))) + chk("an alias and a heading are stripped down to the slug", + classify_wikilinks("[[MEMORY#Index|the index]]", MEMDIR) == (1, [], [])) + chk("a wikilink inside a code span is not a wikilink", + classify_wikilinks("`[[no-such-memory-xyzzy-9931]]`", MEMDIR) == (0, [], [])) + chk("classify_wikilinks returns clean on empty input [positive control]", + classify_wikilinks("", MEMDIR) == (0, [], [])) + print("\nbreakage age [derived from git — this digest may not cache]:") + chk("a pointer absent at HEAD is NEW", + breakage("x.md", "nothing here", False).startswith("NEW")) + chk("a pointer present at HEAD whose target was there too is NEWLY BROKEN", + breakage("x.md", "[a](x.md)", True).startswith("NEWLY BROKEN")) + chk("a pointer present at HEAD whose target was already gone is pre-existing", + breakage("x.md", "[a](x.md)", False).startswith("pre-existing")) + chk("an out-of-repo target says the break date is unknown [honest degradation]", + "unknown" in breakage("x.md", "[a](x.md)", None)) + chk("no committed index reads as UNKNOWN, never as pre-existing" + " [negative control — absence of evidence]", + breakage("x.md", None, None).startswith("age unknown")) chk("is_homed TRUE for a Desktop map symlinked into maps/ [live substrate]", is_homed(os.path.join(DESKTOP, "corpus-index-2026-07-26.md"), MAPS_DIR)) chk("is_homed FALSE for a regular file [negative control — must not pass everything]", @@ -676,16 +865,23 @@ def main(): o.append(f"\nGOVERNANCE DRIFT — ~/CLAUDE.md: {drift} substrate-contradicted claim(s)" " (detection only; correction needs [ESCALATE])") - ptot, pmis, pdead, pnp = sec_pointers() + ptot, pmis, pdead, pnp, wtot, wmis, wun = sec_pointers() o.append(f"\nMEMORY POINTERS — {ptot} checked · {len(pmis)} mis-authored · {len(pdead)} dead" - + (f" · {len(pnp)} non-portable" if pnp else "")) + + (f" · {len(pnp)} non-portable" if pnp else "") + + f" | {wtot} wikilinks · {len(wmis)} mis-authored · {len(wun)} unwritten") for f, ln, raw, fix in pmis: o.append(f" MIS-AUTHORED {f}:{ln} {raw}") o.append(f" → target exists; replace with: {fix}") - for f, ln, raw, _ in pdead: + for f, ln, raw, age in pdead: o.append(f" DEAD {f}:{ln} {raw} (no file of that name in ~/_Dev or the memory dir)") + o.append(f" → {age}") for f, ln, raw in pnp: o.append(f" NON-PORTABLE {f}:{ln} {raw} (resolves, but hardcodes this machine)") + for f, ln, slug, near in wmis: + o.append(f" MIS-AUTHORED {f}:{ln} [[{slug}]]") + o.append(f" → the file exists; replace with: [[{near}]]") + for f, ln, slug in wun: + o.append(f" UNWRITTEN {f}:{ln} [[{slug}]] (permitted forward-reference, not an error)") strays = stray_maps() if strays: