diff --git a/.github/workflows/docs-update-recipe-ref.yml b/.github/workflows/docs-update-recipe-ref.yml new file mode 100644 index 000000000000..9cce79979100 --- /dev/null +++ b/.github/workflows/docs-update-recipe-ref.yml @@ -0,0 +1,293 @@ +# Automatically updates the Recipe Reference Guide when recipe struct fields, +# validation rules, or schema constraints change between releases. +# +# Triggers: Manual (for testing) or on release (production) +# Testing: Use dry_run mode to review outputs without creating PRs +# See: documentation/automation/recipe-schema-tracking/TESTING.md + +name: Update Recipe Documentation + +on: + workflow_dispatch: # Manual trigger for testing + inputs: + old_version: + description: 'Previous version (e.g., v1.14.0). Leave empty to auto-detect.' + required: false + type: string + new_version: + description: 'New version (e.g., v1.15.0). Leave empty to use HEAD.' + required: false + type: string + dry_run: + description: 'Dry run mode - generate files but do not create PR' + required: false + type: boolean + default: true + + # Uncomment to enable automatic triggering on releases + # release: + # types: [published] + +permissions: + contents: write # Create branches and commit files + pull-requests: write # Create PRs + +jobs: + update-docs: + name: Update Recipe Documentation + runs-on: ubuntu-latest + + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for version comparison + fetch-tags: true # Fetch all tags so we can checkout version tags + + - name: Fetch upstream tags (for forks) + if: github.repository != 'block/goose' + run: | + # Add upstream remote and fetch tags (only needed when testing in forks) + git remote add upstream https://github.com/block/goose.git || git remote set-url upstream https://github.com/block/goose.git + git fetch upstream --tags --force + echo "✅ Fetched tags from upstream (fork mode)" + echo "Total tags available: $(git tag | wc -l)" + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y jq ripgrep + + - name: Set up Node.js + uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # pin@v3 + with: + node-version: '20' + + - name: Install goose CLI + run: | + mkdir -p /home/runner/.local/bin + curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh \ + | CONFIGURE=false GOOSE_BIN_DIR=/home/runner/.local/bin bash + echo "/home/runner/.local/bin" >> $GITHUB_PATH + goose --version + + - name: Configure goose for CI + env: + GOOSE_PROVIDER: ${{ vars.GOOSE_PROVIDER || 'openai' }} + GOOSE_MODEL: ${{ vars.GOOSE_MODEL || 'gpt-4o' }} + run: | + mkdir -p ~/.config/goose + cat < ~/.config/goose/config.yaml + GOOSE_PROVIDER: $GOOSE_PROVIDER + GOOSE_MODEL: $GOOSE_MODEL + keyring: false + EOF + echo "✅ Created goose config:" + cat ~/.config/goose/config.yaml + + - name: Determine versions to compare + id: versions + env: + GH_TOKEN: ${{ github.token }} + INPUT_OLD_VERSION: ${{ github.event.inputs.old_version }} + INPUT_NEW_VERSION: ${{ github.event.inputs.new_version }} + EVENT_NAME: ${{ github.event_name }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + get_previous_release() { + gh release list --limit 2 --json tagName --jq '.[].tagName' | sed -n '2p' + } + + if [ -n "$INPUT_OLD_VERSION" ]; then + OLD_VERSION="$INPUT_OLD_VERSION" + else + OLD_VERSION=$(get_previous_release) + fi + + if [ -n "$INPUT_NEW_VERSION" ]; then + NEW_VERSION="$INPUT_NEW_VERSION" + elif [ "$EVENT_NAME" = "release" ]; then + NEW_VERSION="$RELEASE_TAG" + else + NEW_VERSION="HEAD" # For testing unreleased changes + fi + + if [ -z "$OLD_VERSION" ] || [ -z "$NEW_VERSION" ]; then + echo "Error: Could not determine versions to compare" + exit 1 + fi + + echo "old_version=$OLD_VERSION" >> $GITHUB_OUTPUT + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "OLD_VERSION=$OLD_VERSION" >> $GITHUB_ENV + echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV + + echo "✅ Comparing $OLD_VERSION → $NEW_VERSION" + + - name: Extract and compare schemas + id: extract + timeout-minutes: 15 + working-directory: documentation/automation/recipe-schema-tracking + env: + GOOSE_REPO: ${{ github.workspace }} + run: | + set -o pipefail # Ensure pipeline failures are caught + + mkdir -p output + ./scripts/run-pipeline.sh "$OLD_VERSION" "$NEW_VERSION" 2>&1 | tee output/pipeline.log + + HAS_CHANGES=$(jq -r '.has_changes' output/validation-changes.json) + echo "has_changes=$HAS_CHANGES" >> $GITHUB_OUTPUT + + if [ "$HAS_CHANGES" = "false" ]; then + echo "✅ No changes detected" + else + echo "✅ Changes detected" + fi + + - name: Update recipe-reference.md (AI synthesis) + if: steps.extract.outputs.has_changes == 'true' + timeout-minutes: 10 + working-directory: documentation/automation/recipe-schema-tracking/output + env: + RECIPE_REF_PATH: ${{ github.workspace }}/documentation/docs/guides/recipes/recipe-reference.md + run: | + echo "🔍 Environment diagnostics:" + echo " GOOSE_PROVIDER: $GOOSE_PROVIDER" + echo " GOOSE_MODEL: $GOOSE_MODEL" + echo " OPENAI_API_KEY: ${OPENAI_API_KEY:0:8}..." # Show first 8 chars only + echo " RECIPE_REF_PATH: $RECIPE_REF_PATH" + echo " HOME: $HOME" + echo " PATH: $PATH" + echo "" + echo "📁 Goose config file:" + cat ~/.config/goose/config.yaml || echo "Config file not found!" + echo "" + echo "📁 Current directory:" + pwd + ls -la + echo "" + echo "🤖 Step 1: Running validation changes synthesis..." + goose run --recipe ../recipes/synthesize-validation-changes.yaml + + echo "" + echo "🤖 Step 2: Applying changes to recipe-reference.md..." + goose run --recipe ../recipes/update-recipe-reference.yaml + + - name: Upload automation outputs + if: always() + uses: actions/upload-artifact@v4 + with: + name: recipe-docs-update-${{ steps.versions.outputs.old_version }}-to-${{ steps.versions.outputs.new_version }} + path: | + documentation/automation/recipe-schema-tracking/output/*.json + documentation/automation/recipe-schema-tracking/output/*.md + documentation/automation/recipe-schema-tracking/output/*.log + retention-days: 30 + + - name: Create Pull Request + if: | + steps.extract.outputs.has_changes == 'true' && + (github.event.inputs.dry_run != 'true' || github.event_name == 'release') + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6 + with: + branch: docs/auto-recipe-reference-${{ steps.versions.outputs.new_version }} + delete-branch: true + + commit-message: | + docs: Update recipe reference for ${{ steps.versions.outputs.new_version }} + + Automated update based on schema changes between ${{ steps.versions.outputs.old_version }} and ${{ steps.versions.outputs.new_version }}. + + title: "docs: Update Recipe Reference Guide for ${{ steps.versions.outputs.new_version }}" + body: | + ## Summary + + This PR updates the Recipe Reference Guide based on schema and validation changes detected between **${{ steps.versions.outputs.old_version }}** and **${{ steps.versions.outputs.new_version }}**. + + ### Type of Change + - [x] Documentation + + ### AI Assistance + - [x] This PR was created or reviewed with AI assistance + + #### 🤖 Automation Details + + - **Workflow**: `docs-update-recipe-ref.yml` + - **Triggered by**: ${{ github.event_name }} + - **Previous version**: ${{ steps.versions.outputs.old_version }} + - **New version**: ${{ steps.versions.outputs.new_version }} + + #### 📋 Changes Detected + + Review the workflow artifacts for detailed change analysis: + - `validation-changes.json` - Structured diff of changes + - `validation-changes.md` - Human-readable change documentation + - `update-summary.md` - Summary of documentation updates applied + + Download artifacts from the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). + + ### ✅ Review Checklist + + - [ ] Verify all schema changes are accurately documented + - [ ] Check that examples are updated correctly + - [ ] Ensure validation rules are clearly explained + - [ ] Confirm no unintended changes were made + - [ ] Do changes require additional updates in this or other recipe topics? + + ### 🔗 Related + + - Release: ${{ github.event.release.html_url || 'N/A' }} + + --- + + *This PR was automatically generated by the Recipe Documentation Automation workflow.* + + labels: | + documentation + automated + recipe-reference + + - name: Workflow summary + if: always() + env: + OLD_VERSION: ${{ steps.versions.outputs.old_version }} + NEW_VERSION: ${{ steps.versions.outputs.new_version }} + HAS_CHANGES: ${{ steps.extract.outputs.has_changes }} + DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} + run: | + echo "## 📊 Recipe Documentation Update Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version Comparison**: $OLD_VERSION → $NEW_VERSION" >> $GITHUB_STEP_SUMMARY + echo "**Changes Detected**: $HAS_CHANGES" >> $GITHUB_STEP_SUMMARY + echo "**Dry Run Mode**: $DRY_RUN" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "$HAS_CHANGES" = "true" ]; then + echo "### ✅ Documentation Updated" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The Recipe Reference Guide has been updated to reflect changes in $NEW_VERSION." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "$DRY_RUN" = "true" ]; then + echo "**Note**: Running in dry-run mode - no PR was created. Review the artifacts to see the generated changes." >> $GITHUB_STEP_SUMMARY + else + echo "A pull request has been created with the documentation updates." >> $GITHUB_STEP_SUMMARY + fi + else + echo "### ℹ️ No Changes Needed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "No recipe schema or validation changes were detected between $OLD_VERSION and $NEW_VERSION." >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 📦 Artifacts" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Download the workflow artifacts to review:" >> $GITHUB_STEP_SUMMARY + echo "- Extracted schemas and validation structures" >> $GITHUB_STEP_SUMMARY + echo "- Change detection results" >> $GITHUB_STEP_SUMMARY + echo "- Human-readable change documentation" >> $GITHUB_STEP_SUMMARY + echo "- Documentation update summary" >> $GITHUB_STEP_SUMMARY diff --git a/documentation/automation/README.md b/documentation/automation/README.md new file mode 100644 index 000000000000..91c5f501b51d --- /dev/null +++ b/documentation/automation/README.md @@ -0,0 +1,58 @@ +# Documentation Automation + +This directory contains automated pipelines for keeping goose documentation synchronized with code changes. + +## Overview + +Each automation project tracks specific types of changes and updates corresponding documentation: + +| Project | Status | Tracks | Updates | +|---------|--------|--------|---------| +| [recipe-schema-tracking](./recipe-schema-tracking/) | ✅ Active | Recipe schema & validation rules | Recipe Reference Guide | +| cli-command-tracking | 🔮 Planned | CLI commands & options | CLI documentation | +| provider-tracking | 🔮 Planned | Supported AI providers | Provider documentation | +| extension-tracking | 🔮 Planned | Built-in extensions | Extension documentation | + +## Architecture + +Each automation project follows a consistent pattern: + +``` +project-name/ +├── README.md # Project-specific documentation +├── TESTING.md # How to test this automation +├── config/ # Configuration files +├── scripts/ # Deterministic extraction/diff scripts +└── recipes/ # AI-powered synthesis/update recipes +``` + +### Design Principles + +1. **Modular**: Each project is self-contained +2. **Testable**: Clear inputs/outputs at each stage +3. **Transparent**: Intermediate files can be inspected +4. **Reusable**: Common patterns across projects + +### Hybrid Approach + +- **Shell scripts**: Deterministic extraction and comparison +- **AI recipes**: Synthesis and documentation updates + +## GitHub Actions Integration + +Automation projects can be triggered via GitHub Actions workflows in `.github/workflows/`. + +See individual project TESTING.md files for workflow usage. + +## Adding New Automations + +When creating a new automation project: + +1. Create a subdirectory: `documentation/automation/your-project/` +2. Follow the standard structure (README, TESTING, config, scripts, recipes) +3. Create corresponding GitHub Actions workflow (if needed) +4. Update this README with the new project + +## Questions? + +For project-specific questions, see the README in each project directory. diff --git a/documentation/automation/recipe-schema-tracking/.gitignore b/documentation/automation/recipe-schema-tracking/.gitignore new file mode 100644 index 000000000000..6d4efdc56603 --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/.gitignore @@ -0,0 +1,5 @@ +# Generated output files +output/ + +# macOS +.DS_Store diff --git a/documentation/automation/recipe-schema-tracking/README.md b/documentation/automation/recipe-schema-tracking/README.md new file mode 100644 index 000000000000..1e9015ec2931 --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/README.md @@ -0,0 +1,398 @@ +# Recipe Schema Tracking + +Automated pipeline for detecting and documenting Recipe schema and validation rule changes between goose releases. + +## Overview + +This automation keeps the [Recipe Reference Guide](https://block.github.io/goose/docs/guides/recipes/recipe-reference) synchronized with code changes by: + +1. **Extracting** schema and validation rules from source code (deterministic) +2. **Detecting** changes between versions (deterministic diff) +3. **Synthesizing** human-readable change documentation (AI-powered) +4. **Updating** the Core Recipe Schema, Field Specifications, and Validation Rule sections in the Recipe Reference Guide (AI-powered) + +The automation runs automatically on new releases via GitHub Actions, or can be run manually for testing. + +## Quick Start + +### Automated (GitHub Actions) + +The automation runs automatically when a new release is published. See [TESTING.md](./TESTING.md) for testing instructions. + +### Manual (Local Testing) + +```bash +# Run the complete pipeline +./scripts/run-pipeline.sh v1.14.0 v1.15.0 + +# Or run individual steps: +# 1. Extract validation structures +./scripts/extract-validation-structure.sh v1.14.0 > output/old-validation-structure.json +./scripts/extract-validation-structure.sh v1.15.0 > output/new-validation-structure.json + +# 2. Extract schemas +./scripts/extract-schema.sh v1.15.0 > output/new-schema.json + +# 3. Detect changes +./scripts/diff-validation-structures.sh output/old-validation-structure.json \ + output/new-validation-structure.json \ + > output/validation-changes.json + +# 4. Generate human-readable change documentation +cd output && goose run --recipe ../recipes/synthesize-validation-changes.yaml + +# 5. Update recipe-reference.md +export RECIPE_REF_PATH=/path/to/recipe-reference.md +goose run --recipe ../recipes/update-recipe-reference.yaml +``` + +## Architecture + +### Modular Pipeline Design + +The automation uses a **hybrid approach**: deterministic shell scripts for data extraction/diffing, AI recipes for analysis and documentation updates. + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ EXTRACTION (Deterministic) │ +├─────────────────────────────────────────────────────────────────┤ +│ extract-schema.sh extract-validation-structure.sh │ +│ ↓ ↓ │ +│ new-schema.json new-validation-structure.json │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ DIFFING (Deterministic) │ +├─────────────────────────────────────────────────────────────────┤ +│ diff-validation-structures.sh │ +│ ↓ │ +│ validation-changes.json │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ SYNTHESIS (AI-Powered) │ +├─────────────────────────────────────────────────────────────────┤ +│ synthesize-validation-changes.yaml │ +│ ↓ │ +│ validation-changes.md (human-readable) │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ UPDATE (AI-Powered) │ +├─────────────────────────────────────────────────────────────────┤ +│ update-recipe-reference.yaml │ +│ ↓ │ +│ recipe-reference.md (updated) + update-summary.md │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Why This Design? + +**Scripts handle deterministic tasks:** +- Version-specific code extraction using `git show` +- JSON schema parsing and comparison +- No interpretation or inference - direct text extraction + +**AI recipes handle synthesis and updates:** +- Analyzing changes and explaining implications +- Generating migration guidance and examples +- Updating documentation with proper formatting and context + +**Benefits:** +- **Reliability**: Extraction is deterministic and reproducible +- **Testability**: Each stage has clear inputs/outputs +- **Maintainability**: Easy to update individual components +- **Transparency**: Intermediate files can be inspected + +### Data Flow + +All stages communicate via JSON/Markdown files in the `output/` directory: + +| File | Producer | Consumer | Purpose | +|------|----------|----------|---------| +| `old-schema.json` | `extract-schema.sh` | `synthesize-validation-changes.yaml` | Previous version OpenAPI schema | +| `new-schema.json` | `extract-schema.sh` | `synthesize-validation-changes.yaml` | Current version OpenAPI schema | +| `old-validation-structure.json` | `extract-validation-structure.sh` | `diff-validation-structures.sh` | Previous version struct fields + validation functions | +| `new-validation-structure.json` | `extract-validation-structure.sh` | `diff-validation-structures.sh` | Current version struct fields + validation functions | +| `validation-changes.json` | `diff-validation-structures.sh` | `synthesize-validation-changes.yaml` | Detected changes (structured) | +| `validation-changes.md` | `synthesize-validation-changes.yaml` | `update-recipe-reference.yaml` | Human-readable change documentation | +| `update-summary.md` | `update-recipe-reference.yaml` | Human review | Summary of documentation updates | + +## Configuration + +### Environment Variables + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `RECIPE_REF_PATH` | No | - | Full path to `recipe-reference.md` file (overrides `GOOSE_REPO` construction) | +| `GOOSE_REPO` | No | Auto-detect | Path to goose repository root | + +**Example (for local testing):** +```bash +export RECIPE_REF_PATH=/path/to/local/goose/documentation/docs/guides/recipes/recipe-reference.md +# OR +export GOOSE_REPO=/path/to/local/goose +``` + +### Configuration Files + +#### `config/serde-attributes.json` + +Defines Serde attribute meanings for parsing struct fields: + +```json +{ + "skip_serializing_if": "Field is optional and skipped when value matches condition", + "default": "Field uses default value when missing during deserialization", + "flatten": "Field's contents are flattened into parent struct", + "rename": "Field is serialized with a different name" +} +``` + +**When to update:** When new Serde attributes are introduced in Recipe struct definitions. + +#### `config/known-validation-files.json` + +Lists source files containing recipe validation logic: + +```json +{ + "validation_files": [ + "crates/goose/src/recipe/validate_recipe.rs", + "crates/goose/src/agents/types.rs" + ] +} +``` + +**When to update:** When validation logic is added to new files or moved to different locations. + +### Scope and Exclusions + +#### In Scope +- Top-level Recipe struct fields (all fields in `Recipe` struct) +- Validation functions in `validate_recipe.rs` +- Field types, optionality, and default values +- Validation error messages and requirements +- Enum value changes (e.g., new input types) + +#### Excluded (By Design) +- **Extension schema deep-dives**: Extensions use a dual-purpose type (`ExtensionConfig`) shared across recipes, CLI, and runtime with mismatched validation requirements. The automation documents basic structure only. Extension-specific validation is documented separately. + +**Why extensions are excluded:** The `ExtensionConfig` type serves multiple contexts with different validation needs: +- **Recipe context**: Looser validation for user-authored configurations +- **CLI context**: Stricter validation for command-line arguments +- **Runtime context**: Additional validation for server connections + +Attempting to document all extension validation rules in the Recipe Reference would create confusion about which rules apply when. Extension documentation is maintained separately. + +## Scripts + +### `extract-schema.sh` + +Extracts OpenAPI schema from the goose codebase. + +**Usage:** +```bash +./scripts/extract-schema.sh [version] > output/new-schema.json +``` + +**Arguments:** +- `version` (optional): Git tag or commit to extract from (default: current working directory) + +**Output:** JSON schema with field descriptions, types, and constraints + +**Example:** +```bash +# Extract from current code +./scripts/extract-schema.sh > output/new-schema.json + +# Extract from specific version +./scripts/extract-schema.sh v1.15.0 > output/old-schema.json +``` + +### `extract-validation-structure.sh` + +Extracts Recipe struct fields and validation functions from source code. + +**Usage:** +```bash +./scripts/extract-validation-structure.sh [version] > output/new-validation-structure.json +``` + +**Arguments:** +- `version` (optional): Git tag or commit to extract from (default: current working directory) + +**Output:** JSON with struct fields (name, type, optionality, comments) and validation functions (signature, error messages) + +**Example:** +```bash +# Extract from current code +./scripts/extract-validation-structure.sh > output/new-validation-structure.json + +# Extract from v1.15.0 +./scripts/extract-validation-structure.sh v1.15.0 > output/old-validation-structure.json +``` + +### `diff-validation-structures.sh` + +Compares two validation structure files and outputs detected changes. + +**Usage:** +```bash +./scripts/diff-validation-structures.sh > output/validation-changes.json +``` + +**Arguments:** +- `old-file`: Path to old validation structure JSON +- `new-file`: Path to new validation structure JSON + +**Output:** JSON with categorized changes: +- `struct_fields.added`: New fields +- `struct_fields.removed`: Deleted fields +- `struct_fields.type_changed`: Type modifications +- `struct_fields.comment_changed`: Comment updates +- `validation_functions.added`: New validation rules +- `validation_functions.removed`: Deleted validation rules + +**Example:** +```bash +./scripts/diff-validation-structures.sh \ + output/old-validation-structure.json \ + output/new-validation-structure.json \ + > output/validation-changes.json +``` + +## Recipes + +### `synthesize-validation-changes.yaml` + +Analyzes detected changes and generates human-readable documentation. + +**Inputs:** +- `output/validation-changes.json` - Detected changes from diff script +- `output/old-schema.json` - Previous version schema (for descriptions) +- `output/new-schema.json` - Current version schema (for descriptions) + +**Output:** +- `output/validation-changes.md` - Human-readable change documentation with: + - Breaking changes with migration guidance + - Non-breaking changes with usage examples + - Validation rule additions/removals/modifications + - Migration checklist + +**Usage:** +```bash +cd output +goose run --recipe ../recipes/synthesize-validation-changes.yaml +``` + +**What it does:** +- Compares old and new schemas to detect enum changes and required field changes +- Analyzes struct field changes (additions, removals, type changes) +- Explains validation rule changes with examples +- Generates migration guidance for breaking changes +- Creates actionable checklist for recipe authors + +### `update-recipe-reference.yaml` + +Updates the Recipe Reference Guide based on synthesized changes. + +**Inputs:** +- `output/validation-changes.md` - Change documentation from synthesis recipe +- `recipe-reference.md` - Target documentation file (path from `RECIPE_REF_PATH` or `GOOSE_REPO` env var) + +**Outputs:** +- Updated `recipe-reference.md` with changes applied +- `output/update-summary.md` - Summary of changes for review + +**Usage:** +```bash +export RECIPE_REF_PATH=/path/to/recipe-reference.md +goose run --recipe recipes/update-recipe-reference.yaml +``` + +**What it does:** +- Updates Core Recipe Schema table (field additions/removals/type changes) +- Adds/removes/updates Field Specification sections for complex fields +- Updates Validation Rules section with new/modified/removed rules +- Updates enum lists in Field Specifications (e.g., input types) +- Generates summary of all changes for review + +**Target sections:** +1. **Core Recipe Schema table** - Field-level changes +2. **Field Specifications sections** - Detailed documentation for complex fields +3. **Validation Rules section** - Validation function changes + +## Directory Structure + +``` +recipe-schema-tracking/ +├── README.md # This file +├── TESTING.md # Testing guide for GitHub Actions workflow +├── .gitignore # Excludes output/ directory +├── config/ # Configuration files +│ ├── serde-attributes.json # Serde attribute definitions +│ ├── known-validation-files.json # Validation source files +│ ├── extraction-output-schema.json # Schema for extraction output +│ └── validation-output-schema.json # Schema for validation output +├── scripts/ # Shell scripts (deterministic) +│ ├── extract-schema.sh # Extract OpenAPI schema +│ ├── extract-validation-structure.sh # Extract struct fields + validation +│ ├── diff-validation-structures.sh # Compare structures +│ └── run-pipeline.sh # End-to-end pipeline runner +├── recipes/ # AI recipes +│ ├── synthesize-validation-changes.yaml # Generate change docs +│ └── update-recipe-reference.yaml # Update documentation +└── output/ # Generated files (gitignored) + ├── old-schema.json # Previous version schema + ├── new-schema.json # Current version schema + ├── old-validation-structure.json # Previous version structure + ├── new-validation-structure.json # Current version structure + ├── validation-changes.json # Detected changes (structured) + ├── validation-changes.md # Change documentation (human-readable) + ├── update-summary.md # Documentation update summary + └── pipeline.log # Pipeline execution log +``` + +## GitHub Actions Workflow + +The automation runs via `.github/workflows/docs-update-recipe-ref.yml`: + +- **Trigger**: Automatically on new releases, or manually for testing +- **Process**: Extracts schemas, detects changes, updates documentation +- **Output**: Creates a PR with updated `recipe-reference.md` if changes detected +- **Testing**: See [TESTING.md](./TESTING.md) for detailed testing instructions + +## What Gets Tracked + +### Struct Fields (6 structs) +- `Recipe` - Top-level recipe structure +- `Author` - Recipe author information +- `Settings` - Recipe settings (model, provider, etc.) +- `Response` - Structured output schema +- `SubRecipe` - Sub-recipe definitions +- `RecipeParameter` - Parameter definitions + +### Changes Detected +- ✅ Fields added/removed +- ✅ Field type changes (e.g., `Option` → `T`) +- ✅ Comment changes (inline documentation) +- ✅ Validation functions added/removed/modified +- ✅ Error messages changed +- ✅ Enum value changes + +## Maintenance + +When modifying the automation: + +1. **Test locally first**: Run `./scripts/run-pipeline.sh` with test versions +2. **Verify outputs**: Check generated files against source code +3. **Update configuration**: If validation files move or new attributes added +4. **Test in fork**: Use GitHub Actions workflow with dry-run mode +5. **Document changes**: Update this README with design decisions + +## Related Documentation + +- [TESTING.md](./TESTING.md) - How to test the GitHub Actions workflow +- [Automation Overview](../README.md) - All automation projects +- [Recipe Reference Guide](../../docs/guides/recipes/recipe-reference.md) - Target documentation diff --git a/documentation/automation/recipe-schema-tracking/TESTING.md b/documentation/automation/recipe-schema-tracking/TESTING.md new file mode 100644 index 000000000000..947a815aa575 --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/TESTING.md @@ -0,0 +1,229 @@ +# Testing Recipe Schema Tracking Automation + +This guide covers how to test the recipe schema tracking automation both locally and via GitHub Actions. + +## Local Testing + +### Prerequisites + +- goose CLI installed +- jq installed (for JSON processing) +- Git repository with goose source code + +### Manual Pipeline Execution + +Test the complete pipeline locally: + +```bash +cd documentation/automation/recipe-schema-tracking + +# Test with no changes expected +./scripts/run-pipeline.sh v1.14.0 v1.15.0 + +# Test with changes expected +./scripts/run-pipeline.sh v1.9.0 v1.15.0 +``` + +### Individual Script Testing + +Test each script independently: + +```bash +# Extract schema from a version +./scripts/extract-schema.sh v1.15.0 > output/test-schema.json + +# Extract validation structure +./scripts/extract-validation-structure.sh v1.15.0 > output/test-validation.json + +# Compare two validation structures +./scripts/diff-validation-structures.sh output/old.json output/new.json > output/test-changes.json +``` + +### Recipe Testing + +Test the AI recipes: + +```bash +# Generate change documentation +cd output +goose run --recipe ../recipes/synthesize-validation-changes.yaml + +# Update recipe-reference.md +export RECIPE_REF_PATH=/path/to/recipe-reference.md +goose run --recipe ../recipes/update-recipe-reference.yaml +``` + +## GitHub Actions Testing + +### Test in Your Fork + +The workflow can be tested in your fork without affecting the upstream repository. + +#### Step 1: Push Branch to Fork + +```bash +git push origin your-branch-name +``` + +#### Step 2: Enable GitHub Actions + +1. Go to your fork on GitHub +2. Click "Actions" tab +3. Enable workflows if prompted + +#### Step 3: Run Workflow Manually + +1. Click "Update Recipe Documentation" workflow (docs-update-recipe-ref.yml) +2. Click "Run workflow" button +3. Select your branch +4. Configure inputs (see test scenarios below) +5. Click "Run workflow" + +### Test Scenarios + +#### Scenario 1: Dry-Run with No Changes + +**Purpose**: Verify the workflow runs successfully when no changes are detected. + +**Inputs**: +- `old_version`: `v1.14.0` +- `new_version`: `v1.15.0` +- `dry_run`: `true` + +**Expected Results**: +- ✅ Workflow completes successfully +- ✅ "No changes detected" message in summary +- ✅ Artifacts uploaded with extraction results +- ✅ No PR created + +#### Scenario 2: Dry-Run with Changes + +**Purpose**: Test change detection and documentation generation without creating a PR. + +**Inputs**: +- `old_version`: `v1.9.0` +- `new_version`: `v1.15.0` +- `dry_run`: `true` + +**Expected Results**: +- ✅ Workflow detects changes (4 validation rules, 1 field removal) +- ✅ Generates `validation-changes.md` with documentation +- ✅ Updates `recipe-reference.md` +- ✅ Artifacts uploaded with all generated files +- ✅ No PR created (dry-run mode) + +**Review Artifacts**: +1. Download artifact zip from workflow run +2. Check `validation-changes.md` - should document all changes +3. Check `update-summary.md` - should show what was updated +4. Compare updated `recipe-reference.md` with original + +#### Scenario 3: Full Run with PR Creation + +**Purpose**: Test end-to-end including PR creation. + +**Inputs**: +- `old_version`: `v1.9.0` +- `new_version`: `v1.15.0` +- `dry_run`: `false` + +**Expected Results**: +- ✅ Workflow runs successfully +- ✅ Creates PR: `docs/recipe-reference-v1.15.0` +- ✅ PR contains updated `recipe-reference.md` +- ✅ PR description includes change summary and checklist + +**Review PR**: +1. Check only `recipe-reference.md` was modified +2. Verify changes match dry-run artifacts +3. Confirm no unintended modifications +4. Test documentation renders correctly + +#### Scenario 4: Auto-Detection + +**Purpose**: Test automatic version detection (simulates production mode). + +**Inputs**: +- `old_version`: *(leave empty)* +- `new_version`: *(leave empty)* +- `dry_run`: `true` + +**Expected Results**: +- ✅ Auto-detects two most recent releases +- ✅ Compares them automatically +- ✅ Uploads artifacts + +### Reviewing Workflow Results + +#### Check Workflow Summary + +Each workflow run provides a summary with: +- Version comparison performed +- Whether changes were detected +- Dry-run mode status +- Links to artifacts + +#### Download and Review Artifacts + +Artifacts include: +- `old-validation-structure.json` - Extracted from old version +- `new-validation-structure.json` - Extracted from new version +- `validation-changes.json` - Structured diff +- `validation-changes.md` - Human-readable changes +- `update-summary.md` - Documentation update summary +- `pipeline.log` - Full pipeline execution log + +#### Check Workflow Logs + +For detailed debugging: +1. Click on the workflow run +2. Click on the "Update Recipe Documentation" job +3. Expand each step to see detailed logs +4. Look for error messages or unexpected behavior + +## Troubleshooting + +### Workflow doesn't appear in Actions tab + +- Verify workflow file is in `.github/workflows/` +- Check file has `.yml` or `.yaml` extension +- Ensure GitHub Actions is enabled in fork + +### "No changes detected" when expecting changes + +- Check artifact `validation-changes.json` to see what was compared +- Verify versions exist: `git tag | grep v1.15.0` +- Review extraction script logs + +### goose CLI installation fails + +- Workflow installs from current repository +- Ensure `crates/goose-cli` builds successfully +- Check Rust toolchain installation + +### PR creation fails + +- Verify workflow has required permissions +- Check branch name doesn't already exist +- Review workflow logs for error messages + +## Production Deployment + +Once testing is complete: + +1. **Merge automation to main**: Create PR for `documentation/automation/recipe-schema-tracking/` +2. **Merge baseline docs**: Create PR for revised `recipe-reference.md` +3. **Merge workflow**: Create PR for `.github/workflows/docs-update-recipe-ref.yml` +4. **Enable release trigger**: Uncomment `release:` section in workflow + +After deployment, the workflow will automatically: +- Trigger on new releases +- Compare with previous release +- Create PR if changes detected +- Notify team for review + +## Related Documentation + +- [Recipe Schema Tracking README](./README.md) - Automation details +- [Recipe Reference Guide](../../docs/guides/recipes/recipe-reference.md) - Target documentation +- [Automation Overview](../README.md) - All automation projects diff --git a/documentation/automation/recipe-schema-tracking/config/extraction-output-schema.json b/documentation/automation/recipe-schema-tracking/config/extraction-output-schema.json new file mode 100644 index 000000000000..2d046a95a265 --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/config/extraction-output-schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Validation Structure Extraction Output Schema", + "description": "Schema for the output of extract-validation-structure.sh script", + "type": "object", + "required": ["version", "extracted_at", "struct_fields", "validation_functions"], + "properties": { + "version": { + "type": "string", + "description": "The goose version tag that was extracted (e.g., 'v1.15.0')" + }, + "extracted_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when extraction occurred" + }, + "struct_fields": { + "type": "array", + "description": "Array of Recipe struct fields extracted from mod.rs", + "items": { + "type": "object", + "required": ["field", "type", "optional", "inline_comment"], + "properties": { + "field": { + "type": "string", + "description": "Field name (e.g., 'version', 'title', 'instructions')" + }, + "type": { + "type": "string", + "description": "Rust type (e.g., 'String', 'Option', 'Option>')" + }, + "optional": { + "type": "boolean", + "description": "Whether the field is optional (true if type is Option)" + }, + "inline_comment": { + "type": "string", + "description": "Inline comment from source code after // (may be empty string if no comment)" + } + } + } + }, + "validation_functions": { + "type": "array", + "description": "Array of validation functions extracted from validate_recipe.rs", + "items": { + "type": "object", + "required": ["function", "signature", "error_messages", "code_snippet"], + "properties": { + "function": { + "type": "string", + "description": "Function name (e.g., 'validate_prompt_or_instructions')" + }, + "signature": { + "type": "string", + "description": "Full function signature from source code" + }, + "error_messages": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of error message strings extracted from anyhow::anyhow! calls" + }, + "code_snippet": { + "type": "string", + "description": "Relevant code excerpt (currently placeholder '...')" + } + } + } + } + } +} diff --git a/documentation/automation/recipe-schema-tracking/config/known-validation-files.json b/documentation/automation/recipe-schema-tracking/config/known-validation-files.json new file mode 100644 index 000000000000..e1731c2df9b9 --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/config/known-validation-files.json @@ -0,0 +1,17 @@ +{ + "description": "List of source files containing recipe validation logic", + "validation_files": [ + "crates/goose/src/recipe/validate_recipe.rs", + "crates/goose/src/recipe/mod.rs" + ], + "exclude_patterns": [ + "extension", + "build_recipe", + "template_recipe" + ], + "notes": { + "validate_recipe.rs": "Contains parse-time validation functions (validate_prompt_or_instructions, validate_optional_parameters, etc.)", + "mod.rs": "Contains Recipe struct definition with field types and serde annotations", + "exclude_patterns": "Patterns to exclude: extensions (manual docs), build_recipe (runtime validation), template_recipe (rendering validation)" + } +} diff --git a/documentation/automation/recipe-schema-tracking/config/serde-attributes.json b/documentation/automation/recipe-schema-tracking/config/serde-attributes.json new file mode 100644 index 000000000000..6c54ff8fb237 --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/config/serde-attributes.json @@ -0,0 +1,79 @@ +{ + "version": "v1.15.0", + "last_updated": "2025-11-21", + "description": "Serde attributes that affect deserialization behavior and defaults. This file should be manually updated when serde attributes change in the codebase.", + + "fields_with_defaults": [ + { + "type": "Recipe", + "field": "version", + "rust_type": "String", + "serde_attribute": "#[serde(default = \"default_version\")]", + "default_value": "1.0.0", + "default_function": "default_version", + "location": "crates/goose/src/recipe/mod.rs:27-29", + "notes": "Users can omit this field and it will default to 1.0.0. This explains why the OpenAPI schema marks it as optional (can be omitted) while the struct marks it as required (not nullable)." + }, + { + "type": "SubRecipe", + "field": "sequential_when_repeated", + "rust_type": "bool", + "serde_attribute": "#[serde(default)]", + "default_value": false, + "default_function": null, + "location": "crates/goose/src/recipe/mod.rs:102", + "notes": "Uses Rust's Default trait for bool, which returns false. Controls whether repeated sub-recipe calls execute sequentially or in parallel." + }, + { + "type": "SubRecipe", + "field": "values", + "rust_type": "Option>", + "serde_attribute": "#[serde(default, deserialize_with = \"deserialize_value_map_as_string\")]", + "default_value": null, + "default_function": null, + "custom_deserializer": "deserialize_value_map_as_string", + "location": "crates/goose/src/recipe/mod.rs:100-101", + "notes": "Uses Rust's Default trait for Option, which returns None. Also has custom deserializer that converts any JSON value to string representation." + } + ], + + "custom_deserializers": [ + { + "field": "Recipe.extensions", + "function": "recipe_extension_adapter::deserialize_recipe_extensions", + "location": "crates/goose/src/recipe/recipe_extension_adapter.rs", + "purpose": "Handles complex extension type deserialization with multiple variants (sse, stdio, builtin, platform, streamable_http, frontend, inline_python)", + "notes": "This is why extensions have complex validation - the deserializer handles type discrimination and field requirements vary by extension type" + }, + { + "field": "SubRecipe.values", + "function": "deserialize_value_map_as_string", + "location": "crates/goose/src/recipe/mod.rs:104-120", + "purpose": "Converts any JSON value (number, bool, object, etc.) to string representation for parameter passing to sub-recipes", + "example": "{\"count\": 42} becomes {\"count\": \"42\"}", + "notes": "This allows flexible parameter passing where all values are normalized to strings" + } + ], + + "maintenance_checklist": [ + "Update this file when:", + " - New fields with #[serde(default)] are added to Recipe or nested types", + " - Default values or default functions change", + " - New custom deserializers are added", + " - Serde attributes are modified (rename_all, skip_serializing_if, etc.)", + "", + "How to check:", + " 1. Run: rg '#\\[serde.*default' crates/goose/src/recipe/", + " 2. Run: rg 'deserialize_with' crates/goose/src/recipe/", + " 3. Compare results against this file", + " 4. Update version field to match checked version", + " 5. Update last_updated timestamp" + ], + + "validation_notes": [ + "The analyze-validation-changes.yaml recipe should:", + " - Read this file to document default values in validation rules", + " - Flag any discrepancies between this file and extracted struct fields", + " - Note custom deserializers when documenting field behavior" + ] +} diff --git a/documentation/automation/recipe-schema-tracking/config/validation-output-schema.json b/documentation/automation/recipe-schema-tracking/config/validation-output-schema.json new file mode 100644 index 000000000000..afd2329e107a --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/config/validation-output-schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Recipe Validation Rules Output Schema", + "description": "Schema for validation rule discovery output", + "type": "object", + "required": ["version", "extracted_at", "validation_rules"], + "properties": { + "version": { + "type": "string", + "description": "The goose version these rules apply to" + }, + "extracted_at": { + "type": "string", + "format": "date-time", + "description": "ISO timestamp when rules were extracted" + }, + "validation_rules": { + "type": "array", + "description": "List of validation rules", + "items": { + "type": "object", + "required": ["rule_id", "category", "description", "affected_fields", "error_message"], + "properties": { + "rule_id": { + "type": "string", + "description": "Unique identifier for the rule (e.g., 'prompt_or_instructions_required')" + }, + "category": { + "type": "string", + "description": "Category of validation (e.g., 'required_fields', 'parameters', 'structure')", + "enum": ["required_fields", "parameters", "structure", "schema", "cross_field"] + }, + "description": { + "type": "string", + "description": "Human-readable description of the validation rule" + }, + "affected_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of Recipe fields this rule validates (e.g., ['instructions', 'prompt'])" + }, + "requirement_type": { + "type": "string", + "description": "Type of requirement (e.g., 'at_least_one', 'conditional', 'required', 'optional')" + }, + "condition": { + "type": "string", + "description": "Condition when rule applies (e.g., 'When requirement=optional')" + }, + "error_message": { + "type": "string", + "description": "Exact error message users see when validation fails" + }, + "validation_function": { + "type": "string", + "description": "Name of the validation function in code (e.g., 'validate_optional_parameters')" + }, + "introduced_in": { + "type": "string", + "description": "Version when rule was introduced (default: 'unknown' for V1)" + }, + "last_modified": { + "type": "string", + "description": "Version when rule was last modified" + }, + "code_snippet": { + "type": "string", + "description": "Relevant code snippet showing validation logic" + } + } + } + } + } +} diff --git a/documentation/automation/recipe-schema-tracking/recipes/synthesize-validation-changes.yaml b/documentation/automation/recipe-schema-tracking/recipes/synthesize-validation-changes.yaml new file mode 100644 index 000000000000..79c939b48ee4 --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/recipes/synthesize-validation-changes.yaml @@ -0,0 +1,254 @@ +version: "2" +title: "Synthesize Validation Changes" +description: "Generate human-readable documentation for validation rule changes between two versions" + +extensions: + - type: builtin + name: developer + +instructions: | + You are a technical documentation specialist creating release notes for Recipe validation changes. + + ## Your Task + + Analyze the validation changes between two goose versions and generate clear, user-focused + documentation explaining what changed and why it matters. + + ## Input Files + + You have access to SIX data sources: + + 1. **validation-changes.json** - The diff (what changed): + - struct_fields: added, removed, type_changed, comment_changed + - validation_functions: added, removed, signature_changed, error_messages_changed + + 2. **old-validation-structure.json** - Before state (for context): + - Complete struct fields from old version + - Complete validation functions from old version + + 3. **new-validation-structure.json** - After state (for context): + - Complete struct fields from new version + - Complete validation functions from new version + + 4. **old-schema.json** - Previous OpenAPI schema (for enum comparisons): + - Field descriptions from old version + - Enum values from old version + - Required fields from old version + + 5. **new-schema.json** - Current OpenAPI schema (for descriptions): + - Field descriptions (authoritative) + - Enum values (compare with old to detect additions) + - Nested type structures + - Required fields (compare with old to detect requirement changes) + + 6. **../config/serde-attributes.json** - Serde defaults: + - Fields with default values + - Custom deserializers + + ## Output Format + + Create a Markdown file (validation-changes.md) with this structure: + + IMPORTANT: Use indented code blocks (4 spaces) instead of fenced code blocks with backticks + to avoid triggering security alerts. + + # Recipe Validation Changes + + **From**: {old_version} + **To**: {new_version} + **Analyzed**: {timestamp} + + ## Summary + + Brief overview of changes (2-3 sentences). + + ## Field Changes + + ### Added Fields + + For each added field: + - **Struct.field_name** (Type) - ALWAYS include the struct name prefix + - Description: [from schema] + - Default: [from serde-attributes if applicable] + - Impact: How this affects recipe authors + + ### Removed Fields + + For each removed field: + - **Struct.field_name** (was Type) - ALWAYS include the struct name prefix + - Reason: Why it was removed (infer from context) + - Migration: How to update existing recipes + + ### Type Changes + + For each type change: + - **Struct.field_name** - ALWAYS include the struct name prefix + - Old: old_type + - New: new_type + - Impact: What this means for recipe authors + - Breaking: Yes/No + + ### Comment Changes + + Only document if the comment change indicates a semantic change. + Skip if it's just formatting or trivial clarification. + + ## Schema Changes + + Changes detected by comparing old-schema.json and new-schema.json: + + ### Enum Value Additions + + For each enum with new values: + - **Field.enum_name** - Field where enum is used + - Added Values: List of new enum values + - Purpose: Why these values were added + - Impact: New capabilities available + + ### Enum Value Removals + + For each enum with removed values: + - **Field.enum_name** - Field where enum is used + - Removed Values: List of removed enum values + - Reason: Why these values were removed + - Impact: Breaking change if recipes use removed values + + ### Required Field Changes + + For fields added/removed from "required" arrays: + - **Field_name** + - Change: Added to required / Removed from required + - Impact: Breaking change or relaxed validation + + ## Validation Rule Changes + + ### New Validation Rules + + For each added validation function: + - **Rule**: {Descriptive name} + - Function: function_name + - Purpose: What this rule validates + - Requirements: Specific requirements + - Error Messages: What users will see + - Impact: How this affects existing recipes + + ### Removed Validation Rules + + For each removed validation function: + - **Rule**: {Descriptive name} + - Function: function_name + - Reason: Why it was removed + - Impact: What recipes are now allowed + + ### Changed Validation Rules + + For signature or error message changes: + - **Rule**: {Descriptive name} + - Function: function_name + - What Changed: Specific changes + - Impact: How this affects recipe authors + + ## Breaking Changes + + List all breaking changes with migration guidance: + - Change description + - How to update recipes + - Example before/after (use indented code blocks) + + ## Non-Breaking Changes + + List all non-breaking changes (new optional fields, relaxed validation, etc.) + + ## Analysis Guidelines + + 1. **ALWAYS Include Struct Context**: Every field change MUST include the struct name + - Read the "struct" field from validation-changes.json + - Format as **Struct.field** (e.g., **Recipe.context**, **SubRecipe.description**) + - This is CRITICAL because multiple structs can have fields with the same name + - Example: Both Recipe and SubRecipe have a "description" field + + 2. **Detect Schema-Only Changes**: Compare old-schema.json and new-schema.json for: + - **Enum additions**: New values added to enum fields (e.g., input_type: "multiselect") + - **Enum removals**: Values removed from enum fields + - **Required field changes**: Fields added/removed from "required" arrays + - **Extension type changes**: New extension types in the extensions oneOf array + - Document these in a separate "Schema Changes" section + + 3. **Focus on User Impact**: Explain changes from recipe author perspective + + 4. **Prioritize Breaking Changes**: Highlight anything that breaks existing recipes + + 5. **Use Context**: Use old and new structures to understand relationships + - Example: If "context" field removed, check if functionality moved elsewhere + + 6. **Infer Intent**: Use function names, error messages, and field types to understand why changes were made + + 7. **Be Specific**: Include concrete examples of what changed + + 8. **Skip Noise**: Don't document trivial comment formatting changes + + 9. **Use Schema Descriptions**: Prefer schema descriptions over inline comments + + 10. **Document Defaults**: Note when fields have serde defaults from config + + ## Security Scanner Constraints + + CRITICAL: The security scanner flags backticks as command substitution. You MUST NEVER use them: + + 1. **NO BACKTICKS ANYWHERE**: + - NOT in inline code: Write **field_name** instead + - NOT around field names: Write **context** not with backticks + - NOT in code blocks: Use 4-space indentation only + - NOT in any text: Even mentioning backticks triggers alerts + + 2. **NO SHELL PATTERNS**: + - No "2>/dev/null", "rm -f", "rm -rf" + - No file deletion examples + - No shell redirections + + 3. **Safe Alternatives**: + - Field names: Use **bold** (e.g., **context**, **description**) + - Code blocks: Use 4-space indentation + - Types: Write plain text (e.g., "Option of String") + - Functions: Use **bold** (e.g., **validate_json_schema**) + + Examples: + - ❌ NEVER: Any use of the backtick character + - ✅ ALWAYS: **bold** for emphasis, 4-space indented blocks for code + + ## Special Cases + + - **Empty changes arrays**: If a category has no changes, skip that section entirely + + - **Context field removal**: This is the "context" field we discussed - it was never documented + + - **Validation functions with no error messages**: Function likely returns early with Ok or uses inline checks + + ## File Locations + + - Input 1: ./validation-changes.json (the diff) + - Input 2: ./old-validation-structure.json (before state) + - Input 3: ./new-validation-structure.json (after state) + - Input 4: ./old-schema.json (previous schema for comparison) + - Input 5: ./new-schema.json (current schema with descriptions) + - Input 6: ../config/serde-attributes.json (defaults) + - Output: ./validation-changes.md + + Start by reading all SIX input files, then generate the validation changes documentation. + +prompt: | + Please analyze the validation changes and generate comprehensive release notes. + + Steps: + 1. Read all SIX input files (validation-changes.json, old/new-validation-structure.json, old/new-schema.json, serde-attributes.json) + 2. Analyze the changes + 3. Write the documentation to ./validation-changes.md using the text_editor tool + + Focus on: + - User impact (how does this affect recipe authors?) + - Breaking vs non-breaking changes + - Migration guidance for breaking changes + - Schema-only changes (enum additions, required field changes) + - Clear, actionable documentation + + IMPORTANT: You MUST use the text_editor tool to write the output to ./validation-changes.md diff --git a/documentation/automation/recipe-schema-tracking/recipes/update-recipe-reference.yaml b/documentation/automation/recipe-schema-tracking/recipes/update-recipe-reference.yaml new file mode 100644 index 000000000000..ee624b2988eb --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/recipes/update-recipe-reference.yaml @@ -0,0 +1,273 @@ +version: "2" +title: "Update Recipe Reference Documentation" +description: "Apply validation changes to recipe-reference.md based on validation-changes.md" + +extensions: + - type: builtin + name: developer + +instructions: | + You are a technical documentation specialist updating the Recipe Reference Guide with schema and validation rule changes. + + ## ⚠️ CRITICAL RULES + + 1. **Only Update What's Documented**: If a field, rule, or section is NOT mentioned in validation-changes.md, DO NOT touch it + 2. **Verify Before Removing**: Before removing any field or section, double-check it's explicitly listed as "removed" in validation-changes.md + 3. **Preserve Everything Else**: All other content, formatting, examples, and sections must remain exactly as-is + 4. **Table Updates - Use Precise str_replace**: When removing a row from a markdown table: + - Use old_str/new_str (NOT diff format) + - Include the COMPLETE row you want to remove + - Include 1-2 rows before and after for context + - Verify your old_str matches EXACTLY what's in the file + - Double-check you're not accidentally removing adjacent rows + + ## Your Task + + You will update THREE main areas in recipe-reference.md: + 1. **Core Recipe Schema table** - Field additions, removals, and requirement changes + 2. **Field Specifications sections** - Add/remove/update detailed field documentation + 3. **Validation Rules section** - New, removed, or changed validation rules + 4. **Maintain Consistency** + - Match the existing documentation style and tone + - Use the same formatting (bullet points, bold text, etc.) + - Keep language user-focused and actionable + - Maintain alphabetical or logical ordering within subsections + + ## Input Files + + 1. **validation-changes.md** - The change documentation from the pipeline: + - Located at: `./validation-changes.md` + - Contains: Field changes, new validation rules, breaking changes, migration guidance + + 2. **recipe-reference.md** - The target documentation file: + - Located at: `${RECIPE_REF_PATH}` (environment variable) + - Default: `${GOOSE_REPO}/documentation/docs/guides/recipes/recipe-reference.md` + - Contains: Complete recipe reference with Validation Rules section + + 3. **update-summary.md** - Output file for change summary: + - Located at: `./update-summary.md` + - You will create this file to document what was updated + + ## Target Sections + + ### Section 1: Core Recipe Schema table + + Find the **## Core Recipe Schema** section. + + This table lists all Recipe fields with: + - Field name + - Required (✅) or Optional (-) + - Default value + - Description + + **Important**: Character limits, required/optional status, and type information belong in this table, NOT in Validation Rules. + + ### Section 2: Field Specifications + + Find the **## Field Specifications** section. + + This section contains detailed documentation for complex fields (like activities, extensions, parameters, etc.). + + Each field specification typically includes: + - Schema table (if the field has sub-fields) + - Field name + - Type + - Required (✅) or Optional (-) + - Default value + - Description + - Description and usage notes + - Examples + - Special considerations + + ### Section 3: Validation Rules + + Find the **## Validation Rules** section. + + **Critical**: This section should ONLY document validation functions from `validate_recipe.rs`, NOT schema requirements. + + The section should: + - List only actual validation functions (with function names) + - Link to the source code + - Direct readers to the schema table for field requirements + + ## Update Strategy + + ### 1. Read and Analyze + + Read `validation-changes.md` completely and identify ALL changes documented. + + **CRITICAL**: Only make changes that are explicitly documented in validation-changes.md. If a field/rule is not mentioned, DO NOT modify it. + + ### 2. Apply Updates + + For each change documented in validation-changes.md, apply the corresponding update using this guide: + + | Change Type | Where to Update | How to Update | + |-------------|-----------------|---------------| + | **Field added** | Core Schema table | Add new row with Field name, Required (✅/-), Default, Description. Maintain alphabetical order. Use information from validation-changes.md. | + | | Field Specifications | If complex field (objects, arrays of objects, enums): Add new section following pattern of similar fields. Include schema table, description, examples. | + | **Field removed** | Core Schema table | Delete the row for this field. **IMPORTANT**: Use str_replace with old_str containing the row to remove PLUS 1-2 surrounding rows for context. Verify you're only removing the intended row. | + | | Field Specifications (top-level) | If the removed field has its own section: Remove entire section. | + | | Field Specifications (nested) | If the removed field is within another field's section: Update that section's schema table to remove the field row. Review section for any other references to the removed field. | + | **Field renamed** | Core Schema table | Delete old name row, add new name row (appears as remove + add in validation-changes.md). | + | | Field Specifications | Update section header and references if section exists. | + | **Type changed** | Core Schema table | Update Type column. If optionality changed: update Required (✅ ↔ -) and Default columns. Update Description if the change affects field behavior. | + | | Field Specifications | Update section to reflect type change if section exists. | + | **Field description changed (if semantically significant)** | Core Schema table | Update Description column. | + | **Enum value added/removed** | Field Specifications | Update enum lists (e.g., Input Types list in Parameters section). | + | **Validation rule added** | Validation Rules | Add bullet point with description and function name in parentheses. Add to appropriate subsection (Recipe-Level or Parameter Validation). Follow existing format. | + | **Validation rule removed** | Validation Rules | Remove the bullet point. | + | **Validation rule modified** | Validation Rules | Update bullet point text to reflect new requirements. | + + **Important Notes:** + - Simple fields (strings, booleans, simple arrays) do NOT need Field Specification sections + - Complex fields (objects, arrays of objects, enums with options) DO need Field Specification sections + - Look at existing Field Specifications to see which fields have detailed sections + - Match the structure and style of existing sections when adding new ones + + ### 3. Verification + + **Before finalizing:** + 1. Review validation-changes.md one more time + 2. Verify the changes you made to recipe-reference.md match the changes in validation-changes.md + 3. Confirm you did NOT modify any field/rule that was not mentioned in validation-changes.md + 4. Check that alphabetical ordering is maintained in tables + + + ## Output Requirements + + ### 1. Updated recipe-reference.md + + Update THREE areas as detailed in validation-changes.md: + - **## Core Recipe Schema** - Core Structure table (for field changes) + - **## Field Specifications** - Add/remove/update sections for complex fields + - **## Validation Rules** - All subsections (for validation rule changes) + + Do NOT change other sections. Preserve all formatting, headers, and structure. + + ### 2. Create update-summary.md + + Generate a summary document with this structure: + + ```markdown + # Recipe Reference Update Summary + + **Date**: {current_date} + **Source**: validation-changes.md (v1.9.0 → v1.15.0) + **Target**: recipe-reference.md + + ## Changes Applied + + ### Structural Requirements + - Added: {list new fields} + - Modified: {list changed fields} + - Removed: {list removed fields} + + ### Parameter Validation + - Added: {list new rules} + - Modified: {list changed rules} + - Removed: {list removed rules} + + ### [Other Subsections] + ... + + ## Sections Not Modified + + - {list subsections that didn't need changes} + + ## Verification Checklist + + - [ ] All new validation rules from validation-changes.md are documented + - [ ] Removed rules are no longer present + - [ ] Language matches existing documentation style + - [ ] Markdown formatting is valid + - [ ] No unintended changes to other sections + + ## Next Steps + + 1. Review the updated Validation Rules section + 2. Verify accuracy against source code if needed + 3. Commit changes to the recipe-reference-updates branch + 4. Create PR for review + ``` + + ## Guidelines + + 1. **Only Update What Changed**: CRITICAL - Only make changes that are documented in validation-changes.md + - Do NOT add "improvements" or "clarifications" not in the source + - Do NOT reorganize existing content + - Do NOT rewrite descriptions unless they changed + - Do NOT add validation rules that aren't in validation-changes.md + + 2. **Be Precise**: Update only the specific items that changed + - Field added? Add one row to table + - Field removed? Remove one row from table + - Validation rule added? Add one bullet point + - That's it - nothing more + + 3. **Preserve Everything Else**: Keep existing content exactly as-is + - Same section hierarchy and formatting + - Same wording for unchanged items + - Same examples and notes + + 4. **Match Style**: Use the same voice, tone, and formatting as existing content + + 5. **Be Complete**: Ensure all changes from validation-changes.md are reflected + + 6. **Document Changes**: Create a thorough update-summary.md for review + + 7. **Verify**: Double-check that your updates accurately reflect the validation changes + + ## File Locations Summary + + - Input 1: `./validation-changes.md` (change documentation) + - Input 2: `${RECIPE_REF_PATH}` or `${GOOSE_REPO}/documentation/docs/guides/recipes/recipe-reference.md` (target file) + - Output 1: Same as Input 2 (updated in place) + - Output 2: `./update-summary.md` (change summary) + + ## Environment Variables + + - `RECIPE_REF_PATH`: Full path to recipe-reference.md file (overrides default) + - `GOOSE_REPO`: Path to goose repository (used if RECIPE_REF_PATH not set) + + The recipe will check for these environment variables and construct the path accordingly. + + Start by reading both input files, then apply the updates and generate the summary. + +prompt: | + Please update the Recipe Reference Guide based on the validation changes in validation-changes.md. + + CRITICAL INSTRUCTIONS: + 1. Only make changes that are explicitly documented in validation-changes.md + 2. Do NOT make general improvements, reorganizations, or clarifications + 3. Use the EXACT file path from the RECIPE_REF_PATH environment variable when editing recipe-reference.md + - Do NOT create shortened paths or use ~ notation + - The full path is provided in the RECIPE_REF_PATH environment variable + + Update THREE areas: + 1. Core Recipe Schema table - for field additions/removals/type changes + 2. Field Specifications sections - add/remove sections for complex fields (follow existing patterns) + 3. Validation Rules section - for validation function changes + + For each change in validation-changes.md: + - Field added? Add row to schema table + Field Specification section if complex + - Field removed? Remove row from schema table + Field Specification section if exists + - Field type changed? Update type/required columns + update Field Specification if exists + - Enum value added? Update enum lists in Field Specifications (e.g., Input Types) + - Validation rule added? Add bullet point with function name + - Validation rule removed? Remove bullet point + + When adding Field Specification sections: + - Look at similar existing sections (e.g., Activities, Parameters, Settings) + - Match their structure: schema table, description, examples, usage notes + - Keep the same style and formatting + - Use information from validation-changes.md + + Do NOT: + - Rewrite existing descriptions + - Add validation rules not in validation-changes.md + - Reorganize sections + - Make "improvements" to existing content + - Add character limits unless they're new in validation-changes.md + + Generate update-summary.md documenting exactly what you changed. diff --git a/documentation/automation/recipe-schema-tracking/scripts/diff-validation-structures.sh b/documentation/automation/recipe-schema-tracking/scripts/diff-validation-structures.sh new file mode 100755 index 000000000000..a7b29f55036e --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/scripts/diff-validation-structures.sh @@ -0,0 +1,212 @@ +#!/bin/bash +# Compare two validation structure files and output changes +# Usage: ./diff-validation-structures.sh old-validation-structure.json new-validation-structure.json +# Output: validation-changes.json + +set -e + +OLD_FILE=${1:-"old-validation-structure.json"} +NEW_FILE=${2:-"new-validation-structure.json"} + +if [ ! -f "$OLD_FILE" ]; then + echo "Error: Old validation structure file not found: $OLD_FILE" >&2 + exit 1 +fi + +if [ ! -f "$NEW_FILE" ]; then + echo "Error: New validation structure file not found: $NEW_FILE" >&2 + exit 1 +fi + +# Extract versions for metadata +OLD_VERSION=$(jq -r '.version' "$OLD_FILE") +NEW_VERSION=$(jq -r '.version' "$NEW_FILE") + +# Build the changes JSON using jq +jq -n \ + --arg old_version "$OLD_VERSION" \ + --arg new_version "$NEW_VERSION" \ + --arg compared_at "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ + --argjson old_data "$(cat "$OLD_FILE")" \ + --argjson new_data "$(cat "$NEW_FILE")" \ + ' + { + old_version: $old_version, + new_version: $new_version, + compared_at: $compared_at, + has_changes: false, + changes: { + struct_fields: { + added: [], + removed: [], + type_changed: [], + comment_changed: [] + }, + validation_functions: { + added: [], + removed: [], + signature_changed: [], + error_messages_changed: [] + } + } + } | + + # Detect field changes + . as $result | + + # Find added fields (in new but not in old) - compare by struct.field + ($new_data.struct_fields | map(.struct + "." + .field)) as $new_fields | + ($old_data.struct_fields | map(.struct + "." + .field)) as $old_fields | + ($new_fields - $old_fields) as $added_field_keys | + + # Find removed fields (in old but not in new) - compare by struct.field + ($old_fields - $new_fields) as $removed_field_keys | + + # Find fields with type changes - compare by struct.field + ( + $new_data.struct_fields | + map(select((.struct + "." + .field) as $key | $old_fields | contains([$key])) | + {struct: .struct, field: .field, new_type: .type, new_comment: .inline_comment} + ) + ) as $new_common | + ( + $old_data.struct_fields | + map(select((.struct + "." + .field) as $key | $new_fields | contains([$key])) | + {struct: .struct, field: .field, old_type: .type, old_comment: .inline_comment} + ) + ) as $old_common | + + # Compare types and comments for common fields + ( + $new_common | map( + . as $new_item | + ($old_common | map(select(.struct == $new_item.struct and .field == $new_item.field)) | .[0]) as $old_item | + if $old_item.old_type != $new_item.new_type then + { + struct: $new_item.struct, + field: $new_item.field, + old_type: $old_item.old_type, + new_type: $new_item.new_type + } + else + empty + end + ) + ) as $type_changed | + + ( + $new_common | map( + . as $new_item | + ($old_common | map(select(.struct == $new_item.struct and .field == $new_item.field)) | .[0]) as $old_item | + if $old_item.old_comment != $new_item.new_comment then + { + struct: $new_item.struct, + field: $new_item.field, + old_comment: $old_item.old_comment, + new_comment: $new_item.new_comment + } + else + empty + end + ) + ) as $comment_changed | + + # Find validation function changes + ($new_data.validation_functions | map(.function)) as $new_funcs | + ($old_data.validation_functions | map(.function)) as $old_funcs | + ($new_funcs - $old_funcs) as $added_funcs | + ($old_funcs - $new_funcs) as $removed_funcs | + + # Find functions with signature changes + ( + $new_data.validation_functions | + map(select(.function as $f | $old_funcs | contains([$f])) | + {function: .function, new_signature: .signature, new_errors: .error_messages} + ) + ) as $new_common_funcs | + ( + $old_data.validation_functions | + map(select(.function as $f | $new_funcs | contains([$f])) | + {function: .function, old_signature: .signature, old_errors: .error_messages} + ) + ) as $old_common_funcs | + + ( + $new_common_funcs | map( + . as $new_func | + ($old_common_funcs | map(select(.function == $new_func.function)) | .[0]) as $old_func | + if $old_func.old_signature != $new_func.new_signature then + { + function: $new_func.function, + old_signature: $old_func.old_signature, + new_signature: $new_func.new_signature + } + else + empty + end + ) + ) as $signature_changed | + + ( + $new_common_funcs | map( + . as $new_func | + ($old_common_funcs | map(select(.function == $new_func.function)) | .[0]) as $old_func | + if $old_func.old_errors != $new_func.new_errors then + { + function: $new_func.function, + old_errors: $old_func.old_errors, + new_errors: $new_func.new_errors + } + else + empty + end + ) + ) as $error_messages_changed | + + # Build final result with detected changes + .changes.struct_fields.added = ( + $added_field_keys | map( + . as $key | + ($key | split(".")) as $parts | + $new_data.struct_fields | map(select(.struct == $parts[0] and .field == $parts[1])) | .[0] + ) + ) | + .changes.struct_fields.removed = ( + $removed_field_keys | map( + . as $key | + ($key | split(".")) as $parts | + $old_data.struct_fields | map(select(.struct == $parts[0] and .field == $parts[1])) | .[0] + ) + ) | + .changes.struct_fields.type_changed = $type_changed | + .changes.struct_fields.comment_changed = $comment_changed | + + .changes.validation_functions.added = ( + $added_funcs | map( + . as $func | + $new_data.validation_functions | map(select(.function == $func)) | .[0] + ) + ) | + .changes.validation_functions.removed = ( + $removed_funcs | map( + . as $func | + $old_data.validation_functions | map(select(.function == $func)) | .[0] + ) + ) | + .changes.validation_functions.signature_changed = $signature_changed | + .changes.validation_functions.error_messages_changed = $error_messages_changed | + + # Set has_changes flag + .has_changes = ( + (.changes.struct_fields.added | length) > 0 or + (.changes.struct_fields.removed | length) > 0 or + (.changes.struct_fields.type_changed | length) > 0 or + (.changes.struct_fields.comment_changed | length) > 0 or + (.changes.validation_functions.added | length) > 0 or + (.changes.validation_functions.removed | length) > 0 or + (.changes.validation_functions.signature_changed | length) > 0 or + (.changes.validation_functions.error_messages_changed | length) > 0 + ) + ' + + diff --git a/documentation/automation/recipe-schema-tracking/scripts/extract-schema.sh b/documentation/automation/recipe-schema-tracking/scripts/extract-schema.sh new file mode 100755 index 000000000000..7bf7a39f95ab --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/scripts/extract-schema.sh @@ -0,0 +1,161 @@ +#!/bin/bash +# Extract and resolve Recipe schema from OpenAPI spec at a specific git version +# Usage: ./extract-schema.sh +# Example: ./extract-schema.sh v1.15.0 + +set -e + +VERSION=${1:-"main"} +GOOSE_REPO=${GOOSE_REPO:-"$HOME/Development/goose"} + +if [ ! -d "$GOOSE_REPO" ]; then + echo "Error: GOOSE_REPO directory not found: $GOOSE_REPO" >&2 + exit 1 +fi + +cd "$GOOSE_REPO" + +# Verify version exists (for non-main versions) +if [ "$VERSION" != "main" ]; then + if ! git rev-parse "$VERSION" >/dev/null 2>&1; then + echo "Error: Version $VERSION not found in git history" >&2 + exit 1 + fi +fi + +# Extract OpenAPI spec from git +if [ "$VERSION" = "main" ]; then + if [ ! -f ui/desktop/openapi.json ]; then + echo "Error: ui/desktop/openapi.json not found in working directory" >&2 + exit 1 + fi + OPENAPI_JSON=$(cat ui/desktop/openapi.json) +else + OPENAPI_JSON=$(git show "$VERSION:ui/desktop/openapi.json" 2>/dev/null || { + echo "Error: Could not find ui/desktop/openapi.json at version $VERSION" >&2 + exit 1 + }) +fi + +# Use Node.js to extract and resolve Recipe schema +echo "$OPENAPI_JSON" | node -e " +const openApiSpec = JSON.parse(require('fs').readFileSync(0, 'utf-8')); + +/** + * Resolves \$ref references in OpenAPI schemas by expanding them with the actual schema definitions + * Ported from ui/desktop/src/recipe/validation.ts + */ +function resolveRefs(schema, openApiSpec) { + if (!schema || typeof schema !== 'object') { + return schema; + } + + // Handle \$ref + if (typeof schema.\$ref === 'string') { + const refPath = schema.\$ref.replace('#/', '').split('/'); + let resolved = openApiSpec; + + for (const segment of refPath) { + if (resolved && typeof resolved === 'object' && segment in resolved) { + resolved = resolved[segment]; + } else { + console.warn(\`Could not resolve \$ref: \${schema.\$ref}\`); + return schema; // Return original if can't resolve + } + } + + if (resolved && typeof resolved === 'object') { + // Recursively resolve refs in the resolved schema + return resolveRefs(resolved, openApiSpec); + } + + return schema; + } + + // Handle allOf (merge schemas) + if (Array.isArray(schema.allOf)) { + const merged = {}; + for (const subSchema of schema.allOf) { + if (typeof subSchema === 'object' && subSchema !== null) { + const resolved = resolveRefs(subSchema, openApiSpec); + Object.assign(merged, resolved); + } + } + // Keep other properties from the original schema + const { allOf, ...rest } = schema; + return { ...merged, ...rest }; + } + + // Handle oneOf/anyOf (keep as union) + if (Array.isArray(schema.oneOf)) { + return { + ...schema, + oneOf: schema.oneOf.map((subSchema) => + typeof subSchema === 'object' && subSchema !== null + ? resolveRefs(subSchema, openApiSpec) + : subSchema + ), + }; + } + + if (Array.isArray(schema.anyOf)) { + return { + ...schema, + anyOf: schema.anyOf.map((subSchema) => + typeof subSchema === 'object' && subSchema !== null + ? resolveRefs(subSchema, openApiSpec) + : subSchema + ), + }; + } + + // Handle object properties + if (schema.type === 'object' && schema.properties && typeof schema.properties === 'object') { + const resolvedProperties = {}; + for (const [key, value] of Object.entries(schema.properties)) { + if (typeof value === 'object' && value !== null) { + resolvedProperties[key] = resolveRefs(value, openApiSpec); + } else { + resolvedProperties[key] = value; + } + } + return { + ...schema, + properties: resolvedProperties, + }; + } + + // Handle array items + if (schema.type === 'array' && schema.items && typeof schema.items === 'object') { + return { + ...schema, + items: resolveRefs(schema.items, openApiSpec), + }; + } + + // Return schema as-is if no refs to resolve + return schema; +} + +// Extract Recipe schema +const recipeSchema = openApiSpec.components?.schemas?.Recipe; + +if (!recipeSchema) { + console.error('Error: Recipe schema not found in OpenAPI specification'); + process.exit(1); +} + +// Resolve all \$refs in the schema +const resolvedSchema = resolveRefs(recipeSchema, openApiSpec); + +// Convert OpenAPI schema to JSON Schema format +const jsonSchema = { + '\$schema': 'http://json-schema.org/draft-07/schema#', + ...resolvedSchema, + title: resolvedSchema.title || 'Recipe', + description: resolvedSchema.description || 'A Recipe represents a personalized, user-generated agent configuration that defines specific behaviors and capabilities within the Goose system.', +}; + +// Output the resolved schema +console.log(JSON.stringify(jsonSchema, null, 2)); +" diff --git a/documentation/automation/recipe-schema-tracking/scripts/extract-validation-structure.sh b/documentation/automation/recipe-schema-tracking/scripts/extract-validation-structure.sh new file mode 100755 index 000000000000..21ce7e6bcbd6 --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/scripts/extract-validation-structure.sh @@ -0,0 +1,185 @@ +#!/bin/bash +# Extract validation structure from Rust source files +# Usage: ./extract-validation-structure.sh +# Example: ./extract-validation-structure.sh v1.15.0 + +set -e + +VERSION=${1:-"main"} +GOOSE_REPO=${GOOSE_REPO:-"$HOME/Development/goose"} + +if [ ! -d "$GOOSE_REPO" ]; then + echo "Error: GOOSE_REPO directory not found: $GOOSE_REPO" >&2 + exit 1 +fi + +cd "$GOOSE_REPO" + +# Verify version exists (for non-main versions) +if [ "$VERSION" != "main" ]; then + if ! git rev-parse "$VERSION" >/dev/null 2>&1; then + echo "Error: Version $VERSION not found in git history" >&2 + exit 1 + fi +fi + +# Start JSON output +# Use ISO 8601 format that works on both macOS and Linux +TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -Iseconds 2>/dev/null || date -u) +cat << EOF +{ + "version": "$VERSION", + "extracted_at": "$TIMESTAMP", + "struct_fields": [ +EOF + +# Extract fields from multiple structs +FIRST_FIELD=true + +# Get file content from git history or working directory +if [ "$VERSION" = "main" ]; then + MOD_RS_CONTENT=$(cat crates/goose/src/recipe/mod.rs) +else + MOD_RS_CONTENT=$(git show "$VERSION:crates/goose/src/recipe/mod.rs" 2>/dev/null || { + echo "Error: Failed to read mod.rs from version $VERSION" >&2 + exit 1 + }) +fi + +# List of structs to extract (in order of appearance in file) +STRUCTS="Recipe Author Settings Response SubRecipe RecipeParameter" + +# Create a temporary file to collect all fields +TEMP_FIELDS=$(mktemp) + +for STRUCT_NAME in $STRUCTS; do + # Extract just this struct's definition (from "pub struct Name" to the closing "}") + echo "$MOD_RS_CONTENT" | awk " + /^pub struct $STRUCT_NAME/ { in_struct=1; next } + in_struct && /^}/ { exit } + in_struct && /^[[:space:]]+pub [a-z_]+:/ { print } + " | while IFS= read -r line; do + # Extract field name (word after 'pub ') + field_name=$(echo "$line" | sed -E 's/.*pub ([a-z_]+):.*/\1/') + + # Extract type (between : and , or // or end of line) + field_type=$(echo "$line" | sed -E 's/.*:\s*([^,\/]+).*/\1/' | sed 's/[[:space:]]*$//') + + # Extract inline comment (after //) + inline_comment="" + if echo "$line" | grep -q "//"; then + inline_comment=$(echo "$line" | sed -E 's/.*\/\/\s*(.*)$/\1/' | sed 's/[[:space:]]*$//' | sed 's/"/\\"/g') + fi + + # Check if optional + is_optional="false" + if echo "$field_type" | grep -q "Option<"; then + is_optional="true" + fi + + # Output to temp file + cat << FIELD_JSON >> "$TEMP_FIELDS" +{ + "struct": "$STRUCT_NAME", + "field": "$field_name", + "type": "$field_type", + "optional": $is_optional, + "inline_comment": "$inline_comment" +} +FIELD_JSON + done +done + +# Output fields with proper comma separation +if [ -s "$TEMP_FIELDS" ]; then + # Read all JSON objects into an array and format with commas + jq -s '.' "$TEMP_FIELDS" | jq -r 'to_entries | .[] | (if .key > 0 then "," else "" end) + " " + (.value | tostring)' +fi + +rm -f "$TEMP_FIELDS" + +# Close struct_fields array, start validation_functions +cat << EOF + + ], + "validation_functions": [ +EOF + +# Extract validation functions with error messages and code snippets +FIRST_FUNC=true + +# Get validation file content from git history or working directory +# Note: validate_recipe.rs may not exist in older versions +if [ "$VERSION" = "main" ]; then + if [ -f crates/goose/src/recipe/validate_recipe.rs ]; then + VALIDATE_RS_CONTENT=$(cat crates/goose/src/recipe/validate_recipe.rs) + else + VALIDATE_RS_CONTENT="" + fi +else + VALIDATE_RS_CONTENT=$(git show "$VERSION:crates/goose/src/recipe/validate_recipe.rs" 2>/dev/null || echo "") +fi + +if [ -n "$VALIDATE_RS_CONTENT" ]; then + echo "$VALIDATE_RS_CONTENT" | rg "^fn validate_" -A 30 | \ + awk ' + /^fn validate_/ { + if (func_name != "") { + # Output previous function + if (first_func == "true") { + first_func = "false" + } else { + print "," + } + printf " {\n" + printf " \"function\": \"%s\",\n", func_name + printf " \"signature\": \"%s\",\n", signature + printf " \"error_messages\": [%s],\n", error_msgs + printf " \"code_snippet\": %s\n", code_snippet + printf " }" + } + + # Start new function + func_name = $0 + gsub(/^fn /, "", func_name) + gsub(/\(.*/, "", func_name) + signature = $0 + gsub(/"/, "\\\"", signature) + error_msgs = "" + code_snippet = "\"...\"" + first_func = (first_func == "") ? "true" : first_func + } + + /anyhow::anyhow!\("/ { + # Extract error message + msg = $0 + gsub(/.*anyhow::anyhow!\("/, "", msg) + gsub(/".*/, "", msg) + gsub(/"/, "\\\"", msg) + if (error_msgs != "") error_msgs = error_msgs ", " + error_msgs = error_msgs "\"" msg "\"" + } + + END { + # Output last function + if (func_name != "") { + if (first_func != "true") { + print "," + } + printf " {\n" + printf " \"function\": \"%s\",\n", func_name + printf " \"signature\": \"%s\",\n", signature + printf " \"error_messages\": [%s],\n", error_msgs + printf " \"code_snippet\": %s\n", code_snippet + printf " }" + } + } + ' +fi + +# Close validation_functions array and JSON +cat << EOF + + ] +} +EOF diff --git a/documentation/automation/recipe-schema-tracking/scripts/run-pipeline.sh b/documentation/automation/recipe-schema-tracking/scripts/run-pipeline.sh new file mode 100755 index 000000000000..ee9916b7f565 --- /dev/null +++ b/documentation/automation/recipe-schema-tracking/scripts/run-pipeline.sh @@ -0,0 +1,126 @@ +#!/bin/bash +# End-to-end pipeline test +# Usage: ./run-pipeline.sh +# Example: ./run-pipeline.sh v1.9.0 v1.15.0 + +set -e + +OLD_VERSION=${1:-"v1.9.0"} +NEW_VERSION=${2:-"v1.15.0"} + +echo "==========================================" +echo "Recipe Validation Documentation Pipeline" +echo "==========================================" +echo "Old Version: $OLD_VERSION" +echo "New Version: $NEW_VERSION" +echo "" + +# Change to output directory +cd "$(dirname "$0")/../output" + +echo "Step 1: Extracting validation structure from $OLD_VERSION..." +if ! ../scripts/extract-validation-structure.sh "$OLD_VERSION" > old-validation-structure.json 2>&1; then + echo "✗ Failed to extract validation structure from $OLD_VERSION" >&2 + echo "Error output:" >&2 + cat old-validation-structure.json >&2 + exit 1 +fi +echo "✓ Extracted $(jq '.struct_fields | length' old-validation-structure.json) fields, $(jq '.validation_functions | length' old-validation-structure.json) functions" + +echo "" +echo "Step 1b: Extracting schema from $OLD_VERSION..." +if ! ../scripts/extract-schema.sh "$OLD_VERSION" > old-schema.json 2>&1; then + echo "✗ Failed to extract schema from $OLD_VERSION" >&2 + echo "Error output:" >&2 + cat old-schema.json >&2 + exit 1 +fi +echo "✓ Extracted schema ($(jq '.properties | length' old-schema.json) properties)" + +echo "" +echo "Step 2: Extracting validation structure from $NEW_VERSION..." +if ! ../scripts/extract-validation-structure.sh "$NEW_VERSION" > new-validation-structure.json 2>&1; then + echo "✗ Failed to extract validation structure from $NEW_VERSION" >&2 + echo "Error output:" >&2 + cat new-validation-structure.json >&2 + exit 1 +fi +echo "✓ Extracted $(jq '.struct_fields | length' new-validation-structure.json) fields, $(jq '.validation_functions | length' new-validation-structure.json) functions" + +echo "" +echo "Step 2b: Extracting schema from $NEW_VERSION..." +if ! ../scripts/extract-schema.sh "$NEW_VERSION" > new-schema.json 2>&1; then + echo "✗ Failed to extract schema from $NEW_VERSION" >&2 + echo "Error output:" >&2 + cat new-schema.json >&2 + exit 1 +fi +echo "✓ Extracted schema ($(jq '.properties | length' new-schema.json) properties)" + +echo "" +echo "Step 3: Comparing validation structures..." +../scripts/diff-validation-structures.sh old-validation-structure.json new-validation-structure.json > validation-changes.json 2>&1 + +HAS_CHANGES=$(jq -r '.has_changes' validation-changes.json) +echo "✓ Comparison complete. Has changes: $HAS_CHANGES" + +if [ "$HAS_CHANGES" = "true" ]; then + echo "" + echo "Changes detected:" + echo " - Fields added: $(jq '.changes.struct_fields.added | length' validation-changes.json)" + echo " - Fields removed: $(jq '.changes.struct_fields.removed | length' validation-changes.json)" + echo " - Fields type changed: $(jq '.changes.struct_fields.type_changed | length' validation-changes.json)" + echo " - Fields comment changed: $(jq '.changes.struct_fields.comment_changed | length' validation-changes.json)" + echo " - Validation functions added: $(jq '.changes.validation_functions.added | length' validation-changes.json)" + echo " - Validation functions removed: $(jq '.changes.validation_functions.removed | length' validation-changes.json)" + echo " - Validation functions signature changed: $(jq '.changes.validation_functions.signature_changed | length' validation-changes.json)" + echo " - Validation functions error messages changed: $(jq '.changes.validation_functions.error_messages_changed | length' validation-changes.json)" + + echo "" + echo "Step 4: Synthesizing validation changes documentation..." + + # Run goose and capture output, filtering out session logs + goose run --recipe ../recipes/synthesize-validation-changes.yaml 2>&1 | \ + sed -E 's/\x1B\[[0-9;]*[mK]//g' | \ + grep -v "^starting session" | \ + grep -v "^ session id:" | \ + grep -v "^ working directory:" | \ + grep -v "^─── text_editor" | \ + grep -v "^path:" | \ + grep -v "^command:" | \ + grep -v "^Closing session" | \ + grep -v "^Loading recipe:" | \ + grep -v "^Description:" | \ + sed '/^$/N;/^\n$/D' > validation-changes.md.tmp + + # Check if we got meaningful content (more than just whitespace) + if [ -s validation-changes.md.tmp ] && grep -q "# Recipe Validation Changes" validation-changes.md.tmp; then + mv validation-changes.md.tmp validation-changes.md + echo "✓ Generated validation-changes.md ($(wc -l < validation-changes.md) lines)" + echo "" + echo "==========================================" + echo "Pipeline Complete!" + echo "==========================================" + echo "" + echo "Output files:" + echo " - old-validation-structure.json" + echo " - old-schema.json" + echo " - new-validation-structure.json" + echo " - new-schema.json" + echo " - validation-changes.json" + echo " - validation-changes.md" + echo "" + echo "Review validation-changes.md for documentation updates." + else + echo "✗ Failed to generate validation-changes.md" + exit 1 + fi +else + echo "" + echo "==========================================" + echo "No Changes Detected" + echo "==========================================" + echo "" + echo "No validation changes between $OLD_VERSION and $NEW_VERSION." + echo "Documentation update not needed." +fi