Skip to content

Commit e9bb7cb

Browse files
tae2089claude
andcommitted
feat: add --strict flag to lint for CI/pre-commit enforcement
- ccg lint: report only (exit 0) - ccg lint --strict: exit 1 if any issues found (blocks commit) - Pre-commit hook now uses --strict by default Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9268fb4 commit e9bb7cb

3 files changed

Lines changed: 56 additions & 1 deletion

File tree

internal/cli/hooks.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const hookGuardEnd = "# --- ccg hook end ---"
1414

1515
const hookBody = `
1616
` + hookGuardBegin + `
17-
ccg build . && ccg docs && ccg lint
17+
ccg build . && ccg docs && ccg lint --strict
1818
` + hookGuardEnd + `
1919
`
2020

internal/cli/lint.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
func newLintCmd(deps *Deps) *cobra.Command {
1313
var outDir string
1414
var excludePatterns []string
15+
var strict bool
1516

1617
cmd := &cobra.Command{
1718
Use: "lint",
@@ -80,11 +81,16 @@ func newLintCmd(deps *Deps) *cobra.Command {
8081
len(report.Orphans), len(report.Missing), len(report.Stale), len(report.Unannotated))
8182
}
8283

84+
if strict && total > 0 {
85+
return fmt.Errorf("lint found %d issues", total)
86+
}
87+
8388
return nil
8489
},
8590
}
8691

8792
cmd.Flags().StringVar(&outDir, "out", "docs", "Documentation directory to lint")
8893
cmd.Flags().StringArrayVar(&excludePatterns, "exclude", nil, "Exclude files/paths matching pattern (repeatable)")
94+
cmd.Flags().BoolVar(&strict, "strict", false, "Exit with error if any issues are found (useful for CI/pre-commit)")
8995
return cmd
9096
}

internal/cli/lint_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,52 @@ func TestLintCommand_ReportsUnannotated(t *testing.T) {
166166
t.Errorf("expected 'unannotated' label in output, got:\n%s", out)
167167
}
168168
}
169+
170+
func TestLintCommand_Strict_FailsOnIssues(t *testing.T) {
171+
deps, stdout, stderr, db := setupLintTest(t)
172+
173+
db.Create(&model.Node{
174+
QualifiedName: "pkg/bare.go::Bare",
175+
Kind: model.NodeKindFunction,
176+
Name: "Bare",
177+
FilePath: "pkg/bare.go",
178+
StartLine: 1, EndLine: 5,
179+
Hash: "h1", Language: "go",
180+
})
181+
182+
outDir := t.TempDir()
183+
err := executeCmd(deps, stdout, stderr, "lint", "--out", outDir, "--strict")
184+
if err == nil {
185+
t.Fatal("expected error with --strict when issues exist")
186+
}
187+
}
188+
189+
func TestLintCommand_Strict_PassesWhenClean(t *testing.T) {
190+
deps, stdout, stderr, db := setupLintTest(t)
191+
192+
fn := model.Node{
193+
QualifiedName: "pkg/ok.go::Ok",
194+
Kind: model.NodeKindFunction,
195+
Name: "Ok",
196+
FilePath: "pkg/ok.go",
197+
StartLine: 1, EndLine: 5,
198+
Hash: "h1", Language: "go",
199+
}
200+
db.Create(&fn)
201+
db.Create(&model.Annotation{
202+
NodeID: fn.ID,
203+
Tags: []model.DocTag{{Kind: model.TagIntent, Value: "ok", Ordinal: 0}},
204+
})
205+
206+
outDir := t.TempDir()
207+
// Generate docs first
208+
if err := executeCmd(deps, stdout, stderr, "docs", "--out", outDir); err != nil {
209+
t.Fatalf("docs: %v", err)
210+
}
211+
212+
stdout.Reset()
213+
err := executeCmd(deps, stdout, stderr, "lint", "--out", outDir, "--strict")
214+
if err != nil {
215+
t.Fatalf("expected no error with --strict when clean, got: %v", err)
216+
}
217+
}

0 commit comments

Comments
 (0)