@iamnishankhan/opencode-kit-grok 1.0.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 (198) hide show
  1. package/bin/cli.js +46 -0
  2. package/kit/.opencode/AGENTS.md +392 -0
  3. package/kit/.opencode/README.md +71 -0
  4. package/kit/.opencode/USAGE.md +405 -0
  5. package/kit/.opencode/agents/backend-specialist.md +278 -0
  6. package/kit/.opencode/agents/code-archaeologist.md +114 -0
  7. package/kit/.opencode/agents/database-architect.md +233 -0
  8. package/kit/.opencode/agents/debugger.md +234 -0
  9. package/kit/.opencode/agents/devops-engineer.md +252 -0
  10. package/kit/.opencode/agents/documentation-writer.md +111 -0
  11. package/kit/.opencode/agents/explorer-agent.md +83 -0
  12. package/kit/.opencode/agents/frontend-specialist.md +606 -0
  13. package/kit/.opencode/agents/game-developer.md +169 -0
  14. package/kit/.opencode/agents/mobile-developer.md +385 -0
  15. package/kit/.opencode/agents/orchestrator.md +213 -0
  16. package/kit/.opencode/agents/penetration-tester.md +197 -0
  17. package/kit/.opencode/agents/performance-optimizer.md +194 -0
  18. package/kit/.opencode/agents/product-manager.md +120 -0
  19. package/kit/.opencode/agents/product-owner.md +103 -0
  20. package/kit/.opencode/agents/project-planner.md +418 -0
  21. package/kit/.opencode/agents/qa-automation-engineer.md +113 -0
  22. package/kit/.opencode/agents/security-auditor.md +179 -0
  23. package/kit/.opencode/agents/seo-specialist.md +119 -0
  24. package/kit/.opencode/agents/test-engineer.md +169 -0
  25. package/kit/.opencode/commands/brainstorm.md +113 -0
  26. package/kit/.opencode/commands/coordinate.md +72 -0
  27. package/kit/.opencode/commands/create.md +63 -0
  28. package/kit/.opencode/commands/debug.md +102 -0
  29. package/kit/.opencode/commands/deploy.md +175 -0
  30. package/kit/.opencode/commands/enhance.md +62 -0
  31. package/kit/.opencode/commands/orchestrate.md +241 -0
  32. package/kit/.opencode/commands/plan.md +89 -0
  33. package/kit/.opencode/commands/preview.md +80 -0
  34. package/kit/.opencode/commands/remember.md +67 -0
  35. package/kit/.opencode/commands/status.md +86 -0
  36. package/kit/.opencode/commands/test.md +143 -0
  37. package/kit/.opencode/commands/verify.md +71 -0
  38. package/kit/.opencode/memory/MEMORY.md +6 -0
  39. package/kit/.opencode/memory/feedback-history.md +9 -0
  40. package/kit/.opencode/memory/project-conventions.md +16 -0
  41. package/kit/.opencode/memory/tech-decisions.md +10 -0
  42. package/kit/.opencode/memory/user-preferences.md +9 -0
  43. package/kit/.opencode/opencode.json +15 -0
  44. package/kit/.opencode/scripts/README.md +95 -0
  45. package/kit/.opencode/scripts/auto_preview.py +149 -0
  46. package/kit/.opencode/scripts/checklist.py +75 -0
  47. package/kit/.opencode/scripts/component_registry.py +238 -0
  48. package/kit/.opencode/scripts/dependency_graph.py +73 -0
  49. package/kit/.opencode/scripts/generate_manifest.py +44 -0
  50. package/kit/.opencode/scripts/session_manager.py +120 -0
  51. package/kit/.opencode/scripts/tests/test_toolkit.py +238 -0
  52. package/kit/.opencode/scripts/validate_kit.py +397 -0
  53. package/kit/.opencode/scripts/validation_runner.py +216 -0
  54. package/kit/.opencode/scripts/verify_all.py +85 -0
  55. package/kit/.opencode/skills/api-patterns/SKILL.md +83 -0
  56. package/kit/.opencode/skills/api-patterns/api-style.md +42 -0
  57. package/kit/.opencode/skills/api-patterns/auth.md +24 -0
  58. package/kit/.opencode/skills/api-patterns/documentation.md +26 -0
  59. package/kit/.opencode/skills/api-patterns/graphql.md +41 -0
  60. package/kit/.opencode/skills/api-patterns/rate-limiting.md +31 -0
  61. package/kit/.opencode/skills/api-patterns/response.md +37 -0
  62. package/kit/.opencode/skills/api-patterns/rest.md +40 -0
  63. package/kit/.opencode/skills/api-patterns/scripts/api_validator.py +211 -0
  64. package/kit/.opencode/skills/api-patterns/security-testing.md +122 -0
  65. package/kit/.opencode/skills/api-patterns/trpc.md +41 -0
  66. package/kit/.opencode/skills/api-patterns/versioning.md +22 -0
  67. package/kit/.opencode/skills/app-builder/SKILL.md +78 -0
  68. package/kit/.opencode/skills/app-builder/agent-coordination.md +71 -0
  69. package/kit/.opencode/skills/app-builder/feature-building.md +53 -0
  70. package/kit/.opencode/skills/app-builder/project-detection.md +45 -0
  71. package/kit/.opencode/skills/app-builder/scaffolding.md +110 -0
  72. package/kit/.opencode/skills/app-builder/tech-stack.md +41 -0
  73. package/kit/.opencode/skills/app-builder/templates/SKILL.md +39 -0
  74. package/kit/.opencode/skills/app-builder/templates/astro-static/TEMPLATE.md +78 -0
  75. package/kit/.opencode/skills/app-builder/templates/chrome-extension/TEMPLATE.md +96 -0
  76. package/kit/.opencode/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  77. package/kit/.opencode/skills/app-builder/templates/electron-desktop/TEMPLATE.md +97 -0
  78. package/kit/.opencode/skills/app-builder/templates/express-api/TEMPLATE.md +89 -0
  79. package/kit/.opencode/skills/app-builder/templates/flutter-app/TEMPLATE.md +93 -0
  80. package/kit/.opencode/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +97 -0
  81. package/kit/.opencode/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +126 -0
  82. package/kit/.opencode/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +125 -0
  83. package/kit/.opencode/skills/app-builder/templates/nextjs-static/TEMPLATE.md +174 -0
  84. package/kit/.opencode/skills/app-builder/templates/nuxt-app/TEMPLATE.md +127 -0
  85. package/kit/.opencode/skills/app-builder/templates/python-fastapi/TEMPLATE.md +94 -0
  86. package/kit/.opencode/skills/app-builder/templates/react-native-app/TEMPLATE.md +121 -0
  87. package/kit/.opencode/skills/architecture/SKILL.md +57 -0
  88. package/kit/.opencode/skills/architecture/context-discovery.md +43 -0
  89. package/kit/.opencode/skills/architecture/examples.md +94 -0
  90. package/kit/.opencode/skills/architecture/pattern-selection.md +68 -0
  91. package/kit/.opencode/skills/architecture/patterns-reference.md +50 -0
  92. package/kit/.opencode/skills/architecture/trade-off-analysis.md +77 -0
  93. package/kit/.opencode/skills/bash-linux/SKILL.md +201 -0
  94. package/kit/.opencode/skills/batch-operations/SKILL.md +105 -0
  95. package/kit/.opencode/skills/behavioral-modes/SKILL.md +253 -0
  96. package/kit/.opencode/skills/brainstorming/SKILL.md +178 -0
  97. package/kit/.opencode/skills/brainstorming/dynamic-questioning.md +350 -0
  98. package/kit/.opencode/skills/clean-code/SKILL.md +201 -0
  99. package/kit/.opencode/skills/code-review-checklist/SKILL.md +111 -0
  100. package/kit/.opencode/skills/code-review-graph/SKILL.md +305 -0
  101. package/kit/.opencode/skills/context-compression/SKILL.md +145 -0
  102. package/kit/.opencode/skills/coordinator-mode/SKILL.md +208 -0
  103. package/kit/.opencode/skills/database-design/SKILL.md +54 -0
  104. package/kit/.opencode/skills/database-design/database-selection.md +43 -0
  105. package/kit/.opencode/skills/database-design/indexing.md +39 -0
  106. package/kit/.opencode/skills/database-design/migrations.md +48 -0
  107. package/kit/.opencode/skills/database-design/optimization.md +36 -0
  108. package/kit/.opencode/skills/database-design/orm-selection.md +30 -0
  109. package/kit/.opencode/skills/database-design/schema-design.md +56 -0
  110. package/kit/.opencode/skills/database-design/scripts/schema_validator.py +172 -0
  111. package/kit/.opencode/skills/deployment-procedures/SKILL.md +243 -0
  112. package/kit/.opencode/skills/design-spec/SKILL.md +185 -0
  113. package/kit/.opencode/skills/design-spec/collection.md +106 -0
  114. package/kit/.opencode/skills/documentation-templates/SKILL.md +196 -0
  115. package/kit/.opencode/skills/frontend-architecture/SKILL.md +245 -0
  116. package/kit/.opencode/skills/frontend-design/SKILL.md +1222 -0
  117. package/kit/.opencode/skills/frontend-design/redesign.md +178 -0
  118. package/kit/.opencode/skills/frontend-design/scripts/accessibility_checker.py +145 -0
  119. package/kit/.opencode/skills/frontend-design/scripts/ux_audit.py +722 -0
  120. package/kit/.opencode/skills/frontend-design/style-brutalist.md +92 -0
  121. package/kit/.opencode/skills/frontend-design/style-minimalist.md +85 -0
  122. package/kit/.opencode/skills/game-development/2d-games/SKILL.md +118 -0
  123. package/kit/.opencode/skills/game-development/3d-games/SKILL.md +134 -0
  124. package/kit/.opencode/skills/game-development/SKILL.md +169 -0
  125. package/kit/.opencode/skills/game-development/game-art/SKILL.md +184 -0
  126. package/kit/.opencode/skills/game-development/game-audio/SKILL.md +189 -0
  127. package/kit/.opencode/skills/game-development/game-design/SKILL.md +128 -0
  128. package/kit/.opencode/skills/game-development/mobile-games/SKILL.md +107 -0
  129. package/kit/.opencode/skills/game-development/multiplayer/SKILL.md +131 -0
  130. package/kit/.opencode/skills/game-development/pc-games/SKILL.md +143 -0
  131. package/kit/.opencode/skills/game-development/vr-ar/SKILL.md +122 -0
  132. package/kit/.opencode/skills/game-development/web-games/SKILL.md +149 -0
  133. package/kit/.opencode/skills/geo-fundamentals/SKILL.md +158 -0
  134. package/kit/.opencode/skills/geo-fundamentals/scripts/geo_checker.py +273 -0
  135. package/kit/.opencode/skills/i18n-localization/SKILL.md +156 -0
  136. package/kit/.opencode/skills/i18n-localization/scripts/i18n_checker.py +223 -0
  137. package/kit/.opencode/skills/intelligent-routing/SKILL.md +337 -0
  138. package/kit/.opencode/skills/lint-and-validate/SKILL.md +47 -0
  139. package/kit/.opencode/skills/lint-and-validate/scripts/lint_runner.py +184 -0
  140. package/kit/.opencode/skills/lint-and-validate/scripts/type_coverage.py +245 -0
  141. package/kit/.opencode/skills/mcp-builder/SKILL.md +205 -0
  142. package/kit/.opencode/skills/memory-system/SKILL.md +181 -0
  143. package/kit/.opencode/skills/mobile-design/SKILL.md +396 -0
  144. package/kit/.opencode/skills/mobile-design/decision-trees.md +516 -0
  145. package/kit/.opencode/skills/mobile-design/mobile-backend.md +491 -0
  146. package/kit/.opencode/skills/mobile-design/mobile-color-system.md +420 -0
  147. package/kit/.opencode/skills/mobile-design/mobile-debugging.md +122 -0
  148. package/kit/.opencode/skills/mobile-design/mobile-design-thinking.md +357 -0
  149. package/kit/.opencode/skills/mobile-design/mobile-navigation.md +458 -0
  150. package/kit/.opencode/skills/mobile-design/mobile-performance.md +767 -0
  151. package/kit/.opencode/skills/mobile-design/mobile-testing.md +356 -0
  152. package/kit/.opencode/skills/mobile-design/mobile-typography.md +433 -0
  153. package/kit/.opencode/skills/mobile-design/platform-android.md +666 -0
  154. package/kit/.opencode/skills/mobile-design/platform-ios.md +561 -0
  155. package/kit/.opencode/skills/mobile-design/scripts/mobile_audit.py +670 -0
  156. package/kit/.opencode/skills/mobile-design/touch-psychology.md +537 -0
  157. package/kit/.opencode/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +351 -0
  158. package/kit/.opencode/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +240 -0
  159. package/kit/.opencode/skills/nextjs-react-expert/3-server-server-side-performance.md +490 -0
  160. package/kit/.opencode/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +264 -0
  161. package/kit/.opencode/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
  162. package/kit/.opencode/skills/nextjs-react-expert/6-rendering-rendering-performance.md +432 -0
  163. package/kit/.opencode/skills/nextjs-react-expert/7-js-javascript-performance.md +684 -0
  164. package/kit/.opencode/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +150 -0
  165. package/kit/.opencode/skills/nextjs-react-expert/9-cache-components.md +103 -0
  166. package/kit/.opencode/skills/nextjs-react-expert/SKILL.md +293 -0
  167. package/kit/.opencode/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
  168. package/kit/.opencode/skills/nextjs-react-expert/scripts/react_performance_checker.py +281 -0
  169. package/kit/.opencode/skills/nodejs-best-practices/SKILL.md +336 -0
  170. package/kit/.opencode/skills/parallel-agents/SKILL.md +192 -0
  171. package/kit/.opencode/skills/performance-profiling/SKILL.md +145 -0
  172. package/kit/.opencode/skills/performance-profiling/scripts/bundle_analyzer.py +120 -0
  173. package/kit/.opencode/skills/performance-profiling/scripts/lighthouse_audit.py +108 -0
  174. package/kit/.opencode/skills/plan-writing/SKILL.md +153 -0
  175. package/kit/.opencode/skills/powershell-windows/SKILL.md +169 -0
  176. package/kit/.opencode/skills/python-patterns/SKILL.md +443 -0
  177. package/kit/.opencode/skills/red-team-tactics/SKILL.md +201 -0
  178. package/kit/.opencode/skills/rust-pro/SKILL.md +177 -0
  179. package/kit/.opencode/skills/seo-fundamentals/SKILL.md +131 -0
  180. package/kit/.opencode/skills/seo-fundamentals/scripts/seo_checker.py +219 -0
  181. package/kit/.opencode/skills/server-management/SKILL.md +163 -0
  182. package/kit/.opencode/skills/simplify-code/SKILL.md +131 -0
  183. package/kit/.opencode/skills/skillify/SKILL.md +115 -0
  184. package/kit/.opencode/skills/systematic-debugging/SKILL.md +111 -0
  185. package/kit/.opencode/skills/tailwind-patterns/SKILL.md +271 -0
  186. package/kit/.opencode/skills/tdd-workflow/SKILL.md +151 -0
  187. package/kit/.opencode/skills/testing-patterns/SKILL.md +180 -0
  188. package/kit/.opencode/skills/testing-patterns/scripts/test_runner.py +219 -0
  189. package/kit/.opencode/skills/verify-changes/SKILL.md +127 -0
  190. package/kit/.opencode/skills/vulnerability-scanner/SKILL.md +278 -0
  191. package/kit/.opencode/skills/vulnerability-scanner/checklists.md +124 -0
  192. package/kit/.opencode/skills/vulnerability-scanner/scripts/dependency_analyzer.py +144 -0
  193. package/kit/.opencode/skills/vulnerability-scanner/scripts/security_scan.py +410 -0
  194. package/kit/.opencode/skills/web-design-guidelines/SKILL.md +56 -0
  195. package/kit/.opencode/skills/webapp-testing/SKILL.md +189 -0
  196. package/kit/.opencode/skills/webapp-testing/scripts/playwright_runner.py +123 -0
  197. package/lib/init.js +61 -0
  198. package/package.json +21 -0
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ GEO Checker - Generative Engine Optimization Audit
4
+ Checks PUBLIC WEB CONTENT for AI citation readiness.
5
+
6
+ PURPOSE:
7
+ - Analyze pages that will be INDEXED by AI engines (ChatGPT, Perplexity, etc.)
8
+ - Check for structured data, author info, dates, FAQ sections
9
+ - Help content rank in AI-generated answers
10
+
11
+ WHAT IT CHECKS:
12
+ - HTML files (actual web pages)
13
+ - JSX/TSX files (React page components)
14
+ - NOT markdown files (those are developer docs, not public content)
15
+
16
+ Usage:
17
+ python geo_checker.py <project_path>
18
+ """
19
+ from __future__ import annotations
20
+ import sys
21
+ import re
22
+ import json
23
+ from pathlib import Path
24
+
25
+ # Fix Windows console encoding
26
+ try:
27
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
28
+ sys.stderr.reconfigure(encoding='utf-8', errors='replace')
29
+ except AttributeError:
30
+ pass
31
+
32
+
33
+ # Directories that never represent public source content.
34
+ SKIP_DIRS = {
35
+ "node_modules", ".next", "dist", "build", ".git", ".github",
36
+ ".agents", "__pycache__", ".vscode", ".idea", "coverage",
37
+ "test", "tests", "__tests__", "spec", "public", "components",
38
+ }
39
+
40
+ PAGE_SUFFIXES = {".html", ".htm", ".jsx", ".tsx"}
41
+ ARTICLE_SEGMENTS = {"blog", "blogs", "article", "articles", "post", "posts", "news"}
42
+
43
+
44
+ def _lower_parts(file_path: Path) -> list[str]:
45
+ return [part.lower() for part in file_path.parts]
46
+
47
+
48
+ def is_page_file(file_path: Path) -> bool:
49
+ """Return True only for route entry points, not layouts or components."""
50
+ if file_path.suffix.lower() not in PAGE_SUFFIXES:
51
+ return False
52
+ parts = _lower_parts(file_path)
53
+ if any(part in SKIP_DIRS for part in parts):
54
+ return False
55
+ if file_path.suffix.lower() in {".html", ".htm"}:
56
+ return True
57
+
58
+ name = file_path.stem.lower()
59
+ if name.endswith((".test", ".spec")) or name.startswith(("test_", "spec_")):
60
+ return False
61
+
62
+ # Next.js App Router: only page.tsx/page.jsx is a public route entry.
63
+ if "app" in parts:
64
+ return name == "page"
65
+
66
+ # Next.js Pages Router: each source file is a route except framework/API files.
67
+ if "pages" in parts:
68
+ pages_index = parts.index("pages")
69
+ route_parts = parts[pages_index + 1 :]
70
+ if "api" in route_parts or name.startswith("_"):
71
+ return False
72
+ return True
73
+
74
+ # Other routers commonly use explicit page/index route entries.
75
+ return "routes" in parts and name in {"page", "index"}
76
+
77
+
78
+ def find_web_pages(project_path: Path) -> list[Path]:
79
+ """Find public-facing route source files deterministically."""
80
+ files = []
81
+ for suffix in PAGE_SUFFIXES:
82
+ for file_path in project_path.rglob(f"*{suffix}"):
83
+ if is_page_file(file_path):
84
+ files.append(file_path)
85
+ return sorted(set(files), key=lambda item: item.as_posix())[:100]
86
+
87
+
88
+ def _read_route_content(file_path: Path) -> str:
89
+ """Read a route and its directly imported local content modules.
90
+
91
+ Next.js route entries often delegate all visible markup to content.tsx or
92
+ localized MDX files. Static analysis must follow those imports or it scores
93
+ a thin wrapper instead of the page users and crawlers receive.
94
+ """
95
+ content = file_path.read_text(encoding="utf-8", errors="ignore")
96
+ import_paths = re.findall(r"(?:import|from)\s+(?:[^'\"]+?\s+from\s+)?['\"](\.{1,2}/[^'\"]+)['\"]", content)
97
+ extensions = ("", ".tsx", ".ts", ".jsx", ".js", ".mdx", ".md")
98
+ seen = {file_path.resolve()}
99
+ resolved_imports = []
100
+ for import_path in import_paths:
101
+ base = (file_path.parent / import_path).resolve()
102
+ candidates = [Path(f"{base}{extension}") for extension in extensions]
103
+ candidates.extend(base / f"index{extension}" for extension in extensions[1:])
104
+ for candidate in candidates:
105
+ if not candidate.is_file() or candidate.resolve() in seen:
106
+ continue
107
+ seen.add(candidate.resolve())
108
+ resolved_imports.append(candidate)
109
+ break
110
+
111
+ # A localized wrapper renders one locale at a time. Analyze the canonical
112
+ # English source rather than concatenating four mutually exclusive H1s.
113
+ canonical_locale = [item for item in resolved_imports if ".en." in item.name.lower()]
114
+ selected = canonical_locale or resolved_imports
115
+ chunks = [content]
116
+ chunks.extend(item.read_text(encoding="utf-8", errors="ignore") for item in selected)
117
+ return "\n".join(chunks)
118
+
119
+
120
+ def check_page(file_path: Path, project_path: Path | None = None) -> dict:
121
+ """Score a route source file without requiring article metadata on product docs."""
122
+ try:
123
+ content = _read_route_content(file_path)
124
+ except Exception as exc:
125
+ return {"file": str(file_path), "passed": [], "issues": [f"Error: {exc}"], "score": 0}
126
+
127
+ issues = []
128
+ passed = []
129
+ optional_points = 0
130
+ lower_content = content.lower()
131
+ parts = set(_lower_parts(file_path))
132
+ is_article = bool(parts & ARTICLE_SEGMENTS)
133
+ is_root_landing = file_path.parent.name.lower() == "app"
134
+
135
+ heading_content = re.sub(r"```.*?```", "", content, flags=re.S)
136
+ heading_content = re.sub(r"`(?:\.|[^`])*`", "", heading_content, flags=re.S)
137
+ h1_count = len(re.findall(r"<h1[^>]*>", heading_content, re.I))
138
+ h1_count += len(re.findall(r"^#\s+\S", heading_content, re.M))
139
+ h2_count = len(re.findall(r"<h2[^>]*>", heading_content, re.I))
140
+ h2_count += len(re.findall(r"^##\s+\S", heading_content, re.M))
141
+ required_total = 1 if is_root_landing else 2
142
+ required_passed = 0
143
+
144
+ if h1_count == 1:
145
+ passed.append("Single H1 heading (clear topic)")
146
+ required_passed += 1
147
+ elif h1_count == 0:
148
+ issues.append("No H1 heading - page topic unclear")
149
+ else:
150
+ issues.append(f"Multiple H1 headings ({h1_count}) - confusing for AI")
151
+
152
+ if not is_root_landing:
153
+ repeated_heading = h2_count >= 1 and ".map(" in content
154
+ if h2_count >= 2 or repeated_heading:
155
+ detail = "dynamic repeated" if repeated_heading and h2_count < 2 else str(h2_count)
156
+ passed.append(f"{detail} H2 subheadings (good structure)")
157
+ required_passed += 1
158
+ else:
159
+ issues.append("Add at least two H2 subheadings for scannable content")
160
+
161
+ if "application/ld+json" in lower_content:
162
+ passed.append("JSON-LD structured data found")
163
+ optional_points += 10
164
+ if re.search(r'"@type"\s*:\s*"(Organization|Person|Brand|Article|FAQPage)"', content, re.I):
165
+ passed.append("Recognizable schema entity found")
166
+ optional_points += 5
167
+
168
+ author_patterns = ("author", "byline", "written-by", "contributor", 'rel="author"')
169
+ has_author = any(pattern in lower_content for pattern in author_patterns)
170
+ date_patterns = ("datepublished", "datemodified", "datetime=", "pubdate", "article:published")
171
+ has_date = any(pattern in lower_content for pattern in date_patterns)
172
+ if is_article:
173
+ required_total += 2
174
+ if has_author:
175
+ passed.append("Author attribution found")
176
+ required_passed += 1
177
+ else:
178
+ issues.append("Article has no author attribution")
179
+ if has_date:
180
+ passed.append("Publication date found")
181
+ required_passed += 1
182
+ else:
183
+ issues.append("Article has no publication date")
184
+ else:
185
+ optional_points += 3 if has_author else 0
186
+ optional_points += 3 if has_date else 0
187
+
188
+ optional_checks = (
189
+ (r"<details|faq|frequently.?asked|\"FAQPage\"", "FAQ section detected", 5),
190
+ (r"<(ul|ol)[^>]*>", "Structured list content found", 5),
191
+ (r"<table[^>]*>", "Comparison table found", 5),
192
+ (r"\d+%|according to|data\s+(shows|reveals)|\d+x\s+(faster|better|more)", "Data-backed claims found", 5),
193
+ (r"is defined as|refers to|means that|in short,|simply put,|<dfn", "Direct-answer phrasing found", 5),
194
+ )
195
+ for pattern, label, points in optional_checks:
196
+ if re.search(pattern, content, re.I):
197
+ passed.append(label)
198
+ optional_points += points
199
+
200
+ base_score = (required_passed / required_total * 70) if required_total else 70
201
+ score = min(100, round(base_score + min(optional_points, 30)))
202
+ display = file_path.relative_to(project_path).as_posix() if project_path else file_path.as_posix()
203
+ return {"file": display, "passed": passed, "issues": issues, "score": score}
204
+
205
+
206
+ def main():
207
+ target = sys.argv[1] if len(sys.argv) > 1 else "."
208
+ target_path = Path(target).resolve()
209
+
210
+ print("\n" + "=" * 60)
211
+ print(" GEO CHECKER - AI Citation Readiness Audit")
212
+ print("=" * 60)
213
+ print(f"Project: {target_path}")
214
+ print("-" * 60)
215
+
216
+ # Find web pages only
217
+ pages = find_web_pages(target_path)
218
+
219
+ if not pages:
220
+ print("\n[!] No public web pages found.")
221
+ print(" Looking for: HTML, JSX, TSX files in pages/app directories")
222
+ print(" Skipping: docs, tests, config files, node_modules")
223
+ output = {"script": "geo_checker", "pages_found": 0, "passed": True}
224
+ print("\n" + json.dumps(output, indent=2))
225
+ sys.exit(0)
226
+
227
+ print(f"Found {len(pages)} public pages to analyze\n")
228
+
229
+ # Check each page
230
+ results = []
231
+ for page in pages:
232
+ result = check_page(page, target_path)
233
+ results.append(result)
234
+
235
+ # Print results
236
+ for result in results:
237
+ status = "[OK]" if result['score'] >= 60 else "[!]"
238
+ print(f"{status} {result['file']}: {result['score']}%")
239
+ if result['issues'] and result['score'] < 60:
240
+ for issue in result['issues'][:2]: # Show max 2 issues
241
+ print(f" - {issue}")
242
+
243
+ # Average score
244
+ avg_score = sum(r['score'] for r in results) / len(results) if results else 0
245
+
246
+ print("\n" + "=" * 60)
247
+ print(f"AVERAGE GEO SCORE: {avg_score:.0f}%")
248
+ print("=" * 60)
249
+
250
+ if avg_score >= 80:
251
+ print("[OK] Excellent - Content well-optimized for AI citations")
252
+ elif avg_score >= 60:
253
+ print("[OK] Good - Some improvements recommended")
254
+ elif avg_score >= 40:
255
+ print("[!] Needs work - Add structured elements")
256
+ else:
257
+ print("[X] Poor - Content needs GEO optimization")
258
+
259
+ # JSON output
260
+ output = {
261
+ "script": "geo_checker",
262
+ "project": str(target_path),
263
+ "pages_checked": len(results),
264
+ "average_score": round(avg_score),
265
+ "passed": avg_score >= 60
266
+ }
267
+ print("\n" + json.dumps(output, indent=2))
268
+
269
+ sys.exit(0 if avg_score >= 60 else 1)
270
+
271
+
272
+ if __name__ == "__main__":
273
+ main()
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: i18n-localization
3
+ description: Internationalization and localization patterns. Detecting hardcoded strings, managing translations, locale files, RTL support.
4
+ compatibility: opencode
5
+ metadata:
6
+ when_to_use: "\"When internationalizing an app, managing translations, detecting hardcoded strings, or adding RTL support.\""
7
+ version: "1.0.0"
8
+ ---
9
+ # i18n & Localization
10
+
11
+ > Internationalization (i18n) and Localization (L10n) best practices.
12
+
13
+ ---
14
+
15
+ ## 1. Core Concepts
16
+
17
+ | Term | Meaning |
18
+ |------|---------|
19
+ | **i18n** | Internationalization - making app translatable |
20
+ | **L10n** | Localization - actual translations |
21
+ | **Locale** | Language + Region (en-US, tr-TR) |
22
+ | **RTL** | Right-to-left languages (Arabic, Hebrew) |
23
+
24
+ ---
25
+
26
+ ## 2. When to Use i18n
27
+
28
+ | Project Type | i18n Needed? |
29
+ |--------------|--------------|
30
+ | Public web app | ✅ Yes |
31
+ | SaaS product | ✅ Yes |
32
+ | Internal tool | ⚠️ Maybe |
33
+ | Single-region app | ⚠️ Consider future |
34
+ | Personal project | ❌ Optional |
35
+
36
+ ---
37
+
38
+ ## 3. Implementation Patterns
39
+
40
+ ### React (react-i18next)
41
+
42
+ ```tsx
43
+ import { useTranslation } from 'react-i18next';
44
+
45
+ function Welcome() {
46
+ const { t } = useTranslation();
47
+ return <h1>{t('welcome.title')}</h1>;
48
+ }
49
+ ```
50
+
51
+ ### Next.js (next-intl)
52
+
53
+ ```tsx
54
+ import { useTranslations } from 'next-intl';
55
+
56
+ export default function Page() {
57
+ const t = useTranslations('Home');
58
+ return <h1>{t('title')}</h1>;
59
+ }
60
+ ```
61
+
62
+ ### Python (gettext)
63
+
64
+ ```python
65
+ from gettext import gettext as _
66
+
67
+ print(_("Welcome to our app"))
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 4. File Structure
73
+
74
+ ```
75
+ locales/
76
+ ├── en/
77
+ │ ├── common.json
78
+ │ ├── auth.json
79
+ │ └── errors.json
80
+ ├── tr/
81
+ │ ├── common.json
82
+ │ ├── auth.json
83
+ │ └── errors.json
84
+ └── ar/ # RTL
85
+ └── ...
86
+ ```
87
+
88
+ ---
89
+
90
+ ## 5. Best Practices
91
+
92
+ ### DO ✅
93
+
94
+ - Use translation keys, not raw text
95
+ - Namespace translations by feature
96
+ - Support pluralization
97
+ - Handle date/number formats per locale
98
+ - Plan for RTL from the start
99
+ - Use ICU message format for complex strings
100
+
101
+ ### DON'T ❌
102
+
103
+ - Hardcode strings in components
104
+ - Concatenate translated strings
105
+ - Assume text length (German is 30% longer)
106
+ - Forget about RTL layout
107
+ - Mix languages in same file
108
+
109
+ ---
110
+
111
+ ## 6. Common Issues
112
+
113
+ | Issue | Solution |
114
+ |-------|----------|
115
+ | Missing translation | Fallback to default language |
116
+ | Hardcoded strings | Use linter/checker script |
117
+ | Date format | Use Intl.DateTimeFormat |
118
+ | Number format | Use Intl.NumberFormat |
119
+ | Pluralization | Use ICU message format |
120
+
121
+ ---
122
+
123
+ ## 7. RTL Support
124
+
125
+ ```css
126
+ /* CSS Logical Properties */
127
+ .container {
128
+ margin-inline-start: 1rem; /* Not margin-left */
129
+ padding-inline-end: 1rem; /* Not padding-right */
130
+ }
131
+
132
+ [dir="rtl"] .icon {
133
+ transform: scaleX(-1);
134
+ }
135
+ ```
136
+
137
+ ---
138
+
139
+ ## 8. Checklist
140
+
141
+ Before shipping:
142
+
143
+ - [ ] All user-facing strings use translation keys
144
+ - [ ] Locale files exist for all supported languages
145
+ - [ ] Date/number formatting uses Intl API
146
+ - [ ] RTL layout tested (if applicable)
147
+ - [ ] Fallback language configured
148
+ - [ ] No hardcoded strings in components
149
+
150
+ ---
151
+
152
+ ## Script
153
+
154
+ | Script | Purpose | Command |
155
+ |--------|---------|---------|
156
+ | `.opencode/scripts/i18n_checker.py` | Detect hardcoded strings & missing translations | `python .opencode/scripts/i18n_checker.py <project_path>` |
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env python3
2
+ """Audit locale completeness and likely user-facing hard-coded strings."""
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import re
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ SKIP_DIRS = {"node_modules", ".git", ".agents", ".agent", "dist", "build", "__pycache__", ".venv", "venv", ".next", "tests", "test", "spec", "specs"}
13
+ CODE_TYPES = {".tsx": "jsx", ".jsx": "jsx", ".ts": "jsx", ".js": "jsx", ".vue": "vue", ".py": "python"}
14
+ I18N_PATTERNS = (
15
+ re.compile(r"\buseTranslation\s*\("),
16
+ re.compile(r"\buseTranslations\s*\("),
17
+ re.compile(r"(?<![\w$])t\s*\(\s*[\"']"),
18
+ re.compile(r"\$t\s*\("),
19
+ re.compile(r"\bgettext\s*\("),
20
+ re.compile(r"(?<!\w)_\s*\(\s*[\"']"),
21
+ re.compile(r"\bFormattedMessage\b"),
22
+ re.compile(r"\bi18n\."),
23
+ )
24
+ HARDCODED_PATTERNS = {
25
+ "jsx": (
26
+ re.compile(r">\s*([A-Z][A-Za-z][A-Za-z\s!?.,'-]{2,80})\s*</"),
27
+ re.compile(r"(?:title|placeholder|label|alt|aria-label)=[\"']([A-Z][A-Za-z\s!?.,'-]{2,80})[\"']"),
28
+ ),
29
+ "vue": (
30
+ re.compile(r">\s*([A-Z][A-Za-z][A-Za-z\s!?.,'-]{2,80})\s*</"),
31
+ re.compile(r"(?:title|placeholder|label|alt|aria-label)=[\"']([A-Z][A-Za-z\s!?.,'-]{2,80})[\"']"),
32
+ ),
33
+ "python": (
34
+ re.compile(r"\bflash\s*\(\s*[\"']([A-Z][^\"']{4,100})[\"']"),
35
+ ),
36
+ }
37
+
38
+
39
+ def is_skipped(path: Path, root: Path) -> bool:
40
+ try:
41
+ rel = path.relative_to(root)
42
+ except ValueError:
43
+ rel = path
44
+ return any(part in SKIP_DIRS for part in rel.parts)
45
+
46
+
47
+ def find_locale_files(root: Path) -> list[Path]:
48
+ patterns = (
49
+ "**/locales/**/*.json", "**/translations/**/*.json", "**/lang/**/*.json",
50
+ "**/i18n/**/*.json", "**/messages/*.json", "**/*.po",
51
+ )
52
+ found: set[Path] = set()
53
+ for pattern in patterns:
54
+ for path in root.glob(pattern):
55
+ if path.is_file() and not is_skipped(path, root):
56
+ found.add(path)
57
+ return sorted(found)
58
+
59
+
60
+ def flatten_keys(value: Any, prefix: str = "") -> set[str]:
61
+ keys: set[str] = set()
62
+ if not isinstance(value, dict):
63
+ return keys
64
+ for key, child in value.items():
65
+ name = f"{prefix}.{key}" if prefix else str(key)
66
+ if isinstance(child, dict):
67
+ keys.update(flatten_keys(child, name))
68
+ else:
69
+ keys.add(name)
70
+ return keys
71
+
72
+
73
+ def check_locale_completeness(locale_files: list[Path], root: Path) -> dict[str, Any]:
74
+ if not locale_files:
75
+ return {"status": "not_applicable", "files": 0, "languages": [], "issues": [], "notes": ["No locale files found."]}
76
+
77
+ locales: dict[str, dict[str, set[str]]] = {}
78
+ issues: list[dict[str, str]] = []
79
+ for path in locale_files:
80
+ if path.suffix == ".po":
81
+ continue
82
+ try:
83
+ content = json.loads(path.read_text("utf-8"))
84
+ except (OSError, json.JSONDecodeError) as exc:
85
+ issues.append({"severity": "high", "file": path.relative_to(root).as_posix(), "issue": f"Invalid locale JSON: {exc}"})
86
+ continue
87
+ language = path.parent.name
88
+ locales.setdefault(language, {})[path.stem] = flatten_keys(content)
89
+
90
+ languages = sorted(locales)
91
+ if len(languages) >= 2:
92
+ base = languages[0]
93
+ namespaces = set().union(*(set(locales[lang]) for lang in languages))
94
+ for namespace in sorted(namespaces):
95
+ base_keys = locales[base].get(namespace, set())
96
+ for language in languages[1:]:
97
+ keys = locales[language].get(namespace, set())
98
+ missing = sorted(base_keys - keys)
99
+ extra = sorted(keys - base_keys)
100
+ if missing:
101
+ issues.append({"severity": "high", "file": f"{language}/{namespace}", "issue": f"Missing {len(missing)} key(s): {', '.join(missing[:8])}"})
102
+ if extra:
103
+ issues.append({"severity": "medium", "file": f"{language}/{namespace}", "issue": f"Has {len(extra)} extra key(s)"})
104
+
105
+ return {"status": "checked", "files": len(locale_files), "languages": languages, "issues": issues, "notes": []}
106
+
107
+
108
+ def iter_code_files(root: Path):
109
+ for path in root.rglob("*"):
110
+ if path.is_file() and path.suffix.lower() in CODE_TYPES and not is_skipped(path, root):
111
+ yield path
112
+
113
+
114
+ def project_uses_i18n(root: Path, code_files: list[Path], locale_files: list[Path]) -> bool:
115
+ if locale_files:
116
+ return True
117
+ package = root / "package.json"
118
+ if package.is_file():
119
+ try:
120
+ text = package.read_text("utf-8").lower()
121
+ if any(name in text for name in ("i18next", "next-intl", "react-intl", "vue-i18n", "@lingui")):
122
+ return True
123
+ except OSError:
124
+ pass
125
+ for path in code_files[:200]:
126
+ try:
127
+ content = path.read_text("utf-8", errors="ignore")
128
+ except OSError:
129
+ continue
130
+ if any(pattern.search(content) for pattern in I18N_PATTERNS):
131
+ return True
132
+ return False
133
+
134
+
135
+ def check_hardcoded_strings(root: Path, code_files: list[Path], enforce: bool) -> dict[str, Any]:
136
+ if not enforce:
137
+ return {"status": "not_applicable", "files_checked": 0, "files_using_i18n": 0, "issues": [], "notes": ["No i18n framework or locale files detected."]}
138
+
139
+ issues: list[dict[str, Any]] = []
140
+ files_using_i18n = 0
141
+ checked = 0
142
+ for path in code_files[:500]:
143
+ try:
144
+ content = path.read_text("utf-8", errors="ignore")
145
+ except OSError:
146
+ continue
147
+ checked += 1
148
+ has_i18n = any(pattern.search(content) for pattern in I18N_PATTERNS)
149
+ if has_i18n:
150
+ files_using_i18n += 1
151
+ if has_i18n:
152
+ continue
153
+ file_type = CODE_TYPES[path.suffix.lower()]
154
+ for pattern in HARDCODED_PATTERNS[file_type]:
155
+ for match in pattern.finditer(content):
156
+ value = match.group(1).strip()
157
+ if value.lower().startswith(("http", "error", "warning")):
158
+ continue
159
+ line = content.count("\n", 0, match.start()) + 1
160
+ issues.append({
161
+ "severity": "medium",
162
+ "file": path.relative_to(root).as_posix(),
163
+ "line": line,
164
+ "issue": "Likely user-facing hard-coded string",
165
+ "text": value[:100],
166
+ })
167
+ if len(issues) >= 100:
168
+ break
169
+ if len(issues) >= 100:
170
+ break
171
+ if len(issues) >= 100:
172
+ break
173
+ return {"status": "checked", "files_checked": checked, "files_using_i18n": files_using_i18n, "issues": issues, "notes": []}
174
+
175
+
176
+ def audit(root: Path, strict: bool = False) -> dict[str, Any]:
177
+ locale_files = find_locale_files(root)
178
+ code_files = list(iter_code_files(root))
179
+ enforce = strict or project_uses_i18n(root, code_files, locale_files)
180
+ locale = check_locale_completeness(locale_files, root)
181
+ code = check_hardcoded_strings(root, code_files, enforce)
182
+ findings = locale["issues"] + code["issues"]
183
+ high = sum(item["severity"] == "high" for item in findings)
184
+ medium = sum(item["severity"] == "medium" for item in findings)
185
+ return {
186
+ "project": str(root),
187
+ "applicable": enforce,
188
+ "locale_check": locale,
189
+ "code_check": code,
190
+ "summary": {"total": len(findings), "high": high, "medium": medium, "passed": high == 0},
191
+ }
192
+
193
+
194
+ def main() -> int:
195
+ parser = argparse.ArgumentParser(description="Check locale completeness and likely hard-coded UI strings")
196
+ parser.add_argument("project", nargs="?", default=".")
197
+ parser.add_argument("--strict", action="store_true", help="Scan hard-coded strings even when no i18n setup is detected")
198
+ parser.add_argument("--json", action="store_true", dest="as_json")
199
+ args = parser.parse_args()
200
+ root = Path(args.project).resolve()
201
+ if not root.is_dir():
202
+ parser.error(f"Project directory does not exist: {root}")
203
+ report = audit(root, args.strict)
204
+ if args.as_json:
205
+ print(json.dumps(report, indent=2, ensure_ascii=False))
206
+ else:
207
+ print(f"i18n Audit: {root}")
208
+ if not report["applicable"]:
209
+ print("[SKIP] No i18n framework or locale files detected.")
210
+ else:
211
+ locale = report["locale_check"]
212
+ code = report["code_check"]
213
+ print(f"Locale files: {locale['files']} | Languages: {', '.join(locale['languages']) or 'unknown'}")
214
+ print(f"Code files checked: {code['files_checked']} | Files using i18n: {code['files_using_i18n']}")
215
+ for item in (locale["issues"] + code["issues"])[:30]:
216
+ line = f":{item['line']}" if "line" in item else ""
217
+ print(f"[{item['severity'].upper()}] {item['file']}{line} - {item['issue']}")
218
+ print("[PASS] No blocking i18n issues." if report["summary"]["passed"] else "[FAIL] Blocking i18n issues found.")
219
+ return 0 if report["summary"]["passed"] else 1
220
+
221
+
222
+ if __name__ == "__main__":
223
+ raise SystemExit(main())