Skip to content

feat!(spec): add support for license, before/after help metadata#542

Merged
jdx merged 4 commits intomainfrom
feat/537-metadata-fields
Mar 13, 2026
Merged

feat!(spec): add support for license, before/after help metadata#542
jdx merged 4 commits intomainfrom
feat/537-metadata-fields

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Mar 13, 2026

Summary

  • Adds parsing, serialization, and merge support for top-level metadata fields: license, before_help, after_help, before_long_help, after_long_help
  • These fields were documented in the spec reference but not supported by the parser

Closes #537

Test plan

  • cargo build --all passes
  • cargo test --all --all-features passes
  • cargo clippy --all --all-features -- -D warnings passes
  • Verify usage lint no longer errors on specs using these fields

🤖 Generated with Claude Code


Note

Low Risk
Low risk, additive metadata fields that extend spec parsing/serialization and merge logic without changing existing required behavior. Main risk is minor formatting/roundtrip differences when emitting KDL for specs that use the new fields.

Overview
Adds support for new top-level spec metadata: license, before_help, after_help, before_help_long/before_long_help, and after_help_long/after_long_help.

Updates the spec parser to recognize these keys, includes them in Spec::merge, and round-trips them in Display (KDL output). Docs models are extended so generated docs/JSON include the new fields, and Spec is marked #[non_exhaustive] for forward-compatible API evolution.

Written by Cursor Bugbot for commit b5ead4a. This will update automatically on new commits. Configure here.

Add parsing, serialization, and merge support for top-level metadata
fields: license, before_help, after_help, before_long_help,
after_long_help.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 13, 2026 13:31
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces comprehensive support for several previously documented but unimplemented top-level metadata fields within the specification parsing and serialization logic. By integrating license, before_help, after_help, before_long_help, and after_long_help, the PR enhances the completeness and robustness of the spec definition, ensuring that all documented fields are properly handled.

Highlights

  • New Metadata Fields: Added parsing, serialization, and merge support for top-level metadata fields: license, before_help, after_help, before_long_help, and after_long_help.
  • Spec Compliance: These newly supported fields were previously documented in the spec reference but lacked functional implementation within the parser.
Changelog
  • lib/src/spec/mod.rs
    • Added new Option<String> fields (license, before_help, after_help, before_long_help, after_long_help) to the Spec struct.
    • Implemented parsing logic within the Spec::parse method to correctly assign values to these new fields from input nodes.
    • Integrated the new fields into the merge_opt! macro calls within the Spec::merge method to handle merging of these optional values.
    • Added serialization logic in the impl Display for Spec block to output these new fields when converting a Spec instance to its string representation.
Activity
  • The author has confirmed that cargo build --all, cargo test --all --all-features, and cargo clippy --all --all-features -- -D warnings all pass.
  • Verification for usage lint not erroring on specs using these fields is pending.
  • The pull request was generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for several new top-level metadata fields: license, before_help, after_help, before_long_help, and after_long_help. The changes correctly handle parsing, serialization, and merging for these new fields.

My review includes two main points:

  1. A suggestion to add markdown variants (e.g., before_help_md) for the new help-related fields to maintain consistency with existing patterns in the codebase.
  2. A refactoring suggestion to reduce code duplication in the serialization logic by using a macro.

Overall, the changes are a good addition and address the missing functionality.

Comment on lines +52 to +60
pub license: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub before_help: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after_help: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub before_long_help: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after_long_help: Option<String>,

Choose a reason for hiding this comment

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

medium

For consistency with about_md and the fields in SpecCommand, consider adding markdown variants for the new help-related fields (e.g., before_help_md, after_help_md, before_long_help_md, after_long_help_md). This would allow for markdown-formatted help text at the top level, which seems to be a pattern in this crate. If you add these, remember to update the parsing, merging, and serialization logic accordingly.

Comment on lines +394 to +418
if let Some(license) = &self.license {
let mut node = KdlNode::new("license");
node.push(KdlEntry::new(KdlValue::String(license.clone())));
nodes.push(node);
}
if let Some(before_help) = &self.before_help {
let mut node = KdlNode::new("before_help");
node.push(KdlEntry::new(KdlValue::String(before_help.clone())));
nodes.push(node);
}
if let Some(after_help) = &self.after_help {
let mut node = KdlNode::new("after_help");
node.push(KdlEntry::new(KdlValue::String(after_help.clone())));
nodes.push(node);
}
if let Some(before_long_help) = &self.before_long_help {
let mut node = KdlNode::new("before_long_help");
node.push(KdlEntry::new(KdlValue::String(before_long_help.clone())));
nodes.push(node);
}
if let Some(after_long_help) = &self.after_long_help {
let mut node = KdlNode::new("after_long_help");
node.push(KdlEntry::new(KdlValue::String(after_long_help.clone())));
nodes.push(node);
}

Choose a reason for hiding this comment

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

medium

This block of code is highly repetitive. To improve maintainability and reduce code duplication, you can use a local macro to generate these KdlNodes for optional fields. This will make the code cleaner and easier to modify, especially if you add the _md variants as suggested in another comment.

        macro_rules! push_opt_node {
            ($nodes:expr, $field:expr, $name:expr) => {
                if let Some(value) = &$field {
                    let mut node = KdlNode::new($name);
                    node.push(KdlEntry::new(value.clone()));
                    $nodes.push(node);
                }
            };
        }
        push_opt_node!(nodes, self.license, "license");
        push_opt_node!(nodes, self.before_help, "before_help");
        push_opt_node!(nodes, self.after_help, "after_help");
        push_opt_node!(nodes, self.before_long_help, "before_long_help");
        push_opt_node!(nodes, self.after_long_help, "after_long_help");

@greptile-apps
Copy link

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR adds parsing, merge, serialization, and docs-model propagation for five new top-level spec metadata fields (license, before_help, after_help, before_help_long, after_help_long) that were previously documented but silently ignored. The change is largely additive and well-structured, with the parser accepting both KDL spelling variants (before_long_help / before_help_long) and normalizing to a canonical form on output — consistent with how about_long/long_about is handled.

Key observations:

  • Breaking API change: #[non_exhaustive] is added to crate::Spec, a fully public struct. Unlike the existing use of this attribute on Parser (which has private fields, making it a no-op), this directly prevents external crates from constructing Spec via struct literal syntax—including struct-update forms like Spec { before_help: ..., ..Default::default() }. This is a semver-incompatible change.
  • Markdown rendering gap: docs::models::Spec::render_md() does not synthesize *_md variants for the new before_help, after_help, before_help_long, and after_help_long fields, unlike SpecCommand::render_md() which does. Template authors relying on these top-level spec fields will receive raw text rather than HTML-rendered markdown.
  • The From<crate::Spec> conversion and field naming in docs::models::Spec are correctly aligned with SpecCommand conventions (before_help_long / after_help_long).

Confidence Score: 3/5

  • Safe to merge for feature functionality, but #[non_exhaustive] introduces an unannounced breaking change to the public API surface that should be intentional and versioned.
  • The feature implementation itself is correct and consistent — parsing, merge, serialization, and docs model propagation all work as intended. Score is reduced because the addition of #[non_exhaustive] to an all-public struct is a semver-breaking change that appears unintentional and is not called out in the PR description or CHANGELOG.
  • lib/src/spec/mod.rs — the #[non_exhaustive] attribute on Spec is the primary concern.

Important Files Changed

Filename Overview
lib/src/spec/mod.rs Adds license, before_help, after_help, before_help_long, after_help_long fields to Spec; parser, merge, and KDL serialization are all updated consistently. However, #[non_exhaustive] is added to this all-public struct, which is a semver-breaking change for external crates constructing Spec with struct literal syntax.
lib/src/docs/models.rs The docs::models::Spec struct and its From<crate::Spec> conversion are correctly updated to include all five new fields. Minor gap: Spec::render_md() does not synthesize *_md variants for the new help fields, unlike SpecCommand::render_md() which does.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[KDL Input] --> B{Parse node key}
    B -->|license| C[schema.license]
    B -->|before_help| D[schema.before_help]
    B -->|after_help| E[schema.after_help]
    B -->|before_long_help\nbefore_help_long| F[schema.before_help_long]
    B -->|after_long_help\nafter_help_long| G[schema.after_help_long]

    C & D & E & F & G --> H[crate::Spec]

    H -->|Spec::merge| H
    H -->|Display / KDL serialize| I[KDL Output\nnormalized keys]
    H -->|From impl| J[docs::models::Spec]

    J --> K[Tera Template\nraw text only]
    J --> L[Spec::render_md\nonly about_md rendered]

    style L fill:#ffe0b2,stroke:#e65100
    style I fill:#e8f5e9,stroke:#2e7d32
Loading

Comments Outside Diff (1)

  1. lib/src/docs/models.rs, line 307-320 (link)

    New help fields not markdown-rendered in Spec::render_md

    SpecCommand::render_md() synthesizes before_help_md and after_help_md from the raw before_help/before_help_long and after_help/after_help_long text (lines 338–351). But the top-level docs::models::Spec::render_md() does not apply any markdown rendering to the five new fields (before_help, after_help, before_help_long, after_help_long, license), and docs::models::Spec has no corresponding *_md variants.

    As a result, if template authors use {{ spec.before_help }} (or the long variants), they will receive raw text rather than HTML-rendered markdown, while the equivalent fields on subcommands ({{ cmd.before_help_md }}) would be properly rendered.

    Consider either:

    • Adding before_help_md / after_help_md fields to docs::models::Spec and populating them in render_md() (mirroring SpecCommand), or
    • Documenting that these spec-level fields are intentionally raw-text only and that markdown rendering is only available at the command level.

    Fix in Claude Code

Fix All in Claude Code

Last reviewed commit: b5ead4a

Copy link
Contributor

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 support in the Rust spec parser/serializer for several top-level metadata keys that are documented in the spec reference but previously rejected as unsupported, addressing issue #537.

Changes:

  • Extend Spec with optional top-level metadata fields: license, before_help, after_help, before_long_help, after_long_help
  • Teach the KDL parser to recognize these keys and populate the Spec
  • Include these fields in Spec::merge and Display (KDL serialization)

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

Comment on lines +160 to +164
"license" => schema.license = Some(node.arg(0)?.ensure_string()?),
"before_help" => schema.before_help = Some(node.arg(0)?.ensure_string()?),
"after_help" => schema.after_help = Some(node.arg(0)?.ensure_string()?),
"before_long_help" => schema.before_long_help = Some(node.arg(0)?.ensure_string()?),
"after_long_help" => schema.after_long_help = Some(node.arg(0)?.ensure_string()?),
@codecov
Copy link

codecov bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 6.81818% with 41 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.37%. Comparing base (15a13d3) to head (7d04d81).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
lib/src/spec/mod.rs 0.00% 24 Missing and 15 partials ⚠️
lib/src/docs/models.rs 60.00% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #542      +/-   ##
==========================================
- Coverage   77.94%   72.37%   -5.57%     
==========================================
  Files          48       48              
  Lines        6682     6831     +149     
  Branches     6682     6831     +149     
==========================================
- Hits         5208     4944     -264     
- Misses       1114     1243     +129     
- Partials      360      644     +284     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

jdx and others added 3 commits March 13, 2026 14:39
- Rename before_long_help/after_long_help to before_help_long/after_help_long
  to match SpecCommand convention
- Accept both KDL spellings (before_long_help and before_help_long)
- Add all new fields to docs::models::Spec and its From<crate::Spec> impl

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
use crate::{SpecArg, SpecComplete, SpecFlag};

#[derive(Debug, Default, Clone, Serialize)]
#[non_exhaustive]
Copy link

Choose a reason for hiding this comment

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

#[non_exhaustive] is a semver-breaking change

Adding #[non_exhaustive] to a public struct with all-public fields is a backward-incompatible change. Any external crate that constructs a Spec instance using struct literal syntax—including the struct update form Spec { before_help: Some("...".into()), ..Default::default() }—will fail to compile after this change.

The other #[non_exhaustive] usage in this codebase (Parser in parse.rs) only has private fields, so the attribute was effectively a no-op there. Here it has real impact because all Spec fields are pub.

If this is intentional (e.g., to future-proof the API for adding fields without semver bumps going forward), it still requires a semver-major bump per the SemVer compatibility rules for Rust crates, or at minimum a CHANGELOG/migration note. If unintentional, the attribute should be removed.

Fix in Claude Code

@jdx jdx changed the title feat(spec): add support for license, before/after help metadata feat!(spec): add support for license, before/after help metadata Mar 13, 2026
@jdx jdx merged commit 9fcfcb7 into main Mar 13, 2026
6 of 9 checks passed
@jdx jdx deleted the feat/537-metadata-fields branch March 13, 2026 14:05
jdx pushed a commit that referenced this pull request Mar 13, 2026
### 🚀 Features

- **(spec)** **breaking** add support for license, before/after help
metadata by [@jdx](https://github.com/jdx) in
[#542](#542)

### 🐛 Bug Fixes

- **(cobra)** escape newlines, tabs, and carriage returns in
kdlQuoteAlways by [@thecodesmith](https://github.com/thecodesmith) in
[#539](#539)
- bump major version for breaking changes in release automation by
[@jdx](https://github.com/jdx) in
[#544](#544)
- add custom_major_increment_regex for breaking change detection by
[@jdx](https://github.com/jdx) in
[#545](#545)
- handle all breaking change commit formats in major bump regex by
[@jdx](https://github.com/jdx) in
[27e1ab1](27e1ab1)
- normalize breaking change commit format in preprocessor by
[@jdx](https://github.com/jdx) in
[aa72b92](aa72b92)

### 📚 Documentation

- add argparse-usage integration by [@jdx](https://github.com/jdx) in
[#531](#531)
- mark KDL code blocks as KDL and use correct inline-comment `//` by
[@muzimuzhi](https://github.com/muzimuzhi) in
[#536](#536)
- fix include syntax to match implementation by
[@jdx](https://github.com/jdx) in
[#540](#540)
- consolidate integration list to single source by
[@jdx](https://github.com/jdx) in
[#541](#541)
- fix link to integrations by [@muzimuzhi](https://github.com/muzimuzhi)
in [#543](#543)

### 🛡️ Security

- **(deps)** update dependency eslint to v10 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#526](#526)

### 🔍 Other Changes

- Added an integration with ruby's OptionParser by
[@packrat386](https://github.com/packrat386) in
[#533](#533)

### 📦️ Dependency Updates

- update actions/setup-node digest to 53b8394 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#525](#525)
- update jdx/mise-action action to v3 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#528](#528)
- update rust crate roff to v1 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#529](#529)

### New Contributors

- @thecodesmith made their first contribution in
[#539](#539)
- @packrat386 made their first contribution in
[#533](#533)
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.

Unsupported top-level metadata license, (before|after)_help, (before|after)_long_help

2 participants