# Property Change Detection — Expectations Document

This document describes the observable behavior of the property change
detection module used during `agdt-setup` issue-type re-discovery.

## Overview

When `agdt-setup` re-discovers issue-type properties from a provider (Jira,
GitHub, etc.), the `detect_property_changes()` function compares the freshly
discovered property schema against the previously saved one and:

1. Classifies each property into one of five categories: NEW, REMOVED,
   EXCLUDED, CHANGED, or UNCHANGED.
2. Preserves manual `included_in_template=false` exclusions.
3. Emits structured INFO-level log entries for each detected change.
4. Emits a single summary log line with counts per category.
5. Returns a merged schema and a `has_changes` indicator.

## Categories

| Category | Meaning |
|----------|---------|
| NEW | Property exists in fresh discovery but not in saved schema |
| REMOVED | Property exists in saved schema but not in fresh discovery |
| EXCLUDED | Property has `included_in_template=false` in saved schema |
| CHANGED | Property exists in both but has attribute-level differences |
| UNCHANGED | Property exists in both with no attribute changes |

## Log Formats

| Category | Format |
|----------|--------|
| NEW | `NEW: property '<key>' discovered (included_in_template=true)` |
| REMOVED | `REMOVED: property '<key>' no longer present in provider` |
| EXCLUDED (baseline) | `EXCLUDED: property '<key>' is excluded from template (included_in_template=false)` |
| EXCLUDED (attr) | `EXCLUDED: property '<key>' attribute '<attr>' changed from '<old>' to '<new>' (included_in_template=false preserved)` |
| CHANGED | `CHANGED: property '<key>' attribute '<attr>' changed from '<old>' to '<new>'` |
| Summary | `Property change detection complete: <n> new, <n> removed, <n> excluded, <n> changed, <n> unchanged` |

## Bootstrap Behavior

- `saved=None` (first-time discovery): all fresh properties classified as NEW,
  `has_changes=True` always (even when fresh is empty).
- `saved={}` with `fresh={}` (steady-state empty): `has_changes=False`.
- Identical schemas: `has_changes=False`.
- EXCLUDED-only cases with no merged-schema delta: `has_changes=False`.

## Integration Point

`detect_property_changes()` is called from
`discover_properties_for_project()` in `issue_type_discovery.py`. The property
write to persistence is guarded by `result.has_changes` while
`at_least_one_success` remains unconditional (fetch success ≠ property change).
