From f12a0c70570a7b26bdaf2ad78918cdd3fbb5c537 Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Tue, 2 Dec 2025 14:00:39 -0800 Subject: [PATCH 1/6] Add reformat mode to document generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a "reformat" mode to the `update documentation` command. This mode will strictly only reformat the document by moving existing content into the most appropriate section of the documentation template. The only action that should be performed with this mode is to move content. It will not modify or delete existing content, or add any new content. For documentation which is already high-quality, this command can help a user apply the updated template without any risk of changing the meaning of the existing documentation. šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 16 ++-- cmd/update.go | 1 + cmd/update_documentation.go | 62 +++++++++---- .../docagent/_static/reformat_prompt.txt | 17 ++++ internal/llmagent/docagent/docagent.go | 86 ++++++++++++++++++ internal/llmagent/docagent/prompts.go | 55 ++++++++++++ internal/llmagent/docagent/prompts_test.go | 87 +++++++++++++++++++ internal/llmagent/docagent/resources.go | 3 + 8 files changed, 304 insertions(+), 23 deletions(-) create mode 100644 internal/llmagent/docagent/_static/reformat_prompt.txt diff --git a/README.md b/README.md index e34aba872..457e74788 100644 --- a/README.md +++ b/README.md @@ -673,7 +673,7 @@ _Context: global_ Use this command to update package documentation using an AI agent or to get manual instructions for update. -The AI agent supports two modes: +The AI agent supports three modes: 1. Rewrite mode (default): Full documentation regeneration - Analyzes your package structure, data streams, and configuration - Generates comprehensive documentation following Elastic's templates @@ -682,6 +682,11 @@ The AI agent supports two modes: - Makes specific changes to existing documentation - Requires existing documentation file at /_dev/build/docs/ - Use --modify-prompt flag for non-interactive modifications +3. Reformat mode: Reorganize existing content + - Reorganizes existing documentation to match template structure + - Only moves content between sections, does not modify or add content + - Adds TODO placeholders for empty sections + - Use --reformat flag for non-interactive reformatting Multi-file support: - Use --doc-file to specify which markdown file to update (defaults to README.md) @@ -689,22 +694,19 @@ Multi-file support: - Supports packages with multiple documentation files (e.g., README.md, vpc.md, etc.) Interactive workflow: -After confirming you want to use the AI agent, you'll choose between rewrite or modify mode. +After confirming you want to use the AI agent, you'll choose between rewrite, modify, or reformat mode. You can review results and request additional changes iteratively. Non-interactive mode: Use --non-interactive to skip all prompts and automatically accept the first result from the LLM. Combine with --modify-prompt "instructions" for targeted non-interactive changes. +Use --reformat for non-interactive reformatting (mutually exclusive with --modify-prompt). If no LLM provider is configured, this command will print instructions for updating the documentation manually. Configuration options for LLM providers (environment variables or profile config): - GEMINI_API_KEY / llm.gemini.api_key: API key for Gemini -- GEMINI_MODEL / llm.gemini.model: Model ID (defaults to gemini-2.5-pro) -- LOCAL_LLM_ENDPOINT / llm.local.endpoint: Endpoint for local LLM server -- LOCAL_LLM_MODEL / llm.local.model: Model name for local LLM (defaults to llama2) -- LOCAL_LLM_API_KEY / llm.local.api_key: API key for local LLM (optional) -- LLM_EXTERNAL_PROMPTS / llm.external_prompts: Enable external prompt files (defaults to false). +- GEMINI_MODEL / llm.gemini.model: Model ID (defaults to gemini-2.5-pro). ### `elastic-package version` diff --git a/cmd/update.go b/cmd/update.go index 20ef00c46..37fece232 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -28,6 +28,7 @@ func setupUpdateCommand() *cobraext.Command { 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") + updateDocumentationCmd.Flags().Bool("reformat", false, "reformat existing documentation to match template structure (mutually exclusive with --modify-prompt)") cmd := &cobra.Command{ Use: "update", diff --git a/cmd/update_documentation.go b/cmd/update_documentation.go index e4fa31b87..ffc32e631 100644 --- a/cmd/update_documentation.go +++ b/cmd/update_documentation.go @@ -22,7 +22,7 @@ import ( const updateDocumentationLongDescription = `Use this command to update package documentation using an AI agent or to get manual instructions for update. -The AI agent supports two modes: +The AI agent supports three modes: 1. Rewrite mode (default): Full documentation regeneration - Analyzes your package structure, data streams, and configuration - Generates comprehensive documentation following Elastic's templates @@ -31,6 +31,11 @@ The AI agent supports two modes: - Makes specific changes to existing documentation - Requires existing documentation file at /_dev/build/docs/ - Use --modify-prompt flag for non-interactive modifications +3. Reformat mode: Reorganize existing content + - Reorganizes existing documentation to match template structure + - Only moves content between sections, does not modify or add content + - Adds TODO placeholders for empty sections + - Use --reformat flag for non-interactive reformatting Multi-file support: - Use --doc-file to specify which markdown file to update (defaults to README.md) @@ -38,12 +43,13 @@ Multi-file support: - Supports packages with multiple documentation files (e.g., README.md, vpc.md, etc.) Interactive workflow: -After confirming you want to use the AI agent, you'll choose between rewrite or modify mode. +After confirming you want to use the AI agent, you'll choose between rewrite, modify, or reformat mode. You can review results and request additional changes iteratively. Non-interactive mode: Use --non-interactive to skip all prompts and automatically accept the first result from the LLM. Combine with --modify-prompt "instructions" for targeted non-interactive changes. +Use --reformat for non-interactive reformatting (mutually exclusive with --modify-prompt). If no LLM provider is configured, this command will print instructions for updating the documentation manually. @@ -52,8 +58,9 @@ Configuration options for LLM providers (environment variables or profile config - GEMINI_MODEL / llm.gemini.model: Model ID (defaults to gemini-2.5-pro)` const ( - modePromptRewrite = "Rewrite (full regeneration)" - modePromptModify = "Modify (targeted changes)" + modePromptRewrite = "Rewrite (full regeneration)" + modePromptModify = "Modify (targeted changes)" + modePromptReformat = "Reformat (reorganize structure)" ) // getConfigValue retrieves a configuration value with fallback from environment variable to profile config @@ -218,8 +225,19 @@ func updateDocumentationCommandAction(cmd *cobra.Command, args []string) error { cmd.Printf("Selected documentation file: %s\n", targetDocFile) } + // Check for reformat flag + reformat, err := cmd.Flags().GetBool("reformat") + if err != nil { + return fmt.Errorf("failed to get reformat flag: %w", err) + } + + // Validate mutually exclusive flags + if reformat && modifyPrompt != "" { + return fmt.Errorf("--reformat and --modify-prompt are mutually exclusive") + } + // Determine the mode based on user input - var useModifyMode bool + var selectedMode string // Skip confirmation prompt in non-interactive mode if !nonInteractive { @@ -237,26 +255,32 @@ func updateDocumentationCommandAction(cmd *cobra.Command, args []string) error { return nil } - // If no modify-prompt flag was provided, ask user to choose mode - if modifyPrompt == "" { - modePrompt := tui.NewSelect("Do you want to rewrite or modify the documentation?", []string{ + // If no modify-prompt or reformat flag was provided, ask user to choose mode + if modifyPrompt == "" && !reformat { + modePrompt := tui.NewSelect("What would you like to do with the documentation?", []string{ modePromptRewrite, modePromptModify, + modePromptReformat, }, modePromptRewrite) - var mode string - err = tui.AskOne(modePrompt, &mode) + err = tui.AskOne(modePrompt, &selectedMode) if err != nil { return fmt.Errorf("prompt failed: %w", err) } - - useModifyMode = mode == "Modify (targeted changes)" + } else if reformat { + selectedMode = modePromptReformat } else { - useModifyMode = true + selectedMode = modePromptModify } } else { cmd.Println("Running in non-interactive mode - proceeding automatically.") - useModifyMode = modifyPrompt != "" + if reformat { + selectedMode = modePromptReformat + } else if modifyPrompt != "" { + selectedMode = modePromptModify + } else { + selectedMode = modePromptRewrite + } } // Find repository root for file operations @@ -280,12 +304,18 @@ func updateDocumentationCommandAction(cmd *cobra.Command, args []string) error { } // Run the documentation update process based on selected mode - if useModifyMode { + switch selectedMode { + case modePromptModify: err = docAgent.ModifyDocumentation(cmd.Context(), nonInteractive, modifyPrompt) if err != nil { return fmt.Errorf("documentation modification failed: %w", err) } - } else { + case modePromptReformat: + err = docAgent.ReformatDocumentation(cmd.Context(), nonInteractive) + if err != nil { + return fmt.Errorf("documentation reformat failed: %w", err) + } + default: // modePromptRewrite err = docAgent.UpdateDocumentation(cmd.Context(), nonInteractive) if err != nil { return fmt.Errorf("documentation update failed: %w", err) diff --git a/internal/llmagent/docagent/_static/reformat_prompt.txt b/internal/llmagent/docagent/_static/reformat_prompt.txt new file mode 100644 index 000000000..f58ee125c --- /dev/null +++ b/internal/llmagent/docagent/_static/reformat_prompt.txt @@ -0,0 +1,17 @@ +Reorganize this documentation to match the template section structure below. + +RULES: +1. ONLY MOVE content - do not modify, rewrite, or add new text +2. PRESERVE all original text exactly as written +3. For empty sections, add: <> +4. Content that doesn't fit any section goes under: # UNKNOWN SECTION CONTENT + +TEMPLATE SECTIONS: +%s + +CURRENT DOCUMENT TO REFORMAT: +--- +%s +--- + +Return ONLY the reformatted document with no explanation. diff --git a/internal/llmagent/docagent/docagent.go b/internal/llmagent/docagent/docagent.go index a443349a8..dfbd446fc 100644 --- a/internal/llmagent/docagent/docagent.go +++ b/internal/llmagent/docagent/docagent.go @@ -337,6 +337,92 @@ func (d *DocumentationAgent) ModifyDocumentation(ctx context.Context, nonInterac return nil } +// ReformatDocumentation runs the documentation reformat process using single-call mode. +// This is optimized for efficiency: reads document, makes one LLM call, writes result. +func (d *DocumentationAgent) ReformatDocumentation(ctx context.Context, nonInteractive bool) error { + // Check if documentation file exists + docPath := filepath.Join(d.packageRoot, "_dev", "build", "docs", d.targetDocFile) + if _, err := os.Stat(docPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("cannot reformat documentation: %s does not exist at _dev/build/docs/%s", d.targetDocFile, d.targetDocFile) + } + return fmt.Errorf("failed to check %s: %w", d.targetDocFile, err) + } + + // Backup original README content before making any changes + d.backupOriginalReadme() + + // Read current document content directly (no tool call needed) + currentContent, err := d.readCurrentReadme() + if err != nil { + return fmt.Errorf("failed to read current documentation: %w", err) + } + + if strings.TrimSpace(currentContent) == "" { + return fmt.Errorf("current documentation is empty, nothing to reformat") + } + + fmt.Println("Starting documentation reformat...") + fmt.Printf("šŸ“„ Document size: %d characters\n", len(currentContent)) + + // Build the reformat prompt with document content included + prompt := d.buildReformatPrompt(currentContent) + logger.Debugf("Reformat prompt size: %d characters", len(prompt)) + + fmt.Println("šŸ¤– Sending to LLM...") + + // Execute the task using the executor + result, err := d.executor.ExecuteTask(ctx, prompt) + if err != nil { + return fmt.Errorf("LLM call failed: %w", err) + } + + if result.FinalContent == "" { + return fmt.Errorf("LLM returned empty response") + } + + fmt.Println("āœ… LLM response received") + logger.Debugf("Response size: %d characters", len(result.FinalContent)) + + // Extract the reformatted document from the response + reformattedContent := strings.TrimSpace(result.FinalContent) + + // Write the reformatted content + if err := d.writeDocumentation(docPath, reformattedContent); err != nil { + return fmt.Errorf("failed to write reformatted documentation: %w", err) + } + + // Display result in interactive mode + if !nonInteractive { + err = d.displayReadme() + if err != nil { + logger.Debugf("Could not display readme: %v", err) + } + + // Get user confirmation + action, err := d.getUserAction() + if err != nil { + return err + } + + switch action { + case ActionAccept: + fmt.Println("āœ… Documentation reformat completed!") + case ActionCancel: + fmt.Println("āŒ Reformat cancelled, restoring original.") + d.restoreOriginalReadme() + case ActionRequest: + // For reformat, we don't support iterative changes + fmt.Println("āš ļø Reformat mode doesn't support iterative changes. Accept or cancel.") + d.restoreOriginalReadme() + } + } else { + fmt.Printf("āœ… %s was reformatted successfully!\n", d.targetDocFile) + } + + return nil +} + // runNonInteractiveMode handles the non-interactive documentation update flow func (d *DocumentationAgent) runNonInteractiveMode(ctx context.Context, prompt string) error { fmt.Println("Starting non-interactive documentation update process...") diff --git a/internal/llmagent/docagent/prompts.go b/internal/llmagent/docagent/prompts.go index c5adc2cca..9d70c8465 100644 --- a/internal/llmagent/docagent/prompts.go +++ b/internal/llmagent/docagent/prompts.go @@ -8,11 +8,14 @@ import ( "fmt" "os" "path/filepath" + "regexp" + "strings" "github.com/elastic/elastic-package/internal/configuration/locations" "github.com/elastic/elastic-package/internal/environment" "github.com/elastic/elastic-package/internal/logger" "github.com/elastic/elastic-package/internal/packages" + "github.com/elastic/elastic-package/internal/packages/archetype" "github.com/elastic/elastic-package/internal/profile" ) @@ -22,6 +25,7 @@ const ( promptFileSectionGeneration = "section_generation_prompt.txt" promptFileModificationAnalysis = "modification_analysis_prompt.txt" promptFileModification = "modification_prompt.txt" + promptFileReformat = "reformat_prompt.txt" ) type PromptType int @@ -32,6 +36,7 @@ const ( PromptTypeSectionGeneration PromptTypeModificationAnalysis PromptTypeModification + PromptTypeReformat ) // loadPromptFile loads a prompt file from external location if enabled, otherwise uses embedded content @@ -115,6 +120,10 @@ func (d *DocumentationAgent) buildPrompt(promptType PromptType, ctx PromptContex promptFile = promptFileModification embeddedContent = ModificationPrompt formatArgs = d.buildModificationPromptArgs(ctx) + case PromptTypeReformat: + // Reformat uses a separate method: buildReformatPrompt() + // This case should not be reached in normal flow + return "" } promptContent := loadPromptFile(promptFile, embeddedContent, d.profile) @@ -245,6 +254,18 @@ func (d *DocumentationAgent) buildModificationPromptArgs(ctx PromptContext) []in } } +// buildReformatPrompt builds a single-call reformat prompt with document content included +func (d *DocumentationAgent) buildReformatPrompt(documentContent string) string { + // Get minimal template sections (headers only, no comments) + minimalSections := getMinimalTemplateSections() + + // Load the prompt template + promptContent := loadPromptFile(promptFileReformat, ReformatPrompt, d.profile) + + // Format with minimal sections and document content + return fmt.Sprintf(promptContent, minimalSections, documentContent) +} + // Helper to create context with service info func (d *DocumentationAgent) createPromptContext(manifest *packages.PackageManifest, changes string) PromptContext { return PromptContext{ @@ -253,3 +274,37 @@ func (d *DocumentationAgent) createPromptContext(manifest *packages.PackageManif Changes: changes, } } + +// extractTemplateSections extracts only markdown headers and minimal structure from the template, +// removing Go template comments ({{/* ... */}}) and placeholder content. +// This reduces prompt size by ~60% while preserving the section structure. +func extractTemplateSections(fullTemplate string) string { + // Remove multi-line Go template comments: {{/* ... */}} + commentRegex := regexp.MustCompile(`\{\{/\*[\s\S]*?\*/\}\}`) + stripped := commentRegex.ReplaceAllString(fullTemplate, "") + + // Remove template directives like {{- generatedHeader }} + directiveRegex := regexp.MustCompile(`\{\{[^}]*\}\}`) + stripped = directiveRegex.ReplaceAllString(stripped, "") + + // Process line by line to keep only headers and remove placeholder content + lines := strings.Split(stripped, "\n") + var result []string + + for _, line := range lines { + trimmed := strings.TrimSpace(line) + + // Keep markdown headers + if strings.HasPrefix(trimmed, "#") { + result = append(result, trimmed) + } + } + + return strings.Join(result, "\n") +} + +// getMinimalTemplateSections returns a minimal section list for the reformat prompt +func getMinimalTemplateSections() string { + fullTemplate := archetype.GetPackageDocsReadmeTemplate() + return extractTemplateSections(fullTemplate) +} diff --git a/internal/llmagent/docagent/prompts_test.go b/internal/llmagent/docagent/prompts_test.go index abf82783f..8005e434e 100644 --- a/internal/llmagent/docagent/prompts_test.go +++ b/internal/llmagent/docagent/prompts_test.go @@ -190,3 +190,90 @@ func TestCreatePromptContext(t *testing.T) { assert.Equal(t, "docs/README.md", ctx.TargetDocFile) assert.Equal(t, "test changes", ctx.Changes) } + +func TestExtractTemplateSections(t *testing.T) { + t.Run("removes Go template comments", func(t *testing.T) { + input := `# Title +{{/* This is a comment */}} +## Section One +{{/* Another +multiline +comment */}} +### Subsection` + + result := extractTemplateSections(input) + + assert.Contains(t, result, "# Title") + assert.Contains(t, result, "## Section One") + assert.Contains(t, result, "### Subsection") + assert.NotContains(t, result, "This is a comment") + assert.NotContains(t, result, "multiline") + }) + + t.Run("removes template directives", func(t *testing.T) { + input := `{{- generatedHeader }} +# Title +{{ fields "data_stream" }} +## Section` + + result := extractTemplateSections(input) + + assert.Contains(t, result, "# Title") + assert.Contains(t, result, "## Section") + assert.NotContains(t, result, "generatedHeader") + assert.NotContains(t, result, "fields") + }) + + t.Run("keeps only headers", func(t *testing.T) { + input := `# Main Title +Some description text +## Section One +More text here +### Subsection +Even more text` + + result := extractTemplateSections(input) + + assert.Contains(t, result, "# Main Title") + assert.Contains(t, result, "## Section One") + assert.Contains(t, result, "### Subsection") + assert.NotContains(t, result, "description text") + assert.NotContains(t, result, "More text") + }) + + t.Run("handles real template format", func(t *testing.T) { + input := `{{- generatedHeader }} +{{/* +This template can be used as a starting point +*/}} +# {[.Manifest.Title]} Integration for Elastic + +## Overview +{{/* Complete this section */}} +The integration enables... + +### Compatibility +This is compatible with...` + + result := extractTemplateSections(input) + + assert.Contains(t, result, "# {[.Manifest.Title]} Integration for Elastic") + assert.Contains(t, result, "## Overview") + assert.Contains(t, result, "### Compatibility") + assert.NotContains(t, result, "starting point") + assert.NotContains(t, result, "Complete this section") + }) +} + +func TestGetMinimalTemplateSections(t *testing.T) { + sections := getMinimalTemplateSections() + + // Should contain key section headers + assert.Contains(t, sections, "## Overview") + assert.Contains(t, sections, "## Troubleshooting") + assert.Contains(t, sections, "## Reference") + + // Should not contain template comments + assert.NotContains(t, sections, "{{/*") + assert.NotContains(t, sections, "*/}}") +} diff --git a/internal/llmagent/docagent/resources.go b/internal/llmagent/docagent/resources.go index 67b83fc46..92e4db35c 100644 --- a/internal/llmagent/docagent/resources.go +++ b/internal/llmagent/docagent/resources.go @@ -23,3 +23,6 @@ var ModificationAnalysisPrompt string //go:embed _static/modification_prompt.txt var ModificationPrompt string + +//go:embed _static/reformat_prompt.txt +var ReformatPrompt string From 0539d8769f375ee8f9c42d3bcc1a5d0f5cef1764 Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Wed, 10 Dec 2025 12:05:34 -0800 Subject: [PATCH 2/6] Update tests --- .../docagent/section_parser_deep_test.go | 14 ++++---- internal/llmagent/tools/examples_test.go | 32 ------------------- 2 files changed, 7 insertions(+), 39 deletions(-) diff --git a/internal/llmagent/docagent/section_parser_deep_test.go b/internal/llmagent/docagent/section_parser_deep_test.go index 26c2d7cc4..b8ff5a357 100644 --- a/internal/llmagent/docagent/section_parser_deep_test.go +++ b/internal/llmagent/docagent/section_parser_deep_test.go @@ -142,13 +142,13 @@ func TestParseSections_RealTemplate(t *testing.T) { // Verify section titles and structure expectedSections := map[string]int{ - "Overview": 2, // 2 subsections: Compatibility, How it works - "What data does this integration collect?": 1, // 1 subsection: Supported use cases - "What do I need to use this integration?": 0, // No subsections - "How do I deploy this integration?": 5, // 5 subsections - "Troubleshooting": 0, // No subsections - "Performance and scaling": 0, // No subsections - "Reference": 3, // 3 subsections + "Overview": 2, + "What data does this integration collect?": 1, + "What do I need to use this integration?": 0, + "How do I deploy this integration?": 6, + "Troubleshooting": 0, + "Performance and scaling": 0, + "Reference": 3, } for i, section := range sections { diff --git a/internal/llmagent/tools/examples_test.go b/internal/llmagent/tools/examples_test.go index 48446bfcd..9667bfb55 100644 --- a/internal/llmagent/tools/examples_test.go +++ b/internal/llmagent/tools/examples_test.go @@ -83,38 +83,6 @@ func TestGetExampleHandler(t *testing.T) { } } -func TestGetDefaultExampleContent(t *testing.T) { - content := GetDefaultExampleContent() - - assert.NotEmpty(t, content) - assert.Contains(t, content, "# Fortinet FortiGate") - assert.Contains(t, content, "## Overview") -} - -func TestGetExampleContent(t *testing.T) { - t.Run("full file", func(t *testing.T) { - content, err := GetExampleContent("fortinet_fortigate.md", "") - require.NoError(t, err) - assert.Contains(t, content, "# Fortinet FortiGate") - }) - - t.Run("specific section", func(t *testing.T) { - content, err := GetExampleContent("fortinet_fortigate.md", "Overview") - require.NoError(t, err) - assert.Contains(t, content, "## Overview") - }) - - t.Run("non-existent file", func(t *testing.T) { - _, err := GetExampleContent("non_existent.md", "") - assert.Error(t, err) - }) - - t.Run("non-existent section", func(t *testing.T) { - _, err := GetExampleContent("fortinet_fortigate.md", "Non Existent") - assert.Error(t, err) - }) -} - func TestParseExampleSections(t *testing.T) { content := `# Title From d67b6a149f11301ae259b74d74ae4eb6bf07d4c0 Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Thu, 11 Dec 2025 10:25:45 -0800 Subject: [PATCH 3/6] Update workflow paths for all update subcommands --- README.md | 67 +++--- cmd/update.go | 4 +- cmd/update_documentation.go | 190 ++++++++++-------- .../llmagent/docagent/specialists/registry.go | 1 - .../docagent/specialists/urlvalidator.go | 1 - .../docagent/specialists/validator.go | 1 - tools/readme/readme.md.tmpl | 52 +++-- 7 files changed, 189 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 457e74788..cd0ef3cb1 100644 --- a/README.md +++ b/README.md @@ -673,20 +673,19 @@ _Context: global_ Use this command to update package documentation using an AI agent or to get manual instructions for update. -The AI agent supports three modes: -1. Rewrite mode (default): Full documentation regeneration +The AI agent supports three modes (--mode flag): +1. rewrite (default): Full documentation regeneration - Analyzes your package structure, data streams, and configuration - Generates comprehensive documentation following Elastic's templates - Creates or updates markdown files in /_dev/build/docs/ -2. Modify mode: Targeted documentation changes +2. modify: Targeted documentation changes - Makes specific changes to existing documentation - Requires existing documentation file at /_dev/build/docs/ - - Use --modify-prompt flag for non-interactive modifications -3. Reformat mode: Reorganize existing content + - Use --modify-prompt for non-interactive modifications +3. reformat: Reorganize existing content - Reorganizes existing documentation to match template structure - Only moves content between sections, does not modify or add content - Adds TODO placeholders for empty sections - - Use --reformat flag for non-interactive reformatting Multi-file support: - Use --doc-file to specify which markdown file to update (defaults to README.md) @@ -699,8 +698,8 @@ You can review results and request additional changes iteratively. Non-interactive mode: Use --non-interactive to skip all prompts and automatically accept the first result from the LLM. -Combine with --modify-prompt "instructions" for targeted non-interactive changes. -Use --reformat for non-interactive reformatting (mutually exclusive with --modify-prompt). +Combine with --mode=modify --modify-prompt "instructions" for targeted non-interactive changes. +Use --mode=reformat for non-interactive reformatting. If no LLM provider is configured, this command will print instructions for updating the documentation manually. @@ -771,30 +770,45 @@ When using AI-powered documentation generation, **file content from your local f #### Operation Modes -The command supports two modes of operation: +The command supports three modes of operation, selected via the `--mode` flag: -1. **Rewrite Mode** (default): Full documentation regeneration +1. **rewrite** (default): Full documentation regeneration - Analyzes your package structure, data streams, and configuration - Generates comprehensive documentation following Elastic's templates - - Creates or updates the README.md file in `/_dev/build/docs/` + - Creates or updates markdown files in `/_dev/build/docs/` -2. **Modify Mode**: Targeted documentation changes +2. **modify**: Targeted documentation changes - Makes specific changes to existing documentation - - Requires existing README.md file at `/_dev/build/docs/README.md` - - Use `--modify-prompt` flag for non-interactive modifications + - Requires existing documentation file at `/_dev/build/docs/` + - Use `--modify-prompt` for non-interactive modifications + +3. **reformat**: Reorganize existing content + - Reorganizes existing documentation to match template structure + - Only moves content between sections, does not modify or add content + - Adds TODO placeholders for empty sections + +#### Command Flags + +| Flag | Description | +|------|-------------| +| `--mode` | Documentation update mode: `rewrite` (default), `modify`, or `reformat` | +| `--modify-prompt` | Modification instructions for modify mode (implies `--mode=modify` if mode not set) | +| `--doc-file` | Specify which markdown file to update (e.g., `README.md`, `vpc.md`). Defaults to `README.md` | +| `--non-interactive` | Skip all prompts and automatically accept the first result from the LLM | +| `--profile`, `-p` | Use a specific elastic-package profile with LLM configuration | #### Workflow Options **Interactive Mode** (default): The command will guide you through the process, allowing you to: -- Choose between rewrite or modify mode +- Choose between rewrite, modify, or reformat mode +- Select which documentation file to update (if multiple exist) - Review generated documentation - Request iterative changes - Accept or cancel the update **Non-Interactive Mode**: Use `--non-interactive` to skip all prompts and automatically accept the first result. -Combine with `--modify-prompt "instructions"` for targeted non-interactive changes. If no LLM provider is configured, the command will print manual instructions for updating documentation. @@ -814,18 +828,23 @@ Environment variables (e.g., `GEMINI_API_KEY`, `LOCAL_LLM_ENDPOINT`) take preced #### Usage Examples ```bash -# Interactive documentation update (rewrite mode) +# Interactive documentation update (prompts for mode selection) elastic-package update documentation -# Interactive modification mode -elastic-package update documentation -# (choose "Modify" when prompted) - -# Non-interactive rewrite +# Non-interactive rewrite (default mode) elastic-package update documentation --non-interactive -# Non-interactive targeted changes -elastic-package update documentation --modify-prompt "Add more details about authentication configuration" +# Non-interactive modify with instructions +elastic-package update documentation --non-interactive --mode=modify --modify-prompt "Add troubleshooting section" + +# Shorthand for modify (--modify-prompt implies --mode=modify) +elastic-package update documentation --non-interactive --modify-prompt "Fix typos in setup instructions" + +# Non-interactive reformat +elastic-package update documentation --non-interactive --mode=reformat + +# Update a specific documentation file +elastic-package update documentation --doc-file vpc.md # Use specific profile with LLM configuration elastic-package update documentation --profile production diff --git a/cmd/update.go b/cmd/update.go index 37fece232..61f7cf87d 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -26,9 +26,9 @@ 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("mode", "", "documentation update mode: rewrite (default), modify, or reformat") + updateDocumentationCmd.Flags().String("modify-prompt", "", "modification instructions for modify mode (implies --mode=modify if mode not set)") updateDocumentationCmd.Flags().String("doc-file", "", "specify which markdown file to update (e.g., README.md, vpc.md). Defaults to README.md") - updateDocumentationCmd.Flags().Bool("reformat", false, "reformat existing documentation to match template structure (mutually exclusive with --modify-prompt)") cmd := &cobra.Command{ Use: "update", diff --git a/cmd/update_documentation.go b/cmd/update_documentation.go index ffc32e631..1b1d65c7b 100644 --- a/cmd/update_documentation.go +++ b/cmd/update_documentation.go @@ -22,20 +22,19 @@ import ( const updateDocumentationLongDescription = `Use this command to update package documentation using an AI agent or to get manual instructions for update. -The AI agent supports three modes: -1. Rewrite mode (default): Full documentation regeneration +The AI agent supports three modes (--mode flag): +1. rewrite (default): Full documentation regeneration - Analyzes your package structure, data streams, and configuration - Generates comprehensive documentation following Elastic's templates - Creates or updates markdown files in /_dev/build/docs/ -2. Modify mode: Targeted documentation changes +2. modify: Targeted documentation changes - Makes specific changes to existing documentation - Requires existing documentation file at /_dev/build/docs/ - - Use --modify-prompt flag for non-interactive modifications -3. Reformat mode: Reorganize existing content + - Use --modify-prompt for non-interactive modifications +3. reformat: Reorganize existing content - Reorganizes existing documentation to match template structure - Only moves content between sections, does not modify or add content - Adds TODO placeholders for empty sections - - Use --reformat flag for non-interactive reformatting Multi-file support: - Use --doc-file to specify which markdown file to update (defaults to README.md) @@ -48,8 +47,8 @@ You can review results and request additional changes iteratively. Non-interactive mode: Use --non-interactive to skip all prompts and automatically accept the first result from the LLM. -Combine with --modify-prompt "instructions" for targeted non-interactive changes. -Use --reformat for non-interactive reformatting (mutually exclusive with --modify-prompt). +Combine with --mode=modify --modify-prompt "instructions" for targeted non-interactive changes. +Use --mode=reformat for non-interactive reformatting. If no LLM provider is configured, this command will print instructions for updating the documentation manually. @@ -58,11 +57,88 @@ Configuration options for LLM providers (environment variables or profile config - GEMINI_MODEL / llm.gemini.model: Model ID (defaults to gemini-2.5-pro)` const ( - modePromptRewrite = "Rewrite (full regeneration)" - modePromptModify = "Modify (targeted changes)" - modePromptReformat = "Reformat (reorganize structure)" + modeRewrite = "rewrite" + modeModify = "modify" + modeReformat = "reformat" ) +var ( + validModes = []string{modeRewrite, modeModify, modeReformat} + modeLabels = map[string]string{ + modeRewrite: "Rewrite (full regeneration)", + modeModify: "Modify (targeted changes)", + modeReformat: "Reformat (reorganize structure)", + } +) + +// labelToMode converts a display label to its mode value +func labelToMode(label string) string { + for mode, l := range modeLabels { + if l == label { + return mode + } + } + return modeRewrite +} + +// modeLabelsSlice returns the display labels in order for UI selection +func modeLabelsSlice() []string { + return []string{modeLabels[modeRewrite], modeLabels[modeModify], modeLabels[modeReformat]} +} + +// isValidMode checks if a mode string is valid +func isValidMode(mode string) bool { + for _, m := range validModes { + if m == mode { + return true + } + } + return false +} + +// determineMode determines the mode from flags or prompts the user +func determineMode(cmd *cobra.Command, nonInteractive bool) (string, error) { + modeFlag, err := cmd.Flags().GetString("mode") + if err != nil { + return "", fmt.Errorf("failed to get mode flag: %w", err) + } + + modifyPrompt, err := cmd.Flags().GetString("modify-prompt") + if err != nil { + return "", fmt.Errorf("failed to get modify-prompt flag: %w", err) + } + + // If mode flag is explicitly set, validate and use it + if modeFlag != "" { + if !isValidMode(modeFlag) { + return "", fmt.Errorf("invalid mode %q: must be one of %v", modeFlag, validModes) + } + return modeFlag, nil + } + + // If modify-prompt is provided without mode, infer modify mode + if modifyPrompt != "" { + return modeModify, nil + } + + // In non-interactive mode, default to rewrite + if nonInteractive { + return modeRewrite, nil + } + + // Interactive mode: prompt user to choose + modePrompt := tui.NewSelect("What would you like to do with the documentation?", + modeLabelsSlice(), modeLabels[modeRewrite]) + + var selectedLabel string + err = tui.AskOne(modePrompt, &selectedLabel) + if err != nil { + return "", fmt.Errorf("mode selection failed: %w", err) + } + + return labelToMode(selectedLabel), nil +} + // getConfigValue retrieves a configuration value with fallback from environment variable to profile config func getConfigValue(profile *profile.Profile, envVar, configKey, defaultValue string) string { // First check environment variable @@ -187,27 +263,17 @@ func updateDocumentationCommandAction(cmd *cobra.Command, args []string) error { return fmt.Errorf("locating package root failed: %w", err) } - // Check for non-interactive flag nonInteractive, err := cmd.Flags().GetBool("non-interactive") if err != nil { return fmt.Errorf("failed to get non-interactive flag: %w", err) } - // Check for modify-prompt flag - modifyPrompt, err := cmd.Flags().GetString("modify-prompt") - if err != nil { - return fmt.Errorf("failed to get modify-prompt flag: %w", err) - } - - // Get profile for configuration access profile, err := cobraext.GetProfileFlag(cmd) if err != nil { return fmt.Errorf("failed to get profile: %w", err) } - // Get Gemini configuration apiKey, modelID := getGeminiConfig(profile) - if apiKey == "" { printNoProviderInstructions(cmd) return nil @@ -215,82 +281,44 @@ func updateDocumentationCommandAction(cmd *cobra.Command, args []string) error { cmd.Printf("Using Gemini provider with model: %s\n", modelID) - // Select which documentation file to update + // Select documentation file targetDocFile, err := selectDocumentationFile(cmd, packageRoot, nonInteractive) if err != nil { return fmt.Errorf("failed to select documentation file: %w", err) } - if !nonInteractive && targetDocFile != "README.md" { cmd.Printf("Selected documentation file: %s\n", targetDocFile) } - // Check for reformat flag - reformat, err := cmd.Flags().GetBool("reformat") - if err != nil { - return fmt.Errorf("failed to get reformat flag: %w", err) - } - - // Validate mutually exclusive flags - if reformat && modifyPrompt != "" { - return fmt.Errorf("--reformat and --modify-prompt are mutually exclusive") - } - - // Determine the mode based on user input - var selectedMode string - - // Skip confirmation prompt in non-interactive mode - if !nonInteractive { - // Prompt user for confirmation + // Handle confirmation and mode selection + if nonInteractive { + cmd.Println("Running in non-interactive mode - proceeding automatically.") + } else { confirmPrompt := tui.NewConfirm("Do you want to update the documentation using the AI agent?", true) - var confirm bool - err = tui.AskOne(confirmPrompt, &confirm, tui.Required) - if err != nil { + if err = tui.AskOne(confirmPrompt, &confirm, tui.Required); err != nil { return fmt.Errorf("prompt failed: %w", err) } - if !confirm { cmd.Println("Documentation update cancelled.") return nil } + } - // If no modify-prompt or reformat flag was provided, ask user to choose mode - if modifyPrompt == "" && !reformat { - modePrompt := tui.NewSelect("What would you like to do with the documentation?", []string{ - modePromptRewrite, - modePromptModify, - modePromptReformat, - }, modePromptRewrite) - - err = tui.AskOne(modePrompt, &selectedMode) - if err != nil { - return fmt.Errorf("prompt failed: %w", err) - } - } else if reformat { - selectedMode = modePromptReformat - } else { - selectedMode = modePromptModify - } - } else { - cmd.Println("Running in non-interactive mode - proceeding automatically.") - if reformat { - selectedMode = modePromptReformat - } else if modifyPrompt != "" { - selectedMode = modePromptModify - } else { - selectedMode = modePromptRewrite - } + // Determine mode + selectedMode, err := determineMode(cmd, nonInteractive) + if err != nil { + return err } - // Find repository root for file operations + // Find repository root repositoryRoot, err := files.FindRepositoryRootFrom(packageRoot) if err != nil { return fmt.Errorf("failed to find repository root: %w", err) } defer repositoryRoot.Close() - // Create the documentation agent using ADK + // Create agent docAgent, err := docagent.NewDocumentationAgent(cmd.Context(), docagent.AgentConfig{ APIKey: apiKey, ModelID: modelID, @@ -303,21 +331,19 @@ func updateDocumentationCommandAction(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to create documentation agent: %w", err) } - // Run the documentation update process based on selected mode + // Execute based on mode switch selectedMode { - case modePromptModify: - err = docAgent.ModifyDocumentation(cmd.Context(), nonInteractive, modifyPrompt) - if err != nil { + case modeModify: + modifyPrompt, _ := cmd.Flags().GetString("modify-prompt") + if err = docAgent.ModifyDocumentation(cmd.Context(), nonInteractive, modifyPrompt); err != nil { return fmt.Errorf("documentation modification failed: %w", err) } - case modePromptReformat: - err = docAgent.ReformatDocumentation(cmd.Context(), nonInteractive) - if err != nil { + case modeReformat: + if err = docAgent.ReformatDocumentation(cmd.Context(), nonInteractive); err != nil { return fmt.Errorf("documentation reformat failed: %w", err) } - default: // modePromptRewrite - err = docAgent.UpdateDocumentation(cmd.Context(), nonInteractive) - if err != nil { + default: // modeRewrite + if err = docAgent.UpdateDocumentation(cmd.Context(), nonInteractive); err != nil { return fmt.Errorf("documentation update failed: %w", err) } } diff --git a/internal/llmagent/docagent/specialists/registry.go b/internal/llmagent/docagent/specialists/registry.go index c2b9b3660..c7172df82 100644 --- a/internal/llmagent/docagent/specialists/registry.go +++ b/internal/llmagent/docagent/specialists/registry.go @@ -123,4 +123,3 @@ func DefaultRegistry() *Registry { r.Register(NewURLValidatorAgent()) return r } - diff --git a/internal/llmagent/docagent/specialists/urlvalidator.go b/internal/llmagent/docagent/specialists/urlvalidator.go index 803370f2c..b51bdadf8 100644 --- a/internal/llmagent/docagent/specialists/urlvalidator.go +++ b/internal/llmagent/docagent/specialists/urlvalidator.go @@ -302,4 +302,3 @@ func isLocalhostURL(url string) bool { } return false } - diff --git a/internal/llmagent/docagent/specialists/validator.go b/internal/llmagent/docagent/specialists/validator.go index 37bb757d6..8a77e6756 100644 --- a/internal/llmagent/docagent/specialists/validator.go +++ b/internal/llmagent/docagent/specialists/validator.go @@ -152,4 +152,3 @@ func (v *ValidatorAgent) validateContent(content string) ValidationResult { Warnings: warnings, } } - diff --git a/tools/readme/readme.md.tmpl b/tools/readme/readme.md.tmpl index bf9371eba..18655e9da 100644 --- a/tools/readme/readme.md.tmpl +++ b/tools/readme/readme.md.tmpl @@ -223,30 +223,45 @@ When using AI-powered documentation generation, **file content from your local f #### Operation Modes -The command supports two modes of operation: +The command supports three modes of operation, selected via the `--mode` flag: -1. **Rewrite Mode** (default): Full documentation regeneration +1. **rewrite** (default): Full documentation regeneration - Analyzes your package structure, data streams, and configuration - Generates comprehensive documentation following Elastic's templates - - Creates or updates the README.md file in `/_dev/build/docs/` + - Creates or updates markdown files in `/_dev/build/docs/` -2. **Modify Mode**: Targeted documentation changes +2. **modify**: Targeted documentation changes - Makes specific changes to existing documentation - - Requires existing README.md file at `/_dev/build/docs/README.md` - - Use `--modify-prompt` flag for non-interactive modifications + - Requires existing documentation file at `/_dev/build/docs/` + - Use `--modify-prompt` for non-interactive modifications + +3. **reformat**: Reorganize existing content + - Reorganizes existing documentation to match template structure + - Only moves content between sections, does not modify or add content + - Adds TODO placeholders for empty sections + +#### Command Flags + +| Flag | Description | +|------|-------------| +| `--mode` | Documentation update mode: `rewrite` (default), `modify`, or `reformat` | +| `--modify-prompt` | Modification instructions for modify mode (implies `--mode=modify` if mode not set) | +| `--doc-file` | Specify which markdown file to update (e.g., `README.md`, `vpc.md`). Defaults to `README.md` | +| `--non-interactive` | Skip all prompts and automatically accept the first result from the LLM | +| `--profile`, `-p` | Use a specific elastic-package profile with LLM configuration | #### Workflow Options **Interactive Mode** (default): The command will guide you through the process, allowing you to: -- Choose between rewrite or modify mode +- Choose between rewrite, modify, or reformat mode +- Select which documentation file to update (if multiple exist) - Review generated documentation - Request iterative changes - Accept or cancel the update **Non-Interactive Mode**: Use `--non-interactive` to skip all prompts and automatically accept the first result. -Combine with `--modify-prompt "instructions"` for targeted non-interactive changes. If no LLM provider is configured, the command will print manual instructions for updating documentation. @@ -266,18 +281,23 @@ Environment variables (e.g., `GEMINI_API_KEY`, `LOCAL_LLM_ENDPOINT`) take preced #### Usage Examples ```bash -# Interactive documentation update (rewrite mode) -elastic-package update documentation - -# Interactive modification mode +# Interactive documentation update (prompts for mode selection) elastic-package update documentation -# (choose "Modify" when prompted) -# Non-interactive rewrite +# Non-interactive rewrite (default mode) elastic-package update documentation --non-interactive -# Non-interactive targeted changes -elastic-package update documentation --modify-prompt "Add more details about authentication configuration" +# Non-interactive modify with instructions +elastic-package update documentation --non-interactive --mode=modify --modify-prompt "Add troubleshooting section" + +# Shorthand for modify (--modify-prompt implies --mode=modify) +elastic-package update documentation --non-interactive --modify-prompt "Fix typos in setup instructions" + +# Non-interactive reformat +elastic-package update documentation --non-interactive --mode=reformat + +# Update a specific documentation file +elastic-package update documentation --doc-file vpc.md # Use specific profile with LLM configuration elastic-package update documentation --profile production From 6001f2f471af03c33eec853893e7c736e0f71ed4 Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Thu, 11 Dec 2025 10:38:01 -0800 Subject: [PATCH 4/6] remove unused functions --- internal/llmagent/docagent/docagent.go | 132 ---------------------- internal/llmagent/docagent/interactive.go | 26 ----- 2 files changed, 158 deletions(-) diff --git a/internal/llmagent/docagent/docagent.go b/internal/llmagent/docagent/docagent.go index dfbd446fc..763bfa26f 100644 --- a/internal/llmagent/docagent/docagent.go +++ b/internal/llmagent/docagent/docagent.go @@ -423,122 +423,6 @@ func (d *DocumentationAgent) ReformatDocumentation(ctx context.Context, nonInter return nil } -// runNonInteractiveMode handles the non-interactive documentation update flow -func (d *DocumentationAgent) runNonInteractiveMode(ctx context.Context, prompt string) error { - fmt.Println("Starting non-interactive documentation update process...") - fmt.Println("The LLM agent will analyze your package and generate documentation automatically.") - fmt.Println() - - // First attempt - result, err := d.executeTaskWithLogging(ctx, prompt) - if err != nil { - return err - } - - // Show the result - fmt.Println("\nšŸ“ Agent Response:") - fmt.Println(strings.Repeat("-", 50)) - fmt.Println(result.FinalContent) - fmt.Println(strings.Repeat("-", 50)) - - analysis := d.responseAnalyzer.AnalyzeResponse(result.FinalContent, result.Conversation) - - switch analysis.Status { - case responseError: - fmt.Println("\nāŒ Error detected in LLM response.") - fmt.Println("In non-interactive mode, exiting due to error.") - return fmt.Errorf("LLM agent encountered an error: %s", result.FinalContent) - } - - // Check if documentation file was successfully updated - if updated, _ := d.handleReadmeUpdate(); updated { - fmt.Printf("\nšŸ“„ %s was updated successfully!\n", d.targetDocFile) - return nil - } - - // If documentation was not updated, but there was no error response, make another attempt with specific instructions - fmt.Printf("āš ļø %s was not updated. Trying again with specific instructions...\n", d.targetDocFile) - specificPrompt := fmt.Sprintf("You haven't updated the %s file yet. Please write the %s file in the _dev/build/docs/ directory based on your analysis. This is required to complete the task.", d.targetDocFile, d.targetDocFile) - - if _, err := d.executeTaskWithLogging(ctx, specificPrompt); err != nil { - return fmt.Errorf("second attempt failed: %w", err) - } - - // Final check - if updated, _ := d.handleReadmeUpdate(); updated { - fmt.Printf("\nšŸ“„ %s was updated on second attempt!\n", d.targetDocFile) - return nil - } - - return fmt.Errorf("failed to create %s after two attempts", d.targetDocFile) -} - -// runInteractiveMode handles the interactive documentation update flow -func (d *DocumentationAgent) runInteractiveMode(ctx context.Context, prompt string) error { - fmt.Println("Starting documentation update process...") - fmt.Println("The LLM agent will analyze your package and update the documentation.") - fmt.Println() - - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - // Execute the task - result, err := d.executeTaskWithLogging(ctx, prompt) - if err != nil { - return err - } - - analysis := d.responseAnalyzer.AnalyzeResponse(result.FinalContent, result.Conversation) - - switch analysis.Status { - case responseError: - newPrompt, shouldContinue, err := d.handleInteractiveError() - if err != nil { - return err - } - if !shouldContinue { - d.restoreOriginalReadme() - return fmt.Errorf("user chose to exit due to LLM error") - } - prompt = newPrompt - continue - } - - // Display README content if updated - readmeUpdated, err := d.isReadmeUpdated() - if err != nil { - logger.Debugf("could not determine if readme is updated: %v", err) - } - if readmeUpdated { - err = d.displayReadme() - if err != nil { - // This may be recoverable, only log the error - logger.Debugf("displaying readme: %v", err) - } - } - - // Get and handle user action - action, err := d.getUserAction() - if err != nil { - return err - } - actionResult := d.handleUserAction(action, readmeUpdated) - if actionResult.Err != nil { - return actionResult.Err - } - if actionResult.ShouldContinue { - prompt = actionResult.NewPrompt - continue - } - // If we reach here, should exit - return nil - } -} - // logAgentResponse logs debug information about the agent response func (d *DocumentationAgent) logAgentResponse(result *TaskResult) { logger.Debugf("DEBUG: Full agent task response follows (may contain sensitive content)") @@ -552,22 +436,6 @@ func (d *DocumentationAgent) logAgentResponse(result *TaskResult) { } } -// executeTaskWithLogging executes a task and logs the result -func (d *DocumentationAgent) executeTaskWithLogging(ctx context.Context, prompt string) (*TaskResult, error) { - fmt.Println("šŸ¤– LLM Agent is working...") - - result, err := d.executor.ExecuteTask(ctx, prompt) - if err != nil { - fmt.Println("āŒ Agent task failed") - fmt.Printf("āŒ result is %v\n", result) - return nil, fmt.Errorf("agent task failed: %w", err) - } - - fmt.Println("āœ… Task completed") - d.logAgentResponse(result) - return result, nil -} - // NewResponseAnalyzer creates a new ResponseAnalyzer with default patterns // // These responses should be chosen to represent LLM responses to states, but are unlikely to appear in generated diff --git a/internal/llmagent/docagent/interactive.go b/internal/llmagent/docagent/interactive.go index 1f185f81f..eeb9f97fe 100644 --- a/internal/llmagent/docagent/interactive.go +++ b/internal/llmagent/docagent/interactive.go @@ -108,32 +108,6 @@ func (d *DocumentationAgent) handleReadmeUpdate() (bool, error) { return true, nil } -// handleInteractiveError handles error responses in interactive mode -func (d *DocumentationAgent) handleInteractiveError() (string, bool, error) { - fmt.Println("\nāŒ Error detected in LLM response.") - - errorPrompt := tui.NewSelect("What would you like to do?", []string{ - ActionTryAgain, - ActionExit, - }, ActionTryAgain) - - var errorAction string - err := tui.AskOne(errorPrompt, &errorAction) - if err != nil { - return "", false, fmt.Errorf("prompt failed: %w", err) - } - - if errorAction == ActionExit { - fmt.Println("āš ļø Exiting due to LLM error.") - return "", false, nil - } - - // Continue with retry prompt - promptCtx := d.createPromptContext(d.manifest, "The previous attempt encountered an error. Please try a different approach to analyze the package and update the documentation.") - prompt := d.buildPrompt(PromptTypeRevision, promptCtx) - return prompt, true, nil -} - // handleUserAction processes the user's chosen action func (d *DocumentationAgent) handleUserAction(action string, readmeUpdated bool) ActionResult { switch action { From 3e026696ccd82315209084e3a54aed1ebc1ef34f Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Thu, 11 Dec 2025 10:48:43 -0800 Subject: [PATCH 5/6] Use path for path handling --- internal/llmagent/tools/examples.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/llmagent/tools/examples.go b/internal/llmagent/tools/examples.go index 310c012bb..78728af5c 100644 --- a/internal/llmagent/tools/examples.go +++ b/internal/llmagent/tools/examples.go @@ -8,7 +8,7 @@ import ( "bufio" "embed" "fmt" - "path/filepath" + "path" "strings" "google.golang.org/adk/tool" @@ -98,7 +98,7 @@ func getExampleHandler() functiontool.Func[GetExampleArgs, GetExampleResult] { } // Read the example file from embedded FS - filePath := filepath.Join("_static/examples", args.Name) + filePath := path.Join("_static/examples", args.Name) content, err := examplesFS.ReadFile(filePath) if err != nil { return GetExampleResult{Error: fmt.Sprintf("failed to read example file '%s': %v", args.Name, err)}, nil @@ -269,7 +269,7 @@ func findSectionByTitleInSubsections(sections []*exampleSection, titleLower stri // GetExampleContent retrieves the content of a specific example file. // If section is provided, only that section's content is returned. func GetExampleContent(name, section string) (string, error) { - filePath := filepath.Join("_static/examples", name) + filePath := path.Join("_static/examples", name) content, err := examplesFS.ReadFile(filePath) if err != nil { return "", fmt.Errorf("failed to read example file '%s': %w", name, err) From 0644c94880fe40a2d551e5106c8af219381fb826 Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Thu, 11 Dec 2025 11:02:02 -0800 Subject: [PATCH 6/6] Fix import stanzas --- internal/llmagent/docagent/section_parser_deep_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/llmagent/docagent/section_parser_deep_test.go b/internal/llmagent/docagent/section_parser_deep_test.go index b8ff5a357..5f40e27ac 100644 --- a/internal/llmagent/docagent/section_parser_deep_test.go +++ b/internal/llmagent/docagent/section_parser_deep_test.go @@ -7,8 +7,9 @@ package docagent import ( "testing" - "github.com/elastic/elastic-package/internal/packages/archetype" "github.com/stretchr/testify/assert" + + "github.com/elastic/elastic-package/internal/packages/archetype" ) func TestParseSections_DeepNesting(t *testing.T) {