@wootsup/mcp 0.1.0 → 0.4.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.
Files changed (208) hide show
  1. package/CHANGELOG.md +157 -83
  2. package/README.md +31 -27
  3. package/SECURITY.md +15 -6
  4. package/dist/auth/keychain.d.ts +27 -1
  5. package/dist/auth/keychain.js +48 -2
  6. package/dist/auth/keychain.js.map +1 -1
  7. package/dist/catalog/build-catalog.d.ts +31 -0
  8. package/dist/catalog/build-catalog.js +68 -0
  9. package/dist/catalog/build-catalog.js.map +1 -0
  10. package/dist/cli-hint.d.ts +22 -0
  11. package/dist/cli-hint.js +55 -0
  12. package/dist/cli-hint.js.map +1 -0
  13. package/dist/index.js +129 -22
  14. package/dist/index.js.map +1 -1
  15. package/dist/install-skill.js +1 -1
  16. package/dist/modules/apimapper/auto-layout.d.ts +21 -0
  17. package/dist/modules/apimapper/auto-layout.js +54 -0
  18. package/dist/modules/apimapper/auto-layout.js.map +1 -0
  19. package/dist/modules/apimapper/cache.js +25 -17
  20. package/dist/modules/apimapper/cache.js.map +1 -1
  21. package/dist/modules/apimapper/client.d.ts +115 -4
  22. package/dist/modules/apimapper/client.js +699 -304
  23. package/dist/modules/apimapper/client.js.map +1 -1
  24. package/dist/modules/apimapper/connections-format.d.ts +31 -1
  25. package/dist/modules/apimapper/connections-format.js +97 -5
  26. package/dist/modules/apimapper/connections-format.js.map +1 -1
  27. package/dist/modules/apimapper/connections.d.ts +9 -7
  28. package/dist/modules/apimapper/connections.js +449 -127
  29. package/dist/modules/apimapper/connections.js.map +1 -1
  30. package/dist/modules/apimapper/credential-sanitizer.d.ts +5 -0
  31. package/dist/modules/apimapper/credential-sanitizer.js +60 -1
  32. package/dist/modules/apimapper/credential-sanitizer.js.map +1 -1
  33. package/dist/modules/apimapper/credentials.js +105 -61
  34. package/dist/modules/apimapper/credentials.js.map +1 -1
  35. package/dist/modules/apimapper/diagnose.js +21 -2
  36. package/dist/modules/apimapper/diagnose.js.map +1 -1
  37. package/dist/modules/apimapper/elicitation.d.ts +29 -0
  38. package/dist/modules/apimapper/elicitation.js +62 -0
  39. package/dist/modules/apimapper/elicitation.js.map +1 -1
  40. package/dist/modules/apimapper/example-extract.d.ts +13 -0
  41. package/dist/modules/apimapper/example-extract.js +111 -0
  42. package/dist/modules/apimapper/example-extract.js.map +1 -0
  43. package/dist/modules/apimapper/filter-operators.d.ts +24 -0
  44. package/dist/modules/apimapper/filter-operators.js +103 -0
  45. package/dist/modules/apimapper/filter-operators.js.map +1 -0
  46. package/dist/modules/apimapper/flows-format.js +92 -22
  47. package/dist/modules/apimapper/flows-format.js.map +1 -1
  48. package/dist/modules/apimapper/flows.d.ts +8 -7
  49. package/dist/modules/apimapper/flows.js +275 -120
  50. package/dist/modules/apimapper/flows.js.map +1 -1
  51. package/dist/modules/apimapper/gateway/advanced-read-tool.d.ts +9 -0
  52. package/dist/modules/apimapper/gateway/advanced-read-tool.js +172 -0
  53. package/dist/modules/apimapper/gateway/advanced-read-tool.js.map +1 -0
  54. package/dist/modules/apimapper/gateway/advanced-tool.js +66 -106
  55. package/dist/modules/apimapper/gateway/advanced-tool.js.map +1 -1
  56. package/dist/modules/apimapper/gateway/collect-module-tools.d.ts +17 -0
  57. package/dist/modules/apimapper/gateway/collect-module-tools.js +44 -0
  58. package/dist/modules/apimapper/gateway/collect-module-tools.js.map +1 -0
  59. package/dist/modules/apimapper/gateway/essentials.d.ts +1 -1
  60. package/dist/modules/apimapper/gateway/essentials.js +21 -2
  61. package/dist/modules/apimapper/gateway/essentials.js.map +1 -1
  62. package/dist/modules/apimapper/gateway/gateway-shared.d.ts +21 -0
  63. package/dist/modules/apimapper/gateway/gateway-shared.js +124 -0
  64. package/dist/modules/apimapper/gateway/gateway-shared.js.map +1 -0
  65. package/dist/modules/apimapper/gateway/test-support.d.ts +1 -17
  66. package/dist/modules/apimapper/gateway/test-support.js +4 -33
  67. package/dist/modules/apimapper/gateway/test-support.js.map +1 -1
  68. package/dist/modules/apimapper/get-skill-cores.d.ts +4 -0
  69. package/dist/modules/apimapper/get-skill-cores.js +220 -0
  70. package/dist/modules/apimapper/get-skill-cores.js.map +1 -0
  71. package/dist/modules/apimapper/get-skill.d.ts +1 -1
  72. package/dist/modules/apimapper/get-skill.js +74 -9
  73. package/dist/modules/apimapper/get-skill.js.map +1 -1
  74. package/dist/modules/apimapper/graph-builder.d.ts +85 -2
  75. package/dist/modules/apimapper/graph-builder.js +151 -15
  76. package/dist/modules/apimapper/graph-builder.js.map +1 -1
  77. package/dist/modules/apimapper/graph.js +152 -48
  78. package/dist/modules/apimapper/graph.js.map +1 -1
  79. package/dist/modules/apimapper/index.js +27 -13
  80. package/dist/modules/apimapper/index.js.map +1 -1
  81. package/dist/modules/apimapper/jmespath-test.d.ts +4 -0
  82. package/dist/modules/apimapper/jmespath-test.js +152 -0
  83. package/dist/modules/apimapper/jmespath-test.js.map +1 -0
  84. package/dist/modules/apimapper/library.js +553 -88
  85. package/dist/modules/apimapper/library.js.map +1 -1
  86. package/dist/modules/apimapper/license.js +12 -36
  87. package/dist/modules/apimapper/license.js.map +1 -1
  88. package/dist/modules/apimapper/list-footer.d.ts +27 -0
  89. package/dist/modules/apimapper/list-footer.js +57 -0
  90. package/dist/modules/apimapper/list-footer.js.map +1 -0
  91. package/dist/modules/apimapper/local-sources.js +100 -57
  92. package/dist/modules/apimapper/local-sources.js.map +1 -1
  93. package/dist/modules/apimapper/mcp-client-identity.d.ts +32 -0
  94. package/dist/modules/apimapper/mcp-client-identity.js +70 -0
  95. package/dist/modules/apimapper/mcp-client-identity.js.map +1 -0
  96. package/dist/modules/apimapper/merge-constants.d.ts +6 -0
  97. package/dist/modules/apimapper/merge-constants.js +26 -0
  98. package/dist/modules/apimapper/merge-constants.js.map +1 -0
  99. package/dist/modules/apimapper/misc.js +13 -27
  100. package/dist/modules/apimapper/misc.js.map +1 -1
  101. package/dist/modules/apimapper/node-schema.d.ts +52 -2
  102. package/dist/modules/apimapper/node-schema.js +95 -4
  103. package/dist/modules/apimapper/node-schema.js.map +1 -1
  104. package/dist/modules/apimapper/onboarding.d.ts +59 -1
  105. package/dist/modules/apimapper/onboarding.js +231 -28
  106. package/dist/modules/apimapper/onboarding.js.map +1 -1
  107. package/dist/modules/apimapper/read-cache.d.ts +16 -3
  108. package/dist/modules/apimapper/read-cache.js +59 -4
  109. package/dist/modules/apimapper/read-cache.js.map +1 -1
  110. package/dist/modules/apimapper/render/index.js +26 -5
  111. package/dist/modules/apimapper/render/index.js.map +1 -1
  112. package/dist/modules/apimapper/resource-id.d.ts +13 -0
  113. package/dist/modules/apimapper/resource-id.js +69 -0
  114. package/dist/modules/apimapper/resource-id.js.map +1 -0
  115. package/dist/modules/apimapper/schema.js +9 -18
  116. package/dist/modules/apimapper/schema.js.map +1 -1
  117. package/dist/modules/apimapper/settings.js +49 -52
  118. package/dist/modules/apimapper/settings.js.map +1 -1
  119. package/dist/modules/apimapper/sites-tools.d.ts +29 -0
  120. package/dist/modules/apimapper/sites-tools.js +165 -0
  121. package/dist/modules/apimapper/sites-tools.js.map +1 -0
  122. package/dist/modules/apimapper/tool-result.d.ts +66 -0
  123. package/dist/modules/apimapper/tool-result.js +125 -0
  124. package/dist/modules/apimapper/tool-result.js.map +1 -0
  125. package/dist/modules/apimapper/toolslist-size.d.ts +12 -11
  126. package/dist/modules/apimapper/toolslist-size.js +34 -21
  127. package/dist/modules/apimapper/toolslist-size.js.map +1 -1
  128. package/dist/modules/apimapper/types.d.ts +34 -0
  129. package/dist/modules/apimapper/types.js +1 -1
  130. package/dist/modules/apimapper/types.js.map +1 -1
  131. package/dist/modules/apimapper/whitelist-drift.d.ts +85 -0
  132. package/dist/modules/apimapper/whitelist-drift.js +375 -0
  133. package/dist/modules/apimapper/whitelist-drift.js.map +1 -0
  134. package/dist/modules/apimapper/workflows.js +302 -58
  135. package/dist/modules/apimapper/workflows.js.map +1 -1
  136. package/dist/modules/apimapper/yootheme-binding.d.ts +35 -0
  137. package/dist/modules/apimapper/yootheme-binding.js +267 -0
  138. package/dist/modules/apimapper/yootheme-binding.js.map +1 -0
  139. package/dist/platform/index.d.ts +56 -0
  140. package/dist/platform/index.js +158 -2
  141. package/dist/platform/index.js.map +1 -1
  142. package/dist/proxy/bridge.d.ts +35 -0
  143. package/dist/proxy/bridge.js +129 -0
  144. package/dist/proxy/bridge.js.map +1 -0
  145. package/dist/proxy/mode.d.ts +9 -0
  146. package/dist/proxy/mode.js +20 -0
  147. package/dist/proxy/mode.js.map +1 -0
  148. package/dist/setup/detect-clients.d.ts +40 -1
  149. package/dist/setup/detect-clients.js +148 -1
  150. package/dist/setup/detect-clients.js.map +1 -1
  151. package/dist/setup/probe-auth.d.ts +51 -0
  152. package/dist/setup/probe-auth.js +141 -0
  153. package/dist/setup/probe-auth.js.map +1 -0
  154. package/dist/setup/probe-handshake.js +40 -7
  155. package/dist/setup/probe-handshake.js.map +1 -1
  156. package/dist/setup/remove-config.d.ts +8 -0
  157. package/dist/setup/remove-config.js +145 -0
  158. package/dist/setup/remove-config.js.map +1 -0
  159. package/dist/setup/uninstall.d.ts +34 -0
  160. package/dist/setup/uninstall.js +147 -0
  161. package/dist/setup/uninstall.js.map +1 -0
  162. package/dist/setup-cli.d.ts +16 -0
  163. package/dist/setup-cli.js +63 -1
  164. package/dist/setup-cli.js.map +1 -1
  165. package/dist/sites/loader.d.ts +48 -0
  166. package/dist/sites/loader.js +134 -0
  167. package/dist/sites/loader.js.map +1 -0
  168. package/dist/sites/schema.d.ts +69 -0
  169. package/dist/sites/schema.js +71 -0
  170. package/dist/sites/schema.js.map +1 -0
  171. package/dist/sites/secret-resolver.d.ts +47 -0
  172. package/dist/sites/secret-resolver.js +150 -0
  173. package/dist/sites/secret-resolver.js.map +1 -0
  174. package/dist/skill-instructions.d.ts +14 -1
  175. package/dist/skill-instructions.js +35 -6
  176. package/dist/skill-instructions.js.map +1 -1
  177. package/dist/transports/stdio.js +4 -4
  178. package/dist/transports/stdio.js.map +1 -1
  179. package/dist/uninstall-skill.d.ts +27 -0
  180. package/dist/uninstall-skill.js +89 -0
  181. package/dist/uninstall-skill.js.map +1 -0
  182. package/docs/architecture.md +21 -21
  183. package/docs/customgraph-internal-migration.md +4 -4
  184. package/docs/security.md +2 -21
  185. package/docs/tools.md +40 -12
  186. package/manifest.json +77 -79
  187. package/package.json +69 -65
  188. package/skills/apimapper/SKILL.md +128 -7
  189. package/skills/apimapper/reference/conditional-style-multi-items.md +114 -0
  190. package/skills/apimapper/reference/dynamize-existing-layout.md +158 -0
  191. package/skills/apimapper/reference/jmespath-cookbook.md +241 -0
  192. package/skills/apimapper/reference/jmespath-pitfalls.md +189 -0
  193. package/skills/apimapper/reference/joomla.md +1 -1
  194. package/skills/apimapper/reference/library-template-discovery.md +65 -0
  195. package/skills/apimapper/reference/merge-two-sources-on-key.md +204 -0
  196. package/skills/apimapper/reference/oauth.md +143 -52
  197. package/skills/apimapper/reference/troubleshooting.md +22 -2
  198. package/skills/apimapper/reference/yootheme-source-to-builder-handoff.md +348 -0
  199. package/skills/apimapper/reference/yootheme.md +75 -44
  200. package/dist/auth/oauth-provider.d.ts +0 -68
  201. package/dist/auth/oauth-provider.js +0 -232
  202. package/dist/auth/oauth-provider.js.map +0 -1
  203. package/dist/server-http.d.ts +0 -22
  204. package/dist/server-http.js +0 -159
  205. package/dist/server-http.js.map +0 -1
  206. package/dist/transports/http.d.ts +0 -29
  207. package/dist/transports/http.js +0 -267
  208. package/dist/transports/http.js.map +0 -1
@@ -4,6 +4,8 @@ import { request, hintFor } from "./client.js";
4
4
  import { unwrapEntity } from "./envelope.js";
5
5
  import { buildFlowGraph } from "./graph-builder.js";
6
6
  import { normalizeMergeStrategy } from "./normalizers.js";
7
+ import { MERGE_STRATEGIES_INPUT, MERGE_JOIN_TYPES } from "./merge-constants.js";
8
+ import { FILTER_OPERATORS } from "./filter-operators.js";
7
9
  /**
8
10
  * F-37 (W1.22): matches the default labels emitted by
9
11
  * `apimapper_flow_change_merge_strategy`. User-customised labels (which
@@ -14,8 +16,30 @@ import { normalizeMergeStrategy } from "./normalizers.js";
14
16
  * casings count as user-custom and are preserved as-is.
15
17
  */
16
18
  const DEFAULT_MERGE_LABEL_RE = /^Merge: (Concat|Join)$/;
17
- const SourceSpec = z.object({
18
- connection: z.string().describe("Connection ID. Use apimapper_connection_list to find."),
19
+ // F25 (2026-06-10): a local-source descriptor (CMS-native WordPress posts /
20
+ // Joomla articles) queried straight from the platform DB, no Connection.
21
+ // Mirrors the `local-source` node-schema (`data.contentType`, limit, filters).
22
+ const LocalSourceSpec = z.object({
23
+ content_type: z
24
+ .string()
25
+ .min(1)
26
+ .describe('Fully-qualified content type, e.g. "wordpress/post", "wordpress/page", "joomla/article". ' +
27
+ 'The bare "post" is rejected at save time — always qualify with the platform prefix.'),
28
+ limit: z.number().int().min(1).optional().describe("Max items (clamped to tier max server-side)"),
29
+ filters: z
30
+ .record(z.string(), z.unknown())
31
+ .optional()
32
+ .describe('Server-side filters (e.g. {"category":"news"})'),
33
+ });
34
+ const SourceSpec = z
35
+ .object({
36
+ // F25: `connection` is now OPTIONAL — a source is EITHER a connection
37
+ // source OR a local source. The .superRefine below enforces exactly-one.
38
+ connection: z
39
+ .string()
40
+ .optional()
41
+ .describe("Connection ID (API source). Use apimapper_connection_list to find."),
42
+ local_source: LocalSourceSpec.optional().describe("Local-source descriptor (CMS-native source). Mutually exclusive with `connection`."),
19
43
  endpoint: z.string().optional().describe('Endpoint id (e.g., "Scheduled Events")'),
20
44
  template_fields: z
21
45
  .record(z.string(), z.string())
@@ -23,16 +47,102 @@ const SourceSpec = z.object({
23
47
  .describe('Template field values (e.g., {"user_uri":"...","spreadsheet_id":"..."})'),
24
48
  items_path: z.string().optional().describe("Override items_path"),
25
49
  label: z.string().optional().describe("Display label for the source node"),
50
+ })
51
+ .superRefine((s, ctx) => {
52
+ const hasConnection = typeof s.connection === "string" && s.connection !== "";
53
+ const hasLocal = !!s.local_source;
54
+ if (!hasConnection && !hasLocal) {
55
+ ctx.addIssue({
56
+ code: z.ZodIssueCode.custom,
57
+ message: "each source must set either `connection` (API source) or `local_source` (CMS source)",
58
+ });
59
+ }
60
+ if (hasConnection && hasLocal) {
61
+ ctx.addIssue({
62
+ code: z.ZodIssueCode.custom,
63
+ message: "a source must set `connection` OR `local_source`, not both",
64
+ });
65
+ }
26
66
  });
67
+ // A8/F45 (2026-06-10): the merge join key is the make-or-break input of the
68
+ // two-source task, and a wrong/incomparable key silently yields 0 rows. The
69
+ // describe()s steer the agent to DISCOVER the key by sampling both sources
70
+ // (apimapper_connection_data) before choosing it, and name the
71
+ // connection_data sampling + the merge-two-sources-on-key topic explicitly so
72
+ // the contract is learnable at the chokepoint, not only inside a skill topic.
27
73
  const MergeSpec = z.object({
28
- strategy: z.enum(["join", "concat", "append"]).describe('Merge strategy ("append" alias for "concat")'),
29
- joinKey: z.string().optional().describe("Left-side join key field (required for strategy=join)"),
30
- joinKeyRight: z.string().optional().describe("Right-side join key field (defaults to joinKey)"),
31
- joinType: z.enum(["left", "inner"]).default("left").describe("Join type"),
74
+ // F156 — strategy + joinType enums come from the shared merge-constants so
75
+ // the composite path and the raw node-schema path can never drift. 'append'
76
+ // is the customer-facing alias normalized to 'concat' downstream.
77
+ strategy: z.enum(MERGE_STRATEGIES_INPUT).describe('Merge strategy ("append" alias for "concat")'),
78
+ // A8/F45/F149 — surface the joinKey DISCOVERY hint right at the chokepoint,
79
+ // not only in the merge-two-sources-on-key skill topic. A cold agent has no
80
+ // idea WHICH field is the shared key; point it at connection_data to inspect
81
+ // the rows and call out the same-type requirement.
82
+ joinKey: z
83
+ .string()
84
+ .optional()
85
+ .describe("Left-side join key field (required for strategy=join). This is the field PRESENT IN BOTH " +
86
+ "sources' rows on which they join. DISCOVER it first: call apimapper_connection_data on " +
87
+ "EACH source and find a field whose values overlap and are the SAME type/format on both " +
88
+ "sides (a string \"P-100\" never equals a number 42). If the keys differ in shape, add a " +
89
+ "Transform before the merge to make them comparable (e.g. date-primitive transforms for a " +
90
+ "weekday name ↔ ISO date). See apimapper_get_skill({ topic: 'merge-two-sources-on-key' }) " +
91
+ "→ 'Discovering the join key'."),
92
+ joinKeyRight: z
93
+ .string()
94
+ .optional()
95
+ .describe("Right-side join key field (defaults to joinKey). Set this when the two sources name the " +
96
+ "same value differently (e.g. left 'property_id' vs right 'propertyId'). Sample both with " +
97
+ "apimapper_connection_data to confirm the values actually match."),
98
+ // F156 — full SQL-style join kinds (left/right/inner/outer), matching the
99
+ // raw node-schema mergeNodeSchema. The old composite enum only had
100
+ // left/inner, so an agent that learned joinType:'outer' on the raw graph
101
+ // path got a hard reject here.
102
+ joinType: z.enum(MERGE_JOIN_TYPES).default("left").describe("Join type (left/right/inner/outer)"),
103
+ });
104
+ // F53/F54 (2026-06-10): one-shot filter. `operator` is a CLOSED enum so the
105
+ // schema itself advertises the valid vocabulary — a cold agent reaching for
106
+ // `greater_than` / `>` / `<` is rejected at the zod boundary with the full set
107
+ // named, instead of shipping a flow that silently yields 0 rows at render.
108
+ // Mirrors FilterOperators::ALL (PHP) and FILTER_OPERATOR_VALUES (admin-ui).
109
+ const FILTER_OPERATOR_ENUM = z.enum(FILTER_OPERATORS);
110
+ const FilterConditionSpec = z.object({
111
+ field: z.string().min(1).describe('Field to filter on (e.g., "price", "media_type", "user.role")'),
112
+ operator: FILTER_OPERATOR_ENUM.describe('Comparison operator. Numeric: gt/gte/lt/lte. Text: equals/not_equals/contains/' +
113
+ 'starts_with/ends_with. Set: in/not_in. Presence: exists/not_exists/empty/not_empty. ' +
114
+ 'Symbolic operators (">", "<") are NOT accepted — use "gt"/"lt".'),
115
+ value: z
116
+ .union([z.string(), z.number(), z.boolean(), z.null(), z.array(z.union([z.string(), z.number()]))])
117
+ .optional()
118
+ .describe('Value to compare against. Omit for value-less operators (exists/not_exists/empty/not_empty; ' +
119
+ "for empty/not_empty, 0 and '0' are NOT empty). " +
120
+ 'Numeric operators auto-coerce formatted strings like "$1,250,000".'),
121
+ });
122
+ // Accepts EITHER an inline single condition `{ field, operator, value }` OR a
123
+ // `{ conditions: [...], logic }` list. buildFlowGraph()/normalizeFilterSpec()
124
+ // re-validates and normalises both shapes.
125
+ const FilterSpec = z.object({
126
+ field: z.string().min(1).optional().describe("Inline single-condition field (shorthand)"),
127
+ operator: FILTER_OPERATOR_ENUM.optional().describe("Inline single-condition operator (shorthand)"),
128
+ value: z
129
+ .union([z.string(), z.number(), z.boolean(), z.null(), z.array(z.union([z.string(), z.number()]))])
130
+ .optional()
131
+ .describe("Inline single-condition value (shorthand)"),
132
+ conditions: z.array(FilterConditionSpec).optional().describe("Explicit condition list (for 2+ conditions)"),
133
+ logic: z.enum(["and", "or"]).optional().describe('Combinator across conditions; defaults to "and"'),
32
134
  });
33
135
  const TransformSpec = z.object({
34
136
  expression: z.string().default("[*]").describe('JMESPath expression (e.g., "[*]" or "[*].{title:name,id:id}")'),
35
137
  });
138
+ // F181 (2026-06-12): a transform is EITHER one TransformSpec OR an array of
139
+ // them. The JMESPath depth cap (10 levels of bracket/pipe nesting) forces a
140
+ // deeply-nested reshape to be SPLIT into two (or more) Transform nodes piped
141
+ // together — each node's pipe resets the depth counter. The composite builder
142
+ // chains an array as source(s) → [merge →] [filter →] t1 → t2 → … → output. A
143
+ // single object and a one-element array produce an identical graph; an empty
144
+ // array is treated as no transform at all.
145
+ const TransformChainSpec = z.union([TransformSpec, z.array(TransformSpec)]);
36
146
  const OutputSpec = z.object({
37
147
  type: z.enum(["yootheme", "shortcode"]).default("yootheme").describe("Output type"),
38
148
  name: z.string().min(1).describe("Source name shown in YOOtheme dropdown"),
@@ -42,23 +152,67 @@ export function registerWorkflowTools(server) {
42
152
  // ── apimapper_flow_setup_with_sources ──────────────────────────────
43
153
  server.registerTool("apimapper_flow_setup_with_sources", {
44
154
  title: "Setup Multi-Source Flow (Composite)",
45
- description: "Compose a complete flow from connections in one call. Sequence: " +
155
+ description: "Compose a complete flow from connections AND/OR CMS-native local sources in one call. " +
156
+ "Sequence: " +
46
157
  "(1) build graph via buildFlowGraph() — pure function, deterministic layout. " +
47
158
  "(2) POST /flows (creates row). " +
48
159
  "(3) POST /flows/{id}/compile (publishes to YOOtheme). " +
49
- "Returns flow_id + per-step status. On partial failure, returns cleanup hint." +
50
- "\n\nExample:\n apimapper_flow_setup_with_sources({ name: 'Pexels + IG Gallery', sources: [{ connection: 'con_pexels' }, { connection: 'con_instagram' }], merge: { strategy: 'concat' }, output: { type: 'yootheme', name: 'Gallery' } })",
160
+ "Returns flow_id + per-step status. On partial failure, returns cleanup hint. " +
161
+ "Each source is EITHER an API connection (`connection`) OR a local source " +
162
+ "(`local_source: { content_type, limit?, filters? }`) — never both. Local " +
163
+ "content_type must be fully-qualified (e.g. 'wordpress/post', 'joomla/article'); " +
164
+ "a bare 'post' is rejected at save time." +
165
+ "\n\nConnection-source example:\n apimapper_flow_setup_with_sources({ name: 'Pexels + IG Gallery', sources: [{ connection: 'con_pexels' }, { connection: 'con_instagram' }], merge: { strategy: 'concat' }, output: { type: 'yootheme', name: 'Gallery' } })" +
166
+ "\n\nLocal-source example:\n apimapper_flow_setup_with_sources({ name: 'Latest Posts', sources: [{ local_source: { content_type: 'wordpress/post', limit: 12 } }], output: { type: 'yootheme', name: 'Posts' } })" +
167
+ "\n\nFilter example (one-shot — no raw graph editing needed):\n apimapper_flow_setup_with_sources({ name: 'Warm EU Cities', sources: [{ connection: 'con_weather' }], filter: { field: 'temp', operator: 'gt', value: 10 }, output: { type: 'yootheme', name: 'Warm' } })" +
168
+ "\n\nFilter operators: equals, not_equals, contains, starts_with, ends_with, gt, gte, lt, lte, exists, not_exists, empty, not_empty, in, not_in. " +
169
+ "Use the named operators (gt/lt), NOT symbols (>/<). Numeric operators auto-coerce formatted strings ('$1,250,000'). " +
170
+ "The filter runs AFTER merge and BEFORE transform." +
171
+ "\n\nMulti-stage reshape? Pass `transform` as an ARRAY of stages — each stage gets its own depth budget." +
172
+ "\n\nMulti-transform example (JMESPath depth cap → split into chained transforms):\n apimapper_flow_setup_with_sources({ name: 'Opening Hours', sources: [{ connection: 'con_hours' }], transform: [{ expression: '[1:]' }, { expression: '[*].{day: [0], hours: [1]}' }], output: { type: 'yootheme', name: 'Hours' } })" +
173
+ "\nPass `transform` as an ARRAY to chain N transform nodes (t1 → t2 → …). The pipe between chained nodes resets the JMESPath depth counter, so a reshape that overflows the depth cap (10) in one expression succeeds when split across two. A single `{ expression }` object still works for the one-transform case.",
51
174
  inputSchema: {
52
175
  name: z.string().min(1).describe('Flow name (appears in YOOtheme dropdown)'),
53
176
  description: z.string().optional().describe("Optional description"),
54
- sources: z.array(SourceSpec).min(1).describe("1..N source connections"),
177
+ sources: z
178
+ .array(SourceSpec)
179
+ .min(1)
180
+ .describe("1..N sources — each a connection source OR a local_source (CMS-native)"),
55
181
  merge: MergeSpec.optional().describe("Merge config — required if sources.length >= 2"),
56
- transform: TransformSpec.optional().describe("Optional Transform node"),
182
+ filter: FilterSpec.optional().describe("Optional Filter node (F54). Inline `{field, operator, value}` or `{conditions:[...], logic}`. " +
183
+ "Runs after merge, before transform. Unknown operators are rejected hard."),
184
+ transform: TransformChainSpec.optional().describe("Optional Transform node(s). Multi-stage reshape? Pass `transform` as an ARRAY of stages — " +
185
+ "each stage gets its own depth budget. A single `{ expression }` covers the one-reshape case; " +
186
+ "an ARRAY CHAINs multiple transforms (t1 → t2 → …). Chaining is the remedy for the " +
187
+ "JMESPath depth cap (10): split one over-nested expression into two shallow ones — the " +
188
+ "pipe between chained nodes resets the depth counter. " +
189
+ "See apimapper_get_skill({ topic: 'jmespath-cookbook' }) → 'Two-Transform split'."),
57
190
  output: OutputSpec.describe("Output node config"),
191
+ // Phase 2.7 (change protocol): this composite builds a whole flow, so
192
+ // the action is IMPLICIT (`flow.created`) — only `summary` is needed;
193
+ // the handler defaults the action. Threaded into the create POST body
194
+ // as `change: { summary }` (conditional-spread when present).
195
+ summary: z
196
+ .string()
197
+ .max(280)
198
+ .optional()
199
+ .describe("Optional one-line summary of this flow for the change history " +
200
+ '(e.g., "Set up a merged Pexels + Instagram gallery").'),
58
201
  compile: z.boolean().default(true).describe("Compile after save (= publish to YOOtheme)"),
202
+ autofix: z
203
+ .boolean()
204
+ .default(true)
205
+ .describe("Defaults to true. When compile:true, auto-repair empty-output-schema compile errors " +
206
+ "by calling detect-schema then retrying compile so the new source compiles and " +
207
+ "appears. Pass false to opt out (verbatim old behaviour — no detect-schema retry). " +
208
+ "Mirrors `flow_full_recompile_publish` autofix."),
59
209
  },
60
210
  annotations: creating({ title: "Setup Flow with Sources", openWorld: true }),
61
- }, async ({ name, description, sources, merge, transform, output, compile }, extra) => {
211
+ }, async ({ name, description, sources, merge, filter, transform, output, compile, autofix, summary }, extra) => {
212
+ // C (2026-06-04): autofix defaults to true. Treat `undefined` (omitted
213
+ // arg / unit-test harness that bypasses the schema default) as the
214
+ // on-default; only an explicit `autofix:false` opts out.
215
+ const autofixEnabled = autofix !== false;
62
216
  // W3.5 — progress side-channel. `null` when the caller sent no
63
217
  // progressToken; `progress?.report(...)` then no-ops. Total steps:
64
218
  // build-graph + create + (compile when requested).
@@ -71,6 +225,7 @@ export function registerWorkflowTools(server) {
71
225
  graph = buildFlowGraph({
72
226
  sources,
73
227
  merge: merge,
228
+ filter: filter,
74
229
  transform: transform,
75
230
  output: output,
76
231
  });
@@ -78,8 +233,8 @@ export function registerWorkflowTools(server) {
78
233
  catch (e) {
79
234
  return formatResult({
80
235
  error: e instanceof Error ? e.message : String(e),
81
- context: { source_count: sources.length, has_merge: !!merge },
82
- hint: "Check the validation rules in buildFlowGraph — typically missing merge for multi-source, or missing joinKey for strategy=join.",
236
+ context: { source_count: sources.length, has_merge: !!merge, has_filter: !!filter },
237
+ hint: "Check the validation rules in buildFlowGraph — typically missing merge for multi-source, missing joinKey for strategy=join, or an invalid filter operator (use gt/lt, not >/<).",
83
238
  }, true);
84
239
  }
85
240
  // Step 1: create
@@ -95,6 +250,9 @@ export function registerWorkflowTools(server) {
95
250
  nodes: graph.nodes,
96
251
  edges: graph.edges,
97
252
  viewport: graph.viewport,
253
+ // Phase 2.7 — implicit action (`flow.created`); attach `change:
254
+ // { summary }` only when supplied so the body is unchanged otherwise.
255
+ ...(summary !== undefined ? { change: { summary } } : {}),
98
256
  }),
99
257
  });
100
258
  const createdFlow = unwrapEntity(createR.data, "flow");
@@ -104,7 +262,10 @@ export function registerWorkflowTools(server) {
104
262
  status: createR.status,
105
263
  errorCode: createR.errorCode,
106
264
  context: { name, stage: "create" },
107
- hint: hintFor(createR.errorCode),
265
+ // F83: pass the create error message so a JMESPath depth-limit 422
266
+ // (classifies to "unknown") routes to the two-transform-split
267
+ // remedy + jmespath-cookbook pointer, NOT the generic health hint.
268
+ hint: hintFor(createR.errorCode, createR.error),
108
269
  }, true);
109
270
  }
110
271
  const flowId = createdFlow.id;
@@ -116,9 +277,29 @@ export function registerWorkflowTools(server) {
116
277
  // does not include the full flow object.
117
278
  let compiled = false;
118
279
  let compileError;
280
+ const autofixActions = [];
119
281
  if (compile) {
120
282
  await progress?.report(2, totalSteps, "Compiling and publishing to YOOtheme…");
121
- const compileR = await request(`/flows/${encodeURIComponent(flowId)}/compile`, { method: "POST" });
283
+ let compileR = await request(`/flows/${encodeURIComponent(flowId)}/compile`, { method: "POST" });
284
+ // F5 (W9b, 2026-05-29): autofix mirror of flow_full_recompile_publish.
285
+ // CODE_EMPTY_SCHEMA = 4 (PHP FlowCompilerException constant, frozen).
286
+ // On empty-schema error, call detect-schema then retry compile. This
287
+ // makes the composite tool self-healing on the most common compile
288
+ // failure (newly created flow has no output schema yet).
289
+ const COMPILER_CODE_EMPTY_SCHEMA = 4;
290
+ if (!compileR.success &&
291
+ autofixEnabled &&
292
+ compileR.errorBody?.compiler_error_code === COMPILER_CODE_EMPTY_SCHEMA) {
293
+ autofixActions.push("Detected empty output schema, running detect-schema");
294
+ const detectR = await request(`/flows/${encodeURIComponent(flowId)}/detect-schema`, { method: "POST" });
295
+ if (detectR.success) {
296
+ autofixActions.push("Schema detected, retrying compile");
297
+ compileR = await request(`/flows/${encodeURIComponent(flowId)}/compile`, { method: "POST" });
298
+ }
299
+ else {
300
+ autofixActions.push(`Schema detection failed: ${detectR.error ?? "unknown"}; cannot retry compile`);
301
+ }
302
+ }
122
303
  if (!compileR.success) {
123
304
  compileError = compileR.error;
124
305
  steps.push(`2. Compile FAILED: ${compileR.error}`);
@@ -132,7 +313,8 @@ export function registerWorkflowTools(server) {
132
313
  }
133
314
  else {
134
315
  compiled = true;
135
- steps.push("2. Compiled flow visible in YOOtheme dropdown");
316
+ const autofixSuffix = autofixActions.length > 0 ? " (autofix applied)" : "";
317
+ steps.push(`2. Compiled — flow visible in YOOtheme dropdown${autofixSuffix}`);
136
318
  }
137
319
  }
138
320
  }
@@ -142,10 +324,11 @@ export function registerWorkflowTools(server) {
142
324
  flow_id: flowId,
143
325
  compiled: false,
144
326
  compile_error: compileError,
327
+ autofix_actions: autofixActions.length > 0 ? autofixActions : undefined,
145
328
  steps,
146
329
  next_action: "investigate_compile_error",
147
- cleanup: `Call apimapper_flow_delete with id="${flowId}" + confirm=true to roll back the orphan, OR fix node config and call apimapper_flow_compile again.`,
148
- hint: "Compile errors usually mean missing joinKey, invalid JMESPath, or missing connection. Use apimapper_flow_get to inspect node data.",
330
+ cleanup: `Roll back the orphan via apimapper_advanced({ tool: "apimapper_flow_delete", arguments: { id: "${flowId}", confirm: true } }), OR fix node config and recompile via apimapper_flow_full_recompile_publish.`,
331
+ hint: 'Compile errors usually mean missing joinKey, invalid JMESPath, or missing connection. Inspect node data via apimapper_advanced({ tool: "apimapper_flow_get", arguments: { id } }).',
149
332
  }, true);
150
333
  }
151
334
  await progress?.report(totalSteps, totalSteps, compiled ? "Flow created and published" : "Flow created");
@@ -158,8 +341,8 @@ export function registerWorkflowTools(server) {
158
341
  merge_strategy: merge ? normalizeMergeStrategy(merge.strategy) : null,
159
342
  steps,
160
343
  next: compiled
161
- ? "Use apimapper_graph_preview to test items, or bind in YOOtheme Builder."
162
- : "Call apimapper_flow_compile to publish.",
344
+ ? 'Test items via apimapper_advanced({ tool: "apimapper_graph_preview", arguments: { flow_id } }), or bind in the YOOtheme Builder.'
345
+ : "Publish via apimapper_flow_full_recompile_publish.",
163
346
  }, false, { maxChars: 3000 });
164
347
  });
165
348
  // ── apimapper_flow_change_merge_strategy ───────────────────────────
@@ -171,14 +354,24 @@ export function registerWorkflowTools(server) {
171
354
  "\n\nExample:\n apimapper_flow_change_merge_strategy({ flow_id: 'flow_Z2fLg70M84', strategy: 'join', joinKey: 'id' })",
172
355
  inputSchema: {
173
356
  flow_id: z.string().describe("Flow ID. Use apimapper_flow_list to find."),
174
- strategy: z.enum(["join", "concat", "append"]).describe("New merge strategy"),
357
+ // F156 shared enums (see merge-constants.ts) keep this in lock-step
358
+ // with the raw node-schema + flow_setup_with_sources MergeSpec.
359
+ strategy: z.enum(MERGE_STRATEGIES_INPUT).describe("New merge strategy"),
175
360
  joinKey: z.string().optional().describe("Left-side join key (required for strategy=join)"),
176
361
  joinKeyRight: z.string().optional().describe("Right-side join key (defaults to joinKey)"),
177
- joinType: z.enum(["left", "inner"]).optional().describe("Join type override"),
362
+ joinType: z.enum(MERGE_JOIN_TYPES).optional().describe("Join type override (left/right/inner/outer)"),
363
+ // Phase 2.7 (change protocol): the action is IMPLICIT (`merge.changed`)
364
+ // — only `summary` is needed; the handler defaults the action.
365
+ // Threaded into the PUT body as `change: { summary }`.
366
+ summary: z
367
+ .string()
368
+ .max(280)
369
+ .optional()
370
+ .describe('Optional one-line summary of this merge change (e.g., "Switched merge to join on id").'),
178
371
  compile: z.boolean().default(true).describe("Compile after save"),
179
372
  },
180
373
  annotations: mutating({ title: "Change Flow Merge Strategy", openWorld: true }),
181
- }, async ({ flow_id, strategy, joinKey, joinKeyRight, joinType, compile }) => {
374
+ }, async ({ flow_id, strategy, joinKey, joinKeyRight, joinType, compile, summary }) => {
182
375
  const getR = await request(`/flows/${encodeURIComponent(flow_id)}`);
183
376
  if (!getR.success || !getR.data) {
184
377
  return formatResult({
@@ -196,7 +389,7 @@ export function registerWorkflowTools(server) {
196
389
  return formatResult({
197
390
  error: "flow.nodes is not an array",
198
391
  context: { flow_id, type: typeof flow.nodes },
199
- hint: "Flow data shape unexpected. Use apimapper_flow_get to inspect.",
392
+ hint: 'Flow data shape unexpected. Inspect via apimapper_advanced({ tool: "apimapper_flow_get", arguments: { id: flow_id } }).',
200
393
  }, true);
201
394
  }
202
395
  const mergeNode = flow.nodes.find((n) => n.type === "merge");
@@ -238,7 +431,12 @@ export function registerWorkflowTools(server) {
238
431
  // `(putR.data as any)?.flow` before reading fields.
239
432
  const putR = await request(`/flows/${encodeURIComponent(flow_id)}`, {
240
433
  method: "PUT",
241
- body: JSON.stringify({ nodes: flow.nodes }),
434
+ body: JSON.stringify({
435
+ nodes: flow.nodes,
436
+ // Phase 2.7 — implicit action (`merge.changed`); attach `change:
437
+ // { summary }` only when supplied so the body is unchanged otherwise.
438
+ ...(summary !== undefined ? { change: { summary } } : {}),
439
+ }),
242
440
  });
243
441
  if (!putR.success) {
244
442
  return formatResult({
@@ -246,7 +444,9 @@ export function registerWorkflowTools(server) {
246
444
  status: putR.status,
247
445
  errorCode: putR.errorCode,
248
446
  context: { flow_id, stage: "update" },
249
- hint: hintFor(putR.errorCode),
447
+ // F83: a depth-limit 422 can surface on this write path too — route
448
+ // the message to the two-transform-split remedy.
449
+ hint: hintFor(putR.errorCode, putR.error),
250
450
  }, true);
251
451
  }
252
452
  let compiled = false;
@@ -268,10 +468,10 @@ export function registerWorkflowTools(server) {
268
468
  compiled: false,
269
469
  compile_error: compileError,
270
470
  next_action: "investigate_compile_error_or_revert",
271
- cleanup: "Option A: fix the merge config (e.g., correct joinKey field name) and call apimapper_flow_compile. " +
272
- "Option B: revert via apimapper_flow_change_merge_strategy back to the previous strategy. " +
471
+ cleanup: "Option A: fix the merge config (e.g., correct joinKey field name) and recompile via apimapper_flow_full_recompile_publish. " +
472
+ 'Option B: revert via apimapper_advanced({ tool: "apimapper_flow_change_merge_strategy" }) back to the previous strategy. ' +
273
473
  "The flow PUT succeeded — node data is updated but the flow won't appear in YOOtheme until compile passes.",
274
- hint: "Compile errors usually mean wrong joinKey field name. Use apimapper_flow_get to inspect node data.",
474
+ hint: 'Compile errors usually mean wrong joinKey field name. Inspect node data via apimapper_advanced({ tool: "apimapper_flow_get", arguments: { id: flow_id } }).',
275
475
  }, true);
276
476
  }
277
477
  return formatResult({
@@ -298,20 +498,30 @@ export function registerWorkflowTools(server) {
298
498
  // so the source appears in YOOtheme immediately without a second MCP
299
499
  // package (matches audit recommendation A1 P1).
300
500
  //
301
- // The default (`autofix:false`) preserves the v2.0.7 behaviour verbatim so
302
- // existing tool callers do not see a behaviour change. The PHP layer
303
- // surfaces `compiler_error_code` on compile-error responses (FlowHandler.php)
304
- // so this branch reads a numeric class rather than string-matching messages.
501
+ // C (2026-06-04): the default is now `autofix:true`. Observed live in a
502
+ // Claude Desktop session: when the AI omits `autofix`, the old default
503
+ // (false) meant the YOOtheme schema cache was never flushed, so the source
504
+ // did NOT appear in the YOOtheme builder after publish. Flipping the default
505
+ // makes the autofix path (detect-schema retry + flush-yt) the normal path,
506
+ // so a plain `apimapper_flow_full_recompile_publish({id})` reliably publishes
507
+ // a visible source. `autofix:false` is now an explicit opt-out that restores
508
+ // the verbatim v2.0.7 behaviour (compile + admin-cache only, no detect-schema
509
+ // retry, no YT flush). The handler treats `autofix !== false` as enabled, so
510
+ // an omitted/undefined value behaves identically to the schema default at the
511
+ // real SDK call boundary. The PHP layer surfaces `compiler_error_code` on
512
+ // compile-error responses (FlowHandler.php) so this branch reads a numeric
513
+ // class rather than string-matching messages.
305
514
  server.registerTool("apimapper_flow_full_recompile_publish", {
306
515
  title: "Full Recompile + Cache Clear (Composite)",
307
- description: "Compile a flow + invalidate admin cache in one call. Use after upstream API changes " +
308
- "to ensure the flow + schema are fresh. Each step's success is surfaced separately, " +
309
- "and a cache-clear failure does NOT silently downgrade compile success. " +
310
- "Pass autofix:true to auto-repair common compile errors " +
311
- "(empty output schema → re-detect; then retry compile) and to flush the " +
312
- "YOOtheme schema cache so the source becomes visible in YOOtheme without " +
313
- "a second MCP package." +
314
- "\n\nExample:\n apimapper_flow_full_recompile_publish({ id: 'flow_Z2fLg70M84', autofix: true })",
516
+ description: "Compile a flow + invalidate admin cache + flush the YOOtheme schema cache in one call. " +
517
+ "Use after upstream API changes to ensure the flow + schema are fresh. Each step's success " +
518
+ "is surfaced separately, and a cache-clear failure does NOT silently downgrade compile success. " +
519
+ "autofix defaults to true: it auto-repairs common compile errors " +
520
+ "(empty output schema → re-detect; then retry compile) AND flushes the " +
521
+ "YOOtheme schema cache so the source becomes visible in the YOOtheme builder " +
522
+ "no second MCP package needed. Pass autofix:false to opt out (compile + admin-cache " +
523
+ "only, no detect-schema retry, no YT flush — the verbatim v2.0.7 behaviour)." +
524
+ "\n\nExample:\n apimapper_flow_full_recompile_publish({ id: 'flow_Z2fLg70M84' })",
315
525
  inputSchema: {
316
526
  // L-6 (W3F-5): accept either `id` (canonical, matches every other
317
527
  // flow_* tool) or `flow_id` (legacy alias kept so existing callers
@@ -327,13 +537,24 @@ export function registerWorkflowTools(server) {
327
537
  .describe("Deprecated alias for `id` — accepted for back-compat with pre-W3F-5 callers."),
328
538
  autofix: z
329
539
  .boolean()
330
- .default(false)
331
- .describe("When true: on empty-schema compile error, call detect-schema and retry compile. " +
332
- "On compile success, also flush the YOOtheme schema cache so the source surfaces " +
333
- "in the YOOtheme builder. Default false preserves v2.0.7 behaviour."),
540
+ .default(true)
541
+ .describe("When true (default): on empty-schema compile error, call detect-schema and retry " +
542
+ "compile. On compile success, also flush the YOOtheme schema cache so the source " +
543
+ "surfaces in the YOOtheme builder. Pass false to opt out — compile + admin-cache " +
544
+ "only, no detect-schema retry, no YT flush (the verbatim v2.0.7 behaviour)."),
545
+ // Phase 2.7 (change protocol): recompile = (re)publish, so the action
546
+ // is IMPLICIT (`flow.published`) — only `summary` is needed; the handler
547
+ // defaults the action. Attached to the compile POST body as
548
+ // `change: { summary }` (the compile POST otherwise has no body).
549
+ summary: z
550
+ .string()
551
+ .max(280)
552
+ .optional()
553
+ .describe('Optional one-line summary of this publish for the change history ' +
554
+ '(e.g., "Republished after refreshing the upstream schema").'),
334
555
  },
335
556
  annotations: mutating({ title: "Full Recompile & Publish Flow", openWorld: true }),
336
- }, async ({ id, flow_id: flowIdAlias, autofix }, extra) => {
557
+ }, async ({ id, flow_id: flowIdAlias, autofix, summary }, extra) => {
337
558
  // L-6 (W3F-5): resolve the dual-name input — `id` (canonical) takes
338
559
  // priority over the legacy `flow_id` alias when both are present.
339
560
  const flow_id = id ?? flowIdAlias;
@@ -346,20 +567,36 @@ export function registerWorkflowTools(server) {
346
567
  const steps = [];
347
568
  const errors = [];
348
569
  const autofixActions = [];
349
- // W3.5: progress side-channel. autofix:true adds a third stage
570
+ // C (2026-06-04): autofix defaults to true. Treat `undefined` (caller
571
+ // omitted the arg, and the unit-test harness bypasses the schema default)
572
+ // identically to the on-default, so only an explicit `autofix:false`
573
+ // opts out. This keeps the handler faithful to the documented default
574
+ // whether or not the SDK applied the Zod default before dispatch.
575
+ const autofixEnabled = autofix !== false;
576
+ // W3.5: progress side-channel. autofix adds a third stage
350
577
  // (YOOtheme schema cache flush) so it's a 3-step pipeline.
351
- const totalSteps = autofix ? 3 : 2;
578
+ const totalSteps = autofixEnabled ? 3 : 2;
352
579
  const progress = extra ? createProgressReporter(extra) : null;
353
580
  await progress?.report(0, totalSteps, "Recompiling flow…");
354
581
  // Stage 1: compile (with optional autofix retry)
355
- let compileR = await request(`/flows/${encodeURIComponent(flow_id)}/compile`, { method: "POST" });
582
+ // Phase 2.7 implicit action (`flow.published`). Attach `change:
583
+ // { summary }` only when supplied so the body is unchanged otherwise. The
584
+ // autofix-retry compile below does NOT re-attach it (one publish = one
585
+ // ChangeEvent; the first compile carries the summary).
586
+ let compileR = await request(`/flows/${encodeURIComponent(flow_id)}/compile`, summary !== undefined
587
+ ? {
588
+ method: "POST",
589
+ body: JSON.stringify({ change: { summary } }),
590
+ headers: { "Content-Type": "application/json" },
591
+ }
592
+ : { method: "POST" });
356
593
  // W2-3 autofix: on CODE_EMPTY_SCHEMA (4), call detect-schema then retry.
357
594
  // PHP FlowCompilerException::CODE_EMPTY_SCHEMA = 4 (constant frozen for
358
595
  // wire-stability across plugin versions; same number on WP + Joomla
359
596
  // because both share src/modules/core/src/Flow/FlowCompilerException.php).
360
597
  const COMPILER_CODE_EMPTY_SCHEMA = 4;
361
598
  if (!compileR.success &&
362
- autofix === true &&
599
+ autofixEnabled &&
363
600
  compileR.errorBody?.compiler_error_code === COMPILER_CODE_EMPTY_SCHEMA) {
364
601
  autofixActions.push("Detected empty output schema, running detect-schema");
365
602
  const detectR = await request(`/flows/${encodeURIComponent(flow_id)}/detect-schema`, { method: "POST" });
@@ -377,9 +614,11 @@ export function registerWorkflowTools(server) {
377
614
  status: compileR.status,
378
615
  errorCode: compileR.errorCode,
379
616
  compiler_error_code: compileR.errorBody?.compiler_error_code,
380
- context: { flow_id, stage: "compile", autofix: !!autofix },
617
+ context: { flow_id, stage: "compile", autofix: autofixEnabled },
381
618
  autofix_actions: autofixActions.length > 0 ? autofixActions : undefined,
382
- hint: hintFor(compileR.errorCode),
619
+ // F83: the depth-limit overflow can surface late at compile /
620
+ // detect-schema time too — route its message to the split remedy.
621
+ hint: hintFor(compileR.errorCode, compileR.error),
383
622
  }, true);
384
623
  }
385
624
  steps.push("1. Flow compiled");
@@ -406,7 +645,7 @@ export function registerWorkflowTools(server) {
406
645
  // {flushed: false, error: <msg>} in the body when the buster threw.
407
646
  // We treat the request-level success/failure as authoritative and
408
647
  // record any flushed:false as a non-fatal warning step.
409
- if (autofix === true) {
648
+ if (autofixEnabled) {
410
649
  await progress?.report(2, totalSteps, "Flushing YOOtheme schema cache…");
411
650
  const ytR = await request("/cache/flush-yt", { method: "POST" });
412
651
  if (ytR.success && ytR.data?.flushed !== false) {
@@ -434,11 +673,16 @@ export function registerWorkflowTools(server) {
434
673
  errors: ok ? undefined : errors,
435
674
  autofix_actions: autofixActions.length > 0 ? autofixActions : undefined,
436
675
  next: ok
437
- ? autofix
676
+ ? autofixEnabled
438
677
  ? "Flow recompiled, admin cache invalidated, and YOOtheme schema cache flushed. The source should now be visible in the YOOtheme builder."
439
- : "Flow is fresh in both admin + render layers. YOOtheme schema cache is NOT cleared by this tool; pass autofix:true (or call yootheme_clear_cache via the YOOtheme MCP) if the source does not appear in the builder."
440
- : "Compile succeeded but a cache step had issues. Manually call apimapper_cache_invalidate scope=all if downstream still sees stale data.",
441
- }, ok, { maxChars: 2500 });
678
+ : "Flow is fresh in both admin + render layers. You passed autofix:false, so the YOOtheme schema cache was NOT cleared by this tool; re-run without autofix:false (the default flushes it) or call yootheme_clear_cache via the YOOtheme MCP if the source does not appear in the builder."
679
+ : 'Compile succeeded but a cache step had issues. Manually invalidate via apimapper_advanced({ tool: "apimapper_cache_invalidate", arguments: { scope: "all" } }) if downstream still sees stale data.',
680
+ },
681
+ // Wave-5 F7 fix (2026-05-29): formatResult's 2nd positional is `isError`.
682
+ // Pre-fix we passed `ok` here, which inverted the meaning — ok:true was
683
+ // tagged as `<error>` envelope. Pass `!ok` so success-with-no-errors
684
+ // renders as success and partial (errors.length > 0) renders as error.
685
+ !ok, { maxChars: 2500 });
442
686
  });
443
687
  }
444
688
  //# sourceMappingURL=workflows.js.map