The README features block grew to 38 bullets plus a full lint-rules
table, making the document hard to scan. This commit:
- Creates FEATURES.md with a structured reference covering lint rules
(GL001–GL043 tables), include resolution, context simulation, output
formats, configuration, graph visualization, and developer tools.
- Replaces the flat bullet list in README with a 6-line "What it does"
category summary that links to FEATURES.md and ROADMAP.md.
- Removes the redundant ## Lint rules section from README (now in
FEATURES.md).
- Adds 'explain' to the commands block in the README Usage section.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- `glint explain <RULE>`: new subcommand printing rule description,
rationale, bad-YAML example and fix for every GL001–GL043 rule.
`glint explain` (no arg) lists all rules with ID, severity, title.
Rule IDs are case-insensitive.
- GL042 (rules:if: evaluated reachability): warns when every rules:if:
condition evaluates to false given the values of variables declared in
the pipeline YAML, making the job statically unreachable. Conservative:
only fires when all referenced variables are declared in YAML; predefined
CI_* / GITLAB_* variables are skipped to avoid false positives.
- GL043 (inherit: completeness): warns when inherit: default: is declared
but there is no default: block in the pipeline (dead declaration), or
when the list form names fields not set in the default: block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds project-level configuration and per-job suppression directives:
.glint.yml (searched from pipeline dir up to the git root):
- ignore: [GL007, GL032] — suppress rules globally for the project
- severity: {GL004: warning} — override rule severity (error/warning/ignore)
- stages: [quality] — extra stages beyond the pipeline's stages: block
- token: / url: / cache_dir: — defaults for flags; lower priority than
CLI flags and environment variables
Inline suppression (# glint: ignore):
- Place "# glint: ignore GL007" immediately before a job definition to
suppress that rule for the specific job only
- Multiple rules: "# glint: ignore GL007, GL032" (comma or space separated)
- Wildcard: "# glint: ignore all" suppresses every finding for the job
- Suppressions are scoped to the annotated job; pipeline-level findings
are unaffected
- Parsed from yaml.Node head/line comments in the first parse pass;
stored in Pipeline.Suppressions (root file only, not includes)
New packages: internal/config (Load, walk-up search, .git boundary stop)
New files: cmd/glint/filter.go (applyConfig, isSuppressed helpers)
Tests: config_test.go, parser_suppress_test.go, filter_test.go
Validate fixtures: testdata/config_ignored/, config_severity/, config_suppress/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bundles three patch releases (v0.2.16–v0.2.18):
v0.2.18 — output formats (--format flag on glint check):
- json: stable JSON report (schema_version: 1, findings array, summary)
- sarif: SARIF 2.1.0 for GitHub Code Scanning / GitLab SAST
- junit: JUnit XML for CI test-report artifacts (artifacts:reports:junit)
- github: GitHub Actions ::error:: / ::warning:: annotation lines
- Unknown --format value exits 2 with a helpful error message
- Summary line routed to stderr in structured formats; context suppressed
v0.2.17 — include resolution improvements:
- Recursive include depth capped at 100 (matches GitLab's own limit)
- project: and component: includes tracked in visited set (cycle detection)
- $[[ inputs.KEY ]] / $[[ inputs.KEY | default(…) ]] substituted from with:
- --cache-dir: persist fetched remote templates to disk (SHA-256 keyed)
- --offline: serve from cache only; defaults to ~/.cache/glint
v0.2.16 — new lint rules (GL034–GL041):
- GL034: services map form requires name; alias must be valid DNS label
- GL035: rules:changes / rules:exists absolute path detection
- GL036: timeout format validation (job-level + default.timeout)
- GL037: id_tokens entries must have an aud key
- GL038: secrets entries must declare a provider (vault / gcp / azure)
- GL039: pages: keyword + artifacts.paths consistency
- GL040: duplicate stage names in stages: list
- GL041: cache.key.files must be exact paths, not globs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add rule GL033 that warns when every rule in a job's rules: block has
an explicit when: never, making the job permanently excluded from any
pipeline run. This is a pure static check — no if: evaluation or context
required. Only rules with literal when: never trigger it; rules with no
when: (defaults to on_success), when: manual, when: always, or
when: on_failure are treated as reachable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Workflow rules now use strict if: evaluation (parse failure → skip rule,
not match); fixes premature matching that blocked later rules and injected
wrong variables into the context
- Single = accepted as alias for == in rules:if: expressions
- File/Line preserved through extends: resolution (lost during YAML
encode/decode round-trip in the resolver)
- Findings sorted by (File, Line, Rule) so same-file issues group together
- All warnings use ruff-style path: [warning] message format (includes,
extends chains, workflow non-start)
- Add --version / -v flag; version shown at top of every --help output
- Build injects version via ldflags using git describe
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add $VAR / ${VAR} expansion in effective context (ctx.ExpandVars):
iterates up to 10 passes to resolve transitive chains; circular
references are left as-is after the limit.
- Handle non-string YAML scalars (bool, int, float64) in
ExtractStringVars and varValueString via new ScalarString helper;
values like BUILD: true no longer render as "(complex)" or get
silently dropped from the effective context.
- Variable precedence (GitLab spec): pipeline defaults < workflow-rule
vars < CLI --var flags; implemented correctly in enrichContext;
expansion applied after all sources are merged.
- Update README, CHANGELOG, ROADMAP for v0.2.13.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Finding format now follows file:line: RULEID [severity] message,
matching ruff and other modern linters (GL003 [error] job "x": ...)
- glint check and glint graph default to --branch main --source push
when no context flag is given; rules:if: is always evaluated
- --list-vars flag on both commands prints sorted KEY=VALUE of all
collected variables (YAML, workflow-rule union, effective context)
- CHANGELOG [Unreleased] promoted to [0.2.11]; README badge updated;
ROADMAP marks newly shipped items
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Warn when a $VAR or ${VAR} reference in a rules:if: expression is not
declared in pipeline variables:, the job's own variables:, or any
workflow:rules:variables: block. Predefined GitLab CI namespaces (CI_*,
GITLAB_*, FF_*, RUNNER_*, TRIGGER_*, CHAT_*) are always exempt.
Each undeclared variable is reported at most once per job. The finding
is a WARNING (not an error) because variables may also be set in GitLab
CI/CD project settings, which are invisible to glint at lint time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every Finding now carries a stable Rule string field with a GL### code.
The ID appears in output between the source location and the message:
[ERROR] job "deploy" (ci.yml:14) GL003: missing required field 'script'
[WARNING] (ci.yml) GL001: no stages defined
Rules:
GL001 no-stages GL002 workflow-when GL003 missing-script
GL004 unknown-stage GL005 only-rules-conflict GL006 except-rules-conflict
GL007 deprecated-only GL008 invalid-when GL009 delayed-no-start-in
GL010 start-in-no-delayed GL011 invalid-parallel GL012 invalid-retry
GL013 invalid-retry-when GL014 invalid-allow-failure GL015 invalid-interruptible
GL016 trigger-with-script GL017 invalid-trigger GL018 invalid-coverage
GL019 invalid-release GL020 invalid-environment GL021 invalid-artifacts
GL022 pages-public GL023 invalid-cache GL024 invalid-rules-when
GL025 invalid-image GL026 invalid-inherit GL027 needs-unknown
GL028 needs-stage-order GL029 needs-cycle GL030 unknown-dependency
GL031 dependency-stage
Changes:
- internal/linter/rules.go: new file with all 31 constants + doc comments
- linter.Finding: add Rule string field; String() inserts it before the
message colon when non-empty; format unchanged when Rule == ""
- All Finding{} literals in linter.go, keywords.go, needs.go,
dependencies.go updated with the correct Rule: constant
- README.md lint rules table: new ID column added to all four sections
- CHANGELOG.md: entry in [Unreleased]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Variables with value/description/options sub-keys, default.image in map
form, default.before_script / default.after_script as block scalars, and
rules.changes / rules.exists in {paths, compare_to} map form all caused
"yaml: cannot unmarshal !!map into string" because the struct fields were
typed too narrowly.
Changed types in model.Pipeline, model.DefaultConfig, and model.Rule to
accept any to match GitLab CI spec flexibility (13.7+ variable declarations,
15.3+ rules.changes map form, image map form in default block).
Adds testdata/script_multiline.yml covering all these patterns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>