create-yss-spec 1.1.1 → 1.1.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 (257) hide show
  1. package/package.json +1 -1
  2. package/template/.agents/skills/high-fidelity-html-prototype/SKILL.md +102 -0
  3. package/template/.codex/skills/data-analytics/.app.json +84 -0
  4. package/template/.codex/skills/data-analytics/.codex-plugin/plugin.json +66 -0
  5. package/template/.codex/skills/data-analytics/.mcp.json +30 -0
  6. package/template/.codex/skills/data-analytics/AGENTS.md +20 -0
  7. package/template/.codex/skills/data-analytics/DEPENDENCIES.MD +27 -0
  8. package/template/.codex/skills/data-analytics/README.md +60 -0
  9. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html +16 -0
  10. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part001 +1 -0
  11. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part002 +1 -0
  12. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part003 +1 -0
  13. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part004 +1 -0
  14. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part005 +1 -0
  15. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part006 +1 -0
  16. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html +16 -0
  17. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part001 +1 -0
  18. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part002 +1 -0
  19. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part003 +1 -0
  20. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part004 +1 -0
  21. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part005 +1 -0
  22. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part006 +1 -0
  23. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part007 +1 -0
  24. package/template/.codex/skills/data-analytics/assets/datascience-small.svg +5 -0
  25. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html +16 -0
  26. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part001 +1 -0
  27. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part002 +1 -0
  28. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part003 +1 -0
  29. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part004 +1 -0
  30. package/template/.codex/skills/data-analytics/assets/datascience.png +0 -0
  31. package/template/.codex/skills/data-analytics/assets/datascience.svg +10 -0
  32. package/template/.codex/skills/data-analytics/mcp/server.cjs +2963 -0
  33. package/template/.codex/skills/data-analytics/package-lock.json +3048 -0
  34. package/template/.codex/skills/data-analytics/package.json +43 -0
  35. package/template/.codex/skills/data-analytics/scripts/normalize-widget-assets.mjs +75 -0
  36. package/template/.codex/skills/data-analytics/skills/analyze-data-quality/SKILL.md +160 -0
  37. package/template/.codex/skills/data-analytics/skills/analyze-data-quality/agents/openai.yaml +6 -0
  38. package/template/.codex/skills/data-analytics/skills/build-dashboard/SKILL.md +148 -0
  39. package/template/.codex/skills/data-analytics/skills/build-dashboard/agents/openai.yaml +6 -0
  40. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/bi-platform-dashboard.md +18 -0
  41. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/html-dashboard.md +24 -0
  42. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/mcp-artifact-dashboard.md +71 -0
  43. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/streamlit-dashboard.md +85 -0
  44. package/template/.codex/skills/data-analytics/skills/build-report/SKILL.md +207 -0
  45. package/template/.codex/skills/data-analytics/skills/build-report/agents/openai.yaml +6 -0
  46. package/template/.codex/skills/data-analytics/skills/build-report/assets/executive-report-shell.html +70 -0
  47. package/template/.codex/skills/data-analytics/skills/build-report/assets/technical-report-shell.html +66 -0
  48. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/SKILL.md +118 -0
  49. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/agents/openai.yaml +6 -0
  50. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/__init__.py +1 -0
  51. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/cli.py +57 -0
  52. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/constants.py +61 -0
  53. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/docx_writer.py +362 -0
  54. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/html_parser.py +829 -0
  55. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/model.py +30 -0
  56. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/plan.py +129 -0
  57. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/quality.py +374 -0
  58. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/rendering.py +613 -0
  59. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/table_utils.py +54 -0
  60. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/utils.py +16 -0
  61. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc_plan.py +9 -0
  62. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-slides/SKILL.md +78 -0
  63. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-slides/agents/openai.yaml +6 -0
  64. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-slides/scripts/report_to_google_slides.py +2379 -0
  65. package/template/.codex/skills/data-analytics/skills/build-report/report-to-pdf/SKILL.md +88 -0
  66. package/template/.codex/skills/data-analytics/skills/build-report/report-to-pdf/agents/openai.yaml +6 -0
  67. package/template/.codex/skills/data-analytics/skills/build-report/specifications/executive-report.md +97 -0
  68. package/template/.codex/skills/data-analytics/skills/build-report/specifications/mcp-app-report.md +59 -0
  69. package/template/.codex/skills/data-analytics/skills/build-report/specifications/technical-report.md +75 -0
  70. package/template/.codex/skills/data-analytics/skills/design-kpis/SKILL.md +103 -0
  71. package/template/.codex/skills/data-analytics/skills/design-kpis/agents/openai.yaml +6 -0
  72. package/template/.codex/skills/data-analytics/skills/gather-business-context/SKILL.md +68 -0
  73. package/template/.codex/skills/data-analytics/skills/gather-business-context/agents/openai.yaml +6 -0
  74. package/template/.codex/skills/data-analytics/skills/index/SKILL.md +251 -0
  75. package/template/.codex/skills/data-analytics/skills/index/agents/openai.yaml +6 -0
  76. package/template/.codex/skills/data-analytics/skills/jupyter-notebooks/SKILL.md +131 -0
  77. package/template/.codex/skills/data-analytics/skills/jupyter-notebooks/agents/openai.yaml +6 -0
  78. package/template/.codex/skills/data-analytics/skills/kpi-reporting/SKILL.md +141 -0
  79. package/template/.codex/skills/data-analytics/skills/kpi-reporting/agents/openai.yaml +6 -0
  80. package/template/.codex/skills/data-analytics/skills/kpi-reporting/references/report-templates.md +32 -0
  81. package/template/.codex/skills/data-analytics/skills/market-sizing/SKILL.md +106 -0
  82. package/template/.codex/skills/data-analytics/skills/market-sizing/agents/openai.yaml +6 -0
  83. package/template/.codex/skills/data-analytics/skills/metric-diagnostics/SKILL.md +130 -0
  84. package/template/.codex/skills/data-analytics/skills/metric-diagnostics/agents/openai.yaml +6 -0
  85. package/template/.codex/skills/data-analytics/skills/product-business-analysis/SKILL.md +141 -0
  86. package/template/.codex/skills/data-analytics/skills/product-business-analysis/agents/openai.yaml +6 -0
  87. package/template/.codex/skills/data-analytics/skills/spreadsheets/SKILL.md +178 -0
  88. package/template/.codex/skills/data-analytics/skills/spreadsheets/agents/openai.yaml +9 -0
  89. package/template/.codex/skills/data-analytics/skills/spreadsheets/assets/file-spreadsheet.png +0 -0
  90. package/template/.codex/skills/data-analytics/skills/spreadsheets/charts.md +31 -0
  91. package/template/.codex/skills/data-analytics/skills/spreadsheets/references/artifact_tool_api.md +466 -0
  92. package/template/.codex/skills/data-analytics/skills/spreadsheets/style_guidelines.md +99 -0
  93. package/template/.codex/skills/data-analytics/skills/user-context/SKILL.md +195 -0
  94. package/template/.codex/skills/data-analytics/skills/user-context/agents/openai.yaml +6 -0
  95. package/template/.codex/skills/data-analytics/skills/user-context/plugin-author-config/automation-config.md +26 -0
  96. package/template/.codex/skills/data-analytics/skills/user-context/plugin-author-config/source-category-config.json +51 -0
  97. package/template/.codex/skills/data-analytics/skills/user-context/plugin-author-config/user-context-config.md +66 -0
  98. package/template/.codex/skills/data-analytics/skills/user-context/references/automation.md +69 -0
  99. package/template/.codex/skills/data-analytics/skills/user-context/references/onboarding-examples.md +204 -0
  100. package/template/.codex/skills/data-analytics/skills/user-context/references/onboarding-state-template.json +87 -0
  101. package/template/.codex/skills/data-analytics/skills/user-context/references/onboarding.md +497 -0
  102. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/connector-playbook.md +74 -0
  103. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/setup.md +65 -0
  104. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/skill-template.md +160 -0
  105. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/source-intake.md +75 -0
  106. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/weekly-polling-automation.md +96 -0
  107. package/template/.codex/skills/data-analytics/skills/user-context/references/source-category-runtime.md +263 -0
  108. package/template/.codex/skills/data-analytics/skills/user-context/scripts/data_analytics_preflight.py +1460 -0
  109. package/template/.codex/skills/data-analytics/skills/user-context/scripts/init_user_context_state.py +128 -0
  110. package/template/.codex/skills/data-analytics/skills/user-context/scripts/reset_user_context_state.py +101 -0
  111. package/template/.codex/skills/data-analytics/skills/user-context/scripts/validate_user_context_preflight.py +499 -0
  112. package/template/.codex/skills/data-analytics/skills/user-context/tests/test_state_helpers.py +978 -0
  113. package/template/.codex/skills/data-analytics/skills/validate-data/SKILL.md +190 -0
  114. package/template/.codex/skills/data-analytics/skills/validate-data/agents/openai.yaml +6 -0
  115. package/template/.codex/skills/data-analytics/skills/visualize-data/SKILL.md +157 -0
  116. package/template/.codex/skills/data-analytics/skills/visualize-data/agents/openai.yaml +6 -0
  117. package/template/.codex/skills/data-analytics/skills/visualize-data/references/seaborn-templates.md +774 -0
  118. package/template/.codex/skills/data-analytics/src/DESIGN.md +222 -0
  119. package/template/.codex/skills/data-analytics/src/analytics-app/App.tsx +3666 -0
  120. package/template/.codex/skills/data-analytics/src/analytics-app/analytics-layout.test.mjs +136 -0
  121. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartFrame.tsx +54 -0
  122. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartLegend.tsx +96 -0
  123. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartRenderer.tsx +1647 -0
  124. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartTooltip.tsx +245 -0
  125. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-app-helpers.tsx +462 -0
  126. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-capabilities.ts +107 -0
  127. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-compatibility.ts +164 -0
  128. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-contract.ts +192 -0
  129. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-theme.ts +203 -0
  130. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-tokens.css +619 -0
  131. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-transforms.ts +402 -0
  132. package/template/.codex/skills/data-analytics/src/analytics-app/fonts/SystemSansVariableVF.woff2 +0 -0
  133. package/template/.codex/skills/data-analytics/src/analytics-app/imageExport.ts +370 -0
  134. package/template/.codex/skills/data-analytics/src/analytics-app/layout/AnalyticsLayoutCanvas.tsx +536 -0
  135. package/template/.codex/skills/data-analytics/src/analytics-app/layout/RichMarkdown.tsx +681 -0
  136. package/template/.codex/skills/data-analytics/src/analytics-app/layout/analyticsLayoutCore.ts +164 -0
  137. package/template/.codex/skills/data-analytics/src/analytics-app/main.tsx +21 -0
  138. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/chart_contract.py +472 -0
  139. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/check_analytics_app_runtime_links.py +75 -0
  140. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/design_contract.py +142 -0
  141. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/package_utils.py +142 -0
  142. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/tests/test_package_utils.py +377 -0
  143. package/template/.codex/skills/data-analytics/src/analytics-app/styles.css +3468 -0
  144. package/template/.codex/skills/data-analytics/src/analytics-app/tables/DataTable.d.ts +44 -0
  145. package/template/.codex/skills/data-analytics/src/analytics-app/tables/DataTable.jsx +639 -0
  146. package/template/.codex/skills/data-analytics/src/analytics-app/tables/data-table.css +327 -0
  147. package/template/.codex/skills/data-analytics/src/analytics-app/tokens.css +225 -0
  148. package/template/.codex/skills/data-analytics/src/analytics-app/types.ts +204 -0
  149. package/template/.codex/skills/data-analytics/src/analytics-app-core.md +85 -0
  150. package/template/.codex/skills/data-analytics/src/codex-style-contract.md +30 -0
  151. package/template/.codex/skills/data-analytics/src/datascience-artifact-widget.html +12 -0
  152. package/template/.codex/skills/data-analytics/src/datascience-artifact-widget.jsx +1008 -0
  153. package/template/.codex/skills/data-analytics/src/datascience-chart-widget.css +2117 -0
  154. package/template/.codex/skills/data-analytics/src/datascience-chart-widget.html +122 -0
  155. package/template/.codex/skills/data-analytics/src/datascience-chart-widget.js +3588 -0
  156. package/template/.codex/skills/data-analytics/src/datascience-table-widget.css +483 -0
  157. package/template/.codex/skills/data-analytics/src/datascience-table-widget.html +32 -0
  158. package/template/.codex/skills/data-analytics/src/datascience-table-widget.js +333 -0
  159. package/template/.codex/skills/data-analytics/src/mcp-host.js +153 -0
  160. package/template/.codex/skills/data-analytics/src/recharts-config.js +315 -0
  161. package/template/.codex/skills/data-analytics/src/recharts-renderer.jsx +294 -0
  162. package/template/.codex/skills/data-analytics/src/sql-source-view.css +33 -0
  163. package/template/.codex/skills/data-analytics/src/sql-source-view.js +157 -0
  164. package/template/.codex/skills/data-analytics/src/styles/codex-theme.css +121 -0
  165. package/template/.codex/skills/data-analytics/src/table-renderer.jsx +28 -0
  166. package/template/.codex/skills/data-analytics/tests/chart-transforms.test.mjs +87 -0
  167. package/template/.codex/skills/data-analytics/tests/funnel-smoke.html +105 -0
  168. package/template/.codex/skills/data-analytics/tests/inline-widget-compare.html +233 -0
  169. package/template/.codex/skills/data-analytics/tests/mcp-server.test.mjs +1500 -0
  170. package/template/.codex/skills/data-analytics/tests/native-style-contract.test.mjs +219 -0
  171. package/template/.codex/skills/data-analytics/tests/recharts-config.test.mjs +144 -0
  172. package/template/.codex/skills/data-analytics/tests/recharts-renderer.test.mjs +11 -0
  173. package/template/.codex/skills/data-analytics/tests/rich-markdown.test.mjs +54 -0
  174. package/template/.codex/skills/data-analytics/tests/widget-render-harness.html +321 -0
  175. package/template/.codex/skills/data-analytics/tsconfig.json +19 -0
  176. package/template/.codex/skills/data-analytics/vite.config.ts +46 -0
  177. package/template/.codex/skills/high-fidelity-html-prototype/SKILL.md +102 -0
  178. package/template/.codex/skills/high-fidelity-html-prototype/agents/openai.yaml +5 -0
  179. package/template/.codex/skills/product-design/.app.json +7 -0
  180. package/template/.codex/skills/product-design/.codex-plugin/plugin.json +50 -0
  181. package/template/.codex/skills/product-design/README.md +50 -0
  182. package/template/.codex/skills/product-design/agents/openai.yaml +4 -0
  183. package/template/.codex/skills/product-design/assets/composerIcon.svg +9 -0
  184. package/template/.codex/skills/product-design/assets/logo.png +0 -0
  185. package/template/.codex/skills/product-design/package.json +11 -0
  186. package/template/.codex/skills/product-design/references/browser-order.md +9 -0
  187. package/template/.codex/skills/product-design/references/communication-protocol.md +45 -0
  188. package/template/.codex/skills/product-design/references/critical-overrides.md +58 -0
  189. package/template/.codex/skills/product-design/references/local-prototype-preflight.md +16 -0
  190. package/template/.codex/skills/product-design/scripts/bootstrap-prototype.mjs +147 -0
  191. package/template/.codex/skills/product-design/skills/audit/SKILL.md +159 -0
  192. package/template/.codex/skills/product-design/skills/audit/agents/openai.yaml +6 -0
  193. package/template/.codex/skills/product-design/skills/audit/references/design-audit-framework.md +73 -0
  194. package/template/.codex/skills/product-design/skills/design-qa/SKILL.md +133 -0
  195. package/template/.codex/skills/product-design/skills/design-qa/agents/openai.yaml +6 -0
  196. package/template/.codex/skills/product-design/skills/design-qa/references/qa-rubric.md +61 -0
  197. package/template/.codex/skills/product-design/skills/get-context/SKILL.md +81 -0
  198. package/template/.codex/skills/product-design/skills/get-context/agents/openai.yaml +6 -0
  199. package/template/.codex/skills/product-design/skills/ideate/SKILL.md +179 -0
  200. package/template/.codex/skills/product-design/skills/ideate/agents/openai.yaml +6 -0
  201. package/template/.codex/skills/product-design/skills/image-to-code/SKILL.md +105 -0
  202. package/template/.codex/skills/product-design/skills/image-to-code/agents/openai.yaml +6 -0
  203. package/template/.codex/skills/product-design/skills/index/SKILL.md +126 -0
  204. package/template/.codex/skills/product-design/skills/index/agents/openai.yaml +6 -0
  205. package/template/.codex/skills/product-design/skills/prototype/SKILL.md +128 -0
  206. package/template/.codex/skills/product-design/skills/prototype/agents/openai.yaml +6 -0
  207. package/template/.codex/skills/product-design/skills/prototype/references/existing-codebase-edits.md +18 -0
  208. package/template/.codex/skills/product-design/skills/research/SKILL.md +92 -0
  209. package/template/.codex/skills/product-design/skills/research/agents/openai.yaml +6 -0
  210. package/template/.codex/skills/product-design/skills/share/SKILL.md +41 -0
  211. package/template/.codex/skills/product-design/skills/share/agents/openai.yaml +6 -0
  212. package/template/.codex/skills/product-design/skills/url-to-code/SKILL.md +124 -0
  213. package/template/.codex/skills/product-design/skills/url-to-code/agents/openai.yaml +6 -0
  214. package/template/.codex/skills/product-design/skills/user-context/SKILL.md +147 -0
  215. package/template/.codex/skills/product-design/skills/user-context/agents/openai.yaml +6 -0
  216. package/template/.codex/skills/product-design/skills/user-context/plugin-author-config/user-context-template.md +63 -0
  217. package/template/.codex/skills/product-design/skills/user-context/references/onboarding.md +66 -0
  218. package/template/.codex/skills/product-design/skills/user-context/scripts/init_user_context.py +86 -0
  219. package/template/.codex/skills/product-design/skills/user-context/scripts/user_context_preflight.py +204 -0
  220. package/template/.codex/skills/product-design/templates/prototype/AGENTS.md +7 -0
  221. package/template/.codex/skills/product-design/templates/prototype/index.html +12 -0
  222. package/template/.codex/skills/product-design/templates/prototype/package.json +18 -0
  223. package/template/.codex/skills/product-design/templates/prototype/src/App.jsx +5 -0
  224. package/template/.codex/skills/product-design/templates/prototype/src/main.jsx +10 -0
  225. package/template/.codex/skills/product-design/templates/prototype/src/styles.css +13 -0
  226. package/template/.codex/skills/product-design/templates/prototype/vite.config.mjs +14 -0
  227. package/template/.codex/skills/product-design-prototype/SKILL.md +2 -4
  228. package/template/.codex/skills/prototype-review/SKILL.md +7 -7
  229. package/template/.codex/skills/yss-design-system/SKILL.md +4 -2
  230. package/template/.codex/skills/yss-product-lifecycle/SKILL.md +10 -7
  231. package/template/.codex/skills/yss-product-lifecycle/references/artifact-checklist.md +1 -0
  232. package/template/.codex/skills/yss-product-lifecycle/references/stage-routing.md +1 -1
  233. package/template/.codex/skills/yss-ui/SKILL.md +4 -10
  234. package/template/.codex/skills/yss-ui/references/quick-recipes.md +1 -1
  235. package/template/AGENTS.md +9 -3
  236. package/template/CONTEXT.md +1 -0
  237. package/template/docs/adr/adr-product-design-prototype-entrypoint.md +54 -0
  238. package/template/docs/api/templates/openapi-draft-review-checklist.md +5 -1
  239. package/template/docs/design/README.md +13 -6
  240. package/template/docs/design/prototypes/.gitkeep +1 -0
  241. package/template/docs/design/templates/interaction-spec-template.md +8 -3
  242. package/template/docs/design/templates/product-overview-design-template.md +34 -5
  243. package/template/docs/design/templates/prototype-confirmation-template.md +57 -0
  244. package/template/docs/design/templates/prototype-review-checklist.md +7 -3
  245. package/template/docs/design/templates/state-matrix-template.md +1 -1
  246. package/template/docs/process/harness-executive-blueprint.md +6 -1
  247. package/template/docs/process/harness-process-tailoring.md +3 -3
  248. package/template/docs/process/harness-work-unit-map.md +1 -1
  249. package/template/docs/process/lifecycle-artifact-map.md +7 -5
  250. package/template/docs/process/product-prototype-workflow.md +82 -0
  251. package/template/docs/templates/requirement-freeze-template.md +4 -3
  252. package/template/docs/user-guide/product-lifecycle-workflow.md +2 -2
  253. package/template/docs/user-guide/product-rd-lifecycle-best-practices.md +54 -24
  254. package/template/.codex/skills/component-story-prototype/SKILL.md +0 -55
  255. package/template/.codex/skills/component-story-prototype/agents/openai.yaml +0 -4
  256. package/template/.codex/skills/mock-api-prototype/SKILL.md +0 -48
  257. package/template/.codex/skills/mock-api-prototype/agents/openai.yaml +0 -4
@@ -0,0 +1,978 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.util
4
+ import json
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ import unittest
10
+ from pathlib import Path
11
+
12
+ SKILL_ROOT = Path(__file__).resolve().parents[1]
13
+ SCRIPT_DIR = SKILL_ROOT / "scripts"
14
+ PLUGIN_ROOT = SKILL_ROOT.parents[1]
15
+ ONBOARDING_REFERENCE = SKILL_ROOT / "references/onboarding.md"
16
+ SOURCE_RUNTIME_REFERENCE = SKILL_ROOT / "references/source-category-runtime.md"
17
+ AUTOMATION_REFERENCE = SKILL_ROOT / "references/automation.md"
18
+ SOURCE_CATEGORY_CONFIG = SKILL_ROOT / "plugin-author-config/source-category-config.json"
19
+ APP_MANIFEST = PLUGIN_ROOT / ".app.json"
20
+ SEMANTIC_LAYER_SETUP_REFERENCE = SKILL_ROOT / "references/semantic-layer/setup.md"
21
+ SEMANTIC_LAYER_SOURCE_INTAKE_REFERENCE = SKILL_ROOT / "references/semantic-layer/source-intake.md"
22
+ SEMANTIC_LAYER_SKILL_TEMPLATE_REFERENCE = SKILL_ROOT / "references/semantic-layer/skill-template.md"
23
+ SEMANTIC_LAYER_WEEKLY_POLLING_REFERENCE = (
24
+ SKILL_ROOT / "references/semantic-layer/weekly-polling-automation.md"
25
+ )
26
+ HERO_SKILLS = (
27
+ PLUGIN_ROOT / "skills/metric-diagnostics/SKILL.md",
28
+ PLUGIN_ROOT / "skills/product-business-analysis/SKILL.md",
29
+ PLUGIN_ROOT / "skills/kpi-reporting/SKILL.md",
30
+ )
31
+ PREFLIGHT_SPEC = importlib.util.spec_from_file_location(
32
+ "data_analytics_preflight",
33
+ SCRIPT_DIR / "data_analytics_preflight.py",
34
+ )
35
+ assert PREFLIGHT_SPEC and PREFLIGHT_SPEC.loader
36
+ PREFLIGHT_MODULE = importlib.util.module_from_spec(PREFLIGHT_SPEC)
37
+ PREFLIGHT_SPEC.loader.exec_module(PREFLIGHT_MODULE)
38
+
39
+ # Representative functions.list_available_plugins_to_install fixture for plugin-first routing.
40
+ LIVE_INSTALLABLE_PLUGIN_CANDIDATES = [
41
+ {
42
+ "id": "databricks@openai-curated",
43
+ "name": "databricks",
44
+ "tool_type": "plugin",
45
+ "has_skills": True,
46
+ "app_connector_ids": ["templated_apps_Databricks"],
47
+ },
48
+ {
49
+ "id": "bigquery@openai-curated",
50
+ "name": "bigquery",
51
+ "tool_type": "plugin",
52
+ "has_skills": True,
53
+ "app_connector_ids": ["connector_7c3f2c8cdbf64bb0b183ff52f527a06e"],
54
+ },
55
+ {
56
+ "id": "snowflake@openai-curated",
57
+ "name": "snowflake",
58
+ "tool_type": "plugin",
59
+ "has_skills": True,
60
+ "app_connector_ids": ["templated_apps_Snowflake"],
61
+ },
62
+ {
63
+ "id": "outlook-calendar@openai-curated",
64
+ "name": "outlook-calendar",
65
+ "tool_type": "plugin",
66
+ "has_skills": True,
67
+ "app_connector_ids": ["connector_e6a7394682e24467ac68c60696f275a4"],
68
+ },
69
+ {
70
+ "id": "outlook-email@openai-curated",
71
+ "name": "outlook-email",
72
+ "tool_type": "plugin",
73
+ "has_skills": True,
74
+ "app_connector_ids": ["connector_4aaab2856305417b993eca9a216aaf6e"],
75
+ },
76
+ {
77
+ "id": "sharepoint@openai-curated",
78
+ "name": "sharepoint",
79
+ "tool_type": "plugin",
80
+ "has_skills": True,
81
+ "app_connector_ids": ["connector_1e4f6a44acf14e3ca1d96672f8c945bc"],
82
+ },
83
+ {
84
+ "id": "slack@openai-curated",
85
+ "name": "slack",
86
+ "tool_type": "plugin",
87
+ "has_skills": True,
88
+ "app_connector_ids": ["REPLACE_WITH_SLACK_APP_OR_CONNECTOR_ID"],
89
+ },
90
+ {
91
+ "id": "teams@openai-curated",
92
+ "name": "teams",
93
+ "tool_type": "plugin",
94
+ "has_skills": True,
95
+ "app_connector_ids": ["connector_246af0940da3457da0e751171dc1ce60"],
96
+ },
97
+ {
98
+ "id": "thoughtspot@openai-curated",
99
+ "name": "thoughtspot",
100
+ "tool_type": "plugin",
101
+ "has_skills": True,
102
+ "app_connector_ids": ["REPLACE_WITH_THOUGHTSPOT_APP_OR_CONNECTOR_ID"],
103
+ },
104
+ {
105
+ "id": "omni-analytics@openai-curated",
106
+ "name": "omni-analytics",
107
+ "tool_type": "plugin",
108
+ "has_skills": True,
109
+ "app_connector_ids": ["REPLACE_WITH_OMNI_ANALYTICS_APP_OR_CONNECTOR_ID"],
110
+ },
111
+ {
112
+ "id": "metabase@openai-curated",
113
+ "name": "metabase",
114
+ "tool_type": "plugin",
115
+ "has_skills": True,
116
+ "app_connector_ids": ["templated_apps_Metabase"],
117
+ },
118
+ {
119
+ "id": "amplitude@openai-curated",
120
+ "name": "amplitude",
121
+ "tool_type": "plugin",
122
+ "has_skills": True,
123
+ "app_connector_ids": ["REPLACE_WITH_AMPLITUDE_APP_OR_CONNECTOR_ID"],
124
+ },
125
+ {
126
+ "id": "mixpanel@openai-curated",
127
+ "name": "mixpanel",
128
+ "tool_type": "plugin",
129
+ "has_skills": True,
130
+ "app_connector_ids": ["REPLACE_WITH_MIXPANEL_APP_OR_CONNECTOR_ID"],
131
+ },
132
+ {
133
+ "id": "hex@openai-curated",
134
+ "name": "hex",
135
+ "tool_type": "plugin",
136
+ "has_skills": True,
137
+ "app_connector_ids": ["REPLACE_WITH_HEX_APP_OR_CONNECTOR_ID"],
138
+ },
139
+ {
140
+ "id": "deepnote@openai-curated",
141
+ "name": "deepnote",
142
+ "tool_type": "plugin",
143
+ "has_skills": True,
144
+ "app_connector_ids": ["REPLACE_WITH_DEEPNOTE_APP_OR_CONNECTOR_ID"],
145
+ },
146
+ ]
147
+ SESSION_ENABLED_PLUGIN_CANDIDATES = [
148
+ {"id": "github@openai-curated", "name": "github", "tool_type": "plugin"},
149
+ {"id": "gmail@openai-curated", "name": "gmail", "tool_type": "plugin"},
150
+ {"id": "google-calendar@openai-curated", "name": "google-calendar", "tool_type": "plugin"},
151
+ {"id": "google-drive@openai-curated", "name": "google-drive", "tool_type": "plugin"},
152
+ {"id": "notion@openai-curated", "name": "notion", "tool_type": "plugin"},
153
+ ]
154
+
155
+
156
+ class DataAnalyticsStateHelperTests(unittest.TestCase):
157
+ def setUp(self) -> None:
158
+ self._tmp = tempfile.TemporaryDirectory(
159
+ prefix="data-analytics-user-context-tests-",
160
+ dir="/private/tmp",
161
+ )
162
+ self.tmp_path = Path(self._tmp.name)
163
+ self.state_dir = self.tmp_path / "state"
164
+ self.maxDiff = None
165
+
166
+ def tearDown(self) -> None:
167
+ self._tmp.cleanup()
168
+
169
+ def run_json(self, args: list[str], *, expect: int = 0) -> dict:
170
+ proc = subprocess.run(args, text=True, capture_output=True)
171
+ self.assertEqual(
172
+ proc.returncode,
173
+ expect,
174
+ msg=f"command failed: {' '.join(args)}\nSTDOUT:\n{proc.stdout}\nSTDERR:\n{proc.stderr}",
175
+ )
176
+ return json.loads(proc.stdout)
177
+
178
+ def preflight(self, *extra: str, state_dir: Path | None = None) -> dict:
179
+ return self.run_json(
180
+ [
181
+ sys.executable,
182
+ str(SCRIPT_DIR / "data_analytics_preflight.py"),
183
+ "--workflow",
184
+ "metric-diagnostics",
185
+ "--state-dir",
186
+ str(state_dir or self.state_dir),
187
+ *extra,
188
+ ]
189
+ )
190
+
191
+ def write_state(
192
+ self,
193
+ *,
194
+ user_context: str | None = None,
195
+ onboarding_state: dict | None = None,
196
+ state_dir: Path | None = None,
197
+ ) -> Path:
198
+ target = state_dir or self.state_dir
199
+ target.mkdir(parents=True, exist_ok=True)
200
+ if user_context is not None:
201
+ (target / "user-context.md").write_text(user_context, encoding="utf-8")
202
+ if onboarding_state is not None:
203
+ (target / "onboarding-state.json").write_text(
204
+ json.dumps(onboarding_state),
205
+ encoding="utf-8",
206
+ )
207
+ return target
208
+
209
+ def active_core_categories(self) -> dict[str, dict[str, str]]:
210
+ return {
211
+ "structured_data": {"status": "active", "preferred": "Databricks"},
212
+ "team_communication": {"status": "active", "preferred": "Slack"},
213
+ "company_docs": {"status": "active", "preferred": "Google Drive"},
214
+ }
215
+
216
+ def source_category_config(self) -> dict:
217
+ return json.loads(SOURCE_CATEGORY_CONFIG.read_text(encoding="utf-8"))["categories"]
218
+
219
+ def test_missing_preflight_returns_setup_obligation_context_gap_and_empty_hero_prompt(
220
+ self,
221
+ ) -> None:
222
+ payload = self.preflight(state_dir=self.tmp_path / "missing")
223
+ self.assertTrue(payload["read_only"])
224
+ self.assertEqual(payload["state"]["user_context_status"], "missing")
225
+ self.assertEqual(payload["state"]["onboarding_state_status"], "missing")
226
+ self.assertTrue(payload["current_skill_experience"]["intro_eligible"])
227
+ obligation = payload["control"]["final_obligations"][0]
228
+ self.assertEqual(obligation["id"], "offer_data_analytics_onboarding_next_step")
229
+ self.assertIn("## Data Analytics Setup Required", obligation["template"])
230
+ self.assertEqual(payload["control"]["conditional_guidance"][0]["id"], "context_gap_note")
231
+ self.assertEqual(payload["context"]["hero_prompt_candidates"], [])
232
+ self.assertIsNone(payload["context"]["primary_hero_prompt"])
233
+ self.assertEqual(
234
+ payload["context"]["connector_setup_summary"]["needs_attention"][0]["status"],
235
+ "needs_confirmation",
236
+ )
237
+
238
+ def test_direct_and_guided_modes_suppress_ordinary_setup_obligation(self) -> None:
239
+ direct_payload = self.preflight("--request-mode", "direct_onboarding_status")
240
+ guided_payload = self.preflight("--request-mode", "guided_onboarding_workflow")
241
+ self.assertEqual(direct_payload["control"]["final_obligations"], [])
242
+ self.assertEqual(guided_payload["control"]["final_obligations"], [])
243
+ self.assertEqual(direct_payload["control"]["conditional_guidance"], [])
244
+ self.assertEqual(guided_payload["control"]["conditional_guidance"], [])
245
+ self.assertEqual(guided_payload["current_skill_experience"]["cta_owner"], "onboarding")
246
+
247
+ def test_legacy_verified_connector_status_maps_to_active_try_on_use(self) -> None:
248
+ self.assertEqual(
249
+ PREFLIGHT_MODULE.source_status({"status": "connected"}),
250
+ "active",
251
+ )
252
+ self.assertEqual(PREFLIGHT_MODULE.source_status({"status": "manual"}), "active")
253
+ self.assertEqual(
254
+ PREFLIGHT_MODULE.source_status({"status": "selected_needs_auth"}),
255
+ "needs_confirmation",
256
+ )
257
+ self.assertEqual(PREFLIGHT_MODULE.source_status({"status": "choose_source"}), "needs_confirmation")
258
+
259
+ def test_configured_plugin_routes_are_manifest_backed_and_add_google_calendar(
260
+ self,
261
+ ) -> None:
262
+ categories = self.source_category_config()
263
+ self.assertEqual(categories["team_communication"]["preferred_plugins"], ["Slack", "Teams"])
264
+ self.assertEqual(
265
+ categories["company_docs"]["preferred_plugins"],
266
+ ["Google Drive", "SharePoint", "Notion"],
267
+ )
268
+ self.assertEqual(
269
+ categories["email_context"]["preferred_plugins"],
270
+ ["Gmail", "Outlook Email"],
271
+ )
272
+ self.assertEqual(
273
+ categories["calendar_context"]["preferred_plugins"],
274
+ ["Google Calendar", "Outlook Calendar"],
275
+ )
276
+ self.assertNotIn("preferred_apps", categories["calendar_context"])
277
+ self.assertEqual(categories["code_repository"]["preferred_plugins"], ["GitHub"])
278
+ app_manifest = json.loads(APP_MANIFEST.read_text(encoding="utf-8"))
279
+ self.assertEqual(
280
+ app_manifest["apps"]["google_calendar"]["id"],
281
+ "connector_947e0d954944416db111db556030eea6",
282
+ )
283
+ self.assertEqual(
284
+ PREFLIGHT_MODULE.load_app_connector_ids()["googlecalendar"],
285
+ "connector_947e0d954944416db111db556030eea6",
286
+ )
287
+
288
+ def test_live_install_listing_fixture_matches_every_installable_data_analytics_plugin(
289
+ self,
290
+ ) -> None:
291
+ matches = PREFLIGHT_MODULE.match_installable_plugin_candidates(
292
+ self.source_category_config(),
293
+ {"tools": LIVE_INSTALLABLE_PLUGIN_CANDIDATES},
294
+ )
295
+ self.assertEqual(
296
+ {
297
+ category_id: [entry["preferred_plugin"] for entry in entries]
298
+ for category_id, entries in matches.items()
299
+ },
300
+ {
301
+ "structured_data": ["Databricks", "BigQuery", "Snowflake"],
302
+ "team_communication": ["Slack", "Teams"],
303
+ "company_docs": ["SharePoint"],
304
+ "dashboards_bi": ["ThoughtSpot", "Omni Analytics", "Metabase"],
305
+ "behavior_signals": ["Amplitude", "Mixpanel"],
306
+ "notebook_lab": ["Hex", "Deepnote"],
307
+ "email_context": ["Outlook Email"],
308
+ "calendar_context": ["Outlook Calendar"],
309
+ },
310
+ )
311
+
312
+ def test_configured_plugins_match_installable_or_enabled_plugin_surfaces(self) -> None:
313
+ categories = self.source_category_config()
314
+ matches = PREFLIGHT_MODULE.match_installable_plugin_candidates(
315
+ categories,
316
+ [*LIVE_INSTALLABLE_PLUGIN_CANDIDATES, *SESSION_ENABLED_PLUGIN_CANDIDATES],
317
+ )
318
+ self.assertEqual(
319
+ {
320
+ category_id: [entry["preferred_plugin"] for entry in entries]
321
+ for category_id, entries in matches.items()
322
+ },
323
+ {
324
+ category_id: metadata["preferred_plugins"]
325
+ for category_id, metadata in categories.items()
326
+ if metadata.get("preferred_plugins")
327
+ },
328
+ )
329
+
330
+ def test_plugin_candidate_matching_uses_connector_id_when_slug_changes(self) -> None:
331
+ matches = PREFLIGHT_MODULE.match_installable_plugin_candidates(
332
+ self.source_category_config(),
333
+ [
334
+ {
335
+ "id": "renamed-plugin@openai-curated",
336
+ "name": "renamed-plugin",
337
+ "tool_type": "plugin",
338
+ "app_connector_ids": ["REPLACE_WITH_SLACK_APP_OR_CONNECTOR_ID"],
339
+ }
340
+ ],
341
+ )
342
+ slack_match = matches["team_communication"][0]
343
+ self.assertEqual(slack_match["preferred_plugin"], "Slack")
344
+ self.assertEqual(slack_match["match_reasons"], ["app_connector_id"])
345
+
346
+ def test_plugin_candidate_matching_ignores_description_only_category_similarity(self) -> None:
347
+ matches = PREFLIGHT_MODULE.match_installable_plugin_candidates(
348
+ self.source_category_config(),
349
+ [
350
+ {
351
+ "id": "broad-search@openai-curated",
352
+ "name": "broad-search",
353
+ "display_name": "Broad Search",
354
+ "description": "Indexes Slack and Teams for team communication.",
355
+ "tool_type": "plugin",
356
+ "has_skills": True,
357
+ }
358
+ ],
359
+ )
360
+ self.assertNotIn("team_communication", matches)
361
+
362
+ def test_preflight_exposes_plugin_setup_before_connector_fallback(self) -> None:
363
+ payload = self.preflight(state_dir=self.tmp_path / "missing")
364
+ confirmation = payload["context"]["connector_confirmation"]
365
+ slack = confirmation["team_communication"]
366
+ self.assertEqual(slack["setup_action"], "run_plugin_first_source_setup")
367
+ self.assertEqual(
368
+ slack["fallback_setup_action"],
369
+ "ask_user_to_choose_or_confirm_source",
370
+ )
371
+ self.assertEqual(slack["plugin_preference_order"], ["Slack", "Teams"])
372
+ self.assertEqual(
373
+ slack["setup_recovery"]["candidate_lookup"],
374
+ "functions.list_available_plugins_to_install",
375
+ )
376
+ self.assertEqual(
377
+ slack["setup_recovery"]["install_request"],
378
+ "functions.request_plugin_install",
379
+ )
380
+ calendar = confirmation["calendar_context"]
381
+ self.assertEqual(
382
+ calendar["plugin_preference_order"],
383
+ ["Google Calendar", "Outlook Calendar"],
384
+ )
385
+ self.assertIn(
386
+ "team_communication",
387
+ {
388
+ entry["id"]
389
+ for entry in payload["context"]["connector_setup_summary"][
390
+ "plugin_setup_opportunities"
391
+ ]
392
+ },
393
+ )
394
+
395
+ def test_preflight_keeps_onboarding_state_provenance_without_raw_state_echo(self) -> None:
396
+ self.write_state(onboarding_state={"status": "active", "connector_confirmation": {}})
397
+ payload = self.preflight()
398
+ onboarding_state = payload["files"]["onboarding_state"]
399
+ self.assertEqual(onboarding_state["status"], "present")
400
+ self.assertFalse(onboarding_state["omitted"])
401
+ self.assertNotIn("content", onboarding_state)
402
+ self.assertNotIn("data", onboarding_state)
403
+ self.assertNotIn(
404
+ "setup_recovery",
405
+ payload["context"]["connector_setup_summary"]["needs_attention"][0],
406
+ )
407
+ self.assertIn(
408
+ "setup_recovery",
409
+ payload["context"]["connector_confirmation"]["structured_data"],
410
+ )
411
+
412
+ def test_connector_inventory_classifies_routes_without_eager_reads(self) -> None:
413
+ self.write_state(
414
+ onboarding_state={
415
+ "status": "active",
416
+ "connector_inventory": {
417
+ "active_apps": ["Slack"],
418
+ "available_apps": ["Databricks"],
419
+ },
420
+ }
421
+ )
422
+ payload = self.preflight("--request-mode", "direct_onboarding_status")
423
+ confirmation = payload["context"]["connector_confirmation"]
424
+
425
+ self.assertEqual(confirmation["structured_data"]["status"], "needs_confirmation")
426
+ self.assertEqual(confirmation["structured_data"]["candidates"], ["Databricks"])
427
+ self.assertEqual(confirmation["team_communication"]["status"], "needs_confirmation")
428
+ self.assertEqual(confirmation["team_communication"]["candidates"], ["Slack"])
429
+ self.assertEqual(confirmation["company_docs"]["status"], "missing")
430
+ self.assertFalse(confirmation["structured_data"]["eager_read"])
431
+ self.assertEqual(
432
+ confirmation["structured_data"]["workflow_time_behavior"],
433
+ "attempt_actual_reads_only_when_a_workflow_needs_the_source",
434
+ )
435
+
436
+ def test_helper_skills_apply_to_app_fallback_but_not_active_plugin_routes(self) -> None:
437
+ source_config = {
438
+ "team_communication": {
439
+ "label": "Team communication",
440
+ "preferred_plugins": ["slack"],
441
+ "preferred_apps": ["Slack"],
442
+ "relevant_skills": [{"app": "Slack", "skill": "slack-helper"}],
443
+ }
444
+ }
445
+ connector_fallback = PREFLIGHT_MODULE.normalized_connector_confirmation(
446
+ source_config,
447
+ {
448
+ "connector_confirmation": {
449
+ "categories": {
450
+ "team_communication": {
451
+ "status": "active",
452
+ "preferred": "Slack",
453
+ "source_kind": "connector",
454
+ }
455
+ }
456
+ }
457
+ },
458
+ )["team_communication"]
459
+ self.assertEqual(connector_fallback["setup_action"], "run_plugin_first_source_setup")
460
+ self.assertEqual(connector_fallback["fallback_setup_action"], "none_try_on_use")
461
+ self.assertEqual(connector_fallback["helper_skills_apply_when"], ["app", "connector"])
462
+
463
+ plugin_route = PREFLIGHT_MODULE.normalized_connector_confirmation(
464
+ source_config,
465
+ {
466
+ "connector_confirmation": {
467
+ "categories": {
468
+ "team_communication": {
469
+ "status": "active",
470
+ "preferred": "slack",
471
+ "source_kind": "plugin",
472
+ "plugin_install_evidence": "plugin_in_available_plugins",
473
+ "skill_surface_evidence": "plugin_skills_visible",
474
+ }
475
+ }
476
+ }
477
+ },
478
+ )["team_communication"]
479
+ self.assertEqual(plugin_route["setup_action"], "none_try_on_use")
480
+ self.assertNotIn("fallback_setup_action", plugin_route)
481
+ self.assertNotIn("setup_recovery", plugin_route)
482
+ self.assertNotIn("helper_skills", plugin_route)
483
+
484
+ def test_validator_rejects_preferred_plugin_without_manifest_dependency(self) -> None:
485
+ plugin_copy = self.tmp_path / "data-analytics"
486
+ shutil.copytree(PLUGIN_ROOT, plugin_copy)
487
+ config_path = plugin_copy / "skills/user-context/plugin-author-config/source-category-config.json"
488
+ config = json.loads(config_path.read_text(encoding="utf-8"))
489
+ config["categories"]["calendar_context"]["preferred_plugins"].append("missing-calendar")
490
+ config_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
491
+ proc = subprocess.run(
492
+ [
493
+ sys.executable,
494
+ str(SCRIPT_DIR / "validate_user_context_preflight.py"),
495
+ str(plugin_copy),
496
+ "--plugin-id",
497
+ "data-analytics",
498
+ ],
499
+ text=True,
500
+ capture_output=True,
501
+ )
502
+ self.assertNotEqual(proc.returncode, 0)
503
+ self.assertIn(
504
+ "preferred plugin missing-calendar must be declared in .app.json",
505
+ proc.stderr,
506
+ )
507
+
508
+ def test_validator_rejects_preferred_app_without_manifest_dependency(self) -> None:
509
+ plugin_copy = self.tmp_path / "data-analytics"
510
+ shutil.copytree(PLUGIN_ROOT, plugin_copy)
511
+ config_path = plugin_copy / "skills/user-context/plugin-author-config/source-category-config.json"
512
+ config = json.loads(config_path.read_text(encoding="utf-8"))
513
+ config["categories"]["calendar_context"]["preferred_apps"] = ["Missing Calendar"]
514
+ config_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
515
+ proc = subprocess.run(
516
+ [
517
+ sys.executable,
518
+ str(SCRIPT_DIR / "validate_user_context_preflight.py"),
519
+ str(plugin_copy),
520
+ "--plugin-id",
521
+ "data-analytics",
522
+ ],
523
+ text=True,
524
+ capture_output=True,
525
+ )
526
+ self.assertNotEqual(proc.returncode, 0)
527
+ self.assertIn(
528
+ "preferred app Missing Calendar must be declared in .app.json",
529
+ proc.stderr,
530
+ )
531
+
532
+ def test_validator_rejects_saved_context_heading_in_author_config(self) -> None:
533
+ plugin_copy = self.tmp_path / "data-analytics"
534
+ shutil.copytree(PLUGIN_ROOT, plugin_copy)
535
+ config_path = plugin_copy / "skills/user-context/plugin-author-config/user-context-config.md"
536
+ config = config_path.read_text(encoding="utf-8")
537
+ config_path.write_text(
538
+ config.replace(
539
+ "# Semantic Layers\n\n",
540
+ "# Semantic Layers\n\n## Saved Links And Context\n\n",
541
+ 1,
542
+ ),
543
+ encoding="utf-8",
544
+ )
545
+ proc = subprocess.run(
546
+ [
547
+ sys.executable,
548
+ str(SCRIPT_DIR / "validate_user_context_preflight.py"),
549
+ str(plugin_copy),
550
+ "--plugin-id",
551
+ "data-analytics",
552
+ ],
553
+ text=True,
554
+ capture_output=True,
555
+ )
556
+ self.assertNotEqual(proc.returncode, 0)
557
+ self.assertIn("default user context must not contain: ## Saved Links And Context", proc.stderr)
558
+
559
+ def test_selected_source_needs_confirmation_stays_unresolved_until_user_resolves_it(
560
+ self,
561
+ ) -> None:
562
+ self.write_state(
563
+ user_context="""# Data Analytics Source Routing Preferences
564
+
565
+ Store durable Data Analytics source-routing choices explicitly selected for future use.
566
+
567
+ ## structured_data
568
+
569
+ - Prefer:
570
+ - Avoid:
571
+
572
+ # Semantic Layers
573
+
574
+ status: not provided
575
+ """,
576
+ onboarding_state={
577
+ "status": "active",
578
+ "orientation": {"status": "shown"},
579
+ "connector_confirmation": {
580
+ "structured_data": {
581
+ "status": "needs_confirmation",
582
+ "preferred": "Databricks",
583
+ },
584
+ "team_communication": {"status": "active", "preferred": "Slack"},
585
+ "company_docs": {"status": "active", "preferred": "Google Drive"},
586
+ },
587
+ },
588
+ )
589
+ payload = self.preflight()
590
+ summary = payload["context"]["connector_setup_summary"]
591
+ self.assertEqual(payload["context"]["user_context"]["semantic_layer_count"], 0)
592
+ self.assertFalse(summary["core_complete"])
593
+ self.assertEqual(summary["next_action"]["category_id"], "structured_data")
594
+ self.assertEqual(
595
+ summary["next_action"]["setup_action"],
596
+ "run_plugin_first_source_setup",
597
+ )
598
+ self.assertEqual(summary["needs_attention"][0]["status"], "needs_confirmation")
599
+ self.assertNotIn(
600
+ "structured_data",
601
+ {entry["id"] for entry in summary["ready"]},
602
+ )
603
+ self.assertEqual(
604
+ payload["control"]["final_obligations"][0]["id"],
605
+ "complete_data_analytics_core_onboarding",
606
+ )
607
+
608
+ def test_unacknowledged_deferred_core_sources_reopen_as_needs_confirmation(self) -> None:
609
+ self.write_state(
610
+ onboarding_state={
611
+ "status": "active",
612
+ "orientation": {"status": "shown"},
613
+ "connector_confirmation": {
614
+ "status": "completed",
615
+ "structured_data": {"status": "deferred"},
616
+ "team_communication": {"status": "deferred"},
617
+ "company_docs": {"status": "active", "preferred": "Google Drive"},
618
+ },
619
+ }
620
+ )
621
+ payload = self.preflight("--request-mode", "direct_onboarding_status")
622
+ confirmation = payload["context"]["connector_confirmation"]
623
+ summary = payload["context"]["connector_setup_summary"]
624
+ self.assertEqual(confirmation["structured_data"]["status"], "needs_confirmation")
625
+ self.assertEqual(confirmation["structured_data"]["recorded_status"], "deferred")
626
+ self.assertEqual(
627
+ confirmation["structured_data"]["setup_note"],
628
+ "core_source_requires_explicit_user_resolution_before_fallback",
629
+ )
630
+ self.assertEqual(confirmation["team_communication"]["status"], "needs_confirmation")
631
+ self.assertFalse(summary["core_complete"])
632
+ self.assertEqual(
633
+ summary["unresolved_core_ids"],
634
+ ["structured_data", "team_communication"],
635
+ )
636
+ self.assertEqual(
637
+ [entry["id"] for entry in summary["needs_attention"][:2]],
638
+ ["structured_data", "team_communication"],
639
+ )
640
+ self.assertEqual(summary["next_action"]["category_id"], "structured_data")
641
+ self.assertEqual(
642
+ payload["control"]["onboarding_progress"]["task_list"][1]["status"],
643
+ "in_progress",
644
+ )
645
+
646
+ def test_connector_confirmation_status_without_core_categories_stays_pending(self) -> None:
647
+ self.write_state(
648
+ onboarding_state={
649
+ "status": "active",
650
+ "orientation": {"status": "shown"},
651
+ "connector_confirmation": {"status": "completed"},
652
+ }
653
+ )
654
+ payload = self.preflight("--request-mode", "direct_onboarding_status")
655
+ summary = payload["context"]["connector_setup_summary"]
656
+ self.assertEqual(summary["classification_status"], "pending")
657
+ self.assertFalse(summary["core_complete"])
658
+ self.assertEqual(
659
+ payload["control"]["onboarding_progress"]["task_list"][1]["status"],
660
+ "in_progress",
661
+ )
662
+
663
+ def test_explicit_core_source_defer_counts_as_recorded_known_gap(self) -> None:
664
+ self.write_state(
665
+ onboarding_state={
666
+ "status": "active",
667
+ "connector_confirmation": {
668
+ "structured_data": {"status": "deferred", "resolution": "user_deferred"},
669
+ "team_communication": {
670
+ "status": "deferred",
671
+ "resolution": "user_continued_with_known_gap",
672
+ },
673
+ "company_docs": {"status": "active", "preferred": "Google Drive"},
674
+ },
675
+ }
676
+ )
677
+ payload = self.preflight("--request-mode", "direct_onboarding_status")
678
+ confirmation = payload["context"]["connector_confirmation"]
679
+ summary = payload["context"]["connector_setup_summary"]
680
+ self.assertEqual(confirmation["structured_data"]["status"], "deferred")
681
+ self.assertEqual(confirmation["structured_data"]["resolution"], "user_deferred")
682
+ self.assertTrue(summary["core_complete"])
683
+ self.assertEqual(summary["unresolved_core_ids"], [])
684
+ self.assertEqual(
685
+ {entry["id"] for entry in summary["fallback_or_closed"]},
686
+ {"structured_data", "team_communication"},
687
+ )
688
+
689
+ def test_core_onboarding_complete_requires_active_or_explicit_fallback_sources(self) -> None:
690
+ self.write_state(
691
+ onboarding_state={
692
+ "status": "active",
693
+ "orientation": {"status": "shown"},
694
+ "connector_confirmation": {
695
+ **self.active_core_categories(),
696
+ },
697
+ "semantic_layer_setup": {"status": "created"},
698
+ "semantic_layer_refresh": {"status": "declined"},
699
+ }
700
+ )
701
+ payload = self.preflight("--request-mode", "direct_onboarding_status")
702
+ progress = payload["control"]["onboarding_progress"]
703
+ task_statuses = {item["id"]: item["status"] for item in progress["task_list"]}
704
+ self.assertTrue(progress["core_onboarding"]["complete"])
705
+ self.assertEqual(task_statuses["source_setup_confirmation"], "completed")
706
+ self.assertEqual(task_statuses["semantic_layer_setup"], "completed")
707
+ self.assertEqual(task_statuses["hero_prompt"], "in_progress")
708
+
709
+ def test_core_onboarding_complete_allows_semantic_layer_skip_without_refresh(self) -> None:
710
+ self.write_state(
711
+ onboarding_state={
712
+ "status": "active",
713
+ "orientation": {"status": "shown"},
714
+ "connector_confirmation": {
715
+ "status": "completed",
716
+ **self.active_core_categories(),
717
+ },
718
+ "semantic_layer_setup": {"status": "skipped"},
719
+ }
720
+ )
721
+ payload = self.preflight("--request-mode", "direct_onboarding_status")
722
+ progress = payload["control"]["onboarding_progress"]
723
+ task_statuses = {item["id"]: item["status"] for item in progress["task_list"]}
724
+ self.assertTrue(progress["core_onboarding"]["complete"])
725
+ self.assertEqual(task_statuses["semantic_layer_setup"], "completed")
726
+ self.assertEqual(task_statuses["hero_prompt"], "in_progress")
727
+
728
+ def test_active_core_incomplete_returns_single_reminder_with_next_action(self) -> None:
729
+ self.write_state(
730
+ user_context="""# Data Analytics Source Routing Preferences
731
+
732
+ Store durable Data Analytics source-routing choices explicitly selected for future use.
733
+
734
+ ## structured_data
735
+
736
+ - Prefer:
737
+ - Avoid:
738
+
739
+ # Semantic Layers
740
+
741
+ status: not provided
742
+ """,
743
+ onboarding_state={"status": "active", "orientation": {"status": "shown"}},
744
+ )
745
+ payload = self.preflight()
746
+ obligation = payload["control"]["final_obligations"][0]
747
+ self.assertEqual(obligation["id"], "complete_data_analytics_core_onboarding")
748
+ self.assertEqual(obligation["next_action"]["id"], "confirm_analytics_sources")
749
+ self.assertIn("**Next Step**", obligation["template"])
750
+ self.assertIn("Reply `continue onboarding`", obligation["template"])
751
+ self.assertEqual(len(payload["control"]["final_obligations"]), 1)
752
+
753
+ def test_complete_and_quiet_onboarding_suppress_auto_obligations(self) -> None:
754
+ for status in ("complete", "quiet"):
755
+ with self.subTest(status=status):
756
+ state_dir = self.tmp_path / status
757
+ self.write_state(onboarding_state={"status": status}, state_dir=state_dir)
758
+ payload = self.preflight(state_dir=state_dir)
759
+ self.assertEqual(payload["control"]["final_obligations"], [])
760
+
761
+ def test_semantic_layer_registry_and_hero_prompt_candidates_come_from_context(
762
+ self,
763
+ ) -> None:
764
+ self.write_state(
765
+ user_context="""# Data Analytics Source Routing Preferences
766
+
767
+ Store durable Data Analytics source-routing choices explicitly selected for future use.
768
+
769
+ ## structured_data
770
+
771
+ - Prefer: Databricks
772
+ - Avoid: Snowflake
773
+
774
+ # Semantic Layers
775
+
776
+ - Area: API
777
+ - Skill Name: api-semantic-layer
778
+ - Skill Path: /tmp/api-semantic-layer
779
+ - Source Inventory Path: /tmp/api-semantic-layer/references/source-inventory.md
780
+ - Last Updated: 2026-05-29
781
+ """,
782
+ onboarding_state={"status": "active", "semantic_layer_setup": {"area": "API"}},
783
+ )
784
+ payload = self.preflight()
785
+ self.assertNotIn("user_context_markdown", payload["context"])
786
+ self.assertNotIn("content", payload["files"]["user_context"])
787
+ self.assertTrue(payload["context"]["user_context"]["normalization_complete"])
788
+ self.assertEqual(payload["context"]["semantic_layers"][0]["area"], "API")
789
+ self.assertEqual(payload["context"]["source_preferences"]["structured_data"], {
790
+ "preferred": ["Databricks"],
791
+ "avoid": ["Snowflake"],
792
+ })
793
+ self.assertEqual(len(payload["context"]["hero_prompt_candidates"]), 1)
794
+ self.assertEqual(payload["context"]["primary_hero_prompt"]["skill"], "product-business-analysis")
795
+ self.assertEqual(
796
+ payload["context"]["primary_hero_prompt"]["label"],
797
+ "Build a decision-ready report",
798
+ )
799
+ self.assertEqual(
800
+ payload["context"]["primary_hero_prompt"]["prompt"],
801
+ (
802
+ "Build a decision-ready report for API: explain what is happening, "
803
+ "identify the main drivers, recommend where the team should focus next, "
804
+ "and include sources, caveats, and useful charts."
805
+ ),
806
+ )
807
+ self.assertEqual(payload["context"]["extra_hero_prompt_candidates"], [])
808
+
809
+ def test_large_user_context_renders_source_preferences_and_requested_section(self) -> None:
810
+ self.write_state(
811
+ user_context="""# Data Analytics Source Routing Preferences
812
+
813
+ Store durable Data Analytics source-routing choices explicitly selected for future use.
814
+
815
+ ## structured_data
816
+
817
+ - Prefer: Databricks
818
+ - Avoid: Snowflake
819
+
820
+ ## company_docs
821
+
822
+ - Prefer: Google Drive
823
+ - Avoid:
824
+
825
+ # Semantic Layers
826
+
827
+ """ + ("x" * 2048),
828
+ )
829
+ payload = self.preflight(
830
+ "--max-context-bytes",
831
+ "80",
832
+ "--section",
833
+ "Data Analytics Source Routing Preferences",
834
+ )
835
+ self.assertNotIn("user_context_markdown", payload["context"])
836
+ self.assertNotIn("content", payload["files"]["user_context"])
837
+ self.assertTrue(payload["files"]["user_context"]["omitted"])
838
+ self.assertEqual(payload["files"]["user_context"]["sections"][0]["status"], "present")
839
+ self.assertTrue(payload["context"]["user_context"]["normalization_complete"])
840
+ self.assertEqual(
841
+ payload["context"]["source_preferences"],
842
+ {
843
+ "structured_data": {
844
+ "preferred": ["Databricks"],
845
+ "avoid": ["Snowflake"],
846
+ },
847
+ "company_docs": {"preferred": ["Google Drive"]},
848
+ },
849
+ )
850
+ self.assertEqual(
851
+ payload["context"]["user_context"]["source_routing_preferences"],
852
+ payload["context"]["source_preferences"],
853
+ )
854
+
855
+ def test_reset_moves_active_state_files(self) -> None:
856
+ self.state_dir.mkdir(parents=True)
857
+ user_context_path = self.state_dir / "user-context.md"
858
+ onboarding_state_path = self.state_dir / "onboarding-state.json"
859
+ user_context_path.write_text("# Context\n", encoding="utf-8")
860
+ onboarding_state_path.write_text(json.dumps({"status": "active"}), encoding="utf-8")
861
+ backup_dir = self.tmp_path / "backup"
862
+ proc = subprocess.run(
863
+ [
864
+ sys.executable,
865
+ str(SCRIPT_DIR / "reset_user_context_state.py"),
866
+ "--state-dir",
867
+ str(self.state_dir),
868
+ "--backup-dir",
869
+ str(backup_dir),
870
+ ],
871
+ text=True,
872
+ capture_output=True,
873
+ )
874
+ self.assertEqual(proc.returncode, 0, msg=proc.stderr)
875
+ self.assertFalse(user_context_path.exists())
876
+ self.assertFalse(onboarding_state_path.exists())
877
+ self.assertTrue((backup_dir / "user-context.md").exists())
878
+ self.assertTrue((backup_dir / "onboarding-state.json").exists())
879
+
880
+ def test_initializer_creates_user_context_and_onboarding_state(self) -> None:
881
+ proc = subprocess.run(
882
+ [
883
+ sys.executable,
884
+ str(SCRIPT_DIR / "init_user_context_state.py"),
885
+ "--state-dir",
886
+ str(self.state_dir),
887
+ ],
888
+ text=True,
889
+ capture_output=True,
890
+ )
891
+ self.assertEqual(proc.returncode, 0, msg=proc.stderr)
892
+ user_context = (self.state_dir / "user-context.md").read_text(encoding="utf-8")
893
+ self.assertNotIn("# Data Analytics User Context Config", user_context)
894
+ self.assertNotIn("- Priority:", user_context)
895
+ self.assertNotIn("- Use When:", user_context)
896
+ self.assertIn(
897
+ "Store durable Data Analytics source-routing choices explicitly selected for future use.",
898
+ user_context,
899
+ )
900
+ self.assertEqual(user_context.count("## "), 9)
901
+ self.assertEqual(user_context.count("- Prefer:"), 9)
902
+ self.assertEqual(user_context.count("- Avoid:"), 9)
903
+ self.assertEqual(user_context.count("status: not provided"), 1)
904
+ onboarding_state = json.loads((self.state_dir / "onboarding-state.json").read_text())
905
+ self.assertEqual(onboarding_state["status"], "active")
906
+ self.assertNotIn("connector_inventory", onboarding_state)
907
+ self.assertNotIn("seed_sources", onboarding_state["semantic_layer_setup"])
908
+ self.assertEqual(onboarding_state["semantic_layer_setup"]["seed_source_count"], 0)
909
+ self.assertIn("hero_prompt_choice", onboarding_state)
910
+ self.assertIn("skill_experience", onboarding_state)
911
+
912
+ def test_onboarding_contract_mentions_active_sources_and_one_first_hero_prompt(
913
+ self,
914
+ ) -> None:
915
+ onboarding_text = ONBOARDING_REFERENCE.read_text(encoding="utf-8")
916
+ source_runtime_text = SOURCE_RUNTIME_REFERENCE.read_text(encoding="utf-8")
917
+ automation_text = AUTOMATION_REFERENCE.read_text(encoding="utf-8")
918
+ self.assertIn("Check Main Analytics Sources", onboarding_text)
919
+ self.assertIn("Resolve Source Questions", onboarding_text)
920
+ self.assertIn("transition without a proof read", onboarding_text)
921
+ self.assertIn("Set Up Data Context", onboarding_text)
922
+ self.assertIn("Codex gets better at data work", onboarding_text)
923
+ self.assertIn("Send anything you would point a new analyst to", onboarding_text)
924
+ self.assertIn("Reply with any starting points, or say `skip for now`", onboarding_text)
925
+ self.assertIn("Do not: require the user to name a product or business area up front", onboarding_text)
926
+ self.assertNotIn("smallest useful seed", onboarding_text)
927
+ self.assertNotIn("first semantic layer", onboarding_text)
928
+ self.assertNotIn("main source categories", onboarding_text)
929
+ self.assertIn("Show one strong prompt first", onboarding_text)
930
+ self.assertIn("Do not: show three hero prompts initially", onboarding_text)
931
+ self.assertIn("write the prompt you would rather try instead", onboarding_text)
932
+ self.assertIn("`skip` to finish onboarding", onboarding_text)
933
+ self.assertIn("Step 5, `complete_or_quiet`, when the user skips or defers", onboarding_text)
934
+ self.assertIn("`needs_confirmation`", source_runtime_text)
935
+ self.assertIn("Do not run a proof query during onboarding", source_runtime_text)
936
+ self.assertIn("Do not mark setup complete", automation_text)
937
+ for hero_skill in HERO_SKILLS:
938
+ text = hero_skill.read_text(encoding="utf-8")
939
+ self.assertIn("data-analytics:user-context", text)
940
+
941
+ def test_semantic_layer_setup_uses_lightweight_generated_skills_and_source_checkpoint(
942
+ self,
943
+ ) -> None:
944
+ setup_text = SEMANTIC_LAYER_SETUP_REFERENCE.read_text(encoding="utf-8")
945
+ source_intake_text = SEMANTIC_LAYER_SOURCE_INTAKE_REFERENCE.read_text(encoding="utf-8")
946
+ template_text = SEMANTIC_LAYER_SKILL_TEMPLATE_REFERENCE.read_text(encoding="utf-8")
947
+ weekly_polling_text = SEMANTIC_LAYER_WEEKLY_POLLING_REFERENCE.read_text(
948
+ encoding="utf-8"
949
+ )
950
+ self.assertIn("source-use checkpoint", setup_text)
951
+ self.assertIn("rejected or lower-confidence candidates", setup_text)
952
+ self.assertIn("Infer expected source lanes", setup_text)
953
+ self.assertIn("nearby but not exact", setup_text)
954
+ self.assertIn("authoritative data documentation", setup_text)
955
+ self.assertIn("source-backed semantic-layer skills", setup_text)
956
+ self.assertIn("user-trusted canonical data skills", setup_text)
957
+ self.assertIn("treat that as authoritative data documentation", setup_text)
958
+ self.assertIn("authoritative data documentation", weekly_polling_text)
959
+ self.assertIn("source-backed semantic-layer skills", weekly_polling_text)
960
+ self.assertIn("user-trusted canonical data skills", weekly_polling_text)
961
+ self.assertIn("Infer useful source families", source_intake_text)
962
+ self.assertIn("do not treat ambient app availability as user approval", source_intake_text)
963
+ self.assertIn("Send anything you would point a new analyst to", source_intake_text)
964
+ self.assertIn("Reply with any starting points, or say `skip for now`", source_intake_text)
965
+ self.assertIn("## Start Here", template_text)
966
+ self.assertIn("## References", template_text)
967
+ self.assertIn("Answering Rules", template_text)
968
+ self.assertIn("## Quick Reference", template_text)
969
+ self.assertIn("## Entity Clarification", template_text)
970
+ self.assertIn("## Standard Filters And Dimensions", template_text)
971
+ self.assertIn("evidence.md`: detailed provenance, only when the layer needs separate evidence tracking", template_text)
972
+ self.assertIn("Do not put setup-time policy or full evidence procedures", template_text)
973
+ self.assertNotIn("### Source Order", template_text)
974
+ self.assertNotIn("### Evidence Standard", template_text)
975
+
976
+
977
+ if __name__ == "__main__":
978
+ unittest.main()