@jahia/agentic 0.3.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 (153) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/claude/.claude/agents/cnd-child-nodes.md +74 -0
  3. package/dist/claude/.claude/agents/cnd-jahia-mixins.md +113 -0
  4. package/dist/claude/.claude/agents/cnd-numbers-dates.md +61 -0
  5. package/dist/claude/.claude/agents/cnd-string-selectors.md +94 -0
  6. package/dist/claude/.claude/agents/jahia-cnd-author.md +130 -0
  7. package/dist/claude/.claude/agents/jahia-dev-worker.md +264 -0
  8. package/dist/claude/.claude/agents/jahia-reviewer.md +105 -0
  9. package/dist/claude/.claude/rules/jahia.md +15 -6
  10. package/dist/claude/.claude/skills/jahia/SKILL.md +5 -1
  11. package/dist/claude/.claude/skills/jahia-dev-accessibility/SKILL.md +3 -3
  12. package/dist/claude/.claude/skills/jahia-dev-build-component/SKILL.md +10 -7
  13. package/dist/claude/.claude/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  14. package/dist/claude/.claude/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  15. package/dist/claude/.claude/skills/jahia-dev-create-view/SKILL.md +3 -3
  16. package/dist/claude/.claude/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  17. package/dist/claude/.claude/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  18. package/dist/claude/.claude/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  19. package/dist/claude/.claude/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  20. package/dist/claude/.claude/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  21. package/dist/claude/.claude/skills/jahia-dev-site-review/SKILL.md +70 -0
  22. package/dist/claude/.claude/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  23. package/dist/claude/.claude/skills/jahia-dev-start-local/SKILL.md +18 -26
  24. package/dist/claude/.claude/skills/jahia-orchestrate/SKILL.md +148 -0
  25. package/dist/claude/.claude/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  26. package/dist/claude/CLAUDE.md +16 -7
  27. package/dist/codex/.agents/skills/jahia/SKILL.md +5 -1
  28. package/dist/codex/.agents/skills/jahia-dev-accessibility/SKILL.md +3 -3
  29. package/dist/codex/.agents/skills/jahia-dev-build-component/SKILL.md +10 -7
  30. package/dist/codex/.agents/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  31. package/dist/codex/.agents/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  32. package/dist/codex/.agents/skills/jahia-dev-create-view/SKILL.md +3 -3
  33. package/dist/codex/.agents/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  34. package/dist/codex/.agents/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  35. package/dist/codex/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  36. package/dist/codex/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  37. package/dist/codex/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  38. package/dist/codex/.agents/skills/jahia-dev-site-review/SKILL.md +70 -0
  39. package/dist/codex/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  40. package/dist/codex/.agents/skills/jahia-dev-start-local/SKILL.md +18 -26
  41. package/dist/codex/.agents/skills/jahia-orchestrate/SKILL.md +148 -0
  42. package/dist/codex/.agents/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  43. package/dist/codex/.codex/agents/cnd-child-nodes.toml +3 -0
  44. package/dist/codex/.codex/agents/cnd-jahia-mixins.toml +3 -0
  45. package/dist/codex/.codex/agents/cnd-numbers-dates.toml +3 -0
  46. package/dist/codex/.codex/agents/cnd-string-selectors.toml +3 -0
  47. package/dist/codex/.codex/agents/jahia-cnd-author.toml +3 -0
  48. package/dist/codex/.codex/agents/jahia-dev-worker.toml +3 -0
  49. package/dist/codex/.codex/agents/jahia-reviewer.toml +3 -0
  50. package/dist/codex/AGENTS.md +17 -8
  51. package/dist/copilot/.agents/skills/jahia/SKILL.md +5 -1
  52. package/dist/copilot/.agents/skills/jahia-dev-accessibility/SKILL.md +3 -3
  53. package/dist/copilot/.agents/skills/jahia-dev-build-component/SKILL.md +10 -7
  54. package/dist/copilot/.agents/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  55. package/dist/copilot/.agents/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  56. package/dist/copilot/.agents/skills/jahia-dev-create-view/SKILL.md +3 -3
  57. package/dist/copilot/.agents/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  58. package/dist/copilot/.agents/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  59. package/dist/copilot/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  60. package/dist/copilot/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  61. package/dist/copilot/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  62. package/dist/copilot/.agents/skills/jahia-dev-site-review/SKILL.md +70 -0
  63. package/dist/copilot/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  64. package/dist/copilot/.agents/skills/jahia-dev-start-local/SKILL.md +18 -26
  65. package/dist/copilot/.agents/skills/jahia-orchestrate/SKILL.md +148 -0
  66. package/dist/copilot/.agents/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  67. package/dist/copilot/AGENTS.md +17 -8
  68. package/dist/cursor/.agents/skills/jahia/SKILL.md +5 -1
  69. package/dist/cursor/.agents/skills/jahia-dev-accessibility/SKILL.md +3 -3
  70. package/dist/cursor/.agents/skills/jahia-dev-build-component/SKILL.md +10 -7
  71. package/dist/cursor/.agents/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  72. package/dist/cursor/.agents/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  73. package/dist/cursor/.agents/skills/jahia-dev-create-view/SKILL.md +3 -3
  74. package/dist/cursor/.agents/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  75. package/dist/cursor/.agents/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  76. package/dist/cursor/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  77. package/dist/cursor/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  78. package/dist/cursor/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  79. package/dist/cursor/.agents/skills/jahia-dev-site-review/SKILL.md +70 -0
  80. package/dist/cursor/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  81. package/dist/cursor/.agents/skills/jahia-dev-start-local/SKILL.md +18 -26
  82. package/dist/cursor/.agents/skills/jahia-orchestrate/SKILL.md +148 -0
  83. package/dist/cursor/.agents/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  84. package/dist/cursor/.cursor/agents/cnd-child-nodes.md +74 -0
  85. package/dist/cursor/.cursor/agents/cnd-jahia-mixins.md +113 -0
  86. package/dist/cursor/.cursor/agents/cnd-numbers-dates.md +61 -0
  87. package/dist/cursor/.cursor/agents/cnd-string-selectors.md +94 -0
  88. package/dist/cursor/.cursor/agents/jahia-cnd-author.md +130 -0
  89. package/dist/cursor/.cursor/agents/jahia-dev-worker.md +264 -0
  90. package/dist/cursor/.cursor/agents/jahia-reviewer.md +105 -0
  91. package/dist/cursor/.cursor/rules/jahia.mdc +15 -6
  92. package/dist/gemini/.agents/skills/jahia/SKILL.md +5 -1
  93. package/dist/gemini/.agents/skills/jahia-dev-accessibility/SKILL.md +3 -3
  94. package/dist/gemini/.agents/skills/jahia-dev-build-component/SKILL.md +10 -7
  95. package/dist/gemini/.agents/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  96. package/dist/gemini/.agents/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  97. package/dist/gemini/.agents/skills/jahia-dev-create-view/SKILL.md +3 -3
  98. package/dist/gemini/.agents/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  99. package/dist/gemini/.agents/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  100. package/dist/gemini/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  101. package/dist/gemini/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  102. package/dist/gemini/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  103. package/dist/gemini/.agents/skills/jahia-dev-site-review/SKILL.md +70 -0
  104. package/dist/gemini/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  105. package/dist/gemini/.agents/skills/jahia-dev-start-local/SKILL.md +18 -26
  106. package/dist/gemini/.agents/skills/jahia-orchestrate/SKILL.md +148 -0
  107. package/dist/gemini/.agents/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  108. package/dist/gemini/AGENTS.md +17 -8
  109. package/dist/gemini/GEMINI.md +2 -2
  110. package/dist/index.js +13 -0
  111. package/dist/opencode/.agents/skills/jahia/SKILL.md +5 -1
  112. package/dist/opencode/.agents/skills/jahia-dev-accessibility/SKILL.md +3 -3
  113. package/dist/opencode/.agents/skills/jahia-dev-build-component/SKILL.md +10 -7
  114. package/dist/opencode/.agents/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  115. package/dist/opencode/.agents/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  116. package/dist/opencode/.agents/skills/jahia-dev-create-view/SKILL.md +3 -3
  117. package/dist/opencode/.agents/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  118. package/dist/opencode/.agents/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  119. package/dist/opencode/.agents/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  120. package/dist/opencode/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  121. package/dist/opencode/.agents/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  122. package/dist/opencode/.agents/skills/jahia-dev-site-review/SKILL.md +70 -0
  123. package/dist/opencode/.agents/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  124. package/dist/opencode/.agents/skills/jahia-dev-start-local/SKILL.md +18 -26
  125. package/dist/opencode/.agents/skills/jahia-orchestrate/SKILL.md +148 -0
  126. package/dist/opencode/.agents/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  127. package/dist/opencode/.opencode/agents/cnd-child-nodes.md +74 -0
  128. package/dist/opencode/.opencode/agents/cnd-jahia-mixins.md +113 -0
  129. package/dist/opencode/.opencode/agents/cnd-numbers-dates.md +61 -0
  130. package/dist/opencode/.opencode/agents/cnd-string-selectors.md +94 -0
  131. package/dist/opencode/.opencode/agents/jahia-cnd-author.md +130 -0
  132. package/dist/opencode/.opencode/agents/jahia-dev-worker.md +264 -0
  133. package/dist/opencode/.opencode/agents/jahia-reviewer.md +105 -0
  134. package/dist/opencode/AGENTS.md +17 -8
  135. package/dist/windsurf/.windsurf/rules/jahia.md +15 -6
  136. package/dist/windsurf/.windsurf/skills/jahia/SKILL.md +5 -1
  137. package/dist/windsurf/.windsurf/skills/jahia-dev-accessibility/SKILL.md +3 -3
  138. package/dist/windsurf/.windsurf/skills/jahia-dev-build-component/SKILL.md +10 -7
  139. package/dist/windsurf/.windsurf/skills/jahia-dev-create-page-template/SKILL.md +59 -21
  140. package/dist/windsurf/.windsurf/skills/jahia-dev-create-template-set/SKILL.md +20 -47
  141. package/dist/windsurf/.windsurf/skills/jahia-dev-create-view/SKILL.md +3 -3
  142. package/dist/windsurf/.windsurf/skills/jahia-dev-define-content-type/SKILL.md +43 -486
  143. package/dist/windsurf/.windsurf/skills/jahia-dev-define-content-type/references/modeling-decisions.md +52 -0
  144. package/dist/windsurf/.windsurf/skills/jahia-dev-review-cnd/SKILL.md +79 -0
  145. package/dist/windsurf/.windsurf/skills/jahia-dev-review-cnd/scripts/check-cnd.d.mts +13 -0
  146. package/dist/windsurf/.windsurf/skills/jahia-dev-review-cnd/scripts/check-cnd.mjs +198 -0
  147. package/dist/windsurf/.windsurf/skills/jahia-dev-site-review/SKILL.md +70 -0
  148. package/dist/windsurf/.windsurf/skills/jahia-dev-site-review/scripts/review-pages.mjs +85 -0
  149. package/dist/windsurf/.windsurf/skills/jahia-dev-start-local/SKILL.md +18 -26
  150. package/dist/windsurf/.windsurf/skills/jahia-orchestrate/SKILL.md +148 -0
  151. package/dist/windsurf/.windsurf/skills/jahia-orchestrate/scripts/verify-pages.mjs +59 -0
  152. package/dist/windsurf/AGENTS.md +17 -8
  153. package/package.json +1 -1
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: jahia-dev-review-cnd
3
+ description: Use after writing any CND file to validate it against Jahia best practices. Runs the deterministic cnd-checker script and reports PASS / FAIL with file:line citations and fixes. Run /jahia-dev-review-cnd <path> to check a specific file or directory, or without arguments to check all CND files in the current module.
4
+ allowed-tools: Bash
5
+ ---
6
+
7
+ ## Step 1 — Run the checker
8
+
9
+ ```bash
10
+ CND_SCRIPT=$(find .claude .agents -name "check-cnd.mjs" 2>/dev/null | head -1)
11
+ node "$CND_SCRIPT" <path-to-file-or-directory>
12
+ # or, to check all CND files in the module:
13
+ node "$CND_SCRIPT" src/
14
+ ```
15
+
16
+ The script exits with code 0 for PASS and code 1 for FAIL.
17
+
18
+ ## Step 2 — Fix and repeat until clean
19
+
20
+ This is a loop. Run the checker, fix every issue reported, run it again. Repeat until the result is `PASS`.
21
+
22
+ - **FAIL** — fix every issue, re-run. Do not proceed until exit code is 0.
23
+ - **PASS** — clean. Continue.
24
+
25
+ ---
26
+
27
+ ## Antipattern reference
28
+
29
+ The checker enforces these patterns. Use this as a guide when interpreting output or fixing issues manually.
30
+
31
+ ### `rawStringLink`
32
+ Property whose name contains `link`, `url`, `href`, or `path` declared as `(string)`.
33
+ **Fix**: Use the link picker:
34
+ ```cnd
35
+ - j:linkType (string, choicelist[linkTypeInitializer]) mandatory
36
+ ```
37
+
38
+ ### `singleHardcodedCta`
39
+ A type with both a CTA label (`ctaText`, `ctaLabel`, `buttonText`, `buttonLabel`) and a CTA link (`ctaLink`, `ctaUrl`, `ctaHref`, `buttonLink`) as flat properties, with no child node.
40
+ **Fix**: Replace with a child node:
41
+ ```cnd
42
+ + * (ns:cta)
43
+
44
+ [ns:cta] > jnt:content, nsmix:component
45
+ - label (string) i18n mandatory
46
+ - j:linkType (string, choicelist[linkTypeInitializer]) mandatory
47
+ ```
48
+
49
+ ### `directDroppable`
50
+ A concrete type extending `jmix:droppableContent` directly.
51
+ **Fix**: Extend the module mixin: `[ns:hero] > jnt:content, nsmix:component`
52
+
53
+ ### `missingRatingConstraint`
54
+ `rating (long)` without a range constraint — unconstrained ratings cause data integrity issues.
55
+ **Fix**: Add `< "[1,5]"`
56
+
57
+ ### `redundantImageAlt`
58
+ `imageAlt (string)` alongside an image weakreference. The image node already has `jcr:title`.
59
+ **Fix**: Remove `imageAlt`. In the view: `image.getPropertyAsString("jcr:title") ?? ""`
60
+
61
+ ### `rawTitleProp`
62
+ Property named `title`, `heroTitle`, `pageTitle`, or `sectionTitle` typed as `(string)`.
63
+ **Fix**: Remove it, extend `mix:title`. Access as `props["jcr:title"]`.
64
+
65
+ ### `weakrefNoConstraint`
66
+ `(weakreference)` with no `< ` type constraint.
67
+ **Fix**: Add constraint — `< jmix:image` for images, `< jnt:file` for files.
68
+
69
+ ### `weakrefWrongConstraint`
70
+ `< 'jnt:file'` (quoted form).
71
+ **Fix**: `< jmix:image` (unquoted).
72
+
73
+ ### `missingI18n`
74
+ User-visible string (`title`, `text`, `label`, `description`, `subtitle`, `caption`, `alt`, `heading`, `summary`, `excerpt`, `body`) without `i18n`.
75
+ **Fix**: Add `i18n` after the type declaration.
76
+
77
+ ### `studioOnly`
78
+ Any use of `jmix:studioOnly`.
79
+ **Fix**: Replace with `jmix:hiddenType`.
@@ -0,0 +1,13 @@
1
+ export interface CndIssue {
2
+ file: string;
3
+ line?: number;
4
+ pattern: string;
5
+ message: string;
6
+ fix: string;
7
+ }
8
+
9
+ export function checkCndFiles(projectDir: string): {
10
+ score: number;
11
+ issues: CndIssue[];
12
+ filesChecked: number;
13
+ };
@@ -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,148 @@
1
+ ---
2
+ name: jahia-orchestrate
3
+ description: Orchestrates building a Jahia module from a task description. Writes a build plan, delegates development and review to subagents in a loop, and exits cleanly. Keeps the orchestrator context lean — reads only small status files, never source code.
4
+ ---
5
+
6
+ You are the Jahia build orchestrator. Your role is coordination, not execution. You keep your context lean by delegating all code work to subagents and communicating only through small status files.
7
+
8
+ **Max iterations: 3.** If the reviewer still reports NEEDS_WORK after 3 cycles, proceed anyway with the best available state.
9
+
10
+ ---
11
+
12
+ ## Step 1 — Parse the task
13
+
14
+ Read the task description from your context. Identify:
15
+ - The module being built (site name, company type, pages, components)
16
+ - The module path (working directory, or check `PLAN.md` if already set up)
17
+ - Any efficiency rules already provided
18
+
19
+ Determine the module path:
20
+ ```bash
21
+ pwd
22
+ ls package.json 2>/dev/null && cat package.json | grep '"name"' | head -1
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Step 2 — Write PLAN.md
28
+
29
+ Write `PLAN.md` in the project root with the full build spec. Use the format:
30
+
31
+ ```
32
+ # Build Plan — Round 1
33
+
34
+ ## Module
35
+ - Path: <absolute path from pwd>
36
+ - Site key: <inferred from package.json name or task description>
37
+ - Namespace: <infer from task, e.g. "forsure">
38
+
39
+ ## Pages
40
+ <list from task description>
41
+
42
+ ## Page Template
43
+ - `src/templates/<ModuleName>Template/default.server.tsx` — root layout with `<header>` (nav), `<main>` (areas), `<footer>`, and `<title>` tag
44
+ - Build this FIRST before any page-specific components
45
+
46
+ ## Components
47
+ <list from task description with field descriptions>
48
+
49
+ ## Efficiency Rules
50
+ - ONE build+deploy at the end — do not deploy after each component
51
+ - Skip UI validation in Page Builder
52
+ - Write CND directly — load cnd reference files from .claude/agents/ for patterns
53
+ - Use MCP tools for all content operations
54
+
55
+ ## Done when
56
+ - Page template built with header/nav/main/footer
57
+ - All components have definition.cnd + default.server.tsx + component.module.css
58
+ - yarn build && yarn jahia-deploy succeeded
59
+ - All pages created via MCP and published
60
+ - pages.json written as array of public URLs
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Step 3 — Development cycle
66
+
67
+ Set `round = 1`.
68
+
69
+ **3a. Spawn the developer worker:**
70
+
71
+ Use the Agent tool to spawn `@jahia-dev-worker`. Pass this prompt:
72
+
73
+ > "Read PLAN.md in the current directory. You are the Jahia developer worker — follow the instructions in your agent file. Module path: <absolute path>."
74
+
75
+ Do NOT read the agent's returned output. After the agent completes, proceed to 3b.
76
+
77
+ **3b. Read DEV_STATUS.md:**
78
+
79
+ ```bash
80
+ cat DEV_STATUS.md
81
+ ```
82
+
83
+ If Status is FAILED and the failure is unrecoverable (e.g. Jahia never started), stop here and report the failure. Otherwise continue.
84
+
85
+ ---
86
+
87
+ ## Step 4 — Review cycle
88
+
89
+ **4a. Spawn the code reviewer:**
90
+
91
+ Use the Agent tool to spawn `@jahia-reviewer`. Pass this prompt:
92
+
93
+ > "Read REVIEW.md (if it exists) for round context, then review the current source code. Write REVIEW.md with your findings for round <round>."
94
+
95
+ Do NOT read the agent's returned output. After the agent completes, proceed to 4b.
96
+
97
+ **4b. Read REVIEW.md:**
98
+
99
+ ```bash
100
+ cat REVIEW.md
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Step 5 — Decide: loop or finish
106
+
107
+ - If REVIEW.md says `Verdict: PASS` → proceed to Step 6.
108
+ - If `Verdict: NEEDS_WORK` and `round < 3`:
109
+ - Increment round.
110
+ - Append a "## Round N Fix-Ups" section to `PLAN.md` with the critical issues from REVIEW.md.
111
+ - Return to Step 3.
112
+ - If `Verdict: NEEDS_WORK` and `round >= 3`:
113
+ - Log "Max iterations reached — proceeding with current state."
114
+ - Proceed to Step 6.
115
+
116
+ ---
117
+
118
+ ## Step 6 — Verify pages.json and page quality
119
+
120
+ ```bash
121
+ cat pages.json 2>/dev/null || echo "MISSING"
122
+ ```
123
+
124
+ If missing, spawn `@jahia-dev-worker` once more:
125
+ > "Deploy is already done. Only create content via MCP, verify all pages render correctly, and write pages.json. Read PLAN.md for the page list."
126
+
127
+ If present, verify every URL actually renders real content — a file that exists but points to error pages is a failed run:
128
+
129
+ ```bash
130
+ node scripts/verify-pages.mjs
131
+ ```
132
+
133
+ If any URL shows `ERROR_PAGE` or a non-200 status, spawn `@jahia-dev-worker` with:
134
+ > "Pages are failing. Read DEV_STATUS.md and REVIEW.md for context, investigate the render errors, fix them, redeploy, and re-verify all pages. Do not update pages.json unless all pages pass."
135
+
136
+ ---
137
+
138
+ ## Step 7 — Done
139
+
140
+ Report the outcome:
141
+
142
+ ```
143
+ Orchestration complete.
144
+ - Rounds: N
145
+ - Dev status: <from DEV_STATUS.md>
146
+ - Review verdict: <from REVIEW.md>
147
+ - pages.json: <present/missing>
148
+ ```