- Add build steps for linux/arm64, darwin/amd64, darwin/arm64
- Rename Windows output to glint-<tag>-windows-amd64.exe (consistent
with Taskfile and INSTALL.md)
- Add -X main.version=<tag> to all ldflags (was missing, causing
release binaries to report "dev" as version)
- Use $TAG variable in upload loop instead of repeating the expression
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Taskfile:
- Add build-linux-arm64, build-darwin-amd64, build-darwin-arm64 targets
- Rename build-linux to build-linux-amd64 (keep build-linux alias)
- Rename Windows output to glint-<tag>-windows-amd64.exe for consistency
- Add build-release task that builds all five platforms in one shot
Formula/glint.rb:
- Homebrew source-build formula; depends_on "go" => :build
- tap: brew tap k3nny/glint https://github.com/k3nny/homebrew-glint
- Includes basic test block (--version + lint a trivial pipeline)
INSTALL.md:
- Pre-built binary download instructions for all five platforms
- Homebrew tap setup and formula update procedure
- go install one-liner
- Link to README integrations section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two categories of predefined variables are now injected automatically:
1. Always-available (CI=true, GITLAB_CI=true): set at lowest priority for
every non-empty context so that rules:if: expressions like '$CI == "true"'
evaluate correctly without requiring --var.
2. MR-specific (CI_MERGE_REQUEST_IID, CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,
CI_MERGE_REQUEST_TARGET_BRANCH_NAME, …): injected as placeholder values
when --source merge_request_event is given, so MR-gated jobs evaluate
as active rather than silently skipped. CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
is derived from --branch when provided.
All injected defaults are non-pinned: --var overrides them, and pipeline
variables: blocks can also override via Inject(). The empty context (no
flags at all) is unchanged — no predefined vars are injected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When no --branch, --tag, --source, or --var flags are given, glint now
runs "git rev-parse --abbrev-ref HEAD" in the pipeline file's directory
to determine the current branch. Falls back to "main" when the directory
is not inside a git repository or the repo is in detached-HEAD state.
This makes implicit context simulation accurate without requiring users
to pass --branch on every invocation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously glint graph with no mode printed tree + separator + includes
Mermaid. Now glint graph defaults to tree only; use glint graph includes
for the Mermaid include-dependency output. Simplifies the common case
and makes the default output immediately actionable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
glint check --help was missing --proxy.
glint graph --help was missing --cache-dir, --offline, and --proxy.
All three flags were already implemented; only the Usage text was absent.
Also added --no-warn, --no-skipped, and --proxy examples to the
respective command example sections.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Gitea CI runner executes as root, which bypasses filesystem
permission checks. The TestLoad_ReadError test creates a mode-0000 file
and expects a read error, but root can always read files regardless of
mode. Skip the test under root instead of removing it so it still runs
in restricted environments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Exit codes (breaking change from exit 1):
- 0 clean (no findings)
- 2 one or more errors
- 10 one or more warnings, no errors
Errors take precedence over warnings.
glint check --no-warn:
Discard warning findings before output and exit-code calculation.
Mixed pipelines (errors + warnings) still exit 2 but only errors print.
Warnings-only pipelines exit 0 with "OK" when --no-warn is set.
glint render <PIPELINE>:
New subcommand. Resolves all include: and extends: chains, then writes
a single flat .gitlab-ci.yml (default: rendered.gitlab-ci.yml, or
stdout with --output -). Strips consumed keys (include:, extends:).
Template jobs (.) are retained. Flags: --output, --token, --gitlab-url,
--cache-dir, --offline, --proxy (all with .glint.yml fallback).
To support render, Resolve() now writes the merged raw map back to
p.RawJobs[name] and also preserves j.Column from the original job.
glint graph --no-skipped:
Removes jobs that evaluate to JobSkipped in the given context before
any graph function sees the pipeline. Works for tree, pipeline (SVG,
HTML, Mermaid), includes, and all modes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the per-line fmt.Println loop with writeTextFindings() in
cmd/glint/output.go. The new renderer:
- Aligns all findings into four space-separated columns: location,
rule ID, severity, message — widths computed from the full finding
set so all lines are flush
- Colors severity words when stdout is a TTY and NO_COLOR is not set:
red+bold for "error", orange+bold for "warning"; location dimmed;
rule ID bold
- Formats location as file:line:col when column is known, file:line
otherwise, falling back to just file
To populate column numbers, add Column int to model.Job (set from
keyNode.Column in the YAML parser) and Finding.Column (set during
the checkJob source-location attachment pass and in the ten cross-job
check sites that explicitly set Line: job.Line).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Security fixes:
- Path traversal guard in include: local: — paths with ../ that escape
the repo root are rejected instead of reading arbitrary host files
- HTTP timeout (30 s) on all fetcher requests to prevent indefinite hangs
- Response size cap (10 MiB) via io.LimitReader to prevent memory exhaustion
- Cache directory and file permissions tightened to 0700/0600
- LSP Content-Length cap (64 MiB) to guard against DoS from a malicious client
New feature:
- --proxy flag on check, graph, and lsp subcommands; also proxy: key in
.glint.yml; overrides system HTTP_PROXY / HTTPS_PROXY env vars when set;
cmdGraph and cmdLSP now also load .glint.yml for proxy/token/url fallbacks
New lint rule:
- GL045 (Warning): include: remote: using plain http:// instead of https://
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
editors/vscode/ is a TypeScript VS Code extension that starts glint lsp
as a child process and connects to it via vscode-languageclient. The
documentSelector restricts LSP processing to **/.gitlab-ci.yml so other
YAML files are unaffected. The glint.executablePath setting controls the
binary location (default: glint on PATH). Build with task ext-compile;
package as .vsix with task ext-package. Taskfile tasks added:
ext-install, ext-compile, ext-package. Root .gitignore updated to
exclude node_modules/, out/, and *.vsix from the extension directory.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New internal/lsp package implements a minimal JSON-RPC 2.0 LSP server
over stdin/stdout with Content-Length framing. Supported lifecycle:
initialize → initialized → shutdown → exit. Document sync: Full (sends
complete text on every change). Handles textDocument/didOpen,
didChange, didSave, didClose; publishes textDocument/publishDiagnostics
after every change. Rule IDs surface as the diagnostic `code` field
with `"glint"` as source. Parse errors produce an Error diagnostic at
the top of the document. Include resolution is best-effort (GITLAB_TOKEN
env var; ~/.cache/glint default cache). CLI: glint lsp [--token]
[--gitlab-url] [--cache-dir] [--offline].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace ~~text~~ / ✓ shipped notation with [x] / [ ] GitHub-flavoured
markdown checkboxes throughout ROADMAP.md. Completed items are [x],
pending items are [ ]. Content and version references unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
templates/check.yml — GitLab CI/CD Catalog component; downloads the glint
Linux binary and runs glint check; inputs: stage, pipeline_file, version,
allow_failure, extra_args.
action.yml — GitHub Actions composite action; downloads glint into
$RUNNER_TEMP and runs glint check; inputs: version, file, args. Mirror
to github.com/k3nny/glint to use as `uses: k3nny/glint@vX.Y.Z`.
.pre-commit-hooks.yaml — language: golang hook; pre-commit builds glint
from source on first run and re-runs on staged .gitlab-ci.yml changes.
Reference as repo: https://git.k3nny.fr/k3nny/glint.
README updated with an Integrations section covering all three.
Git remote corrected to https://git.k3nny.fr/k3nny/glint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run fuzz tests found a real bug: a bare '?' YAML input (null mapping key)
caused ParseBytes to store p.Jobs[""] — fixed in internal/model/parser.go by
rejecting empty keys with an explicit error.
New fuzz targets added:
- FuzzEvalIf / FuzzExpandVarRefs (internal/cicontext) — exercises the
hand-rolled recursive-descent rules:if: parser and variable expander
- FuzzLint (internal/linter) — drives the full Parse → Lint path against
arbitrary YAML; triggers every type-assertion in the lint rules
task fuzz now runs all 5 targets sequentially (30 s each by default).
All targets clean: 90-120 s runs, 400k-3.5M executions, zero failures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A bare '?' in YAML is an explicit-key indicator for a null key, which
produced a job with an empty name (p.Jobs[""]) — a parser invariant
violation caught by FuzzParseBytes.
ParseBytes now returns an error when keyNode.Value is empty.
Failing corpus entry retained as a seed so CI exercises this path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add FuzzParseBytes and FuzzSanitizeYAMLEscapes in internal/model/fuzz_test.go.
Both targets run as regular seed-based tests in CI (go test ./...) and can be
run continuously via `task fuzz` (default 30 s per target; FUZZ_TIME=60s to
extend). Fuzz corpus failures are saved to testdata/fuzz/ for regression.
Add cliff.toml configuring git-cliff to generate Keep-a-Changelog-compatible
release notes from Conventional Commits. New tasks: `task changelog`
(regenerate full CHANGELOG.md) and `task changelog-next` (preview unreleased
entries without writing). Requires git-cliff (brew/cargo install git-cliff).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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 deps are in sub-column 0,
jobs that depend on them shift one column right. A new computeColumns()
function handles the topo-sort; a narrower subStageGap (20 px vs 50 px
stageGap) separates sub-columns; stage headers span all sub-columns.
SVG connector lines (Bézier curves in DAG mode, bus-bar stubs in classic
mode) are now emitted before job chip rectangles so connectors visually
pass behind chips rather than on top of them.
100% statement coverage maintained (99 tests in graph package).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- when: on_failure visual distinction: red circle (#d9534f), X-mark icon, dashed
chip border, and Mermaid classDef; legend entry added
- Job tooltip / detail panel: each chip wrapped in <g data-job="…"><title>…</title>
<desc>…</desc> so SVG viewers and the HTML sidebar show stage, when, image, needs
- Multi-job connector accuracy: classic mode now uses a bus-bar pattern (vertical
rail at midpoint + per-job stubs) instead of one center-to-center line per stage pair
- Blocked/skipped state colouring: RenderPipeline and pipelineSVG accept
*cicontext.Context; skipped jobs rendered in grey (#868686) with dimmed text
- Interactive HTML output (--format html): self-contained .html with inline SVG,
mouse-wheel zoom, drag-to-pan, double-click reset, and a click-to-open sidebar
- Mermaid pipeline output (--format mermaid): prints existing Pipeline() flowchart
to stdout; suitable for mermaid.live or Markdown embedding
- imageString() helper to extract image name from string or map form
- 100% statement coverage maintained; 809 tests pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add GL044 to validate jobs listed in rules:needs: overrides (GitLab CI
16.4+). rules:needs: lets a specific rule override the job's top-level
needs: list; any referenced job must exist in the pipeline. Unknown jobs
produce an error; optional: true entries produce a warning, matching the
GL027 behaviour for top-level needs:. Cross-pipeline needs and skipped
jobs (when a context is active) are excluded from checking.
Implementation:
- model.Rule gains a Needs []any field (yaml:"needs")
- checkRulesNeeds(p, skipped) added to needs.go; wired into Lint
- GL044 / RuleRulesNeedsUnknown added to rules.go and explain.go
- 6 unit tests + 2 testdata fixtures; task validate updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When glint check runs in single-context mode (--branch, --tag, --source,
or --var), jobs that resolve to JobSkipped against that context are now
excluded from needs: and dependencies: cross-job checks (GL027-GL031).
This eliminates false-positive errors for jobs intentionally gated to
specific pipeline events (e.g. a deploy job with rules:if: CI_COMMIT_TAG
no longer triggers GL027 on branch pipelines).
linter.Lint now accepts a skipped map[string]bool (nil = check all jobs);
checkNeeds and checkDependencies skip jobs present in the map. Multi-context
mode (--context) passes nil, so all jobs are checked regardless of context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add --context KEY=VALUE[,...] (repeatable) to glint check. When two or
more --context flags are given, glint evaluates every pipeline job across
all contexts and prints a side-by-side comparison table:
glint check --context branch=main --context branch=develop .yml
Each column shows active / manual / skipped / blocked per job. Known
context keys are branch, tag, source (case-insensitive); any other
KEY=VALUE pair is treated as a CI variable override. The --changes /
--changes-from changed-file list is shared across all contexts. Implicit
branch=main / source=push defaults are skipped when --context is used.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add --changes PATH and --changes-from REF flags to glint check and glint graph
for rules:changes: evaluation. --changes marks files explicitly; --changes-from
runs git diff --name-only <REF> automatically. Both flags can be combined.
- Implement doublestar glob matching (*, ** across path segments) in EvalJob and
EvalWorkflow; extended {paths, compare_to} map form supported.
- Without --changes/--changes-from the condition stays permissive (existing behaviour).
- Context summary line now shows changed-file count when file data is provided.
- Achieve 100% statement coverage: comprehensive tests added across all packages;
removed provably dead code; added testability seams (exit, userHomeDirFn,
execCommandOutput variables) to cover previously unreachable paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added comprehensive table-driven test suites for all packages:
cmd/glint, cicontext, fetcher, graph, linter, model, resolver.
Coverage reaches 98%+ statement coverage across the codebase.
- Replaced os.Exit calls in cmd/glint with an `exit` variable so tests
can capture exit codes without terminating the test process.
- Removed unreachable code found during coverage analysis:
dead guard in cicontext.parseRegexLiteral; dead len(jobs)==0 branch
in graph.Pipeline; skipWin struct field and dead continue in
graph.convertToPNG; pipelineSVG return type simplified to string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add FEATURES.md and USAGE.md entries to the [0.2.20] changelog section.
No code changes — documentation reorganisation only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move all command-by-command reference (output formats, context
simulation, remote includes, cache/offline, component format, graph
modes, explain, project config, inline suppression, example output)
from README.md into a new USAGE.md. Replace the 326-line Usage section
in README with the commands block and a single link to USAGE.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
workflow:rules: can define variables: on matching rules (GitLab CI 15.0+).
These variables are now injected into the evaluation context before job
rules:if: expressions are evaluated, making patterns like:
workflow:
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
DEPLOY_TARGET: production
deploy:
rules:
- if: '$DEPLOY_TARGET == "production"'
work correctly with glint check --branch main.
Changes:
- model.Rule: add Variables map[string]any field (yaml:"variables")
- cicontext.Context: add pinned map tracking which vars must not be
overwritten; New() pins all shortcut and --var variables; add
Inject(key, value) which writes only when key is not pinned
- cicontext.ExtractStringVars: shared helper that converts map[string]any
variable blocks (plain string or {value:...} form) to map[string]string
- cicontext.EvalWorkflow: returns (bool, map[string]string) — the vars of
the matching workflow rule alongside the runs/no-runs result
- cmd/glint/main.go: enrichContext() injects pipeline-level variable
defaults then workflow-rule variables before printContext; applied in
both cmdCheck and cmdGraph
Injection priority (highest wins):
--var CLI overrides > --branch/--tag/--source shortcuts
> workflow-rule variables > pipeline variables: defaults
Adds 15 unit tests (TestEvalWorkflow, TestContextInject,
TestExtractStringVars, TestWorkflowVarsJobEval) and a testdata fixture
(workflow_vars.yml) validated across four branch contexts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GitLab CI expressions allow unquoted true, false, and integers as
comparison operands (all treated as their string representations):
$GATEWAY_ENABLED == true (equivalent to == "true")
$FEATURE_FLAG == false (equivalent to == "false")
$PARALLEL == 4 (equivalent to == "4")
$ENABLED == 1 / == 0
Previously these fell through to permissive true because parseValue
only recognised $VAR, "${VAR}", quoted strings, and null. Added:
- true/false keyword branch → returns "true"/"false"
- integer literal branch (digits only) → returns decimal string
All three new forms are correctly excluded from longer identifier
prefixes (identByte boundary check). Adds 8 new unit tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four correctness fixes to the GitLab CI expression parser in
internal/cicontext/eval.go:
- Multi-line: \n and \r are now treated as whitespace in skipWS so
block-scalar or folded-scalar if: values with || / && on continuation
lines evaluate correctly instead of falling back to permissive true.
- ${VAR} curly-brace variable syntax now supported in parseValue.
- Regex flags (/pattern/i, /pattern/m, /pattern/s) are now consumed and
translated to Go (?i)/(?m)/(?s) prefixes via applyRegexFlags.
- Variable on RHS of =~ / !~: when the right operand is $VAR, the
variable's value is interpreted as a /regex/[flags] string via
extractRegexFromString; non-regex values fall back to permissive true.
Adds 16 new unit tests covering all four cases and a testdata fixture
(rules_if_expr.yml) exercising multi-line, ${VAR}, and /pattern/i in a
real pipeline with context flags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remote includes (include: remote: https://...) were previously skipped
silently in the resolver and rendered as unexpanded leaf nodes in the
graph.
Changes:
- fetcher.FetchURL: new shared unauthenticated HTTP GET helper
- resolver: resolveRemoteInclude fetches the URL, parses YAML, sets job
origin to the URL string, recursively resolves sub-includes, and emits
a warning on failure (lint continues on the rest of the pipeline)
- graph: recurseRemote fetches the URL, captures direct job names, and
recurses into sub-includes so remote nodes expand like local ones
Adds testdata/includes_remote.yml fixture.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>