Skip to content

Commit c0407a7

Browse files
authored
Resolve AppKit and Agent Skills versions from compatibility manifest (#5139)
## Summary Introduces a CLI compatibility manifest (`internal/build/cli-compat.json`) that maps CLI versions to compatible AppKit template and Agent Skills versions. This enables template updates to reach users without CLI releases. ### Manifest format The manifest is purely range-based — each versioned entry defines a **range floor** that applies to that CLI version and all versions above it, up to the next entry. The manifest should be sparse: only add a new entry when a compatibility boundary changes (e.g., new AppKit templates require specific CLI features). ### Resolution - **Exact match** → use that entry - **Between entries** → nearest lower version - **Newer than all** → highest versioned entry - **Dev builds** (`0.0.0-dev*`) → highest versioned entry ### Manifest sources (fallback chain) 1. Fresh local cache (< 1h) 2. Remote fetch from GitHub (with retry) 3. Stale local cache or embedded manifest fallback Set `DATABRICKS_FORCE_EMBEDDED_COMPAT=true` to skip remote fetch and use only the embedded manifest (useful for local development). ### Companion PRs - [databricks/appkit#333](databricks/appkit#333) - [databricks/databricks-agent-skills#64](databricks/databricks-agent-skills#64) ## Screenshot <img width="1046" height="570" alt="image" src="https://github.com/user-attachments/assets/1b51cd90-bcaa-4b0e-bcbd-a5370da572ef" /> --------- Signed-off-by: Pawel Kosiec <pawel.kosiec@databricks.com>
1 parent f0e8c11 commit c0407a7

20 files changed

Lines changed: 1350 additions & 49 deletions

File tree

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
---
2+
name: bump-cli-compat
3+
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."
4+
user-invocable: true
5+
allowed-tools: Read, Edit, Write, Bash, Glob, Grep, AskUserQuestion
6+
---
7+
8+
# Bump CLI Compatibility Manifest
9+
10+
Updates `internal/build/cli-compat.json` with new AppKit and Agent Skills versions, validates the result, and creates a PR.
11+
12+
## Arguments
13+
14+
Parse the user's input for optional named flags:
15+
16+
- `--appkit <version>` → AppKit version (e.g. `0.28.0`)
17+
- `--skills <version>` → Agent Skills version (e.g. `0.1.6`)
18+
- No args → auto-detect latest versions from GitHub tags
19+
20+
Versions should be provided **without** the `v` prefix (e.g. `0.28.0`, not `v0.28.0`). If provided with the prefix, strip it.
21+
22+
## Workflow
23+
24+
### Step 1: Resolve versions
25+
26+
If both `--appkit` and `--skills` versions were provided, skip to Step 2.
27+
28+
For any missing version, fetch the latest tag from GitHub:
29+
30+
```bash
31+
# Latest appkit version (strip leading 'v')
32+
gh api repos/databricks/appkit/tags --jq '.[0].name' | sed 's/^v//'
33+
34+
# Latest skills version (strip leading 'v')
35+
gh api repos/databricks/databricks-agent-skills/tags --jq '.[0].name' | sed 's/^v//'
36+
```
37+
38+
Show the resolved versions to the user and ask:
39+
40+
> The latest versions are:
41+
> - AppKit: `{appkit_version}`
42+
> - Agent Skills: `{skills_version}`
43+
>
44+
> Have these versions been evaluated (evals passed with no regressions)?
45+
46+
**Do NOT proceed until the user confirms.** If the user says no or wants different versions, ask them to provide the correct versions.
47+
48+
### Step 2: Validate tags exist
49+
50+
Verify that the corresponding Git tags exist on GitHub. For AppKit, also validate the `template-v` tag (used by `apps init`):
51+
52+
```bash
53+
# AppKit release tag
54+
gh api repos/databricks/appkit/git/ref/tags/v{appkit_version} --jq '.ref'
55+
56+
# AppKit template tag (used by apps init)
57+
gh api repos/databricks/appkit/git/ref/tags/template-v{appkit_version} --jq '.ref'
58+
59+
# Agent Skills tag
60+
gh api repos/databricks/databricks-agent-skills/git/ref/tags/v{skills_version} --jq '.ref'
61+
```
62+
63+
If any tag doesn't exist, report the error and stop.
64+
65+
### Step 3: Read current manifest
66+
67+
Read `internal/build/cli-compat.json`. Note the current versions and the list of versioned entries.
68+
69+
### Step 4: Determine update type
70+
71+
Ask the user:
72+
73+
> Do any of these apply?
74+
> - **AppKit**: The new templates require new CLI logic in `apps init` (e.g. new flags, prompts, or template handling that older CLIs don't have)
75+
> - **Skills**: The new skills version uses CLI commands that older CLIs don't support
76+
>
77+
> If **neither** applies, this is a non-breaking bump (default).
78+
79+
- **No breaking changes** (default): proceed to Step 4a.
80+
- **Breaking changes**: proceed to Step 4b.
81+
82+
### Step 4a: No breaking changes (update in-place)
83+
84+
Update the **highest versioned entry** to the new appkit and skills versions. Do NOT add new versioned keys. The manifest is range-based: updating the highest entry automatically covers all CLI versions in that range.
85+
86+
Write the updated `internal/build/cli-compat.json`.
87+
88+
### Step 4b: Breaking changes (add new entry)
89+
90+
Ask the user for the **minimum CLI version** that supports the new features.
91+
92+
Add a new entry keyed to that CLI version with the new appkit and skills versions. Keep older entries unchanged so older CLI binaries stay compatible.
93+
94+
Write the updated `internal/build/cli-compat.json`.
95+
96+
### Step 5: Validate
97+
98+
Run the Go tests to ensure the manifest is well-formed:
99+
100+
```bash
101+
go test ./libs/clicompat/... -run TestEmbeddedManifest -v
102+
```
103+
104+
If validation fails, show the errors and fix them before proceeding.
105+
106+
### Step 6: Create branch, commit, and PR
107+
108+
```bash
109+
# Create a new branch from the current branch (or main)
110+
git checkout -b bump-cli-compat-appkit-{appkit_version}-skills-{skills_version}
111+
112+
# Stage and commit
113+
git add internal/build/cli-compat.json
114+
git commit -s -m "Bump cli-compat to appkit {appkit_version}, skills {skills_version}"
115+
116+
# Push and create PR
117+
git push -u origin HEAD
118+
gh pr create \
119+
--title "Bump cli-compat to appkit {appkit_version}, skills {skills_version}" \
120+
--body "$(cat <<'EOF'
121+
## Summary
122+
Bump `cli-compat.json` to use:
123+
- AppKit `{appkit_version}`
124+
- Agent Skills `{skills_version}`
125+
126+
## Checklist
127+
- [ ] Evals passed with no regressions
128+
- [ ] `go test ./libs/clicompat/... -run TestEmbeddedManifest` passes
129+
EOF
130+
)"
131+
```
132+
133+
Show the PR URL to the user when done.
134+
135+
## Examples
136+
137+
### Example: With explicit versions
138+
```
139+
/bump-cli-compat --appkit 0.28.0 --skills 0.1.6
140+
```
141+
Validates tags exist (including `template-v0.28.0`), updates manifest, creates PR.
142+
143+
### Example: Auto-detect latest
144+
```
145+
/bump-cli-compat
146+
```
147+
Fetches latest tags, asks for eval confirmation, then updates and creates PR.
148+
149+
### Example: Only bump AppKit
150+
```
151+
/bump-cli-compat --appkit 0.28.0
152+
```
153+
Auto-detects latest skills version, asks for confirmation, then updates both.

.github/OWNERS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,9 @@
5959
# Internal
6060
/internal/ team:platform
6161

62+
# CLI compatibility manifest
63+
/internal/build/cli-compat.json team:eng-apps-devex team:platform
64+
/libs/clicompat/ team:eng-apps-devex team:platform
65+
6266
# Experimental
6367
/experimental/aitools/ team:eng-apps-devex @lennartkats-db

cmd/apps/init.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/databricks/cli/libs/apps/initializer"
2424
"github.com/databricks/cli/libs/apps/manifest"
2525
"github.com/databricks/cli/libs/apps/prompt"
26+
"github.com/databricks/cli/libs/clicompat"
2627
"github.com/databricks/cli/libs/cmdctx"
2728
"github.com/databricks/cli/libs/cmdio"
2829
"github.com/databricks/cli/libs/env"
@@ -38,7 +39,6 @@ const (
3839
appkitTemplateDir = "template"
3940
appkitDefaultBranch = "main"
4041
appkitTemplateTagPfx = "template-v"
41-
appkitDefaultVersion = "template-v0.24.0"
4242
defaultProfile = "DEFAULT"
4343
)
4444

@@ -169,7 +169,7 @@ Environment variables:
169169

170170
cmd.Flags().StringVar(&templatePath, "template", "", "Template path (local directory or GitHub URL)")
171171
cmd.Flags().StringVar(&branch, "branch", "", "Git branch or tag (for GitHub templates, mutually exclusive with --version)")
172-
cmd.Flags().StringVar(&version, "version", "", fmt.Sprintf("AppKit version to use (default: %s, use 'latest' for main branch)", appkitDefaultVersion))
172+
cmd.Flags().StringVar(&version, "version", "", "AppKit version to use (default: auto-detected, use 'latest' for main branch)")
173173
cmd.Flags().StringVar(&name, "name", "", "Project name (prompts if not provided)")
174174
cmd.Flags().StringVar(&warehouseID, "warehouse-id", "", "SQL warehouse ID")
175175
_ = cmd.Flags().MarkDeprecated("warehouse-id", "use --set <plugin>.sql-warehouse.id=<value> instead")
@@ -805,8 +805,12 @@ func runCreate(ctx context.Context, opts createOptions) error {
805805
case opts.version != "":
806806
gitRef = normalizeVersion(opts.version)
807807
default:
808-
// Default: use pinned version
809-
gitRef = appkitDefaultVersion
808+
appkitVersion, err := clicompat.ResolveAppKitVersion(ctx)
809+
if err != nil {
810+
return fmt.Errorf("could not resolve AppKit template version: %w; use --version to specify a version manually", err)
811+
}
812+
gitRef = normalizeVersion(appkitVersion)
813+
cmdio.LogString(ctx, "Using AppKit template version "+appkitVersion)
810814
}
811815
templateSrc = appkitRepoURL
812816
}
@@ -859,6 +863,20 @@ func runCreate(ctx context.Context, opts createOptions) error {
859863

860864
// Step 2: Wait for template (may already be done if the user took time typing the name)
861865
resolvedPath, cleanup, err := awaitTemplate(ctx, templateCh)
866+
// Only fall back to the embedded version when the version was auto-resolved
867+
// from the manifest, not when the user explicitly passed --version or --branch.
868+
versionAutoResolved := opts.version == "" && opts.branch == ""
869+
if err != nil && usingDefaultTemplate && versionAutoResolved && clicompat.IsNotFoundError(err) {
870+
fallbackVersion, fbErr := clicompat.ResolveEmbeddedAppKitVersion()
871+
if fbErr == nil && fallbackVersion != "" && normalizeVersion(fallbackVersion) != branchForClone {
872+
log.Warnf(ctx, "Template version not found, falling back to embedded version %s", fallbackVersion)
873+
fallbackRef := normalizeVersion(fallbackVersion)
874+
templateCh = resolveTemplateAsync(ctx, templateSrc, fallbackRef, appkitTemplateDir)
875+
resolvedPath, cleanup, err = awaitTemplate(ctx, templateCh)
876+
} else if fbErr != nil {
877+
log.Warnf(ctx, "Could not resolve embedded AppKit version: %v", fbErr)
878+
}
879+
}
862880
if err != nil {
863881
return err
864882
}

cmd/apps/init_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ func TestNormalizeVersion(t *testing.T) {
443443
{"", ""},
444444
{"main", "main"},
445445
{"feat/something", "feat/something"},
446-
{appkitDefaultVersion, appkitDefaultVersion},
446+
{"template-v0.24.0", "template-v0.24.0"},
447447
}
448448

449449
for _, tt := range tests {

cmd/apps/manifest.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import (
99
"path/filepath"
1010

1111
"github.com/databricks/cli/libs/apps/manifest"
12+
"github.com/databricks/cli/libs/clicompat"
1213
"github.com/databricks/cli/libs/env"
14+
"github.com/databricks/cli/libs/log"
1315
"github.com/spf13/cobra"
1416
)
1517

@@ -27,7 +29,11 @@ func runManifestOnly(ctx context.Context, templatePath, branch, version string)
2729
case version != "":
2830
gitRef = normalizeVersion(version)
2931
default:
30-
gitRef = appkitDefaultVersion
32+
appkitVersion, err := clicompat.ResolveAppKitVersion(ctx)
33+
if err != nil {
34+
return fmt.Errorf("could not resolve AppKit template version: %w; use --version to specify a version manually", err)
35+
}
36+
gitRef = normalizeVersion(appkitVersion)
3137
}
3238
templateSrc = appkitRepoURL
3339
}
@@ -39,6 +45,17 @@ func runManifestOnly(ctx context.Context, templatePath, branch, version string)
3945
subdirForClone = appkitTemplateDir
4046
}
4147
resolvedPath, cleanup, err := resolveTemplate(ctx, templateSrc, branchForClone, subdirForClone)
48+
versionAutoResolved := version == "" && branch == ""
49+
if err != nil && usingDefaultTemplate && versionAutoResolved && clicompat.IsNotFoundError(err) {
50+
fallbackVersion, fbErr := clicompat.ResolveEmbeddedAppKitVersion()
51+
if fbErr == nil && fallbackVersion != "" && normalizeVersion(fallbackVersion) != gitRef {
52+
log.Warnf(ctx, "Template version not found, falling back to embedded version %s", fallbackVersion)
53+
fallbackRef := normalizeVersion(fallbackVersion)
54+
resolvedPath, cleanup, err = resolveTemplate(ctx, templateSrc, fallbackRef, appkitTemplateDir)
55+
} else if fbErr != nil {
56+
log.Warnf(ctx, "Could not resolve embedded AppKit version: %v", fbErr)
57+
}
58+
}
4259
if err != nil {
4360
return err
4461
}

experimental/aitools/cmd/list.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,13 @@ func newListCmd() *cobra.Command {
4848
func defaultListSkills(cmd *cobra.Command, scope string) error {
4949
ctx := cmd.Context()
5050

51-
ref := installer.GetSkillsRef(ctx)
51+
ref, explicit, err := installer.GetSkillsRef(ctx)
52+
if err != nil {
53+
return err
54+
}
5255

5356
src := &installer.GitHubManifestSource{}
54-
manifest, err := src.FetchManifest(ctx, ref)
57+
manifest, ref, err := installer.FetchSkillsManifestWithFallback(ctx, src, ref, !explicit)
5558
if err != nil {
5659
return fmt.Errorf("failed to fetch manifest: %w", err)
5760
}

experimental/aitools/cmd/version.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/databricks/cli/experimental/aitools/lib/installer"
99
"github.com/databricks/cli/libs/cmdio"
10+
"github.com/databricks/cli/libs/log"
1011
"github.com/spf13/cobra"
1112
)
1213

@@ -44,7 +45,10 @@ func newVersionCmd() *cobra.Command {
4445
return nil
4546
}
4647

47-
latestRef := installer.GetSkillsRef(ctx)
48+
latestRef, _, err := installer.GetSkillsRef(ctx)
49+
if err != nil {
50+
log.Debugf(ctx, "could not resolve skills version: %v", err)
51+
}
4852
bothScopes := globalState != nil && projectState != nil
4953

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

87+
if latestRef == "" {
88+
cmdio.LogString(ctx, fmt.Sprintf(" %s: v%s (%d %s)", label, version, len(state.Skills), skillNoun))
89+
cmdio.LogString(ctx, " Last updated: "+state.LastUpdated.Format("2006-01-02"))
90+
return
91+
}
92+
8393
if latestRef == state.Release {
8494
cmdio.LogString(ctx, fmt.Sprintf(" %s: v%s (%d %s, up to date)", label, version, len(state.Skills), skillNoun))
8595
cmdio.LogString(ctx, " Last updated: "+state.LastUpdated.Format("2006-01-02"))

experimental/aitools/lib/installer/SKILLS_VERSION

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)