APM

>Agent Skill

@microsoft/diff-upstream

skilldevelopment

Regenerate the local emitter and diff the generated code against the upstream baseline checked into autorest.python. Use this skill when the user wants to see how their emitter changes affect generated code compared to what's currently in production, says things like "diff upstream", "compare to baseline", "what changed vs production", "show me the diff", "how does this affect generated code", or wants to validate their changes produce the expected output differences.

pythongittesting
apm::install
$apm install @microsoft/diff-upstream
apm::skill.md
---
name: diff-upstream
description: >
  Regenerate the local emitter and diff the generated code against the upstream
  baseline checked into autorest.python. Use this skill when the user wants to
  see how their emitter changes affect generated code compared to what's currently
  in production, says things like "diff upstream", "compare to baseline", "what
  changed vs production", "show me the diff", "how does this affect generated code",
  or wants to validate their changes produce the expected output differences.
---

# Diff Upstream Skill

Regenerates local emitter output and compares it against the baseline generated
code checked into the autorest.python repository. Shows exactly what would change
if the current emitter changes were shipped.

Optimized for local development with cached baseline and editor integration.

## Paths

All paths are relative to the http-client-python package root.

| Item                        | Path                                                    |
| --------------------------- | ------------------------------------------------------- |
| Package root                | `~/Desktop/github/typespec/packages/http-client-python` |
| Local generated (unbranded) | `generator/test/unbranded/generated`                    |
| Local generated (azure)     | `generator/test/azure/generated`                        |
| Baseline cache (unbranded)  | `generator/test/unbranded/.baseline` (gitignored)       |
| Baseline cache (azure)      | `generator/test/azure/.baseline` (gitignored)           |
| Diff summary output         | `generator/test/.diff-summary.md` (gitignored)          |
| Regenerate marker           | `.last-regenerate`                                      |
| Upstream repo               | `https://github.com/Azure/autorest.python`              |

## Workflow

### Step 1: Build and regenerate

Always regenerate to see current changes:

```bash
cd ~/Desktop/github/typespec/packages/http-client-python
npm run build
npm run regenerate
touch .last-regenerate
```

If build or regenerate fails, report errors and stop.

### Step 2: Fetch or update baseline

Fetch baseline files directly into the test folders using sparse checkout:

```bash
cd ~/Desktop/github/typespec/packages/http-client-python

BASELINE_UNBRANDED="generator/test/unbranded/.baseline"
BASELINE_AZURE="generator/test/azure/.baseline"
TEMP_CLONE="/tmp/autorest-python-baseline-$$"

# Check if we need to fetch/update
if [ ! -d "$BASELINE_UNBRANDED" ] || [ ! -d "$BASELINE_AZURE" ]; then
  echo "Fetching upstream baseline..."

  # Clone with sparse checkout to temp location
  git clone --depth 1 --filter=blob:none --sparse \
    https://github.com/Azure/autorest.python.git "$TEMP_CLONE"

  cd "$TEMP_CLONE"
  git sparse-checkout set \
    packages/typespec-python/test/unbranded/generated \
    packages/typespec-python/test/azure/generated

  cd ~/Desktop/github/typespec/packages/http-client-python

  # Copy to baseline locations
  rm -rf "$BASELINE_UNBRANDED" "$BASELINE_AZURE"
  cp -r "$TEMP_CLONE/packages/typespec-python/test/unbranded/generated" "$BASELINE_UNBRANDED"
  cp -r "$TEMP_CLONE/packages/typespec-python/test/azure/generated" "$BASELINE_AZURE"

  # Cleanup temp clone
  rm -rf "$TEMP_CLONE"

  echo "Baseline cached in generator/test/*/.baseline/"
else
  echo "Using cached baseline (delete generator/test/*/.baseline to refresh)"
fi
```

### Step 3: Generate diff summary

Create a markdown summary file with package-level comparison:

```bash
cd ~/Desktop/github/typespec/packages/http-client-python

LOCAL_UNBRANDED="generator/test/unbranded/generated"
LOCAL_AZURE="generator/test/azure/generated"
BASELINE_UNBRANDED="generator/test/unbranded/.baseline"
BASELINE_AZURE="generator/test/azure/.baseline"

cat > generator/test/.diff-summary.md << EOF
# Diff Summary: Local vs Upstream (autorest.python)

Generated: $(date)

## Unbranded Packages

| Metric | Count |
|--------|-------|
| Local | $(ls "$LOCAL_UNBRANDED" 2> /dev/null | wc -l | tr -d ' ') |
| Upstream | $(ls "$BASELINE_UNBRANDED" 2> /dev/null | wc -l | tr -d ' ') |

### Missing locally (in upstream only):
$(comm -13 <(ls "$LOCAL_UNBRANDED" 2> /dev/null | sort) <(ls "$BASELINE_UNBRANDED" 2> /dev/null | sort) | sed 's/^/- /' || echo "- (none)")

### New locally (not in upstream):
$(comm -23 <(ls "$LOCAL_UNBRANDED" 2> /dev/null | sort) <(ls "$BASELINE_UNBRANDED" 2> /dev/null | sort) | sed 's/^/- /' || echo "- (none)")

## Azure Packages

| Metric | Count |
|--------|-------|
| Local | $(ls "$LOCAL_AZURE" 2> /dev/null | wc -l | tr -d ' ') |
| Upstream | $(ls "$BASELINE_AZURE" 2> /dev/null | wc -l | tr -d ' ') |

### Missing locally (in upstream only):
$(comm -13 <(ls "$LOCAL_AZURE" 2> /dev/null | sort) <(ls "$BASELINE_AZURE" 2> /dev/null | sort) | sed 's/^/- /' || echo "- (none)")

### New locally (not in upstream):
$(comm -23 <(ls "$LOCAL_AZURE" 2> /dev/null | sort) <(ls "$BASELINE_AZURE" 2> /dev/null | sort) | sed 's/^/- /' || echo "- (none)")

---

## Viewing Detailed Diffs

### List changed files in a package:
\`\`\`bash
diff -rq generator/test/unbranded/generated/PACKAGE generator/test/unbranded/.baseline/PACKAGE
\`\`\`

### View full diff for a package:
\`\`\`bash
diff -r generator/test/unbranded/generated/PACKAGE generator/test/unbranded/.baseline/PACKAGE
\`\`\`

### Open side-by-side diff in VS Code:
\`\`\`bash
code --diff generator/test/unbranded/.baseline/PACKAGE/path/to/file.py generator/test/unbranded/generated/PACKAGE/path/to/file.py
\`\`\`

EOF
```

### Step 4: Open in editor

Open the summary file in the user's preferred editor:

```bash
cd ~/Desktop/github/typespec/packages/http-client-python

# Try common editors in order of preference
if command -v code &> /dev/null; then
  code generator/test/.diff-summary.md
elif [ -n "$VISUAL" ]; then
  $VISUAL generator/test/.diff-summary.md
elif [ -n "$EDITOR" ]; then
  $EDITOR generator/test/.diff-summary.md
elif command -v vim &> /dev/null; then
  vim generator/test/.diff-summary.md
else
  cat generator/test/.diff-summary.md
fi
```

### Step 5: Report to user

Tell the user:

- Summary file is open at `generator/test/.diff-summary.md`
- Baseline is cached alongside generated code in `generator/test/*/.baseline/`
- How to view detailed diffs (commands are in the summary file)
- How to refresh baseline: `rm -rf generator/test/*/.baseline`

## Viewing Detailed File Diffs

After showing the summary, if the user wants to see specific file changes:

### Find changed files in a package:

```bash
cd ~/Desktop/github/typespec/packages/http-client-python
diff -rq \
  generator/test/unbranded/generated/typetest-array \
  generator/test/unbranded/.baseline/typetest-array
```

### Show inline diff for a file:

```bash
diff -u \
  generator/test/unbranded/.baseline/typetest-array/typetest/array/_client.py \
  generator/test/unbranded/generated/typetest-array/typetest/array/_client.py
```

### VS Code side-by-side (if user has VS Code):

```bash
code --diff \
  generator/test/unbranded/.baseline/typetest-array/typetest/array/_client.py \
  generator/test/unbranded/generated/typetest-array/typetest/array/_client.py
```

## Integration with test-unbranded-emitter

After running `diff-upstream`, you can immediately run tests:

- The `.last-regenerate` marker is updated
- `test-unbranded-emitter` will skip regeneration since it's fresh

## Cleanup

The baseline is cached for fast subsequent runs. To refresh:

```bash
# Remove baseline (will re-fetch on next run)
rm -rf generator/test/unbranded/.baseline generator/test/azure/.baseline

# Remove summary file
rm generator/test/.diff-summary.md
```

## Gitignore

These patterns should be in `.gitignore`:

```
generator/test/unbranded/.baseline/
generator/test/azure/.baseline/
generator/test/.diff-summary.md
.last-regenerate
```

## Interpreting Diffs

After generating the diff, analyze what changed and report to the user whether the diffs are expected.

### Step 1: Check what code changes were made on the branch

First, understand what emitter changes were made:

```bash
# Show commits on this branch
git log main..HEAD --oneline -- "emitter/src/*.ts"

# Show the actual changes
git diff main..HEAD -- emitter/src/
```

### Step 2: Correlate code changes with expected generated output changes

Based on the emitter changes, determine what generated code SHOULD change:

| Emitter change type                            | Expected generated code impact               |
| ---------------------------------------------- | -------------------------------------------- |
| Namespace logic refactor (behavior-preserving) | No generated code changes                    |
| New decorator support                          | Changes in files using that decorator        |
| Serialization changes                          | Changes in `_serialization.py`, model files  |
| Client initialization changes                  | Changes in `_client.py`, `_configuration.py` |
| Operation changes                              | Changes in `_operations/*.py`                |

### Step 3: Compare expected vs actual diffs

- **If actual diffs match expected:** Tell user "The diffs are expected based on your emitter changes to [describe changes]. The generated code correctly reflects your modifications."

- **If no diffs but expected some:** Tell user "No generated code changes detected. If you expected changes, verify your emitter modifications are being triggered by the test specs, or there may be more development needed. Should I investigate further?"

- **If unexpected diffs appear:** Tell user "Found unexpected changes in [files]. These don't appear to match your emitter modifications. There may be unintended side effects - would you like me to investigate?"

### Pre-existing repo differences (always present, not caused by your changes)

These diffs exist between typespec and autorest.python repos regardless of emitter changes. They should be ignored when evaluating your changes:

| File type                     | Typical diff              | Cause                                       |
| ----------------------------- | ------------------------- | ------------------------------------------- |
| `_patch.py`                   | `List[str]``list[str]` | Type hint modernization (Python 3.9+ style) |
| `CHANGELOG.md`                | Formatting differences    | Template differences                        |
| `README.md`                   | Service display names     | Template/config differences                 |
| `pyproject.toml` / `setup.py` | Different packaging       | Tooling preferences                         |

**If only these files differ:** "The only diffs are pre-existing repo differences (type hints, config files), not caused by your emitter changes. Your changes are behavior-preserving."

### Unexpected diffs (actual code changes)

If you see differences in these files, the emitter changes ARE affecting generated code:

- `_client.py` - Client class definitions
- `_operations/*.py` - Operation implementations
- `models/*.py` - Model definitions (excluding `_patch.py`)
- `_serialization.py` - Serialization logic
- `aio/*.py` - Async client code

**If these files differ:** First check if the changes match what the emitter modifications should produce. If they match, say "The generated code changes are expected based on your emitter changes." If they don't match or are unexpected, say "Found changes that don't appear to match your emitter modifications. There may be more development needed - would you like me to investigate?"

### Quick diff analysis command

To check if there are actual code changes (excluding expected differences):

```bash
diff -rq generator/test/unbranded/generated generator/test/unbranded/.baseline 2> /dev/null | grep "\.py differ" | grep -v "_patch.py"
```

If this returns nothing, only expected diffs exist.

## Notes

### Diff direction

The diff shows changes from upstream (baseline) to local (your changes):

- `-` lines: removed from upstream (or changed)
- `+` lines: added in your local version

### Why packages might be missing locally

Some upstream packages may not generate locally due to:

- TypeSpec version incompatibilities in the monorepo
- Specs that require specific dependencies not installed
- Test configurations that differ between repos

This is expected for ARM/azure-resource-manager specs if there are version conflicts.

### Performance

- First run: ~30-60s (fetch baseline)
- Subsequent runs: instant (uses cached baseline)
- Regenerate: ~2-3 minutes (always runs for diff)