diff --git a/go-libs/llnotes/announce.go b/go-libs/llnotes/announce.go index fe3eced1..de1095a6 100644 --- a/go-libs/llnotes/announce.go +++ b/go-libs/llnotes/announce.go @@ -9,16 +9,28 @@ import ( "github.com/databricks/databricks-sdk-go/logger" ) -var blogPrompt = MessageTemplate(`Do not hallucinate. -You are professional technical writer and you receive draft release notes for {{.version}} of project called {{.repo.Name}} in a markdown format from multiple team members. -Project can be described as "{{.repo.Description}}" +var blogPrompt = MessageTemplate(`Role: Technical content writer for {{.repo.Name}} release announcements. -You write a long post announcement that takes at least 5 minutes to read, summarize the most important features, and mention them on top. Keep the markdown links when relevant. -Do not use headings. Write fluent paragraphs, that are at least few sentences long. Blog post title should nicely summarize the feature increments of this release. +Project: {{.repo.Description}} +Version: {{.version}} -Don't abuse lists. paragraphs should have at least 3-4 sentences. The title should be one-sentence summary of the incremental updates for this release +Task: Create a concise blog post announcing this release. -You aim at driving more adoption of the project on Medium.`) +Structure: +1. Title: One-sentence summary of key improvements (10-15 words) +2. Opening: Lead with the most impactful feature (2-3 sentences) +3. Key Changes: 3-5 paragraphs, each covering one major feature area +4. Summary: Brief closing with call-to-action (1-2 sentences) + +Requirements: +- Target length: 400-600 words (3-4 minute read) +- Each paragraph: 3-5 sentences focused on user benefits +- Use bullet points sparingly (max 1 list if essential) +- Maintain technical accuracy while being accessible +- Include markdown links from original notes +- Emphasize adoption value and practical use cases + +Tone: Professional, enthusiastic, developer-focused`) func (lln *llNotes) versionNotes(ctx context.Context, newVersion string) ([]string, error) { versions, err := listing.ToSlice(ctx, lln.gh.Versions(ctx, lln.org, lln.repo)) diff --git a/go-libs/llnotes/diff.go b/go-libs/llnotes/diff.go index ea4dbaec..0856d241 100644 --- a/go-libs/llnotes/diff.go +++ b/go-libs/llnotes/diff.go @@ -12,14 +12,21 @@ import ( "github.com/sourcegraph/go-diff/diff" ) -const reduceDiffPrompt = `Do not hallucinate. -You are a professional Technical Writer writing feature change description for the open-source library. -Do not use file names, because they are not relevant for the feature description. -Do not use phrases like "In this release". -Your target audience is software engineers. -You receive a change description from your software engineering team about the newly developed features. -Write a one-paragraph summary of this change for the release notes. -It has to be one paragraph of text, because it will be included in a bigger document.` +const reduceDiffPrompt = `Role: Professional technical writer creating release notes. + +Task: Consolidate these change descriptions into a single, cohesive changelog entry. + +Requirements: +- Maximum 50 words +- One paragraph only +- Highlight the primary user benefit or feature +- Use active voice and past tense +- Group related changes into themes +- Omit technical jargon and implementation details + +Output format: : . . + +Example: "Enhanced authentication system with OAuth2 support and session management, improving security and user experience. Added rate limiting to prevent abuse."` func (lln *llNotes) explainDiff(ctx context.Context, history History, buf *bytes.Buffer) (History, error) { prDiff, err := diff.ParseMultiFileDiff(buf.Bytes()) diff --git a/go-libs/llnotes/pull_request.go b/go-libs/llnotes/pull_request.go index 3605cf15..6cab6c7b 100644 --- a/go-libs/llnotes/pull_request.go +++ b/go-libs/llnotes/pull_request.go @@ -11,19 +11,22 @@ import ( "github.com/databrickslabs/sandbox/go-libs/github" ) -var fileDiffTemplate = MessageTemplate(`Here is the commit message terminated by --- for the context: -{{.Message}} ---- +var fileDiffTemplate = MessageTemplate(`Context: {{.Message}} -Do not hallucinate. -You are Staff Software Engineer, and you are reviewing one file at a time in a unitified diff format. -Do not use phrases like "In this diff", "In this pull request", or "In this file". -Do not mention file names, because they are not relevant for the feature description. -If new methods are added, explain what these methods are doing. -If existing funcitonality is changed, explain the scope of these changes. -Please summarize the input as a signle paragraph of text written in American English. -Your target audience is software engineers, who adopt your project. -If the prompt contains ordered or unordered lists, rewrite the entire response as a paragraph of text.`) +Role: You are a senior software engineer writing changelog entries. + +Task: Analyze this unified diff and write a concise changelog entry. + +Requirements: +- Write 1-2 sentences maximum (20-40 words) +- Focus on user-facing impact and behavior changes +- Omit file names, implementation details, and meta-commentary +- Use past tense (e.g., "Added", "Fixed", "Updated") +- Target audience: developers using this library + +Format: : + +Example: "Added support for custom validators in form fields, enabling client-side validation before submission."`) func (lln *llNotes) CommitBySHA(ctx context.Context, sha string) (History, error) { commit, err := lln.gh.GetCommit(ctx, lln.org, lln.repo, sha) diff --git a/go-libs/llnotes/talk.go b/go-libs/llnotes/talk.go index cf1ae625..f6de7fd9 100644 --- a/go-libs/llnotes/talk.go +++ b/go-libs/llnotes/talk.go @@ -13,6 +13,15 @@ import ( "github.com/databrickslabs/sandbox/go-libs/sed" ) +type contextKey string + +const maxTokensKey contextKey = "maxTokens" + +// WithMaxTokens returns a new context with the specified maxTokens value +func WithMaxTokens(ctx context.Context, maxTokens int) context.Context { + return context.WithValue(ctx, maxTokensKey, maxTokens) +} + type Settings struct { GitHub github.GitHubConfig Databricks config.Config @@ -34,7 +43,7 @@ func New(cfg *Settings) (*llNotes, error) { cfg.Model = "databricks-meta-llama-3-3-70b-instruct" } if cfg.MaxTokens == 0 { - cfg.MaxTokens = 4000 + cfg.MaxTokens = 2048 // Reduced from 4000 for crisper output } if cfg.Workers == 0 { cfg.Workers = 15 @@ -72,11 +81,17 @@ type llNotes struct { } func (lln *llNotes) Talk(ctx context.Context, h History) (History, error) { - logger.Debugf(ctx, "Talking with AI:\n%s", h.Excerpt(80)) + // Read maxTokens from context, fallback to config default + maxTokens := lln.cfg.MaxTokens + if ctxMaxTokens, ok := ctx.Value(maxTokensKey).(int); ok && ctxMaxTokens > 0 { + maxTokens = ctxMaxTokens + } + logger.Debugf(ctx, "Talking with AI (max tokens: %d):\n%s", maxTokens, h.Excerpt(80)) response, err := lln.w.ServingEndpoints.Query(ctx, serving.QueryEndpointInput{ - Name: lln.model, - Messages: h.Messages(), - MaxTokens: lln.cfg.MaxTokens, + Name: lln.model, + Messages: h.Messages(), + MaxTokens: maxTokens, + Temperature: 0.1, }) if err != nil { return nil, fmt.Errorf("llm: %w", err) diff --git a/go.work b/go.work index 20e0f0d1..b83c9830 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.24 +go 1.24.0 toolchain go1.24.2 diff --git a/go.work.sum b/go.work.sum index 913b6f59..bfcf0f10 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1172,6 +1172,7 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1227,6 +1228,8 @@ golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1338,6 +1341,7 @@ golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXct golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= +golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1428,6 +1432,7 @@ golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/llnotes/CLAUDE.md b/llnotes/CLAUDE.md new file mode 100644 index 00000000..afa2cb1c --- /dev/null +++ b/llnotes/CLAUDE.md @@ -0,0 +1,122 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**llnotes** is a CLI tool that generates GitHub release notes using LLMs hosted on Databricks Model Serving. It's part of the databrickslabs/sandbox monorepo and uses the shared `go-libs` library for common functionality. + +## Monorepo Structure + +This project exists within a Go workspace monorepo (`../go.work`). Key locations: +- **Application code**: `./llnotes/` (this directory) +- **Shared libraries**: `../go-libs/` - contains reusable packages including: + - `llnotes`: Core business logic for release notes generation + - `lite`: CLI framework for building commands + - `github`: GitHub API integration utilities + - `git`: Git operations helpers +- **Other projects**: `../acceptance/`, `../metascan/`, etc. + +## Build & Development Commands + +```bash +# Build the project (default target) +make + +# Run linting +make lint + +# Run tests +make test + +# View test coverage in browser +make coverage + +# Format code +make fmt + +# Update dependencies and vendor folder +make vendor +``` + +**Important**: This project uses `go work vendor` (not `go mod vendor`) because it's part of a Go workspace. Always run `make vendor` instead of `go mod vendor`. + +## Running the Application + +### Authentication Setup + +Before using llnotes, authenticate with both services: + +```bash +# Databricks authentication +databricks auth login https://....cloud.databricks.com/ + +# GitHub authentication +gh auth login +``` + +### Available Commands + +```bash +# Generate pull request description +llnotes pull-request --number + +# Generate release notes for upcoming release +llnotes upcoming-release + +# Generate release notes between two git references +llnotes diff --since --until + +# Generate release announcement +llnotes announce --version +``` + +### Configuration Flags + +Common flags available across commands: +- `--model`: Serving chat model (default: "databricks-claude-sonnet-4-5") +- `--org`: GitHub organization (default: "databrickslabs") +- `--repo`: GitHub repository (default: "ucx") +- `--profile`: Databricks config profile +- GitHub authentication: `--github-token`, `--github-app-id`, `--github-app-installation-id`, etc. + +Config is stored in `$HOME/.databricks/labs/llnotes/` + +## Architecture + +### Command Structure + +The application uses the `lite` framework (from `go-libs/lite`) for CLI scaffolding. Commands are registered in `main.go`: +- Each command follows the `lite.Command` pattern with `Name`, `Short`, `Flags`, and `Run` functions +- Commands interact with the core `llnotes` library (`../go-libs/llnotes/`) +- The `askFor()` helper provides interactive prompts for iterative refinement + +### Core Library (go-libs/llnotes) + +The business logic lives in `../go-libs/llnotes/`: +- `pull_request.go`: PR description generation +- `release_notes.go`: Release notes from commits +- `diff.go`: Diff-based release notes +- `announce.go`: Release announcements +- `talk.go`: Interactive chat with LLM +- `chain.go`: Message chain management + +### Key Design Patterns + +1. **Conversation Chain**: Uses a message chain pattern (`chain.go`) to maintain context across LLM interactions +2. **Interactive Mode**: `pull-request` and `announce` commands support iterative refinement through user prompts +3. **Settings Configuration**: Central `Settings` struct manages GitHub and Databricks credentials, model selection, and repo details + +## Dependencies + +- `github.com/databricks/databricks-sdk-go`: Databricks API SDK for model serving +- `github.com/databrickslabs/sandbox/go-libs`: Shared monorepo libraries +- `github.com/spf13/pflag`: CLI flag parsing +- `github.com/fatih/color`: Terminal color output + +## Testing + +Tests use the standard Go testing framework with `gotestsum` for better output formatting: +- Test files would follow `*_test.go` naming convention +- Run with `make test` (includes coverage output to `coverage.txt`) +- Use `-short` flag to skip long-running tests diff --git a/llnotes/main.go b/llnotes/main.go index 64bdd985..05ef9eb4 100644 --- a/llnotes/main.go +++ b/llnotes/main.go @@ -34,7 +34,7 @@ func main() { flags.StringVar(&cfg.GitHub.PrivateKeyPath, "github-app-private-key-path", "", "GitHub App Private Key file (*.pem) path") flags.StringVar(&cfg.GitHub.PrivateKeyBase64, "github-app-private-key", "", "GitHub App Private Key encoded in base64") flags.StringVar(&cfg.Databricks.Profile, "profile", "", "Databricks config profile") - flags.StringVar(&cfg.Model, "model", "databricks-meta-llama-3-3-70b-instruct", "Serving chat model") + flags.StringVar(&cfg.Model, "model", "databricks-claude-sonnet-4-5", "Serving chat model") flags.StringVar(&cfg.Org, "org", "databrickslabs", "GitHub org") flags.StringVar(&cfg.Repo, "repo", "ucx", "GitHub repository") },