@fluentcommerce/ai-skills 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @fluentcommerce/ai-skills might be problematic. Click here for more details.

Files changed (169) hide show
  1. package/README.md +866 -616
  2. package/bin/cli.mjs +2112 -1973
  3. package/content/cli/agents/fluent-cli/agent.json +149 -149
  4. package/content/cli/agents/fluent-cli.md +132 -132
  5. package/content/cli/skills/fluent-bootstrap/SKILL.md +214 -190
  6. package/content/cli/skills/fluent-cli-index/SKILL.md +1 -1
  7. package/content/cli/skills/fluent-cli-mcp-cicd/SKILL.md +117 -1
  8. package/content/cli/skills/fluent-cli-reference/SKILL.md +1040 -623
  9. package/content/cli/skills/fluent-cli-retailer/SKILL.md +27 -2
  10. package/content/cli/skills/fluent-cli-settings/SKILL.md +21 -1
  11. package/content/cli/skills/fluent-connect/SKILL.md +937 -886
  12. package/content/cli/skills/fluent-module-deploy/SKILL.md +181 -17
  13. package/content/cli/skills/fluent-profile/SKILL.md +73 -0
  14. package/content/cli/skills/fluent-workflow/SKILL.md +360 -310
  15. package/content/dev/agents/fluent-backend-dev/AGENT.md +58 -0
  16. package/content/dev/agents/fluent-backend-dev/agent.json +69 -0
  17. package/content/dev/agents/fluent-backend-dev.md +287 -0
  18. package/content/dev/agents/fluent-dev/AGENT.md +98 -76
  19. package/content/dev/agents/fluent-dev/agent.json +24 -2
  20. package/content/dev/agents/fluent-dev.md +194 -524
  21. package/content/dev/agents/fluent-frontend-dev/AGENT.md +63 -0
  22. package/content/dev/agents/fluent-frontend-dev/agent.json +52 -0
  23. package/content/dev/agents/fluent-frontend-dev.md +323 -0
  24. package/content/dev/skills/fluent-archive/SKILL.md +234 -0
  25. package/content/dev/skills/fluent-build/SKILL.md +312 -170
  26. package/content/dev/skills/fluent-connection-analysis/SKILL.md +422 -386
  27. package/content/dev/skills/fluent-custom-code/SKILL.md +15 -9
  28. package/content/dev/skills/fluent-data-module-scaffold/SKILL.md +731 -0
  29. package/content/dev/skills/fluent-e2e-test/SKILL.md +501 -394
  30. package/content/dev/skills/fluent-event-api/SKILL.md +962 -945
  31. package/content/dev/skills/fluent-feature-explain/SKILL.md +680 -603
  32. package/content/dev/skills/fluent-feature-plan/PLAN_TEMPLATE.md +40 -11
  33. package/content/dev/skills/fluent-feature-plan/SKILL.md +478 -221
  34. package/content/dev/skills/fluent-feature-status/SKILL.md +335 -0
  35. package/content/dev/skills/fluent-feedback/SKILL.md +221 -0
  36. package/content/dev/skills/fluent-implementation-map/SKILL.md +644 -0
  37. package/content/dev/skills/fluent-job-batch/SKILL.md +10 -0
  38. package/content/dev/skills/fluent-module-scaffold/SKILL.md +134 -3
  39. package/content/dev/skills/fluent-module-validate/SKILL.md +778 -775
  40. package/content/dev/skills/fluent-mystique-analyze/SKILL.md +817 -0
  41. package/content/dev/skills/fluent-mystique-builder/COMPONENT_TEMPLATE.md +81 -0
  42. package/content/dev/skills/fluent-mystique-builder/README.md +63 -0
  43. package/content/dev/skills/fluent-mystique-builder/SKILL.md +1294 -0
  44. package/content/dev/skills/fluent-mystique-builder/components/INDEX.md +92 -0
  45. package/content/dev/skills/fluent-mystique-builder/components/fc.accordion.md +48 -0
  46. package/content/dev/skills/fluent-mystique-builder/components/fc.action.field.fulfilmentpack.md +20 -0
  47. package/content/dev/skills/fluent-mystique-builder/components/fc.action.field.multiparcel.md +21 -0
  48. package/content/dev/skills/fluent-mystique-builder/components/fc.action.field.returnitems.md +21 -0
  49. package/content/dev/skills/fluent-mystique-builder/components/fc.action.field.wavepick.md +21 -0
  50. package/content/dev/skills/fluent-mystique-builder/components/fc.action.inline.md +24 -0
  51. package/content/dev/skills/fluent-mystique-builder/components/fc.activity.entity.md +25 -0
  52. package/content/dev/skills/fluent-mystique-builder/components/fc.analytics.viz.md +20 -0
  53. package/content/dev/skills/fluent-mystique-builder/components/fc.attribute.column.md +111 -0
  54. package/content/dev/skills/fluent-mystique-builder/components/fc.attribute.json.md +20 -0
  55. package/content/dev/skills/fluent-mystique-builder/components/fc.attribute.jsoneditor.md +54 -0
  56. package/content/dev/skills/fluent-mystique-builder/components/fc.attribute.locationId.md +51 -0
  57. package/content/dev/skills/fluent-mystique-builder/components/fc.attribute.retailerId.md +52 -0
  58. package/content/dev/skills/fluent-mystique-builder/components/fc.button.bar.md +57 -0
  59. package/content/dev/skills/fluent-mystique-builder/components/fc.button.print.download.md +53 -0
  60. package/content/dev/skills/fluent-mystique-builder/components/fc.button.print.inline.compatibility.md +60 -0
  61. package/content/dev/skills/fluent-mystique-builder/components/fc.button.print.inline.md +53 -0
  62. package/content/dev/skills/fluent-mystique-builder/components/fc.button.print.md +24 -0
  63. package/content/dev/skills/fluent-mystique-builder/components/fc.button.print.pick.md +61 -0
  64. package/content/dev/skills/fluent-mystique-builder/components/fc.buttons.add.reject.md +20 -0
  65. package/content/dev/skills/fluent-mystique-builder/components/fc.card.attribute.md +73 -0
  66. package/content/dev/skills/fluent-mystique-builder/components/fc.card.attributes.grid.md +40 -0
  67. package/content/dev/skills/fluent-mystique-builder/components/fc.card.image.md +37 -0
  68. package/content/dev/skills/fluent-mystique-builder/components/fc.card.map.point.md +24 -0
  69. package/content/dev/skills/fluent-mystique-builder/components/fc.card.multi.md +79 -0
  70. package/content/dev/skills/fluent-mystique-builder/components/fc.card.product.md +27 -0
  71. package/content/dev/skills/fluent-mystique-builder/components/fc.chart.area.md +34 -0
  72. package/content/dev/skills/fluent-mystique-builder/components/fc.chart.area.wrapper.feed.md +98 -0
  73. package/content/dev/skills/fluent-mystique-builder/components/fc.chart.bar.md +52 -0
  74. package/content/dev/skills/fluent-mystique-builder/components/fc.chart.bar.wrapper.source.md +104 -0
  75. package/content/dev/skills/fluent-mystique-builder/components/fc.chart.gauge.md +28 -0
  76. package/content/dev/skills/fluent-mystique-builder/components/fc.chart.gauge.wrapper.threshold.md +118 -0
  77. package/content/dev/skills/fluent-mystique-builder/components/fc.chart.line.md +32 -0
  78. package/content/dev/skills/fluent-mystique-builder/components/fc.conditional.md +62 -0
  79. package/content/dev/skills/fluent-mystique-builder/components/fc.dashboard.threshold.md +65 -0
  80. package/content/dev/skills/fluent-mystique-builder/components/fc.daterange.wrapper.forwarder.md +56 -0
  81. package/content/dev/skills/fluent-mystique-builder/components/fc.drawer.button.md +21 -0
  82. package/content/dev/skills/fluent-mystique-builder/components/fc.event.detail.md +20 -0
  83. package/content/dev/skills/fluent-mystique-builder/components/fc.events.search.md +21 -0
  84. package/content/dev/skills/fluent-mystique-builder/components/fc.field.daterange.md +83 -0
  85. package/content/dev/skills/fluent-mystique-builder/components/fc.field.filterComplex.md +106 -0
  86. package/content/dev/skills/fluent-mystique-builder/components/fc.field.intrange.md +82 -0
  87. package/content/dev/skills/fluent-mystique-builder/components/fc.field.multistring.md +50 -0
  88. package/content/dev/skills/fluent-mystique-builder/components/fc.filterPanel.md +53 -0
  89. package/content/dev/skills/fluent-mystique-builder/components/fc.json.editor.md +22 -0
  90. package/content/dev/skills/fluent-mystique-builder/components/fc.json.viewer.md +21 -0
  91. package/content/dev/skills/fluent-mystique-builder/components/fc.list.customAction.md +79 -0
  92. package/content/dev/skills/fluent-mystique-builder/components/fc.list.md +116 -0
  93. package/content/dev/skills/fluent-mystique-builder/components/fc.list.wrapper.bppmetrics.md +69 -0
  94. package/content/dev/skills/fluent-mystique-builder/components/fc.list.wrapper.feed.md +65 -0
  95. package/content/dev/skills/fluent-mystique-builder/components/fc.list.wrapper.source.md +64 -0
  96. package/content/dev/skills/fluent-mystique-builder/components/fc.modal.button.addItem.md +60 -0
  97. package/content/dev/skills/fluent-mystique-builder/components/fc.modal.button.md +21 -0
  98. package/content/dev/skills/fluent-mystique-builder/components/fc.mutation.inline.md +88 -0
  99. package/content/dev/skills/fluent-mystique-builder/components/fc.mystique.collapsible.attributes.md +83 -0
  100. package/content/dev/skills/fluent-mystique-builder/components/fc.mystique.collapsible.text.md +33 -0
  101. package/content/dev/skills/fluent-mystique-builder/components/fc.mystique.link.md +30 -0
  102. package/content/dev/skills/fluent-mystique-builder/components/fc.order.itemDetails.md +20 -0
  103. package/content/dev/skills/fluent-mystique-builder/components/fc.order.shipmentDetails.md +20 -0
  104. package/content/dev/skills/fluent-mystique-builder/components/fc.page.filter.select.md +87 -0
  105. package/content/dev/skills/fluent-mystique-builder/components/fc.page.md +64 -0
  106. package/content/dev/skills/fluent-mystique-builder/components/fc.page.refresh.md +48 -0
  107. package/content/dev/skills/fluent-mystique-builder/components/fc.page.section.column.md +71 -0
  108. package/content/dev/skills/fluent-mystique-builder/components/fc.page.section.header.md +61 -0
  109. package/content/dev/skills/fluent-mystique-builder/components/fc.page.section.md +59 -0
  110. package/content/dev/skills/fluent-mystique-builder/components/fc.page.wizard.md +45 -0
  111. package/content/dev/skills/fluent-mystique-builder/components/fc.page.wizard.summary.md +56 -0
  112. package/content/dev/skills/fluent-mystique-builder/components/fc.progress.circular.md +20 -0
  113. package/content/dev/skills/fluent-mystique-builder/components/fc.provider.graphql.md +71 -0
  114. package/content/dev/skills/fluent-mystique-builder/components/fc.quantity.list.md +87 -0
  115. package/content/dev/skills/fluent-mystique-builder/components/fc.repeater.md +56 -0
  116. package/content/dev/skills/fluent-mystique-builder/components/fc.reports.ipuipc.md +54 -0
  117. package/content/dev/skills/fluent-mystique-builder/components/fc.return.rowExpansion.md +19 -0
  118. package/content/dev/skills/fluent-mystique-builder/components/fc.scanner.barcode.md +21 -0
  119. package/content/dev/skills/fluent-mystique-builder/components/fc.scanner.barcodeFilter.md +72 -0
  120. package/content/dev/skills/fluent-mystique-builder/components/fc.scanner.camera.md +20 -0
  121. package/content/dev/skills/fluent-mystique-builder/components/fc.settingForm.md +64 -0
  122. package/content/dev/skills/fluent-mystique-builder/components/fc.sourcing.profile.drawer.button.md +19 -0
  123. package/content/dev/skills/fluent-mystique-builder/components/fc.sourcing.profile.modal.button.md +64 -0
  124. package/content/dev/skills/fluent-mystique-builder/components/fc.sourcing.strategy.modal.button.md +20 -0
  125. package/content/dev/skills/fluent-mystique-builder/components/fc.stepper.md +20 -0
  126. package/content/dev/skills/fluent-mystique-builder/components/fc.tab.content.md +56 -0
  127. package/content/dev/skills/fluent-mystique-builder/components/fc.tabs.card.md +64 -0
  128. package/content/dev/skills/fluent-mystique-builder/components/fc.tabs.md +69 -0
  129. package/content/dev/skills/fluent-mystique-builder/components/fc.tile.metric.md +73 -0
  130. package/content/dev/skills/fluent-mystique-builder/components/fc.workflow.provider.md +77 -0
  131. package/content/dev/skills/fluent-mystique-builder/validate-docs.ps1 +260 -0
  132. package/content/dev/skills/fluent-mystique-scaffold/SKILL.md +1830 -0
  133. package/content/dev/skills/fluent-mystique-validate/SKILL.md +646 -0
  134. package/content/dev/skills/fluent-pre-deploy-check/SKILL.md +1144 -1090
  135. package/content/dev/skills/fluent-retailer-config/SKILL.md +1162 -1120
  136. package/content/dev/skills/fluent-rollback/SKILL.md +387 -0
  137. package/content/dev/skills/fluent-rule-scaffold/SKILL.md +515 -394
  138. package/content/dev/skills/fluent-scope-decompose/SKILL.md +1123 -1021
  139. package/content/dev/skills/fluent-session-audit-export/SKILL.md +880 -632
  140. package/content/dev/skills/fluent-session-summary/SKILL.md +320 -195
  141. package/content/dev/skills/fluent-settings/SKILL.md +151 -2
  142. package/content/dev/skills/fluent-source-onboard/SKILL.md +23 -4
  143. package/content/dev/skills/fluent-sourcing/SKILL.md +14 -0
  144. package/content/dev/skills/fluent-system-monitoring/SKILL.md +771 -767
  145. package/content/dev/skills/fluent-test-data/SKILL.md +514 -513
  146. package/content/dev/skills/fluent-trace/SKILL.md +1169 -1143
  147. package/content/dev/skills/fluent-transition-api/SKILL.md +364 -346
  148. package/content/dev/skills/fluent-use-case-discover/SKILL.md +593 -471
  149. package/content/dev/skills/fluent-use-case-discover/SPEC_TEMPLATE.md +22 -1
  150. package/content/dev/skills/fluent-version-manage/SKILL.md +44 -3
  151. package/content/dev/skills/fluent-workflow-analyzer/SKILL.md +995 -959
  152. package/content/dev/skills/fluent-workflow-builder/SKILL.md +668 -326
  153. package/content/dev/skills/fluent-workflow-deploy/SKILL.md +480 -0
  154. package/content/dev/skills/fluent-workspace-tree/SKILL.md +281 -0
  155. package/content/mcp-extn/agents/fluent-mcp.md +133 -132
  156. package/content/mcp-extn/skills/fluent-mcp-tools/SKILL.md +812 -800
  157. package/content/mcp-official/agents/fluent-mcp-core.md +91 -91
  158. package/content/mcp-official/skills/fluent-mcp-core/SKILL.md +94 -94
  159. package/content/rfl/skills/fluent-rfl-assess/SKILL.md +172 -172
  160. package/docs/CAPABILITY_MAP.md +106 -73
  161. package/docs/DEPLOYMENT_PROMOTION_RUNBOOK.md +218 -0
  162. package/docs/DESIGN-implementation-map.md +698 -0
  163. package/docs/DEV_WORKFLOW.md +814 -802
  164. package/docs/FLOW_RUN.md +142 -142
  165. package/docs/GETTING_STARTED.md +427 -0
  166. package/docs/USE_CASES.md +906 -50
  167. package/metadata.json +184 -155
  168. package/package.json +7 -2
  169. package/docs/USE_CASES.pdf +0 -0
@@ -1,394 +1,515 @@
1
- ---
2
- name: fluent-rule-scaffold
3
- description: Scaffold new Fluent Commerce Rubix rules. Generates Java class, test class, and wires into module.json. Triggers on "create rule", "new rule", "scaffold rule", "add rule class".
4
- user-invocable: true
5
- allowed-tools: Bash, Read, Write, Edit, Glob, Grep
6
- argument-hint: <rule-name> [--entity-type ORDER|FULFILMENT|...] [--package common|fulfilment|order|...]
7
- ---
8
-
9
- # Rule Scaffolder
10
-
11
- Generate new Fluent Commerce Rubix rule classes with proper structure, annotations, test skeletons, and module wiring.
12
-
13
- ## Planning Gate
14
-
15
- ### Pre-flight: Plan Verification
16
-
17
- Before proceeding, check for an existing approved plan:
18
-
19
- 1. **Search** `accounts/<PROFILE>/plans/` for a file with `Status: APPROVED` that covers this rule
20
- 2. **If multi-artifact work** (this rule + workflow changes + settings): STOP. Invoke `/fluent-feature-plan` first this skill cannot be used for multi-artifact work without a feature plan
21
- 3. **If approved plan found:** Skip to implementation, referencing the plan's §7 (Rules) and §8 (Detailed Design) sections
22
- 4. **If single-rule-only work with no plan:** Continue to the Planning Gate below to write a plan for this skill
23
-
24
- **Before scaffolding any rule, write a plan using the template from `PLAN_TEMPLATE.md` in the `fluent-feature-plan` skill.** Every table row must carry a Source column (NEW/EXISTING/MODIFIED/REUSED/OOTB). For NEW rules, provide full pseudo logic.
25
-
26
- **If this rule is part of a larger feature** (multiple rules + workflow changes + settings), use `/fluent-feature-plan` first to produce the comprehensive plan. Then reference the approved plan during scaffolding.
27
-
28
- **Rule-scaffold specific emphasis — ensure these are covered:**
29
-
30
- 1. **Business Context (§1)** what business need drives this rule; why custom, not OOTB
31
- 2. **State machine (§3.1)** show the workflow status where this rule fires, with transitions
32
- 3. **Sequence diagram (§3.2)** data flow: READ, VALIDATE, QUERY, BUILD, MUTATE, LOG. Use Mermaid `sequenceDiagram`. Validate syntax per `/fluent-mermaid-validate`
33
- 4. **Rulesets (§6)** — which rulesets use this rule, trigger event, from/to status
34
- 5. **Rules Inventory (§7)** this rule classified as NEW with source label
35
- 6. **Detailed Design (§8)** — parameters table, pseudo logic, GraphQL operations
36
- 7. **Settings (§9)** settings the rule reads, with context, value type, and shape
37
- 8. **GraphQL Operations (§12)** — queries and mutations, marked as NEW or REUSED pattern
38
-
39
- **Write the plan to:** `accounts/<PROFILE>/plans/<YYYY-MM-DD>-rule-scaffold-<slug>.md`. Set `Status: PENDING`.
40
-
41
- Present the full plan content to the user and wait for approval before generating any files. On approval, update the file to `Status: APPROVED`. If the user says "just do it", proceed directly (still write the file for audit trail).
42
-
43
- ## When to Use
44
-
45
- - Creating a new custom Rubix rule
46
- - Adding a rule that follows the project's BaseRule pattern
47
- - Generating boilerplate with proper annotations and logging
48
- - Wiring a new rule into `module.json`
49
-
50
- ## Pre-Check: Use OOTB or Build Custom?
51
-
52
- Before scaffolding, determine if an out-of-the-box (OOTB) rule already covers the requirement.
53
-
54
- ### Decision Tree
55
-
56
- ```
57
- User asks: "I need a rule that does X"
58
-
59
- ├── 1. Check module inventory
60
- │ Read: accounts/<PROFILE>/analysis/module-validate/module-inventory.json
61
- │ If missing run /fluent-module-validate inventory first
62
-
63
- ├── 2. Search registered rules for matching capability
64
- │ Use MCP tool: plugin.list with name filter
65
- │ Look for rules whose description matches the ask
66
- │ Check both FLUENTRETAIL.* (platform) and <ACCOUNT>.* (deployed)
67
-
68
- ├── 3. Evaluate match
69
- │ ├── EXACT MATCH Recommend OOTB rule
70
- │ │ Tell user: "Use <RuleName> with parameters: ..."
71
- │ │ Show accepted entity types and parameters
72
- │ │ No scaffolding needed
73
- │ │
74
- │ ├── PARTIAL MATCH Evaluate gap
75
- │ │ Small gap (missing one parameter) Consider extending OOTB
76
- │ │ Large gap (fundamentally different) Build custom
77
- │ │
78
- │ └── NO MATCH → Build custom rule
79
- │ Proceed with scaffolding below
80
- ```
81
-
82
- ### When OOTB Rules Suffice
83
-
84
- | Need | OOTB Rule | Why not custom? |
85
- |------|-----------|----------------|
86
- | Change entity status | `SendEvent` / `ChangeStateGQL` | Standard lifecycle no custom logic needed |
87
- | Send event to another entity | `SendEventToEntity` | Platform handles entity resolution |
88
- | Forward event conditionally | `ForwardIf*` family (dozens exist) | Many condition checks already built |
89
- | Update attributes from event | `SetAttributes` / `UpsertAttributes` | Standard attribute operations |
90
- | Create a fulfilment | `CreateFulfilmentRule` | Standard fulfilment creation |
91
-
92
- ### When Custom Rules Are Needed
93
-
94
- | Need | Why custom? | Real example |
95
- |------|------------|-------------|
96
- | Setting-driven dynamic behavior | OOTB rules have fixed parameters, can't read settings for runtime config | `SendWebhookWithDynamicAttributes`, `UpdateEntityFromSetting` |
97
- | Complex data transformation | Multi-step JSON path extraction + field mapping | `UpsertAttributeFromPath`, `UpdateFulfilmentItemsFromEvent` |
98
- | Cross-entity composition | Query one entity to create/update another atomically | `CreateFulfilmentFromSourcingLocation` |
99
- | Domain-specific validation | Business logic not covered by generic conditions | `CreateMissingVariantProducts` |
100
-
101
- ### Rule Discovery via MCP
102
-
103
- ```
104
- # Search all registered rules by keyword
105
- plugin.list name="Fulfilment" → all fulfilment-related rules
106
- plugin.list name="SendEvent" → all event-forwarding rules
107
- plugin.list name="Attribute" → all attribute manipulation rules
108
- plugin.list name="Webhook" → webhook-related rules
109
- ```
110
-
111
- Each result includes `description`, `parameters[]`, and `accepts[]` (entity types) enough to determine fit.
112
-
113
- ## Rubix Rule Framework
114
-
115
- All custom rules in this project follow this architecture:
116
-
117
- ```
118
- BaseRule (abstract)
119
- └── extends Rule (Rubix SDK)
120
- └── wraps Context in ContextWrapper
121
- └── provides automatic logging via CommonUtils.generateLog()
122
- └── subclasses implement: run(ContextWrapper context)
123
- ```
124
-
125
- ### Required Annotations
126
-
127
- ```java
128
- @RuleInfo(
129
- name = "MyRuleName",
130
- description = "What this rule does in one sentence"
131
- )
132
- @ParamString(name = "paramName", description = "What this parameter controls")
133
- @Slf4j // Lombok logging
134
- public class MyRuleName extends BaseRule {
135
- @Override
136
- public void run(ContextWrapper context) {
137
- // Rule implementation
138
- }
139
- }
140
- ```
141
-
142
- ### Key APIs
143
-
144
- | API | Usage |
145
- |-----|-------|
146
- | `context.getProp("name")` | Read ruleset prop value |
147
- | `context.getEvent()` | Get the triggering event |
148
- | `context.getEvent().getAccountId()` | Account ID for logging |
149
- | `context.getEvent().getEntityId()` | Entity ID |
150
- | `context.getEvent().getEntityRef()` | Entity reference |
151
- | `context.getEvent().getEntityType()` | Entity type |
152
- | `context.getEvent().getAttributes()` | Event attributes map |
153
- | `context.addLog("message")` | Add to execution log |
154
- | `context.action().mutation()` | Queue a GraphQL mutation (executes AFTER rule completes) |
155
- | `context.action().eventAction()` | Queue an event to fire |
156
- | `DynamicUtils.query(context, paths, queryName, params)` | Execute dynamic GraphQL query |
157
- | `DynamicUtils.mutate(context, values)` | Execute dynamic mutation |
158
- | `DynamicUtils.getJsonPath(context, jsonPath)` | Resolve value from event or entity |
159
- | `SettingUtils.getSettingByRef(context, ref)` | Read a Fluent Setting |
160
-
161
- ## Rule Class Template
162
-
163
- ```java
164
- package com.fluentcommerce.rule.<PACKAGE>;
165
-
166
- import com.fluentcommerce.common.BaseRule;
167
- import com.fluentcommerce.common.ContextWrapper;
168
- import com.fluentretail.rubix.rule.meta.ParamString;
169
- import com.fluentretail.rubix.rule.meta.RuleInfo;
170
- import lombok.extern.slf4j.Slf4j;
171
-
172
- /**
173
- * <DESCRIPTION>
174
- */
175
- @RuleInfo(
176
- name = "<RULE_NAME>",
177
- description = "<DESCRIPTION>"
178
- )
179
- @ParamString(name = "<PARAM1>", description = "<PARAM1_DESC>")
180
- @Slf4j
181
- public class <RULE_NAME> extends BaseRule {
182
-
183
- private static final String CLASS_NAME = <RULE_NAME>.class.getSimpleName();
184
-
185
- @Override
186
- public void run(ContextWrapper context) {
187
- String accountId = context.getEvent().getAccountId();
188
-
189
- log.info("[{}] [{}] - Processing event: {}", accountId, CLASS_NAME,
190
- context.getEvent().getName());
191
- context.addLog("Processing " + CLASS_NAME);
192
-
193
- // Read props
194
- String param1 = context.getProp("<PARAM1>");
195
- if (param1 == null || param1.trim().isEmpty()) {
196
- log.error("[{}] [{}] - Required prop '<PARAM1>' is missing", accountId, CLASS_NAME);
197
- context.addLog("Error: <PARAM1> is required");
198
- return;
199
- }
200
-
201
- try {
202
- // === Rule logic here ===
203
-
204
- context.addLog(CLASS_NAME + " completed successfully");
205
- } catch (Exception e) {
206
- log.error("[{}] [{}] - Error: {}", accountId, CLASS_NAME, e.getMessage(), e);
207
- context.addLog("Error in " + CLASS_NAME + ": " + e.getMessage());
208
- }
209
- }
210
- }
211
- ```
212
-
213
- ## Test Class Template
214
-
215
- ```java
216
- package com.fluentcommerce.rule.<PACKAGE>;
217
-
218
- import com.fluentretail.rubix.event.Event;
219
- import com.fluentretail.rubix.rule.meta.RuleInfo;
220
- import com.fluentretail.rubix.v2.context.Context;
221
- import com.fluentretail.rubix.v2.action.Action;
222
- import com.fluentretail.rubix.v2.action.MutationAction;
223
- import com.fluentretail.rubix.v2.action.EventAction;
224
- import org.junit.jupiter.api.BeforeEach;
225
- import org.junit.jupiter.api.Test;
226
- import org.junit.jupiter.api.DisplayName;
227
- import org.mockito.Mock;
228
- import org.mockito.MockitoAnnotations;
229
-
230
- import java.util.HashMap;
231
- import java.util.Map;
232
-
233
- import static org.junit.jupiter.api.Assertions.*;
234
- import static org.mockito.Mockito.*;
235
-
236
- class <RULE_NAME>Test {
237
-
238
- private <RULE_NAME> rule;
239
-
240
- @Mock private Context context;
241
- @Mock private Event event;
242
- @Mock private Action action;
243
- @Mock private MutationAction mutationAction;
244
- @Mock private EventAction eventAction;
245
-
246
- @BeforeEach
247
- void setUp() {
248
- MockitoAnnotations.openMocks(this);
249
- rule = new <RULE_NAME>();
250
-
251
- when(context.getEvent()).thenReturn(event);
252
- when(context.action()).thenReturn(action);
253
- when(action.mutation()).thenReturn(mutationAction);
254
- when(action.eventAction()).thenReturn(eventAction);
255
- when(event.getAccountId()).thenReturn("TEST_ACCOUNT");
256
- when(event.getName()).thenReturn("TestEvent");
257
- }
258
-
259
- @Test
260
- @DisplayName("Rule has correct @RuleInfo annotation")
261
- void hasRuleInfo() {
262
- RuleInfo info = <RULE_NAME>.class.getAnnotation(RuleInfo.class);
263
- assertNotNull(info);
264
- assertEquals("<RULE_NAME>", info.name());
265
- }
266
-
267
- @Test
268
- @DisplayName("Handles missing required prop gracefully")
269
- void missingRequiredProp() {
270
- Map<String, String> props = new HashMap<>();
271
- when(context.getProp("<PARAM1>")).thenReturn(null);
272
-
273
- // Should not throw — rule logs error and returns
274
- assertDoesNotThrow(() -> rule.run(context));
275
- }
276
-
277
- @Test
278
- @DisplayName("Processes event successfully with valid props")
279
- void processesSuccessfully() {
280
- when(context.getProp("<PARAM1>")).thenReturn("testValue");
281
-
282
- rule.run(context);
283
-
284
- // Verify expected behavior
285
- // verify(mutationAction).xxx(...);
286
- }
287
- }
288
- ```
289
-
290
- ## Pre-Check: Load Source Analysis
291
-
292
- Before scaffolding, check if `/fluent-custom-code` analysis artifacts exist:
293
-
294
- ```
295
- accounts/<PROFILE>/analysis/custom-code/source-map.json
296
- accounts/<PROFILE>/analysis/custom-code/constraints.json
297
- ```
298
-
299
- If found, read them to discover:
300
- - `modules[].patterns.basePackage` correct package prefix
301
- - `modules[].patterns.subPackages` — available sub-packages
302
- - `modules[].patterns.baseClass` base class to extend
303
- - `modules[].buildRoot` — where to run Maven
304
- - `modules[].rules[]` — existing rules (avoid name collisions)
305
- - `constraints[].extensionConstraints` — rules to follow
306
-
307
- If not found, suggest running `/fluent-custom-code <PROFILE>` first, or fall back to the defaults below. Treat artifact-gate failures as "not found" (for example missing required files, missing hashes, or blocking `missingSources` in `constraints.json`).
308
-
309
- ## Scaffolding Steps
310
-
311
- ### 1. Determine rule location
312
-
313
- Rules live under the plugin source tree:
314
-
315
- ```
316
- plugins/rules/<module-alias>/src/main/java/com/fluentcommerce/rule/
317
- ├── common/ # Entity-agnostic rules
318
- ├── fulfilment/ # Fulfilment-specific rules
319
- ├── variantproduct/ # Product-specific rules
320
- ├── order/ # Order-specific rules (create if needed)
321
- └── returnorder/ # Return order rules (create if needed)
322
- ```
323
-
324
- ### 2. Create the rule class
325
-
326
- Write the Java file at the appropriate package path using the template above. Replace all `<PLACEHOLDERS>`.
327
-
328
- ### 3. Create the test class
329
-
330
- Write the test at the mirror path under `src/test/java/`. Test class name = `<RuleName>Test.java`.
331
-
332
- ### 4. Wire into module.json
333
-
334
- Add the rule name to the `rules` array in the module's `resources/module.json`:
335
-
336
- ```json
337
- {
338
- "rules": [
339
- "ExistingRule1",
340
- "ExistingRule2",
341
- "NewRuleName"
342
- ]
343
- }
344
- ```
345
-
346
- ### 5. Verify compilation
347
-
348
- ```bash
349
- cd plugins/ && mvn clean install -pl rules/<module-alias> -am
350
- ```
351
-
352
- If Maven fails, check:
353
- - Node.js version (must be 18 for Apollo codegen): `nvm use 18` on Unix; on Windows with nvm-windows (nvm4w), use the full version number: `nvm use 18.x.x`
354
- - Import paths are correct
355
- - `BaseRule` and `ContextWrapper` are in the `common` package
356
-
357
- ## Rules That Use Dynamic GraphQL
358
-
359
- For rules that need to query or mutate entities dynamically:
360
-
361
- ```java
362
- import com.fluentcommerce.util.dynamic.DynamicUtils;
363
-
364
- // Query entity fields via JSON paths
365
- List<String> paths = Arrays.asList("ref", "status", "fulfilments.ref", "fulfilments.status");
366
- JsonNode result = DynamicUtils.query(context, paths, "orderQuery", null);
367
-
368
- // Mutate entity
369
- Map<String, Object> values = new HashMap<>();
370
- values.put("id", entityId);
371
- values.put("status", "NEW_STATUS");
372
- values.put("attributes", attributeList);
373
- DynamicUtils.mutate(context, values);
374
- ```
375
-
376
- **Path syntax:** Omit `edges` and `node` — DynamicEntityQuery adds them automatically. Use `items.product.name` not `items.edges.node.product.name`.
377
-
378
- ## Rules That Read Settings
379
-
380
- ```java
381
- import com.fluentcommerce.util.SettingUtils;
382
- import com.fluentretail.rubix.foundation.graphql.type.Setting;
383
-
384
- Setting setting = SettingUtils.getSettingByRef(context, "my.setting.ref");
385
- JsonNode config = JsonUtils.stringToPojo(setting.getLobValue(), JsonNode.class);
386
- ```
387
-
388
- ## Gotchas
389
-
390
- - **`@ParamString` names are CASE-SENSITIVE** — `"jsonpath"` and `"jsonPath"` are different props
391
- - **`context.action().mutation()` only QUEUES** — the mutation executes AFTER the rule returns
392
- - **`DynamicMutation.buildMutationDocument` appends `!`** — never include `!` in type names
393
- - **`updateFulfilmentItem` does NOT exist** use `updateFulfilment` with nested `items` array
394
- - **BaseRule wraps Context** — implement `run(ContextWrapper context)`, not `run(Context context)`
1
+ ---
2
+ name: fluent-rule-scaffold
3
+ description: Scaffold new Fluent Commerce Rubix rules. Generates Java class, test class, and wires into module.json. Triggers on "create rule", "new rule", "scaffold rule", "add rule class".
4
+ user-invocable: true
5
+ allowed-tools: Bash, Read, Write, Edit, Glob, Grep
6
+ argument-hint: <rule-name> [--entity-type ORDER|FULFILMENT|...] [--package common|fulfilment|order|...]
7
+ ---
8
+
9
+ # Rule Scaffolder
10
+
11
+ Generate new Fluent Commerce Rubix rule classes with proper structure, annotations, test skeletons, and module wiring.
12
+
13
+ ## Pre-flight: Feedback Check
14
+
15
+ Before starting, check for past learnings from this skill:
16
+
17
+ 1. Check if `accounts/<PROFILE>/feedback/fluent-rule-scaffold.jsonl` exists
18
+ 2. If yes, read last 20 records
19
+ 3. Filter to: same entity type/subtype if known, FAILURE or USER_CORRECTED outcomes, confidence = CONFIRMED or PROMOTED
20
+ 4. Extract top 3 relevant learningsuse as internal "WATCH OUT" guidance during execution
21
+ 5. Do NOT show raw feedback to the user silently adjust approach based on past issues
22
+
23
+ ## Planning Gate
24
+
25
+ ### Pre-flight: Plan Verification
26
+
27
+ Before proceeding, check for an existing approved plan:
28
+
29
+ 1. **Search** `accounts/<PROFILE>/features/*/status.json` for an entry with `plan: "APPROVED"` that covers this rule, OR check `accounts/<PROFILE>/tasks/` for a task plan with `Status: APPROVED`
30
+ 2. **If multi-artifact work** (this rule + workflow changes + settings): STOP. Invoke `/fluent-feature-plan` first this skill cannot be used for multi-artifact work without a feature plan
31
+ 3. **If approved plan found:** Skip to implementation, referencing the plan's §7 (Rules) and §8 (Detailed Design) sections
32
+ 4. **If single-rule-only work with no plan:** Continue to the Planning Gate below to write a plan for this skill
33
+
34
+ **Before scaffolding any rule, write a plan using the template from `PLAN_TEMPLATE.md` in the `fluent-feature-plan` skill.** Every table row must carry a Source column (NEW/EXISTING/MODIFIED/REUSED/OOTB). For NEW rules, provide full pseudo logic.
35
+
36
+ **If this rule is part of a larger feature** (multiple rules + workflow changes + settings), use `/fluent-feature-plan` first to produce the comprehensive plan. Then reference the approved plan during scaffolding.
37
+
38
+ **Rule-scaffold specific emphasis — ensure these are covered:**
39
+
40
+ 1. **Business Context (§1)** — what business need drives this rule; why custom, not OOTB
41
+ 2. **State machine (§3.1)** show the workflow status where this rule fires, with transitions
42
+ 3. **Sequence diagram (§3.2)** — data flow: READ, VALIDATE, QUERY, BUILD, MUTATE, LOG. Use Mermaid `sequenceDiagram`. Validate syntax per `/fluent-mermaid-validate`
43
+ 4. **Rulesets (§6)** — which rulesets use this rule, trigger event, from/to status
44
+ 5. **Rules Inventory (§7)** — this rule classified as NEW with source label
45
+ 6. **Detailed Design (§8)** parameters table, pseudo logic, GraphQL operations
46
+ 7. **Settings (§9)** — settings the rule reads, with context, value type, and shape
47
+ 8. **GraphQL Operations (§12)** queries and mutations, marked as NEW or REUSED pattern
48
+
49
+ **Write the plan to:** `accounts/<PROFILE>/tasks/<YYYY-MM-DD>-rule-scaffold-<slug>.md`. Set `Status: PENDING`.
50
+
51
+ Present the full plan content to the user and wait for approval before generating any files. On approval, update the file to `Status: APPROVED`. If the user says "just do it", proceed directly (still write the file for audit trail).
52
+
53
+ ## Handoff Protocol
54
+
55
+ ### Signals emitted by this skill
56
+
57
+ | Signal | Condition | Example |
58
+ |--------|-----------|---------|
59
+ | `-> READY: <path>` | Rule class + test scaffolded | `-> READY: accounts/HMDEV/SOURCE/.../CancelOrderRule.java` |
60
+ | `-> NEXT: /fluent-<skill>` | Next skill in chain | `-> NEXT: /fluent-build` |
61
+ | `-> BLOCKED: <reason>` | Prerequisite missing | `-> BLOCKED: PREREQ_MISSING — No module in SOURCE/. Run /fluent-module-scaffold` |
62
+ | `-> SKIP: <reason>` | Rule already exists | `-> SKIP: Rule CancelOrderRule already registered in module.json` |
63
+
64
+ ### Error codes
65
+
66
+ | Code | Condition | Recovery |
67
+ |------|-----------|----------|
68
+ | `PLAN_REQUIRED` | Multi-artifact work attempted without approved plan | Run `/fluent-feature-plan` first |
69
+ | `PREREQ_MISSING` | No module found in `accounts/<PROFILE>/SOURCE/` | Run `/fluent-module-scaffold` or clone repo |
70
+ | `VALIDATION_FAILED` | Rule name conflicts or entity type mismatch | Fix input parameters and retry |
71
+
72
+ ## When to Use
73
+
74
+ - Creating a new custom Rubix rule
75
+ - Adding a rule that follows the project's BaseRule pattern
76
+ - Generating boilerplate with proper annotations and logging
77
+ - Wiring a new rule into `module.json`
78
+
79
+ ## Pre-flight: Module Existence
80
+
81
+ Before scaffolding any rule, verify a target module exists:
82
+
83
+ 1. **Search** `accounts/<PROFILE>/SOURCE/` recursively for `module.json` files
84
+ 2. **If module found:** Confirm the module's entity types cover this rule's entity type. Note the module path for downstream scaffolding.
85
+ 3. **If NO module found:** **STOP.** Do not scaffold. Inform the user: *"No module found in `accounts/<PROFILE>/SOURCE/`. Run `/fluent-module-scaffold` first to create one, or clone an existing repo into `SOURCE/`."*
86
+ 4. **If module found but source-map.json is missing or stale:** Run `/fluent-custom-code` to analyze the module before adding new rules. This ensures package conventions, base class patterns, and existing rule names are known.
87
+
88
+ This check prevents scaffolding a rule with nowhere to put it.
89
+
90
+ ## Pre-Check: Use OOTB or Build Custom?
91
+
92
+ Before scaffolding, determine if an out-of-the-box (OOTB) rule already covers the requirement.
93
+
94
+ ### Decision Tree
95
+
96
+ ```
97
+ User asks: "I need a rule that does X"
98
+
99
+ ├── 1. Check module inventory
100
+ │ Read: accounts/<PROFILE>/analysis/modules/module-inventory.json
101
+ │ If missing run /fluent-module-validate inventory first
102
+
103
+ ├── 2. Search registered rules for matching capability
104
+ │ Use MCP tool: plugin.list with name filter
105
+ │ Look for rules whose description matches the ask
106
+ │ Check both FLUENTRETAIL.* (platform) and <ACCOUNT>.* (deployed)
107
+
108
+ ├── 3. Evaluate match
109
+ │ ├── EXACT MATCH → Recommend OOTB rule
110
+ │ │ Tell user: "Use <RuleName> with parameters: ..."
111
+ │ │ Show accepted entity types and parameters
112
+ │ │ No scaffolding needed
113
+ │ │
114
+ │ ├── PARTIAL MATCH → Evaluate gap
115
+ │ │ Small gap (missing one parameter) Consider extending OOTB
116
+ │ │ Large gap (fundamentally different) → Build custom
117
+ │ │
118
+ │ └── NO MATCH → Build custom rule
119
+ │ Proceed with scaffolding below
120
+ ```
121
+
122
+ ### When OOTB Rules Suffice
123
+
124
+ | Need | OOTB Rule | Why not custom? |
125
+ |------|-----------|----------------|
126
+ | Change entity status | `SendEvent` / `ChangeStateGQL` | Standard lifecycle — no custom logic needed |
127
+ | Send event to another entity | `SendEventToEntity` | Platform handles entity resolution |
128
+ | Forward event conditionally | `ForwardIf*` family (dozens exist) | Many condition checks already built |
129
+ | Update attributes from event | `SetAttributes` / `UpsertAttributes` | Standard attribute operations |
130
+ | Create a fulfilment | `CreateFulfilmentRule` | Standard fulfilment creation |
131
+
132
+ ### When Custom Rules Are Needed
133
+
134
+ | Need | Why custom? | Real example |
135
+ |------|------------|-------------|
136
+ | Setting-driven dynamic behavior | OOTB rules have fixed parameters, can't read settings for runtime config | `SendWebhookWithDynamicAttributes`, `UpdateEntityFromSetting` |
137
+ | Complex data transformation | Multi-step JSON path extraction + field mapping | `UpsertAttributeFromPath`, `UpdateFulfilmentItemsFromEvent` |
138
+ | Cross-entity composition | Query one entity to create/update another atomically | `CreateFulfilmentFromSourcingLocation` |
139
+ | Domain-specific validation | Business logic not covered by generic conditions | `CreateMissingVariantProducts` |
140
+
141
+ ### Rule Discovery via MCP
142
+
143
+ ```
144
+ # Search all registered rules by keyword
145
+ plugin.list name="Fulfilment" → all fulfilment-related rules
146
+ plugin.list name="SendEvent" → all event-forwarding rules
147
+ plugin.list name="Attribute" → all attribute manipulation rules
148
+ plugin.list name="Webhook" → webhook-related rules
149
+ ```
150
+
151
+ Each result includes `description`, `parameters[]`, and `accepts[]` (entity types) enough to determine fit.
152
+
153
+ ## Rubix Rule Framework
154
+
155
+ All custom rules in this project follow this architecture:
156
+
157
+ ```
158
+ BaseRule (abstract)
159
+ └── extends Rule (Rubix SDK)
160
+ └── wraps Context in ContextWrapper
161
+ └── provides automatic logging via CommonUtils.generateLog()
162
+ └── subclasses implement: run(ContextWrapper context)
163
+ ```
164
+
165
+ ### Required Annotations
166
+
167
+ ```java
168
+ @RuleInfo(
169
+ name = "MyRuleName",
170
+ description = "What this rule does in one sentence"
171
+ )
172
+ @ParamString(name = "paramName", description = "What this parameter controls")
173
+ @Slf4j // Lombok logging
174
+ public class MyRuleName extends BaseRule {
175
+ @Override
176
+ public void run(ContextWrapper context) {
177
+ // Rule implementation
178
+ }
179
+ }
180
+ ```
181
+
182
+ ### Key APIs
183
+
184
+ | API | Usage |
185
+ |-----|-------|
186
+ | `context.getProp("name")` | Read ruleset prop value |
187
+ | `context.getEvent()` | Get the triggering event |
188
+ | `context.getEvent().getAccountId()` | Account ID for logging |
189
+ | `context.getEvent().getEntityId()` | Entity ID |
190
+ | `context.getEvent().getEntityRef()` | Entity reference |
191
+ | `context.getEvent().getEntityType()` | Entity type |
192
+ | `context.getEvent().getAttributes()` | Event attributes map |
193
+ | `context.addLog("message")` | Add to execution log |
194
+ | `context.action().mutation()` | Queue a GraphQL mutation (executes AFTER rule completes) |
195
+ | `context.action().eventAction()` | Queue an event to fire |
196
+ | `DynamicUtils.query(context, paths, queryName, params)` | Execute dynamic GraphQL query |
197
+ | `DynamicUtils.mutate(context, values)` | Execute dynamic mutation |
198
+ | `DynamicUtils.getJsonPath(context, jsonPath)` | Resolve value from event or entity |
199
+ | `SettingUtils.getSettingByRef(context, ref)` | Read a Fluent Setting |
200
+
201
+ ## Rule Class Template
202
+
203
+ ```java
204
+ package com.fluentcommerce.rule.<PACKAGE>;
205
+
206
+ import com.fluentcommerce.common.BaseRule;
207
+ import com.fluentcommerce.common.ContextWrapper;
208
+ import com.fluentretail.rubix.rule.meta.ParamString;
209
+ import com.fluentretail.rubix.rule.meta.RuleInfo;
210
+ import lombok.extern.slf4j.Slf4j;
211
+
212
+ /**
213
+ * <DESCRIPTION>
214
+ */
215
+ @RuleInfo(
216
+ name = "<RULE_NAME>",
217
+ description = "<DESCRIPTION>"
218
+ )
219
+ @ParamString(name = "<PARAM1>", description = "<PARAM1_DESC>")
220
+ @Slf4j
221
+ public class <RULE_NAME> extends BaseRule {
222
+
223
+ private static final String CLASS_NAME = <RULE_NAME>.class.getSimpleName();
224
+
225
+ @Override
226
+ public void run(ContextWrapper context) {
227
+ String accountId = context.getEvent().getAccountId();
228
+
229
+ log.info("[{}] [{}] - Processing event: {}", accountId, CLASS_NAME,
230
+ context.getEvent().getName());
231
+ context.addLog("Processing " + CLASS_NAME);
232
+
233
+ // Read props
234
+ String param1 = context.getProp("<PARAM1>");
235
+ if (param1 == null || param1.trim().isEmpty()) {
236
+ log.error("[{}] [{}] - Required prop '<PARAM1>' is missing", accountId, CLASS_NAME);
237
+ context.addLog("Error: <PARAM1> is required");
238
+ return;
239
+ }
240
+
241
+ try {
242
+ // === Rule logic here ===
243
+
244
+ context.addLog(CLASS_NAME + " completed successfully");
245
+ } catch (Exception e) {
246
+ log.error("[{}] [{}] - Error: {}", accountId, CLASS_NAME, e.getMessage(), e);
247
+ context.addLog("Error in " + CLASS_NAME + ": " + e.getMessage());
248
+ }
249
+ }
250
+ }
251
+ ```
252
+
253
+ ## Test Class Template
254
+
255
+ ```java
256
+ package com.fluentcommerce.rule.<PACKAGE>;
257
+
258
+ import com.fluentretail.rubix.event.Event;
259
+ import com.fluentretail.rubix.rule.meta.RuleInfo;
260
+ import com.fluentretail.rubix.v2.context.Context;
261
+ import com.fluentretail.rubix.v2.action.Action;
262
+ import com.fluentretail.rubix.v2.action.MutationAction;
263
+ import com.fluentretail.rubix.v2.action.EventAction;
264
+ import org.junit.jupiter.api.BeforeEach;
265
+ import org.junit.jupiter.api.Test;
266
+ import org.junit.jupiter.api.DisplayName;
267
+ import org.mockito.Mock;
268
+ import org.mockito.MockitoAnnotations;
269
+
270
+ import java.util.HashMap;
271
+ import java.util.Map;
272
+
273
+ import static org.junit.jupiter.api.Assertions.*;
274
+ import static org.mockito.Mockito.*;
275
+
276
+ class <RULE_NAME>Test {
277
+
278
+ private <RULE_NAME> rule;
279
+
280
+ @Mock private Context context;
281
+ @Mock private Event event;
282
+ @Mock private Action action;
283
+ @Mock private MutationAction mutationAction;
284
+ @Mock private EventAction eventAction;
285
+
286
+ @BeforeEach
287
+ void setUp() {
288
+ MockitoAnnotations.openMocks(this);
289
+ rule = new <RULE_NAME>();
290
+
291
+ when(context.getEvent()).thenReturn(event);
292
+ when(context.action()).thenReturn(action);
293
+ when(action.mutation()).thenReturn(mutationAction);
294
+ when(action.eventAction()).thenReturn(eventAction);
295
+ when(event.getAccountId()).thenReturn("TEST_ACCOUNT");
296
+ when(event.getName()).thenReturn("TestEvent");
297
+ }
298
+
299
+ @Test
300
+ @DisplayName("Rule has correct @RuleInfo annotation")
301
+ void hasRuleInfo() {
302
+ RuleInfo info = <RULE_NAME>.class.getAnnotation(RuleInfo.class);
303
+ assertNotNull(info);
304
+ assertEquals("<RULE_NAME>", info.name());
305
+ }
306
+
307
+ @Test
308
+ @DisplayName("Handles missing required prop gracefully")
309
+ void missingRequiredProp() {
310
+ Map<String, String> props = new HashMap<>();
311
+ when(context.getProp("<PARAM1>")).thenReturn(null);
312
+
313
+ // Should not throw rule logs error and returns
314
+ assertDoesNotThrow(() -> rule.run(context));
315
+ }
316
+
317
+ @Test
318
+ @DisplayName("Processes event successfully with valid props")
319
+ void processesSuccessfully() {
320
+ when(context.getProp("<PARAM1>")).thenReturn("testValue");
321
+
322
+ rule.run(context);
323
+
324
+ // Verify expected behavior
325
+ // verify(mutationAction).xxx(...);
326
+ }
327
+ }
328
+ ```
329
+
330
+ ## Pre-Check: Load Source Analysis
331
+
332
+ Before scaffolding, check if `/fluent-custom-code` analysis artifacts exist:
333
+
334
+ ```
335
+ accounts/<PROFILE>/analysis/code/source-map.json
336
+ accounts/<PROFILE>/analysis/code/constraints.json
337
+ ```
338
+
339
+ If found, read them to discover:
340
+ - `modules[].patterns.basePackage` — correct package prefix
341
+ - `modules[].patterns.subPackages` — available sub-packages
342
+ - `modules[].patterns.baseClass` — base class to extend
343
+ - `modules[].buildRoot` — where to run Maven
344
+ - `modules[].rules[]` — existing rules (avoid name collisions)
345
+ - `constraints[].extensionConstraints` — rules to follow
346
+
347
+ If not found, suggest running `/fluent-custom-code <PROFILE>` first, or fall back to the defaults below. Treat artifact-gate failures as "not found" (for example missing required files, missing hashes, or blocking `missingSources` in `constraints.json`).
348
+
349
+ ## Scaffolding Steps
350
+
351
+ ### 1. Determine rule location
352
+
353
+ Rules live under the plugin source tree:
354
+
355
+ ```
356
+ plugins/rules/<module-alias>/src/main/java/com/fluentcommerce/rule/
357
+ ├── common/ # Entity-agnostic rules
358
+ ├── fulfilment/ # Fulfilment-specific rules
359
+ ├── variantproduct/ # Product-specific rules
360
+ ├── order/ # Order-specific rules (create if needed)
361
+ └── returnorder/ # Return order rules (create if needed)
362
+ ```
363
+
364
+ ### 2. Create the rule class
365
+
366
+ Write the Java file at the appropriate package path using the template above. Replace all `<PLACEHOLDERS>`.
367
+
368
+ ### 3. Create the test class
369
+
370
+ Write the test at the mirror path under `src/test/java/`. Test class name = `<RuleName>Test.java`.
371
+
372
+ ### 4. Wire into module.json
373
+
374
+ Add the rule name to the `rules` array in the module's `resources/module.json`:
375
+
376
+ ```json
377
+ {
378
+ "rules": [
379
+ "ExistingRule1",
380
+ "ExistingRule2",
381
+ "NewRuleName"
382
+ ]
383
+ }
384
+ ```
385
+
386
+ ### 5. Verify compilation
387
+
388
+ ```bash
389
+ cd plugins/ && mvn clean install -pl rules/<module-alias> -am
390
+ ```
391
+
392
+ If Maven fails, check:
393
+ - Node.js version (must be 18 for Apollo codegen): `nvm use 18` on Unix; on Windows with nvm-windows (nvm4w), use the full version number: `nvm use 18.x.x`
394
+ - Import paths are correct
395
+ - `BaseRule` and `ContextWrapper` are in the `common` package
396
+
397
+ ## Rules That Use Dynamic GraphQL
398
+
399
+ For rules that need to query or mutate entities dynamically:
400
+
401
+ ```java
402
+ import com.fluentcommerce.util.dynamic.DynamicUtils;
403
+
404
+ // Query entity fields via JSON paths
405
+ List<String> paths = Arrays.asList("ref", "status", "fulfilments.ref", "fulfilments.status");
406
+ JsonNode result = DynamicUtils.query(context, paths, "orderQuery", null);
407
+
408
+ // Mutate entity
409
+ Map<String, Object> values = new HashMap<>();
410
+ values.put("id", entityId);
411
+ values.put("status", "NEW_STATUS");
412
+ values.put("attributes", attributeList);
413
+ DynamicUtils.mutate(context, values);
414
+ ```
415
+
416
+ **Path syntax:** Omit `edges` and `node` — DynamicEntityQuery adds them automatically. Use `items.product.name` not `items.edges.node.product.name`.
417
+
418
+ ## Rules That Read Settings
419
+
420
+ ```java
421
+ import com.fluentcommerce.util.SettingUtils;
422
+ import com.fluentretail.rubix.foundation.graphql.type.Setting;
423
+
424
+ Setting setting = SettingUtils.getSettingByRef(context, "my.setting.ref");
425
+ JsonNode config = JsonUtils.stringToPojo(setting.getLobValue(), JsonNode.class);
426
+ ```
427
+
428
+ ## Post-Scaffold Validation
429
+
430
+ After generating rule files, verify the output before handing off to `/fluent-build`:
431
+
432
+ 1. **Java syntax check** — Read the generated `.java` file and confirm it parses (no unclosed braces, missing imports, or placeholder text like `TODO` or `FIXME` in logic paths)
433
+ 2. **Test file exists** — Confirm the test class was generated alongside the rule class
434
+ 3. **module.json wired** — Read `resources/module.json` and verify the new rule name appears in the `rules` array
435
+ 4. **No duplicate registrations** — Confirm the rule name doesn't appear twice in `module.json`
436
+ 5. **Compilation check** — Run `mvn compile -pl plugins -q` (or the module's build command) to verify the generated Java compiles without errors. If Maven is unavailable, recommend `/fluent-build` immediately to catch compilation issues early
437
+
438
+ If any check fails, fix before proceeding. Do not hand off a broken scaffold to `/fluent-build`.
439
+
440
+ ## Session Tracking
441
+
442
+ When invoked, log the following to the session tracking protocol (consumed by `/fluent-session-summary` and `/fluent-session-audit-export`):
443
+
444
+ **On entry:**
445
+ ```json
446
+ { "skill": "<this-skill-name>", "timestamp": "<ISO-8601>", "arguments": { "<key>": "<value>", "...": "..." } }
447
+ ```
448
+
449
+ **On exit:**
450
+ ```json
451
+ { "skill": "<this-skill-name>", "outcome": "<completed|failed|skipped>", "changesProduced": ["<seq-numbers>"], "toolCallsProduced": ["<seq-numbers>"], "nextRecommended": "<from-handoff-section>" }
452
+ ```
453
+
454
+ All MCP tool calls made during execution should include `"skill": "<this-skill-name>"` in their tracking record for attribution. The `arguments` object should capture the key parameters actually passed (profile, retailer, entity type, etc.) — not a fixed schema. The `nextRecommended` value comes from the Handoff section below.
455
+
456
+ ## Handoff
457
+
458
+ | Output Artifact | Consumed By | Path |
459
+ |----------------|-------------|------|
460
+ | Generated Java rule class | `/fluent-build` | `accounts/<PROFILE>/SOURCE/<repo>/plugins/rules/<alias>/src/main/java/.../<RuleName>.java` |
461
+ | Generated test class | `/fluent-build` | `accounts/<PROFILE>/SOURCE/<repo>/plugins/rules/<alias>/src/test/java/.../<RuleName>Test.java` |
462
+ | Updated module.json | `/fluent-module-validate` | `accounts/<PROFILE>/SOURCE/<repo>/resources/module.json` |
463
+
464
+ ## Error Reporting
465
+
466
+ Errors from this skill follow the standard format:
467
+
468
+ | Field | Description |
469
+ |-------|-------------|
470
+ | `phase` | Skill phase where error occurred (e.g., `pre-flight`, `scaffold`, `validation`, `wiring`) |
471
+ | `severity` | `CRITICAL` (blocks downstream), `HIGH` (needs fix), `MEDIUM` (advisory), `LOW` (informational) |
472
+ | `message` | Human-readable error description |
473
+ | `resolution` | Suggested fix or downstream skill to invoke |
474
+
475
+ ## Post-Flight Verification
476
+
477
+ After scaffolding completes, verify the generated code:
478
+
479
+ 1. **Compile check**: Run `mvn compile -pl plugins/<module-name>` to verify generated Java compiles without errors.
480
+ 2. **Test compile check**: Run `mvn test-compile -pl plugins/<module-name>` to verify the test class compiles.
481
+ 3. **Registration check**: Read `module.json` back and confirm the new rule appears in the `rules` array with correct `name`, `class`, and `type`.
482
+ 4. **If compilation fails**: Fix the issue (missing imports, incorrect class name, typo) before declaring the scaffold complete. Do not proceed to next steps with broken code.
483
+
484
+ ## Gotchas
485
+
486
+ - **`@ParamString` names are CASE-SENSITIVE** — `"jsonpath"` and `"jsonPath"` are different props
487
+ - **`context.action().mutation()` only QUEUES** — the mutation executes AFTER the rule returns
488
+ - **`DynamicMutation.buildMutationDocument` appends `!`** — never include `!` in type names
489
+ - **`updateFulfilmentItem` does NOT exist** — use `updateFulfilment` with nested `items` array
490
+ - **BaseRule wraps Context** — implement `run(ContextWrapper context)`, not `run(Context context)`
491
+
492
+ ## Post-Execution: Feedback Capture
493
+
494
+ After completing this skill (whether success or failure), write a feedback record:
495
+
496
+ 1. **Classify outcome:** `SUCCESS` (clean scaffold), `PARTIAL_SUCCESS` (scaffolded after fixing issues), `FAILURE` (could not complete), `BLOCKED` (no module found), `USER_CORRECTED` (user overrode approach)
497
+ 2. **Build record:** Create a `feedback-record-v1` JSON object with:
498
+ - `skill`: `"fluent-rule-scaffold"`
499
+ - `feature`: feature slug if applicable
500
+ - `entityType` / `entitySubtype`: from the rule's target entity
501
+ - `phases`: trace of each phase (pre-flight, plan, generate-class, generate-test, wire-module-json, compile-check) with PASS/FAIL/SKIP
502
+ - `learnings`: any gotchas discovered during execution
503
+ - `userCorrections`: any corrections the user provided
504
+ 3. **Redact secrets.** Keep business identifiers.
505
+ 4. **Append** one JSON line to `accounts/<PROFILE>/feedback/fluent-rule-scaffold.jsonl`
506
+ 5. **Update** `accounts/<PROFILE>/feedback/index.json` counters (create with `mkdir -p` if missing)
507
+
508
+ **Skip capture if:** The execution was trivially simple (e.g., user just asked a question, no actual scaffolding happened).
509
+
510
+ ## Known Pitfalls
511
+ <!-- feedback-promoted: managed section, updated by feedback loop -->
512
+
513
+ _(No promoted learnings yet.)_
514
+
515
+ <!-- end feedback-promoted -->