Skip to content

feat: new yt channel and instagram ingest#22

Merged
pedronauck merged 4 commits into
mainfrom
ytchan
Jun 27, 2026
Merged

feat: new yt channel and instagram ingest#22
pedronauck merged 4 commits into
mainfrom
ytchan

Conversation

@pedronauck

@pedronauck pedronauck commented Jun 27, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added ingest instagram support and YouTube channel bulk ingest.
    • Added caption-language selection (--sub-langs/--lang) plus translated-caption controls for media ingestion.
  • Bug Fixes
    • Improved transcript/caption fallback behavior and better handling of already-ingested items during bulk ingest.
    • Enhanced bulk extraction robustness with throttling and retry behavior for transient failures.
  • Documentation
    • Updated README/config examples and ingest configuration to reflect Instagram, caption-language, and translated-caption options.

@pedronauck pedronauck self-assigned this Jun 27, 2026
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ba4cef26-8d9e-45aa-991e-2d5bf3588d3f

📥 Commits

Reviewing files that changed from the base of the PR and between 7fdf630 and d6af236.

⛔ Files ignored due to path filters (27)
  • .agents/skills/kb-yt-channel/SKILL.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/kb-yt-channel/assets/report-template.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/kb-yt-channel/references/troubleshooting.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/kb-yt-channel/scripts/ingest-channel.py is excluded by !.agents/**
  • .claude/ledger/2026-06-26-MEMORY-yt-channel-skill.md is excluded by !**/*.md, !.claude/**
  • .compozy/tasks/pr-22/reviews-001/issue_001.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_002.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_003.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_004.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_005.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_006.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_007.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_008.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_009.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_010.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_011.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_012.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_013.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_014.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_015.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_016.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_017.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_018.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_019.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_020.md is excluded by !**/*.md
  • .compozy/tasks/pr-22/reviews-001/issue_021.md is excluded by !**/*.md
  • skills-lock.json is excluded by !**/*.json
📒 Files selected for processing (17)
  • internal/adapter/java_adapter_test.go
  • internal/cli/ingest_channel.go
  • internal/cli/ingest_instagram_test.go
  • internal/cli/ingest_test.go
  • internal/cli/inspect_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/ingest/ingest_test.go
  • internal/ingest/youtube.go
  • internal/instagram/instagram.go
  • internal/instagram/instagram_test.go
  • internal/logger/logger_test.go
  • internal/mediadl/mediadl_test.go
  • internal/mediadl/transcription.go
  • internal/mediadl/ytdlp.go
  • internal/youtube/channel.go
  • internal/youtube/channel_test.go

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


Walkthrough

The PR adds shared media extraction, YouTube channel bulk ingest, and Instagram ingest flows, expands config and source-kind handling, and updates frontmatter, parser helpers, adapters, and tooling across the repository.

Changes

Media Ingestion and Extraction

Layer / File(s) Summary
Config and ingest wiring
internal/config/*, internal/models/kb_models.go, internal/models/kb_models_test.go, internal/ingest/ingest.go, internal/ingest/ingest_test.go
Config adds YouTube caption and bulk fields plus Instagram defaults; SourceKindInstagramVideo and ingest-path handling are added; ingest tests cover the new source kind and YouTube video-ID scan.
Shared media extractor core
internal/mediadl/*
mediadl defines shared extraction models, transcription policy parsing, yt-dlp backend config, playlist handling, STT fallback, and related tests.
YouTube adapter, channel bulk ingest, and CLI wiring
internal/youtube/*, internal/ingest/youtube.go, internal/cli/ingest.go, internal/cli/ingest_youtube.go, internal/cli/ingest_test.go
The YouTube adapter delegates to mediadl; channel URL normalization and bulk extraction are added; CLI ingest adds caption-language parsing, resume filtering, and video_id frontmatter.
Instagram extractor and CLI ingest
internal/instagram/*, internal/cli/ingest_instagram*.go
Instagram extraction composes caption and transcript output, and the new CLI command and tests ingest Instagram URLs into vault documents.

Frontmatter and Utility Modernization

Layer / File(s) Summary
Frontmatter and rendered document maps
internal/models/models.go, internal/vault/reader.go, internal/vault/render*.go, internal/frontmatter/frontmatter_test.go, internal/generate/generate_test.go, internal/logger/logger_test.go, internal/cli/inspect_test.go, internal/vault/writer_test.go
Vault, generation, and logging fixtures switch to map[string]any, and frontmatter parsing and merging use strings.Cut and maps.Copy.
Parser and collection helpers
internal/convert/csv.go, internal/convert/image_common.go, internal/convert/pdf.go, internal/convert/registry.go, internal/generate/benchmark_policy.go, internal/metrics/compute.go, internal/metrics/compute_test.go, internal/repohealth/portable_paths.go, internal/topic/topic.go, internal/lint/lint.go, internal/vault/writer.go, internal/vault/writer_test.go
Conversion, metrics, repohealth, topic, lint, benchmark, and writer helpers switch to newer slice and string iteration patterns.
Table-driven test cleanup and helper updates
internal/convert/*_test.go, internal/generate/benchmark_policy_test.go, internal/cli/inspect_helpers_test.go, internal/cli/inspect_test.go, internal/cli/lint_test.go, internal/cli/workflow_test_helpers_test.go, internal/firecrawl/client_test.go, internal/output/formatter_test.go, internal/repohealth/portable_paths_test.go, internal/scanner/scanner_test.go, internal/topic/topic_test.go, internal/vault/pathutils_test.go, internal/vault/textutils_test.go, internal/generate/generate_test.go, internal/logger/logger_test.go
Table-driven tests drop per-iteration shadow copies and update helper assertions and fixtures.

Adapter Parser Cleanup

Layer / File(s) Summary
Go, Java, Rust, and TS adapter cleanup
internal/adapter/go_adapter.go, internal/adapter/java_adapter.go, internal/adapter/java_adapter_test.go, internal/adapter/rust_adapter.go, internal/adapter/treesitter_test.go, internal/adapter/ts_adapter.go
Adapter parsing helpers remove redundant rebindings; Java helpers use strings.Cut and SplitSeq, and adapter tests change deterministic loop generation.

Automation and Review Artifacts

Layer / File(s) Summary
Peer-review status artifacts
.peer-reviews/20260627T012748Z/impl-review-changed-files-round1.txt, .peer-reviews/20260627T012748Z/impl-review-status-before-round1.txt, .peer-reviews/20260627T012748Z/impl-review-status-after-round1.txt
The peer-review status files list the changed paths and status summaries for the PR.
Pinned tooling and release checks
magefile.go, test/release_config_test.go
Mage task execution pins lint and test tools, and the release config test drops a shadow copy.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • compozy/kb#13: Java adapter parsing and resolution changes in internal/adapter/java_adapter.go are close to the adapter cleanup in this PR.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding YouTube channel and Instagram ingest support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ytchan

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
internal/adapter/java_adapter_test.go (1)

1071-1101: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Wrap these cases in t.Run("Should...") subtests.

These functions still execute their assertions directly at the top level. Please wrap each scenario in a named subtest to match the repository's required test shape. As per path instructions, **/*_test.go: MUST use t.Run("Should...") pattern for ALL test cases.

Also applies to: 1103-1129

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/adapter/java_adapter_test.go` around lines 1071 - 1101, The test
cases in
createJavaResolutionFallbackDiagnosticTruncatesHighVolumeDetailsDeterministically
(and the other nearby test scenario in java_adapter_test.go) are still running
as top-level assertions instead of named subtests. Wrap each scenario in t.Run
with a “Should...” name, keep the existing assertions inside the subtest, and
preserve the current setup/expectations around
createJavaResolutionFallbackDiagnostic and the truncation checks.

Source: Path instructions

internal/cli/inspect_test.go (1)

661-663: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename these subtests to the required Should... pattern.

The loop structure is fine; only the case names are out of policy here. As per path instructions, **/*_test.go: MUST use t.Run("Should...") pattern for ALL test cases.

✏️ Suggested rename
-		t.Run(subcommand, func(t *testing.T) {
+		t.Run("Should show help for "+subcommand, func(t *testing.T) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/inspect_test.go` around lines 661 - 663, The subtests in
inspect_test.go are using noncompliant names in the t.Run loop over subcommands;
update the subtest naming in the relevant test function to follow the required
Should... pattern for every case. Keep the existing loop structure and only
change the t.Run case name generation so each subtest under the subcommands
slice is named with a Should-prefixed description.

Source: Path instructions

internal/mediadl/transcription.go (1)

123-130: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Forced STT can currently succeed without a transcriber.

The early return on Line 123 makes the new nil-transcriber branch on Lines 129-130 unreachable when result.TranscriptionPolicy == TranscriptionPolicySTT and extractor.stt == nil, so Extract(..., {TranscriptionPolicy: STT}) can return (result, nil) with no transcript at all.

[suggested fix]

Diff
-	if !extractor.shouldAttemptSTT(result.TranscriptionPolicy) {
+	if result.TranscriptionPolicy == TranscriptionPolicyCaptions {
 		return result, transcriptErr
 	}
 	if extractor.ytDLP == nil {
 		return result, errors.Join(transcriptErr, errors.New("media stt: yt-dlp backend is nil"))
 	}
 	if extractor.stt == nil {
+		if result.TranscriptionPolicy == TranscriptionPolicyAuto {
+			return result, transcriptErr
+		}
 		return result, errors.Join(transcriptErr, errors.New("media stt: transcriber is nil"))
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/mediadl/transcription.go` around lines 123 - 130, The early return
in Extract prevents the nil-transcriber check from running for forced STT, so a
missing transcriber can still return success with no transcript. Update the
control flow around shouldAttemptSTT in TranscriptionExtractor.Extract so
TranscriptionPolicySTT does not exit before validating extractor.stt. Make the
nil-stt branch reachable for forced STT and ensure it returns an error (joined
with transcriptErr) instead of a nil error when transcription is requested but
unavailable.
internal/logger/logger_test.go (1)

31-47: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the configured level instead of only successful construction.

wantLevel is never checked, so every non-error row passes even if New ignores tc.level and always returns the same logger. Emitting a record per case and asserting the "level" field would make this table cover the behavior it names; while touching it, please also switch the subtest names to the required Should... form. As per path instructions, MUST test meaningful business logic, not trivial operations, Ensure tests verify behavior outcomes, not just function calls, and MUST use t.Run("Should...") pattern for ALL test cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/logger/logger_test.go` around lines 31 - 47, The logger table test
only verifies that New constructs a logger, so it never checks the configured
level or catches regressions where tc.level is ignored. Update the
logger_test.go cases around New to emit a log record for each tc.level and
assert the resulting “level” field matches tc.wantLevel, using the existing
tc.name/level table to validate behavior outcomes. Also rename each t.Run
subtest to the required Should... form while keeping the same table-driven
coverage.

Source: Path instructions

🧹 Nitpick comments (8)
internal/ingest/ingest_test.go (1)

745-792: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a small table-driven Should... matrix here.

These cases already share the same setup shape, so a table would remove duplication and align the test with the repo's required subtest pattern. As per coding guidelines, **/*_test.go: Default to table-driven tests with focused helpers and t.TempDir() for filesystem isolation. As per path instructions, **/*_test.go: MUST use t.Run("Should...") pattern for ALL test cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/ingest/ingest_test.go` around lines 745 - 792, The
ExistingYouTubeVideoIDs test uses separate ad hoc subtests instead of the
required table-driven Should... pattern. Refactor TestExistingYouTubeVideoIDs to
use a small table of cases with shared setup/helpers, keep filesystem isolation
via t.TempDir(), and rename each t.Run to the required "Should..." format so the
two scenarios remain focused but follow the repo test convention.

Sources: Coding guidelines, Path instructions

internal/cli/ingest_instagram_test.go (1)

29-234: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align these CLI tests with the repo test harness conventions.

These scenarios are still one-off top-level tests, and both command invocations hard-code /tmp/vault. Please move them behind t.Run("Should...") tables/helpers and feed a per-test vault from t.TempDir() so later filesystem behavior changes do not couple the cases together.

As per coding guidelines, **/*_test.go: Default to table-driven tests with focused helpers and t.TempDir() for filesystem isolation; as per path instructions, **/*_test.go: MUST use t.Run("Should...") pattern for ALL test cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/ingest_instagram_test.go` around lines 29 - 234, The Instagram
CLI tests are still standalone top-level cases and they hard-code a shared vault
path, which breaks the repo’s test-harness conventions. Refactor
TestIngestInstagramCommandComposesCaptionAndIngests and
TestIngestInstagramCommandHonorsTranscribeFlag into t.Run("Should...") subtests
(ideally table-driven with small shared helpers), and replace the fixed
"/tmp/vault" argument with a per-test directory from t.TempDir() so each case is
isolated. Keep the behavior assertions the same, but route command setup/execute
through reusable helpers to match the existing *_test.go pattern.

Sources: Coding guidelines, Path instructions

internal/mediadl/mediadl_test.go (1)

13-174: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restructure these helper tests into table-driven subtests.

This file is mostly case matrices (ParseTranscriptionPolicy, language matching, status classification), but every scenario is still a standalone top-level test, and the invalid-policy path only checks err != nil. Please collapse these into tables behind t.Run("Should...") and assert the expected error text for negative cases so failures stay behavior-specific.

As per coding guidelines, **/*_test.go: Default to table-driven tests with focused helpers and t.TempDir() for filesystem isolation; as per path instructions, **/*_test.go: MUST use t.Run("Should...") pattern for ALL test cases and MUST have specific error assertions (ErrorContains, ErrorAs).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/mediadl/mediadl_test.go` around lines 13 - 174, Restructure the
helper coverage in mediadl_test.go into table-driven subtests using
t.Run("Should...") for every case, especially ParseTranscriptionPolicy,
languageMatches/normalizeLanguages, and the status-code helpers. Consolidate the
standalone test bodies into shared tables with focused helper assertions, and
for negative paths like ParseTranscriptionPolicy("maybe") verify the specific
error text instead of only checking err != nil. Keep the existing behavior
checks tied to the relevant symbols such as ParseTranscriptionPolicy,
languageMatches, isStatusCodeNetworkBlocked, and isStatusCodeRateLimited.

Sources: Coding guidelines, Path instructions

internal/cli/ingest_test.go (2)

1199-1201: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Check the specific rejection reason here.

This only asserts that some error came back, so an unrelated failure would still satisfy the test. Please assert the invalid-URL diagnostic from ingest channel as well. As per path instructions, MUST have specific error assertions (ErrorContains, ErrorAs).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/ingest_test.go` around lines 1199 - 1201, The test for `ingest
channel` only checks that `command.ExecuteContext` returns an error, so it can
pass on unrelated failures. Update the assertion in `ingest_test.go` to verify
the specific invalid-URL diagnostic from the `ingest channel` path as well,
using a targeted error assertion such as `ErrorContains` or `ErrorAs` alongside
the existing failure check.

Source: Path instructions


1012-1202: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Wrap these new command scenarios in t.Run("Should...") cases.

The added channel-ingest coverage is all expressed as standalone top-level tests. Converting them into Should... subtests would keep this file aligned with the repo’s required test pattern and make shared CLI setup easier to reuse. As per coding guidelines, **/*_test.go: Default to table-driven tests with focused helpers and t.TempDir() for filesystem isolation; and as per path instructions, MUST use t.Run("Should...") pattern for ALL test cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/ingest_test.go` around lines 1012 - 1202, The new channel ingest
coverage is currently written as standalone top-level tests instead of the
required `t.Run("Should...")` pattern. Wrap
`TestIngestChannelCommandBulkIngestsWithResume`,
`TestIngestChannelCommandDryRunSkipsIngest`, and
`TestIngestChannelCommandRejectsVideoURL` bodies in `t.Run("Should...")`
subtests, keeping the shared CLI setup in each test or extracting helpers if
needed. Use the existing test symbols like `newRootCommand`,
`restoreIngestGlobals`, and the `ingest` command setup to keep the scenarios
aligned with the repo’s test structure.

Sources: Coding guidelines, Path instructions

internal/youtube/channel_test.go (2)

39-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the invalid-URL contract, not just any error.

The wantErr branch only checks err != nil, so this still passes if NormalizeChannelURL starts returning the wrong error kind or message. Please assert *Error/ErrorKindInvalidURL and the expected diagnostic for the rejected input. As per path instructions, MUST have specific error assertions (ErrorContains, ErrorAs).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/youtube/channel_test.go` around lines 39 - 47, The invalid-URL test
branch in NormalizeChannelURL only checks for a non-nil error, so it should be
tightened to assert the specific contract. Update the test in channel_test.go to
use the existing ErrorAs/ErrorContains helpers (or equivalent) to verify the
returned error is a *Error with ErrorKindInvalidURL, and that it includes the
expected diagnostic for the rejected input, rather than accepting any error.

Source: Path instructions


15-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align these scenarios with the repo’s required subtest shape.

Most of the new coverage is expressed as standalone top-level tests, and the table-driven case does not use Should... subtest names. Please fold these scenarios into t.Run("Should...") cases so the file follows the repo’s required test structure consistently. As per coding guidelines, **/*_test.go: Default to table-driven tests with focused helpers and t.TempDir() for filesystem isolation; and as per path instructions, MUST use t.Run("Should...") pattern for ALL test cases.

Also applies to: 59-251

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/youtube/channel_test.go` around lines 15 - 57, The channel URL
normalization coverage needs to follow the repo’s required test naming
structure. Update TestNormalizeChannelURL and any related cases to use t.Run
with subtest names starting with Should..., keeping the table-driven style and
routing each scenario through those subtests. Use the existing
NormalizeChannelURL test helper pattern to keep the cases concise while making
every test case conform to the required subtest shape.

Sources: Coding guidelines, Path instructions

internal/cli/ingest_channel.go (1)

69-159: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Move the bulk-ingest workflow out of the Cobra handler.

This RunE now owns normalization, resume filtering, bulk extraction orchestration, per-video ingest, and summary mutation. That makes the command itself the workflow, which is harder to reuse and test than a package-level service/helper. As per coding guidelines, {cmd/kb/**/*.go,internal/cli/**/*.go}: Keep Cobra commands thin and push behavior into internal packages, particularly in internal/cli.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/ingest_channel.go` around lines 69 - 159, The Cobra RunE handler
is doing the full ingest workflow instead of delegating it, which makes it hard
to reuse and test. Move the normalization, resume filtering, bulk extraction,
per-video ingest, and summary mutation out of the command into a package-level
helper/service in internal/cli, then keep the RunE function as a thin wrapper
that only parses flags, calls the helper, and writes the result. Use the
existing symbols like RunE, newYouTubeChannelExtractor, bulk extraction,
ingestChannelVideo, and newChannelIngestSummary to locate and extract the
workflow.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/config/config_test.go`:
- Around line 325-328: The new empty YouTube caption languages case in Config
validation is too broad because it passes on any error; update the test to
explicitly assert that the failure comes from the youtube.caption_languages
validation branch. In the Config test case around the YouTube caption language
mutation, check the returned error text for youtube.caption_languages so the
test will fail if business logic changes and a different validation path is
triggered.

In `@internal/config/config.go`:
- Around line 220-222: The Validate() flow for YouTube.CaptionLanguages only
trims values to check for emptiness, but leaves the original slice unchanged, so
trimmed languages are not persisted before later use. Update the
validation/normalization logic in config.go so the validated
cfg.YouTube.CaptionLanguages is rewritten with trimmed entries, and make sure
the same normalization is applied in the related YouTube config paths referenced
by Validate() and downstream CLI forwarding so the ingest path receives the
cleaned list.

In `@internal/ingest/youtube.go`:
- Around line 47-50: The frontmatter parsing in the YouTube ingest flow is
silently skipping corrupted documents, which can make already-written videos
look unseen. Update the frontmatter.Parse handling in the ingest logic to
surface the parse failure instead of continuing, using the existing YouTube
ingest path that reads values from the parsed frontmatter. Return or propagate
the error from this branch so resumable ingestion fails loudly on malformed
files rather than reprocessing them.

In `@internal/instagram/instagram_test.go`:
- Around line 74-160: The extractor tests are duplicated standalone cases and
should be consolidated into a table-driven test using the repo’s required
t.Run("Should...") naming pattern. Refactor the scenarios in
TestExtractComposesCaptionAndTranscript, TestExtractTranscriptOnlyWhenNoCaption,
TestExtractCaptionOnlyFallbackWhenTranscriptUnavailable,
TestExtractPropagatesErrorWhenNoCaptionAvailable, and
TestExtractRejectsInvalidURL into one table with focused arrange/act/assert
helpers, keeping the existing Extract and stubCore behavior covered under
separate Should... subtests.
- Around line 148-149: The negative-path checks in instagram_test should assert
the exact error type instead of only checking that an error occurred, so the
tests fail on the wrong error. Update the Extract-based assertions to use
errors.As against the expected mediadl error type and verify the specific
mediadl.ErrorKind value in both failing cases, including the other check at the
additional referenced spot, so the behavior is pinned to the intended
transcript/caption absence path.

In `@internal/instagram/instagram.go`:
- Around line 68-81: Backfill the canonical Instagram URL into Metadata.URL
before returning from the Instagram extraction flow. In the code path around
extractor.core.Extract and the post-processing in
internal/instagram/instagram.go, preserve the normalized URL from
parseInstagramURL and assign it to result.Metadata.URL when yt-dlp does not
provide webpage_url. Make sure this value survives both the successful return
path and the transcript-unavailable fallback so internal/cli/ingest_instagram.go
and internal/ingest/ingest.go always receive a valid canonical source URL.

In `@internal/mediadl/transcription.go`:
- Around line 136-151: The fallback path in transcribeAudioPath currently
preserves transcriptErr when joining audio-download or generic STT failures,
which causes mediadl.IsTranscriptUnavailable to misclassify real
backend/config/network errors. Update the transcript assembly in
transcription.go so only the empty-transcript case carries
ErrorKindTranscriptUnavailable, and ensure audioErr/sttErr failures are returned
without the transcript-unavailable marker; use transcribeAudioPath,
isEmptyTranscriptionError, and the existing ErrorKindTranscriptUnavailable
handling to locate the fix.

In `@internal/mediadl/ytdlp.go`:
- Around line 707-727: The default-caption selection in selectYTDLPCaption is
too dependent on info.Language, so manual subtitles can be missed when yt-dlp
omits language metadata. Update the matching logic in ytDLPCaptionCandidates /
matchingYTDLPCaptionCandidates / ytDLPCaptionLess so a no-preferences request
still accepts usable manual subtitle tracks even when info.Language is empty,
rather than requiring an orig/original classification. Keep the fallback
behavior in selectYTDLPCaption but ensure it can pick a real caption candidate
from subtitles-only payloads like those covered by the ytdlp tests.

In `@internal/youtube/channel.go`:
- Around line 66-72: The host check in NormalizeChannelURL is too permissive
because strings.Contains(host, "youtube.com") accepts lookalike domains. Tighten
the validation in NormalizeChannelURL by checking the parsed host against actual
YouTube hosts only, using host-specific matching for youtube.com and its
legitimate subdomains (and rejecting unrelated domains such as notyoutube.com or
youtube.com.evil.test). Keep the existing ErrorKindInvalidURL path and message
behavior for rejected URLs.

---

Outside diff comments:
In `@internal/adapter/java_adapter_test.go`:
- Around line 1071-1101: The test cases in
createJavaResolutionFallbackDiagnosticTruncatesHighVolumeDetailsDeterministically
(and the other nearby test scenario in java_adapter_test.go) are still running
as top-level assertions instead of named subtests. Wrap each scenario in t.Run
with a “Should...” name, keep the existing assertions inside the subtest, and
preserve the current setup/expectations around
createJavaResolutionFallbackDiagnostic and the truncation checks.

In `@internal/cli/inspect_test.go`:
- Around line 661-663: The subtests in inspect_test.go are using noncompliant
names in the t.Run loop over subcommands; update the subtest naming in the
relevant test function to follow the required Should... pattern for every case.
Keep the existing loop structure and only change the t.Run case name generation
so each subtest under the subcommands slice is named with a Should-prefixed
description.

In `@internal/logger/logger_test.go`:
- Around line 31-47: The logger table test only verifies that New constructs a
logger, so it never checks the configured level or catches regressions where
tc.level is ignored. Update the logger_test.go cases around New to emit a log
record for each tc.level and assert the resulting “level” field matches
tc.wantLevel, using the existing tc.name/level table to validate behavior
outcomes. Also rename each t.Run subtest to the required Should... form while
keeping the same table-driven coverage.

In `@internal/mediadl/transcription.go`:
- Around line 123-130: The early return in Extract prevents the nil-transcriber
check from running for forced STT, so a missing transcriber can still return
success with no transcript. Update the control flow around shouldAttemptSTT in
TranscriptionExtractor.Extract so TranscriptionPolicySTT does not exit before
validating extractor.stt. Make the nil-stt branch reachable for forced STT and
ensure it returns an error (joined with transcriptErr) instead of a nil error
when transcription is requested but unavailable.

---

Nitpick comments:
In `@internal/cli/ingest_channel.go`:
- Around line 69-159: The Cobra RunE handler is doing the full ingest workflow
instead of delegating it, which makes it hard to reuse and test. Move the
normalization, resume filtering, bulk extraction, per-video ingest, and summary
mutation out of the command into a package-level helper/service in internal/cli,
then keep the RunE function as a thin wrapper that only parses flags, calls the
helper, and writes the result. Use the existing symbols like RunE,
newYouTubeChannelExtractor, bulk extraction, ingestChannelVideo, and
newChannelIngestSummary to locate and extract the workflow.

In `@internal/cli/ingest_instagram_test.go`:
- Around line 29-234: The Instagram CLI tests are still standalone top-level
cases and they hard-code a shared vault path, which breaks the repo’s
test-harness conventions. Refactor
TestIngestInstagramCommandComposesCaptionAndIngests and
TestIngestInstagramCommandHonorsTranscribeFlag into t.Run("Should...") subtests
(ideally table-driven with small shared helpers), and replace the fixed
"/tmp/vault" argument with a per-test directory from t.TempDir() so each case is
isolated. Keep the behavior assertions the same, but route command setup/execute
through reusable helpers to match the existing *_test.go pattern.

In `@internal/cli/ingest_test.go`:
- Around line 1199-1201: The test for `ingest channel` only checks that
`command.ExecuteContext` returns an error, so it can pass on unrelated failures.
Update the assertion in `ingest_test.go` to verify the specific invalid-URL
diagnostic from the `ingest channel` path as well, using a targeted error
assertion such as `ErrorContains` or `ErrorAs` alongside the existing failure
check.
- Around line 1012-1202: The new channel ingest coverage is currently written as
standalone top-level tests instead of the required `t.Run("Should...")` pattern.
Wrap `TestIngestChannelCommandBulkIngestsWithResume`,
`TestIngestChannelCommandDryRunSkipsIngest`, and
`TestIngestChannelCommandRejectsVideoURL` bodies in `t.Run("Should...")`
subtests, keeping the shared CLI setup in each test or extracting helpers if
needed. Use the existing test symbols like `newRootCommand`,
`restoreIngestGlobals`, and the `ingest` command setup to keep the scenarios
aligned with the repo’s test structure.

In `@internal/ingest/ingest_test.go`:
- Around line 745-792: The ExistingYouTubeVideoIDs test uses separate ad hoc
subtests instead of the required table-driven Should... pattern. Refactor
TestExistingYouTubeVideoIDs to use a small table of cases with shared
setup/helpers, keep filesystem isolation via t.TempDir(), and rename each t.Run
to the required "Should..." format so the two scenarios remain focused but
follow the repo test convention.

In `@internal/mediadl/mediadl_test.go`:
- Around line 13-174: Restructure the helper coverage in mediadl_test.go into
table-driven subtests using t.Run("Should...") for every case, especially
ParseTranscriptionPolicy, languageMatches/normalizeLanguages, and the
status-code helpers. Consolidate the standalone test bodies into shared tables
with focused helper assertions, and for negative paths like
ParseTranscriptionPolicy("maybe") verify the specific error text instead of only
checking err != nil. Keep the existing behavior checks tied to the relevant
symbols such as ParseTranscriptionPolicy, languageMatches,
isStatusCodeNetworkBlocked, and isStatusCodeRateLimited.

In `@internal/youtube/channel_test.go`:
- Around line 39-47: The invalid-URL test branch in NormalizeChannelURL only
checks for a non-nil error, so it should be tightened to assert the specific
contract. Update the test in channel_test.go to use the existing
ErrorAs/ErrorContains helpers (or equivalent) to verify the returned error is a
*Error with ErrorKindInvalidURL, and that it includes the expected diagnostic
for the rejected input, rather than accepting any error.
- Around line 15-57: The channel URL normalization coverage needs to follow the
repo’s required test naming structure. Update TestNormalizeChannelURL and any
related cases to use t.Run with subtest names starting with Should..., keeping
the table-driven style and routing each scenario through those subtests. Use the
existing NormalizeChannelURL test helper pattern to keep the cases concise while
making every test case conform to the required subtest shape.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8a511e6e-2c45-4241-8254-bf54727bcfa7

📥 Commits

Reviewing files that changed from the base of the PR and between f8c37fd and 7fdf630.

⛔ Files ignored due to path filters (29)
  • .agents/skills/grilling/SKILL.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/handoff/SKILL.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/impl-peer-review/SKILL.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/impl-peer-review/references/impl-review-prompt.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/impl-peer-review/references/readiness-checks.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/impl-peer-review/scripts/validate-findings.sh is excluded by !.agents/**
  • .agents/skills/kb-yt-channel/SKILL.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/kb-yt-channel/assets/report-template.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/kb-yt-channel/references/channel-topic-contract.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/kb-yt-channel/references/troubleshooting.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/kb-yt-channel/scripts/ingest-channel.py is excluded by !.agents/**
  • .agents/skills/spec-peer-review/SKILL.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/spec-peer-review/references/peer-review-prompt.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/spec-peer-review/references/quality-markers.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/spec-peer-review/scripts/validate-findings.sh is excluded by !.agents/**
  • .claude/ledger/2026-06-26-MEMORY-instagram-ingest.md is excluded by !**/*.md, !.claude/**
  • .claude/plans/2026-06-26-kb-ingest-channel.md is excluded by !**/*.md, !.claude/**
  • .codex/plans/20260626221122-youtube-caption-language.md is excluded by !**/*.md
  • .peer-reviews/20260627T012748Z/impl-review-findings-round1.md is excluded by !**/*.md
  • .peer-reviews/20260627T012748Z/impl-review-prompt-round1.md is excluded by !**/*.md
  • .peer-reviews/20260627T012748Z/impl-review-summary-round1.md is excluded by !**/*.md
  • CLAUDE.md is excluded by !**/*.md
  • README.md is excluded by !**/*.md
  • config.example.toml is excluded by !**/*.toml
  • skills-lock.json is excluded by !**/*.json
  • skills/kb/SKILL.md is excluded by !**/*.md
  • skills/kb/references/architecture.md is excluded by !**/*.md
  • skills/kb/references/error-handling.md is excluded by !**/*.md
  • skills/kb/references/frontmatter-schemas.md is excluded by !**/*.md
📒 Files selected for processing (86)
  • .peer-reviews/20260627T012748Z/impl-review-changed-files-round1.txt
  • .peer-reviews/20260627T012748Z/impl-review-diff-round1.patch
  • .peer-reviews/20260627T012748Z/impl-review-events-round1.jsonl
  • .peer-reviews/20260627T012748Z/impl-review-result-round1.err
  • .peer-reviews/20260627T012748Z/impl-review-status-after-round1.txt
  • .peer-reviews/20260627T012748Z/impl-review-status-before-round1.txt
  • internal/adapter/go_adapter.go
  • internal/adapter/java_adapter.go
  • internal/adapter/java_adapter_test.go
  • internal/adapter/rust_adapter.go
  • internal/adapter/treesitter_test.go
  • internal/adapter/ts_adapter.go
  • internal/cli/ingest.go
  • internal/cli/ingest_channel.go
  • internal/cli/ingest_instagram.go
  • internal/cli/ingest_instagram_integration_test.go
  • internal/cli/ingest_instagram_test.go
  • internal/cli/ingest_test.go
  • internal/cli/ingest_youtube.go
  • internal/cli/inspect_helpers_test.go
  • internal/cli/inspect_test.go
  • internal/cli/lint_test.go
  • internal/cli/workflow_test_helpers_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/env.go
  • internal/convert/csv.go
  • internal/convert/docx_test.go
  • internal/convert/epub_test.go
  • internal/convert/html_test.go
  • internal/convert/image_common.go
  • internal/convert/image_test.go
  • internal/convert/pdf.go
  • internal/convert/pdf_test.go
  • internal/convert/pptx_test.go
  • internal/convert/registry.go
  • internal/convert/registry_test.go
  • internal/convert/xlsx_test.go
  • internal/firecrawl/client_test.go
  • internal/frontmatter/frontmatter_test.go
  • internal/generate/benchmark_policy.go
  • internal/generate/benchmark_policy_test.go
  • internal/generate/generate_test.go
  • internal/ingest/ingest.go
  • internal/ingest/ingest_test.go
  • internal/ingest/youtube.go
  • internal/instagram/instagram.go
  • internal/instagram/instagram_test.go
  • internal/lint/lint.go
  • internal/logger/logger_test.go
  • internal/mediadl/mediadl.go
  • internal/mediadl/mediadl_test.go
  • internal/mediadl/openai.go
  • internal/mediadl/openai_test.go
  • internal/mediadl/openrouter.go
  • internal/mediadl/openrouter_test.go
  • internal/mediadl/transcription.go
  • internal/mediadl/transcription_test.go
  • internal/mediadl/ytdlp.go
  • internal/mediadl/ytdlp_test.go
  • internal/metrics/compute.go
  • internal/metrics/compute_test.go
  • internal/models/kb_models.go
  • internal/models/kb_models_test.go
  • internal/models/models.go
  • internal/output/formatter_test.go
  • internal/repohealth/portable_paths.go
  • internal/repohealth/portable_paths_test.go
  • internal/scanner/scanner_test.go
  • internal/topic/topic.go
  • internal/topic/topic_test.go
  • internal/vault/pathutils_test.go
  • internal/vault/reader.go
  • internal/vault/render.go
  • internal/vault/render_base.go
  • internal/vault/render_test.go
  • internal/vault/render_wiki.go
  • internal/vault/textutils_test.go
  • internal/vault/writer.go
  • internal/vault/writer_test.go
  • internal/youtube/channel.go
  • internal/youtube/channel_test.go
  • internal/youtube/youtube.go
  • internal/youtube/youtube_test.go
  • magefile.go
  • test/release_config_test.go
💤 Files with no reviewable changes (25)
  • internal/convert/image_test.go
  • internal/vault/pathutils_test.go
  • internal/convert/pptx_test.go
  • internal/generate/benchmark_policy_test.go
  • internal/convert/epub_test.go
  • internal/output/formatter_test.go
  • internal/cli/workflow_test_helpers_test.go
  • internal/convert/registry_test.go
  • internal/adapter/treesitter_test.go
  • internal/repohealth/portable_paths_test.go
  • internal/convert/xlsx_test.go
  • internal/adapter/rust_adapter.go
  • internal/vault/textutils_test.go
  • internal/convert/html_test.go
  • internal/cli/inspect_helpers_test.go
  • internal/scanner/scanner_test.go
  • internal/convert/pdf_test.go
  • internal/topic/topic_test.go
  • test/release_config_test.go
  • internal/cli/lint_test.go
  • internal/convert/docx_test.go
  • internal/adapter/go_adapter.go
  • internal/firecrawl/client_test.go
  • internal/youtube/youtube_test.go
  • internal/adapter/ts_adapter.go

Comment thread internal/config/config_test.go
Comment thread internal/config/config.go
Comment thread internal/ingest/youtube.go
Comment thread internal/instagram/instagram_test.go Outdated
Comment thread internal/instagram/instagram_test.go Outdated
Comment thread internal/instagram/instagram.go
Comment thread internal/mediadl/transcription.go Outdated
Comment thread internal/mediadl/ytdlp.go
Comment thread internal/youtube/channel.go Outdated
@pedronauck
pedronauck merged commit bfdac53 into main Jun 27, 2026
7 of 9 checks passed
@pedronauck
pedronauck deleted the ytchan branch June 27, 2026 03:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant