Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions .agent/skills/bump-cli-compat/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
name: bump-cli-compat
description: "Bump cli-compat.json with new AppKit and Agent Skills versions, then create a PR. Use when the user says 'bump cli-compat', 'update cli-compat', 'bump compatibility manifest', 'new appkit release cli-compat', or wants to update the CLI compatibility manifest after an AppKit or Agent Skills release."
user-invocable: true
allowed-tools: Read, Edit, Write, Bash, Glob, Grep, AskUserQuestion
---

# Bump CLI Compatibility Manifest

Updates `internal/build/cli-compat.json` with new AppKit and Agent Skills versions, validates the result, and creates a PR.

## Arguments

Parse the user's input for optional named flags:

- `--appkit <version>` → AppKit version (e.g. `0.28.0`)
- `--skills <version>` → Agent Skills version (e.g. `0.1.6`)
- No args → auto-detect latest versions from GitHub tags

Versions should be provided **without** the `v` prefix (e.g. `0.28.0`, not `v0.28.0`). If provided with the prefix, strip it.

## Workflow

### Step 1: Resolve versions

If both `--appkit` and `--skills` versions were provided, skip to Step 2.

For any missing version, fetch the latest tag from GitHub:

```bash
# Latest appkit version (strip leading 'v')
gh api repos/databricks/appkit/tags --jq '.[0].name' | sed 's/^v//'

# Latest skills version (strip leading 'v')
gh api repos/databricks/databricks-agent-skills/tags --jq '.[0].name' | sed 's/^v//'
```

Show the resolved versions to the user and ask:

> The latest versions are:
> - AppKit: `{appkit_version}`
> - Agent Skills: `{skills_version}`
>
> Have these versions been evaluated (evals passed with no regressions)?

**Do NOT proceed until the user confirms.** If the user says no or wants different versions, ask them to provide the correct versions.

### Step 2: Validate tags exist

Verify that the corresponding Git tags exist on GitHub. For AppKit, also validate the `template-v` tag (used by `apps init`):

```bash
# AppKit release tag
gh api repos/databricks/appkit/git/ref/tags/v{appkit_version} --jq '.ref'

# AppKit template tag (used by apps init)
gh api repos/databricks/appkit/git/ref/tags/template-v{appkit_version} --jq '.ref'

# Agent Skills tag
gh api repos/databricks/databricks-agent-skills/git/ref/tags/v{skills_version} --jq '.ref'
```

If any tag doesn't exist, report the error and stop.

### Step 3: Read current manifest

Read `internal/build/cli-compat.json`. Note the current versions and the list of versioned entries.

### Step 4: Update the manifest

Update **all entries** (both `next` and all versioned CLI entries) to the new appkit and skills versions. This is the "no template changes" scenario — a simple search & replace.

Write the updated `internal/build/cli-compat.json`.

### Step 5: Validate

Run the Go tests to ensure the manifest is well-formed:

```bash
go test ./libs/clicompat/... -run TestEmbeddedManifest -v
```

If validation fails, show the errors and fix them before proceeding.

### Step 6: Create branch, commit, and PR

```bash
# Create a new branch from the current branch (or main)
git checkout -b bump-cli-compat-appkit-{appkit_version}-skills-{skills_version}

# Stage and commit
git add internal/build/cli-compat.json
git commit -s -m "Bump cli-compat to appkit {appkit_version}, skills {skills_version}"

# Push and create PR
git push -u origin HEAD
gh pr create \
--title "Bump cli-compat to appkit {appkit_version}, skills {skills_version}" \
--body "$(cat <<'EOF'
## Summary
Bump `cli-compat.json` to use:
- AppKit `{appkit_version}`
- Agent Skills `{skills_version}`

## Checklist
- [ ] Evals passed with no regressions
- [ ] `go test ./libs/clicompat/... -run TestEmbeddedManifest` passes
EOF
)"
```

Show the PR URL to the user when done.

## Examples

### Example: With explicit versions
```
/bump-cli-compat --appkit 0.28.0 --skills 0.1.6
```
Validates tags exist (including `template-v0.28.0`), updates manifest, creates PR.

### Example: Auto-detect latest
```
/bump-cli-compat
```
Fetches latest tags, asks for eval confirmation, then updates and creates PR.

### Example: Only bump AppKit
```
/bump-cli-compat --appkit 0.28.0
```
Auto-detects latest skills version, asks for confirmation, then updates both.
4 changes: 4 additions & 0 deletions .github/OWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,9 @@
# Internal
/internal/ team:platform

# CLI compatibility manifest
/internal/build/cli-compat.json team:eng-apps-devex team:platform
/libs/clicompat/ team:eng-apps-devex team:platform

# Experimental
/experimental/aitools/ team:eng-apps-devex @lennartkats-db
27 changes: 23 additions & 4 deletions cmd/apps/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/databricks/cli/libs/apps/initializer"
"github.com/databricks/cli/libs/apps/manifest"
"github.com/databricks/cli/libs/apps/prompt"
"github.com/databricks/cli/libs/clicompat"
"github.com/databricks/cli/libs/cmdctx"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/env"
Expand All @@ -38,7 +39,6 @@ const (
appkitTemplateDir = "template"
appkitDefaultBranch = "main"
appkitTemplateTagPfx = "template-v"
appkitDefaultVersion = "template-v0.24.0"
defaultProfile = "DEFAULT"
)

Expand Down Expand Up @@ -169,7 +169,11 @@ Environment variables:

cmd.Flags().StringVar(&templatePath, "template", "", "Template path (local directory or GitHub URL)")
cmd.Flags().StringVar(&branch, "branch", "", "Git branch or tag (for GitHub templates, mutually exclusive with --version)")
cmd.Flags().StringVar(&version, "version", "", fmt.Sprintf("AppKit version to use (default: %s, use 'latest' for main branch)", appkitDefaultVersion))
versionDesc := "AppKit version to use (use 'latest' for main branch)"
if v, err := clicompat.ResolveEmbeddedAppKitVersion(); err == nil && v != "" {
versionDesc = fmt.Sprintf("AppKit version to use (default: %s, use 'latest' for main branch)", v)
}
cmd.Flags().StringVar(&version, "version", "", versionDesc)
cmd.Flags().StringVar(&name, "name", "", "Project name (prompts if not provided)")
cmd.Flags().StringVar(&warehouseID, "warehouse-id", "", "SQL warehouse ID")
_ = cmd.Flags().MarkDeprecated("warehouse-id", "use --set <plugin>.sql-warehouse.id=<value> instead")
Expand Down Expand Up @@ -805,8 +809,12 @@ func runCreate(ctx context.Context, opts createOptions) error {
case opts.version != "":
gitRef = normalizeVersion(opts.version)
default:
// Default: use pinned version
gitRef = appkitDefaultVersion
appkitVersion, err := clicompat.ResolveAppKitVersion(ctx)
if err != nil {
return fmt.Errorf("could not resolve AppKit template version: %w; use --version to specify a version manually", err)
}
gitRef = normalizeVersion(appkitVersion)
cmdio.LogString(ctx, "Using AppKit template version "+appkitVersion)
}
templateSrc = appkitRepoURL
}
Expand Down Expand Up @@ -859,6 +867,17 @@ func runCreate(ctx context.Context, opts createOptions) error {

// Step 2: Wait for template (may already be done if the user took time typing the name)
resolvedPath, cleanup, err := awaitTemplate(ctx, templateCh)
if err != nil && usingDefaultTemplate && clicompat.IsNotFoundError(err) {
// The resolved version doesn't exist as a tag. Fall back to the
// embedded manifest which ships a known-good version.
fallbackVersion, fbErr := clicompat.ResolveEmbeddedAppKitVersion()
if fbErr == nil && fallbackVersion != "" {
log.Warnf(ctx, "Template version not found, falling back to embedded version %s", fallbackVersion)
fallbackRef := normalizeVersion(fallbackVersion)
templateCh = resolveTemplateAsync(ctx, templateSrc, fallbackRef, appkitTemplateDir)
resolvedPath, cleanup, err = awaitTemplate(ctx, templateCh)
}
}
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/apps/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ func TestNormalizeVersion(t *testing.T) {
{"", ""},
{"main", "main"},
{"feat/something", "feat/something"},
{appkitDefaultVersion, appkitDefaultVersion},
{"template-v0.24.0", "template-v0.24.0"},
}

for _, tt := range tests {
Expand Down
7 changes: 6 additions & 1 deletion cmd/apps/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"path/filepath"

"github.com/databricks/cli/libs/apps/manifest"
"github.com/databricks/cli/libs/clicompat"
"github.com/databricks/cli/libs/env"
"github.com/spf13/cobra"
)
Expand All @@ -27,7 +28,11 @@ func runManifestOnly(ctx context.Context, templatePath, branch, version string)
case version != "":
gitRef = normalizeVersion(version)
default:
gitRef = appkitDefaultVersion
appkitVersion, err := clicompat.ResolveAppKitVersion(ctx)
if err != nil {
return fmt.Errorf("could not resolve AppKit template version: %w; use --version to specify a version manually", err)
}
gitRef = normalizeVersion(appkitVersion)
}
templateSrc = appkitRepoURL
}
Expand Down
5 changes: 4 additions & 1 deletion experimental/aitools/cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ func newListCmd() *cobra.Command {
func defaultListSkills(cmd *cobra.Command, scope string) error {
ctx := cmd.Context()

ref := installer.GetSkillsRef(ctx)
ref, err := installer.GetSkillsRef(ctx)
if err != nil {
return err
}

src := &installer.GitHubManifestSource{}
manifest, err := src.FetchManifest(ctx, ref)
Expand Down
12 changes: 11 additions & 1 deletion experimental/aitools/cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/databricks/cli/experimental/aitools/lib/installer"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/log"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -44,7 +45,10 @@ func newVersionCmd() *cobra.Command {
return nil
}

latestRef := installer.GetSkillsRef(ctx)
latestRef, err := installer.GetSkillsRef(ctx)
if err != nil {
log.Debugf(ctx, "could not resolve skills version: %v", err)
}
bothScopes := globalState != nil && projectState != nil

cmdio.LogString(ctx, "Databricks AI Tools:")
Expand Down Expand Up @@ -80,6 +84,12 @@ func printVersionLine(ctx context.Context, label string, state *installer.Instal
skillNoun = "skill"
}

if latestRef == "" {
cmdio.LogString(ctx, fmt.Sprintf(" %s: v%s (%d %s)", label, version, len(state.Skills), skillNoun))
cmdio.LogString(ctx, " Last updated: "+state.LastUpdated.Format("2006-01-02"))
return
}

if latestRef == state.Release {
cmdio.LogString(ctx, fmt.Sprintf(" %s: v%s (%d %s, up to date)", label, version, len(state.Skills), skillNoun))
cmdio.LogString(ctx, " Last updated: "+state.LastUpdated.Format("2006-01-02"))
Expand Down
1 change: 0 additions & 1 deletion experimental/aitools/lib/installer/SKILLS_VERSION

This file was deleted.

34 changes: 26 additions & 8 deletions experimental/aitools/lib/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/databricks/cli/experimental/aitools/lib/agents"
"github.com/databricks/cli/internal/build"
"github.com/databricks/cli/libs/clicompat"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/env"
"github.com/databricks/cli/libs/log"
Expand All @@ -32,13 +33,17 @@ const (
// It is a package-level var so tests can replace it with a mock.
var fetchFileFn = fetchSkillFile

// GetSkillsRef returns the skills repo ref to use. If DATABRICKS_SKILLS_REF
// is set, it returns that value; otherwise it returns the default ref.
func GetSkillsRef(ctx context.Context) string {
// GetSkillsRef returns the skills repo ref to use.
// Resolution order: DATABRICKS_SKILLS_REF env var → compatibility manifest → error.
func GetSkillsRef(ctx context.Context) (string, error) {
if ref := env.Get(ctx, "DATABRICKS_SKILLS_REF"); ref != "" {
return ref
return ref, nil
}
return defaultSkillsRepoRef
v, err := clicompat.ResolveAgentSkillsVersion(ctx)
if err != nil {
return "", fmt.Errorf("could not resolve skills version: %w", err)
}
return "v" + v, nil
}

// Manifest describes the skills manifest fetched from the skills repo.
Expand Down Expand Up @@ -92,8 +97,22 @@ func fetchSkillFile(ctx context.Context, ref, skillName, filePath string) ([]byt
// This is the core installation function. Callers are responsible for agent detection,
// prompting, and printing the "Installing..." header.
func InstallSkillsForAgents(ctx context.Context, src ManifestSource, targetAgents []*agents.Agent, opts InstallOptions) error {
ref := GetSkillsRef(ctx)
ref, err := GetSkillsRef(ctx)
if err != nil {
return err
}
tag := strings.TrimPrefix(ref, "v")
cmdio.LogString(ctx, "Using skills version "+tag)
manifest, err := src.FetchManifest(ctx, ref)
if err != nil && clicompat.IsNotFoundError(err) {
// The resolved version doesn't exist. Fall back to the embedded manifest.
fallbackVersion, fbErr := clicompat.ResolveEmbeddedAgentSkillsVersion()
if fbErr == nil && fallbackVersion != "" && fallbackVersion != tag {
log.Warnf(ctx, "Skills version %s not found, falling back to embedded version %s", tag, fallbackVersion)
ref = "v" + fallbackVersion
manifest, err = src.FetchManifest(ctx, ref)
}
}
if err != nil {
return err
}
Expand Down Expand Up @@ -193,12 +212,11 @@ func InstallSkillsForAgents(ctx context.Context, src ManifestSource, targetAgent
return err
}

tag := strings.TrimPrefix(ref, "v")
noun := "skills"
if len(targetSkills) == 1 {
noun = "skill"
}
cmdio.LogString(ctx, fmt.Sprintf("Installed %d %s (v%s).", len(targetSkills), noun, tag))
cmdio.LogString(ctx, fmt.Sprintf("Installed %d %s.", len(targetSkills), noun))
return nil
}

Expand Down
Loading
Loading