Skip to content

feat(deps): filter and group prompts - #18

Open
moshloop wants to merge 17 commits into
mainfrom
feat/deps-filters-grouped-prompts
Open

feat(deps): filter and group prompts#18
moshloop wants to merge 17 commits into
mainfrom
feat/deps-filters-grouped-prompts

Conversation

@moshloop

@moshloop moshloop commented Aug 6, 2026

Copy link
Copy Markdown
Member

What

  • Expose dependency filters through deps update and combine them with positional patterns.
  • Deduplicate identical version prompts for consistent updates.

Notes

  • Breaking: rename UpdateOptions.Expression to Filters and update VersionSelector to receive UpdateVersionPrompt.

Summary by CodeRabbit

  • New Features

    • Added dependency scanning for Go, Maven, Gradle, npm, pnpm, container images, and Helm charts.
    • Added dependency updates with filtering, version selection, dry-run, check, staging, and interactive selection.
    • Added deps diff for comparing dependency changes across Git revisions.
    • Added cache-warm to prepare and verify dependency caches, including offline workflows.
    • Added Helm chart references, image base discovery, stable/prerelease version checks, and improved dependency tree output.
  • Documentation

    • Expanded command guidance and updated output-format options.
  • Bug Fixes

    • Improved path display and Kubernetes resource/source resolution.

moshloop added 15 commits June 12, 2026 06:55
…semantic versioning

Add support for tracking both latest stable and pre-release versions separately using semantic versioning. This enables users to see available pre-release updates alongside stable updates.

Key changes:
- New LatestVersions struct to hold both stable and prerelease versions
- ResolveLatestVersions() method returns both version classes in a single lookup
- ImageInfo struct extended with LatestPrerelease and PrereleaseUpdateAvailable fields
- UI columns expanded to show separate stable and pre-release update indicators
- New semverUpdateAvailable() function for proper semantic version comparison
- Display paths now relative to working directory for better readability
- Added comprehensive tests for pre-release version handling

BREAKING CHANGE: ResolveLatest() behavior unchanged but ResolveLatestVersions() is the new recommended API for getting version information.
…m, and pnpm

Implement a new `deps` command that generates dependency graphs for multiple package managers. Supports auto-detection of manifests, native package manager resolution with fallback to manifest parsing, filtering, and depth control. Includes comprehensive support for Go modules, Maven POMs, Gradle builds, npm/pnpm lockfiles with circular dependency detection and duplicate analysis.

Features:
- Auto-detect and resolve dependencies for go, maven, gradle, npm, pnpm
- Native resolution via package manager tools with manifest fallback
- Configurable depth limiting and dependency filtering
- Duplicate and conflict detection
- JSON export for programmatic consumption
- Pretty-printed tree output with styled dependency metadata

Refs: dependency graph analysis
Implement a new `deps update` subcommand that enables users to discover, resolve, and apply updates to direct dependencies across Go, npm, pnpm, container images, and Helm charts.

Key features:
- Interactive tree-based UI for selecting dependencies to update with filtering support
- Version resolution and availability checking via package managers and image registries
- Dry-run mode to preview changes without applying them
- Check mode to list available updates without prompting
- Support for MatchItem expressions to filter dependencies by name, manager, scope, and file path
- Automatic git-aware manifest discovery that respects .gitignore
- Enhanced pnpm support with dependency map parsing

Changes include:
- New UpdateOptions, UpdateCandidate, UpdateChoice, and UpdatePlan types
- Image and Helm chart update discovery and application via imageupdate package
- Interactive tree picker UI using bubbletea for dependency selection
- Version sorting and filtering logic with semantic versioning support
- Comprehensive test coverage for all update scenarios
- Refactored manifest discovery to use git ls-files for better performance
…onal transitive graph support

Replace native/manifest/auto resolution modes with a single offline-first approach that reads local manifests and lockfiles without running package-manager commands. Add --depth 0 support for transitive graphs via tool-specific resolvers (go mod graph, mvn dependency:tree, gradle dependencies) that fail fast with toolError when tools are unavailable, suggesting --depth 1 for offline output.

Add new features:
- deps diff subcommand to compare dependency graphs across git revisions
- --flat flag to export flat node/edge lists instead of tree structure
- --include-indirect flag for Go to include indirect requirements at --depth 1
- Support for image and Helm chart discovery from Kubernetes manifests
- Comparison analysis with added/removed/updated change tracking

Remove deprecated options:
- --mode (native/manifest/auto)
- --configuration (Gradle-specific)
- --strict flag

Breaking changes:
- Default behavior now offline-only; use --depth 0 for transitive resolution
- JSON export structure changes: roots/nodes/edges now conditional on --flat flag
- Metadata.configurations replaced with Metadata.flat

Refs: refactor to simplify resolution logic and improve offline-first user experience
…lt tags

Tree rendering improvements computed before tree-type selection so flat and
tree views stay consistent:

- Drop the redundant manager prefix on child nodes; only the group root carries
  it (e.g. [go] appears once on the module root, not on every dependency).
- Suppress default/std scope tags (direct, require, compile, dependencies) and
  surface only meaningful ones (indirect, replaced, dev, optional, local).
- Group image/helm dependencies by Namespace -> Kind instead of a flat list.
- Print package roots (go.mod, etc.) as repo-relative paths instead of absolute.

Namespace/kind/resource/container are carried as out-of-band node properties so
the JSON model and statistics are unchanged.
Drop the synthetic "container images"/"helm charts" root wrappers from the
rendered tree and group all kubernetes-derived dependencies (images + helm) by:

  Namespace (☸ icon) > Kind (white) > resource name (shortened path) > image/chart

Image and helm dependencies in the same namespace now share one namespace node.
The shortened path keeps the last two segments to disambiguate sibling files
(e.g. manifests/ vs golden/). JSON model and statistics are unchanged.
Add automatic staging of updated dependency files after successful updates. When a dependency update is written, the affected manifest and lockfile(s) are now staged with `git add`. Dry-run and check modes do not stage files.

New UpdatePlan fields track staged files and any staging errors. The stageUpdatedFiles function handles manager-specific file patterns (go.mod/go.sum, package.json/package-lock.json, etc.) and only stages files that exist. Pretty() and Row() methods updated to display staging status.

Refs the update workflow documentation change describing this behavior.
Render each shared dependency once at its resolved (shallowest BFS)
occurrence instead of repeating it under every parent. The resolved node
is tagged with the count of other parents and each parent that hid a
duplicate gets a trailing "(and N other dependencies)" marker. The model
and JSON carry the new other_parents/hidden_duplicates counts.

--show-duplicates restores the full per-parent repetition with dup:N
tags. Stats, --flat node/edge lists, and deps diff stay on the full
uncollapsed graph (collapse is forced off internally for diffs).
Discover local Helm chart directories (Chart.yaml) in addition to Flux
HelmRelease manifests. Each chart becomes a dependency root carrying its
declared subchart dependencies (helm, version recorded verbatim from the
Chart.yaml range) plus the container images referenced in values.yaml and
templates/ (image). Templated values (`{{ ... }}`) are skipped since they
cannot be resolved offline.

Chart discovery is a filesystem walk independent of the git-backed
k8s-manifest scanner, so a chart directory outside a git repo still
resolves; image-discovery errors now degrade to a warning when other
roots resolved. Vendored subcharts under a parent chart's charts/ dir are
skipped. Chart roots render package-style (manager-prefixed children),
distinct from the namespace/kind grouping used for k8s manifest images.
At --depth other than 1 (mirroring go/maven/gradle), dependency scanning
now recurses into remote dependencies, backed by a persistent cache under
the user cache dir:

- Helm charts: each subchart is fetched from its Helm repository (index
  resolved via SemVer range, chart .tgz loaded with the Helm SDK chart
  loader), and its own subcharts and values/template images are recursed.
- Container images: the base image is resolved from the OCI
  base-image label and from the Dockerfile FROM directives in the image's
  source repository (located via the OCI source label or a ghcr/quay
  heuristic, cloned into the cache), then recursed.

The cache (remote_cache.go) keeps immutable artifacts (git clones, chart
archives, image config by digest) long and revalidates indexes/tags
daily, with negative caching and singleflight. Remote failures degrade to
per-node warnings; only cache initialization is fatal. --depth 1 stays
fully offline.

Helm is pinned to v3.17.x and only pkg/chart/loader is used (index.yaml
parsed directly): v3.18+ bumps distribution/v3 past what registry-scanner
needs, and pkg/repo transitively requires a k8s API removed in the pinned
k8s release.
…s under deps command

Consolidate image/helm and package manager (go/npm/pnpm) update logic into a single `deps update` command with unified filtering, version resolution, and interactive selection. The legacy `images update` command now delegates to `deps update --manager image,helm`.

Key changes:
- Add --latest and --version flags to resolve dependencies non-interactively
- Add resource filters (--kind, --namespace, --name, --selector) and name patterns (--image, --chart) for image/helm targets
- Make expression argument optional; empty expression matches all dependencies
- Support Flux v2 HelmRelease spec.chartRef (OCIRepository/HelmChart) in addition to inline spec.chart
- Extract version math, matching, plan rendering, resolution, and prompts into separate files
- Add helm credentials support (basic auth, TLS) for private chart repositories
- Refactor imageupdate discovery into DiscoverRepoTargets and Filter functions
- Add comprehensive tests for --latest, --version, resource filters, and chartRef resolution

BREAKING CHANGE: `images update` is deprecated; use `deps update --manager image,helm` instead. The `deps update` expression argument is now optional (was required).
Apply effective Kustomize namespaces to discovered targets, distinguish exact-file scans from directory scans, and report untracked YAML files. Preserve chart source-resolution errors so callers can surface actionable diagnostics.
Expose Kubernetes metadata filters for image and Helm dependencies, while preserving positional paths and root help behavior. Restore implicit scan routing for bare paths, flags, and empty invocations.
Add Kubernetes resource filtering for image and Helm scans, with actionable errors for untracked manifest targets. Reuse published-version lookups across duplicate occurrences while preserving per-occurrence updates. Collapse package-manager duplicates silently by default and retain duplicate analysis only with --show-duplicates.

BREAKING CHANGE: Remove Node.other_parents and Node.hidden_duplicates and their rendered markers; duplicate statistics and sections are no longer produced by default.
Expose MatchItem dependency filters through `deps update` and combine them with positional patterns.
Deduplicate identical version prompts so one confirmation updates repeated declarations consistently.
BREAKING CHANGE: rename `UpdateOptions.Expression` to `Filters` and change `VersionSelector` to receive `UpdateVersionPrompt`.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This change adds dependency scanning, comparison, remote resolution, update workflows, cache warming, image and Helm discovery, and related CLI commands. It also updates documentation, path display handling, output formats, and dependency declarations.

Changes

CLI entrypoints and repository integration

Layer / File(s) Summary
Dependency commands
cmd/repomap/deps.go, cmd/repomap/deps_diff.go, cmd/repomap/*_test.go
Adds deps, deps update, and deps diff commands with manager, filter, depth, path, Git reference, and update controls.
Cache warming command
cmd/repomap/cache_warm.go, cmd/repomap/cache_warm_test.go
Adds the cache-warm command for Go, npm, and pnpm specifications with build and offline verification flags.
CLI defaults and paths
cmd/repomap/main.go, cmd/repomap/paths.go, cmd/repomap/scan.go, tracked_yaml.go
Preserves implicit scan behavior, formats repository paths relative to the configured working directory, and loads tracked YAML contents.
Documentation and configuration
README.md, .gitignore, go.mod
Documents dependency scanning and cache warming, updates output-format examples, ignores .grite/, and adds direct module dependencies.

Dependency scanning and comparison

Layer / File(s) Summary
Dependency models and manifest resolution
deps/model.go, deps/discover.go, deps/go.go, deps/maven.go, deps/gradle.go, deps/npm.go, deps/pnpm.go
Adds dependency models, manifest discovery, and offline resolution for Go, Maven, Gradle, npm, and pnpm.
Graph construction and normalization
deps/edgegraph.go, deps/filter.go, deps/collapse.go, deps/common.go, deps/dockerfile.go
Adds graph expansion, cycle handling, depth limits, filtering, deterministic ordering, duplicate analysis, and Dockerfile base-image parsing.
Scan orchestration and rendering
deps/scan.go, deps/scan_chart.go, deps/scan_image.go, deps/pretty.go, deps/pretty_tree.go
Builds dependency exports from package manifests, Kubernetes resources, Helm charts, values, and templates. It applies filters, remote resolution, statistics, and structured rendering.
Comparison and Git revisions
deps/compare.go, deps/compare_pretty.go, deps/compare_scan.go, cmd/repomap/deps_diff.go
Compares dependency exports, classifies added, removed, and updated dependencies, renders results, and scans Git revisions or the working tree.

Remote resolution and update workflows

Layer / File(s) Summary
Remote cache and expansion
deps/remote_cache.go, deps/remote_helm_client.go, deps/resolve_remote.go, deps/chart_remote.go, deps/image_base.go, deps/helm_credentials.go
Adds cached HTTP, Git, image, and Helm retrieval. It expands Helm subcharts and image base images with warnings, credentials, cleanup, and depth limits.
Image and chart source resolution
imageupdate/chartref.go, imageupdate/discover.go, imageupdate/extract.go, imageupdate/sourceref.go, imageupdate/target.go, imageupdate/labels.go, imageupdate/resolver.go
Adds Flux chart-reference resolution, source indexing, Kubernetes target discovery, OCI label lookup, and separate stable and prerelease version resolution.
Dependency update workflow
deps/update.go, deps/update_match.go, deps/update_resolve.go, deps/update_modes.go, deps/update_apply.go, deps/update_image.go, deps/update_stage.go
Adds candidate discovery, filtering, version lookup, explicit/latest/check/dry-run modes, manager-specific edits, image and Helm updates, and Git staging.
Update presentation and selection
deps/update_plan.go, deps/update_prompt.go, deps/update_tree.go, deps/update_version.go
Adds update-plan rendering, shared prompts, an interactive Bubble Tea picker, deterministic grouping, and semantic-version helpers.

Cache warming implementation

Layer / File(s) Summary
Warming contracts and orchestration
deps/manifest/*.go, deps/runner.go, deps/cachewarm.go, deps/cachewarm_plan.go
Adds command and warming-step abstractions, manager aliases, per-spec execution, result reporting, cache summaries, and structured output.
Manager-specific warming
deps/manager/gomod/*, deps/manager/node/*, deps/manager/npm/*, deps/manager/pnpm/*
Adds Go, npm, and pnpm warmers with download, build, cleanup, and offline verification steps. Tests validate command order, environment settings, probes, and pnpm version gates.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.66% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the dependency filtering and prompt grouping changes identified as the PR objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/deps-filters-grouped-prompts
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/deps-filters-grouped-prompts
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/deps-filters-grouped-prompts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (20)
go.mod-22-22 (1)

22-22: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Other (CWE-1395)

Reachability: External

Reachability path
● Entry
  deps/remote_cache.go:1
  golang.org/x/sync
│
▼
● Sink
  go.mod

Upgrade Helm before merge.

helm.sh/helm/v3 v3.17.3 is affected by chart-driven security advisories used by deps/remote_helm_client.go when resolving and loading remote Helm charts. Upgrade to a patched version outside all affected ranges, such as Helm v3.17.4+ or v3.18.4+. Avoid v3.18.0 through v3.18.3 for the code-injection advisory. Verify the module graph after the upgrade.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` at line 22, Upgrade the helm.sh/helm/v3 dependency from v3.17.3 to a
patched release such as v3.17.4+ or v3.18.4+, avoiding v3.18.0–v3.18.3, then
verify the resulting Go module graph and ensure deps/remote_helm_client.go
remains compatible.

Source: Linters/SAST tools

deps/compare.go-71-95 (1)

71-95: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve scan warnings in comparison output.

Compare drops warnings from both exports. Comparison.Pretty also skips warning rendering when no dependency changed. A partial or degraded scan can therefore appear as a clean comparison.

  • deps/compare.go#L71-L95: Copy warnings from each non-nil input export into comparison.Warnings.
  • deps/compare_pretty.go#L34-L45: Append "No dependency changes" without returning, then render comparison.Warnings.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/compare.go` around lines 71 - 95, Update Compare in deps/compare.go
(lines 71-95) to copy warnings from each non-nil input export into
comparison.Warnings while preserving the existing dependency comparison. Update
Comparison.Pretty in deps/compare_pretty.go (lines 34-45) to append “No
dependency changes” without returning, then render comparison.Warnings even when
no dependency changes exist.
deps/update_image.go-112-121 (1)

112-121: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

imageVersionOnly returns the digest hex instead of the tag.

The function locates the tag separator before it strips the digest. strings.LastIndex(value, ":") finds the colon inside @sha256:..., so:

  • nginx:1.25.3@sha256:abcd returns abcd instead of 1.25.3.
  • nginx@sha256:abcd returns abcd instead of an empty version.

The @ strip on line 115 runs after the slice, so it never removes the digest. stripImageVersion on lines 102-110 already handles this correctly by stripping @ first.

This value becomes UpdateCandidate.Current, so UpdatePlan.OldVersion is wrong and selectedVersionIsCurrent compares against a digest fragment for digest-pinned images.

🐛 Proposed fix
 func imageVersionOnly(value string) string {
+	if at := strings.Index(value, "@"); at >= 0 {
+		value = value[:at]
+	}
 	if i := imageTagSeparator(value); i >= 0 {
-		version := value[i+1:]
-		if at := strings.Index(version, "@"); at >= 0 {
-			version = version[:at]
-		}
-		return version
+		return value[i+1:]
 	}
-	return value
+	return ""
 }

Confirm the intended return for an image with no tag. The current code returns the whole reference, which reads as a version in plan output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/update_image.go` around lines 112 - 121, Update imageVersionOnly to
remove any `@digest` suffix before locating the tag separator, reusing the
ordering established by stripImageVersion. Return only the tag for tagged
references, and return an empty version for digest-only or untagged references
instead of returning the full image reference.
cmd/repomap/images.go-14-25 (1)

14-25: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle digest suffixes and registry ports in versionOnly.

strings.LastIndex finds the colon inside the digest, not the tag separator. For ghcr.io/acme/app:1.2.3@sha256:abc… the function returns the digest hex, because the @ strip runs on a substring that no longer contains @. A registry port without a tag, for example registry:5000/app, returns 5000/app.

The result feeds semverUpdateAvailable in cmd/repomap/images_list.go. semver.NewVersion then fails for the digest hex, the code falls back to latest != current, and the row reports a false update for every digest-pinned image.

Strip the digest first, then take the colon after the last /.

🐛 Proposed fix
 func versionOnly(currentValue string) string {
-	if i := strings.LastIndex(currentValue, ":"); i >= 0 {
-		v := currentValue[i+1:]
-		if at := strings.Index(v, "@"); at >= 0 {
-			v = v[:at]
-		}
-		return v
-	}
-	return currentValue
+	v := currentValue
+	if at := strings.Index(v, "@"); at >= 0 {
+		v = v[:at]
+	}
+	// Only a colon after the last path separator is a tag separator; an
+	// earlier one belongs to a registry host:port.
+	start := strings.LastIndex(v, "/") + 1
+	if i := strings.LastIndex(v[start:], ":"); i >= 0 {
+		return v[start+i+1:]
+	}
+	return v
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/repomap/images.go` around lines 14 - 25, Update versionOnly to remove any
digest suffix at “@” before parsing the tag, then locate the final colon only in
the portion after the last “/” so registry ports without tags are preserved
correctly. Return the resulting tag/version when a post-path colon exists,
otherwise return the digest-stripped value unchanged.
imageupdate/chartref.go-128-150 (1)

128-150: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not mutate the target before the version check fails.

Lines 130-145 write RepoURL, IsOCI, File, FieldLine, FieldJSONPath, CurrentValue, and ChartName onto t. Line 146 then returns an error when CurrentValue is empty. The target keeps every partial mutation.

Resolve is documented as idempotent and returns early when t.RepoURL != "" (imageupdate/sourceref.go line 109). Because this error path already set RepoURL, a second Resolve call on the same target returns nil. DiscoverTargets records SourceErr and keeps the target (imageupdate/discover.go lines 72-79); checkInfo in cmd/repomap/images_list.go line 169 then calls Resolve again on it. That second call reports success, the "no editable version" error disappears, and version resolution runs with an empty CurrentValue.

Validate the version before you write to t.

🐛 Proposed fix
+	if src.version == "" {
+		return fmt.Errorf("HelmRelease %s/%s references %s %q which has no editable version (uses semver/digest, not a tag)",
+			t.Ref.Namespace, t.Ref.Name, src.kind, t.ChartRefName)
+	}
+
 	switch src.kind {
 	case "OCIRepository":
 		t.RepoURL = src.repoURL
 		t.IsOCI = src.isOCI
 	case "HelmChart":
 		repo, err := idx.lookupHelmRepository(src.srcRefName, chartSourceNamespace(src, wantNS), t.Ref)
 		if err != nil {
 			return fmt.Errorf("HelmChart %s/%s: %w", src.srcRefNamespace, src.chartName, err)
 		}
 		t.RepoURL = repo.URL
 		t.IsOCI = repo.IsOCI
 	}
 
 	t.File = src.file
 	t.FieldLine = src.versionLine
 	t.FieldJSONPath = src.versionPath
 	t.CurrentValue = src.version
 	t.ChartName = src.chartName
-	if t.CurrentValue == "" {
-		return fmt.Errorf("HelmRelease %s/%s references %s %q which has no editable version (uses semver/digest, not a tag)",
-			t.Ref.Namespace, t.Ref.Name, src.kind, t.ChartRefName)
-	}
 	return nil
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@imageupdate/chartref.go` around lines 128 - 150, Update the target-population
flow in Resolve so src.version is validated for an editable, non-empty version
before assigning any fields to t. Keep the existing error behavior for
unsupported semver/digest references, and only mutate RepoURL, IsOCI, File,
FieldLine, FieldJSONPath, CurrentValue, and ChartName after that validation
succeeds.
cmd/repomap/images_list.go-166-192 (1)

166-192: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Build info after sourceIndex.Resolve, not before.

Line 167 snapshots t into info before line 169 resolves it. For a chartRef HelmRelease, Resolve sets CurrentValue, File, FieldLine, and ChartName on the target (imageupdate/chartref.go lines 141-145); before resolution those fields are empty. imageupdate/chartref_test.go line 47 asserts exactly that.

Two results follow for every chartRef chart row:

  • info.Current stays empty, so the Current column is blank while the Latest column shows a version and a green update mark.
  • info.File keeps the HelmRelease file instead of the OCIRepository/HelmChart file that holds the version literal, so the listed file does not match the file deps update edits.

The update flags at lines 189-190 already read the resolved t, which makes the row internally inconsistent.

🐛 Proposed fix
 func checkInfo(ctx context.Context, resolver *imageupdate.Resolver, sourceIndex *imageupdate.SourceIndex, t imageupdate.UpdateTarget, displayFile string, tk *task.Task) ImageInfo {
-	info := baseInfo(t, true, displayFile)
 	if t.Kind == imageupdate.TargetChart {
 		if err := sourceIndex.Resolve(&t); err != nil {
 			tk.Warnf("source unresolved: %v", err)
+			info := baseInfo(t, true, displayFile)
 			info.Error = err.Error()
 			return info
 		}
 	}
+	// Resolve redirects a chartRef target onto the source object that holds the
+	// version literal, so snapshot the row only after it succeeds.
+	info := baseInfo(t, true, displayFile)
 	tk.Infof("looking up latest stable and pre-release versions")

The caller computes displayFile from the pre-resolution target.File. Recompute it from the resolved t.File so the listed file matches the edit anchor. That needs the displayPath function inside checkInfo:

// cmd/repomap/images_list.go — pass displayPath instead of a precomputed string
group.Add(taskName(target, displayPath(target.File)), func(ctx flanksourceContext.Context, tk *task.Task) (int, error) {
	infos[idx] = checkInfo(ctx, resolver, sourceIndex, target, displayPath, tk)
	return idx, nil
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/repomap/images_list.go` around lines 166 - 192, Move the baseInfo call in
checkInfo until after sourceIndex.Resolve(&t) completes, so chart targets use
resolved CurrentValue, File, FieldLine, and ChartName. Change checkInfo to
receive the displayPath function rather than a precomputed displayFile, then
compute the display file from the resolved t.File before building info; update
its caller accordingly while preserving non-chart behavior.
tracked_yaml.go-16-25 (1)

16-25: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use NUL-delimited git output for path discovery.

TrackedYAMLContents() splits git ls-files output on \n, but git ls-files quotes paths with non-ASCII bytes, newlines, or double quotes by default. A quoted path is passed as an escaped filename to ReadFileWithFallback and skipped, so discovery omits YAML files that should be included.

Use git ls-files -z and split on \x00; the scan and chart catalog paths already use this pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tracked_yaml.go` around lines 16 - 25, Update TrackedYAMLContents to invoke
git ls-files with the -z option and split result.Stdout on NUL characters
instead of newlines. Preserve the existing trimming, YAML filtering, and file
processing behavior so paths containing special characters are passed unescaped
to ReadFileWithFallback.
deps/discover.go-132-151 (1)

132-151: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply ignoredDirs in the git discovery path.

walkManifestFiles skips vendor, node_modules, build, dist, target, and .gradle. gitManifestFiles skips none of them. git ls-files --others --exclude-standard only removes gitignored paths, so a committed vendor/ or node_modules/ tree yields manifests here that the filesystem walk would drop. The discovered project set then changes based on whether git is available.

Filter the git results with the same rule.

🐛 Proposed fix to share the ignore rule
 	var files []string
 	for _, rel := range strings.Split(string(out), "\x00") {
 		if rel == "" {
 			continue
 		}
-		name := filepath.Base(filepath.FromSlash(rel))
+		local := filepath.FromSlash(rel)
+		if hasIgnoredDir(local) {
+			continue
+		}
+		name := filepath.Base(local)
 		if managerForManifest(name) == "" {
 			continue
 		}
-		files = append(files, filepath.Join(root, filepath.FromSlash(rel)))
+		files = append(files, filepath.Join(root, local))
 	}

Add the helper:

func hasIgnoredDir(rel string) bool {
	for _, part := range strings.Split(filepath.ToSlash(filepath.Dir(rel)), "/") {
		if ignoredDirs[part] {
			return true
		}
	}
	return false
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/discover.go` around lines 132 - 151, Apply the existing ignoredDirs rule
to gitManifestFiles so committed vendor, node_modules, build, dist, target, and
.gradle paths are excluded consistently with walkManifestFiles. Add and use a
shared hasIgnoredDir helper when filtering each relative git result, before
managerForManifest and appending the path.
deps/npm.go-132-176 (1)

132-176: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Unbounded tree expansion for hoisted npm lockfiles.

packageLockChildren re-expands the full subtree of every dependency at every parent that declares it. seen only stops cycles along a single path; it does not stop repeats across sibling branches, and no MaxDepth is applied during construction. A hoisted package-lock.json in which many packages depend on the same popular package therefore materializes that package's subtree once per referencing parent, so node count grows combinatorially before filterAndPrune can bound anything.

buildTreeFromEdgeGraph in deps/edgegraph.go already solves this by expanding a key only at its shallowest BFS occurrence and by pruning past MaxDepth during construction. Consider building an edgeGraph from lock.Packages and reusing that path, or at minimum thread Options.MaxDepth into this recursion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/npm.go` around lines 132 - 176, Bound npm lockfile tree construction in
packageLockChildren by applying Options.MaxDepth during recursion and preventing
repeated subtree expansion across sibling branches, preferably by building an
edgeGraph from lock.Packages and reusing buildTreeFromEdgeGraph’s shallowest-BFS
expansion behavior. Ensure hoisted dependencies are not materialized once per
referencing parent before filterAndPrune runs.
deps/filter.go-75-97 (1)

75-97: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Statistics.Circular under-counts circular references.

The if node.Circular check sits inside the if _, exists := nodeMap[node.ID]; !exists block. A circular occurrence always repeats an ancestor ID, and that ancestor was inserted into nodeMap earlier as a non-circular node. The circular occurrence therefore hits the exists branch and is never counted, so circular_references reports 0 for the cycle case that TestBuildTreeTerminatesOnCycle constructs. Move the circular tally outside the dedup guard.

🐛 Proposed fix
 		if _, exists := nodeMap[node.ID]; !exists {
 			nodeMap[node.ID] = FlatNode{
 				...
 			}
 			stats.ByManager[node.Manager]++
 			if node.Depth > stats.MaxDepth {
 				stats.MaxDepth = node.Depth
 			}
-			if node.Circular {
-				circular++
-			}
 		}
+		if node.Circular {
+			circular++
+		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/filter.go` around lines 75 - 97, Move the node.Circular tally in the
node-processing logic outside the nodeMap deduplication guard, while keeping
node insertion and manager/depth statistics inside it. Ensure every circular
occurrence increments the circular count, including repeated ancestor IDs
handled by the exists branch, so Statistics.Circular reports cycle references
correctly.
deps/go_graph.go-105-134 (1)

105-134: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

applyGoModMetadata marks deeper occurrences as direct.

requires is keyed by module path only, and walk applies it at every depth. go mod graph lists MVS inputs, so the same module path appears at several versions and at several depths. Every occurrence of a required path receives Direct = true and Scope = "require", including nodes at depth 2 or deeper. This disagrees with resolveGoManifest in deps/go.go, which marks only depth-1 children as direct, and it corrupts direct-dependency counts and filters.

Key the lookup by path and version, and set Direct only for depth-1 nodes.

🐛 Proposed fix
 	var walk func(n *Node)
 	walk = func(n *Node) {
 		if n == nil {
 			return
 		}
 		if req, ok := requires[n.Name]; ok {
-			n.Direct = !req.Indirect
+			n.Direct = !req.Indirect && n.Depth == 1
 			if req.Indirect {
 				n.Scope = "indirect"
 			} else {
 				n.Scope = "require"
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/go_graph.go` around lines 105 - 134, Update applyGoModMetadata to track
traversal depth and mark Direct and Scope as direct/require only for depth-1
children of root; deeper matching nodes must not receive direct metadata. Key
requires by both module path and version, and use that path-version lookup when
applying replacement and dependency metadata while preserving indirect handling
for direct children.
deps/pnpm.go-110-142 (1)

110-142: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

The recursion enumerates every path and ignores MaxDepth.

buildPNPMNode calls cloneBoolMap(seen) for each child at Line 138. The seen set is therefore per-path, not global. A package reached through N distinct paths is expanded N times, and each expansion clones the whole set again.

npm and pnpm graphs share subtrees heavily. A diamond of depth d produces on the order of 2^d expansions. A real pnpm-lock.yaml with a few thousand packages can consume very large amounts of CPU and memory in a single scan.

There is no depth cap on this path. resolvePNPMManifest takes no Options, so Options.MaxDepth is never consulted. The Go, Maven, and Gradle resolvers all route through buildTreeFromEdgeGraph(edgeTreeOptions{Graph: graph, MaxDepth: opts.MaxDepth}) and get truncation for free.

Build an edgeGraph from the lockfile and reuse buildTreeFromEdgeGraph. That fixes the depth cap and the repeated expansion together, and it makes pnpm consistent with the other managers.

Run the following script to confirm the resolver signatures and how MaxDepth reaches the other managers:

#!/bin/bash
# Description: Compare pnpm resolution against the edge-graph resolvers and locate the dispatch site.
set -euo pipefail

rg -nP --type=go -C3 '\bresolvePNPMManifest\s*\(|\bresolveNPMManifest\s*\('
rg -nP --type=go -C6 'buildTreeFromEdgeGraph\('
ast-grep run --pattern 'func cloneBoolMap($$$) { $$$ }' --lang go deps
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/pnpm.go` around lines 110 - 142, Replace the recursive buildPNPMNode
traversal with an edgeGraph constructed from the pnpm lockfile, then resolve it
through buildTreeFromEdgeGraph using edgeTreeOptions with opts.MaxDepth. Update
resolvePNPMManifest and its dispatch callers to accept and propagate Options,
and remove the per-path cloneBoolMap recursion so shared subtrees are expanded
once while respecting the depth cap.
deps/helm_credentials.go-93-112 (1)

93-112: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Set a timeout and surface failures to load TLS material.

Two problems in httpClientForCreds:

  1. The returned client has no Timeout. A chart repository that accepts the connection and then stalls blocks the scan for as long as the operating system permits. http.DefaultClient on Lines 95 and 81 has the same property. Set an explicit Timeout, or rely on a context deadline applied by the caller.
  2. Lines 99-110 discard every error. If CAFile is unreadable, or contains no valid PEM, or LoadX509KeyPair fails, the client silently falls back to system roots and no client certificate. The user configured a pinned CA and mutual TLS, and receives neither, with no diagnostic. Return the error, or record a warning.
🛠️ Proposed change for the timeout
-	return &http.Client{Transport: &http.Transport{TLSClientConfig: tlsCfg}}
+	return &http.Client{
+		Timeout:   helmHTTPTimeout,
+		Transport: &http.Transport{TLSClientConfig: tlsCfg},
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/helm_credentials.go` around lines 93 - 112, Update httpClientForCreds to
enforce an explicit request timeout for both custom and default clients, or
ensure its callers apply an equivalent context deadline. Stop silently ignoring
CA and client-certificate loading failures: propagate the error through the
function’s return contract or emit a warning while preserving the configured TLS
material behavior, including unreadable/invalid CA files and failed
LoadX509KeyPair calls.
deps/maven.go-23-23 (1)

23-23: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Load arbitrary POM property elements under dependencies

xml:"properties>*" makes pom.Properties always empty because Go’s XML decoder matches the final path segment literally, so an element named * never occurs. Copy this pattern around line 64 into all dependency Version fields in deps/maven_tree.go so dependency versions resolved from POM properties are not left as unexpanded ${...} values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/maven.go` at line 23, Update the dependency Version XML mappings in
maven_tree.go to use the working arbitrary-property-element pattern already
present near line 64, rather than xml:"properties>*". Apply it to every
dependency Version field so POM property references are loaded and can be
expanded instead of remaining as ${...} values.
deps/gradle_tree.go-49-63 (1)

49-63: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the Gradle wrapper search to the scan root.

gradleCommand walks from project.Dir to the filesystem root. If no gradlew exists under the selected dependency scan root, it can execute a wrapper from an unrelated parent directory, such as $HOME/gradlew or /gradlew. Stop the walk once the search leaves the scan root.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/gradle_tree.go` around lines 49 - 63, Update gradleCommand so its upward
search is bounded by the dependency scan root, stopping before checking
directories outside that root; retain the existing wrapper detection within the
root and return "gradle" when no in-root gradlew is found.
deps/pnpm.go-65-82 (1)

65-82: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The bare-name index entry is nondeterministic.

Lines 76-78 store a package under its bare name when that key is free. The enclosing loop ranges over a Go map, and Go randomizes map iteration order. When a lockfile contains several versions of the same package, which version wins is different on each run.

buildPNPMNode uses that entry as a fallback at Line 120 whenever the exact name@version lookup misses. The resulting node then gets a different Source and a different child set between runs of the same scan.

This breaks reproducible output. deps diff compares two exports and will report changes that do not exist.

Choose the fallback deterministically. Sort the keys before indexing, or select the highest version.

🐛 Proposed fix
 func pnpmPackageIndex(raw map[string]any) map[string]pnpmPackage {
 	out := map[string]pnpmPackage{}
 	for _, section := range []string{"packages", "snapshots"} {
-		for key, value := range asMap(raw[section]) {
+		entries := asMap(raw[section])
+		keys := make([]string, 0, len(entries))
+		for key := range entries {
+			keys = append(keys, key)
+		}
+		sort.Strings(keys)
+		for _, key := range keys {
+			value := entries[key]
 			name, version := splitPNPMPackageKey(key)
 			if name == "" {
 				continue
 			}
 			data := asMap(value)
 			pkg := pnpmPackage{Name: name, Version: version, Key: key, Data: data, Children: pnpmDeps(data)}
 			out[pnpmPackageID(name, version)] = pkg
 			if _, ok := out[name]; !ok {
 				out[name] = pkg
 			}
 		}
 	}
 	return out
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/pnpm.go` around lines 65 - 82, Make the bare-name fallback in
pnpmPackageIndex deterministic when multiple versions exist. Sort package keys
before iterating the packages and snapshots maps, or consistently select the
highest version, so the entry used by buildPNPMNode after an exact lookup miss
always yields the same package and children.
deps/helm_credentials.go-79-91 (1)

79-91: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sensitive Data Exposure (CWE-319): Cleartext Transmission of Sensitive Information

Reachability: Internal

Restrict Basic credentials to HTTPS endpoints without downgrade paths.

authorize sends Basic auth when the repository URL matches, including http:// repos, and the returned client uses Go’s default redirect policy. An https:// repository can retain the Authorization header across a same-host redirect to http://; the custom client never checks CheckRedirect at all. Reject matching credentials unless the initial request is https, and refuse downgrade redirects by clearing Authorization and returning an error when the redirected URL is not https.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/helm_credentials.go` around lines 79 - 91, The authorize method must
only apply matching credentials when req.URL uses HTTPS; otherwise return
http.DefaultClient without setting Basic auth. Update the client returned by
httpClientForCreds to enforce a CheckRedirect policy that clears Authorization
and returns an error whenever a redirect target is not HTTPS, while preserving
safe HTTPS redirects.

Source: Linters/SAST tools

deps/scan.go-70-77 (1)

70-77: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Degrade chart-discovery failures to warnings, as image discovery does.

chartErr aborts the whole scan. imageErr only aborts when nothing else resolved (Lines 88-96). discoverChartFiles propagates filepath.WalkDir errors verbatim, so one unreadable directory fails a scan that already resolved Go, Maven, or npm roots. Apply the same degrade policy to chartErr.

🛠️ Proposed fix
+	var chartErr error
 	if scanImages {
 		...
 		if matcher.IsEmpty() {
-			chartRoots, chartWarnings, chartErr := discoverChartDependencyRoots(absPath, opts.Managers)
+			var chartRoots []*Node
+			var chartWarnings []Warning
+			chartRoots, chartWarnings, chartErr = discoverChartDependencyRoots(absPath, opts.Managers)
 			warnings = append(warnings, chartWarnings...)
-			if chartErr != nil {
-				return nil, chartErr
-			}
 			roots = append(roots, chartRoots...)
 		}

Then handle chartErr next to imageErr: return it only when len(roots) == 0, otherwise append it as a Warning.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/scan.go` around lines 70 - 77, Update the chartErr handling in the scan
flow around discoverChartDependencyRoots to match imageErr: if len(roots) == 0,
return chartErr; otherwise append chartErr as a Warning and continue scanning.
Preserve the existing chartRoots and chartWarnings accumulation for successful
or partially successful discovery.
deps/runner.go-27-39 (1)

27-39: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound external command execution with a default timeout.

ExecRunner.Run inherits whatever context the caller supplies. Scan and Update forward the caller context, which commonly has no deadline. mvn dependency:tree and gradle dependencies reach the network and can hang. A hang inside resolveProjectsWithTasks stalls the whole scan with no bound and no output.

Add a configurable default deadline on the runner.

🛠️ Proposed fix
-type ExecRunner struct{}
+type ExecRunner struct {
+	// Timeout bounds a single command. Zero selects defaultCommandTimeout.
+	Timeout time.Duration
+}
+
+const defaultCommandTimeout = 5 * time.Minute
 
-func (ExecRunner) Run(ctx context.Context, spec Command) (CommandResult, error) {
+func (r ExecRunner) Run(ctx context.Context, spec Command) (CommandResult, error) {
+	timeout := r.Timeout
+	if timeout == 0 {
+		timeout = defaultCommandTimeout
+	}
+	ctx, cancel := context.WithTimeout(ctx, timeout)
+	defer cancel()
 	cmd := exec.CommandContext(ctx, spec.Name, spec.Args...)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/runner.go` around lines 27 - 39, Update ExecRunner.Run to apply a
configurable default timeout when the supplied context has no deadline, while
preserving caller-provided deadlines. Create the derived context before
exec.CommandContext and ensure its cancellation is released after command
execution. Keep the existing command output and error handling unchanged.
deps/remote_helm_client.go-175-186 (1)

175-186: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the decompressed size of chart archives.

io.Copy writes the tar entry to disk with no limit. The //nolint:gosec comment states that chart archives are size-bounded, but that bound applies to the compressed bytes that cache.Fetch returned. Gzip expansion is not bounded by it. A 10 MB archive from a remote index can expand to many gigabytes and exhaust the disk. The archive host is chosen by the scanned Chart.yaml.

Track a running total across entries and stop when it exceeds a cap.

🛠️ Proposed fix
+// maxChartExtractBytes caps the total expanded size of a chart archive.
+const maxChartExtractBytes = 512 << 20
+
 func extractTarGz(data []byte, dest string) error {
 	gz, err := gzip.NewReader(bytes.NewReader(data))
 	if err != nil {
 		return err
 	}
 	defer func() { _ = gz.Close() }()
 	tr := tar.NewReader(gz)
+	var written int64
 	for {
 		f, err := os.Create(target)
 		if err != nil {
 			return err
 		}
-		if _, err := io.Copy(f, tr); err != nil { //nolint:gosec // chart archives are size-bounded
+		n, err := io.Copy(f, io.LimitReader(tr, maxChartExtractBytes-written))
+		written += n
+		if err != nil {
 			_ = f.Close()
 			return err
 		}
+		if written >= maxChartExtractBytes {
+			_ = f.Close()
+			return fmt.Errorf("chart archive exceeds %d bytes when extracted", maxChartExtractBytes)
+		}
 		if err := f.Close(); err != nil {
 			return err
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/remote_helm_client.go` around lines 175 - 186, In the chart extraction
flow around the tar-entry copy and file handling, track cumulative decompressed
bytes across all entries and enforce a maximum archive-size cap. Replace
unrestricted io.Copy with a bounded copy that cannot write beyond the remaining
allowance, stop and return an error when the cap is exceeded, and preserve
cleanup of the open file on copy failure. Keep the existing close-error handling
and compressed cache.Fetch limit unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 85a6e85b-3399-4985-8cd7-0667623561a2

📥 Commits

Reviewing files that changed from the base of the PR and between c795c64 and 965b7c2.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (95)
  • .gitignore
  • .grite/export.json
  • README.md
  • cmd/repomap/deps.go
  • cmd/repomap/deps_diff.go
  • cmd/repomap/deps_diff_test.go
  • cmd/repomap/deps_test.go
  • cmd/repomap/images.go
  • cmd/repomap/images_list.go
  • cmd/repomap/images_list_test.go
  • cmd/repomap/images_test_helpers.go
  • cmd/repomap/images_update.go
  • cmd/repomap/images_update_test.go
  • cmd/repomap/main.go
  • cmd/repomap/main_test.go
  • cmd/repomap/paths.go
  • cmd/repomap/scan.go
  • deps/chart_remote.go
  • deps/collapse.go
  • deps/collapse_test.go
  • deps/common.go
  • deps/compare.go
  • deps/compare_pretty.go
  • deps/compare_pretty_test.go
  • deps/compare_scan.go
  • deps/compare_scan_test.go
  • deps/compare_test.go
  • deps/discover.go
  • deps/dockerfile.go
  • deps/dockerfile_test.go
  • deps/edgegraph.go
  • deps/edgegraph_test.go
  • deps/filter.go
  • deps/go.go
  • deps/go_graph.go
  • deps/go_graph_test.go
  • deps/go_test.go
  • deps/gradle.go
  • deps/gradle_tree.go
  • deps/gradle_tree_test.go
  • deps/helm_credentials.go
  • deps/helm_credentials_test.go
  • deps/image_base.go
  • deps/maven.go
  • deps/maven_tree.go
  • deps/maven_tree_test.go
  • deps/model.go
  • deps/npm.go
  • deps/npm_test.go
  • deps/pnpm.go
  • deps/pretty.go
  • deps/pretty_tree.go
  • deps/pretty_tree_test.go
  • deps/remote_cache.go
  • deps/remote_cache_test.go
  • deps/remote_helm_client.go
  • deps/remote_helm_client_test.go
  • deps/resolve_remote.go
  • deps/resolve_remote_test.go
  • deps/runner.go
  • deps/scan.go
  • deps/scan_chart.go
  • deps/scan_chart_test.go
  • deps/scan_image.go
  • deps/scan_test.go
  • deps/update.go
  • deps/update_apply.go
  • deps/update_image.go
  • deps/update_match.go
  • deps/update_modes.go
  • deps/update_modes_test.go
  • deps/update_plan.go
  • deps/update_prompt.go
  • deps/update_prompt_test.go
  • deps/update_resolve.go
  • deps/update_stage.go
  • deps/update_test.go
  • deps/update_tree.go
  • deps/update_tree_test.go
  • deps/update_version.go
  • go.mod
  • imageupdate/chartref.go
  • imageupdate/chartref_test.go
  • imageupdate/discover.go
  • imageupdate/discover_test.go
  • imageupdate/extract.go
  • imageupdate/labels.go
  • imageupdate/resolver.go
  • imageupdate/resolver_test.go
  • imageupdate/sourceref.go
  • imageupdate/sourceref_test.go
  • imageupdate/target.go
  • imageupdate/testdata/manifests/helmrelease-chartref-helmchart.yaml
  • imageupdate/testdata/manifests/helmrelease-chartref-oci.yaml
  • tracked_yaml.go
💤 Files with no reviewable changes (1)
  • cmd/repomap/images_update_test.go

Comment thread deps/go.go
Comment on lines +11 to +18
func resolveGoManifest(project Project, opts Options) (*Node, []Warning, error) {
file, err := loadGoModFile(project.Dir)
if err != nil {
return nil, nil, err
}
root := NewNode(ManagerGo, file.Module.Mod.Path, "")
root.Path = filepath.Join(project.Dir, "go.mod")
root.Source = "go.mod"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

golang.org/x/mod/modfile Parse Module field nil missing module directive

💡 Result:

In the golang.org/x/mod/modfile package, the Parse function does not return an error if the input go.mod file is missing a module directive [1]. Instead, it successfully returns a *modfile.File struct in which the Module field is nil [2][1]. This behavior can lead to runtime panics (e.g., null pointer dereferences) if code accessing the parsed File struct does not explicitly check whether Module is nil before attempting to access its fields [2][1]. Because modfile.Parse treats a missing module directive as a valid parsed state rather than a parse error, it is the responsibility of the consumer to validate the existence of the module declaration if required by their application logic [1]. For example, the Go toolchain's internal modload package includes explicit logic to check for a nil Module field after calling modfile.Parse and returns a custom error message when the declaration is missing [3][4]. When working with modfile.File, you should always perform a nil-check on the Module field before accessing it to ensure the go.mod file contains a valid module directive [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)deps/(go\.(go|mod|sum)|go_graph\.go)|go.mod$|go.sum$' || true

echo "== locate relevant symbols =="
rg -n "func loadGoModFile|loadGoModFile|file\.Module|Module\.Mod\.Path|go.mod" deps -S

echo "== deps/go.go outline and relevant section =="
if [ -f deps/go.go ]; then
  ast-grep outline deps/go.go --view compact || true
  sed -n '1,80p' deps/go.go | cat -n
fi

echo "== deps/go_graph.go outline and relevant section =="
if [ -f deps/go_graph.go ]; then
  ast-grep outline deps/go_graph.go --view compact || true
  sed -n '1,60p' deps/go_graph.go | cat -n
  sed -n '90,150p' deps/go_graph.go | cat -n
fi

Repository: flanksource/repomap

Length of output: 14651


Validate go.mod modules before dereferencing Module. modfile.Parse does not return an error when go.mod has no module directive and can leave file.Module nil. Both deps/go.go and deps/go_graph.go call loadGoModFile and later dereference file.Module.Mod.Path; move the nil Module guard into loadGoModFile and return a descriptive error so malformed Go manifests fail as scan errors instead of panicking.

📍 Affects 2 files
  • deps/go.go#L11-L18 (this comment)
  • deps/go_graph.go#L20-L24
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/go.go` around lines 11 - 18, The loadGoModFile helper must validate that
the parsed manifest has a non-nil Module and return a descriptive error when the
module directive is missing, before callers dereference file.Module. Apply this
root-cause fix for both deps/go.go:11-18 and deps/go_graph.go:20-24; those call
sites require no direct changes beyond propagating the helper error.

Comment thread deps/remote_cache.go
Comment on lines +171 to +196
func (c *diskCache) cloneInto(ctx context.Context, url, ref, dir string) error {
tmp, err := os.MkdirTemp(c.root, "clone-*")
if err != nil {
return err
}
defer func() { _ = os.RemoveAll(tmp) }()

args := []string{"clone", "--depth", "1", "--single-branch", url, tmp}
if ref != "" {
args = []string{"clone", "--depth", "1", "--single-branch", "--branch", ref, url, tmp}
}
if _, err := c.runner.Run(ctx, Command{Name: "git", Args: args}); err != nil {
// Ref may not be a branch/tag; fall back to the default branch.
if ref == "" {
return notFoundError{url}
}
if _, err2 := c.runner.Run(ctx, Command{Name: "git", Args: []string{"clone", "--depth", "1", url, tmp}}); err2 != nil {
return notFoundError{url}
}
}
if err := os.MkdirAll(filepath.Dir(dir), 0o755); err != nil {
return err
}
_ = os.RemoveAll(dir)
return os.Rename(tmp, dir)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Trace GitRepo callers and the image label that supplies the clone URL.
set -euo pipefail

echo "== GitRepo call sites =="
rg -nP --type=go -C 6 '\bGitRepo\s*\(' 

echo "== labelSource definition and readers =="
rg -nP --type=go -C 6 '\blabelSource\b'

echo "== base image resolution implementation =="
fd -t f 'image_base.go' --exec cat -n {}

Repository: flanksource/repomap

Length of output: 182


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files deps | sort | sed -n '1,160p'

echo "== remote_cache.go outline =="
if command -v ast-grep >/dev/null 2>&1; then
  ast-grep outline deps/remote_cache.go --view expanded || true
fi

echo "== relevant source slices =="
for f in deps/remote_cache.go deps/resolve_remote.go; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    wc -l "$f"
    cat -n "$f" | sed -n '1,260p'
  fi
done

echo "== dependency definitions including images and image labels =="
rg -n --type=go "type Image|ImageConfig|labelSource|labelBaseName|image.source|FROM |FROM.*image source|GitRepo|cloneInto|baseImages" deps -C 4 | sed -n '1,260p'

Repository: flanksource/repomap

Length of output: 34349


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
root = Path("deps")
for p in root.glob("*.go"):
    s = p.read_text()
    for needle in ["GitRepo", "cloneInto", "ImageConfig", "labelSource", "baseImages"]:
        if needle in s:
            print(f"{p}: {needle} @ line(s) " + ") line(s) ".join(str(i+1) for i,line in enumerate(s.splitlines()) if needle in line))

print("\n== static argv construction probe ==")
src = Path("deps/remote_cache.go").read_text()
checks = {
    "git_clone_args_with_url": "clone",
    "git_clone_args_with_ref": "--branch",
    "no_separator_before_url": '"--", url' in src or "'--', url" in src,
    "env_allow_protocol": "GIT_ALLOW_PROTOCOL" in src,
}
for k,v in checks.items():
    print(f"{k}: {v}")
PY

Repository: flanksource/repomap

Length of output: 1195


🌐 Web query:

Git git-ext custom transport external commands URL documentation

💡 Result:

Git provides a mechanism for custom transport protocols through its remote helper architecture [1][2]. When Git encounters a repository URL with a prefix it does not natively recognize (e.g., <protocol>://), it attempts to locate and execute a program named git-remote-<protocol> in your system's PATH [1][2]. For simpler use cases, Git includes a built-in remote helper called git-remote-ext [3][4]. This tool allows you to pipe Git's native smart transport protocol through an arbitrary external command rather than writing a full-blown remote helper [3][4]. Usage of git-remote-ext: You can specify an external command using the ext:: prefix in your repository URL [5][3]: git remote add "ext::[ ...]" When invoked, Git executes the specified command and bridges the transport by connecting the command's standard input (stdin) and output (stdout) to Git's internal protocol stream [5][3]. The command is responsible for communicating with the remote server [5][3]. Key details: - Environment Variables: Git passes the requested service (e.g., git-upload-pack, git-receive-pack) to the external command via the GIT_EXT_SERVICE environment variable [5][3]. - Placeholders: Several placeholders can be used within the command string to pass repository information dynamically [3][6]: - %G: Replaced by the repository path [3][6]. - %S: Replaced by the service name [3][6]. - %V: Used to specify a virtual hostname [3][6]. - Security: Because git-remote-ext executes arbitrary shell commands, it presents a significant security risk [7][8]. It has been disabled by default in newer versions of Git (since version 2.38) and requires explicit configuration (e.g., protocol.ext.allow=always) to enable [8]. Vulnerabilities related to improper filtering of this transport (such as CVE-2026-62200) have highlighted the dangers of allowing this mechanism in environments where user-provided inputs might reach Git commands [8][9][10]. For more complex requirements, creating a dedicated git-remote-<protocol> helper program is the recommended approach, as it operates as a controlled, independent process that communicates with Git via a structured, line-based protocol on stdin/stdout [1][11][4].

Citations:


🌐 Web query:

git clone git-upload-pack arbitrary command exploitation URL git clone

💡 Result:

The --upload-pack flag in git clone is a legitimate configuration option intended to specify the path to the git-upload-pack executable on a remote repository [1][2]. However, it is a well-documented vector for arbitrary command execution (ACE) or argument injection when misused, particularly in automated tools or libraries that wrap git commands [3][4][5]. The core issue arises because the --upload-pack option accepts an arbitrary string that is executed on the client-side system via a shell [6]. If an application allows untrusted user input to be passed as an argument to this flag, an attacker can supply malicious commands instead of a legitimate path [6]. Key security findings include: 1. Mechanism of Exploitation: When a git clone command is executed, the --upload-pack flag can be exploited to run arbitrary system commands by crafting input that breaks out of the intended argument structure [6]. For example, injecting command substitution payloads like '-u$({touch,/tmp/foo})' can lead to execution [6]. 2. Vulnerabilities in Wrappers: Multiple vulnerabilities have been identified in software libraries that wrap Git commands, such as GitPython and various npm packages [3][4][7][8]. These vulnerabilities often involve "argument injection," where the library fails to properly sanitize inputs, allowing user-controlled strings to reach the underlying git command as malicious arguments [3][7][8]. 3. Specific CVEs: - CVE-2026-42215: A vulnerability in GitPython where underscore-form keyword arguments (e.g., upload_pack) bypassed security guards intended to block the --upload-pack flag, leading to command injection [9][7]. - CVE-2022-25900 and CVE-2022-24437: Vulnerabilities in npm packages (git-clone and git-pull-or-clone) where inadequate neutralization of inputs allowed for argument injection via the --upload-pack feature [4][5][8][10]. To mitigate these risks, applications should avoid passing untrusted user input directly to Git commands, maintain strict allowlists for command-line arguments, and use libraries that explicitly sanitize or restrict access to dangerous options like --upload-pack [3][7]. Using higher-level, safe APIs that do not require constructing shell commands is recommended [4].

Citations:


Injection (CWE-88): Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')

Reachability: External

Reachability path
● Entry
  deps/update_test.go:583
  TestUpdateRejectsUnsupportedManagers
│
▼
● Sink
  deps/remote_cache.go

Reject unsafe repository URLs before calling git clone.

normalizeGitURL(source) accepts source values from org.opencontainers.image.source in scanned image metadata and passes them to cloneInto. cloneInto builds git clone argv directly, including ref after --branch, so a value beginning with - can change Git options and transport URLs can select Git protocols. Validate url with an allowlisted URL parser whose scheme/host matches only trusted repository sources, reject a ref starting with -, use -- before the position-only URL in all clone commands, and restrict allowed transports in the command environment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/remote_cache.go` around lines 171 - 196, Harden cloneInto before
invoking git: validate url with the repository-source allowlist used by
normalizeGitURL, permitting only trusted schemes and hosts; reject any ref
beginning with “-”. Add “--” before the positional URL in both clone argument
variants, and configure the git command environment to restrict transports to
the approved protocols.

Add cache warming for CI, sandbox, and air-gapped environments. Use real package managers to populate shared caches, optionally compile dependencies, and verify offline readiness while preserving partial results and actionable command errors.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
deps/cachewarm.go (1)

92-111: 🩺 Stability & Availability | 🔵 Trivial

Document that CommandRunner.Run must be concurrent-safe.

WarmCache uses one injected runner across cacheWarmConcurrency goroutines. ExecRunner{} is stateless, but callers can use recording or stateful test runners; add this to the CommandRunner interface contract or add tests proving injected runners handle concurrent Run calls safely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/cachewarm.go` around lines 92 - 111, Update the CommandRunner interface
contract to explicitly require that Run supports concurrent calls from multiple
goroutines, reflecting WarmCache’s shared runner usage under
cacheWarmConcurrency. Document this concurrency guarantee near the interface and
preserve existing runner implementations; alternatively, add focused tests
demonstrating injected stateful or recording runners safely handle concurrent
Run calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deps/cachewarm.go`:
- Around line 264-285: Update walkWarmed to increment count and capture the
matching version when a distinct node is popped, after the nil/seen check and
before traversing its children; remove the child-loop accounting so shared
dependencies are counted only once.

In `@README.md`:
- Around line 114-123: Update the README paragraph describing `--build` to
explicitly state that Go builds persist compiled artifacts in `GOCACHE`, in
addition to dependency sources in `GOMODCACHE`; keep the existing npm and pnpm
cache descriptions unchanged.

---

Nitpick comments:
In `@deps/cachewarm.go`:
- Around line 92-111: Update the CommandRunner interface contract to explicitly
require that Run supports concurrent calls from multiple goroutines, reflecting
WarmCache’s shared runner usage under cacheWarmConcurrency. Document this
concurrency guarantee near the interface and preserve existing runner
implementations; alternatively, add focused tests demonstrating injected
stateful or recording runners safely handle concurrent Run calls.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 937fa7a5-c5c1-4865-958c-237431bb721e

📥 Commits

Reviewing files that changed from the base of the PR and between 965b7c2 and 1a23216.

📒 Files selected for processing (20)
  • README.md
  • cmd/repomap/cache_warm.go
  • cmd/repomap/cache_warm_test.go
  • deps/cachewarm.go
  • deps/cachewarm_plan.go
  • deps/cachewarm_test.go
  • deps/manager/gomod/warm.go
  • deps/manager/gomod/warm_test.go
  • deps/manager/node/warm.go
  • deps/manager/node/warm_test.go
  • deps/manager/npm/warm.go
  • deps/manager/npm/warm_test.go
  • deps/manager/pnpm/warm.go
  • deps/manager/pnpm/warm_test.go
  • deps/manifest/command.go
  • deps/manifest/manager.go
  • deps/manifest/warm.go
  • deps/model.go
  • deps/runner.go
  • deps/update_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • deps/update_test.go

Comment thread deps/cachewarm.go
Comment on lines +264 to +285
func walkWarmed(root *Node, name string) (count int, version string) {
seen := map[*Node]bool{}
stack := []*Node{root}
for len(stack) > 0 {
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if node == nil || seen[node] {
continue
}
seen[node] = true
for _, child := range node.Children {
if child.Version != "" {
count++
if version == "" && child.Name == name {
version = child.Version
}
}
stack = append(stack, child)
}
}
return count, version
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Count each package once.

The counter increments for every edge, not for every distinct node. seen deduplicates the traversal but not the count. A dependency reachable from two parents is counted twice, so Packages overstates the closure for graphs that share transitive dependencies. Move the accounting to the pop site, after the seen check.

🐛 Proposed fix to count distinct nodes
 func walkWarmed(root *Node, name string) (count int, version string) {
 	seen := map[*Node]bool{}
 	stack := []*Node{root}
 	for len(stack) > 0 {
 		node := stack[len(stack)-1]
 		stack = stack[:len(stack)-1]
 		if node == nil || seen[node] {
 			continue
 		}
 		seen[node] = true
+		if node != root && node.Version != "" {
+			count++
+			if version == "" && node.Name == name {
+				version = node.Version
+			}
+		}
 		for _, child := range node.Children {
-			if child.Version != "" {
-				count++
-				if version == "" && child.Name == name {
-					version = child.Version
-				}
-			}
 			stack = append(stack, child)
 		}
 	}
 	return count, version
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func walkWarmed(root *Node, name string) (count int, version string) {
seen := map[*Node]bool{}
stack := []*Node{root}
for len(stack) > 0 {
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if node == nil || seen[node] {
continue
}
seen[node] = true
for _, child := range node.Children {
if child.Version != "" {
count++
if version == "" && child.Name == name {
version = child.Version
}
}
stack = append(stack, child)
}
}
return count, version
}
func walkWarmed(root *Node, name string) (count int, version string) {
seen := map[*Node]bool{}
stack := []*Node{root}
for len(stack) > 0 {
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if node == nil || seen[node] {
continue
}
seen[node] = true
if node != root && node.Version != "" {
count++
if version == "" && node.Name == name {
version = node.Version
}
}
for _, child := range node.Children {
stack = append(stack, child)
}
}
return count, version
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deps/cachewarm.go` around lines 264 - 285, Update walkWarmed to increment
count and capture the matching version when a distinct node is popped, after the
nil/seen check and before traversing its children; remove the child-loop
accounting so shared dependencies are counted only once.

Comment thread README.md
Comment on lines +114 to +123
For each `name@version` spec, repomap creates a throwaway single-dependency
project in a temporary directory, drives the real package manager to download the
closure into the machine's shared cache, then deletes the project. Nothing in the
working tree is touched; what persists is the warmed cache (`GOMODCACHE`, the pnpm
store, or the npm cache). Omit the version to take whatever the manager considers
current — the concrete resolved version is reported back, including Go
pseudo-versions.

`--build` goes further than downloading. For Go it compiles every package in the
module so `GOCACHE` holds build artifacts and not just source. For npm and pnpm it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document GOCACHE persistence for Go builds.

This paragraph lists only dependency caches as persistent outputs. Lines 122-123 state that Go --build also persists compiled artifacts in GOCACHE. Clarify this distinction so the documented cache results match the --build behavior.

Proposed wording
- project. Nothing in the working tree is touched; what persists is the warmed cache (`GOMODCACHE`, the pnpm store, or the npm cache).
+ project. Nothing in the working tree is touched; the warmed dependency cache persists in `GOMODCACHE`, the pnpm store, or the npm cache. With Go `--build`, compiled artifacts also persist in `GOCACHE`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
For each `name@version` spec, repomap creates a throwaway single-dependency
project in a temporary directory, drives the real package manager to download the
closure into the machine's shared cache, then deletes the project. Nothing in the
working tree is touched; what persists is the warmed cache (`GOMODCACHE`, the pnpm
store, or the npm cache). Omit the version to take whatever the manager considers
current — the concrete resolved version is reported back, including Go
pseudo-versions.
`--build` goes further than downloading. For Go it compiles every package in the
module so `GOCACHE` holds build artifacts and not just source. For npm and pnpm it
For each `name@version` spec, repomap creates a throwaway single-dependency
project in a temporary directory, drives the real package manager to download the
closure into the machine's shared cache, then deletes the project. Nothing in the
working tree is touched; the warmed dependency cache persists in `GOMODCACHE`, the
pnpm store, or the npm cache. With Go `--build`, compiled artifacts also persist
in `GOCACHE`. Omit the version to take whatever the manager considers
current — the concrete resolved version is reported back, including Go
pseudo-versions.
`--build` goes further than downloading. For Go it compiles every package in the
module so `GOCACHE` holds build artifacts and not just source. For npm and pnpm it
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 114 - 123, Update the README paragraph describing
`--build` to explicitly state that Go builds persist compiled artifacts in
`GOCACHE`, in addition to dependency sources in `GOMODCACHE`; keep the existing
npm and pnpm cache descriptions unchanged.

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.

1 participant