# `/cli-app-sync diff-entities` — Reference

> Detailed algorithm and rules for the `diff-entities` sub-mode of `/cli-app-sync`.

## What it solves

The default `cli-app-sync` mode compares **known file pairs** (Program.cs ↔ Program.cs.template, vite.config.ts ↔ inline `init.ts`, etc.). It is good at catching **explicit drift** in those known surfaces.

It is blind to **trous** : when SmartStack.app introduces a brand-new entity (e.g. `AiTool` in v3.46) that no skill in `templates/skills/` mentions, nothing fires. The drift only surfaces during a manual audit (which is what just happened in the v3.46 alignment iteration 3 — 7 uncovered entities discovered by reading the `Domain/` directory by hand).

`diff-entities` automates that audit : it enumerates every Domain entity and reports those without coverage in the CLI skills.

## Inputs

| Variable | Default | Source |
|---|---|---|
| `APP_PATH` | `D:/01 - projets/SmartStack.app/02-Develop` | `--app-path=...` flag, otherwise the `02-Develop` worktree (develop, current source of truth) |
| `CLI_SKILLS` | `D:/01 - projets/SmartStack.cli/02-Develop/templates/skills` | hardcoded |
| `MIN_REFS` | `3` | a "well-covered" entity has at least 3 mentions across templates |

## Step-by-step algorithm

### 1. Enumerate Domain entities (the haystack)

Pattern (Bash + grep) :
```bash
APP_DOMAIN="$APP_PATH/src/SmartStack.Domain"

grep -rPnh \
  "^\s*public\s+(?:partial\s+)?(?:abstract\s+)?(?:class|record)\s+([A-Z][A-Za-z0-9]+)\s*:?\s*(?:BaseEntity|I[A-Z][A-Za-z]+Entity|IDomainEvent|DomainEvent)\b" \
  "$APP_DOMAIN" --include='*.cs' \
  | sed -E "s/.*\b(?:class|record)\s+([A-Z][A-Za-z0-9]+).*/\1/" \
  | sort -u > /tmp/app-entities.txt
```

This captures :
- Classes inheriting `BaseEntity` (most aggregate roots)
- Classes implementing `IXxxEntity` interfaces (`ITenantEntity`, `IOptionalTenantEntity`, …)
- Domain events (inheriting `DomainEvent` or implementing `IDomainEvent`)

It deliberately excludes :
- Pure value objects (no marker interface)
- Internal helper classes
- DTOs (those live in `Application/`)

If you suspect an entity is missed, broaden the regex or add a manual entry in the exclusion list.

### 2. Count references in CLI skills (the needles)

For each entity name `E`, count how many `templates/skills/**/*.md|*.sh` files mention it as a whole word :

```bash
CLI_SKILLS="D:/01 - projets/SmartStack.cli/02-Develop/templates/skills"

while IFS= read -r ENT; do
  COUNT=$(grep -rl --include='*.md' --include='*.sh' -E "\\b${ENT}\\b" "$CLI_SKILLS" | wc -l)
  echo -e "${ENT}\t${COUNT}"
done < /tmp/app-entities.txt > /tmp/entity-coverage.tsv
```

Counting **distinct files** (not occurrences) is intentional — 50 mentions in one file ≈ 1 file with deep coverage, not 50 files of shallow coverage.

### 3. Classify

| Bucket | Rule | Action |
|---|---|---|
| `[NEW UNCOVERED]` | `count == 0` | Propose a skill destination (table below) |
| `[THIN]` | `1 <= count < MIN_REFS` (default `MIN_REFS=3`) | Suggest extending an existing skill or adding a dedicated `references/` doc |
| `[COVERED]` | `count >= MIN_REFS` | Hide from report unless `--verbose` |

### 4. Suggest a skill destination

Map the entity's namespace to a target skill :

```python
def suggest_skill(namespace, entity_name):
    rules = [
        ("Domain.AI.Evaluations",   "ai-prompt/references/eval-framework.md"),
        ("Domain.AI.Tools",          "ai-prompt"),
        ("Domain.AI.Agents",         "ai-prompt"),
        ("Domain.AI.Skills",         "ai-prompt"),
        ("Domain.AI",                "ai-prompt"),
        ("Domain.Communications.Workflow",      "workflow"),
        ("Domain.Communications.EmailTemplate", "notification"),
        ("Domain.Communications",               "workflow"),
        ("Domain.Navigation",                   "application"),
        ("Domain.Platform.Administration.UiConfiguration", "application/references/themes-db-driven.md"),
        ("Domain.Platform.Administration",      "application"),
        ("Domain.Licensing",                    "conventions (Licensing section)"),
        ("Domain.Common",                       "conventions"),
        ("Domain.Support",                      "notification"),
        ("Application.Common.Interfaces.Hooks", "conventions (Entity Lifecycle Hooks)"),
    ]
    for prefix, skill in rules:
        if namespace.startswith(prefix):
            return skill
    return f"NEW SKILL — propose name based on namespace: {namespace.split('.')[-1].lower()}"
```

When the namespace doesn't match any rule, propose a brand-new skill name derived from the leaf segment.

### 5. Render the report

See the example output in `SKILL.md` (`<diff_entities_workflow>` section).

Sort order : `[NEW UNCOVERED]` first, then `[THIN]`, then (only with `--verbose`) `[COVERED]`. Within each bucket, alphabetical.

## Exclusion list

Some entities are intentionally NOT documented by any skill — they are infrastructure plumbing the SDK abstracts away. Maintain this list manually :

```
EXCLUDED_ENTITIES = {
    # Marker / framework types — not domain concepts
    "DomainEvent",          # abstract base, not a concrete event
    "BaseEntity",           # already documented in smartstack-api.md
    "EntityScope",          # enum, documented inline

    # Internal helpers — not part of the public SDK
    # (add as needed)
}
```

Implementation : after step 1, filter out names in this set before counting.

## Output flags

| Flag | Effect |
|---|---|
| `--verbose` | Show `[COVERED]` entities too (debugging) |
| `--report-only` | Don't write cache, print to stdout only |
| `--app-path=<path>` | Override the default APP_PATH (e.g. testing against `02-Develop` or another branch) |
| `--min-refs=<n>` | Custom threshold for `[THIN]` vs `[COVERED]` (default 3) |
| `--json` | Output machine-readable JSON instead of human report |

## Exit code

- 0 if zero entities classified `[NEW UNCOVERED]`
- 1 otherwise (so `diff-entities` can be a CI quality gate)

`[THIN]` entities do NOT fail the gate — they're warnings.

## Performance

- Step 1 (enumerate) : ~200-400 ms (grep over `Domain/` ≈ 100 files)
- Step 2 (count) : ~2-5 s (grep over `templates/skills/` ≈ 350 files × N entities)
- Total : < 10 s for a typical run — eligible for pre-commit hooks and CI

## Limitations

- **Heuristic regex for entity discovery** : may miss entities that don't follow the `class X : BaseEntity` pattern. Augment with explicit lookups for known patterns if false negatives appear.
- **Word-boundary count** : `AiTool` matches `AiTool` exactly but also matches a `AiTool` mention in a code comment. Acceptable noise — the goal is to spot zero-reference entities.
- **No DTO / Service coverage** : the skill only enumerates Domain entities. DTOs and Services live in `Application/` and are not in scope (they're typically auto-generated by MCP, so missing skill coverage is less impactful).
- **Doesn't catch DELETED entities** : if an entity is removed from app but still cited in skills, this skill won't flag it. Use the existing `cli-app-sync report` for that direction.

## Relation to other skills / checks

- **`cli-app-sync report`** : detects **content drift** in known file pairs. Complementary, not redundant.
- **`smoke-generation --quick`** : detects **broken imports** (historically the EntityLookup paradox, now resolved by the `ui-primitives` scaffolder; the detector remains for future paradoxes of the same shape). Catches references to things the CLI documents but neither `SmartStack.app` SDK nor the CLI's local primitive scaffolders provide. `diff-entities` catches the inverse direction.
- **`apex-verify`** : runs after generation, catches generated code that doesn't follow conventions. Operates at a different layer.

Together, the trio `cli-app-sync report` + `cli-app-sync diff-entities` + `smoke-generation` covers the full bidirectional drift surface.
