Skip to content

feat: add rtk scaffold command for contributor boilerplate#153

Open
FlorianBruniaux wants to merge 4 commits intomasterfrom
feat/scaffold-command
Open

feat: add rtk scaffold command for contributor boilerplate#153
FlorianBruniaux wants to merge 4 commits intomasterfrom
feat/scaffold-command

Conversation

@FlorianBruniaux
Copy link
Collaborator

Summary

  • Adds rtk scaffold <tool> --strategy <plain|regex|json|ndjson|text> [--dry-run] command that generates new filter modules with all RTK patterns (tracking, tee, exit codes, tests)
  • Adds docs/ADDING_TOOLS.md contributor guide with decision tree, 9-step integration process, and swift test walkthrough
  • Adds smoke tests for all 5 strategies in scripts/test-all.sh

Closes #87

Details

Problem: Adding a new RTK filter takes 2-3h of repetitive boilerplate (60% of the code is identical across modules).

Solution: rtk scaffold generates a ready-to-customize module with:

  • run() function with TimedExecution tracking + tee_and_hint() recovery
  • Strategy-specific filter skeleton (regex with lazy_static!, JSON with serde, state machine for text, etc.)
  • Test scaffolding with count_tokens() helper
  • Exit code preservation for CI/CD

Validation:

  • Tool name validation (alphanumeric + underscore only, no collisions with existing modules)
  • src/ directory existence check with helpful error message
  • --dry-run mode to preview without writing files

Test plan

  • 17 unit tests covering validation, PascalCase, template generation, dry-run mode
  • 369 total tests passing (cargo test)
  • cargo fmt --all && cargo clippy --all-targets clean
  • Manual dry-run tested for all 5 strategies
  • Error cases verified (empty name, invalid chars, existing module)
  • Smoke tests via bash scripts/test-all.sh (requires installed binary)

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings February 16, 2026 21:08
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new contributor-focused CLI command (rtk scaffold) to generate boilerplate for new tool filter modules, plus documentation and smoke tests to standardize/validate the integration workflow.

Changes:

  • Introduces rtk scaffold <tool> --strategy <...> [--dry-run] to generate src/<tool>_cmd.rs templates with tracking/tee/exit-code patterns and starter tests.
  • Adds a contributor guide (docs/ADDING_TOOLS.md) describing strategy selection and a step-by-step integration/testing process.
  • Extends scripts/test-all.sh with smoke coverage for rtk scaffold across all strategies (dry-run).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
src/scaffold_cmd.rs Implements the scaffold command, strategies, module template generation, and unit tests.
src/main.rs Wires the new Scaffold subcommand into the CLI.
scripts/test-all.sh Adds scaffold smoke tests (help + dry-run for each strategy).
docs/ADDING_TOOLS.md New contributor guide for adding tools and using the scaffold workflow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +124 to +127
for (file, messages) in &grouped {{
output.push(format!("{{}} ({{}} issues)", file, messages.len()));
for msg in messages {{
output.push(msg.clone());
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The regex template groups into a HashMap and then iterates for (file, messages) in &grouped, which yields nondeterministic ordering. This can make output unstable (and later tests flaky) across runs. Consider sorting keys (and optionally messages) before emitting output.

Suggested change
for (file, messages) in &grouped {{
output.push(format!("{{}} ({{}} issues)", file, messages.len()));
for msg in messages {{
output.push(msg.clone());
let mut files: Vec<_> = grouped.keys().collect();
files.sort();
for file in files {{
if let Some(messages) = grouped.get(file) {{
output.push(format!("{{}} ({{}} issues)", file, messages.len()));
let mut sorted_messages = messages.clone();
sorted_messages.sort();
for msg in sorted_messages {{
output.push(msg);
}}

Copilot uses AI. Check for mistakes.
// We can't test filesystem collision in unit tests without a real file,
// so we test the logic path indirectly
// The validate function checks Path::new("src/{}_cmd.rs").exists()
// In test context, CWD is the project root, so this should detect real modules
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

test_validate_existing_module_collision currently has no assertions, so it always passes without testing anything. Since the repo already contains modules like src/ruff_cmd.rs, this can be a real unit test (e.g., assert validate_tool_name("ruff") returns an error).

Suggested change
// In test context, CWD is the project root, so this should detect real modules
// In test context, CWD is the project root, so this should detect real modules
let result = validate_tool_name("ruff");
assert!(
result.is_err(),
"expected 'ruff' to be rejected due to an existing src/ruff_cmd.rs module"
);

Copilot uses AI. Check for mistakes.
assert_help "rtk <tool>" rtk <tool> --help
assert_ok "rtk <tool>" rtk <tool> <safe-args>
else
skip "<tool> not installed"
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The smoke-test snippet uses skip "<tool> not installed", but scripts/test-all.sh defines skip_test (not skip). To keep the guide copy-pastable, update the snippet to use the actual helper name used by the script.

Suggested change
skip "<tool> not installed"
skip_test "<tool> not installed"

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +153
let items: Vec<Item> = serde_json::from_str(input)
.context("Failed to parse {} JSON output")?;
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

In the JSON template, .context("Failed to parse {} JSON output") will literally include {} in the error message (it’s not formatted), and the template currently propagates the parse error rather than falling back to raw/truncated output. Consider switching to a formatted context (with_context/format!) and/or handling JSON parse failures the same way existing commands do (return a passthrough/truncated output instead of failing the whole command).

Suggested change
let items: Vec<Item> = serde_json::from_str(input)
.context("Failed to parse {} JSON output")?;
let items: Vec<Item> = match serde_json::from_str(input) {{
Ok(items) => items,
Err(_) => {{
// If JSON parsing fails, fall back to returning the raw JSON output
return Ok(input.to_string());
}}
}};

Copilot uses AI. Check for mistakes.
let (filter_return_type, filter_call) = match strategy {
Strategy::Json => (
"Result<String>",
format!(" let filtered = {}(&raw)?;", filter_fn),
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

filter_call passes &raw (stdout+stderr) into the JSON filter. For tools that emit JSON to stdout but warnings/progress to stderr, this will reliably break JSON parsing in real use. Prefer parsing stdout only (or extracting the JSON object from stdout) while still keeping raw for tracking/tee.

Suggested change
format!(" let filtered = {}(&raw)?;", filter_fn),
// For JSON filters, only parse stdout to avoid stderr noise breaking JSON.
format!(" let filtered = {}(&stdout)?;", filter_fn),

Copilot uses AI. Check for mistakes.
println!("{{}}", filtered);
}}

if !stderr.trim().is_empty() {{
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The generated run() template prints filtered to stdout and then prints (truncated) stderr again to stderr. This can duplicate output and undermines the goal of compact filtered output; it also diverges from existing command modules which typically print only the filtered string. Consider removing this, or gating it behind verbose > 0 (and if removed, truncate may no longer be needed in the template).

Suggested change
if !stderr.trim().is_empty() {{
if verbose > 0 && !stderr.trim().is_empty() {{

Copilot uses AI. Check for mistakes.
@pszymkowiak
Copy link
Collaborator

please resolve conflicts

FlorianBruniaux and others added 4 commits February 18, 2026 22:09
Generates new command filter modules with strategy-based templates
(plain/regex/json/ndjson/text), reducing new filter setup from 2-3h
to ~30min. Includes contributor guide at docs/ADDING_TOOLS.md.

Closes #87

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix CI validate-docs check: update version from 0.18.x to 0.19.0 in
README.md, CLAUDE.md, ARCHITECTURE.md. Add scaffold_cmd and
hook_audit_cmd to ARCHITECTURE.md module list (47 → 49).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…o 0.20.0

- Add scripts/validate-docs.sh to Claude Code pre-commit hook so
  version/module count mismatches are caught before push
- Update version references from 0.19.0 to 0.20.0 in README.md,
  CLAUDE.md, and ARCHITECTURE.md after release-please bump

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "Adding a New Tool" section in README Contributing with
  rtk scaffold usage and link to docs/ADDING_TOOLS.md
- Add GitHub issue template for new tool requests (structured form
  with strategy picker, sample output, contribution willingness)
- Add GitHub issue template for bug reports (version, platform,
  command, actual vs expected, raw output)
- Add config.yml with contact links

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add support or template support for additional compilers

2 participants

Comments