feat(toolchain): add update command, fix version-pinning bugs - #2894
feat(toolchain): add update command, fix version-pinning bugs#2894Erik Osterman (Cloud Posse) (osterman) wants to merge 13 commits into
Conversation
Adds `atmos toolchain update` to move a pinned tool to its newest available version safely, and fixes several bugs found while field testing the update/pinning workflow: - which/exec resolved the wrong version on a multi-version .tool-versions line (last token instead of the default first token), causing false "not installed" errors. - set appended instead of replacing the default version, contradicting its own documented behavior. - add/install silently accepted SemVer range syntax (^1.2.0, ~>1.0.0) and only failed later with a raw HTTP 404; now rejected immediately with a hint toward dependencies.tools/atmos version track. - atmos version track add/set corrupted any value containing <, >, or & (a json.Marshal HTML-escaping bug), breaking exactly the ~>/>= constraint syntax the toolchain docs recommend. - atmos toolchain versions --help silently rendered the wrong command's help and exited 0 instead of erroring; fixed globally in root help routing. Removed the stale toolchain-versions and toolchain-aliases docs for commands that were never implemented. - Implemented six previously documented-but-missing flags: list --format/--installed-only/--pending-only, clean --dry-run/--cache-only/--force, exec --dry-run. - updateToolVersionsFile wrote to the hardcoded default .tool-versions path instead of the configured one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…dmap Adds the changelog post for the new atmos toolchain update command and links it from the toolchain milestone list in the roadmap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Tip Atmos Pro
No affected stacks workflow was detected for this pull request. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR adds ChangesToolchain modernization
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/toolchain/install_validation_helpers.go (1)
49-61: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCorrect the validation comment.
ValidateVersionSpecaccepts literal vendor tags such asjq-1.7.1thatParseVersionSpecdoes not recognize. DescribeValidateVersionSpecin this comment.Proposed fix
-// version is a format ParseVersionSpec recognizes, before any network call is made. +// version is accepted by ValidateVersionSpec before any network call is made.🤖 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 `@pkg/toolchain/install_validation_helpers.go` around lines 49 - 61, Update the comment above validateToolAndVersion to state that the version format is validated by ValidateVersionSpec, replacing the inaccurate reference to ParseVersionSpec while preserving the existing description of non-empty tool and version validation before network 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 `@cmd/toolchain/add.go`:
- Around line 46-48: Wrap the errors returned by addParser.BindFlagsToViper in
cmd/toolchain/add.go:46-48 and cleanParser.BindFlagsToViper in
cmd/toolchain/clean.go:29-33 with their applicable static errors from
errors/errors.go, preserving each original error using %w before returning it.
In `@cmd/toolchain/update.go`:
- Around line 35-40: Before reading v.GetBool("dry-run") in the update and exec
command flows, use StandardParser.IsBoolFlagExplicitlySet to determine whether
the command’s own CLI flag or registered environment variable was explicitly
provided. Apply this change in cmd/toolchain/update.go lines 35-40 and
cmd/toolchain/exec.go lines 35-40, ensuring each command does not consume the
other command’s dry-run environment variable while preserving the existing
dry-run behavior.
In `@errors/errors.go`:
- Around line 353-376: Add the static ErrToolchainCleanConfirmation sentinel in
errors/errors.go, wrap the confirmation prompt failure in pkg/toolchain/clean.go
using that sentinel and %w, and wrap the tool-versions load failure in
pkg/toolchain/update.go with ErrToolVersionsFileOperation and %w. Apply these
changes at errors/errors.go lines 353-376, pkg/toolchain/clean.go lines 138-143,
and pkg/toolchain/update.go lines 55-59 respectively, preserving the underlying
errors for errors.Is checks.
In `@pkg/toolchain/exec_test.go`:
- Around line 205-210: Update the test around RunExecCommandWithOptions to cover
a missing-binary dry-run case: do not pre-install terraform, invoke the command
with dry-run enabled, and assert the tool installation occurs while the injected
execFunc is never called. Prefer adding this as a behavior-focused table-driven
case using the existing fake tool setup.
In `@pkg/toolchain/list.go`:
- Around line 231-242: The empty-configuration path in RunListWithOptions must
emit valid JSON when format is "json". Before returning after the existing UI
message, write ListToolsOutput with an empty Tools slice via data.WriteJSON for
JSON output, while preserving the current table-output message; add a test
covering empty .tool-versions JSON output.
In `@pkg/toolchain/set.go`:
- Around line 343-347: In SetToolVersion, validate the selected version with
ValidateVersionSpec after interactive selection and before calling
AddToolToVersionsAsDefault. Prevent persistence when validation fails,
preserving the existing default-version replacement behavior for valid versions.
In `@pkg/toolchain/update_test.go`:
- Around line 160-171: The TestRunUpdate_ConcurrencyPreservesOrder test
currently verifies only the error result; capture RunUpdate’s reported output
and assert owner/a appears before owner/b and owner/c, with owner/b before
owner/c. Use the existing test I/O setup and keep the test focused on output
order under MaxConcurrency: 4.
In `@pkg/toolchain/update.go`:
- Around line 237-249: Reorder the update flow around
installSingleToolWithInstaller and AddToolToVersionsAsDefault so newest is
installed successfully before .tool-versions is changed. Preserve the existing
failure result for installation errors, and add a failure-path test verifying
the previous default remains configured when installation fails.
In `@pkg/version/manager/crud.go`:
- Around line 182-183: Update marshalJSONNoEscape to wrap enc.Encode failures
with the existing ErrEncode sentinel before returning them, so SetEntryFields
and AddEntry expose errors.Is-compatible encoding errors. Add a negative test
using an unsupported field value and assert that the returned error matches
ErrEncode via errors.Is.
In `@website/docs/cli/commands/toolchain/toolchain-info.mdx`:
- Around line 212-215: Update the Tool Aliases link in the toolchain-info
documentation to target the new toolchain aliases page, matching the route
defined by that page rather than the legacy aliases route. Remove the duplicate
legacy aliases page if it is no longer referenced or needed.
In `@website/docs/cli/commands/toolchain/toolchain-update.mdx`:
- Around line 36-38: Update the documentation bullet describing pr:, sha:, and
ref: pins to remove the claim that they are immutable and clarify that updates
skip all three to preserve the explicitly selected source; retain the existing
guidance to use atmos toolchain add to change them.
- Around line 44-47: Update the dependencies.tools link in the toolchain update
documentation to point to /stacks/dependencies, matching the configuration
reference used by toolchain usage documentation; leave the surrounding
version-tracking guidance unchanged.
In `@website/static/casts/screengrabs/atmos-toolchain-exec--help.cast`:
- Line 8: Fix the toolchain exec help rendering so examples contain only the
executable version reference, not an injected mailto link, after terraform@1.5.0
and kubectl@1.28.0. Update the source/renderer responsible for the exec
examples, then regenerate atmos-toolchain-exec--help.cast and verify copied
commands contain no mailto arguments.
In `@website/static/casts/screengrabs/atmos-toolchain-uninstall--help.cast`:
- Line 8: Update the uninstall command help text to mention tools installed via
component, workflow, and command dependencies, matching the existing dependency
wording. Then regenerate the atmos-toolchain-uninstall help cast so it reflects
the updated output.
---
Outside diff comments:
In `@pkg/toolchain/install_validation_helpers.go`:
- Around line 49-61: Update the comment above validateToolAndVersion to state
that the version format is validated by ValidateVersionSpec, replacing the
inaccurate reference to ParseVersionSpec while preserving the existing
description of non-empty tool and version validation before network 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 243991f8-cabb-465b-97ec-cf333d4b27b1
📒 Files selected for processing (74)
agent-skills/skills/atmos-toolchain/SKILL.mdcmd/root.gocmd/root_help_routing_test.gocmd/toolchain/add.gocmd/toolchain/add_test.gocmd/toolchain/clean.gocmd/toolchain/clean_test.gocmd/toolchain/command_provider_test.gocmd/toolchain/exec.gocmd/toolchain/exec_test.gocmd/toolchain/list.gocmd/toolchain/list_test.gocmd/toolchain/provider_test.gocmd/toolchain/toolchain.gocmd/toolchain/update.gocmd/toolchain/update_test.godemo/casts/atmos.d/screengrabs/cli.yamlerrors/errors.gopkg/ai/tools/atmos/toolchain_add.gopkg/toolchain/add.gopkg/toolchain/add_test.gopkg/toolchain/clean.gopkg/toolchain/clean_test.gopkg/toolchain/exec.gopkg/toolchain/exec_test.gopkg/toolchain/install.gopkg/toolchain/install_validation_helpers.gopkg/toolchain/list.gopkg/toolchain/list_test.gopkg/toolchain/lockfile/lockfile.gopkg/toolchain/set.gopkg/toolchain/set_test.gopkg/toolchain/update.gopkg/toolchain/update_test.gopkg/toolchain/version_spec.gopkg/toolchain/version_spec_test.gopkg/toolchain/which.gopkg/toolchain/which_test.gopkg/version/manager/crud.gopkg/version/manager/crud_test.gotests/snapshots/TestCLICommands_atmos_toolchain_--help.stdout.goldenwebsite/blog/2026-08-06-toolchain-update-command.mdxwebsite/docs/cli/commands/toolchain/toolchain-aliases.mdxwebsite/docs/cli/commands/toolchain/toolchain-get.mdxwebsite/docs/cli/commands/toolchain/toolchain-info.mdxwebsite/docs/cli/commands/toolchain/toolchain-remove.mdxwebsite/docs/cli/commands/toolchain/toolchain-set.mdxwebsite/docs/cli/commands/toolchain/toolchain-uninstall.mdxwebsite/docs/cli/commands/toolchain/toolchain-update.mdxwebsite/docs/cli/commands/toolchain/toolchain-versions.mdxwebsite/docs/cli/commands/toolchain/usage.mdxwebsite/src/data/roadmap.jswebsite/static/casts/screengrabs/atmos-toolchain--help.castwebsite/static/casts/screengrabs/atmos-toolchain-add--help.castwebsite/static/casts/screengrabs/atmos-toolchain-aliases--help.castwebsite/static/casts/screengrabs/atmos-toolchain-clean--help.castwebsite/static/casts/screengrabs/atmos-toolchain-du--help.castwebsite/static/casts/screengrabs/atmos-toolchain-env--help.castwebsite/static/casts/screengrabs/atmos-toolchain-exec--help.castwebsite/static/casts/screengrabs/atmos-toolchain-get--help.castwebsite/static/casts/screengrabs/atmos-toolchain-info--help.castwebsite/static/casts/screengrabs/atmos-toolchain-install--help.castwebsite/static/casts/screengrabs/atmos-toolchain-list--help.castwebsite/static/casts/screengrabs/atmos-toolchain-path--help.castwebsite/static/casts/screengrabs/atmos-toolchain-registry--help.castwebsite/static/casts/screengrabs/atmos-toolchain-registry-list--help.castwebsite/static/casts/screengrabs/atmos-toolchain-registry-search--help.castwebsite/static/casts/screengrabs/atmos-toolchain-remove--help.castwebsite/static/casts/screengrabs/atmos-toolchain-search--help.castwebsite/static/casts/screengrabs/atmos-toolchain-set--help.castwebsite/static/casts/screengrabs/atmos-toolchain-uninstall--help.castwebsite/static/casts/screengrabs/atmos-toolchain-update--help.castwebsite/static/casts/screengrabs/atmos-toolchain-versions--help.castwebsite/static/casts/screengrabs/atmos-toolchain-which--help.cast
💤 Files with no reviewable changes (5)
- website/static/casts/screengrabs/atmos-toolchain-versions--help.cast
- website/static/casts/screengrabs/atmos-toolchain-aliases--help.cast
- website/docs/cli/commands/toolchain/toolchain-get.mdx
- website/docs/cli/commands/toolchain/toolchain-aliases.mdx
- website/docs/cli/commands/toolchain/toolchain-versions.mdx
| // RunListWithOptions prints tools from .tool-versions in the requested format, optionally | ||
| // filtered to only installed or only pending (not-yet-installed) tools. | ||
| func RunListWithOptions(format string, installedOnly, pendingOnly bool) error { | ||
| defer perf.Track(nil, "toolchain.RunListWithOptions")() | ||
|
|
||
| if !slices.Contains(supportedListFormats, format) { | ||
| return fmt.Errorf("%w: %q (supported: %v)", errUtils.ErrInvalidFlagValue, format, supportedListFormats) | ||
| } | ||
| if installedOnly && pendingOnly { | ||
| return errUtils.ErrMutuallyExclusiveFlags | ||
| } | ||
|
|
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Return an empty JSON result when no tools are configured.
When format == "json" and .tool-versions is empty, this function returns after writing a UI message. JSON consumers receive no JSON document and cannot deserialize the result.
Write ListToolsOutput{Tools: []ListToolEntry{}} through data.WriteJSON for this case. Keep the current message for table output. Add an empty-config JSON test.
🤖 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 `@pkg/toolchain/list.go` around lines 231 - 242, The empty-configuration path
in RunListWithOptions must emit valid JSON when format is "json". Before
returning after the existing UI message, write ListToolsOutput with an empty
Tools slice via data.WriteJSON for JSON output, while preserving the current
table-output message; add a test covering empty .tool-versions JSON output.
| - [`atmos toolchain get`](/cli/commands/toolchain/get) - Browse available versions with `--all --limit` | ||
| - [`atmos toolchain list`](/cli/commands/toolchain/list) - View installed tools | ||
| - [`atmos toolchain registry search`](/cli/commands/toolchain/registry/registry-search) - Search for tools | ||
| - [`atmos toolchain aliases`](/cli/commands/toolchain/aliases) - View configured aliases | ||
| - [Tool Aliases](/cli/configuration/toolchain/aliases) - Configure convenient tool name aliases |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
mapfile -t files < <(fd -i -t f 'toolchain-(get|registry-search)|aliases' website/docs)
if ((${`#files`[@]} == 0)); then
echo "No target documentation files found." >&2
exit 1
fi
for file in "${files[@]}"; do
printf '== %s ==\n' "$file"
sed -n '1,35p' "$file" | rg -n '^(id|slug|title):' || true
doneRepository: cloudposse/atmos
Length of output: 206
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== tracked file candidates =="
git ls-files website/docs | rg -i 'toolchain-(get|registry-search)|aliases|registry-search|toolchain.*get|aliases|aliases\.mdx' || true
echo "== locate reference locations in info file =="
file="website/docs/cli/commands/toolchain/toolchain-info.mdx"
if [ -f "$file" ]; then
wc -l "$file"
sed -n '200,220p' "$file" | cat -n -v
else
echo "missing $file"
fi
echo "== frontmatter id/slug/title for candidate files =="
mapfile -t files < <(git ls-files website/docs | rg -i 'toolchain-(get|registry-search)|aliases|registry-search|toolchain.*get')
for f in "${files[@]}"; do
printf '\n== %s ==\n' "$f"
sed -n '1,40p' "$f" | rg -n '^(id|slug|title):' || rg -n '^(id|slug|title):' "$f" || true
done
echo "== site route config (Docusaurus) =="
find website -maxdepth 3 -type f \( -name "docusaurus.config.*" -o -name "sidebar.*" -o -name "sidebars.*" \) -print | sort
for f in $(find website -maxdepth 3 -type f \( -name "docusaurus.config.*" -o -name "sidebar.*" -o -name "sidebars.*" \)); do
echo "--- $f ---"
sed -n '1,220p' "$f"
doneRepository: cloudposse/atmos
Length of output: 1654
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== frontmatter id/slug/title for candidate files =="
for f in \
website/docs/cli/commands/toolchain/toolchain-get.mdx \
website/docs/cli/configuration/aliases.mdx \
website/docs/cli/configuration/toolchain/aliases.mdx
do
echo "== $f =="
sed -n '1,45p' "$f" | rg -n '^(id|slug|title):' | sed -n '1,20p' || true
done
echo "== tracked docs with alias references =="
git ls-files | rg 'alias|Tool Aliases|registry-search|toolchain.*registry' | sed -n '1,120p'
echo "== site config and route docs =="
for f in $(find website -maxdepth 4 -type f \( -name "docusaurus.config.*" -o -name "sidebars.*" -o -name "sidebar.*" \) 2>/dev/null | sort); do
echo "--- $f ---"
sed -n '1,260p' "$f" | sed -n '1,260p' | rg -n 'cli|toolchain|id:|routeBasePath|docs|sidebars|registry|alias|slug|toolchain-get|aliases' || true
doneRepository: cloudposse/atmos
Length of output: 8611
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== frontend docusaurus/plugin/redirects =="
find website -maxdepth 4 -type f \( -name "docusaurus.config.*" -o -name "plugin*.js" -o -name "*redirect*" \) -print 2>/dev/null | sort
for f in $(find website -maxdepth 4 -type f \( -name "docusaurus.config.*" -o -name "plugin*.js" -o -name "*redirect*" \) 2>/dev/null | sort); do
echo "--- $f ---"
sed -n '1,320p' "$f" | rg -n 'plugin-client-redirects|redirects|cli/commands/toolchain|toolchain-get|toolchain registry|registry/search|aliases|toolchain/aliases' || true
done
echo "== cli sidebar exact section =="
node - <<'JS'
const sidebar = require('./website/sidebars.js')
for (let depth=0; depth<3; depth++) { if (sidebar.length<depth+1) continue; const group=sidebar[depth]; console.log(JSON.stringify(group, null, 2)) }
JS
echo "== markdown headings for aliases at target file =="
for f in \
website/docs/cli/configuration/aliases.mdx \
website/docs/cli/configuration/toolchain/aliases.mdx
do
echo "== $f =="
rg -n '^#+ .*((Toolchain|Toolchain Aliases|Aliases)|id: ' "$f"
doneRepository: cloudposse/atmos
Length of output: 696
🌐 Web query:
Docusaurus plugin-client-redirects from option slug path without leading slash route
💡 Result:
In Docusaurus, the @docusaurus/plugin-client-redirects plugin expects path values (both from and to) to be absolute-like pathnames starting with a leading slash (e.g., /my-page) [1][2][3]. Unlike document front matter slug settings, which can interpret paths without a leading slash as folder-relative [4][5], the plugin-client-redirects configuration validates paths to ensure they are standard, site-root-relative pathnames [2][3]. Providing a path without a leading slash in the plugin configuration is generally considered invalid, as the plugin relies on these paths to correctly identify and match existing routes within the site's generated structure [1][6]. If you are experiencing issues with routes containing or missing slashes, note the following: 1. Validation: The plugin configuration schema requires paths to start with a slash [2][3]. Providing a path without one may result in configuration errors [2]. 2. Trailing Slashes: The plugin's behavior regarding trailing slashes is influenced by your site's overall trailingSlash configuration in docusaurus.config.js [6][7]. The plugin automatically normalizes to paths to match your site's trailing slash preference (e.g., ensuring a redirect target correctly points to the trailing-slashed or non-slashed version of a page) [6]. 3. Relative vs. Absolute: While document slug front matter supports relative paths (e.g., slug: my-page becomes /docs/my-page) [4], the plugin-client-redirects expects the to field to be the full, absolute pathname of the target route as it exists in the final built site [1][3]. To resolve issues with redirects, ensure all from and to paths in your plugin configuration begin with a leading slash [1][3]. If a redirect is failing to match, verify the exact route path generated by your build (e.g., check the output files in your build directory) and use that exact string, including the leading slash [6][5].
Citations:
- 1: https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-client-redirects
- 2: plugin-client-redirects should allow fully resolved URIs facebook/docusaurus#6845
- 3: https://github.com/facebook/docusaurus/blob/e551c9a1/packages/docusaurus-plugin-client-redirects/src/options.ts
- 4: https://docusaurus.io/docs/create-doc
- 5: hardwario/website-hardwario-docs@7c26312
- 6: https://github.com/facebook/docusaurus/blob/16f53730/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts
- 7: https://docusaurus.io/docs/api/docusaurus-config
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== aliases file content =="
for f in \
website/docs/cli/configuration/aliases.mdx \
website/docs/cli/configuration/toolchain/aliases.mdx
do
echo "== $f =="
sed -n '1,180p' "$f"
done
echo "== redirects block =="
sed -n '54,125p' website/docusaurus.config.js | cat -n
echo "== toolchain registry search frontmatter =="
for f in \
website/docs/cli/commands/toolchain/registry/search.mdx \
website/docs/cli/commands/toolchain/registry/list.mdx \
website/docs/cli/commands/toolchain/registry/usage.mdx
do
echo "== $f =="
sed -n '1,50p' "$f" | rg -n '^(---|id:|slug:|title:|---)' || true
done
echo "== git tracked toolchain registry search file exists =="
git ls-files website/docs/cli/commands/toolchain/registry/search.mdx || trueRepository: cloudposse/atmos
Length of output: 12294
🌐 Web query:
Docusaurus docs id file nested folder route example _category_ id slug file
💡 Result:
In Docusaurus, URL paths are primarily determined by the document's file path and can be customized using the slug front matter [1][2]. Document ID and URL Every document has a unique id, which defaults to the file path relative to the root docs directory (excluding the file extension) [1][3]. By default, the URL is derived from this ID, appended to the plugin's routeBasePath (defaulting to /docs) [1][3]. Customizing URLs To override the default URL, use the slug front matter in your markdown file [1][3]: --- id: custom-id slug: /my-custom-url --- This allows you to change the URL without changing the file location [1][3]. You can use relative slugs (e.g., slug:./another-path) or absolute slugs (e.g., slug: /root-level-url) [1][3]. Nested Folders and category.json When using autogenerated sidebars, the folder structure determines the sidebar structure [1][3]. If you have a nested folder, you can control the index page of that category using a category.json or category.yml file [4][5]. To set a custom slug for a category index page (which defaults to /category/[categoryName]), you can specify a link in the category.json file [5][6]: { "label": "My Category", "link": { "type": "generated-index", "slug": "/my-custom-category-slug" } } Summary of Best Practices 1. File Organization: Keep your file system structure mirroring your desired sidebar structure to simplify autogeneration [1][3]. 2. URL Stability: To prevent broken links when renaming files, use explicit slug front matter [1][3]. 3. Category Customization: Use category.json for category metadata, position, and to define a custom slug for the category index page if needed [4][5].
Citations:
- 1: https://docusaurus.io/docs/create-doc
- 2: https://docusaurus.io/docs/advanced/routing
- 3: https://docusaurus.io/docs/next/create-doc
- 4: https://docusaurus.io/docs/sidebar/autogenerated
- 5: https://docusaurus.io/docs/sidebar/items
- 6: https://github.com/PokemonWorkshop/PokemonWorkshopDocs/blob/main/CONTRIBUTING.md
🌐 Web query:
Docusaurus autogenerated sidebars docs folder file id route documentation
💡 Result:
In Docusaurus, autogenerated sidebars automatically map your filesystem structure within the docs folder to your site's navigation sidebar [1][2]. When you use the autogenerated type, Docusaurus creates a sidebar slice where each folder becomes a category and each file becomes a document link [1][3]. Key Concepts and Mappings: 1. Document ID: Every document has a unique ID, which by default is the document's path relative to the root docs directory (excluding the file extension) [4][5]. For example, a file at docs/api/setup.md has the ID api/setup [4]. You can override this ID by setting it manually in the file's front matter [4]. 2. Routes: By default, a document's URL (route) is derived from its ID [4]. For example, the ID api/setup corresponds to the URL /docs/api/setup [6]. If you need to decouple the URL from the filesystem structure, you can use the slug front matter to customize the document's route [4][6]. 3. Sidebar Generation: When defining an autogenerated sidebar in sidebars.js, you specify a dirName, which is the source folder relative to the docs directory [1][2]. - Example configuration: js export default { mySidebar: [ { type: 'autogenerated', dirName: 'api', // Generates sidebar from docs/api }, ], }; - Only the items inside the specified directory become part of the sidebar slice [1][7]. - You can control category behavior (like ordering or labeling) by adding a category.json or category.yml file within the corresponding folder [7][8]. 4. Advanced Customization: If the default generation logic does not meet your needs, you can provide a custom sidebarItemsGenerator function in your docs plugin configuration to programmatically control how the sidebar is built [5][7].
Citations:
- 1: https://docusaurus.io/docs/sidebar/autogenerated
- 2: https://docusaurus.io/docs/sidebar
- 3: https://docusaurus.io/docs/sidebar/items
- 4: https://docusaurus.io/docs/create-doc
- 5: https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-docs
- 6: https://docusaurus.io/docs/advanced/routing
- 7: https://docusaurus.io/docs/3.9.2/sidebar/autogenerated
- 8: https://docusaurus.io/docs/2.x/sidebar/autogenerated
Fix the Tool Aliases documentation target.
/cli/configuration/toolchain/aliases is the wrong page: website/docs/cli/configuration/aliases.mdx uses that exact route, while cli/commands/toolchain/toolchain-info.mdx also already links to a toolchain alias reference. Update this to the new toolchain aliases page and remove the duplicate legacy page if it is no longer needed.
🧰 Tools
🪛 LanguageTool
[typographical] ~212-~212: To join two clauses or introduce examples, consider using an em dash.
Context: ...chain get](/cli/commands/toolchain/get) - Browse available versions with --all --...
(DASH_RULE)
[typographical] ~213-~213: To join two clauses or introduce examples, consider using an em dash.
Context: ...ain list](/cli/commands/toolchain/list) - View installed tools - [atmos toolchain...
(DASH_RULE)
[typographical] ~215-~215: To join two clauses or introduce examples, consider using an em dash.
Context: ...s](/cli/configuration/toolchain/aliases) - Configure convenient tool name aliases ...
(DASH_RULE)
🤖 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 `@website/docs/cli/commands/toolchain/toolchain-info.mdx` around lines 212 -
215, Update the Tool Aliases link in the toolchain-info documentation to target
the new toolchain aliases page, matching the route defined by that page rather
than the legacy aliases route. Remove the duplicate legacy aliases page if it is
no longer referenced or needed.
Source: Learnings
Addresses 13 of 14 findings from CodeRabbit's review (the 14th, a doc link, was already correct): - Wrap addParser/cleanParser BindFlagsToViper errors with a new ErrFlagBinding sentinel instead of returning them raw. - Fix update/exec --dry-run flags reading the wrong command's env var (both register a Viper key literally named "dry-run", and BindEnv on a shared key lets the second parser's registration silently override the first's env var binding). Use IsBoolFlagExplicitlySet instead of v.GetBool so each command only reads its own flag/env var. - Add ErrToolchainCleanConfirmation sentinel for the clean confirmation prompt failure path; wrap update's .tool-versions load failure with ErrToolVersionsFileOperation. - Add a dry-run test that starts with no binary installed, confirming exec auto-installs before reporting instead of always assuming the tool is already present. - Fix `list --format=json` on an empty .tool-versions to emit a valid empty JSON document instead of only a human-readable message. - Fix `set` never validating the version before persisting it -- it could write invalid range syntax into .tool-versions the same way `add`/`install` could before this PR's earlier fix. - Fix update's exact-version path writing the new version into .tool-versions as the default *before* installing it; if install then failed, the configured default pointed at an uninstalled version. Install first, then persist. Add a failure-path test. - Rewrite the concurrency-order test to actually assert output order, and fix a real bug it caught: `atmos toolchain update` with no arguments iterated a Go map (randomized order) instead of a sorted one, so tools were reported in a different order on every run. - Wrap marshalJSONNoEscape's encode failures with the existing ErrEncode sentinel. - Fix an unrelated pre-existing bug the exec cast surfaced: command help/usage text rendered through pkg/ui/formatter.go's bare glamour renderer had no strict-linkify protection, so package/tool references like foo/bar@1.0.0 rendered with a stray mailto: link auto-attached. Export ApplyStrictLinkify for that renderer to use, and fix a latent ast.String Pos() issue in the linkify extension that could reorder or drop the replacement text once wired in. - Correct the toolchain-update.mdx doc's claim that ref: pins are immutable (a named ref can move; it's skipped to preserve the user's explicit source selection, not because it can't change), fix its dependencies.tools link to point at /stacks/dependencies, and widen uninstall's --all help text to match its actual dependency scope. Regenerate the affected casts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@pkg/toolchain/update_test.go`:
- Around line 114-149: Make
TestUpdateOneTool_ExactPin_InstallFailureLeavesToolVersionsUnchanged
deterministic by mocking or injecting the installation step to fail locally
instead of relying on the real installer and unavailable upstream release.
Retain the mocked GitHub release list, exercise updateOneTool with the injected
failure, and keep asserting that the original tool version remains unchanged.
In `@pkg/ui/markdown/extensions/linkify.go`:
- Around line 86-125: Replace the global searchFrom-based lookup in
replacementTextNode and its caller with the current ast.AutoLink’s source
position, deriving the ast.Text segment directly from that node’s label/source
span so repeated labels bind to the correct occurrence. Update the affected
linkify logic in pkg/ui/markdown/extensions/linkify.go (lines 86-125) and adjust
the corresponding coverage in pkg/ui/markdown/extensions/extensions_test.go
(lines 627-650) to validate repeated identical links preserve their source
order.
- Around line 144-153: Update stringNodeRenderer.renderString to capture and
return the error from w.Write(str.Value) instead of discarding it, while
preserving the existing walk status and early-return behavior for non-entering
nodes and non-string AST nodes.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 03f95944-a417-42a3-81ad-09e8b4fc7740
📒 Files selected for processing (25)
cmd/toolchain/add.gocmd/toolchain/clean.gocmd/toolchain/exec.gocmd/toolchain/uninstall.gocmd/toolchain/update.goerrors/errors.gopkg/toolchain/clean.gopkg/toolchain/exec_test.gopkg/toolchain/list.gopkg/toolchain/list_test.gopkg/toolchain/set.gopkg/toolchain/set_test.gopkg/toolchain/update.gopkg/toolchain/update_test.gopkg/ui/formatter.gopkg/ui/formatter_test.gopkg/ui/markdown/custom_renderer.gopkg/ui/markdown/extensions/extensions_test.gopkg/ui/markdown/extensions/linkify.gopkg/version/manager/crud.gopkg/version/manager/crud_test.gowebsite/docs/cli/commands/toolchain/toolchain-uninstall.mdxwebsite/docs/cli/commands/toolchain/toolchain-update.mdxwebsite/static/casts/screengrabs/atmos-toolchain-exec--help.castwebsite/static/casts/screengrabs/atmos-toolchain-uninstall--help.cast
🚧 Files skipped from review as they are similar to previous changes (15)
- website/static/casts/screengrabs/atmos-toolchain-uninstall--help.cast
- errors/errors.go
- website/static/casts/screengrabs/atmos-toolchain-exec--help.cast
- cmd/toolchain/clean.go
- cmd/toolchain/update.go
- pkg/toolchain/set.go
- website/docs/cli/commands/toolchain/toolchain-update.mdx
- website/docs/cli/commands/toolchain/toolchain-uninstall.mdx
- cmd/toolchain/add.go
- pkg/toolchain/update.go
- pkg/version/manager/crud.go
- cmd/toolchain/exec.go
- pkg/toolchain/clean.go
- pkg/toolchain/list_test.go
- pkg/toolchain/list.go
| // TestUpdateOneTool_ExactPin_InstallFailureLeavesToolVersionsUnchanged reproduces a bug where | ||
| // the exact-pin update path wrote the new "newest" version into .tool-versions as the default | ||
| // BEFORE installing it. If install then failed, .tool-versions pointed at a version that was | ||
| // never actually installed. This asserts install is attempted first, and a failed install | ||
| // leaves the previously-configured (and actually-installed) default version untouched. | ||
| func TestUpdateOneTool_ExactPin_InstallFailureLeavesToolVersionsUnchanged(t *testing.T) { | ||
| setupTestIO(t) | ||
|
|
||
| filePath := createTempToolVersionsFile(t, "hashicorp/terraform 1.9.8\n") | ||
|
|
||
| // InstallPath MUST be isolated to a per-test temp dir: a failed install still touches | ||
| // the real, shared, XDG toolchain cache directory otherwise (see install_test.go for the | ||
| // same isolation requirement and rationale). | ||
| prevConfig := atmosConfig | ||
| t.Cleanup(func() { SetAtmosConfig(prevConfig) }) | ||
| SetAtmosConfig(&schema.AtmosConfiguration{Toolchain: schema.Toolchain{ | ||
| VersionsFile: filePath, | ||
| InstallPath: filepath.Join(t.TempDir(), ".tools"), | ||
| }}) | ||
|
|
||
| // "99.99.99" is not a real hashicorp/terraform release, so the real (unmocked) install | ||
| // step must fail -- this is the "newest" version fetchAllGitHubVersions reports via the | ||
| // mocked GitHub API, but the mock only fakes the release listing, not the download. | ||
| mock := NewMockGitHubAPI() | ||
| mock.SetReleases("hashicorp", "terraform", []string{"1.9.8", "99.99.99"}) | ||
| SetGitHubAPI(mock) | ||
| t.Cleanup(ResetGitHubAPI) | ||
|
|
||
| outcome := updateOneTool("hashicorp/terraform", UpdateOptions{MaxConcurrency: 1}) | ||
| assert.Equal(t, updateResultFailed, outcome.result) | ||
|
|
||
| toolVersions, err := LoadToolVersions(filePath) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, []string{"1.9.8"}, toolVersions.Tools["hashicorp/terraform"], | ||
| "a failed install must not overwrite .tool-versions with the un-installed version") | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Make the installation failure deterministic.
Lines 134-142 deliberately call the real install path against an unavailable upstream release. This unit test depends on outbound network access and remote failure behavior. It can fail from rate limits, network errors, or installer changes unrelated to the persistence contract.
Inject a local installation failure and retain the mocked release list.
As per coding guidelines, “Prefer behavior-focused, table-driven unit tests with mocks; avoid tautological, stub, always-skipped, or coverage-only tests.”
🤖 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 `@pkg/toolchain/update_test.go` around lines 114 - 149, Make
TestUpdateOneTool_ExactPin_InstallFailureLeavesToolVersionsUnchanged
deterministic by mocking or injecting the installation step to fail locally
instead of relying on the real installer and unavailable upstream release.
Retain the mocked GitHub release list, exercise updateOneTool with the injected
failure, and keep asserting that the original tool version remains unchanged.
Source: Coding guidelines
| // Replace each auto-link with plain text, walked (and thus replaced) in document | ||
| // order, so searchFrom always advances forward through source and lands on the | ||
| // correct occurrence even when the same label repeats later in the document. | ||
| searchFrom := 0 | ||
| for _, autoLink := range nodesToReplace { | ||
| parent := autoLink.Parent() | ||
| if parent == nil { | ||
| continue | ||
| } | ||
|
|
||
| // Create a text node with the original content. | ||
| // AutoLink stores its text in a single child segment. | ||
| textNode := ast.NewString(autoLink.Label(source)) | ||
| parent.ReplaceChild(parent, autoLink, textNode) | ||
| label := autoLink.Label(source) | ||
| parent.ReplaceChild(parent, autoLink, replacementTextNode(source, label, &searchFrom)) | ||
| } | ||
| } | ||
|
|
||
| // replacementTextNode builds the plain-text replacement for an unlinked auto-link. | ||
| // | ||
| // It prefers an ast.Text node anchored to the label's real position in source | ||
| // (found via a forward-only search from *searchFrom, advanced past each match so | ||
| // repeated labels resolve to their next occurrence in document order), because | ||
| // ast.Text's Pos() reflects that real source offset, which glamour's ANSI | ||
| // renderer relies on to place inline content in the correct order. In contrast, | ||
| // ast.String nodes always report Pos() -1 ("not associated with a source text"), | ||
| // which previously caused glamour to render the replacement text out of order | ||
| // relative to its surrounding words. | ||
| // | ||
| // If the label can't be located (should not normally happen -- AutoLink labels are | ||
| // verbatim source bytes), this falls back to an ast.String node so the content is | ||
| // still rendered, just without an order guarantee; NewStrictLinkifyExtension also | ||
| // registers a renderer for ast.KindString to keep that fallback from silently | ||
| // disappearing under glamour's ANSI renderer, which has no built-in handling for it. | ||
| func replacementTextNode(source, label []byte, searchFrom *int) ast.Node { | ||
| if idx := bytes.Index(source[*searchFrom:], label); idx >= 0 { | ||
| start := *searchFrom + idx | ||
| stop := start + len(label) | ||
| *searchFrom = stop | ||
| return ast.NewTextSegment(text.NewSegment(start, stop)) | ||
| } | ||
| return ast.NewString(label) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)pkg/ui/markdown/extensions/(linkify|extensions)_test\.go$|go\.mod$' || true
echo "== linkify outline =="
ast-grep outline pkg/ui/markdown/extensions/linkify.go --view expanded || true
echo "== relevant linkify source =="
cat -n pkg/ui/markdown/extensions/linkify.go | sed -n '1,180p'
echo "== relevant test source =="
cat -n pkg/ui/markdown/extensions/extensions_test.go | sed -n '600,670p'
echo "== goldmark / glamour deps =="
rg 'github.com/yuin/goldmark|github.com/charmbracelet/glamour' go.mod go.sum || trueRepository: cloudposse/atmos
Length of output: 13509
🌐 Web query:
goldmark ast Text Segment text reader source bytes Index
💡 Result:
In the goldmark Go library, AST nodes do not store text content directly to maintain memory efficiency [1][2]. Instead, nodes store references to the original source document via the text.Segment structure, which acts as a pointer into the source byte slice [2][3]. The text.Segment structure consists of three primary attributes: 1. Start: The starting byte offset in the original source [2]. 2. End (often referred to as Stop): The ending byte offset in the original source (exclusive) [2][4]. 3. Padding: An integer representing the number of virtual spaces to prepend to the segment, commonly used for handling indentation without modifying the source data [2]. To access the actual text associated with a node, you must use the original document source (the []byte slice) in conjunction with these segments [2]. The text.Reader interface manages the source bytes during parsing, and methods such as reader.Value(segment) are used to retrieve the underlying []byte content for a given segment [5][6]. When working with collections of text, the text.Segments type provides a container (a slice of Segment structs) to manage multiple segments [4]. Nodes that hold block-level text (such as paragraphs) typically use this to track their lines [7][2]. Access to individual segments within such a collection is provided by index via methods like At(index int) [4].
Citations:
- 1: https://pkg.go.dev/github.com/yuin/goldmark/ast
- 2: https://deepwiki.com/yuin/goldmark/4-abstract-syntax-tree
- 3: https://pkg.go.dev/github.com/yuin/goldmark/
- 4: https://code.asyncmachine.dev/src/github.com/yuin/goldmark/text/segment.go.html
- 5: https://pkg.go.dev/github.com/yuin/goldmark/text
- 6: https://code.asyncmachine.dev/src/github.com/yuin/goldmark/text/reader.go.html
- 7: https://github.com/yuin/goldmark/blob/master/ast/ast.go
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== go.mod module cache availability =="
go env GOMODCACHE || true
echo "== goldmark ast.AutoLink API if module cache present =="
if [ "${GOMODCACHE:-}" != "" ]; then
grep -R "type AutoLink struct" "${GOMODCACHE}/github.com/yuin/goldmark@" 2>/dev/null | head -5 || true
grep -R "func .*AutoLink.*Label|func .*AutoLink.*URL|func .*AutoLink.*Segment" "${GOMODCACHE}/github.com/yuin/goldmark@" 2>/dev/null | head -20 || true
fi
echo "== goldmark source files in module cache =="
find "${GOMODCACHE}/github.com/yuin/goldmark@v1.8.2" -type f -name '*.go' 2>/dev/null | head -50 || true
echo "== package reference transformer priority vs auto-link priority candidates =="
rg -n "AutoLink|Linkify|PackageRef|TransformerPriority|AutoLinkEmail|AutoLinkURL" "${GOMODCACHE}/github.com/yuin/goldmark@v1.8.2" 2>/dev/null | head -80 || trueRepository: cloudposse/atmos
Length of output: 397
🌐 Web query:
github.com/yuin/goldmark v1.8.2 source ast.Autolink Label method
💡 Result:
In the yuin/goldmark library (including version v1.8.2), the ast.AutoLink struct provides a Label method [1][2]. The method is defined as: func (n *AutoLink) Label(source []byte) []byte It returns the label (the text content) of the AutoLink node [1][2]. When called, it accepts the source document bytes as an argument to retrieve the corresponding text segment for that node [1][2]. This method is part of the Node interface implementation for the AutoLink AST node type [2].
Citations:
- 1: https://code.asyncmachine.dev/pkg/github.com/yuin/goldmark/ast.html
- 2: https://mugli.github.io/tmp-literate-golds/pkg/github.com/yuin/goldmark/ast.html
🌐 Web query:
github.com/yuin/goldmark github.com/yuin/goldmark/text Reader Segment source Label AutoLink
💡 Result:
In the goldmark library, the components you referenced relate to how the parser manages document source content and represents it within the Abstract Syntax Tree (AST). text.Reader The text.Reader is the interface used by goldmark to read and traverse the Markdown source document [1][2]. It provides methods such as PeekLine and Advance to navigate the source text during the parsing process [3][4]. text.Segment Because goldmark is designed to be memory-efficient, AST nodes do not store the actual text content of the Markdown document [1][2]. Instead, they use the text.Segment structure to store references to specific parts of the source [2]. A text.Segment contains three attributes [1][2]: - Start: The starting byte offset in the source document [2]. - End: The ending byte offset in the source document (exclusive) [2]. - Padding: The number of spaces to virtually prepend to the segment [2]. AutoLink and Source Mapping The AutoLink node is an AST element representing an autolink [4][5]. A technical challenge arises because some AutoLink implementations internally store their text content in a way that may not be directly accessible as a standard child node, which can complicate retrieving the exact source position (the text.Segment) of the link [6]. When an AutoLink is created by a parser (such as the default AutoLink parser or the Linkify extension), it is typically initialized with a text.Segment that maps to the original source [4][5]. For example, the parser identifies the boundaries of a URL or email in the source using the text.Reader, creates a new text.Segment based on those offsets, and then associates that segment with the resulting ast.AutoLink node [4][5]. If you are working with these nodes and need their source position, you are essentially retrieving the text.Segment that was assigned during the node's creation [4][5].
Citations:
- 1: https://pkg.go.dev/github.com/yuin/goldmark/
- 2: https://deepwiki.com/yuin/goldmark/4-abstract-syntax-tree
- 3: https://pkg.go.dev/github.com/yuin/goldmark/text
- 4: https://code.asyncmachine.dev/src/github.com/yuin/goldmark/parser/auto_link.go.html
- 5: https://github.com/yuin/goldmark/blob/master/extension/linkify.go
- 6: How can I know the source position of `AutoLink` node? yuin/goldmark#441
Bind replacement segments to the current auto-link source location.
The global searchFrom scan can match an earlier identical byte span before the current ast.AutoLink. If that happens, the replacement ast.Text segment points at the wrong source offset and renders in the wrong inline order. Derive the replacement segment from the current auto-link’s label/source span instead of searching from the document start.
📍 Affects 2 files
pkg/ui/markdown/extensions/linkify.go#L86-L125(this comment)pkg/ui/markdown/extensions/extensions_test.go#L627-L650
🤖 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 `@pkg/ui/markdown/extensions/linkify.go` around lines 86 - 125, Replace the
global searchFrom-based lookup in replacementTextNode and its caller with the
current ast.AutoLink’s source position, deriving the ast.Text segment directly
from that node’s label/source span so repeated labels bind to the correct
occurrence. Update the affected linkify logic in
pkg/ui/markdown/extensions/linkify.go (lines 86-125) and adjust the
corresponding coverage in pkg/ui/markdown/extensions/extensions_test.go (lines
627-650) to validate repeated identical links preserve their source order.
| func (r *stringNodeRenderer) renderString(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { | ||
| if !entering { | ||
| return ast.WalkContinue, nil | ||
| } | ||
| str, ok := n.(*ast.String) | ||
| if !ok { | ||
| return ast.WalkContinue, nil | ||
| } | ||
| _, _ = w.Write(str.Value) | ||
| return ast.WalkContinue, nil |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the relevant renderer and the fallback/write handling path.
printf '--- files ---\n'
git ls-files | grep -F 'pkg/ui/markdown/extensions/linkify.go' || true
printf '\n--- linkify.go outline ---\n'
ast-grep outline pkg/ui/markdown/extensions/linkify.go || true
printf '\n--- relevant linkify.go sections ---\n'
sed -n '1,240p' pkg/ui/markdown/extensions/linkify.go
printf '\n--- usages of renderMarkdown/renderString/linkify ---\n'
rg -n "renderMarkdown|renderString|linkify|StringNodeRenderer|stringNodeRenderer" pkg/ui/markdown/extensions pkg/ui/markdown -S || trueRepository: cloudposse/atmos
Length of output: 245
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- files ---'
git ls-files | grep -F 'pkg/ui/markdown/extensions/linkify.go' || true
printf '%b\n' '\n--- linkify.go outline ---'
ast-grep outline pkg/ui/markdown/extensions/linkify.go || true
printf '%s\n' '--- relevant linkify.go sections ---'
sed -n '1,240p' pkg/ui/markdown/extensions/linkify.go
printf '%b\n' '\n--- usages of renderMarkdown/renderString/linkify ---'
rg -n "renderMarkdown|renderString|linkify|StringNodeRenderer|stringNodeRenderer" pkg/ui/markdown/extensions pkg/ui/markdown -S || trueRepository: cloudposse/atmos
Length of output: 11343
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- markdown custom renderer sections ---'
sed -n '1,260p' pkg/ui/markdown/custom_renderer.go
printf '%s\n' '--- markdown formatter sections ---'
sed -n '1,260p' pkg/ui/formatter.go
printf '%s\n' '--- renderer fallback/write handling search ---'
rg -n "renderMarkdown|renderMarkdownCore|renderString|customRenderer|renderer|walk|Write|render\(" pkg/ui/markdown pkg/ui -S || trueRepository: cloudposse/atmos
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- formatter render markdown sections ---'
sed -n '1090,1155p' pkg/ui/formatter.go
sed -n '846,872p' pkg/ui/formatter.go
sed -n '260,290p' pkg/ui/formatter.go
printf '%s\n' '--- go.mod glamour and goldmark versions ---'
rg -n "charmbracelet/glamour|yuin/goldmark" go.mod go.sum || true
printf '%s\n' '--- goldmark/render/BufWriter source in vendored/modules cache if available ---'
cache="$(go env GOMODCACHE 2>/dev/null || true)"
if [ -n "$cache" ]; then
find "$cache" -path '*github.com/yuin/goldmark*' -type d 2>/dev/null | head -20
find "$cache" -path '*glamour/ansi*' -type d 2>/dev/null | head -20
match="$cache"/github.com/yuin/goldmark*/renderer/renderer.go
if [ -f "$match" ]; then
sed -n '1,220p' "$match"
fi
fi
printf '%s\n' '--- standalone goldmark renderer NodeRendererFunc source ---'
python3 - <<'PY'
import os, subprocess, tempfile, textwrap
try:
subprocess.run(["go", "get", "-d", "github.com/yuin/goldmark@latest"], check=True, capture_output=True, text=True)
except Exception as e:
print("go get failed:", e)
raise SystemExit(0)
match = subprocess.check_output(["go", "list", "-f", "{{.Dir}}/renderer/renderer.go", "github.com/yuin/goldmark/renderer"], text=True).strip()
print(match)
with open(match) as fh:
for i,line in enumerate(fh,1):
print(f"{i}: {line}", end="")
PY
printf '%s\n' '--- deterministic Go error propagation probe ---'
python3 - <<'PY'
import subprocess, tempfile, os
src = textwrap.dedent('''
package main
import (
"fmt"
)
type BufWriter interface{ Write([]byte) (int, error) }
type bad int
func (bad) Write(p []byte) (int, error) { return len(p)/2, fmt.Errorf("write failed") }
type Node interface{}
type stringNodeRenderer struct{}
func (r stringNodeRenderer) renderString(w BufWriter, node Node) error {
_, _ = w.Write([]byte("x"))
return nil
}
type renderer func(BufWriter, Node) error
func render(w BufWriter, node Node, fn renderer) error {
status, err := fn(w, node)
if err != nil {
return fmt.Errorf("writer: %w: status=%d", err, status)
}
return nil
}
func main() {
err := render(bad(0), nil, func(w BufWriter, node Node) (int, error) {
r := stringNodeRenderer{}
return 0, r.renderString(w, node)
})
fmt.Println(err)
}
''')
with tempfile.NamedTemporaryFile(suffix=".go", delete=False) as f:
f.write(src.encode())
name=f.name
try:
subprocess.run(["go","build","-o","/tmp/probe_bin",name], check=True)
out=subprocess.check_output(["/tmp/probe_bin"], text=True)
print(out)
finally:
os.unlink(name)
PYRepository: cloudposse/atmos
Length of output: 5153
🌐 Web query:
github.com yuin goldmark 1.8.2 renderer renderer.go WalkFunc WalkStatus error Write
💡 Result:
In the goldmark library, specifically within the renderer package (renderer/renderer.go), the rendering process uses a tree traversal mechanism to generate output [1][2]. The core type involved in this traversal is the NodeRendererFunc, which is defined as follows: type NodeRendererFunc func(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) [2] This function signature controls the rendering of an AST node [3]. It returns an ast.WalkStatus and an error to guide the traversal: 1. ast.WalkStatus: This return value allows the renderer to control the flow of the walk, such as continuing to the next node (ast.WalkContinue) or skipping the children of the current node (ast.WalkSkipChildren) [3]. 2. error: If an error is returned during the rendering process, the traversal stops, and the error is propagated up to the caller [1][2]. During the Render execution, the renderer performs an ast.Walk on the AST node. For each node encountered, it retrieves the corresponding NodeRendererFunc from its registry and executes it [1][2]. The writer used is an abstraction (util.BufWriter) that allows for efficient buffered output [1][2]. It is important to distinguish this from the standard library's filepath.Walk, which serves an entirely different purpose (file system traversal) and uses a different WalkFunc signature [4]. Similarly, other markdown libraries for Go, such as gomarkdown, have their own distinct implementations of AST walking [5][6][7].
Citations:
- 1: https://code.asyncmachine.dev/src/github.com/yuin/goldmark/renderer/renderer.go.html
- 2: https://mugli.github.io/tmp-literate-golds/src/github.com/yuin/goldmark/renderer/renderer.go.html
- 3: https://deepwiki.com/yuin/goldmark/5-html-renderer
- 4: https://documentation.help/Golang/path_filepath.htm
- 5: https://github.com/gomarkdown/markdown/blob/master/ast/node.go
- 6: https://pkg.go.dev/github.com/gomarkdown/markdown/ast
- 7: https://git.linux.ucla.edu/lug/matterbridge/src/commit/9a25297d5156442b6d85a23f0e6cf511c5351f6d/vendor/github.com/gomarkdown/markdown/ast/node.go
Propagate the renderer write error.
Return the w.Write(str.Value) error from renderString; goldmark treats renderer errors as writer failures, and this path is used by renderMarkdown, which falls back to plain content.
🤖 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 `@pkg/ui/markdown/extensions/linkify.go` around lines 144 - 153, Update
stringNodeRenderer.renderString to capture and return the error from
w.Write(str.Value) instead of discarding it, while preserving the existing walk
status and early-return behavior for non-entering nodes and non-string AST
nodes.
Source: Coding guidelines
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dependency Review✅ No vulnerabilities or license issues found.Scanned Files
|
Removed content duplicated in references/commands-reference.md and the Custom Registries/Configuration sections; the CI "Validate agent skills structure and size" job flagged the file at 502 lines. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resource Changes Found for
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@agent-skills/skills/atmos-toolchain/SKILL.md`:
- Around line 206-216: Update the common command list in the toolchain skill
documentation to include atmos toolchain update [tool...], noting its --dry-run
and --max-concurrency options or linking to the full update reference. Keep the
existing command entries unchanged.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 9a9979ff-fe9b-4adf-8d73-fda963a84b40
📒 Files selected for processing (1)
agent-skills/skills/atmos-toolchain/SKILL.md
Addresses CodeRabbit review comment: the toolchain skill's command list and reference doc omitted the update command introduced in this PR, including its --dry-run and --max-concurrency flags. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Generates or refreshes toolchain.lock.yaml (checksum/provenance records) without reinstalling tools, for the use_lock_file workflow where a lockfile is wanted but a full reinstall isn't. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…l tests install_test.go read updated tool-versions state from the hardcoded DefaultToolVersionsFilePath constant instead of the tempDir-isolated path the test actually configured. This was masked by a matching bug in updateToolVersionsFile (already fixed) that made writes land on the same wrong path; once the write path was corrected, these tests started reading a stale/shared file and picking up unrelated content from CI's own toolchain install step, causing intermittent failures. Also regenerate the install --help golden snapshot, stale since the mailto-linkify fix landed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (2)
pkg/toolchain/installer/lock_tool_test.go (2)
73-113: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winVerify the full no-op contract when lock files are disabled.
Installer.LockToolstill builds the asset URL, downloads the asset, and verifies it beforeupdateLockFileinpkg/toolchain/installer/installer.goLines 451-481. This test only checks that no lock file exists, so a regression that performs network I/O whileuseLockFileis false would still pass. Count requests and assert zero, or rename the test and comment to promise only that no lock file is written.As per coding guidelines, behavior-focused tests must assert the behavior named by the test.
🤖 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 `@pkg/toolchain/installer/lock_tool_test.go` around lines 73 - 113, Strengthen TestLockTool_NoOpsWhenLockFileDisabled by counting HTTP requests handled by the test server and asserting the count remains zero after LockTool returns. Keep the existing no-error and no-lock-file assertions, ensuring the test verifies the complete no-op behavior when useLockFile is false.Source: Coding guidelines
39-70: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winExercise the existing-binary path.
The test starts with an empty
binDir, soassert.Emptyonly proves thatLockTooldid not create a new entry. It does not prove that an existing binary remains unchanged. SeedbinDirwith sentinel contents and assert that the contents remain unchanged after locking.As per coding guidelines, new features need comprehensive behavior-focused tests.
🤖 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 `@pkg/toolchain/installer/lock_tool_test.go` around lines 39 - 70, Update the LockTool test around the existing binDir setup to create a sentinel binary file with known contents before calling installer.LockTool. After locking, read that same file and assert its contents are unchanged, replacing the empty-directory-only assertion while preserving the lock-file verification.Source: Coding guidelines
🤖 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 `@cmd/toolchain/lock.go`:
- Around line 63-84: Add GoDoc comments to the exported methods on
LockCommandProvider: GetCommand, GetName, GetGroup, GetFlagsBuilder,
GetPositionalArgsBuilder, and GetCompatibilityFlags. Start each comment with the
corresponding method name and briefly describe its returned command, name,
group, flags builder, positional-argument builder, or compatibility flags.
- Around line 55-57: Update cmd/toolchain/lock.go lines 55-57 by introducing an
injectable lock-runner interface or dependency and have runLock invoke it while
preserving argument and MaxConcurrency forwarding. In cmd/toolchain/lock_test.go
lines 19-48, add table-driven mock-runner tests covering default and explicit
concurrency, invalid concurrency values, and multiple tool arguments; both sites
require changes.
- Around line 25-27: Replace the inline Example text in the toolchain lock
command with a dedicated cmd/markdown/*_usage.md usage file, embed that file
using //go:embed, and render the embedded content through
utils.PrintfMarkdown().
- Around line 46-47: Update the error return in lockCmd after
lockParser.BindFlagsToViper fails to wrap the underlying error with fmt.Errorf
using errors.ErrWrapFormat and errors.ErrFlagBinding, preserving the original
error while adding command-context classification.
In `@pkg/toolchain/install_test.go`:
- Line 334: Update the comment near the tool registration test to end with a
period after “resolution,” preserving the existing wording and meaning.
In `@pkg/toolchain/lock_test.go`:
- Around line 73-96: Add a success-path test for RunLock using a valid tool
fixture and LockOptions with UseLockFile: false. Assert the configured lock file
is written with the expected contents and verify that no binary installation
occurs, covering the command’s force-write behavior without relying only on
helper or failure-path tests.
In `@pkg/toolchain/lock.go`:
- Around line 52-55: Update the error returned by the tool-versions loading flow
around GetToolVersionsFilePath and LoadToolVersions to report the configured
filePath value instead of hardcoding “.tool-versions”, while preserving the
existing wrapped errors and operation context.
- Around line 120-123: Update the error return after installer.ParseToolSpec in
the tool resolution flow to wrap errUtils.ErrInvalidToolSpec alongside the
underlying error, preserving the existing tool name context and enabling
errors.Is classification for malformed specifications.
In
`@tests/snapshots/TestCLICommands_atmos_toolchain_install_--help.stdout.golden`:
- Around line 10-11: Separate the two install examples in the source help text
for the toolchain install command so they render as distinct commands, then
regenerate the affected golden snapshot using -regenerate-snapshots; do not edit
the snapshot directly.
---
Nitpick comments:
In `@pkg/toolchain/installer/lock_tool_test.go`:
- Around line 73-113: Strengthen TestLockTool_NoOpsWhenLockFileDisabled by
counting HTTP requests handled by the test server and asserting the count
remains zero after LockTool returns. Keep the existing no-error and no-lock-file
assertions, ensuring the test verifies the complete no-op behavior when
useLockFile is false.
- Around line 39-70: Update the LockTool test around the existing binDir setup
to create a sentinel binary file with known contents before calling
installer.LockTool. After locking, read that same file and assert its contents
are unchanged, replacing the empty-directory-only assertion while preserving the
lock-file verification.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: de714dcd-b404-42ce-aa1c-2c0338605a3e
📒 Files selected for processing (36)
agent-skills/skills/atmos-toolchain/SKILL.mdagent-skills/skills/atmos-toolchain/references/commands-reference.mdcmd/toolchain/lock.gocmd/toolchain/lock_test.gocmd/toolchain/provider_test.gocmd/toolchain/toolchain.godemo/casts/atmos.d/screengrabs/cli.yamlpkg/toolchain/install_test.gopkg/toolchain/installer/installer.gopkg/toolchain/installer/lock_tool_test.gopkg/toolchain/lock.gopkg/toolchain/lock_test.gopkg/toolchain/types.gotests/snapshots/TestCLICommands_atmos_toolchain_--help.stdout.goldentests/snapshots/TestCLICommands_atmos_toolchain_install_--help.stdout.goldenwebsite/docs/cli/commands/toolchain/toolchain-lock.mdxwebsite/static/casts/screengrabs/atmos-toolchain--help.castwebsite/static/casts/screengrabs/atmos-toolchain-add--help.castwebsite/static/casts/screengrabs/atmos-toolchain-clean--help.castwebsite/static/casts/screengrabs/atmos-toolchain-du--help.castwebsite/static/casts/screengrabs/atmos-toolchain-env--help.castwebsite/static/casts/screengrabs/atmos-toolchain-exec--help.castwebsite/static/casts/screengrabs/atmos-toolchain-get--help.castwebsite/static/casts/screengrabs/atmos-toolchain-install--help.castwebsite/static/casts/screengrabs/atmos-toolchain-list--help.castwebsite/static/casts/screengrabs/atmos-toolchain-lock--help.castwebsite/static/casts/screengrabs/atmos-toolchain-path--help.castwebsite/static/casts/screengrabs/atmos-toolchain-registry--help.castwebsite/static/casts/screengrabs/atmos-toolchain-registry-list--help.castwebsite/static/casts/screengrabs/atmos-toolchain-registry-search--help.castwebsite/static/casts/screengrabs/atmos-toolchain-remove--help.castwebsite/static/casts/screengrabs/atmos-toolchain-search--help.castwebsite/static/casts/screengrabs/atmos-toolchain-set--help.castwebsite/static/casts/screengrabs/atmos-toolchain-uninstall--help.castwebsite/static/casts/screengrabs/atmos-toolchain-update--help.castwebsite/static/casts/screengrabs/atmos-toolchain-which--help.cast
🚧 Files skipped from review as they are similar to previous changes (23)
- website/static/casts/screengrabs/atmos-toolchain-which--help.cast
- website/static/casts/screengrabs/atmos-toolchain-env--help.cast
- website/static/casts/screengrabs/atmos-toolchain-registry-search--help.cast
- cmd/toolchain/provider_test.go
- tests/snapshots/TestCLICommands_atmos_toolchain_--help.stdout.golden
- website/static/casts/screengrabs/atmos-toolchain-list--help.cast
- website/static/casts/screengrabs/atmos-toolchain-set--help.cast
- website/static/casts/screengrabs/atmos-toolchain-registry-list--help.cast
- website/static/casts/screengrabs/atmos-toolchain-add--help.cast
- website/static/casts/screengrabs/atmos-toolchain-du--help.cast
- website/static/casts/screengrabs/atmos-toolchain-get--help.cast
- website/static/casts/screengrabs/atmos-toolchain-update--help.cast
- website/static/casts/screengrabs/atmos-toolchain-uninstall--help.cast
- demo/casts/atmos.d/screengrabs/cli.yaml
- website/static/casts/screengrabs/atmos-toolchain--help.cast
- website/static/casts/screengrabs/atmos-toolchain-registry--help.cast
- website/static/casts/screengrabs/atmos-toolchain-install--help.cast
- website/static/casts/screengrabs/atmos-toolchain-clean--help.cast
- website/static/casts/screengrabs/atmos-toolchain-path--help.cast
- website/static/casts/screengrabs/atmos-toolchain-remove--help.cast
- agent-skills/skills/atmos-toolchain/SKILL.md
- website/static/casts/screengrabs/atmos-toolchain-exec--help.cast
- website/static/casts/screengrabs/atmos-toolchain-search--help.cast
| Example: ` atmos toolchain lock | ||
| atmos toolchain lock terraform | ||
| atmos toolchain lock terraform kubectl --max-concurrency 2`, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Use the required embedded Markdown help.
Replace the inline Example value with a cmd/markdown/*_usage.md file. Embed it with //go:embed and render it with utils.PrintfMarkdown().
As per coding guidelines, “Embed command usage examples from cmd/markdown/*_usage.md with //go:embed and render them with utils.PrintfMarkdown().”
🤖 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/toolchain/lock.go` around lines 25 - 27, Replace the inline Example text
in the toolchain lock command with a dedicated cmd/markdown/*_usage.md usage
file, embed that file using //go:embed, and render the embedded content through
utils.PrintfMarkdown().
Source: Coding guidelines
| return toolchain.RunLock(args, toolchain.LockOptions{ | ||
| MaxConcurrency: maxConcurrency, | ||
| }) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Make the lock runner testable and test command behavior.
runLock directly calls toolchain.RunLock. The tests only inspect metadata. This leaves argument forwarding and --max-concurrency behavior untested.
cmd/toolchain/lock.go#L55-L57: inject a lock-runner dependency that the command can call.cmd/toolchain/lock_test.go#L19-L48: add table-driven tests with a mock runner for default concurrency, explicit concurrency, invalid values, and multiple tool arguments.
As per coding guidelines, “Use interfaces and dependency injection for major functionality” and “Every new feature must include comprehensive unit tests.”
📍 Affects 2 files
cmd/toolchain/lock.go#L55-L57(this comment)cmd/toolchain/lock_test.go#L19-L48
🤖 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/toolchain/lock.go` around lines 55 - 57, Update cmd/toolchain/lock.go
lines 55-57 by introducing an injectable lock-runner interface or dependency and
have runLock invoke it while preserving argument and MaxConcurrency forwarding.
In cmd/toolchain/lock_test.go lines 19-48, add table-driven mock-runner tests
covering default and explicit concurrency, invalid concurrency values, and
multiple tool arguments; both sites require changes.
Source: Coding guidelines
| func (l *LockCommandProvider) GetCommand() *cobra.Command { | ||
| return lockCmd | ||
| } | ||
|
|
||
| func (l *LockCommandProvider) GetName() string { | ||
| return "lock" | ||
| } | ||
|
|
||
| func (l *LockCommandProvider) GetGroup() string { | ||
| return "Toolchain Commands" | ||
| } | ||
|
|
||
| func (l *LockCommandProvider) GetFlagsBuilder() flags.Builder { | ||
| return lockParser | ||
| } | ||
|
|
||
| func (l *LockCommandProvider) GetPositionalArgsBuilder() *flags.PositionalArgsBuilder { | ||
| return nil | ||
| } | ||
|
|
||
| func (l *LockCommandProvider) GetCompatibilityFlags() map[string]compat.CompatibilityFlag { | ||
| return nil |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Document the exported provider methods.
Add GoDoc comments for GetCommand, GetName, GetGroup, GetFlagsBuilder, GetPositionalArgsBuilder, and GetCompatibilityFlags.
As per coding guidelines, “Document all exported functions, types, and methods following Go's documentation conventions.”
🤖 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/toolchain/lock.go` around lines 63 - 84, Add GoDoc comments to the
exported methods on LockCommandProvider: GetCommand, GetName, GetGroup,
GetFlagsBuilder, GetPositionalArgsBuilder, and GetCompatibilityFlags. Start each
comment with the corresponding method name and briefly describe its returned
command, name, group, flags builder, positional-argument builder, or
compatibility flags.
Source: Coding guidelines
| Examples: atmos toolchain install hashicorp/terraform@1.5.0 atmos toolchain | ||
| install opentofu@1.6.0 tflint@0.50.0 kubectl@1.29.0 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Separate the two install examples in the source help text.
The snapshot renders atmos toolchain install hashicorp/terraform@1.5.0 atmos toolchain install ... as one command. Copying it passes atmos, toolchain, and install as extra tool arguments. Add a newline or another clear command separator in the source help text, then regenerate this snapshot. Do not edit the golden file manually.
As per coding guidelines, regenerate golden snapshots with -regenerate-snapshots instead of editing them manually.
🤖 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 `@tests/snapshots/TestCLICommands_atmos_toolchain_install_--help.stdout.golden`
around lines 10 - 11, Separate the two install examples in the source help text
for the toolchain install command so they render as distinct commands, then
regenerate the affected golden snapshot using -regenerate-snapshots; do not edit
the snapshot directly.
Source: Coding guidelines
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (77.66%) is below the target coverage (85.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #2894 +/- ##
========================================
Coverage 82.76% 82.77%
========================================
Files 1861 1865 +4
Lines 180478 181124 +646
========================================
+ Hits 149380 149926 +546
- Misses 23311 23376 +65
- Partials 7787 7822 +35
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Fixes from PR #2894 review: wrap lock command's flag-binding error with the static ErrFlagBinding sentinel, interpolate the actual configured tool-versions path (instead of a hardcoded ".tool-versions" literal) into RunLock's load-failure message, classify ParseToolSpec failures in resolveLockTargets under ErrInvalidToolSpec so callers can errors.Is() them, add a period to a dangling comment, and split `toolchain install`'s Long description away from its Example field so the two usage lines no longer render concatenated on one line in --help output. Also adds TestRunLock_ForceWritesLockFileWithoutInstalling, covering the force-write contract `atmos toolchain lock` exists to provide (lock file written and no binary installed even with toolchain.use_lock_file: false). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
pkg/toolchain/lock_test.go (1)
114-126: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy liftKeep the default unit-test path offline.
Lines 114-118 state that this test performs a real registry lookup and download. This makes the unit-test result depend on network availability, registry behavior, and remote artifacts.
Use a deterministic package-level hook or mock for this test. Put live registry coverage in an opt-in integration test.
🤖 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 `@pkg/toolchain/lock_test.go` around lines 114 - 126, Update the test setup around RunLock to replace the real NewInstaller registry lookup and download with a deterministic package-level hook or mock, ensuring the default unit-test path remains offline while preserving the existing temporary InstallPath isolation. Move live registry/download coverage into a separately opt-in integration test.Sources: Coding guidelines, Learnings
🤖 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 `@pkg/toolchain/lock_test.go`:
- Around line 131-135: Strengthen the lock-file assertions in the test around
lockFilePath by decoding lockData with the existing lock-file model, then locate
the hashicorp/terraform@1.11.4 entry and assert its tool, version,
checksum_algorithm, and non-empty checksum value instead of checking only raw
text containment.
---
Nitpick comments:
In `@pkg/toolchain/lock_test.go`:
- Around line 114-126: Update the test setup around RunLock to replace the real
NewInstaller registry lookup and download with a deterministic package-level
hook or mock, ensuring the default unit-test path remains offline while
preserving the existing temporary InstallPath isolation. Move live
registry/download coverage into a separately opt-in integration test.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 387ea6ab-baa8-4705-84a9-930ed0c5b092
📒 Files selected for processing (7)
cmd/toolchain/install.gocmd/toolchain/lock.gopkg/toolchain/install_test.gopkg/toolchain/lock.gopkg/toolchain/lock_test.gotests/snapshots/TestCLICommands_atmos_toolchain_install_--help.stdout.goldenwebsite/static/casts/screengrabs/atmos-toolchain-info--help.cast
💤 Files with no reviewable changes (1)
- tests/snapshots/TestCLICommands_atmos_toolchain_install_--help.stdout.golden
🚧 Files skipped from review as they are similar to previous changes (4)
- website/static/casts/screengrabs/atmos-toolchain-info--help.cast
- pkg/toolchain/install_test.go
- pkg/toolchain/lock.go
- cmd/toolchain/lock.go
Bumps js-yaml (3.15.0->3.15.1, 4.3.0->4.3.1) and mermaid (11.16.0->11.16.1) pnpm overrides to patched versions. All are patch-level bumps within the allowed (non-major) range per .github/dependabot.yml's ignore policy. Fixes GHSA-5p4m-2wfm-xmqj (js-yaml quadratic CPU in !!omap resolution, high severity, alerts #268/#269) and GHSA-rhh3-jpg6-66xh/GHSA-c4c3-pg64-4m4v/ GHSA-6x64-9x62-f2gx/GHSA-3rrr-jr9j-h3q3/GHSA-2v8p-3f2j-5mp7 (mermaid, medium/low severity, alerts #263-#267). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ainment TestRunLock_ForceWritesLockFileWithoutInstalling only checked that "checksum_algorithm" and "terraform" appeared somewhere in the raw lock file bytes, which would still pass if the requested hashicorp/terraform@1.11.4 entry itself had no checksum. Decode with lockfile.Load and assert the specific entry's version, checksum algorithm, and non-empty checksum for the current platform. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ulti-version bugs Found via a field-test pass of `atmos toolchain update`/`atmos toolchain lock`: AddVersionToTool(asDefault=true) only prepended a new version instead of replacing the line, so `set`, `add --default`, and `update` all left stale versions permanently pinned alongside the new one (existing tests encoded this as correct behavior, checking versions[0] but never the full slice). Fixed to fully replace the line, matching asdf's own `set` convention (its docs describe `asdf set <tool> <version>` as equivalent to `echo "<tool> <version>" > .tool-versions`). toolchain.lock.yaml's schema keyed tools by owner/repo with a single flat Version field, so locking a second version of an already-locked tool silently discarded the first's checksum -- a real risk since .tool-versions can legitimately pin multiple versions of one tool (e.g. examples/toolchain's "yq 4.45.1 4.50.1"). Restructured Tool to hold a nested Versions map so each locked version's data survives independently, and bumped lock_file_version to 2. `install` never verified a freshly downloaded checksum against what was already recorded in the lock file, despite `lock`'s own warning implying it would once toolchain.use_lock_file is enabled -- a tampered or corrupted lock entry was silently overwritten instead of failing. Added a verifyAgainstLock installer field (true for config-driven installs, false for `lock`'s own force-write/refresh path) and a checksum-mismatch check before any lock entry is overwritten. Also: resolveLockFilePath's default fallback now matches GetInstallPath()'s XDG-cache-first chain instead of a hardcoded relative ".tools" (they previously resolved to different directories by default); `add`'s error wrapping used a double-%w fmt.Errorf that silently discarded cockroachdb/errors hints attached via errUtils.Build, now uses WithCause so the range/constraint-syntax rejection hint actually reaches users; and `update`'s --help text no longer claims ref:-pinned tools are "immutable by design" (a named ref can move; it's skipped by choice). Nine regression tests were written and confirmed failing before each fix landed. Fixing the shared replace-semantics helper and the lock schema surfaced two more affected consumers not caught by the original field test: pkg/toolchain/filemanager's ToolVersionsFileManager/LockFileManager (unwired, but still compiled against and tested), whose own tests also encoded the same bugs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
what
atmos toolchain update [tool...]to move a pinned tool to its newest available version and reinstall it, with--dry-runand bounded--max-concurrency. Tools pinned topr:/sha:/ref:are skipped with an explanation instead of silently left alone.which/execresolving the wrong version (last token instead of the default first token) on a multi-version.tool-versionsline, which caused false "not installed" errors.setappending instead of replacing the default version, contradicting its documented behavior.add/installsilently accepting SemVer range syntax (^1.2.0,~>1.0.0) and only failing later with a raw HTTP 404; now rejected immediately with a hint towarddependencies.tools/atmos version track.atmos version track add/setcorrupting any value containing<,>, or&(ajson.MarshalHTML-escaping bug), which broke the exact~>/>=constraint syntax the toolchain docs recommend.atmos toolchain versions --helpsilently rendering the wrong command's help and exiting 0 instead of erroring; fixed globally in root help routing (atmos <cmd> <bogus-subcommand> --helpnow errors for every command tree). Removes the staletoolchain-versionsandtoolchain-aliasesdocs/casts for commands that were never implemented.list --format/--installed-only/--pending-only,clean --dry-run/--cache-only/--force,exec --dry-run.updateToolVersionsFilewriting to the hardcoded default.tool-versionspath instead of the configured one.updatecommand.why
atmos toolchainsurfaced that there was no way to update a pinned tool to a newer version, and no clear signal for why range/constraint syntax (^1.2.0,~>1.0.0) didn't work when the docs implied it should.add <tool>@latest+install --reinstall) reproduced a real crash inwhich/exec, which led to finding the rest of the bugs above along the way — a documentedsetbehavior that didn't match reality, a JSON-escaping bug corrupting exactly the constraint syntax the toolchain skill doc recommends, and a help-routing bug that letatmos toolchain versions --helpsilently succeed for a command that doesn't exist (which is also why its docs page and cast looked legitimate despite documenting nothing real).atmos toolchain's docs promised and what the CLI actually did, and give users a real, safe way to move a pinned tool forward.references
osterman/toolchain-update-pinning-field-test)Summary by CodeRabbit
New Features
toolchain updatefor selected or all tools, with dry-run and concurrency controls.toolchain lockto verify artifacts and create lock entries without installation.toolchain uninstall --allto remove all installed tools.Bug Fixes
Documentation