refactor(knowledge)!: make the single-video digest source-agnostic and rename it to video-digest - #2823
Conversation
…ract Design slice for folding X (Twitter) video into the single-public-video lane as an engine-layer source adapter, without duplicating the pipeline and without touching course-digest. Artifacts under docs/topics/source-agnostic-video-digest/design/: capability-matrix, design-threads (14), inherited-decisions, hub-split-budget, consumer-context. Research slices under .work/source-agnostic-video-digest/ — four topics, each gate-checked and adversarially verified by an independent context. Resolved this pass: - Source is slice metadata, not a directory level. No per-source epic dirs; default path unchanged, so no migration and no orphaning. - select-caption.js is NOT a managed surface. The inherited "never edit there" constraint was unsupported; standards' sync manifest declares 37 components and none is in plugins/knowledge. - No authoritative source states a 200-line SKILL.md target. The real constraint is the spec's < 5,000 tokens, which this hub exceeds at ~1.9x while passing the 500-line gate. - Dispatch: a static host-keyed registry, not a computed dynamic import. - Multi-media posture reversed: return a uniform 0..N collection; arity is a property of the result, never of the adapter. Defects found in existing code: - checkJs is false in both extraction trees with zero @ts-check directives, so CI's tsc --noEmit passes while checking nothing. - resolveAdapter interpolates unvalidated input into a module specifier (CWE-829, CWE-22). Low severity today; high once an adapter is selected from a URL. - twitter.py returns linked off-platform videos merged with the tweet's full provenance, so a digest would cite a foreign video as the X post. Open: T5's choice of transcript fallback. Platform-captions-only is eliminated (X captions are absent more often than present with no predictor); ASR feasibility clears on consumer hardware (~2m37s for a 2-hour video, batched). Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ff gate Resolve the eleven threads the gate FAILed on, adopting each thread's recorded recommendation under user delegation (2026-08-15): - T1 adapter home: extraction/adapters/ (contracts are different abstractions; vendor/ stays source-agnostic) - T2a: widened description ships with the feature, xlsx shape - T2b: rename to video-digest; pin name:; breaking-change terms bind the PLAN; three stale /youtube refs are the sweep regression test - A2: env rename with compatibility read; sixth env var named; adapter-namespaced config TAGGED-DEFERRED with trigger - T3: static host-keyed registry; type lane on is step zero - T4: five required methods + declared attributes; stage 2 collapses to shared (per-source stages 7 -> 6, capability-matrix updated) - T5: directional with [T5-ASR-*] tags - transcriptStrategy seam; YouTube captions, X captions+repair / asr-on-absent; (iv) rejected - T6: D-A closed - no-video post yields text-only digest - T7: reference/sources/ spokes with explicit conditional routing - T9: shared conformance suite, closed-by-default capabilities, X golden fixture - T10: sub-decision (ii) adapter-level URL canonicalization - T11: four distinct error types; per-adapter pattern table; cookie fallback gated on login-required class only Add design-handoff-gate.md recording both gate runs (FAIL then PASS) and the root cause: research-gate-passed was conflated with thread-resolved in the prior session's handoff. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…tion plan Eight-phase PLAN for the youtube-digest -> video-digest refactor: type lane on (checkJs), adapter contract + static host-keyed registry, X adapter, transcript strategy seam, conformance suite, naming/env hygiene with compatibility reads, hub split + widened description, and the terminal breaking rename with its enumerated sweep. Stress-tested by a cross-vendor Codex review (3C/16I/2S) and a fresh-context devils-advocate pass (2C/4H/9M/5L); all findings verified against the tree, three rejected on evidence, the rest folded in. Approved 2026-08-15 with all six FALLBACK rows at their recommended options. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Phase 0 of the source-agnostic video digest work: the adapter contract landing in youtube-digest/extraction needs the checker actually reading the JS, so flip checkJs first and clear every diagnostic it surfaces. TypeScript 6.0 defaults strict to true, so flipping checkJs alone dragged in the full strict tier (408 diagnostics in course-digest, 135 in youtube-digest) which the repo never opted into. Most of that is TS7016 against the vendored @melodic/video-digestion, whose only root-cause fix is typing the vendor package -- out of scope here, and the alternatives (a blanket `any` module stub, or hand-written declarations mirroring upstream) are both worse than an explicit posture. So strict is pinned false explicitly rather than left to the compiler default, which a caret-ranged typescript upgrade can move again. strictNullChecks is pinned back on: without it TypeScript silently stops narrowing discriminated unions, and the adapter contract's result envelope and error taxonomy are exactly that. It costs 14 further diagnostics, all fixed. include gains **/*.mjs -- the launchers (run.mjs, setup-deps.mjs, the hook shims) matched no include pattern and were never type-checked at all. Every fix is a type annotation or a tsconfig change; no runtime logic changed, proven by both suites passing unchanged (course-digest 91, youtube-digest 270). No @ts-ignore or @ts-expect-error was added. Notable root causes, as opposed to silenced symptoms: - PreflightResult declared `@property {string} handle - raw channel @handle`; the bare @handle parsed as a new JSDoc tag and truncated the typedef, so displayTitle and displayChannel were never declared. - 13 JSDoc import() specifiers named vendor subpaths with a .js suffix that the vendor's exports map does not publish -- they would throw ERR_PACKAGE_PATH_NOT_EXPORTED if they were ever runtime imports. - WatchState under-declared four frameSelection/artifactPaths properties that both writers set and the resume-prompt reader consumes. - spawnWithAcquireRetry forwards extra options to spawn but declared only its own retry options, so every cwd-passing caller was a type error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 0 landed in aaa62cc and passed fresh-context acceptance verification. DEVIATIONS.md records the strict-tier posture call (TypeScript 6.0 defaults strict on; pinned false with strictNullChecks re-enabled), the suppression-grep precision fix, and the @Satisfies deferral to Phase 1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…seam Adapter contract (5 required methods, declared attributes, 0..N result envelope, four-type error taxonomy), static host-keyed registry that fails closed on unknown hosts, and a YouTube adapter owning URL grammar, enqueue acceptance, error patterns, extractor args, and link-harvest composition. Shared machinery consumes adapter declarations: comment/extractor-args flags and the browser-cookie fallback loop are closed by default and gated on adapter capabilities; cookie fallback fires on login-required classification only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…envelope Contract validation (arity, closed capabilities, reserved content-claim), static-import registry conformance with fail-closed unknown-host dispatch and subdomain rules, YouTube adapter claims/declarations, CLI wrapper unknown-host exits, and fixture-driven 0/1/N envelope consumption through both the watch and transcript code paths. JSDoc type references in adapters/ use the @import tag so the no-dynamic-import grep gate stays clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… draft)
Hub SKILL.md body drops from 37,620 to 13,008 chars (~3,252 est. tokens,
under the <5,000-token guidance) by moving the watch-action detail to
context/watch-pipeline.md, queue detail to context/watch-queue.md, the
output contract to a new context/output-contract.md spoke, and per-source
material to reference/sources/{youtube,x}.md, each cited from the hub with
a conditional "when" routing clause. The description widens to cover
x.com/twitter.com status URLs with natural-language triggers and an
explicit course-platform Do NOT boundary.
Draft stage: 12 RECONCILE markers flag implementation-dependent claims for
the post-implementation reconciliation gate before the Phase 7 rename.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Recovery reads sourceUrl from the slice's persisted watch.json instead of synthesizing a YouTube URL; production consumes yt-dlp flags and spawn classification straight off the adapter declarations (adapterSourceDeclarations + classifyErrorDetail), collapsing the private side-channel object; the URL grammar moves into the YouTube adapter and dispatch (acquireMedia) into the registry, making the adapter module graph acyclic so the registry consistency check runs at module init; dispatch fails closed on a null URL claim from the owning adapter; transcript.txt pairs with the primary (media-bearing) entry and the CLI's legacy single-entry fields follow that entry at N>1; the slice README records the URL-authoritative slice key; deriveVideoSlug rejects non-path-safe slice keys and yt-dlp argv gets a -- end-of-options sentinel before the URL (verified against yt-dlp 2026.07.04); caller-less writeTranscriptArtifacts removed; a shared adapter-test harness (describe-factory) carries the contract-generic assertions so the next adapter's tests compose instead of copy-pasting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 1 landed in e61fb53 + 56b11a6 and was hardened in 5020fd4 after security and architecture review (recovery-lane sourceUrl from slice state, N>1 transcript handling, side-channel collapse onto adapter declarations, acyclic dispatch graph, null-claim fail-closed, path-safe slice keys, argv sentinel, shared adapter-test harness). Fresh-context verification passed 10/10; DEVIATIONS.md records the registry-side dispatch-seam supersession. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…st seam Second implementer of the source-adapter contract: x.com/twitter.com status URLs claim, canonicalize (adapter-level, every entry path by construction), and acquire through the shared yt-dlp machinery with a probe-first pass so the provenance guard blocks foreign-extractor delegations before any media download. Snowflake slice identity (display_id canonical, media id as discriminator, flagged quote/retweet aliasing recorded in slice metadata), compound 429 syndication-degradation detector (retryable, never success), 0..N envelope arities incl. both text-only 0-cases, raw-language caption keys with the word-timing-tag cleanup pass, and login-required cookie gating limited to the three documented cases. Shared seams consumed: per-source yt-dlp caption/no-formats declarations in build-yt-dlp-args (subLangs, omitAutoSubs, ignoreNoFormatsError, convertSubs) and canonical-URL preflight probing; registry registers the x module statically. Storage invariant covered by a mixed-source test: one queue root, shared claims namespace, source never a directory level. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keeps the adapters directory free of `import(` in any spelling so the static-registry audit grep stays a zero-row assertion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live probe of the design's verified public status shows one <X-word-ms> tag per cue wrapping the text, with ms durations and character_ranges as attributes; the fixture now mirrors that shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SSRF guard: adapters declare a yt-dlp extractor allow-list
(allowedExtractors, contract attribute; X = twitter.*) that every probe,
media, and preflight invocation pushes as --use-extractors, so a link
post's delegated foreign URL is refused by yt-dlp WITHOUT any fetch
(verified empirically: refusal line "ERROR: No suitable extractor found
for URL <url>"). The refusal is parsed before classification and the
post resolves as a well-formed 0-media result with the blocked link
recorded; a foreign info JSON on disk is now a hard provenance failure
(defense-in-depth), and a successful spawn that wrote no info JSON fails
instead of laundering into a durable 0-result.
Also: caption cleanup is atomic (tagged originals move aside and are
unlinked only after the cleanup pass succeeds with output; failure fails
the acquisition); status-id/media-index grammar bounded (\d{1,25} /
\d{1,3}) closing the BigInt sink and slice-key length path; /video/0
survives canonicalization; login-required patterns anchored to
[twitter]-tagged ERROR lines so attacker-influenced URLs on stderr can
never gate cookie retries; preflight fails closed on a null URL claim
and markdown-escapes stderr-derived note/reason cells.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Structural media-pass gate (foreign-free in the condition, not only the earlier return); AcquireContext.workDir documented as empty-and-exclusive per acquisition; blocked-delegation harvest links URL-parsed with an http/https scheme allowlist; the self-referential retryable pattern removed (the table describes source stderr signatures only); counts- missing degradation judged only on post-level payloads (playlist or metadata-only info), never a media-entry stand-in, with an attempt-cap note; deterministic pinned-index failures (Media #n is not a video, Video #n is unavailable) mapped fatal; metadata-only info collected as an array; inspection re-derived after the media pass so degradation, ordering, and entry paths never read stale state; srt-to-vtt conversion scoped to the post's own media ids. New mediaOptional capability (contract + validator): 0-media posts are well-formed for every yt-dlp consumer — acquisition AND queue preflight now push --ignore-no-formats-error from the adapter declaration, so a valid 0-video X post enqueues as metadata-only instead of rejecting (T6 D-A text-only digest). Quote-tweet and retweet aliasing get distinct fixtures completing the four named identity cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 2 landed across db73ef0, 7b88443, 6f2ef44, then hardened in 8b0ec7b and 898a288 after two independent security reviews and a fresh-context acceptance pass (11/11). DEVIATIONS.md records the two review-driven contract additions (required allowedExtractors closing the delegation SSRF at every spawn; closed-by-default mediaOptional aligning queue and watch on 0-video posts), the added deterministic fatal patterns, and the deferred countsMissing retry-cap trigger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ase 3) The shared caption ladder now consumes the adapter's declared captionClass (platform-asr: EN -> und, every rung auto-class), fixing the X .en.vtt -> manual-en misclassification that routed X platform ASR through the manual cleaner. transcriptStrategy resolves per entry from the adapter default plus an explicit --transcript-strategy override: captions unchanged (YouTube default), captions+repair layers deterministic proper-noun repair over the platform VTT (lexicon = post text + harvested links), and caption-absent entries take the optional faster-whisper asr rung when the runtime-detected toolchain is present — never auto-installed — else degrade explicitly via the named transcriptDegradation provenance field (watch.json + CLI output), never silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ract Settles 8 of the 12 draft-stage RECONCILE markers whose facts landed with phases 1-3: slice key is the URL status id alone (the id pair rides as metadata), the two 0-video cases split (a link post's own text is unrecoverable after an unfetched allow-list refusal), the provenance guard's two mechanisms documented with their opposite outcomes, the fatal table grown to five patterns, mediaOptional and the twitter.* allow-list added to the capability table, YouTube's caption class and /v/<id> + subdomain acceptance corrected, and shared-vs-adapter failure attribution made explicit. Folds in the phase-2 handoff notes (anonymous-acquisition posture, 0-video queue behavior, run-harvest --url). Four markers remain, retagged to name what they wait on (P3 probes, P5 env names, P5 naming). Records one open question in output-contract.md: source:snowflakeAliasing is written onto the acquisition envelope but persists to no slice artifact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ice metadata The three Phase 3 probes ran against the phase-2 verified X clip with faster-whisper large-v3 (int8, CPU): TIMESTAMPS passes (96.2% word alignment, median start delta 21ms, every platform cue boundary within 421ms), so the asr rung stays default-on for caption-absent entries; LEXICON fails (one Spygate fix against a feds->Fed regression and a prompt-worsened repetition hallucination), so the post-text lexicon stays repair-only and initialPrompt remains unfed; ENTITY records a clear faster-whisper advantage over platform ASR for any future ASR-replace decision. PLAN probe rows carry the dated outcomes. The envelope's source:-prefixed metadata (snowflake aliasing, blocked delegations, counts) now persists into watch.json via a contract-owned subset helper, closing the gap where a flagged aliasing was unreadable after the run; an unflagged run writes no key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 3 landed in 0545158 (strategy seam) and 122924f (T5-ASR probes + source-metadata persistence) and passed fresh-context verification. Probe outcomes: TIMESTAMPS pass ratifies the asr rung default-on for caption-absent entries; LEXICON fail keeps the post-text lexicon repair-only; ENTITY records a faster-whisper advantage for any future ASR-replace decision. The Phase 7 CI pre-check outcome block that rode 122924f's PLAN staging was this session's own pre-flight record, landed early by design. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…en eval (plan phase 4) Extend the shared adapter suite into the full conformance surface (canonicalization idempotence, per-host fixture coverage, harvest well-formedness) with a findHostCollisions check matching the registry's owned-host rule; add a capability-omitting fixture adapter proving declarations skew closed, plus mutation probes showing the suite and the collision test bite. Strengthen runtime validateAdapter for the type lane's checker holes: duplicate hosts, empty-string extractor declarations (which would silently drop the SSRF guard), stateful g/y error patterns, and slug-shaped ids. Add a full-registry round-trip test (canonical example URL per adapter — insurance for adapter three) and close the review-named test debt: cleanup-pass-succeeded-without-srt, preflight null-claim fail-closed, blocked-delegation scheme gate, YouTube argv never carrying --use-extractors / --ignore-no-formats-error, and a CLI-level run-watch fixture proving capability-absent ASR degradation exits 0 with transcriptDegradation recorded. Pin the X golden behavior in evals/fixtures/x-status-goldens.json with an offline vitest gate (result shape, slice-key pair, persisted source:* provenance incl. snowflake aliasing) — picked up by the existing CI test step, no workflow change needed — and a matching model-graded eval case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pace
Rename the extraction lane's user-visible YouTube-specific surfaces for the
source-agnostic pipeline (Phase 5):
- Env namespace: all six YOUTUBE_* knobs become VIDEO_DIGEST_*; a shared
resolveEnvWithLegacy helper keeps every legacy spelling working with a
once-per-process deprecation warning (new name wins when both are set).
The auth-fallback lane resolves through the same helper so legacy-only
cookie config still gates the browser-profile loop.
- Launcher: run.mjs / run-args.js forward the new names and gain
--acquire-phase-gap, closing the sixth-knob gap.
- Temp prefixes: youtube-{frames,sheets,extraction}- -> video-*, and the
staged fallback literal video-sheets-unknown. The acquire lock dir and the
youtube-watch epic dir keep their literals (stable cross-version
coordination/storage identifiers).
- Resume prompts render paths from the resolved slice dir, never the epic
constant, so non-default work roots always yield resumable paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 4 (cfb7de4) and Phase 5 (c9fa5d5) both passed fresh-context verification (10/10 and 8/8). Records the liveness-lane tracker item (#2797), the Phase 5 staged-literal audit's KEEP survivors, the two granted fence extensions (the auth-lane env reads the six-var table missed, proven by failing tests; fixture-literal renames the acceptance grep requires), and a new Phase 7 inventory row for the setup-deps install stamp that no youtube-digest sweep can see. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rkers Phases 3-5 landed, so every deferred marker is answerable. Each resolution is written from the landed code rather than the phase briefs. - Resume argument: `resume <video-slug>` keeps its name through the rename; renaming a user-facing argument is outside approved scope. - ASR prerequisite: faster-whisper is documented as optional and `watch`-only. The rung needs the media file as well as the capability (`asrAvailable && mediaAvailable`), and the `transcript` action never downloads media, so the hub row states the code's rule and cannot contradict the X spoke. - Env namespace: name `VIDEO_DIGEST_WORK_ROOT` concretely and state the `YOUTUBE_`-spelling compatibility read. No variable enumeration — `run-args.js` owns that list. - X strategy: the landed selection table was already correct, so only the T5-ASR-LEXICON delta is added — the lexicon is repair-only and never reaches ASR as an `initial_prompt`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both defects misstated code the previous commit cited. The prerequisites paragraph said the `asr` rung runs only for caption-absent entries. `resolveTranscriptStrategy`'s `requested === "asr"` branch carries no `captionPresent` guard, so an explicit `--transcript-strategy asr` runs ASR with captions present; both entry paths are now stated. Its absence behavior was also too narrow — the seam falls back to a caption strategy when a caption exists and only then completes without a transcript. The X spoke said feeding the lexicon to ASR as an `initial_prompt` made proper-noun accuracy worse. The T5-ASR-LEXICON probe found +1 fix and -1 regression: no net proper-noun gain, with a materially worsened hallucination. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The output contract still called source:snowflakeAliasing envelope-only and unreadable after a run; phase 3 landed sourceMetadataSubset persistence into watch.json, settling the question for watch runs. The transcript action keeps no run-state file, so its CLI output remains the only carrier there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hub split drafted parallel to implementation, reconciled in two passes, and verified fresh-context (9/9, including an 11-claim spoke-vs-code audit with zero contradictions). Body lands at 13,328 chars against the 20,000-char budget with all routing conditional and the widened description inside the frontmatter cap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…igest Structural move only (git mv, no content changes). The skill now digests X posts at parity with YouTube; the name stops claiming YouTube-only scope. Reference sweeps follow in separate commits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o-digest Self-references become /knowledge:video-digest; run.mjs launcher lines point at skills/video-digest/; the user-facing resume prompt and the bootstrap recovery command emit the new paths, each pinned by a fixture test. The description keeps the '/youtube-digest' trigger phrase as the migration alias per the Phase 6 trigger-token baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plugin manifest: four userConfig titles now say (video-digest), keywords gain x/twitter, the description drops "YouTube pipeline", and the version bumps 0.12.0 -> 0.13.0 (pre-1.0 minor-as-breaking). CHANGELOG gains the breaking entry naming the five old-name consumer surfaces (deny permission rules fail open) plus the VIDEO_DIGEST_* env deprecations. CI job id youtube-extraction renames to video-extraction (not a required status check per the recorded ruleset pre-check). The extraction npm package renames @melodic/youtube-extraction -> @melodic/video-extraction with a regenerated lockfile, and the setup-deps install stamp becomes .video-extraction.stamp (one harmless dependency reinstall). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gest Exactly the rename exemption's enumerated cross-references: routing lines in SKILL.md, storage-schema.md, discovery-checklist.md, the setup-deps comment, and the renamed eval case youtube-url-routes-to-video-digest-skill. No behavioral or structural change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The three known-miss /youtube consumer refs, the TUNING.md path to variation-matrix-backlog.json, and two youtube-extraction/ shorthand headings that ride the npm package rename. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rename git-grep gate over tracked and untracked files with an explicit inline allowlist (CHANGELOG, the '/youtube-digest' trigger phrase, the sweep itself, and the fixture negative assertions); the self-test seeds a stale ref under vendor/ and asserts the sweep catches it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The terminal rename landed across six commits (structural git mv isolated from the sweeps, Tidy First) and passed fresh-context verification 13/13. Records the pre-flight baseline (33 files / 114 occurrences), the sweep-allowlist growth to four classes (the trigger-token migration alias made the PLAN's two-row allowlist unsatisfiable), and the in-spirit sweep additions. Also corrects the two yt-dlp cookie userConfig descriptions that still described YouTube-only cookie semantics, inaccurate now that the X adapter is cookies-file-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two decisions from the source-agnostic video-digest effort pass the ADR admission test (hard to reverse, surprising without context, real trade-off): dispatching sources through a static adapter registry rather than a computed dynamic import, and keeping storage-format identifiers (the youtube-watch epic dir, the acquisition lock dir) stable across the skill rename. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Close-out per the topic-docs lifecycle: the branch-lived contract slice (PLAN.md, DEVIATIONS.md, design/) is published in the PR description and its durable outcomes graduated to docs/adr/0010 and 0011; follow-ups live in tracker issues #2797 and #2818. The git history of this branch retains every revision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Composes both sides' intent across the youtube-digest -> video-digest rename: - Version supremacy: plugin.json stays 0.13.0 (> main's 0.12.6) with this branch's widened description; CHANGELOG keeps main's 0.12.1-0.12.6 entries verbatim below the 0.13.0 entry, which now notes the 0.12.6 <slice-slug> resume argument carries forward under /knowledge:video-digest. - Resume-arg port (main #2821): the <slice-slug> rename is ported into the renamed hub's SKILL.md (description, argument-hint, action router, resume block, handoff message), run-resume.js usage strings, and the resume eval expectation - main's breaking change is preserved, not reverted. - Liveness relocation (main #2817): extraction/liveness/ and the #2703 scripts/run-tests.sh facade are git mv'd into video-digest/ with every path reference updated (LIVENESS.md, probes.json, the source-liveness workflow, ci.yml facade calls); the harness's VIDEO_DIGEST_-first cookie env resolution and its legacy YOUTUBE_ fallback are kept as designed. - ADR renumber: main owns 0010/0011 by merge order; this branch's ADRs move to 0012/0013 (dispatch-video-sources, keep-storage-format). - check-rename-sweep allowlists the two rename ADRs (historical-record class, same rationale as the CHANGELOG); main-side stale skill refs in knowledge/reference and docs-hygiene are updated to video-digest. Gates: tsc clean, vitest 486/486 (71 files, +19 liveness), rename sweep + self-test green, check-skill PASS (video-digest, course-digest), markdownlint clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Claude finished @kyle-sexton's task in 2m 41s —— View job Claude is reviewing this PR
|
|
Claude finished @kyle-sexton's task in 2m 36s —— View job Security Review in progress
|
|
Claude has reviewed this PR 4 times. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
|
Last security-reviewed head: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 207a6ba201
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The topic-docs convention keeps .work/ as the never-committed memory tier, but the slice was committed before the self-ignore guard healed .work/.gitignore. Untracking it also clears the markdownlint and editorconfig hygiene findings, all of which sat in these files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eo-digest
Respell "unparseable" to typos' preferred "unparsable", mark the
osaDistance transposition datum with the blessed spellchecker
directive, drop the upstream tracker reference from the x.js header
per comment hygiene, and state the video-id class explicitly in the
youtube reference prose (`[A-Za-z0-9_-]{11}`) so the shell-portability
lint stops reading `\w` as a GNU-only construct; the JS pattern is
unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
shellcheck SC2292 (style) flags POSIX [ ] tests in bash scripts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
scripts/sync-plugin-options-docs.py regeneration after the branch's userConfig cookie-description edits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Changelog-parity bump for the derivability status board's templates/sources.md row now pointing at the renamed video-digest skill path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three graded cases covering the read-only check contract, the guidance-only library_dir apply path, and install-deps verification by re-probe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 20s —— View job Claude is reviewing this PR
|
|
Claude finished @kyle-sexton's task in 3m 3s —— View job Security Review
|
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 24s —— View job Claude is running
|
…cookie capability The env-configured browser-cookie profile was pushed into EVERY source's yt-dlp argv, bypassing the capability that gates the automatic fallback loop — an X (cookies-file-only) acquisition or preflight could carry --cookies-from-browser. resolveYtDlpAuthArgs now takes the same allowBrowserCookieProfileFallback declaration (closed by default) on both argv paths; a cookies FILE stays allowed for every source. Conformance tests pin X argv browser-cookie-free across all acquisition modes and preflight, with YouTube unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ting backups On a multi-video X post the caption-cleanup gate was satisfied by ONE converted .srt, then the cleanup deleted EVERY .tagged-original backup — silently losing captions for entries whose conversion produced nothing. The gate now requires a converted counterpart for every tagged VTT; a partial conversion fails the acquisition (fail-loud, matching the existing cleanup-failure path) with all backups intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 49s —— View job Claude is running
|
|
Claude finished @kyle-sexton's task in 1m 54s —— View job Security Review
FindingsNone outstanding. Both issues raised in the prior security-review round on this PR are now fixed, verified correct, and covered by targeted regression tests. 1. Fixed — X's cookies-file-only auth policy is now honored on every argv path Commit
X declares 2. Fixed — partial caption-cleanup no longer deletes backups for unconverted entries Commit No regressions introduced by either fix: the override path used by the auth-fallback loop is now also gated on the same capability, but since the loop itself only invokes with an override when the capability is already true (spawn-yt-dlp-with-auth-fallback.js#L69), this is defense-in-depth, not a behavior change for YouTube. SummaryBoth findings from the prior security-review pass on this PR (X's |
|
Both review findings verified and fixed:
Suite 494/494, tsc clean. |

BREAKING:
knowledge0.13.0 — source-agnostic video digest;youtube-digest→video-digestThe single-video digest skill now handles X (Twitter) video posts at full watch parity with
YouTube through an engine-layer source-adapter contract, and renames to
/knowledge:video-digest.course-digestreceives no behavioral change. No on-disk consumer data migrates: theyoutube-watchepic queue directory and the acquisition lock directory are stablestorage-format identifiers (ADR 0011).
What landed
extraction/adapters/): five required methods, a0..N acquisition envelope, a four-type error taxonomy, per-adapter yt-dlp extractor
allow-lists (closes a delegation SSRF found in security review), path-safe slice keys, and a
shared conformance suite + X golden eval insuring adapter three (ADR 0010).
detection persisted to slice metadata, provenance guard (refused-before-fetch delegations),
429 silent-degradation detector, 0-video posts digest text-only end-to-end.
captionsfor YouTube unchanged;captions+repairfor X), an optional runtime-detected faster-whisperasrrung (neverauto-installed) ratified by three recorded T5-ASR probes, and a named
transcriptDegradationprovenance field — degradation is never silent.
YOUTUBE_*vars →VIDEO_DIGEST_*with a warn-once compatibilityread; new
--acquire-phase-gaplauncher flag.spoke routing and a description widened to x.com/twitter.com triggers.
git mvisolated from sweeps; a checked-in, self-testedregression sweep; CHANGELOG entry naming the five silently-failing consumer surfaces;
plugin 0.12.0 → 0.13.0.
Verification
Every phase passed a fresh-context verifier against its acceptance criteria; Phases 1–2 also
passed independent security review (the CRITICAL SSRF finding was fixed at the contract seam
and re-verified) and Phase 1 an architecture review. Final outcome verification (fresh
context, whole-branch diff): CONFIRMED — all 5 success criteria, all scope boundaries.
Suite: 70 files / 467 tests green,
tsc --noEmitclean in both extraction lanes withcheckJs: true, zero suppressions,check-skillPASS onvideo-digestandcourse-digest.Live one-time evidence: anonymous X acquisition end-to-end (media + platform VTT + word-tag
cleanup) and YouTube slice-layout parity, recorded in the machine-local memory slice.
Breaking-change migration (the five silent surfaces)
/knowledge:youtube-digest— edit the routine prompt to/knowledge:video-digest(silent no-match otherwise)./loopprompts — update stored prompts naming the old skill.Skill(knowledge:youtube-digest)is exact-match; an allow rulesilently stops matching and a deny rule fails open (audit denies first). Rewrite to
Skill(knowledge:video-digest).skills:allowlists — rename the entry (loud failure: skill absent)./youtube-digestreferences — the freed bare name is squattable; sweep docs andsaved prompts. Conversational routing keeps working: the description retains
'/youtube-digest'as a migration alias.Also:
YOUTUBE_*env spellings are deprecated (compat read warns once per process; new namewins); first run after upgrade reinstalls extraction deps once (stamp renamed).
Close-out pointers
docs/adr/0012-dispatch-video-sources-through-a-static-adapter-registry.md,docs/adr/0013-keep-storage-format-identifiers-stable-across-renames.md(renumbered aftermain's merge-order claim on 0010/0011)
mid-flight: knowledge: scheduled liveness lane for video-digest source adapters #2797 → main 0.12.5 (liveness lane, old paths) and knowledge/video-digest: rename the user-facing 'resume <video-slug>' argument to a source-neutral name #2818 → main 0.12.6
(BREAKING
resume <slice-slug>). This branch merges main and carries both forward acrossthe rename: the liveness tree is relocated into
video-digest/with all referencesupdated, and the
<slice-slug>argument is ported into the rewritten hub — main'sbreaking change is preserved, not reverted.
docs/topics/source-agnostic-video-digest/is pruned beforemerge; full revision history on this branch. The approved PLAN and the deviation ledger are
pasted below.
.work/source-agnostic-video-digest/research tree (~13.9k lines) against the repo'smemory-tier convention (ADR 0004:
.work/never leaves its checkout). It is untracked viagit rm --cachedin7eb5833a— content preserved on disk and in this branch's history.No linked issue.
Related
Approved PLAN.md (final revision, pre-prune)
DEVIATIONS.md — the autonomous-run deviation ledger (final revision, pre-prune)
🤖 Generated with Claude Code