parseNeedJobNames is replaced by parseNeedEntries which preserves the optional flag from each needs: entry. When a referenced job does not exist and optional:true is set, the finding is now WARNING instead of ERROR, matching GitLab CI runtime behavior (the dependency is silently skipped when the job is absent from a conditional include). Optional missing deps are also excluded from the cycle-detection graph since there is no real dependency edge to trace. Adds a fixture case in testdata/needs.yml to prevent regression. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
12 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.
[Unreleased]
Added
-
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
-
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