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>
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>
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>