Skip to main content

clap_noun_verb_macros/
lib.rs

1// Copyright (c) 2024 Sean Chatman
2// SPDX-License-Identifier: MIT OR Apache-2.0
3#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
4#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))]
5
6//! Procedural macros for clap-noun-verb
7//!
8//! This crate provides attribute macros `#[noun]` and `#[verb]` for
9//! declarative CLI command registration.
10//!
11//! # I/O Support (v4.0)
12//!
13//! The #[verb] macro now auto-detects clio::Input and clio::Output types
14//! in function parameters and automatically wires them with appropriate
15//! clap configuration.
16//!
17//! # Compile-Time Error Proofing (Poka-Yoke)
18//!
19//! The macro system includes advanced compile-time validation:
20//! - Gap 1: Forgotten #[verb] detection
21//! - Gap 2: Duplicate verb detection
22//! - Gap 3: Return type must implement Serialize
23//! - Gap 4: Enhanced attribute syntax validation
24
25mod io_detection;
26mod rdf_generation;
27mod telemetry_validation;
28mod validation;
29
30// Frontier: Meta-Framework for self-introspection
31// Note: proc-macro crates cannot export modules, only proc_macro functions
32mod meta_framework;
33
34// Frontier: Fractal Pattern macros
35mod macros;
36
37use proc_macro::TokenStream;
38use quote::quote;
39use syn::{parse::Parser, parse_macro_input, ItemFn};
40
41// Note: #[arg(...)] attributes on function parameters cannot be a real proc_macro_attribute
42// because Rust doesn't allow proc_macro_attribute on parameters - only on items.
43// The #[verb] macro parses #[arg(...)] attributes directly from pat_type.attrs.
44//
45// This proc_macro_attribute exists solely so Rust accepts #[arg] on parameters
46// without triggering "unknown attribute" errors. When applied to an item (function,
47// struct, etc.), it emits a compile error — #[arg] should ONLY appear on parameters
48// within #[verb] functions, where it is parsed as raw tokens by the verb macro.
49
50/// Attribute macro for #[arg(...)] on function parameters within #[verb] functions.
51///
52/// **DO NOT apply this to items (functions, structs, etc.)** — it will produce a
53/// compile error. Use it only on function parameters inside a `#[verb]` function.
54///
55/// # Correct usage (on parameters)
56///
57/// ```rust,ignore
58/// #[verb("set")]
59/// fn set_config(
60///     #[arg(env = "SERVER_PORT", default_value = "8080")]
61///     port: u16,
62/// ) -> Result<()> {}
63/// ```
64///
65/// # Incorrect usage (on item) — will error
66///
67/// ```rust,ignore
68/// #[arg(something)]  // ERROR: #[arg] cannot be applied to items
69/// fn my_function() {}
70/// ```
71#[proc_macro_attribute]
72pub fn arg(_args: TokenStream, _input: TokenStream) -> TokenStream {
73    // This macro fires when #[arg] is applied to an item (function, struct, etc.).
74    // On parameters inside #[verb] functions, it never executes — #[verb] reads
75    // the raw attribute tokens directly. So if we're here, it's misuse.
76    syn::Error::new(
77        proc_macro2::Span::call_site(),
78        "#[arg(...)] cannot be applied to items directly. \
79         It should only be used on function parameters within a #[verb] function.\n\
80         \n\
81         Correct:\n  #[verb(\"set\")]\n  fn set(#[arg(default_value = \"80\")] port: u16) {{}}\n\
82         \n\
83         For argument metadata, you can also use doc comment tags:\n  /// [default: 80]\n  /// [env: PORT]\n  port: u16"
84    )
85    .to_compile_error()
86    .into()
87}
88
89/// Attribute macro for generating self-introspecting meta-framework capabilities
90///
91/// Generates RDF introspection methods, optimization queries, capability discovery,
92/// and type-safe wrappers for structs.
93///
94/// # Example
95///
96/// ```rust,ignore
97/// use clap_noun_verb_macros::meta_aware;
98///
99/// #[meta_aware]
100/// struct AgentCapabilities {
101///     name: String,
102///     max_concurrency: usize,
103/// }
104///
105/// let caps = AgentCapabilities { name: "worker".to_string(), max_concurrency: 10 };
106/// let rdf = caps.introspect_capabilities(); // RDF triples
107/// let opts = caps.query_optimizations(); // Optimization hints
108/// ```
109#[proc_macro_attribute]
110pub fn meta_aware(_args: TokenStream, input: TokenStream) -> TokenStream {
111    let input_parsed = parse_macro_input!(input as syn::DeriveInput);
112
113    match meta_framework::generate_meta_aware(input_parsed) {
114        Ok(tokens) => tokens.into(),
115        Err(e) => e.to_compile_error().into(),
116    }
117}
118/// Declare a telemetry span for compile-time validation
119///
120/// This macro creates a span constant and registers it in the distributed slice
121/// for compile-time validation. If the span is never used in a `span!` macro,
122/// compilation will fail.
123///
124/// # Example
125///
126/// ```rust,ignore
127/// use clap_noun_verb_macros::declare_span;
128///
129/// // Declare span
130/// declare_span!(PROCESS_REQUEST, "process_request");
131///
132/// // Use it (required or compilation fails)
133/// fn process() {
134///     span!(PROCESS_REQUEST, {
135///         // ... work ...
136///     })
137/// }
138/// ```
139#[proc_macro]
140pub fn declare_span(input: TokenStream) -> TokenStream {
141    let input = proc_macro2::TokenStream::from(input);
142
143    // Parse: IDENT, "name"
144    let parser = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
145    let args = match Parser::parse2(parser, input) {
146        Ok(args) => args,
147        Err(e) => return e.to_compile_error().into(),
148    };
149
150    if args.len() != 2 {
151        return syn::Error::new_spanned(
152            quote::quote! { #args },
153            "declare_span! requires exactly 2 arguments: IDENT, \"name\"",
154        )
155        .to_compile_error()
156        .into();
157    }
158
159    let ident = match &args[0] {
160        syn::Expr::Path(path) => {
161            if let Some(ident) = path.path.get_ident() {
162                ident.clone()
163            } else {
164                return syn::Error::new_spanned(
165                    &args[0],
166                    "First argument must be an identifier (e.g., PROCESS_REQUEST)",
167                )
168                .to_compile_error()
169                .into();
170            }
171        }
172        _ => {
173            return syn::Error::new_spanned(
174                &args[0],
175                "First argument must be an identifier (e.g., PROCESS_REQUEST)",
176            )
177            .to_compile_error()
178            .into()
179        }
180    };
181
182    let name = match &args[1] {
183        syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) => s.value(),
184        _ => {
185            return syn::Error::new_spanned(
186                &args[1],
187                "Second argument must be a string literal (e.g., \"process_request\")",
188            )
189            .to_compile_error()
190            .into()
191        }
192    };
193
194    let output = telemetry_validation::generate_span_declaration(&ident, &name);
195    output.into()
196}
197
198/// Instrument a code block with a telemetry span
199///
200/// This macro wraps a block of code with span instrumentation and registers
201/// usage for compile-time validation.
202///
203/// # Example
204///
205/// ```rust,ignore
206/// use clap_noun_verb_macros::{declare_span, span};
207///
208/// declare_span!(PROCESS_DATA, "process_data");
209///
210/// fn process() -> Result<Data> {
211///     span!(PROCESS_DATA, {
212///         // Work happens here
213///         Ok(Data::new())
214///     })
215/// }
216/// ```
217#[proc_macro]
218pub fn span(input: TokenStream) -> TokenStream {
219    let input = proc_macro2::TokenStream::from(input);
220
221    // Parse: IDENT, { block }
222    let parser = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
223    let args = match Parser::parse2(parser, input.clone()) {
224        Ok(args) => args,
225        Err(e) => return e.to_compile_error().into(),
226    };
227
228    if args.len() != 2 {
229        return syn::Error::new_spanned(
230            quote::quote! { #input },
231            "span! requires exactly 2 arguments: SPAN_CONST, { block }",
232        )
233        .to_compile_error()
234        .into();
235    }
236
237    let span_ident = match &args[0] {
238        syn::Expr::Path(path) => {
239            if let Some(ident) = path.path.get_ident() {
240                ident.clone()
241            } else {
242                return syn::Error::new_spanned(
243                    &args[0],
244                    "First argument must be a span constant (e.g., PROCESS_REQUEST)",
245                )
246                .to_compile_error()
247                .into();
248            }
249        }
250        _ => {
251            return syn::Error::new_spanned(
252                &args[0],
253                "First argument must be a span constant (e.g., PROCESS_REQUEST)",
254            )
255            .to_compile_error()
256            .into()
257        }
258    };
259
260    let block = &args[1];
261
262    // Register usage (telemetry removed — no-op)
263    let _usage = &span_ident;
264
265    // Generate instrumented code
266    let expanded = quote::quote! {
267        {
268            // Execute block
269            let _result = #block;
270
271            _result
272        }
273    };
274
275    expanded.into()
276}
277
278/// Attribute macro for registering a noun command
279///
280/// Usage:
281/// ```rust,ignore
282/// #[noun("services", "Manage services")]
283/// fn my_function() {}
284/// ```
285#[proc_macro_attribute]
286pub fn noun(_args: TokenStream, input: TokenStream) -> TokenStream {
287    let input_fn = parse_macro_input!(input as ItemFn);
288
289    // DEPRECATED: #[noun] is no longer needed. Nouns are auto-detected from:
290    // - Filename (e.g., papers.rs -> noun "papers")
291    // - Module doc comments (//! ...) for the noun description
292    //
293    // This macro is now a no-op. Remove #[noun(...)] from your code.
294    // The noun name and about arguments are ignored.
295
296    // Remove #[noun] attribute from output (it's been processed)
297    let mut output_fn = input_fn.clone();
298    output_fn.attrs.retain(|attr| {
299        !attr.path().is_ident("noun")
300            && attr.path().segments.last().map(|seg| seg.ident != "noun").unwrap_or(true)
301    });
302
303    // Emit deprecation warning
304    let expanded = quote! {
305        #[deprecated(
306            since = "5.6.0",
307            note = "#[noun] is no longer needed — nouns are auto-detected from filename and module doc comments (//!). Remove this attribute."
308        )]
309        #output_fn
310    };
311
312    expanded.into()
313}
314
315/// Attribute macro for registering a verb command
316///
317/// Usage:
318/// ```rust,ignore
319/// #[verb("status")]
320/// fn show_status() -> Result<Status> {}
321/// ```
322///
323/// # Compile-Time Validation
324///
325/// This macro performs extensive compile-time validation:
326/// - Return type must exist and implement Serialize
327/// - Attribute syntax must be correct (helpful error messages)
328/// - Duplicate verb registration detection
329/// - Parameter attributes (#[arg]) must be valid
330#[proc_macro_attribute]
331pub fn verb(args: TokenStream, input: TokenStream) -> TokenStream {
332    let input_fn = parse_macro_input!(input as ItemFn);
333    let args_tokens = proc_macro2::TokenStream::from(args.clone());
334
335    // GAP 3: Validate return type implements Serialize
336    if let Err(e) = validation::validate_return_type(&input_fn.sig.output, &input_fn.sig.ident) {
337        return e.to_compile_error().into();
338    }
339
340    // GAP 4: Validate attribute syntax with helpful errors
341    if let Err(e) = validation::validate_verb_attribute_syntax(&args_tokens, &input_fn) {
342        return e.to_compile_error().into();
343    }
344
345    // 🛡️ POKA-YOKE FM-1.1: Validate verb function complexity (CLI layer purity)
346    // Prevents business logic from leaking into verb functions
347    if let Err(e) = validation::validate_verb_complexity(&input_fn) {
348        return e.to_compile_error().into();
349    }
350
351    // 🛡️ POKA-YOKE FM-1.2: Validate no CLI types in parameters (domain independence)
352    // Prevents domain functions from depending on CLI types
353    if let Err(e) = validation::validate_no_cli_types_in_params(&input_fn.sig) {
354        return e.to_compile_error().into();
355    }
356
357    // Validate #[arg] attributes on parameters
358    for input in &input_fn.sig.inputs {
359        if let syn::FnArg::Typed(pat_type) = input {
360            if let Err(e) = validation::validate_arg_attribute_syntax(&pat_type.attrs) {
361                return e.to_compile_error().into();
362            }
363        }
364    }
365
366    // Parse verb name from args
367    let parser = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
368    let args_vec: syn::punctuated::Punctuated<_, _> =
369        match Parser::parse2(parser, args_tokens.clone()) {
370            Ok(args) => args,
371            Err(_) => {
372                // If parsing fails, extract verb name from function name
373                let verb_name = extract_verb_name_from_fn_name(&input_fn);
374                let docstring = extract_docstring(&input_fn);
375                let arg_relationships = parse_argument_descriptions_with_relationships(&docstring);
376                return generate_verb_registration(
377                    input_fn,
378                    verb_name,
379                    None,
380                    None,
381                    arg_relationships,
382                );
383            }
384        };
385
386    let verb_name = if args_vec.is_empty() {
387        extract_verb_name_from_fn_name(&input_fn)
388    } else {
389        match &args_vec[0] {
390            syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) => s.value(),
391            _ => {
392                return syn::Error::new_spanned(
393                    &args_vec[0],
394                    "First argument must be a string literal",
395                )
396                .to_compile_error()
397                .into()
398            }
399        }
400    };
401
402    // Extract noun name if provided as second arg, or auto-detect from #[noun] attribute or file context
403    let noun_name = if args_vec.len() > 1 {
404        match &args_vec[1] {
405            syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) => Some(s.value()),
406            _ => None,
407        }
408    } else {
409        // Try to auto-detect noun name:
410        // 1. First check for #[noun] attribute on same function
411        // 2. Then try to infer from filename using file!() macro
412        extract_noun_name_from_attributes(&input_fn)
413            .or_else(|| extract_noun_name_from_file_context(&input_fn))
414    };
415
416    // If verb name was auto-inferred and noun name was auto-detected,
417    // strip the noun name from the verb name if it appears in the function name
418    // Example: show_collector_status() with noun="collector" -> verb="status" (not "collector_status")
419    let verb_name = if args_vec.is_empty() {
420        if let Some(noun) = noun_name.as_ref() {
421            // Check if verb_name starts with noun_name (e.g., "collector_status" starts with "collector")
422            if verb_name.starts_with(noun) && verb_name.len() > noun.len() {
423                // Check if there's a separator (underscore) after the noun
424                if verb_name.as_bytes()[noun.len()] == b'_' {
425                    // Strip noun_ prefix (e.g., "collector_status" -> "status")
426                    verb_name[noun.len() + 1..].to_string()
427                } else {
428                    verb_name
429                }
430            } else {
431                verb_name
432            }
433        } else {
434            verb_name
435        }
436    } else {
437        verb_name
438    };
439
440    // Extract docstring for help text
441    let docstring = extract_docstring(&input_fn);
442
443    // Parse argument descriptions with relationship metadata from docstring
444    let arg_relationships = parse_argument_descriptions_with_relationships(&docstring);
445
446    // Clean docstring for about - remove # Arguments section and relationship tags
447    let clean_about = clean_docstring_for_about(&docstring);
448    generate_verb_registration(input_fn, verb_name, noun_name, Some(clean_about), arg_relationships)
449}
450
451/// Extract verb name from function name (remove common prefixes)
452fn extract_verb_name_from_fn_name(input_fn: &ItemFn) -> String {
453    let fn_name = input_fn.sig.ident.to_string();
454
455    // List of prefixes to strip in order of priority
456    let prefixes = [
457        "show_", "get_", "list_", "create_", "delete_", "update_", "fetch_", "display_", "print_",
458        "run_", "execute_", "check_", "verify_", "start_", "stop_", "restart_", "add_", "remove_",
459        "set_", "unset_",
460    ];
461
462    // Try each prefix
463    for prefix in &prefixes {
464        if let Some(stripped) = fn_name.strip_prefix(prefix) {
465            return stripped.to_string();
466        }
467    }
468
469    // If no prefix matches, return the function name as-is
470    fn_name
471}
472
473/// Extract noun name from filename using file!() macro
474///
475/// Core team approach: Infer noun name from source filename.
476/// Example: `services.rs` -> `"services"`, `user_management.rs` -> `"user_management"`
477fn extract_noun_name_from_file_context(_input_fn: &ItemFn) -> Option<String> {
478    // Core team approach: Infer noun name from filename
479    // We can't access filename at compile time in stable Rust, so we'll extract it
480    // at runtime using file!() macro in the generated code.
481    // Return None here to signal that we should extract from file!() at runtime.
482    None // Will be extracted at runtime using file!() in generated code
483}
484
485// Note: Module doc extraction from `//!` comments is complex in proc macros
486// because we need to parse the entire file. For now, we use function doc as fallback.
487// Future enhancement: Use span information or file parsing to extract module docs.
488
489/// Extract noun name from attributes on same function
490///
491/// Core team approach: Check for helper doc comment first (emitted by #[noun] when #[verb] is present),
492/// then fall back to original #[noun] attribute. This works regardless of macro processing order.
493///
494/// The helper doc comment `#[doc = "__noun_name_internal:name"]` is emitted by #[noun] when it detects
495/// #[verb] is also present, ensuring reliable noun name detection without Rust trying to process it.
496fn extract_noun_name_from_attributes(input_fn: &ItemFn) -> Option<String> {
497    // First, check for helper doc comment emitted by #[noun]
498    // Format: #[doc = "__noun_name_internal:name"]
499    for attr in &input_fn.attrs {
500        if attr.path().is_ident("doc") {
501            if let syn::Meta::NameValue(nv) = &attr.meta {
502                if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) = &nv.value {
503                    let doc_value = s.value();
504                    if let Some(noun_name) = doc_value.strip_prefix("__noun_name_internal:") {
505                        return Some(noun_name.to_string());
506                    }
507                }
508            }
509        }
510    }
511
512    // Fallback: Check for original #[noun] attribute (when #[verb] processes first)
513    for attr in &input_fn.attrs {
514        let is_noun_attr = {
515            if attr.path().is_ident("noun") {
516                true
517            } else {
518                let segments: Vec<_> = attr.path().segments.iter().collect();
519                segments.last().map(|seg| seg.ident == "noun").unwrap_or(false)
520            }
521        };
522
523        if is_noun_attr {
524            if let syn::Meta::List(meta_list) = &attr.meta {
525                let parser =
526                    syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
527                if let Ok(args_vec) = parser.parse2(meta_list.tokens.clone()) {
528                    if !args_vec.is_empty() {
529                        if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) =
530                            &args_vec[0]
531                        {
532                            return Some(s.value());
533                        }
534                    }
535                }
536            }
537        }
538    }
539
540    None
541}
542
543/// Extract docstring from function attributes
544///
545/// Doc comments in syn are stored as Meta::List with "doc" as the path.
546/// Each doc comment line is a separate attribute.
547fn extract_docstring(input_fn: &ItemFn) -> String {
548    input_fn
549        .attrs
550        .iter()
551        .filter_map(|attr| {
552            if attr.path().is_ident("doc") {
553                // Doc comments in syn 2.0 are stored as Meta::NameValue
554                // Format: #[doc = "text"]
555                let meta = &attr.meta;
556                match meta {
557                    syn::Meta::NameValue(nv) => {
558                        if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) =
559                            &nv.value
560                        {
561                            Some(s.value().trim().to_string())
562                        } else {
563                            None
564                        }
565                    }
566                    // Some doc comments might be in List format
567                    syn::Meta::List(list) => {
568                        // Extract tokens from list
569                        let tokens = list.tokens.to_string();
570                        // Remove quotes and extra formatting
571                        Some(tokens.trim_matches('"').trim().to_string())
572                    }
573                    _ => None,
574                }
575            } else {
576                None
577            }
578        })
579        .collect::<Vec<_>>()
580        .join("\n")
581        .trim()
582        .to_string()
583}
584
585/// Argument relationship metadata extracted from doc comments
586///
587/// Typer-like syntax for relationships in doc comments:
588/// - `[group: name]` - Argument belongs to exclusive group "name"
589/// - `[requires: arg]` - Argument requires "arg" to be present
590/// - `[conflicts: arg]` - Argument conflicts with "arg"
591///
592/// Example:
593/// ```text
594/// # Arguments
595/// * `json` - Export as JSON [group: format]
596/// * `yaml` - Export as YAML [group: format]
597/// * `filename` - Output filename [requires: format]
598/// * `raw` - Raw output mode [conflicts: format]
599/// * `config` - Config file [env: APP_CONFIG] [default: config.toml]
600/// * `verbose` - Verbose output [hide]
601/// * `format` - Output format [value_hint: file_path]
602/// * `debug` - Debug mode [global]
603/// * `force` - Force operation [exclusive]
604/// * `output` - Output options [help_heading: Output]
605/// ```
606#[derive(Default, Clone)]
607struct DocArgRelationships {
608    /// Group name (for exclusive groups)
609    group: Option<String>,
610    /// Arguments this one requires
611    requires: Vec<String>,
612    /// Arguments this one conflicts with
613    conflicts_with: Vec<String>,
614    /// Environment variable to read value from
615    env: Option<String>,
616    /// Whether this argument should be hidden from help
617    hide: bool,
618    /// Default value for the argument
619    default_value: Option<String>,
620    /// Value hint for shell completion (file_path, dir_path, url, etc.)
621    value_hint: Option<String>,
622    /// Whether this argument is global (propagates to subcommands)
623    global: bool,
624    /// Whether this argument is exclusive (cannot be used with any other args)
625    exclusive: bool,
626    /// Help heading to group this argument under
627    help_heading: Option<String>,
628    /// Clean description without relationship tags
629    description: String,
630}
631
632/// Remove markdown code fences from text
633///
634/// Removes triple-backtick code blocks (```rust, ```bash, ``` etc.) while preserving
635/// the text content. This prevents code examples from appearing literally in help output.
636///
637/// Examples:
638/// - Input: "Example:\n```rust\nlet x = 5;\n```\nDone."
639/// - Output: "Example:\nlet x = 5;\nDone."
640fn strip_markdown_code_fences(text: &str) -> String {
641    let mut result = String::new();
642    let lines: Vec<&str> = text.lines().collect();
643    let mut i = 0;
644
645    while i < lines.len() {
646        let line = lines[i];
647        let trimmed = line.trim();
648
649        // Check if this line starts a code fence
650        if trimmed.starts_with("```") {
651            // Skip the opening fence line
652            i += 1;
653
654            // Skip lines until we find the closing fence
655            while i < lines.len() {
656                let closing_line = lines[i].trim();
657                if closing_line.starts_with("```") {
658                    // Found closing fence, skip it and move past
659                    i += 1;
660                    break;
661                } else {
662                    // Include content lines (but only if they're meaningful)
663                    let content = lines[i];
664                    if !content.trim().is_empty() {
665                        if !result.is_empty() && !result.ends_with('\n') {
666                            result.push('\n');
667                        }
668                        result.push_str(content);
669                    }
670                    i += 1;
671                }
672            }
673        } else {
674            // Regular line, include it
675            if !result.is_empty() && !result.ends_with('\n') {
676                result.push('\n');
677            }
678            result.push_str(line);
679            i += 1;
680        }
681    }
682
683    result.trim().to_string()
684}
685
686/// Clean docstring for command about text
687///
688/// Removes the `# Arguments` section entirely (clap generates its own help for arguments)
689/// and strips any relationship tags from the remaining text.
690fn clean_docstring_for_about(docstring: &str) -> String {
691    let mut result_lines = Vec::new();
692    let mut in_arguments_section = false;
693
694    for line in docstring.lines() {
695        let trimmed = line.trim();
696
697        // Check if we've entered the Arguments section
698        if trimmed == "# Arguments" || trimmed.starts_with("# Arguments") {
699            in_arguments_section = true;
700            continue;
701        }
702
703        // If we hit another section heading, we're done with Arguments
704        if in_arguments_section && trimmed.starts_with('#') && !trimmed.starts_with("# Arguments") {
705            in_arguments_section = false;
706        }
707
708        // Skip lines in the Arguments section
709        if in_arguments_section {
710            continue;
711        }
712
713        // Strip any relationship tags from the line
714        let clean_line = strip_relationship_tags(line);
715        if !clean_line.is_empty() || line.is_empty() {
716            result_lines.push(clean_line);
717        }
718    }
719
720    // Trim trailing empty lines
721    while result_lines.last().map(|s| s.trim().is_empty()).unwrap_or(false) {
722        result_lines.pop();
723    }
724
725    let joined = result_lines.join("\n").trim().to_string();
726
727    // Remove markdown code fences from the final result
728    strip_markdown_code_fences(&joined)
729}
730
731/// Strip relationship tags from a line of text
732///
733/// Removes [group: xxx], [requires: xxx], [conflicts: xxx], [env: xxx], [hide],
734/// [default: xxx], [value_hint: xxx], [global], [exclusive], [help_heading: xxx] tags.
735fn strip_relationship_tags(text: &str) -> String {
736    let mut result = String::new();
737    let mut remaining = text;
738
739    while !remaining.is_empty() {
740        if let Some(tag_start) = remaining.find('[') {
741            // Add text before the tag
742            result.push_str(&remaining[..tag_start]);
743
744            // Find the end of the tag
745            if let Some(tag_end) = remaining[tag_start..].find(']') {
746                let tag_content = &remaining[tag_start + 1..tag_start + tag_end];
747
748                // Check if this is a relationship tag we should strip
749                let is_relationship_tag = if let Some(colon_pos) = tag_content.find(':') {
750                    let key = tag_content[..colon_pos].trim().to_lowercase();
751                    matches!(
752                        key.as_str(),
753                        "group"
754                            | "requires"
755                            | "require"
756                            | "conflicts"
757                            | "conflicts_with"
758                            | "conflict"
759                            | "env"
760                            | "default"
761                            | "value_hint"
762                            | "help_heading"
763                    )
764                } else {
765                    // Tags without colon
766                    let key = tag_content.trim().to_lowercase();
767                    matches!(key.as_str(), "hide" | "global" | "exclusive")
768                };
769
770                if !is_relationship_tag {
771                    // Keep unknown tags
772                    result.push('[');
773                    result.push_str(tag_content);
774                    result.push(']');
775                }
776
777                // Move past the tag
778                remaining = &remaining[tag_start + tag_end + 1..];
779            } else {
780                // No closing bracket, add rest as-is
781                result.push_str(remaining);
782                break;
783            }
784        } else {
785            // No more tags, add the rest
786            result.push_str(remaining);
787            break;
788        }
789    }
790
791    result
792}
793
794/// Parse relationship metadata from a description string
795///
796/// Extracts all Typer-like tags from argument descriptions:
797/// - `[group: name]` - Argument belongs to exclusive group
798/// - `[requires: arg]` - Argument requires another argument
799/// - `[conflicts: arg]` - Argument conflicts with another argument
800/// - `[env: VAR]` - Read value from environment variable
801/// - `[hide]` - Hide this argument from help
802/// - `[default: value]` - Default value for the argument
803/// - `[value_hint: type]` - Value hint for shell completion
804/// - `[global]` - Propagate to subcommands
805/// - `[exclusive]` - Cannot be used with any other args
806/// - `[help_heading: name]` - Group under help heading
807fn parse_doc_relationships(description: &str) -> DocArgRelationships {
808    let mut result = DocArgRelationships::default();
809    let mut clean_parts = Vec::new();
810
811    // Parse description, extracting relationship tags
812    let mut remaining = description;
813
814    while !remaining.is_empty() {
815        // Look for a tag
816        if let Some(tag_start) = remaining.find('[') {
817            // Add text before the tag
818            let before = remaining[..tag_start].trim();
819            if !before.is_empty() {
820                clean_parts.push(before.to_string());
821            }
822
823            // Find the end of the tag
824            if let Some(tag_end) = remaining[tag_start..].find(']') {
825                let tag_content = &remaining[tag_start + 1..tag_start + tag_end];
826
827                // Parse tag content: "key: value" or "key" (for boolean tags)
828                if let Some(colon_pos) = tag_content.find(':') {
829                    let key = tag_content[..colon_pos].trim().to_lowercase();
830                    let value = tag_content[colon_pos + 1..].trim().to_string();
831
832                    match key.as_str() {
833                        "group" => result.group = Some(value),
834                        "requires" | "require" => result.requires.push(value),
835                        "conflicts" | "conflicts_with" | "conflict" => {
836                            result.conflicts_with.push(value)
837                        }
838                        "env" => result.env = Some(value),
839                        "default" | "default_value" => result.default_value = Some(value),
840                        "value_hint" | "hint" => result.value_hint = Some(value),
841                        "help_heading" | "heading" => result.help_heading = Some(value),
842                        _ => {
843                            // Unknown tag, keep it in the description
844                            clean_parts.push(format!("[{}]", tag_content));
845                        }
846                    }
847                } else {
848                    // Boolean tags without colon (e.g., [hide], [global], [exclusive])
849                    let key = tag_content.trim().to_lowercase();
850                    match key.as_str() {
851                        "hide" | "hidden" => result.hide = true,
852                        "global" => result.global = true,
853                        "exclusive" => result.exclusive = true,
854                        _ => {
855                            // Unknown tag, keep it in the description
856                            clean_parts.push(format!("[{}]", tag_content));
857                        }
858                    }
859                }
860
861                // Move past the tag
862                remaining = &remaining[tag_start + tag_end + 1..];
863            } else {
864                // No closing bracket, add rest as-is
865                clean_parts.push(remaining.to_string());
866                break;
867            }
868        } else {
869            // No more tags, add the rest
870            let rest = remaining.trim();
871            if !rest.is_empty() {
872                clean_parts.push(rest.to_string());
873            }
874            break;
875        }
876    }
877
878    let description = clean_parts.join(" ").trim().to_string();
879
880    // Remove markdown code fences from the description
881    result.description = strip_markdown_code_fences(&description);
882    result
883}
884
885/// Parse argument descriptions WITH relationship metadata from docstring
886///
887/// Similar to parse_argument_descriptions but also extracts Typer-like
888/// relationship tags from the description text.
889fn parse_argument_descriptions_with_relationships(
890    docstring: &str,
891) -> std::collections::HashMap<String, DocArgRelationships> {
892    let mut results = std::collections::HashMap::new();
893
894    // Split docstring into lines
895    let lines: Vec<&str> = docstring.lines().collect();
896    let mut in_arguments_section = false;
897
898    for line in lines {
899        let trimmed = line.trim();
900
901        // Check if we've entered the Arguments section
902        if trimmed == "# Arguments" || trimmed.starts_with("# Arguments") {
903            in_arguments_section = true;
904            continue;
905        }
906
907        // If we hit another section heading, stop parsing
908        if in_arguments_section && trimmed.starts_with('#') && !trimmed.starts_with("# Arguments") {
909            break;
910        }
911
912        // Parse argument description line
913        if in_arguments_section && trimmed.starts_with('*') {
914            let rest = trimmed[1..].trim();
915
916            if let Some(dash_pos) = rest.find('-') {
917                let before_dash = rest[..dash_pos].trim();
918                let description = rest[dash_pos + 1..].trim();
919
920                // Extract argument name (remove backticks and asterisks)
921                let arg_name =
922                    before_dash.trim_start_matches('*').trim().trim_matches('`').trim().to_string();
923
924                if !arg_name.is_empty() && !description.is_empty() {
925                    // Parse relationships from description
926                    let relationships = parse_doc_relationships(description);
927                    results.insert(arg_name, relationships);
928                }
929            }
930        }
931    }
932
933    results
934}
935
936/// Generate verb registration code with full type inference
937fn generate_verb_registration(
938    input_fn: ItemFn,
939    verb_name: String,
940    noun_name: Option<String>,
941    about: Option<String>,
942    arg_relationships: std::collections::HashMap<String, DocArgRelationships>,
943) -> TokenStream {
944    let fn_name = &input_fn.sig.ident;
945    let wrapper_name = quote::format_ident!("__{}_wrapper", fn_name);
946    let init_fn_name = quote::format_ident!("__init_{}", fn_name);
947
948    // Analyze function signature for arguments
949    let mut arg_extractions = Vec::new();
950    let mut arg_calls = Vec::new();
951
952    for input in &input_fn.sig.inputs {
953        if let syn::FnArg::Typed(pat_type) = input {
954            let arg_name = match &*pat_type.pat {
955                syn::Pat::Ident(ident) => &ident.ident,
956                _ => continue,
957            };
958
959            let arg_name_str = arg_name.to_string();
960
961            // Determine if optional (Option<T>) or required
962            let is_option = is_option_type(&pat_type.ty);
963            let inner_type = extract_inner_type(&pat_type.ty);
964            let is_flag = is_bool_type(&pat_type.ty);
965            let is_usize_type = if let syn::Type::Path(type_path) = &*pat_type.ty {
966                type_path.path.segments.last().map(|s| s.ident == "usize").unwrap_or(false)
967            } else {
968                false
969            };
970            let is_vec = is_vec_type(&pat_type.ty);
971            let vec_inner_type =
972                if is_vec { extract_inner_type(&pat_type.ty) } else { (*pat_type.ty).clone() };
973
974            // Parse arg config to check for Count action
975            let arg_config = parse_arg_attributes(&pat_type.attrs);
976            let is_count_action = if let Some(config) = &arg_config {
977                config.action.as_ref().map(|a| a == "count").unwrap_or(false)
978                    || (is_usize_type && !is_option)
979            } else {
980                is_usize_type && !is_option
981            };
982
983            let has_set_false_action = if let Some(config) = &arg_config {
984                config.action.as_ref().map(|a| a == "set_false").unwrap_or(false)
985            } else {
986                false
987            };
988
989            if is_count_action {
990                // Count action - extract count from __handler_input
991                arg_extractions.push(quote! {
992                    let #arg_name: usize = __handler_input.args.get(#arg_name_str)
993                        .and_then(|v| v.parse::<usize>().ok())
994                        .unwrap_or(0);
995                });
996                arg_calls.push(quote! { #arg_name });
997            } else if is_flag {
998                // Boolean flags — the registry stores SetTrue flags in `args`
999                // (always as "true" when present), never in `opts`. Read from
1000                // `args` first, fall back to `opts` for compatibility.
1001                let default_val = if has_set_false_action {
1002                    quote! { true }
1003                } else {
1004                    quote! { false }
1005                };
1006                arg_extractions.push(quote! {
1007                    let #arg_name = __handler_input.args.get(#arg_name_str)
1008                        .or_else(|| __handler_input.opts.get(#arg_name_str))
1009                        .map(|v| v.parse::<bool>().unwrap_or(#default_val))
1010                        .unwrap_or(#default_val);
1011                });
1012                arg_calls.push(quote! { #arg_name });
1013            } else if is_vec {
1014                // Vec<T> types - extract from __handler_input.args as comma-separated string, then parse
1015                // The registry extracts multiple values and joins them
1016                arg_extractions.push(quote! {
1017                    let #arg_name: #pat_type.ty = if let Some(value_str) = __handler_input.args.get(#arg_name_str) {
1018                        // Parse comma-separated values
1019                        value_str.split(',')
1020                            .map(|s| s.trim().parse::<#vec_inner_type>())
1021                            .collect::<Result<Vec<_>, _>>()
1022                            .map_err(|_| ::clap_noun_verb::error::NounVerbError::argument_error(
1023                                format!("Invalid value for argument '{}'", #arg_name_str)
1024                            ))?
1025                    } else {
1026                        Vec::new()
1027                    };
1028                });
1029                arg_calls.push(quote! { #arg_name });
1030            } else if is_option {
1031                // Optional arguments
1032                arg_extractions.push(quote! {
1033                    let #arg_name = __handler_input.args.get(#arg_name_str)
1034                        .and_then(|v| v.parse::<#inner_type>().ok());
1035                });
1036                arg_calls.push(quote! { #arg_name });
1037            } else {
1038                // Required arguments
1039                arg_extractions.push(quote! {
1040                    let #arg_name = __handler_input.args.get(#arg_name_str)
1041                        .ok_or_else(|| ::clap_noun_verb::error::NounVerbError::missing_argument(#arg_name_str))?
1042                        .parse::<#inner_type>()
1043                        .map_err(|_| ::clap_noun_verb::error::NounVerbError::argument_error(
1044                            format!("Invalid value for argument '{}'", #arg_name_str)
1045                        ))?;
1046                });
1047                arg_calls.push(quote! { #arg_name });
1048            }
1049        }
1050    }
1051
1052    // Generate argument metadata for registration
1053    let mut arg_metadata = Vec::new();
1054    for input in &input_fn.sig.inputs {
1055        if let syn::FnArg::Typed(pat_type) = input {
1056            let arg_name = match &*pat_type.pat {
1057                syn::Pat::Ident(ident) => ident.ident.to_string(),
1058                _ => continue,
1059            };
1060
1061            let is_option = is_option_type(&pat_type.ty);
1062            let is_flag = is_bool_type(&pat_type.ty);
1063
1064            // Check if it's usize (can be used with Count action for flags like -v, -vv, -vvv)
1065            let is_usize_type = if let syn::Type::Path(type_path) = &*pat_type.ty {
1066                type_path.path.segments.last().map(|s| s.ident == "usize").unwrap_or(false)
1067            } else {
1068                false
1069            };
1070
1071            // Parse arg config to check for explicit Count action
1072            let arg_config = parse_arg_attributes(&pat_type.attrs);
1073            let has_count_action = if let Some(config) = &arg_config {
1074                config.action.as_ref().map(|a| a == "count").unwrap_or(false)
1075            } else {
1076                false
1077            };
1078
1079            // usize type without Option is treated as a flag (Count action)
1080            let is_flag_type = is_flag || (is_usize_type && !is_option) || has_count_action;
1081
1082            // Get inner type for validation (unwrap Option if needed)
1083            let inner_ty =
1084                if is_option { extract_inner_type(&pat_type.ty) } else { (*pat_type.ty).clone() };
1085
1086            // Auto-infer validation from type
1087            let (mut min_val, mut max_val, mut min_len, mut max_len) =
1088                get_type_validation(&inner_ty);
1089
1090            // Parse validation attributes from parameter (e.g., #[validate(min = 0, max = 100)])
1091            if let Some(validation) = parse_validation_attributes(&pat_type.attrs) {
1092                // Override type-inferred validation with explicit attributes
1093                if let Some(min) = validation.min_value {
1094                    min_val = Some(min);
1095                }
1096                if let Some(max) = validation.max_value {
1097                    max_val = Some(max);
1098                }
1099                if let Some(min) = validation.min_length {
1100                    min_len = Some(min);
1101                }
1102                if let Some(max) = validation.max_length {
1103                    max_len = Some(max);
1104                }
1105            }
1106
1107            // Parse argument attributes (e.g., #[arg(short = 'v', default_value = "50")])
1108            // Note: arg_config already parsed above for is_flag_type check
1109
1110            // Auto-detect multiple values from Vec<T> type
1111            let is_vec_type = is_vec_type(&inner_ty);
1112            let multiple_values =
1113                arg_config.as_ref().map(|c| c.multiple).unwrap_or(false) || is_vec_type;
1114
1115            // Auto-infer action: usize type → Count (for flags like -v, -vv, -vvv),
1116            // bool flags → SetTrue (unless overridden)
1117            let inferred_action = if (is_usize_type && !is_option) || has_count_action {
1118                // usize type without Option is inferred as Count (for -v, -vv, -vvv patterns)
1119                Some("count".to_string())
1120            } else if is_flag && arg_config.as_ref().and_then(|c| c.action.as_ref()).is_none() {
1121                Some("set_true".to_string()) // Default for bool flags
1122            } else {
1123                None
1124            };
1125
1126            let min_value_token = if let Some(min) = min_val {
1127                quote! { Some(#min.to_string()) }
1128            } else {
1129                quote! { None }
1130            };
1131
1132            let max_value_token = if let Some(max) = max_val {
1133                quote! { Some(#max.to_string()) }
1134            } else {
1135                quote! { None }
1136            };
1137
1138            let min_length_token = if let Some(min) = min_len {
1139                quote! { Some(#min) }
1140            } else {
1141                quote! { None }
1142            };
1143
1144            let max_length_token = if let Some(max) = max_len {
1145                quote! { Some(#max) }
1146            } else {
1147                quote! { None }
1148            };
1149
1150            // Get help text - priority: #[arg(help = "...")] > docstring > default
1151            // Note: For docstring, use the clean description (without relationship tags)
1152            let help_token = if let Some(config) = &arg_config {
1153                if let Some(ref explicit_help) = config.help {
1154                    quote! { Some(#explicit_help.to_string()) }
1155                } else if let Some(rel) = arg_relationships.get(&arg_name) {
1156                    let help = &rel.description;
1157                    if !help.is_empty() {
1158                        quote! { Some(#help.to_string()) }
1159                    } else {
1160                        quote! { None }
1161                    }
1162                } else {
1163                    quote! { None }
1164                }
1165            } else if let Some(rel) = arg_relationships.get(&arg_name) {
1166                let help = &rel.description;
1167                if !help.is_empty() {
1168                    quote! { Some(#help.to_string()) }
1169                } else {
1170                    quote! { None }
1171                }
1172            } else {
1173                quote! { None }
1174            };
1175
1176            // Get long_help from #[arg(long_help = "...")]
1177            let long_help_token = if let Some(config) = &arg_config {
1178                if let Some(ref lh) = config.long_help {
1179                    quote! { Some(#lh.to_string()) }
1180                } else {
1181                    quote! { None }
1182                }
1183            } else {
1184                quote! { None }
1185            };
1186
1187            // Generate tokens for argument attributes
1188            let short_token = if let Some(config) = &arg_config {
1189                if let Some(s) = config.short {
1190                    quote! { Some(#s) }
1191                } else {
1192                    quote! { None }
1193                }
1194            } else {
1195                quote! { None }
1196            };
1197
1198            let default_value_token = if let Some(config) = &arg_config {
1199                if let Some(ref dv) = config.default_value {
1200                    quote! { Some(#dv.to_string()) }
1201                } else {
1202                    quote! { None }
1203                }
1204            } else {
1205                quote! { None }
1206            };
1207
1208            let env_token = if let Some(config) = &arg_config {
1209                if let Some(ref e) = config.env {
1210                    quote! { Some(#e.to_string()) }
1211                } else {
1212                    quote! { None }
1213                }
1214            } else {
1215                quote! { None }
1216            };
1217
1218            let value_name_token = if let Some(config) = &arg_config {
1219                if let Some(ref vn) = config.value_name {
1220                    quote! { Some(#vn.to_string()) }
1221                } else {
1222                    quote! { None }
1223                }
1224            } else {
1225                quote! { None }
1226            };
1227
1228            let aliases_token = if let Some(config) = &arg_config {
1229                if !config.aliases.is_empty() {
1230                    let aliases_vec = &config.aliases;
1231                    quote! { vec![#(#aliases_vec.to_string()),*] }
1232                } else {
1233                    quote! { vec![] }
1234                }
1235            } else {
1236                quote! { vec![] }
1237            };
1238
1239            let positional_token = if let Some(config) = &arg_config {
1240                if let Some(pos) = config.positional {
1241                    quote! { Some(#pos) }
1242                } else {
1243                    quote! { None }
1244                }
1245            } else {
1246                quote! { None }
1247            };
1248
1249            // Generate action token - use explicit action if provided, otherwise use inferred
1250            let action_token = if let Some(config) = &arg_config {
1251                if let Some(ref act) = config.action {
1252                    // Parse action string to ArgAction
1253                    match act.as_str() {
1254                        "count" => quote! { Some(::clap_noun_verb::ArgAction::Count) },
1255                        "set" => quote! { Some(::clap_noun_verb::ArgAction::Set) },
1256                        "set_false" => quote! { Some(::clap_noun_verb::ArgAction::SetFalse) },
1257                        "set_true" => quote! { Some(::clap_noun_verb::ArgAction::SetTrue) },
1258                        "append" => quote! { Some(::clap_noun_verb::ArgAction::Append) },
1259                        _ => quote! { None },
1260                    }
1261                } else if let Some(ref inferred) = inferred_action {
1262                    match inferred.as_str() {
1263                        "count" => quote! { Some(::clap_noun_verb::ArgAction::Count) },
1264                        "set_true" => quote! { Some(::clap_noun_verb::ArgAction::SetTrue) },
1265                        _ => quote! { None },
1266                    }
1267                } else {
1268                    quote! { None }
1269                }
1270            } else if let Some(ref inferred) = inferred_action {
1271                match inferred.as_str() {
1272                    "count" => quote! { Some(::clap_noun_verb::ArgAction::Count) },
1273                    "set_true" => quote! { Some(::clap_noun_verb::ArgAction::SetTrue) },
1274                    _ => quote! { None },
1275                }
1276            } else {
1277                quote! { None }
1278            };
1279
1280            // Get group from doc comment or #[arg] attribute
1281            // Priority: #[arg(group = "...")] > doc comment [group: ...]
1282            let doc_rel = arg_relationships.get(&arg_name);
1283            let group_token = if let Some(config) = &arg_config {
1284                if let Some(ref g) = config.group {
1285                    quote! { Some(#g.to_string()) }
1286                } else if let Some(rel) = doc_rel {
1287                    if let Some(ref g) = rel.group {
1288                        quote! { Some(#g.to_string()) }
1289                    } else {
1290                        quote! { None }
1291                    }
1292                } else {
1293                    quote! { None }
1294                }
1295            } else if let Some(rel) = doc_rel {
1296                if let Some(ref g) = rel.group {
1297                    quote! { Some(#g.to_string()) }
1298                } else {
1299                    quote! { None }
1300                }
1301            } else {
1302                quote! { None }
1303            };
1304
1305            // Get requires from doc comment or #[arg] attribute
1306            // Merges both sources if present
1307            let requires_token = {
1308                let mut requires_all = Vec::new();
1309
1310                // Add from #[arg(requires = "...")]
1311                if let Some(config) = &arg_config {
1312                    requires_all.extend(config.requires.iter().cloned());
1313                }
1314
1315                // Add from doc comment [requires: ...]
1316                if let Some(rel) = doc_rel {
1317                    for req in &rel.requires {
1318                        if !requires_all.contains(req) {
1319                            requires_all.push(req.clone());
1320                        }
1321                    }
1322                }
1323
1324                if requires_all.is_empty() {
1325                    quote! { vec![] }
1326                } else {
1327                    quote! { vec![#(#requires_all.to_string()),*] }
1328                }
1329            };
1330
1331            // Get conflicts_with from doc comment or #[arg] attribute
1332            // Merges both sources if present
1333            let conflicts_with_token = {
1334                let mut conflicts_all = Vec::new();
1335
1336                // Add from #[arg(conflicts_with = "...")]
1337                if let Some(config) = &arg_config {
1338                    conflicts_all.extend(config.conflicts_with.iter().cloned());
1339                }
1340
1341                // Add from doc comment [conflicts: ...]
1342                if let Some(rel) = doc_rel {
1343                    for conf in &rel.conflicts_with {
1344                        if !conflicts_all.contains(conf) {
1345                            conflicts_all.push(conf.clone());
1346                        }
1347                    }
1348                }
1349
1350                if conflicts_all.is_empty() {
1351                    quote! { vec![] }
1352                } else {
1353                    quote! { vec![#(#conflicts_all.to_string()),*] }
1354                }
1355            };
1356
1357            // Handle value_parser
1358            // Extract TokenStream string representation if explicit value_parser was specified
1359            let value_parser_token = if let Some(config) = &arg_config {
1360                if let Some(ref vp_ts) = config.value_parser {
1361                    // Explicit value_parser specified - extract string representation
1362                    // The TokenStream contains a string literal with the expression
1363                    let ts_str = vp_ts.to_string();
1364                    // Extract the actual expression string from the literal
1365                    let expr_str = if ts_str.starts_with('"') && ts_str.ends_with('"') {
1366                        ts_str.trim_matches('"').to_string()
1367                    } else {
1368                        ts_str
1369                    };
1370                    quote! { Some(#expr_str.to_string()) }
1371                } else {
1372                    // Try auto-inference
1373                    let inferred_parser = infer_type_parser(&inner_ty);
1374                    if let Some(ref parser) = inferred_parser {
1375                        quote! { Some(#parser.to_string()) }
1376                    } else {
1377                        quote! { None }
1378                    }
1379                }
1380            } else {
1381                // Try auto-inference
1382                let inferred_parser = infer_type_parser(&inner_ty);
1383                if let Some(ref parser) = inferred_parser {
1384                    quote! { Some(#parser.to_string()) }
1385                } else {
1386                    quote! { None }
1387                }
1388            };
1389
1390            let next_line_help_token = if let Some(config) = &arg_config {
1391                let value = config.next_line_help;
1392                quote! { #value }
1393            } else {
1394                quote! { false }
1395            };
1396
1397            let display_order_token = if let Some(config) = &arg_config {
1398                if let Some(order) = config.display_order {
1399                    quote! { Some(#order) }
1400                } else {
1401                    quote! { None }
1402                }
1403            } else {
1404                quote! { None }
1405            };
1406
1407            let exclusive_token = if let Some(config) = &arg_config {
1408                if let Some(excl) = config.exclusive {
1409                    quote! { Some(#excl) }
1410                } else {
1411                    quote! { None }
1412                }
1413            } else {
1414                quote! { None }
1415            };
1416
1417            let trailing_vararg_token = if let Some(config) = &arg_config {
1418                let value = config.trailing_vararg;
1419                quote! { #value }
1420            } else {
1421                quote! { false }
1422            };
1423
1424            let allow_negative_numbers_token = if let Some(config) = &arg_config {
1425                let value = config.allow_negative_numbers;
1426                quote! { #value }
1427            } else {
1428                quote! { false }
1429            };
1430
1431            // New tokens for extended doc comment tags
1432            // Priority: #[arg] attribute > doc comment tag
1433
1434            // Get env from doc comment (already have env_token from #[arg])
1435            let doc_env_token = if let Some(rel) = doc_rel {
1436                if let Some(ref env_var) = rel.env {
1437                    quote! { Some(#env_var.to_string()) }
1438                } else {
1439                    quote! { None }
1440                }
1441            } else {
1442                quote! { None }
1443            };
1444            // Merge: prefer #[arg(env)] if present, else use doc comment
1445            let final_env_token = if let Some(config) = &arg_config {
1446                if config.env.is_some() {
1447                    env_token.clone()
1448                } else {
1449                    doc_env_token
1450                }
1451            } else {
1452                doc_env_token
1453            };
1454
1455            // Get hide from doc comment
1456            let hide_token = if let Some(rel) = doc_rel {
1457                let hide_val = rel.hide;
1458                quote! { #hide_val }
1459            } else {
1460                quote! { false }
1461            };
1462
1463            // Get default_value from doc comment (already have default_value_token from #[arg])
1464            let doc_default_token = if let Some(rel) = doc_rel {
1465                if let Some(ref dv) = rel.default_value {
1466                    quote! { Some(#dv.to_string()) }
1467                } else {
1468                    quote! { None }
1469                }
1470            } else {
1471                quote! { None }
1472            };
1473            // Merge: prefer #[arg(default_value)] if present, else use doc comment
1474            let final_default_token = if let Some(config) = &arg_config {
1475                if config.default_value.is_some() {
1476                    default_value_token.clone()
1477                } else {
1478                    doc_default_token
1479                }
1480            } else {
1481                doc_default_token
1482            };
1483
1484            // Get value_hint from doc comment
1485            let value_hint_token = if let Some(rel) = doc_rel {
1486                if let Some(ref vh) = rel.value_hint {
1487                    quote! { Some(#vh.to_string()) }
1488                } else {
1489                    quote! { None }
1490                }
1491            } else {
1492                quote! { None }
1493            };
1494
1495            // Get global from doc comment
1496            let global_token = if let Some(rel) = doc_rel {
1497                let global_val = rel.global;
1498                quote! { #global_val }
1499            } else {
1500                quote! { false }
1501            };
1502
1503            // Get exclusive from doc comment (merge with #[arg(exclusive)])
1504            let doc_exclusive_token = if let Some(rel) = doc_rel {
1505                if rel.exclusive {
1506                    quote! { Some(true) }
1507                } else {
1508                    quote! { None }
1509                }
1510            } else {
1511                quote! { None }
1512            };
1513            let final_exclusive_token = if let Some(config) = &arg_config {
1514                if config.exclusive.is_some() {
1515                    exclusive_token.clone()
1516                } else {
1517                    doc_exclusive_token
1518                }
1519            } else {
1520                doc_exclusive_token
1521            };
1522
1523            // Get help_heading from doc comment
1524            let help_heading_token = if let Some(rel) = doc_rel {
1525                if let Some(ref hh) = rel.help_heading {
1526                    quote! { Some(#hh.to_string()) }
1527                } else {
1528                    quote! { None }
1529                }
1530            } else {
1531                quote! { None }
1532            };
1533
1534            arg_metadata.push(quote! {
1535                ::clap_noun_verb::cli::registry::ArgMetadata {
1536                    name: #arg_name.to_string(),
1537                    required: !#is_option,
1538                    is_flag: #is_flag_type,
1539                    help: #help_token,
1540                    min_value: #min_value_token,
1541                    max_value: #max_value_token,
1542                    min_length: #min_length_token,
1543                    max_length: #max_length_token,
1544                    short: #short_token,
1545                    default_value: #final_default_token,
1546                    env: #final_env_token,
1547                    multiple: #multiple_values,
1548                    value_name: #value_name_token,
1549                    aliases: #aliases_token,
1550                    positional: #positional_token,
1551                    action: #action_token,
1552                    group: #group_token,
1553                    requires: #requires_token,
1554                    conflicts_with: #conflicts_with_token,
1555                    value_parser: #value_parser_token,
1556                    hide: #hide_token,
1557                    next_help_heading: #help_heading_token,
1558                    long_help: #long_help_token,
1559                    next_line_help: #next_line_help_token,
1560                    display_order: #display_order_token,
1561                    exclusive: #final_exclusive_token,
1562                    trailing_vararg: #trailing_vararg_token,
1563                    allow_negative_numbers: #allow_negative_numbers_token,
1564                    value_hint: #value_hint_token,
1565                    global: #global_token,
1566                }
1567            });
1568        }
1569    }
1570
1571    // GAP 2: Generate duplicate verb detection
1572    let noun_name_for_check = noun_name.as_deref().unwrap_or("__auto__");
1573    let duplicate_check =
1574        validation::generate_duplicate_detection(&verb_name, noun_name_for_check, fn_name);
1575
1576    // Telemetry instrumentation removed (no-op)
1577    let _telemetry_instrumentation = ();
1578
1579    // Generate wrapper function
1580    // Empty string "" means root-level verb (no noun)
1581    // "__auto__" means auto-infer from filename
1582    let noun_name_str = noun_name.as_deref().unwrap_or("__auto__");
1583    let about_str = about.as_deref().unwrap_or("");
1584
1585    let mut output_fn = input_fn.clone();
1586    output_fn.attrs.retain(|attr| {
1587        let is_noun = attr.path().is_ident("noun")
1588            || attr.path().segments.last().map(|seg| seg.ident == "noun").unwrap_or(false);
1589        !is_noun
1590    });
1591
1592    // Strip #[arg] and #[validate] attributes from parameters in output_fn
1593    for input in &mut output_fn.sig.inputs {
1594        if let syn::FnArg::Typed(pat_type) = input {
1595            pat_type.attrs.retain(|attr| {
1596                let is_arg = attr.path().is_ident("arg")
1597                    || attr.path().segments.last().map(|seg| seg.ident == "arg").unwrap_or(false);
1598                let is_validate = attr.path().is_ident("validate")
1599                    || attr
1600                        .path()
1601                        .segments
1602                        .last()
1603                        .map(|seg| seg.ident == "validate")
1604                        .unwrap_or(false);
1605                !is_arg && !is_validate
1606            });
1607        }
1608    }
1609
1610    let expanded = quote! {
1611        #output_fn
1612
1613        // GAP 2: Compile-time duplicate verb detection
1614        #duplicate_check
1615
1616        // Wrapper function that adapts HandlerInput to function signature
1617        // NOTE: Use __handler_input to avoid shadowing if user has an arg named "input"
1618        fn #wrapper_name(__handler_input: ::clap_noun_verb::logic::HandlerInput) -> ::clap_noun_verb::error::Result<::clap_noun_verb::logic::HandlerOutput> {
1619            // Execute handler with argument extraction
1620            #(#arg_extractions)*
1621            let result = #fn_name(#(#arg_calls),*)?;
1622
1623            ::clap_noun_verb::logic::HandlerOutput::from_data(result)
1624        }
1625
1626        // Auto-generated registration
1627        // CRITICAL FIX: Use named function instead of closure to satisfy fn() type requirement
1628        #[allow(non_upper_case_globals)]
1629        #[linkme::distributed_slice(::clap_noun_verb::cli::registry::__VERB_REGISTRY)]
1630        static #init_fn_name: fn() = {
1631            fn __register_impl() {
1632                // Core team approach: Auto-infer noun name from filename if not explicitly provided
1633                // Special case: "root" means root-level verb (no noun)
1634                let (noun_name_static, noun_about_static, verb_name_final) = if #noun_name_str == "root" {
1635                    // Root-level verb: no noun, verb appears directly under CLI binary
1636                    // Pass empty string to registry to signal root verb
1637                    ("", "", #verb_name)
1638                } else if #noun_name_str == "__auto__" {
1639                    // Extract noun name from filename using file!() macro
1640                    let file_path = file!();
1641                    let inferred_name = ::std::path::Path::new(file_path)
1642                        .file_stem()
1643                        .and_then(|s| s.to_str())
1644                        .unwrap_or("unknown")
1645                        .to_string();
1646
1647                    // If verb name was auto-inferred, strip noun name from verb name if it appears
1648                    // Example: show_collector_status() -> verb_name="collector_status", noun="collector" -> verb="status"
1649                    let mut final_verb_name = #verb_name.to_string();
1650                    if final_verb_name.starts_with(&inferred_name) && final_verb_name.len() > inferred_name.len() {
1651                        if final_verb_name.as_bytes()[inferred_name.len()] == b'_' {
1652                            // Strip noun_ prefix (e.g., "collector_status" -> "status")
1653                            final_verb_name = final_verb_name[inferred_name.len() + 1..].to_string();
1654                        }
1655                    }
1656
1657                    // Extract module doc comments (//! ...) from source file
1658                    // The noun description comes from the file's module-level docs,
1659                    // not from the verb's function docs.
1660                    let file_path = file!();
1661                    let noun_about = ::std::fs::read_to_string(file_path)
1662                        .ok()
1663                        .and_then(|content| {
1664                            let lines: Vec<&str> = content
1665                                .lines()
1666                                .take(20)
1667                                .filter(|line| line.trim_start().starts_with("//!"))
1668                                .map(|line| line.trim_start_matches("//!").trim())
1669                                .collect();
1670                            if lines.is_empty() { None } else { Some(lines.join("\n")) }
1671                        })
1672                        .unwrap_or_default();
1673
1674                    // Leak strings to get static lifetime for registration (acceptable for CLI construction)
1675                    let name_static: &'static str = Box::leak(inferred_name.into_boxed_str());
1676                    let about_static: &'static str = Box::leak(noun_about.into_boxed_str());
1677                    let verb_static: &'static str = Box::leak(final_verb_name.into_boxed_str());
1678
1679                    // Auto-register noun with inferred name and doc
1680                    ::clap_noun_verb::cli::registry::CommandRegistry::register_noun(
1681                        name_static,
1682                        about_static,
1683                    );
1684
1685                    (name_static, about_static, verb_static)
1686                } else {
1687                    // Leak explicit noun name and about to get static lifetime
1688                    let name_static: &'static str = Box::leak(#noun_name_str.to_string().into_boxed_str());
1689                    let about_static: &'static str = Box::leak(String::new().into_boxed_str());
1690                    let verb_static: &'static str = #verb_name;
1691
1692                    // BUGFIX: Auto-register noun even with explicit noun name
1693                    // This ensures the noun exists even if no #[noun] attribute was used
1694                    ::clap_noun_verb::cli::registry::CommandRegistry::register_noun(
1695                        name_static,
1696                        about_static,
1697                    );
1698
1699                    (name_static, about_static, verb_static)
1700                };
1701
1702                let args = vec![#(#arg_metadata),*];
1703                ::clap_noun_verb::cli::registry::CommandRegistry::register_verb_with_args::<_>(
1704                    noun_name_static,
1705                    verb_name_final,
1706                    #about_str,
1707                    args,
1708                    #wrapper_name,
1709                );
1710            }
1711            __register_impl  // Return function pointer (not a call!)
1712        };
1713    };
1714
1715    expanded.into()
1716}
1717
1718/// Check if type is Option<T>
1719fn is_option_type(ty: &syn::Type) -> bool {
1720    if let syn::Type::Path(type_path) = ty {
1721        if let Some(segment) = type_path.path.segments.last() {
1722            segment.ident == "Option"
1723        } else {
1724            false
1725        }
1726    } else {
1727        false
1728    }
1729}
1730
1731/// Check if type is bool
1732fn is_bool_type(ty: &syn::Type) -> bool {
1733    if let syn::Type::Path(type_path) = ty {
1734        if let Some(segment) = type_path.path.segments.last() {
1735            segment.ident == "bool"
1736        } else {
1737            false
1738        }
1739    } else {
1740        false
1741    }
1742}
1743
1744/// Check if type is Vec<T>
1745fn is_vec_type(ty: &syn::Type) -> bool {
1746    if let syn::Type::Path(type_path) = ty {
1747        if let Some(segment) = type_path.path.segments.last() {
1748            segment.ident == "Vec"
1749        } else {
1750            false
1751        }
1752    } else {
1753        false
1754    }
1755}
1756
1757/// Validation constraints parsed from attributes
1758struct ValidationConstraints {
1759    min_value: Option<String>,
1760    max_value: Option<String>,
1761    min_length: Option<usize>,
1762    max_length: Option<usize>,
1763}
1764
1765/// Argument configuration parsed from #[arg(...)] attributes
1766struct ArgConfig {
1767    short: Option<char>,
1768    default_value: Option<String>,
1769    env: Option<String>,
1770    multiple: bool,
1771    value_name: Option<String>,
1772    aliases: Vec<String>,
1773    positional: Option<usize>,
1774    action: Option<String>, // Store as string: "count", "set", "set_false", "set_true", "append"
1775    group: Option<String>,
1776    requires: Vec<String>,
1777    conflicts_with: Vec<String>,
1778    value_parser: Option<proc_macro2::TokenStream>, // Store TokenStream for compile-time expansion
1779    help: Option<String>,                           // Override docstring help
1780    long_help: Option<String>,                      // Long help text
1781    next_line_help: bool,                           // Next line help formatting
1782    display_order: Option<usize>,                   // Display order in help
1783    exclusive: Option<bool>,                        // Exclusive group flag
1784    trailing_vararg: bool,                          // Trailing varargs support
1785    allow_negative_numbers: bool,                   // Allow negative numbers
1786}
1787
1788/// Parse argument attributes from parameter attributes
1789///
1790/// Parses `#[arg(short = 'v', default_value = "50", env = "PORT", multiple, value_name = "FILE")]` attributes
1791fn parse_arg_attributes(attrs: &[syn::Attribute]) -> Option<ArgConfig> {
1792    for attr in attrs {
1793        if attr.path().is_ident("arg") {
1794            if let syn::Meta::List(list) = &attr.meta {
1795                // Parse tokens manually to handle both flags (just names) and key-value pairs
1796                let mut config = ArgConfig {
1797                    short: None,
1798                    default_value: None,
1799                    env: None,
1800                    multiple: false,
1801                    value_name: None,
1802                    aliases: Vec::new(),
1803                    positional: None,
1804                    action: None,
1805                    group: None,
1806                    requires: Vec::new(),
1807                    conflicts_with: Vec::new(),
1808                    value_parser: None,
1809                    help: None,
1810                    long_help: None,
1811                    next_line_help: false,
1812                    display_order: None,
1813                    exclusive: None,
1814                    trailing_vararg: false,
1815                    allow_negative_numbers: false,
1816                };
1817
1818                // Try parsing as MetaList first (handles key=value pairs)
1819                let parser =
1820                    syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated;
1821                if let Ok(meta_list) = parser.parse2(list.tokens.clone()) {
1822                    for meta in meta_list {
1823                        match &meta {
1824                            syn::Meta::NameValue(nv) => {
1825                                let ident = nv.path.get_ident()?.to_string();
1826                                match ident.as_str() {
1827                                    "short" => {
1828                                        // Parse short = 'v' or short = "v"
1829                                        if let syn::Expr::Lit(syn::ExprLit {
1830                                            lit: syn::Lit::Char(c),
1831                                            ..
1832                                        }) = &nv.value
1833                                        {
1834                                            config.short = Some(c.value());
1835                                        } else if let syn::Expr::Lit(syn::ExprLit {
1836                                            lit: syn::Lit::Str(s),
1837                                            ..
1838                                        }) = &nv.value
1839                                        {
1840                                            let s_val = s.value();
1841                                            if s_val.len() == 1 {
1842                                                config.short = s_val.chars().next();
1843                                            }
1844                                        }
1845                                    }
1846                                    "default_value" => {
1847                                        if let syn::Expr::Lit(syn::ExprLit {
1848                                            lit: syn::Lit::Str(s),
1849                                            ..
1850                                        }) = &nv.value
1851                                        {
1852                                            config.default_value = Some(s.value());
1853                                        }
1854                                    }
1855                                    "env" => {
1856                                        if let syn::Expr::Lit(syn::ExprLit {
1857                                            lit: syn::Lit::Str(s),
1858                                            ..
1859                                        }) = &nv.value
1860                                        {
1861                                            config.env = Some(s.value());
1862                                        }
1863                                    }
1864                                    "value_name" => {
1865                                        if let syn::Expr::Lit(syn::ExprLit {
1866                                            lit: syn::Lit::Str(s),
1867                                            ..
1868                                        }) = &nv.value
1869                                        {
1870                                            config.value_name = Some(s.value());
1871                                        }
1872                                    }
1873                                    "aliases" => {
1874                                        // Parse aliases = ["verbose", "v"]
1875                                        if let syn::Expr::Array(arr) = &nv.value {
1876                                            for expr in &arr.elems {
1877                                                if let syn::Expr::Lit(syn::ExprLit {
1878                                                    lit: syn::Lit::Str(s),
1879                                                    ..
1880                                                }) = expr
1881                                                {
1882                                                    config.aliases.push(s.value());
1883                                                }
1884                                            }
1885                                        }
1886                                    }
1887                                    "alias" => {
1888                                        // Parse alias = "verbose" (single alias)
1889                                        if let syn::Expr::Lit(syn::ExprLit {
1890                                            lit: syn::Lit::Str(s),
1891                                            ..
1892                                        }) = &nv.value
1893                                        {
1894                                            config.aliases.push(s.value());
1895                                        }
1896                                    }
1897                                    "index" => {
1898                                        // Parse index = 0 (positional argument index)
1899                                        if let syn::Expr::Lit(syn::ExprLit {
1900                                            lit: syn::Lit::Int(i),
1901                                            ..
1902                                        }) = &nv.value
1903                                        {
1904                                            if let Ok(index) = i.base10_parse::<usize>() {
1905                                                config.positional = Some(index);
1906                                            }
1907                                        }
1908                                    }
1909                                    "action" => {
1910                                        // Parse action = "count", action = "set_false", etc.
1911                                        if let syn::Expr::Lit(syn::ExprLit {
1912                                            lit: syn::Lit::Str(s),
1913                                            ..
1914                                        }) = &nv.value
1915                                        {
1916                                            config.action = Some(s.value());
1917                                        }
1918                                    }
1919                                    "group" => {
1920                                        // Parse group = "filter"
1921                                        if let syn::Expr::Lit(syn::ExprLit {
1922                                            lit: syn::Lit::Str(s),
1923                                            ..
1924                                        }) = &nv.value
1925                                        {
1926                                            config.group = Some(s.value());
1927                                        }
1928                                    }
1929                                    "requires" => {
1930                                        // Parse requires = ["arg1", "arg2"] or requires = "arg1"
1931                                        if let syn::Expr::Array(arr) = &nv.value {
1932                                            for expr in &arr.elems {
1933                                                if let syn::Expr::Lit(syn::ExprLit {
1934                                                    lit: syn::Lit::Str(s),
1935                                                    ..
1936                                                }) = expr
1937                                                {
1938                                                    config.requires.push(s.value());
1939                                                }
1940                                            }
1941                                        } else if let syn::Expr::Lit(syn::ExprLit {
1942                                            lit: syn::Lit::Str(s),
1943                                            ..
1944                                        }) = &nv.value
1945                                        {
1946                                            config.requires.push(s.value());
1947                                        }
1948                                    }
1949                                    "conflicts_with" => {
1950                                        // Parse conflicts_with = ["arg1", "arg2"] or conflicts_with = "arg1"
1951                                        if let syn::Expr::Array(arr) = &nv.value {
1952                                            for expr in &arr.elems {
1953                                                if let syn::Expr::Lit(syn::ExprLit {
1954                                                    lit: syn::Lit::Str(s),
1955                                                    ..
1956                                                }) = expr
1957                                                {
1958                                                    config.conflicts_with.push(s.value());
1959                                                }
1960                                            }
1961                                        } else if let syn::Expr::Lit(syn::ExprLit {
1962                                            lit: syn::Lit::Str(s),
1963                                            ..
1964                                        }) = &nv.value
1965                                        {
1966                                            config.conflicts_with.push(s.value());
1967                                        }
1968                                    }
1969                                    "value_parser" => {
1970                                        // Parse value_parser = ...
1971                                        // Workaround: Convert TokenStream to string and match common patterns
1972                                        // This allows us to support common expressions like:
1973                                        // - clap_noun_verb::value_parser!(u16).range(1..=65535)
1974                                        // - clap_noun_verb::value_parser!(u32).range(1..)
1975                                        // - clap_noun_verb::value_parser!(PathBuf)
1976                                        // etc.
1977                                        // Convert syn::Expr to TokenStream, then to string
1978                                        let ts = quote::quote! { #nv.value };
1979                                        let ts_string = ts.to_string();
1980                                        config.value_parser =
1981                                            Some(proc_macro2::TokenStream::from_iter(
1982                                                std::iter::once(proc_macro2::TokenTree::Literal(
1983                                                    proc_macro2::Literal::string(&ts_string),
1984                                                )),
1985                                            ));
1986                                    }
1987                                    "help" => {
1988                                        // Parse help = "..." to override docstring
1989                                        if let syn::Expr::Lit(syn::ExprLit {
1990                                            lit: syn::Lit::Str(s),
1991                                            ..
1992                                        }) = &nv.value
1993                                        {
1994                                            config.help = Some(s.value());
1995                                        }
1996                                    }
1997                                    "long_help" => {
1998                                        // Parse long_help = "..." for detailed help
1999                                        if let syn::Expr::Lit(syn::ExprLit {
2000                                            lit: syn::Lit::Str(s),
2001                                            ..
2002                                        }) = &nv.value
2003                                        {
2004                                            config.long_help = Some(s.value());
2005                                        }
2006                                    }
2007                                    "display_order" => {
2008                                        // Parse display_order = N
2009                                        if let syn::Expr::Lit(syn::ExprLit {
2010                                            lit: syn::Lit::Int(i),
2011                                            ..
2012                                        }) = &nv.value
2013                                        {
2014                                            if let Ok(order) = i.base10_parse::<usize>() {
2015                                                config.display_order = Some(order);
2016                                            }
2017                                        }
2018                                    }
2019                                    "exclusive" => {
2020                                        // Parse exclusive = true/false
2021                                        if let syn::Expr::Lit(syn::ExprLit {
2022                                            lit: syn::Lit::Bool(b),
2023                                            ..
2024                                        }) = &nv.value
2025                                        {
2026                                            config.exclusive = Some(b.value);
2027                                        }
2028                                    }
2029                                    "trailing_vararg" => {
2030                                        // Parse trailing_vararg = true
2031                                        if let syn::Expr::Lit(syn::ExprLit {
2032                                            lit: syn::Lit::Bool(b),
2033                                            ..
2034                                        }) = &nv.value
2035                                        {
2036                                            config.trailing_vararg = b.value;
2037                                        }
2038                                    }
2039                                    "allow_negative_numbers" => {
2040                                        // Parse allow_negative_numbers = true
2041                                        if let syn::Expr::Lit(syn::ExprLit {
2042                                            lit: syn::Lit::Bool(b),
2043                                            ..
2044                                        }) = &nv.value
2045                                        {
2046                                            config.allow_negative_numbers = b.value;
2047                                        }
2048                                    }
2049                                    _ => {}
2050                                }
2051                            }
2052                            syn::Meta::Path(path) => {
2053                                // Handle flag attributes like `multiple`, `next_line_help`, `trailing_vararg`
2054                                if let Some(ident) = path.get_ident() {
2055                                    match ident.to_string().as_str() {
2056                                        "multiple" => config.multiple = true,
2057                                        "next_line_help" => config.next_line_help = true,
2058                                        "trailing_vararg" => config.trailing_vararg = true,
2059                                        "allow_negative_numbers" => {
2060                                            config.allow_negative_numbers = true
2061                                        }
2062                                        _ => {}
2063                                    }
2064                                }
2065                            }
2066                            _ => {}
2067                        }
2068                    }
2069
2070                    return Some(config);
2071                }
2072            }
2073        }
2074    }
2075    None
2076}
2077
2078/// Parse validation attributes from parameter attributes
2079///
2080/// Parses `#[validate(min = 0, max = 100, min_length = 1, max_length = 50)]` attributes
2081fn parse_validation_attributes(attrs: &[syn::Attribute]) -> Option<ValidationConstraints> {
2082    for attr in attrs {
2083        if attr.path().is_ident("validate") {
2084            if let syn::Meta::List(list) = &attr.meta {
2085                let parser = syn::punctuated::Punctuated::<syn::MetaNameValue, syn::Token![,]>::parse_terminated;
2086                if let Ok(meta_list) = parser.parse2(list.tokens.clone()) {
2087                    let mut constraints = ValidationConstraints {
2088                        min_value: None,
2089                        max_value: None,
2090                        min_length: None,
2091                        max_length: None,
2092                    };
2093
2094                    for meta in meta_list {
2095                        let ident = meta.path.get_ident()?.to_string();
2096                        let value = match &meta.value {
2097                            syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(i), .. }) => {
2098                                if ident == "min" || ident == "min_value" {
2099                                    constraints.min_value = Some(i.base10_digits().to_string());
2100                                } else if ident == "max" || ident == "max_value" {
2101                                    constraints.max_value = Some(i.base10_digits().to_string());
2102                                } else if ident == "min_length" {
2103                                    if let Ok(v) = i.base10_parse::<usize>() {
2104                                        constraints.min_length = Some(v);
2105                                    }
2106                                } else if ident == "max_length" {
2107                                    if let Ok(v) = i.base10_parse::<usize>() {
2108                                        constraints.max_length = Some(v);
2109                                    }
2110                                }
2111                                None
2112                            }
2113                            syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) => {
2114                                if ident == "min"
2115                                    || ident == "min_value"
2116                                    || ident == "max"
2117                                    || ident == "max_value"
2118                                {
2119                                    Some(s.value())
2120                                } else {
2121                                    None
2122                                }
2123                            }
2124                            _ => None,
2125                        };
2126
2127                        if let Some(val) = value {
2128                            if ident == "min" || ident == "min_value" {
2129                                constraints.min_value = Some(val);
2130                            } else if ident == "max" || ident == "max_value" {
2131                                constraints.max_value = Some(val);
2132                            }
2133                        }
2134                    }
2135
2136                    return Some(constraints);
2137                }
2138            }
2139        }
2140    }
2141    None
2142}
2143
2144/// Get auto-validation constraints for a type
2145///
2146/// Returns validation constraints that can be inferred from the type:
2147/// - `u32`, `u64`, `usize` = min_value = "0"
2148/// - `u8`, `u16` = min_value = "0", max_value inferred from type max
2149/// - `i32`, `i64`, `isize` = no auto validation (can be negative)
2150/// - `String` = no auto validation (but could add min_length/max_length later)
2151fn get_type_validation(
2152    ty: &syn::Type,
2153) -> (Option<String>, Option<String>, Option<usize>, Option<usize>) {
2154    if let syn::Type::Path(type_path) = ty {
2155        let type_name =
2156            type_path.path.segments.last().map(|s| s.ident.to_string()).unwrap_or_default();
2157
2158        match type_name.as_str() {
2159            // Unsigned integers: min = 0
2160            "u8" => (Some("0".to_string()), Some("255".to_string()), None, None),
2161            "u16" => (Some("0".to_string()), Some("65535".to_string()), None, None),
2162            "u32" | "u64" | "usize" => (Some("0".to_string()), None, None, None),
2163            // Signed integers: no auto validation (can be negative)
2164            "i8" => (Some("-128".to_string()), Some("127".to_string()), None, None),
2165            "i16" => (Some("-32768".to_string()), Some("32767".to_string()), None, None),
2166            "i32" | "i64" | "isize" => (None, None, None, None),
2167            // String: no auto validation (can add min_length/max_length from attributes later)
2168            "String" => (None, None, None, None),
2169            _ => (None, None, None, None),
2170        }
2171    } else {
2172        (None, None, None, None)
2173    }
2174}
2175
2176/// Extract inner type from Option<T>, Vec<T>, or return original
2177fn extract_inner_type(ty: &syn::Type) -> syn::Type {
2178    if let syn::Type::Path(type_path) = ty {
2179        if let Some(segment) = type_path.path.segments.last() {
2180            if segment.ident == "Option" || segment.ident == "Vec" {
2181                if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
2182                    if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
2183                        return inner_ty.clone();
2184                    }
2185                }
2186            }
2187        }
2188    }
2189    ty.clone()
2190}
2191
2192/// Infer type parser for common types
2193///
2194/// Returns a string representation of the parser expression for auto-inferred types:
2195/// - `PathBuf` → `clap_noun_verb::value_parser!(PathBuf)`
2196/// - `IpAddr` → `clap_noun_verb::value_parser!(IpAddr)`
2197/// - `Ipv4Addr` → `clap_noun_verb::value_parser!(Ipv4Addr)`
2198/// - `Ipv6Addr` → `clap_noun_verb::value_parser!(Ipv6Addr)`
2199/// - `Url` → `clap_noun_verb::value_parser!(Url)` (if url feature available)
2200/// - Numeric types already handled by validation constraints
2201fn infer_type_parser(ty: &syn::Type) -> Option<String> {
2202    if let syn::Type::Path(type_path) = ty {
2203        let type_name =
2204            type_path.path.segments.last().map(|s| s.ident.to_string()).unwrap_or_default();
2205
2206        match type_name.as_str() {
2207            "PathBuf" => Some("clap_noun_verb::value_parser!(::std::path::PathBuf)".to_string()),
2208            "IpAddr" => Some("clap_noun_verb::value_parser!(::std::net::IpAddr)".to_string()),
2209            "Ipv4Addr" => Some("clap_noun_verb::value_parser!(::std::net::Ipv4Addr)".to_string()),
2210            "Ipv6Addr" => Some("clap_noun_verb::value_parser!(::std::net::Ipv6Addr)".to_string()),
2211            // Url requires url crate - check if available at compile time
2212            // For now, we'll include it and let compilation fail if url feature isn't enabled
2213            "Url" => Some("clap_noun_verb::value_parser!(::url::Url)".to_string()),
2214            // Duration requires custom parser - defer to explicit specification
2215            _ => None,
2216        }
2217    } else {
2218        None
2219    }
2220}
2221
2222/// Mark a CLI as participatory in the federated network
2223///
2224/// This macro generates federation initialization code for CLI discovery,
2225/// capability advertisement, and peer authentication.
2226///
2227/// # Example
2228///
2229/// ```rust,ignore
2230/// #[federated(
2231///     discovery_url = "https://cli-federation.example.com",
2232///     identity = "my-cli-v1.0",
2233///     trust_anchor = "./certs/root.pem"
2234/// )]
2235/// struct MyCli;
2236/// ```
2237#[proc_macro_attribute]
2238pub fn federated(args: TokenStream, input: TokenStream) -> TokenStream {
2239    let args_tokens = proc_macro2::TokenStream::from(args);
2240    let input_tokens = proc_macro2::TokenStream::from(input);
2241
2242    match macros::federated_network::federated_impl(args_tokens, input_tokens) {
2243        Ok(tokens) => tokens.into(),
2244        Err(e) => e.to_compile_error().into(),
2245    }
2246}
2247
2248/// Advertise a capability to the federated network
2249///
2250/// This macro generates RDF metadata for a CLI command and publishes it to the
2251/// discovery service for remote invocation.
2252///
2253/// # Example
2254///
2255/// ```rust,ignore
2256/// #[advertise_capability(
2257///     capability_id = "process-data",
2258///     description = "Process data files",
2259///     inputs = ["file:path", "format:string"],
2260///     outputs = ["result:json"]
2261/// )]
2262/// #[verb("process")]
2263/// fn process_data(file: PathBuf, format: String) -> Result<ProcessResult> {
2264///     // Implementation
2265/// }
2266/// ```
2267#[proc_macro_attribute]
2268pub fn advertise_capability(args: TokenStream, input: TokenStream) -> TokenStream {
2269    let args_tokens = proc_macro2::TokenStream::from(args);
2270    let input_tokens = proc_macro2::TokenStream::from(input);
2271
2272    match macros::federated_network::advertise_capability_impl(args_tokens, input_tokens) {
2273        Ok(tokens) => tokens.into(),
2274        Err(e) => e.to_compile_error().into(),
2275    }
2276}
2277
2278/// Enable remote invocation of a CLI capability
2279///
2280/// This macro generates type-safe RPC stubs for calling remote CLI commands
2281/// with automatic serialization, authentication, and result validation.
2282///
2283/// # Example
2284///
2285/// ```rust,ignore
2286/// #[remote_invoke(
2287///     target = "remote-cli-v1.0",
2288///     capability = "process-data",
2289///     timeout_ms = 5000
2290/// )]
2291/// fn remote_process(file: PathBuf, format: String) -> Result<ProcessResult>;
2292/// ```
2293#[proc_macro_attribute]
2294pub fn remote_invoke(args: TokenStream, input: TokenStream) -> TokenStream {
2295    let args_tokens = proc_macro2::TokenStream::from(args);
2296    let input_tokens = proc_macro2::TokenStream::from(input);
2297
2298    match macros::federated_network::remote_invoke_impl(args_tokens, input_tokens) {
2299        Ok(tokens) => tokens.into(),
2300        Err(e) => e.to_compile_error().into(),
2301    }
2302}
2303
2304#[cfg(test)]
2305#[allow(clippy::unwrap_used, clippy::expect_used, clippy::items_after_test_module)]
2306mod tests {
2307    use super::*;
2308
2309    #[test]
2310    fn test_parse_doc_relationships_group() {
2311        let desc = "Export as JSON [group: format]";
2312        let result = parse_doc_relationships(desc);
2313        assert_eq!(result.group, Some("format".to_string()));
2314        assert_eq!(result.description, "Export as JSON");
2315    }
2316
2317    #[test]
2318    fn test_parse_doc_relationships_requires() {
2319        let desc = "Output filename [requires: format]";
2320        let result = parse_doc_relationships(desc);
2321        assert!(result.requires.contains(&"format".to_string()));
2322        assert_eq!(result.description, "Output filename");
2323    }
2324
2325    #[test]
2326    fn test_parse_doc_relationships_conflicts() {
2327        let desc = "Output format [conflicts: raw]";
2328        let result = parse_doc_relationships(desc);
2329        assert!(result.conflicts_with.contains(&"raw".to_string()));
2330        assert_eq!(result.description, "Output format");
2331    }
2332
2333    #[test]
2334    fn test_parse_argument_descriptions_with_relationships() {
2335        let docstring = r#"Test command
2336
2337# Arguments
2338* `json` - Export as JSON [group: format]
2339* `yaml` - Export as YAML [group: format]
2340* `filename` - Output filename [requires: format]
2341"#;
2342        let result = parse_argument_descriptions_with_relationships(docstring);
2343
2344        assert!(result.contains_key("json"), "Should contain 'json' key");
2345        assert!(result.contains_key("yaml"), "Should contain 'yaml' key");
2346        assert!(result.contains_key("filename"), "Should contain 'filename' key");
2347
2348        let json_rel = result.get("json").unwrap();
2349        assert_eq!(json_rel.group, Some("format".to_string()), "json should have group='format'");
2350
2351        let yaml_rel = result.get("yaml").unwrap();
2352        assert_eq!(yaml_rel.group, Some("format".to_string()), "yaml should have group='format'");
2353
2354        let filename_rel = result.get("filename").unwrap();
2355        assert!(
2356            filename_rel.requires.contains(&"format".to_string()),
2357            "filename should require 'format'"
2358        );
2359    }
2360}
2361
2362// ============================================================================
2363// FRONTIER: Fractal Pattern Macros
2364// ============================================================================
2365
2366/// Attribute macro for defining nouns at different architectural levels
2367///
2368/// This macro generates `FractalNoun` trait implementations for structs,
2369/// enabling type-safe cross-level composition.
2370///
2371/// # Usage
2372///
2373/// ```rust,ignore
2374/// use clap_noun_verb_macros::noun_level;
2375///
2376/// #[noun_level(Level::CLI)]
2377/// struct ServiceCommand {
2378///     name: String,
2379/// }
2380///
2381/// #[noun_level(Level::Agent)]
2382/// struct ServiceAgent {
2383///     capability: String,
2384/// }
2385///
2386/// #[noun_level(Level::Ecosystem)]
2387/// struct ServiceCollective {
2388///     members: Vec<String>,
2389/// }
2390/// ```
2391#[proc_macro_attribute]
2392pub fn noun_level(args: TokenStream, input: TokenStream) -> TokenStream {
2393    let input_struct = parse_macro_input!(input as syn::DeriveInput);
2394    let args_tokens = proc_macro2::TokenStream::from(args);
2395
2396    // Parse level from arguments
2397    let level = match macros::fractal_patterns::parse_level_arg(args_tokens) {
2398        Ok(level) => level,
2399        Err(e) => return e.to_compile_error().into(),
2400    };
2401
2402    // Generate FractalNoun implementation
2403    let impl_code = macros::fractal_patterns::generate_noun_impl(&input_struct, level);
2404
2405    // Combine original struct with generated implementation
2406    let expanded = quote! {
2407        #input_struct
2408        #impl_code
2409    };
2410
2411    expanded.into()
2412}
2413
2414/// Attribute macro for defining verbs at different architectural levels
2415///
2416/// This macro generates `FractalVerb` trait implementations for impl blocks,
2417/// enabling type-safe verb-noun composition at each level.
2418///
2419/// # Usage
2420///
2421/// ```rust,ignore
2422/// use clap_noun_verb_macros::verb_level;
2423///
2424/// #[verb_level(Level::CLI)]
2425/// impl ServiceCommand {
2426///     fn start(&self) -> Result<(), String> {
2427///         Ok(())
2428///     }
2429/// }
2430///
2431/// #[verb_level(Level::Agent)]
2432/// impl ServiceAgent {
2433///     fn execute(&self) -> Result<(), String> {
2434///         Ok(())
2435///     }
2436/// }
2437///
2438/// #[verb_level(Level::Ecosystem)]
2439/// impl ServiceCollective {
2440///     fn orchestrate(&self) -> Result<(), String> {
2441///         Ok(())
2442///     }
2443/// }
2444/// ```
2445#[proc_macro_attribute]
2446pub fn verb_level(args: TokenStream, input: TokenStream) -> TokenStream {
2447    let input_impl = parse_macro_input!(input as syn::ItemImpl);
2448    let args_tokens = proc_macro2::TokenStream::from(args);
2449
2450    // Parse level from arguments
2451    let level = match macros::fractal_patterns::parse_level_arg(args_tokens) {
2452        Ok(level) => level,
2453        Err(e) => return e.to_compile_error().into(),
2454    };
2455
2456    // Generate FractalVerb implementation
2457    let impl_code = macros::fractal_patterns::generate_verb_impl(&input_impl, level);
2458
2459    // Combine original impl with generated code
2460    let expanded = quote! {
2461        #input_impl
2462        #impl_code
2463    };
2464
2465    expanded.into()
2466}
2467
2468/// Mark a function as semantically composable capability
2469///
2470/// This macro enables semantic discovery, type-safe composition, and MCP protocol
2471/// integration for CLI capabilities. It generates:
2472/// - RDF metadata for SPARQL-based discovery
2473/// - Type-level composition validators
2474/// - MCP protocol descriptors for agent communication
2475/// - Distributed slice registration for auto-discovery
2476///
2477/// # Arguments
2478///
2479/// - `uri` (required): Unique capability URI in IRI format
2480/// - `inputs` (optional): RDF type expression for input parameters
2481/// - `outputs` (optional): RDF type expression for return type
2482/// - `constraints` (optional): SPARQL ASK query for composition constraints
2483/// - `mcp_version` (optional): MCP protocol version (default: "2024.1")
2484///
2485/// # Example
2486///
2487/// ```rust,ignore
2488/// use clap_noun_verb_macros::semantic_composable;
2489///
2490/// #[semantic_composable(
2491///     uri = "urn:example:capability:file-reader",
2492///     inputs = "rdf:type fs:Path",
2493///     outputs = "rdf:type text:Content",
2494///     constraints = "ASK WHERE { ?s rdf:type fs:ReadableFile }"
2495/// )]
2496/// fn read_file(path: PathBuf) -> Result<String, std::io::Error> {
2497///     std::fs::read_to_string(path)
2498/// }
2499/// ```
2500///
2501/// # Compile-Time Validation
2502///
2503/// - Function must return `Result<T, E>` for error handling
2504/// - Async functions not yet supported (FUTURE: tokio integration)
2505/// - Unsafe functions not allowed (memory safety requirement)
2506/// - All parameters must be serializable (for MCP protocol)
2507///
2508/// # Runtime Integration
2509///
2510/// Capabilities are registered in `SEMANTIC_CAPABILITIES` distributed slice
2511/// and can be discovered at runtime via SPARQL queries on the RDF store.
2512///
2513/// See `clap_noun_verb::semantic` module for runtime support.
2514#[proc_macro_attribute]
2515pub fn semantic_composable(args: TokenStream, input: TokenStream) -> TokenStream {
2516    let attrs = match syn::parse::<macros::semantic_composition::SemanticAttributes>(args) {
2517        Ok(attrs) => attrs,
2518        Err(e) => return e.to_compile_error().into(),
2519    };
2520
2521    let function = match syn::parse::<ItemFn>(input) {
2522        Ok(f) => f,
2523        Err(e) => return e.to_compile_error().into(),
2524    };
2525
2526    match macros::semantic_composition::expand_semantic_composable(attrs, function) {
2527        Ok(tokens) => tokens.into(),
2528        Err(e) => e.to_compile_error().into(),
2529    }
2530}
2531
2532// ============================================================================
2533// FRONTIER: Executable Specifications Macros
2534// ============================================================================
2535
2536/// Converts documentation into executable tests with proof generation
2537///
2538/// This macro extracts specifications from doc comments and generates:
2539/// - Property-based tests
2540/// - Proof evidence collection
2541/// - Audit trail metrics
2542/// - Specification versioning
2543///
2544/// # Usage
2545///
2546/// ```rust,ignore
2547/// /// Calculate sum of two numbers
2548/// /// @version 1.0.0
2549/// /// @property[correctness] result >= a && result >= b
2550/// /// @property[performance] execution_time < 1ms
2551/// #[spec]
2552/// fn add(a: u32, b: u32) -> u32 {
2553///     a + b
2554/// }
2555/// ```
2556///
2557/// # Features
2558///
2559/// - **Type-First**: Specifications encoded at compile time
2560/// - **Zero-Cost**: All validation happens at compile time
2561/// - **Evidence**: Automatic proof generation for compliance
2562#[proc_macro_attribute]
2563pub fn spec(_args: TokenStream, input: TokenStream) -> TokenStream {
2564    let input_fn = parse_macro_input!(input as ItemFn);
2565
2566    match macros::executable_specs::generate_spec(&input_fn.attrs, &input_fn) {
2567        Ok(tokens) => tokens.into(),
2568        Err(e) => e.to_compile_error().into(),
2569    }
2570}
2571
2572/// Marks achievement targets with criteria tracking
2573///
2574/// This macro generates compile-time milestone tracking with:
2575/// - Target date validation
2576/// - Criteria collection
2577/// - Status tracking
2578/// - Progress metrics
2579///
2580/// # Usage
2581///
2582/// ```rust,ignore
2583/// /// Feature: User authentication
2584/// /// @milestone Phase1-Auth
2585/// /// @target 2024-12-31
2586/// /// @criteria OAuth2 integration complete
2587/// /// @criteria JWT token validation working
2588/// #[milestone]
2589/// fn auth_milestone() {}
2590/// ```
2591#[proc_macro_attribute]
2592pub fn milestone(_args: TokenStream, input: TokenStream) -> TokenStream {
2593    let input_fn = parse_macro_input!(input as ItemFn);
2594
2595    match macros::executable_specs::generate_milestone(&input_fn.attrs, &input_fn) {
2596        Ok(tokens) => tokens.into(),
2597        Err(e) => e.to_compile_error().into(),
2598    }
2599}
2600
2601/// Runtime validation of invariant properties
2602///
2603/// This macro generates runtime checks that invariants hold:
2604/// - Pre-condition validation
2605/// - Post-condition validation
2606/// - Severity-based handling (error, warning, info)
2607/// - Configurable check frequency
2608///
2609/// # Usage
2610///
2611/// ```rust,ignore
2612/// /// Process user data
2613/// /// @invariant[non_negative] value >= 0
2614/// /// @severity error
2615/// /// @frequency always
2616/// #[invariant]
2617/// fn process_value() {
2618///     // Implementation
2619/// }
2620/// ```
2621///
2622/// # Configuration
2623///
2624/// - Enable panic on failure: `--features invariant_panic`
2625/// - Otherwise prints warning to stderr
2626#[proc_macro_attribute]
2627pub fn invariant(_args: TokenStream, input: TokenStream) -> TokenStream {
2628    let input_fn = parse_macro_input!(input as ItemFn);
2629
2630    match macros::executable_specs::generate_invariant(&input_fn.attrs, &input_fn) {
2631        Ok(tokens) => tokens.into(),
2632        Err(e) => e.to_compile_error().into(),
2633    }
2634}
2635
2636// ============================================================================
2637// Learning Trajectory Macros (Frontier)
2638// ============================================================================
2639
2640/// Define a competency dimension with multi-dimensional skill tracking
2641///
2642/// This macro generates CompetencyDimension trait implementation for a struct,
2643/// enabling proficiency tracking across multiple skill areas.
2644///
2645/// # Arguments
2646///
2647/// - `dimension = "name"` - The name of the competency dimension
2648///
2649/// # Example
2650///
2651/// ```rust,ignore
2652/// use clap_noun_verb_macros::competency;
2653/// use clap_noun_verb_macros::macros::learning_trajectories::ProficiencyLevel;
2654///
2655/// #[competency(dimension = "CLI Development")]
2656/// struct CliSkills {
2657///     parsing: ProficiencyLevel,
2658///     validation: ProficiencyLevel,
2659///     composition: ProficiencyLevel,
2660/// }
2661///
2662/// let skills = CliSkills {
2663///     parsing: ProficiencyLevel::new(0.8),
2664///     validation: ProficiencyLevel::new(0.7),
2665///     composition: ProficiencyLevel::new(0.6),
2666/// };
2667///
2668/// assert_eq!(skills.name(), "CLI Development");
2669/// assert!(skills.aggregate_proficiency().value() >= 0.7);
2670/// ```
2671#[proc_macro_attribute]
2672pub fn competency(args: TokenStream, input: TokenStream) -> TokenStream {
2673    let input_parsed = parse_macro_input!(input as syn::DeriveInput);
2674    let args_stream = proc_macro2::TokenStream::from(args);
2675
2676    match macros::learning_trajectories::parse_competency_args(args_stream) {
2677        Ok(dimension) => {
2678            let impl_tokens =
2679                macros::learning_trajectories::generate_competency_impl(&input_parsed, &dimension);
2680            let original = quote! { #input_parsed };
2681            quote! {
2682                #original
2683                #impl_tokens
2684            }
2685            .into()
2686        }
2687        Err(e) => e.to_compile_error().into(),
2688    }
2689}
2690
2691/// Define an assessment function with proficiency evaluation
2692///
2693/// This macro generates AssessmentEngine trait implementation for a function,
2694/// enabling learner proficiency evaluation with configurable thresholds.
2695///
2696/// # Arguments
2697///
2698/// - `threshold = 0.8` - The passing threshold (default: 0.8)
2699///
2700/// # Example
2701///
2702/// ```rust,ignore
2703/// use clap_noun_verb_macros::assessment;
2704/// use clap_noun_verb_macros::macros::learning_trajectories::AssessmentResult;
2705///
2706/// #[assessment(threshold = 0.75)]
2707/// fn evaluate_proficiency() -> AssessmentResult {
2708///     // Evaluation logic
2709///     AssessmentResult::new(0.85, "Proficient")
2710/// }
2711/// ```
2712#[proc_macro_attribute]
2713pub fn assessment(args: TokenStream, input: TokenStream) -> TokenStream {
2714    let input_parsed = parse_macro_input!(input as syn::ItemFn);
2715    let args_stream = proc_macro2::TokenStream::from(args);
2716
2717    match macros::learning_trajectories::parse_assessment_args(args_stream) {
2718        Ok(threshold) => {
2719            let impl_tokens =
2720                macros::learning_trajectories::generate_assessment_impl(&input_parsed, threshold);
2721            let original = quote! { #input_parsed };
2722            quote! {
2723                #original
2724                #impl_tokens
2725            }
2726            .into()
2727        }
2728        Err(e) => e.to_compile_error().into(),
2729    }
2730}
2731
2732/// Define a learning path generator with optimal sequence planning
2733///
2734/// This macro generates PathOptimizer trait implementation for a function,
2735/// enabling generation of optimal learning sequences to reach target competency.
2736///
2737/// # Arguments
2738///
2739/// - `target = "level"` - Target competency level (foundation, intermediate, advanced, expert)
2740///
2741/// # Example
2742///
2743/// ```rust,ignore
2744/// use clap_noun_verb_macros::learning_path;
2745/// use clap_noun_verb_macros::macros::learning_trajectories::{CompetencyLevel, LearningPath, LearningStep};
2746///
2747/// #[learning_path(target = "Expert")]
2748/// fn generate_cli_path(current: CompetencyLevel, target: CompetencyLevel) -> LearningPath {
2749///     let steps = vec![
2750///         LearningStep::new(CompetencyLevel::Foundation, "Learn CLI basics"),
2751///         LearningStep::new(CompetencyLevel::Intermediate, "Master patterns"),
2752///         LearningStep::new(CompetencyLevel::Advanced, "Implement features"),
2753///         LearningStep::new(CompetencyLevel::Expert, "Design frameworks"),
2754///     ];
2755///     LearningPath::new(steps, target)
2756/// }
2757/// ```
2758#[proc_macro_attribute]
2759pub fn learning_path(args: TokenStream, input: TokenStream) -> TokenStream {
2760    let input_parsed = parse_macro_input!(input as syn::ItemFn);
2761    let args_stream = proc_macro2::TokenStream::from(args);
2762
2763    match macros::learning_trajectories::parse_learning_path_args(args_stream) {
2764        Ok(target) => {
2765            let impl_tokens =
2766                macros::learning_trajectories::generate_path_impl(&input_parsed, target);
2767            let original = quote! { #input_parsed };
2768            quote! {
2769                #original
2770                #impl_tokens
2771            }
2772            .into()
2773        }
2774        Err(e) => e.to_compile_error().into(),
2775    }
2776}
2777
2778/// Automatically generate tests from semantic combinations
2779///
2780/// This macro analyzes the annotated function and generates comprehensive test cases:
2781/// - Basic functionality tests
2782/// - Property-based tests using proptest
2783/// - Edge case and boundary tests
2784///
2785/// # Example
2786///
2787/// ```rust,ignore
2788/// use clap_noun_verb_macros::auto_test;
2789///
2790/// #[auto_test]
2791/// fn parse_command(input: &str) -> Result<Command, ParseError> {
2792///     // Implementation
2793///     Ok(Command::default())
2794/// }
2795///
2796/// // Generates:
2797/// // - test_parse_command_basic
2798/// // - test_parse_command_property
2799/// // - test_parse_command_edge_cases
2800/// ```
2801#[proc_macro_attribute]
2802pub fn auto_test(args: TokenStream, input: TokenStream) -> TokenStream {
2803    let input_parsed = parse_macro_input!(input as ItemFn);
2804    let args_stream = proc_macro2::TokenStream::from(args);
2805
2806    match macros::reflexive_testing_macro::generate_auto_test(args_stream, input_parsed) {
2807        Ok(tokens) => tokens.into(),
2808        Err(e) => e.to_compile_error().into(),
2809    }
2810}