Skip to content
Merged
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
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,12 @@ _Context: package_

Use this command to update package documentation using an AI agent or to get manual instructions for update.

The AI agent analyzes your package structure, data streams, and configuration, and generates a new documentation file based on the template and the package context.
The AI agent supports two modes:
1. Rewrite mode (default): Full documentation regeneration
- Analyzes your package structure, data streams, and configuration, and generates a new documentation file based on the template and the package context.
2. Modify mode: Targeted documentation changes
- The LLM will perform a targeted change to the documentation, based on user-provided instructions.
- Use --modify-prompt flag to provide instructions for non-interactive modifications

For packages with multiple documentation files, the user can specify which file to update in interactive mode, or use the --doc-file flag to specify the file to update in non-interactive mode.

Expand Down Expand Up @@ -854,7 +859,7 @@ The following settings are available per profile:

### AI-powered Documentation Configuration

The `elastic-package update documentation` command can generate or update package documentation using an LLM.
The `elastic-package update documentation` command can generate or update package documentation using an LLM. It supports full rewrites and targeted modifications.

For full details on configuration, LLM provider setup, and the optional service knowledge base, see [docs/howto/ai_documentation.md](docs/howto/ai_documentation.md).

Expand Down
1 change: 1 addition & 0 deletions cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func setupUpdateCommand() *cobraext.Command {
RunE: updateDocumentationCommandAction,
}
updateDocumentationCmd.Flags().Bool("non-interactive", false, "run in non-interactive mode, accepting the first result from the LLM")
updateDocumentationCmd.Flags().String("modify-prompt", "", "modification instructions for targeted documentation changes (skips full rewrite)")
updateDocumentationCmd.Flags().String("doc-file", "", "specify which markdown file to update (e.g., README.md, vpc.md). Defaults to README.md")

cmd := &cobra.Command{
Expand Down
94 changes: 85 additions & 9 deletions cmd/updatedocumentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ import (

const updateDocumentationLongDescription = `Use this command to update package documentation using an AI agent or to get manual instructions for update.

The AI agent analyzes your package structure, data streams, and configuration, and generates a new documentation file based on the template and the package context.
The AI agent supports two modes:
1. Rewrite mode (default): Full documentation regeneration
- Analyzes your package structure, data streams, and configuration, and generates a new documentation file based on the template and the package context.
2. Modify mode: Targeted documentation changes
- The LLM will perform a targeted change to the documentation, based on user-provided instructions.
- Use --modify-prompt flag to provide instructions for non-interactive modifications

For packages with multiple documentation files, the user can specify which file to update in interactive mode, or use the --doc-file flag to specify the file to update in non-interactive mode.

Expand All @@ -34,6 +39,11 @@ Configuration options for LLM providers (environment variables or profile config
- ELASTIC_PACKAGE_LLM_PROVIDER / llm.provider: Provider name (only Gemini provider currently supported).
- Gemini: GOOGLE_API_KEY / llm.gemini.api_key, GEMINI_MODEL / llm.gemini.model, GEMINI_THINKING_BUDGET / llm.gemini.thinking_budget`

const (
modePromptRewrite = "Rewrite (full regeneration)"
modePromptModify = "Modify (targeted changes)"
)

// discoverDocumentationTemplates finds all .md files in _dev/build/docs/
func discoverDocumentationTemplates(packageRoot string) ([]string, error) {
docsDir := filepath.Join(packageRoot, "_dev", "build", "docs")
Expand All @@ -53,10 +63,12 @@ func discoverDocumentationTemplates(packageRoot string) ([]string, error) {
}
}

// If no files found, return README.md as default
if len(mdFiles) == 0 {
return []string{"README.md"}, nil
}

// Sort with README.md first, others alphabetically
sort.Slice(mdFiles, func(i, j int) bool {
if mdFiles[i] == "README.md" {
return true
Expand All @@ -72,11 +84,13 @@ func discoverDocumentationTemplates(packageRoot string) ([]string, error) {

// selectDocumentationFile determines which documentation file to update
func selectDocumentationFile(cmd *cobra.Command, packageRoot string, nonInteractive bool) (string, error) {
// Check if --doc-file flag was provided
docFile, err := cmd.Flags().GetString("doc-file")
if err != nil {
return "", fmt.Errorf("failed to get doc-file flag: %w", err)
}

// If flag is provided, validate its an .md file and use it
if docFile != "" {
if filepath.Ext(docFile) != ".md" {
return "", fmt.Errorf("doc-file must be a .md file, got: %s", docFile)
Expand All @@ -87,11 +101,13 @@ func selectDocumentationFile(cmd *cobra.Command, packageRoot string, nonInteract
return docFile, nil
}

// Discover available markdown files
mdFiles, err := discoverDocumentationTemplates(packageRoot)
if err != nil {
return "", err
}

// If only one file, use it (no prompt)
if len(mdFiles) == 1 {
return mdFiles[0], nil
}
Expand Down Expand Up @@ -130,6 +146,65 @@ func printNoProviderInstructions(cmd *cobra.Command) {
cmd.Println(tui.Info(" - For Gemini: set GOOGLE_API_KEY or llm.gemini.api_key in your elastic-package profile config"))
}

// standardModeFlags holds CLI flags for standard (non-evaluation) mode
type standardModeFlags struct {
nonInteractive bool
modifyPrompt string
}

// getStandardModeFlags extracts CLI flags needed for standard mode
func getStandardModeFlags(cmd *cobra.Command) (standardModeFlags, error) {
var flags standardModeFlags
var err error

flags.nonInteractive, err = cmd.Flags().GetBool("non-interactive")
if err != nil {
return flags, fmt.Errorf("failed to get non-interactive flag: %w", err)
}

flags.modifyPrompt, err = cmd.Flags().GetString("modify-prompt")
if err != nil {
return flags, fmt.Errorf("failed to get modify-prompt flag: %w", err)
}

return flags, nil
}

// selectUpdateMode prompts the user to select rewrite or modify mode, or determines it automatically
func selectUpdateMode(cmd *cobra.Command, nonInteractive bool, modifyPrompt string) (useModifyMode bool, err error) {
cmd.Println("Updating documentation using AI agent...")

if nonInteractive || modifyPrompt != "" {
return modifyPrompt != "", nil
}

modePrompt := tui.NewSelect("Do you want to rewrite or modify the documentation?", []string{
modePromptRewrite,
modePromptModify,
}, modePromptRewrite)

var mode string
if err := tui.AskOne(modePrompt, &mode); err != nil {
return false, fmt.Errorf("prompt failed: %w", err)
}

return mode == modePromptModify, nil
}

// runDocumentationUpdate executes the documentation update using the agent
func runDocumentationUpdate(cmd *cobra.Command, docAgent *docagent.DocumentationAgent, useModifyMode, nonInteractive bool, modifyPrompt string) error {
if useModifyMode {
if err := docAgent.ModifyDocumentation(cmd.Context(), nonInteractive, modifyPrompt); err != nil {
return fmt.Errorf("documentation modification failed: %w", err)
}
} else {
if err := docAgent.UpdateDocumentation(cmd.Context(), nonInteractive); err != nil {
return fmt.Errorf("documentation update failed: %w", err)
}
}
return nil
}

func updateDocumentationCommandAction(cmd *cobra.Command, _ []string) error {
p, err := cobraext.GetProfileFlag(cmd)
if err != nil {
Expand All @@ -147,9 +222,9 @@ func handleStandardMode(cmd *cobra.Command, p *profile.Profile, cfg llmconfig.LL
return fmt.Errorf("locating package root failed: %w", err)
}

nonInteractive, err := cmd.Flags().GetBool("non-interactive")
flags, err := getStandardModeFlags(cmd)
if err != nil {
return fmt.Errorf("failed to get non-interactive flag: %w", err)
return err
}

if cfg.APIKey == "" {
Expand All @@ -163,11 +238,11 @@ func handleStandardMode(cmd *cobra.Command, p *profile.Profile, cfg llmconfig.LL
cmd.Printf("Using LLM model \"%s\"\n", cfg.ModelID)
}

targetDocFile, err := selectDocumentationFile(cmd, packageRoot, nonInteractive)
targetDocFile, err := selectDocumentationFile(cmd, packageRoot, flags.nonInteractive)
if err != nil {
return fmt.Errorf("failed to select documentation file: %w", err)
}
if !nonInteractive && targetDocFile != "README.md" {
if !flags.nonInteractive && targetDocFile != "README.md" {
cmd.Printf("Selected documentation file: %s\n", targetDocFile)
}

Expand All @@ -194,9 +269,10 @@ func handleStandardMode(cmd *cobra.Command, p *profile.Profile, cfg llmconfig.LL
return fmt.Errorf("failed to create documentation agent: %w", err)
}

cmd.Println("Updating documentation using AI agent...")
if err := docAgent.UpdateDocumentation(cmd.Context(), nonInteractive); err != nil {
return fmt.Errorf("documentation update failed: %w", err)
useModifyMode, err := selectUpdateMode(cmd, flags.nonInteractive, flags.modifyPrompt)
if err != nil {
return err
}
return nil

return runDocumentationUpdate(cmd, docAgent, useModifyMode, flags.nonInteractive, flags.modifyPrompt)
}
2 changes: 1 addition & 1 deletion docs/howto/ai_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Currently only the Gemini provider is supported. Gemini is the default provider.
```yaml
llm.provider: gemini
llm.gemini.api_key: "YOUR_API_KEY"
llm.gemini.model: "gemini-3-flash-preview"
llm.gemini.model: "gemini-3.5-flash"
llm.gemini.thinking_budget: "128"
```

Expand Down
2 changes: 1 addition & 1 deletion internal/llmagent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ Example: `workflow.DefaultConfig().WithModel(m).WithModelID("...").WithTools(too
```go
agent, err := docagent.NewDocumentationAgent(ctx, docagent.AgentConfig{
APIKey: apiKey,
ModelID: "gemini-3-flash-preview",
ModelID: "gemini-3.5-flash",
PackageRoot: "/path/to/package",
DocFile: "README.md",
})
Expand Down
2 changes: 1 addition & 1 deletion internal/llmagent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
)

const (
DefaultModelID = "gemini-3-flash-preview"
DefaultModelID = "gemini-3.5-flash"
DefaultThinkingBudget int32 = 128
DefaultProvider = "gemini"
)
Expand Down
Loading