Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .structlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ ignore:
- ".idea"
- ".vscode"
- "*.sublime-*"
- ".agents"
- ".codex"

# OS files
- ".DS_Store"
Expand All @@ -130,3 +132,4 @@ ignore:
- "*.log"
- "*.tmp"
- "*.temp"
- "structlint"
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,11 @@ structlint validate
| Inconsistent project structure across team | Enforce allowed/disallowed paths |
| Sensitive files committed (.env, keys) | Block forbidden file patterns |
| Missing essential files (README, configs) | Require specific files |
| Files drifting into the wrong layer | Enforce placement rules |
| Packages missing local entrypoints/docs | Enforce required groups |
| Cross-layer imports creeping in | Enforce Go, JS/TS, and Python boundaries |
| AI tools placing files incorrectly | Clear structure rules for AI context |
| CI/CD structural compliance | JSON reports + exit codes |
| CI/CD structural compliance | JSON/SARIF/GitHub reports + exit codes |

## Configuration

Expand Down Expand Up @@ -157,6 +160,32 @@ ignore:

</details>

<details>
<summary><strong>Organization Drift Rules</strong></summary>

```yaml
placement:
- id: migrations-only
files: ["*.sql"]
mustBeUnder: ["migrations/**"]

requiredGroups:
- id: build-entrypoint
oneOf: ["Makefile", "Taskfile.yml", "justfile"]
- id: commands-have-main
eachDirMatching: "cmd/*"
mustContain: ["main.go"]

boundaries:
- id: domain-no-infrastructure
from: "internal/domain/**"
cannotImport: ["internal/db/**", "internal/http/**"]
```

Boundary rules are language-aware for Go, JavaScript, TypeScript, and Python imports. See [Configuration Reference](docs/user/configuration.md) and [CI/CD Integration](docs/user/ci-cd-integration.md).

</details>

<details>
<summary><strong>Glob Pattern Syntax</strong></summary>

Expand Down
75 changes: 75 additions & 0 deletions docs/AI/configuration-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ file_naming_pattern:
disallowed: [] # []string - glob patterns
required: [] # []string - glob patterns

placement: [] # []PlacementRule - file placement contracts
requiredGroups: [] # []RequiredGroup - one-of and per-directory requirements
boundaries: [] # []BoundaryRule - import boundary contracts
ignore: [] # []string - exact paths or patterns
```

Expand All @@ -25,6 +28,9 @@ type Config struct {
DirStructure DirStructureConfig `yaml:"dir_structure" json:"dir_structure"`
FileNamingPattern FileNamingPatternConfig `yaml:"file_naming_pattern" json:"file_naming_pattern"`
Ignore []string `yaml:"ignore" json:"ignore"`
Placement []PlacementRule `yaml:"placement" json:"placement"`
RequiredGroups []RequiredGroup `yaml:"requiredGroups" json:"requiredGroups"`
Boundaries []BoundaryRule `yaml:"boundaries" json:"boundaries"`
}

type DirStructureConfig struct {
Expand All @@ -38,6 +44,30 @@ type FileNamingPatternConfig struct {
Disallowed []string `yaml:"disallowed" json:"disallowed"`
Required []string `yaml:"required" json:"required"`
}

type PlacementRule struct {
ID string `yaml:"id" json:"id"`
Files []string `yaml:"files" json:"files"`
MustBeUnder []string `yaml:"mustBeUnder" json:"mustBeUnder"`
Severity string `yaml:"severity" json:"severity"`
}

type RequiredGroup struct {
ID string `yaml:"id" json:"id"`
OneOf []string `yaml:"oneOf" json:"oneOf"`
EachDirMatching string `yaml:"eachDirMatching" json:"eachDirMatching"`
MustContain []string `yaml:"mustContain" json:"mustContain"`
MustContainOneOf []string `yaml:"mustContainOneOf" json:"mustContainOneOf"`
RequireMatch bool `yaml:"requireMatch" json:"requireMatch"`
Severity string `yaml:"severity" json:"severity"`
}

type BoundaryRule struct {
ID string `yaml:"id" json:"id"`
From string `yaml:"from" json:"from"`
CannotImport []string `yaml:"cannotImport" json:"cannotImport"`
Severity string `yaml:"severity" json:"severity"`
}
```

## Glob Pattern Syntax
Expand Down Expand Up @@ -121,6 +151,41 @@ ignore:
- "bin"
```

### Placement

**placement**: Matching files must be under one of the configured roots.

```yaml
placement:
- id: sql-in-migrations
files: ["*.sql"]
mustBeUnder: ["migrations/**"]
```

### Required Groups

**requiredGroups**: Higher-level required-file contracts.

```yaml
requiredGroups:
- id: build-entrypoint
oneOf: ["Makefile", "Taskfile.yml", "justfile"]
- id: commands-have-main
eachDirMatching: "cmd/*"
mustContain: ["main.go"]
```

### Boundaries

**boundaries**: Import boundary rules for Go, JS/TS, and Python source files.

```yaml
boundaries:
- id: domain-no-db
from: "internal/domain/**"
cannotImport: ["internal/db/**"]
```

## JSON Report Schema

```go
Expand All @@ -129,10 +194,20 @@ ignore:
type JSONReport struct {
Successes int `json:"successes"`
Failures int `json:"failures"`
TotalViolations int `json:"total_violations"`
Errors []string `json:"errors"`
Violations []Violation `json:"violations"`
Summary ValidationSummary `json:"summary,omitempty"`
}

type Violation struct {
Code string `json:"code"`
Severity string `json:"severity"`
Path string `json:"path"`
Rule string `json:"rule"`
Message string `json:"message"`
}

type ValidationSummary struct {
DirsChecked int `json:"directories_checked"`
FilesChecked int `json:"files_checked"`
Expand Down
54 changes: 52 additions & 2 deletions docs/user/ci-cd-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,30 @@ jobs:
run: structlint validate --config .structlint.yaml
```

### GitHub Annotations

Use annotation output when you want violations to appear inline on pull requests.

```yaml
- name: Validate structure
run: structlint validate --format github
```

### SARIF Upload

Use SARIF when your pipeline collects code-scanning reports.

```yaml
- name: Validate structure
run: structlint validate --format sarif > structlint.sarif

- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: structlint.sarif
```

### With JSON Report Artifact

```yaml
Expand Down Expand Up @@ -54,9 +78,35 @@ structlint:
when: always
paths:
- report.json
expire_in: 1 week
expire_in: 1 week
```

## Baselines

Baselines let legacy repositories adopt structlint without blocking every existing violation. First, create a report from the current state:

```bash
structlint validate --json-output .structlint-baseline.json || true
```

Then fail only on new violations:

```bash
structlint validate --baseline .structlint-baseline.json
```

The baseline matches typed violations by `code`, `path`, and `rule`.

## Changed Files

For fast pull-request checks, validate only changed files:

```bash
structlint validate --changed-only
```

This uses `git diff --name-only --diff-filter=ACMRT HEAD`. Repository-wide requirements such as required paths still run, while file-oriented checks are limited to changed files.

## Jenkins

```groovy
Expand Down Expand Up @@ -135,7 +185,7 @@ ci: lint test lint-structure build

1. **Run early in the pipeline** - Structure validation is fast and catches issues before expensive builds

2. **Use strict mode in CI** - Add `--strict` flag to fail on warnings
2. **Use typed output in CI** - Prefer `--format github`, `--format sarif`, or `--json-output`

3. **Generate reports** - Always generate JSON reports for debugging

Expand Down
62 changes: 46 additions & 16 deletions docs/user/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,25 @@

### validate

Validate directory structure and file naming patterns.
Validate directory structure, file naming patterns, placement rules, required groups, and import boundaries.

```bash
structlint validate [options] [path]
structlint validate [options]
```

**Options:**

| Option | Description |
|--------|-------------|
| `--config, -c` | Path to config file |
| `--json-output` | Path to write JSON report |
| `--silent` | Suppress output (exit code only) |
| `--strict` | Treat warnings as errors |
| Option | Default | Description |
|--------|---------|-------------|
| `--path` / `$STRUCTLINT_PATH` | `.` | Directory to validate |
| `--config` / `$STRUCTLINT_CONFIG` | `.structlint.yaml` | Path to config file |
| `--json-output` / `$STRUCTLINT_JSON_OUTPUT` | — | Path to write JSON report |
| `--format` / `$STRUCTLINT_FORMAT` | `text` | Output format: `text`, `json`, `sarif`, or `github` |
| `--baseline` / `$STRUCTLINT_BASELINE` | — | JSON report with known violations to suppress |
| `--changed-only` / `$STRUCTLINT_CHANGED_ONLY` | false | Validate only files changed in `git diff --name-only HEAD` |
| `--silent` / `$STRUCTLINT_SILENT` | false | Suppress text logging |
| `--group-violations` / `$STRUCTLINT_GROUP_VIOLATIONS` | true | Group text output by violation type |
| `--verbose` / `$STRUCTLINT_VERBOSE` | false | Show successful checks as well as violations |

**Examples:**

Expand All @@ -42,10 +47,19 @@ structlint validate --config .structlint.yaml
structlint validate --json-output report.json

# Validate specific directory
structlint validate /path/to/project
structlint validate --path /path/to/project

# Silent mode (for scripts)
structlint validate --silent && echo "Valid"

# GitHub Actions annotations
structlint validate --format github

# SARIF for code scanning
structlint validate --format sarif > structlint.sarif

# Suppress known drift while failing on new drift
structlint validate --baseline .structlint-baseline.json
```

### version
Expand Down Expand Up @@ -102,17 +116,33 @@ When using `--json-output`, the report structure is:
{
"successes": 42,
"failures": 2,
"total_violations": 2,
"errors": [
"Directory not in allowed list: tmp",
"Disallowed file found: .env.local"
"Disallowed file naming pattern found: .env.local"
],
"summary": {
"directories_checked": 15,
"files_checked": 27,
"violations_by_type": {
"dir_not_allowed": 1,
"file_disallowed": 1
"violations": [
{
"code": "unallowed_directory",
"severity": "error",
"path": "tmp",
"rule": "dir_structure.allowedPaths",
"message": "Directory not in allowed list: tmp"
},
{
"code": "disallowed_file_pattern",
"severity": "error",
"path": ".env.local",
"rule": "*.env*",
"message": "Disallowed file naming pattern found: .env.local"
}
],
"summary": {
"total_successes": 42,
"total_failures": 2,
"violations": []
}
}
```

The `violations` array is the stable CI contract. Human-readable `errors` are kept for backward compatibility.
Loading
Loading