---
name: fluent-custom-code
description: Analyze custom Fluent implementation source code under accounts/<PROFILE>/SOURCE/backend, explain behavior, and prepare safe extension plans even when repo/module layout is non-standard. Triggers on "analyze custom code", "explain this plugin", "extend rule logic", "understand source", "non-standard module structure".
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
argument-hint: <PROFILE> [--retailer <RETAILER_REF>] [--objective "<what to change>"]
cursor-tier: manual
---

# Custom Code Intelligence

Understand and extend custom Fluent implementation code safely, even when repo layout does not follow standard Fluent module structure.

## Pre-flight: Feedback Check

Before starting, check for past learnings from this skill:

1. Check if `accounts/<PROFILE>/feedback/fluent-custom-code.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

## Ownership Boundary

This skill owns source discovery, code mapping, behavior explanation, and extension readiness for custom repos.

- Workflow structure analysis is owned by `/fluent-workflow-analyzer`.
- Runtime failure tracing is owned by `/fluent-trace`.
- Workflow editing is owned by `/fluent-workflow-builder`.

## When to Use

- "Explain what this custom code does end-to-end"
- "Which class/rule handles this behavior?"
- "How can we extend this logic safely?"
- Source layout is unclear, nested, or non-standard

## Required Inputs

1. `PROFILE` (maps to `accounts/<PROFILE>/`)
2. Optional `RETAILER_REF` (needed to download workflows if missing)
3. Optional objective (what behavior to explain or extend)

## Workspace Convention

Primary paths:

- Source repos: `accounts/<PROFILE>/SOURCE/backend/`
- Workflow JSONs: `accounts/<PROFILE>/workflows/<RETAILER_REF>/`

If `accounts/<PROFILE>/workflows/<RETAILER_REF>/` is missing or empty, download first:

```bash
fluent workflow list -p <PROFILE> -r <RETAILER_REF>
fluent workflow download -p <PROFILE> -r <RETAILER_REF> -w all -o accounts/<PROFILE>/workflows/<RETAILER_REF>/
```

If the host OS cannot write workflow files containing reserved characters (for example `::` on Windows), or if CLI returns 401, use MCP tools instead:

```
# Download ALL workflows to disk in one call (returns summary only, keeps large JSON out of context):
workflow.list({ retailerId: "<retailer_id>", outputDir: "accounts/<PROFILE>/workflows/<RETAILER_REF>" })
# Or fetch a single workflow to file:
workflow.get({ entityType: "ORDER", entitySubtype: "HD", retailerId: "<retailer_id>", outputFile: "accounts/<PROFILE>/workflows/<RETAILER_REF>/ORDER-HD.json" })
```

Alternatively, export JSON via CLI and normalize file names:

```bash
fluent workflow download -p <PROFILE> -r <RETAILER_REF> -w all --json > accounts/<PROFILE>/workflows/<RETAILER_REF>/workflows-raw.json
```

Then split into one file per workflow with sanitized names, and persist `workflow-file-map.json` (original workflow name -> sanitized filename).

> **If `workflow-file-map.json` is missing**, generate it from the workflow directory before proceeding. See `/fluent-workflow` for the generation script. Without the map, workflow name resolution may fail on Windows.

If `RETAILER_REF` is omitted and multiple retailer folders exist under `accounts/<PROFILE>/workflows/`, require explicit retailer selection before analysis.

### Account intake contract

Treat `accounts/<PROFILE>/` as the single analysis workspace for that account:

- `accounts/<PROFILE>/SOURCE/backend/` may contain:
  - editable source repos (`module.json`, `src/main/java`, tests)
  - read-only deployment artifacts (`*.jar`, `*.zip`)
- `accounts/<PROFILE>/workflows/<RETAILER_REF>/` contains downloaded workflow JSON baselines.
- `accounts/<PROFILE>/workflows/<RETAILER_REF>/workflow-context.json` captures the owning profile + retailer and must match analysis target.
- `accounts/<PROFILE>/analysis/` contains generated analysis artifacts for reuse.

The analysis objective is a holistic inventory:

1. What custom rules exist (source + decompiled artifacts).
2. What is deployed/available (installed rules snapshot and workflow references).
3. What can be safely reused vs what needs new code.
4. What remains unknown and needs additional source input.

### Pre-Check: Context Verification and Module Inventory

**Verify MCP target matches analysis profile:** Run `config.validate` via MCP. If the `baseUrl` points at a different account than `<PROFILE>`, the MCP `plugin.list` results will carry the wrong `<ACCOUNT>` prefix. In that case, use the Fluent CLI instead: `fluent module list -p <PROFILE>`.

**Discover all retailers:** A single profile may serve multiple retailers with different workflows. Enumerate retailer configs (`~/.fluentcommerce/<PROFILE>/retailer.*.json`) and list workflows per retailer. Download workflows for all retailers that have deployed content.

**Check module inventory:** Before scanning source, check if a module inventory exists:

```
accounts/<PROFILE>/analysis/modules/module-inventory.json
```

If found, read it to identify:
- Which modules are `customExtension` (have rules to analyze)
- Which have `hasSource: true` (source available under SOURCE/backend/)
- Which have `hasSource: false` (need JAR/ZIP decompilation)
- The `sourceDir` for each module (exact path to source root)

If missing, recommend running `/fluent-module-validate inventory` first.

This pre-check avoids scanning for modules that are reference (no custom code) or data-only (no rules).

## Pre-flight: Project Knowledge Check

Check for project-specific knowledge that should inform code analysis:

1. Check if `accounts/<PROFILE>/knowledge/` exists
2. If yes, read it and find files where `relevant-skills` includes `fluent-custom-code`
   **Account knowledge:** Read from `accounts/<PROFILE>/knowledge/` when `FLUENT_PROFILE` is set. These contain project-specific conventions.

3. Read matching knowledge files — typically:
   - `knowledge/platform/domain-model.md` — use for entity edges, GraphQL traversal paths, cross-domain relationships
   - `knowledge/platform/graphql-in-rules.md` — use for DynamicUtils vs Apollo class analysis, GraphQL API patterns in rules
   - `accounts/<PROFILE>/knowledge/coding-standards.md` — use for code conventions, logging, error handling patterns
   - `accounts/<PROFILE>/knowledge/client/business-rules.md` — use for understanding rule business logic context
   - `accounts/<PROFILE>/knowledge/client/integrations.md` — use for external API calls in rule code
   - `knowledge/platform/rule-development-contract.md` — **MANDATORY**: annotation audit checklist (`@ParamString` vs `@EventAttribute`), reading patterns, anti-patterns to flag
4. Also check `accounts/<PROFILE>/knowledge/` for account-specific overrides
5. If `accounts/<PROFILE>/memory/index.md` exists, read the top account-specific code and deployment gotchas and apply them silently.
6. Apply knowledge silently — do NOT show raw knowledge to user, just use it to inform analysis

## Artifact Generation Sequence

Follow this order to ensure all dependencies are available:

```
Phase 1: Input Validation (Steps 1-1B)
  |- Check module-inventory.json (prerequisite from /fluent-module-validate)
  |- Discover repo roots (Step 1) — classify each root
  |- Read reference module metadata directly from module.json (no decompile)
  |- Decompile standalone JARs only (Step 1A)
  +- Validate workflow context (Step 1B)

Phase 2: Metadata Extraction (Steps 2-2A)
  |- Build code map (Step 2) — for custom_extension roots only
  |- Extract module identity (Step 2A) — version, symbolicName, Maven coords
  +- Query live installed rules (MCP plugin.list) -> installed-rules.json

Phase 3: Mapping & Analysis (Steps 3-5)
  |- source-map.json        <- Step 2 output, foundation for everything
  |- workflow-rule-map.json  <- Step 3, needs source-map + installed-rules + workflows
  |- constraints.json        <- Step 5 input, needs source-map
  |- behavior-map.md         <- Step 4, needs source-map + workflow-rule-map
  +- extension-plan.md       <- Step 5, needs all above

Phase 4: Caching (Step 6)
  +- fingerprint.json        <- source input hash + artifact timestamps
```

Dependency graph:
```
  module-inventory.json          downloaded workflows
        |                               |
  source-map.json <---- installed-rules.json
       |          \             |
  constraints.json  workflow-rule-map.json
                          |
                    behavior-map.md
                          |
                    extension-plan.md
```

## Execution Context: The Rubix User

Rules execute as a per-retailer **Rubix user** (`<ACCOUNT>-<RETAILER_ID>-rubix`, type `API`). When analyzing `context.api()` calls in custom code, note that these run under the Rubix user's permissions, not the dispatching user's. If a rule queries entities across retailers or needs elevated permissions, the Rubix user's roles must support it.

## Analysis Workflow

### Step 1: Discover repository roots (recursive, no assumptions)

Under `accounts/<PROFILE>/SOURCE/backend/`, recursively detect candidate module/repo roots by scanning for any of:

- `module.json`
- `pom.xml`
- `build.gradle` / `build.gradle.kts`
- `settings.gradle` / `settings.gradle.kts`
- `src/main/java/`
- `*.jar` / `*.zip` module artifacts
- rule markers (`@RuleInfo`, `extends BaseRule`, `ContextWrapper`)

If standard files are missing, continue with marker-based discovery instead of stopping.

**Handle double-nested git clone directories:**
A common pattern is `<repo-name>/<repo-name>/` where git clone creates a wrapper dir. If a directory under SOURCE contains only one subdirectory with a similar name and no direct module markers (no `pom.xml`, `module.json`, or `src/` at the outer level), treat the inner directory as the actual module root. Record `"doubleNested": true` in the source map.

**Classify each discovered root by type:**

| Classification | Indicators | Editable? | Buildable? |
|---------------|-----------|-----------|-----------|
| `custom_extension` | Has `src/main/java/`, `pom.xml`, rules in `module.json` | Yes | Yes (Maven) |
| `configuration_only` | Has `module.json` with empty `rules[]`, no `pom.xml`, no Java source | Yes (JSON) | No |
| `reference_artifact` | Has `module.json` + JAR under `assets/rules/`, no `src/main/java/` | No (read-only) | No |
| `standalone_jar` | Has `.jar`/`.zip` only, no `module.json` alongside | No (read-only) | No |

This classification drives downstream decisions: only `custom_extension` modules get full source analysis; `reference_artifact` modules get metadata directly from `module.json` without decompilation; `configuration_only` modules skip rule analysis entirely.

### Step 1A: Decompile artifacts found under SOURCE

**Pre-check before decompiling:** For each JAR/ZIP artifact, check if a `module.json` exists alongside or inside it (reference module pattern with `assets/rules/` structure). If `module.json` is present, read rule metadata from it directly — decompilation is only needed for understanding rule implementation logic, not for metadata extraction. Mark these as `reference_artifact`.

**Locate JARs comprehensively:** Reference modules place JARs in multiple locations:
- Custom extension dist/: `SOURCE/backend/<repo>/dist/*.jar`
- Reference module assets: `SOURCE/backend/<module-name>/assets/rules/*.jar`
- Build intermediates: `SOURCE/backend/<repo>/plugins/rules/*/target/*.jar`
- Standalone: `SOURCE/backend/<module>.jar`

Use the Glob tool with pattern `accounts/<PROFILE>/SOURCE/backend/**/*.jar` instead of shell `find` for cross-platform compatibility. Exclude `target/dependency/`, `.git/`, and `node_modules/` paths from results.

```bash
find accounts/<PROFILE>/SOURCE/backend/ -name "*.jar" \
  -not -path "*/target/dependency/*" \
  -not -path "*/.git/*" \
  -not -path "*/node_modules/*" 2>/dev/null
```

```powershell
# PowerShell
Get-ChildItem "accounts\<PROFILE>\SOURCE\backend" -Recurse -Filter "*.jar" -ErrorAction SilentlyContinue |
  Where-Object { $_.FullName -notlike "*\target\dependency\*" -and $_.FullName -notlike "*\.git\*" -and $_.FullName -notlike "*\node_modules\*" }
```

If JAR/ZIP artifacts are already present under `accounts/<PROFILE>/SOURCE/backend/`, do not wait for additional inputs:

1. Create a deterministic workspace:
   - `accounts/<PROFILE>/SOURCE/backend/.decompiled/<artifact-name>/`
2. For `.zip` module artifacts, extract first and locate nested `.jar`/`.class` payloads before decompiling.
3. Decompile into that folder (target classes first, then expand only if needed).
4. Write a marker file `DECOMPILED.md` with:
   - source artifact path
   - decompiler used
   - generated timestamp
   - caveats (synthetic names, missing comments/generics)
5. Treat decompiled output as analysis input alongside normal source trees.
6. Mark evidence as `confirmed by decompile` in mappings/notes.
7. Do not edit JAR/ZIP or decompiled output; treat both as read-only evidence.

Example commands:

> **Cross-platform note:** The decompile commands below assume a Unix-like shell (bash, Git Bash, WSL). On Windows without bash, use the PowerShell equivalents shown after each block. Alternatively, use the built-in Glob/Grep/Read tools for file discovery and content inspection.

**Auto-download CFR if not present:**
```bash
# CFR is a single-JAR decompiler (~2MB), no installation needed
CFR_DIR="$HOME/.fluent-ai-skills/tools"
CFR_JAR="$CFR_DIR/cfr.jar"
if [ ! -f "$CFR_JAR" ]; then
  mkdir -p "$CFR_DIR"
  curl -L -o "$CFR_JAR" "https://github.com/leibnitz27/cfr/releases/download/0.152/cfr-0.152.jar"
fi
```

```bash
mkdir -p accounts/<PROFILE>/SOURCE/backend/.decompiled/<artifact-name>/

# Jar class listing (instant, reads ZIP index only — no decompilation)
jar tf accounts/<PROFILE>/SOURCE/backend/<artifact>.jar

# Preferred decompile: CFR with --jarfilter to extract only rule classes
java -jar "$CFR_JAR" "accounts/<PROFILE>/SOURCE/backend/<artifact>.jar" \
  --outputdir "accounts/<PROFILE>/SOURCE/backend/.decompiled/<artifact-name>" \
  --jarfilter "com.fluentcommerce.rule"

# Zip module extraction
unzip -o accounts/<PROFILE>/SOURCE/backend/<artifact>.zip -d accounts/<PROFILE>/SOURCE/backend/.decompiled/<artifact-name>/raw/
```

The `--jarfilter` flag restricts decompilation to classes matching the package prefix, keeping output focused on rule logic only. Omit `--jarfilter` to decompile all classes.

```powershell
# PowerShell — auto-download CFR
$CfrDir = "$env:USERPROFILE\.fluent-ai-skills\tools"
$CfrJar = "$CfrDir\cfr.jar"
if (-not (Test-Path $CfrJar)) {
  New-Item -ItemType Directory -Force -Path $CfrDir
  Invoke-WebRequest -Uri "https://github.com/leibnitz27/cfr/releases/download/0.152/cfr-0.152.jar" -OutFile $CfrJar
}

New-Item -ItemType Directory -Force -Path "accounts\<PROFILE>\SOURCE\backend\.decompiled\<artifact-name>"

# Jar class listing
jar tf "accounts\<PROFILE>\SOURCE\backend\<artifact>.jar"

# Preferred decompile: CFR with --jarfilter
java -jar $CfrJar "accounts\<PROFILE>\SOURCE\backend\<artifact>.jar" --outputdir "accounts\<PROFILE>\SOURCE\backend\.decompiled\<artifact-name>" --jarfilter "com.fluentcommerce.rule"

# Zip module extraction
Expand-Archive -Path "accounts\<PROFILE>\SOURCE\backend\<artifact>.zip" -DestinationPath "accounts\<PROFILE>\SOURCE\backend\.decompiled\<artifact-name>\raw" -Force
```

If no decompiler is available, extract metadata without full decompilation using JDK tools:

```bash
# Extract manifest to discover registered rule names
jar xf <artifact>.jar META-INF/MANIFEST.MF
grep -A5 "Rubix-Rules" META-INF/MANIFEST.MF

# Disassemble specific classes for method signatures and annotations
javap -p "com/fluentcommerce/rule/SomeRule.class"
```

```powershell
# PowerShell
jar xf <artifact>.jar META-INF/MANIFEST.MF
Select-String -Path "META-INF\MANIFEST.MF" -Pattern "Rubix-Rules" -Context 0,5

javap -p "com/fluentcommerce/rule/SomeRule.class"
```

This gives rule names and method signatures without a third-party decompiler. Continue with workflow-only + metadata analysis if even this is not possible (do not block the full run).

### Step 1B: Validate workflow context before mapping

Before mapping workflow rules, validate `accounts/<PROFILE>/workflows/<RETAILER_REF>/workflow-context.json`:

- `profile` must match `<PROFILE>`
- `retailerRef` (or `retailer`) must match `<RETAILER_REF>`
- workflow file count should be non-zero for a full mapping run

If context is missing or mismatched, recreate it and flag confidence as reduced until workflows are refreshed.

### Step 2: Build a Code Map

Create a compact map with:

- Repo/module root
- Build system (Maven/Gradle/unknown)
- Module identity (`symbolicName`, `version`, and extraction source)
- Rule classes discovered
- Shared utils/services
- Suspected entry points and side-effect points

Minimum output table:

| Repo | Root | Classification | Build | Rule markers | Notes |
|---|---|---|---|---|---|

### Step 2A: Module identity extraction (version + symbolic name)

Capture both `symbolicName` and `version` per module using this precedence:

`symbolicName`:
1. `resources/module.json` -> `name` (preferred)
2. JAR `META-INF/MANIFEST.MF` -> `Bundle-SymbolicName` (if present)
3. POM fallback -> `<groupId>/<artifactId>` (or `<artifactId>` if `groupId` missing)

`version`:
1. `resources/module.json` -> `version` (preferred)
2. POM -> `<project><version>` (fallback to `<project><parent><version>` if module version omitted)
3. JAR manifest -> `Implementation-Version` or `Bundle-Version`

Always store where values came from (for traceability), for example `versionSource: "module.json"` or `versionSource: "pom.parent.version"`.

**Maven multi-module version resolution:**
Many custom modules use a parent/child POM pattern:
```
plugins/pom.xml                   (parent, defines version)
  plugins/types/<name>/pom.xml    (child, inherits version)
  plugins/util/<name>/pom.xml     (child, inherits version)
  plugins/rules/<name>/pom.xml    (child, inherits version)
```

Version extraction algorithm:
1. Read `plugins/pom.xml` -> `<project><version>` (parent version, source of truth)
2. If parent has no `<version>`, read `<parent><version>` from parent's parent
3. For any child POM with no `<version>`: read `<parent><version>` reference
4. Record inheritance depth: `"pom.project.version"` vs `"pom.parent.version"`

### Step 3: Map workflow rules to source classes

From workflow JSON(s), collect rule names and map to discovered classes.

For each mapped rule, extract:

- expected props/inputs
- query/mutation/event calls
- status changes
- branch conditions and guards

**Annotation audit (MANDATORY for custom rules):** For each custom rule class, check for annotation mismatches:

- **Flag:** Rule reads `event.getAttributes()` or `event.getAttribute(...)` but has NO `@EventAttribute` annotation. This causes silent null reads at runtime.
- **Flag:** Rule uses `DynamicUtils.getJsonPath(context, "event.X")` to read event data. This is a fragile pattern; should use Event API directly.
- **Verify:** Every `@ParamString` declared on the class has a corresponding `context.getProp()` call (and vice versa).
- **Verify:** Every `@EventAttribute` declared on the class has a corresponding `event.getAttributes().get()` or `event.getAttribute()` call.
- **Cross-check with OOTB categories:** @ParamString should be static config (eventName, entity types, refs, status lists). @EventAttribute should be dynamic data (user input, upstream rule output, coordinates, entity references). If a @ParamString holds user input or a @EventAttribute holds static config, flag as design smell.
- Record annotation findings in `source-map.json` per rule entry (e.g., `"annotationIssues": ["missing @EventAttribute for appeasementInfo"]`).

### Step 4: Explain behavior with confidence labels

For each important behavior, report:

- trigger (event/status/user action)
- processing path (ruleset -> class -> key methods)
- side effects (state/event/attributes/external calls)
- confidence:
  - **confirmed by source**
  - **inferred from workflow metadata**
  - **inferred from runtime evidence**

### Step 5: Extension readiness analysis

Return extension options with risk:

1. Add a new ruleset + existing rule reuse
2. Add a new rule class (recommended for isolated behavior)
3. Modify existing rule (higher regression risk)

For each option include:

- changed files
- test impact
- deployment impact
- rollback path

### Step 6: Write or refresh analysis artifacts

At the end of every run, write or refresh the full artifact bundle (or the incremental subset when freshness rules allow):

- `accounts/<PROFILE>/analysis/code/source-map.json`
- `accounts/<PROFILE>/analysis/code/workflow-rule-map.json`
- `accounts/<PROFILE>/analysis/code/constraints.json`
- `accounts/<PROFILE>/analysis/code/behavior-map.md`
- `accounts/<PROFILE>/analysis/code/extension-plan.md`
- `accounts/<PROFILE>/analysis/code/fingerprint.json`

Also persist installed rule evidence:

- `accounts/<PROFILE>/analysis/installed-rules.json`

If any required file cannot be generated, record the blocker and leave explicit TODO notes in `constraints.json` under `missingSources`/`risks`.

## Handling Non-Standard Structures

Do not require strict Fluent module conventions.

If structure is non-standard:

1. Use semantic markers (annotations, base classes, utility calls) to locate logic.
2. Infer module boundaries from package names and build files.
3. Validate assumptions against workflow bindings and runtime event evidence.
4. Call out unknowns explicitly instead of guessing.

## Escalation Path

If source is incomplete or unavailable:

1. Check for existing JAR/ZIP under `accounts/<PROFILE>/SOURCE/backend/` and decompile first.
2. Ask for missing repos under `accounts/<PROFILE>/SOURCE/backend/` if neither source nor artifacts exist.
3. Request module ZIP/JAR if still missing.
4. Decompile only target classes tied to mapped rules.
5. Re-run mapping and confidence labels.
6. For implementation requests, recommend source-level change options and ask for source repo onboarding when only artifacts are present.

## Reusable Artifact Contract

Always write analysis artifacts to:

`accounts/<PROFILE>/analysis/code/`

Required files:

1. `source-map.json`
   - Repo/module roots discovered
   - Build system
   - Rule classes and marker evidence
2. `workflow-rule-map.json`
   - Workflow ruleset/rule names mapped to source classes
   - Match confidence (`exact`, `probable`, `unresolved`)
3. `constraints.json`
   - Non-standard structure notes
   - Missing sources/artifacts
   - Known extension constraints and risks
4. `behavior-map.md`
   - Human-readable behavior flow with evidence labels
5. `extension-plan.md`
   - Recommended extension option, file-level changes, tests, deploy/rollback notes
6. `fingerprint.json`
   - Input hashes/fingerprints for change detection
   - Freshness decision (`reuse` vs `regenerate`)

Required metadata in each JSON file:

- `profile`
- `generatedAt` (ISO timestamp)
- `objective`
- `sourceInputHash`
- `contentHash`
- `confidenceSummary`
- `inputFingerprint`

For markdown artifacts (`behavior-map.md`, `extension-plan.md`), include a short header block with at least:

- `profile`
- `generatedAt`
- `sourceInputHash`

### Artifact generation gate (must pass before completion)

Do not mark analysis as complete until all checks pass:

1. All six required artifacts exist in `accounts/<PROFILE>/analysis/code/`.
2. `source-map.json`, `workflow-rule-map.json`, `constraints.json`, and `fingerprint.json` include `sourceInputHash` + `contentHash`.
3. `workflow-rule-map.json` mappings use `source` (`custom` | `reference` | `unresolved`) and avoid legacy `unmapped` buckets.
4. If decompiled evidence is used, impacted mappings include confidence text that indicates decompile-derived evidence.
5. `accounts/<PROFILE>/analysis/installed-rules.json` exists (from MCP `plugin.list`, or a documented fallback snapshot if MCP is unavailable).

### freshness model

Use this decision before re-parsing:

1. Build `inputFingerprint` from source/workflow inputs (paths + checksums/hashes or deterministic counts).
2. Compare with `fingerprint.json` from the last run.
3. If unchanged and objective is compatible, reuse existing artifacts.
4. If changed (or objective differs), regenerate all artifacts and overwrite fingerprints.

### incremental refresh (default)

Do not regenerate everything for every change. Refresh only what changed:

| Change detected | Refresh scope |
|---|---|
| One or few `src/main/java` rule files changed | Update affected rule entries in `source-map.json`, then recompute only impacted mappings in `workflow-rule-map.json`, plus related risks in `constraints.json` |
| Only tests changed | Update `hasTest`/test metadata in `source-map.json` and any test-related risk notes in `constraints.json` |
| Only workflow JSON changed | Refresh `workflow-rule-map.json` + `behavior-map.md`; keep source map if source fingerprint unchanged |
| `module.json` changed | Refresh `source-map.json` + `workflow-rule-map.json` + `constraints.json` |
| Folder structure moved/large rename | Full regenerate of all artifacts |

Full regenerate is fallback, not default.

### source-map.json schema

```json
{
  "profile": "<PROFILE>",
  "generatedAt": "2026-02-22T10:00:00Z",
  "objective": "full analysis",
  "sourceInputHash": "f8e7d6c5b4a39281",
  "contentHash": "a1b2c3d4e5f6a7b8",
  "confidenceSummary": {
    "exact": 10,
    "probable": 2,
    "unresolved": 0
  },
  "inputFingerprint": {
    "moduleJsonCount": 2,
    "javaFileCount": 48,
    "workflowJsonCount": 17
  },
  "modules": [
    {
      "name": "fc-module-custom-extension",
      "classification": "custom_extension",
      "isEditable": true,
      "isBuildable": true,
      "doubleNested": false,
      "symbolicName": "fluent-commerce/fc-module-custom-extension",
      "version": "0.0.29",
      "versionSource": "module.json",
      "identitySource": "module.json",
      "osgiSymbolicName": "_.hmextensions",
      "artifactCoordinates": {
        "groupId": "com.fluentcommerce",
        "artifactId": "fc-module-custom-extension",
        "version": "0.0.29"
      },
      "repoPath": "accounts/<PROFILE>/SOURCE/backend/fluentcommerce-fc-module-custom-extension",
      "moduleJsonPath": "resources/module.json",
      "pomPath": "plugins/pom.xml",
      "manifestPath": "META-INF/MANIFEST.MF",
      "buildSystem": "maven",
      "buildRoot": "plugins/",
      "buildCommand": "cd plugins && mvn clean install",
      "packageScript": "scripts/build-module.sh",
      "outputArtifact": "dist/<name>-<version>.zip",
      "dependencies": [
        { "name": "fc-module-core", "type": "fluent", "version": "1.0.0" }
      ],
      "compatibility": {
        "minRubixVersion": "2.0.0",
        "rubixPluginSdk": "2.0.0"
      },
      "patterns": {
        "basePackage": "com.fluentcommerce.rule",
        "subPackages": ["common", "fulfilment", "order", "returnorder", "variantproduct"],
        "baseClass": "BaseRule",
        "contextWrapper": "ContextWrapper",
        "testFramework": "JUnit 5 + Mockito",
        "testMirror": "src/test/java/ mirrors src/main/java/"
      },
      "rules": [
        {
          "name": "SendWebhookWithDynamicAttributes",
          "class": "com.fluentcommerce.rule.common.SendWebhookWithDynamicAttributes",
          "classPath": "plugins/rules/.../SendWebhookWithDynamicAttributes.java",
          "description": "Send webhooks with dynamically built attributes",
          "parameters": [
            { "name": "setting", "type": "String", "description": "Setting ref for webhook config" }
          ],
          "entityTypes": ["ORDER", "FULFILMENT"],
          "graphqlUsage": [],
          "sendsEvents": false,
          "setsState": false,
          "readsSettings": true,
          "hasTest": true,
          "testPath": "plugins/rules/.../SendWebhookWithDynamicAttributesTest.java",
          "testCount": 5,
          "declaredParamStrings": ["setting"],
          "declaredEventAttributes": [],
          "annotationIssues": []
        }
      ],
      "graphqlFiles": [
        { "path": "src/main/resources/graphql/orderQuery.graphql", "type": "query", "usedByRules": ["CreateMissingProductVariant"] }
      ],
      "retailerConfigs": [
        { "file": "module.config.<RETAILER_REF>.fc-module-custom-extension.json", "retailer": "<RETAILER_REF>" }
      ]
    }
  ],
  "summary": {
    "totalModules": 2,
    "codeModules": 1,
    "configOnlyModules": 1,
    "referenceModuleArtifacts": 4,
    "totalRules": 12,
    "rulesWithTests": 8,
    "rulesWithoutTests": 4,
    "testCoveragePercent": 67,
    "settingsFiles": 28,
    "workflowFragments": 4
  }
}
```

### workflow-rule-map.json schema

```json
{
  "profile": "<PROFILE>",
  "generatedAt": "2026-02-22T10:00:00Z",
  "objective": "workflow to rule mapping",
  "sourceInputHash": "f8e7d6c5b4a39281",
  "contentHash": "99aa88bb77cc66dd",
  "confidenceSummary": {
    "exact": 22,
    "probable": 41,
    "unresolved": 3
  },
  "inputFingerprint": {
    "workflowJsonCount": 17,
    "ruleInvocationCount": 66
  },
  "mappings": [
    {
      "workflow": "ORDER::HD",
      "ruleset": "OnOrderComplete",
      "ruleName": "<ACCOUNT>.custom-extension.SendWebhookWithDynamicAttributes",
      "source": "custom",
      "sourceClass": "com.fluentcommerce.rule.common.SendWebhookWithDynamicAttributes",
      "classPath": "plugins/rules/.../SendWebhookWithDynamicAttributes.java",
      "confidence": "exact",
      "props": {
        "setting": "webhook.order.complete"
      }
    },
    {
      "workflow": "ORDER::HD",
      "ruleset": "OnOrderCreated",
      "ruleName": "<ACCOUNT>.core.SetState",
      "source": "reference",
      "referenceModule": "core",
      "confidence": "probable",
      "props": {
        "status": "RECEIVED"
      }
    },
    {
      "workflow": "ORDER::HD",
      "ruleset": "UnexpectedRuleset",
      "ruleName": "<ACCOUNT>.custom.UnknownRule",
      "source": "unresolved",
      "reason": "Not found in custom source, installed-rules snapshot, or reference catalog",
      "confidence": "unresolved"
    }
  ]
}
```

### constraints.json schema

```json
{
  "profile": "<PROFILE>",
  "generatedAt": "2026-02-22T10:00:00Z",
  "objective": "extend webhook logic",
  "sourceInputHash": "f8e7d6c5b4a39281",
  "contentHash": "11223344aabbccdd",
  "confidenceSummary": {
    "exact": 22,
    "probable": 41,
    "unresolved": 3
  },
  "inputFingerprint": {
    "moduleJsonCount": 2,
    "javaFileCount": 48,
    "workflowJsonCount": 17
  },
  "structure": {
    "isStandard": true,
    "deviations": []
  },
  "missingSources": [],
  "missingTests": ["CreateMissingProductVariant"],
  "extensionConstraints": [
    {
      "constraint": "BaseRule required",
      "detail": "All rules must extend BaseRule, not Rule directly",
      "impact": "new rules"
    }
  ],
  "risks": [
    {
      "area": "webhook rule modification",
      "risk": "medium",
      "riskSeverity": "medium",
      "mitigation": "Add integration tests across ORDER and FULFILMENT flows before deploy",
      "reason": "Used in 3 workflows across ORDER and FULFILMENT entities"
    }
  ]
}
```

### fingerprint.json schema

```json
{
  "profile": "<PROFILE>",
  "generatedAt": "2026-02-22T10:00:00Z",
  "objective": "full analysis",
  "freshnessDecision": "regenerate",
  "sourceInputHash": "f8e7d6c5b4a39281",
  "contentHash": "ddee1122ccbb3344",
  "lastRegeneratedAt": "2026-02-22T10:00:00Z",
  "inputFingerprint": {
    "moduleJsonCount": 2,
    "javaFileCount": 48,
    "workflowJsonCount": 17
  },
  "incrementalRefreshMatrix": {
    "javaRuleChange": "refresh source-map + impacted workflow mappings + related constraints",
    "testsOnly": "refresh test metadata and test-risk notes",
    "workflowJsonChange": "refresh workflow-rule-map + behavior-map",
    "moduleJsonChange": "refresh source-map + workflow-rule-map + constraints",
    "structureMove": "full regenerate"
  },
  "incrementalScope": {
    "sourceMapStale": true,
    "workflowRuleMapStale": true,
    "constraintsStale": true,
    "behaviorMapStale": true,
    "extensionPlanStale": true
  }
}
```

### markdown artifact header template

Use this header block at the top of both `behavior-map.md` and `extension-plan.md`:

```markdown
# <Artifact Title>

Generated at: <ISO-8601>
Profile: <PROFILE>
Source input hash: <sourceInputHash>
```

## Reference Module Integration

### Rule naming pattern

Fluent Commerce rules follow the pattern `<ACCOUNT>.<MODULE>.<RuleName>`:
- `FLUENTRETAIL.base.*` — Platform-standard rules (always available)
- `FLUENTRETAIL.globalinventory.*` — Platform inventory rules (always available)
- `<ACCOUNT>.order.*` — Order module rules installed on the account
- `<ACCOUNT>.core.*` — Core module rules installed on the account
- `<ACCOUNT>.custom-extension.*` — Custom module rules

### Pre-fed reference catalog

A static catalog of platform-standard (`FLUENTRETAIL.*`) rules ships at:

```
fluent-ai-skills/content/dev/reference-modules/catalog.json
```

This contains 232 rules with full metadata (description, parameters, entity types, produced events). Use this for offline analysis or when MCP tools are unavailable.

### Live installed rules snapshot

During analysis, query `plugin.list` via MCP to get all rules installed on the account:

```
MCP: plugin.list (no filter)
```

Save the output as:

```
accounts/<PROFILE>/analysis/installed-rules.json
```

This captures both reference AND custom rules specific to the account, with the account prefix.

**Important:** Verify that MCP is connected to the same account as the analysis profile (see Pre-Check). If MCP points at a different account, the `plugin.list` results will have the wrong `<ACCOUNT>` prefix and must NOT be saved for this profile.

If MCP is unavailable or connected to a different account, write a fallback snapshot with `"source": "workflow-observed-fallback"` built from all rule names observed in workflow JSONs, and flag reduced confidence in `constraints.json`.

### Rule resolution order

When resolving a rule name from a workflow:

1. **Custom source** — check `source-map.json` → `modules[].rules[]` (has source code)
2. **Installed rules** — check `installed-rules.json` (has metadata from plugin.list)
3. **Reference catalog** — check `reference-modules/catalog.json` (offline fallback)
4. **Unresolved** — mark as unknown, flag for investigation

### workflow-rule-map.json: source field

Every mapping must include a `source` field:

```json
{
  "ruleName": "<ACCOUNT>.custom-extension.SendWebhookWithDynamicAttributes",
  "source": "custom",
  "sourceClass": "com.fluentcommerce.rule.common.SendWebhookWithDynamicAttributes"
}
```

```json
{
  "ruleName": "<ACCOUNT>.core.SetState",
  "source": "reference",
  "referenceModule": "base",
  "description": "Sets entity status to specified value",
  "parameters": [{ "name": "status", "type": "STRING" }]
}
```

```json
{
  "ruleName": "<ACCOUNT>.custom.UnknownRule",
  "source": "unresolved",
  "reason": "Not found in custom source, installed rules, or reference catalog"
}
```

This eliminates the "unmapped" category — every rule is resolved or explicitly unresolved.

## Content Hash and Change Detection

### Staleness check

Every JSON artifact includes a `contentHash` field — a SHA-256 hash of the source inputs used to generate it:

```json
{
  "profile": "<PROFILE>",
  "generatedAt": "2026-02-22T10:00:00Z",
  "contentHash": "a1b2c3d4e5f6g7h8",
  "sourceInputHash": "f8e7d6c5b4a39281"
}
```

- `contentHash` — hash of the artifact content itself
- `sourceInputHash` — hash of the source files used to generate it

### Computing source input hash

Hash these inputs together (SHA-256, first 16 hex chars):

1. All `module.json` files under `accounts/<PROFILE>/SOURCE/backend/` (content)
2. All `*.java` files under `accounts/<PROFILE>/SOURCE/backend/` (file paths + sizes, NOT full content — for speed)
3. All workflow JSON files under `accounts/<PROFILE>/workflows/<RETAILER_REF>/` (content)

### Skip/refresh logic

```
On invocation:
1. Compute current sourceInputHash from source files
2. Read existing source-map.json (if present)
3. Compare sourceInputHash values
   ├── Same → artifacts are fresh, skip analysis (print "No changes detected")
   └── Different → source has changed, re-analyze
4. --refresh flag → always re-analyze regardless of hash
5. --module <name> → re-analyze only that module
```

### Hash computation pseudo-code

```
inputs = []
for each module.json in accounts/<PROFILE>/SOURCE/backend/**/:
    inputs.append(file_content)
for each *.java in accounts/<PROFILE>/SOURCE/backend/**/:
    inputs.append(file_path + ":" + file_size)
for each *.json in accounts/<PROFILE>/workflows/<RETAILER_REF>/:
    inputs.append(file_content)
sourceInputHash = sha256(sort(inputs).join("\n"))[:16]
```

Using file paths + sizes for Java files (instead of full content) keeps the hash fast for large codebases while still detecting new/deleted/renamed files. Full content hashing of module.json and workflows is fine because those files are small.

## Cross-Skill Reuse Rules

Other skills should consume these artifacts before re-scanning:

- `/fluent-workflow-builder`: reuse `workflow-rule-map.json` + `constraints.json`
- `/fluent-rule-scaffold`: reuse `source-map.json` + `extension-plan.md`
- `/fluent-trace`: reuse `behavior-map.md` + `workflow-rule-map.json`
- `/fluent-e2e-test`: reuse `constraints.json` for scenario boundaries
- `/fluent-workflow-analyzer`: reuse `source-map.json` for rule annotations

Reference module knowledge is also available to all skills:

- `reference-modules/catalog.json` — offline rule lookup (232 FLUENTRETAIL rules)
- `installed-rules.json` — live snapshot of all rules on the account

If artifacts are stale (hash mismatch) or objective changed, regenerate and keep the same file names.

## Learning Capture

This is an analysis skill — follow the **Learning Capture Protocol** (see CLAUDE.md Cross-Skill Conventions). After analysis, when the user confirms or corrects findings about rule behavior, module architecture, or runtime quirks, write the confirmed learning to the `implementation-learnings.md` auto-memory file under the relevant account heading.

## Output Template

Use this structure:

1. **Source map** (repos/modules/rules found)
2. **Installed rules** (from plugin.list, reference + custom)
3. **Behavior map** (what code does, with evidence)
4. **Extension plan** (recommended option + risks)
5. **Open gaps** (missing sources/artifacts or ambiguous logic)

## 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-custom-code"`
   - `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-custom-code.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 -->
