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.
- yt_audio_extractor-0.1.0/.claude/agents/code-implementator.md +58 -0
- yt_audio_extractor-0.1.0/.claude/agents/review-coverage.md +46 -0
- yt_audio_extractor-0.1.0/.claude/agents/review-python.md +49 -0
- yt_audio_extractor-0.1.0/.claude/agents/review-security.md +56 -0
- yt_audio_extractor-0.1.0/.claude/agents/synthesis-reviewer.md +69 -0
- yt_audio_extractor-0.1.0/.claude/commands/review.md +233 -0
- yt_audio_extractor-0.1.0/.claude/skills/code-review/SKILL.md +112 -0
- yt_audio_extractor-0.1.0/.claude/skills/coverage-analysis/SKILL.md +56 -0
- yt_audio_extractor-0.1.0/.claude/skills/orchestration/SKILL.md +80 -0
- yt_audio_extractor-0.1.0/.github/workflows/ci.yml +55 -0
- yt_audio_extractor-0.1.0/.github/workflows/release.yml +47 -0
- yt_audio_extractor-0.1.0/.gitignore +33 -0
- yt_audio_extractor-0.1.0/CHANGELOG.md +33 -0
- yt_audio_extractor-0.1.0/CLAUDE.md +142 -0
- yt_audio_extractor-0.1.0/LICENSE +21 -0
- yt_audio_extractor-0.1.0/PKG-INFO +231 -0
- yt_audio_extractor-0.1.0/README.md +204 -0
- yt_audio_extractor-0.1.0/pyproject.toml +76 -0
- yt_audio_extractor-0.1.0/src/ytaudio/__init__.py +33 -0
- yt_audio_extractor-0.1.0/src/ytaudio/__main__.py +7 -0
- yt_audio_extractor-0.1.0/src/ytaudio/cli.py +226 -0
- yt_audio_extractor-0.1.0/src/ytaudio/environment.py +53 -0
- yt_audio_extractor-0.1.0/src/ytaudio/exceptions.py +39 -0
- yt_audio_extractor-0.1.0/src/ytaudio/extractor.py +148 -0
- yt_audio_extractor-0.1.0/src/ytaudio/options.py +74 -0
- yt_audio_extractor-0.1.0/src/ytaudio/py.typed +0 -0
- yt_audio_extractor-0.1.0/src/ytaudio/resilience.py +54 -0
- yt_audio_extractor-0.1.0/tests/__init__.py +0 -0
- yt_audio_extractor-0.1.0/tests/conftest.py +48 -0
- yt_audio_extractor-0.1.0/tests/test_cli.py +207 -0
- yt_audio_extractor-0.1.0/tests/test_environment.py +56 -0
- yt_audio_extractor-0.1.0/tests/test_extractor.py +306 -0
- yt_audio_extractor-0.1.0/tests/test_options.py +77 -0
- 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.
|