ai-design-context 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 (288) hide show
  1. package/AGENTS.md +156 -0
  2. package/CHANGELOG.md +71 -0
  3. package/CODE_OF_CONDUCT.md +29 -0
  4. package/CONTRIBUTING.md +128 -0
  5. package/LICENSE +21 -0
  6. package/README.md +283 -0
  7. package/RELEASE_NOTES_v0.1.0.md +45 -0
  8. package/RELEASE_NOTES_v0.3.0.md +47 -0
  9. package/RELEASE_NOTES_v0.4.0.md +29 -0
  10. package/RELEASE_READINESS.md +107 -0
  11. package/ROADMAP.md +60 -0
  12. package/SECURITY.md +40 -0
  13. package/assets/logo.svg +9 -0
  14. package/benchmarks/README.md +19 -0
  15. package/benchmarks/chat.md +37 -0
  16. package/benchmarks/mobile-home-screen.md +38 -0
  17. package/benchmarks/notes-app.md +37 -0
  18. package/benchmarks/settings.md +37 -0
  19. package/benchmarks/shopping-list.md +37 -0
  20. package/benchmarks/todo-app.md +144 -0
  21. package/checklists/DESIGN_QA.md +102 -0
  22. package/checklists/GENERATED_INDEX.md +7 -0
  23. package/docs/AGENT_CONTEXT.md +111 -0
  24. package/docs/BENCHMARK.md +170 -0
  25. package/docs/DESIGNLINT_READINESS.md +110 -0
  26. package/docs/DESIGN_SYSTEM.md +23 -0
  27. package/docs/EVALUATION_RUBRIC.md +154 -0
  28. package/docs/GITHUB_SETUP.md +28 -0
  29. package/docs/GLOSSARY.md +45 -0
  30. package/docs/INDEX.md +109 -0
  31. package/docs/KNOWLEDGE_ENGINE.md +594 -0
  32. package/docs/MOBILE_FIRST.md +18 -0
  33. package/docs/NPM_RELEASE.md +43 -0
  34. package/docs/PATTERN_SPEC.md +113 -0
  35. package/docs/PHILOSOPHY.md +25 -0
  36. package/docs/PRODUCT_THINKING.md +26 -0
  37. package/docs/STYLE_GUIDE.md +49 -0
  38. package/docs/TRACEABILITY.md +45 -0
  39. package/evidence/README.md +64 -0
  40. package/evidence/TEMPLATE.md +46 -0
  41. package/evidence/chat/.gitkeep +1 -0
  42. package/evidence/mobile-home/.gitkeep +1 -0
  43. package/evidence/notes/.gitkeep +1 -0
  44. package/evidence/settings/.gitkeep +1 -0
  45. package/evidence/shopping/.gitkeep +1 -0
  46. package/evidence/todo/.gitkeep +1 -0
  47. package/evidence/todo/2026-06-25-codex-gpt-5/EVALUATION.md +66 -0
  48. package/evidence/todo/2026-06-25-codex-gpt-5/README.md +40 -0
  49. package/evidence/todo/2026-06-25-codex-gpt-5/ai-design-rules/generated-output.md +163 -0
  50. package/evidence/todo/2026-06-25-codex-gpt-5/ai-design-rules/metadata.json +38 -0
  51. package/evidence/todo/2026-06-25-codex-gpt-5/ai-design-rules/notes.md +20 -0
  52. package/evidence/todo/2026-06-25-codex-gpt-5/ai-design-rules/prompt.md +16 -0
  53. package/evidence/todo/2026-06-25-codex-gpt-5/ai-design-rules/scores.md +18 -0
  54. package/evidence/todo/2026-06-25-codex-gpt-5/baseline/generated-output.md +98 -0
  55. package/evidence/todo/2026-06-25-codex-gpt-5/baseline/metadata.json +19 -0
  56. package/evidence/todo/2026-06-25-codex-gpt-5/baseline/notes.md +19 -0
  57. package/evidence/todo/2026-06-25-codex-gpt-5/baseline/prompt.md +9 -0
  58. package/evidence/todo/2026-06-25-codex-gpt-5/baseline/scores.md +18 -0
  59. package/evidence/todo/2026-09-28-codex-paired-todo/EVALUATION.md +70 -0
  60. package/evidence/todo/2026-09-28-codex-paired-todo/README.md +52 -0
  61. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/GENERATION_NOTES.md +133 -0
  62. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/app.js +289 -0
  63. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/context/context-preserving-preview.json +443 -0
  64. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/context/daily-home-surface.json +373 -0
  65. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/context/quick-capture.json +301 -0
  66. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/context/sources-read.txt +36 -0
  67. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/generated-output.md +11 -0
  68. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/index.html +64 -0
  69. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/launch-prompt.md +1 -0
  70. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/metadata.json +55 -0
  71. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/notes.md +15 -0
  72. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/parent-status-message.md +1 -0
  73. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/prompt.md +49 -0
  74. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/runtime-checks.json +776 -0
  75. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/scores.md +20 -0
  76. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-completed.png +0 -0
  77. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-default.png +0 -0
  78. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-detail-reduced-motion.png +0 -0
  79. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-detail.png +0 -0
  80. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-empty.png +0 -0
  81. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-keyboard-focus.png +0 -0
  82. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-save-error.png +0 -0
  83. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-saved.png +0 -0
  84. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-saving.png +0 -0
  85. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-validation.png +0 -0
  86. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-completed.png +0 -0
  87. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-default.png +0 -0
  88. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-detail-reduced-motion.png +0 -0
  89. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-detail.png +0 -0
  90. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-empty.png +0 -0
  91. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-keyboard-focus.png +0 -0
  92. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-save-error.png +0 -0
  93. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-saved.png +0 -0
  94. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-saving.png +0 -0
  95. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-validation.png +0 -0
  96. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/source-hashes.json +5 -0
  97. package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/styles.css +122 -0
  98. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/GENERATION_NOTES.md +86 -0
  99. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/app.js +303 -0
  100. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/generated-output.md +11 -0
  101. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/index.html +71 -0
  102. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/launch-prompt.md +1 -0
  103. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/metadata.json +55 -0
  104. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/notes.md +15 -0
  105. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/parent-status-message.md +1 -0
  106. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/prompt.md +49 -0
  107. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/runtime-checks.json +772 -0
  108. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/scores.md +20 -0
  109. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-completed.png +0 -0
  110. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-default.png +0 -0
  111. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-detail-reduced-motion.png +0 -0
  112. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-detail.png +0 -0
  113. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-empty.png +0 -0
  114. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-keyboard-focus.png +0 -0
  115. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-save-error.png +0 -0
  116. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-saved.png +0 -0
  117. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-saving.png +0 -0
  118. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-validation.png +0 -0
  119. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-completed.png +0 -0
  120. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-default.png +0 -0
  121. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-detail-reduced-motion.png +0 -0
  122. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-detail.png +0 -0
  123. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-empty.png +0 -0
  124. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-keyboard-focus.png +0 -0
  125. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-save-error.png +0 -0
  126. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-saved.png +0 -0
  127. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-saving.png +0 -0
  128. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-validation.png +0 -0
  129. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/source-hashes.json +5 -0
  130. package/evidence/todo/2026-09-28-codex-paired-todo/baseline/styles.css +9 -0
  131. package/evidence/todo/2026-09-28-codex-paired-todo/capture-ai-design-rules.js +60 -0
  132. package/evidence/todo/2026-09-28-codex-paired-todo/capture-baseline.js +60 -0
  133. package/evidence/todo/2026-09-28-codex-paired-todo/common/brief.md +35 -0
  134. package/evidence/todo/2026-09-28-codex-paired-todo/common/knowledge-files.json +127 -0
  135. package/evidence/todo/2026-09-28-codex-paired-todo/common/knowledge.patch +890 -0
  136. package/evidence/todo/2026-09-28-codex-paired-todo/common/review-context.json +550 -0
  137. package/evidence/todo/2026-09-28-codex-paired-todo/common/seed.json +86 -0
  138. package/evidence/todo/2026-09-28-codex-paired-todo/common/setup.json +47 -0
  139. package/evidence/todo/2026-09-28-codex-paired-todo/common/technical-envelope.md +11 -0
  140. package/evidence/todo/2026-09-28-codex-paired-todo/layout-metrics.json +133 -0
  141. package/evidence/todo/2026-09-28-codex-paired-todo/layout-probe.js +15 -0
  142. package/examples/GENERATED_INDEX.md +8 -0
  143. package/examples/INDEX.md +16 -0
  144. package/examples/README.md +15 -0
  145. package/examples/consequential-action-confirmation-reference.md +83 -0
  146. package/examples/todo-app-benchmark.md +117 -0
  147. package/examples/todo-reference/README.md +29 -0
  148. package/examples/todo-reference/app.js +154 -0
  149. package/examples/todo-reference/favicon.svg +4 -0
  150. package/examples/todo-reference/index.html +67 -0
  151. package/examples/todo-reference/review-evidence/2026-07-15/desktop-default.png +0 -0
  152. package/examples/todo-reference/review-evidence/2026-07-15/desktop-detail.png +0 -0
  153. package/examples/todo-reference/review-evidence/2026-07-15/mobile-detail.png +0 -0
  154. package/examples/todo-reference/review-evidence/2026-07-15/mobile-error.png +0 -0
  155. package/examples/todo-reference/styles.css +241 -0
  156. package/graph/GENERATED_GRAPH.md +71 -0
  157. package/observations/README.md +23 -0
  158. package/observations/accessibility/reduced-interaction-motion.md +49 -0
  159. package/observations/accessibility/textual-error-recovery.md +50 -0
  160. package/observations/accessibility/visible-keyboard-focus.md +49 -0
  161. package/observations/ai/agent-consequential-action-controls.md +56 -0
  162. package/observations/ai/agent-work-visibility-and-intervention.md +51 -0
  163. package/observations/ai/figma-agent-context-and-skills.md +52 -0
  164. package/observations/finance/high-risk-wallet-recovery-flows.md +55 -0
  165. package/observations/finance/market-interface-adaptive-trading-surfaces.md +55 -0
  166. package/observations/interaction/chrome-scoped-view-transitions.md +52 -0
  167. package/observations/interaction/figma-motion-as-system-capability.md +53 -0
  168. package/observations/mobile/mobile-bottom-sheet-choice-contracts.md +55 -0
  169. package/observations/performance/reserved-loading-space.md +51 -0
  170. package/observations/product-repos/pinned-ai-design-rules-integration.md +49 -0
  171. package/observations/visual/apple-liquid-glass-functional-layer.md +52 -0
  172. package/observations/visual/chrome-content-first-adaptive-ui.md +51 -0
  173. package/observations/visual/material-3-expressive-system.md +52 -0
  174. package/package.json +65 -0
  175. package/patterns/GENERATED_INDEX.md +13 -0
  176. package/patterns/INDEX.md +41 -0
  177. package/patterns/README.md +5 -0
  178. package/patterns/consequential-action-confirmation.md +127 -0
  179. package/patterns/context-preserving-preview.md +145 -0
  180. package/patterns/daily-home-surface.md +134 -0
  181. package/patterns/mobile-primary-action.md +110 -0
  182. package/patterns/object-status-list.md +114 -0
  183. package/patterns/progressive-detail.md +113 -0
  184. package/patterns/quick-capture.md +132 -0
  185. package/prompts/CONSEQUENTIAL_ACTION_REVIEW.md +100 -0
  186. package/prompts/DESIGN_EVIDENCE_REVIEW.md +49 -0
  187. package/prompts/GENERATED_INDEX.md +11 -0
  188. package/prompts/INDEX.md +24 -0
  189. package/prompts/PROTOTYPE_REVIEW.md +189 -0
  190. package/prompts/QUICK_CAPTURE_STATE_REVIEW.md +74 -0
  191. package/prompts/TODO_APP_BENCHMARK.md +88 -0
  192. package/registry/objects.json +568 -0
  193. package/registry/relationships.json +972 -0
  194. package/research/GENERATED_INDEX.md +18 -0
  195. package/research/INDEX.md +40 -0
  196. package/research/accessibility/keyboard-focus-and-interaction-motion.md +62 -0
  197. package/research/accessibility/textual-error-recovery.md +57 -0
  198. package/research/performance/reserved-loading-space.md +56 -0
  199. package/research/products/apple-reminders.md +59 -0
  200. package/research/products/arc.md +61 -0
  201. package/research/products/linear.md +62 -0
  202. package/research/products/telegram.md +61 -0
  203. package/research/products/things-3.md +60 -0
  204. package/research/ux/contextual-agentic-interface.md +68 -0
  205. package/research/ux/high-risk-operational-flows.md +89 -0
  206. package/research/ux/motion-as-state-continuity.md +71 -0
  207. package/research/visual/expressive-system-ui-2026.md +75 -0
  208. package/reviews/GENERATED_INDEX.md +9 -0
  209. package/reviews/README.md +25 -0
  210. package/reviews/consequential-action-confirmation-review.md +92 -0
  211. package/reviews/todo-app-benchmark-reference-review.md +86 -0
  212. package/reviews/todo-reference-fixture-review.md +77 -0
  213. package/rules/GENERATED_INDEX.md +23 -0
  214. package/rules/INDEX.md +37 -0
  215. package/rules/accessibility/A11Y-001.md +58 -0
  216. package/rules/accessibility/A11Y-002.md +55 -0
  217. package/rules/accessibility/A11Y-003.md +54 -0
  218. package/rules/accessibility/A11Y-004.md +54 -0
  219. package/rules/ia/IA-001.md +60 -0
  220. package/rules/ia/IA-002.md +61 -0
  221. package/rules/performance/PERF-001.md +55 -0
  222. package/rules/product/PRD-001.md +61 -0
  223. package/rules/product/PRD-002.md +61 -0
  224. package/rules/ux/UX-001.md +58 -0
  225. package/rules/ux/UX-002.md +61 -0
  226. package/rules/ux/UX-003.md +60 -0
  227. package/rules/ux/UX-004.md +61 -0
  228. package/rules/ux/UX-005.md +62 -0
  229. package/rules/ux/UX-006.md +69 -0
  230. package/rules/visual/VIS-001.md +59 -0
  231. package/rules/visual/VIS-002.md +59 -0
  232. package/schema/checklist.schema.json +21 -0
  233. package/schema/common.schema.json +178 -0
  234. package/schema/observation.schema.json +21 -0
  235. package/schema/pattern.schema.json +21 -0
  236. package/schema/prompt.schema.json +21 -0
  237. package/schema/reference-project.schema.json +21 -0
  238. package/schema/research.schema.json +21 -0
  239. package/schema/review.schema.json +21 -0
  240. package/schema/rule.schema.json +21 -0
  241. package/skills/README.md +48 -0
  242. package/skills/accessibility-reviewer/SKILL.md +101 -0
  243. package/skills/agent-context/SKILL.md +34 -0
  244. package/skills/agent-context/agents/openai.yaml +4 -0
  245. package/skills/design-evidence-researcher/SKILL.md +47 -0
  246. package/skills/design-evidence-researcher/agents/openai.yaml +4 -0
  247. package/skills/design-reviewer/SKILL.md +113 -0
  248. package/skills/design-system-architect/SKILL.md +101 -0
  249. package/skills/information-architect/SKILL.md +97 -0
  250. package/skills/interaction-designer/SKILL.md +104 -0
  251. package/skills/knowledge-graph-architect/SKILL.md +96 -0
  252. package/skills/mobile-ux-expert/SKILL.md +105 -0
  253. package/skills/motion-designer/SKILL.md +100 -0
  254. package/skills/performance-reviewer/SKILL.md +97 -0
  255. package/skills/product-designer/SKILL.md +104 -0
  256. package/skills/prompt-architect/SKILL.md +105 -0
  257. package/skills/reference-driven-design/SKILL.md +119 -0
  258. package/skills/ux-reviewer/SKILL.md +99 -0
  259. package/skills/visual-designer/SKILL.md +107 -0
  260. package/starter-kit/AGENTS.md +37 -0
  261. package/starter-kit/BOOTSTRAP.md +13 -0
  262. package/starter-kit/INSTALL_WITH_AGENT.md +133 -0
  263. package/starter-kit/PROJECT_INTEGRATION.md +60 -0
  264. package/starter-kit/README.md +41 -0
  265. package/starter-kit/benchmarks/BENCHMARK_CHECKLIST.md +10 -0
  266. package/starter-kit/docs/DESIGN_DECISIONS.md +22 -0
  267. package/starter-kit/docs/INFORMATION_ARCHITECTURE.md +25 -0
  268. package/starter-kit/docs/PERSONAS.md +14 -0
  269. package/starter-kit/docs/PRD.md +32 -0
  270. package/starter-kit/docs/USER_FLOWS.md +19 -0
  271. package/starter-kit/reviews/DESIGN_REVIEW.md +49 -0
  272. package/starter-kit/templates/FEATURE_TEMPLATE.md +36 -0
  273. package/starter-kit/templates/TASK_TEMPLATE.md +22 -0
  274. package/templates/CHECKLIST_TEMPLATE.md +43 -0
  275. package/templates/OBSERVATION_TEMPLATE.md +41 -0
  276. package/templates/PATTERN_TEMPLATE.md +100 -0
  277. package/templates/PROMPT_TEMPLATE.md +46 -0
  278. package/templates/PROTOTYPE_REVIEW.md +50 -0
  279. package/templates/REFERENCE_PROJECT_TEMPLATE.md +49 -0
  280. package/templates/REVIEW_TEMPLATE.md +55 -0
  281. package/templates/RULE_TEMPLATE.md +57 -0
  282. package/tools/cli.mjs +46 -0
  283. package/tools/context.mjs +342 -0
  284. package/tools/designlint.mjs +77 -0
  285. package/tools/generate-indexes.mjs +621 -0
  286. package/tools/init.mjs +99 -0
  287. package/tools/validate-benchmark-evidence.mjs +248 -0
  288. package/tools/validate-knowledge.mjs +481 -0
@@ -0,0 +1,481 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { generateIndexFiles } from './generate-indexes.mjs';
4
+
5
+ const root = process.cwd();
6
+ const requiredFields = [
7
+ 'id',
8
+ 'alias',
9
+ 'slug',
10
+ 'title',
11
+ 'object_type',
12
+ 'status',
13
+ 'version',
14
+ 'category',
15
+ 'tags',
16
+ 'maturity',
17
+ 'risk_level',
18
+ 'relationships',
19
+ ];
20
+
21
+ const idPrefixes = {
22
+ observation: 'OBS-',
23
+ research: 'RESEARCH-',
24
+ rule: 'RULE-',
25
+ pattern: 'PAT-',
26
+ prompt: 'PROMPT-',
27
+ checklist: 'CHECK-',
28
+ review: 'REVIEW-',
29
+ reference_project: 'REF-',
30
+ };
31
+
32
+ const registryBackedObjectTypes = new Set([
33
+ 'research',
34
+ 'rule',
35
+ 'pattern',
36
+ 'prompt',
37
+ 'checklist',
38
+ 'review',
39
+ 'reference_project',
40
+ ]);
41
+
42
+ const allowedRelationshipTypes = new Set([
43
+ 'derived_from',
44
+ 'cites',
45
+ 'inspired_by',
46
+ 'requires',
47
+ 'validates',
48
+ 'implements',
49
+ 'related_to',
50
+ 'supersedes',
51
+ 'deprecated_by',
52
+ 'replaced_by',
53
+ ]);
54
+ const maturityLevels = new Set(['seed', 'reviewed', 'validated', 'canonical']);
55
+
56
+ function readJson(filePath) {
57
+ return JSON.parse(fs.readFileSync(path.join(root, filePath), 'utf8'));
58
+ }
59
+
60
+ function findDuplicateJsonKeyErrors(filePath) {
61
+ const raw = fs.readFileSync(path.join(root, filePath), 'utf8');
62
+ const errors = [];
63
+
64
+ for (const objectMatch of raw.matchAll(/\{([^{}]*)\}/g)) {
65
+ const keys = new Set();
66
+
67
+ for (const keyMatch of objectMatch[1].matchAll(/"([^"\\]+)"\s*:/g)) {
68
+ const key = keyMatch[1];
69
+ if (keys.has(key)) {
70
+ errors.push(`${filePath}: duplicate JSON key ${key}`);
71
+ }
72
+ keys.add(key);
73
+ }
74
+ }
75
+
76
+ return errors;
77
+ }
78
+
79
+ function collectMarkdownFiles(dir) {
80
+ const entries = fs.readdirSync(path.join(root, dir), { withFileTypes: true });
81
+ const files = [];
82
+
83
+ for (const entry of entries) {
84
+ const fullPath = path.join(dir, entry.name);
85
+
86
+ if (entry.isDirectory()) {
87
+ files.push(...collectMarkdownFiles(fullPath));
88
+ continue;
89
+ }
90
+
91
+ if (!entry.name.endsWith('.md')) {
92
+ continue;
93
+ }
94
+
95
+ if (entry.name === 'INDEX.md' || entry.name === 'README.md' || entry.name.startsWith('GENERATED_')) {
96
+ continue;
97
+ }
98
+
99
+ files.push(fullPath);
100
+ }
101
+
102
+ return files;
103
+ }
104
+
105
+ function parseScalar(value) {
106
+ const trimmed = value.trim();
107
+ if (trimmed === '[]') {
108
+ return [];
109
+ }
110
+ if (
111
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
112
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
113
+ ) {
114
+ return trimmed.slice(1, -1);
115
+ }
116
+ return trimmed;
117
+ }
118
+
119
+ function validateSkillMetadata(filePath, metadata, errors) {
120
+ const skillName = path.basename(path.dirname(filePath));
121
+
122
+ if (metadata.name !== skillName) {
123
+ errors.push(`${filePath}: skill name must match directory ${skillName}`);
124
+ }
125
+
126
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(metadata.name ?? '')) {
127
+ errors.push(`${filePath}: skill name must use lowercase kebab-case`);
128
+ }
129
+
130
+ if (typeof metadata.description !== 'string' || metadata.description.trim() === '') {
131
+ errors.push(`${filePath}: skill description must be a non-empty string`);
132
+ }
133
+ }
134
+
135
+ function relationshipKey(relationship) {
136
+ return `${relationship.type}:${relationship.source}:${relationship.target}`;
137
+ }
138
+
139
+ function parseFrontMatter(markdown, filePath) {
140
+ if (!markdown.startsWith('---\n')) {
141
+ throw new Error(`${filePath}: missing YAML front matter`);
142
+ }
143
+
144
+ const end = markdown.indexOf('\n---', 4);
145
+ if (end === -1) {
146
+ throw new Error(`${filePath}: unterminated YAML front matter`);
147
+ }
148
+
149
+ const yaml = markdown.slice(4, end).split('\n');
150
+ const metadata = {};
151
+ let currentKey = null;
152
+ let currentRelationship = null;
153
+
154
+ for (const rawLine of yaml) {
155
+ const line = rawLine.replace(/\s+$/, '');
156
+ if (!line.trim()) {
157
+ continue;
158
+ }
159
+
160
+ const topLevel = line.match(/^([a-z_]+):(?:\s*(.*))?$/);
161
+ if (topLevel) {
162
+ currentKey = topLevel[1];
163
+ const value = topLevel[2] ?? '';
164
+
165
+ if (value === '') {
166
+ metadata[currentKey] = [];
167
+ } else {
168
+ metadata[currentKey] = parseScalar(value);
169
+ }
170
+ currentRelationship = null;
171
+ continue;
172
+ }
173
+
174
+ const listItem = line.match(/^ -\s*(.*)$/);
175
+ if (listItem && currentKey) {
176
+ if (!Array.isArray(metadata[currentKey])) {
177
+ throw new Error(`${filePath}: ${currentKey} must be an array`);
178
+ }
179
+
180
+ const item = listItem[1];
181
+ const inlinePair = item.match(/^([a-z_]+):\s*(.*)$/);
182
+
183
+ if (inlinePair) {
184
+ currentRelationship = {
185
+ [inlinePair[1]]: parseScalar(inlinePair[2]),
186
+ };
187
+ metadata[currentKey].push(currentRelationship);
188
+ } else {
189
+ metadata[currentKey].push(parseScalar(item));
190
+ currentRelationship = null;
191
+ }
192
+ continue;
193
+ }
194
+
195
+ const nestedPair = line.match(/^ ([a-z_]+):\s*(.*)$/);
196
+ if (nestedPair && currentRelationship) {
197
+ currentRelationship[nestedPair[1]] = parseScalar(nestedPair[2]);
198
+ continue;
199
+ }
200
+
201
+ throw new Error(`${filePath}: unsupported front matter line "${line}"`);
202
+ }
203
+
204
+ return metadata;
205
+ }
206
+
207
+ function validateMetadata(filePath, metadata, registryIds, errors) {
208
+ for (const field of requiredFields) {
209
+ if (!(field in metadata)) {
210
+ errors.push(`${filePath}: missing required field ${field}`);
211
+ }
212
+ }
213
+
214
+ const prefix = idPrefixes[metadata.object_type];
215
+ if (!prefix) {
216
+ errors.push(`${filePath}: unsupported object_type ${metadata.object_type}`);
217
+ } else if (!String(metadata.id || '').startsWith(prefix)) {
218
+ errors.push(`${filePath}: id ${metadata.id} does not match ${metadata.object_type}`);
219
+ }
220
+
221
+ if (!Array.isArray(metadata.tags) || metadata.tags.length === 0) {
222
+ errors.push(`${filePath}: tags must be a non-empty array`);
223
+ }
224
+
225
+ if (!maturityLevels.has(metadata.maturity)) {
226
+ errors.push(`${filePath}: unsupported maturity ${metadata.maturity}`);
227
+ }
228
+
229
+ if (metadata.maturity === 'seed' && metadata.status !== 'draft') {
230
+ errors.push(`${filePath}: seed maturity requires draft status`);
231
+ }
232
+
233
+ if (['reviewed', 'validated', 'canonical'].includes(metadata.maturity)) {
234
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(metadata.last_reviewed_at ?? '')) {
235
+ errors.push(`${filePath}: ${metadata.maturity} maturity requires last_reviewed_at in YYYY-MM-DD format`);
236
+ }
237
+ }
238
+
239
+ if (['validated', 'canonical'].includes(metadata.maturity) && metadata.status !== 'active') {
240
+ errors.push(`${filePath}: ${metadata.maturity} maturity requires active status`);
241
+ }
242
+
243
+ if (metadata.maturity === 'canonical' && !/^[1-9]\d*\.\d+\.\d+$/.test(metadata.version ?? '')) {
244
+ errors.push(`${filePath}: canonical maturity requires a stable major version`);
245
+ }
246
+
247
+ if (!Array.isArray(metadata.relationships)) {
248
+ errors.push(`${filePath}: relationships must be an array`);
249
+ return;
250
+ }
251
+
252
+ for (const relationship of metadata.relationships) {
253
+ if (!relationship.type || !relationship.target) {
254
+ errors.push(`${filePath}: relationship must include type and target`);
255
+ continue;
256
+ }
257
+
258
+ if (!allowedRelationshipTypes.has(relationship.type)) {
259
+ errors.push(`${filePath}: unsupported relationship type ${relationship.type}`);
260
+ }
261
+
262
+ if (/^[A-Z]+-\d{5}$/.test(relationship.target)) {
263
+ if (!registryIds.has(relationship.target)) {
264
+ errors.push(`${filePath}: relationship target ${relationship.target} is not in registry`);
265
+ }
266
+ continue;
267
+ }
268
+
269
+ const targetPath = path.join(root, relationship.target);
270
+ if (!fs.existsSync(targetPath)) {
271
+ errors.push(`${filePath}: relationship target ${relationship.target} does not exist`);
272
+ }
273
+ }
274
+ }
275
+
276
+ const errors = [
277
+ ...findDuplicateJsonKeyErrors('registry/objects.json'),
278
+ ...findDuplicateJsonKeyErrors('registry/relationships.json'),
279
+ ];
280
+ const registryObjects = readJson('registry/objects.json').objects;
281
+ const registryRelationships = readJson('registry/relationships.json').relationships;
282
+ const registryIds = new Set(registryObjects.map((object) => object.id));
283
+ const registryByPath = new Map(registryObjects.map((object) => [object.path, object]));
284
+ const files = [
285
+ ...collectMarkdownFiles('observations'),
286
+ ...collectMarkdownFiles('research'),
287
+ ...collectMarkdownFiles('rules'),
288
+ ...collectMarkdownFiles('patterns'),
289
+ ...collectMarkdownFiles('prompts'),
290
+ ...collectMarkdownFiles('checklists'),
291
+ ...collectMarkdownFiles('reviews'),
292
+ ...collectMarkdownFiles('examples'),
293
+ ].sort();
294
+ const skillFiles = collectMarkdownFiles('skills').filter((file) => path.basename(file) === 'SKILL.md').sort();
295
+ const aliases = new Map();
296
+ const slugs = new Map();
297
+ const ids = new Map();
298
+ const metadataById = new Map();
299
+ const frontMatterRelationshipKeys = new Set();
300
+
301
+ for (const filePath of files) {
302
+ const markdown = fs.readFileSync(path.join(root, filePath), 'utf8');
303
+ let metadata;
304
+
305
+ try {
306
+ metadata = parseFrontMatter(markdown, filePath);
307
+ } catch (error) {
308
+ errors.push(error.message);
309
+ continue;
310
+ }
311
+
312
+ validateMetadata(filePath, metadata, registryIds, errors);
313
+ if (metadata.id) {
314
+ metadataById.set(metadata.id, { filePath, metadata });
315
+ }
316
+
317
+ if (Array.isArray(metadata.relationships)) {
318
+ for (const relationship of metadata.relationships) {
319
+ if (/^[A-Z]+-\d{5}$/.test(relationship.target)) {
320
+ frontMatterRelationshipKeys.add(
321
+ relationshipKey({ ...relationship, source: metadata.id }),
322
+ );
323
+ }
324
+ }
325
+ }
326
+
327
+ for (const [map, keyName] of [
328
+ [ids, 'id'],
329
+ [aliases, 'alias'],
330
+ [slugs, 'slug'],
331
+ ]) {
332
+ const key = metadata[keyName];
333
+ if (!key) {
334
+ continue;
335
+ }
336
+ if (map.has(key)) {
337
+ errors.push(`${filePath}: duplicate ${keyName} ${key}; first seen in ${map.get(key)}`);
338
+ } else {
339
+ map.set(key, filePath);
340
+ }
341
+ }
342
+
343
+ if (!registryBackedObjectTypes.has(metadata.object_type)) {
344
+ continue;
345
+ }
346
+
347
+ const registryObject = registryByPath.get(filePath);
348
+ if (!registryObject) {
349
+ errors.push(`${filePath}: missing registry object`);
350
+ continue;
351
+ }
352
+
353
+ for (const field of ['id', 'alias', 'slug', 'title', 'object_type', 'status', 'version', 'category', 'maturity']) {
354
+ if (registryObject[field] !== metadata[field]) {
355
+ errors.push(
356
+ `${filePath}: registry ${field} is ${registryObject[field] ?? '<missing>'}, expected ${metadata[field]}`,
357
+ );
358
+ }
359
+ }
360
+ }
361
+
362
+ for (const filePath of skillFiles) {
363
+ const markdown = fs.readFileSync(path.join(root, filePath), 'utf8');
364
+
365
+ try {
366
+ validateSkillMetadata(filePath, parseFrontMatter(markdown, filePath), errors);
367
+ } catch (error) {
368
+ errors.push(error.message);
369
+ }
370
+ }
371
+
372
+ for (const [field, label] of [
373
+ ['id', 'id'],
374
+ ['alias', 'alias'],
375
+ ['slug', 'slug'],
376
+ ]) {
377
+ const values = new Map();
378
+
379
+ for (const object of registryObjects) {
380
+ const value = object[field];
381
+ if (values.has(value)) {
382
+ errors.push(
383
+ `registry/objects.json: duplicate ${label} ${value}; first seen in ${values.get(value)}`,
384
+ );
385
+ } else {
386
+ values.set(value, object.id);
387
+ }
388
+ }
389
+ }
390
+
391
+ for (const object of registryObjects) {
392
+ if (!registryBackedObjectTypes.has(object.object_type)) {
393
+ continue;
394
+ }
395
+
396
+ const filePath = ids.get(object.id);
397
+ if (!filePath) {
398
+ errors.push(`registry/objects.json: ${object.id} has no matching knowledge file`);
399
+ } else if (filePath !== object.path) {
400
+ errors.push(
401
+ `registry/objects.json: ${object.id} path is ${object.path}, expected ${filePath}`,
402
+ );
403
+ }
404
+ }
405
+
406
+ for (const [id, { filePath, metadata }] of metadataById) {
407
+ if (!registryBackedObjectTypes.has(metadata.object_type) || !['validated', 'canonical'].includes(metadata.maturity)) {
408
+ continue;
409
+ }
410
+
411
+ const hasValidationRelationship = registryRelationships.some(
412
+ (relationship) => relationship.type === 'validates' && (relationship.source === id || relationship.target === id),
413
+ );
414
+ if (!hasValidationRelationship) {
415
+ errors.push(`${filePath}: ${metadata.maturity} maturity requires a validates relationship`);
416
+ }
417
+ }
418
+
419
+ for (const relationship of registryRelationships) {
420
+ if (!allowedRelationshipTypes.has(relationship.type)) {
421
+ errors.push(`registry/relationships.json: unsupported relationship type ${relationship.type}`);
422
+ }
423
+
424
+ if (!registryIds.has(relationship.source)) {
425
+ errors.push(`registry/relationships.json: source ${relationship.source} is not in registry`);
426
+ }
427
+
428
+ if (!registryIds.has(relationship.target)) {
429
+ errors.push(`registry/relationships.json: target ${relationship.target} is not in registry`);
430
+ }
431
+
432
+ if (!frontMatterRelationshipKeys.has(relationshipKey(relationship))) {
433
+ errors.push(
434
+ `registry/relationships.json: ${relationship.type} ${relationship.source} -> ${relationship.target} is missing from front matter`,
435
+ );
436
+ }
437
+ }
438
+
439
+ for (const relationshipKeyFromFrontMatter of frontMatterRelationshipKeys) {
440
+ if (!registryRelationships.some((relationship) => relationshipKey(relationship) === relationshipKeyFromFrontMatter)) {
441
+ errors.push(
442
+ `knowledge front matter: ${relationshipKeyFromFrontMatter} is missing from registry/relationships.json`,
443
+ );
444
+ }
445
+ }
446
+
447
+ const generatedFiles = generateIndexFiles(root);
448
+ for (const [filePath, expectedContent] of Object.entries(generatedFiles)) {
449
+ const fullPath = path.join(root, filePath);
450
+
451
+ if (!fs.existsSync(fullPath)) {
452
+ errors.push(`${filePath}: generated index is missing; run npm run generate:indexes`);
453
+ continue;
454
+ }
455
+
456
+ const actualContent = fs.readFileSync(fullPath, 'utf8');
457
+ if (actualContent !== expectedContent) {
458
+ errors.push(`${filePath}: generated index is out of sync; run npm run generate:indexes`);
459
+ }
460
+ }
461
+
462
+ if (errors.length > 0) {
463
+ console.error(`Knowledge validation failed with ${errors.length} error(s):`);
464
+ for (const error of errors) {
465
+ console.error(`- ${error}`);
466
+ }
467
+ process.exit(1);
468
+ }
469
+
470
+ const ruleCount = files.filter((file) => file.startsWith('rules/')).length;
471
+ const patternCount = files.filter((file) => file.startsWith('patterns/')).length;
472
+ const researchCount = files.filter((file) => file.startsWith('research/')).length;
473
+ const promptCount = files.filter((file) => file.startsWith('prompts/')).length;
474
+ const checklistCount = files.filter((file) => file.startsWith('checklists/')).length;
475
+ const reviewCount = files.filter((file) => file.startsWith('reviews/')).length;
476
+ const referenceProjectCount = files.filter((file) => file.startsWith('examples/')).length;
477
+ const observationCount = files.filter((file) => file.startsWith('observations/')).length;
478
+
479
+ console.log(
480
+ `Knowledge validation passed: ${observationCount} observations, ${researchCount} research, ${ruleCount} rules, ${patternCount} patterns, ${promptCount} prompts, ${checklistCount} checklists, ${reviewCount} reviews, ${referenceProjectCount} reference projects, ${skillFiles.length} skills.`,
481
+ );