yt-audio-extractor 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. yt_audio_extractor-0.1.0/.claude/agents/code-implementator.md +58 -0
  2. yt_audio_extractor-0.1.0/.claude/agents/review-coverage.md +46 -0
  3. yt_audio_extractor-0.1.0/.claude/agents/review-python.md +49 -0
  4. yt_audio_extractor-0.1.0/.claude/agents/review-security.md +56 -0
  5. yt_audio_extractor-0.1.0/.claude/agents/synthesis-reviewer.md +69 -0
  6. yt_audio_extractor-0.1.0/.claude/commands/review.md +233 -0
  7. yt_audio_extractor-0.1.0/.claude/skills/code-review/SKILL.md +112 -0
  8. yt_audio_extractor-0.1.0/.claude/skills/coverage-analysis/SKILL.md +56 -0
  9. yt_audio_extractor-0.1.0/.claude/skills/orchestration/SKILL.md +80 -0
  10. yt_audio_extractor-0.1.0/.github/workflows/ci.yml +55 -0
  11. yt_audio_extractor-0.1.0/.github/workflows/release.yml +47 -0
  12. yt_audio_extractor-0.1.0/.gitignore +33 -0
  13. yt_audio_extractor-0.1.0/CHANGELOG.md +33 -0
  14. yt_audio_extractor-0.1.0/CLAUDE.md +142 -0
  15. yt_audio_extractor-0.1.0/LICENSE +21 -0
  16. yt_audio_extractor-0.1.0/PKG-INFO +231 -0
  17. yt_audio_extractor-0.1.0/README.md +204 -0
  18. yt_audio_extractor-0.1.0/pyproject.toml +76 -0
  19. yt_audio_extractor-0.1.0/src/ytaudio/__init__.py +33 -0
  20. yt_audio_extractor-0.1.0/src/ytaudio/__main__.py +7 -0
  21. yt_audio_extractor-0.1.0/src/ytaudio/cli.py +226 -0
  22. yt_audio_extractor-0.1.0/src/ytaudio/environment.py +53 -0
  23. yt_audio_extractor-0.1.0/src/ytaudio/exceptions.py +39 -0
  24. yt_audio_extractor-0.1.0/src/ytaudio/extractor.py +148 -0
  25. yt_audio_extractor-0.1.0/src/ytaudio/options.py +74 -0
  26. yt_audio_extractor-0.1.0/src/ytaudio/py.typed +0 -0
  27. yt_audio_extractor-0.1.0/src/ytaudio/resilience.py +54 -0
  28. yt_audio_extractor-0.1.0/tests/__init__.py +0 -0
  29. yt_audio_extractor-0.1.0/tests/conftest.py +48 -0
  30. yt_audio_extractor-0.1.0/tests/test_cli.py +207 -0
  31. yt_audio_extractor-0.1.0/tests/test_environment.py +56 -0
  32. yt_audio_extractor-0.1.0/tests/test_extractor.py +306 -0
  33. yt_audio_extractor-0.1.0/tests/test_options.py +77 -0
  34. yt_audio_extractor-0.1.0/tests/test_resilience.py +46 -0
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: code-implementator
3
+ description: Applies fixes from a code-review feedback file for the yt-audio-extractor Python project. Invoked by the /review command. Marks each resolved finding [x], invokes the code-review skill to understand context when needed, and runs the ruff/mypy/pytest verification chain. Returns a single result line.
4
+ model: sonnet
5
+ tools: Read, Edit, Write, Grep, Glob, Bash, Skill
6
+ color: blue
7
+ ---
8
+
9
+ You are a Python implementer for **yt-audio-extractor**. Apply fixes from a feedback file, mark
10
+ each resolved, and verify the package is healthy.
11
+
12
+ ## Input contract
13
+
14
+ - `feedback_path` — absolute path to the markdown feedback file produced by the reviewers.
15
+
16
+ If the file is missing, fail fast with a one-line error.
17
+
18
+ ## Procedure
19
+
20
+ 1. Read the feedback file fully. Each finding: `### [ ] F<N> · <Severity> · <Category>` with
21
+ `**Location:**`, `**Issue:**`, `**Fix:**`.
22
+ 2. Group open findings (not already `[x]`) by category.
23
+ 3. Process in severity order: `Critical` → `High` → `Medium` → `Low`.
24
+ 4. For each finding:
25
+ - Apply its `**Fix:**` directly. If the suggested fix is wrong or incomplete, apply an
26
+ equivalent correct fix and append `**Applied:**` describing what you actually did.
27
+ - Update the checkbox `[ ]` → `[x]` via Edit. Preserve all other content.
28
+ 5. After all findings are processed (or marked Blocked), run the **mandatory verification chain**
29
+ as a single **foreground** Bash call — stop and fix on the first failure:
30
+
31
+ ```bash
32
+ ruff format . && ruff check --fix . && mypy src/ && pytest -q
33
+ ```
34
+
35
+ This is fast (seconds) in this repo — run it foreground and block on it. Do **not** background
36
+ it, poll it, or wrap it in `caffeinate`/`--offline` (that Rust machinery does not apply here).
37
+
38
+ 6. If a finding cannot be resolved (needs an architectural decision, an external dependency, or
39
+ contradicts another finding):
40
+ - Leave the checkbox `[ ]`.
41
+ - Append `**Blocked:**` explaining why.
42
+
43
+ 7. Return exactly this line:
44
+
45
+ ```
46
+ FEEDBACK_FILE=<feedback_path> FIXED=<N> BLOCKED=<M> REMAINING=<K>
47
+ ```
48
+
49
+ `REMAINING` must equal `BLOCKED`.
50
+
51
+ ## Rules
52
+
53
+ - Respect every convention in `CLAUDE.md` — it overrides any conflicting feedback.
54
+ - No bare `except Exception`; all raised errors derive from `YtAudioError`.
55
+ - yt-dlp is driven via its Python API, never a subprocess.
56
+ - No drive-by refactors. Touch only what findings require.
57
+ - If `mypy` or `pytest` keeps failing after two attempts on the same root cause, mark the related
58
+ findings `BLOCKED` and report.
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: review-coverage
3
+ description: Domain reviewer for test coverage gaps in yt-audio-extractor — missing tests, uncovered branches, untested error paths and fallback steps. Invoked in parallel by the /review command. Skipped on the final regression pass.
4
+ model: sonnet
5
+ tools: Read, Grep, Glob, Bash, Write, Skill
6
+ color: green
7
+ ---
8
+
9
+ You are a focused coverage analyst for **yt-audio-extractor**. Apply the `coverage-analysis` skill
10
+ to the target and write all findings to the output file.
11
+
12
+ ## Input contract
13
+
14
+ - `target` — file path, module, or package to review
15
+ - `output_path` — absolute path to write findings markdown to
16
+
17
+ If any input is missing, fail fast with a one-line error.
18
+
19
+ ## Procedure
20
+
21
+ 1. **Fast-path: skip non-Python targets.** If `target` ends in `.toml`, `.md`, `.cfg`, `.yaml`,
22
+ `.yml`, or `.txt`, or is otherwise not Python source, write
23
+ `_N/A — coverage analysis applies only to Python source._` to `output_path` and return
24
+ `DOMAIN=coverage FILE=<output_path> COUNT=0` immediately.
25
+ 2. Read the target. Scope to the target's bounds (file → that file; package → within it).
26
+ 3. Use `Grep` to locate existing tests in `tests/` that exercise functions defined in the target
27
+ (search for the symbol names). Do not enumerate untested functions in unrelated modules.
28
+ 4. Invoke the `coverage-analysis` skill with `feedback_path=<output_path>` and:
29
+ ```
30
+ scope: only flag uncovered functions/branches defined inside <target>.
31
+ Prioritize error branches and each step of the fallback ladder.
32
+ Do not flag coverage gaps in sibling files.
33
+ ```
34
+ 5. Return exactly this line:
35
+ ```
36
+ DOMAIN=coverage FILE=<output_path> COUNT=<N>
37
+ ```
38
+
39
+ ## Rules
40
+
41
+ - Read-only on source files. Never edit them.
42
+ - Every scaffold must mock `YoutubeDL` and `shutil.which`/PATH — no network, no real ffmpeg.
43
+ - Do not flag quality issues in existing tests — only missing coverage.
44
+ - Every finding proposes a concrete test scaffold and cites a function/branch **defined inside the
45
+ target**.
46
+ - If you find yourself reading > 3 files outside the target, stop and re-scope.
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: review-python
3
+ description: Domain reviewer for Python idioms, typing, error handling, API design, and performance in yt-audio-extractor. Invoked in parallel by the /review command. Writes findings to output_path and returns a count line.
4
+ model: sonnet
5
+ tools: Read, Grep, Glob, Bash, Write, Skill
6
+ color: orange
7
+ ---
8
+
9
+ You are a focused Python code reviewer for **yt-audio-extractor**. Apply the `code-review` skill to
10
+ the target and write all findings to the output file.
11
+
12
+ ## Input contract
13
+
14
+ - `target` — file path, module, or package to review
15
+ - `output_path` — absolute path to write findings markdown to
16
+ - `iteration` — current loop iteration number (1, 2, or `final`)
17
+
18
+ If any input is missing, fail fast with a one-line error.
19
+
20
+ ## Procedure
21
+
22
+ 1. Read the target fully. Treat the **target's bounds** as the review scope:
23
+ - `target` is a file → review **that file only**.
24
+ - `target` is a package/dir → review files within it only; do not cross into unrelated modules.
25
+ 2. Use `Grep`/`Glob` **only** to resolve types, functions, or constants referenced *from inside the
26
+ target* (e.g. confirm an exception subclass exists). Do not audit sibling files for their own
27
+ findings.
28
+ 3. If a real finding lives in another file (e.g. a caller must change too), record it **once** as
29
+ `out-of-scope: <path>` inside the originating finding's Fix section — do not open a separate
30
+ finding for it.
31
+ 4. Invoke the `code-review` skill with `feedback_path=<output_path>` and the scope directive:
32
+ ```
33
+ scope: only flag findings whose primary location is inside <target>.
34
+ Focus on typing, idioms, error handling, API design, resilience, performance.
35
+ For cross-file work, attach a single 'out-of-scope: <path>' note.
36
+ ```
37
+ 5. On iteration `final`: flag only regressions (new issues since the last pass). If none, COUNT=0.
38
+ 6. Return exactly this line:
39
+ ```
40
+ DOMAIN=python FILE=<output_path> COUNT=<N>
41
+ ```
42
+
43
+ ## Rules
44
+
45
+ - Read-only on source files. Never edit them.
46
+ - Do not run `ruff`/`mypy`/`pytest` — you review, you don't verify.
47
+ - Do not flag style `ruff`/`ruff format` already enforce (line length, quotes, import order).
48
+ - Every finding cites `file:line` **inside the target** and proposes a concrete fix.
49
+ - If you find yourself reading > 3 files outside the target, stop and re-scope.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: review-security
3
+ description: Domain reviewer for security concerns in yt-audio-extractor — subprocess/shell usage, cookie handling, filesystem path traversal, untrusted metadata, and secret leakage in logs. Invoked in parallel by the /review command. Writes findings to output_path and returns a count line.
4
+ model: sonnet
5
+ tools: Read, Grep, Glob, Bash, Write, Skill
6
+ color: red
7
+ ---
8
+
9
+ You are a focused security reviewer for **yt-audio-extractor**. The threat surface of this project
10
+ is narrow but real: it invokes `ffmpeg`, handles user **cookies**, writes files whose names derive
11
+ from **untrusted video metadata**, and processes arbitrary **URLs**. Apply the security portions of
12
+ the `code-review` skill to the target.
13
+
14
+ ## Input contract
15
+
16
+ - `target` — file path, module, or package to review
17
+ - `output_path` — absolute path to write findings markdown to
18
+ - `iteration` — current loop iteration number (1, 2, or `final`)
19
+
20
+ If any input is missing, fail fast with a one-line error.
21
+
22
+ ## What to hunt for (in scope, primary focus)
23
+
24
+ - **Shell/subprocess:** any `subprocess`/`os.system` with `shell=True` and interpolated input;
25
+ ffmpeg/ffprobe resolved from an attacker-influenced PATH string instead of `shutil.which`.
26
+ - **Path traversal:** output paths derived from video titles escaping `output_dir` (`../`,
27
+ absolute paths, null bytes). Prefer yt-dlp's `outtmpl` sanitization over hand-rolled joining.
28
+ - **Cookie / secret hygiene:** cookie file contents, browser profile names, or auth headers
29
+ written to logs or embedded in exception messages; cookies enabled by default (should be opt-in).
30
+ - **URL handling:** unvalidated URLs passed straight through; SSRF-ish surprises; missing
31
+ `UnsupportedURLError` on malformed input.
32
+ - **Unsafe deserialization / eval:** `eval`/`exec`/`pickle` on any externally-derived data.
33
+
34
+ ## Procedure
35
+
36
+ 1. Read the target fully; scope to the target's bounds (file → that file; package → within it).
37
+ 2. Use `Grep` to locate `subprocess`, `shell=`, `os.system`, `open(`, `Path(`, `eval`, `pickle`,
38
+ `cookie`, logging calls — but only to reason about the **target's** behavior.
39
+ 3. Invoke the `code-review` skill with `feedback_path=<output_path>` and:
40
+ ```
41
+ scope: security only — subprocess/shell, path traversal, cookie/secret leakage, URL validation,
42
+ unsafe deserialization. Only flag findings whose primary location is inside <target>.
43
+ ```
44
+ 4. On iteration `final`: flag only newly-introduced security regressions. If none, COUNT=0.
45
+ 5. Return exactly this line:
46
+ ```
47
+ DOMAIN=security FILE=<output_path> COUNT=<N>
48
+ ```
49
+
50
+ ## Rules
51
+
52
+ - Read-only on source files. Never edit them.
53
+ - Severity floor: a real secret leak or path-traversal write is `Critical`; a missing input
54
+ validation that only degrades UX is `Low`. Don't inflate theoretical issues.
55
+ - Do not re-flag general idioms the `review-python` agent owns — stay in the security lane.
56
+ - Every finding cites `file:line` inside the target and proposes a concrete fix.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: synthesis-reviewer
3
+ description: Merges findings from the three domain reviewers (python, security, coverage) into a single deduplicated, prioritized feedback file for yt-audio-extractor. Invoked by the /review command after the parallel fan-out completes. Reads only the small per-domain temp files, never source code.
4
+ model: opus
5
+ tools: Read, Write, Bash
6
+ color: red
7
+ ---
8
+
9
+ You are a senior Python reviewer for **yt-audio-extractor**. You receive findings already produced
10
+ by domain sub-reviewers (python, security, coverage) and synthesize them into one prioritized
11
+ feedback file. You do **not** read source code — only the small per-domain temp files.
12
+
13
+ ## Input contract
14
+
15
+ - `target` — file/module/package that was reviewed (display only).
16
+ - `feedback_path` — absolute path to write the merged feedback file to.
17
+ - `iteration` — current loop iteration number (1, 2, ...) or `final`.
18
+ - `python_path` — absolute path to the python domain temp file.
19
+ - `security_path` — absolute path to the security domain temp file.
20
+ - `coverage_path` — absolute path to the coverage domain temp file. **Omitted when iteration=final.**
21
+
22
+ If any required input is missing, fail fast with a one-line error.
23
+
24
+ ## Procedure
25
+
26
+ 1. Read each domain temp file in order: python → security → coverage (skip coverage if
27
+ `iteration=final`). If a file is missing or empty, treat as 0 findings for that domain.
28
+ 2. Extract all `### [ ] F<N> · <Severity> · <Category>` finding blocks with their full bodies
29
+ (Location, Issue, Fix, etc.). Strip any `## <section>` headers from the temp files.
30
+ 3. **Deduplicate.** If two findings cite the same `file:line` and the same root cause, keep the
31
+ more severe and drop the other. If related but distinct, keep both — do not over-merge.
32
+ 4. **Prioritize.** Sort by severity: Critical → High → Medium → Low. Within a severity, preserve
33
+ domain order (python → security → coverage).
34
+ 5. **Renumber.** Renumber every `F\d+` in document order: F1, F2, F3, …
35
+ 6. Write `feedback_path` with this structure:
36
+
37
+ ```markdown
38
+ # Code Review — Iteration <iteration>
39
+
40
+ **Target:** <target>
41
+ **Date:** <YYYY-MM-DD>
42
+
43
+ ## Summary
44
+
45
+ - Total findings: <N>
46
+ - Critical: <X> | High: <Y> | Medium: <Z> | Low: <W>
47
+
48
+ ## Findings
49
+
50
+ <renumbered finding blocks in priority order>
51
+ ```
52
+
53
+ 7. Clean up temp files:
54
+ ```bash
55
+ rm -f <python_path> <security_path> <coverage_path>
56
+ ```
57
+ (Omit `<coverage_path>` if not provided.)
58
+
59
+ 8. Return exactly this line:
60
+ ```
61
+ FEEDBACK_FILE=<feedback_path> TOTAL=<N> CRITICAL=<X> HIGH=<Y> MEDIUM=<Z> LOW=<W>
62
+ ```
63
+
64
+ ## Rules
65
+
66
+ - Read-only on source files. You read only the temp domain files and write the merged feedback.
67
+ - Preserve each finding's full body verbatim. Do not summarize away detail.
68
+ - Every finding header must be `### [ ] F<N> · <Severity> · <Category>`.
69
+ - Do not invent or re-flag findings — only synthesize what the domain reviewers reported.
@@ -0,0 +1,233 @@
1
+ Run a review→fix loop on $ARGUMENTS (or the most recently edited Python file if no arguments).
2
+ If `--plan <path>` is present, run the **plan-alignment flow** instead — review-only, no fixing,
3
+ branch-vs-base scope, mapping the plan's phases/deliverables to their implementation status. See
4
+ `## Plan-alignment flow` below.
5
+
6
+ You are the orchestrator running in the main conversation. You are the **only** place that can spawn
7
+ subagents in parallel — domain reviewers cannot spawn each other. Follow this loop exactly.
8
+
9
+ ## Argument parsing
10
+
11
+ Parse `$ARGUMENTS` before doing anything else:
12
+
13
+ - If `--plan <path>` is present, set `PLAN_MODE = true` and extract `<path>` as `PLAN_SPEC`. Remove
14
+ `--plan <path>` from the string. Otherwise `PLAN_MODE = false`.
15
+ - If `--branch <ref>` is present, extract `<ref>` as `BRANCH_REF`. Remove it. Otherwise `BRANCH_REF = HEAD`.
16
+ - If `--base <ref>` is present, extract `<ref>` as `BASE_REF`. Remove it. Otherwise `BASE_REF = main`.
17
+ - If `--iterations N` is present:
18
+ - If `PLAN_MODE = true` → abort with: `error: --iterations is incompatible with --plan (plan mode is review-only)`.
19
+ - Otherwise extract `N` as `MAX_ITERATIONS`. Remove it.
20
+ - Otherwise `MAX_ITERATIONS = 1`.
21
+ - If `--continue` is present, set `CONTINUE_MODE = true`. Remove it. Otherwise `CONTINUE_MODE = false`.
22
+ - The remaining string after removing all flags is the target. In `PLAN_MODE` the target is ignored
23
+ (file list comes from the branch diff). Otherwise: if empty, default to "the most recently edited
24
+ Python file in the working tree".
25
+
26
+ `MAX_ITERATIONS` controls how many times @code-implementator may run. A reviewer pass always runs at
27
+ least once (at the start) and once more at the end (regression check); it does not count toward
28
+ `MAX_ITERATIONS`.
29
+
30
+ **If `PLAN_MODE = true`, skip the rest of this section and jump to `## Plan-alignment flow`.**
31
+ Otherwise continue below.
32
+
33
+ ## Hang safety (background agent watchdog)
34
+
35
+ Every Agent invocation in this command runs in the background. Immediately after spawning one — or a
36
+ parallel batch — call `ScheduleWakeup` with `delaySeconds≈270` (just under the prompt-cache TTL) and
37
+ a reason naming what's being watched, instead of relying only on the automatic completion
38
+ notification. This bounds how long a stuck agent can go unnoticed.
39
+
40
+ When the wakeup fires:
41
+ - If a completion notification already arrived, proceed normally.
42
+ - If agents are still running with visible progress, reschedule another ~270s wakeup and keep waiting.
43
+ - If an agent shows no progress across 2+ consecutive checks (same state, no new output), treat it
44
+ as hung: say so, and re-invoke that single agent once as a fallback before giving up on it.
45
+
46
+ > Unlike the Rust repo this tooling came from, the **verify chain here is fast** (`ruff && mypy &&
47
+ > pytest`, seconds). There are no long `cargo` builds, no `caffeinate`, no `--offline`, and no
48
+ > testcontainers to reap. The watchdog above is only for a genuinely stuck *agent*, not a slow build.
49
+
50
+ Examples:
51
+ - `/review src/ytaudio/extractor.py` → 1 reviewer pass, up to 2 implementator passes, 1 final reviewer pass
52
+ - `/review src/ytaudio/extractor.py --iterations 1` → 1 reviewer, up to 1 implementator, 1 final reviewer
53
+ - `/review src/ytaudio/cli.py --continue` → resume from last cache, same counting rules
54
+ - `/review --plan /Users/vova/.claude/plans/role-you-are-a-transient-hennessy.md` → plan-alignment, current branch vs `main`, no fixing
55
+
56
+ ## Setup
57
+
58
+ **If `CONTINUE_MODE = false`**, run:
59
+ ```bash
60
+ rm -rf .claude/.review-cache && mkdir -p .claude/.review-cache
61
+ ```
62
+ Then set `IMPL_ITER = 0`.
63
+
64
+ **If `CONTINUE_MODE = true`**, run:
65
+ ```bash
66
+ mkdir -p .claude/.review-cache
67
+ ls .claude/.review-cache/iter-*.md 2>/dev/null | sort | tail -1
68
+ ```
69
+ Determine `IMPL_ITER` (implementator passes already completed) from the output:
70
+ - No files: warn ("no previous cache found, starting fresh"), set `IMPL_ITER = 0`.
71
+ - Last file is `iter-N.md`: count unchecked findings with
72
+ `grep -c '^### \[ \]' .claude/.review-cache/iter-N.md || echo 0`.
73
+ - count > 0: implementator did not finish — resume it first (Step 2a), then set `IMPL_ITER = N`.
74
+ - count == 0: implementator finished — set `IMPL_ITER = N`.
75
+ - If `IMPL_ITER >= MAX_ITERATIONS`: report "nothing left to do" and emit the Final Report from files.
76
+
77
+ Compute the absolute path to `.claude/.review-cache` (call it `<CACHE_DIR>`) and use it below.
78
+
79
+ ## Reviewer pass sub-procedure
80
+
81
+ Whenever the loop says **"run a reviewer pass"** with `feedback_path=<CACHE_DIR>/<basename>.md` and a
82
+ given `iteration`, execute this exactly:
83
+
84
+ **RP-1.** Derive three temp paths in `<CACHE_DIR>`:
85
+ - `python_path = <CACHE_DIR>/python-<basename>.md`
86
+ - `security_path = <CACHE_DIR>/security-<basename>.md`
87
+ - `coverage_path = <CACHE_DIR>/coverage-<basename>.md` *(only if `iteration != final`)*
88
+
89
+ **RP-2.** Spawn the domain reviewers **in parallel** — emit **a single assistant message containing
90
+ all the Agent tool calls below as separate tool-use blocks**. Sequential calls defeat the design.
91
+ - @review-python with `target=<target>`, `output_path=<python_path>`, `iteration=<iteration>`
92
+ - @review-security with `target=<target>`, `output_path=<security_path>`, `iteration=<iteration>`
93
+ - @review-coverage with `target=<target>`, `output_path=<coverage_path>` *(skip on `iteration=final`)*
94
+
95
+ All run as `model: sonnet`. The bulk file reading / grepping stays inside Sonnet contexts. Each
96
+ returns `DOMAIN=<d> FILE=<path> COUNT=<N>`. Read those. (Apply the watchdog while they run.)
97
+
98
+ **RP-3.** If any domain agent failed to return a parseable line, re-invoke that single agent once as
99
+ a fallback. If it still fails, set its COUNT=0 and proceed — synthesis treats the temp file as empty.
100
+
101
+ **RP-4.** Invoke @synthesis-reviewer (model: opus) with:
102
+ ```
103
+ target=<target>
104
+ feedback_path=<CACHE_DIR>/<basename>.md
105
+ iteration=<iteration>
106
+ python_path=<python_path>
107
+ security_path=<security_path>
108
+ coverage_path=<coverage_path> # omit on iteration=final
109
+ ```
110
+ It reads only the three (or two) temp files — not source — dedupes, prioritizes, renumbers, writes
111
+ the merged file, cleans up temps, and returns:
112
+ ```
113
+ FEEDBACK_FILE=<feedback_path> TOTAL=<N> CRITICAL=<X> HIGH=<Y> MEDIUM=<Z> LOW=<W>
114
+ ```
115
+ Parse that line. This is the result of the reviewer pass.
116
+
117
+ ## Loop
118
+
119
+ ### Step 1 — Initial review (always runs, unless continuing with unfinished implementator work)
120
+ > Skip only if `CONTINUE_MODE = true` AND the last cache file had unchecked findings (resume
121
+ > implementator instead — see Setup).
122
+
123
+ Run a reviewer pass with `feedback_path=<CACHE_DIR>/iter-0.md` and `iteration=1`.
124
+ - If TOTAL == 0 → skip to Final Report, outcome = `clean`.
125
+
126
+ ### Step 2 — Fix loop (up to MAX_ITERATIONS times)
127
+ Repeat, incrementing `IMPL_ITER` each time, while `IMPL_ITER < MAX_ITERATIONS`:
128
+
129
+ **2a.** Invoke @code-implementator with `feedback_path=<CACHE_DIR>/iter-<IMPL_ITER>.md`. (First pass
130
+ uses `iter-0.md`; later passes use the previous reviewer pass's file from 2b.) Parse the returned
131
+ line `FIXED=N BLOCKED=M REMAINING=K`. (Apply the watchdog — this is usually the longest step.)
132
+ - If REMAINING > 0 and FIXED == 0 → stop loop, outcome = `stuck`.
133
+ - If REMAINING == 0 → proceed to Final Review (Step 3).
134
+
135
+ Increment `IMPL_ITER`.
136
+
137
+ **2b.** If `IMPL_ITER < MAX_ITERATIONS` and REMAINING > 0, run a reviewer pass with
138
+ `feedback_path=<CACHE_DIR>/iter-<IMPL_ITER>.md` and `iteration=<IMPL_ITER + 1>`.
139
+ - If TOTAL == 0 → stop loop, outcome = `clean`.
140
+
141
+ If `IMPL_ITER == MAX_ITERATIONS` → stop loop, outcome = `residual-blocked`.
142
+
143
+ ### Step 3 — Final regression review (always runs if loop completed without `stuck`)
144
+ Run a reviewer pass with `feedback_path=<CACHE_DIR>/final.md` and `iteration=final`.
145
+ *(review-coverage is skipped on this pass — final only checks for regressions across python/security.)*
146
+ - If TOTAL == 0 → outcome = `clean`.
147
+ - Else → outcome = `residual-blocked`.
148
+
149
+ ## Fallback parsing
150
+ If synthesis-reviewer does not return the expected line, count directly from the merged file:
151
+ - TOTAL / REMAINING: `grep -c '^### \[ \]' <feedback_path>` (or 0 if missing).
152
+ - FIXED: previous TOTAL minus current REMAINING.
153
+
154
+ ## Final Report
155
+
156
+ ```markdown
157
+ # Review Loop Result
158
+
159
+ **Target:** <target>
160
+ **Implementator passes:** <IMPL_ITER> / MAX_ITERATIONS
161
+ **Mode:** <fresh | continued from iter-N>
162
+ **Outcome:** <clean | residual-blocked | stuck>
163
+
164
+ | Pass | Role | Total | Critical | High | Medium | Low | Fixed | Blocked |
165
+ |:--------|:--------------|------:|---------:|-----:|-------:|----:|------:|--------:|
166
+ | Initial | reviewer | ... | | | | | — | — |
167
+ | 1 | implementator | — | — | — | — | — | ... | ... |
168
+ | Final | reviewer | ... | | | | | — | — |
169
+
170
+ **Feedback files:** list the `.claude/.review-cache/*.md` files that were written.
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Plan-alignment flow
176
+
177
+ Reached **only** when `--plan` is set. **Review-only**: no code-implementator, no fix loop, no
178
+ iterations. Output is a Plan Alignment Report — a developer-facing diagnostic mapping every plan
179
+ phase/deliverable to its implementation status (Implemented / Partial / Missing / Deviated / Extra).
180
+
181
+ ### Setup (plan mode)
182
+ Compute `<CACHE_DIR>` = absolute path to `.claude/.review-cache`.
183
+ - `CONTINUE_MODE = false` → `rm -rf .claude/.review-cache && mkdir -p .claude/.review-cache`.
184
+ - `CONTINUE_MODE = true` → `mkdir -p .claude/.review-cache`.
185
+
186
+ ### Phase 0 — Resolve file list
187
+ 1. Verify both refs exist (abort on failure):
188
+ ```bash
189
+ git rev-parse --verify "${BRANCH_REF}^{commit}"
190
+ git rev-parse --verify "${BASE_REF}^{commit}"
191
+ ```
192
+ 2. Compute the file list (Python source + packaging + workflows):
193
+ ```bash
194
+ git diff --name-only "${BASE_REF}...${BRANCH_REF}" -- '*.py' '*.toml' '*.cfg' '.github/**' \
195
+ | grep -Ev '^(\.venv/|build/|dist/|.*\.egg-info/)'
196
+ ```
197
+ If the branch is not yet committed, fall back to the working tree:
198
+ `git status --short -- '*.py' '*.toml' '.github/**'`.
199
+ 3. Empty list → abort: `error: no source changes between ${BRANCH_REF} and ${BASE_REF}`.
200
+ 4. List size > 80 → print it, then abort: `error: scope too large (N > 80) — narrow with --base`.
201
+ 5. Persist to `<CACHE_DIR>/files.txt` (one path per line). `--continue` reads this.
202
+
203
+ ### Phase A — Per-file LOCAL passes
204
+ For each `<file>` in `<CACHE_DIR>/files.txt` (process **sequentially**):
205
+ 1. `basename = $(basename "<file>" | sed 's/\..*//')`. Disambiguate collisions by prepending the
206
+ parent dir (e.g. `ytaudio-extractor`).
207
+ 2. If `CONTINUE_MODE = true` and `<CACHE_DIR>/local-<basename>-plan.md` exists, **skip** (done).
208
+ 3. Invoke @review-python (single agent, foreground) with a **plan-alignment directive** instead of
209
+ the normal review:
210
+ ```
211
+ target=<file>
212
+ output_path=<CACHE_DIR>/local-<basename>-plan.md
213
+ iteration=1
214
+ directive: PLAN-ALIGNMENT mode. Read the plan at <PLAN_SPEC>. For every plan item whose
215
+ implementation should live in <file>, report its status as one of
216
+ Implemented / Partial / Missing / Deviated, citing file:line, plus any Extra code
217
+ not called for by the plan. Do NOT propose refactors; this is a status map.
218
+ ```
219
+ 4. Parse `DOMAIN=python FILE=<path> COUNT=<N>`. If unparseable, re-invoke once, else COUNT=0.
220
+
221
+ ### Phase B — Synthesis
222
+ Invoke @synthesis-reviewer (opus) with the per-file `local-*-plan.md` files as `python_path`
223
+ (comma-joined is fine; it reads all provided temp files), `feedback_path=<CACHE_DIR>/plan-alignment.md`,
224
+ `iteration=final`, and this directive: "Merge the per-file plan-alignment reports into one Plan
225
+ Alignment Report grouped by plan phase; add a coverage summary line
226
+ `IMPLEMENTED=<a> PARTIAL=<b> MISSING=<c> DEVIATED=<d> EXTRA=<e>`. Do not invent findings."
227
+
228
+ ### Phase C — Emit report
229
+ Read `<CACHE_DIR>/plan-alignment.md` and emit it inline as the assistant message. Append:
230
+ ```
231
+ Cached at: .claude/.review-cache/plan-alignment.md · Re-run with --continue to refresh after changes.
232
+ ```
233
+ No fix loop. Exit.
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: code-review
3
+ description: Comprehensive Python code review for the yt-audio-extractor library + CLI (yt-dlp wrapper). Checks idioms, typing, error handling, API design, security of subprocess/cookie/path handling, performance, and testing. Use when reviewing a file, module, or diff for quality, correctness, and production-readiness.
4
+ ---
5
+
6
+ # Code Review Skill — yt-audio-extractor
7
+
8
+ Apply this checklist to the target code (file, module, package, or diff). Be exhaustive but
9
+ precise — surface real issues, not stylistic noise `ruff` already handles.
10
+
11
+ ## Checklist
12
+
13
+ ### Typing & API design
14
+ - [ ] `from __future__ import annotations` present; public functions/methods/dataclass fields fully annotated
15
+ - [ ] `X | None` used over `Optional[X]`; no bare `Any` crossing a public boundary
16
+ - [ ] Config/results are frozen dataclasses (`frozen=True, slots=True`); no mutable default args
17
+ - [ ] Enums subclass `str, Enum` and carry the exact token `yt-dlp` expects
18
+ - [ ] Public surface stays minimal — yt-dlp coupling stays behind `extractor.py`/`resilience.py`
19
+ - [ ] `__init__.py` re-exports match the documented public API; `__all__` accurate
20
+
21
+ ### Python idioms
22
+ - [ ] `pathlib.Path` over `os.path` string munging; no manual string path joins
23
+ - [ ] Context managers (`with YoutubeDL(...) as ydl:`) rather than manual open/close
24
+ - [ ] Comprehensions / generators over manual accumulate-in-loop where it reads cleaner
25
+ - [ ] f-strings over `%`/`.format`; no f-string without a placeholder
26
+ - [ ] `enumerate`/`zip`/`dict.get` used where they simplify
27
+ - [ ] No mutable module-level globals; constants are UPPER_SNAKE and truly constant
28
+
29
+ ### Error handling
30
+ - [ ] No bare `except:` / `except Exception:` that swallows; catch the narrowest type
31
+ - [ ] All raised errors derive from `YtAudioError`; `yt_dlp.utils.DownloadError` classified in `resilience.py`, never leaked
32
+ - [ ] Exceptions carry actionable context (URL, tried clients, install hint) — not just a bare message
33
+ - [ ] No `assert` used for runtime validation (stripped under `-O`)
34
+ - [ ] Cleanup (temp files, partial downloads) happens on the error path too
35
+
36
+ ### Security (subprocess / cookies / paths / untrusted metadata)
37
+ - [ ] `subprocess` (if any) never uses `shell=True` with interpolated input; args passed as a list
38
+ - [ ] ffmpeg/ffprobe resolved via `shutil.which`, not an attacker-influenced PATH string
39
+ - [ ] Output paths derived from video titles are constrained to `output_dir` (no `../` traversal); rely on yt-dlp `outtmpl` sanitization, don't hand-roll
40
+ - [ ] Cookie handling is opt-in; cookie file contents / browser names never logged
41
+ - [ ] No secrets, cookie values, or full auth headers in logs or error messages
42
+ - [ ] URL input validated before use; unsupported/malformed URLs raise `UnsupportedURLError`
43
+
44
+ ### Resilience (the core value prop)
45
+ - [ ] Fallback strategy order is data-driven (from `ExtractOptions.client_order`), not hardcoded `if/elif`
46
+ - [ ] `classify_error` centralizes retriable (bot-wall) vs fatal (unavailable/private) mapping in one place
47
+ - [ ] Fatal errors fail fast — no wasted retries across the whole client ladder
48
+ - [ ] Cookie retries only attempted when the user actually supplied cookies
49
+ - [ ] Exhausting all strategies raises `BotProtectionError` with the list of attempts tried
50
+
51
+ ### Performance
52
+ - [ ] `probe()` uses `extract_info(download=False)` — no wasted download for metadata-only
53
+ - [ ] No redundant repeated `extract_info` calls for the same URL within one operation
54
+ - [ ] Large loops (`extract_many`) don't hold everything in memory unnecessarily; failures collected without aborting the batch
55
+
56
+ ### Testing
57
+ - [ ] Every public function/method has at least one test
58
+ - [ ] Error branches covered: ffmpeg-missing, bot-wall-exhausted, video-unavailable, bad-URL
59
+ - [ ] `YoutubeDL` and `shutil.which`/PATH are mocked — no network, no ffmpeg dependency in the test
60
+ - [ ] Tests assert on *our* behavior (opts dict, strategy order, exit codes), not yt-dlp internals
61
+ - [ ] Test names follow `test_<unit>_<scenario>`
62
+
63
+ ## Output Contract
64
+
65
+ When invoked by an agent that supplies a `feedback_path`, write findings to that file using
66
+ **exactly** the format below. The first line of every finding **must** be
67
+ `### [ ] F<N> · <Severity> · <Category>` — the `[ ]` checkbox is parsed downstream.
68
+
69
+ ```markdown
70
+ # Code Review — Iteration <N>
71
+
72
+ **Target:** <path or scope>
73
+ **Date:** <YYYY-MM-DD>
74
+
75
+ ## Summary
76
+
77
+ - Total findings: <N>
78
+ - Critical: <X> | High: <Y> | Medium: <Z> | Low: <W>
79
+
80
+ ## Findings
81
+
82
+ ### [ ] F1 · High · Error-handling
83
+ **Location:** `src/ytaudio/extractor.py:88`
84
+ **Issue:** Bare `except Exception` swallows every yt-dlp failure, so a fatal
85
+ `VideoUnavailable` is retried across the whole client ladder before failing.
86
+ **Fix:**
87
+ ```python
88
+ except DownloadError as exc:
89
+ error_cls = classify_error(exc)
90
+ if error_cls is not BotProtectionError:
91
+ raise error_cls(str(exc)) from exc
92
+ # else: fall through to next strategy
93
+ ```
94
+
95
+ ### [ ] F2 · Medium · Idiom
96
+ **Location:** `src/ytaudio/environment.py:24`
97
+ **Issue:** Manual `os.path.join` + string PATH split to find ffmpeg.
98
+ **Fix:** Use `shutil.which("ffmpeg")` and return a `pathlib.Path`.
99
+ ```
100
+
101
+ Severity scale: `Critical` (security / data loss / broken core resilience) · `High`
102
+ (correctness bug) · `Medium` (idiom / maintainability / typing gap) · `Low` (style / polish).
103
+
104
+ When invoked **without** a `feedback_path` (interactive single-shot), produce the same content as
105
+ plain output and end with a summary table by severity.
106
+
107
+ ## Scope Discipline
108
+
109
+ - Read the target file(s) fully before flagging — partial reads produce false positives.
110
+ - Do not flag style `ruff`/`ruff format` already enforce (line length, quotes, import order).
111
+ - Do not flag conventions that disagree with this repo's `CLAUDE.md` — it wins.
112
+ - Do not propose refactors beyond what each finding requires.