@jahia/agentic 0.4.1 → 0.5.1

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 (178) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/antigravity/.agents/rules/jahia.md +51 -0
  3. package/dist/antigravity/.agents/skills/jahia-cnd-author/SKILL.md +94 -0
  4. package/dist/antigravity/.agents/skills/jahia-cnd-author/references/cnd-area-types.md +55 -0
  5. package/dist/antigravity/.agents/skills/jahia-cnd-author/references/cnd-authoring-experience.md +92 -0
  6. package/dist/antigravity/.agents/skills/jahia-cnd-author/references/cnd-child-nodes.md +74 -0
  7. package/dist/antigravity/.agents/skills/jahia-cnd-author/references/cnd-jahia-mixins.md +510 -0
  8. package/dist/antigravity/.agents/skills/jahia-cnd-author/references/cnd-modeling-decisions.md +87 -0
  9. package/dist/antigravity/.agents/skills/jahia-cnd-author/references/cnd-numbers-dates.md +92 -0
  10. package/dist/antigravity/.agents/skills/jahia-cnd-author/references/cnd-string-selectors.md +133 -0
  11. package/dist/antigravity/.agents/skills/jahia-cnd-author/references/cnd-syntax.md +119 -0
  12. package/dist/antigravity/.agents/skills/jahia-cnd-author/references/types-ts-mapping.md +73 -0
  13. package/dist/antigravity/.agents/skills/jahia-dev-accessibility/SKILL.md +11 -0
  14. package/dist/antigravity/.agents/skills/jahia-dev-build-component/SKILL.md +133 -0
  15. package/dist/antigravity/.agents/skills/jahia-dev-create-page-template/SKILL.md +341 -0
  16. package/dist/antigravity/.agents/skills/jahia-dev-create-template-set/SKILL.md +205 -0
  17. package/dist/antigravity/.agents/skills/jahia-dev-create-view/SKILL.md +896 -0
  18. package/dist/antigravity/.agents/skills/jahia-dev-debug/SKILL.md +176 -0
  19. package/dist/antigravity/.agents/skills/jahia-dev-import-from/SKILL.md +244 -0
  20. package/dist/antigravity/.agents/skills/jahia-dev-jexperience/SKILL.md +269 -0
  21. package/dist/antigravity/.agents/skills/jahia-dev-ops/SKILL.md +50 -0
  22. package/dist/antigravity/.agents/skills/jahia-dev-ops/references/docker.md +151 -0
  23. package/dist/antigravity/.agents/skills/jahia-dev-ops/references/monitoring.md +195 -0
  24. package/dist/antigravity/.agents/skills/jahia-dev-ops/references/provisioning.md +269 -0
  25. package/dist/antigravity/.agents/skills/jahia-dev-properties/SKILL.md +147 -0
  26. package/dist/antigravity/.agents/skills/jahia-dev-properties/references/all-properties.md +231 -0
  27. package/dist/antigravity/.agents/skills/jahia-dev-query-content/SKILL.md +204 -0
  28. package/dist/antigravity/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  29. package/dist/antigravity/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  30. package/dist/antigravity/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  31. package/dist/antigravity/.agents/skills/jahia-dev-screenshot/SKILL.md +177 -0
  32. package/dist/antigravity/.agents/skills/jahia-dev-start-local/SKILL.md +121 -0
  33. package/dist/antigravity/.agents/skills/jahia-jcr-sql2/SKILL.md +257 -0
  34. package/dist/antigravity/.agents/skills/jahia-review/SKILL.md +63 -0
  35. package/dist/{claude/.claude/skills/jahia-dev-review → antigravity/.agents/skills/jahia-review-code}/SKILL.md +3 -3
  36. package/dist/antigravity/.agents/skills/jahia-review-site/SKILL.md +52 -0
  37. package/dist/antigravity/.agents/skills/jahia-review-site/scripts/review-pages.mjs +74 -0
  38. package/dist/antigravity/AGENTS.md +62 -0
  39. package/dist/claude/.claude/rules/jahia.md +1 -1
  40. package/dist/claude/.claude/skills/jahia-dev-build-component/SKILL.md +2 -2
  41. package/dist/claude/.claude/skills/jahia-dev-create-view/SKILL.md +2 -2
  42. package/dist/claude/.claude/skills/jahia-dev-import-from/SKILL.md +1 -1
  43. package/dist/claude/.claude/skills/jahia-dev-properties/SKILL.md +1 -1
  44. package/dist/claude/.claude/skills/jahia-dev-query-content/SKILL.md +1 -1
  45. package/dist/claude/.claude/skills/jahia-jcr-sql2/SKILL.md +1 -2
  46. package/dist/claude/.claude/skills/jahia-review/SKILL.md +63 -0
  47. package/dist/{copilot/.agents/skills/jahia-dev-review → claude/.claude/skills/jahia-review-code}/SKILL.md +3 -3
  48. package/dist/claude/.claude/skills/jahia-review-site/SKILL.md +52 -0
  49. package/dist/claude/.claude/skills/jahia-review-site/scripts/review-pages.mjs +74 -0
  50. package/dist/claude/.mcp.json +11 -0
  51. package/dist/claude/CLAUDE.md +2 -2
  52. package/dist/codex/.agents/skills/jahia-dev-build-component/SKILL.md +2 -2
  53. package/dist/codex/.agents/skills/jahia-dev-create-view/SKILL.md +2 -2
  54. package/dist/codex/.agents/skills/jahia-dev-import-from/SKILL.md +1 -1
  55. package/dist/codex/.agents/skills/jahia-dev-properties/SKILL.md +1 -1
  56. package/dist/codex/.agents/skills/jahia-dev-query-content/SKILL.md +1 -1
  57. package/dist/codex/.agents/skills/jahia-jcr-sql2/SKILL.md +1 -2
  58. package/dist/codex/.agents/skills/jahia-review/SKILL.md +63 -0
  59. package/dist/{cursor/.agents/skills/jahia-dev-review → codex/.agents/skills/jahia-review-code}/SKILL.md +3 -3
  60. package/dist/codex/.agents/skills/jahia-review-site/SKILL.md +52 -0
  61. package/dist/codex/.agents/skills/jahia-review-site/scripts/review-pages.mjs +74 -0
  62. package/dist/codex/AGENTS.md +2 -2
  63. package/dist/copilot/.agents/skills/jahia-dev-build-component/SKILL.md +2 -2
  64. package/dist/copilot/.agents/skills/jahia-dev-create-view/SKILL.md +2 -2
  65. package/dist/copilot/.agents/skills/jahia-dev-import-from/SKILL.md +1 -1
  66. package/dist/copilot/.agents/skills/jahia-dev-properties/SKILL.md +1 -1
  67. package/dist/copilot/.agents/skills/jahia-dev-query-content/SKILL.md +1 -1
  68. package/dist/copilot/.agents/skills/jahia-jcr-sql2/SKILL.md +1 -2
  69. package/dist/copilot/.agents/skills/jahia-review/SKILL.md +63 -0
  70. package/dist/{codex/.agents/skills/jahia-dev-review → copilot/.agents/skills/jahia-review-code}/SKILL.md +3 -3
  71. package/dist/copilot/.agents/skills/jahia-review-site/SKILL.md +52 -0
  72. package/dist/copilot/.agents/skills/jahia-review-site/scripts/review-pages.mjs +74 -0
  73. package/dist/copilot/AGENTS.md +2 -2
  74. package/dist/cursor/.agents/skills/jahia-dev-build-component/SKILL.md +2 -2
  75. package/dist/cursor/.agents/skills/jahia-dev-create-view/SKILL.md +2 -2
  76. package/dist/cursor/.agents/skills/jahia-dev-import-from/SKILL.md +1 -1
  77. package/dist/cursor/.agents/skills/jahia-dev-properties/SKILL.md +1 -1
  78. package/dist/cursor/.agents/skills/jahia-dev-query-content/SKILL.md +1 -1
  79. package/dist/cursor/.agents/skills/jahia-jcr-sql2/SKILL.md +1 -2
  80. package/dist/cursor/.agents/skills/jahia-review/SKILL.md +63 -0
  81. package/dist/cursor/.agents/skills/jahia-review-code/SKILL.md +228 -0
  82. package/dist/cursor/.agents/skills/jahia-review-site/SKILL.md +52 -0
  83. package/dist/cursor/.agents/skills/jahia-review-site/scripts/review-pages.mjs +74 -0
  84. package/dist/cursor/.cursor/mcp.json +11 -0
  85. package/dist/cursor/.cursor/rules/jahia.mdc +1 -1
  86. package/dist/gemini/.agents/skills/jahia-dev-build-component/SKILL.md +2 -2
  87. package/dist/gemini/.agents/skills/jahia-dev-create-view/SKILL.md +2 -2
  88. package/dist/gemini/.agents/skills/jahia-dev-import-from/SKILL.md +1 -1
  89. package/dist/gemini/.agents/skills/jahia-dev-properties/SKILL.md +1 -1
  90. package/dist/gemini/.agents/skills/jahia-dev-query-content/SKILL.md +1 -1
  91. package/dist/gemini/.agents/skills/jahia-jcr-sql2/SKILL.md +1 -2
  92. package/dist/gemini/.agents/skills/jahia-review/SKILL.md +63 -0
  93. package/dist/gemini/.agents/skills/jahia-review-code/SKILL.md +228 -0
  94. package/dist/gemini/.agents/skills/jahia-review-site/SKILL.md +52 -0
  95. package/dist/gemini/.agents/skills/jahia-review-site/scripts/review-pages.mjs +74 -0
  96. package/dist/gemini/.gemini/settings.json +10 -0
  97. package/dist/gemini/AGENTS.md +2 -2
  98. package/dist/index.js +14 -2
  99. package/dist/kiro/.kiro/settings/mcp.json +10 -0
  100. package/dist/kiro/.kiro/skills/jahia-cnd-author/SKILL.md +94 -0
  101. package/dist/kiro/.kiro/skills/jahia-cnd-author/references/cnd-area-types.md +55 -0
  102. package/dist/kiro/.kiro/skills/jahia-cnd-author/references/cnd-authoring-experience.md +92 -0
  103. package/dist/kiro/.kiro/skills/jahia-cnd-author/references/cnd-child-nodes.md +74 -0
  104. package/dist/kiro/.kiro/skills/jahia-cnd-author/references/cnd-jahia-mixins.md +510 -0
  105. package/dist/kiro/.kiro/skills/jahia-cnd-author/references/cnd-modeling-decisions.md +87 -0
  106. package/dist/kiro/.kiro/skills/jahia-cnd-author/references/cnd-numbers-dates.md +92 -0
  107. package/dist/kiro/.kiro/skills/jahia-cnd-author/references/cnd-string-selectors.md +133 -0
  108. package/dist/kiro/.kiro/skills/jahia-cnd-author/references/cnd-syntax.md +119 -0
  109. package/dist/kiro/.kiro/skills/jahia-cnd-author/references/types-ts-mapping.md +73 -0
  110. package/dist/kiro/.kiro/skills/jahia-dev-accessibility/SKILL.md +11 -0
  111. package/dist/kiro/.kiro/skills/jahia-dev-build-component/SKILL.md +133 -0
  112. package/dist/kiro/.kiro/skills/jahia-dev-create-page-template/SKILL.md +341 -0
  113. package/dist/kiro/.kiro/skills/jahia-dev-create-template-set/SKILL.md +205 -0
  114. package/dist/kiro/.kiro/skills/jahia-dev-create-view/SKILL.md +896 -0
  115. package/dist/kiro/.kiro/skills/jahia-dev-debug/SKILL.md +176 -0
  116. package/dist/kiro/.kiro/skills/jahia-dev-import-from/SKILL.md +244 -0
  117. package/dist/kiro/.kiro/skills/jahia-dev-jexperience/SKILL.md +269 -0
  118. package/dist/kiro/.kiro/skills/jahia-dev-ops/SKILL.md +50 -0
  119. package/dist/kiro/.kiro/skills/jahia-dev-ops/references/docker.md +151 -0
  120. package/dist/kiro/.kiro/skills/jahia-dev-ops/references/monitoring.md +195 -0
  121. package/dist/kiro/.kiro/skills/jahia-dev-ops/references/provisioning.md +269 -0
  122. package/dist/kiro/.kiro/skills/jahia-dev-properties/SKILL.md +147 -0
  123. package/dist/kiro/.kiro/skills/jahia-dev-properties/references/all-properties.md +231 -0
  124. package/dist/kiro/.kiro/skills/jahia-dev-query-content/SKILL.md +204 -0
  125. package/dist/kiro/.kiro/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  126. package/dist/kiro/.kiro/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  127. package/dist/kiro/.kiro/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  128. package/dist/kiro/.kiro/skills/jahia-dev-screenshot/SKILL.md +177 -0
  129. package/dist/kiro/.kiro/skills/jahia-dev-start-local/SKILL.md +121 -0
  130. package/dist/kiro/.kiro/skills/jahia-jcr-sql2/SKILL.md +257 -0
  131. package/dist/kiro/.kiro/skills/jahia-review/SKILL.md +63 -0
  132. package/dist/kiro/.kiro/skills/jahia-review-code/SKILL.md +228 -0
  133. package/dist/kiro/.kiro/skills/jahia-review-site/SKILL.md +52 -0
  134. package/dist/kiro/.kiro/skills/jahia-review-site/scripts/review-pages.mjs +74 -0
  135. package/dist/kiro/.kiro/steering/jahia.md +55 -0
  136. package/dist/kiro/AGENTS.md +62 -0
  137. package/dist/opencode/.agents/skills/jahia-dev-build-component/SKILL.md +2 -2
  138. package/dist/opencode/.agents/skills/jahia-dev-create-view/SKILL.md +2 -2
  139. package/dist/opencode/.agents/skills/jahia-dev-import-from/SKILL.md +1 -1
  140. package/dist/opencode/.agents/skills/jahia-dev-properties/SKILL.md +1 -1
  141. package/dist/opencode/.agents/skills/jahia-dev-query-content/SKILL.md +1 -1
  142. package/dist/opencode/.agents/skills/jahia-jcr-sql2/SKILL.md +1 -2
  143. package/dist/opencode/.agents/skills/jahia-review/SKILL.md +63 -0
  144. package/dist/opencode/.agents/skills/jahia-review-code/SKILL.md +228 -0
  145. package/dist/opencode/.agents/skills/jahia-review-site/SKILL.md +52 -0
  146. package/dist/opencode/.agents/skills/jahia-review-site/scripts/review-pages.mjs +74 -0
  147. package/dist/opencode/AGENTS.md +2 -2
  148. package/dist/opencode/opencode.json +12 -0
  149. package/dist/windsurf/.windsurf/rules/jahia.md +1 -1
  150. package/dist/windsurf/.windsurf/skills/jahia-dev-build-component/SKILL.md +2 -2
  151. package/dist/windsurf/.windsurf/skills/jahia-dev-create-view/SKILL.md +2 -2
  152. package/dist/windsurf/.windsurf/skills/jahia-dev-import-from/SKILL.md +1 -1
  153. package/dist/windsurf/.windsurf/skills/jahia-dev-properties/SKILL.md +1 -1
  154. package/dist/windsurf/.windsurf/skills/jahia-dev-query-content/SKILL.md +1 -1
  155. package/dist/windsurf/.windsurf/skills/jahia-jcr-sql2/SKILL.md +1 -2
  156. package/dist/windsurf/.windsurf/skills/jahia-review/SKILL.md +63 -0
  157. package/dist/windsurf/.windsurf/skills/jahia-review-code/SKILL.md +228 -0
  158. package/dist/windsurf/.windsurf/skills/jahia-review-site/SKILL.md +52 -0
  159. package/dist/windsurf/.windsurf/skills/jahia-review-site/scripts/review-pages.mjs +74 -0
  160. package/dist/windsurf/AGENTS.md +2 -2
  161. package/package.json +1 -1
  162. package/dist/claude/.claude/skills/jahia-dev-site-review/SKILL.md +0 -70
  163. package/dist/claude/.claude/skills/jahia-dev-site-review/scripts/review-pages.mjs +0 -85
  164. package/dist/codex/.agents/skills/jahia-dev-site-review/SKILL.md +0 -70
  165. package/dist/codex/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +0 -85
  166. package/dist/copilot/.agents/skills/jahia-dev-site-review/SKILL.md +0 -70
  167. package/dist/copilot/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +0 -85
  168. package/dist/cursor/.agents/skills/jahia-dev-site-review/SKILL.md +0 -70
  169. package/dist/cursor/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +0 -85
  170. package/dist/gemini/.agents/skills/jahia-dev-review/SKILL.md +0 -228
  171. package/dist/gemini/.agents/skills/jahia-dev-site-review/SKILL.md +0 -70
  172. package/dist/gemini/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +0 -85
  173. package/dist/opencode/.agents/skills/jahia-dev-review/SKILL.md +0 -228
  174. package/dist/opencode/.agents/skills/jahia-dev-site-review/SKILL.md +0 -70
  175. package/dist/opencode/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +0 -85
  176. package/dist/windsurf/.windsurf/skills/jahia-dev-review/SKILL.md +0 -228
  177. package/dist/windsurf/.windsurf/skills/jahia-dev-site-review/SKILL.md +0 -70
  178. package/dist/windsurf/.windsurf/skills/jahia-dev-site-review/scripts/review-pages.mjs +0 -85
@@ -0,0 +1,257 @@
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-cnd-author` — define the content types you will query
257
+
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: jahia-review
3
+ description: Full quality review of a Jahia JavaScript module — runs code review and live site review in parallel via subagents. Use after deploying to get a complete pass/fail signal before finalizing work.
4
+ allowed-tools: Agent, Bash, Read
5
+ ---
6
+
7
+ # Skill: jahia-review
8
+
9
+ Runs both `/jahia-review-code` and `/jahia-review-site` in parallel via independent subagents, then consolidates their findings. Use this before writing `pages.json` or marking work complete.
10
+
11
+ ---
12
+
13
+ ## Step 1 — Collect live URLs
14
+
15
+ Read `pages.json` to get the list of live page URLs:
16
+
17
+ ```bash
18
+ cat pages.json
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Step 2 — Spawn review subagents in parallel
24
+
25
+ Invoke two subagents simultaneously:
26
+
27
+ **Subagent A — code review:**
28
+ > Invoke `/jahia-review-code` on this module. Scan all CND files, TypeScript views, and page templates. Report every issue found (critical, warning, suggestion) with file locations and fix guidance.
29
+
30
+ **Subagent B — site review:**
31
+ > Invoke `/jahia-review-site` against the URLs in `pages.json`. Run the review script and report every a11y and SEO violation found.
32
+
33
+ ---
34
+
35
+ ## Step 3 — Consolidate and report
36
+
37
+ Combine findings from both subagents into a single report:
38
+
39
+ ```
40
+ ## Review Results
41
+
42
+ ### Code (jahia-review-code)
43
+ [findings from subagent A]
44
+
45
+ ### Site (jahia-review-site)
46
+ [findings from subagent B]
47
+
48
+ ### Verdict
49
+ ✅ PASS — no violations found.
50
+ ❌ FAIL — N issue(s) must be fixed before finalizing.
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Step 4 — Fix and iterate
56
+
57
+ If any violations were found:
58
+
59
+ 1. Fix every reported issue
60
+ 2. Redeploy: `yarn build && yarn jahia-deploy`
61
+ 3. Re-run `/jahia-review` until the verdict is ✅ PASS
62
+
63
+ Only proceed (write `pages.json`, mark work complete) once the review passes.
@@ -0,0 +1,228 @@
1
+ ---
2
+ name: jahia-review-code
3
+ description: Reviews a Jahia JavaScript module for generic and Jahia-specific best practices. Scans CND definitions, TypeScript views, and page templates. Reports issues in order of importance with fix suggestions. Covers 8 critical checks, 9 warnings, and 10 suggestions.
4
+ allowed-tools: Bash, Read
5
+ ---
6
+
7
+ # Skill: jahia-review-code
8
+
9
+ Reviews a Jahia JavaScript module for correctness and best practices. Scans real files, reports issues in order of severity (🔴 Critical → 🟡 Warning → 🔵 Suggestion), and proposes fixes.
10
+
11
+ ---
12
+
13
+ ## Step 1 — Locate the module
14
+
15
+ Find the module root: look for `package.json` with `@jahia/javascript-modules-library`. Determine the `src/` directory.
16
+
17
+ ```bash
18
+ find . -name "package.json" -not -path "*/node_modules/*" | xargs grep -l "javascript-modules-library" 2>/dev/null | head -1
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Step 2 — Collect files to review
24
+
25
+ ```bash
26
+ find src/ -name "definition.cnd" | sort
27
+ find src/ -name "*.server.tsx" | sort
28
+ find src/ -name "*.client.tsx" | sort
29
+ find src/ -name "types.ts" | sort
30
+ find src/templates/ -name "*.server.tsx" | sort
31
+ cat settings/definitions.cnd
32
+ ```
33
+
34
+ Read ALL collected files before starting the review.
35
+
36
+ ---
37
+
38
+ ## Step 3 — Run checks in order of severity
39
+
40
+ ### 🔴 CRITICAL — Will cause broken editor UX, broken pages, or security issues
41
+
42
+ **C1 — `jmix:droppableContent` used directly**
43
+ Check: any CND type (not mixin declarations in settings/definitions.cnd) extending `jmix:droppableContent` directly.
44
+ Fix: extend the module's custom component mixin (e.g. `namespacemix:component`) instead.
45
+
46
+ **C2 — `fullPage` view uses `componentType: "template"`**
47
+ Check: any `name: "fullPage"` view that also has `componentType: "template"`.
48
+ Fix: change to `componentType: "view"`. The `src/templates/MainResource/default.server.tsx` template already routes `jmix:mainResource` nodes to the `fullPage` view.
49
+
50
+ **C3 — `j:linknode` or `j:url` explicitly declared in CND**
51
+ Check: any CND type that explicitly declares `j:linknode` or `j:url` fields alongside `choicelist[linkTypeInitializer]`.
52
+ Fix: remove those two fields from the CND. They are injected automatically by Jahia's mixins at runtime. Only declare `- j:linkType (string, choicelist[linkTypeInitializer])`. The fields remain available in `types.ts` and in the view.
53
+
54
+ **C4 — `j:linkType` used as a URL in a view**
55
+ Check: any `.server.tsx` or `.client.tsx` file that uses `props["j:linkType"]` or `j:linkType` directly as an `href`.
56
+ Fix: use a `switch (props["j:linkType"])` with `buildNodeUrl(props["j:linknode"])` for internal and `props["j:url"]` for external.
57
+
58
+ **C5 — Weakreference node properties read directly in a view (cache issue)**
59
+ Check: views that access `.getPropertyAsString()`, `.getProperty(...)`, or property destructuring from a weakreference prop (other than `buildNodeUrl`).
60
+ Fix: render the referenced node via `<Render node={refNode} view="..." />` to get proper cache invalidation.
61
+
62
+ **C6 — Client component imports server-only APIs**
63
+ Check: any `.client.tsx` file that imports from `@jahia/javascript-modules-library` (except `Island` which is re-exported).
64
+ Fix: move server-side logic to the `.server.tsx` wrapper and pass results as serializable props to the Island.
65
+
66
+ **C7 — Cache explicitly disabled (`cache.expiration="0"`)**
67
+ Check: any `jahiaComponent` call with `properties: { "cache.expiration": "0" }`.
68
+ Fix: never set expiration to 0. If truly fresh data is needed, use a small value like `"5"` (5 seconds) to still protect under load.
69
+
70
+ **C8 — Generic area type used for every Area**
71
+ Check: page templates where every `<Area>` uses the same generic area type (e.g. `nodeType="namespace:pageArea"` everywhere). This means editors see ALL `pageComponent` types as droppable options in every area — a hero section will appear as an option in a feature card grid.
72
+ Fix: create **one typed area node per section** in `settings/definitions.cnd`, each with a tight child constraint:
73
+ ```cnd
74
+ [namespace:heroArea] > jnt:content, jmix:list, jmix:hiddenType orderable
75
+ + * (namespace:heroSection)
76
+
77
+ [namespace:featuresArea] > jnt:content, jmix:list, jmix:hiddenType orderable
78
+ + * (namespace:featureCard)
79
+ ```
80
+ Only use a generic `pageArea` for flexible areas (e.g. footer) where any component is valid.
81
+
82
+ ---
83
+
84
+ ### 🟡 WARNING — Will likely cause editor confusion, stale content, or runtime errors
85
+
86
+ **W1 — User-facing string fields without `i18n`**
87
+ Check: CND string/textarea/richtext properties that don't have `i18n` (exclude system properties like `j:linkType`, `j:url`, non-user-facing fields).
88
+ Fix: add `i18n` to all user-visible text properties.
89
+
90
+ **W2 — `jmix:mainResource` on non-content types**
91
+ Check: CND types that have `jmix:mainResource` but no richtext body or no obvious "detail page" use case (e.g. a visual composition type like a card or hero).
92
+ Fix: only use `jmix:mainResource` for content that genuinely needs both a listing card AND a full-page detail view.
93
+
94
+ **W3 — Structural container types missing `jmix:hiddenType`**
95
+ Check: CND types that have no `namespacemix:component` mixin (so they can't be dropped as components) but also don't have `jmix:hiddenType` — editors would never see them but they don't show up with a clear "hidden" intent.
96
+ Fix: add `jmix:hiddenType` to structural/container types. Do NOT use `jmix:studioOnly` — it can interfere with area rendering.
97
+
98
+ **W4 — Props not typed as optional (`?:`) / not guarded in views**
99
+ Check: (a) `types.ts` props typed as required (`title: string`) — all props must use `?:` because Jahia does not guarantee values are present at render time. (b) Views that use props without null/undefined guards, especially `buildNodeUrl(prop)` — passing `undefined` throws `"Expected a node in buildNodeUrl, received undefined"`.
100
+ Fix: use `?:` for all props in `types.ts`. Add conditional rendering (`{prop && <span>{prop}</span>}`) and guard node URLs (`prop ? { backgroundImage: \`url(${buildNodeUrl(prop)})\` } : undefined`).
101
+
102
+ **W5 — `weakreference multiple` not null-filtered before `.map()`**
103
+ Check: views mapping over a `weakreference multiple` prop without `.filter(x => x !== null)`.
104
+ Fix: `items?.filter(item => item !== null).map(...)`.
105
+
106
+ **W6 — Cache not configured for views using external/dynamic data**
107
+ Check: views that call external functions, use `Date.now()`, or fetch data, without `properties: { "cache.expiration": "..." }`.
108
+ Fix: add `cache.expiration` to the `jahiaComponent` properties.
109
+
110
+ **W7 — Missing `import.xml` or no homepage defined**
111
+ Check: look for `import.xml` at the module root. If absent or if it doesn't contain `j:isHomePage="true"`, editors won't have a default homepage.
112
+ Fix: add an `import.xml` with a homepage node (`j:isHomePage="true"`). Also add "Offline pages/Models", "Offline pages/Drafts", "Offline pages/Archive" folders with `jmix:systemNameReadonly` and `jmix:nolive` mixins. Add content folders with `jmix:contributeMode` restrictions where appropriate.
113
+
114
+ **W8 — Node type extends something other than `jnt:content`**
115
+ Check: CND types that extend anything other than `jnt:content`, `jnt:page`, `jmix:*`, or standard Jahia base types.
116
+ Fix: extend only `jnt:content` (or `jnt:page` for page types). To add fields to a type you don't control, use a mixin with `extends=<targetType>`. Unusual inheritance chains break edition interfaces in unpredictable ways.
117
+
118
+ **W9 — Hardcoded link URLs in views**
119
+ Check: any `.server.tsx`, `.client.tsx`, or template file containing a literal `href="http`, `href="/"`, or `href="/en/` (except in edit-mode chrome helpers). Also flag plain string `src="http` for non-bundled assets. Also flag any content data with `j:linkType: "external"` pointing to a path that looks like an internal Jahia URL (e.g. `/sites/`, `/cms/`, `/en/`).
120
+ Fix: **All navigable URLs must come from contributed content.** Use `j:linkType`/`j:linknode`/`j:url` props for editorial links, `buildNodeUrl(node)` for JCR node links.
121
+ 🚫 **NEVER use `j:linkType: "external"` to link to an internal Jahia page** — use `"internal"` + `j:linknode`. An external URL pointing internally breaks on environment changes, language switches, live/preview workspace toggling, and vanity URL rewrites. If no target page exists yet, omit the link; do not substitute an external workaround.
122
+
123
+ ---
124
+
125
+ ### 🔵 SUGGESTION — Quality improvements
126
+
127
+ **S1 — Non-semantic HTML**
128
+ Check: views that use `<div>` where `<article>`, `<section>`, `<nav>`, `<header>`, or `<footer>` would be more appropriate.
129
+ Fix: use semantic HTML for better accessibility and SEO.
130
+
131
+ **S2 — Images without meaningful alt text**
132
+ Check: `<img>` tags with `alt=""` or no `alt` attribute (unless there's a comment saying it's decorative).
133
+ Fix: add descriptive alt text. Decorative images should have `alt=""` with a comment.
134
+
135
+ **S3 — Accessibility violations (axe-core audit)**
136
+ Check: run `/jahia-dev-accessibility` against all live pages. A clean module has zero `critical` or `serious` violations.
137
+ Common issues in Jahia modules:
138
+ - `color-contrast`: hardcoded colours with insufficient contrast ratio — check with https://webaim.org/resources/contrastchecker/
139
+ - `image-alt`: `<img>` missing a meaningful `alt` prop sourced from CND
140
+ - `button-name`: icon-only `<button>` or `<a>` without `aria-label`
141
+ - `landmark-one-main`: page template missing a `<main>` wrapper
142
+ - `page-has-heading-one`: no `<h1>` rendered on any page
143
+ - `heading-order`: skipped heading levels between components (e.g. h1 → h3)
144
+ - `html-has-lang`: template not setting `lang` via `useServerContext().currentLanguage`
145
+ - `focus-visible` suppressed: global `* { outline: none }` in CSS kills keyboard navigation
146
+
147
+ Fix: identify each violating component by matching the axe target selector to a `.server.tsx` file, apply the fix, rebuild, and re-run the audit.
148
+
149
+ **S4 — Types using `any`**
150
+ Check: `types.ts` files or view files using TypeScript `any`.
151
+ Fix: use `JCRNodeWrapper` for node references, `string` / `number` / `boolean` for primitives.
152
+
153
+ **S5 — Bare `<Area>` without a `nodeType`**
154
+ Check: page templates using `<Area name="..." />` without a `nodeType` prop.
155
+ Fix: create a custom area type with `jmix:list`, `jmix:hiddenType`, and `orderable`, and reference it with `nodeType="namespace:areaType"`.
156
+
157
+ **S6 — `mix:title` inherited but `jcr:title` not in `types.ts`**
158
+ Check: CND types that extend `mix:title` but whose `types.ts` doesn't include `"jcr:title": string`.
159
+ Fix: add `"jcr:title"?: string` to the Props type.
160
+
161
+ **S7 — Missing `.properties` file entries or icon for new content types**
162
+ Check: for each node type found in `definition.cnd` files, verify that `settings/resources/<module>.properties` has a label (`cndNamespace_typeName=...`) and a corresponding icon exists at `settings/content-types-icons/<cndNamespace>_<typeName>.png`. The prefix must be the CND namespace (e.g. `ns_heroSection.png`), **not** the module name with hyphens (e.g. `my-module_heroSection.png` is wrong — the archetype generates wrong names that must be manually corrected).
163
+ Fix: add labels (and optionally `ui.tooltip` for fields) to the properties files. Rename any icons that use the module name with hyphens to use the CND namespace. Create a 32×32 PNG icon (free source: [flaticon.com](https://www.flaticon.com/)). Without these, editors see raw technical names and blank icon squares in the content picker.
164
+
165
+ **S8 — Hardcoded user-visible strings in views**
166
+ Check: `.server.tsx` / `.client.tsx` files with JSX string literals that are not coming from props or i18n functions (e.g. `<p>Learn more</p>`, `<button>Submit</button>`).
167
+ Fix: move UI labels to `settings/locales/en.json` and `fr.json` and resolve them with `useTranslation()`. Hardcoded strings break multilingual sites.
168
+
169
+ **S9 — Content list queries not using `ISDESCENDANTNODE` (non-recursive)**
170
+ Check: JCR-SQL2 queries using `jcr:path LIKE '/sites/.../content/%'` or a fixed path to limit results, instead of `ISDESCENDANTNODE(node, '/sites/.../content')`.
171
+ Fix: use `ISDESCENDANTNODE` to ensure queries work correctly even if editors reorganize content into sub-folders.
172
+
173
+ **S10 — No escape hatch when using a custom component mixin**
174
+ Check: a custom section type that restricts children to a custom mixin (e.g. `+ * (namespacemix:component)`) but the module provides no "content stack" escape hatch type that itself accepts `jmix:droppableContent`.
175
+ Fix: add a `namespace:contentStack > jnt:content, namespacemix:component + * (jmix:droppableContent)` type so power editors can still add arbitrary content when needed.
176
+
177
+ **S11 — Scaffold/boilerplate components still present**
178
+ Check: components under `src/components/Hello/` (or any other archetype-generated boilerplate) that are no longer referenced in `settings/import.xml` and no longer used by any view or page template.
179
+ ```bash
180
+ # Check if Hello components are still referenced anywhere
181
+ grep -r "helloWorld\|helloCard\|Hello/" src/templates/ settings/ --include="*.tsx" --include="*.xml" --include="*.cnd"
182
+ ```
183
+ Fix: once `import.xml` no longer provisions Hello World content and no template uses them, delete the entire `src/components/Hello/` directory, remove their entries from `.properties` files, and delete their icons from `settings/content-types-icons/`. Keeping dead components inflates the content picker and confuses editors.
184
+
185
+ ---
186
+
187
+ ## Step 4 — Report results
188
+
189
+ Format the output as:
190
+
191
+ ```
192
+ ## Jahia Module Review — <module name>
193
+
194
+ ### 🔴 Critical (N issues)
195
+ [C1] src/components/Hero/Section/definition.cnd — `jmix:droppableContent` used directly
196
+ Fix: extend `namespacemix:component` instead
197
+
198
+ ### 🟡 Warnings (N issues)
199
+ ...
200
+
201
+ ### 🔵 Suggestions (N issues)
202
+ ...
203
+
204
+ ### ✅ Summary
205
+ - N critical issues (must fix before shipping)
206
+ - N warnings (fix before sharing with editors)
207
+ - N suggestions (improve when time allows)
208
+ ```
209
+
210
+ If no issues found in a category, print `✅ None`.
211
+
212
+ ---
213
+
214
+ ## Step 5 — Ask to fix
215
+
216
+ After the report, ask: **"Would you like me to fix any of these issues?"**
217
+
218
+ If yes, fix them — use the guidance from the relevant skill (`jahia-cnd-author`, `jahia-dev-create-view`) and run `yarn build && yarn jahia-deploy` to push changes.
219
+
220
+ ---
221
+
222
+ ## References
223
+
224
+ - Native Jahia mixins & node types: https://github.com/Jahia/jahia/tree/master/war/src/main/webapp/WEB-INF/etc/repository/nodetypes
225
+ - Integration best practices: https://github.com/Jahia/gautier-braindump/blob/main/articles/integration-best-practices/README.md
226
+ - Developer training: https://github.com/Jahia/developer-training/blob/main/js-training/slides.md
227
+
228
+ > If a check result is uncertain (e.g. "does this mixin exist?"), fetch the nodetypes directory above before reporting.
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: jahia-review-site
3
+ description: Scores live pages for accessibility and SEO.
4
+ allowed-tools: Bash, Read, Write, Edit
5
+ ---
6
+
7
+ # Skill: jahia-review-site
8
+
9
+ Reads URLs from `pages-to-review.json`, runs automated a11y and SEO checks, and — only if all checks pass — writes `pages.json`. Exits non-zero without writing `pages.json` if any violation is found.
10
+
11
+ **A11y:** axe-core full ruleset (WCAG + best-practice) — any violation fails.
12
+
13
+ **SEO:** Lighthouse SEO category — any failing audit fails. Violations reported by audit ID (e.g. `document-title`, `meta-description`, `hreflang`, `is-crawlable`, `link-text`, `image-alt`).
14
+
15
+ ---
16
+
17
+ ## Step 1 — Ensure tooling is installed
18
+
19
+ ```bash
20
+ node -e "require('@axe-core/playwright'); require('playwright')" 2>/dev/null || \
21
+ npm install --no-save @axe-core/playwright playwright && npx playwright install chromium --with-deps
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Step 2 — Run the review
27
+
28
+ ```bash
29
+ SCRIPT=$(find .claude .agents -name "review-pages.mjs" 2>/dev/null | head -1)
30
+ node "$SCRIPT" 2>&1 | tee /tmp/site-review.txt
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Step 3 — Interpret and fix
36
+
37
+ The script exits 1 if any page has any a11y violation or any failing Lighthouse SEO audit.
38
+
39
+ After fixing, redeploy and re-run:
40
+
41
+ ```bash
42
+ yarn build && yarn jahia-deploy
43
+ node "$SCRIPT"
44
+ ```
45
+
46
+ Iterate until the script exits 0 and `pages.json` is written.
47
+
48
+ ---
49
+
50
+ ## Validation checklist
51
+ - [ ] Script exits 0 (zero a11y violations, zero failing SEO audits)
52
+ - [ ] `pages.json` exists (created by the script on pass)
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ // Reads URLs from pages-to-review.json, runs a11y (axe-core) + SEO (Lighthouse) checks.
3
+ // On pass: writes pages.json and exits 0. On fail: exits 1 without writing pages.json.
4
+ import { chromium } from "playwright";
5
+ import { AxeBuilder } from "@axe-core/playwright";
6
+ import lighthouse from "lighthouse";
7
+ import { readFileSync, writeFileSync } from "fs";
8
+
9
+ const SCORED_MODES = new Set(["binary", "numeric"]);
10
+
11
+ const draft = readFileSync("pages-to-review.json", "utf-8");
12
+ const urls = JSON.parse(draft);
13
+ const port = 9222;
14
+ const browser = await chromium.launch({
15
+ args: ["--no-sandbox", `--remote-debugging-port=${port}`],
16
+ });
17
+ const context = await browser.newContext();
18
+ const page = await context.newPage();
19
+
20
+ const results = [];
21
+
22
+ for (const url of urls) {
23
+ process.stdout.write(`\nChecking ${url} … `);
24
+ await page.goto(url, { waitUntil: "networkidle", timeout: 30_000 });
25
+
26
+ // A11y
27
+ const axe = await new AxeBuilder({ page }).analyze();
28
+
29
+ // SEO — Lighthouse reuses the already-open browser via the debug port
30
+ const lhr = (await lighthouse(url, { port, output: "json", onlyCategories: ["seo"] }))?.lhr;
31
+ const seoViolations = lhr
32
+ ? Object.values(lhr.audits).filter(
33
+ a => SCORED_MODES.has(a.scoreDisplayMode) && a.score !== null && a.score < 1,
34
+ )
35
+ : [];
36
+
37
+ process.stdout.write(`a11y=${axe.violations.length} violation(s) seo=${seoViolations.length} violation(s)\n`);
38
+ results.push({ url, violations: axe.violations, seoViolations });
39
+ }
40
+
41
+ await browser.close();
42
+
43
+ // ── Report ──────────────────────────────────────────────────────────────────
44
+ let failed = false;
45
+
46
+ for (const r of results) {
47
+ const pageOk = r.violations.length === 0 && r.seoViolations.length === 0;
48
+ if (!pageOk) failed = true;
49
+
50
+ console.log(`\n${"─".repeat(70)}`);
51
+ console.log(`${pageOk ? "✅" : "❌"} ${r.url}`);
52
+
53
+ for (const v of r.violations) {
54
+ console.log(` ❌ A11y [${v.id}] — ${v.description} (${v.nodes.length} node${v.nodes.length !== 1 ? "s" : ""})`);
55
+ for (const node of v.nodes.slice(0, 3)) {
56
+ console.log(` ${node.html.slice(0, 100)}`);
57
+ }
58
+ }
59
+
60
+ for (const a of r.seoViolations) {
61
+ console.log(` ❌ SEO [${a.id}] — ${a.title}`);
62
+ if (a.description) console.log(` ${a.description.slice(0, 120)}`);
63
+ }
64
+ }
65
+
66
+ console.log(`\n${"═".repeat(70)}`);
67
+ if (failed) {
68
+ console.log("\n❌ FAIL — fix the issues above, redeploy, and re-run.");
69
+ process.exit(1);
70
+ } else {
71
+ writeFileSync("pages.json", draft);
72
+ console.log("\n✅ PASS — pages.json written.");
73
+ process.exit(0);
74
+ }