Skip to content

v0.4.0 — The Living Grimoire: self-updating engine + Editorial Constellation frontend#10

Open
vedantggwp wants to merge 13 commits into
mainfrom
cc/reverent-borg-b16bda
Open

v0.4.0 — The Living Grimoire: self-updating engine + Editorial Constellation frontend#10
vedantggwp wants to merge 13 commits into
mainfrom
cc/reverent-borg-b16bda

Conversation

@vedantggwp

Copy link
Copy Markdown
Owner

Summary

Grimoire learns the maintain half of the LLM-maintained-wiki pattern, and the frontend gets the overhaul to match. 13 commits, version 0.3.1 → 0.4.0, tests 193 → 338.

Self-updating engine

  • /grimoire:update — headless scheduled editorial pass: delta scout (angles from the wiki's own open questions, coverage gaps, and a user watchlist; cross-run URL dedup) → policy-gated batch ingest → connection mining with persistent exclusions → staleness verification → digest → PR-gated shipping (never touches the default branch; no-op runs create no PRs)
  • Deterministic substrate: lib/update-policy.ts, lib/source-ledger.ts, lib/freshness.ts, lib/connections.ts; compile now emits freshness.json, connection-candidates.json, update-context.json
  • Scout gains Delta Mode; serve surfaces stale articles in grimoire_coverage_gaps
  • Schedulers: local cron runs on the existing Claude Code login (zero credentials); the GitHub Actions adapter (--setup) authenticates with a subscription token (claude setup-token) — no API key required

Editorial Constellation frontend

  • Design-token layer: motion durations, 4px spacing grid with density semantics, OKLCH categorical ramp derived from the palette accent (lib/present/color.ts)
  • Per-article routes (read/{slug}/) with cross-document View Transitions and title morphs; legacy hash links redirect
  • Hub: ambient constellation canvas of the real knowledge graph, scroll parallax, count-up stats, card tilt
  • Reading: wikilink hover previews, backlinks panel, numbered sources, freshness badges, sliding TOC marker
  • Modes: graph focus/hulls/palette colors + warm start; gaps treemap computed at build time (d3 dropped, ~290KB → ~21KB) with a freshness lens; quiz 3D flip + streaks; feed digest cards; search keyboard navigation
  • All motion behind prefers-reduced-motion and the motion: config; WCAG AA contrast verified per palette in tests

Quality & housekeeping

  • Present reorg: 1,462-line css monolith split into partials (byte-identical output verified), shared esc()/slug helpers
  • README rewritten for v0.4.0; PII sweep (machine-specific .mcp.json untracked, local paths genericized)

Pre-Landing Review

Adversarial review (fresh-context subagent) found 4 issues — all fixed in fix(present): harden inline script embeds:

  • [P1] </script> breakout XSS via inline JSON embeds carrying frontmatter from scouted sources → new jsonForScript() at all 5 embed sites
  • [P1] graph panel joined raw tags into innerHTMLtextContent
  • [P2] inverted staleness windows silently unreachable → validated
  • Actions template: id-token grant dropped, gh narrowed, blast-radius note added

Cleared by review: serve slug traversal (regex-blocked), rmSync(site/) (gated on workspace checks), layout NaN/div-zero guards, feed $1 injection.

Test Coverage

338 tests across 20 suites (was 193). New suites cover the update substrate (policy parsing, URL normalization, freshness tier boundaries with injected clocks, connection candidates), the token layer (4px-grid lint, motion scaling, AA contrast matrix across all 8 palettes × 2 themes), routes (per-article files, disabled modes), deterministic layouts (force + treemap), and XSS regression (</script> neutralization). Visual QA by screenshot over http: hub constellation, article freshness badges, gaps lens.

Verification on first real run

Three things only a live run can confirm (designed fallbacks for each, see docs/self-updating.md): /grimoire:update --dry-run end-to-end, plugin auto-load in headless CI, WebSearch availability under the Actions runner (watchlist URLs are the fallback).

Issues

Closes #1
Closes #2
Closes #3
Closes #4
Closes #5
Closes #6
Closes #7
Closes #8
Closes #9

🤖 Generated with Claude Code

…helper

- lib/present/css.ts (1,462 lines) -> 13 focused css/ partials assembled
  in fixed order by css/index.ts; output verified byte-identical on
  sample-wiki and examples/mcp fixtures
- css-modes.ts absorbed into read/graph/search partials (overrides still
  emitted last to preserve byte order)
- 8 duplicate esc() definitions -> lib/present/esc.ts
- theme-toggle script -> lib/present/js/runtime.ts
- 193/193 tests green, dist bundles regenerated
- #1: derive slugs like papyr-core (basename + slugify lower/strict);
  nested-taxonomy wikis now render all articles instead of 0; new
  article.category field; collision warning for duplicate basenames;
  slugify promoted to an explicit dependency
- #6: strip leading h2 that slug-matches the title, drop it from
  headings so TOC stays consistent
- #7: hub lead strips terminal periods, caps at 120 chars on a word
  boundary, phrases level audiences as 'a/an {level} audience'
- #8: rewrite legacy /client-config/{slug} hrefs alongside #/note/,
  warn on survivors
- #5: treemap labels full-contrast --color-text on every tier; tier
  conveyed by tint/stroke/dot; .gap-card explicit text color
- new nested-wiki fixture; 19 new tests; 212/212 green
Deterministic half of the self-updating engine, following the v0.3.1
hybrid-enforcement pattern (lib emits evidence, skills apply judgment):

- lib/update-policy.ts: _config/update.md parser (zod frontmatter +
  watchlist/exclusion body sections) with conservative defaults when
  absent — the policy file replaces interactive checkpoints for
  headless runs
- lib/source-ledger.ts: cross-run dedup ledger (normalizeUrl,
  approved-sources.md any-shape URL extraction, raw/ frontmatter,
  lastUpdate derived from collected dates + log headers)
- lib/freshness.ts: per-article staleness tiers; checked beats updated;
  dateless articles are unknown, never stale
- lib/connections.ts: unlinked-pair candidates (shared tags/sources/
  component), exclusion-filtered, scored, capped at 20
- lib/compile.ts Step 12: emits freshness.json + update-context.json
  always, connection-candidates.json conditionally; stale conditional
  artifacts now removed (incl. taxonomy-proposal.json); notes.json
  gains updated/checked/evergreen; frontmatter extraction resolves
  nested-taxonomy files via wiki walk (compile-side issue #1)
- lib/serve.ts: loads freshness.json null-safely, appends Stale
  Articles to grimoire_coverage_gaps, version 0.4.0
- lib/slug.ts: shared slug derivation (papyr-core lockstep)
- gray-matter promoted to explicit dependency
- 53 new tests, 265/265 green
…apter

The judgment half of the self-updating engine (Phase 2):

- skills/update/SKILL.md: headless scheduled editorial pass — never
  asks, policy file + PR review replace the two taste checkpoints;
  12 steps with explicit degradation ladder (pr -> branch -> digest-only),
  no-op runs produce no empty PRs, same-day reruns reuse the open PR
- skills/update/references: digest + PR body templates (below-threshold
  sources stay visible so reviewers can promote them)
- skills/update/assets/github-workflow.yml: weekly cron + manual
  trigger via claude-code-action@v1 automation mode (inputs verified
  against current docs), allowedTools allowlist, recursion-guard note
- skills/scout: Delta Mode — temporal/open-questions/gaps/watchlist
  angles, hard cross-run dedup against update-context.json knownUrls
- skills/init scaffolds _config/update.md (template round-trips through
  lib/update-policy.ts); skills/run suggests update --setup
- docs/self-updating.md: full setup guide incl. org PR-permission
  toggle, GITHUB_TOKEN recursion guard, serve restart after merge
- 265/265 tests green
Phase 3a of the Editorial Constellation frontend:

- lib/present/color.ts: OKLCH math (Ottosson), categorical ramp from the
  palette accent (achromatic-safe), sRGB mixing, WCAG contrast — pure
- css/tokens.ts: motion tokens (--dur-1..4 scaled by motion level, 0.01ms
  for none), --ease-out/in-out/spring (linear() behind @supports), strict
  4px spacing grid --space-1..10 (#4) + density-swapped semantic tokens
  (--pad-card/--pad-card-lg/--pad-section/--gap-grid/--gap-stack),
  z-layer scale, --cat-0..7 light+dark from the accent hue
- config.ts: motion/density validated unions (warn + default on unknown),
  individual font-heading/font-body/font-mono overrides with font-display
  alias (#3), modes: list with read forced on (#9)
- html.ts: <html class="motion-* density-*">, nav filtered by enabled
  modes; index.ts gates generators + hub cards + recommended badge (#9)
- fonts now load only via <link> (removed the double-fetching CSS @import)
- bento/cards consume semantic spacing tokens
- 34 new tests incl. AA contrast matrix across all 8 palettes x 2 themes
  (issue #5 regression); 299/299 green
… issue #2

Phase 3b:

- site/read/{slug}/index.html per article: 3-col shell at depth 2, real
  prev/next links (centrality order), sidebar links with aria-current,
  per-article TOC with scroll spy, accurate per-page reading progress
- read/index.html is now a reading index: centrality-ordered rows with
  summaries, continue-reading pickup (localStorage), and a redirect shim
  keeping legacy read/index.html#slug deep links working on file://
- known wikilinks rewritten to real ../{slug}/index.html routes at build
  time; unresolved ones stay marked new and inert (aria-disabled)
- cross-document View Transitions: @view-transition navigation auto,
  page cross-fade + 8px slide, title morphs via vta-{slug} names, nav
  brand morph — all inside prefers-reduced-motion: no-preference, fully
  omitted for motion: none
- shared .reveal utility with the JS-presence safety gate (html.js) so
  no-JS readers always see content; motion runtime script in every shell
- pageShell/navBar/htmlHead gain depth param; search results navigate to
  per-article routes; site/ cleared before generation (no stale pages)
- 13 new route tests incl. disabled-modes workspace; 307/307 green
… tilt

Phase 3c:

- lib/present/layout.ts: deterministic force layout (FNV-seeded, no
  randomness — identical inputs produce identical sites) capped at 80
  nodes + squarified treemap (Bruls et al) for the d3-free gaps mode
- js/constellation.ts: ambient canvas behind the hero drawing the wiki's
  REAL knowledge graph — seeded sine drift, faint edges, accent glow on
  the top-3 nodes, pointer attraction with label surfacing; delta-clamped
  rAF, pauses on hidden tab, single static frame when motion is off,
  dpr-aware, theme-toggle repaint, aria-hidden
- js/runtime.ts: count-up stats (IO-triggered, 600ms ease-out cubic) and
  rAF-throttled card tilt + specular glow (hover:hover + pointer:fine +
  motion gates); runtime script moved to top of <body> so inline page
  scripts can rely on GRIMOIRE_MOTION_OK and reveals never flash
- css/hub.ts: hero entrance choreography (staggered rise), scroll-driven
  parallax behind @supports (animation-timeline: scroll()) — static
  fallback everywhere else, tilt transforms driven by --tilt-max token
- bento cards join the shared .reveal stagger
- 14 new tests (layout determinism/bounds, treemap area/overlap/
  proportionality, hub markup); 320/320 green
…shness

Phase 3d:

- js/popover.ts: Wikipedia-style wikilink hover previews from page-local
  window.LINK_PREVIEWS (only the slugs each page references, <2KB);
  native Popover API with fixed-div fallback, 250ms hover intent,
  focus/Escape keyboard support, skipped on touch
- article pages gain a 'Linked from' backlinks panel (title + teaser),
  a numbered Sources section with domain chips, source-count chip links
  to #sources, confidence badges explain themselves via title text
- freshness badges (aging/stale) on article meta, driven by the update
  engine's freshness.json via a zod-tolerant loader (lib/present/
  freshness.ts) — pre-v0.4 workspaces degrade silently; SiteData gains
  freshness
- TOC scroll-spy upgraded to a sliding accent marker (translateY +
  height transitions) instead of class flips
- reading progress takes the pure-CSS scroll-timeline path where
  supported, scroll-listener fallback elsewhere
- typography polish: text-wrap pretty, ::selection, scroll-margin-top
  on [id], hanging punctuation on quotes, mobile hyphenation
- 325/325 green
Phase 3e:

- graph: nodes colored from the palette's --cat-* OKLCH ramp (theme
  toggle recolors instantly, no more hardcoded schemeTableau10);
  deterministic warm-start positions from layout.ts (no spawn explosion);
  click = 1-hop focus mode (Esc/background clears), dblclick opens the
  article; cluster-hull toggle (top-5 tags, 3+ members, 6% fill); panel
  gains the summary and an Open in Read link
- gaps: d3 dropped entirely — build-time squarified treemap rendered as
  absolutely-positioned percent-geometry DOM cells (page ~290KB -> 21KB);
  cells are keyboard-focusable with descriptive aria-labels; Coverage |
  Freshness lens recolors by per-tag staleness from the update engine;
  CSS-only staggered pop-in
- quiz: real 3D flip (grid-stacked faces, preserve-3d, spring easing,
  instant under reduced motion), card slide-in, streak chip at 3+, and a
  CSS-only 12-particle confetti burst at 3/5/10 in --cat-* colors
- feed: update-run digest entries (the SKILL.md Step 9 log contract)
  render as accent-bordered stat-chip cards; newest dot pulses briefly
- search: arrow-key result navigation (aria-activedescendant), Enter to
  open, result rows finally navigate on click, staggered entrance,
  empty state offers top-tag recovery pills, kbd hint row
- examples/mcp full-site weight: 628KB total (was ~800KB)
- 334/334 green
- version 0.4.0 in package.json + plugin.json
- roadmap Phase 8 (The Living Grimoire), decisions.md entry with
  rejected alternatives, changelog entry, SOUL principle 5 clause
  (control relocates to policy + PR review in scheduled mode)
- architecture.md: update stage row + new workspace artifacts
- design-config template: real motion/density semantics, modes: key,
  individual font overrides documented
- README + CLAUDE.md skill tables gain update
- visual QA pass on examples/mcp over http: hub constellation, article
  freshness badge, gaps treemap verified by screenshot; fixed the
  double-legend bug (.gaps-legend[hidden]), full-width Recommended
  badge, and the favicon 404 (inline SVG favicon)
- 334/334 green, bundles rebuilt
The plugin itself never needs an API key — locally (interactive or cron)
/grimoire:update runs on the user's existing Claude Code login like every
other skill. The one place a credential is needed is the GitHub Actions
runner (fresh VM, no Claude login), and the right default there for a
Claude Code plugin audience is the subscription OAuth token from
`claude setup-token` (CLAUDE_CODE_OAUTH_TOKEN). API key demoted to the
metered-billing alternative across the workflow template, --setup
instructions, and docs; local cron called out as the zero-credential path.
README rebuilt against a documentation-quality rubric (value clarity,
time-to-first-success, information architecture, audience fit, evidence
freshness, completeness, scannability, honesty):
- stale v0.3.0 badges and 183-test count corrected (v0.4.0, 334)
- self-updating loop promoted to a first-class section with both
  schedulers and the subscription-auth story (no API key narrative)
- 'Why Grimoire' positioning vs notes apps / chatbots / RAG pipelines
- frontend + MCP sections re-grounded in shipped v0.4 behavior
  (per-article routes, view transitions, constellation, freshness lens)
- 8-skill table with corrected heading; duplicate install removed;
  CONTRIBUTING/SECURITY/docs linked

PII sweep: .mcp.json (machine-specific absolute paths) untracked and
gitignored; no emails, names, or local paths remain in tracked docs.
Pre-landing adversarial review findings (all fixed):

- [P1] JSON.stringify does not escape '<' — frontmatter-derived strings
  (title/summary/tags from scouted web sources) embedded via inline
  <script> globals (HUB_GRAPH, SEARCH_ARTICLES, LINK_PREVIEWS,
  QUIZ_CARDS, GRAPH_DATA) could carry '</script>' and execute attacker
  script in the reader's browser. New jsonForScript() escapes < to
  \u003c (+ U+2028/2029) at every embed site; round-trips losslessly
- [P1] graph detail panel joined raw frontmatter tags into innerHTML —
  now filled via textContent like the summary; panel slug URL-encoded
- [P2] update policy now rejects inverted staleness windows
  (aging < fresh), which previously made the aging tier unreachable
- GitHub Actions template hardened: id-token grant dropped (OAuth-token
  auth needs no OIDC), gh narrowed to pr/auth subcommands, blast-radius
  note added for the untrusted-web-ingestion surface

+4 regression tests (script-breakout neutralization, separator escapes,
policy ordering); 338/338 green; bundles rebuilt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment