@jahia/agentic 0.2.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 (238) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +28 -0
  3. package/dist/claude/.claude/agents/cnd-child-nodes.md +74 -0
  4. package/dist/claude/.claude/agents/cnd-jahia-mixins.md +113 -0
  5. package/dist/claude/.claude/agents/cnd-numbers-dates.md +61 -0
  6. package/dist/claude/.claude/agents/cnd-string-selectors.md +94 -0
  7. package/dist/claude/.claude/agents/jahia-cnd-author.md +130 -0
  8. package/dist/claude/.claude/agents/jahia-dev-worker.md +264 -0
  9. package/dist/claude/.claude/agents/jahia-reviewer.md +105 -0
  10. package/dist/claude/.claude/rules/jahia.md +15 -6
  11. package/dist/claude/.claude/skills/jahia/SKILL.md +23 -11
  12. package/dist/claude/.claude/skills/jahia-content/SKILL.md +102 -84
  13. package/dist/claude/.claude/skills/jahia-content-create-content/SKILL.md +255 -280
  14. package/dist/claude/.claude/skills/jahia-content-explore-structure/SKILL.md +187 -96
  15. package/dist/claude/.claude/skills/jahia-content-media-upload/SKILL.md +197 -0
  16. package/dist/claude/.claude/skills/jahia-content-move-content/SKILL.md +160 -165
  17. package/dist/claude/.claude/skills/jahia-content-organize/SKILL.md +209 -0
  18. package/dist/claude/.claude/skills/jahia-content-publish/SKILL.md +181 -0
  19. package/dist/claude/.claude/skills/jahia-content-query-content/SKILL.md +122 -92
  20. package/dist/claude/.claude/skills/jahia-content-translate-content/SKILL.md +154 -225
  21. package/dist/claude/.claude/skills/jahia-dev-accessibility/SKILL.md +3 -3
  22. package/dist/claude/.claude/skills/jahia-dev-build-component/SKILL.md +10 -7
  23. package/dist/claude/.claude/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  24. package/dist/claude/.claude/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  25. package/dist/claude/.claude/skills/jahia-dev-create-view/SKILL.md +3 -3
  26. package/dist/claude/.claude/skills/jahia-dev-cypress/SKILL.md +150 -330
  27. package/dist/claude/.claude/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  28. package/dist/claude/.claude/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  29. package/dist/claude/.claude/skills/jahia-dev-query-content/SKILL.md +93 -296
  30. package/dist/claude/.claude/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  31. package/dist/claude/.claude/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  32. package/dist/claude/.claude/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  33. package/dist/claude/.claude/skills/jahia-dev-site-review/SKILL.md +70 -0
  34. package/dist/claude/.claude/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  35. package/dist/claude/.claude/skills/jahia-dev-start-local/SKILL.md +18 -26
  36. package/dist/claude/.claude/skills/jahia-jcr-sql2/SKILL.md +258 -0
  37. package/dist/claude/.claude/skills/jahia-orchestrate/SKILL.md +148 -0
  38. package/dist/claude/.claude/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  39. package/dist/claude/CLAUDE.md +16 -13
  40. package/dist/codex/.agents/skills/jahia/SKILL.md +23 -11
  41. package/dist/codex/.agents/skills/jahia-content/SKILL.md +102 -84
  42. package/dist/codex/.agents/skills/jahia-content-create-content/SKILL.md +255 -280
  43. package/dist/codex/.agents/skills/jahia-content-explore-structure/SKILL.md +187 -96
  44. package/dist/codex/.agents/skills/jahia-content-media-upload/SKILL.md +197 -0
  45. package/dist/codex/.agents/skills/jahia-content-move-content/SKILL.md +160 -165
  46. package/dist/codex/.agents/skills/jahia-content-organize/SKILL.md +209 -0
  47. package/dist/codex/.agents/skills/jahia-content-publish/SKILL.md +181 -0
  48. package/dist/codex/.agents/skills/jahia-content-query-content/SKILL.md +122 -92
  49. package/dist/codex/.agents/skills/jahia-content-translate-content/SKILL.md +154 -225
  50. package/dist/codex/.agents/skills/jahia-dev-accessibility/SKILL.md +3 -3
  51. package/dist/codex/.agents/skills/jahia-dev-build-component/SKILL.md +10 -7
  52. package/dist/codex/.agents/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  53. package/dist/codex/.agents/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  54. package/dist/codex/.agents/skills/jahia-dev-create-view/SKILL.md +3 -3
  55. package/dist/codex/.agents/skills/jahia-dev-cypress/SKILL.md +150 -330
  56. package/dist/codex/.agents/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  57. package/dist/codex/.agents/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  58. package/dist/codex/.agents/skills/jahia-dev-query-content/SKILL.md +93 -296
  59. package/dist/codex/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  60. package/dist/codex/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  61. package/dist/codex/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  62. package/dist/codex/.agents/skills/jahia-dev-site-review/SKILL.md +70 -0
  63. package/dist/codex/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  64. package/dist/codex/.agents/skills/jahia-dev-start-local/SKILL.md +18 -26
  65. package/dist/codex/.agents/skills/jahia-jcr-sql2/SKILL.md +258 -0
  66. package/dist/codex/.agents/skills/jahia-orchestrate/SKILL.md +148 -0
  67. package/dist/codex/.agents/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  68. package/dist/codex/.codex/agents/cnd-child-nodes.toml +3 -0
  69. package/dist/codex/.codex/agents/cnd-jahia-mixins.toml +3 -0
  70. package/dist/codex/.codex/agents/cnd-numbers-dates.toml +3 -0
  71. package/dist/codex/.codex/agents/cnd-string-selectors.toml +3 -0
  72. package/dist/codex/.codex/agents/jahia-cnd-author.toml +3 -0
  73. package/dist/codex/.codex/agents/jahia-dev-worker.toml +3 -0
  74. package/dist/codex/.codex/agents/jahia-reviewer.toml +3 -0
  75. package/dist/codex/AGENTS.md +17 -10
  76. package/dist/copilot/.agents/skills/jahia/SKILL.md +23 -11
  77. package/dist/copilot/.agents/skills/jahia-content/SKILL.md +102 -84
  78. package/dist/copilot/.agents/skills/jahia-content-create-content/SKILL.md +255 -280
  79. package/dist/copilot/.agents/skills/jahia-content-explore-structure/SKILL.md +187 -96
  80. package/dist/copilot/.agents/skills/jahia-content-media-upload/SKILL.md +197 -0
  81. package/dist/copilot/.agents/skills/jahia-content-move-content/SKILL.md +160 -165
  82. package/dist/copilot/.agents/skills/jahia-content-organize/SKILL.md +209 -0
  83. package/dist/copilot/.agents/skills/jahia-content-publish/SKILL.md +181 -0
  84. package/dist/copilot/.agents/skills/jahia-content-query-content/SKILL.md +122 -92
  85. package/dist/copilot/.agents/skills/jahia-content-translate-content/SKILL.md +154 -225
  86. package/dist/copilot/.agents/skills/jahia-dev-accessibility/SKILL.md +3 -3
  87. package/dist/copilot/.agents/skills/jahia-dev-build-component/SKILL.md +10 -7
  88. package/dist/copilot/.agents/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  89. package/dist/copilot/.agents/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  90. package/dist/copilot/.agents/skills/jahia-dev-create-view/SKILL.md +3 -3
  91. package/dist/copilot/.agents/skills/jahia-dev-cypress/SKILL.md +150 -330
  92. package/dist/copilot/.agents/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  93. package/dist/copilot/.agents/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  94. package/dist/copilot/.agents/skills/jahia-dev-query-content/SKILL.md +93 -296
  95. package/dist/copilot/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  96. package/dist/copilot/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  97. package/dist/copilot/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  98. package/dist/copilot/.agents/skills/jahia-dev-site-review/SKILL.md +70 -0
  99. package/dist/copilot/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  100. package/dist/copilot/.agents/skills/jahia-dev-start-local/SKILL.md +18 -26
  101. package/dist/copilot/.agents/skills/jahia-jcr-sql2/SKILL.md +258 -0
  102. package/dist/copilot/.agents/skills/jahia-orchestrate/SKILL.md +148 -0
  103. package/dist/copilot/.agents/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  104. package/dist/copilot/AGENTS.md +17 -10
  105. package/dist/cursor/.agents/skills/jahia/SKILL.md +23 -11
  106. package/dist/cursor/.agents/skills/jahia-content/SKILL.md +102 -84
  107. package/dist/cursor/.agents/skills/jahia-content-create-content/SKILL.md +255 -280
  108. package/dist/cursor/.agents/skills/jahia-content-explore-structure/SKILL.md +187 -96
  109. package/dist/cursor/.agents/skills/jahia-content-media-upload/SKILL.md +197 -0
  110. package/dist/cursor/.agents/skills/jahia-content-move-content/SKILL.md +160 -165
  111. package/dist/cursor/.agents/skills/jahia-content-organize/SKILL.md +209 -0
  112. package/dist/cursor/.agents/skills/jahia-content-publish/SKILL.md +181 -0
  113. package/dist/cursor/.agents/skills/jahia-content-query-content/SKILL.md +122 -92
  114. package/dist/cursor/.agents/skills/jahia-content-translate-content/SKILL.md +154 -225
  115. package/dist/cursor/.agents/skills/jahia-dev-accessibility/SKILL.md +3 -3
  116. package/dist/cursor/.agents/skills/jahia-dev-build-component/SKILL.md +10 -7
  117. package/dist/cursor/.agents/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  118. package/dist/cursor/.agents/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  119. package/dist/cursor/.agents/skills/jahia-dev-create-view/SKILL.md +3 -3
  120. package/dist/cursor/.agents/skills/jahia-dev-cypress/SKILL.md +150 -330
  121. package/dist/cursor/.agents/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  122. package/dist/cursor/.agents/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  123. package/dist/cursor/.agents/skills/jahia-dev-query-content/SKILL.md +93 -296
  124. package/dist/cursor/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  125. package/dist/cursor/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  126. package/dist/cursor/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  127. package/dist/cursor/.agents/skills/jahia-dev-site-review/SKILL.md +70 -0
  128. package/dist/cursor/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  129. package/dist/cursor/.agents/skills/jahia-dev-start-local/SKILL.md +18 -26
  130. package/dist/cursor/.agents/skills/jahia-jcr-sql2/SKILL.md +258 -0
  131. package/dist/cursor/.agents/skills/jahia-orchestrate/SKILL.md +148 -0
  132. package/dist/cursor/.agents/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  133. package/dist/cursor/.cursor/agents/cnd-child-nodes.md +74 -0
  134. package/dist/cursor/.cursor/agents/cnd-jahia-mixins.md +113 -0
  135. package/dist/cursor/.cursor/agents/cnd-numbers-dates.md +61 -0
  136. package/dist/cursor/.cursor/agents/cnd-string-selectors.md +94 -0
  137. package/dist/cursor/.cursor/agents/jahia-cnd-author.md +130 -0
  138. package/dist/cursor/.cursor/agents/jahia-dev-worker.md +264 -0
  139. package/dist/cursor/.cursor/agents/jahia-reviewer.md +105 -0
  140. package/dist/cursor/.cursor/rules/jahia.mdc +15 -6
  141. package/dist/gemini/.agents/skills/jahia/SKILL.md +23 -11
  142. package/dist/gemini/.agents/skills/jahia-content/SKILL.md +102 -84
  143. package/dist/gemini/.agents/skills/jahia-content-create-content/SKILL.md +255 -280
  144. package/dist/gemini/.agents/skills/jahia-content-explore-structure/SKILL.md +187 -96
  145. package/dist/gemini/.agents/skills/jahia-content-media-upload/SKILL.md +197 -0
  146. package/dist/gemini/.agents/skills/jahia-content-move-content/SKILL.md +160 -165
  147. package/dist/gemini/.agents/skills/jahia-content-organize/SKILL.md +209 -0
  148. package/dist/gemini/.agents/skills/jahia-content-publish/SKILL.md +181 -0
  149. package/dist/gemini/.agents/skills/jahia-content-query-content/SKILL.md +122 -92
  150. package/dist/gemini/.agents/skills/jahia-content-translate-content/SKILL.md +154 -225
  151. package/dist/gemini/.agents/skills/jahia-dev-accessibility/SKILL.md +3 -3
  152. package/dist/gemini/.agents/skills/jahia-dev-build-component/SKILL.md +10 -7
  153. package/dist/gemini/.agents/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  154. package/dist/gemini/.agents/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  155. package/dist/gemini/.agents/skills/jahia-dev-create-view/SKILL.md +3 -3
  156. package/dist/gemini/.agents/skills/jahia-dev-cypress/SKILL.md +150 -330
  157. package/dist/gemini/.agents/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  158. package/dist/gemini/.agents/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  159. package/dist/gemini/.agents/skills/jahia-dev-query-content/SKILL.md +93 -296
  160. package/dist/gemini/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  161. package/dist/gemini/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  162. package/dist/gemini/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  163. package/dist/gemini/.agents/skills/jahia-dev-site-review/SKILL.md +70 -0
  164. package/dist/gemini/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  165. package/dist/gemini/.agents/skills/jahia-dev-start-local/SKILL.md +18 -26
  166. package/dist/gemini/.agents/skills/jahia-jcr-sql2/SKILL.md +258 -0
  167. package/dist/gemini/.agents/skills/jahia-orchestrate/SKILL.md +148 -0
  168. package/dist/gemini/.agents/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  169. package/dist/gemini/AGENTS.md +17 -10
  170. package/dist/gemini/GEMINI.md +2 -2
  171. package/dist/index.js +13 -0
  172. package/dist/opencode/.agents/skills/jahia/SKILL.md +23 -11
  173. package/dist/opencode/.agents/skills/jahia-content/SKILL.md +102 -84
  174. package/dist/opencode/.agents/skills/jahia-content-create-content/SKILL.md +255 -280
  175. package/dist/opencode/.agents/skills/jahia-content-explore-structure/SKILL.md +187 -96
  176. package/dist/opencode/.agents/skills/jahia-content-media-upload/SKILL.md +197 -0
  177. package/dist/opencode/.agents/skills/jahia-content-move-content/SKILL.md +160 -165
  178. package/dist/opencode/.agents/skills/jahia-content-organize/SKILL.md +209 -0
  179. package/dist/opencode/.agents/skills/jahia-content-publish/SKILL.md +181 -0
  180. package/dist/opencode/.agents/skills/jahia-content-query-content/SKILL.md +122 -92
  181. package/dist/opencode/.agents/skills/jahia-content-translate-content/SKILL.md +154 -225
  182. package/dist/opencode/.agents/skills/jahia-dev-accessibility/SKILL.md +3 -3
  183. package/dist/opencode/.agents/skills/jahia-dev-build-component/SKILL.md +10 -7
  184. package/dist/opencode/.agents/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  185. package/dist/opencode/.agents/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  186. package/dist/opencode/.agents/skills/jahia-dev-create-view/SKILL.md +3 -3
  187. package/dist/opencode/.agents/skills/jahia-dev-cypress/SKILL.md +150 -330
  188. package/dist/opencode/.agents/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  189. package/dist/opencode/.agents/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  190. package/dist/opencode/.agents/skills/jahia-dev-query-content/SKILL.md +93 -296
  191. package/dist/opencode/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  192. package/dist/opencode/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  193. package/dist/opencode/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  194. package/dist/opencode/.agents/skills/jahia-dev-site-review/SKILL.md +70 -0
  195. package/dist/opencode/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  196. package/dist/opencode/.agents/skills/jahia-dev-start-local/SKILL.md +18 -26
  197. package/dist/opencode/.agents/skills/jahia-jcr-sql2/SKILL.md +258 -0
  198. package/dist/opencode/.agents/skills/jahia-orchestrate/SKILL.md +148 -0
  199. package/dist/opencode/.agents/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  200. package/dist/opencode/.opencode/agents/cnd-child-nodes.md +74 -0
  201. package/dist/opencode/.opencode/agents/cnd-jahia-mixins.md +113 -0
  202. package/dist/opencode/.opencode/agents/cnd-numbers-dates.md +61 -0
  203. package/dist/opencode/.opencode/agents/cnd-string-selectors.md +94 -0
  204. package/dist/opencode/.opencode/agents/jahia-cnd-author.md +130 -0
  205. package/dist/opencode/.opencode/agents/jahia-dev-worker.md +264 -0
  206. package/dist/opencode/.opencode/agents/jahia-reviewer.md +105 -0
  207. package/dist/opencode/AGENTS.md +17 -10
  208. package/dist/windsurf/.windsurf/rules/jahia.md +15 -6
  209. package/dist/windsurf/.windsurf/skills/jahia/SKILL.md +23 -11
  210. package/dist/windsurf/.windsurf/skills/jahia-content/SKILL.md +102 -84
  211. package/dist/windsurf/.windsurf/skills/jahia-content-create-content/SKILL.md +255 -280
  212. package/dist/windsurf/.windsurf/skills/jahia-content-explore-structure/SKILL.md +187 -96
  213. package/dist/windsurf/.windsurf/skills/jahia-content-media-upload/SKILL.md +197 -0
  214. package/dist/windsurf/.windsurf/skills/jahia-content-move-content/SKILL.md +160 -165
  215. package/dist/windsurf/.windsurf/skills/jahia-content-organize/SKILL.md +209 -0
  216. package/dist/windsurf/.windsurf/skills/jahia-content-publish/SKILL.md +181 -0
  217. package/dist/windsurf/.windsurf/skills/jahia-content-query-content/SKILL.md +122 -92
  218. package/dist/windsurf/.windsurf/skills/jahia-content-translate-content/SKILL.md +154 -225
  219. package/dist/windsurf/.windsurf/skills/jahia-dev-accessibility/SKILL.md +3 -3
  220. package/dist/windsurf/.windsurf/skills/jahia-dev-build-component/SKILL.md +10 -7
  221. package/dist/windsurf/.windsurf/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  222. package/dist/windsurf/.windsurf/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  223. package/dist/windsurf/.windsurf/skills/jahia-dev-create-view/SKILL.md +3 -3
  224. package/dist/windsurf/.windsurf/skills/jahia-dev-cypress/SKILL.md +150 -330
  225. package/dist/windsurf/.windsurf/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  226. package/dist/windsurf/.windsurf/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  227. package/dist/windsurf/.windsurf/skills/jahia-dev-query-content/SKILL.md +93 -296
  228. package/dist/windsurf/.windsurf/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  229. package/dist/windsurf/.windsurf/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  230. package/dist/windsurf/.windsurf/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  231. package/dist/windsurf/.windsurf/skills/jahia-dev-site-review/SKILL.md +70 -0
  232. package/dist/windsurf/.windsurf/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  233. package/dist/windsurf/.windsurf/skills/jahia-dev-start-local/SKILL.md +18 -26
  234. package/dist/windsurf/.windsurf/skills/jahia-jcr-sql2/SKILL.md +258 -0
  235. package/dist/windsurf/.windsurf/skills/jahia-orchestrate/SKILL.md +148 -0
  236. package/dist/windsurf/.windsurf/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  237. package/dist/windsurf/AGENTS.md +17 -10
  238. package/package.json +3 -3
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, readdirSync, statSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+
5
+ function findCndFiles(dir) {
6
+ const results = [];
7
+ function walk(current) {
8
+ try {
9
+ const stat = statSync(current);
10
+ if (stat.isFile()) {
11
+ if (current.endsWith(".cnd")) results.push(current);
12
+ return;
13
+ }
14
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
15
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git") {
16
+ walk(join(current, entry.name));
17
+ } else if (entry.isFile() && entry.name.endsWith(".cnd")) {
18
+ results.push(join(current, entry.name));
19
+ }
20
+ }
21
+ } catch {
22
+ // skip unreadable paths
23
+ }
24
+ }
25
+ walk(dir);
26
+ return results;
27
+ }
28
+
29
+ function checkFile(filePath, content) {
30
+ const issues = [];
31
+ const lines = content.split("\n");
32
+
33
+ lines.forEach((line, i) => {
34
+ const lineNum = i + 1;
35
+ const trimmed = line.trim();
36
+ if (trimmed.startsWith("//") || trimmed.startsWith("<")) return;
37
+
38
+ // rawStringLink
39
+ if (
40
+ /^-\s+\w*(Url|Href|Link)\s+\(string[,)]/i.test(trimmed) &&
41
+ !/choicelist\[linkTypeInitializer\]/.test(trimmed)
42
+ ) {
43
+ const propName = trimmed.match(/^-\s+(\w+)/)?.[1] ?? "unknown";
44
+ issues.push({
45
+ file: filePath, line: lineNum,
46
+ pattern: "rawStringLink",
47
+ message: `"${propName}" uses (string) for a link/url — use choicelist[linkTypeInitializer]`,
48
+ fix: "Replace with: - j:linkType (string, choicelist[linkTypeInitializer]) mandatory",
49
+ });
50
+ }
51
+
52
+ // rawTitleProp
53
+ if (/^-\s+(title|heroTitle|pageTitle|sectionTitle)\s+\(string[,)]/i.test(trimmed)) {
54
+ const propName = trimmed.match(/^-\s+(\w+)/)?.[1] ?? "unknown";
55
+ issues.push({
56
+ file: filePath, line: lineNum,
57
+ pattern: "rawTitleProp",
58
+ message: `"${propName}" is a plain string — extend mix:title instead`,
59
+ fix: "Add mix:title to the type declaration and remove this property",
60
+ });
61
+ }
62
+
63
+ // weakrefNoConstraint: (weakreference) with no < constraint on same line
64
+ if (/\(weakreference[,)]/.test(trimmed) && !/<\s*\S/.test(trimmed)) {
65
+ issues.push({
66
+ file: filePath, line: lineNum,
67
+ pattern: "weakrefNoConstraint",
68
+ message: "Unconstrained weakreference — add a type constraint",
69
+ fix: "Add e.g. (weakreference, picker[type='image']) < jmix:image",
70
+ });
71
+ }
72
+
73
+ // weakrefWrongConstraint
74
+ if (/< ['"]jnt:file['"]/.test(trimmed)) {
75
+ issues.push({
76
+ file: filePath, line: lineNum,
77
+ pattern: "weakrefWrongConstraint",
78
+ message: "< 'jnt:file' (quoted) does not enforce image type",
79
+ fix: "Replace with < jmix:image for images",
80
+ });
81
+ }
82
+
83
+ // missingI18n: user-visible string without i18n
84
+ if (
85
+ /^-\s+\w+\s+\(string(,\s*(textarea|richtext))?[,)]/.test(trimmed) &&
86
+ !/ i18n/.test(trimmed) &&
87
+ !/^-\s+j:/.test(trimmed) &&
88
+ /(title|text|label|description|subtitle|caption|alt|heading|summary|excerpt|body)/i.test(trimmed)
89
+ ) {
90
+ issues.push({
91
+ file: filePath, line: lineNum,
92
+ pattern: "missingI18n",
93
+ message: "User-visible string property missing i18n",
94
+ fix: "Add i18n keyword after the type declaration",
95
+ });
96
+ }
97
+
98
+ // directDroppable: concrete type (not mixin) extending jmix:droppableContent
99
+ if (trimmed.startsWith("[") && /jmix:droppableContent/.test(trimmed) && !/\bmixin\b/.test(trimmed)) {
100
+ issues.push({
101
+ file: filePath, line: lineNum,
102
+ pattern: "directDroppable",
103
+ message: "Extends jmix:droppableContent directly — always extend the module component mixin",
104
+ fix: "Replace jmix:droppableContent with nsmix:component (or your module's equivalent)",
105
+ });
106
+ }
107
+
108
+ // studioOnly
109
+ if (/jmix:studioOnly/.test(trimmed)) {
110
+ issues.push({
111
+ file: filePath, line: lineNum,
112
+ pattern: "studioOnly",
113
+ message: "jmix:studioOnly causes silent rendering issues",
114
+ fix: "Replace with jmix:hiddenType",
115
+ });
116
+ }
117
+
118
+ // redundantImageAlt: imageAlt as plain string — image node already has jcr:title
119
+ if (/^-\s+imageAlt\s+\(string[,)]/i.test(trimmed)) {
120
+ issues.push({
121
+ file: filePath, line: lineNum,
122
+ pattern: "redundantImageAlt",
123
+ message: '"imageAlt" is redundant — the image node\'s jcr:title (mix:title) serves as alt text',
124
+ fix: 'Remove imageAlt. In the view, use image.getPropertyAsString("jcr:title") for alt text',
125
+ });
126
+ }
127
+
128
+ // missingRatingConstraint: rating (long) without a range constraint
129
+ if (/^-\s+rating\s+\(long[,)]/i.test(trimmed) && !/<\s*"?\[/.test(trimmed)) {
130
+ issues.push({
131
+ file: filePath, line: lineNum,
132
+ pattern: "missingRatingConstraint",
133
+ message: '"rating" (long) has no range constraint — unconstrained ratings cause data integrity issues',
134
+ fix: 'Add: < "[1,5]"',
135
+ });
136
+ }
137
+ });
138
+
139
+ // singleHardcodedCta: check whole-file type blocks
140
+ const typeBlocks = content.split(/(?=^\[)/m);
141
+ for (const block of typeBlocks) {
142
+ if (!block.trim().startsWith("[")) continue;
143
+ const hasCtaLabel = /^\s*-\s+cta(Text|Label|ButtonText|ButtonLabel)\s+\(/im.test(block);
144
+ const hasCtaLink = /^\s*-\s+cta(Link|Url|Href|ButtonLink|ButtonUrl)\s+\(/im.test(block);
145
+ const hasChildNodes = /^\s*\+\s+/.test(block);
146
+ if (hasCtaLabel && hasCtaLink && !hasChildNodes) {
147
+ const typeName = block.match(/^\[(\S+)\]/m)?.[1] ?? "unknown";
148
+ const typeLineIdx = lines.findIndex((l) => l.includes(`[${typeName}]`));
149
+ issues.push({
150
+ file: filePath,
151
+ ...(typeLineIdx >= 0 ? { line: typeLineIdx + 1 } : {}),
152
+ pattern: "singleHardcodedCta",
153
+ message: `${typeName}: flat ctaText+ctaLink forces a single CTA — model as child nodes`,
154
+ fix: "Remove ctaText and ctaLink. Add: + * (ns:cta). Create a [ns:cta] type with label + j:linkType",
155
+ });
156
+ }
157
+ }
158
+
159
+ return issues;
160
+ }
161
+
162
+ export function checkCndFiles(projectDir) {
163
+ const files = findCndFiles(projectDir);
164
+ const allIssues = [];
165
+
166
+ for (const file of files) {
167
+ try {
168
+ const content = readFileSync(file, "utf-8");
169
+ allIssues.push(...checkFile(file, content));
170
+ } catch {
171
+ // skip unreadable files
172
+ }
173
+ }
174
+
175
+ return { score: Math.exp(-allIssues.length * 0.5), issues: allIssues, filesChecked: files.length };
176
+ }
177
+
178
+ if (import.meta.main) {
179
+ const targetPath = resolve(process.argv[2] ?? ".");
180
+ const { score, issues, filesChecked } = checkCndFiles(targetPath);
181
+
182
+ console.log(`\nCND Review: ${filesChecked} file${filesChecked !== 1 ? "s" : ""} checked\n`);
183
+
184
+ if (issues.length > 0) {
185
+ console.log(`ISSUES (${issues.length}):`);
186
+ for (const issue of issues) {
187
+ const loc = issue.line ? `${issue.file}:${issue.line}` : issue.file;
188
+ console.log(` [${issue.pattern}] ${loc}`);
189
+ console.log(` ${issue.message}`);
190
+ console.log(` Fix: ${issue.fix}`);
191
+ }
192
+ console.log();
193
+ }
194
+
195
+ const verdict = issues.length > 0 ? "FAIL" : "PASS";
196
+ console.log(`Result: ${verdict} (score=${score.toFixed(2)})`);
197
+ process.exit(issues.length > 0 ? 1 : 0);
198
+ }
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: jahia-dev-site-review
3
+ description: Scores live pages for accessibility (WCAG 2.1 AA via axe-core) and SEO (title, meta description, h1, alt). Use after deploying to get a pass/fail signal with per-violation detail before completing development.
4
+ allowed-tools: Bash, Read, Write, Edit
5
+ ---
6
+
7
+ # Skill: jahia-dev-site-review
8
+
9
+ Runs automated a11y and SEO checks against every URL in `pages.json`. Reports a numeric score per page, lists violations by severity, and exits non-zero on any critical/serious a11y violation or missing SEO baseline.
10
+
11
+ **A11y scoring:** `Math.exp(-Σ impact_weights)` where `critical=1, serious=0.5, moderate=0.25, minor=0.1`. Score of 1.0 = perfect; 0.607 = one serious violation.
12
+
13
+ ---
14
+
15
+ ## Step 1 — Ensure tooling is installed
16
+
17
+ ```bash
18
+ node -e "require('@axe-core/playwright'); require('playwright')" 2>/dev/null || \
19
+ npm install --no-save @axe-core/playwright playwright && npx playwright install chromium --with-deps
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Step 2 — Run the review
25
+
26
+ ```bash
27
+ SCRIPT=$(find .claude .agents -name "review-pages.mjs" 2>/dev/null | head -1)
28
+ node "$SCRIPT" 2>&1 | tee /tmp/site-review.txt
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Step 3 — Interpret and fix
34
+
35
+ The script exits 1 if any page has:
36
+ - A `🔴 [critical]` or `🔴 [serious]` a11y violation
37
+ - A `🔍 SEO` issue (missing title, meta description, h1, or img alt)
38
+
39
+ `🟡 [moderate]` and `🟡 [minor]` violations are reported but do not fail the run — fix them for a higher score.
40
+
41
+ **Common violations and where to fix them:**
42
+
43
+ | Violation | Fix location |
44
+ |---|---|
45
+ | `landmark-*` empty nav or footer | Page template — ensure `<nav>` has inline content, `<footer>` has fallback text |
46
+ | `page-has-heading-one` | Page template — add `<h1>{title}</h1>` |
47
+ | `image-alt` | Component `.server.tsx` — use `imageAlt \|\| title \|\| 'Image'` |
48
+ | `color-contrast` | Component `.module.css` — check foreground/background ratio ≥ 4.5:1 |
49
+ | `heading-order` | Component — components start at `<h2>`, sub-items at `<h3>` |
50
+ | Missing `<title>` | Page template `<head>` |
51
+ | Missing meta description | Page template `<head>` — add `<meta name="description" content={…} />` |
52
+ | Multiple `<h1>` | Remove `<h1>` from components; only the template renders one |
53
+
54
+ After fixing, redeploy and re-run:
55
+
56
+ ```bash
57
+ yarn build && yarn jahia-deploy
58
+ node "$SCRIPT"
59
+ ```
60
+
61
+ Iterate until the script exits 0.
62
+
63
+ ---
64
+
65
+ ## Validation checklist
66
+ - [ ] Script exits 0 (no critical/serious violations, no SEO issues)
67
+ - [ ] Average a11y score ≥ 0.8
68
+ - [ ] Every page has a unique, non-empty `<title>`
69
+ - [ ] Every page has `<meta name="description">`
70
+ - [ ] Every page has exactly one `<h1>`
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ // Runs a11y (axe-core) + SEO checks on every URL in pages.json.
3
+ // Exits 1 if any page has critical/serious a11y violations or missing SEO basics.
4
+ import { chromium } from "playwright";
5
+ import { AxeBuilder } from "@axe-core/playwright";
6
+ import { readFileSync } from "fs";
7
+
8
+ const IMPACTS = { minor: 0.1, moderate: 0.25, serious: 0.5, critical: 1 };
9
+
10
+ const urls = JSON.parse(readFileSync("pages.json", "utf-8"));
11
+ const browser = await chromium.launch({ args: ["--no-sandbox"] });
12
+ const context = await browser.newContext();
13
+ const page = await context.newPage();
14
+
15
+ const results = [];
16
+
17
+ for (const url of urls) {
18
+ process.stdout.write(`\nChecking ${url} … `);
19
+ await page.goto(url, { waitUntil: "networkidle", timeout: 30_000 });
20
+
21
+ // A11y
22
+ const axe = await new AxeBuilder({ page })
23
+ .withTags(["wcag2a", "wcag2aa", "wcag21aa"])
24
+ .analyze();
25
+
26
+ const a11yScore = Math.exp(
27
+ -axe.violations.reduce((t, v) => t + (IMPACTS[v.impact] ?? 0), 0),
28
+ );
29
+
30
+ // SEO
31
+ const title = await page.title();
32
+ const metaDesc = await page
33
+ .$eval('meta[name="description"]', el => el.getAttribute("content"))
34
+ .catch(() => null);
35
+ const h1s = await page.$$eval("h1", els => els.map(e => e.textContent?.trim()));
36
+ const imgsMissingAlt = await page.$$eval(
37
+ "img",
38
+ els => els.filter(e => !e.getAttribute("alt")).map(e => e.outerHTML.slice(0, 80)),
39
+ );
40
+
41
+ const seoIssues = [];
42
+ if (!title) seoIssues.push("missing <title>");
43
+ if (!metaDesc) seoIssues.push("missing <meta name=description>");
44
+ if (h1s.length === 0) seoIssues.push("no <h1>");
45
+ if (h1s.length > 1) seoIssues.push(`${h1s.length} <h1> elements (must be exactly 1)`);
46
+ if (imgsMissingAlt.length > 0)
47
+ seoIssues.push(`${imgsMissingAlt.length} <img> missing alt attribute`);
48
+
49
+ process.stdout.write(`a11y=${a11yScore.toFixed(3)}\n`);
50
+ results.push({ url, a11yScore, violations: axe.violations, seoIssues });
51
+ }
52
+
53
+ await browser.close();
54
+
55
+ // ── Report ──────────────────────────────────────────────────────────────────
56
+ let failed = false;
57
+
58
+ for (const r of results) {
59
+ const critical = r.violations.filter(v => v.impact === "critical" || v.impact === "serious");
60
+ const pageOk = critical.length === 0 && r.seoIssues.length === 0;
61
+ if (!pageOk) failed = true;
62
+
63
+ console.log(`\n${"─".repeat(70)}`);
64
+ console.log(`${pageOk ? "✅" : "❌"} ${r.url}`);
65
+ console.log(` A11y score : ${r.a11yScore.toFixed(3)} (1.0 = perfect)`);
66
+
67
+ for (const v of r.violations) {
68
+ const marker = v.impact === "critical" || v.impact === "serious" ? "🔴" : "🟡";
69
+ console.log(` ${marker} [${v.impact}] ${v.id} — ${v.description} (${v.nodes.length} node${v.nodes.length !== 1 ? "s" : ""})`);
70
+ for (const node of v.nodes.slice(0, 3)) {
71
+ console.log(` ${node.html.slice(0, 100)}`);
72
+ }
73
+ }
74
+
75
+ for (const issue of r.seoIssues) {
76
+ console.log(` 🔍 SEO: ${issue}`);
77
+ }
78
+ }
79
+
80
+ console.log(`\n${"═".repeat(70)}`);
81
+ const avg = results.reduce((t, r) => t + r.a11yScore, 0) / results.length;
82
+ console.log(`Average a11y score: ${avg.toFixed(3)}`);
83
+ console.log(failed ? "\n❌ FAIL — fix the issues above, redeploy, and re-run." : "\n✅ PASS");
84
+
85
+ process.exit(failed ? 1 : 0);
@@ -79,36 +79,28 @@ Follow the instructions on that page for the user's platform, then return here t
79
79
 
80
80
  ## Step 4 — Create a new site in Jahia
81
81
 
82
- Once the module is deployed to Jahia, create the site via the Provisioning API — **do not use the UI**.
82
+ Once the module is deployed to Jahia, create the site via MCP:
83
83
 
84
- > ⚠️ **CRITICAL: syntax is `- createSite: ""`** — the empty string `""` after the colon is **mandatory**. Without it, Jahia returns HTTP 200 but silently creates nothing. Using `- createSite:` with nested properties is **wrong and will fail silently**.
85
-
86
- ```bash
87
- MODULE_NAME=<module-name> # value of "name" in package.json
88
-
89
- cat > /tmp/create-site.yaml <<EOF
90
- - createSite: ""
91
- siteKey: ${MODULE_NAME}
92
- title: "My Site"
93
- defaultLanguage: en
94
- serverName: localhost
95
- templateSet: ${MODULE_NAME}
96
- EOF
97
-
98
- curl -u root:root1234 -X POST -H "Content-Type: application/yaml" \
99
- --data-binary @/tmp/create-site.yaml \
100
- http://localhost:8080/modules/api/provisioning
101
84
  ```
85
+ tool: site.create
86
+ args: {
87
+ "siteKey": "<module-name>",
88
+ "title": "My Site",
89
+ "templateSet": "<module-name>",
90
+ "defaultLanguage": "en",
91
+ "serverName": "localhost"
92
+ }
93
+ ```
94
+
95
+ Replace `<module-name>` with the `name` from `package.json`. `templateSet` must exactly match the deployed module name.
102
96
 
103
97
  Verify the site exists:
104
- ```bash
105
- curl -s -u root:root1234 \
106
- -H "Content-Type: application/json" -H "Origin: http://localhost:8080" \
107
- -X POST http://localhost:8080/modules/graphql \
108
- -d "{\"query\":\"{ jcr { nodeByPath(path:\\\"/sites/${MODULE_NAME}\\\") { name } } }\"}"
98
+
99
+ ```
100
+ tool: site.list
109
101
  ```
110
102
 
111
- The response must contain `"name": "<module-name>"`. If it returns `null`, the site was not created — check that `templateSet` exactly matches the deployed module name.
103
+ The site key must appear in the response. If it does not, check that `templateSet` exactly matches the deployed module name.
112
104
 
113
105
  Then open **Page Builder** at http://localhost:8080/jahia/page-builder to start building.
114
106
 
@@ -119,8 +111,8 @@ Then open **Page Builder** at http://localhost:8080/jahia/page-builder to start
119
111
  - [ ] `docker compose up --wait` completes without errors (Docker path)
120
112
  - [ ] Jahia UI is reachable at http://localhost:8080
121
113
  - [ ] Module deployed to Jahia (`yarn build && yarn jahia-deploy` run; or `yarn dev` in user terminal for interactive development)
122
- - [ ] Site created via Provisioning API (`createSite: ""` with correct templateSet)
123
- - [ ] GraphQL confirms `/sites/<site-key>` node exists
114
+ - [ ] Site created via `site.create` MCP tool with correct `templateSet`
115
+ - [ ] `site.list` confirms site key exists
124
116
 
125
117
  ## Troubleshooting
126
118
 
@@ -0,0 +1,258 @@
1
+ ---
2
+ name: jahia-jcr-sql2
3
+ description: JCR-SQL2 reference for Jahia queries. Use when building, reviewing, or debugging SQL2 statements for content listings, full-text search, sorting, pagination, or Java back-end query code.
4
+ ---
5
+
6
+ # Skill: jahia-jcr-sql2
7
+
8
+ Use this skill when you need the JCR-SQL2 language itself: selectors, path constraints, filters, ordering, full-text syntax, joins, pagination rules, and performance guardrails.
9
+
10
+ ---
11
+
12
+ ## When to use JCR-SQL2
13
+
14
+ JCR-SQL2 is the standard Jahia query language for:
15
+
16
+ - listing pages or content with filtering and sorting
17
+ - querying a folder subtree
18
+ - searching by property value, date, or reference
19
+ - full-text search across indexed content
20
+ - back-end Java code using `QueryManagerWrapper`
21
+ - template-set listings that use `useJCRQuery` or the Page Builder query component
22
+
23
+ ---
24
+
25
+ ## Basic syntax
26
+
27
+ ### Select by node type
28
+
29
+ ```sql
30
+ SELECT * FROM [jnt:page] AS page
31
+ SELECT * FROM [jnt:content] AS content
32
+ SELECT * FROM [jnt:file] AS file
33
+ ```
34
+
35
+ The selector matches the named type and its subtypes.
36
+
37
+ ### Common node types
38
+
39
+ | Type | Meaning |
40
+ |------|---------|
41
+ | `jnt:page` | pages |
42
+ | `jnt:content` | editorial content |
43
+ | `jnt:file` | files |
44
+ | `jnt:virtualsite` | sites |
45
+ | `jmix:searchable` | general searchable content |
46
+ | `nt:base` | all nodes — avoid unless paired with a strict path |
47
+
48
+ ---
49
+
50
+ ## Path constraints
51
+
52
+ ### Recursive subtree
53
+
54
+ ```sql
55
+ SELECT * FROM [jnt:page] AS page
56
+ WHERE ISDESCENDANTNODE(page, '/sites/luxe/home')
57
+ ```
58
+
59
+ ### Direct children only
60
+
61
+ ```sql
62
+ SELECT * FROM [jnt:page] AS page
63
+ WHERE ISCHILDNODE(page, '/sites/luxe/home')
64
+ ```
65
+
66
+ **Guardrail:** always constrain by path to avoid repository-wide scans.
67
+
68
+ ---
69
+
70
+ ## Property constraints
71
+
72
+ ### Exact match
73
+
74
+ ```sql
75
+ WHERE page.[j:templateName] = 'home'
76
+ WHERE node.[jcr:title] = 'My Title'
77
+ ```
78
+
79
+ ### Pattern match
80
+
81
+ ```sql
82
+ WHERE node.[jcr:title] LIKE '%keyword%'
83
+ WHERE node.[j:nodename] LIKE '%.png'
84
+ ```
85
+
86
+ ### Null checks
87
+
88
+ ```sql
89
+ WHERE page.[jcr:title] IS NOT NULL
90
+ ```
91
+
92
+ ### Boolean
93
+
94
+ ```sql
95
+ WHERE node.[j:published] = CAST('true' AS BOOLEAN)
96
+ ```
97
+
98
+ ### Date comparison
99
+
100
+ ```sql
101
+ WHERE page.[jcr:lastModified] > CAST('2026-01-01T00:00:00.000Z' AS DATE)
102
+ ```
103
+
104
+ Use the millisecond form `yyyy-MM-dd'T'HH:mm:ss.SSSX` for SQL2 date casts.
105
+
106
+ ### Multiple conditions
107
+
108
+ ```sql
109
+ WHERE ISDESCENDANTNODE(page, '/sites/luxe')
110
+ AND page.[jcr:lastModified] > CAST('2026-01-01T00:00:00.000Z' AS DATE)
111
+ ```
112
+
113
+ ### OR conditions
114
+
115
+ ```sql
116
+ WHERE node.[jcr:primaryType] = 'jnt:bigText'
117
+ OR node.[jcr:primaryType] = 'jnt:article'
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Ordering
123
+
124
+ ```sql
125
+ ORDER BY page.[jcr:lastModified] DESC
126
+ ORDER BY page.[jcr:created] ASC
127
+ ORDER BY node.[jcr:title]
128
+ ```
129
+
130
+ Multiple columns:
131
+
132
+ ```sql
133
+ ORDER BY page.[j:templateName] ASC, page.[jcr:lastModified] DESC
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Full-text search
139
+
140
+ ### Search indexed content
141
+
142
+ ```sql
143
+ WHERE CONTAINS(node.*, 'digital')
144
+ ```
145
+
146
+ ### Search one property
147
+
148
+ ```sql
149
+ WHERE CONTAINS(node.[jcr:title], 'welcome')
150
+ ```
151
+
152
+ ### Expression syntax
153
+
154
+ | Syntax | Meaning |
155
+ |--------|---------|
156
+ | `term` | must contain the term |
157
+ | `term1 term2` | implicit AND |
158
+ | `term1 OR term2` | either term |
159
+ | `"exact phrase"` | exact phrase |
160
+ | `-term` | exclude term |
161
+
162
+ ### Relevance sort
163
+
164
+ ```sql
165
+ SELECT * FROM [jnt:content] AS n
166
+ WHERE ISDESCENDANTNODE(n, '/sites/luxe')
167
+ AND CONTAINS(n.*, 'digital')
168
+ ORDER BY SCORE(n) DESC
169
+ ```
170
+
171
+ Combine full-text with path constraints for performance.
172
+
173
+ ---
174
+
175
+ ## Joins
176
+
177
+ ```sql
178
+ SELECT * FROM [jnt:imageReferenceLink] AS img
179
+ INNER JOIN [jnt:file] AS file
180
+ ON img.[j:node] = file.[jcr:uuid]
181
+ WHERE img.[j:node] = 'UUID'
182
+ ```
183
+
184
+ Jahia supports inner joins, but keep them focused and path-constrained whenever possible.
185
+
186
+ ---
187
+
188
+ ## Using SQL2 in Jahia code
189
+
190
+ ### Template-set listing with `useJCRQuery`
191
+
192
+ ```tsx
193
+ const posts = useJCRQuery({
194
+ query: `SELECT * FROM [namespace:blogPost] AS post
195
+ WHERE ISDESCENDANTNODE(post, '/sites/${siteKey}/contents/blog')
196
+ ORDER BY post.[publicationDate] DESC`,
197
+ });
198
+ ```
199
+
200
+ ### Java back-end query execution
201
+
202
+ ```java
203
+ QueryManagerWrapper qm = session.getWorkspace().getQueryManager();
204
+ QueryWrapper query = qm.createQuery(sql2Statement, Query.JCR_SQL2);
205
+ query.setLimit(limit);
206
+ query.setOffset(offset);
207
+ JCRNodeIteratorWrapper nodes = query.execute().getNodes();
208
+ ```
209
+
210
+ **Guardrail:** never embed `LIMIT` or `OFFSET` inside the SQL2 string. Use `setLimit()` and `setOffset()`.
211
+
212
+ ---
213
+
214
+ ## Security and validation
215
+
216
+ ### Escape user input
217
+
218
+ In Java back-end code, escape user-provided values with `JCRContentUtils.sqlEncode()` before interpolating them into a SQL2 string.
219
+
220
+ ```java
221
+ String safeValue = JCRContentUtils.sqlEncode(userInput);
222
+ ```
223
+
224
+ ### Validate dynamic sort fields
225
+
226
+ If a user can choose the sort field, validate it against a whitelist before interpolating it into `ORDER BY`.
227
+
228
+ ---
229
+
230
+ ## Performance best practices
231
+
232
+ 1. Always constrain by path.
233
+ 2. Use the most specific node type possible.
234
+ 3. Keep result sets small.
235
+ 4. Prefer indexed equality filters over broad `LIKE '%...%'` patterns.
236
+ 5. Use full-text sparingly on large trees.
237
+ 6. Sort on common indexed fields such as `jcr:lastModified` or `jcr:created`.
238
+ 7. Cap API result limits to a sane maximum.
239
+
240
+ ---
241
+
242
+ ## Quick checklist
243
+
244
+ - [ ] Query has a path constraint
245
+ - [ ] Node type is specific
246
+ - [ ] Sort field is intentional and safe
247
+ - [ ] Full-text is combined with a subtree path
248
+ - [ ] Dates use `yyyy-MM-dd'T'HH:mm:ss.SSSX`
249
+ - [ ] Java code uses `setLimit()` and `setOffset()` instead of inline SQL clauses
250
+
251
+ ---
252
+
253
+ ## Related skills
254
+
255
+ - `/jahia-dev-query-content` — apply SQL2 inside Page Builder queries and JS module views
256
+ - `/jahia-dev-define-content-type` — define the content types you will query
257
+ - `/jahia-java-jcr` — implement back-end JCR logic around the query
258
+