Repository navigation
fix(ai): close DX gaps found in atmos ai field test - #2903
Conversation
Fixes 16 findings from a hands-on field test of `atmos ai` commands: skill install/uninstall: - Enforce the compatibility.atmos version gate for bundled and multi-skill Git package installs, not just single-skill Git clones - Reject unrecognized --client values instead of silently no-op'ing (extends pkg/flags WithValidValues to string-slice flags generically) - Warn when --path is combined with --client/--scope/--global, since --path skips auto-distribution and those flags are otherwise ignored - Give the registry-corruption error an actionable hint via the error-builder pattern - Show a skill's minimum required Atmos version in `skill list --detailed` and flag when an installed skill has a newer catalog version available - Fix `agent-skills/skills/atmos-ai/SKILL.md` doc drift (never documented `skill install`) and remove a phantom `info` subcommand from --help - Add local-path/file:// support to skill source parsing and the downloader - Document --scope/--global precedence ask/exec/sessions/MCP: - Make `exec`/`ask --session` actually persist and resume conversations (previously a complete no-op despite being a documented flag) - Resolve a session's Model from the constructed AI client instead of a raw config lookup, fixing sessions export/import for the default zero-config claude-code provider path - Apply --mcp server filtering for CLI providers too (was silently ignored, all configured servers were always passed through) - Reject invalid --format values instead of silently falling back to text Two intentional behavior changes: - `ai exec` can now return exit code 2 for a genuine infrastructure-level tool failure (e.g. an unregistered tool) without waiting on the 25-iteration tool-call loop to exhaust - `sessions clean --older-than 0d` now deletes all sessions immediately, distinguished from the flag not being passed at all (which still defaults to 30 days); negative durations are now a hard parse error instead of silently falling back to the default Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bumps pnpm overrides for two transitive website dependencies to their patched versions: - js-yaml 3.15.0 -> 3.15.1, 4.3.0 -> 4.3.1 (GHSA-5p4m-2wfm-xmqj, quadratic CPU consumption in !!omap resolution, high severity, alerts #268/#269) - mermaid 11.16.0 -> 11.16.1 (GHSA-rhh3-jpg6-66xh, GHSA-c4c3-pg64-4m4v, GHSA-6x64-9x62-f2gx, GHSA-3rrr-jr9j-h3q3, GHSA-2v8p-3f2j-5mp7, alerts #263-#267) Both are within-major-version patches (not blocked by dependabot.yml's major-bump ignore policy). No CodeQL alerts were open. Verified with a full `pnpm run build` in website/ — succeeds, no new broken links. NOTICE is unchanged (no license-set change from these bumps). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Tip Atmos Pro
No affected stacks workflow was detected for this pull request. |
Dependency Review✅ No vulnerabilities or license issues found.Scanned Files
|
TestManager_ExportSession_WarnsOnUnimportableCheckpoint asserted on the raw ui.Warning() output, including ANSI styling. Under CI=true (the real GitHub Actions env, confirmed by reproducing locally with CI=true) the formatter emits the message across two adjacent styled runs, which split the literal substring "not be re-importable" the test was checking for -- the visible text was identical, only the styling boundary differed from a local run. Passed locally, failed in CI. Strip ANSI codes and collapse whitespace before asserting on captured UI output, so the test checks content, not rendering byte-layout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2903 +/- ##
==========================================
- Coverage 82.79% 82.78% -0.01%
==========================================
Files 1863 1866 +3
Lines 180652 181025 +373
==========================================
+ Hits 149568 149868 +300
- Misses 23293 23341 +48
- Partials 7791 7816 +25
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Closes the gap from finding #11 of the field-test fix pass: bundled skills are static copies made at install time, so upgrading the atmos binary alone never refreshed a skill you'd already installed, even when the new release shipped improved skill content. `atmos ai skill list --detailed` already surfaced an "update available" hint; nothing acted on it. `atmos ai skill update [name]` compares each installed bundled skill's recorded version against the catalog embedded in the running binary and reinstalls only the ones that are actually outdated: - With no <name>, updates every installed bundled skill that has a newer version available (a single confirmation, not one per skill). Skills already current are left untouched and reported separately, so it's safe to run repeatedly. - An outdated skill is reinstalled the same way `install <name> --force` would install it, reusing all the same client-distribution/scope flags and logic (--client, --all-clients, --scope, --global, --path). - Skills installed from GitHub aren't covered yet (no cheap way to check their upstream version without a fetch); update returns a clear error pointing at `install <source> --force` as the manual path for those. Both `atmos ai skill list`'s "update available" indicator and the new `update` command now share one `marketplace.SkillVersionOutdated` helper instead of duplicating the comparison, so they can never disagree. Docs, blog post, and roadmap updated; the `atmos ai skill --help` screengrab regenerated (--filter'd to just that one cast). Relabeling this PR patch -> minor since this is new user-visible functionality, which requires both per this repo's release-doc policy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per the fix-log skill: one record each for the 16-finding atmos ai field-test fix pass, the website Dependabot remediation, the flaky CI test-assertion fix, and the new atmos ai skill update command. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Important Cloud Posse Engineering Team Review RequiredThis pull request modifies files that require Cloud Posse's review. Please be patient, and a core maintainer will review your changes. To expedite this process, reach out to us on Slack in the |
ValidateFlagValues only checked cmd.Flags().Changed(), so an invalid value supplied purely via a bound environment variable (e.g. ATMOS_AI_SKILL_CLIENT=bogus) silently bypassed validation -- a skill install/update would report success while distributing to zero clients. Now validates whenever Viper reports the value as actually set (CLI or env), not just CLI-changed. Found via a field-test pass on the atmos ai skill update / pkg/flags changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
UpdateAllBundled/InstallAllBundled's batch path logged a warning per failed skill but always returned nil, so a real per-skill failure (e.g. a reinstall that can't remove the existing install) was reported as overall success with exit code 0 -- the only signal was an easily-missed log line, invisible entirely at logs.level: Error. Adds a distinct outcomeRejected for expected compatibility/structure skips (already covered by an existing test), keeping it separate from genuine outcomeFailed so only real failures propagate an error. Found via a field-test pass on the atmos ai skill update changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The anonymous-session branch (no --session given) still called getModelFromConfig -- a raw config lookup with no default fallback -- instead of client.GetModel(), unlike the named-session branch fixed in a prior commit. A zero-config claude-code chat with no --session got a blank Model on its session record, breaking re-import (which requires a non-empty Model). Found via a field-test pass on the atmos ai session DX fixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
prepareSession returned (nil, nil) identically for "no --session given" and "--session given but ai.sessions.enabled is false", so a user could run `exec --session foo` across multiple invocations believing conversation context was being carried over, with nothing persisted and no indication why -- only discoverable indirectly via `sessions list` erroring "sessions are not enabled". Found via a field-test pass on the atmos ai session DX fixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two processes opening the same new session database at once (e.g. concurrent `atmos ai exec --session <same-name>` invocations) could crash with SQLITE_BUSY: PRAGMA journal_mode = WAL briefly needs exclusive access to switch modes, and busy_timeout was set after it in the pragma list, so a connection that lost the race had no busy timeout in effect yet. Reorders busy_timeout first and adds a bounded retry loop around pragma execution, since the pure-Go sqlite driver (modernc.org/sqlite) doesn't consistently honor busy_timeout for that specific one-time mode switch. Found via a field-test pass on the atmos ai session DX fixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏 |
…i-commands # Conflicts: # cmd/ai/skill/list.go # cmd/ai/skill/list_test.go
- github.com/go-git/go-git/v5: 5.19.1 -> 5.19.2, fixing a symlink traversal in worktree operations (GHSA-hc8v-wwc9-vgxm, high) and a path traversal in reference name handling (GHSA-qgq7-7hm3-q39j, medium). Alerts #270, #271. - dompurify (website, transitive via pnpm override): 3.4.12 -> 3.4.13, fixing an IN_PLACE sanitization XSS via detached subtree hook removal (GHSA-55q2-fjhq-7xh7, medium). Alert #272. - nanoid (website, transitive via pnpm override, both the direct override target and postcss's own dependency): 3.3.15/3.3.16 -> 3.3.18, fixing two infinite-loop DoS issues with negative/zero size (GHSA-28wg-ghj8-5hjv, GHSA-2v37-7h3g-55p8, high). Alerts #273, #274. Not fixed: #275/#276 (npm image-size <= 2.0.2, high) -- no patched version exists yet upstream (first_patched_version is null on both advisories); nothing to bump to. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Tip Atmos Pro
No affected stacks workflow was detected for this pull request. |
|
Tip Atmos Pro
No affected stacks workflow was detected for this pull request. |
TestSetDescribeAffectedFlagValueInCliArgs_BaseResolution's "CI auto-detect when enabled and no explicit base" subtest cleared GITHUB_ACTIONS/CI but not GITHUB_EVENT_PATH. When this test suite runs locally or under a regular pull_request CI trigger, no merge_group event file is present, so resolveMergeGroupBase falls back to the simulated GITHUB_BASE_REF the subtest sets, and the test passes. But when this test actually runs inside a real merge_group-triggered workflow (i.e. a PR going through the GitHub merge queue), the runner's real GITHUB_EVENT_PATH points at a genuine merge_group event payload with a real base_sha -- and resolveMergeGroupBase correctly prefers that real payload data over GITHUB_BASE_REF, per its documented fallback order. The subtest never accounted for this, so it fails with an empty Ref field specifically -- and only -- in real merge-queue runs, not locally or in regular PR CI. Reproduced locally by pointing GITHUB_EVENT_PATH at a synthetic merge_group payload; fixed by explicitly clearing GITHUB_EVENT_PATH so the subtest is hermetic regardless of the ambient CI context it happens to execute in. This was pre-existing on main (from PR #2395) and surfaced when PR #2903 hit the merge queue for the first time; not a regression from this branch's own changes, but fixed here directly per repo convention for CI-blocking issues found on an open PR's branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…_EVENT_PATH TestSetDescribeAffectedFlagValueInCliArgs_BaseResolution's merge_group subtest only overrode GITHUB_ACTIONS/GITHUB_EVENT_NAME/GITHUB_BASE_REF, so it depended on $GITHUB_EVENT_PATH being unset in the ambient environment. That's true for local/regular CI runs, but not when the test itself runs inside a real GitHub Actions merge_group job (i.e. the merge queue): the real event payload's merge_group.base_sha then takes precedence over the simulated GITHUB_BASE_REF, resolving to a SHA instead of the "refs/remotes/origin/main" ref the test asserts. This was failing the merge queue for this PR and, independently, for #2900 and #2903 as well -- confirmed pre-existing and unrelated to any of their changes. Fixed by explicitly forcing GITHUB_EVENT_PATH="" so the subtest deterministically exercises the fallback path it's meant to test, regardless of the real job's event context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…i-commands # Conflicts: # website/pnpm-lock.yaml
|
Tip Atmos Pro
No affected stacks workflow was detected for this pull request. |
|
Tip Atmos Pro
No affected stacks workflow was detected for this pull request. |
|
Warning Release Documentation RequiredThis PR is labeled
|
…udposse#2880) * fix(steps): resolve relative paths against step.WorkingDirectory The archive, file, workdir, junit, and container build step handlers resolved relative source/destination/path/glob/context fields via template substitution only, then let filesystem calls resolve them against the Atmos process's own cwd instead of step.WorkingDirectory. This surfaced most visibly for `type: archive` hooks, since the hooks engine correctly defaults working_directory to the component path but the handler never read it back. Add a shared BaseHandler.ResolveInWorkingDirectory helper that anchors a relative resolved value to step.WorkingDirectory (falling back to process cwd when unset, matching prior behavior), and apply it across the five affected handlers. container_build.go additionally anchors Dockerfile to the resolved Context rather than WorkingDirectory directly, matching Docker's own convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(fixes): add fix record for step WorkingDirectory bug Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(runner/step): close coverage gaps in ResolveInWorkingDirectory and loadReport Codecov flagged handler_base.go and junit.go below the 85% patch-coverage threshold. Add a case that exercises the relative (non-template) WorkingDirectory branch in resolveWorkingDirectory, and a junit test that triggers a WorkingDirectory template-resolution error from inside loadReport's per-pattern loop, distinct from the already-covered `files` template error path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(steps): anchor container bake/cache paths and preserve workflow error context A field test of the WorkingDirectory fix found the container_build.go handler was only partially fixed: build.bake.file/bake.files and cache.from/cache.to type: local src/dest still resolved against the Atmos process's own cwd instead of step.WorkingDirectory, reproduced live as a silent build against the wrong bake file. Both now route through ResolveInWorkingDirectory like context/dockerfile already do. Also root-caused and fixed a separate defect surfaced while verifying error output: buildWorkflowStepError dual-wrapped step errors with fmt.Errorf before building the final error, and cockroachdb/errors treats that Go 1.20 multi-error shape as an opaque leaf node, silently dropping any hints/context a handler attached deeper in the chain. Switched to WithCause, which extracts them eagerly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(steps): anchor container bake/cache paths and preserve workflow error context Address CodeRabbit findings on PR cloudposse#2880: - Remove the host-specific /tmp/atmos-field-test/ path from the fix doc, replacing it with a worktree-relative fixture reference. - Split TestBuildWorkflowStepError and TestBuildWorkflowStepErrorPreservesInnerHintsAndContext out of workflow_utils_test.go into a focused workflow_step_error_test.go, keeping step-error tests co-located and out of the oversized (pre-existing) workflow_utils_test.go file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(runner/step): close container_build.go coverage gaps from bake/cache anchoring Codecov flagged container_build.go's patch coverage below 85% after the bake/cache anchoring follow-up. Add regression tests for the three previously-uncovered error branches: resolveBakeFiles propagating a per-entry template failure, anchorCacheLocalPaths' own resolve failure, and that failure propagating out through resolveBuildCache. handler_base.go's remaining gap (os.Getwd/filepath.Abs failing inside resolveWorkingDirectory) is left uncovered deliberately -- it requires corrupting the process's own working directory to reach, has no DI seam, and matches this repo's existing untested pattern for the identical os.Getwd/filepath.Abs fallback in container_run.go. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(docs): exclude reproducible-builds.org from link checking (CI connection refused) CI's Check Markdown Links job failed on docs/prd/archive-step.md's citation of the SOURCE_DATE_EPOCH origin -- the CI runner's outbound request was refused while the page returns 200 OK outside CI. Follows the repo's existing precedent for excluding CI-hostile hosts (docs.docker.com, otelic.com, taskfile.dev, etc.) in lychee.toml. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(hooks): anchor bare-relative working_directory to the component An explicit working_directory: on a kind: step/kind: steps hook was always resolved against the Atmos process's own cwd, regardless of shape. Following docs/prd/base-path-resolution-semantics.md's existing Dot/Bare convention: a dot-prefixed value (., .., ./x, ../x) keeps resolving against the process cwd; a bare relative value (x, x/y) now resolves against the component's own working directory instead -- the same directory ComponentPath(ctx) already computes for the unset-default case, so this stays compatible with provisioned working directories and metadata.component aliasing for free. Workflows and custom commands are unaffected -- they have no "current component" concept, so bare-relative values there still resolve against the process cwd exactly as before. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(security): remediate 7 open Dependabot alerts Bump pnpm.overrides floors for transitive npm dependencies flagged by Dependabot (all patch/minor bumps within the same major, not blocked by dependabot.yml's major-version ignore policy): - js-yaml 3.15.0 -> 3.15.1 (cloudposse#269, GHSA-5p4m-2wfm-xmqj: quadratic CPU consumption in !!omap resolution, high) - js-yaml 4.3.0 -> 4.3.1 (cloudposse#268, same advisory, 4.x line, high) - mermaid 11.16.0 -> 11.16.1 (cloudposse#267, cloudposse#266, cloudposse#265, cloudposse#264, cloudposse#263: five advisories ranging low-medium) NOTICE unchanged (no license changes). Verified: pnpm install regenerated website/pnpm-lock.yaml with the bumped versions resolved, atmos fix lint (patch-scoped, no Go files touched) is a no-op, and cd website && npm run build succeeds with no new broken links. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(fixes): fix markdownlint MD018 in working-directory fix record Address CodeRabbit finding on PR cloudposse#2880: a paragraph wrap left "cloudposse#2880):" at the start of a line, which markdownlint's atx-heading rule (MD018) flags as a heading missing a space after the hash. Rewrap so the line doesn't start with a bare hash-prefixed token. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(runner/step): close remaining working_directory anchoring gaps Found by field-testing the working_directory fix against real hooks, workflows, and a live docker build instead of just unit tests: - workdir step: `source` (local relative path) now anchors to `working_directory` via a new `sourceprov.WithBaseDir` option, matching `path`'s existing anchoring instead of silently falling back to the process cwd. - workflow-level `working_directory:` default now reaches extended step types (archive/file/junit/workdir/container), not just shell/exec/atmos steps, which silently ignored it before. - step-level `working_directory` now expands a leading `~`, matching the tilde expansion --chdir already gets. - Corrected agent-skills/atmos-steps and the workflow/hooks docs, which claimed a single blanket CWD-vs-base_path rule that doesn't match actual per-surface behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(steps): document handler-field and unset-hook working_directory contracts Addresses CodeRabbit feedback on PR cloudposse#2880: state that relative handler fields (source, destination, path, files, context) resolve against the already-resolved working_directory, that a container Dockerfile resolves relative to context rather than working_directory directly, and that an unset kind: step hook working_directory (not just a bare value) anchors to the component's own working directory. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(runner/step): isolate CI base-resolution test from ambient GITHUB_EVENT_PATH TestSetDescribeAffectedFlagValueInCliArgs_BaseResolution's merge_group subtest only overrode GITHUB_ACTIONS/GITHUB_EVENT_NAME/GITHUB_BASE_REF, so it depended on $GITHUB_EVENT_PATH being unset in the ambient environment. That's true for local/regular CI runs, but not when the test itself runs inside a real GitHub Actions merge_group job (i.e. the merge queue): the real event payload's merge_group.base_sha then takes precedence over the simulated GITHUB_BASE_REF, resolving to a SHA instead of the "refs/remotes/origin/main" ref the test asserts. This was failing the merge queue for this PR and, independently, for cloudposse#2900 and cloudposse#2903 as well -- confirmed pre-existing and unrelated to any of their changes. Fixed by explicitly forcing GITHUB_EVENT_PATH="" so the subtest deterministically exercises the fallback path it's meant to test, regardless of the real job's event context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(security): remediate 5 of 7 open Dependabot alerts - github.com/go-git/go-git/v5: v5.19.1 -> v5.19.2, fixing GHSA-hc8v-wwc9-vgxm (symlink traversal in worktree operations) and the companion malicious reference-name advisory (cloudposse#270, cloudposse#271). Patch-level bump, no API changes. - website: dompurify pnpm override ^3.4.12 -> ^3.4.13, fixing an IN_PLACE hook removal XSS (cloudposse#272). - website: nanoid pnpm overrides bumped to ^3.3.17 (added a second selector, nanoid@^3.3.16, to also catch postcss's own nanoid range, which the existing nanoid@3.3.3 selector didn't match), fixing two indefinite-loop DoS advisories (cloudposse#273, cloudposse#274). The nanoid 5.x line was already patched by the existing override. - NOTICE regenerated for the go-git/dompurify/nanoid version bumps. Not fixed: image-size (cloudposse#275, cloudposse#276, both DoS via infinite loop) has no patched release yet as of this commit -- GitHub's advisories list "<= 2.0.2" as vulnerable with no first_patched_version. Left as-is pending an upstream fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Andriy Knysh <aknysh@users.noreply.github.com>
|
These changes were released in v1.226.0-rc.4. |
what
atmos ai skill update [name], a new command that compares each installed bundled skill's recorded version against the catalog embedded in the running binary and reinstalls only the ones that are actually outdated — closes the "no update command" gap the fixes below originally left deferred. See the blog post for the full story.compatibility.atmosversion-compatibility gate for bundled and multi-skill Git package skill installs, not just single-skill Git clones (it was previously skipped entirely for those two paths).atmos ai skill install/uninstall/update --clientvalues instead of silently no-op'ing, by extendingpkg/flags'sWithValidValuesto work on string-slice flags generically (this also fixed a latent bug whereWithValidValueswas silently dead for every command that binds flags viaBindFlagsToViperwithout calling the fullParse()pipeline).--pathis combined with--client/--scope/--global/--all-clientsonskill install, since--pathskips auto-distribution and those flags are otherwise silently ignored.skill list --detailed, and flags when an installed skill has a newer catalog version available (using the same comparisonupdatenow acts on).agent-skills/skills/atmos-ai/SKILL.mddoc drift (it never documentedskill installat all) and removes a phantominfosubcommand fromatmos ai skill --help.file://support to skill source parsing and the downloader.--scope/--globalprecedence onskill install/uninstall/update.atmos ai exec/ask --sessionactually persist and resume conversations — previously a documented flag that was a complete no-op.Modelfrom the constructed AI client instead of an independent config lookup, fixingsessions export/importfor the default zero-configclaude-codeprovider path (previously exported checkpoints for that path could never be re-imported).--mcpserver filtering for CLI providers (claude-code/codex-cli/copilot-cli/gemini-cli) too — it was silently ignored, so all configured MCP servers were always passed through regardless of the flag.--formatvalues onai execinstead of silently falling back totext.ai execcan now return exit code 2 for a genuine infrastructure-level tool failure (e.g. an unregistered tool) immediately, without waiting on the 25-iteration tool-call loop to exhaust as it did before.sessions clean --older-than 0dnow deletes all sessions immediately, distinguished from the flag not being passed at all (which still defaults to 30 days); negative durations are now a hard parse error instead of silently falling back to the default.js-yaml(GHSA-5p4m-2wfm-xmqj, quadratic CPU consumption in!!omapresolution) andmermaid(5 advisories), viapnpm.overridesbumps within their existing major versions. No CodeQL alerts were open.TestManager_ExportSession_WarnsOnUnimportableCheckpointCI failure: the test asserted on raw ANSI-styledui.Warning()output, which the formatter renders as two adjacent styled runs underCI=true— same visible text, different byte layout, so the test passed locally and failed in CI. Strips ANSI before asserting on content now.why
atmos aicommand surface — reading the real implementation, hypothesizing plausible misuse an automated test wouldn't catch, and executing for real against isolated fixtures — rather than a spec change or feature request. Most are silent DX gaps (a flag that looks like it works but doesn't, an error with no way forward, a validation check that only applies on some of the paths that need it).0dinstead of doing what it says is a footgun in the other direction (a user who deliberately asks to delete everything gets nothing, silently).atmos ai skill updateexists because, once asked, leaving "no update command" as a documented gap wasn't the right call — bundled skills going stale after a binary upgrade is exactly the kind of silent drift this whole PR is about fixing elsewhere.references
atmos ai skill updatecommand that was initially scoped out and then built once asked for.