Skip to content

feat: resolve tagless images through the governing chart's appVersion - #43

Merged
CptSchnitz merged 6 commits into
masterfrom
ticket-24/chart-context
Sep 24, 2026
Merged

CptSchnitz merged 6 commits into
masterfrom
ticket-24/chart-context

Conversation

@CptSchnitz

Copy link
Copy Markdown
Collaborator

Tagless image references start getting answers. An image: block with a repository and no tag is now checked against the appVersion of the chart that governs the file, matching what Helm itself would render, where before it produced a muted question mark and nothing else. File scope widens with it: any YAML file beneath a chart directory is checked, so environment overlays like production.yaml get the same scrutiny as values.yaml, and anything under that chart's templates/ is skipped because Go template source renders to nothing checkable.

The governing chart is the one in the nearest ancestor directory holding Chart.yaml. That single rule is what makes a subchart's values file resolve against the subchart's own appVersion rather than its parent's, which is the version actually deployed. One walk up the tree answers scope, the templates exclusion, and tag resolution together, rather than three rules that could disagree.

A tag the file never wrote needs different handling on every surface, so the resolved tag is a discriminated union. A file tag carries a range to underline. A chart-metadata tag carries the metadata path instead, so its diagnostic attaches to the repository value and its message names the chart. Surfaces cannot forget the distinction, because there is no range on that arm to reach for. Saving a Chart.yaml re-checks the open files that chart governs, since a version bump is exactly when a stale checkmark costs the most.

Chart context lives in the Helm package with the filesystem read injected, per the decision recorded in #17: which chart governs which values file is Helm knowledge, and leaving it in an editor extension guarantees a future CLI reimplements it. The module holds no platform path semantics and splits URI paths itself.

no-tag is gone. The ticket requires silence when there is no chart metadata or no appVersion, so those references are dropped before any registry is asked, which made the reason dead. Its ReferenceVerdict and UncheckedReason aliases went with it in the same wave rather than surviving as shims, and the five consumers now use ImageVerdict and UnverifiableReason from the registry package directly.

Reviewer notes

  • Read the five commits in order. The first is a pure extraction of raw scalar reading so chart metadata can reuse it, and it leaves the existing extractor tests untouched. appVersion goes through the same path as tags for the same reason: YAML coerces appVersion: 1.10 to the float 1.1, and a chart whose appVersion is templated is no more usable than one with none.
  • The last two commits are review fallout and carry the only behaviour changes worth a second look. Chart.yml was accepted alongside Chart.yaml and should not have been, since Helm's own loader recognises only Chart.yaml, so accepting it would resolve an appVersion out of a file Helm ignores. Dropping it also halves the filesystem probes per directory level and lets the watcher glob be derived from the resolver's constant instead of spelled out again in another workspace.
  • A chart's own Chart.yaml came back in scope as a values file, because the exclusion only tested for a templates segment. Harmless in practice, since dependencies[].repository entries carry no corroborating sibling key, but it is fixed rather than left to chance.
  • The watcher's onDidCreate registration was deleted because it could match nothing. A document checked without a chart records no metadata path, so a Chart.yaml appearing above it would never re-check it. Catching that means re-resolving every open document on every write, which no ticket has asked for, and the limitation is documented on the handler.

Known gaps

  • The ancestor walk is unmemoised, costing up to one read per directory level on every open and on every chart metadata change. Real but out of scope here, and Persistent result cache and request concurrency cap #27 is where request cost gets addressed.
  • vscode.Uri.file pins the reader's scheme to file, so a virtual workspace resolves no charts and falls back to the values-file naming rule. Noted in the reader's own docstring.

Closes #24

🤖 Generated with Claude Code

CptSchnitz and others added 6 commits September 22, 2026 11:41
Reading a scalar's exact source text, rather than the value YAML parsed it
into, is about to have a second reader: chart metadata needs `appVersion`
read under the same rules a tag is read under. Move the primitive into its
own module so both readers agree on what "usable" means instead of one
importing an internal of the other.

Pure move. The extract-image-references test suite is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A values file means little on its own. Its tags may be absent, in which
case Helm renders `.Chart.AppVersion` in their place, and whether the file
is worth reading at all depends on the chart it sits under.

The nearest ancestor holding `Chart.yaml` wins, so a subchart's values file
resolves against the subchart's own metadata rather than its parent's, which
is what Helm would actually deploy. Everything under the chart's
`templates/` directory is out of scope, Go template source being nothing
this package can check. With no chart above it, a file is in scope only when
its own name says it is a values file.

Paths are split on `/` rather than handed to `node:path`. Callers pass a
URI path, forward-slash separated on every platform, and this package stays
free of platform path semantics.

`ResolvedTag` distinguishes the two provenances because they differ in what
they can offer a message: a tag written in the file has a range to
underline, a tag taken from chart metadata has a file to point at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ppVersion

Which YAML files this extension marks up is now the Helm package's answer,
not a filename pattern here: anything beneath a chart directory is in scope,
that chart's `templates/` directory never is, and a tagless reference is
checked against the governing chart's `appVersion`, which is what Helm would
render in its place.

A tagless reference nothing can resolve is dropped before the registry is
asked, so it renders no mark, no hover, and no diagnostic. That makes the
`'no-tag'` reason dead, and with it the `UncheckedReason` and
`ReferenceVerdict` wrappers: every surface now takes `ImageVerdict` and
`UnverifiableReason` from the registry package directly. Callers migrated and
the old names deleted in one wave rather than aliased.

A tag the file never wrote needs saying so. Its `tag-not-found` diagnostic
attaches to the repository value, there being nothing else to underline, and
both the diagnostic and the hover name the chart it came from through one
shared sentence, so the two surfaces cannot word it differently.

A `Chart.{yaml,yml}` watcher re-checks the open documents each chart governs.
A version bump otherwise leaves a stale checkmark standing at exactly the
moment correctness matters most.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Accepting `Chart.yml` alongside `Chart.yaml` resolved an `appVersion` out
of a file Helm's own loader ignores, contradicting the reason chart
context exists: to answer with what Helm would actually deploy. Dropping
it also halves the filesystem probes per ancestor directory, and leaves a
single exported constant a watcher can be built from rather than a list
another workspace has to spell out again.

A chart's own `Chart.yaml` also came back in scope as a values file, since
the exclusion test only looked for a `templates` segment. It declares a
chart rather than supplying values to one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The watcher glob spelled out the chart metadata names a second time, in a
different workspace from the resolver that reads them. A future third
spelling would have left the watcher silently under-covering, which shows
up as nothing happening. It is derived from the resolver's constant now.

The `onDidCreate` registration could match nothing. A document checked
without a chart records no metadata path, so a chart appearing above it
never re-checks it. Catching that means re-resolving every open document
on every write, which no ticket has asked for, so the dead registration
goes and the limitation is written down on the handler instead.

`chartProvenanceSentence` moves out of the module that produces checks and
into its own, beside `source-range`, the established home for a helper the
diagnostic and hover surfaces share.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two Chart.yaml saves in quick succession could let the slower registry
answer publish last, leaving a result for an appVersion already replaced.
Only the latest check started for a document now publishes.

The watcher test now bumps appVersion before the change event, so it
proves the re-check uses the new version, and the ImageReference
docstring no longer defers appVersion resolution to a later ticket.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@CptSchnitz
CptSchnitz merged commit bb75c81 into master Sep 24, 2026
6 checks passed
@CptSchnitz
CptSchnitz deleted the ticket-24/chart-context branch September 24, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chart context: file scope and appVersion-derived tags

1 participant