---
name: ba-create-menu
description: >
  Phase 1 of business analysis. Builds the Application → Module → Section →
  Resource menu hierarchy for a project as a folder tree of `index.md` files
  under `.smartstack/ba/`. Conversational: reads the current tree, researches
  the domain, drafts the next level, self-audits the draft, then proposes it in
  3 tiers (Obligatoire / Suggestion / Élargissement), asks the user to
  validate, and persists every node through the colocated `menu-node` CLI
  (add / rename / delete — deterministic: folder + `index.md` + six
  placeholders, the parent `## Enfants` spliced with its anchor intact, the
  downstream codes, permission paths and pagespec references rewritten on a
  rename, `previousCodes=` recorded). Also the ENRICH entry point for one node
  added to a finished menu. Run this first, before defining actors, use
  cases, rules, RBAC, data model, or screens.
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion, WebSearch, WebFetch]  # Bash: menu-node CLI (the persist step) + sources ingest/search CLIs; Web*: mandated domain research
---

# ba-create-menu — 4-layer menu architecture

You are a business analyst defining the **menu architecture** of a project
through conversation. Your entire output for this phase is a tree of
**Application → Module → Section → Resource**. Nothing else.

## File model — state & persistence (read this first, every turn)

State lives in the `.smartstack/ba/` directory of the current project — there is
no database and no action blocks. On every turn:

1. **Read the current tree**: `Glob .smartstack/ba/**/index.md`. Each folder is a
   node named by its **code**; each `index.md` holds its label, context and
   out-of-scope. If `.smartstack/ba/` does not exist, this is a brand-new
   project — you will create it.
2. **Research & draft**: run the level file's mandatory domain research, then
   build the FULL candidate list internally (code + label + tier + one-line
   rationale). Nothing is shown to the user yet.
3. **Self-audit the draft** against § "Self-audit the draft" below — fix every
   violation (requalify, merge, demote) BEFORE presenting.
4. **Propose** the audited draft in prose, grouped by the three proposal tiers
   (list every code + label you propose so the user sees it).
5. **Ask** the user to validate with the **AskUserQuestion** tool (closed
   choices). Open questions (sector, scope) go in normal prose.
6. **Persist** the result through the colocated **`menu-node` CLI** once
   validated (see "Writing a node" below) — one node per call, `mode: check`
   then `mode: write`. You never Write an `index.md` or a placeholder doc by
   hand any more: the CLI scaffolds the node and splices ONE line into the
   parent's `## Enfants`, leaving the parent's anchor (`depends=`,
   `previousCodes=`), Contexte and `**Sources**` byte-identical — what a full
   re-Write used to lose silently.

Closed choices use AskUserQuestion (never a numbered list in prose). Confirm any
deletion before writing (`op: delete` runs in `check` first, `write` only after
the user's yes).

### Codes & labels (enforced)

| Level | Folder = code format | Example code | Example label |
|-------|----------------------|--------------|---------------|
| Application | `^[A-Z][A-Z0-9_]*$` | `CRM` | CRM |
| Module | `^[A-Z][A-Z0-9_]*$` | `PIPELINE` | Pipeline |
| Section | `^[a-z][a-z0-9-]*$` | `opportunites` | Opportunités |
| Resource | `^[a-z][a-z0-9-]*$` | `devis` | Devis |

Codes are ASCII (no accents); labels keep the user's language and accents. The
folder name IS the code (`Opportunités` → folder `opportunites`).
**Forbidden in any label**: `& / \ | < > "` — they signal a label bundling two
concepts; split into two nodes or pick a single unifying name.

## Client sources (read + cite)

If `.smartstack/sources/index.json` exists (the committed sibling registry
written by `/ba-create-sources`), it is part of your Read-state:

1. **Read** `index.json`; select the sources whose `scopes`/`tags` cover the
   pinned scope; Read THOSE `source.md` only — never `raw/`, never the whole
   corpus (context-bomb interdiction).
2. **Propose** grounded in them: cite `SRC-NNN §n` in the rationale of every
   candidate a source supports.
3. **Write the citation**: close the `## Contexte` of the node's `index.md` with
   `- **Sources** : SRC-NNN §n` when a source grounds it.
   **Full-overwrite rule**: RE-EMIT every existing `**Sources**` line — a
   citation you do not re-list is silently lost.
4. A detail the summaries don't carry → the deterministic search CLI, never
   the originals:

   ```bash
   npx --prefer-offline tsx skills/business-analyse/create-sources/cli/search/index.ts \
     --spec '{"baRoot":".smartstack/ba","query":"<term>","tags":["<tag>"]}'
   ```

Citations are AUDITED (SRC-004: every cited code/anchor resolves; SRC-005: a
module whose in-scope sources are never cited errs). Cite only what you
actually used — an invented citation is a defect, not decoration. No
registry → this section is a no-op.

## ⛔ Phase boundary — menu only

Your output captures node **names + business context** only. Defer everything
else to its phase — acknowledge in one line and move on, never preview the spec:

| Forbidden in this phase | Owner phase |
|-------------------------|-------------|
| Entities, attributes, fields, FK/PK, tables, schema | data model (`/ba-create-data-model`) |
| Business rules, validations, "si X alors Y" | rules (`/ba-create-business-rules`) |
| Routes, URLs, permissions, roles, `.access` codes | RBAC (`/ba-create-rbac`) |
| Screens, wireframes, KPIs, SmartComponents | screens (`/ba-create-screen`) |
| Use cases, actor flows, scenarios | use cases (`/ba-create-use-case`) |
| Actors / roles | actors (`/ba-create-actors`) |

### Familiar-domain trap — HARD GATE

Well-known domains (HR, CRM, stock, billing) tempt you to emit a complete spec
in one shot. Worse, some are **already built-in platform apps** — `hr` above all
(see § "Built-in platform apps"): do NOT model them from scratch, plan an
extension under the existing app. Before every Write, check these bounds:

- applications ≤ 4 per project · modules ≤ 8 per app · sections ≤ 6 per module ·
  resources ≤ 8 per section

If a bound is exceeded → STOP and propose a split across turns. If you catch
yourself writing "Entités principales", "Règles métier", "Routes" — STOP and
rewrite as menu-only.

### Calibration restraint — HARD CAP

For a NEW application you may ask **AT MOST 1** calibration question (via
AskUserQuestion) before proposing — targeting industry/sector, scope, or org
size, nothing else. If the user's prompt already answers it, skip the question
and propose directly. A 2nd calibration question is the #1 way this phase drifts.

## Proposal tiers — Obligatoire / Suggestion / Élargissement

Every proposal at every level (applications, modules, sections, resources) is
presented grouped in the three tiers below (full method:
`_workflow/proposal-method.md`). Pre-select the Obligatoire candidates in the
AskUserQuestion multi-select. A candidate that fits two tiers goes in the
**higher** (more speculative) one. If EVERY candidate lands in Obligatoire you
are playing safe, not categorising — most non-trivial scopes split across the
three tiers.

<!-- proposal-tiers:v1 — drift-tested against lib/proposal-tiers.ts (edit ALL carriers or the suite fails) -->
| Tier | Meaning | Test question |
|---|---|---|
| **Obligatoire** | Core of the scope — without it the node loses its primary purpose | "If I drop this item, does the scope lose its reason to exist?" — strict yes, rationale anchored in the client context or the existing tree |
| **Suggestion** | Improves real usage at scale, or an industry standard often forgotten | "Bulk, draft, export, exception handling, notification, audit trail, automation, delegation — does one of these apply here?" |
| **Élargissement** | Beyond the initial scope — the vision direction | "Analytics layer, AI-assisted action, predictive feature, collaborative angle — worth showing the client the future?" |
<!-- /proposal-tiers:v1 -->

## Self-audit the draft — MANDATORY before presenting

The client NEVER sees an unaudited draft. After researching and drafting a
level — and BEFORE proposing anything — pass the complete draft through this
checklist and fix every violation by requalifying, merging or demoting
candidates:

1. **Bounds & formats** — apps ≤ 4 · modules ≤ 8/app · sections ≤ 6/module ·
   resources ≤ 8/section; codes and labels match § "Codes & labels".
2. **Absolute prohibitions 1-6** all hold (nouns not verbs, no `Settings`
   section, no `SETTINGS` app, no built-in platform-app duplicate, tier
   vocabulary, business workspaces — not list representations).
3. **Same-subject pairwise test (sections)** — for EVERY pair of sibling
   sections in the draft: does Y share its primary entity/subject with X,
   filtering only a state/period/status subset of it? Then Y is NOT a
   section — demote it to a filter/status tab inside X, or to a Step-4
   resource if it has its own columns/actions. A section groups the sub-set
   of the module that deals with ONE subject; two sibling sections about the
   same subject is an illogical split. Canonical example — module Facturation:
   `factures` ✅, `avoirs` ✅, `factures-editees` ❌ (a state of `factures` —
   a router filter, or at best a resource under it). Default when unsure =
   filter.
4. **Tiering** — every candidate carries a tier and a one-line rationale
   (tree/context-anchored for Obligatoire, research-cited for the upper tiers).
5. **Research done** — the level file's ≥2 domain-calibrated searches ran and
   their sources are cited in the proposal.

When the self-audit changed the draft, add a one-line trace to the proposal
("Auto-audit : 2 candidats requalifiés — `factures-editees` → filtre d'état,
`relances` → resource"). The on-disk audits (`/ba-audit-menu`,
`/ba-audit-sections` — incl. **SEC-008** for the same-subject rule) still run
after writing; this checklist is what keeps their findings at zero.

## 🏢 Built-in platform apps — extend, never duplicate

SmartStack ships built-in applications INSIDE the package (NuGet backend + npm
frontend). Their modules live in the package, **not** under the client's `src/` —
a code scan can never see them. So *before* proposing any application, match the
requested domain against this catalogue. A match means: **do not create a new
app — plan a client extension UNDER the existing one.**

<!-- platform-apps:v1 — drift-tested against lib/platform-catalog.ts (edit BOTH or the suite fails) -->
| App (code) | Label | Personal | Extendable | Domain aliases (FR/EN) |
|---|---|---|---|---|
| administration | Administration | no | no | Admin, System, Système, Paramétrage système |
| support | Support | no | yes | Helpdesk, Ticketing, Billetterie, Assistance, SAV |
| hr | Human Resources | no | yes | RH, HR, Ressources Humaines, Ressources humaines, Personnel, GRH, Human Resources |
| api | API | no | yes | External API, Data export, Export de données, Intégrations externes |
| myspace | My Space | yes | no | Espace personnel, Mon espace, Personal workspace |
<!-- /platform-apps:v1 -->

Match whole-token / whole-phrase (accent- & case-insensitive), never substring
(`HRManager` is not `hr`).

### The `hr` app is already built (the #1 duplication trap)

"Une application RH" is NOT a new app — the platform `hr` app
(`ApplicationId 9cbeae29-772f-43b1-ac93-c56f2cb90921`) already ships these modules.
NEVER recreate them:

| Built-in HR module | Route | Sections |
|---|---|---|
| employees | `/hr/employees` | list, org-chart |
| organization | `/hr/organization` | departments, job-titles, offices, holidays, contract-types |
| absences | `/hr/absences` | requests, calendar, types |
| time | `/hr/time` | inbox, entries, activities, settings |
| my-absences | `/hr/my-absences` | requests, balance, team-calendar |
| my-time | `/hr/my-time` | week, overview |
| reporting | `/hr/reporting` | overview |

The employee self-service portal (`my-absences`, `my-time`) already exists — do
NOT model an "Employee portal" for HR. `Employee`, `Absence`, `TimeEntry` are
platform-owned entities too (the data-model phase references/extends them, never
re-models — see `/ba-audit-cross-ref-code` CODE-005).

### Extend vs. duplicate — the protocol

When the requested domain matches a built-in app (the typical case: HR):

1. **Announce it**: tell the user the platform already provides `<app>` and list
   its built-in modules (above, for HR). Nothing there is re-created.
2. **Propose only the delta**: the client's NEW modules the platform does not
   already cover (e.g. `trainings`, `performance-reviews`, `recruitment`), added
   UNDER the existing app at `/<app>/<module>`.
3. **Anchor them on the built-in app**: author the new modules under the BA app
   node whose code maps to the built-in code — BA code `HR` (uppercased) →
   platform `hr`. Record in that node's `## Contexte`: *"Extension de l'application
   plateforme `hr` (ApplicationId 9cbeae29-…) — ces modules s'ajoutent aux modules
   livrés, jamais une 2ᵉ app HR."* so downstream RBAC/dev seed the modules against
   the existing app, never a duplicate `hr` application.
4. **Standalone override**: if the user genuinely wants an unrelated app that only
   *sounds* like a built-in, confirm explicitly and pick a DISTINCT code + label.

`administration` and `myspace` are platform-managed — never author client modules
under them. `support`, `api` and `hr` are the extendable business apps.

## The 4 levels & decision table

```
Application → Module → Section → Resource
```

Read the current tree, match the row, follow the level file. Re-read the tree
after every Write and re-enter this table.

| Tree state (for the focus app X) | Follow |
|----------------------------------|--------|
| `.smartstack/ba/` empty / first message | `./levels/applications.md` |
| App X has no modules | `./levels/modules.md` |
| X has modules, modules lack sections | `./levels/sections.md` |
| All X's modules have sections | `./levels/resources.md` |
| Complete tree for X | hand off to `/ba-create-actors` |
| User asks to add ONE node to a finished menu (a forgotten section, a sibling module, a resource) | § "ENRICH — one node after the fact" — `menu-node op=add` |
| User asks to rename a module / section / resource | § "Modifying & deleting" — `menu-node op=rename` (`check` → AskUserQuestion → `write`) |
| User asks to delete a module / section / resource | § "Modifying & deleting" — `menu-node op=delete` (`check` → confirm → `write`) |
| User asks to re-context / relabel a node WITHOUT changing its code | Edit the node's `index.md` (Contexte, Hors-périmètre, title label) — the anchor line stays verbatim |
| User asks to merge two modules | `./levels/modules.md` § "Fusion" |
| Informational question, no change | answer in prose, no Write |

The workflow is **per-app**: each app goes through applications → modules →
sections → resources independently.

## Writing a node (the persist step)

When the user validates, persist each node with the colocated **`menu-node`
CLI** — never by hand. You author the BUSINESS content (label, Contexte,
Hors-périmètre, Sources); the CLI does the shape: the folder named by the
code, the canonical `index.md`, the six placeholder docs, and ONE spliced line
in the parent's `## Enfants`. Write the spec to the scratchpad and pass
`--spec-file` (multi-line inline JSON breaks under Git Bash / PowerShell):

```bash
npx --prefer-offline tsx skills/business-analyse/create-menu/cli/menu-node/index.ts \
  --spec-file <scratchpad>/menu-node.json
```

```json
{ "baRoot": ".smartstack/ba", "op": "add", "mode": "check",
  "level": "section", "parent": { "app": "CRM", "module": "PIPELINE" },
  "code": "devis", "label": "Devis",
  "contexte": "3-5 sentences — WHO uses it, WHAT problem it solves, its LIMITS.",
  "horsPerimetre": ["La signature électronique passe par un tiers (hors v1)."],
  "sources": ["SRC-001 §2"] }
```

- `level` ∈ `application | module | section | resource`; `parent` carries the
  codes down to the parent (`app` for a module, `app`+`module` for a section,
  `app`+`module`+`section` for a resource; nothing for an application).
- `contexte` and `horsPerimetre` are REQUIRED for an application or a module
  (`[]` writes the explicit empty marker `_Aucune exclusion connue à ce stade._`
  — MENU-008); optional on a section/resource. `sources` = the `SRC-NNN §n`
  citations closing the Contexte (omit when no source applies).
- A built-in platform app (RH → `hr`) is refused unless `"extension": true`
  AND the Contexte carries « Extension de l'application plateforme `hr` … »
  (§ "Built-in platform apps"). The first application on an empty tree also
  creates the root `index.md` — pass `"project": { "label", "contexte",
  "horsPerimetre" }` for it.
- **`mode: check` first**: the envelope's `report.blocked[]` is the fail-closed
  gate — code format, forbidden label chars, duplicate sibling, hierarchy code
  collision (MENU-006), platform app/module collision (MENU-003), settings
  section, action/representation suffix (`-detail`, `-board`, …), bounds (apps
  ≤ 4 · modules ≤ 8 · sections ≤ 6 · resources ≤ 8), missing parent index.md.
  A non-empty `blocked[]` means STOP: fix the proposal (requalify, merge,
  rename) — never the tree. `report.warnings[]` mirror the audit's warns
  (Contexte < 120 chars, SEC-005 ambiguous name, XAPP-001 motif in another
  app) — say them, they are not a refusal. `report.plan` lists the files.
- **`mode: write`** applies exactly that plan. `report.noop: true` = the node
  already exists as requested (nothing written). Read `nextSteps`: when the
  module's `rbac.md` is authored, re-run `derive-permission-floor` (RBAC-009).

### `index.md` shape (every node) — what the CLI writes from your spec

```markdown
<!-- ba:node kind=node level=application code=CRM -->
# CRM — Gestion commerciale

## Contexte
{3-5 sentences: WHO uses this node (the actors it serves), WHAT business
problem it solves, and its LIMITS (where it stops). The business WHY, not a
paraphrase of the label.}
- **Sources** : SRC-001 §1

## Hors-périmètre
- {explicit exclusion — "not in v1", "covered by BILLING", a refused behaviour}

## Enfants
- [PIPELINE](./PIPELINE/index.md) — Pipeline d'opportunités
```

- `level` ∈ `project | application | module | section | resource`.
- `## Contexte` is REQUIRED (apps/modules/sections); **3-5 sentences carrying
  WHO / WHAT / LIMITS** — a one-liner is a stub (MENU-007 warns under ~120
  chars, errs on empty). Resources may have a one-line context. When a
  registered client source grounds the context, close it with the citation
  line `- **Sources** : SRC-NNN §n` (omit the line when no source applies —
  never invent one).
- `## Hors-périmètre` is **MANDATORY at app and module level** (MENU-008 errs
  when missing): list the explicit exclusions the user signalled ("pas X",
  "plus tard", "ça c'est ailleurs") — the downstream PRD recopies every
  non-empty one as a non-goal. When NO exclusion exists, write the explicit
  empty marker on its own line:

  ```markdown
  ## Hors-périmètre
  _Aucune exclusion connue à ce stade._
  ```

  The absence of exclusions is an ASSERTION, not an omission — the marker is
  what distinguishes « considered, nothing to exclude » from « never asked ».
  (The PRD does NOT copy the marker as a non-goal.) Encouraged on sections,
  not required.
- `## Enfants` lists child nodes with relative links; refresh it whenever you add
  a child. The root `.smartstack/ba/index.md` (`level=project`) lists the apps.

### The six placeholder docs — created by the CLI

Every new node folder also receives the six concept docs as placeholders so
later phases fill them in place (`acteur.md`, `entité.md`, `use-case.md`,
`règles-métier.md`, `rbac.md`, `screen.md` — French, accented filenames,
verbatim):

```markdown
<!-- ba:acteur level=application code=CRM -->
_À définir lors de la phase « acteurs » (/ba-create-actors)._
```

`menu-node op=add` emits them (`lib/ba-menu-tree.ts` `placeholderDocs`) — you
never write them. Later phases replace the placeholder with real content at
the authoritative level and write rollups elsewhere — that is their job, not
yours.

### Modifying & deleting

- **Re-context / relabel WITHOUT changing the code**: Edit the node's
  `index.md` (Contexte, Hors-périmètre, the label after the em-dash of the
  title). The anchor line `<!-- ba:node … -->` stays verbatim — it may carry
  `depends=` / `previousCodes=` other CLIs read. Update the parent's
  `## Enfants` label with an Edit of that one line.
- **Rename** (module / section / resource — the code changes):
  `menu-node op=rename` with `"to": { "code": "tiers", "label": "Tiers" }`.
  Knowing `from → to`, the CLI rewrites deterministically what
  `/ba-reconcile-menu` would otherwise have to GUESS: every long code
  `UC/SCR/BR/RBAC-APP-MOD-SEC[-RES]-NNN` of the tree (module renames included —
  the reconciler never recognises those), the human permission paths of the
  module's `rbac.md` (machine blocks untouched), for a module the `depends=`
  anchors + `## Dépendances` links and the `scope cross-module (APP/MOD)`
  relations of every `entité.md`, the pagespec references (`section`/`module`,
  `permission`, `navRoute`, `fkTo`, `relatedModule`/`relatedSection`,
  `apiEndpoint`, `targetRoute`), then moves the folder, writes `code=` +
  `previousCodes=<old, nav form>` on the node's anchor (THE channel through
  which the prod seed delta learns a rename) and the parent's `## Enfants`
  line. Run `check` first and present: `plan.codeRewrites` /
  `permissionPathRewrites` / `pagespecRewrites` (counts per file),
  `plan.residual` (leftover occurrences of the old code the token lists did
  not cover — relative links in rollups, prose: relay them, fix by Edit after
  the write) and `plan.reportOnly` (sources `scopes`, `_plan/*`, rbac machine
  blocks, `_audit/*`, `.run-snapshot.json`, core-seed — other writers own
  those; the `nextSteps` name the CLIs to re-run). An APPLICATION rename is
  refused (`app-rename-unsupported`) — manual route + `/ba-reconcile-menu`.
- **Delete** (module / section / resource): `menu-node op=delete` in `check`
  → show `plan.downstreamCodes` (blocks and body references that will go, per
  file), `plan.permissionPathRewrites` (rows removed), `plan.reportOnly`
  (pagespecs of a deleted SECTION — NEVER deleted by the CLI, they may carry
  `uiDesign`/`lifecycle` of `/ui-design`; `cross-module` relations pointing at
  a deleted MODULE — a model decision) and the warnings (« dernière section :
  le module devient vide », descendants) → AskUserQuestion → `write`. The CLI
  removes the folder, the downstream blocks and citations, the human rbac rows,
  a module's `depends=` in its siblings and the parent's `## Enfants` line;
  `reconcile-menu --dryRun` then finds no ghost. An APPLICATION delete is
  refused — manual route.
- **Merge two modules** (fusion): still a hand-made operation — re-parent the
  source module's children under the target (`op=add` each section under the
  target then move its docs, or move the folders and Edit the anchors),
  merge their `## Contexte`, `op=delete` the source module, and run
  `/ba-reconcile-menu` for what the hand-made moves left behind. See
  `./levels/modules.md` § "Fusion".

## ENRICH — one node after the fact

A finished menu (actors, use cases, screens already authored) gains one node
exactly like a first-pass one: research the level, self-audit, propose the
single candidate with its tier and rationale, then `menu-node op=add` (`check`
→ `write`). Nothing downstream is touched — the new node only has
placeholders; enrich it with `/ba-loop` on the module (or the `/ba-create-*`
phases one by one). `/ba-change` routes « a new entity needs a NEW section »
here. When the module's `rbac.md` is authored, the envelope's `nextSteps`
ask for `derive-permission-floor` (the `ba:rbac-floor` mirror lists node
paths — RBAC-009 otherwise).

## After writing a level → checkpoint, don't auto-drill

After writing applications (or modules, …), acknowledge in **one line** and ask
the user — via AskUserQuestion — whether to continue to the next level, add a
sibling, or pause. Do NOT auto-jump into proposing modules before the user opts
in. Never list "what phase next?" — the order is fixed (menu → actors → …).

## Absolute prohibitions

1. **Sections are nouns**, never verbs/actions (`factures`, not `create`/`emettre`).
   Sole exception: `list`/`liste` (maps to a list view). Actions are buttons, not sections.
2. **Never a `Settings` section** — settings are a MODULE.
3. **Never a `SETTINGS` application** — each app owns its settings.
4. **Never duplicate a built-in platform app** (see § "Built-in platform apps —
   extend, never duplicate"): the platform ships `administration`, `support`,
   `hr`, `api`, `myspace`, plus cross-cutting features (Auth/SSO, Notifications,
   Workflows, Data export). A requested domain that matches one (e.g. RH → `hr`)
   is an EXTENSION under it, never a new application.
5. **Never "CORE" terminology, never "Fundamental / Recommended / Advanced"** —
   proposals are tiered **Obligatoire / Suggestion / Élargissement** only
   (§ "Proposal tiers").
6. Sections are autonomous **business** workspaces (Dashboard, Directory, Catalogue,
   List), not transient CRUD operations — and never a *representation* of a list
   (a kanban / board / pipeline / calendar is a view authored as a second screen in
   the entity's `*-list` section, never its own section).

## Per-level guidance

Load the level file that matches the decision table for the detailed proposal
heuristics: `./levels/applications.md`, `./levels/modules.md`,
`./levels/sections.md`, `./levels/resources.md`. They carry the exploration
checklist, two-axis (manager/portal) pattern, naming rules, and edge cases.
