Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion .claude/skills/enriching-x-knowledge/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: enriching-x-knowledge
description: Use when the user wants to induce their XBrain topic vocabulary, enrich their knowledge base (item summaries + topics) or synthesize its topic-page overviews with their Claude subscription instead of the paid API. Drives the `xbrain vocab`, `xbrain enrich` and `xbrain topics` worksheet flows end to end.
description: Use when the user wants to induce their XBrain topic vocabulary, enrich their knowledge base (item summaries + topics), synthesize its topic-page overviews, digest bookmarked videos into long-form readable notes, or verify enrichment outputs (LLM-as-judge) with their Claude subscription instead of the paid API. Drives the `xbrain vocab`, `xbrain enrich`, `xbrain topics`, `xbrain video-digest` and `xbrain verify` worksheet flows end to end.
---

# Enriching the XBrain knowledge base
Expand Down Expand Up @@ -94,7 +94,53 @@ each topic page — also via a worksheet, at no API cost.
validates the judgments, stores the overviews and rewrites the topic pages.
If it reports rejected topics, fix those judgments and run `--apply` again.

## Video digests

`video-digest` turns a bookmarked video (already transcribed by `digest-video`)
into a long-form readable `digest` — also via a worksheet, at no API cost.

1. **Export the worksheet.** Run `xbrain video-digest --executor claude-code`. It
writes `data/video-digest-worksheet.json` with the videos pending a digest
(their transcript + frame descriptions) and the `rubric-video-digest.md` rubric.
If it reports no videos pending, stop. If none has a transcript, run
`xbrain digest-video` first.

2. **Read `data/video-digest-worksheet.json`** and, per entry, write one `digest`
following the embedded rubric: the *What it is · Key points · Why it matters*
shape, grounded in the transcript (what the video says) and the frame
descriptions (what it shows), faithful with no hype. For a mute video, build it
from the frames alone. Append `{item_id, digest}` to the `judgments` array.

3. **Apply.** Run `xbrain video-digest --apply data/video-digest-worksheet.json`.
It snapshots `data/`, writes each `source.digest`, and `generate` then leads the
note with the digest. Fix any rejected entries and re-run `--apply`.

## Verifying enrichment outputs

`verify` is an LLM-as-judge pass that scores existing enrichment outputs (a
`summary`, a video `digest`, or a `topics` assignment) for faithfulness +
adherence — report-only, it never mutates the store.

1. **Export the worksheet.** Run `xbrain verify --target all --executor claude-code`
(or `--target summary|digest|topics`). It writes `data/verify-worksheet.json`
with, per `(item, target)`, the source, the generated output, its generation
rubric and the `rubric-verify.md` verify rubric. If it reports nothing to
verify, run `enrich` (and, for `digest`, `video-digest`) first.

2. **Judge each entry**, defaulting SKEPTICAL, per the verify rubric: **faithfulness**
(every claim/number/name grounded in the source — one unsupported claim is a
FAIL) and **adherence** (the output obeys its own generation rubric). Append one
`{item_id, target, verdict, faithfulness, adherence, flags}` per entry. For a
real ensemble, copy the worksheet **once per judge** and fill each independently.

3. **Apply.** Run `xbrain verify --apply ws1.json --apply ws2.json …` — one file per
judge. It aggregates them (faithfulness is unforgiving: one judge's FAIL sinks the
group) and writes `data/verify-report.{json,md}`. The `.md` leads with the
FAIL/REVIEW verdicts + flagged claims for a human to act on.

## Notes

- No API key and no per-token cost — this session does the LLM work.
- `data/enrich-worksheet.json` is gitignored and disposable once applied.
- `video-digest` and `verify` share this same `claude-code` worksheet handoff and
read the same `[enrich].executor` default; neither has an `api` track.
35 changes: 29 additions & 6 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

40 changes: 34 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,17 @@ generates an Obsidian wiki.
fetch+transcribe, all get the source. No-speech videos attach with empty text +
`has_speech=False` (never a hard failure). Idempotent (skips items with a fresh
`x_video` source unless `--force`); destructive → auto-snapshot.
- Video digest — pipeline integration (PR3): the attached `x_video` transcript
- Video digest — pipeline integration (PR3, + #75): the attached `x_video` transcript
flows through the **existing** `enrich → topics → generate` steps, no new stage.
`enrich` splices a `Video transcript:` block into the item prompt (skips
no-speech; caps at `TRANSCRIPT_CHAR_LIMIT`=12000 chars); `topics` folds a tighter
per-video excerpt (`TOPIC_TRANSCRIPT_CHAR_LIMIT`=2000) into the synthesis prompt;
`generate` renders a `## Video digest` section (or a one-line silent-video note).
`enrich` feeds the transcript (+ frame descriptions) into the item prompt (skips
no-speech). **The two tracks differ on length:** the `api` executor splices a
`Video transcript:` block capped at `TRANSCRIPT_CHAR_LIMIT`=12000 chars, while the
worksheet (`claude-code`/`manual`) track sends the **FULL untruncated** transcript
(`worksheet._video_transcript`) — a full-context agent judges it — plus a
`video_frame_descriptions` field (what the video SHOWS — the slide descriptions,
#75; the `api` track injects the same as a `Video frames:` block). `topics` folds a
tighter per-video excerpt (`TOPIC_TRANSCRIPT_CHAR_LIMIT`=2000) into the synthesis
prompt; `generate` renders a `## Video digest` section (or a one-line silent-video note).
This is what fixes video items showing topic `—`. Re-enrichment trigger:
`attach_transcript` bumps `content.fetched_at`, and `enrich` re-enriches any item
whose `content.fetched_at > enriched.enriched_at`, so a transcript attached AFTER
Expand All @@ -74,6 +79,27 @@ generates an Obsidian wiki.
downloaded photos (`_media/` mirroring). Content-aware: talking-head/interview
videos are detected and the visual layer is skipped + logged (never a silent
drop). Default off — a normal `digest-video` run never touches ffmpeg/vision.
- Video digest — long-form synthesis (`video-digest`, #44 / PR #78): a **separate**
worksheet stage (not folded into `digest-video`) that reads the transcript + frame
descriptions and writes a readable long-form digest ("what it is · key points · why
it matters") to the `x_video` source's **additive `digest: str = ""`** field on
`ContentSourceSuccess` (`""` = "no digest yet", so every pre-digest record loads
unchanged). Worksheet flow like enrich (`--executor manual|claude-code`, reuses
`[enrich].executor`; NO `api` track, NO config section of its own); `--apply`
imports the filled worksheet, writes every `source.digest`, and **auto-snapshots**
(the apply branch is the one that mutates `items.json`; export only writes the
worksheet JSON). `generate` then renders the digest as the section HEADLINE,
demoting the raw transcript + frames into a collapsible `<details>`
(`i18n.Strings.video_evidence_header`); an empty `digest` falls back to the old
inline raw layout (back-compat).
- Enrichment verification (`verify`, LLM-as-judge, #79 / PR #80): a **report-only**
QA stage — an ensemble of LLM judges scores each enrichment output (`summary`,
video `digest`, `topics`) for **faithfulness** (grounded in source?) + **adherence**
(follows rubric?). `--target summary|digest|topics|all`; worksheet flow (`--executor
manual|claude-code`, reuses `[enrich].executor`, no `api`); `--apply` accepts
**multiple** worksheets (one per judge), aggregates them (faithfulness unforgiving:
one judge's FAIL sinks the group), and writes `data/verify-report.{json,md}`.
**Never mutates the store, never snapshots** (mirrors `cv-guardrail`).
- X Articles as blogposts — model seam (#39 PR1): an `x_article`
`ContentSourceSuccess` carries an additive, ordered `blocks: list[ArticleBlock]`
body — `ArticleTextBlock` (`kind="text"`) + `ArticleImageBlock` (`kind="image"`,
Expand Down Expand Up @@ -142,4 +168,6 @@ generates an Obsidian wiki.
All are gitignored.

## Git workflow
- This repo has only `main`: `feature-branch → PR → main`.
- `develop` is the integration branch: `feature-branch → PR → develop`. Branch
from `develop` (never from `main`) and target every PR at `develop`.
- `develop → main` only via PR — never merge or push directly to `main`.
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ file is not tracked by git.
## Safety: destructive operations auto-snapshot

Destructive commands (`vocab --regenerate`, `topics --resynth`, `fetch --force`,
`refresh-media`, `media`, `describe`, `download-videos`, `digest-video`) copy the
full `data/` directory to
`data/snapshots/<UTC-ts>-pre-<command>/` before they write anything. (`download-videos`
`refresh-media`, `media`, `describe`, `download-videos`, `digest-video`,
`video-digest --apply`) copy the full `data/` directory to
`data/snapshots/<UTC-ts>-pre-<command>/` before they write anything. (`video-digest`
snapshots only on `--apply` — the branch that writes each `source.digest` — never on
plain worksheet export; `verify` writes only its report and is not destructive.) (`download-videos`
takes its snapshot *after* the interactive size-gate confirmation, so a declined
run leaves no stray snapshot — but always before the first byte is written.) If your change introduces or modifies a destructive
operation, **wire the auto-snapshot** — see `_auto_snapshot` in `src/xbrain/cli.py`
Expand Down
43 changes: 29 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,17 +181,27 @@ A **bookmarked video** gets the same treatment: run [`digest-video`](#commands)
and its transcript is attached to the item, so the video flows through the *same*
`enrich → topics → generate` pipeline as an article. The note gains a real
`primary_topic` (video items used to show `—`, because enrich only ever saw the
2-line tweet), appears on its topic page(s), and renders a **`## Video digest`**
section with the talk's title and transcript — a 72-minute talk you never watched
becomes a readable, searchable, topic-linked note. A silent/no-speech clip
degrades gracefully to a one-line "silent video" note instead of an empty digest.

For **slide/screen/demo-heavy** talks, add the opt-in `--frames` flag: xbrain
extracts the key slides, describes each with an **external** vision model, and
embeds them into the digest section next to the transcript — so the visual
content is captured too, not just the audio. It is content-aware: an interview /
talking-head video is detected and its (camera-cut) frames are skipped, so you
never waste vision calls where the slides are noise.
2-line tweet) and appears on its topic page(s). The **full** transcript — and, with
`--frames`, the slide **frame descriptions** — also feed `enrich` and `topics`, so
what the video *says* and what it *shows* both count toward its topics and summary.

Then run [`video-digest`](#commands) to turn the raw transcript + frames into a
**readable long-form digest** (what it is · key points · why it matters).
`generate` renders that digest as the **headline** of the note's **`## Video
digest`** section, with the raw transcript and slide frames demoted into a
collapsible `<details>` block below it — a 72-minute talk you never watched becomes
a readable, searchable, topic-linked note. Before you run `video-digest` (or for a
video with no digest yet) the section falls back to the raw transcript inline,
exactly as before. A silent/no-speech clip degrades gracefully to a one-line
"silent video" note instead of an empty digest.

For **slide/screen/demo-heavy** talks, add the opt-in `--frames` flag to
`digest-video`: xbrain extracts the key slides, describes each with an **external**
vision model, and records the descriptions on the video source — they feed the
digest, `enrich` and `topics`, and embed into the `<details>` evidence block next
to the transcript, so the visual content is captured too, not just the audio. It is
content-aware: an interview / talking-head video is detected and its (camera-cut)
frames are skipped, so you never waste vision calls where the slides are noise.

*Example:*

Expand Down Expand Up @@ -631,9 +641,11 @@ uv run xbrain <command> [options]
| `list-videos` | **Read-only** catalog of every video referenced in `items.json` — one row per video entry with its state (`downloaded` / `failed` / `pending` / `poster-era`), estimated size (exact once downloaded, `unknown` without bitrate/duration), the item's `primary_topic` and a text snippet. Filters: `--topic`, `--status`, `--max-size`, `--source`, `--limit`. Human table by default; `--json` emits a stable machine array (`id, url, state, topic, size_bytes\|null, mp4_url, text`) an agent can parse to choose which videos to fetch. Writes nothing, takes no snapshot. |
| `fetch-video` | **Ephemeral** download of the real mp4 for selected videos to `--to <dir>/<id>.mp4`, for agent-side processing (transcription/analysis is external — see below). Select with `--ids a,b` and/or `--topic <t>` (+ `--max-size`, `--limit`, `--source`). Reuses `download-videos`' content-validation, failure classification, atomic write and mp4/HLS/poster discriminator; HLS and poster-era are skipped + counted. **Deliberately non-persisting:** never mutates `items.json`, never snapshots, never touches `data/media/` — it writes only under `--to`. `--json` for machine output. |
| `digest-video` | Turn bookmarked videos into text: **ephemeral** fetch → **external** transcriber (`[transcribe].command`, default `parakeet-mlx` — the ASR is *not* bundled in xbrain) → attach the transcript to the item as an `x_video` content source → discard the bytes. **Dedups by video identity** (the stable `amplify_video`/`ext_tw_video`/`tweet_video` id from the mp4 path, not the signed URL): N bookmarks of one video → **one** fetch+transcribe, every item gets the transcript. No-speech / no-audio videos attach with empty text + `has_speech=false` (never a hard failure). Idempotent — skips items already carrying an `x_video` source unless `--force`. Destructive (rewrites `items.json`) → auto-snapshot. Select with `--ids a,b`, `--topic <t>`, or `--all-pending` (+ `--source`, `--limit`, `--language`). **`--frames`** (opt-in visual layer, needs `[vision].command`): for slide-heavy videos it extracts key slides (ffmpeg scene detection + interval sampling so the whole video is covered), describes each via the **external** vision model, records the descriptions on the `x_video` source, and embeds the slide images into the note like downloaded photos; talking-head videos are detected and skipped (logged). The transcript then flows through the normal `enrich → topics → generate` pipeline. |
| `video-digest` | Generate the long-form **readable digest** per bookmarked video — the "what it is · key points · why it matters" synthesis of its transcript + slide-frame descriptions, written into the `x_video` source's `digest` field. Worksheet flow like the LLM stages: `--executor manual\|claude-code` exports `data/video-digest-worksheet.json`; fill it (Claude Code session or by hand) and `--apply <file>` writes every digest back to `items.json`. `generate` then renders the digest as the **headline** of the `## Video digest` section, with the raw transcript + slide frames demoted into a collapsible `<details>`. Auto-snapshots on `--apply` (the store write). Defaults to `[enrich].executor`; worksheet tracks only (no `api`). |
| `vocab` | Induce the topic taxonomy. `--executor`, `--apply <file>`, `--regenerate`. |
| `enrich` | Enrich items with a summary + topics. `--executor`, `--apply <file>`. |
| `topics` | Synthesise topic pages. `--executor`, `--apply <file>`, `--resynth`. |
| `verify` | **LLM-as-judge** QA over the enrichment outputs — scores each `summary`, video `digest` and `topics` assignment for **faithfulness** (grounded in its source?) and **adherence** (follows its rubric?). `--target summary\|digest\|topics\|all`, `--executor manual\|claude-code`. Worksheet flow: exports `data/verify-worksheet.json`; copy it once **per judge**, fill each, then `--apply` accepts **multiple** worksheets (repeat the flag, one per judge) and aggregates them into `data/verify-report.{json,md}`. **Report-only — never mutates the store, never snapshots.** Defaults to `[enrich].executor`; worksheet tracks only (no `api`). |
| `generate` | Render the wiki into the vault. |
| `sync` | `extract` + `fetch` + `generate`, in order. |
| `status` | Counts and last-run timestamps. |
Expand Down Expand Up @@ -939,9 +951,12 @@ xbrain diff <snap-a> --format json | jq '.summary.reassigned_pct'

## Execution modes

`vocab`, `enrich` and `topics` need an LLM. XBrain never embeds a Claude
subscription token — instead the LLM work is **pluggable**, with three modes,
selected by `--executor` or `config.toml`'s `[enrich].executor`.
`vocab`, `enrich`, `topics`, `video-digest` and `verify` need an LLM. XBrain never
embeds a Claude subscription token — instead the LLM work is **pluggable**, with
three modes, selected by `--executor` or `config.toml`'s `[enrich].executor`. All
five stages read that one `[enrich].executor` default; `video-digest` and `verify`
have no config section of their own and run **only** their worksheet / `claude-code`
tracks (never `api`).

| Mode | Cost | When you reach for it |
|------|------|----------------------|
Expand Down
3 changes: 3 additions & 0 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ handle = "your_handle"
# "claude-code" - exports a worksheet a Claude Code session fills (no API cost) [default]
# "api" - calls the Anthropic API (needs ANTHROPIC_API_KEY, pay-per-token)
# "manual" - exports a worksheet to fill by hand
# Also read by `xbrain video-digest` and `xbrain verify` — they have no config
# section of their own. Those two run only the worksheet tracks
# ("claude-code" / "manual"), never "api".
executor = "claude-code"
# Model used by the "api" executor. A cheap model for volume; a stronger one for quality.
model = "claude-haiku-4-5-20251001"
Expand Down
Loading
Loading