create-yss-spec 3.5.2 → 3.5.3

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 (438) hide show
  1. package/package.json +1 -1
  2. package/src/template/asset-runtime.js +3 -1
  3. package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
  4. package/template/.agents/skills/codebase-design/SKILL.md +4 -0
  5. package/template/{.codex/skills/improve-codebase-architecture/SKILL.md → .agents/skills/codebase-design/references/architecture-audit.md} +3 -9
  6. package/template/.agents/skills/frontend-commit/SKILL.md +4 -93
  7. package/template/.agents/skills/git-commit-core/SKILL.md +18 -0
  8. package/template/.agents/skills/java-backend-commit/SKILL.md +4 -96
  9. package/template/.agents/skills/llm-wiki/SKILL.md +12 -13
  10. package/template/.agents/skills/llm-wiki/assets/CLAUDE.md.template +7 -1
  11. package/template/.agents/skills/llm-wiki/references/compile.md +21 -114
  12. package/template/.agents/skills/llm-wiki/references/ingest.md +8 -33
  13. package/template/.agents/skills/llm-wiki/references/lint.md +9 -46
  14. package/template/.agents/skills/llm-wiki/references/query.md +9 -18
  15. package/template/.agents/skills/llm-wiki/references/schema.md +56 -29
  16. package/template/.agents/skills/llm-wiki/references/transactions.md +48 -0
  17. package/template/.agents/skills/llm-wiki/references/writing.md +4 -4
  18. package/template/.agents/skills/llm-wiki/scripts/advise.mjs +46 -58
  19. package/template/.agents/skills/llm-wiki/scripts/advise.test.mjs +11 -16
  20. package/template/.agents/skills/llm-wiki/scripts/core.mjs +262 -0
  21. package/template/.agents/skills/llm-wiki/scripts/extract.mjs +35 -18
  22. package/template/.agents/skills/llm-wiki/scripts/extract.test.mjs +5 -8
  23. package/template/.agents/skills/llm-wiki/scripts/feedback.mjs +51 -0
  24. package/template/.agents/skills/llm-wiki/scripts/inventory.mjs +43 -171
  25. package/template/.agents/skills/llm-wiki/scripts/inventory.test.mjs +25 -18
  26. package/template/.agents/skills/llm-wiki/scripts/lint-wikilinks.mjs +69 -92
  27. package/template/.agents/skills/llm-wiki/scripts/lint-wikilinks.test.mjs +25 -37
  28. package/template/.agents/skills/llm-wiki/scripts/migrate.mjs +51 -0
  29. package/template/.agents/skills/llm-wiki/scripts/query.mjs +149 -0
  30. package/template/.agents/skills/llm-wiki/scripts/regressions.test.mjs +101 -0
  31. package/template/.agents/skills/llm-wiki/scripts/sources.mjs +166 -0
  32. package/template/.agents/skills/llm-wiki/scripts/transaction.mjs +384 -0
  33. package/template/.agents/skills/llm-wiki/scripts/v2.test.mjs +354 -0
  34. package/template/.agents/skills/prototype/LOGIC.md +1 -1
  35. package/template/.agents/skills/prototype/SKILL.md +5 -19
  36. package/template/.agents/skills/setup-matt-pocock-skills/SKILL.md +3 -3
  37. package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
  38. package/template/.agents/skills/yss-implementation-contract-compiler/references/boundaries.md +2 -3
  39. package/template/.agents/skills/yss-implementation-contract-compiler/references/yss-skill-execution-result.md +14 -0
  40. package/template/.agents/skills/yss-product-lifecycle/SKILL.md +8 -8
  41. package/template/.agents/skills/yss-product-lifecycle/references/external-input-questionnaire.md +19 -0
  42. package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +5 -5
  43. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +20 -4
  44. package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  45. package/template/.agents/skills/yss-product-lifecycle/references/plan-requirements.md +10 -0
  46. package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +1 -1
  47. package/template/.agents/skills/yss-research/SKILL.md +4 -0
  48. package/template/.codex/skills/codebase-design/SKILL.md +4 -0
  49. package/template/{.pi/skills/improve-codebase-architecture/SKILL.md → .codex/skills/codebase-design/references/architecture-audit.md} +3 -9
  50. package/template/.codex/skills/frontend-commit/SKILL.md +4 -93
  51. package/template/.codex/skills/git-commit-core/SKILL.md +18 -0
  52. package/template/.codex/skills/java-backend-commit/SKILL.md +4 -96
  53. package/template/.codex/skills/llm-wiki/SKILL.md +12 -13
  54. package/template/.codex/skills/llm-wiki/assets/CLAUDE.md.template +7 -1
  55. package/template/.codex/skills/llm-wiki/references/compile.md +21 -114
  56. package/template/.codex/skills/llm-wiki/references/ingest.md +8 -33
  57. package/template/.codex/skills/llm-wiki/references/lint.md +9 -46
  58. package/template/.codex/skills/llm-wiki/references/query.md +9 -18
  59. package/template/.codex/skills/llm-wiki/references/schema.md +56 -29
  60. package/template/.codex/skills/llm-wiki/references/transactions.md +48 -0
  61. package/template/.codex/skills/llm-wiki/references/writing.md +4 -4
  62. package/template/.codex/skills/llm-wiki/scripts/advise.mjs +46 -58
  63. package/template/.codex/skills/llm-wiki/scripts/advise.test.mjs +11 -16
  64. package/template/.codex/skills/llm-wiki/scripts/core.mjs +262 -0
  65. package/template/.codex/skills/llm-wiki/scripts/extract.mjs +35 -18
  66. package/template/.codex/skills/llm-wiki/scripts/extract.test.mjs +5 -8
  67. package/template/.codex/skills/llm-wiki/scripts/feedback.mjs +51 -0
  68. package/template/.codex/skills/llm-wiki/scripts/inventory.mjs +43 -171
  69. package/template/.codex/skills/llm-wiki/scripts/inventory.test.mjs +25 -18
  70. package/template/.codex/skills/llm-wiki/scripts/lint-wikilinks.mjs +69 -92
  71. package/template/.codex/skills/llm-wiki/scripts/lint-wikilinks.test.mjs +25 -37
  72. package/template/.codex/skills/llm-wiki/scripts/migrate.mjs +51 -0
  73. package/template/.codex/skills/llm-wiki/scripts/query.mjs +149 -0
  74. package/template/.codex/skills/llm-wiki/scripts/regressions.test.mjs +101 -0
  75. package/template/.codex/skills/llm-wiki/scripts/sources.mjs +166 -0
  76. package/template/.codex/skills/llm-wiki/scripts/transaction.mjs +384 -0
  77. package/template/.codex/skills/llm-wiki/scripts/v2.test.mjs +354 -0
  78. package/template/.codex/skills/prototype/LOGIC.md +1 -1
  79. package/template/.codex/skills/prototype/SKILL.md +5 -19
  80. package/template/.codex/skills/setup-matt-pocock-skills/SKILL.md +3 -3
  81. package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
  82. package/template/.codex/skills/yss-implementation-contract-compiler/references/boundaries.md +2 -3
  83. package/template/.codex/skills/yss-implementation-contract-compiler/references/yss-skill-execution-result.md +14 -0
  84. package/template/.codex/skills/yss-product-lifecycle/SKILL.md +8 -8
  85. package/template/.codex/skills/yss-product-lifecycle/references/external-input-questionnaire.md +19 -0
  86. package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +5 -5
  87. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +20 -4
  88. package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  89. package/template/.codex/skills/yss-product-lifecycle/references/plan-requirements.md +10 -0
  90. package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +1 -1
  91. package/template/.codex/skills/yss-research/SKILL.md +4 -0
  92. package/template/.cursor/skills/codebase-design/SKILL.md +4 -0
  93. package/template/.cursor/skills/{improve-codebase-architecture/SKILL.md → codebase-design/references/architecture-audit.md} +3 -9
  94. package/template/.cursor/skills/frontend-commit/SKILL.md +4 -93
  95. package/template/.cursor/skills/git-commit-core/SKILL.md +18 -0
  96. package/template/.cursor/skills/java-backend-commit/SKILL.md +4 -96
  97. package/template/.cursor/skills/llm-wiki/SKILL.md +12 -13
  98. package/template/.cursor/skills/llm-wiki/assets/CLAUDE.md.template +7 -1
  99. package/template/.cursor/skills/llm-wiki/references/compile.md +21 -114
  100. package/template/.cursor/skills/llm-wiki/references/ingest.md +8 -33
  101. package/template/.cursor/skills/llm-wiki/references/lint.md +9 -46
  102. package/template/.cursor/skills/llm-wiki/references/query.md +9 -18
  103. package/template/.cursor/skills/llm-wiki/references/schema.md +56 -29
  104. package/template/.cursor/skills/llm-wiki/references/transactions.md +48 -0
  105. package/template/.cursor/skills/llm-wiki/references/writing.md +4 -4
  106. package/template/.cursor/skills/llm-wiki/scripts/advise.mjs +46 -58
  107. package/template/.cursor/skills/llm-wiki/scripts/advise.test.mjs +11 -16
  108. package/template/.cursor/skills/llm-wiki/scripts/core.mjs +262 -0
  109. package/template/.cursor/skills/llm-wiki/scripts/extract.mjs +35 -18
  110. package/template/.cursor/skills/llm-wiki/scripts/extract.test.mjs +5 -8
  111. package/template/.cursor/skills/llm-wiki/scripts/feedback.mjs +51 -0
  112. package/template/.cursor/skills/llm-wiki/scripts/inventory.mjs +43 -171
  113. package/template/.cursor/skills/llm-wiki/scripts/inventory.test.mjs +25 -18
  114. package/template/.cursor/skills/llm-wiki/scripts/lint-wikilinks.mjs +69 -92
  115. package/template/.cursor/skills/llm-wiki/scripts/lint-wikilinks.test.mjs +25 -37
  116. package/template/.cursor/skills/llm-wiki/scripts/migrate.mjs +51 -0
  117. package/template/.cursor/skills/llm-wiki/scripts/query.mjs +149 -0
  118. package/template/.cursor/skills/llm-wiki/scripts/regressions.test.mjs +101 -0
  119. package/template/.cursor/skills/llm-wiki/scripts/sources.mjs +166 -0
  120. package/template/.cursor/skills/llm-wiki/scripts/transaction.mjs +384 -0
  121. package/template/.cursor/skills/llm-wiki/scripts/v2.test.mjs +354 -0
  122. package/template/.cursor/skills/prototype/LOGIC.md +1 -1
  123. package/template/.cursor/skills/prototype/SKILL.md +5 -19
  124. package/template/.cursor/skills/setup-matt-pocock-skills/SKILL.md +3 -3
  125. package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
  126. package/template/.cursor/skills/yss-implementation-contract-compiler/references/boundaries.md +2 -3
  127. package/template/.cursor/skills/yss-implementation-contract-compiler/references/yss-skill-execution-result.md +14 -0
  128. package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +8 -8
  129. package/template/.cursor/skills/yss-product-lifecycle/references/external-input-questionnaire.md +19 -0
  130. package/template/.cursor/skills/yss-product-lifecycle/references/matt-yss-adapter.md +5 -5
  131. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +20 -4
  132. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  133. package/template/.cursor/skills/yss-product-lifecycle/references/plan-requirements.md +10 -0
  134. package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +1 -1
  135. package/template/.cursor/skills/yss-research/SKILL.md +4 -0
  136. package/template/.pi/skills/codebase-design/SKILL.md +4 -0
  137. package/template/{.agents/skills/improve-codebase-architecture/SKILL.md → .pi/skills/codebase-design/references/architecture-audit.md} +3 -9
  138. package/template/.pi/skills/frontend-commit/SKILL.md +4 -93
  139. package/template/.pi/skills/git-commit-core/SKILL.md +18 -0
  140. package/template/.pi/skills/java-backend-commit/SKILL.md +4 -96
  141. package/template/.pi/skills/llm-wiki/SKILL.md +12 -13
  142. package/template/.pi/skills/llm-wiki/assets/CLAUDE.md.template +7 -1
  143. package/template/.pi/skills/llm-wiki/references/compile.md +21 -114
  144. package/template/.pi/skills/llm-wiki/references/ingest.md +8 -33
  145. package/template/.pi/skills/llm-wiki/references/lint.md +9 -46
  146. package/template/.pi/skills/llm-wiki/references/query.md +9 -18
  147. package/template/.pi/skills/llm-wiki/references/schema.md +56 -29
  148. package/template/.pi/skills/llm-wiki/references/transactions.md +48 -0
  149. package/template/.pi/skills/llm-wiki/references/writing.md +4 -4
  150. package/template/.pi/skills/llm-wiki/scripts/advise.mjs +46 -58
  151. package/template/.pi/skills/llm-wiki/scripts/advise.test.mjs +11 -16
  152. package/template/.pi/skills/llm-wiki/scripts/core.mjs +262 -0
  153. package/template/.pi/skills/llm-wiki/scripts/extract.mjs +35 -18
  154. package/template/.pi/skills/llm-wiki/scripts/extract.test.mjs +5 -8
  155. package/template/.pi/skills/llm-wiki/scripts/feedback.mjs +51 -0
  156. package/template/.pi/skills/llm-wiki/scripts/inventory.mjs +43 -171
  157. package/template/.pi/skills/llm-wiki/scripts/inventory.test.mjs +25 -18
  158. package/template/.pi/skills/llm-wiki/scripts/lint-wikilinks.mjs +69 -92
  159. package/template/.pi/skills/llm-wiki/scripts/lint-wikilinks.test.mjs +25 -37
  160. package/template/.pi/skills/llm-wiki/scripts/migrate.mjs +51 -0
  161. package/template/.pi/skills/llm-wiki/scripts/query.mjs +149 -0
  162. package/template/.pi/skills/llm-wiki/scripts/regressions.test.mjs +101 -0
  163. package/template/.pi/skills/llm-wiki/scripts/sources.mjs +166 -0
  164. package/template/.pi/skills/llm-wiki/scripts/transaction.mjs +384 -0
  165. package/template/.pi/skills/llm-wiki/scripts/v2.test.mjs +354 -0
  166. package/template/.pi/skills/prototype/LOGIC.md +1 -1
  167. package/template/.pi/skills/prototype/SKILL.md +5 -19
  168. package/template/.pi/skills/setup-matt-pocock-skills/SKILL.md +3 -3
  169. package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
  170. package/template/.pi/skills/yss-implementation-contract-compiler/references/boundaries.md +2 -3
  171. package/template/.pi/skills/yss-implementation-contract-compiler/references/yss-skill-execution-result.md +14 -0
  172. package/template/.pi/skills/yss-product-lifecycle/SKILL.md +8 -8
  173. package/template/.pi/skills/yss-product-lifecycle/references/external-input-questionnaire.md +19 -0
  174. package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +5 -5
  175. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +20 -4
  176. package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  177. package/template/.pi/skills/yss-product-lifecycle/references/plan-requirements.md +10 -0
  178. package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +1 -1
  179. package/template/.pi/skills/yss-research/SKILL.md +4 -0
  180. package/template/.template-spec/agents/README.md +1 -1
  181. package/template/.template-spec/agents/digital-human-roles.yaml +1 -1
  182. package/template/.template-spec/agents/skill-migrations.md +15 -0
  183. package/template/.template-spec/agents/yss-skill-registry.yaml +10 -29
  184. package/template/.template-spec/architecture/README.md +3 -34
  185. package/template/.template-spec/plan/templates/competitive-analysis-template.md +2 -2
  186. package/template/.template-spec/process/contract-reading.md +24 -0
  187. package/template/.template-spec/process/lifecycle-registry-baseline.json +3 -1
  188. package/template/.template-spec/process/lifecycle-registry.yaml +6 -0
  189. package/template/.template-spec/process/research-completion.md +23 -0
  190. package/template/.template-spec/process/schemas/digital-human-task-package.schema.json +202 -12
  191. package/template/.template-spec/process/schemas/frontend-implementation-evidence.schema.json +7 -15
  192. package/template/.template-spec/process/subagent-collaboration.md +8 -0
  193. package/template/.template-spec/process/templates/frontend-implementation-verification-template.yaml +15 -1
  194. package/template/AGENTS.md +1 -1
  195. package/template/CONTEXT.md +5 -1
  196. package/template/README.md +12 -24
  197. package/template/scripts/contract +5 -5
  198. package/template/scripts/lib/contract-views.mjs +57 -2
  199. package/template/scripts/lib/execution-evidence.mjs +51 -0
  200. package/template/scripts/lib/harness-execution-scope.mjs +1 -1
  201. package/template/scripts/lib/implementation-contract-compiler.mjs +2 -0
  202. package/template/scripts/lib/lifecycle-context-query.mjs +15 -3
  203. package/template/scripts/lib/lifecycle-status.mjs +32 -6
  204. package/template/scripts/lib/lifecycle-transition.mjs +19 -1
  205. package/template/scripts/lib/maintenance-research.mjs +63 -0
  206. package/template/scripts/lib/read-only-intake.mjs +132 -0
  207. package/template/scripts/lib/skill-registry.mjs +2 -1
  208. package/template/scripts/lib/skill-supply-chain.mjs +4 -0
  209. package/template/scripts/lib/slice-task-package.mjs +1 -1
  210. package/template/scripts/lib/task-package.mjs +9 -2
  211. package/template/scripts/lifecycle-status +2 -2
  212. package/template/scripts/prepare-read-only-intake +16 -0
  213. package/template/scripts/run-read-only-intake +14 -0
  214. package/template/scripts/verify-digital-human-task-package +2 -2
  215. package/template/scripts/verify-frontend-implementation-evidence +24 -1
  216. package/template/scripts/verify-maintenance-research +13 -0
  217. package/template/skills-lock.json +19 -76
  218. package/template.manifest.json +118 -29
  219. package/template.snapshot.json +5 -6
  220. package/template/.agents/skills/grill-with-docs/SKILL.md +0 -20
  221. package/template/.agents/skills/grill-with-docs/agents/openai.yaml +0 -5
  222. package/template/.agents/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
  223. package/template/.agents/skills/prototype/UI.md +0 -112
  224. package/template/.agents/skills/setup-matt-pocock-skills/domain.md +0 -37
  225. package/template/.agents/skills/to-questionnaire/SKILL.md +0 -55
  226. package/template/.agents/skills/to-questionnaire/agents/openai.yaml +0 -5
  227. package/template/.agents/skills/wait-what/SKILL.md +0 -7
  228. package/template/.agents/skills/wait-what/agents/openai.yaml +0 -5
  229. package/template/.codex/skills/data-analytics/.app.json +0 -84
  230. package/template/.codex/skills/data-analytics/.codex-plugin/plugin.json +0 -66
  231. package/template/.codex/skills/data-analytics/.mcp.json +0 -30
  232. package/template/.codex/skills/data-analytics/AGENTS.md +0 -20
  233. package/template/.codex/skills/data-analytics/DEPENDENCIES.MD +0 -27
  234. package/template/.codex/skills/data-analytics/README.md +0 -60
  235. package/template/.codex/skills/data-analytics/__yss_dotfile__.gitignore +0 -2
  236. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html +0 -16
  237. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part001 +0 -1
  238. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part002 +0 -1
  239. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part003 +0 -1
  240. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part004 +0 -1
  241. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part005 +0 -1
  242. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part006 +0 -1
  243. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html +0 -16
  244. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part001 +0 -1
  245. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part002 +0 -1
  246. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part003 +0 -1
  247. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part004 +0 -1
  248. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part005 +0 -1
  249. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part006 +0 -1
  250. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part007 +0 -1
  251. package/template/.codex/skills/data-analytics/assets/datascience-small.svg +0 -5
  252. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html +0 -16
  253. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part001 +0 -1
  254. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part002 +0 -1
  255. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part003 +0 -1
  256. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part004 +0 -1
  257. package/template/.codex/skills/data-analytics/assets/datascience.png +0 -0
  258. package/template/.codex/skills/data-analytics/assets/datascience.svg +0 -10
  259. package/template/.codex/skills/data-analytics/mcp/server.cjs +0 -2964
  260. package/template/.codex/skills/data-analytics/package-lock.json +0 -3048
  261. package/template/.codex/skills/data-analytics/package.json +0 -43
  262. package/template/.codex/skills/data-analytics/scripts/normalize-widget-assets.mjs +0 -75
  263. package/template/.codex/skills/data-analytics/skills/analyze-data-quality/SKILL.md +0 -137
  264. package/template/.codex/skills/data-analytics/skills/analyze-data-quality/agents/openai.yaml +0 -6
  265. package/template/.codex/skills/data-analytics/skills/analyze-data-quality/references/quality-checks.md +0 -29
  266. package/template/.codex/skills/data-analytics/skills/build-dashboard/SKILL.md +0 -148
  267. package/template/.codex/skills/data-analytics/skills/build-dashboard/agents/openai.yaml +0 -6
  268. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/bi-platform-dashboard.md +0 -18
  269. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/html-dashboard.md +0 -24
  270. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/mcp-artifact-dashboard.md +0 -71
  271. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/streamlit-dashboard.md +0 -85
  272. package/template/.codex/skills/data-analytics/skills/build-report/SKILL.md +0 -207
  273. package/template/.codex/skills/data-analytics/skills/build-report/agents/openai.yaml +0 -6
  274. package/template/.codex/skills/data-analytics/skills/build-report/assets/executive-report-shell.html +0 -70
  275. package/template/.codex/skills/data-analytics/skills/build-report/assets/technical-report-shell.html +0 -66
  276. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/SKILL.md +0 -108
  277. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/agents/openai.yaml +0 -6
  278. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/__init__.py +0 -1
  279. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/cli.py +0 -52
  280. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/constants.py +0 -61
  281. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/docx_writer.py +0 -362
  282. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/html_parser.py +0 -829
  283. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/model.py +0 -30
  284. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/plan.py +0 -127
  285. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/quality.py +0 -374
  286. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/rendering.py +0 -613
  287. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/table_utils.py +0 -54
  288. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/utils.py +0 -16
  289. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc_plan.py +0 -9
  290. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/tests/test_delivery_plan.py +0 -45
  291. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-slides/SKILL.md +0 -77
  292. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-slides/agents/openai.yaml +0 -6
  293. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-slides/scripts/report_to_google_slides.py +0 -2379
  294. package/template/.codex/skills/data-analytics/skills/build-report/report-to-pdf/SKILL.md +0 -88
  295. package/template/.codex/skills/data-analytics/skills/build-report/report-to-pdf/agents/openai.yaml +0 -6
  296. package/template/.codex/skills/data-analytics/skills/build-report/specifications/executive-report.md +0 -97
  297. package/template/.codex/skills/data-analytics/skills/build-report/specifications/mcp-app-report.md +0 -59
  298. package/template/.codex/skills/data-analytics/skills/build-report/specifications/technical-report.md +0 -75
  299. package/template/.codex/skills/data-analytics/skills/design-kpis/SKILL.md +0 -103
  300. package/template/.codex/skills/data-analytics/skills/design-kpis/agents/openai.yaml +0 -6
  301. package/template/.codex/skills/data-analytics/skills/gather-business-context/SKILL.md +0 -68
  302. package/template/.codex/skills/data-analytics/skills/gather-business-context/agents/openai.yaml +0 -6
  303. package/template/.codex/skills/data-analytics/skills/index/SKILL.md +0 -251
  304. package/template/.codex/skills/data-analytics/skills/index/agents/openai.yaml +0 -6
  305. package/template/.codex/skills/data-analytics/skills/jupyter-notebooks/SKILL.md +0 -131
  306. package/template/.codex/skills/data-analytics/skills/jupyter-notebooks/agents/openai.yaml +0 -6
  307. package/template/.codex/skills/data-analytics/skills/kpi-reporting/SKILL.md +0 -141
  308. package/template/.codex/skills/data-analytics/skills/kpi-reporting/agents/openai.yaml +0 -6
  309. package/template/.codex/skills/data-analytics/skills/kpi-reporting/references/report-templates.md +0 -32
  310. package/template/.codex/skills/data-analytics/skills/market-sizing/SKILL.md +0 -106
  311. package/template/.codex/skills/data-analytics/skills/market-sizing/agents/openai.yaml +0 -6
  312. package/template/.codex/skills/data-analytics/skills/metric-diagnostics/SKILL.md +0 -130
  313. package/template/.codex/skills/data-analytics/skills/metric-diagnostics/agents/openai.yaml +0 -6
  314. package/template/.codex/skills/data-analytics/skills/product-business-analysis/SKILL.md +0 -141
  315. package/template/.codex/skills/data-analytics/skills/product-business-analysis/agents/openai.yaml +0 -6
  316. package/template/.codex/skills/data-analytics/skills/spreadsheets/SKILL.md +0 -178
  317. package/template/.codex/skills/data-analytics/skills/spreadsheets/agents/openai.yaml +0 -9
  318. package/template/.codex/skills/data-analytics/skills/spreadsheets/assets/file-spreadsheet.png +0 -0
  319. package/template/.codex/skills/data-analytics/skills/spreadsheets/charts.md +0 -31
  320. package/template/.codex/skills/data-analytics/skills/spreadsheets/references/artifact_tool_api.md +0 -466
  321. package/template/.codex/skills/data-analytics/skills/spreadsheets/style_guidelines.md +0 -99
  322. package/template/.codex/skills/data-analytics/skills/user-context/SKILL.md +0 -197
  323. package/template/.codex/skills/data-analytics/skills/user-context/agents/openai.yaml +0 -6
  324. package/template/.codex/skills/data-analytics/skills/user-context/plugin-author-config/automation-config.md +0 -26
  325. package/template/.codex/skills/data-analytics/skills/user-context/plugin-author-config/source-category-config.json +0 -51
  326. package/template/.codex/skills/data-analytics/skills/user-context/plugin-author-config/user-context-config.md +0 -66
  327. package/template/.codex/skills/data-analytics/skills/user-context/references/automation.md +0 -69
  328. package/template/.codex/skills/data-analytics/skills/user-context/references/onboarding-examples.md +0 -204
  329. package/template/.codex/skills/data-analytics/skills/user-context/references/onboarding-state-template.json +0 -87
  330. package/template/.codex/skills/data-analytics/skills/user-context/references/onboarding.md +0 -497
  331. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/connector-playbook.md +0 -74
  332. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/setup.md +0 -65
  333. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/skill-template.md +0 -160
  334. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/source-intake.md +0 -75
  335. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/weekly-polling-automation.md +0 -96
  336. package/template/.codex/skills/data-analytics/skills/user-context/references/source-category-runtime.md +0 -263
  337. package/template/.codex/skills/data-analytics/skills/user-context/scripts/data_analytics_preflight.py +0 -1462
  338. package/template/.codex/skills/data-analytics/skills/user-context/scripts/init_user_context_state.py +0 -128
  339. package/template/.codex/skills/data-analytics/skills/user-context/scripts/reset_user_context_state.py +0 -101
  340. package/template/.codex/skills/data-analytics/skills/user-context/scripts/validate_user_context_preflight.py +0 -499
  341. package/template/.codex/skills/data-analytics/skills/user-context/tests/test_state_helpers.py +0 -978
  342. package/template/.codex/skills/data-analytics/skills/validate-data/SKILL.md +0 -126
  343. package/template/.codex/skills/data-analytics/skills/validate-data/agents/openai.yaml +0 -6
  344. package/template/.codex/skills/data-analytics/skills/validate-data/references/validation-methods.md +0 -70
  345. package/template/.codex/skills/data-analytics/skills/visualize-data/SKILL.md +0 -157
  346. package/template/.codex/skills/data-analytics/skills/visualize-data/agents/openai.yaml +0 -6
  347. package/template/.codex/skills/data-analytics/skills/visualize-data/references/seaborn-templates.md +0 -774
  348. package/template/.codex/skills/data-analytics/src/DESIGN.md +0 -222
  349. package/template/.codex/skills/data-analytics/src/analytics-app/App.tsx +0 -3666
  350. package/template/.codex/skills/data-analytics/src/analytics-app/analytics-layout.test.mjs +0 -136
  351. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartFrame.tsx +0 -54
  352. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartLegend.tsx +0 -96
  353. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartRenderer.tsx +0 -1647
  354. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartTooltip.tsx +0 -245
  355. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-app-helpers.tsx +0 -462
  356. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-capabilities.ts +0 -107
  357. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-compatibility.ts +0 -164
  358. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-contract.ts +0 -192
  359. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-theme.ts +0 -203
  360. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-tokens.css +0 -619
  361. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-transforms.ts +0 -402
  362. package/template/.codex/skills/data-analytics/src/analytics-app/fonts/SystemSansVariableVF.woff2 +0 -0
  363. package/template/.codex/skills/data-analytics/src/analytics-app/imageExport.ts +0 -370
  364. package/template/.codex/skills/data-analytics/src/analytics-app/layout/AnalyticsLayoutCanvas.tsx +0 -536
  365. package/template/.codex/skills/data-analytics/src/analytics-app/layout/RichMarkdown.tsx +0 -681
  366. package/template/.codex/skills/data-analytics/src/analytics-app/layout/analyticsLayoutCore.ts +0 -164
  367. package/template/.codex/skills/data-analytics/src/analytics-app/main.tsx +0 -21
  368. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/chart_contract.py +0 -472
  369. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/check_analytics_app_runtime_links.py +0 -75
  370. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/design_contract.py +0 -142
  371. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/package_utils.py +0 -142
  372. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/tests/test_package_utils.py +0 -377
  373. package/template/.codex/skills/data-analytics/src/analytics-app/styles.css +0 -3468
  374. package/template/.codex/skills/data-analytics/src/analytics-app/tables/DataTable.d.ts +0 -44
  375. package/template/.codex/skills/data-analytics/src/analytics-app/tables/DataTable.jsx +0 -639
  376. package/template/.codex/skills/data-analytics/src/analytics-app/tables/data-table.css +0 -327
  377. package/template/.codex/skills/data-analytics/src/analytics-app/tokens.css +0 -225
  378. package/template/.codex/skills/data-analytics/src/analytics-app/types.ts +0 -204
  379. package/template/.codex/skills/data-analytics/src/analytics-app-core.md +0 -85
  380. package/template/.codex/skills/data-analytics/src/codex-style-contract.md +0 -30
  381. package/template/.codex/skills/data-analytics/src/datascience-artifact-widget.html +0 -12
  382. package/template/.codex/skills/data-analytics/src/datascience-artifact-widget.jsx +0 -1008
  383. package/template/.codex/skills/data-analytics/src/datascience-chart-widget.css +0 -2117
  384. package/template/.codex/skills/data-analytics/src/datascience-chart-widget.html +0 -122
  385. package/template/.codex/skills/data-analytics/src/datascience-chart-widget.js +0 -3588
  386. package/template/.codex/skills/data-analytics/src/datascience-table-widget.css +0 -483
  387. package/template/.codex/skills/data-analytics/src/datascience-table-widget.html +0 -32
  388. package/template/.codex/skills/data-analytics/src/datascience-table-widget.js +0 -333
  389. package/template/.codex/skills/data-analytics/src/mcp-host.js +0 -153
  390. package/template/.codex/skills/data-analytics/src/recharts-config.js +0 -315
  391. package/template/.codex/skills/data-analytics/src/recharts-renderer.jsx +0 -294
  392. package/template/.codex/skills/data-analytics/src/sql-source-view.css +0 -33
  393. package/template/.codex/skills/data-analytics/src/sql-source-view.js +0 -157
  394. package/template/.codex/skills/data-analytics/src/styles/codex-theme.css +0 -121
  395. package/template/.codex/skills/data-analytics/src/table-renderer.jsx +0 -28
  396. package/template/.codex/skills/data-analytics/tests/chart-transforms.test.mjs +0 -87
  397. package/template/.codex/skills/data-analytics/tests/funnel-smoke.html +0 -105
  398. package/template/.codex/skills/data-analytics/tests/inline-widget-compare.html +0 -233
  399. package/template/.codex/skills/data-analytics/tests/mcp-server.test.mjs +0 -1500
  400. package/template/.codex/skills/data-analytics/tests/native-style-contract.test.mjs +0 -219
  401. package/template/.codex/skills/data-analytics/tests/recharts-config.test.mjs +0 -144
  402. package/template/.codex/skills/data-analytics/tests/recharts-renderer.test.mjs +0 -11
  403. package/template/.codex/skills/data-analytics/tests/rich-markdown.test.mjs +0 -54
  404. package/template/.codex/skills/data-analytics/tests/widget-render-harness.html +0 -321
  405. package/template/.codex/skills/data-analytics/tsconfig.json +0 -19
  406. package/template/.codex/skills/data-analytics/vite.config.ts +0 -46
  407. package/template/.codex/skills/grill-with-docs/SKILL.md +0 -20
  408. package/template/.codex/skills/grill-with-docs/agents/openai.yaml +0 -5
  409. package/template/.codex/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
  410. package/template/.codex/skills/prototype/UI.md +0 -112
  411. package/template/.codex/skills/setup-matt-pocock-skills/domain.md +0 -37
  412. package/template/.codex/skills/to-questionnaire/SKILL.md +0 -55
  413. package/template/.codex/skills/to-questionnaire/agents/openai.yaml +0 -5
  414. package/template/.codex/skills/wait-what/SKILL.md +0 -7
  415. package/template/.codex/skills/wait-what/agents/openai.yaml +0 -5
  416. package/template/.cursor/skills/grill-with-docs/SKILL.md +0 -20
  417. package/template/.cursor/skills/grill-with-docs/agents/openai.yaml +0 -5
  418. package/template/.cursor/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
  419. package/template/.cursor/skills/prototype/UI.md +0 -112
  420. package/template/.cursor/skills/setup-matt-pocock-skills/domain.md +0 -37
  421. package/template/.cursor/skills/to-questionnaire/SKILL.md +0 -55
  422. package/template/.cursor/skills/to-questionnaire/agents/openai.yaml +0 -5
  423. package/template/.cursor/skills/wait-what/SKILL.md +0 -7
  424. package/template/.cursor/skills/wait-what/agents/openai.yaml +0 -5
  425. package/template/.pi/skills/grill-with-docs/SKILL.md +0 -20
  426. package/template/.pi/skills/grill-with-docs/agents/openai.yaml +0 -5
  427. package/template/.pi/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
  428. package/template/.pi/skills/prototype/UI.md +0 -112
  429. package/template/.pi/skills/setup-matt-pocock-skills/domain.md +0 -37
  430. package/template/.pi/skills/to-questionnaire/SKILL.md +0 -55
  431. package/template/.pi/skills/to-questionnaire/agents/openai.yaml +0 -5
  432. package/template/.pi/skills/wait-what/SKILL.md +0 -7
  433. package/template/.pi/skills/wait-what/agents/openai.yaml +0 -5
  434. package/template/.template-spec/agents/domain.md +0 -31
  435. /package/template/.agents/skills/{improve-codebase-architecture → codebase-design/references}/HTML-REPORT.md +0 -0
  436. /package/template/.codex/skills/{improve-codebase-architecture → codebase-design/references}/HTML-REPORT.md +0 -0
  437. /package/template/.cursor/skills/{improve-codebase-architecture → codebase-design/references}/HTML-REPORT.md +0 -0
  438. /package/template/.pi/skills/{improve-codebase-architecture → codebase-design/references}/HTML-REPORT.md +0 -0
@@ -1,1500 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { readFileSync } from "node:fs";
3
- import { createRequire } from "node:module";
4
- import { test } from "node:test";
5
-
6
-
7
- const require = createRequire(import.meta.url);
8
- const server = require("../mcp/server.cjs");
9
-
10
- function sourceQueryForTest() {
11
- return {
12
- query: {
13
- engine: "trino",
14
- sql: "SELECT category, value FROM warehouse.chart_source",
15
- description: "Loads category/value rows for chart tests.",
16
- id: "test-query",
17
- },
18
- };
19
- }
20
-
21
- function queryTablePayload() {
22
- return {
23
- title: "ARR trend",
24
- source: {
25
- query: {
26
- engine: "databricks",
27
- sql: "SELECT reporting_date, arr_b FROM gtm.example",
28
- description: "Loads ARR by reporting date for widget tests.",
29
- id: "query-123",
30
- executed_at: "2026-05-01T00:00:00Z",
31
- },
32
- },
33
- table: {
34
- columns: [
35
- { key: "reporting_date", label: "Reporting date", type: "date" },
36
- { key: "arr_b", label: "ARR", type: "number", unit: "$B" },
37
- ],
38
- rows: [
39
- { reporting_date: "2026-03-31", arr_b: 2.73 },
40
- { reporting_date: "2026-04-30", arr_b: 2.9 },
41
- ],
42
- row_count: 2,
43
- truncated: false,
44
- },
45
- chart: {
46
- type: "line",
47
- fields: {
48
- x: { field: "reporting_date", type: "temporal", time_unit: "month" },
49
- y: { field: "arr_b", type: "quantitative", aggregate: "sum", unit: "$B" },
50
- },
51
- },
52
- display: {
53
- baseline: 2.8,
54
- unit: "$B",
55
- controls: true,
56
- },
57
- };
58
- }
59
-
60
- function artifactPayload(surface = "dashboard") {
61
- const manifest = {
62
- version: 1,
63
- surface,
64
- title: "Revenue momentum",
65
- generatedAt: "2026-05-07T00:00:00Z",
66
- cards: [
67
- {
68
- id: "revenue_card",
69
- dataset: "weekly_revenue",
70
- metrics: [{ label: "Revenue", field: "revenue_m", format: "currency" }],
71
- },
72
- ],
73
- charts: [
74
- {
75
- id: "revenue_chart",
76
- title: "Revenue by segment",
77
- type: "bar",
78
- dataset: "weekly_revenue",
79
- sourceId: "weekly_revenue_sql",
80
- encodings: {
81
- x: { field: "segment", type: "nominal" },
82
- y: { field: "revenue_m", type: "quantitative" },
83
- },
84
- },
85
- ],
86
- tables: [
87
- {
88
- id: "revenue_table",
89
- title: "Revenue rows",
90
- dataset: "weekly_revenue",
91
- sourceId: "weekly_revenue_sql",
92
- columns: [
93
- { field: "segment", label: "Segment" },
94
- { field: "revenue_m", label: "Revenue", format: "currency" },
95
- ],
96
- },
97
- ],
98
- sources: [{ id: "weekly_revenue_sql", label: "Revenue SQL", path: "queries/revenue.sql" }],
99
- };
100
- if (surface === "report") {
101
- manifest.blocks = [
102
- {
103
- id: "summary_text",
104
- type: "markdown",
105
- body: "Revenue is concentrated in Beta.",
106
- },
107
- {
108
- id: "revenue_chart_block",
109
- type: "chart",
110
- chartId: "revenue_chart",
111
- },
112
- {
113
- id: "revenue_table_block",
114
- type: "table",
115
- tableId: "revenue_table",
116
- },
117
- ];
118
- } else {
119
- manifest.blocks = [
120
- {
121
- id: "revenue_metrics",
122
- type: "metric-strip",
123
- cardIds: ["revenue_card"],
124
- },
125
- {
126
- id: "revenue_chart_block",
127
- type: "chart",
128
- chartId: "revenue_chart",
129
- },
130
- {
131
- id: "revenue_table_block",
132
- type: "table",
133
- tableId: "revenue_table",
134
- },
135
- ];
136
- }
137
- return {
138
- surface,
139
- manifest,
140
- snapshot: {
141
- version: 1,
142
- generatedAt: "2026-05-07T00:00:00Z",
143
- status: "ready",
144
- datasets: {
145
- weekly_revenue: [
146
- { segment: "Alpha", revenue_m: 12 },
147
- { segment: "Beta", revenue_m: 18 },
148
- ],
149
- },
150
- },
151
- sources: [
152
- {
153
- id: "weekly_revenue_sql",
154
- query: {
155
- engine: "trino",
156
- sql: "SELECT segment, revenue_m FROM warehouse.weekly_revenue",
157
- description: "Loads weekly revenue by segment for artifact tests.",
158
- },
159
- },
160
- ],
161
- };
162
- }
163
-
164
- async function widgetResourceHtml(uri) {
165
- const response = await server.handleRpc({
166
- jsonrpc: "2.0",
167
- id: 1,
168
- method: "resources/read",
169
- params: { uri },
170
- });
171
- return response.result.contents[0].text;
172
- }
173
-
174
- test("MCP widget resources serve bundled apps, not local development redirects", async () => {
175
- const chartHtml = await widgetResourceHtml(server.CHART_WIDGET_URI);
176
- const tableHtml = await widgetResourceHtml(server.TABLE_WIDGET_URI);
177
- const artifactHtml = await widgetResourceHtml(server.ARTIFACT_WIDGET_URI);
178
-
179
- for (const html of [chartHtml, tableHtml, artifactHtml]) {
180
- assert.doesNotMatch(html, /Redirecting to the local widget source/);
181
- assert.doesNotMatch(html, /window\.location\.replace\(target\)/);
182
- }
183
- assert.match(chartHtml, /@modelcontextprotocol\/ext-apps/);
184
- assert.match(tableHtml, /@modelcontextprotocol\/ext-apps/);
185
- assert.match(artifactHtml, /Data Analytics Artifact App/);
186
- });
187
-
188
- test("JavaScript MCP server renders hosted artifact payloads", () => {
189
- const payload = server.callTool("render_artifact", artifactPayload("report"));
190
-
191
- assert.equal(payload.widget_type, "artifact");
192
- assert.equal(payload.surface, "report");
193
- assert.equal(payload.manifest.title, "Revenue momentum");
194
- assert.equal(payload.snapshot.datasets.weekly_revenue[1].segment, "Beta");
195
- });
196
-
197
- test("JavaScript MCP server exposes only canonical tool names", () => {
198
- const names = server.toolDefinitions().map((tool) => tool.name);
199
- assert.deepEqual(names, [
200
- "validate_artifact",
201
- "render_artifact",
202
- "export_artifact_package",
203
- "render_chart",
204
- "render_table",
205
- ]);
206
-
207
- assert.throws(
208
- () => server.callTool("render_datascience_chart", queryTablePayload()),
209
- /unknown Data Analytics widget tool: render_datascience_chart/,
210
- );
211
- });
212
-
213
- test("JavaScript MCP server advertises Data Analytics icons on server info", async () => {
214
- assert.equal(server.DATA_ANALYTICS_ICONS[0].mimeType, "image/svg+xml");
215
- assert.deepEqual(server.DATA_ANALYTICS_ICONS[0].sizes, ["24x24"]);
216
- assert.match(server.DATA_ANALYTICS_ICONS[0].src, /^data:image\/svg\+xml;base64,/);
217
- assert.equal(server.DATA_ANALYTICS_ICONS[1].mimeType, "image/png");
218
- assert.deepEqual(server.DATA_ANALYTICS_ICONS[1].sizes, ["360x360"]);
219
- assert.match(server.DATA_ANALYTICS_ICONS[1].src, /^data:image\/png;base64,/);
220
-
221
- for (const tool of server.toolDefinitions()) {
222
- assert.equal(tool.icons, undefined);
223
- }
224
-
225
- const response = await server.handleRpc({
226
- jsonrpc: "2.0",
227
- id: 1,
228
- method: "initialize",
229
- params: {},
230
- });
231
- assert.equal(response.result.serverInfo.title, "Data Analytics Widgets");
232
- assert.equal(
233
- response.result.serverInfo.description,
234
- "Render Data Analytics charts, tables, dashboards, and report artifacts.",
235
- );
236
- assert.deepEqual(response.result.serverInfo.icons, server.DATA_ANALYTICS_ICONS);
237
- });
238
-
239
- test("JavaScript MCP server validates artifact payloads without widget metadata", async () => {
240
- const payload = server.callTool("validate_artifact", artifactPayload("report"));
241
-
242
- assert.equal(payload.ok, true);
243
- assert.equal(payload.validation_type, "artifact");
244
- assert.equal(payload.surface, "report");
245
- assert.equal(payload.manifest_title, "Revenue momentum");
246
- assert.equal(payload.dataset_count, 1);
247
- assert.equal(payload.artifact_payload.widget_type, "artifact");
248
-
249
- const validateTool = server
250
- .toolDefinitions()
251
- .find((tool) => tool.name === "validate_artifact");
252
- assert.ok(validateTool);
253
- assert.equal(validateTool._meta, undefined);
254
-
255
- const rpcPayload = await server.handleRpc({
256
- jsonrpc: "2.0",
257
- id: 1,
258
- method: "tools/call",
259
- params: { name: "validate_artifact", arguments: artifactPayload("report") },
260
- });
261
- assert.equal(rpcPayload.result.structuredContent.ok, true);
262
- assert.equal(rpcPayload.result._meta, undefined);
263
- });
264
-
265
- test("JavaScript MCP server rejects artifact chart y encodings without numeric values", () => {
266
- const args = artifactPayload("report");
267
- args.manifest.charts[0] = {
268
- ...args.manifest.charts[0],
269
- title: "Product-line share by country",
270
- dataset: "product_line_share",
271
- encodings: {
272
- x: { field: "country_name", type: "nominal" },
273
- y: { field: "product_line", type: "nominal" },
274
- },
275
- };
276
- args.snapshot.datasets.product_line_share = [
277
- { country_name: "India", product_line: "Consumer", share_pct: 71 },
278
- { country_name: "India", product_line: "API", share_pct: 18 },
279
- ];
280
-
281
- assert.throws(
282
- () => server.callTool("validate_artifact", args),
283
- /manifest\.charts\[0\]\.encodings\.y\.field "product_line" must reference a numeric dataset field/,
284
- );
285
- });
286
-
287
- test("JavaScript MCP server rejects legacy artifact chart fields", () => {
288
- const args = artifactPayload("report");
289
- args.manifest.charts[0].xField = "segment";
290
-
291
- assert.throws(
292
- () => server.callTool("validate_artifact", args),
293
- /manifest\.charts\[0\]\.xField is not supported for artifact charts; use encodings/,
294
- );
295
- });
296
-
297
- test("JavaScript MCP server rejects table-shaped artifact snapshot datasets", () => {
298
- const args = artifactPayload("report");
299
- args.snapshot.datasets.weekly_revenue = {
300
- columns: [
301
- { key: "segment", label: "Segment" },
302
- { key: "revenue_m", label: "Revenue", type: "currency" },
303
- ],
304
- rows: args.snapshot.datasets.weekly_revenue,
305
- };
306
-
307
- assert.throws(
308
- () => server.callTool("render_artifact", args),
309
- /snapshot\.datasets\.weekly_revenue must be an array of row objects/,
310
- );
311
- });
312
-
313
- test("JavaScript MCP server rejects key aliases for report table columns", () => {
314
- const args = artifactPayload("report");
315
- args.manifest.tables[0].columns = [
316
- { key: "segment", label: "Segment" },
317
- { key: "revenue_m", label: "Revenue", type: "currency" },
318
- ];
319
-
320
- assert.throws(
321
- () => server.callTool("render_artifact", args),
322
- /manifest\.tables\["revenue_table"\]\.columns\[0\]\.field/,
323
- );
324
- });
325
-
326
- test("JavaScript MCP server accepts body strings in report markdown blocks", () => {
327
- const args = artifactPayload("report");
328
- args.manifest.blocks[0] = {
329
- id: "summary_text",
330
- type: "markdown",
331
- body: "## Summary\n\n- **Beta** leads\n- [Source](https://example.com)",
332
- };
333
-
334
- const payload = server.callTool("render_artifact", args);
335
-
336
- assert.equal(payload.manifest.blocks[0].body, "## Summary\n\n- **Beta** leads\n- [Source](https://example.com)");
337
- });
338
-
339
- test("JavaScript MCP server rejects legacy narrative fields and removed block types", () => {
340
- const args = artifactPayload("report");
341
- args.manifest.blocks[0].markdown = "Legacy markdown field";
342
- delete args.manifest.blocks[0].body;
343
- assert.throws(
344
- () => server.callTool("render_artifact", args),
345
- /uses markdown, but markdown blocks render body/,
346
- );
347
-
348
- const titled = artifactPayload("report");
349
- titled.manifest.blocks[0].title = "Legacy title";
350
- titled.manifest.blocks[0].content = "Legacy content";
351
- delete titled.manifest.blocks[0].body;
352
- assert.throws(
353
- () => server.callTool("render_artifact", titled),
354
- /uses content, but markdown blocks render body/,
355
- );
356
-
357
- const bodyArray = artifactPayload("report");
358
- bodyArray.manifest.blocks[0].body = ["Legacy array body"];
359
- assert.throws(
360
- () => server.callTool("render_artifact", bodyArray),
361
- /body must be a non-empty string/,
362
- );
363
-
364
- const textType = artifactPayload("report");
365
- textType.manifest.blocks[0].type = "text";
366
- assert.throws(
367
- () => server.callTool("render_artifact", textType),
368
- /type must be one of markdown, metric-strip, chart, table, html/,
369
- );
370
-
371
- const removedType = artifactPayload("report");
372
- removedType.manifest.blocks.push({
373
- id: "next_steps",
374
- type: "recommendation",
375
- body: "Review account drivers.",
376
- });
377
- assert.throws(
378
- () => server.callTool("render_artifact", removedType),
379
- /type must be one of markdown, metric-strip, chart, table, html/,
380
- );
381
- });
382
-
383
- test("JavaScript MCP server preserves inline ordered-list markers in report body", () => {
384
- const args = artifactPayload("report");
385
- args.manifest.blocks[0] = {
386
- id: "summary_text",
387
- type: "markdown",
388
- body: "1. Review account drivers 2. Ask account teams for context 3. Monitor next week",
389
- };
390
- args.manifest.blocks.push({
391
- id: "questions_text",
392
- type: "markdown",
393
- body: "1. Is the gain durable? 2. Which migrations explain it?",
394
- });
395
-
396
- const payload = server.callTool("render_artifact", args);
397
-
398
- assert.equal(
399
- payload.manifest.blocks[0].body,
400
- "1. Review account drivers 2. Ask account teams for context 3. Monitor next week",
401
- );
402
- assert.equal(
403
- payload.manifest.blocks[3].body,
404
- "1. Is the gain durable? 2. Which migrations explain it?",
405
- );
406
- });
407
-
408
- test("JavaScript MCP server leaves percent chart scale to the manifest data contract", () => {
409
- const args = artifactPayload("report");
410
- args.manifest.charts[0].valueFormat = "percent";
411
- args.manifest.charts[0].encodings.y = { field: "share", type: "quantitative" };
412
- args.snapshot.datasets.weekly_revenue = [
413
- { segment: "Alpha", share: 2.0 },
414
- { segment: "Beta", share: 0.851 },
415
- ];
416
-
417
- assert.equal(server.callTool("validate_artifact", args).ok, true);
418
- });
419
-
420
- test("JavaScript MCP server requires explicit artifact chart source links", () => {
421
- const args = artifactPayload("report");
422
- delete args.manifest.charts[0].sourceId;
423
-
424
- assert.throws(
425
- () => server.callTool("validate_artifact", args),
426
- /actual SQL query text/,
427
- );
428
- });
429
-
430
- test("JavaScript MCP server accepts custom HTML artifact blocks", () => {
431
- const args = artifactPayload("report");
432
- args.manifest.blocks.push({
433
- id: "custom_html",
434
- type: "html",
435
- body: '<section style="font: 14px sans-serif"><strong>Custom HTML</strong><div data-value="42"></div></section>',
436
- });
437
-
438
- const payload = server.callTool("render_artifact", args);
439
-
440
- assert.equal(payload.manifest.blocks[3].type, "html");
441
- assert.match(payload.manifest.blocks[3].body, /Custom HTML/);
442
-
443
- const htmlField = artifactPayload("report");
444
- htmlField.manifest.blocks.push({
445
- id: "custom_html",
446
- type: "html",
447
- html: "<strong>Custom HTML</strong>",
448
- });
449
- assert.throws(
450
- () => server.callTool("render_artifact", htmlField),
451
- /uses html, but html blocks render body/,
452
- );
453
-
454
- const fixedHeight = artifactPayload("report");
455
- fixedHeight.manifest.blocks.push({
456
- id: "custom_html",
457
- type: "html",
458
- body: "<strong>Custom HTML</strong>",
459
- height: 240,
460
- });
461
- assert.throws(
462
- () => server.callTool("render_artifact", fixedHeight),
463
- /height is not supported for html blocks/,
464
- );
465
-
466
- const missingHtml = artifactPayload("report");
467
- missingHtml.manifest.blocks.push({
468
- id: "custom_html",
469
- type: "html",
470
- });
471
-
472
- assert.throws(
473
- () => server.callTool("validate_artifact", missingHtml),
474
- /manifest\.blocks\[3\]\.body must be a non-empty HTML string/,
475
- );
476
- });
477
-
478
- test("JavaScript MCP server accepts additional ordered report blocks", () => {
479
- const args = artifactPayload("report");
480
- args.manifest.blocks.push({
481
- id: "questions_text",
482
- type: "markdown",
483
- body: "- Is the revenue shift durable?",
484
- });
485
-
486
- const payload = server.callTool("render_artifact", args);
487
-
488
- assert.equal(payload.manifest.blocks[3].id, "questions_text");
489
- });
490
-
491
- test("JavaScript MCP server validates metric-strip blocks", () => {
492
- const args = artifactPayload("report");
493
- args.manifest.blocks.unshift({
494
- id: "headline_metrics",
495
- type: "metric-strip",
496
- cardIds: ["revenue_card"],
497
- });
498
-
499
- const payload = server.callTool("validate_artifact", args);
500
- assert.equal(payload.ok, true);
501
-
502
- const missingCard = artifactPayload("report");
503
- missingCard.manifest.blocks.unshift({
504
- id: "headline_metrics",
505
- type: "metric-strip",
506
- cardIds: ["missing_card"],
507
- });
508
- assert.throws(
509
- () => server.callTool("validate_artifact", missingCard),
510
- /cardIds\[0\] does not match a manifest card/,
511
- );
512
- });
513
-
514
- test("funnel labels use an explicit high-contrast fill", () => {
515
- const chartTokens = readFileSync(
516
- new URL("../src/analytics-app/charting/chart-tokens.css", import.meta.url),
517
- "utf8",
518
- );
519
- const appCss = readFileSync(new URL("../src/analytics-app/styles.css", import.meta.url), "utf8");
520
-
521
- assert.match(chartTokens, /--ds-chart-funnel-label: #ffffff;/);
522
- for (const css of [chartTokens, appCss]) {
523
- const funnelLabelBlock = css.split(".funnel-label", 2)[1].split("}", 1)[0];
524
- assert.match(funnelLabelBlock, /fill: var\(--ds-chart-funnel-label, #ffffff\);/);
525
- assert.doesNotMatch(funnelLabelBlock, /mix-blend-mode/);
526
- assert.doesNotMatch(funnelLabelBlock, /fill: var\(--ds-surface/);
527
- }
528
- });
529
-
530
- test("bar charts render direct value labels without hover", () => {
531
- const renderer = readFileSync(
532
- new URL("../src/analytics-app/charting/ChartRenderer.tsx", import.meta.url),
533
- "utf8",
534
- );
535
- const chartTokens = readFileSync(
536
- new URL("../src/analytics-app/charting/chart-tokens.css", import.meta.url),
537
- "utf8",
538
- );
539
-
540
- assert.match(renderer, /function shouldShowBarValueLabels/);
541
- assert.match(renderer, /function visibleBarValueLabelSides/);
542
- assert.match(renderer, /const showBarValueLabels = shouldShowBarValueLabels/);
543
- assert.match(renderer, /barValueLabelSides\.hasNegative/);
544
- assert.match(renderer, /barValueLabelSides\.hasNonNegative/);
545
- assert.match(renderer, /<LabelList/);
546
- assert.match(renderer, /renderBarValueLabel/);
547
- assert.match(chartTokens, /\.chart-bar-value-label/);
548
- });
549
-
550
- test("signed vertical bar labels are positioned outside negative bars", () => {
551
- const renderer = readFileSync(
552
- new URL("../src/analytics-app/charting/ChartRenderer.tsx", import.meta.url),
553
- "utf8",
554
- );
555
- const labelRenderer = renderer
556
- .split("function renderBarValueLabel", 2)[1]
557
- .split("function categoryBarTooltipColor", 1)[0];
558
-
559
- assert.match(labelRenderer, /const top = Math\.min\(numericY, numericY \+ numericHeight\);/);
560
- assert.match(labelRenderer, /const bottom = Math\.max\(numericY, numericY \+ numericHeight\);/);
561
- assert.match(labelRenderer, /y=\{isNegative \? bottom \+ BAR_NEGATIVE_VALUE_LABEL_OFFSET : top - BAR_VALUE_LABEL_OFFSET\}/);
562
- });
563
-
564
- test("signed vertical bar labels reserve compact axis clearance", () => {
565
- const labelRenderer = readFileSync(
566
- new URL("../src/analytics-app/charting/ChartRenderer.tsx", import.meta.url),
567
- "utf8",
568
- );
569
- assert.match(labelRenderer, /const BAR_NEGATIVE_VALUE_LABEL_AXIS_GUTTER = 40;/);
570
- });
571
-
572
- test("encoded funnel charts keep a single series even when color is present", () => {
573
- const helpers = readFileSync(
574
- new URL("../src/analytics-app/charting/chart-app-helpers.tsx", import.meta.url),
575
- "utf8",
576
- );
577
-
578
- assert.match(helpers, /if \(chart\.type === "funnel" \|\| !colorField \|\| shouldKeepSignedBarAsSingleSeries/);
579
- });
580
-
581
- test("encoded grouped scatter rows preserve size and label fields", () => {
582
- const helpers = readFileSync(
583
- new URL("../src/analytics-app/charting/chart-app-helpers.tsx", import.meta.url),
584
- "utf8",
585
- );
586
- const groupedSeriesBlock = helpers
587
- .split("const seriesFields = new Map", 2)[1]
588
- .split("const rowsByX = new Map", 1)[0];
589
-
590
- assert.match(groupedSeriesBlock, /if \(chart\.type === "scatter"\)/);
591
- assert.match(groupedSeriesBlock, /rows: rows\.map/);
592
- assert.match(groupedSeriesBlock, /\{ \.\.\.row, \[seriesField\]: row\[yField\] \}/);
593
- });
594
-
595
- test("encoded grouped scatter renders each bubble from the encoded y field", () => {
596
- const renderer = readFileSync(
597
- new URL("../src/analytics-app/charting/ChartRenderer.tsx", import.meta.url),
598
- "utf8",
599
- );
600
- const scatterBlock = renderer
601
- .split(') : chart.type === "scatter" ? (', 2)[1]
602
- .split(') : chart.type === "area" || chart.type === "stackedArea" ?', 1)[0];
603
-
604
- assert.match(renderer, /const scatterYField = chart\.type === "scatter" \? chartEncodingField\(chart, "y"\) : undefined;/);
605
- assert.match(scatterBlock, /renderYAxis\(scatterYField \?\? firstSeries\.field\)/);
606
- assert.match(scatterBlock, /const seriesRows = rows\.filter\(\(row\) => asNumber\(row\[series\.field\]\) != null\);/);
607
- assert.match(scatterBlock, /const scatterDataKey = scatterYField && seriesRows\.some\(\(row\) => asNumber\(row\[scatterYField\]\) != null\)/);
608
- assert.match(scatterBlock, /data=\{seriesRows\}/);
609
- assert.match(scatterBlock, /dataKey=\{scatterDataKey\}/);
610
- });
611
-
612
- test("legend buttons stay transparent at rest while exposing hover and focus states", () => {
613
- const chartTokens = readFileSync(
614
- new URL("../src/analytics-app/charting/chart-tokens.css", import.meta.url),
615
- "utf8",
616
- );
617
-
618
- assert.match(chartTokens, /\.chart-legend-button:hover/);
619
- assert.match(chartTokens, /\.chart-legend-button:focus-visible/);
620
- assert.match(chartTokens, /\.chart-legend-button \{[\s\S]*border: 0;[\s\S]*background: transparent;/);
621
- assert.match(chartTokens, /\.chart-legend-button:hover \{[\s\S]*color: var\(--ds-chart-text\);/);
622
- assert.match(chartTokens, /box-shadow: 0 0 0 3px/);
623
- });
624
-
625
- test("source data modal tables fill the available modal width", () => {
626
- const app = readFileSync(new URL("../src/analytics-app/App.tsx", import.meta.url), "utf8");
627
- const css = readFileSync(new URL("../src/analytics-app/styles.css", import.meta.url), "utf8");
628
-
629
- assert.match(app, /className="source-data-table"/);
630
- assert.match(css, /\.source-data-table \{/);
631
- assert.match(css, /\.source-data-table \.table-scroll-content \{/);
632
- assert.match(css, /width: 100%;/);
633
- assert.match(css, /min-width: 100%;/);
634
- });
635
-
636
- test("funnel shapes use Recharts label view box geometry", () => {
637
- const renderer = readFileSync(
638
- new URL("../src/analytics-app/charting/ChartRenderer.tsx", import.meta.url),
639
- "utf8",
640
- );
641
- const shapeRenderer = renderer
642
- .split("function renderRoundedFunnelShape", 2)[1]
643
- .split("function renderFunnelCenterLabel", 1)[0];
644
-
645
- assert.match(shapeRenderer, /labelViewBox\?\.x \?\? x/);
646
- assert.match(shapeRenderer, /labelViewBox\?\.y \?\? y/);
647
- assert.match(shapeRenderer, /labelViewBox\?\.upperWidth \?\? upperWidth/);
648
- assert.match(shapeRenderer, /labelViewBox\?\.lowerWidth \?\? lowerWidth/);
649
- assert.match(shapeRenderer, /labelViewBox\?\.height \?\? height/);
650
- });
651
-
652
- test("category x-axis labels wrap when crowded and rotate only when requested", () => {
653
- const renderer = readFileSync(
654
- new URL("../src/analytics-app/charting/ChartRenderer.tsx", import.meta.url),
655
- "utf8",
656
- );
657
- const rotationPolicy = renderer
658
- .split("function shouldRotateCategoryXAxisLabels", 2)[1]
659
- .split("function shouldWrapCategoryXAxisLabels", 1)[0];
660
- const wrappingHeuristic = renderer
661
- .split("function shouldWrapCategoryXAxisLabels", 2)[1]
662
- .split("function getFunnelStageColor", 1)[0];
663
-
664
- assert.match(rotationPolicy, /categoryLabelPolicy === "rotate"/);
665
- assert.match(wrappingHeuristic, /availableWidth: number/);
666
- assert.match(wrappingHeuristic, /labelPolicy === "wrap"/);
667
- assert.match(wrappingHeuristic, /longestLabelWidth <= labelSlotWidth\) return false/);
668
- assert.match(wrappingHeuristic, /if \(maxLength >= CATEGORY_X_AXIS_LONG_LABEL_LENGTH\) return true/);
669
- assert.match(renderer, /function WrappedCategoryXAxisTick/);
670
- });
671
-
672
- test("numeric axis ticks omit repeated units while axis titles carry unit context", () => {
673
- const renderer = readFileSync(
674
- new URL("../src/analytics-app/charting/ChartRenderer.tsx", import.meta.url),
675
- "utf8",
676
- );
677
- const helpers = readFileSync(
678
- new URL("../src/analytics-app/charting/chart-app-helpers.tsx", import.meta.url),
679
- "utf8",
680
- );
681
- const transforms = readFileSync(
682
- new URL("../src/analytics-app/charting/chart-transforms.ts", import.meta.url),
683
- "utf8",
684
- );
685
- const numericTick = renderer
686
- .split("function NumericYAxisTick", 2)[1]
687
- .split("function XAxisEndpointTick", 1)[0];
688
- const horizontalBar = renderer
689
- .split('horizontal && !stacked && (chart.type === "bar" || chart.type === "horizontalBar") ? (', 2)[1]
690
- .split(") : horizontal && stacked && (", 1)[0];
691
-
692
- assert.match(numericTick, /formatValue\(payload\?\.value, valueFormat\)/);
693
- assert.doesNotMatch(numericTick, /\bunit\b/);
694
- assert.doesNotMatch(renderer, /tickFormatter=\{\(value\) => formatValue\(value, [^}\n]*chart\.unit/);
695
- assert.match(transforms, /labels: ticks\.map\(\(value\) => formatValue\(value, chart\.valueFormat\)\)/);
696
- assert.match(helpers, /function chartEncodingAxisTitle/);
697
- assert.match(helpers, /return `\$\{label\} \(\$\{unit\}\)`/);
698
- assert.match(horizontalBar, /<XAxis[^>]*label=\{horizontalValueAxisLabel\}/);
699
- assert.match(horizontalBar, /<YAxis[^>]*label=\{horizontalCategoryAxisLabel\}/);
700
- assert.doesNotMatch(horizontalBar, /label=\{yAxisLabel\}/);
701
- assert.doesNotMatch(horizontalBar, /label=\{xAxisLabel\}/);
702
- });
703
-
704
- test("the top bar uses the main page background surface", () => {
705
- const styles = readFileSync(new URL("../src/analytics-app/styles.css", import.meta.url), "utf8");
706
- const topBar = styles
707
- .split(".analytics-top-bar {", 2)[1]
708
- .split("}", 1)[0];
709
-
710
- assert.match(topBar, /background: var\(--ds-bg\);/);
711
- assert.doesNotMatch(topBar, /background: var\(--ds-surface\);/);
712
- });
713
-
714
- test("artifact table movement cells render explicit positive and negative signs", () => {
715
- const app = readFileSync(new URL("../src/analytics-app/App.tsx", import.meta.url), "utf8");
716
- const tableFormatter = app
717
- .split("function formatTableCellValue", 2)[1]
718
- .split("function normalizedTableTextLength", 1)[0];
719
-
720
- assert.match(tableFormatter, /tableColumnLooksLikeMovement\(column\)/);
721
- assert.match(tableFormatter, /numeric == null \|\| numeric === 0/);
722
- assert.match(tableFormatter, /return `\$\{numeric > 0 \? "\+" : ""\}\$\{rendered\}`/);
723
- assert.match(app, /tableCellMovementClass\(column, value\)/);
724
- });
725
-
726
- test("artifact card chart type menu is limited by data shape", () => {
727
- const compatibility = readFileSync(
728
- new URL("../src/analytics-app/charting/chart-compatibility.ts", import.meta.url),
729
- "utf8",
730
- );
731
- const helpers = readFileSync(
732
- new URL("../src/analytics-app/charting/chart-app-helpers.tsx", import.meta.url),
733
- "utf8",
734
- );
735
- const app = readFileSync(new URL("../src/analytics-app/App.tsx", import.meta.url), "utf8");
736
-
737
- assert.match(compatibility, /export function compatibleChartTypesForDataShape/);
738
- assert.match(compatibility, /const FUNNEL_SHAPE_TYPES: ChartType\[\] = \["bar", "funnel"\]/);
739
- assert.match(compatibility, /types: \["bar", "leaderboard"\]/);
740
- assert.match(compatibility, /const HIDDEN_BAR_VARIANT_TYPE_LIST: ChartType\[\] = \[/);
741
- assert.match(compatibility, /if \(chart\.type === "funnel" \|\| chart\.intent === "funnel"\) return FUNNEL_SHAPE_TYPES;/);
742
- assert.match(helpers, /export function compatibleChartTypesForArtifactCard/);
743
- assert.equal((app.match(/compatibleChartTypesForArtifactCard\(overriddenChart, chartRows\)/g) || []).length, 2);
744
- assert.match(app, /applyChartSpecOverride\(chart, chartSpecOverrides\[chart\.id\]\)/);
745
- assert.match(app, /const chartModalTypeOptions = chartModalBaseChart\s+\?\s+compatibleChartTypesFor\(chartModalBaseChart, chartModalRows\)/);
746
- });
747
-
748
- test("artifact data source dialogs use sectioned shared table content", () => {
749
- const app = readFileSync(new URL("../src/analytics-app/App.tsx", import.meta.url), "utf8");
750
- const styles = readFileSync(new URL("../src/analytics-app/styles.css", import.meta.url), "utf8");
751
-
752
- assert.match(app, /<h3>Details<\/h3>/);
753
- assert.match(app, /<h3>Source query<\/h3>/);
754
- assert.match(app, /<h3>Data table<\/h3>/);
755
- assert.match(app, /function SourceDataTable/);
756
- assert.match(app, /className="source-data-table"/);
757
- assert.match(app, /<TableContent allowColumnResize=\{false\} columnWidths=/);
758
- assert.doesNotMatch(app, /function SourcePreviewRows/);
759
- assert.match(app, /className="modal-close-button"/);
760
- assert.match(app, /useModalScrollLock\(true\)/);
761
- assert.match(styles, /\.native-modal\.source-modal/);
762
- assert.match(styles, /\.source-query \{[\s\S]*?overflow-x: hidden;[\s\S]*?white-space: pre-wrap;/);
763
- });
764
-
765
- test("JavaScript MCP server rejects ready artifacts with access issues", () => {
766
- const args = artifactPayload("report");
767
- args.snapshot.accessIssues = [
768
- {
769
- id: "optional_join_denied",
770
- sourceId: "exploratory_join",
771
- message: "Optional use-case join was not readable.",
772
- },
773
- ];
774
-
775
- assert.throws(
776
- () => server.callTool("render_artifact", args),
777
- /\$\.snapshot\.accessIssues is only allowed when \$\.snapshot\.status is partial or blocked/,
778
- );
779
- });
780
-
781
- test("JavaScript MCP server rejects malformed report block shapes before rendering", () => {
782
- const missingTitle = artifactPayload("report");
783
- delete missingTitle.manifest.title;
784
- assert.throws(
785
- () => server.callTool("render_artifact", missingTitle),
786
- /\$\.manifest\.title is required for artifact rendering/,
787
- );
788
-
789
- const blankTitle = artifactPayload("report");
790
- blankTitle.manifest.title = " ";
791
- assert.throws(
792
- () => server.callTool("validate_artifact", blankTitle),
793
- /\$\.manifest\.title is required for artifact rendering/,
794
- );
795
-
796
- const withAudience = artifactPayload("report");
797
- withAudience.manifest.audience = "product stakeholders";
798
- assert.throws(
799
- () => server.callTool("validate_artifact", withAudience),
800
- /\$\.manifest\.audience is not supported/,
801
- );
802
-
803
- const withFreshness = artifactPayload("report");
804
- withFreshness.manifest.freshness = { snapshotPath: "data/snapshot.json" };
805
- assert.throws(
806
- () => server.callTool("validate_artifact", withFreshness),
807
- /\$\.manifest\.freshness is not supported/,
808
- );
809
-
810
- const withLegacyMetricDelta = artifactPayload("report");
811
- withLegacyMetricDelta.manifest.cards[0].deltaField = "wow";
812
- assert.throws(
813
- () => server.callTool("validate_artifact", withLegacyMetricDelta),
814
- /\$\.manifest\.cards\[0\]\.deltaField is not supported; use metrics\[\]/,
815
- );
816
-
817
- const withInitialView = artifactPayload("report");
818
- withInitialView.initial_view = { displayMode: "inline" };
819
- assert.throws(
820
- () => server.callTool("render_artifact", withInitialView),
821
- /initial_view is not supported/,
822
- );
823
-
824
- const withDefinitions = artifactPayload("report");
825
- withDefinitions.snapshot.definitions = [{ metric: "Revenue", definition: "Booked usage." }];
826
- assert.throws(
827
- () => server.callTool("validate_artifact", withDefinitions),
828
- /\$\.snapshot\.definitions is not supported/,
829
- );
830
-
831
- const noBlocks = artifactPayload("report");
832
- delete noBlocks.manifest.blocks;
833
- assert.throws(
834
- () => server.callTool("render_artifact", noBlocks),
835
- /must contain top-level artifact blocks/,
836
- );
837
- });
838
-
839
- test("JavaScript MCP server allows a visible report heading that matches the report title", () => {
840
- const args = artifactPayload("report");
841
- args.manifest.blocks[0].body = "# Revenue momentum";
842
-
843
- assert.equal(server.callTool("validate_artifact", args).ok, true);
844
- });
845
-
846
- test("JavaScript MCP server requires canonical dashboard blocks", () => {
847
- const noBlocks = artifactPayload("dashboard");
848
- delete noBlocks.manifest.blocks;
849
- assert.throws(
850
- () => server.callTool("render_artifact", noBlocks),
851
- /must contain top-level artifact blocks/,
852
- );
853
-
854
- });
855
-
856
- test("JavaScript MCP server requires reports to include a chart visualization", () => {
857
- const args = artifactPayload("report");
858
- args.manifest.blocks = args.manifest.blocks.filter((block) => block.type !== "chart");
859
-
860
- assert.throws(
861
- () => server.callTool("validate_artifact", args),
862
- /must include at least one chart block for report artifacts/,
863
- );
864
- assert.throws(
865
- () => server.callTool("render_artifact", args),
866
- /must include at least one chart block for report artifacts/,
867
- );
868
- });
869
-
870
- test("JavaScript MCP server advertises artifact handoff before fallback widgets", () => {
871
- const artifactTool = server
872
- .toolDefinitions()
873
- .find((tool) => tool.name === "render_artifact");
874
- const validateTool = server
875
- .toolDefinitions()
876
- .find((tool) => tool.name === "validate_artifact");
877
-
878
- assert.ok(artifactTool);
879
- assert.ok(validateTool);
880
- assert.match(server.SERVER_INSTRUCTIONS, /^Before rendering a report or dashboard artifact/);
881
- assert.match(server.SERVER_INSTRUCTIONS, /manifest\.blocks/);
882
- assert.match(server.SERVER_INSTRUCTIONS, /first markdown block whose body is a # heading matching manifest\.title/);
883
- assert.match(validateTool.description, /without rendering a hosted widget/);
884
- assert.equal(validateTool._meta, undefined);
885
- assert.match(artifactTool.description, /dashboard or report artifact/);
886
- assert.match(artifactTool.description, /Call validate_artifact first/);
887
- assert.match(artifactTool.inputSchema.properties.manifest.description, /top-level manifest\.blocks/);
888
- assert.match(artifactTool.inputSchema.properties.manifest.description, /first content heading/);
889
- assert.match(artifactTool.inputSchema.properties.manifest.properties.blocks.description, /type "html"/);
890
- assert.match(artifactTool.inputSchema.properties.manifest.properties.title.description, /first content heading/);
891
- const manifestSchema = artifactTool.inputSchema.properties.manifest;
892
- const cardSchema = manifestSchema.properties.cards.items;
893
- assert.ok(cardSchema.required.includes("metrics"));
894
- assert.equal(cardSchema.properties.valueField, undefined);
895
- assert.equal(cardSchema.properties.metrics.items.properties.field.type, "string");
896
-
897
- const tableColumnSchema = manifestSchema.properties.tables.items.properties.columns.items;
898
- assert.ok(tableColumnSchema.required.includes("field"));
899
- assert.equal(tableColumnSchema.properties.key, undefined);
900
-
901
- const blockSchema = manifestSchema.properties.blocks.items;
902
- assert.ok(blockSchema.required.includes("id"));
903
- assert.equal(blockSchema.properties.markdown, undefined);
904
- assert.deepEqual(artifactTool._meta.ui, {
905
- resourceUri: server.ARTIFACT_WIDGET_URI,
906
- visibility: ["model"],
907
- });
908
- });
909
-
910
- test("JavaScript MCP server rejects artifact source and safety violations", () => {
911
- const args = artifactPayload();
912
- args.sources[0].id = "not_declared";
913
- assert.throws(
914
- () => server.callTool("render_artifact", args),
915
- /declared in manifest\.sources/,
916
- );
917
-
918
- const unsafe = artifactPayload();
919
- unsafe.snapshot.datasets.weekly_revenue[0].customer_email = "person@example.com";
920
- assert.throws(
921
- () => server.callTool("render_artifact", unsafe),
922
- /looks unsafe/,
923
- );
924
- });
925
-
926
- test("JavaScript MCP server allows reviewed customer dimensions in artifacts", () => {
927
- const args = artifactPayload();
928
- args.contains_sensitive_data = true;
929
- args.snapshot.datasets.weekly_revenue[0].customer_name = "Acme Corp";
930
-
931
- const payload = server.callTool("render_artifact", args);
932
-
933
- assert.equal(payload.widget_type, "artifact");
934
- assert.equal(payload.snapshot.datasets.weekly_revenue[0].customer_name, "Acme Corp");
935
- });
936
-
937
- test("JavaScript MCP server renders query-shaped chart payloads", () => {
938
- const payload = server.callTool("render_chart", queryTablePayload());
939
-
940
- assert.equal(payload.widget_type, "chart");
941
- assert.equal(payload.source.query.id, "query-123");
942
- assert.equal(payload.chart.type, "line");
943
- assert.equal(payload.chart_spec.type, "line");
944
- assert.equal(payload.chart_spec.encodings.x.field, "reporting_date");
945
- assert.equal(payload.chart_spec.encodings.y.field, "arr_b");
946
- assert.equal("xField" in payload.chart_spec, false);
947
- assert.equal("series" in payload.chart_spec, false);
948
- assert.equal(payload.chart_spec.xAxisTitle, undefined);
949
- assert.equal(payload.chart_spec.yAxisTitle, undefined);
950
- assert.equal(payload.chart_spec.unit, "$B");
951
- assert.equal("referenceLines" in payload.chart_spec, false);
952
- assert.equal("chart_rows" in payload, false);
953
- assert.equal("x_field" in payload, false);
954
- assert.equal("y_field" in payload, false);
955
- assert.equal("x_axis_title" in payload, false);
956
- assert.equal("y_axis_title" in payload, false);
957
- assert.equal("time_unit" in payload, false);
958
- assert.deepEqual(payload.data, [
959
- {
960
- reporting_date: "2026-03-31",
961
- arr_b: 2.73,
962
- x: "2026-03-31",
963
- y: 2.73,
964
- size: null,
965
- series: "ARR",
966
- },
967
- {
968
- reporting_date: "2026-04-30",
969
- arr_b: 2.9,
970
- x: "2026-04-30",
971
- y: 2.9,
972
- size: null,
973
- series: "ARR",
974
- },
975
- ]);
976
- });
977
-
978
- test("JavaScript MCP server requires model-provided chart type and fields", () => {
979
- const missingType = queryTablePayload();
980
- delete missingType.chart.type;
981
- assert.throws(() => server.callTool("render_chart", missingType), /chart\.type is required/);
982
-
983
- const missingX = queryTablePayload();
984
- delete missingX.chart.fields.x;
985
- assert.throws(() => server.callTool("render_chart", missingX), /chart\.fields\.x\.field is required/);
986
-
987
- const missingY = queryTablePayload();
988
- delete missingY.chart.fields.y;
989
- assert.throws(() => server.callTool("render_chart", missingY), /chart\.fields\.y\.field is required/);
990
- });
991
-
992
- test("JavaScript MCP server accepts custom chart axis titles", () => {
993
- const args = queryTablePayload();
994
- args.display.x_axis_title = "Fiscal month";
995
- args.display.y_axis_title = "ARR, billions";
996
-
997
- const payload = server.callTool("render_chart", args);
998
-
999
- assert.equal(payload.display.x_axis_title, "Fiscal month");
1000
- assert.equal(payload.display.y_axis_title, "ARR, billions");
1001
- assert.equal(payload.chart_spec.xAxisTitle, "Fiscal month");
1002
- assert.equal(payload.chart_spec.yAxisTitle, "ARR, billions");
1003
- assert.equal("x_label" in payload, false);
1004
- assert.equal("y_label" in payload, false);
1005
- });
1006
-
1007
- test("JavaScript MCP server normalizes heatmap encodings to widget field roles", () => {
1008
- const payload = server.callTool("render_chart", {
1009
- title: "Heatmap",
1010
- source: {
1011
- query: {
1012
- engine: "trino",
1013
- sql: "SELECT day_name, segment, score FROM warehouse.heatmap_source",
1014
- id: "heatmap-query",
1015
- },
1016
- },
1017
- table: {
1018
- columns: [
1019
- { key: "day_name", label: "Day", type: "text" },
1020
- { key: "segment", label: "Segment", type: "text" },
1021
- { key: "score", label: "Score", type: "number" },
1022
- ],
1023
- rows: [
1024
- { day_name: "Mon", segment: "Self-serve", score: 18 },
1025
- { day_name: "Mon", segment: "Enterprise", score: 26 },
1026
- { day_name: "Tue", segment: "Self-serve", score: 21 },
1027
- { day_name: "Tue", segment: "Enterprise", score: 31 },
1028
- ],
1029
- },
1030
- chart: {
1031
- type: "heatmap",
1032
- fields: {
1033
- x: { field: "day_name", type: "ordinal" },
1034
- y: { field: "segment", type: "nominal" },
1035
- color: { field: "score", type: "quantitative" },
1036
- },
1037
- },
1038
- });
1039
-
1040
- assert.equal(payload.chart.fields.y.field, "score");
1041
- assert.equal(payload.chart.fields.color.field, "segment");
1042
- assert.deepEqual(payload.data.map((point) => [point.x, point.y, point.series]), [
1043
- ["Mon", 18, "Self-serve"],
1044
- ["Mon", 26, "Enterprise"],
1045
- ["Tue", 21, "Self-serve"],
1046
- ["Tue", 31, "Enterprise"],
1047
- ]);
1048
- });
1049
-
1050
- test("JavaScript MCP server reports heatmap field roles when value encoding is not numeric", () => {
1051
- assert.throws(() => server.callTool("render_chart", {
1052
- title: "Heatmap",
1053
- source: {
1054
- query: {
1055
- engine: "trino",
1056
- sql: "SELECT day_name, segment, score_label FROM warehouse.heatmap_source",
1057
- id: "heatmap-query",
1058
- },
1059
- },
1060
- table: {
1061
- columns: [
1062
- { key: "day_name", label: "Day", type: "text" },
1063
- { key: "segment", label: "Segment", type: "text" },
1064
- { key: "score_label", label: "Score label", type: "text" },
1065
- ],
1066
- rows: [
1067
- { day_name: "Mon", segment: "Self-serve", score_label: "Low" },
1068
- { day_name: "Mon", segment: "Enterprise", score_label: "High" },
1069
- ],
1070
- },
1071
- chart: {
1072
- type: "heatmap",
1073
- fields: {
1074
- x: { field: "day_name", type: "ordinal" },
1075
- y: { field: "score_label", type: "text" },
1076
- color: { field: "segment", type: "nominal" },
1077
- },
1078
- },
1079
- }), /numeric heatmap cell value/);
1080
- });
1081
-
1082
- test("JavaScript MCP server keeps scatter size on the source field", () => {
1083
- const payload = server.callTool("render_chart", {
1084
- title: "ARR efficiency by segment",
1085
- source: sourceQueryForTest(),
1086
- table: {
1087
- columns: [
1088
- { key: "conversion_rate", label: "Conversion", type: "percent" },
1089
- { key: "arr_usd", label: "ARR", type: "currency", unit: "USD" },
1090
- { key: "active_accounts", label: "Active Accounts", type: "number" },
1091
- { key: "segment", label: "Segment", type: "text" },
1092
- ],
1093
- rows: [
1094
- { conversion_rate: 0.24, arr_usd: 1445000, active_accounts: 95, segment: "Enterprise" },
1095
- { conversion_rate: 0.20, arr_usd: 846000, active_accounts: 161, segment: "Mid-Market" },
1096
- ],
1097
- },
1098
- chart: {
1099
- type: "scatter",
1100
- fields: {
1101
- x: { field: "conversion_rate", type: "quantitative" },
1102
- y: { field: "arr_usd", type: "quantitative" },
1103
- size: { field: "active_accounts", type: "quantitative" },
1104
- color: { field: "segment", type: "nominal" },
1105
- },
1106
- },
1107
- });
1108
-
1109
- assert.equal(payload.chart_spec.encodings.size.field, "active_accounts");
1110
- assert.equal(payload.chart_spec.encodings.size.label, "Active Accounts");
1111
- assert.equal(payload.chart_spec.encodings.size.format, "number");
1112
- assert.equal(payload.chart_spec.encodings.x.field, "conversion_rate");
1113
- assert.equal(payload.chart_spec.encodings.y.field, "arr_usd");
1114
- assert.equal(payload.chart_spec.encodings.color.field, "segment");
1115
- assert.equal("chart_rows" in payload, false);
1116
- assert.deepEqual(payload.table.rows.map((row) => row.active_accounts), [95, 161]);
1117
- assert.equal(payload.table.rows.some((row) => "__pointSize" in row), false);
1118
- });
1119
-
1120
- test("JavaScript MCP server accepts non-scatter label fields as optional display metadata", () => {
1121
- const payload = server.callTool("render_chart", {
1122
- title: "Leaderboard",
1123
- source: sourceQueryForTest(),
1124
- table: {
1125
- columns: [
1126
- { key: "rank_order", label: "Rank", type: "number" },
1127
- { key: "team", label: "Team", type: "text" },
1128
- { key: "score", label: "Score", type: "number" },
1129
- ],
1130
- rows: [
1131
- { rank_order: 1, team: "Team Atlas", score: 980 },
1132
- { rank_order: 2, team: "Team Beacon", score: 870 },
1133
- ],
1134
- },
1135
- chart: {
1136
- type: "leaderboard",
1137
- fields: {
1138
- x: { field: "team", type: "nominal" },
1139
- y: { field: "score", type: "quantitative" },
1140
- label: { field: "rank_order", type: "quantitative" },
1141
- },
1142
- },
1143
- });
1144
-
1145
- assert.equal(payload.ok, true);
1146
- assert.equal(payload.chart.fields.label.field, "rank_order");
1147
- });
1148
-
1149
- test("JavaScript MCP server exposes chart v1 settings in the chart schema", () => {
1150
- const chartTool = server.toolDefinitions().find((tool) => tool.name === "render_chart");
1151
- const properties = chartTool.inputSchema.properties;
1152
- const chart = chartTool.inputSchema.properties.chart.properties;
1153
- const options = chart.options.properties;
1154
- const display = chartTool.inputSchema.properties.display.properties;
1155
- const tableColumn = properties.table.properties.columns.items.properties;
1156
-
1157
- assert.match(properties.subtitle.description, /insight or takeaway/);
1158
- assert.match(properties.subtitle.description, /source\.query/);
1159
- assert.deepEqual(options.orientation.enum, ["vertical", "horizontal", null]);
1160
- assert.deepEqual(options.grouping.enum, ["single", "grouped", "stacked", "stacked100", null]);
1161
- assert.ok(options.points);
1162
- assert.equal("zero_line" in options, false);
1163
- assert.equal("limit" in options, false);
1164
- assert.equal("show_values" in options, false);
1165
- assert.equal("category_label_policy" in options, false);
1166
- assert.equal("sort" in options, false);
1167
- assert.equal("show_latest_value" in options, false);
1168
- assert.equal("bins" in options, false);
1169
- assert.equal("max_segments" in options, false);
1170
- assert.equal("other_threshold" in options, false);
1171
- assert.equal("show_percent" in options, false);
1172
- assert.equal("legend" in display, false);
1173
- assert.equal("view_mode" in display, false);
1174
- assert.equal("labels" in display, false);
1175
- assert.equal("number_format" in display, false);
1176
- assert.deepEqual(tableColumn.format.enum, ["compact", "number", "percent", "currency", null]);
1177
- assert.equal("version" in chart, false);
1178
- assert.equal("comparisonContext" in chart, false);
1179
- assert.equal("notes" in properties, false);
1180
- assert.equal("datasets" in properties, false);
1181
- assert.equal("selected_dataset" in properties, false);
1182
- assert.equal("query_binding" in properties, false);
1183
- assert.equal("facet" in chart.fields.properties, false);
1184
- assert.equal("tooltip" in chart.fields.properties, false);
1185
- assert.equal("sort" in chart, false);
1186
- assert.equal("notes" in display, false);
1187
- assert.equal("tooltip" in display, false);
1188
- assert.equal("empty_state" in display, false);
1189
- assert.equal("reference_lines" in display, false);
1190
- assert.match(server.SERVER_INSTRUCTIONS, /chart\.type "bar"/);
1191
- assert.match(server.SERVER_INSTRUCTIONS, /Chart subtitles should add a reader-facing insight/);
1192
- });
1193
-
1194
- test("JavaScript MCP server rejects removed inline snake_case chart names", () => {
1195
- const args = queryTablePayload();
1196
- args.chart.type = "grouped_column";
1197
- assert.throws(
1198
- () => server.callTool("render_chart", args),
1199
- /chart\.type must be one of/,
1200
- );
1201
- });
1202
-
1203
- test("JavaScript MCP server strips inline chart notes while table notes remain supported", () => {
1204
- const chartArgs = queryTablePayload();
1205
- chartArgs.notes = ["top-level chart note"];
1206
- chartArgs.chart.version = "legacy";
1207
- chartArgs.chart.comparisonContext = { baseline: "legacy" };
1208
- chartArgs.chart.sort = "legacy";
1209
- chartArgs.chart.options = {
1210
- show_values: true,
1211
- category_label_policy: "wrap",
1212
- show_latest_value: true,
1213
- bins: 12,
1214
- max_segments: 8,
1215
- other_threshold: 0.1,
1216
- show_percent: true,
1217
- };
1218
- chartArgs.chart.fields.facet = { field: "legacy" };
1219
- chartArgs.chart.fields.tooltip = [{ field: "legacy" }];
1220
- chartArgs.display.notes = ["presentation chart note"];
1221
- chartArgs.display.layout = "split";
1222
- chartArgs.display.source_panel = "sidebar";
1223
- chartArgs.display.sql_panel_position = "bottom_right";
1224
- chartArgs.display.table_panel_position = "bottom_left";
1225
- chartArgs.display.legend = { title: "Legacy" };
1226
- chartArgs.display.view_mode = "table";
1227
- chartArgs.display.number_format = "currency";
1228
- chartArgs.display.labels = { values: "all" };
1229
- chartArgs.query_binding = { label: "legacy binding" };
1230
- chartArgs.datasets = [{ id: "legacy dataset input" }];
1231
- chartArgs.selected_dataset = "legacy dataset input";
1232
-
1233
- const chartPayload = server.callTool("render_chart", chartArgs);
1234
- const tablePayload = server.callTool("render_table", {
1235
- title: "Preview",
1236
- source: chartArgs.source,
1237
- notes: ["table note"],
1238
- columns: [{ key: "segment", label: "Segment", type: "text" }],
1239
- rows: [{ segment: "Enterprise" }],
1240
- });
1241
- const tableSchema =
1242
- server
1243
- .toolDefinitions()
1244
- .find((tool) => tool.name === "render_table")
1245
- .inputSchema.properties;
1246
-
1247
- assert.equal("notes" in chartPayload, false);
1248
- assert.equal("notes" in (chartPayload.display || {}), false);
1249
- assert.equal("version" in chartPayload.chart, false);
1250
- assert.equal("comparisonContext" in chartPayload.chart, false);
1251
- assert.equal("sort" in chartPayload.chart, false);
1252
- assert.deepEqual(chartPayload.chart.options, undefined);
1253
- assert.equal("facet" in chartPayload.chart.fields, false);
1254
- assert.equal("tooltip" in chartPayload.chart.fields, false);
1255
- assert.equal("layout" in (chartPayload.display || {}), false);
1256
- assert.equal("source_panel" in (chartPayload.display || {}), false);
1257
- assert.equal("sql_panel_position" in (chartPayload.display || {}), false);
1258
- assert.equal("table_panel_position" in (chartPayload.display || {}), false);
1259
- assert.equal("legend" in (chartPayload.display || {}), false);
1260
- assert.equal("view_mode" in (chartPayload.display || {}), false);
1261
- assert.equal("number_format" in (chartPayload.display || {}), false);
1262
- assert.equal("legend" in chartPayload.chart_spec, false);
1263
- assert.equal("labels" in chartPayload.chart_spec, false);
1264
- assert.equal("viewMode" in chartPayload.chart_spec.surface, false);
1265
- assert.equal("view_mode" in chartPayload, false);
1266
- assert.equal("labels" in (chartPayload.display || {}), false);
1267
- assert.equal("datasets" in chartPayload, false);
1268
- assert.equal("selected_dataset" in chartPayload, false);
1269
- assert.equal("query_binding" in chartPayload, false);
1270
- assert.ok("notes" in tableSchema);
1271
- assert.deepEqual(tablePayload.notes, ["table note"]);
1272
- });
1273
-
1274
- test("JavaScript MCP server preserves chart-compatible source SQL for table widgets", () => {
1275
- const chartArgs = queryTablePayload();
1276
- const payload = server.callTool("render_table", {
1277
- title: "ARR rows",
1278
- source: chartArgs.source,
1279
- result_table: chartArgs.table,
1280
- });
1281
- const tableSchema =
1282
- server
1283
- .toolDefinitions()
1284
- .find((tool) => tool.name === "render_table")
1285
- .inputSchema.properties;
1286
-
1287
- assert.equal(payload.widget_type, "table");
1288
- assert.equal(payload.source.query.sql, "SELECT reporting_date, arr_b FROM gtm.example");
1289
- assert.equal("query" in payload.source.query, false);
1290
- assert.equal(payload.source.query.id, "query-123");
1291
- assert.equal(payload.source_query, undefined);
1292
- assert.ok(tableSchema.source);
1293
- assert.ok(tableSchema.source.properties.query.properties.sql);
1294
- assert.ok(tableSchema.source.properties.query.properties.filters);
1295
- assert.ok(tableSchema.source.properties.query.properties.description);
1296
- });
1297
-
1298
- test("JavaScript MCP server removes source.query.query from table widget payloads", () => {
1299
- const chartArgs = queryTablePayload();
1300
- chartArgs.source.query.query = "Loads ARR by reporting date for widget tests.";
1301
-
1302
- const payload = server.callTool("render_table", {
1303
- title: "ARR rows",
1304
- source: chartArgs.source,
1305
- result_table: chartArgs.table,
1306
- });
1307
-
1308
- assert.equal(payload.widget_type, "table");
1309
- assert.equal(payload.source.query.sql, "SELECT reporting_date, arr_b FROM gtm.example");
1310
- assert.equal("query" in payload.source.query, false);
1311
- assert.equal(payload.result_table.rows.length, 2);
1312
- });
1313
-
1314
- test("JavaScript MCP server rejects payloads that only use removed source.query.query", () => {
1315
- const chartArgs = queryTablePayload();
1316
- delete chartArgs.source.query.sql;
1317
- chartArgs.source.query.query = "SELECT reporting_date, arr_b FROM gtm.example";
1318
-
1319
- assert.throws(
1320
- () => server.callTool("render_table", {
1321
- title: "ARR rows",
1322
- source: chartArgs.source,
1323
- result_table: chartArgs.table,
1324
- }),
1325
- /actual SQL query text/,
1326
- );
1327
- });
1328
-
1329
- test("JavaScript MCP server rejects redundant bar chart type aliases", () => {
1330
- assert.throws(() => server.callTool("render_chart", {
1331
- title: "Revenue mix",
1332
- source: sourceQueryForTest(),
1333
- table: {
1334
- columns: [
1335
- { key: "segment", type: "text" },
1336
- { key: "channel", type: "text" },
1337
- { key: "revenue", type: "number" },
1338
- ],
1339
- rows: [
1340
- { segment: "Enterprise", channel: "Direct", revenue: 40 },
1341
- { segment: "Enterprise", channel: "Partner", revenue: 60 },
1342
- ],
1343
- },
1344
- chart: {
1345
- type: "horizontalStackedBar100",
1346
- fields: {
1347
- x: { field: "segment", type: "nominal" },
1348
- y: { field: "revenue", type: "quantitative" },
1349
- color: { field: "channel", type: "nominal" },
1350
- },
1351
- },
1352
- }), /chart\.type must be one of/);
1353
- });
1354
-
1355
- test("JavaScript MCP server rejects invalid grouped and percent stacked bar configs", () => {
1356
- const baseArgs = {
1357
- title: "Revenue mix",
1358
- source: sourceQueryForTest(),
1359
- table: {
1360
- columns: [
1361
- { key: "segment", type: "text" },
1362
- { key: "channel", type: "text" },
1363
- { key: "revenue", type: "number" },
1364
- ],
1365
- rows: [{ segment: "Enterprise", channel: "Direct", revenue: 0 }],
1366
- },
1367
- chart: {
1368
- type: "bar",
1369
- options: { grouping: "stacked" },
1370
- fields: {
1371
- x: { field: "segment", type: "nominal" },
1372
- y: { field: "revenue", type: "quantitative" },
1373
- },
1374
- },
1375
- };
1376
-
1377
- assert.throws(
1378
- () => server.callTool("render_chart", baseArgs),
1379
- /requires chart\.fields\.color\.field/,
1380
- );
1381
-
1382
- const invalidPercent = structuredClone(baseArgs);
1383
- invalidPercent.chart.options.grouping = "stacked100";
1384
- invalidPercent.chart.fields.color = { field: "channel", type: "nominal" };
1385
- assert.throws(
1386
- () => server.callTool("render_chart", invalidPercent),
1387
- /positive denominator/,
1388
- );
1389
- });
1390
-
1391
- test("JavaScript MCP server rejects auto chart setting modes", () => {
1392
- const args = queryTablePayload();
1393
-
1394
- args.chart.options = { points: "auto" };
1395
- assert.throws(
1396
- () => server.callTool("render_chart", args),
1397
- /chart\.options\.points must be one of always, never/,
1398
- );
1399
- });
1400
-
1401
- test("JavaScript MCP server warns on dense compact bars and pies", () => {
1402
- const rows = Array.from({ length: 25 }, (_, index) => ({ category: `C${index}`, value: index + 1 }));
1403
- const columns = [{ key: "category", type: "text" }, { key: "value", type: "number" }];
1404
- const barPayload = server.callTool("render_chart", {
1405
- title: "Dense categories",
1406
- source: sourceQueryForTest(),
1407
- table: { columns, rows },
1408
- chart: {
1409
- type: "bar",
1410
- fields: { x: { field: "category" }, y: { field: "value" } },
1411
- },
1412
- });
1413
- const piePayload = server.callTool("render_chart", {
1414
- title: "Dense slices",
1415
- source: sourceQueryForTest(),
1416
- table: { columns, rows: rows.slice(0, 9) },
1417
- chart: {
1418
- type: "pie",
1419
- fields: { x: { field: "category" }, y: { field: "value" } },
1420
- },
1421
- });
1422
-
1423
- assert.match(barPayload.quality_warnings.join(" "), /Compact bar chart has 25 categories/);
1424
- assert.match(piePayload.quality_warnings.join(" "), /Compact pie chart has 9 slices/);
1425
- });
1426
-
1427
- test("JavaScript MCP server returns typed resource errors", async () => {
1428
- const response = await server.handleRpc({
1429
- jsonrpc: "2.0",
1430
- id: 1,
1431
- method: "resources/read",
1432
- params: { uri: "ui://widget/missing.html" },
1433
- });
1434
-
1435
- assert.equal(response.error.code, -32602);
1436
- assert.match(response.error.message, /unknown Data Analytics widget resource/);
1437
- });
1438
-
1439
- test("JavaScript MCP server rejects legacy widget resource aliases", async () => {
1440
- for (const uri of [
1441
- server.CHART_WIDGET_URI.replace(".html", "-old.html"),
1442
- server.TABLE_WIDGET_URI.replace(".html", "-old.html"),
1443
- server.ARTIFACT_WIDGET_URI.replace(".html", "-old.html"),
1444
- ]) {
1445
- const response = await server.handleRpc({
1446
- jsonrpc: "2.0",
1447
- id: 1,
1448
- method: "resources/read",
1449
- params: { uri },
1450
- });
1451
-
1452
- assert.equal(response.error.code, -32602);
1453
- assert.match(response.error.message, /unknown Data Analytics widget resource/);
1454
- }
1455
- });
1456
-
1457
- test("artifact share menu labels Sites publishing clearly", () => {
1458
- const app = readFileSync(new URL("../src/analytics-app/App.tsx", import.meta.url), "utf8");
1459
- assert.match(app, /site: "Publish to Sites"/);
1460
- assert.doesNotMatch(app, /Publish hosted link/);
1461
- });
1462
-
1463
- test("local artifact fallback uses canonical chart encodings", () => {
1464
- const widget = readFileSync(new URL("../src/datascience-artifact-widget.jsx", import.meta.url), "utf8");
1465
- const fallback = widget.split("const fallbackPayload =", 2)[1].split("const hostedEmptyPayload", 1)[0];
1466
- assert.match(fallback, /sourceId: "snapshot"/);
1467
- assert.match(fallback, /encodings: \{/);
1468
- assert.doesNotMatch(fallback, /xField:/);
1469
- assert.doesNotMatch(fallback, /^\s+series:/m);
1470
- });
1471
-
1472
- test("artifact charts wire clickable legend state into the renderer", () => {
1473
- const app = readFileSync(new URL("../src/analytics-app/App.tsx", import.meta.url), "utf8");
1474
- assert.match(app, /const \[visibleSeries, setVisibleSeries\] = useState\(\)/);
1475
- assert.match(app, /onVisibleSeriesChange=\{setVisibleSeries\}/);
1476
- assert.match(app, /visibleSeries=\{visibleSeries\}/);
1477
- });
1478
-
1479
- test("JavaScript MCP server rejects unsafe widget fields", () => {
1480
- const args = queryTablePayload();
1481
- args.table.columns.push({ key: "customer_email", label: "Email" });
1482
- args.table.rows[0].customer_email = "person@example.com";
1483
-
1484
- assert.throws(
1485
- () => server.callTool("render_chart", args),
1486
- /looks unsafe/,
1487
- );
1488
- });
1489
-
1490
- test("JavaScript MCP server allows reviewed customer dimensions in widgets", () => {
1491
- const args = queryTablePayload();
1492
- args.table.contains_sensitive_data = true;
1493
- args.table.columns.push({ key: "customer_name", label: "Customer" });
1494
- args.table.rows[0].customer_name = "Acme Corp";
1495
-
1496
- const payload = server.callTool("render_chart", args);
1497
-
1498
- assert.equal(payload.widget_type, "chart");
1499
- assert.equal(payload.table.rows[0].customer_name, "Acme Corp");
1500
- });