---
name: cli-app-sync
description: Detect and fix template drift between SmartStack.app (source of truth) and CLI templates
argument-hint: "[report|fix|check <file>]"
allowed-tools: Read, Grep, Glob, Bash, Edit, Write
---

## Current state (auto-injected)
- CLI version: !`node -p "require('./package.json').version" 2>/dev/null || echo "unknown"`
- CLI branch: !`git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown"`
- App branch: !`git -C "D:/01 - projets/SmartStack.app/02-Develop" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown"`
- App last commit: !`git -C "D:/01 - projets/SmartStack.app/02-Develop" log --oneline -1 2>/dev/null || echo "unknown"`

<objective>
Detect drift between SmartStack.app (the platform / source of truth) and the CLI project templates used by `ss init`.

SmartStack.app defines the reference architecture. The CLI templates generate **client projects** that consume the platform via NuGet/npm. Some differences are **intentional by design** (see exclusions below). This skill identifies **unintentional drift** that should be corrected.

See [references/comparison-map.md](references/comparison-map.md) for the full file mapping and exclusion rules.
</objective>

<quick_start>
```bash
/cli-app-sync                  # Full drift report (read-only)
/cli-app-sync report           # Same as above
/cli-app-sync fix              # Detect drift + apply fixes interactively
/cli-app-sync check Program    # Check a single file mapping
/cli-app-sync diff-entities    # List Domain entities NOT referenced by any skill
```
</quick_start>

<subcommands>

| Command | Description |
|---------|-------------|
| `/cli-app-sync` | Full drift report across all comparison points |
| `/cli-app-sync report` | Same as above (explicit) |
| `/cli-app-sync fix` | Detect drift and propose fixes interactively |
| `/cli-app-sync check <name>` | Check a single comparison point by name |
| `/cli-app-sync diff-entities` | List Domain entities present in app but NOT covered by any CLI skill (uncovered drift detector) |

</subcommands>

<paths>

**Source of truth (SmartStack.app):**
```
APP_ROOT = D:/01 - projets/SmartStack.app/02-Develop
APP_API  = $APP_ROOT/src/SmartStack.Api
APP_WEB  = $APP_ROOT/web/smartstack-web
```

**CLI templates (to validate):**
```
CLI_ROOT      = D:/01 - projets/SmartStack.cli/02-Develop
CLI_TEMPLATES = $CLI_ROOT/templates/project
CLI_INIT      = $CLI_ROOT/src/commands/init.ts
CLI_PWA_GEN   = $CLI_ROOT/templates/skills/development/frontend/pwa/cli/scaffold-pwa/generate.ts
```

</paths>

<workflow>

## Step 1: Resolve paths and verify access

```bash
APP_ROOT="D:/01 - projets/SmartStack.app/02-Develop"
CLI_ROOT="D:/01 - projets/SmartStack.cli/02-Develop"

# Verify both projects are accessible
[ -d "$APP_ROOT/src/SmartStack.Api" ] || { echo "ERROR: SmartStack.app not found at $APP_ROOT"; exit 1; }
[ -d "$CLI_ROOT/templates/project" ] || { echo "ERROR: CLI templates not found at $CLI_ROOT"; exit 1; }
```

## Step 2: Run comparison for each mapping

For each entry in the [comparison map](references/comparison-map.md):

1. **Read the reference file** from SmartStack.app
2. **Read the template file** from CLI (either `.template` file or inline content in `init.ts`)
3. **Compare** the relevant patterns/sections (not byte-for-byte — templates have `{{ProjectName}}` placeholders)
4. **Classify** the result: `OK`, `DRIFT`, or `INTENTIONAL`

### Comparison strategy by type

**Template files** (`templates/project/*.template`):
- Read both files
- Replace `{{ProjectName}}` with `SmartStack` in the template for comparison
- Compare structure, imports, method calls, middleware order

**Inline templates in `init.ts`**:
- Extract the template string from `init.ts` (look for the variable assignment)
- Compare key properties/patterns against the reference file
- Focus on: dependency versions, compiler options, proxy config, CSS directives

## Step 3: Generate report

```
================================================================================
              CLI ↔ APP TEMPLATE SYNC REPORT
================================================================================

SmartStack.app: {branch} @ {commit}
SmartStack.cli: v{version} @ {branch}
Date: {date}

COMPARISON POINTS: {total}
--------------------------------------------------------------------------------

  [OK]    Program.cs — Serilog, Kestrel, Swagger, AppInsights present
  [DRIFT] package.json — @types/node ^24.0.0 vs ^26.0.0 in app
  [DRIFT] vite.config.ts — bypass() present but missing ws reconnect
  [OK]    tsconfig.json — ES2022, verbatimModuleSyntax, erasableSyntaxOnly
  [OK]    index.css — @custom-variant dark present
  [OK]    DependencyInjection.Application — MediatR warning present
  [OK]    DependencyInjection.Infrastructure — SQL Server pattern matches
  [SKIP]  App.tsx — Intentionally different (client uses PageRegistry+DynamicRouter v3.7+)
  [SKIP]  ExtensionsDbContext — Intentionally simplified

--------------------------------------------------------------------------------
SUMMARY: {ok} OK | {drift} DRIFT | {skip} SKIPPED
================================================================================
```

## Step 4: Handle drifts (fix mode)

If running `/cli-app-sync fix` and drifts are detected:

```yaml
AskUserQuestion:
  header: "Fix drifts"
  question: "Drifts detected. How do you want to proceed?"
  options:
    - label: "Fix all (Recommended)"
      description: "Apply all fixes automatically"
    - label: "Fix interactively"
      description: "Review and approve each fix individually"
    - label: "Report only"
      description: "Show details without fixing"
  multiSelect: false
```

For each drift in fix mode:
1. Show the diff (what changed in app vs what the template has)
2. Propose the fix
3. Apply using Edit tool (for `.template` files) or inline edit (for `init.ts`)

## Step 5: Post-fix verification

After applying fixes:
```bash
cd "$CLI_ROOT" && npm run build 2>&1
```

If build succeeds, display summary of changes.

</workflow>

<intentional_differences>

These differences are **by design** and should always be marked `[SKIP]`:

| File/Pattern | Reason |
|---|---|
| `App.tsx` — routing pattern (`PageRegistry`+`DynamicRouter` v3.7+) | Client projects use their own routing entry point, not platform routing |
| `ExtensionsDbContext` | Intentionally simplified vs `CoreDbContext` — different architectural role |
| `api.ts` | Re-exports SmartStack client — wrapper pattern is correct |
| `DependencyInjection.Infrastructure.cs` | SQL Server connection pattern identical by design |
| Skills/Agents/Hooks | Installed globally via `ss install`, not by `ss init` |
| SmartStack.app-only skills | `bugfix-issue`, `feature-test`, `sonar`, `version-bump` are platform-specific |
| `appsettings.json` — **VALUES only** (ports 7074/7042 vs 6173/5142, `ConnectionStrings`, `{{Placeholders}}`, the empty `Jwt:Secret`) | Per-environment values differ by design. ⚠ The **key structure** is NOT an intentional difference: a client app runs the same package and binds the same options classes, so a missing section means a setting the client cannot configure. Compare key sets structurally — see the comparison map's `appsettings` section. |

</intentional_differences>

<comparison_categories>

### Backend comparisons

| ID | App reference | CLI template | Key patterns to check |
|---|---|---|---|
| `program-cs` | `src/SmartStack.Api/Program.cs` | `templates/project/Program.cs.template` | Serilog bootstrap, Kestrel HTTP/1.1, appsettings.Local.json, UseSmartStackSerilog, AppInsights, UseSmartStackSwagger, try/catch/finally |
| `di-app` | *(no direct equivalent)* | `templates/project/DependencyInjection.Application.cs.template` | MediatR warning comment, no double-registration |
| `di-infra` | `src/SmartStack.Infrastructure/DependencyInjection.cs` | `templates/project/DependencyInjection.Infrastructure.cs.template` | SQL Server connection pattern, AddDbContext |
| `appsettings` | `src/SmartStack.Api/appsettings.json` | `templates/project/appsettings.json.template` | **Structural key-set diff, both directions** (app-only paths = settings the client cannot configure; template-only paths = dead config shipped to clients). Not a pattern grep — see the comparison map. |

### Frontend comparisons

| ID | App reference | CLI template (in `init.ts`) | Key patterns to check |
|---|---|---|---|
| `package-json` | `web/smartstack-web/package.json` | `init.ts` → `packageJson` variable | Dependency versions (`@types/node`, `typescript`, `eslint-plugin-react-refresh`), testing stack (`vitest`, `@testing-library/*`), scripts (`test`, `test:watch`, `test:coverage`) |
| `vite-config` | `web/smartstack-web/vite.config.ts` | `init.ts` → `viteConfig` variable | Proxy bypass() function, plugin list, resolve aliases |
| `tsconfig` | `web/smartstack-web/tsconfig.json` or `tsconfig.app.json` | `init.ts` → `tsConfig` variable | `target`, `lib`, `verbatimModuleSyntax`, `erasableSyntaxOnly`, no `isolatedModules` |
| `index-css` | `web/smartstack-web/src/index.css` | `init.ts` → `indexCss` variable | `@import "tailwindcss"`, `@source` directive, `@custom-variant dark` |

### Skill-embedded literals (CLI side = template string in a scaffolder's `generate.ts`)

⚠ App reference exists only on the branch carrying the PWA split (`features/pwa`,
a6b9f2a5+) — pass `--app-path` accordingly. Full method + deviations in the
comparison map's "PWA Service-Worker Template" section.

| ID | App reference | CLI literal (in `CLI_PWA_GEN`) | Key patterns to check |
|---|---|---|---|
| `pwa-sw` | `web/smartstack-web/src/pwa/sw.ts` | `swSource()` template literal | Byte-faithful modulo banner + `${title}`; imports from `./cacheKey`; PURGE_API_CACHE handler |
| `pwa-cachekey` | `web/smartstack-web/src/pwa/cacheKey.ts` | `CACHE_KEY_SOURCE` | Byte-faithful below banner; `buildApiCacheKey` 3 dimensions (`__ss_tenant`/`__ss_lang`/`__ss_user`) |
| `pwa-swmessages` | `web/smartstack-web/src/pwa/swMessages.ts` | `SW_MESSAGES_SOURCE` | Byte-faithful below banner; `purgeApiCache()` posting `PURGE_API_CACHE_MESSAGE` |

### List representation & field captions (socle CSS ↔ scaffolder literals)

The client app renders a list through components nobody used to compare, while
relying on CSS that lives in the socle package. Full patterns in the comparison
map's "List-representation & field-caption" section.

| ID | App reference | CLI side | Key patterns to check |
|---|---|---|---|
| `btn-nowrap` | `web/smartstack-web/src/base.css` | *(consumed as-is by every generated page)* | `.btn` carries `white-space: nowrap` |
| `page-header-wrap` | `web/smartstack-web/src/components/ui/PageHeader.tsx` | `scaffold-layout/generate.ts` → `pageTemplate()` | Row wraps; title block has a real `basis-80`; actions block has neither `min-w-0` nor `shrink-0` |
| `table-representation` | `web/smartstack-web/src/components/ui/tableRepresentation.ts` | `scaffold-ui-primitives/generate.ts` → `tableRepresentationModule()` | Module emitted; sole owner of `BREAKPOINTS`; `ResponsiveDataTable` measures its container, never `window.innerWidth` |
| `table-min-width` | `web/smartstack-web/src/components/ui/DataTable.tsx` | `scaffold-ui-primitives/generate.ts` → `dataTableComponent()` | `<table>` carries a real `min-width` so `overflow-x-auto` can fire |
| `filter-grid` | *(no direct equivalent — CLI-only surface)* | `filterBarComponent()` + `render/list.ts` | `FILTER_GRID` declared once, used by both rows; `advanced={<>`; no `min-w-[…]` filter cells |
| `field-caption-signature` | `web/smartstack-web/scripts/audit-field-labels.mjs` | `scaffold-component/render/list.ts` | No caption both smaller and dimmer than `text-sm font-medium text-[var(--text-primary)]` |
| `cards-fallback` | *(no direct equivalent — CLI-only surface)* | `scaffold-component/render/list.ts` | Cards branch emitted unconditionally; `viewModes` gates only the manual toggle |

</comparison_categories>

<extraction_patterns>

### Extracting inline templates from `init.ts`

Templates in `init.ts` are assigned as template literals or objects. Use these patterns to locate them:

```
package.json  → search for: "const packageJson = {"
vite.config   → search for: "const viteConfig = \`"
tsconfig      → search for: "const tsConfig = {"
index.css     → search for: "const indexCss = \`"
```

### Extracting skill-embedded literals from `scaffold-pwa/generate.ts` (`CLI_PWA_GEN`)

```
sw.ts         → search for: "export function swSource("
cacheKey.ts   → search for: "export const CACHE_KEY_SOURCE = \`"
swMessages.ts → search for: "export const SW_MESSAGES_SOURCE = \`"
```

These are TypeScript template literals: before diffing against the socle file,
STRIP the leading `AUTO-GENERATED` banner block, un-escape (`\`` → `` ` ``,
`\\` → `\`), and for `swSource` treat `${title}` as `SmartStack`. Below the
banner the comparison IS byte-for-byte — any residual diff = DRIFT.

### Comparing versions

For `package.json` dependencies, compare:
- Major version range: `^22.0.0` vs `^24.0.0` → DRIFT if major differs by > 1
- Tilde vs caret: `~5.9.0` vs `^5.7.0` → DRIFT if strategy differs
- Missing packages: present in app but absent in template → DRIFT
- Extra packages in template are OK (client-specific needs)

### Comparing TypeScript config

Compare key compiler options:
- `target` and `lib` → must match
- `verbatimModuleSyntax` → must be present (replaces deprecated `isolatedModules`)
- `erasableSyntaxOnly` → must be present
- Other options → informational only

</extraction_patterns>

<output_format>

### Per-comparison detail (verbose)

```
--- program-cs ---
Reference: SmartStack.app/src/SmartStack.Api/Program.cs
Template:  templates/project/Program.cs.template

Checks:
  [OK] Serilog bootstrap logger (Log.Logger = new LoggerConfiguration...)
  [OK] try/catch/finally pattern
  [OK] ConfigureKestrel HTTP/1.1
  [OK] appsettings.Local.json loading
  [OK] UseSmartStackSerilog()
  [OK] Application Insights conditional
  [OK] UseSmartStackSwagger()
  [OK] Log.Information start/success messages

Status: OK
```

### Drift detail

```
--- package-json ---
Reference: SmartStack.app/web/smartstack-web/package.json
Template:  init.ts → packageJson

Drifts:
  [DRIFT] @types/node: template=^24.0.0, app=^26.0.0
  [DRIFT] Missing devDependency: @testing-library/dom

Status: DRIFT (2 issues)
```

</output_format>

<diff_entities_workflow>

## `/cli-app-sync diff-entities` — uncovered Domain entities

**Why this exists.** `cli-app-sync` (default mode) compares known files in `templates/project/` and `init.ts` against their counterparts in `SmartStack.app`. It detects **explicit drift** but is blind to **trous** : a brand-new entity in the platform (e.g. `AiTool`, `WorkflowVersion`, `AiEvalDataset` added in v3.46) that no CLI skill mentions yet. Without `diff-entities`, those gaps are only caught by manual audit.

See [references/diff-entities.md](references/diff-entities.md) for the full algorithm (rules, exclusions, output format).

### Quick algorithm

1. **Enumerate Domain entities** :
   ```bash
   APP_DOMAIN="${APP_PATH:-D:/01 - projets/SmartStack.app/02-Develop}/src/SmartStack.Domain"
   # Classes/records that look like aggregate roots (inherit BaseEntity or are records under Domain/)
   grep -rPnh "^\s*public\s+(?:partial\s+)?(?:class|record)\s+([A-Z][A-Za-z0-9]+)\s*:?\s*(?:BaseEntity|I[A-Z][A-Za-z]+Entity|IDomainEvent)" "$APP_DOMAIN" \
     --include='*.cs' \
     | sed -E "s/.*\b(class|record)\s+([A-Z][A-Za-z0-9]+).*/\2/" \
     | sort -u > /tmp/app-entities.txt
   ```

2. **Enumerate entities referenced in CLI skills** :
   ```bash
   CLI_SKILLS="D:/01 - projets/SmartStack.cli/02-Develop/templates/skills"
   # Any PascalCase token in code blocks that matches an entity name
   while IFS= read -r ENT; do
     COUNT=$(grep -r --include='*.md' --include='*.sh' -lE "\b${ENT}\b" "$CLI_SKILLS" | wc -l)
     echo "${ENT}|${COUNT}"
   done < /tmp/app-entities.txt > /tmp/entity-coverage.tsv
   ```

3. **Classify** each entity :
   - `count = 0` → `[NEW UNCOVERED]` — propose a skill to cover it
   - `count = 1-2` → `[THIN]` — covered but might benefit from a dedicated reference
   - `count ≥ 3` → `[COVERED]` — well documented (skip in report unless `--verbose`)

4. **Suggest a skill destination** based on the entity's namespace :

   | Namespace prefix | Suggested skill |
   |---|---|
   | `Domain.AI.Agents.*` / `Domain.AI.Skills.*` | `ai-prompt` |
   | `Domain.AI.Evaluations.*` | `ai-prompt` (`references/eval-framework.md`) |
   | `Domain.AI.Tools.*` | `ai-prompt` |
   | `Domain.Communications.Workflow*` | `workflow` |
   | `Domain.Communications.EmailTemplate*` | `notification` (or `workflow`) |
   | `Domain.Navigation.*` | `application` |
   | `Domain.Platform.Administration.UiConfiguration.*` | `application/references/themes-db-driven.md` |
   | `Domain.Licensing.*` | `conventions` (Licensing section) |
   | `Domain.Common.*` (interfaces, enums) | `conventions` |
   | `Domain.Support.*` | `notification` (or new skill) |
   | other | propose a new skill name based on the namespace |

### Output (typical)

```
================================================================================
              UNCOVERED DOMAIN ENTITIES — {date}
================================================================================
APP : develop @ {commit}
Total Domain entities scanned: 87

[NEW UNCOVERED] (0 references in templates/skills/)
  AiTool                     → Domain/AI/Tools/         → ai-prompt skill
  AiSkillTool                → Domain/AI/Tools/         → ai-prompt skill
  AiEvalDataset              → Domain/AI/Evaluations/   → ai-prompt/references/eval-framework.md
  AiEvalDatasetItem          → Domain/AI/Evaluations/   → idem
  AiEvaluation               → Domain/AI/Evaluations/   → idem
  AiEvalResult               → Domain/AI/Evaluations/   → idem
  AiAgentExecution           → Domain/AI/Agents/        → ai-prompt skill
  AiAgentStepExecution       → Domain/AI/Agents/        → ai-prompt skill
  WorkflowVersion            → Domain/Communications/   → workflow skill
  IBeforeCreate              → Application/Hooks/       → smartstack-api.md (Hooks section)
  IDomainEvent               → Domain/Support/Events/   → smartstack-api.md (Domain Events)

[THIN] (1-2 references — consider dedicated docs)
  License                    (2 refs)  → smartstack-api.md (Licensing section)
  BrandingAsset              (1 ref)   → themes-db-driven.md
  UiThemeTenantPublication   (1 ref)   → themes-db-driven.md

[COVERED] (≥3 references — well documented, hidden unless --verbose)
  87 entities — 76 covered, 11 thin/uncovered

--------------------------------------------------------------------------------
SUMMARY : 11 entities need attention (8 NEW UNCOVERED, 3 THIN)
================================================================================
```

### Integration with the rest of `cli-app-sync`

`diff-entities` is independent from the comparison map (no overlap with `report` / `fix`). It only adds new findings ; it never modifies templates. To act on findings, the user runs the suggested skill author flow manually (or invokes `/apex` if a brand-new domain warrants a full skill).

Recommended cadence :
- Run after every bump of the `APP_PATH` branch (e.g. moving from a merged feature worktree back to `02-Develop`)
- Run before each release of the CLI (catch entities the platform shipped that the CLI hasn't documented yet)
- Run weekly as a background `/loop /cli-app-sync diff-entities --report-only`

</diff_entities_workflow>

<success_criteria>
- All comparison points from the map are checked
- Intentional differences are correctly identified as SKIP
- Drift detection catches: missing patterns, outdated versions, removed options
- Fix mode correctly updates template files and init.ts inline templates
- Build passes after fixes are applied
- Report is clear, actionable, and easy to scan
- `diff-entities` lists every Domain entity NOT covered by any CLI skill, with a suggested destination skill
- `diff-entities` matches the manual audit pattern that surfaced AiTool / AiEvalDataset / WorkflowVersion / Hooks gaps in the v3.46 alignment work
</success_criteria>
