# SmartStack Skills — Index

> Organized by business phase. Each skill has a `SKILL.md` + optionally a `cli/`.
> A sub-context loads only the relevant skill + 1-2 JSON spec files.

> **Language rule**: All skills must be written in English.

## Scope

This directory ships **only the skills deployed by the SmartStack CLI** into
`~/.claude/skills/` (or `./.claude/skills/` with `--local`). Each skill exists
to **analyze**, **develop**, **audit**, or **validate** a client
SmartStack.app project.

Skills that operate on the SmartStack.Studio source itself (Studio self-audit,
EPCT workflow for Studio features, Electron app debugging, Studio runtime
internals) are **not deployed by the CLI** — they live in the Studio repo.

## Architecture — Skills + colocalized CLIs (no MCP)

The legacy `SmartStack.mcp` server was removed in v5. Every operation it used
to perform is now implemented as a skill with a colocalized CLI:

```
skills/
├── lib/              # Shared TypeScript utilities (detector, dotnet, fs, git, …)
├── efcore/           # EF Core migrations (6 CLIs)
├── gitflow/          # GitFlow workflow (12 CLIs)
├── external/         # Wrappers around external tools (ctx7, dev-browser)
├── business-analyse/             # 28 BA skills (audit, define, generate, modeling, design, test data)
├── ba-develop/       # PRD-driven phased pipeline (Domain → Data → API → Frontend)
├── ba-develop-plan/  # Multi-module orchestrator over dev-plan.json (preflight + waves)
├── development/
│   ├── backend/        # entity, dto, repository, business-layer, controller, …
│   ├── frontend/       # theme, layout, routes, component, api-client, …
│   ├── audit-dev-*/    # per-layer audit rule packs
│   ├── debug/, run/, testing/
├── validation/         # validate-conventions, cross-validate, eslint, roslyn, readiness-report, project-inventory
├── conventions/        # SmartStack.app conventions reference
├── init/, upgrade/, review/   # Project lifecycle skills
├── check-version/      # Verify package.json ↔ docs alignment
├── cli-app-sync/       # Detect template drift between SmartStack.app and CLI
├── dev-start/          # `ss dev` orchestrator (backend + frontend + admin credentials)
├── documentation/      # Technical doc generator (extract-doc + scaffold-doc CLIs)
├── external-api/       # /external-api — publish a PUBLIC (third-party, M2M) API from a client extension
│                       #   (derive-external-api-spec, scaffold-external-api, publish-api-contract,
│                       #    provision-external-app) — audited by development/audit-dev-external-api
├── quick-search/       # Fast codebase Q&A
├── smoke-generation/   # Static analysis of CLI templates against SmartStack.app
├── support-report/     # /support-report — verified CLI-failure reports for support@atlashub.ch
├── test-conventions/   # Conventions for WRITING tests in a client extension
├── uat/                # Full UAT pipeline (6 CLIs: plan, provision, api, ui, report, run)
├── utils/              # Utility sub-skills for CLI development
└── validate-feature/   # End-to-end validation of a scaffolded feature
```

Internal CLIs are invoked via:

```bash
npx --prefer-offline tsx skills/<phase>/<skill>/cli/<cli>/index.ts --spec '<JSON>'
```

Each CLI returns a JSON envelope on stdout (see `lib/output.ts`).

## Infrastructure skills

The following skills provide cross-cutting infrastructure (not a business phase):

| Skill | Role |
|-------|------|
| `efcore` | EF Core migration workflow (`create`, `squash`, `rebase-snapshot`, `apply`, `list`, `status`) |
| `external` | Wrappers around external tools (`context7`, `dev-browser`) |
| `gitflow` | GitFlow workflow (12 CLIs covering init → finish + update + generate-msg) |
| `lib` | Shared TypeScript utilities for CLIs |
| `support-report` | `/support-report` — when a colocated CLI fails at a CLIENT site, the sanctioned path instead of "fixing" the deployed copy under `~/.claude` (skills-guard blocks that): the `create` CLI is the FAIL-CLOSED verifier (evidence rule: stack trace inside the skills tree OR ≥ 2 identical-signature runs OR an envelope contradicting ITSELF — a `dedupOf` mirror finding in err while its primary is ok on the same scope, read mechanically from `report.findings[]` (the DemoGestionFlotte XD-005 incident) — OR an argued verdict dispute anchored on `disputedRuleIds` the envelope carries; controlled envelopes, environment, single unproven runs, flaky signatures and unanchored disputes are refused with guidance), checks installed-vs-npm-latest (newer → propose update + retest before transmitting), attaches what the CLI READ (`inputs[]` — traversal-guarded, excluded, scrubbed, capped — lib/support-bundle) and writes the deduplicated French report bundle + `support-<fingerprint>.zip` (+ sha256, `repro.md`) to `.smartstack/support/<fingerprint>/` for the user to send to **support@atlashub.ch** |

## lib (shared utilities)

| File | Role |
|------|------|
| `fs.ts` | File system helpers — findFiles (minimatch-based), read/write, path security |
| `ba-rules-rows.ts` | THE first deterministic `règles-métier.md` parser (until it, no .ts read the rules docs — the BR silent-loss root) — `parseRules` (heading `### BR-NNN — Titre` + typed fields Type/Sévérité/Portée/Condition/Expression/Code d'erreur/Cas valides·invalides/Cas d'usage liés + structured `Flow` transitions `from → to (by, guard)`), `loadRulesDoc`, `loadModuleRules` (deepest-scope aggregation module + sections, cross-doc duplicate codes warned — BR codes are DOC-scoped), `enforceableRules` (err+warn). Tolerant (warnings, never throws), drift-tested against the doc-templates + create-business-rules skeletons. Consumers: derive-rule-links (PRD-129/130), DEV-API-008 catch-up, DEV-TEST-009 parity |
| `ba-actors.ts` | Shared `acteur.md` parser + actor → seeded-role mapping — `parseActors`/`loadAppActors` (headings `### BA-…-AC-NNN — Label` + **Type**/**Catégorie** fields), `mapRoleCategory` (tolerant FR-label → RoleCategory enum, drift-tested against `_workflow/role-taxonomy.md`; `Global` refused). The role identity rule lives here: `code = slugifyRoleCode(label)`, `name = label` verbatim — what makes `actorMatchesRole` (ba-rbac-rows) hold by construction. Consumed by create-rbac/derive-rbac-grants |
| `git.ts` | Git commands — branch, diff, changed/staged files |
| `detector.ts` | `findSmartStackStructure()` — locates Domain/Application/Infrastructure/Api/web/migrations |
| `dotnet.ts` | `.csproj` parsing, DbContext detection, `detectNamespaces()` |
| `navroute-parser.ts` | Extracts `[NavRoute]` attributes from C# controllers |
| `string-utils.ts` | kebab/camel/pascal/singular/plural helpers |
| `core-catalog.ts` | Canonical SmartStack Core catalogue — V1 whitelist (+FR/EN aliases, e.g. Organisation/Company→TenantOrganisation), reserved names, person triggers, whole-token matchers, `CORE_WHITELIST_V1(_NAMESPACES)`, `CORE_PROJECTABLE_FIELDS`. Drift-tested against the inline tables in the BA SKILL.md files |
| `capability-catalog.ts` | Canonical catalogue of transverse platform CAPABILITIES (services, not entities — the axis core/platform-catalog can't index): file-storage (`IFileStorageService` — the "no file mechanism exists" incident closer), global-search, time-entry-refs, code-generation (`AddSmartStackCodeKey` — the "every module re-models its own counter table" incident closer), email-sending. `PLATFORM_CAPABILITIES` + `matchCapabilityTrigger` (whole-token OR trailing-word: `InteractionDocuments` → hit; `Documentation` → no) + `matchCapabilityAttribute` (`binary`/`varbinary`/… → file-storage). Feeds C-6 / DM-019 / CODE-006; drift-tested (`platform-capabilities:v1`) against create-data-model, audit-data-model and audit-cross-ref-code |
| `code-pattern-grammar.ts` | The socle's CLOSED code-pattern grammar (mirror of `CodePatternGrammar`/`CodePatternValidator` — tokens `{YYYY\|YY\|MM\|DD\|TENANT\|SEQ:n}` + derived `{FIELD\|UPPER\|LOWER\|SLUG\|INITIALS:Champ}`/`{ABBR:Champ:n}`, `validateFormat` fail-closed, `referencedFields`) + the `**Code pattern**` line contract (`ENT_BLOCK_RE`/`CODE_PATTERN_LINE_RE` + `parseCodePatternLine` — FR/EN facet parsing incl. `libellé « … »`/`label "…"` and `surchargeable à la création`/`supplied on create`, absent facet = null, never a default) + the code-side nets: `findCodePatternNearMisses` (table-cell/wrong-case/unbolded/prose shapes — the green-by-vacuity killer, consumed by derive-code-specs, DEV-API-022, DEV-UI-034, DM-021) with `CODE_PATTERN_CANONICAL_FORM` (the ONE remedy wording), the `code-like-lexicon:v1` (`CODE_LIKE_ATTRIBUTE_WORDS` + `codeLikeWordOf` — Référence/Numéro/Matricule ARE codes, whole-word over the shared `splitWords`, drift-locked against create-data-model attributes.md), and `CODE_SAISI_LINE_RE`/`parseCodeSaisiValue` (the TYPED-species marking). Consumers: scaffold-coded-entity (format gate), scaffold-entity (`GetCodeInputs` emission), audit-dev-api DEV-API-022, audit-dev-frontend DEV-UI-034, derive-code-specs, audit-ba DM-017/DM-021/SCR-015 |
| `proposal-tiers.ts` | Canonical 3-tier BA proposal taxonomy (Obligatoire / Suggestion / Élargissement) — `PROPOSAL_TIERS` + the `proposal-tiers:v1` markdown block. Methodology in `business-analyse/_workflow/proposal-method.md`; drift-tested against the inline tables carried by create-menu, create-use-case (discovery), create-business-rules (identify) and create-screen |
| `detail-cadence.ts` | Canonical DETAIL CADENCE of the two-pass BA phases — `DETAIL_CADENCES` (**Pas à pas** one gate per item / **Par lot** one gate per batch, the DEFAULT / **Enchaîné** no gate, audit verdict instead), `DETAIL_CADENCE_KEYS` (`step`/`batch`/`chained`), `DEFAULT_DETAIL_CADENCE_KEY` and `NON_INTERACTIVE_DETAIL_CADENCE_KEY` (a `/ba-loop` subagent may not call AskUserQuestion — it would deadlock on a question it is forbidden to put). Answers the question the second pass never asked the user: how many items do you draft before I get a say. Methodology in `business-analyse/_workflow/detail-cadence.md`; drift-tested against the inline tables carried by create-use-case (`levels/detail.md`) and create-business-rules (`levels/elaborate.md`). The worklist it iterates is DERIVED, not stored — `lib/ba-use-cases.undetailedUseCases` |
| `page-spec-related-tabs.ts` | Canonical Zod schema of the 360 `relatedTabs[]` (detail pagespec) — `PageRelatedTabSchema` (+ BA aliases `relationship`/`screenTarget`, + `placement: tab\|band` — band rejected on non-summary at the schema), `normalizePageRelatedTab` (labelKey/permission defaults), `relatedTabPlacementOf`/`placementForDisplayMode` (THE derived-placement SSOT: summary → band cartouche row above the strip, else strip trigger — scaffold-component and DEV-UI-031 both resolve here), `parseRelatedTabs`, `relatedPluralOf`, `fkFilterFields` (SSOT of the Guid-FK filter params both backend strata + api-client expose). Invariant: `relationFk` === wire query param === `[FromQuery] Guid?` === `Where(x => x.{Pascal})` |
| `page-spec-lifecycle.ts` | Canonical Zod schema + resolver of the first-order `lifecycle` block (FORM pagespecs) — `PageLifecycleSchema` (statusField + phases anchored VERBATIM on the status enum; phases OWN later-phase fields and/or make fields `requiredFields` once a status is reached), `resolveLifecycle` (seeds the `field.phase`/`requiredInPhase` pivots, per-field effects map, pure/identity on absence), `lifecycleVisibleWhen` (compiles a phase's statuses into the `visibleWhen` grammar — `===` / `IN (…)`). Compile-down: create form/payload exclusion + `isEdit && <status guard>` on edit (scaffold-component), Create DTO/Command/factory exclusion via the per-field `phase` flag (scaffold-business/controller/entity/api-client — the Create-honnête contract: optional non-phased fields now ride Create as nullable). Written by create-prd, `create-prd/cli/derive-lifecycle` (deterministic backfill + the PRD-120 check engine) and the Phase 3.1 lifecycle leg (`apply-form-directives`, additive); audited by SCR-019 / XD-007 / PRD-120/121 / DEV-UI-044; drift-locked (readers import the lib) |
| `pwa-meta.ts` | Canonical Zod schema of the per-page PWA/offline declaration (`pagespec.pwa`) — `PwaMetaSchema` (`support: full\|adapted\|desktop-only`, `offline: read\|write\|bool`), `normalizeOffline` (write ⊇ read), `validatePwaMetaV1` (rejects `full` + desktop-only-offline), `toPageMobileMetaLiteral` (maps onto the socle's `PageMobileMeta` — offline collapses to boolean `offlineCapable`; write-ness is a data-layer fact, never registry metadata). Consumed by scaffold-routes / scaffold-component / scaffold-api-client / derive-pwa-spec / audit-dev-pwa. **Deliberately carries NO shell metadata** (bottom-nav rank, mobile icon…): the mobile shell's palier navigation derives entirely from the DB menu. The file header documents the 6-step cascade any future shell field must follow. |
| `permission-actions.ts` | Canonical RBAC action vocabulary + permission FLOOR + path grammar — `PERMISSION_ACTIONS` (12, mirror of the platform `PermissionAction` enum), `PERMISSION_ACTION_ENUM` (action → C#/SQL literal incl. `read.all → Read`), `PERMISSION_DATA_ACTIONS` (scaffold-controller's 10), `PERMISSION_FLOOR_ACTIONS` (the 7 seeded by default: access/lookup/read/create/update/delete/execute), `FLOOR_BY_GRAIN` (app+module = access only; section+resource = full 7), multi-grain `parsePermissionPath` (2..6 segments, `read.all` tier disambiguation, `RESERVED_NODE_CODES` read/all), `floorPathsForNode` (shared by scaffold-core-seed's actual seed AND the BA mirror block). Drift-tested (`permission-actions:v1`, `permission-floor:v1`) against create-rbac, audit-prd PRD-055, access-rules.md, scaffold-controller, derive-seed-delta |
| `registry-index.ts` | The TOLERANT PageRegistry parser + layout detector — `buildRegistryIndex(webRoot)` (balanced-argument scanner: canonical per-module const-ref, legacy MCP-era monolith with inline `lazy(() => import(…))`, static imports, verbatim `{ mobile: … }` meta), `detectRegistryLayout` (`per-module \| legacy-monolith \| mixed \| none`), `parseRegistrySource`, `resolveRegistryImport`, `PER_MODULE_REGISTRY_RE`. `lib/routes-registry.PAGE_REGISTER_RE` stays the STRICT emission contract; THIS is what audits (audit-dev-pwa, audit-dev-frontend), the fail-closed layout guards (aggregate-component-registry, scaffold-routes) and split-component-registry read with. Closes the PickEBike fail-open (empty scan on a monolithic app) |
| `detail-tab-strip.ts` | SSOT of the RENDERED detail tab bar + the budget thresholds — `resolveDetailStrip` (mirror of scaffold-component render/detail.ts: unified fiche ⇒ field-tab term 0, band cartouches excluded via `relatedTabPlacementOf`, synthetic « info » trigger counted, Zod rejects out), `entityViewsFromPagespecFilenames` (`<Entity>.<view>.md` → per-entity view sets), `DETAIL_TAB_BUDGET_WARN/ERR` (7/9) + `DETAIL_SUMMARY_DEMAND_THRESHOLD` (4). Consumers: derive-related-tabs check (RTV-108/PRD-133), derive-detail-summary (PRD-134), audit-dev-frontend DEV-UI-047 |
| `page-spec-coded-entity.ts` | Canonical Zod schema + helpers of the pagespec `codedEntity` flag — `CodedEntityFlagSchema` (legacy boolean OR the enriched object `{label, supplied, codeKey, codeInputFields}`, `.passthrough()` so unknown slots survive any writer), `isCodedEntity` (THE reader that replaced every `=== true`), `isSupplied`, `codedLabelOf` (fr→fr??en, others→en??fr — the « Référence » business label of the code column/field), `withSupplied` (merge-preserving write, canonical collapse to `true`). Written by derive-code-specs (stamps facets + codeKey/codeInputFields so the pagespec is SELF-SUFFICIENT), read by scaffold-component (readonly gate + i18n label + create-only SmartCodeField injection), scaffold-business/controller/api-client (supplied create surface) |
| `display-field.ts` | SSOT of "which field NAMES a row" — `PREFERRED_DISPLAY_FIELDS` (stored cascade: Name/Label/Code/Title/Libelle/Titre/Reference/Number/Numero), `LOOKUP_DISPLAY_FIELDS` + `preferredDisplayFieldsFor(classification)` (the same cascade **minus `Code`** on a reference table — Code sits 3rd and Libelle 5th, which is how 11 référentiel services shipped every combobox rendering `A_FAIRE` instead of « À faire » and got fixed BY HAND while the generator kept re-emitting it), `PREFERRED_PROJECTED_DISPLAY_FIELDS` (Person entities), `DISPLAYISH_RE` (kebab/camel key test). Consumed by scaffold-business `pickDisplayField` (which FAILS CLOSED — no silent first-string/GUID label; `**Affichage**` in entité.md is the authoring channel, `: Id` the conscious opt-out, and `classification` selects the cascade) and derive-related-tabs-data |
| `ba-referential-codes.ts` | THE shared « a reference value does not carry a code » contract — its LABEL is its identity and its natural key; only the USER may decide a reference table carries one, and that DATED decision (`- **Code décidé** : … — décision utilisateur du AAAA-MM-JJ`) OVERRIDES the rule. `classifyReferentialCode` is the ONE verdict (`clean`/`decided`/`near-miss`/`allocated`/`blocked`/`reprise`) that DM-022, DM-012 and the backfill share, with `allocated` tested BEFORE `decided` on purpose (a decided code is SAISI, never ALLOUÉ — otherwise a decision line would silence a real defect); `citationsFor` + the four source converters (`ruleCitationSources`, `acceptanceCitationSources`, `prdCitationSources`, `initialValuesSources`) build the inventory of what BREAKS if the code goes — code values only, whole-word, accent-folded with a length-preserving fold so excerpts stay verbatim. Lives in lib/ (not beside the CLI) because audit-ba may only import cross-skill from the ENUMERATED installer allowlist. Consumers: `create-data-model/cli/derive-referential-codes`, audit-ba DM-022/DM-012/DM-015 |
| `ba-sources.ts` | THE shared contract of the CLIENT-SOURCES registry (`.smartstack/sources/`, SIBLING root of `ba/`, committed) — index.json contract (`SourceEntry`: SRC-NNN codes never renumbered/reused, 12-hex content fingerprint = dedup key, `blocked/*` typed statuses so « ingested, 0 extracts » cannot exist), `parseSourceDoc` (the normalized citable `source.md`: anchor + §n sections + tags + verbatim blockquote extracts + scopeNotes), `loadSourcesRegistry` (index ↔ disk reconciled fail-closed, `controls` anchors-vs-parsed so a mute parser cannot look green), the citation grammar `SRC-NNN [§n]` (`citationsInText`, `resolveCitation` 4 states), and `searchSources` (accent-folded, counts ALWAYS published — the phases' targeted-extraction path, nobody reads raw/). Lives in lib/ because audit-ba may only import cross-skill from the ENUMERATED installer allowlist. Consumers: `create-sources/cli/{ingest,search,status}`, audit-ba SRC-001..007 |
| `ba-list-split.ts` | THE top-level splitter for BA markdown lists — the reference grammar puts commas INSIDE parentheses (`code (text, requis)` is ONE field), so a bare `split(',')` double-counts entries and truncates what it cites (the DemoGestionFlotte 2026-09-02 SCR-017/018 false-positive class + SCR-019 corrupted evidence). `splitTopLevel` (separators cut at paren depth 0 only, `«…»`/backtick spans protected, orphan `)` clamped), `splitBaList` (+ trim, ONE trailing-`.` strip, `''`/`—` filter — the shared normalization splitList/pushSplit/traceability each half-had), `enumValuesOf` (a Contraintes cell's `/`-bearing top-level segments only — `draft/submitted/approved, requis` never yields a phantom value). Consumers: audit-ba screen-blocks (Colonnes/Filtres/Indicateurs/Champs/Section-Onglet groups, continuations, action pairs) + xd `stateAttrsOf`, ba-screens kanban fallbacks (dropped tokens now WARNED, never silent), ba-use-cases secondaryActors, ba-entities traceability |
| `ba-placeholder.ts` | THE « does this document carry a data model? » predicate — `isPlaceholderEntityDoc` (= NO `### ENT-NNN` block, em-dash or ASCII dash) + `PLACEHOLDER_MARKER_RE` (messages only). Three CLIs used to carry their own `/_À définir lors de la phase/` and test it against the WHOLE file: `/ba-create-menu` writes that marker under every not-yet-authored section, so a fully authored `entité.md` routinely keeps one leftover line — and the document was discarded with every relation, i.e. every foreign key (ba-relations skipped it SILENTLY, create-plan-development fell back to index.md deps, derive-lookup-grants REFUSED a complete model). The cascade was the treacherous part: DM-009/DM-014 cried isolation while DM-004/DM-013/DM-005 went green on an empty graph and control-counts saw nothing (it reconciled headings, never relations). Consumers: ba-relations, create-plan-development/parse, create-rbac/derive-lookup-grants |
| `ba-menu-tree.ts` | THE shared contract of the `.smartstack/ba/` MENU TREE as a machine object — the `<!-- ba:node … -->` anchor (`parseNodeAnchor` / `updateNodeAnchor` preserve `depends=`, `previousCodes=` and every unknown attribute in order — the silent-loss class of the full re-Write), the three spellings of a node code (folder `exchange-history` / long-code `EXCHANGE_HISTORY` / nav `exchange-history` — `sectionCodeToFolder`, `sectionFolderToCode`, `navFormOf`), the long-code engines (`renameCodeSegment` module/section/resource on 3- AND 4-segment codes, `removeCodesFromText` with a module wildcard, `countScopeCodes`), the rbac.md human permission paths (`renamePermissionPaths` / `removePermissionPaths` — machine blocks byte-identical), the pagespec references (`renamePagespecRefs`: exact bounded tokens inside the ```json block, re-serialised like the derive CLIs; `residualTokenScan` PUBLISHES what the token lists miss, machine blocks skipped), `renameCrossModuleScopes` (`scope cross-module (APP/MOD)`), the line-level `spliceEnfants` / `spliceDependances`, `loadMenuTree` / `findNode` (same folder exclusions as audit-ba's corpus), `placeholderDocs` (the six concept docs) and `renderIndexMd` (doc-templates shape, empty marker). Also the SINGLE SOURCE of the mechanical menu/section audit constants (`EMPTY_HP_MARKER`, `CONTEXT_MIN_CHARS`, `CONFIG_APP_RE`, `TECHNICAL_FEATURES_RE`, `EXTENSION_DECLARATION_RE`, `AMBIGUOUS_SECTION_NAMES`, `XAPP_SECTION_PATTERNS`, `MENU_BOUNDS`) that audit-ba `rules/menu.ts` + `rules/sections.ts` AND create-menu `cli/menu-node` apply — a node the CLI accepts is a node the audit accepts. Lives in lib/ because the installer only rewrites cross-BA-skill imports for an ENUMERATED list (create-rbac, create-screen): consumers `create-menu/cli/menu-node` (add/rename/delete) and `reconcile-menu/cli/reconcile-menu` (re-exports for its tests) |
| `entity-columns.ts` | THE definition of « which columns an **Index** may name » — `knownIndexColumns({fields, owningFks, extra, coded})` = declared fields ∪ synthesised FK columns (owning `*→1`/`1→1`, and the FK a parent's `1→*` puts on the child) ∪ data-scope columns (scaffold side) ∪ `code` when coded ∪ `TenantId`/`CreatedAt`; `isTenantColumn`. Imported by BOTH scaffold-entity/validate.ts (dev time, hard error inside /ba-develop Phase 2) and audit-ba DM-023 (BA time, the same error six phases earlier) so the two cannot drift; pinned by its own test |
| `implicit-suffixes.ts` | SSOT of the DynamicRouter IMPLICIT_SUFFIXES table (25 suffixes + BASE_VIEW_KEYS + `isImplicitSuffix`) — mirrors the socle's `DynamicRouter.tsx`. A componentKey's last segment either matches a suffix or must resolve to a seeded nav node. Consumed by uat-plan (`project-roles.ts` typed wrappers) and audit-dev-frontend DEV-UI-046; prose copy in `development/audit/routing-dynamic/SKILL.md` points here |
| `external-api-catalog.ts` | SSOT of the PUBLIC stratum — the surface a THIRD PARTY consumes machine-to-machine. Pins the platform's data seam: `PUBLIC_STRATUM_PREFIX` (`/api/v1/export`, the ONLY prefix the external-app route guard whitelists), the 4th-segment code resolution (`codeFromPublicPath`, mirror of `ResolveEndpointCode`), the code grammar `{app}-{section}[-{op}]` + `validatePublicApiCode` (UNIQUE index, 100 chars, platform-reserved codes → a collision fails the BOOT), `PUBLIC_API_OPERATIONS` (one catalogue code per operation with EXACT permissions — the catalogue holds ONE `RequiredPermission` per code and those strings BECOME the app's claims, so a single all-verb code would force a wildcard that widens every existing grant), `catalogRowsFor` (the derivation the scaffolder, the contract publisher and the audit all share), `permissionMatches` (mirror of the platform `PermissionMatcher` — the difference between 200 and a runtime 403 `permission_mismatch`) and the BA authoring channel `parseExternalApiValue`/`renderExternalApiMarker` (`- **API externe** : read, create` on an ENTITY — a public endpoint has no screen, so no pagespec can declare it). Drift-tested (`external-api-catalog:v1`) against the `/external-api` SKILL.md |
| `guarded-rm.ts` | `guardedRm(relPaths, {outdir, dryRun})` — the legacy-cleanup sweep every scaffolder's `rmSync(legacyPaths)` goes through: an `@customised` file is NEVER deleted (bespoke seam), every deletion/preservation is returned for envelope tracing (`data.filesRemoved` + warnings), traversal-safe. Adopted by scaffold-api-client + scaffold-business/controller/entity/screen-controller/tests/tests-from-ac |
| `template-loader.ts` | Handlebars wrapper with registered helpers |
| `output.ts` | Standardized stdout JSON envelope for dev CLIs |
| `rule-contradictions.ts` | An audit envelope disagreeing with ITSELF — `detectRuleContradictions` (a `dedupOf` mirror finding in err while EVERY covering finding of its primary is ok; err mirrors only, a warn twin is `relatedTo`; primary absent = no proof, fail-closed), `mergeContradictions`/`contradictionKey` (union by mirror|primary|scope, sorted), `contradictionSignature` (the RULE PAIR — scopes out: one bug on ten modules = one report), `contradictionExcerpt`, `scopeCovers`/`scopeKey`/`scopeLabel`. ONE detector, two readers: the audit-ba engine runs it on its own findings at the end of every run (`selfContradictionWarnings` → envelope warning `audit.rule-contradiction`), /support-report runs it on a captured envelope's `report.findings[]` (class `rule-contradiction`). Pure, structural over the finding shape; lives in lib/ because audit-ba may only import cross-skill from the enumerated installer allowlist |
| `support-report.ts` | The fail-closed verifier behind `/support-report` — `classifyFailure` (evidence rule: envelope-controlled → usage-error / **rule-contradiction** (lib/rule-contradictions over `report.findings[]`, re-exported here) / no-failure (with `envelopeRuleIds`, the anchor a verdict dispute must name), stack frame inside the skills tree → cli-internal with 1 run, else ≥ 2 identical normalized signatures; environment signatures and single/diverging runs refused), `normalizeSignature`/`normalizeCommand` (path-fold + volatile masking + `--spec-file`/inline `--spec` masked)/`fingerprintOf` (one defect = one fingerprint = one report; `contradictionSignature` = the rule PAIR, scopes out), `scrubSecrets` (the report travels by email), `isNewerVersion`, `renderReportMd` (French, addressed to support@atlashub.ch — « Contradiction de règles » table, « Entrées jointes », the zip in the transmission block). Consumed by `support-report/cli/create` |
| `support-bundle.ts` | The REPRODUCTION half of a support report — `collectInputs` (spec `inputs[]` relative to the project: `validatePathSecurity` (an escape refuses the whole spec), symlinks never followed, `BUNDLE_DEFAULT_EXCLUDES` (VCS, bin/obj/dist, `.smartstack/support`, `*.Local.json`, `.env*`, key files, client-sources `raw/`), text only (extension allowlist + NUL sniff — a binary cannot be scrubbed, so it is never included), every file through `scrubSecrets`, caps 25 MiB total / 5 MiB per file / 5000 files — over the total cap NOTHING is attached, said out loud), `rewriteCommandForBundle` (absolute project paths → `<bundle>/inputs`), `renderReproMd` (how support re-runs the verbatim command on the bundled layout), `buildZip` (jszip — sorted entries, one fixed date, UTF-8 names so `entité.md` survives Windows Explorer, built from memory so a re-run never nests the previous archive), `sha256Of`. Consumed by `support-report/cli/create` |
| `i18n-keys.ts` | i18n key extraction + resolution shared by validate-page (`i18n-keys-resolve`) and audit-dev-frontend (DEV-UI-038) — `LOCALES` (fr/en/it/de), `extractTCalls` (every `t('…')` of a TSX source, `useTranslation` default ns, `hasDefaultValue` flag), `resolveI18nKey` (3-state `string\|object\|missing` — object = the label/children collision), `flattenI18nLeaves` (key-set parity) |
| `frontend-fixers.ts` | Tailwind / theme auto-fix utilities for the frontend audit chain |
| `screen-grammar.ts` | THE list of `screen.md` bullets the deterministic parsers actually READ (`screen-grammar:v1`) — `SCREEN_BULLETS` (label, folded key, tolerated aliases, readers, kinds, level file) + `SCREEN_BULLET_KEY` (the constants `audit-ba/rules/screen-blocks.ts` switches on — no string literal allowed there) + `renderScreenGrammarTable`. Carried by `create-screen/references/smartcomponents.md`, drift-locked in BOTH directions (table ⇔ SSOT ⇔ the parsers' source) by `lib/__tests__/screen-grammar-drift.test.ts`: a bullet a level file teaches and nobody reads, or a key a parser reads that no reference teaches, cannot exist. `Segments`/`Tri`/`État vide`/`Résumé`/`Widgets` are explicitly « PRD synthesis only » |
| `ba-test-data.ts` | THE shared parser of `jeu-de-test.md` — the module's business TEST DATASET (the SECOND seed tier: dev/test/qual via `SmartStack:EnableDevSeeding`, next to the SETUP tier `**Valeurs initiales**`). `parseTestDataDoc` (marker `ba:jeu-de-test`, `Date de référence`, blocks `### JT-NNN — Entity (ENT-NNN)` — STRICT, nothing after the parenthesis —, `- **Clé**` MANDATORY, one markdown table per block, `Note` reserved), tolerant AND fail-closed on loss (`isTestDataRowLoss`: short row, empty row, second table, near-miss heading are SAID and counted — never « 0 rows »), `loadModuleTestData` (absence = a RESULT), `findTestDataSet`, `dataColumnIndexes`. Consumers: lib/ba-test-data-check, audit-ba corpus (full-tree `testDataUniverse` + `testDataSets` control counter), audit-dev-data DEV-DAT-010 |
| `ba-test-data-check.ts` | THE deterministic check + normalization of a module's test dataset — ONE engine, two readers (`create-test-data/cli/derive-test-data` and audit-ba DM-030..032; lives in lib/ because audit-ba may only import cross-skill from the installer's ENUMERATED allowlist). `deriveTestData` resolves every FK cell WHERE THE RELATION SAYS the target lives (`same-module` → this dataset or the target's Valeurs initiales; `cross-module (APP/MOD)` → the OWNER module's dataset; `core` User → an actor of acteur.md; other Core → a literal resolved at seed) — by KEY or display value, never a Guid —, checks columns/key/required/enum (verbatim, near-miss suggested)/unique/number/boolean/calendar dates/coded Code/cycles intra+inter-modules/Flow statuses/reference dates, and in `derive` mode emits `scaffold-seed testData[]` (blocks in topological order, cells typed, `needsTypes` for enums, `{ref, entity, keyField, key}` / `{actor, label}` / `{core, by, value}` cells, module `rank`). An unresolved citation names the OWNER; an absent file is `status: absent`, never an error |
| `remediation.ts` | THE shared audit -> correction contract — 5 lanes (`derive` a derive-* CLI in write mode, `scaffold` re-run the generator, `rewrite` an in-place mechanical fix, `authoring` hand off to the create-* skill that owns the document's grammar, `manual`), `EXECUTION_ORDER` = derive -> scaffold -> rewrite (a CONSTRAINT: a rewrite applied before a regeneration is overwritten by it), `REMEDY_REGISTRY` (executable remedies only — every row is a `mode:check` engine that already backs the rule), `DIMENSION_AUTHORING_SKILL` (explicit table: `ba-create-${dimension}` is WRONG — use-cases -> ba-create-use-case, rules -> ba-create-business-rules, cross-dimension -> none; DM-029..032 carry their OWN authoring rows -> ba-create-test-data, the dimension default would route jeu-de-test.md at entité.md's author), `resolveRemedy` (TOTAL — falls back to authoring then manual, never undefined) and `remedyFromDevFinding` (adapts the audit-dev-* `fixSkill`/`autoFixable` fields). cliPaths are CONTIGUOUS literals so the installer's `skills/business-analyse/` -> `skills/ba-*` rewrite catches them. Drift-tested (`remediation:v1`); consumed by audit-ba and by /audit-fix |

Import from CLIs via relative path, e.g. `import { … } from '../../../../lib/<file>.js'`
— the number of `../` depends on the skill's depth (3 for `<skill>/cli/<cli>/`,
4 for `<phase>/<skill>/cli/<cli>/`, 5 for `development/<layer>/<skill>/cli/<cli>/`).
A wrong count is invisible to the test suite until the CLI is launched — count from
the CLI folder up to `skills/`.

## external (external tool wrappers)

| Skill | Role |
|-------|------|
| `context7` | Wraps the `ctx7` CLI (Upstash Context7) — fetches up-to-date library docs via Bash. Replaces the legacy MCP server. |
| `dev-browser` | Points to the SawyerHood/dev-browser plugin (browser automation via sandboxed QuickJS WASM). Installed via `/plugin marketplace add sawyerhood/dev-browser`. |

## gitflow (workflow)

| Skill | CLI | Role |
|-------|-----|------|
| `gitflow` | `cli/` (12 colocalized CLIs) | Drives the GitFlow workflow |

Sub-files: `_shared.md`, `agents/*.md` (11 agents — `generate-msg` is CLI-only).
CLIs: `cli/{init,start,commit,sync,update,pr,merge,finish,status,cleanup,abort,generate-msg}/` — each with `types.ts` (Zod), `validate.ts`, `execute.ts`, `index.ts`.
Shared lib: `cli/lib/` — self-contained git utilities.
Config stored at `<repo-root>/.gitflow/config.json` (shared across worktrees; legacy `.claude/gitflow/config.json` is auto-migrated).

## efcore (EF Core migrations)

| Skill | CLI | Role |
|-------|-----|------|
| `efcore` | `cli/` (6 colocalized CLIs) | EF Core migration workflow |

Sub-files: `_shared.md` (naming, `dotnet-ef` PATH, dual DbContext), `agents/*.md` (list, status, create, squash, rebase-snapshot, db-update, recreate-db — `apply` is CLI-only).
CLIs: `cli/{list,status,create,squash,rebase-snapshot,apply}/` — each with `types.ts` (Zod), `validate.ts`, `execute.ts`, `index.ts`.
Shared lib: `cli/lib/` — `detect-dbcontexts`, `parse-csproj-version`, `migration-name`, `ef-runner`.

Naming convention: `{prefix}_v{version}_{seq3}_{Description}` — computed by `cli/lib/migration-name.ts`.

## business-analyse/  →  deployed as top-level `/ba-*` commands

File-based business analysis (native Claude Code — **no Studio, no JSON
envelopes, no `[ACTION]`/`[QUESTION]` blocks**). Each skill reads/writes the
**`.smartstack/ba/` markdown tree** (Application → Module → Section → Resource;
each node folder holds `index.md` + `acteur.md`, `entité.md`, `use-case.md`,
`règles-métier.md`, `rbac.md`, `screen.md`). The shared file model + doc
skeletons live in `_workflow/ba-files.md` and `_workflow/doc-templates.md`;
the client-proposal method (mandatory research → internal draft → self-audit →
3-tier presentation Obligatoire / Suggestion / Élargissement) lives in
`_workflow/proposal-method.md` (drift-locked via `lib/proposal-tiers.ts`).
Authored under `business-analyse/<name>/`, the installer **flattens + prefixes**
each to `ba-<name>/` so it is invocable as `/ba-<name>` (`_workflow/` and any
index are repo-only, not deployed).

| Skill (invoked as) | Phase | Writes |
|--------------------|-------|--------|
| `/ba-create-menu` | 1 menu | the node tree (`index.md` per node) — colocated CLI `cli/menu-node/` (add / rename / delete ONE node, `mode:check` = the plan shown before AskUserQuestion, `mode:write` applies it): the LLM authors label / Contexte / Hors-périmètre / Sources, the CLI does the shape — folder + canonical `index.md` + six placeholders, ONE spliced line in the parent `## Enfants` (its anchor `depends=`/`previousCodes=`, Contexte and `**Sources**` byte-identical — what a full re-Write used to lose); `rename` knows from → to and rewrites deterministically what reconcile-menu would have to GUESS (every long code incl. MODULE renames and 4-segment resource codes, rbac.md human permission paths with machine blocks locked, sibling `depends=` + `## Dépendances`, `scope cross-module (APP/MOD)`, pagespec tokens `section`/`module`/`permission`/`navRoute`/`fkTo`/`relatedModule`/`relatedSection`/`apiEndpoint`/`targetRoute`, folder move, `previousCodes=<nav form>` — leftovers PUBLISHED by a residual scan, other writers' files REPORTED never written: sources scopes, `_plan`, rbac machine blocks, `_audit`, `.run-snapshot.json`, core-seed); `delete` drops the downstream blocks/citations/rows + folder + parent line so reconcile finds no ghost, pagespecs of a deleted section and cross-module relations of a deleted module are reported, never touched; fail-closed `blocked[]` mirrors the mechanical audit rules (format, duplicate sibling, MENU-003 platform collision unless `extension:true` + declared Contexte, MENU-006 hierarchy collision, settings section, action/representation suffix, bounds 4/8/6/8) and `warnings[]` their warns (MENU-007 short Contexte, SEC-005, XAPP-001); applications are add-only (rename/delete refused with the manual route). Also the ENRICH entry point to which `/ba-change` routes « a new section » |
| `/ba-create-sources` | 0 sources (any time) | the CLIENT-SOURCES registry `.smartstack/sources/` (SIBLING root of `ba/`, committed) — `SRC-NNN/source.md` + `index.json`; colocated CLIs `cli/ingest/` (modes plan/write/register-blocked — the ONLY writer: fingerprint dedup 1 content = 1 code, DOCX/XLSX extraction via mammoth/exceljs (the tree's only binary parsers, confined to its sources.ts), scrubSecrets, REFUSES a web source with no verbatim extract and a rendering > 40k chars, `as` attaches an export to a blocked code, `supersedes` retires a replaced one) + `cli/search/` (the phases' targeted-extraction path — accent-folded, counts always published) + `cli/status/` (registry health, drift = data). The phases cite `- **Sources** : SRC-NNN §n`; audit dimension `sources` (SRC-001..007) is the verdict |
| `/ba-create-ba-order` | 1.5 BA order | `_plan/ba-order.md` + `.json` + `depends=` in module `index.md` |
| `/ba-loop` | 2-7 auto | Orchestrator: chains all phases via subagents (create → audit → fix) |
| `/ba-create-actors` | 2 actors | `acteur.md` (app) |
| `/ba-create-use-case` | 3 use cases | `use-case.md` (section) |
| `/ba-create-business-rules` | 4 rules | `règles-métier.md` (valid/invalid examples) |
| `/ba-create-rbac` | 5 RBAC | `rbac.md` (module) — colocated CLIs `cli/derive-lookup-grants/` (machine block `ba:rbac-derived-lookups`, RBAC-008) + `cli/derive-permission-floor/` (machine mirror `ba:rbac-floor` of the default permission floor seeded by construction, RBAC-009) + `cli/derive-rbac-grants/` (deterministic transcription of the matrices into the seed's RBAC fragment — actors→roles via `slugifyRoleCode` + `lib/ba-actors.ts`, human rows app-prefixed, derived rows verbatim, v3.62 `.access` grandfathering; `--mode check` = bidirectional seed-state parity, the engine of audit-dev-core DEV-CORE-011 — Phase 0 pastes the fragment verbatim, never re-types a row) |
| `/ba-create-data-model` | 6 data model | `entité.md` (module) — colocated CLI `cli/derive-referential-codes/` (a reference value does not carry a code: `mode:check` is the read-only INVENTORY — per reference table, its classification, whether a code is there, whether a dated `**Code décidé**` authorises it, the seed key, and the VERBATIM citations of its code values across FOUR sources (règles-métier, critères d'acceptation, **prd*.md/pagespecs**, sibling `**Valeurs initiales**`) — and the engine of DM-022; `mode:backfill` rewrites `entité.md` ONLY, never an entity carrying a decision, never a CITED code (it stops on THAT entity and continues the others), never the shipped code or the migrations) |
| `/ba-create-test-data` | 6.5 test dataset (OPTIONAL) | `jeu-de-test.md` (module) — 5-8 FICTITIOUS rows per business entity in the client's vocabulary: the SECOND seed tier (setup = `**Valeurs initiales**`, every environment; test dataset = dev/test/qual via `SmartStack:EnableDevSeeding`, never prod), the acceptance-test fixtures, and what a BA simulator shows instead of hash noise. Rows are cited across modules by KEY, never copied — the relation's `scope` says where the owner lives (a `User` is cited by its ACTOR). Colocated CLI `cli/derive-test-data/` (`mode:check` = the DM-030..032 engine — lib/ba-test-data-check — naming, for an unresolved citation, the OWNER module; `mode:derive` = the `scaffold-seed testData[]` input with every FK as a row reference and the module rank). Not wired into /ba-loop (phase 6.5 is optional; a `--from-phase` run does not know it) |
| `/ba-create-screen` | 7 screens | `screen.md` (section/resource) — colocated CLIs `cli/derive-related-tabs/` (derive 360 tab candidates from the entité.md Relations graph — an INERT satellite (no SmartForm anywhere) suggests `summary`, band cartouche / validate declared tabs RTV-001..009 incl. the shared tab-bar budget (RTV-009 screen-side, RTV-108 pagespec-side — `lib/detail-tab-strip` 7/9); also runs the audits' SCR-009/014/025 + PRD-103..105/133 gates) + `cli/derive-uc-coverage/` (THE UC → surface counter — until it, nothing required « every user-goal UC is served by ≥1 screen / custom action / scheduled runtime » and `linkedUseCases[]` had no reader; parses use-case.md via the SHARED parse-ac (level + scheduled now first-class), the screen.md channels `Cas d'usage liés` + action `UC:`, and the pagespec `linkedUseCases[]`/`ucReference`; engine of audit-screens SCR-024 (err, BA leg), audit-prd PRD-131 (err, PRD leg) and audit-use-cases UC-022 (EXC↔AC parity)) |
| `/ba-create-prd` | synthesis | `prd.md` + 3 phase slices + `pagespecs/*.md` + `claude.md` — colocated CLIs `cli/derive-form-sections/` (uiDesign sections → first-order `sections[]`, PRD-111 backfill) + `cli/derive-lifecycle/` (status enum + workflow actions → first-order `lifecycle` block, `mode:check` = the deterministic PRD-120 engine, `needs-judgment` routed to the /ui-design rubric §6) + `cli/derive-rule-links/` (règles-métier.md → pagespec `linkedBusinessRules[]` — THE field DEV-API-008 reads; mapping ladder rule-doc folder → linked-UC sections → needs-judgment, exemptions by real channel (access→RBAC, numbering→codePattern, info, `**Enforcement**` field), `mode:check` = the PRD-129/130 engine — closes the audit's main BR silent-loss point) + `cli/derive-detail-summary/` (entité.md `**Affichage**` + single state enum + required stored attributes → detail pagespec `summary` band — `: Id` = opt-out, no anchor = needs-judgment, never invents; `mode:check` = the PRD-134 engine — ≥ 4 rendered strip triggers and no band = warn) + `cli/derive-kanban-spec/` (SmartKanban screen.md → first-order `kanban` block folded on the LIST pagespec — columns re-anchored VERBATIM on the status enum, BR Flow transitions projected (`workflow`/`state-transition` rules via lib/ba-rules-rows), viewModes ∪ kanban, i18n column labels seeded, canonical `move` action derived when the graph warrants DnD; rules whose tokens map to nothing = needs-judgment fail-closed, never a silently open matrix; `mode:check` = the PRD-135 engine) |
| `/ba-translate-prd` | PRD i18n backfill | rewrites `pagespecs/*.md` `i18nKeys` — fills `[xx]` placeholders with real en/it/de (remediation for pre-PRD-089 PRDs) |
| `/ba-create-plan-development` | planning | `_plan/dev-plan.md` + `.json` (modules topo-sorted into parallel waves) |
| `/ba-reconcile-menu` | 0.5 menu reconcile | rewrites downstream docs (UC/SCR/BR/RBAC codes) after menu renames/deletions |
| `/ba-change` | change request (after the fact) | adds or MODIFIES ONE element in a FINISHED scope — a use case, a business rule, an actor, a permission row, an attribute, an entity, a screen — without re-running a phase. Router SKILL.md + one playbook per artefact (`kinds/<kind>.md`: analyse the request → challenge it → author → verify → propagate → audit; all 7 kinds shipped). Colocated READ-ONLY CLI `cli/derive-change-impact/`: the next free code per family (UC / SCR per section, BR / ENT module-wide, ACT project-wide — scanning headings + near-miss headings + body cites + pagespecs + `previousCodes=` aliases + the user's `reserve[]`, never a reused number, the git history honestly NOT scanned), near-duplicates (same/similar title, same error code; cross-app look-alikes REPORTED never written), fail-closed `blocked[]` with a route (section-required, actor-not-found, use-case-not-found, entity-not-found, upstream-trace-missing — the data model only carries what a UC/rule names, every form of the name searched —, core-entity-collision / core-reserved-name, permission-path-app-prefixed/invalid, portee-unknown, permission-not-found, screen-type-unknown, owner-doc-missing/placeholder, target-not-found), and the ORDERED `impact[]` checklist — each step = owner file + skill sub-workflow OR derive CLI (contiguous literal path + spec + writes) + the audit rule that sanctions the omission + the /ba-develop re-entries; `mode:verify` re-parses the owner doc after the Write (count +1/0, nothing `lost`, machine blocks byte-identical, `**Sources**` cited when the registry exists). Never deletes (→ /ba-reconcile-menu), never re-runs /ba-create-prd on a module with pagespecs (uiDesign/lifecycle loss), never invents a code |
| `/ba-audit-menu`, `/ba-audit-sections`, `/ba-audit-actors`, `/ba-audit-use-cases`, `/ba-audit-rules`, `/ba-audit-rbac`, `/ba-audit-data-model`, `/ba-audit-screens`, `/ba-audit-cross-dimension`, `/ba-audit-cross-ref-code`, `/ba-audit-sources` | per-dimension audits | `<scope>/_audit/<dim>.md` verdict |
| `/ba-audit-prd` | PRD audit (~50 checks; US/AC checks moved to /ba-audit-use-cases UC-012..018; PRD-107 = no `type:file` custom action; PRD-108 = route families declared/unique/copied — the sub-view 360 mis-routing gate, CLI half = derive-related-tabs RTV-105..107; PRD-109/110 = list elegance — column visibility budget ≤7 always/high + filter tiers ≤3 primary; PRD-113 = reference filters carry `fkTo` AND name the FK property — backfill via `ba-develop/cli/derive-filter-fks`; PRD-120/121 = form lifecycle — well-formed status-anchored `lifecycle` block / lifecycle authored where the entity lives one, CLI half = `create-prd/cli/derive-lifecycle --mode check`; PRD-128 = action-permission binding — own module.section, section grain, engine = derive-action-specs' blocking rejected[]; PRD-129/130 = business-rule links, CLI half = `create-prd/cli/derive-rule-links --mode check`; PRD-131 = UC coverage, CLI half = `create-screen/cli/derive-uc-coverage`; PRD-132 = coded-entity parity — entité.md `**Code pattern**` ⇔ pagespec `codedEntity` both ways + unusable line = err, CLI half = `ba-develop/cli/derive-code-specs --mode check`; PRD-133 = rendered tab-bar budget ≤ 7 warn / ≤ 9 err — band cartouches excluded, unified fiche honoured, CLI half = derive-related-tabs RTV-108; PRD-134 = summary band authored where the strip warrants it — ≥ 4 rendered triggers and no band = warn, CLI half = `create-prd/cli/derive-detail-summary --mode check`; PRD-135 = kanban representation — `kanban` block folded on the LIST pagespec, columns ⊆ enum verbatim, BR Flow ⇄ transitions parity both ways, viewModes coherence, no standalone kanban pagespec, CLI half = `create-prd/cli/derive-kanban-spec --mode check`) | `<MODULE>/_audit/prd.md` with the GO/NO-GO dev gate (score ≥ 80) |
| `/ba-audit-pre-dev` | readiness orchestrator | aggregates `_audit/*.md` → `_audit/pre-dev.md` (GO/NO-GO) |
| `/ba-modeling-inventory`, `/ba-modeling-detail` | optional 2-pass fast modeling | `_inventory.md` → UC/BR docs |

Audits keep their rule sets verbatim (MENU/ACT/UC/BR/RBAC/DM/SCR/PRD-NNN); only
the I/O is file-based. Then `/ba-develop` consumes the module's PRD
files to generate code. (The Studio-era `create-prd-{scaffold,stories,refs,
pagespecs}` and `_fallback-summary` were folded/removed in the v5 file migration.)

## development/backend/

| Skill | CLI | Role |
|-------|-----|------|
| `structure` | — | .NET Clean Architecture 4 layers (reference doc) |
| `core-seed` | scaffold-core-seed | Bootstrap navigation, applications, modules, sections in `CoreSeedData` |
| `seed-data` | scaffold-seed | Module-scoped seed providers in TWO tiers: `referenceData[]` (`**Valeurs initiales**` — SETUP, every environment) and `testData[]` (`jeu-de-test.md` via derive-test-data — the business TEST DATASET: `{Module}TestDataSeedDataProvider` guarded by `IsDevelopment() \|\| SmartStack:EnableDevSeeding`, `Order = 200 + rank`, every FK resolved BY KEY inside the tenant before `Create()` — `{ref}` → `Set<Target>()`, `{actor}` → the role's test user by email, `{core TenantOrganisation}` → by Name —, a missing reference = row SKIPPED + logged, never an empty Guid; DI landed; absent → byte-identical). Test users per role (dev-only) |
| `data-layer` | scaffold-entity, scaffold-extension-search, scaffold-time-entry-refs, scaffold-data-scope, scaffold-coded-entity, scaffold-migration | Entity + EF config (incl. `dataScope` ownership columns + `codedEntity` Code/ICodedEntity), global-search registration (rowScope derived from data scopes), HR time-entry imputation registration, row-level own/assigned policies (`{Entity}ScopePolicy` + DbContext/DI markers), code-key descriptors (`AddSmartStackCodeKey` markers), EF Core migration generation |

### Mobile-shell backend seams (no CLI yet — hand-written in the client's Infrastructure DI)

The mobile shell's transverse bottom bar reads two aggregates the platform serves at
`GET /api/me/tasks`, `GET /api/me/tasks/count` and `GET /api/me/activity`. Core registers its own
providers (assigned tickets, timesheet/absence decisions, notifications); a client extension merges
into the SAME feeds — nothing to scaffold on the frontend, the pages ship in the package:

| Seam | Shape | Notes |
|---|---|---|
| `ITaskProvider` | `AddSmartStackTaskProvider<TProvider>()` | One provider per queue, for logic the declarative builder cannot express. |
| `AddExtensionTasks<TContext>(builder)` | declarative, mirrors `AddExtensionSearch` | `tasks.Entity<Order>(key, label, icon).RequirePermission(…).Where(scope => o => …).Title(…).Subtitle(…).OccurredAt(…).RouteTo(…).TenantScoped()`. A generic engine counts the pending rows, scopes them (tenant + caller) and returns the oldest N. Call AFTER `AddSmartStack()`. |
| `IActivityProvider` | `AddSmartStackActivityProvider<TProvider>()` | **Provider-only, no declarative builder**: an activity row is an EVENT, not a queryable entity state — there is nothing generic to derive. |

Providers compose through `IEnumerable<I…Provider>`, so a registered queue shows up in both the
aggregate and the bottom-bar badge automatically.
| `business-layer` | scaffold-business | Service/MediatR + FluentValidation |
| `controller` | scaffold-controller | **Integration** controller (served at `/api/{module}/{section}` from `[NavRoute]`, Swagger group `integration`) — CRUD + UC transitions for machine-to-machine consumers |
| `screen-controller` | scaffold-screen-controller | **Screen-driven** controller (`/api/screens/{plural}/...`, Swagger group `screens`) — one endpoint per BA pagespec, payload shaped from `pagespecs/<Entity>.<view>.md`. Phase 2b of `/ba-develop`, fans out per (section, entity). |

## development/frontend/

| Skill | CLI | Role |
|-------|-----|------|
| `structure` | — | React + Vite + Tailwind (reference doc) |
| `theme` | scaffold-theme | Bootstrap `src/index.css` with CSS vars + Shoelace tokens + design tokens. Idempotent; honors `/* @customised */` marker. |
| `layout` | scaffold-layout | Scaffold `src/components/ui/PageTemplate.tsx` — the page wrapper, and NOTHING else. The app chrome (desktop header + sidebar reading `/api/navigation/menu`, AND the mobile "descente par paliers" shell with its transverse bottom bar) is rendered by `@atlashub/smartstack` around `<DynamicRouter />`. It no longer emits `AppShell.tsx`/`Sidebar.tsx`: those duplicated the package chrome, were never mounted by the generated `App.tsx`, and carried no mobile branch. Idempotent; honors `// @customised` marker. |
| `ui-primitives` | scaffold-ui-primitives | Scaffold theme-compliant primitives into `src/components/ui/`: `EntityLookup.tsx` (FK combobox), `DateInput.tsx` (calendar, not native `<input type=date>`), `EnumSelect.tsx` (enum dropdown), `MultiSelect.tsx` (multi-value chips), `TruncatedText.tsx` (ellipsis + tooltip when a cell value is truncated), `DataTable.tsx` (the **owned base table** — sorting/pagination/search/selection, styled via the `--table-*` tokens `scaffold-theme` emits) and `ResponsiveDataTable.tsx` (wraps that local `<DataTable>`: keeps code/label/actions visible, reveals extra columns as the viewport widens via `minBreakpoint`, clips long cells), `TabStrip.tsx` (the detail-page tab-bar container — owns the DEV-UI-027 two-layer underlined-tabs structure, hides the horizontal scrollbar and shows ChevronLeft/Right nudge arrows on overflow; `scaffold-component` mounts it around the inline `role="tab"` triggers) and `SectionCard.tsx` (the titled category card of every sectioned form/detail body — owns the card chrome and the read-first per-section "Modifier"/"Terminer" toggle with the `section-edit/done-<key>` testid contract; controlled + i18n-agnostic, the page resolves the labels). Emitting `DataTable.tsx` here is what makes `@/components/ui/DataTable` resolve in every scaffolded `*ListPage` (a contract test guards against dropping it). Token-styled (read `--radius-input`). Idempotent; honors `// @customised`. Closes the historical EntityLookup paradox. |
| `routes` | scaffold-routes + aggregate-component-registry + split-component-registry | Dynamic routes driven by `GET /api/navigation/menu`. Both emitters fail-close on a legacy MCP-era monolithic registry (`registry.legacy-monolith`/`registry.mixed-layout` — overwriting it would unregister every page); `split-component-registry` is the sanctioned exit: lossless monolith → per-module `{app}-{module}Registry.ts` split (statements verbatim, backup, in-process re-aggregation, idempotent), then canonical regeneration module by module (SKILL section "Migrating a legacy monolithic registry") |
| `component` | scaffold-component (7 patterns) | React TypeScript component |
| `api-client` | scaffold-api-client | TypeScript services + React Query hooks consuming `/api/{module}/{plural}`. Per-entity `pwa.offline: 'write'` additionally emits the outbox spec module (`outbox/{entity}Outbox.ts` — OfflineMutationSpec × 3, overlay reducers) + `useOutboxOverlay` hook folding; `versioned` surfaces `rowVersion` on DetailDto/UpdateDto (409 echo). |
| `pwa` | scaffold-pwa + aggregate-outbox | App-level PWA transform of a CLIENT app: Workbox `injectManifest` SW (faithful socle copy — tenant/lang-isolated GET cache, prompt-only updates), vite-plugin-pwa config patch, manifest + deterministic placeholder icons, iOS metas, `registerSW` → update channel + `initOutbox()` bootstrap in `main.tsx`, Update/Install banners in `App.tsx`, `OutboxStatusChip`, and the `mobile: { … }` block in the SmartStackProvider config (anchors on both the inline `config={{` JSX literal `ss init` emits and a hoisted `const config = {` — the inline form was silently missed before, so `mobile.breakpoint` never reached a standard app). ⚠ The mobile shell is **enabled by default** in the package: the block makes the choice explicit and is the only route to `breakpoint`/`shellComponent`/`bottomNav` — it does not switch the shell on. Manifest `theme_color` reads the app's own accent ramp (`--color-accent-600` → `-500` → Shoelace `--sl-color-primary-500`) before the socle default. Fail-closed gates: client-mode only (`detectFrontendMode`) + `@atlashub/smartstack` capability probe — `setServiceWorkerUpdater` + `initOutbox` (offline channel) AND `MobileShell` + `useMobileNavContext` (mobile shell). `aggregate-outbox` emits `src/extensions/outbox.generated.ts` from the per-entity outbox modules. |
| `auth` | scaffold-frontend-auth | useAuth ADAPTER over the package hook (adds the strip-leading-appCode permission match) + PermissionGuard — Phase 3.0 — wiring for `[RequirePermission]` gating |
| `extension-config` | scaffold-extension-config | `ExtensionConfig` with slot catalog mirroring component emits |
| `ui-polish` | ui-polish (audit-apply) | CSS tokens / design rules audit + mechanical fix |
| `dashboard` | scaffold-dashboard-primitives | Transverse dashboard contract + **editable** primitives into `src/components/dashboard/` (`KpiCard`, `ChartCard` (Recharts), `ListWidget`, `DashboardGrid`, `WidgetRenderer`, `types`, `useDatavizPalette`) — the package's dashboard components are internal/not exported. Reference for correct components/UI/theming (`--dataviz-*`/`--chart-*`/`--kpi-*` tokens from scaffold-theme); consumed by ba-develop Phase 3.0 + scaffold-component's `dashboard` view. Needs `recharts`. |

## development/audit-dev-*

Per-layer audit rule packs, each callable independently:

- `audit-dev-domain` — DEV-DOM-001..008. Deterministic CLI (`cli/audit-dev-domain/`) for **001** (every BA entity has its Domain `.cs`, err) and **002** (every stored attribute of entité.md is a property of that `.cs` — a stale/hand-edited class drops the column for good, err) — both were an LLM prose check in gates.md, never a gate — plus the two rules nothing else in the chain covered: **006** ghost entity (a generated `.cs` under `Entities/<App>/<Module>/` the BA model does not declare — the inverse of DM-009/DM-011; a flat layout is reported as `unscoped`, never as a storm) and **007** every `*Id` Guid paired — navigation of the same stem, `[ForeignKey]`, `HasForeignKey` in the Configuration, or the fk-allowlist (the code-side calque of DM-013: a bare Guid gets no constraint). 006/007 warn. The skill was prose only and no gate invoked it; now runs in the After-Phase-1 (Entities) gate next to audit-dev-data
- `audit-dev-data` — DEV-DAT-001..010. Deterministic CLI (`cli/audit-dev-data/`) for 001/002/003/007/008/**009**/**010** (010, err: the business test dataset `jeu-de-test.md`, when authored, reached its `{Module}TestDataSeedDataProvider` — every JT block, every row, DI-registered and GUARDED by `IsDevelopment() || SmartStack:EnableDevSeeding` — an unguarded one seeds fiction in production; absent dataset = ok; NEVER a DEV-API-030 feeding path): the persistence gate that never ran — the skill was prose only and no /ba-develop gate invoked it, so DEV-DAT-008 (every declared relationship is a REAL FK constraint in the Configuration AND in a migration, with the declared cascade — tenant FK included) was a dead letter and the FK axis had nothing between DM-013 (BA) and smartstack-entity-audit (live DB). 009 is new: every declared **Index** — NON-unique included, which DEV-API-031 excludes by design — reached a `CreateIndex` (tenant-composite variants count). Reads the BA model via lib/ba-entities and the migrations as EF Core writes them. 004/005/006 stay conversational (twins DEV-CORE-011 / DEV-UI-046). Wired as a BLOCKING After-Phase-1 (Entities) gate (gates.md), right after the migration step
- `audit-dev-core` — DEV-CORE-001..N (seed data, navigation)
- `audit-dev-api` — DEV-API-001..034. Deterministic CLI covers 008..034: business rules enforced/traced (008, err — HARDENED: module-scoped word-bounded trace (another module's BR-001 never satisfies), legacy `Guard BR-x — TODO:` stub = todo leg, catch-up `no-rules-declared` err when the BA declares enforceable rules and no pagespec links any — reads lib/ba-rules-rows; heal = derive-rule-links backfill), use-case actions implemented (009, err), server-side list triplet (017), custom-action optional body (018, warn), FK relation-filter triplet controller ⊕ query ⊕ handler (019, err — the 360 related tabs' read path), planned own/assigned data scope actually enforced — policy + ApplyDataScopeFilter + DI + ctor forwarding (020, err), permission constants ⊆ seeded grants — every `{Mod}Permissions.{Section}.cs` value exists verbatim in the same app's `*CorePermissionsSeedDataProvider.cs` (021, err — catches the app-less permissionPrefix 403 bug), coded-entities seam in place for every `**Code pattern**` entity of entité.md — ICodedEntity + AddSmartStackCodeKey DI + ctor IServiceProvider forwarding + unique Code index (022, err — a missing leg ships empty Codes silently), payloadParameters land on a [FromBody]-binding endpoint — reciprocal of 018, the dialog's input is otherwise silently discarded server-side (023, err). Routed GET actions declare a response — a [HttpGet("…")] returning NoContent() is a read whose computed value nobody receives (025, err — the driver-at/forecast/history class; the generation chain now refuses the shape via lib/page-spec-actions + both backend validates, PRD-124 gates authoring). Container registrations — every controller-injected I{X}Service AddScoped'd + client Application assembly scanned when Handlers/Validators exist (026, err — the "green gates, every endpoint 500" leg; heal = re-run scaffold-business, marker blocks BUSINESS-SERVICES-DI / CLIENT-APPLICATION-ASSEMBLY-DI). Core-lookups bridge ↔ socle version both ways — retire on AmbiguousMatch, scaffold when core FKs have no server (027, err — `scaffold-core-lookups` + `lib/socle-version.ts` floor). Scheduled UC runtime triple — Run{X}Async method + AddSmartStackRecurringJob jobId in Program.cs + POST jobs/{slug}/run trigger (028, err — derive-job-specs is the shared derivation). Global-search seam filled — every `view: list` entity carries an ACTIVE `search.Entity<…>` between the `<<< EXTENSION-SEARCH-DI >>>` markers (029, err — the engine is fail-closed by design, an empty seam returns 0 results silently; heal = scaffold-extension-search build-spec + CLI, row-scoped entities carry `.RestrictTo(...)`). No unpopulatable entity — every pagespec entity with a controller has a bare [HttpPost] create, a seed provider writing it (`Set<{Entity}>()` — the entité.md `**Valeurs initiales**` → scaffold-seed `referenceData[]` output), or a verified pagespec `rowsCreatedBy` feeding action (030, err — the AlertRule shape: pagespecs+controller+service+tests and a table at 0 rows forever; the criterion is « no POST AND no declared feeding path », never « no POST » alone). Declared unique indexes actually emitted — every `**Index** … unique` of entité.md has its HasIndex(...).IsUnique() in the entity's Configuration, tenant-composite variants counted (031, err — the 7/23 FK-bearing uniques silently dropped; heal = scaffold-entity `relations[].unique` / `indexes[]` + migration). Tenant isolation actually mounted — every ITenantEntity/IOptionalTenantEntity Domain entity has its `ApplyNamed{Strict|Optional}TenantFilter<…>` line in ExtensionsDbContext (the named "Tenant" filter — extension entities carry NO automatic tenant filter, the socle only filters the Core V1 whitelist) AND no generated service calls `.FindAsync(` which bypasses every query filter (032, err — the cross-tenant read class; heal = re-run scaffold-entity / scaffold-business; this is the catch-up gate for apps generated before the TENANT-FILTERS seam). Every public HTTP-routed action guarded — `[RequirePermission(...)]` in the action's CONTIGUOUS attribute block (or class-level) or an EXPLICIT `[AllowAnonymous]`; `[Authorize]` alone ≠ authorisation (033, err — port of roslyn SS004 into the gate: an unguarded endpoint is open to every authenticated user AND the UAT certifies it — `accessFor` returns allowed when no permission is required; supersedes the conversational DEV-API-004 warn; heal = re-run scaffold-controller with the pagespec permission). No hand-rolled code generator in the C# — Generate*/GetNext* method, counter DbSet/NextValue column (triggers = lib/capability-catalog code-generation, SSOT), or Max(x => x.Code)+increment/format signal within ±15 lines (034, err — the socle’s CodedEntitySaveHandler is the ONLY allocator; BA-side detection only reads entité.md, this is the code-side net; sanctioned ICodeUniquenessProbe/ISuppliedCodeGuard files excluded; heal = declare the **Code pattern** + scaffold both halves + DELETE the generator). 001..007 remain conversational (structural conventions the scaffolders guarantee; 004 superseded by 033).
- `audit-dev-frontend` — DEV-UI-001..049 (019–021 are spec-only — their service↔pagespec subset is covered by the colocated `audit-dev-actions-alignment` CLI; 024–025 unused; 010 and 012 RETIRED — dead `--prd-slice` inputs, replaced by DEV-API-008+DEV-TEST-009 and DEV-CORE-011+DEV-API-021+DEV-UI-046 respectively; 011 REWRITTEN 2026-08: page-level PermissionGuard ↔ the PAGESPEC's `permission` — missing-guard/wrong-permission err via `--module-path`, frontend-only warn via `--backend-path` with const-expressions resolved through the generated `*Permissions*.cs`; companion: scaffold-component derives every guard key from the pagespec permission prefix, so regeneration heals). Incl. theme tokens, dark prefix, hex in className, EntityLookup FK gate, native form-control → DateInput/EnumSelect/MultiSelect, modern form primitives (023), breadcrumb routes-helper (026), two-layer tab strip (027), i18n runtime channel (028, err — hardened: business pages with ZERO business bundle on disk = err, no longer a silent ok), i18n placeholder guard (029, err — no shipped `[xx] <fr>`), server-driven list pages (030, err — no client-side pagination), 360 related tabs rendered + permission-gated + FK-filtered (031, err), detail-page action completeness — edit/delete + header custom actions anchored (032, err), FK Guid never surfaces on list/detail — raw column / free-text FK filter / raw `<dd>` (033, err; legacy Guid fallback = warn; the filter leg matches ANY `filters['x']` key and cross-checks the pagespec, so a filter named after the RELATION — `filters['client']`, no `…Id` hint — is caught too), no editable `code` input on a coded entity's form — signal = `**Code pattern**` in entité.md (034, err), no parallel `i18next.init()` in app src/ — the SDK's store is clobbered → raw keys; legacy scaffold template auto-fixed to the `export {}` seam (035, err), dashboards inherit the theme — theme without `--dataviz-1` (silent fallback palette), colour literal in a dashboard page/primitive, `recharts` outside `ChartCard.tsx`, stale `/dashboard/consolidated` contract (036, err — chart colours travel as JSX props, so every className-scoped colour rule is blind to them; ui-polish sibling R28), no field label left as the humanised property name — a `form.fields.*`/`detail.fields.*`/`list.columns.*` leaf identical fr↔en AND equal to `humanize(key)` is the scaffolder's untranslated floor, invisible to the `[xx]`-marker guard (037, warn — heuristic + allowlist; upstream gate = PRD-106), module-wide i18n catalogue completeness — every bare `t()` key of every business page resolves to a STRING in all 4 locale bundles (an object = the label/children collision), fr/en/it/de key SETS identical per namespace (038, err — exact checks over the ASSEMBLED bundles, the net behind validate-page's per-page `i18n-keys-resolve`; heal = re-scaffold the offending entity, shared lib `lib/i18n-keys.ts`), list pages stay readable — ≤ 7 default-visible columns (columns literal minus `columnVisibilityDefaults` hidden keys) + no flat wall of ≥ 4 filter inputs without the FilterBar primitive (039, err — the AtlasHub 22-columns/12-filters wall; heal = re-scaffold ui-primitives then the entity, author pagespec `priority`/`tier` per PRD-109/110), sectioned fiches — pagespec `sections[]` ↔ page parity (missing/surplus `form.section.*` on a Form/DetailPage vs the spec's sections/tabs/uiDesign) + the single-card ≥8-field wall on a non-@customised FormPage (040, err/warn — the fiche mirror of 039; authoring gates = PRD-111/112, heal = author `sections[]` then re-scaffold; DEV-UI-033 additionally covers the read-first READ grid's `formData.xId` Guid leak as shape `form-raw`), no dead-mockup chrome — navigate('#') / hardcoded `—` KPI / inlined pill markup instead of <Badge> (041, err; unwired StatCard value="—" = warn; heal = re-scaffold ui-primitives then the entity), list KPI stat row parity — pagespec `stats[]` declared but no `list-stats-row` on the generated list page (042, err — needs `--module-path`; authoring gate PRD-115), detail summary band parity — pagespec/overlay `summary` declared but no `detail-summary` band on the generated detail page (043, err — needs `--module-path`; authoring gate PRD-119), lifecycle parity — pagespec `lifecycle` declared but the generated FormPage misses the `phase:<key>` markers / `toCreatePayload` still maps a phase-owned field / no compiled status guard (044, err — needs `--module-path`; authoring gates PRD-120/121, heal = re-scaffold the form). no stacked read grid + raw edit form outside SectionCard — the « formulaire empilé sous la fiche » shape the generator cannot emit (045, err; heal = re-scaffold ui-primitives then the entity; scaffold-component's primitive preflight fail-closes the root cause up front), every registry componentKey is REACHABLE — last segment ∈ IMPLICIT_SUFFIXES (`lib/implicit-suffixes`) or the node chain resolves in the seeded nav (`.smartstack/core-seed/*.state.json` primary, `*CoreNavigationSeedDataProvider.cs` fallback) (046, err — the 21-dead-keys/40% incident: routeFamily keys registered in Phase 3b that Phase 0 never seeded as resources; heal = `ba-develop/cli/derive-nav-resources` + additive scaffold-core-seed re-run), unified-fiche catch-up — a {Entity}DetailPage.tsx still rendering FIELD-tab triggers while `lib/edit-surface.resolveEditSurface` says 'unified' for the entity's pagespec view-set (047, warn — needs `--module-path`; related triggers/band cartouches never false-positive; heal = re-scaffold the detail page + routes, the page predates the unified switch), kanban board parity — pagespec `kanban` block + viewModes 'kanban' but no `list-kanban-board` testid on the generated list page (err), authored transitions[] not compiled into ALLOWED_TRANSITIONS (err — the BR graph silently does not govern the drops), residual GENERATED {Entity}KanbanPage.tsx while the standalone page is retired (warn — delete + re-scaffold) (048 — needs `--module-path`; authoring gate PRD-135, SSOT lib/page-spec-kanban), a cross-module 360 tab follows the TENANT CATALOGUE — a tab whose `(relatedApp ?? own app, relatedModule)` leaves the page's own surface must be gated on `useModuleAvailability().hasModule(targetApp, targetModule)` on its trigger/cartouche AND its panel, naming the TARGET and never `useLicense().hasModule()` (the faux ami: what the customer BOUGHT, not what this tenant HAS) (049, err — needs `--module-path`; scope = lib `requiresAvailabilityGuard`, opt-out `"availabilityCheck": false`, `@customised` exempt; the client symptom is a Factures tab on every customer record of a deployment that never had the billing application — half-covered until now by the side effect of `PermissionService` filtering permissions by the tenant's APPLICATION catalogue, which is blind at module grain and bypassed for global admins).
- `audit-dev-tests` — DEV-TEST-001..004 + 008 + 009 + 011 blocking (BA AC ↔ generated [Trait("AC",…)] coverage + no `// TODO[AC-` markers + no stale traits (003, err — a green [Fact] on a deleted/renumbered AC is a coverage lie, AC churn now blocks) + no stub assertion (004, err — Assert.True(true)/expect(true).toBe(true) = false coverage) + every UC declares ≥1 AC (008, err — the coverage floor) + business-rule test parity (009, err — every enforceable err rule of règles-métier.md carries a MODULE-SCOPED [Trait("BR","BR-NNN")] test under Tests/{Module}/ (BR codes are doc-scoped — another module's trait never satisfies nor false-flags; legacy flat layout falls back project-wide and says so), shared exemptions lib/ba-rules-rows.ruleExemption, stale module-scoped BR traits warned, true zero reported as ok) + no AC lost at parse (011, err — the audit twin of the scaffolder's `lost` exit-1: a malformed/duplicate bullet is an assertion the contract dropped, invisible to 001/008; never-halt cannot swallow this finding)) plus advisory 005..007 (anti-patterns) and 010 (warn — the traced [Fact] body carries the status/error-code tokens its AC pins verbatim, comment lines stripped, HttpStatusCode names accepted; catches the assertion-contradicts-the-pinned-figure class without pretending to judge semantics). BLOCKING gate after /ba-develop Phase 4.
- `audit-dev-wire` — DEV-WIRE-001..003 (service / repository / DI wiring across strata). Knows the third
  `public` stratum and never reports it as a backend orphan: it is third-party-only BY CONSTRUCTION.
- `audit-dev-external-api` — DEV-XAPI-001..014 (the PUBLIC stratum, consumed machine-to-machine by a THIRD PARTY).
  Closes two failures nothing else can see, because a browser never reveals them: a route whose catalogue row
  was never seeded answers **404 `endpoint_not_found`** to every external app while a signed-in human sees it
  work (002/012), and a catalogue `RequiredPermission` disagreeing with the compiled `[RequirePermission]`
  constant answers **403 `permission_mismatch`** on a correct call — the platform builds the app's claims from
  the row ALONE, so no amount of re-granting fixes it (003). Plus: the whitelisted route prefix, never a
  `[NavRoute]` (001); the code grammar + platform-reserved codes, whose UNIQUE index turns a collision into a
  BOOT failure (004); **the cross-tenant gate** — a machine caller carries no ambient tenant, so `?tenantId=`
  must be taken, refused when empty, bound for an external app and REFUSED for a signed-in human, whose
  app-binding and grant-whitelist gates never ran (005); server paging caps against the response-buffering
  middleware (006); the platform `PaginatedExportResult` envelope + ProblemDetails (007); business-layer reuse,
  DEV-API-014 extended (008); write surface declared Write + a unique index as the honest retry guard, the
  platform's `Idempotency-Key` being inert for a machine caller (009); BA declaration parity both ways at
  ENTITY grain (010); published-contract freshness, a removed endpoint being a breaking change already in a
  third party's hands (011); the wildcard the `resource` grain forces (013, warn); and the class-level guard
  block — the platform's own `WorkflowsApiController` defect, refused in a client extension (014).
- `audit-dev-pwa` — DEV-PWA-001..012 (PWA/offline: SW present+registered, manifest/icons resolvable, tenant/lang cache-key isolation markers intact in sw.ts (003, err — cross-tenant leak guard), explicit mobile coverage on registrations, offline pages read via cacheable GETs, offline-READ mutations disabled offline, outbox spec per offline-WRITE resource + aggregated + no idempotencyKey, outbox bootstrap order (import before initOutbox), offline-WRITE entity versioned end-to-end (rowversion → 409), overlay+chip wired with NO wrongful disabling, registry layout recognized + non-empty scan (011, err — the fail-closed gate on the legacy MCP monolith: heal = split-component-registry, NEVER a scaffold-routes/aggregate re-run; scans via lib/registry-index so the monolith and inline lazy ARE read), precache cap vs built chunks (012 — oversized chunk without the sw CacheFirst net = err/blank-screen-offline, with net = warn; dist/ absent = clean skip note)). Also accepts `--spec '{json}'` (flags override). Safe on a non-PWA app (all-ok note).

Each `audit-dev-*` skill has a deterministic CLI sibling for orchestrator-driven
auto-apply on re-run (`audit-apply` mode) — EXCEPT `audit-dev-core`, whose
deterministic engine lives in `create-rbac/cli/derive-rbac-grants` (DEV-CORE-011,
executed by the ba-develop Phase 0 gate — gates.md check 3). `audit-dev-tests` is read-only (no
auto-apply — findings route back to /ba-develop Phase 4 for remediation).

## development/{debug, run, testing, audit, smoke-test}

- `debug` — Systematic bug debugging with deep analysis (sub-skills: `audit-bug`, `discuss-bug`, `fix-bug`)
- `run` — Launch dev environment helpers (orchestrated by top-level `dev-start`)
- `testing` — test scaffolding + reporting CLIs: `scaffold-tests` (test projects + per-layer examples), **`scaffold-tests-from-ac`** (Wave 2, 2026-05-27: emits one xUnit `[Fact]` per BA Acceptance Criterion (`<UC-code>#AC-NN` trait), TODO-skeleton body filled by `/ba-develop` Phase 4; audit `DEV-TEST-001` blocks merge on remaining TODOs), `test-report` (run + aggregate results). Sub-skills: `fix-build` (build fixer via `claude -p`), `smoke-http` (CLI `smoke-http` — HTTP smoke probes), `ui-test` (CLIs `build-manifest` + `run-ui-test` — manifest-driven UI scenarios over dev-browser).
- `audit` — Cross-cutting audits (routing-dynamic, …)
- `smoke-test` (CLI `run-smoke`) — Runtime gate. Boots backend (`dotnet run`) + frontend (`npm run dev`) in background, then FOUR axes: HTTP probes (every page route + API endpoint, fail on 4xx/5xx), headless-Chromium browser pass (loads list/home/dashboard, catches Vite overlay / React crash / CSS 500), nav-menu app-consistency, and **axis 4b — custom-action contract probes** (synthesized valid body at the sentinel id; 415/405/5xx = fail; authenticated via `--admin-token`, else `smoke.interaction-unavailable` note). Designed as the final gate of `ba-develop`.

## validation/

CLI-driven skills invoked by Claude via Bash. Every CLI returns a structured
JSON report on stdout (see `lib/output.ts`).

| Skill | CLI | Role |
|-------|-----|------|
| `validate-conventions` | validate-conventions | Audit namespaces, entities, controllers against SmartStack conventions |
| `cross-validate` | cli/ | Backend ↔ frontend consistency — 3 active checks; `permissions-aligned` + `rbac-consistent` RETIRED 2026-08 (ids kept, status skipped + replacement note → DEV-API-021/033 + DEV-UI-011 + DEV-CORE-011) |
| `readiness-report` | cli/ | LEGACY hors chaîne BA — static-quality pulse /100 (roslyn+eslint+cross-validate only; no AC/BR/RBAC axes, invoked by nothing). The real dev readiness = the audit-dev-* gates |
| `roslyn` | cli/ | Static C# analysis by layer |
| `eslint` | cli/ | 5-layer React architecture rules |
| `project-inventory` | — | Lists existing entities, controllers, routes |

## Lifecycle & support skills

| Skill | Role |
|-------|------|
| `ba-develop` | PRD-driven phased development. Consumes the PRD (devReady gate ≥ 80), orchestrates 4 sub-agents (Domain → Data → API → Frontend), each loaded with ONE slice only. Gates between phases (build + tests). Colocated CLI `cli/derive-code-specs` — THE deterministic deriver of the coded-entities seam (the last derive-* seam that was still LLM-derived): parses the entité.md `**Code pattern**` lines via lib/code-pattern-grammar, emits both scaffolder halves (scaffold-entity `codedEntity:{codeKey,format}` + full scaffold-coded-entity entry, unauthored facets omitted → Zod defaults) and reconciles the pagespec `codedEntity` flags both ways (entité.md = single source of truth); `--mode check` = the PRD-132 parity engine, `blocked` (no mask/invalid format) = BA fix, never an improvised spec. |
| `ba-develop-plan` | Multi-module orchestrator over `dev-plan.json`: preflight (`preflight-develop-plan` CLI — reads each module's `_audit/prd.md` AND the `/ba-audit-pre-dev` aggregate `_audit/pre-dev.md`, THE reader that verdict never had: ❌ / « non audité » rows become wave blockers) then wave-by-wave `/ba-develop` subagents, compile/test checks between waves. |
| `init` | Project initialization (`ss init`) |
| `upgrade` | Project update (`ss upgrade`) |
| `review` | Code review across 9 categories |
| `conventions` | SmartStack.app conventions reference (read-only) |
| `check-version` | Verify `package.json` ↔ documentation alignment |
| `cli-app-sync` | Detect template drift between SmartStack.app and the CLI templates |
| `dev-start` | `ss dev` orchestrator (backend + frontend + admin credentials) |
| `login-config` | Customize a generated app's login/auth via **2 colocated CLIs**. `configure-login`: writes secret-safe appsettings — providers (Local/Microsoft/Google/Entra), initial admin (password fail-fasts on first boot), email provider **+ its credentials block** (Smtp/SendGrid/AzureAcs); secrets→gitignored `appsettings.Local.json`. **Signup canNOT be disabled server-side** (no backend flag; `/api/auth/register` stays open). `scaffold-login-page`: emits an **editable login override** via `extensions.pages[PAGE_KEYS.LOGIN]` (branding + UI-side masking of providers and the sign-up link), wired into `main.tsx` and coexisting with `/site-vitrine`. Needs `@atlashub/smartstack ≥ 3.55.0`. |
| `site-vitrine` | Scaffold the PUBLIC (pre-auth) surface: home (`/` override via `extensions.pages[PAGE_KEYS.HOME]`), presentation pages + full vitrine via `PublicRouteRegistry` (`layout` public/white-label). Emits editable React pages + reusable sections (hero/features/split/cta/faq/logos) + i18n, themed with SDK tokens. Works with `@atlashub/smartstack` ≥ 3.55.0 — no package-contract dependency. |
| `external-api` | `/external-api` — publish part of a generated app as a PUBLIC API a THIRD-PARTY system consumes machine-to-machine. The platform owns identity, token exchange, grants, rate limiting and audit; this skill wires the client extension into it WITHOUT touching the socle, because the seam is DATA: `DataApiAccessMiddleware` takes the 4th path segment as a catalogue code and resolves it in the database. 4 colocated CLIs — `derive-external-api-spec` (reads the `- **API externe**` declaration off the entity, emits the scaffolder spec, records an approved decision back into `entité.md`), `scaffold-external-api` (one controller per catalogue code under `/api/v1/export/{code}`, the idempotent `IClientSeedDataProvider` that upserts the `DataApiEndpoints` rows — the domain factory leaves both REQUIRED navigation FKs empty, so the change tracker is the seam — and the DI block), `publish-api-contract` (versioned OpenAPI **with real DTO schemas**, Postman and a French guide, refusing a breaking change under an unchanged version — the platform's own exporter only knows catalogue metadata), `provision-external-app` (a TEST client, secret returned once). Emits NO permission constants: it reuses `{Mod}Permissions.{Section}`, which is what makes the catalogue row and the compiled constant the same string. Audited by `audit-dev-external-api` |
| `pwa` | `/pwa` — turn a generated client app into an installable PWA with offline pages (read: SW GET cache + disabled mutations; write: outbox capture, optimistic 202, idempotent replay, 409 server-wins) AND wake the **inherited mobile shell**. The shell itself ("descente par paliers" Applications → Modules → Sections, transverse bottom bar Applications/Tâches/Activité/Compte, tenant chip + sheet, transverse search, offline banner + outbox sheet) ships INSIDE `@atlashub/smartstack`, is **on by default**, and needs zero scaffolding. Its navigation is derived from the DB menu the app already seeds; what gates a PAGE inside it is that page's `PageMobileMeta` (`pwa.support` → scaffold-routes). `scaffold-pwa` writes an explicit `mobile: { … }` block for the tunables (`breakpoint`, `shellComponent`, `bottomNav`) and to make the choice visible. Thin orchestrator over the deterministic CLIs (`scaffold-pwa`, `derive-pwa-spec` — colocated, scans/edits the pagespec `pwa` fields, BA stays the SSOT —, backend `versioned`, api-client outbox, routes metadata, component behaviour, `audit-dev-pwa`). Hard gates: client mode only + socle package must ship the PWA/outbox AND mobile-shell exports (`MIN_SMARTSTACK_PWA_VERSION` + capability probe); EF migration for RowVersion only SIGNALLED (sanctioned path `/efcore`). |
| `quick-search` | Fast codebase Q&A |
| `smoke-generation` | Static analysis catching documented imports/types/signatures that don't exist in SmartStack.app |
| `support-report` | `/support-report` — a crashed colocated CLI at a client site, an audit envelope contradicting itself (`dedupOf` mirror err vs primary ok) or a demonstrably wrong verdict (argued `dispute` + `disputedRuleIds`) becomes a VERIFIED, deduplicated, REPRODUCIBLE report bundle (`.smartstack/support/<fingerprint>/` + `support-<fingerprint>.zip` carrying the scrubbed `inputs[]` the CLI read and a `repro.md`) to send to support@atlashub.ch — never a hand-fix of the deployed copy. Fail-closed `create` CLI (evidence rule + installed-vs-npm-latest gate: newer version → propose update + retest first). |
| `validate-feature` | End-to-end validation of a scaffolded feature (compile + tests + API smoke test) |
| `smartstack-entity-audit` | Runtime DB referential-integrity audit — queries the live database (SQL via `sqlcmd`) for every `*Id` column lacking a FK constraint, classifies CRITICAL / EXEMPT / REVIEW. Complements static DEV-DAT-008 / DM-013. |
| `audit-fix` | `/audit-fix` — the remediation ROUTER, the missing half of every audit. RUNS the audits (it does not read report files: only audit-ba persists a JSON, the audit-dev-* print their envelope on stdout and keep nothing, /ba-audit-prd has no CLI), attaches each finding's remedy from `lib/remediation.ts`, executes the correctors that ALREADY exist, re-runs the audits and reports the delta closed / persisting / **introduced**. Executes `derive` (the derive-* CLIs in write mode) and `rewrite` (audit-dev-frontend / ui-polish `--mode apply`, always `--regenerate-threshold 0`); REPORTS `scaffold` (a scaffolder spec cannot be synthesised from a finding) and ROUTES `authoring` by name. Never destroys, never commits, never guesses (a derive-* `needsJudgment` becomes an authoring hand-off, not a silent write), never reports a skipped source as clean (exit 3), and refuses to write into a dirty worktree — the git diff is the guardrail |
| `ui-components` | Verify a page (or the whole app) respects the theme — zero hardcoded colors — and auto-fix. Thin orchestrator over the `development/frontend/ui-polish` engine (no duplicated logic). |
| `ui-design` | UI **judgment** layer (architecture C): decides the design the deterministic scaffolder can't infer — current-user FK ("Me" shortcut), field sections, inline-vs-popover date, column layout, field order — then re-scaffolds via `scaffold-component` directives (regenerable) **or** a `@customised` page (bespoke). Complements `ui-components` (mechanical tokens): runs `scaffold-component → /ui-design → /ui-components`. CLI `apply-form-directives` persists the judgment into the pagespec `uiDesign` overlay AND (rubric §6) the first-order `lifecycle` block (additive — `lib/page-spec-lifecycle.ts`; deterministic anchors pre-written by `create-prd/cli/derive-lifecycle`). |
| `documentation` | Technical documentation generator — `extract-doc` (deterministic extraction: endpoints, rules, entities, overflow risks, `accessRoles` — the « Accès & rôles » join of code permissions × core-seed state roles × rbac.md portée, code = source of truth) + `scaffold-doc` CLIs, driven by `steps/`. Section 9 « Référence technique » is opt-in (`--tech`, OFF by default). |
| `test-conventions` | Conventions for WRITING tests in a client extension (xUnit v3 + FluentAssertions / Vitest + MSW) — one reference per test type under `references/`. |
| `uat` | Full UAT pipeline — 6 CLIs: `uat-plan` (deterministic `.plantest.yml` + `.signature` from the live surface), `uat-provision` (UAT tenant + 1 user/role via the admin API → gitignored `uat-users.json`), `uat-api` (endpoints × roles: expected status + duration + size), `uat-ui` (Playwright Chromium, REAL clicks: login page, sidebar, rows, form submits on UAT-marked data, perf/weight/console per page), `uat-report` (single-file HTML report), `uat-run` (all-in-one orchestrator with drift gate + `ss dev up` boot). UI axis needs `npx playwright install chromium` once. |
| `utils` | Utility sub-skills for CLI development |

## Loading rules

- Never more than 2 skills in a sub-context
- One skill + 1–2 JSON spec files = 5–10 k tokens max
- Each `SKILL.md` has a YAML frontmatter (`name`, `description`, `argument-hint`, `allowed-tools`, …)
- `dev-cli` skills: code lives in `skills/<phase>/<skill>/cli/<cli-name>/`, invoked via `npx tsx`
