Skip to content

feat(cli): complement subcommand — tab-seeded complementary part (S13) - #60

Merged
PhysShell merged 2 commits into
mainfrom
claude/gp-complement
Jun 16, 2026
Merged

feat(cli): complement subcommand — tab-seeded complementary part (S13)#60
PhysShell merged 2 commits into
mainfrom
claude/gp-complement

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 16, 2026

Copy link
Copy Markdown
Owner

What

griff complement <input> <out.mid> [--mode M] [--seed N] [--offset N] — the S13 ComplementArranger surfaced on the .gpx/.mid front door. The engine writes a second part — a complementary guitar/bass derived from your tab's primary track — and saves the tab plus that new part as one MIDI.

Six relation modes (complement::RelationMode):

  • rhythm_lock (default) — B locks to A's onset grid, pitches from A's harmony
  • register_contrast — B in a register band disjoint from A
  • call_response — B answers A in its gaps
  • support_layer — a sparser low layer under A
  • octave_double — A's contour an octave away
  • counter_melody — an independent line (delegates to the S6 generator)

--offset shifts B's register relative to A (e.g. -12 = an octave down); --seed makes it deterministic.

How

Thin CLI wiring over the already-tested core complement::arrange_complement; the returned ComplementCandidate.score is A with part B appended as a new track, exported via midi::export_score (same path as export/generate). No core changes. Adds a CliError::Complement variant.

Verified

On the real GP6 tab (Dance Gavin Dance):

$ griff complement dgd.gpx out.mid
complement (rhythm_lock, seed 0) — part B appended as track 2 (18011 bytes) -> out.mid

→ the MIDI re-imports as 3 tracks (the two guitars + the generated part B on channel 1, 696 notes). All six fixtures complement cleanly under rhythm_lock.

Golden-snapshot tested (complement_golden, mirroring export/generate's path-scrubbing). TDD red → green. 21 cli tests green; clippy --all-targets -D warnings clean; fmt clean.

Scope

CLI surface only — the arrangement logic and its tests already live in core::complement. This completes the trio the user asked for: S4 (phrases) ✓, S14 (structure) ✓, S6 (generate) ✓, S13 (complement) ✓.

https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added a griff complement CLI subcommand that generates “part B” and appends it to the output MIDI alongside the original “part A”.
    • Added --mode to control the complement relationship, deterministic --seed, and optional semitone --offset for register shifting.
  • Bug Fixes
    • Invalid --mode values now fail with a clear CLI argument error.
    • Complement failures are now surfaced consistently as CLI errors.
  • Tests
    • Expanded golden/snapshot coverage for complement across multiple fixtures, including optioned runs and invalid-mode error output.

A golden test that runs `griff complement <fixture> <out.mid>` and snapshots
its summary line. Fails until the subcommand exists: clap rejects the unknown
subcommand and no snapshots are blessed yet.
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 36dd8a2e-916f-4ee1-b132-00fef5587e35

📥 Commits

Reviewing files that changed from the base of the PR and between a0930e1 and f041fe7.

📒 Files selected for processing (9)
  • cli/src/main.rs
  • cli/tests/cli.rs
  • cli/tests/snapshots/complement__multi_track.txt
  • cli/tests/snapshots/complement__opts_counter_melody.txt
  • cli/tests/snapshots/complement__seven_eight.txt
  • cli/tests/snapshots/complement__simple_4_4.txt
  • cli/tests/snapshots/complement__tempo_change.txt
  • cli/tests/snapshots/complement__two_phrases.txt
  • cli/tests/snapshots/error__complement_invalid_mode.txt
✅ Files skipped from review due to trivial changes (3)
  • cli/tests/snapshots/complement__opts_counter_melody.txt
  • cli/tests/snapshots/complement__multi_track.txt
  • cli/tests/snapshots/complement__tempo_change.txt
🚧 Files skipped from review as they are similar to previous changes (6)
  • cli/tests/snapshots/complement__simple_4_4.txt
  • cli/tests/snapshots/complement__two_phrases.txt
  • cli/tests/snapshots/error__complement_invalid_mode.txt
  • cli/tests/cli.rs
  • cli/tests/snapshots/complement__seven_eight.txt
  • cli/src/main.rs

📝 Walkthrough

Walkthrough

A new complement subcommand is added to the griff CLI. It accepts an input MIDI/Guitar Pro file, an output path, and optional --mode, seed, and offset arguments, then calls complement::arrange_complement to produce a complementary part B and writes both parts to a MIDI file. Error handling is extended with a CliError::Complement variant. Golden integration tests and seven fixture snapshots are added.

Changes

Complement CLI subcommand

Layer / File(s) Summary
Subcommand definition, dispatch, and error handling
cli/src/main.rs
Declares Command::Complement with input/output paths, --mode, --seed, and --offset arguments; wires the branch into run() dispatcher; implements cmd_complement to load score, select primary track, parse mode, build ComplementSpec, call arrange_complement, export MIDI, and print result; implements default_offset to supply per-mode default register shifts; implements parse_relation_mode for CLI argument validation; adds CliError::Complement variant with fmt::Display implementation and From<complement::ComplementError> conversion.
Golden integration tests and fixture snapshots
cli/tests/cli.rs, cli/tests/snapshots/complement__*.txt, cli/tests/snapshots/error__complement_invalid_mode.txt
Updates module documentation to list complement among covered subcommands; implements complement_golden to run the subcommand for each fixture, scrub source paths, and assert against named snapshots; implements complement_options_golden for non-default --mode, --seed, --offset variants; implements complement_invalid_mode_golden to verify error output for invalid mode; adds seven snapshots capturing exit codes, stdout messages, and stderr for multi-track, seven-eight, simple 4/4, tempo-change, two-phrases, counter-melody-with-options, and invalid-mode scenarios.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • PhysShell/griff#59: Both PRs extend the shared CLI command dispatch and CliError enum in cli/src/main.rs to add new subcommands and error variants, following the same integration pattern.

Poem

🐇 Hop, hop, a new command appears,
griff complement — part B now cheers!
A seed is planted, an offset set,
The rhythm blooms without regret.
Golden snapshots line the warren floor,
Seven fixtures checked, and maybe more! 🎵

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a complement subcommand to the CLI for generating complementary parts (S13) with seed control.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 claude/gp-complement

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 163a11c66b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/tests/cli.rs
}

#[test]
fn complement_golden() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Split complement CLI into red/green commits

AGENTS.md's mandatory TDD workflow for /workspace/griff requires non-trivial changes to commit failing tests before implementation and explicitly says reviewers must judge the commit sequence, not just the flattened diff. This commit introduces complement_golden and the CLI implementation/snapshots together, with no preceding red test commit for the complement subcommand on this branch, so the sequence is non-compliant; split the golden test commit from the implementation commit.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This is already satisfied on the branch — the complement subcommand was committed red→green:

  • 512c26d test(cli): pin a complement subcommand via golden test (red) — touches only cli/tests/cli.rs (+26/−4). The new complement_golden test fails at this commit: clap rejects the unknown complement subcommand and the snapshot files don't exist yet.
  • a0930e1 feat(cli): complement subcommand … (green) — adds cli/src/main.rs plus the snapshots that make the pinned test pass.

The flattened base→head diff collapses the two into one change, but the commit sequence has the failing-test-first split AGENTS.md asks reviewers to judge.


Generated by Claude Code

Comment thread cli/src/main.rs Outdated
#[arg(long, default_value_t = 0)]
seed: u64,
/// Semitone shift of B's register relative to A (e.g. -12 = octave down).
#[arg(long, default_value_t = 0)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require offsets for modes that reject zero

Because --offset defaults to 0 for every relation, two advertised modes are unusable with the invocation style the help suggests: octave_double rejects zero because it must be a non-zero octave shift, and register_contrast rejects zero because B's shifted band still overlaps A. A user running griff complement --mode octave_double in.mid out.mid gets InvalidSpec instead of an octave; either apply mode-specific defaults or require/validate --offset for those modes before calling the arranger.

Useful? React with 👍 / 👎.

Comment thread cli/src/main.rs Outdated
register_offset: offset,
};
let candidate =
complement::arrange_complement(&score, 0, spec, generate::GenerationSeed(seed))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Select a note-bearing source track

This hard-codes part A to score track 0, but Guitar Pro import preserves rest-only tracks, so a tab whose first GP track is empty while later tracks contain notes will fail with PartHasNoNotes even though it has material to complement. Choose the first primary-voice note-bearing track, matching the existing curation convention, or expose a --track option so multi-track/silent-leading tabs can be arranged.

Useful? React with 👍 / 👎.

@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: 1

🧹 Nitpick comments (1)
cli/tests/cli.rs (1)

150-170: ⚡ Quick win

Add golden coverage for non-default complement args and invalid mode.

Current golden only validates defaults; --mode, --seed, --offset, and the invalid-mode failure path remain unpinned.

🧪 Suggested additions
+#[test]
+fn complement_modes_and_options_golden() {
+    let src = fixture_path("simple_4_4");
+    let dst = env::temp_dir().join("griff_s0_complement_opts.mid");
+    fs::remove_file(&dst).ok();
+    let out = griff(
+        &[
+            "complement",
+            src.to_str().unwrap(),
+            dst.to_str().unwrap(),
+            "--mode",
+            "counter_melody",
+            "--seed",
+            "42",
+            "--offset",
+            "-12",
+        ],
+        dst.to_str(),
+    );
+    let out = out.replace(src.to_str().unwrap(), "<SRC>");
+    assert_golden("complement__opts_counter_melody_seed42_offset-12", &out);
+    fs::remove_file(&dst).ok();
+}
+
+#[test]
+fn complement_invalid_mode_golden() {
+    let src = fixture_path("simple_4_4");
+    let dst = env::temp_dir().join("griff_s0_complement_invalid.mid");
+    let out = griff(
+        &["complement", src.to_str().unwrap(), dst.to_str().unwrap(), "--mode", "bad_mode"],
+        dst.to_str(),
+    );
+    let out = out.replace(src.to_str().unwrap(), "<SRC>");
+    assert_golden("error__complement_invalid_mode", &out);
+}
🤖 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 `@cli/tests/cli.rs` around lines 150 - 170, The complement_golden test function
currently only validates the default behavior of the complement command without
testing various argument combinations or error cases. Extend the test coverage
by adding additional test cases that invoke the griff function with non-default
arguments for --mode, --seed, and --offset parameters, and also add a test case
that passes an invalid mode value to verify the error handling path is correct.
Each test case variant should have corresponding golden file assertions via
assert_golden calls to pin the expected output behavior.
🤖 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 `@cli/src/main.rs`:
- Around line 690-693: The error returned for an unknown complement mode is
using CliError::Ensemble, which incorrectly labels the error as an ensemble
error instead of a complement-specific error. Replace CliError::Ensemble with a
complement-specific error type (such as CliError::Complement if it exists, or
the appropriate variant that corresponds to complement mode validation) in the
error return statement at the location where the "unknown complement mode"
message is constructed, so users see the correct error category prefix.

---

Nitpick comments:
In `@cli/tests/cli.rs`:
- Around line 150-170: The complement_golden test function currently only
validates the default behavior of the complement command without testing various
argument combinations or error cases. Extend the test coverage by adding
additional test cases that invoke the griff function with non-default arguments
for --mode, --seed, and --offset parameters, and also add a test case that
passes an invalid mode value to verify the error handling path is correct. Each
test case variant should have corresponding golden file assertions via
assert_golden calls to pin the expected output behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 469d15a9-6796-4320-a737-f9a80e19de07

📥 Commits

Reviewing files that changed from the base of the PR and between bad7269 and 163a11c.

📒 Files selected for processing (7)
  • cli/src/main.rs
  • cli/tests/cli.rs
  • cli/tests/snapshots/complement__multi_track.txt
  • cli/tests/snapshots/complement__seven_eight.txt
  • cli/tests/snapshots/complement__simple_4_4.txt
  • cli/tests/snapshots/complement__tempo_change.txt
  • cli/tests/snapshots/complement__two_phrases.txt

Comment thread cli/src/main.rs Outdated
@PhysShell
PhysShell force-pushed the claude/gp-complement branch from 163a11c to a0930e1 Compare June 16, 2026 02:13

@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: 1

🤖 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 `@cli/tests/cli.rs`:
- Around line 201-220: The test function complement_invalid_mode_golden() has a
comment on line 201 stating the test should produce no output file, but the test
never validates this behavior. After the assert_golden() call, add an explicit
assertion to verify that the destination file (dst) was not created by checking
that !dst.exists() is true, thereby enforcing the documented contract about the
invalid-mode failure path.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2bbfd39f-60f0-438f-a81c-bc4a5e580ffe

📥 Commits

Reviewing files that changed from the base of the PR and between 163a11c and a0930e1.

📒 Files selected for processing (9)
  • cli/src/main.rs
  • cli/tests/cli.rs
  • cli/tests/snapshots/complement__multi_track.txt
  • cli/tests/snapshots/complement__opts_counter_melody.txt
  • cli/tests/snapshots/complement__seven_eight.txt
  • cli/tests/snapshots/complement__simple_4_4.txt
  • cli/tests/snapshots/complement__tempo_change.txt
  • cli/tests/snapshots/complement__two_phrases.txt
  • cli/tests/snapshots/error__complement_invalid_mode.txt
✅ Files skipped from review due to trivial changes (4)
  • cli/tests/snapshots/complement__multi_track.txt
  • cli/tests/snapshots/complement__simple_4_4.txt
  • cli/tests/snapshots/complement__two_phrases.txt
  • cli/tests/snapshots/complement__seven_eight.txt
🚧 Files skipped from review as they are similar to previous changes (2)
  • cli/tests/snapshots/complement__tempo_change.txt
  • cli/src/main.rs

Comment thread cli/tests/cli.rs
Surface the S13 ComplementArranger (complement::arrange_complement, ADR-0015)
on the .gpx/.mid front door. `griff complement <input> <out.mid>
[--mode M] [--seed N] [--offset N]` derives a complementary part B from the
source's primary track — one of six relations (rhythm_lock, register_contrast,
call_response, support_layer, octave_double, counter_melody) — and writes A
plus B to MIDI. Deterministic under a fixed seed.

Greens the complement_golden test by blessing its summary snapshots. On a real
GP6 tab: a rhythm-locked second part appended as a new track alongside the two
guitars.
@PhysShell
PhysShell force-pushed the claude/gp-complement branch from a0930e1 to f041fe7 Compare June 16, 2026 02:17
@PhysShell
PhysShell merged commit 44c3cc7 into main Jun 16, 2026
1 check passed
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.

2 participants