Skip to content

Commit e5af62e

Browse files
anandgupta42claude
andcommitted
feat: plan agent refinement, feature discovery, and telemetry instrumentation
## Plan Agent Improvements - Two-step plan approach: outline first, confirm, then expand - Plan refinement loop: edit instead of restart (capped at 5 revisions) - Approval detection for common phrases ("looks good", "proceed", "lgtm") - Bug fix: `agent_outcome` telemetry for plan sessions emitting null for `duration_ms`, `tool_calls`, `generations` — `sessionAgentName` set too late ## Feature Discovery & Progressive Disclosure - Post-warehouse-connect contextual suggestions (schema, SQL, lineage, PII) - Progressive: `sql_execute` → `sql_analyze` → `schema_inspect` → `lineage_check` - dbt auto-detection → recommends `/dbt-develop`, `/dbt-troubleshoot` - All suggestions try/catch wrapped, never block core flow ## Telemetry - New `plan_revision` event: revision count + action (refine/approve/reject) - New `feature_suggestion` event: suggestions shown, type, warehouse - `skill_used.trigger`: user_command | llm_selected | auto_suggested - `classifySkillTrigger()` helper ## Tests - 140 new tests across 4 files, all passing Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent cfe1bad commit e5af62e

15 files changed

Lines changed: 972 additions & 10 deletions

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,9 @@ export namespace Telemetry {
349349
skill_name: string
350350
skill_source: "builtin" | "global" | "project"
351351
duration_ms: number
352+
// altimate_change start — skill trigger classification for discovery analytics
353+
trigger: "user_command" | "llm_selected" | "auto_suggested" | "unknown"
354+
// altimate_change end
352355
}
353356
// altimate_change start — first_launch event for new user counting (privacy-safe: only version + machine_id)
354357
| {
@@ -385,6 +388,15 @@ export namespace Telemetry {
385388
source: "cli" | "tui"
386389
}
387390
// altimate_change end
391+
// altimate_change start — plan refinement telemetry event
392+
| {
393+
type: "plan_revision"
394+
timestamp: number
395+
session_id: string
396+
revision_number: number
397+
action: "refine" | "approve" | "reject"
398+
}
399+
// altimate_change end
388400
| {
389401
type: "sql_execute_failure"
390402
timestamp: number
@@ -395,6 +407,16 @@ export namespace Telemetry {
395407
masked_sql: string
396408
duration_ms: number
397409
}
410+
// altimate_change start — feature_suggestion event for post-connect and progressive disclosure tracking
411+
| {
412+
type: "feature_suggestion"
413+
timestamp: number
414+
session_id: string
415+
suggestion_type: "post_warehouse_connect" | "dbt_detected" | "schema_not_indexed" | "progressive_disclosure"
416+
suggestions_shown: string[]
417+
warehouse_type?: string
418+
}
419+
// altimate_change end
398420
| {
399421
type: "core_failure"
400422
timestamp: number
@@ -561,6 +583,16 @@ export namespace Telemetry {
561583
return "standard"
562584
}
563585

586+
// altimate_change start — classify how a skill was triggered for discovery analytics
587+
export function classifySkillTrigger(extra?: { [key: string]: any }): "user_command" | "llm_selected" | "auto_suggested" | "unknown" {
588+
if (!extra) return "llm_selected"
589+
if (extra.trigger === "user_command") return "user_command"
590+
if (extra.trigger === "auto_suggested") return "auto_suggested"
591+
if (extra.trigger === "llm_selected") return "llm_selected"
592+
return "llm_selected"
593+
}
594+
// altimate_change end
595+
564596
export function bucketCount(n: number): string {
565597
if (n <= 0) return "0"
566598
if (n <= 10) return "1-10"
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* Post-connect feature suggestions and progressive disclosure.
3+
*
4+
* After warehouse connect, users often don't know what to do next.
5+
* This module provides contextual suggestions based on the user's
6+
* environment and progressive next-step hints after tool usage.
7+
*/
8+
9+
import { Telemetry } from "../../telemetry"
10+
11+
export namespace PostConnectSuggestions {
12+
export interface SuggestionContext {
13+
warehouseType: string
14+
schemaIndexed: boolean
15+
dbtDetected: boolean
16+
connectionCount: number
17+
toolsUsedInSession: string[]
18+
}
19+
20+
export function getPostConnectSuggestions(ctx: SuggestionContext): string {
21+
const suggestions: string[] = []
22+
23+
if (!ctx.schemaIndexed) {
24+
suggestions.push(
25+
"Index your schema — enables SQL analysis, column-level lineage, and data quality checks. Use the schema_index tool.",
26+
)
27+
}
28+
29+
suggestions.push(
30+
"Run SQL queries against your " +
31+
ctx.warehouseType +
32+
" warehouse using sql_execute",
33+
)
34+
suggestions.push(
35+
"Analyze SQL quality and find potential issues with sql_analyze",
36+
)
37+
38+
if (ctx.dbtDetected) {
39+
suggestions.push(
40+
"dbt project detected — try /dbt-develop to help build models or /dbt-troubleshoot to debug issues",
41+
)
42+
}
43+
44+
suggestions.push(
45+
"Trace data lineage across your models with lineage_check",
46+
)
47+
suggestions.push("Audit for PII exposure with schema_detect_pii")
48+
49+
if (ctx.connectionCount > 1) {
50+
suggestions.push("Compare data across warehouses with data_diff")
51+
}
52+
53+
return (
54+
"\n\n---\nAvailable capabilities for your " +
55+
ctx.warehouseType +
56+
" warehouse:\n" +
57+
suggestions.map((s, i) => `${i + 1}. ${s}`).join("\n")
58+
)
59+
}
60+
61+
/**
62+
* Progressive disclosure: suggest next tool based on what was just used.
63+
* Returns null if no suggestion applies or tool is unknown.
64+
*/
65+
export function getProgressiveSuggestion(
66+
lastToolUsed: string,
67+
): string | null {
68+
const progression: Record<string, string | null> = {
69+
sql_execute:
70+
"Tip: Use sql_analyze to check this query for potential issues, performance optimizations, and best practices.",
71+
sql_analyze:
72+
"Tip: Use schema_inspect to explore the tables and columns referenced in your query.",
73+
schema_inspect:
74+
"Tip: Use lineage_check to see how this data flows through your models.",
75+
schema_index:
76+
"Schema indexed! You can now use sql_analyze for quality checks, schema_inspect for exploration, and lineage_check for data flow analysis.",
77+
warehouse_add: null, // Handled by post-connect suggestions
78+
}
79+
return progression[lastToolUsed] ?? null
80+
}
81+
82+
/**
83+
* Track that feature suggestions were shown, for measuring discovery rates.
84+
*/
85+
export function trackSuggestions(opts: {
86+
suggestionType:
87+
| "post_warehouse_connect"
88+
| "dbt_detected"
89+
| "progressive_disclosure"
90+
suggestionsShown: string[]
91+
warehouseType?: string
92+
}): void {
93+
try {
94+
Telemetry.track({
95+
type: "feature_suggestion",
96+
timestamp: Date.now(),
97+
session_id: Telemetry.getContext().sessionId,
98+
suggestion_type: opts.suggestionType,
99+
suggestions_shown: opts.suggestionsShown,
100+
warehouse_type: opts.warehouseType ?? "unknown",
101+
})
102+
} catch {
103+
// Telemetry must never break tool execution
104+
}
105+
}
106+
}

‎packages/opencode/src/altimate/tools/project-scan.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,23 @@ export const ProjectScanTool = Tool.define("project_scan", {
506506
if (dbtProject.hasPackages) {
507507
lines.push(` ✓ packages.yml or dependencies.yml found`)
508508
}
509+
// altimate_change start — dbt auto-detection skill suggestions
510+
lines.push("")
511+
lines.push(` Recommended skills:`)
512+
lines.push(` - /dbt-develop — Build and modify dbt models with AI assistance`)
513+
lines.push(` - /dbt-troubleshoot — Debug failing dbt models and tests`)
514+
lines.push(` - /dbt-analyze — Analyze dbt project structure and dependencies`)
515+
516+
try {
517+
const { PostConnectSuggestions } = await import("./post-connect-suggestions")
518+
PostConnectSuggestions.trackSuggestions({
519+
suggestionType: "dbt_detected",
520+
suggestionsShown: ["dbt_develop", "dbt_troubleshoot", "dbt_analyze"],
521+
})
522+
} catch {
523+
// Telemetry must never break scan output
524+
}
525+
// altimate_change end
509526
} else {
510527
lines.push("✗ No dbt_project.yml found")
511528
}

‎packages/opencode/src/altimate/tools/schema-index.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import z from "zod"
22
import { Tool } from "../../tool/tool"
33
import { Dispatcher } from "../native"
44
import type { SchemaIndexResult } from "../native/types"
5+
// altimate_change start — progressive disclosure suggestions
6+
import { PostConnectSuggestions } from "./post-connect-suggestions"
7+
// altimate_change end
58

69
export const SchemaIndexTool = Tool.define("schema_index", {
710
description:
@@ -15,14 +18,26 @@ export const SchemaIndexTool = Tool.define("schema_index", {
1518
warehouse: args.warehouse,
1619
})
1720

21+
// altimate_change start — progressive disclosure suggestions
22+
let output = formatIndexResult(result)
23+
const suggestion = PostConnectSuggestions.getProgressiveSuggestion("schema_index")
24+
if (suggestion) {
25+
output += "\n\n" + suggestion
26+
PostConnectSuggestions.trackSuggestions({
27+
suggestionType: "progressive_disclosure",
28+
suggestionsShown: ["sql_analyze", "schema_inspect", "lineage_check"],
29+
warehouseType: result.type,
30+
})
31+
}
32+
// altimate_change end
1833
return {
1934
title: `Schema Indexed: ${result.warehouse}`,
2035
metadata: {
2136
schemas: result.schemas_indexed,
2237
tables: result.tables_indexed,
2338
columns: result.columns_indexed,
2439
},
25-
output: formatIndexResult(result),
40+
output,
2641
}
2742
} catch (e) {
2843
const msg = e instanceof Error ? e.message : String(e)

‎packages/opencode/src/altimate/tools/schema-inspect.ts‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import z from "zod"
22
import { Tool } from "../../tool/tool"
33
import { Dispatcher } from "../native"
44
import type { SchemaInspectResult } from "../native/types"
5+
// altimate_change start — progressive disclosure suggestions
6+
import { PostConnectSuggestions } from "./post-connect-suggestions"
7+
// altimate_change end
58

69
export const SchemaInspectTool = Tool.define("schema_inspect", {
710
description: "Inspect database schema — list columns, types, and constraints for a table.",
@@ -18,10 +21,21 @@ export const SchemaInspectTool = Tool.define("schema_inspect", {
1821
warehouse: args.warehouse,
1922
})
2023

24+
// altimate_change start — progressive disclosure suggestions
25+
let output = formatSchema(result)
26+
const suggestion = PostConnectSuggestions.getProgressiveSuggestion("schema_inspect")
27+
if (suggestion) {
28+
output += "\n\n" + suggestion
29+
PostConnectSuggestions.trackSuggestions({
30+
suggestionType: "progressive_disclosure",
31+
suggestionsShown: ["lineage_check"],
32+
})
33+
}
34+
// altimate_change end
2135
return {
2236
title: `Schema: ${result.table}`,
2337
metadata: { columnCount: result.columns.length, rowCount: result.row_count },
24-
output: formatSchema(result),
38+
output,
2539
}
2640
} catch (e) {
2741
const msg = e instanceof Error ? e.message : String(e)

‎packages/opencode/src/altimate/tools/sql-analyze.ts‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import z from "zod"
22
import { Tool } from "../../tool/tool"
33
import { Dispatcher } from "../native"
44
import type { SqlAnalyzeResult } from "../native/types"
5+
// altimate_change start — progressive disclosure suggestions
6+
import { PostConnectSuggestions } from "./post-connect-suggestions"
7+
// altimate_change end
58

69
export const SqlAnalyzeTool = Tool.define("sql_analyze", {
710
description:
@@ -21,6 +24,17 @@ export const SqlAnalyzeTool = Tool.define("sql_analyze", {
2124
dialect: args.dialect,
2225
})
2326

27+
// altimate_change start — progressive disclosure suggestions
28+
let output = formatAnalysis(result)
29+
const suggestion = PostConnectSuggestions.getProgressiveSuggestion("sql_analyze")
30+
if (suggestion) {
31+
output += "\n\n" + suggestion
32+
PostConnectSuggestions.trackSuggestions({
33+
suggestionType: "progressive_disclosure",
34+
suggestionsShown: ["schema_inspect"],
35+
})
36+
}
37+
// altimate_change end
2438
return {
2539
title: `Analyze: ${result.error ? "PARSE ERROR" : `${result.issue_count} issue${result.issue_count !== 1 ? "s" : ""}`} [${result.confidence}]`,
2640
metadata: {
@@ -29,7 +43,7 @@ export const SqlAnalyzeTool = Tool.define("sql_analyze", {
2943
confidence: result.confidence,
3044
...(result.error && { error: result.error }),
3145
},
32-
output: formatAnalysis(result),
46+
output,
3347
}
3448
} catch (e) {
3549
const msg = e instanceof Error ? e.message : String(e)

‎packages/opencode/src/altimate/tools/sql-execute.ts‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import type { SqlExecuteResult } from "../native/types"
55
// altimate_change start - SQL write access control
66
import { classifyAndCheck } from "./sql-classify"
77
// altimate_change end
8+
// altimate_change start — progressive disclosure suggestions
9+
import { PostConnectSuggestions } from "./post-connect-suggestions"
10+
// altimate_change end
811

912
export const SqlExecuteTool = Tool.define("sql_execute", {
1013
description: "Execute SQL against a connected data warehouse. Returns results as a formatted table.",
@@ -37,7 +40,18 @@ export const SqlExecuteTool = Tool.define("sql_execute", {
3740
limit: args.limit,
3841
})
3942

40-
const output = formatResult(result)
43+
let output = formatResult(result)
44+
// altimate_change start — progressive disclosure suggestions
45+
const suggestion = PostConnectSuggestions.getProgressiveSuggestion("sql_execute")
46+
if (suggestion) {
47+
output += "\n\n" + suggestion
48+
PostConnectSuggestions.trackSuggestions({
49+
suggestionType: "progressive_disclosure",
50+
suggestionsShown: ["sql_analyze"],
51+
warehouseType: args.warehouse ?? "default",
52+
})
53+
}
54+
// altimate_change end
4155
return {
4256
title: `SQL: ${args.query.slice(0, 60)}${args.query.length > 60 ? "..." : ""}`,
4357
metadata: { rowCount: result.row_count, truncated: result.truncated },

‎packages/opencode/src/altimate/tools/warehouse-add.ts‎

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import z from "zod"
22
import { Tool } from "../../tool/tool"
33
import { Dispatcher } from "../native"
4+
// altimate_change start — post-connect feature suggestions
5+
import { PostConnectSuggestions } from "./post-connect-suggestions"
6+
// altimate_change end
47

58
export const WarehouseAddTool = Tool.define("warehouse_add", {
69
description:
@@ -41,10 +44,49 @@ IMPORTANT: For private key file paths, always use "private_key_path" (not "priva
4144
})
4245

4346
if (result.success) {
47+
// altimate_change start — append post-connect feature suggestions
48+
let output = `Successfully added warehouse '${result.name}' (type: ${result.type}).\n\nUse warehouse_test to verify connectivity.`
49+
try {
50+
const schemaCache = await Dispatcher.call("schema.cache_status", {}).catch(() => null)
51+
const schemaIndexed = (schemaCache?.total_tables ?? 0) > 0
52+
const warehouseList = await Dispatcher.call("warehouse.list", {}).catch(() => ({ warehouses: [] }))
53+
54+
let dbtDetected = false
55+
try {
56+
const { detectDbtProject } = await import("./project-scan")
57+
const dbtInfo = await detectDbtProject(process.cwd())
58+
dbtDetected = dbtInfo.found
59+
} catch {
60+
// project-scan unavailable — skip dbt detection
61+
}
62+
63+
const ctx: PostConnectSuggestions.SuggestionContext = {
64+
warehouseType: result.type,
65+
schemaIndexed,
66+
dbtDetected,
67+
connectionCount: warehouseList.warehouses.length,
68+
toolsUsedInSession: [],
69+
}
70+
output += PostConnectSuggestions.getPostConnectSuggestions(ctx)
71+
72+
const suggestionsShown = ["sql_execute", "sql_analyze", "lineage_check", "schema_detect_pii"]
73+
if (!schemaIndexed) suggestionsShown.unshift("schema_index")
74+
if (dbtDetected) suggestionsShown.push("dbt_develop", "dbt_troubleshoot")
75+
if (warehouseList.warehouses.length > 1) suggestionsShown.push("data_diff")
76+
PostConnectSuggestions.trackSuggestions({
77+
suggestionType: "post_warehouse_connect",
78+
suggestionsShown,
79+
warehouseType: result.type,
80+
})
81+
} catch {
82+
// Suggestions must never break the add flow
83+
}
84+
// altimate_change end
85+
4486
return {
4587
title: `Add '${args.name}': OK`,
4688
metadata: { success: true, name: result.name, type: result.type },
47-
output: `Successfully added warehouse '${result.name}' (type: ${result.type}).\n\nUse warehouse_test to verify connectivity.`,
89+
output,
4890
}
4991
}
5092

0 commit comments

Comments
 (0)