---
name: fluent-data-module-scaffold
description: Scaffold a new Fluent Commerce data module (no Java plugins). Generates module structure with assets directories, module.json, config template, and build script. Triggers on "create data module", "scaffold data module", "new data module", "data module".
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
argument-hint: <module-name> [--profile <profile>]
cursor-tier: auto
planning-gate: mandatory
---

# Data Module Scaffolder

Generate a complete Fluent Commerce data module skeleton with no Java plugins, no Maven, and no compilation step. Data modules package infrastructure data (locations, networks, carriers), product catalogues, settings, workflows, and other JSON/CSV assets into a deployable module ZIP.

## Pre-flight: Feedback Check

Before starting, check for past learnings from this skill:

1. Check if `accounts/<PROFILE>/feedback/fluent-data-module-scaffold.jsonl` exists
2. If yes, read last 20 records
3. Filter to: FAILURE or USER_CORRECTED outcomes, confidence = CONFIRMED or PROMOTED
4. Extract top 3 relevant learnings — use as internal guidance during execution
5. Do NOT show raw feedback to the user — silently adjust approach based on past issues

## Planning Gate

### Pre-flight Chain (Three-Level)

Before scaffolding any data module, run the three-level pre-flight check:

1. **Spec check:** If `accounts/<PROFILE>/features/<slug>/status.json` exists for this work:
   - Check `spec: "APPROVED"` — If missing, BLOCK: `→ BLOCKED: PREREQ_MISSING — Run /fluent-use-case-discover first`
   - Check `plan: "APPROVED"` — If missing, BLOCK: `→ BLOCKED: PLAN_REQUIRED — Run /fluent-feature-plan first`
   - If both approved, skip to implementation
2. **Standalone data-module work** (no feature context):
   - Check `accounts/<PROFILE>/tasks/` for a task plan with `Status: APPROVED`
   - If approved task plan found, skip to implementation
   - If no plan, write a plan and get approval before proceeding

**Plan file path:** `accounts/<PROFILE>/tasks/<YYYY-MM-DD>-data-module-scaffold-<slug>.md`

The plan should cover:
1. **Purpose** -- what data this module will contain and why it is a separate module
2. **Asset inventory** -- which asset directories will be populated and with what content
3. **Config variables** -- what `[[variable]]` tokens will be used and their expected values
4. **Dependencies** -- other modules that must be installed first or after
5. **Target retailer(s)** -- which retailer(s) this data is scoped to

Write the plan file, present it to the user, and wait for explicit approval ("yes", "go ahead", "approved", "do it") before generating any files. On approval, update the plan status to `APPROVED`.

**Skip the gate** when: the user says "just do it" or "skip planning".

## Handoff Protocol

### Signals emitted by this skill

| Signal | Condition | Example |
|--------|-----------|---------|
| `-> READY: <path>` | Data module scaffolded | `-> READY: accounts/MY_PROFILE/SOURCE/backend/fc-data-locations/` |
| `-> NEXT: /fluent-<skill>` | Ready for build or deploy | `-> NEXT: /fluent-module-deploy` |
| `-> BLOCKED: <reason>` | Cannot proceed | `-> BLOCKED: PLAN_REQUIRED — Write a plan via /fluent-feature-plan` |

### Error codes

| Code | Condition | Recovery |
|------|-----------|----------|
| `PLAN_REQUIRED` | Scaffolding attempted without approved plan | Run `/fluent-feature-plan` first |
| `VALIDATION_FAILED` | Module name conflicts or missing required assets | Fix input and retry |

## When to Use

- Infrastructure data: locations, networks, carriers, storage areas
- Product catalogues, categories, and product data
- Inventory catalogue and virtual catalogue definitions
- Settings bundles (retailer-scoped or account-scoped)
- Workflow-only modules (deploying workflow JSON without custom rules)
- Sample or test data for development and QA environments
- Any module that contains only JSON/CSV assets and no Java plugins

## When NOT to Use

- If you need custom Java orchestration rules, use `/fluent-module-scaffold` instead
- If you need to add rules to an existing module, use `/fluent-rule-scaffold`
- If you are building an extension module with Maven/OSGi plugins, use `/fluent-module-scaffold`

## Ownership Boundary

This skill owns:
- Creating the data module directory structure
- Generating `module.json` (no rules section)
- Generating `module.config.json` template
- Creating empty asset directories
- Generating build scripts (bash `.sh` and PowerShell `.ps1`) for ZIP packaging
- Providing asset JSON schema examples

This skill does **not** own:
- Java compilation or Maven builds --> `/fluent-module-scaffold`
- Adding rules to any module --> `/fluent-rule-scaffold`
- Building extension modules with plugins --> `/fluent-build`
- Deploying modules --> `/fluent-module-deploy`
- Validating module structure --> `/fluent-module-validate`

## Inputs

| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| `module-name` | Yes | -- | Module identifier (e.g., `hm-infra-data`). Lowercase alphanumeric with hyphens only. Used for directory name and `module.json` name field. |
| `--description` | No | Auto-generated | Module description for `module.json` |
| `--account-prefix` | No | Auto-detect from profile | Account/company prefix for the module name in `module.json` (e.g., `hm`, `acme`) |
| `--profile` | No | Active profile | Fluent CLI profile for path resolution and prefix detection |
| `--initial-version` | No | `1.0.0` | Initial version in `module.json` |

## Pre-Check: Module Already Exists?

Before creating anything, check if the target directory already exists:

```
accounts/<PROFILE>/SOURCE/backend/<module-name>/
```

If it exists, **abort** with message:
```
Module directory already exists: accounts/<PROFILE>/SOURCE/backend/<module-name>/
Choose a different module name or delete the existing directory first.
```

Also check for a Maven-based module with the same name:
```
accounts/<PROFILE>/SOURCE/backend/fluentcommerce-fc-module-<module-name>/
```

If found, warn that an extension module with this name already exists.

## Account Prefix Detection

The account prefix appears in the `module.json` `name` field as `<prefix>/<module-name>`. Detection order:

```
1. If --account-prefix provided, use it directly

2. Else check existing modules under accounts/<PROFILE>/SOURCE/backend/
   Read any module.json and extract the prefix from the "name" field
   e.g., "name": "hm/infra-data" --> prefix is "hm"

3. Else derive from PROFILE name (lowercase)
   MY_PROFILE --> myprofile
   ACME_CORP  --> acmecorp

4. Fallback: "my-company"
   Warn user: "Could not detect account prefix. Update module.json name field manually."
```

## Generated Directory Structure

```
accounts/<PROFILE>/SOURCE/backend/<module-name>/
+-- resources/
|   +-- module.json                    # Module manifest (no rules)
|   +-- module.config.json             # Variable template
|   +-- assets/
|       +-- locations/                 # Location JSON files
|       +-- networks/                  # Network JSON files
|       +-- carriers/                  # Carrier JSON files
|       +-- categories/                # Category JSON files
|       +-- products/                  # Product JSON files
|       +-- inventory-catalogues/      # Inventory catalogue definitions
|       +-- product-catalogues/        # Product catalogue definitions
|       +-- virtual-catalogues/        # Virtual catalogue definitions
|       +-- settings/                  # Settings JSON files
|       +-- workflows/                 # Workflow JSON files
|       +-- workflow-fragments/        # Workflow fragment groups
|       |   +-- <workflow-name>/       # One subdirectory per workflow
|       +-- controls/                  # Control group JSON files
|       +-- storage-areas/             # Storage area definitions
|       +-- users/                     # User JSON files
+-- scripts/
|   +-- build-module.sh                # Bash build script (ZIP packaging)
|   +-- build-module.ps1               # PowerShell build script (ZIP packaging)
+-- dist/                              # Output directory for built ZIPs
```

**Key difference from extension modules:** No `plugins/` directory, no `pom.xml`, no `global/` directory, no `.gitignore` for Maven artifacts. The entire module is just `resources/` + `scripts/`.

## File Templates

### Module Manifest (`resources/module.json`)

```json
{
    "_schema": "1.0.0",
    "name": "${ACCOUNT_PREFIX}/${MODULE_NAME}",
    "version": "${INITIAL_VERSION}",
    "title": "${MODULE_TITLE}",
    "description": "${MODULE_DESCRIPTION}",
    "authors": [
        {
            "name": "Implementation Team"
        }
    ],
    "dependencies": [
        {
            "name": "fluent-commerce/core",
            "type": "module",
            "version": "2.x.x"
        }
    ],
    "contracts": []
}
```

**Important:** Data modules have NO `modules[].provides` section and NO `rules` array. There are no Java plugins to register.

### Config Template (`resources/module.config.json`)

```json
{
    "default:carrier.ref": "",
    "default:network.ref": "",
    "default:catalogue.ref": "",
    "workflow:order:hd:network.ref": "",
    "workflow:order:cc:network.ref": "",
    "setting:api.timeout": "30000"
}
```

Populate this template with keys relevant to the module's asset files. Keys that are not needed can be removed. An empty `{}` is valid if no variables are used.

### Config Prefix System

Config keys in `module.config.json` use a prefix system to control variable scope:

| Prefix | Scope | Example |
|--------|-------|---------|
| `default:` | Applied to all assets | `default:carrier.ref` |
| `workflow:<type>:<subtype>:` | Applied only to matching workflow assets | `workflow:order:hd:network.ref` |
| `setting:` | Applied only to settings assets | `setting:api.timeout` |
| *(none)* | Global scope | `carrier.ref` |

**Specificity rule:** More specific prefixes (more colons) take priority over less specific ones. For example, `workflow:order:hd:network.ref` overrides `default:network.ref` for the ORDER::HD workflow.

**Auto-injected variables** (available without config): `account.id`, `retailer.id`, `retailer.ref`, `retailer.name`.

### Variable Syntax

Use `[[variable]]` tokens inside asset JSON files. The Fluent CLI resolves these at install time using the config file values.

```json
{
    "ref": "NET_HD_1",
    "name": "HD Network",
    "type": "FulfilmentNetwork",
    "defaultCarrier": "[[carrier.ref]]"
}
```

Unresolved tokens are left as-is in the installed data. Always verify that all tokens have corresponding config entries before deploying to production.

## Asset JSON Schema Examples

### Location (`resources/assets/locations/locations.json`)

```json
[
    {
        "ref": "LOC_WH_1",
        "name": "Warehouse 1",
        "type": "WAREHOUSE",
        "defaultCarrier": "[[carrier.ref]]",
        "supportPhoneNumber": "+1-555-0100",
        "attributes": [
            {
                "name": "storageCapacity",
                "type": "STRING",
                "value": "5000"
            }
        ]
    },
    {
        "ref": "LOC_STORE_1",
        "name": "Store 1",
        "type": "STORE",
        "defaultCarrier": "[[carrier.ref]]"
    }
]
```

### Network (`resources/assets/networks/networks.json`)

```json
[
    {
        "ref": "NET_HD_1",
        "name": "HD Network",
        "type": "FulfilmentNetwork",
        "locationRefs": [
            "LOC_WH_1"
        ]
    },
    {
        "ref": "NET_CC_1",
        "name": "CC Network",
        "type": "FulfilmentNetwork",
        "locationRefs": [
            "LOC_STORE_1"
        ]
    }
]
```

### Carrier (`resources/assets/carriers/carriers.json`)

```json
[
    {
        "ref": "CARRIER_STD",
        "name": "Standard Carrier"
    },
    {
        "ref": "CARRIER_EXPRESS",
        "name": "Express Carrier"
    }
]
```

### Setting (`resources/assets/settings/settings.json`)

```json
[
    {
        "name": "WEBHOOK_ORDER_URL",
        "value": "https://example.com/webhook/order",
        "context": "RETAILER"
    },
    {
        "name": "ORDER_NOTES_ENABLED",
        "value": "true",
        "context": "RETAILER"
    },
    {
        "name": "FULFILMENT_BATCH_SIZE",
        "value": "50",
        "context": "ACCOUNT"
    }
]
```

**For Mystique manifest settings**, use `valueType: "JSON"`, `context: "ACCOUNT"`, `contextId: 0`, and `lobValue` (NEVER `value`):

```json
[
    {
        "name": "fc.mystique.manifest.oms.fragment.dashboard",
        "valueType": "JSON",
        "lobValue": { "manifestVersion": "2.0", "routes": [] },
        "context": "ACCOUNT",
        "contextId": 0
    }
]
```

Deploy manifest settings with: `fluent module install <module> --include settings --force`. See `/fluent-mystique-builder` for full safety guardrails (backup, validate, approve, deploy).

### Product Catalogue (`resources/assets/product-catalogues/product-catalogues.json`)

```json
[
    {
        "ref": "PC:MASTER:[[retailer.id]]",
        "name": "Master Product Catalogue",
        "type": "DEFAULT",
        "description": "Primary product catalogue"
    }
]
```

### Inventory Catalogue (`resources/assets/inventory-catalogues/inventory-catalogues.json`)

```json
[
    {
        "ref": "DEFAULT:[[retailer.id]]",
        "name": "Default Inventory Catalogue",
        "type": "DEFAULT",
        "description": "Primary inventory catalogue",
        "retailerRefs": [
            "[[retailer.ref]]"
        ]
    }
]
```

### Virtual Catalogue (`resources/assets/virtual-catalogues/virtual-catalogues.json`)

```json
[
    {
        "ref": "VC:DEFAULT:[[retailer.id]]",
        "name": "Default Virtual Catalogue",
        "type": "DEFAULT",
        "inventoryCatalogueRef": "DEFAULT:[[retailer.id]]",
        "productCatalogueRef": "PC:MASTER:[[retailer.id]]",
        "networkRef": "[[network.ref]]"
    }
]
```

## Build Script Templates

Data module build scripts are simpler than extension module scripts -- no Maven, no Java compilation. They read the version from `module.json` and ZIP the `resources/` directory.

### Bash Build Script (`scripts/build-module.sh`)

```bash
#!/bin/bash
set -euo pipefail

VERBOSE=false

parse_options() {
  while getopts "x" opt; do
    case $opt in
    x) VERBOSE=true ;;
    *)
      echo "Usage: $0 [-x]"
      echo "  -x  Enable verbose mode"
      exit 1
      ;;
    esac
  done
}

log() {
  if [ "$VERBOSE" = true ]; then
    echo "$@"
  fi
}

require_command() {
  local command_name=$1
  if ! command -v "$command_name" >/dev/null 2>&1; then
    echo "Error: required command '$command_name' was not found in PATH"
    exit 1
  fi
}

clean_folder() {
  local dist_dir=$1
  if [ -d "$dist_dir" ]; then
    log "Directory $dist_dir exists, clearing..."
    rm -rf "${dist_dir:?}/"
  fi
}

main() {
  parse_options "$@"
  require_command zip
  require_command sed

  SCRIPT_DIR=$(dirname "$(realpath "$0")")
  MODULE_BASE_DIR="$SCRIPT_DIR/.."
  DIST_DIR="$MODULE_BASE_DIR/dist"
  RESOURCES_DIR="$MODULE_BASE_DIR/resources"

  log "Script Directory: $SCRIPT_DIR"
  log "Module Directory: $MODULE_BASE_DIR"

  if [ ! -f "$RESOURCES_DIR/module.json" ]; then
    echo "Error: resources/module.json not found"
    exit 1
  fi

  module_name=$(sed -n 's/.*"name": *"\([^"]*\)".*/\1/p' "$RESOURCES_DIR/module.json" | head -n 1)
  sanitized_name=$(echo "$module_name" | sed 's|/|-|g')
  module_version=$(sed -n 's/.*"version": *"\([^"]*\)".*/\1/p' "$RESOURCES_DIR/module.json" | head -n 1)
  combined_name="$sanitized_name-$module_version"

  echo "Building data module: $module_name v$module_version"

  clean_folder "$DIST_DIR"

  local module_dist_dir="$DIST_DIR/$combined_name"
  local module_dist_assets_dir="$module_dist_dir/assets"
  mkdir -p "$module_dist_dir"

  # Copy module.json
  cp "$RESOURCES_DIR/module.json" "$module_dist_dir/"

  # Copy module.config.json if present
  if [ -f "$RESOURCES_DIR/module.config.json" ]; then
    cp "$RESOURCES_DIR/module.config.json" "$module_dist_dir/"
  fi

  # Copy all asset directories
  if [ -d "$RESOURCES_DIR/assets" ]; then
    mkdir -p "$module_dist_assets_dir"
    for entry in "$RESOURCES_DIR/assets"/*; do
      [ -e "$entry" ] || continue
      cp -r "$entry" "$module_dist_assets_dir/"
    done
  fi

  # Create ZIP
  local zip_file="$combined_name.zip"
  (cd "$module_dist_dir" && zip -r "$zip_file" . -q)
  mv "$module_dist_dir/$zip_file" "$DIST_DIR/"

  echo "Built: dist/$zip_file"
  echo "Install with: fluent module install ./ -p <PROFILE> -r <RETAILER>"
}

main "$@"
```

### PowerShell Build Script (`scripts/build-module.ps1`)

```powershell
#!/usr/bin/env pwsh

<#
.SYNOPSIS
    Build a Fluent Commerce data module (no Java, ZIP packaging only)

.DESCRIPTION
    Reads version from module.json, copies resources into a staging directory,
    and creates a distributable ZIP file in dist/.

.PARAMETER VerboseLogging
    Enable verbose logging

.EXAMPLE
    .\build-module.ps1

.EXAMPLE
    .\build-module.ps1 -VerboseLogging
#>

[CmdletBinding()]
param(
    [Alias('x')]
    [switch]$VerboseLogging
)

$Script:VerboseMode = $VerboseLogging

function Write-Log {
    param([string]$Message)
    if ($Script:VerboseMode) {
        Write-Host $Message
    }
}

function Remove-FolderIfExists {
    param([string]$Path)
    if (Test-Path $Path) {
        Write-Log "Directory $Path exists, clearing..."
        Remove-Item -Path $Path -Recurse -Force
    }
}

function Get-JsonValue {
    param(
        [string]$FilePath,
        [string]$PropertyName
    )
    if (-not (Test-Path $FilePath)) { return $null }
    try {
        $json = Get-Content $FilePath -Raw | ConvertFrom-Json
        return $json.$PropertyName
    } catch {
        Write-Log "Failed to parse JSON from $FilePath"
        return $null
    }
}

function Main {
    if ($PSScriptRoot) {
        $scriptDir = $PSScriptRoot
    } elseif ($MyInvocation.MyCommand.Path) {
        $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
    } else {
        $scriptDir = (Get-Location).Path
    }

    $moduleBaseDir = Split-Path -Parent $scriptDir
    $distDir = Join-Path $moduleBaseDir "dist"
    $resourcesDir = Join-Path $moduleBaseDir "resources"

    $moduleJsonPath = Join-Path $resourcesDir "module.json"
    if (-not (Test-Path $moduleJsonPath)) {
        Write-Error "resources/module.json not found"
        exit 1
    }

    $moduleName = Get-JsonValue $moduleJsonPath "name"
    $moduleVersion = Get-JsonValue $moduleJsonPath "version"
    if (-not $moduleName -or -not $moduleVersion) {
        Write-Error "Could not extract module name or version from module.json"
        exit 1
    }

    $sanitizedName = $moduleName -replace "/", "-"
    $combinedName = "$sanitizedName-$moduleVersion"

    Write-Host "Building data module: $moduleName v$moduleVersion"

    Remove-FolderIfExists $distDir

    $moduleDistDir = Join-Path $distDir $combinedName
    $moduleDistAssetsDir = Join-Path $moduleDistDir "assets"
    New-Item -ItemType Directory -Path $moduleDistDir -Force | Out-Null

    # Copy module.json
    Copy-Item $moduleJsonPath $moduleDistDir

    # Copy module.config.json if present
    $configPath = Join-Path $resourcesDir "module.config.json"
    if (Test-Path $configPath) {
        Copy-Item $configPath $moduleDistDir
    }

    # Copy all asset directories
    $assetsDir = Join-Path $resourcesDir "assets"
    if (Test-Path $assetsDir) {
        New-Item -ItemType Directory -Path $moduleDistAssetsDir -Force | Out-Null
        Get-ChildItem -Path $assetsDir | ForEach-Object {
            Copy-Item -Path $_.FullName -Destination $moduleDistAssetsDir -Recurse -Force
        }
    }

    # Create ZIP
    $zipPath = Join-Path $distDir "$combinedName.zip"
    Compress-Archive -Path "$moduleDistDir\*" -DestinationPath $zipPath -Force

    Write-Host "Built: dist/$combinedName.zip"
    Write-Host "Install with: fluent module install ./ -p <PROFILE> -r <RETAILER>"
}

Main
```

## Deployment

After building, install the module using the Fluent CLI:

```bash
# Basic install (no config variables)
fluent module install ./<module-name>/ -p <PROFILE> -r <RETAILER>

# Install with config file (resolves [[variable]] tokens)
fluent module install ./<module-name>/ -p <PROFILE> -r <RETAILER> --config module.config.<RETAILER>.<module>.json

# Generate a retailer-specific config from the template
fluent module config <module-name> -p <PROFILE> -r <RETAILER>
```

**Install order matters.** Infrastructure data (locations, networks, carriers) must be installed before modules that reference them (orders, fulfilments). A typical sequence:

```
1. Core module (fluent-commerce/core)     -- usually pre-installed
2. Infrastructure data module             -- locations, networks, carriers
3. Order/fulfilment workflow modules      -- reference network and location refs
4. Product and inventory data modules     -- catalogues and stock
```

## Scaffold Execution Protocol

Follow these steps in order when scaffolding a new data module:

```
1. VALIDATE inputs
   a. module-name: must match /^[a-z][a-z0-9-]*$/ (lowercase, hyphens, no leading hyphen)
   b. module-name length: 1-50 characters
   c. Reject names starting with digits or containing spaces/special characters

2. PRE-CHECK: Module already exists?
   a. Check accounts/<PROFILE>/SOURCE/backend/<module-name>/ exists
   b. Check accounts/<PROFILE>/SOURCE/backend/fluentcommerce-fc-module-<module-name>/ exists
   c. If either exists --> ABORT with guidance

3. DETECT account prefix
   a. If --account-prefix provided --> use it
   b. Else scan existing modules under accounts/<PROFILE>/SOURCE/backend/*/resources/module.json
      Extract prefix from "name" field (part before the slash)
   c. Else derive from PROFILE name (lowercase)
   d. Fallback: "my-company" with warning

4. COMPUTE derived values
   a. MODULE_TITLE = title-case of module-name with hyphens as spaces
      (e.g., "hm-infra-data" --> "Hm Infra Data")
   b. MODULE_DESCRIPTION = description from --description or auto-generated
      (e.g., "Data module for Hm Infra Data")

5. CREATE directory structure
   a. Create the root: accounts/<PROFILE>/SOURCE/backend/<module-name>/
   b. Create resources/ and resources/assets/ with all subdirectories
   c. Create scripts/ and dist/

6. GENERATE files (use Write tool, not bash echo)
   a. resources/module.json -- with substituted values
   b. resources/module.config.json -- empty template {}
   c. scripts/build-module.sh -- bash ZIP script
   d. scripts/build-module.ps1 -- PowerShell ZIP script

7. MAKE build script executable
   bash: chmod +x scripts/build-module.sh

8. REPORT generated files
   List all files and directories created.
   Print next steps:
   - "Data module scaffolded at: accounts/<PROFILE>/SOURCE/backend/<module-name>/"
   - "Next: Add asset JSON files under resources/assets/<type>/"
   - "Build: cd accounts/<PROFILE>/SOURCE/backend/<module-name> && bash scripts/build-module.sh"
   - "Deploy: fluent module install ./<module-name>/ -p <PROFILE> -r <RETAILER>"
```

## Integration with Other Skills

| Skill | Relationship |
|-------|-------------|
| `/fluent-module-scaffold` | For extension modules with Java plugins. If you realize you need custom rules, scaffold an extension module instead. |
| `/fluent-module-validate` | Validates the data module structure. Should pass with 0 FAILs on a freshly scaffolded module. |
| `/fluent-module-deploy` | Deploys the built ZIP to a target retailer. |
| `/fluent-settings` | For managing settings via MCP tools. Data module settings are an alternative approach using the module install pipeline. |
| `/fluent-retailer-config` | For creating locations, networks, and carriers via GraphQL mutations. Data modules are the alternative file-based approach. |
| `/fluent-workflow-builder` | Workflows can be packaged in a data module under `assets/workflows/` for deployment via `fluent module install`. |
| `/fluent-build` | Extension module build skill. Not needed for data modules -- use the included `scripts/build-module.sh` instead. |

## Edge Cases

### Empty module (no assets yet)

A scaffolded module with empty asset directories is valid. The build script will produce a ZIP containing only `module.json` and empty `assets/` directories. This is useful as a starting point -- add JSON files incrementally.

### Module name conflicts

Check both data module and extension module naming patterns:
```bash
ls accounts/<PROFILE>/SOURCE/backend/ | grep -i "<module-name>"
```
If found, abort. Do NOT overwrite an existing module.

### Windows path handling

- Build scripts have both `.sh` (Git Bash, WSL, macOS, Linux) and `.ps1` (PowerShell) variants
- Asset directory names use hyphens, not colons (e.g., `inventory-catalogues`, not `inventory::catalogues`)
- Workflow fragment subdirectories should use Windows-safe names (e.g., `ORDER-HD` instead of `ORDER::HD`)

### Large asset files

For large datasets (thousands of products, inventory positions):
- Split into multiple JSON files within the same asset directory
- The Fluent CLI processes all files in each asset directory
- Consider using CSV format for inventory data (`inventory.csv` instead of `inventory.json`)

### Config variable not resolved

If a `[[variable]]` token has no matching key in the config file:
- The token is left as-is in the installed data (e.g., the literal string `[[carrier.ref]]`)
- This will likely cause errors in the Fluent environment
- Always run `fluent module config <module> -p <PROFILE> -r <RETAILER>` to generate and fill the config before production installs

## Gotchas

- **`module.config.json` is required** even if empty. The Fluent CLI expects this file to exist. Use `{}` for modules with no variables.
- **No `rules` array in `module.json`** for data modules. Including an empty `"rules": []` is harmless but unnecessary. Including rule names that do not exist will cause install warnings.
- **Asset directory names are convention-driven.** The Fluent CLI maps directory names to entity types. Using a non-standard directory name (e.g., `locs/` instead of `locations/`) will cause the assets to be skipped silently.
- **Install order matters.** Locations must exist before networks can reference them. Networks must exist before workflows can reference them. Install infrastructure data before order/fulfilment modules.
- **Workflow fragments** go in `workflow-fragments/<group>/`, not `workflows/`. Fragments are partial workflow definitions that merge into existing workflows. Full workflow JSONs go in `workflows/`.
- **The `_schema` field** in `module.json` must be `"1.0.0"`. This is a Fluent CLI requirement and not a user-defined version.
- **Config key auto-injection:** `account.id`, `retailer.id`, `retailer.ref`, and `retailer.name` are always available as `[[account.id]]`, `[[retailer.id]]`, etc. without needing config entries. Do not duplicate these in `module.config.json`.

## Post-Execution: Feedback Capture

After completing this skill (whether success or failure), write a feedback record:

1. **Classify outcome:** `SUCCESS` (completed cleanly), `PARTIAL_SUCCESS` (completed after fixing issues), `FAILURE` (could not complete), `BLOCKED` (prereq missing), `USER_CORRECTED` (user overrode approach)
2. **Build record:** Create a `feedback-record-v1` JSON object with:
   - `skill`: `"fluent-data-module-scaffold"`
   - `feature`: feature slug if applicable, null otherwise
   - `phases`: trace of each phase with PASS/FAIL/SKIP
   - `learnings`: any gotchas discovered during execution
   - `userCorrections`: any corrections the user provided
3. **Redact secrets:** Strip tokens, passwords, API keys. Keep business identifiers.
4. **Append** one JSON line to `accounts/<PROFILE>/feedback/fluent-data-module-scaffold.jsonl`
5. **Update** `accounts/<PROFILE>/feedback/index.json` counters (create with `mkdir -p` if missing)

**Skip capture if:** The execution was trivially simple (e.g., user asked a question, no actual write operation happened).

## Known Pitfalls
<!-- feedback-promoted: managed section, updated by feedback loop -->

_(No promoted learnings yet.)_

<!-- end feedback-promoted -->
