@@ -20,25 +20,64 @@ var (
2020 outputFormat string
2121 outputFile string
2222 failOn string
23+ fileFlag []string // Explicit file paths (--file flag)
24+ typeFlag string // Force component type (--type flag)
2325)
2426
2527var rootCmd = & cobra.Command {
26- Use : "cclint" ,
28+ Use : "cclint [files...] " ,
2729 Short : "Claude Code Lint - A comprehensive linting tool for Claude Code projects" ,
2830 Long : `CCLint is a linting tool for Claude Code projects that validates agent files,
2931command files, settings, and documentation according to established patterns.
3032
31- By default, cclint scans the entire project and reports on all validation issues.
32- Use specialized commands to focus on specific file types.
33+ USAGE MODES:
34+
35+ Full scan (default):
36+ cclint Lint all component types
37+ cclint agents Lint only agents
38+ cclint commands Lint only commands
39+
40+ Single-file mode:
41+ cclint ./agents/foo.md Lint a specific file
42+ cclint path/to/file.md Lint by path
43+ cclint a.md b.md c.md Lint multiple files
44+
45+ Explicit file mode (for edge cases):
46+ cclint --file agents Lint a file literally named "agents"
47+ cclint --type agent x.md Override type detection
48+
49+ EXAMPLES:
50+
51+ # Lint a single agent
52+ cclint ./agents/my-agent.md
53+
54+ # Lint multiple files
55+ cclint ./agents/a.md ./commands/b.md
56+
57+ # Force type for file outside standard path
58+ cclint --type skill ./custom/methodology.md
3359
3460⚠️ NOTE: cclint is a work in progress. Its suggestions should be validated:
3561 • Cross-reference with official docs: docs.anthropic.com, docs.claude.com
3662 • Clear violations (fake flags, >220 lines agents) are reliable
3763 • Style suggestions should be verified against official documentation` ,
64+ Args : cobra .ArbitraryArgs ,
3865 Run : func (cmd * cobra.Command , args []string ) {
39- if err := runLint (); err != nil {
40- fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
41- os .Exit (1 )
66+ // Determine mode: single-file vs full scan
67+ filesToLint := collectFilesToLint (args )
68+
69+ if len (filesToLint ) > 0 {
70+ // Single-file mode
71+ if err := runSingleFileLint (filesToLint ); err != nil {
72+ fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
73+ os .Exit (2 ) // Exit 2 for invocation errors
74+ }
75+ } else {
76+ // Full scan mode
77+ if err := runLint (); err != nil {
78+ fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
79+ os .Exit (1 )
80+ }
4281 }
4382 },
4483}
@@ -52,6 +91,7 @@ func Execute() {
5291func init () {
5392 cobra .OnInitialize (initConfig )
5493
94+ // Existing flags
5595 rootCmd .PersistentFlags ().StringVarP (& rootPath , "root" , "r" , "" , "Project root directory (auto-detected if not specified)" )
5696 rootCmd .PersistentFlags ().BoolVarP (& quiet , "quiet" , "q" , false , "Suppress non-essential output" )
5797 rootCmd .PersistentFlags ().BoolVarP (& verbose , "verbose" , "v" , false , "Enable verbose output" )
@@ -61,6 +101,11 @@ func init() {
61101 rootCmd .PersistentFlags ().StringVarP (& outputFile , "output" , "o" , "" , "Output file for reports (requires --format)" )
62102 rootCmd .PersistentFlags ().StringVarP (& failOn , "fail-on" , "" , "error" , "Fail build on specified level (error|warning|suggestion)" )
63103
104+ // Single-file mode flags
105+ rootCmd .Flags ().StringArrayVar (& fileFlag , "file" , nil , "Explicit file path(s) to lint (use for files with subcommand names)" )
106+ rootCmd .Flags ().StringVarP (& typeFlag , "type" , "t" , "" , "Force component type (agent|command|skill|settings|context|plugin)" )
107+
108+ // Viper bindings
64109 _ = viper .BindPFlag ("root" , rootCmd .PersistentFlags ().Lookup ("root" ))
65110 _ = viper .BindPFlag ("quiet" , rootCmd .PersistentFlags ().Lookup ("quiet" ))
66111 _ = viper .BindPFlag ("verbose" , rootCmd .PersistentFlags ().Lookup ("verbose" ))
@@ -147,5 +192,80 @@ func runLint() error {
147192 os .Exit (1 )
148193 }
149194
195+ return nil
196+ }
197+
198+ // collectFilesToLint determines which files to lint based on args and flags.
199+ //
200+ // Priority:
201+ // 1. --file flag (explicit files, bypasses subcommand detection)
202+ // 2. Args that look like file paths
203+ // 3. Empty (full scan mode)
204+ //
205+ // Known subcommands (agents, commands, etc.) are NOT treated as file paths
206+ // unless --file flag is used.
207+ func collectFilesToLint (args []string ) []string {
208+ var files []string
209+
210+ // 1. --file flag takes precedence (explicit file mode)
211+ if len (fileFlag ) > 0 {
212+ return fileFlag
213+ }
214+
215+ // 2. Check args for file paths
216+ for _ , arg := range args {
217+ // Skip known subcommands (they'll be handled by Cobra)
218+ if cli .IsKnownSubcommand (arg ) {
219+ continue
220+ }
221+
222+ // Check if it looks like a file path
223+ if cli .LooksLikePath (arg ) {
224+ files = append (files , arg )
225+ }
226+ }
227+
228+ return files
229+ }
230+
231+ // runSingleFileLint lints specific files and outputs results.
232+ //
233+ // Exit codes:
234+ // - 0: All files passed (no errors)
235+ // - 1: One or more files had lint errors
236+ // - 2: Invocation error (file not found, invalid type, etc.)
237+ func runSingleFileLint (files []string ) error {
238+ // Load configuration for output settings
239+ cfg , err := config .LoadConfig (rootPath )
240+ if err != nil {
241+ return fmt .Errorf ("error loading configuration: %w" , err )
242+ }
243+
244+ // Override config with flag values
245+ cfg .Quiet = quiet
246+ cfg .Verbose = verbose
247+
248+ // Lint files
249+ summary , err := cli .LintFiles (files , rootPath , typeFlag , cfg .Quiet , cfg .Verbose )
250+ if err != nil {
251+ return err
252+ }
253+
254+ // Output results
255+ outputter := outputters .NewOutputter (cfg )
256+ if err := outputter .Format (summary , cfg .Format ); err != nil {
257+ return fmt .Errorf ("error formatting output: %w" , err )
258+ }
259+
260+ // Print validation reminder (unless quiet mode)
261+ if ! cfg .Quiet {
262+ fmt .Println ("\n ⚠️ Validate suggestions against docs.anthropic.com or docs.claude.com" )
263+ }
264+
265+ // Exit with error if any file had errors
266+ if summary .TotalErrors > 0 {
267+ os .Exit (1 )
268+ }
269+
150270 return nil
151271}
0 commit comments