Skip to content

Commit 384335d

Browse files
anandgupta42mdesmetclaude
authored
feat: skill follow-up suggestions to improve dbt experience (#546)
* feat(dbt-tools): build entire project when no --model flag given `altimate-dbt build` without arguments now builds the whole project via `unsafeBuildProjectImmediately`, replacing the need for the separate `build-project` command. Updated all dbt skill references accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(dbt-tools): add unit tests for build command routing Covers: no-model → project build, --model → single model, --downstream flag. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address code review — remove `build-project` command, update help text, add stderr test - Remove `build-project` from command map and switch case in `index.ts` (now redundant since `build` without `--model` does the same thing) - Update `build` help text to reflect optional `--model` - Remove unused `project` import from `build.test.ts` - Add test for `format()` stderr error path - Fix comment alignment in all 5 `altimate-dbt-commands.md` reference files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add missing `fullOutput` field to satisfy `CommandProcessResult` type Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add follow-up suggestions after skill completion to reduce first-run churn Telemetry analysis showed 78% of users churn after a single 2.4-minute session, mostly dbt developers who run one skill and leave. Users who reach warehouse/schema tools retain at 24.8% vs 3.3% for those who don't. This adds contextual "What's Next?" suggestions after skill execution: - Maps each skill to relevant follow-up skills (e.g., `dbt-develop` → `dbt-test`, `dbt-docs`) - Includes a warehouse discovery nudge to bridge non-warehouse users - Appended after `</skill_content>` so it doesn't interfere with skill parsing - Covers all dbt skills, SQL skills, and data quality skills (12 skills total) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address code review — truncation-safe followups, telemetry, immutable `get()` - Move follow-up suggestions before `<skill_content>` so they survive output truncation - Add `has_followups` and `followup_count` to `skill_used` telemetry event - Return `readonly Suggestion[]` via `Object.freeze()` from `get()` to prevent shared state mutation - Rewrite integration tests to verify followups appear before `<skill_content>` in output assembly - Wrap custom return block in `altimate_change` markers for upstream safety Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add e2e tests for skill follow-up suggestions via `SkillTool.execute` - Test that `dbt-develop` skill output includes follow-ups before `<skill_content>` - Test that unmapped skills produce no follow-up section - Both tests invoke the real `SkillTool.execute` path end-to-end Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Michiel De Smet <mdesmet@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 33c331b commit 384335d

File tree

15 files changed

+606
-17
lines changed

15 files changed

+606
-17
lines changed

.opencode/skills/dbt-analyze/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ If no manifest is available:
111111
1. Run `lineage_check` on the changed SQL
112112
2. Show column-level data flow
113113
3. Note: downstream impact requires a manifest
114-
4. Suggest: `altimate-dbt build-project` to generate one
114+
4. Suggest: `altimate-dbt build` to generate one
115115

116116
## Common Mistakes
117117

.opencode/skills/dbt-analyze/references/altimate-dbt-commands.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ altimate-dbt info # Project name, adapter, root
2020
## Build & Run
2121

2222
```bash
23-
altimate-dbt build --model <name> [--downstream] # compile + run + test
23+
altimate-dbt build # full project build (compile + run + test)
24+
altimate-dbt build --model <name> [--downstream] # build a single model
2425
altimate-dbt run --model <name> [--downstream] # materialize only
2526
altimate-dbt test --model <name> # run tests only
26-
altimate-dbt build-project # full project build
2727
```
2828

2929
## Compile

.opencode/skills/dbt-develop/references/altimate-dbt-commands.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ altimate-dbt info # Project name, adapter, root
2020
## Build & Run
2121

2222
```bash
23-
altimate-dbt build --model <name> [--downstream] # compile + run + test
23+
altimate-dbt build # full project build (compile + run + test)
24+
altimate-dbt build --model <name> [--downstream] # build a single model
2425
altimate-dbt run --model <name> [--downstream] # materialize only
2526
altimate-dbt test --model <name> # run tests only
26-
altimate-dbt build-project # full project build
2727
```
2828

2929
## Compile

.opencode/skills/dbt-docs/references/altimate-dbt-commands.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ altimate-dbt info # Project name, adapter, root
2020
## Build & Run
2121

2222
```bash
23-
altimate-dbt build --model <name> [--downstream] # compile + run + test
23+
altimate-dbt build # full project build (compile + run + test)
24+
altimate-dbt build --model <name> [--downstream] # build a single model
2425
altimate-dbt run --model <name> [--downstream] # materialize only
2526
altimate-dbt test --model <name> # run tests only
26-
altimate-dbt build-project # full project build
2727
```
2828

2929
## Compile

.opencode/skills/dbt-test/references/altimate-dbt-commands.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ altimate-dbt info # Project name, adapter, root
2020
## Build & Run
2121

2222
```bash
23-
altimate-dbt build --model <name> [--downstream] # compile + run + test
23+
altimate-dbt build # full project build (compile + run + test)
24+
altimate-dbt build --model <name> [--downstream] # build a single model
2425
altimate-dbt run --model <name> [--downstream] # materialize only
2526
altimate-dbt test --model <name> # run tests only
26-
altimate-dbt build-project # full project build
2727
```
2828

2929
## Compile

.opencode/skills/dbt-troubleshoot/references/altimate-dbt-commands.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ altimate-dbt info # Project name, adapter, root
2020
## Build & Run
2121

2222
```bash
23-
altimate-dbt build --model <name> [--downstream] # compile + run + test
23+
altimate-dbt build # full project build (compile + run + test)
24+
altimate-dbt build --model <name> [--downstream] # build a single model
2425
altimate-dbt run --model <name> [--downstream] # materialize only
2526
altimate-dbt test --model <name> # run tests only
26-
altimate-dbt build-project # full project build
2727
```
2828

2929
## Compile

packages/dbt-tools/src/commands/build.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { DBTProjectIntegrationAdapter, CommandProcessResult } from "@altima
22

33
export async function build(adapter: DBTProjectIntegrationAdapter, args: string[]) {
44
const model = flag(args, "model")
5-
if (!model) return { error: "Missing --model" }
5+
if (!model) return project(adapter)
66
const downstream = args.includes("--downstream")
77
const result = await adapter.unsafeBuildModelImmediately({
88
plusOperatorLeft: "",

packages/dbt-tools/src/index.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ const USAGE = {
1111
info: "Get project info (paths, targets, version)",
1212
compile: "Compile a model (Jinja to SQL) --model <name>",
1313
"compile-query": "Compile a raw query --query <sql> [--model <name>]",
14-
build: "Build a model --model <name> [--downstream]",
14+
build: "Build project, or a single model with --model <name> [--downstream]",
1515
run: "Run a model --model <name> [--downstream]",
1616
test: "Test a model --model <name>",
17-
"build-project": "Build entire project",
1817
execute: "Execute SQL --query <sql> [--model <name>] [--limit <n>]",
1918
columns: "Get columns of model --model <name>",
2019
"columns-source": "Get columns of source --source <name> --table <name>",
@@ -171,9 +170,6 @@ async function main() {
171170
case "test":
172171
result = await (await import("./commands/build")).test(adapter, rest)
173172
break
174-
case "build-project":
175-
result = await (await import("./commands/build")).project(adapter)
176-
break
177173
case "execute":
178174
result = await (await import("./commands/execute")).execute(adapter, rest)
179175
break
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { describe, test, expect, mock } from "bun:test"
2+
import { build } from "../src/commands/build"
3+
import type { DBTProjectIntegrationAdapter } from "@altimateai/dbt-integration"
4+
5+
function makeAdapter(overrides: Partial<DBTProjectIntegrationAdapter> = {}): DBTProjectIntegrationAdapter {
6+
return {
7+
unsafeBuildModelImmediately: mock(() => Promise.resolve({ stdout: "model built", stderr: "" })),
8+
unsafeBuildProjectImmediately: mock(() => Promise.resolve({ stdout: "project built", stderr: "" })),
9+
unsafeRunModelImmediately: mock(() => Promise.resolve({ stdout: "", stderr: "" })),
10+
unsafeRunModelTestImmediately: mock(() => Promise.resolve({ stdout: "", stderr: "" })),
11+
dispose: mock(() => Promise.resolve()),
12+
...overrides,
13+
} as unknown as DBTProjectIntegrationAdapter
14+
}
15+
16+
describe("build command", () => {
17+
test("build without --model builds entire project", async () => {
18+
const adapter = makeAdapter()
19+
const result = await build(adapter, [])
20+
expect(adapter.unsafeBuildProjectImmediately).toHaveBeenCalledTimes(1)
21+
expect(adapter.unsafeBuildModelImmediately).not.toHaveBeenCalled()
22+
expect(result).toEqual({ stdout: "project built" })
23+
})
24+
25+
test("build --model <name> builds single model", async () => {
26+
const adapter = makeAdapter()
27+
const result = await build(adapter, ["--model", "orders"])
28+
expect(adapter.unsafeBuildModelImmediately).toHaveBeenCalledTimes(1)
29+
expect(adapter.unsafeBuildModelImmediately).toHaveBeenCalledWith({
30+
plusOperatorLeft: "",
31+
modelName: "orders",
32+
plusOperatorRight: "",
33+
})
34+
expect(adapter.unsafeBuildProjectImmediately).not.toHaveBeenCalled()
35+
expect(result).toEqual({ stdout: "model built" })
36+
})
37+
38+
test("build --model <name> --downstream sets plusOperatorRight", async () => {
39+
const adapter = makeAdapter()
40+
await build(adapter, ["--model", "orders", "--downstream"])
41+
expect(adapter.unsafeBuildModelImmediately).toHaveBeenCalledWith({
42+
plusOperatorLeft: "",
43+
modelName: "orders",
44+
plusOperatorRight: "+",
45+
})
46+
})
47+
48+
test("build surfaces stderr as error", async () => {
49+
const adapter = makeAdapter({
50+
unsafeBuildProjectImmediately: mock(() =>
51+
Promise.resolve({ stdout: "partial output", stderr: "compilation error", fullOutput: "" }),
52+
),
53+
})
54+
const result = await build(adapter, [])
55+
expect(result).toEqual({ error: "compilation error", stdout: "partial output" })
56+
})
57+
})

packages/opencode/src/altimate/telemetry/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,8 @@ export namespace Telemetry {
349349
skill_name: string
350350
skill_source: "builtin" | "global" | "project"
351351
duration_ms: number
352+
has_followups: boolean
353+
followup_count: number
352354
}
353355
// altimate_change start — first_launch event for new user counting (privacy-safe: only version + machine_id)
354356
| {

0 commit comments

Comments
 (0)