48 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog. This project uses Semantic Versioning.
[0.4.0] - 2026-06-26
Added
-
Git branch auto-detection —
glint checkandglint graphnow rungit rev-parse --abbrev-ref HEADin the pipeline file's directory to determine the current branch when no--branch/--tag/--source/--varflags are given. Falls back tomainwhen not inside a git repository or in detached-HEAD state (e.g. CI runners). -
GitLab predefined variable injection — two categories of predefined variables are now present in the simulation context automatically:
CI=trueandGITLAB_CI=trueare injected for every non-empty context, so expressions like$CI == "true"evaluate correctly without--var.- MR-specific variables (
CI_MERGE_REQUEST_IID,CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,CI_MERGE_REQUEST_TARGET_BRANCH_NAME, and four others) are injected as placeholders when--source merge_request_eventis given.CI_MERGE_REQUEST_SOURCE_BRANCH_NAMEis derived from--branchwhen provided. All injected defaults can be overridden with--var.
-
Multi-platform release binaries —
task build-releasenow builds for all five platforms at once: Linux amd64, Linux arm64, macOS Intel, macOS Apple Silicon, Windows x86-64. Individual targets:task build-linux-amd64,task build-linux-arm64,task build-darwin-amd64,task build-darwin-arm64,task build-windows. -
Homebrew formula —
Formula/glint.rbis a source-build Homebrew formula. Set up a tap atk3nny/homebrew-glinton GitHub, then install withbrew tap k3nny/glint https://github.com/k3nny/homebrew-glint && brew install glint. -
INSTALL.md— installation guide covering pre-built binary download for all platforms, Homebrew tap,go install, and build-from-source with/usr/local/binplacement.
[0.3.1] - 2026-06-26
Changed
glint graphdefault mode — no-mode invocation now prints the job tree only (previously printed tree +---separator + Mermaid include graph). Useglint graph includesfor the Mermaid include-dependency output.
Fixed
glint check --help—--proxyoption was implemented but missing from the help text.glint graph --help—--cache-dir,--offline, and--proxyoptions were implemented but missing from the help text.- CI test —
TestLoad_ReadErrorskipped when running as root; Gitea runners execute as root which bypasses file permission checks, causing the test to fail.
[0.3.0] - 2026-06-26
Added
-
glint rendersubcommand — resolves allinclude:andextends:chains and writes the fully flattened pipeline to a single YAML file (default:rendered.gitlab-ci.yml; use--output -for stdout). Strips the consumedinclude:andextends:keys; retains template jobs (.name). Accepts the same network flags asglint check(--token,--gitlab-url,--cache-dir,--offline,--proxy). Useful for inspecting what GitLab CI actually sees or running further local tooling. -
glint check --no-warn— discard all warning findings before output and exit-code calculation. Mixed pipelines (errors + warnings) still exit 2 but only errors are printed. Warnings-only pipelines report "OK" and exit 0. -
glint graph --no-skipped— remove jobs that evaluate toskippedin the given context (or the implicitbranch=maindefault) from all graph output: tree, SVG, HTML, and Mermaid. -
Colorized, columnized text output —
glint check(text format) now renders findings in four aligned columns: location, rule ID, severity, message.erroris printed in bold red;warningin bold orange. Colors are auto-detected (stdout must be a terminal) and suppressed whenNO_COLORis set. Location usesfile:line:colformat when column information is available. -
line:collocations —Column intadded tomodel.Job(set from the YAML parser'syaml.Node.Column) and propagated toFinding.Columnacross all linter rules. Plain-text andFinding.String()now emitfile:line:colwhen column is known.
Changed
- Exit codes (breaking) —
glint checknow exits2when one or more error findings are present (previously1) and10when findings contain only warnings. Exit0remains for a clean pipeline. Errors take precedence over warnings. Scripts that test[ $? -eq 1 ]need to be updated to[ $? -eq 2 ].
[0.2.31] - 2026-06-26
Added
-
Proxy support —
--proxy <URL>flag onglint check,glint graph, andglint lsp; also configurable viaproxy:in.glint.yml. When set it takes precedence overHTTP_PROXY/HTTPS_PROXYenv vars; when unset, system proxy settings are honoured automatically. Covers all remote include fetches and GitLab API calls. -
GL045: HTTP remote include warning — new pipeline-level lint rule that warns when
include: remote:uses a plainhttp://URL. CI templates fetched over unencrypted HTTP are at risk of in-transit tampering;https://is always preferred.
Fixed
-
Path traversal in local includes —
include: local:paths containing../sequences that would escape the repository root (e.g.../../etc/passwd) are now rejected with a warning instead of reading arbitrary files from the host. -
HTTP timeout on remote fetches — all HTTP calls in
internal/fetchernow use a 30-second timeout; previously the client had no timeout and could hang indefinitely on slow or unresponsive servers. -
Unbounded response size — remote include and GitLab API responses are now capped at 10 MiB using
io.LimitReader; previously an arbitrarily large response could exhaust process memory. -
Cache file permissions — the cache directory is created with mode
0700(was0755) and cache files with0600(was0644), preventing other local users from reading cached GitLab tokens or pipeline content. -
LSP Content-Length DoS —
glint lspnow rejects incoming messages whoseContent-Lengthheader exceeds 64 MiB, preventing memory exhaustion from a malicious or misbehaving LSP client.
[0.2.30] - 2026-06-26
Added
- VS Code extension (
editors/vscode/) — TypeScript extension that startsglint lspas a language server and connects to it viavscode-languageclient. Activates on YAML files; thedocumentSelectorrestricts LSP processing to**/.gitlab-ci.ymlso other YAML files are unaffected. Inline error/warning squiggles appear on every save or edit. Theglint.executablePathsetting controls the binary path (default:glintonPATH). Build:task ext-compile; package as.vsix:task ext-package.
[0.2.29] - 2026-06-26
Added
- LSP server (
glint lsp) — newinternal/lsppackage andglint lspsubcommand that starts a Language Server Protocol server over stdin/stdout using Content-Length–framed JSON-RPC 2.0. Editors (VS Code, Neovim, Emacs, JetBrains, etc.) can connect with any generic LSP client configuration. Supported methods:initialize,initialized,shutdown,exit,textDocument/didOpen,textDocument/didChange,textDocument/didSave,textDocument/didClose. On every document open or change the server runs the full glint lint pipeline and publishes diagnostics viatextDocument/publishDiagnostics; each diagnostic carries the rule ID as itscodefield and"glint"assource. Parse errors are surfaced as an Error diagnostic at the top of the document. Include resolution is best-effort (usesGITLAB_TOKEN/GITLAB_URLenv vars; default cache dir~/.cache/glint). CLI flags:--token,--gitlab-url,--cache-dir,--offline.
[0.2.28] - 2026-06-26
Added
-
Pre-commit hook —
.pre-commit-hooks.yamldefines aglinthook withlanguage: golang; pre-commit builds glint from source automatically on first run and re-runsglint checkon any staged.gitlab-ci.ymlchanges. Reference:repo: https://git.k3nny.fr/k3nny/glint, rev: v0.2.28. -
GitLab CI component (
templates/check.yml) — a GitLab CI/CD Catalog–compatible component that downloads the glint Linux binary and runsglint checkas a pipeline job. Accepts inputs:stage(defaultvalidate),pipeline_file(default.gitlab-ci.yml),version(defaultlatest),allow_failure(defaultfalse), andextra_args. Can also be used as a plain local or remote include without the Catalog. -
GitHub Actions composite action (
action.yml) — downloads the glint Linux binary into$RUNNER_TEMP, adds it to$GITHUB_PATH, and runsglint check. Inputs:version,file,args. Mirror this repository to GitHub ask3nny/glintto reference it asuses: k3nny/glint@v0.2.28.
[0.2.27] - 2026-06-25
Added
-
Fuzz testing —
FuzzParseBytesandFuzzSanitizeYAMLEscapesininternal/model/fuzz_test.goverify that neither the YAML parser nor the escape sanitizer panics on arbitrary input. Successful parses are also checked for structural integrity (non-nil pipeline, no empty job names). Run withtask fuzz(default 30 s per target; setFUZZ_TIME=60sto extend). Found failures are saved totestdata/fuzz/for regression. -
Changelog automation —
cliff.tomlconfigures git-cliff to generate Keep-a-Changelog–compatible release notes from Conventional Commits.task changelogregeneratesCHANGELOG.mdfrom the full git history;task changelog-nextpreviews only unreleased commits without writing. Install git-cliff withbrew install git-clifforcargo install git-cliff.
[0.2.26] - 2026-06-25
Changed
-
Same-stage job ordering — when jobs within the same declared GitLab stage have
needs:relationships between each other, the pipeline graph now splits that stage into topological sub-columns: jobs with no same-stage dependencies occupy the leftmost sub-column; jobs that depend on them are placed one sub-column to the right. Sub-columns use a narrower 20 px gap (vs. the 50 px gap between stages), and the stage header spans all sub-columns. Stages with no intra-stageneeds:are unaffected. -
Graph links rendered behind job chips — SVG connector lines (Bézier curves in DAG mode, bus-bar stubs in classic mode) are now drawn before the job chip rectangles, so connector lines pass behind chips rather than on top of them.
[0.2.25] - 2026-06-25
Added
-
when: on_failurevisual distinction — SVG and HTML pipeline graphs now render failure-path jobs with a red status circle (#d9534f), an X-mark icon, and a dashed chip border. A newon_failurelegend entry and MermaidclassDefentry are included. -
Job tooltip / detail panel — every job chip in SVG and HTML output is wrapped in a
<g data-job="…"><title>…</title><desc>…</desc>group. SVG viewers that support<title>show the job name on hover;<desc>carriesstage,when,image, andneedsdetails. In HTML output the sidebar reads these directly from the DOM. -
Multi-job connector accuracy — classic mode (no
needs:) now uses a bus-bar connector: horizontal stubs from every job in stage N to a vertical rail at the midpoint, then stubs from the rail to every job in stage N+1. The previous single center-to-center line left top and bottom jobs visually disconnected. -
Skipped / blocked state colouring —
glint graph pipelinenow accepts the same context flags asglint check(--branch,--tag,--source,--var,--changes,--changes-from). Jobs that evaluate toJobSkippedin the given context are rendered with a grey circle (#868686) and dimmed job name. When no context flags are given, all jobs render with their normal colours. -
Interactive HTML output (
--format html) —glint graph pipeline --format htmlwrites a self-contained.htmlfile to--out. The page embeds the SVG inline with mouse-wheel zoom (centred on cursor), drag-to-pan, double-click-to-reset, and a collapsible sidebar showing job details on chip click. No external dependencies; works offline. -
Mermaid pipeline output (
--format mermaid) —glint graph pipeline --format mermaidprints the existing Mermaid flowchart (pipeline.go) to stdout. Suitable for pasting into mermaid.live or embedding in Markdown documentation. -
imageStringhelper — internal utility that extracts the image name from aJob.Imagefield that may be a plain string or a map (image: {name: ..., entrypoint: ...}); used by the tooltip desc builder.
[0.2.24] - 2026-06-25
Added
-
rules:needs:validation (GL044) — validates jobs listed inrules:needs:overrides (GitLab CI 16.4+). Each entry must reference a job that exists in the pipeline;optional: trueentries that reference missing jobs are downgraded to warnings (same behaviour as GL027 for top-levelneeds:). Cross-pipeline needs (maps with apipeline:key) are ignored. Skipped jobs are excluded when a context is provided.glint explain GL044documents the rule with a bad-YAML example and fix. -
Rule.Needsmodel field —rules:entries now parse theirneeds:key intomodel.Rule.Needs []any, making the per-rule needs list available for validation and future evaluation.
[0.2.23] - 2026-06-25
Added
- Context-scoped linting — when a context is supplied via
--branch,--tag,--source, or--var,glint checknow evaluates every job against that context before running lint rules. Jobs that are statically unreachable in the given context (i.e. theirrules:block resolves toJobSkipped) are excluded fromneeds:anddependencies:cross-job checks (GL027–GL031). This eliminates false-positive errors for jobs that are intentionally gated to specific pipeline events (e.g. a deploy job withrules: [{if: '$CI_COMMIT_TAG != ""'}]does not generate a GL027 error on branch pipelines). Filtering applies only in single-context mode; multi-context mode (--context) always checks all jobs.
[0.2.22] - 2026-06-25
Added
- Multi-context simulation —
glint checknow accepts a repeatable--context KEY=VALUE[,...]flag. Each--contextinvocation defines one simulation context; when two or more are given, glint evaluates every pipeline job across all contexts and prints a side-by-side comparison table withactive,manual,skipped, orblocked(whenworkflow:rules:would prevent the pipeline from starting) per column. Known context keys arebranch,tag, andsource(case-insensitive); any otherKEY=VALUEpair is treated as a CI variable override. The--changes/--changes-fromflags work alongside--contextand the changed-file list is shared across all contexts. Implicit--branch main --source pushdefaults are skipped when--contextis given.
[0.2.21] - 2026-06-21
Added
-
rules:changes:evaluation —glint checkandglint graphnow evaluaterules:changes:conditions when file-change data is provided. Two new flags on both subcommands:--changes <PATH>— mark one or more file paths as changed (repeatable).--changes-from <REF>— rungit diff --name-only <REF>to determine changed files automatically (e.g.--changes-from origin/main). Both flags can be combined. Glob patterns inrules:changes:support*(within a path segment) and**(across segments). When neither flag is given the condition is treated as always matching (permissive), preserving the existing behaviour. The extended map form{ paths: [...], compare_to: ... }is also supported.
-
workflow:rules:changes:evaluation —workflow:rules:entries now also evaluatechanges:patterns when changed-file data is available, consistent with job rules. -
Context summary includes changed files — the
Context:line printed byglint check --format textnow shows the count of changed files when--changes/--changes-fromis given (e.g.branch=main, source=push, 3 changed file(s)). -
Unit test suite — 100% statement coverage across all packages (
cmd/glint,internal/cicontext,internal/fetcher,internal/graph,internal/linter,internal/model,internal/resolver).
Changed
- Internal: replaced all
os.Exitcalls incmd/glintwith anexitvariable to enable unit testing without process termination; no behaviour change. - Internal: removed unreachable code paths found during coverage analysis — dead guard in
cicontext.parseRegexLiteral, unreachablelen(jobs) == 0branch ingraph.Pipeline, and theskipWinstruct field / deadcontinueingraph.convertToPNG;pipelineSVGreturn type simplified from(string, error)tostringas it never returned a non-nil error. No behaviour change.
[0.2.20] - 2026-06-14
Added
-
glint explain <RULE>— new subcommand that prints the description, rationale, a bad-YAML example, and the corrected fix for any rule ID (GL001–GL043).glint explainwith no argument lists all rules with ID, severity, and title. Rule IDs are case-insensitive (gl007andGL007are equivalent). -
rules:if:evaluated reachability (GL042) — warns when everyrules:if:condition in a job evaluates to false using the values of variables declared in the pipeline YAML, meaning the job can never be included in a pipeline run. The check is conservative: it only fires when all variables referenced in theif:expressions are explicitly declared in the YAML; predefinedCI_*/GITLAB_*variables and undeclared variables are treated as unknown (may have any runtime value) to avoid false positives. -
inherit:completeness (GL043) — warns when a job declaresinherit: default:(boolean or list form) but the pipeline has nodefault:block, making the declaration a no-op. Also warns when the list form names fields (e.g.[image, before_script]) that are not actually set in thedefault:block — those list entries have no effect. -
FEATURES.md— full feature reference extracted from the README: all 43 lint rules in categorised tables, include resolution capabilities, context simulation details, output format schemas, configuration reference, graph visualisation modes, and developer tools. The README## Featuressection replaced with a compact 6-line## What it doessummary with a link toFEATURES.md. -
USAGE.md— complete command-by-command usage reference extracted from the README:glint checkwith all output formats and examples, context simulation flags and predefined variable table, remote includes and token resolution, cache and offline mode, component reference format,glint graphmodes,glint explainusage, project config (.glint.yml), and inline suppression. The README## Usagesection replaced with the commands block and a single link toUSAGE.md.
[0.2.19] - 2026-06-14
Added
-
.glint.ymlproject config file — glint now searches for a.glint.ymlfile starting from the pipeline file's directory and walking up to the first.gitboundary. Supported keys:ignore: [GL007, GL032]— suppress rules globally for the project.severity: {GL004: warning}— override rule severity (error,warning, orignore).ignoreis equivalent to listing the rule inignore:.stages: [quality]— declare extra stage names that are valid beyond those in the pipeline's ownstages:block; jobs in these stages are not flagged by GL004.token: glpat-xxx— default GitLab personal access token (lower priority than the--tokenflag andGITLAB_TOKENenv var).url: https://gitlab.example.com— default GitLab instance URL.cache_dir: ~/.cache/glint— default cache directory for fetched remote includes.
-
Inline suppression comments — a
# glint: ignore RULEcomment placed immediately before a job definition suppresses that rule for the specific job. Multiple rules can be comma- or space-separated (# glint: ignore GL007, GL032). Use# glint: ignore allto suppress every finding for the job. Suppressions are scoped to the annotated job; pipeline-level findings are unaffected.
[0.2.18] - 2026-06-14
Added
-
--format json—glint checkcan now emit a structured JSON report instead of plain text. The schema (version1) includesglint_version,pipeline, afindingsarray (each finding hasrule,severity,file,line,job,message), and asummaryblock (total,errors,warnings). An empty findings array is[], notnull. In this mode the human-readable summary line is written to stderr so stdout contains only the JSON payload. -
--format sarif— emits a SARIF 2.1.0 JSON document (schemahttps://json.schemastore.org/sarif-2.1.0.json). Theruns[0].tool.driverlists every unique rule ID found in findings; eachresultcarriesruleId,level(error/warning),message.text(including the job name prefix), andlocations[0].physicalLocationwithartifactLocation.uriandregion.startLine(when available). Pipeline-level findings without a file have nolocationsentry. This format is consumed natively by GitHub Code Scanning and GitLab SAST. -
--format junit— emits a JUnit XML document compatible with CI test-report artifact parsers (GitLab:artifacts:reports:junit; GitHub: upload-artifact + test-reporter). Each finding becomes a<testcase>with a<failure>child whosemessageandtypeattributes carry the finding details. A clean pipeline produces a single passing<testcase>with no<failure>element. -
--format github— emits GitHub Actions workflow-command annotation lines (::error file=…,line=…,title=RULE::message/::warning …). GitHub CI renders these as inline comments on the relevant file in pull requests. Pipeline-level findings without a file omit thefile=parameter. -
Format validation — an unknown
--formatvalue exits with code 2 and a helpful error message listing the valid formats.
[0.2.17] - 2026-06-14
Added
-
Recursive include depth limit —
resolveIncludesnow accepts adepthcounter and returns a warning when the nesting depth exceeds 100 (matching GitLab's documented limit). This guards against pathologically deep include chains. -
Cycle detection for project and component includes —
project:andcomponent:includes were not previously tracked in thevisitedmap (onlylocal:andremote:were). They are now registered before recursing into their sub-includes, preventing cross-file include cycles from causing infinite loops. -
include: inputs:substitution — when acomponent:include entry has awith:block, all$[[ inputs.KEY ]]and$[[ inputs.KEY | default('…') ]]placeholders in the fetched template YAML are substituted with the corresponding values before parsing. Supported default value forms:'single quoted',"double quoted", bare booleans (true/false), and bare integers. Missing keys without a default become empty strings. This replaces the previous behaviour of leaving$[[…]]tokens in the YAML, which caused false-positive lint findings. -
Include cache (
--cache-dir) — pass--cache-dir DIRtoglint checkorglint graphto persist fetched remote templates (bothproject:andcomponent:includes andremote:URLs) to a local directory. Cache entries are keyed by the SHA-256 of the full request coordinates (base URL + project + path + ref). The directory is created automatically on first use. -
Offline mode (
--offline) — pass--offlineto skip all network calls. Remote includes not present in the cache are surfaced as warnings (same UX as "no token"). When--offlineis set without an explicit--cache-dir, the default platform cache directory ($XDG_CACHE_HOME/glintor~/.cache/glint) is used automatically.
[0.2.16] - 2026-06-14
Added
-
services:validation (GL034) — the map form of a service entry requires anamekey; emits anERRORwhen absent. The optionalaliasfield must be a valid DNS label (letters, digits, hyphens, and dots; must start and end with an alphanumeric character); invalid aliases emit anERROR. -
rules:changes/rules:existsabsolute path detection (GL035) — emits aWARNINGwhen a path inrules:changes:orrules:exists:starts with/. GitLab CI evaluates these paths relative to the repository root, so absolute paths can never match. Applies to both the list form and the{paths: …}map form. -
timeout:format validation (GL036) — emits anERRORwhen a job'stimeout:(or the pipeline-leveldefault.timeout:) is not a valid GitLab CI duration string. Valid formats:30m,1h 30m,90 minutes,2 hours 30 minutes,1 day, etc. -
id_tokens:audvalidation (GL037) — emits anERRORfor each OIDC token entry inid_tokens:that is missing the requiredaudkey. GitLab returns an API error at pipeline start whenaudis absent. -
secrets:provider validation (GL038) — emits anERRORfor each secret entry insecrets:that does not declare a provider key (vault,gcp_secret_manager, orazure_key_vault). -
pages:keyword +artifacts.pathsconsistency (GL039) — emits aWARNINGwhen a job uses thepages:keyword butartifacts.pathsdoes not include the publish directory (defaultpublic, or the value ofpages.publish). GitLab Pages will not deploy unless the publish directory is listed as an artifact. -
Duplicate stage names (GL040) — emits a
WARNINGwhen a stage name appears more than once in the top-levelstages:list. GitLab silently merges duplicate stage entries, which can produce confusing pipeline ordering. -
cache.key.filesglob detection (GL041) — emits aWARNINGwhen an entry incache.key.filescontains glob metacharacters (*,?,[). Thefilesfield requires exact file paths; GitLab does not expand globs there.
[0.2.15] - 2026-06-13
Added
- Static reachability check (GL033) — warns when every rule in a job's
rules:block has an explicitwhen: never, making the job permanently excluded from any pipeline run. This is a purely static claim: no matter whichif:condition evaluates to true, the outcome is always "skip"; and if no rule matches, the implicit fallback is also skip. No expression evaluation or context is required. The finding is aWARNING(may be intentional as a "disabled job" pattern). Only jobs where every rule has the literalwhen: nevervalue are flagged; rules with nowhen:(defaulton_success),when: manual,when: always, orwhen: on_failureare not.
[0.2.14] - 2026-06-13
Added
-
--version/-vflag —glint --version,glint -v, andglint versionall print the compiled version string (e.g.glint v0.2.14). The version is also shown at the top of every--helpoutput (global,check --help,graph --help). The version is injected at build time via-ldflags "-X main.version=..."usinggit describe --tags --always --dirty. -
Sorted findings output —
Lintnow returns findings sorted by(File, Line, Rule). All issues from the same source file appear together in ascending line order; pipeline-level findings with no file location sort first. Previously findings were emitted in map-iteration order (non-deterministic).
Fixed
-
Warning format consistency — include-resolution warnings, extends-chain warnings, and the workflow non-start warning now use the same ruff-style
path: [warning] messageformat as lint findings instead of the old[WARNING] …prefix with no file context. -
Workflow rule permissive evaluation — workflow
rules:if:expressions are now evaluated in strict mode: an expression that cannot be fully parsed returnsfalse(skip this rule, try the next) instead oftrue(match everything). Previously, a complex or partially-unsupported condition on the first workflow rule would match every context, blocking all subsequent rules and injecting the wrong variables. Job rules retain permissive evaluation (trueon parse failure) to avoid silently dropping jobs. -
Single
=operator inrules:if:— a bare=not followed by=or~is now accepted as an alias for==. This is a common mistake in GitLab CI YAML; previously it caused a parse failure and triggered the permissive fallback. -
Source location lost through
extends:resolution — when a job was resolved viaextends:, the merged definition was re-encoded and re-decoded as a freshmodel.Jobstruct, which does not carryFileorLine(they are not YAML keys). Those fields are now explicitly copied back from the original job before replacing it inp.Jobs, so extended jobs report the correct source file and line number in findings.
[0.2.13] - 2026-06-12
Added
-
Variable expansion (
$VAR/${VAR}) — variable values that reference other variables are now expanded in the effective context after all sources are merged. Transitive chains (A=$B,B=$C) are resolved over up to ten passes; circular references are left as-is. The expanded values are visible in--list-varsoutput under "Effective context variables" and are used when evaluatingrules:if:expressions. -
Non-string scalar variables (
bool,int,float64) — variables declared with baretrue/falseor integer values (e.g.BUILD: true,RETRIES: 3) are now handled correctly in all variable processing paths. Previously they were rendered as(complex)in--list-varsoutput and silently dropped from the effective context; they now render and inject as their string equivalents ("true","3"), matching GitLab CI's own behaviour where all variable values are strings.
Fixed
- YAML
\/escape in double-quoted strings — regex patterns containing\/(escaped forward-slash) in double-quotedif:expressions (e.g.$CI_COMMIT_BRANCH =~ /^us\//) caused a YAML parse error (found unknown escape character) withgopkg.in/yaml.v3, which does not implement this YAML 1.2 escape. The parser now preprocesses the raw bytes before unmarshalling: inside double-quoted strings,\/is rewritten to\\/, whichyaml.v3parses as a literal backslash followed by a slash — preserving the regex intent.
[0.2.11] - 2026-06-12
Added
-
Ruff-style finding output — findings now follow the
file:line: RULEID [severity] messageformat (e.g..gitlab-ci.yml:14: GL004 [error] job "deploy": stage "production" is not defined in 'stages'), matching the output convention used by ruff and other modern linters. Job-scoped findings prefix the message withjob "name":; pipeline-level findings omit the job prefix. Severity is lowercase inside brackets ([error],[warning]). -
Implicit default context (
--branch main --source push) — whenglint checkorglint graphis invoked without any of--branch,--tag,--source, or--var, the context now defaults to--branch main --source pushso thatrules:if:expressions are always evaluated. Previously the context was empty and no rule evaluation occurred. Any explicit context flag bypasses the defaults entirely. -
--list-varsdebug flag — available on bothglint checkandglint graph; prints all pipeline-level variables collected from the root file and every included file (sortedKEY=VALUE) to stderr, then continues normally. When a context is active, also prints the effective merged variable set (pipeline defaults + workflow-rule variables + CLI flags). Useful for diagnosing GL032 false positives. -
Included-file variables now visible to all lint rules —
variables:blocks declared in included files (local, remote, project, and component includes) are now merged into the pipeline's variable namespace before linting. This eliminates false-positive GL032 warnings for variables declared in shared CI templates. Root-pipeline variables take precedence over included-file variables when the same key appears in both (matching GitLab's own override behaviour). -
Variable reference validation (GL032) — glint now warns when a
rules:if:expression references a variable ($VARor${VAR}) that is not declared anywhere in the pipeline YAML: pipeline-levelvariables:, the job's ownvariables:, or anyworkflow:rules:variables:block. Predefined GitLab CI variable namespaces (CI_*,GITLAB_*,FF_*,RUNNER_*,TRIGGER_*,CHAT_*) are exempt. Because variables can also be set in GitLab CI/CD project settings (invisible to glint), the finding is a[WARNING]rather than an error. Each undeclared variable is reported at most once per job to keep the output concise. -
Structured rule IDs — every finding now carries a stable
GL###identifier (e.g.GL003) that appears in the output alongside the location and message:.gitlab-ci.yml:14: GL003 [error] job "deploy": missing required field 'script'. IDs are assigned per check function across 31 rules (GL001–GL031) and are stable across versions. Thelinter.Findingstruct exposes the ID as aRule stringfield for programmatic consumers. The README lint rules table is updated with ID columns. -
workflow:rules:variables:now propagate to job rule evaluation — when aworkflow:rules:entry matches, anyvariables:it defines are injected into the evaluation context so jobrules:if:expressions can reference them. Pipeline-levelvariables:are also available as defaults (lower priority). Variable priority order, highest first:--varCLI overrides →--branch/--tag/--sourceshortcuts → workflow-rule variables → pipeline-level variable defaults. This means$DEPLOY_TARGET == "production"in a job rule correctly evaluates when a workflow rule setsDEPLOY_TARGET: productionfor the matching branch. Theglint graph treecommand benefits from the same enrichment. -
rules:if:expression evaluator improvements — six correctness fixes to the GitLab CI expression parser:- Multi-line expressions — newlines (
\n,\r) are now treated as whitespace between tokens, so block-scalarif:values (e.g.if: | ...) and folded YAML scalars with||/&&on a continuation line are parsed correctly instead of falling back to permissivetrue. ${VAR}curly-brace variable syntax —${CI_COMMIT_BRANCH}is now equivalent to$CI_COMMIT_BRANCHeverywhere a value is expected.- Regex flags —
/pattern/i,/pattern/m,/pattern/sare now honoured; theiflag (case-insensitive) is translated to Go's(?i)prefix before compiling. Unknown flags are silently ignored. - Variable as regex RHS —
$BRANCH =~ $PATTERNwhere$PATTERNholds a/regex/[flags]string is now evaluated by extracting and compiling the pattern from the variable's value; if the value is empty or does not look like a regex literal the expression falls back to permissivetrue. true/falsekeywords — baretrueandfalse(without quotes) are now recognised as the string values"true"and"false", matching GitLab CI's own behaviour.$GATEWAY_ENABLED == trueand$FEATURE_FLAG == falsenow evaluate correctly.- Integer literals — bare integers (e.g.
$PARALLEL == 4,$ENABLED == 1,$DISABLED == 0) are now parsed as their decimal string representations and compared accordingly.
- Multi-line expressions — newlines (
-
File and line numbers on findings — every finding now includes the source file and line where the job is defined, e.g.
[ERROR] job "deploy" (src/deploy.yml:14): …. For jobs that come from local or fetched includes the file reflects the include source. Pipeline-level findings (workflow rules, missing stages) reference the root pipeline file. -
glint graph includesshows jobs per file — each node in the Mermaid include dependency graph now shows the jobs defined directly in that file. Jobs are rendered as rounded nodes ((name)) in a distinct light-purple style, connected with dashed arrows (-.->) to distinguish ownership from the include hierarchy (solid-->arrows). The root pipeline file always shows its direct jobs; local and fetched project/component nodes show theirs when the file can be read.
Fixed
-
include: remote:URL includes are now fetched and merged — glint fetches plain HTTPS URLs ininclude: remote:entries (no authentication), parses the resulting YAML, merges its jobs into the pipeline, and recursively resolves any sub-includes the remote file itself declares. Unreachable or unparseable URLs emit a[WARNING]and lint continues on the rest of the pipeline. Theglint graph includescommand now expands remote nodes with their jobs and sub-include tree, matching the behaviour of local and project includes. -
needs: optional: truedowngraded to warning — aneeds:entry that carriesoptional: trueand references a job not present in the pipeline now emits[WARNING]instead of[ERROR]. GitLab CI silently skips such dependencies at runtime (the job is absent when its include was not triggered), so the finding was a false positive. Non-optional missing needs remain errors. Optional missing deps are also excluded from the cycle-detection graph. -
extends:jobs with missing script downgraded to warning — a job that declaresextends:but has noscriptafter resolution now emits[WARNING]instead of[ERROR]. The script may legitimately come from a base job in a remote include that could not be fetched at lint time (e.g. no token configured). -
Variable map form now parses correctly —
variables:entries that use the extended{value, description, options}form (GitLab CI 13.7+) no longer causeyaml: cannot unmarshal !!map into string. BothPipeline.Variablesand per-jobVariablesnow accept either plain strings or map-form declarations. -
default.imagemap form now parses correctly —default: image: {name: ..., pull_policy: ...}used to causeyaml: cannot unmarshal !!map into string;DefaultConfig.Imageis now typed asanyto matchJob.Image. -
default.before_script/default.after_scriptnow accept both list and scalar forms — previouslyDefaultConfig.BeforeScriptandDefaultConfig.AfterScriptwere[]string, causing a parse error when the field was written as a block scalar string. They are now typed asanyto match the correspondingJobfields. -
rules.changes/rules.existsmap form now parses correctly — extendedchanges: {paths: [...], compare_to: "..."}syntax (GitLab CI 15.3+) used to causeyaml: cannot unmarshal !!map into []string.
[0.2.0] - 2026-06-11
Added
-
Subcommand CLI — reworked interface inspired by ruff:
glint check <file>— lint a pipeline (replaces bareglint <file>)glint graph [mode] <file>— visualise the pipeline (replaces--graphflag)- Graph modes: no-arg (tree + includes),
tree,includes,pipeline,all - Per-command
--helpwith ruff-style layout:Arguments:,Options:(flag declaration on its own line, description below),[env: ...]/[default: ...]/[possible values: ...]metadata,Examples:section
-
glint graph tree— jobs displayed as a terminal directory tree grouped by stage (like thetreecommand); job-type annotations ([manual],[delayed],[trigger]) when no context is set; evaluated-state annotations ([skipped],[manual]) when a context is provided via--branch/--tag/--source -
Context flags on
glint graph—--branch,--tag,--source,--varare now available onglint graphas well asglint check -
Local include resolution —
include: local:entries are now read from disk, recursively resolved, and merged into the pipeline before linting; enables cross-fileextends:andneeds:validation for multi-file pipelines -
Cross-platform release builds — two Taskfile tasks for tagged release binaries:
task build-windows— cross-compiles for Windows x64; output:glint-<tag>.exetask build-linux— cross-compiles for Linux x64; output:glint-<tag>-linux-amd64- Both tasks require an exact git tag on the current commit
Fixed
extends:unknown base no longer fatal — when a base job referenced byextends:does not exist, glint now emits a resolver warning and skips extends resolution for that job rather than aborting with exit code 2; linting continues on the job's own fieldsscript: |(block scalar) support — jobs using a multiline block scalar forscript:,before_script:, orafter_script:are now parsed correctly; previously caused false-positive "missing script" errors
Changed
glint <file>removed — useglint check <file>--graph <mode>removed — replaced byglint graph [mode]--graph-outrenamed to--out— now a flag onglint graph(glint graph pipeline --out <dir>)
[0.1.0] - 2026-06-07
Added
-
Context simulation (
--branch/--tag/--source/--var) — show which jobs would be active, manual, or skipped for a specific pipeline event without leaving the terminal:--branch <name>— simulates a branch push; populatesCI_COMMIT_BRANCH,CI_COMMIT_REF_NAME,CI_COMMIT_REF_SLUG, and defaultsCI_PIPELINE_SOURCEtopush--tag <name>— simulates a tag push; populatesCI_COMMIT_TAG, clearsCI_COMMIT_BRANCH--source <event>— setsCI_PIPELINE_SOURCEexplicitly (merge_request_event,schedule,web,api,pipeline, …)--var KEY=VALUE— sets any CI variable; repeatable; overrides shortcut values- Evaluates
rules:if:expressions (==,!=,=~,!~,&&,||,!, parentheses,$VAR, string literals,null) - Evaluates
only:/except:ref keywords (branches,tags,merge_requests,schedules,pushes,web,api,pipelines), branch name globs (feat/*), and/regex/patterns - Evaluates
workflow:rules:— warns when the pipeline itself would not start for the given context rules:changes:andrules:exists:are not evaluated (no git tree at lint time); rules withoutif:always match- Linting runs in full regardless of context; context output is printed before findings
- New
internal/cicontextpackage (context.go,eval.go,reachability.go); no new external dependencies - 33 unit tests covering the expression evaluator; 5 fixture runs in
task validate
-
Graph output (
--graph) — visualises the pipeline instead of running lint rules:--graph includes— Mermaid flowchart of include dependencies written to stdout; one node perinclude:entry (project, component, local, remote, template), colour-coded by type; pipe to a.mmdfile or paste into mermaid.live--graph pipeline— GitLab CI-style SVG/PNG pipeline graph written to a timestamped file in--graph-out(default:glint-out/); jobs rendered as white chip cards with a coloured status indicator (blue: regular, orange: manual, purple: trigger, amber: delayed); DAG mode draws job-to-job Bézier arrows when any job hasneeds:, classic mode draws L-shaped connectors between stage columns; converted to PNG automatically whenrsvg-convert,inkscape, ormagickis available--graph all— include Mermaid to stdout, pipeline file path to stderr- New
internal/graphpackage (includes.go,pipeline.go,render.go); no new external dependencies
-
CI/CD catalog component resolution — resolves
include: component:references from the GitLab CI/CD Catalog:- Reference format:
<host>/<project-path>/<component-name>@<version>(host determines which GitLab instance is queried) - Tries single-file layout (
templates/<name>.yml) then directory layout (templates/<name>/template.yml) automatically - Public catalog components are fetched without authentication (no token required)
- References containing CI variables (e.g.
$CI_SERVER_FQDN) are skipped with a warning — they cannot be resolved at lint time - Jobs imported from a component may use
$[[ inputs.xxx ]]input placeholders in stage names; the stage validation check is skipped for those values rather than producing false positives
- Reference format:
-
Remote project include resolution — fetches
include: project:templates from the GitLab REST API before linting; jobs from remote templates are merged into the pipeline soextends:,needs:, anddependencies:references can be validated across file boundaries- Token auto-discovery:
GITLAB_TOKEN(→PRIVATE-TOKENheader) →CI_JOB_TOKEN(→JOB-TOKENheader) →GITLAB_PRIVATE_TOKEN - Instance URL auto-discovery:
--gitlab-urlflag →CI_SERVER_URL→GITLAB_URL→https://gitlab.com --tokenand--gitlab-urlCLI flags for explicit overridesfile:accepts both string and list-of-strings forms- Project includes require a token; they are skipped with a
WARNINGwhen none is configured - Component includes attempt the fetch unauthenticated first; a
WARNINGis emitted only on failure
- Token auto-discovery:
-
Comprehensive keyword validation — checks for all major GitLab CI YAML keywords based on the official docs:
whenvalid values:on_success,on_failure,always,manual,delayed,neverstart_inonly allowed whenwhen: delayed; error if set without itparallelmust be integer 2–200 or map withmatrixkeyretrymax value 0–2;retry.whenfailure types validated against the full enumallow_failuremust be boolean or{exit_codes: ...}interruptiblemust be booleantriggerjobs cannot havescript; map form requiresprojectorincludecoveragemust be a regex pattern wrapped in/…/releaserequirestag_nameenvironment.urlrequiresenvironment.name;environment.actionvalidatedartifacts.whenvalid values;expose_asrequirespathscache.whenandcache.policyvalid valuesrules[*].whenvalidated per-ruleimagemap form requiresnameinherit.default/inherit.variablesmust be boolean or listworkflow.rules[*].whenrestricted toalways/never- Warning when
pagesjobartifacts.pathsdoes not includepublic
-
dependencies:validation — referenced jobs must exist and must be in an earlier stage -
run:keyword support — recognised as alternative toscript:(CI steps); no longer triggers "missing script" error -
spec:reserved key — top-levelspec:is now recognised as a CI component header, not a job -
New job model fields —
interruptible,resource_group,start_in,run -
Testdata fixtures —
keywords_valid.yml(clean pipeline exercising every new check),keywords_invalid.yml(18 deliberate violations) -
extends:resolution — resolves single and chained template inheritance before linting; deep-merges base job fields into derived jobs (child scalars/lists win, maps are merged recursively); cycle detection via topological sort -
needs:DAG validation — checks referenced jobs exist, respect stage ordering, and contain no circular dependencies; handles both the- job-nameshorthand and the- job: namemap form; cross-pipeline needs (pipeline:key) are skipped -
Hidden job support — jobs named with a leading
.are treated as reusable templates and exempted from thescriptrequirement and other per-job checks -
Core linter — initial set of lint rules:
- Missing
scripton non-trigger, non-template jobs (error) - Job
stagenot declared instages(error) only/rulesorexcept/rulesused together (error)- No
stagesblock defined (warning) - Deprecated
only/exceptusage (warning)
- Missing
-
CLI —
glint <file>exits 0 on clean pipelines, 1 on errors; prints findings with severity, job name, and message -
YAML parser — two-pass parse: reserved top-level keys (
stages,variables,default,include,workflow) are decoded into typed structs; remaining keys are treated as job definitions -
Taskfile —
build,test,lint-go,validate,ci,cleantasks via Task -
Testdata fixtures —
valid.yml,invalid.yml,extends.yml,needs.yml,needs_cycle.yml