@harness-engineering/cli 1.7.0 → 1.8.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 (130) hide show
  1. package/dist/agents/personas/documentation-maintainer.yaml +3 -1
  2. package/dist/agents/personas/performance-guardian.yaml +23 -0
  3. package/dist/agents/skills/claude-code/align-documentation/SKILL.md +13 -0
  4. package/dist/agents/skills/claude-code/cleanup-dead-code/SKILL.md +25 -1
  5. package/dist/agents/skills/claude-code/cleanup-dead-code/skill.yaml +5 -2
  6. package/dist/agents/skills/claude-code/detect-doc-drift/SKILL.md +12 -0
  7. package/dist/agents/skills/claude-code/enforce-architecture/SKILL.md +48 -1
  8. package/dist/agents/skills/claude-code/enforce-architecture/skill.yaml +5 -2
  9. package/dist/agents/skills/claude-code/harness-accessibility/SKILL.md +7 -0
  10. package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +9 -1
  11. package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +76 -4
  12. package/dist/agents/skills/claude-code/harness-brainstorming/skill.yaml +2 -0
  13. package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +487 -234
  14. package/dist/agents/skills/claude-code/harness-code-review/skill.yaml +15 -2
  15. package/dist/agents/skills/claude-code/harness-codebase-cleanup/SKILL.md +226 -0
  16. package/dist/agents/skills/claude-code/harness-codebase-cleanup/skill.yaml +64 -0
  17. package/dist/agents/skills/claude-code/harness-dependency-health/SKILL.md +35 -6
  18. package/dist/agents/skills/claude-code/harness-docs-pipeline/SKILL.md +460 -0
  19. package/dist/agents/skills/claude-code/harness-docs-pipeline/skill.yaml +69 -0
  20. package/dist/agents/skills/claude-code/harness-execution/SKILL.md +73 -8
  21. package/dist/agents/skills/claude-code/harness-execution/skill.yaml +1 -0
  22. package/dist/agents/skills/claude-code/harness-hotspot-detector/SKILL.md +32 -6
  23. package/dist/agents/skills/claude-code/harness-i18n/SKILL.md +484 -0
  24. package/dist/agents/skills/claude-code/harness-i18n/skill.yaml +54 -0
  25. package/dist/agents/skills/claude-code/harness-i18n-process/SKILL.md +388 -0
  26. package/dist/agents/skills/claude-code/harness-i18n-process/skill.yaml +43 -0
  27. package/dist/agents/skills/claude-code/harness-i18n-workflow/SKILL.md +512 -0
  28. package/dist/agents/skills/claude-code/harness-i18n-workflow/skill.yaml +53 -0
  29. package/dist/agents/skills/claude-code/harness-impact-analysis/SKILL.md +35 -6
  30. package/dist/agents/skills/claude-code/harness-integrity/SKILL.md +17 -1
  31. package/dist/agents/skills/claude-code/harness-knowledge-mapper/SKILL.md +46 -5
  32. package/dist/agents/skills/claude-code/harness-perf/SKILL.md +37 -8
  33. package/dist/agents/skills/claude-code/harness-perf/skill.yaml +3 -0
  34. package/dist/agents/skills/claude-code/harness-perf-tdd/SKILL.md +17 -4
  35. package/dist/agents/skills/claude-code/harness-planning/SKILL.md +57 -3
  36. package/dist/agents/skills/claude-code/harness-planning/skill.yaml +2 -0
  37. package/dist/agents/skills/claude-code/harness-release-readiness/SKILL.md +16 -0
  38. package/dist/agents/skills/claude-code/harness-roadmap/SKILL.md +562 -0
  39. package/dist/agents/skills/claude-code/harness-roadmap/skill.yaml +43 -0
  40. package/dist/agents/skills/claude-code/harness-security-review/SKILL.md +36 -2
  41. package/dist/agents/skills/claude-code/harness-security-review/skill.yaml +8 -6
  42. package/dist/agents/skills/claude-code/harness-soundness-review/SKILL.md +1267 -0
  43. package/dist/agents/skills/claude-code/harness-soundness-review/skill.yaml +48 -0
  44. package/dist/agents/skills/claude-code/harness-test-advisor/SKILL.md +35 -6
  45. package/dist/agents/skills/claude-code/harness-verification/SKILL.md +66 -0
  46. package/dist/agents/skills/claude-code/harness-verification/skill.yaml +1 -0
  47. package/dist/agents/skills/claude-code/harness-verify/SKILL.md +11 -0
  48. package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +15 -1
  49. package/dist/agents/skills/claude-code/validate-context-engineering/SKILL.md +12 -0
  50. package/dist/agents/skills/gemini-cli/harness-accessibility/SKILL.md +7 -0
  51. package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +9 -1
  52. package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/SKILL.md +226 -0
  53. package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/skill.yaml +64 -0
  54. package/dist/agents/skills/gemini-cli/harness-dependency-health/SKILL.md +35 -6
  55. package/dist/agents/skills/gemini-cli/harness-docs-pipeline/SKILL.md +460 -0
  56. package/dist/agents/skills/gemini-cli/harness-docs-pipeline/skill.yaml +69 -0
  57. package/dist/agents/skills/gemini-cli/harness-hotspot-detector/SKILL.md +32 -6
  58. package/dist/agents/skills/gemini-cli/harness-i18n/SKILL.md +484 -0
  59. package/dist/agents/skills/gemini-cli/harness-i18n/skill.yaml +54 -0
  60. package/dist/agents/skills/gemini-cli/harness-i18n-process/SKILL.md +388 -0
  61. package/dist/agents/skills/gemini-cli/harness-i18n-process/skill.yaml +43 -0
  62. package/dist/agents/skills/gemini-cli/harness-i18n-workflow/SKILL.md +512 -0
  63. package/dist/agents/skills/gemini-cli/harness-i18n-workflow/skill.yaml +53 -0
  64. package/dist/agents/skills/gemini-cli/harness-impact-analysis/SKILL.md +35 -6
  65. package/dist/agents/skills/gemini-cli/harness-knowledge-mapper/SKILL.md +46 -5
  66. package/dist/agents/skills/gemini-cli/harness-perf/SKILL.md +37 -8
  67. package/dist/agents/skills/gemini-cli/harness-perf/skill.yaml +3 -0
  68. package/dist/agents/skills/gemini-cli/harness-perf-tdd/SKILL.md +17 -4
  69. package/dist/agents/skills/gemini-cli/harness-release-readiness/SKILL.md +16 -0
  70. package/dist/agents/skills/gemini-cli/harness-roadmap/SKILL.md +562 -0
  71. package/dist/agents/skills/gemini-cli/harness-roadmap/skill.yaml +43 -0
  72. package/dist/agents/skills/gemini-cli/harness-security-review/skill.yaml +8 -6
  73. package/dist/agents/skills/gemini-cli/harness-soundness-review/SKILL.md +1267 -0
  74. package/dist/agents/skills/gemini-cli/harness-soundness-review/skill.yaml +48 -0
  75. package/dist/agents/skills/gemini-cli/harness-test-advisor/SKILL.md +35 -6
  76. package/dist/agents/skills/shared/i18n-knowledge/accessibility/intersection.yaml +142 -0
  77. package/dist/agents/skills/shared/i18n-knowledge/anti-patterns/encoding.yaml +67 -0
  78. package/dist/agents/skills/shared/i18n-knowledge/anti-patterns/formatting.yaml +106 -0
  79. package/dist/agents/skills/shared/i18n-knowledge/anti-patterns/layout.yaml +80 -0
  80. package/dist/agents/skills/shared/i18n-knowledge/anti-patterns/pluralization.yaml +80 -0
  81. package/dist/agents/skills/shared/i18n-knowledge/anti-patterns/string-handling.yaml +106 -0
  82. package/dist/agents/skills/shared/i18n-knowledge/frameworks/android-resources.yaml +47 -0
  83. package/dist/agents/skills/shared/i18n-knowledge/frameworks/apple-strings.yaml +47 -0
  84. package/dist/agents/skills/shared/i18n-knowledge/frameworks/backend-patterns.yaml +50 -0
  85. package/dist/agents/skills/shared/i18n-knowledge/frameworks/flutter-intl.yaml +47 -0
  86. package/dist/agents/skills/shared/i18n-knowledge/frameworks/i18next.yaml +47 -0
  87. package/dist/agents/skills/shared/i18n-knowledge/frameworks/react-intl.yaml +47 -0
  88. package/dist/agents/skills/shared/i18n-knowledge/frameworks/vue-i18n.yaml +47 -0
  89. package/dist/agents/skills/shared/i18n-knowledge/industries/ecommerce.yaml +66 -0
  90. package/dist/agents/skills/shared/i18n-knowledge/industries/fintech.yaml +66 -0
  91. package/dist/agents/skills/shared/i18n-knowledge/industries/gaming.yaml +69 -0
  92. package/dist/agents/skills/shared/i18n-knowledge/industries/healthcare.yaml +66 -0
  93. package/dist/agents/skills/shared/i18n-knowledge/industries/legal.yaml +66 -0
  94. package/dist/agents/skills/shared/i18n-knowledge/locales/ar.yaml +41 -0
  95. package/dist/agents/skills/shared/i18n-knowledge/locales/de.yaml +35 -0
  96. package/dist/agents/skills/shared/i18n-knowledge/locales/en.yaml +32 -0
  97. package/dist/agents/skills/shared/i18n-knowledge/locales/es.yaml +35 -0
  98. package/dist/agents/skills/shared/i18n-knowledge/locales/fi.yaml +35 -0
  99. package/dist/agents/skills/shared/i18n-knowledge/locales/fr.yaml +35 -0
  100. package/dist/agents/skills/shared/i18n-knowledge/locales/he.yaml +41 -0
  101. package/dist/agents/skills/shared/i18n-knowledge/locales/hi.yaml +35 -0
  102. package/dist/agents/skills/shared/i18n-knowledge/locales/it.yaml +32 -0
  103. package/dist/agents/skills/shared/i18n-knowledge/locales/ja.yaml +38 -0
  104. package/dist/agents/skills/shared/i18n-knowledge/locales/ko.yaml +38 -0
  105. package/dist/agents/skills/shared/i18n-knowledge/locales/nl.yaml +32 -0
  106. package/dist/agents/skills/shared/i18n-knowledge/locales/pl.yaml +35 -0
  107. package/dist/agents/skills/shared/i18n-knowledge/locales/pt.yaml +32 -0
  108. package/dist/agents/skills/shared/i18n-knowledge/locales/ru.yaml +35 -0
  109. package/dist/agents/skills/shared/i18n-knowledge/locales/sv.yaml +32 -0
  110. package/dist/agents/skills/shared/i18n-knowledge/locales/th.yaml +35 -0
  111. package/dist/agents/skills/shared/i18n-knowledge/locales/tr.yaml +35 -0
  112. package/dist/agents/skills/shared/i18n-knowledge/locales/zh-Hans.yaml +38 -0
  113. package/dist/agents/skills/shared/i18n-knowledge/locales/zh-Hant.yaml +35 -0
  114. package/dist/agents/skills/shared/i18n-knowledge/mcp-interop/i18next-mcp.yaml +56 -0
  115. package/dist/agents/skills/shared/i18n-knowledge/mcp-interop/lingo-dev.yaml +56 -0
  116. package/dist/agents/skills/shared/i18n-knowledge/mcp-interop/lokalise.yaml +60 -0
  117. package/dist/agents/skills/shared/i18n-knowledge/mcp-interop/tolgee.yaml +60 -0
  118. package/dist/agents/skills/shared/i18n-knowledge/testing/locale-testing.yaml +107 -0
  119. package/dist/agents/skills/shared/i18n-knowledge/testing/pseudo-localization.yaml +86 -0
  120. package/dist/bin/harness.js +64 -4
  121. package/dist/{chunk-4WUGOJQ7.js → chunk-3JWCBVUZ.js} +1 -1
  122. package/dist/{chunk-FFIX3QVG.js → chunk-LNI4T7R6.js} +131 -41
  123. package/dist/{chunk-GA6GN5J2.js → chunk-SJECMKSS.js} +2244 -34
  124. package/dist/{dist-N4D4QWFV.js → dist-BDO5GFEM.js} +1 -1
  125. package/dist/{dist-C4J67MPP.js → dist-NT3GXHQZ.js} +95 -1
  126. package/dist/index.d.ts +187 -7
  127. package/dist/index.js +7 -3
  128. package/dist/validate-cross-check-2OPGCGGU.js +7 -0
  129. package/package.json +7 -7
  130. package/dist/validate-cross-check-WGXQ7K62.js +0 -7
@@ -0,0 +1,48 @@
1
+ name: harness-soundness-review
2
+ version: "1.0.0"
3
+ description: Deep soundness analysis of specs and plans with auto-fix and convergence loop
4
+ cognitive_mode: meticulous-verifier
5
+ triggers:
6
+ - manual
7
+ platforms:
8
+ - claude-code
9
+ - gemini-cli
10
+ tools:
11
+ - Bash
12
+ - Read
13
+ - Write
14
+ - Edit
15
+ - Glob
16
+ - Grep
17
+ cli:
18
+ command: harness skill run harness-soundness-review
19
+ args:
20
+ - name: path
21
+ description: Project root path
22
+ required: false
23
+ - name: mode
24
+ description: Review mode — "spec" for spec soundness or "plan" for plan soundness
25
+ required: true
26
+ mcp:
27
+ tool: run_skill
28
+ input:
29
+ skill: harness-soundness-review
30
+ path: string
31
+ type: rigid
32
+ phases:
33
+ - name: check
34
+ description: Run all checks for the current mode and classify findings
35
+ required: true
36
+ - name: fix
37
+ description: Auto-fix inferrable issues and log changes
38
+ required: true
39
+ - name: converge
40
+ description: Re-run checks, compare issue counts, loop or stop
41
+ required: true
42
+ - name: surface
43
+ description: Present remaining issues to user for resolution
44
+ required: true
45
+ state:
46
+ persistent: false
47
+ files: []
48
+ depends_on: []
@@ -13,8 +13,23 @@
13
13
 
14
14
  ## Prerequisites
15
15
 
16
- A knowledge graph must exist at `.harness/graph/`. Run `harness scan` if no graph is available.
17
- If the graph exists but code has changed since the last scan, re-run `harness scan` first — stale graph data leads to inaccurate results.
16
+ A knowledge graph at `.harness/graph/` enables full analysis. If no graph exists,
17
+ the skill uses static analysis fallbacks (see Graph Availability section).
18
+ Run `harness scan` to enable graph-enhanced analysis.
19
+
20
+ ### Graph Availability
21
+
22
+ Before starting, check if `.harness/graph/graph.json` exists.
23
+
24
+ **If graph exists:** Check staleness — compare `.harness/graph/metadata.json`
25
+ scanTimestamp against `git log -1 --format=%ct` (latest commit timestamp).
26
+ If graph is more than 10 commits behind (`git log --oneline <scanTimestamp>..HEAD | wc -l`),
27
+ run `harness scan` to refresh before proceeding. (Staleness sensitivity: **Medium**)
28
+
29
+ **If graph exists and is fresh (or refreshed):** Use graph tools as primary strategy.
30
+
31
+ **If no graph exists:** Output "Running without graph (run `harness scan` to
32
+ enable full analysis)" and use fallback strategies for all subsequent steps.
18
33
 
19
34
  ## Process
20
35
 
@@ -43,6 +58,20 @@ For each changed file, use graph traversal to find test files:
43
58
 
44
59
  3. **Co-change tests**: Check `co_changes_with` edges for test files that historically change alongside the modified files.
45
60
 
61
+ #### Fallback (without graph)
62
+
63
+ When no graph is available, use naming conventions, import parsing, and git history:
64
+
65
+ 1. **Tier 1 — Filename convention matching**: For each changed file `foo.ts`, search for:
66
+ - `foo.test.ts`, `foo.spec.ts` (same directory)
67
+ - `__tests__/foo.ts`, `__tests__/foo.test.ts`
68
+ - Test files in a parallel `tests/` directory mirroring the source path
69
+ 2. **Tier 2 — Import-linked tests**: Parse test files' import statements (grep for `import.*from` in `*.test.*` and `*.spec.*` files). If a test file imports the changed file, it belongs in Tier 2 (if not already in Tier 1).
70
+ 3. **Tier 3 — Co-change correlated tests**: Use `git log --format="%H" --name-only` to find test files that frequently change in the same commit as the target file. Files that co-change in >2 commits are co-change correlated.
71
+ 4. **Rank**: Tier 1 = direct filename match, Tier 2 = import-linked tests, Tier 3 = co-change correlated tests. Output the same tiered format as the graph version.
72
+
73
+ > Fallback completeness: ~80% — naming conventions and imports catch most mappings; misses dynamic imports and indirect coverage.
74
+
46
75
  ### Phase 3: PRIORITIZE — Rank and Generate Commands
47
76
 
48
77
  Organize tests into three tiers:
@@ -85,7 +114,7 @@ npx vitest run tests/services/auth.test.ts tests/types/user.test.ts tests/routes
85
114
 
86
115
  ## Harness Integration
87
116
 
88
- - **`harness scan`** — Must run before this skill to ensure graph is current.
117
+ - **`harness scan`** — Recommended before this skill for full graph-enhanced analysis. If graph is missing, skill uses naming convention and import parsing fallbacks.
89
118
  - **`harness validate`** — Run after acting on findings to verify project health.
90
119
  - **Graph tools** — This skill uses `query_graph`, `get_impact`, and `get_relationships` MCP tools.
91
120
 
@@ -95,7 +124,7 @@ npx vitest run tests/services/auth.test.ts tests/types/user.test.ts tests/routes
95
124
  - Executable run commands generated for quick and full test runs
96
125
  - Coverage gaps flagged for changed files with no test coverage
97
126
  - Report follows the structured output format
98
- - All findings are backed by graph query evidence, not heuristics
127
+ - All findings are backed by graph query evidence (with graph) or systematic static analysis (without graph)
99
128
 
100
129
  ## Examples
101
130
 
@@ -122,8 +151,8 @@ Output:
122
151
 
123
152
  ## Gates
124
153
 
125
- - **No advice without graph.** If no graph exists, fall back to: "Run all tests in the same directory as changed files."
126
- - **Always include Tier 1.** Direct test coverage is non-negotiable — always recommend running these.
154
+ - **Graph preferred, fallback available.** If no graph exists, use naming conventions, import parsing, and git co-change analysis to identify relevant tests. Do not stop — produce the best test selection possible.
155
+ - **Always include Tier 1.** Direct test coverage is non-negotiable — always recommend running these (whether found via graph or naming conventions).
127
156
 
128
157
  ## Escalation
129
158
 
@@ -0,0 +1,142 @@
1
+ description: "Rules at the intersection of i18n and accessibility — ensuring localized content remains accessible across languages, scripts, and text directions"
2
+
3
+ rules:
4
+ - name: "lang attribute on html element"
5
+ category: "lang-tags"
6
+ wcag_criteria: ["3.1.1"]
7
+ description: "The <html> element must have a valid lang attribute matching the page's primary language"
8
+ detect:
9
+ method: "Check for lang attribute on <html> element; verify it matches a valid BCP 47 tag and corresponds to the page content language"
10
+ reason: "Screen readers use the lang attribute to select the correct pronunciation engine. Without it, an English screen reader will mispronounce French content."
11
+ fix: "Set <html lang='fr'> for French content, <html lang='ar' dir='rtl'> for Arabic, etc. Update dynamically when locale changes."
12
+ severity: error
13
+ strictness:
14
+ permissive: warn
15
+ standard: error
16
+ strict: error
17
+
18
+ - name: "lang attribute on inline language switches"
19
+ category: "lang-tags"
20
+ wcag_criteria: ["3.1.2"]
21
+ description: "Inline content in a different language must be wrapped with a lang attribute"
22
+ detect:
23
+ method: "Check for foreign-language words or phrases without a lang attribute on their container"
24
+ reason: "Screen readers will pronounce foreign words using the page's primary language rules, making them unintelligible."
25
+ fix: "Wrap language switches: <span lang='fr'>bonjour</span> within English content, <span lang='en'>API</span> within Japanese content."
26
+ severity: warning
27
+ strictness:
28
+ permissive: info
29
+ standard: warn
30
+ strict: error
31
+
32
+ - name: "dir attribute for RTL content"
33
+ category: "bidi-a11y"
34
+ wcag_criteria: ["1.3.2"]
35
+ description: "RTL content must have dir='rtl' set on the appropriate container element"
36
+ detect:
37
+ method: "Check for Arabic, Hebrew, or other RTL script content without a dir attribute on its container"
38
+ reason: "Without dir='rtl', screen readers may read RTL text in the wrong order, and visual rendering will be incorrect."
39
+ fix: "Set dir='rtl' on containers with RTL content. For the full page: <html lang='ar' dir='rtl'>. For inline: <span dir='rtl'>."
40
+ severity: error
41
+ strictness:
42
+ permissive: warn
43
+ standard: error
44
+ strict: error
45
+
46
+ - name: "dir=auto on user-generated content"
47
+ category: "bidi-a11y"
48
+ wcag_criteria: ["1.3.2"]
49
+ description: "User-generated content containers should use dir='auto' to detect text direction automatically"
50
+ detect:
51
+ method: "Check for user-generated content areas (comments, messages, reviews) without dir='auto'"
52
+ reason: "User content may be in any language. Without dir='auto', an Arabic comment in an English page will render incorrectly."
53
+ fix: "Set dir='auto' on elements that display user-generated content: <p dir='auto'>{userComment}</p>"
54
+ severity: warning
55
+ strictness:
56
+ permissive: info
57
+ standard: warn
58
+ strict: warn
59
+
60
+ - name: "Font size scaling for complex scripts"
61
+ category: "script-sizing"
62
+ wcag_criteria: ["1.4.4"]
63
+ description: "Complex scripts (Devanagari, Thai, Arabic, CJK) require larger minimum font sizes for legibility"
64
+ detect:
65
+ method: "Check minimum font sizes when content language uses complex scripts; flag sizes below recommended minimums"
66
+ reason: "Complex scripts have more visual detail per character. Arabic letters reshape by position. Thai stacks diacritics vertically. Below 12-14px, these become illegible."
67
+ fix: "Set minimum font sizes: 12px for CJK, 13px for Arabic, 14px for Devanagari and Thai. Increase line-height to 1.6-1.8 for scripts with stacking marks."
68
+ severity: warning
69
+ strictness:
70
+ permissive: info
71
+ standard: warn
72
+ strict: error
73
+
74
+ - name: "Screen reader pronunciation of numbers and dates"
75
+ category: "screen-readers"
76
+ wcag_criteria: ["1.3.1"]
77
+ description: "Numbers and dates must use semantic markup so screen readers pronounce them correctly per locale"
78
+ detect:
79
+ method: "Check for visually formatted numbers/dates that lack semantic markup (time element, appropriate ARIA)"
80
+ reason: "A screen reader seeing '03/04/2025' cannot determine if it is March 4 or April 3 without semantic context. '1.234' could be one-point-two-three-four or one-thousand-two-hundred-thirty-four."
81
+ fix: "Use <time datetime='2025-03-04'>March 4, 2025</time> for dates. Use aria-label for ambiguous number formats."
82
+ severity: warning
83
+ strictness:
84
+ permissive: info
85
+ standard: warn
86
+ strict: warn
87
+
88
+ - name: "ARIA labels must be translated"
89
+ category: "screen-readers"
90
+ wcag_criteria: ["4.1.2"]
91
+ description: "All ARIA labels, descriptions, and live region content must go through the translation pipeline"
92
+ detect:
93
+ method: "Check for hardcoded English strings in aria-label, aria-description, aria-roledescription attributes"
94
+ reason: "Untranslated ARIA labels make the interface inaccessible to screen reader users in non-English locales."
95
+ fix: "Wrap ARIA attributes in translation functions: aria-label={t('nav.menu')} instead of aria-label='Menu'"
96
+ severity: error
97
+ strictness:
98
+ permissive: warn
99
+ standard: error
100
+ strict: error
101
+
102
+ - name: "hreflang attribute on alternate-language links"
103
+ category: "lang-tags"
104
+ wcag_criteria: ["3.1.1"]
105
+ description: "Links to alternate-language versions of a page must have hreflang attributes"
106
+ detect:
107
+ method: "Check for <link rel='alternate'> elements without hreflang, or language switcher links without hreflang"
108
+ reason: "hreflang helps search engines serve the correct language version and assists screen readers in announcing the target language."
109
+ fix: "Add hreflang to all alternate links: <link rel='alternate' hreflang='es' href='/es/page'>. Include x-default for the default version."
110
+ severity: warning
111
+ strictness:
112
+ permissive: info
113
+ standard: warn
114
+ strict: warn
115
+
116
+ - name: "Translated alt text for images"
117
+ category: "screen-readers"
118
+ wcag_criteria: ["1.1.1"]
119
+ description: "Image alt text must be translated along with all other user-facing content"
120
+ detect:
121
+ method: "Check for img alt attributes containing English text when the page language is non-English"
122
+ reason: "Untranslated alt text is meaningless to screen reader users who do not speak the source language."
123
+ fix: "Include all alt attributes in the translation pipeline: <img alt={t('hero.alt')} /> instead of hardcoded text"
124
+ severity: error
125
+ strictness:
126
+ permissive: warn
127
+ standard: error
128
+ strict: error
129
+
130
+ - name: "Keyboard navigation in RTL layouts"
131
+ category: "bidi-a11y"
132
+ wcag_criteria: ["2.4.3"]
133
+ description: "Tab order must follow visual order in RTL layouts — right-to-left, top-to-bottom"
134
+ detect:
135
+ method: "Check tab order in RTL layouts; verify it matches the visual reading order (right to left)"
136
+ reason: "If tab order remains LTR in an RTL layout, keyboard users navigate against the visual flow, causing confusion."
137
+ fix: "Use CSS logical properties for layout; avoid manual tabindex that assumes LTR order; test tab navigation with RTL locale"
138
+ severity: error
139
+ strictness:
140
+ permissive: info
141
+ standard: warn
142
+ strict: error
@@ -0,0 +1,67 @@
1
+ description: "Encoding anti-patterns — common mistakes in character encoding, Unicode handling, and text measurement that break with non-ASCII content"
2
+
3
+ patterns:
4
+ - name: "Assuming 1 character equals 1 byte"
5
+ severity: error
6
+ scope: all
7
+ detect:
8
+ method: "Check for buffer allocation or data storage sized by string.length for strings that may contain non-ASCII"
9
+ context: "Buffer.alloc(str.length) or database VARCHAR columns sized by character count for UTF-8 data"
10
+ reason: "UTF-8 encodes characters in 1-4 bytes. 'hello' is 5 bytes but 'こんにちは' is 15 bytes. Buffer overflow or truncation results."
11
+ instead: "Use Buffer.byteLength(str, 'utf8') for byte calculations; size database columns for byte length not character count"
12
+ strictness:
13
+ permissive: info
14
+ standard: warn
15
+ strict: error
16
+
17
+ - name: "String length not equal to visual width"
18
+ severity: warning
19
+ scope: all
20
+ detect:
21
+ method: "Check for string.length used to estimate visual display width"
22
+ context: "Layout calculations or column alignment using .length instead of visual width measurement"
23
+ reason: "CJK characters are double-width visually but .length counts them as 1. Emoji may be multiple code points but one visual glyph."
24
+ instead: "Use Intl.Segmenter for grapheme counting; use canvas.measureText() or terminal wcwidth for visual width"
25
+ strictness:
26
+ permissive: info
27
+ standard: warn
28
+ strict: warn
29
+
30
+ - name: "Missing UTF-8 BOM handling"
31
+ severity: warning
32
+ scope: all
33
+ detect:
34
+ method: "Check for file reading that does not strip BOM (U+FEFF) from the beginning of text files"
35
+ context: "Reading CSV, JSON, or translation files that may have been edited in Windows tools that add BOM"
36
+ reason: "BOM at the start of a file can break JSON parsing, CSV parsing, and string comparison. Windows Notepad adds BOM by default."
37
+ instead: "Strip BOM when reading files: content.replace(/^\\uFEFF/, '') or use a BOM-aware file reader"
38
+ strictness:
39
+ permissive: info
40
+ standard: warn
41
+ strict: warn
42
+
43
+ - name: "Emoji and ZWJ sequence handling"
44
+ severity: warning
45
+ scope: all
46
+ detect:
47
+ method: "Check for string operations that split or truncate without grapheme cluster awareness"
48
+ context: "Array.from(str) or str.split('') used to process strings containing emoji"
49
+ reason: "Family emoji (👨‍👩‍👦) is 5 code points joined by ZWJ. Splitting by code point produces broken fragments."
50
+ instead: "Use Intl.Segmenter(locale, { granularity: 'grapheme' }) to iterate over visual characters safely"
51
+ strictness:
52
+ permissive: info
53
+ standard: warn
54
+ strict: warn
55
+
56
+ - name: "Filename and URL encoding not handling non-ASCII"
57
+ severity: error
58
+ scope: all
59
+ detect:
60
+ method: "Check for file path or URL construction that does not encode non-ASCII characters"
61
+ context: "Path concatenation with user input or locale-specific filenames without encodeURIComponent"
62
+ reason: "Non-ASCII filenames and URL segments must be percent-encoded. Unencoded characters cause 404 errors and security issues."
63
+ instead: "Use encodeURIComponent() for URL segments; use encodeURI() for full URLs; normalize filenames to ASCII-safe slugs"
64
+ strictness:
65
+ permissive: warn
66
+ standard: error
67
+ strict: error
@@ -0,0 +1,106 @@
1
+ description: "Formatting anti-patterns — common mistakes in number, date, currency, phone, and address formatting across locales"
2
+
3
+ patterns:
4
+ - name: "Date formatting without locale"
5
+ severity: error
6
+ scope: all
7
+ detect:
8
+ method: "Check for toLocaleDateString() without locale argument or manual date string construction"
9
+ context: "new Date().toLocaleDateString() without first arg, or month + '/' + day + '/' + year concatenation"
10
+ reason: "Without explicit locale, the runtime's default locale is used — which may differ from the user's locale on servers or CI."
11
+ instead: "Always pass locale: new Intl.DateTimeFormat(userLocale, options).format(date)"
12
+ strictness:
13
+ permissive: info
14
+ standard: warn
15
+ strict: error
16
+
17
+ - name: "Number formatting with hardcoded separators"
18
+ severity: error
19
+ scope: all
20
+ detect:
21
+ method: "Check for regex or replace operations that insert commas or periods as number separators"
22
+ context: "value.toFixed(2).replace('.', ',') or regex-based thousand separator insertion"
23
+ reason: "Decimal separator is comma in most of Europe (1.234,56), period in US/UK (1,234.56), and momayyez in Arabic."
24
+ instead: "Use Intl.NumberFormat(locale).format(value) for all number display"
25
+ strictness:
26
+ permissive: warn
27
+ standard: error
28
+ strict: error
29
+
30
+ - name: "Currency display with hardcoded symbol"
31
+ severity: error
32
+ scope: all
33
+ detect:
34
+ method: "Check for string concatenation with currency symbols: '$' + amount or amount + ' EUR'"
35
+ context: "Currency symbol prepended or appended to a formatted number"
36
+ reason: "Symbol position varies: $100 (en-US), 100 $ (fr-FR), 100$ (pt-BR). Some currencies use code (CHF) not symbol."
37
+ instead: "Use Intl.NumberFormat(locale, { style: 'currency', currency: code }).format(amount)"
38
+ strictness:
39
+ permissive: warn
40
+ standard: error
41
+ strict: error
42
+
43
+ - name: "Phone number formatting without libphonenumber"
44
+ severity: warning
45
+ scope: all
46
+ detect:
47
+ method: "Check for regex-based phone number formatting or validation"
48
+ context: "Phone regex like /^\\d{10}$/ or manual formatting with dashes and parentheses"
49
+ reason: "Phone number formats vary enormously by country. UK: 020 7946 0958. US: (202) 555-0123. Japan: 03-1234-5678."
50
+ instead: "Use google-libphonenumber for parsing, validation, and formatting; store in E.164 format"
51
+ strictness:
52
+ permissive: info
53
+ standard: warn
54
+ strict: warn
55
+
56
+ - name: "Address formatting with hardcoded field order"
57
+ severity: warning
58
+ scope: all
59
+ detect:
60
+ method: "Check for address string templates that assume a fixed field order"
61
+ context: "Template like `${street}, ${city}, ${state} ${zip}` used for all countries"
62
+ reason: "Japan: postal code first, then prefecture, city, street. Germany: street, then postal code + city (same line). Formats vary worldwide."
63
+ instead: "Use Google's libaddressinput or i18n-postal-address for country-specific address formatting"
64
+ strictness:
65
+ permissive: info
66
+ standard: warn
67
+ strict: warn
68
+
69
+ - name: "Percentage formatting assumptions"
70
+ severity: warning
71
+ scope: all
72
+ detect:
73
+ method: "Check for percentage display constructed by appending '%' to a number"
74
+ context: "value + '%' or `${value}%` without locale-aware formatting"
75
+ reason: "Some locales put a space before % (French: 25 %). Turkish uses %25 (prefix). Arabic may use the Arabic percent sign."
76
+ instead: "Use Intl.NumberFormat(locale, { style: 'percent' }).format(value / 100)"
77
+ strictness:
78
+ permissive: info
79
+ standard: warn
80
+ strict: warn
81
+
82
+ - name: "Hardcoded decimal precision"
83
+ severity: error
84
+ scope: all
85
+ detect:
86
+ method: "Check for .toFixed(2) applied to currency amounts"
87
+ context: "Universal 2-decimal-place formatting for all currencies"
88
+ reason: "JPY and KRW have 0 decimal places. BHD and KWD have 3. Using 2 for all misrepresents amounts."
89
+ instead: "Use Intl.NumberFormat with currency option which auto-selects correct precision per currency code"
90
+ strictness:
91
+ permissive: warn
92
+ standard: error
93
+ strict: error
94
+
95
+ - name: "Time zone display without IANA identifier"
96
+ severity: warning
97
+ scope: all
98
+ detect:
99
+ method: "Check for timezone abbreviations (EST, PST, CET) in user-facing display"
100
+ context: "Displaying 'EST' or 'PST' without the full IANA timezone identifier"
101
+ reason: "Timezone abbreviations are ambiguous (CST = Central Standard Time or China Standard Time) and not universally understood."
102
+ instead: "Use IANA identifiers (America/New_York) for storage; display user-friendly names via Intl.DateTimeFormat with timeZoneName option"
103
+ strictness:
104
+ permissive: info
105
+ standard: warn
106
+ strict: warn
@@ -0,0 +1,80 @@
1
+ description: "Layout anti-patterns — common mistakes in spatial design, text containers, and directional assumptions that break in localized contexts"
2
+
3
+ patterns:
4
+ - name: "Fixed-width containers for text"
5
+ severity: error
6
+ scope: all
7
+ detect:
8
+ method: "Check for fixed pixel widths on containers that hold translated text"
9
+ context: "Buttons, labels, or cards with hardcoded width in px that hold user-facing text"
10
+ reason: "German expands 30-35% over English. Finnish expands up to 40%. Fixed-width containers cause text overflow or truncation."
11
+ instead: "Use min-width/max-width with flexible layouts; test with the longest target language (typically German or Finnish)"
12
+ strictness:
13
+ permissive: info
14
+ standard: warn
15
+ strict: error
16
+
17
+ - name: "Hardcoded CSS left/right instead of logical properties"
18
+ severity: warning
19
+ scope: web
20
+ detect:
21
+ method: "Check for CSS properties using physical directions: margin-left, padding-right, text-align: left, float: left"
22
+ context: "Layout CSS that uses physical direction properties instead of logical equivalents"
23
+ reason: "Physical left/right breaks in RTL layouts. margin-left becomes the wrong side for Arabic and Hebrew users."
24
+ instead: "Use CSS logical properties: margin-inline-start, padding-inline-end, text-align: start, float: inline-start"
25
+ strictness:
26
+ permissive: info
27
+ standard: warn
28
+ strict: warn
29
+
30
+ - name: "Icon placement assumptions"
31
+ severity: warning
32
+ scope: all
33
+ detect:
34
+ method: "Check for icons hardcoded to left or right side of text"
35
+ context: "Arrow icons, action icons, or navigation indicators with fixed directional placement"
36
+ reason: "Left-side icons in LTR become wrong-side icons in RTL. But some icons should NOT mirror (checkmarks, media play buttons)."
37
+ instead: "Use logical placement (inline-start/end); create an icon exception list for icons that should not mirror in RTL"
38
+ strictness:
39
+ permissive: info
40
+ standard: warn
41
+ strict: warn
42
+
43
+ - name: "Text truncation without dir attribute"
44
+ severity: warning
45
+ scope: web
46
+ detect:
47
+ method: "Check for text-overflow: ellipsis without corresponding dir attribute on the container"
48
+ context: "Truncated text elements that may contain RTL content"
49
+ reason: "Ellipsis appears at the wrong end for RTL text without dir attribute. Bidi truncation shows '...مرحبا' instead of 'مرحبا...'"
50
+ instead: "Set dir attribute on text containers; use CSS direction property in conjunction with text-overflow"
51
+ strictness:
52
+ permissive: info
53
+ standard: warn
54
+ strict: warn
55
+
56
+ - name: "Fixed height on text containers"
57
+ severity: warning
58
+ scope: all
59
+ detect:
60
+ method: "Check for fixed pixel heights on containers that hold multi-line translated text"
61
+ context: "Cards, tooltips, or modals with hardcoded height that hold translatable content"
62
+ reason: "Translated text may wrap to more lines due to expansion. Thai and Devanagari need more vertical space for diacritics."
63
+ instead: "Use min-height instead of height; increase line-height for scripts with stacking diacritics (Thai: 1.8, Devanagari: 1.6)"
64
+ strictness:
65
+ permissive: info
66
+ standard: warn
67
+ strict: warn
68
+
69
+ - name: "Images or graphics containing embedded text"
70
+ severity: error
71
+ scope: all
72
+ detect:
73
+ method: "Check for images, SVGs, or canvas elements with text baked into the visual"
74
+ context: "Hero images, infographics, or icons with embedded text labels"
75
+ reason: "Text in images cannot be translated, searched, or read by screen readers. Each locale requires a new image asset."
76
+ instead: "Overlay text on images using CSS positioning; use SVG with <text> elements that can reference translation keys"
77
+ strictness:
78
+ permissive: info
79
+ standard: warn
80
+ strict: error
@@ -0,0 +1,80 @@
1
+ description: "Pluralization anti-patterns — common mistakes in handling plural forms across languages with varying CLDR plural categories"
2
+
3
+ patterns:
4
+ - name: "Binary plural logic"
5
+ severity: error
6
+ scope: all
7
+ detect:
8
+ method: "Check for ternary or if/else that only handles singular and plural (two forms)"
9
+ context: "count === 1 ? singular : plural or count > 1 ? plural : singular"
10
+ reason: "Many languages need more than two forms. Arabic has 6 (zero, one, two, few, many, other). Polish has 4. Russian has 4. Binary logic covers only English-like languages."
11
+ instead: "Use ICU MessageFormat plural syntax with all needed CLDR categories: {count, plural, zero {...} one {...} two {...} few {...} many {...} other {...}}"
12
+ strictness:
13
+ permissive: warn
14
+ standard: error
15
+ strict: error
16
+
17
+ - name: "Hardcoded English plural rules applied to all locales"
18
+ severity: error
19
+ scope: all
20
+ detect:
21
+ method: "Check for plural functions that add 's' suffix or check count === 1"
22
+ context: "Functions like pluralize(word, count) that append 's' or select between two English forms"
23
+ reason: "English plural rule (add 's') is specific to English. Most languages have completely different plural morphology."
24
+ instead: "Delegate pluralization entirely to the i18n framework which implements CLDR plural rules per locale"
25
+ strictness:
26
+ permissive: warn
27
+ standard: error
28
+ strict: error
29
+
30
+ - name: "Missing CLDR plural categories for target locales"
31
+ severity: error
32
+ scope: all
33
+ detect:
34
+ method: "Check translation files for missing plural category keys compared to what CLDR requires for each locale"
35
+ context: "Arabic translation file with only 'one' and 'other' forms instead of all 6 required forms"
36
+ reason: "Missing plural forms cause fallback to 'other' which may be grammatically incorrect for specific counts."
37
+ instead: "Audit translation files against CLDR plural rules for each target locale; ensure all required categories are present"
38
+ strictness:
39
+ permissive: info
40
+ standard: warn
41
+ strict: error
42
+
43
+ - name: "Ordinal pluralization not handled"
44
+ severity: warning
45
+ scope: all
46
+ detect:
47
+ method: "Check for ordinal display (1st, 2nd, 3rd) constructed manually or with English-only rules"
48
+ context: "Appending 'st', 'nd', 'rd', 'th' suffixes based on English ordinal rules"
49
+ reason: "Ordinal rules vary by locale. English: 1st, 2nd, 3rd. French: 1er, 2e. German: 1., 2. Some languages have no written ordinal form."
50
+ instead: "Use Intl.PluralRules(locale, { type: 'ordinal' }) to select the correct ordinal form per locale"
51
+ strictness:
52
+ permissive: info
53
+ standard: warn
54
+ strict: warn
55
+
56
+ - name: "Plural forms embedded in UI code instead of message format"
57
+ severity: warning
58
+ scope: all
59
+ detect:
60
+ method: "Check for conditional rendering of plural text in component code rather than translation files"
61
+ context: "JSX like {count === 1 ? <span>1 item</span> : <span>{count} items</span>}"
62
+ reason: "Plural logic in code bypasses the translation pipeline. Translators cannot adapt plural forms for their locale."
63
+ instead: "Move plural selection to translation files using ICU or framework plural syntax; render with a single translation key"
64
+ strictness:
65
+ permissive: info
66
+ standard: warn
67
+ strict: warn
68
+
69
+ - name: "Count-based display without using plural-aware formatter"
70
+ severity: warning
71
+ scope: all
72
+ detect:
73
+ method: "Check for number + noun patterns constructed without plural formatter"
74
+ context: "Displaying '5 file(s)' or using parenthetical plural as a workaround"
75
+ reason: "The '(s)' workaround is not grammatical in any language. It looks unprofessional and confuses screen readers."
76
+ instead: "Use proper plural formatting: t('files', { count: 5 }) with locale-specific plural forms in translation files"
77
+ strictness:
78
+ permissive: info
79
+ standard: warn
80
+ strict: warn