hyperweave 0.2.6__tar.gz → 0.2.7__tar.gz

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 (354) hide show
  1. {hyperweave-0.2.6 → hyperweave-0.2.7}/CHANGELOG.md +24 -0
  2. {hyperweave-0.2.6 → hyperweave-0.2.7}/PKG-INFO +1 -1
  3. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/_version.py +2 -2
  4. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/context.py +2 -2
  5. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/resolver.py +53 -61
  6. hyperweave-0.2.7/src/hyperweave/compose/rhythm.py +109 -0
  7. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/config/settings.py +5 -1
  8. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/connectors/github.py +19 -8
  9. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/state.py +6 -0
  10. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/mcp/server.py +22 -0
  11. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/render/chart_engine.py +60 -96
  12. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/telemetry/contract.py +1 -1
  13. hyperweave-0.2.7/src/hyperweave/templates/components/chart-markers/circle.svg.j2 +2 -0
  14. hyperweave-0.2.7/src/hyperweave/templates/components/chart-markers/diamond.svg.j2 +7 -0
  15. hyperweave-0.2.7/src/hyperweave/templates/components/chart-markers/endpoint-diamond.svg.j2 +14 -0
  16. hyperweave-0.2.7/src/hyperweave/templates/components/chart-markers/endpoint-rect.svg.j2 +12 -0
  17. hyperweave-0.2.7/src/hyperweave/templates/components/chart-markers/rect.svg.j2 +12 -0
  18. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/treemap.svg.j2 +2 -2
  19. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/chart/brutalist-content.j2 +6 -1
  20. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/chart/chrome-content.j2 +6 -1
  21. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/receipt.svg.j2 +7 -1
  22. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_chart_engine.py +59 -30
  23. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_chart_frame.py +5 -5
  24. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_connectors.py +122 -0
  25. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_mcp.py +23 -0
  26. hyperweave-0.2.7/tests/test_resolver_rhythm.py +158 -0
  27. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_state_inference.py +22 -0
  28. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_telemetry.py +3 -3
  29. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_telemetry_integration.py +166 -2
  30. {hyperweave-0.2.6 → hyperweave-0.2.7}/.dockerignore +0 -0
  31. {hyperweave-0.2.6 → hyperweave-0.2.7}/.github/workflows/ci.yml +0 -0
  32. {hyperweave-0.2.6 → hyperweave-0.2.7}/.github/workflows/deploy.yml +0 -0
  33. {hyperweave-0.2.6 → hyperweave-0.2.7}/.github/workflows/publish.yml +0 -0
  34. {hyperweave-0.2.6 → hyperweave-0.2.7}/.gitignore +0 -0
  35. {hyperweave-0.2.6 → hyperweave-0.2.7}/.pre-commit-config.yaml +0 -0
  36. {hyperweave-0.2.6 → hyperweave-0.2.7}/Dockerfile +0 -0
  37. {hyperweave-0.2.6 → hyperweave-0.2.7}/LICENSE +0 -0
  38. {hyperweave-0.2.6 → hyperweave-0.2.7}/README.md +0 -0
  39. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/buttons/button-liquid.svg +0 -0
  40. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/cards/card-butterfly.svg +0 -0
  41. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/cards/card-python.svg +0 -0
  42. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/cards/card-sunflower.svg +0 -0
  43. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/cards/card-waves.svg +0 -0
  44. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/badge_critical.svg +0 -0
  45. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/badge_passing.svg +0 -0
  46. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/badge_warning.svg +0 -0
  47. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/banner.svg +0 -0
  48. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/icons/discord.svg +0 -0
  49. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/icons/notion.svg +0 -0
  50. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/icons/reddit.svg +0 -0
  51. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/icons/spotify.svg +0 -0
  52. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/icons/youtube.svg +0 -0
  53. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/marquee_counter.svg +0 -0
  54. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/marquee_horizontal.svg +0 -0
  55. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/marquee_vertical.svg +0 -0
  56. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/profile-cards/chart_stars_full.svg +0 -0
  57. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/profile-cards/stats.svg +0 -0
  58. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/brutalist-emerald/strip.svg +0 -0
  59. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/badge_critical.svg +0 -0
  60. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/badge_passing.svg +0 -0
  61. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/badge_warning.svg +0 -0
  62. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/banner.svg +0 -0
  63. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/icons/bluesky.svg +0 -0
  64. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/icons/github.svg +0 -0
  65. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/icons/instagram.svg +0 -0
  66. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/icons/mastodon.svg +0 -0
  67. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/icons/x.svg +0 -0
  68. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/marquee_counter.svg +0 -0
  69. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/marquee_horizontal.svg +0 -0
  70. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/marquee_vertical.svg +0 -0
  71. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/profile-cards/chart_stars_full.svg +0 -0
  72. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/profile-cards/stats.svg +0 -0
  73. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/chrome-horizon/strip.svg +0 -0
  74. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/telemetry/master_card.svg +0 -0
  75. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/telemetry/receipt.svg +0 -0
  76. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/examples/telemetry/rhythm_strip.svg +0 -0
  77. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/footers/inneraura-footer-liquid.svg +0 -0
  78. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/footers/inneraura-footer-purple.svg +0 -0
  79. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/footers/inneraura-footer.svg +0 -0
  80. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/hyperweave-banner.svg +0 -0
  81. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/icons/cobalt-sapphire-discord.svg +0 -0
  82. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/icons/cobalt-sapphire-docs.svg +0 -0
  83. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/icons/cobalt-sapphire-github.svg +0 -0
  84. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/icons/cobalt-sapphire-instagram.svg +0 -0
  85. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/icons/cobalt-sapphire-linkedin.svg +0 -0
  86. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/icons/cobalt-sapphire-tiktok.svg +0 -0
  87. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/icons/cobalt-sapphire-x.svg +0 -0
  88. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/icons/cobalt-sapphire-youtube.svg +0 -0
  89. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/marquees/badge-showcase-triple.svg +0 -0
  90. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/marquees/genome-marquee-triple.svg +0 -0
  91. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/marquees/sample-badges.svg +0 -0
  92. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/mintlify-assets/404.svg +0 -0
  93. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/mintlify-assets/callout-icons.svg +0 -0
  94. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/mintlify-assets/divider.svg +0 -0
  95. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/mintlify-assets/favicon.svg +0 -0
  96. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/mintlify-assets/hero-banner.svg +0 -0
  97. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/mintlify-assets/hyperweave-banner-v2.svg +0 -0
  98. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/mintlify-assets/hyperweave-navbar-logo.svg +0 -0
  99. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/mintlify-assets/loader.svg +0 -0
  100. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/mintlify-assets/og-image.svg +0 -0
  101. {hyperweave-0.2.6 → hyperweave-0.2.7}/assets/timelines/hyperweave-roadmap.svg +0 -0
  102. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/assets/favicon.svg +0 -0
  103. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/assets/hyperweave-banner.svg +0 -0
  104. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/assets/hyperweave-navbar-logo.svg +0 -0
  105. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/assets/og-image.svg +0 -0
  106. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/explanation/architecture.mdx +0 -0
  107. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/explanation/camo-compatibility.mdx +0 -0
  108. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/explanation/cim-compliance.mdx +0 -0
  109. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/explanation/genome-profile-system.mdx +0 -0
  110. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/explanation/metadata-tiers.mdx +0 -0
  111. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/how-to/add-motion-to-badges.mdx +0 -0
  112. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/how-to/create-session-receipts.mdx +0 -0
  113. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/how-to/set-up-claude-code-hooks.mdx +0 -0
  114. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/how-to/use-live-data-badges.mdx +0 -0
  115. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/introduction.mdx +0 -0
  116. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/mint.json +0 -0
  117. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/quickstart.mdx +0 -0
  118. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/reference/cli.mdx +0 -0
  119. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/reference/compose-spec.mdx +0 -0
  120. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/reference/genomes.mdx +0 -0
  121. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/reference/http-api.mdx +0 -0
  122. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/reference/mcp-tools.mdx +0 -0
  123. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/reference/motions.mdx +0 -0
  124. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/reference/telemetry-contract.mdx +0 -0
  125. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/spec/hyperweave-protocol.mdx +0 -0
  126. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/tutorials/build-an-artifact-kit.mdx +0 -0
  127. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/tutorials/compose-your-first-badge.mdx +0 -0
  128. {hyperweave-0.2.6 → hyperweave-0.2.7}/docs/tutorials/install-session-telemetry.mdx +0 -0
  129. {hyperweave-0.2.6 → hyperweave-0.2.7}/fly.toml +0 -0
  130. {hyperweave-0.2.6 → hyperweave-0.2.7}/hooks/install.py +0 -0
  131. {hyperweave-0.2.6 → hyperweave-0.2.7}/hooks/session_end.sh +0 -0
  132. {hyperweave-0.2.6 → hyperweave-0.2.7}/justfile +0 -0
  133. {hyperweave-0.2.6 → hyperweave-0.2.7}/pyproject.toml +0 -0
  134. {hyperweave-0.2.6 → hyperweave-0.2.7}/scripts/extract_font_metrics.py +0 -0
  135. {hyperweave-0.2.6 → hyperweave-0.2.7}/scripts/extract_glyphs.py +0 -0
  136. {hyperweave-0.2.6 → hyperweave-0.2.7}/scripts/generate_hw_compliant.py +0 -0
  137. {hyperweave-0.2.6 → hyperweave-0.2.7}/scripts/generate_proofset.py +0 -0
  138. {hyperweave-0.2.6 → hyperweave-0.2.7}/scripts/stress_test.py +0 -0
  139. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/__init__.py +0 -0
  140. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/__main__.py +0 -0
  141. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/cli.py +0 -0
  142. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/__init__.py +0 -0
  143. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/assembler.py +0 -0
  144. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/engine.py +0 -0
  145. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/lanes.py +0 -0
  146. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/resolvers/__init__.py +0 -0
  147. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/resolvers/chart.py +0 -0
  148. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/resolvers/stats.py +0 -0
  149. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/resolvers/timeline.py +0 -0
  150. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/compose/validate_paradigms.py +0 -0
  151. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/config/__init__.py +0 -0
  152. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/config/genome_validator.py +0 -0
  153. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/config/loader.py +0 -0
  154. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/config/registry.py +0 -0
  155. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/connectors/__init__.py +0 -0
  156. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/connectors/arxiv.py +0 -0
  157. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/connectors/base.py +0 -0
  158. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/connectors/cache.py +0 -0
  159. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/connectors/rest.py +0 -0
  160. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/__init__.py +0 -0
  161. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/color.py +0 -0
  162. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/contracts.py +0 -0
  163. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/enums.py +0 -0
  164. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/font_metrics.py +0 -0
  165. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/models.py +0 -0
  166. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/paradigm.py +0 -0
  167. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/schema.py +0 -0
  168. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/text.py +0 -0
  169. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/core/thresholds.py +0 -0
  170. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/css/accessibility.css +0 -0
  171. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/css/bridge.css +0 -0
  172. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/css/expression.css +0 -0
  173. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/css/phase-colors.css +0 -0
  174. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/css/status.css +0 -0
  175. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/css/telemetry.css +0 -0
  176. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/font-metrics/inter.json +0 -0
  177. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/font-metrics/jetbrains-mono.json +0 -0
  178. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/font-metrics/orbitron.json +0 -0
  179. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/fonts/jetbrains-mono.b64 +0 -0
  180. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/fonts/jetbrains-mono.meta.json +0 -0
  181. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/fonts/orbitron.b64 +0 -0
  182. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/fonts/orbitron.meta.json +0 -0
  183. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/genomes/brutalist-emerald.json +0 -0
  184. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/genomes/chrome-horizon.json +0 -0
  185. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/genomes/telemetry-void.json +0 -0
  186. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/glyphs.json +0 -0
  187. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/border/chromatic-pulse.yaml +0 -0
  188. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/border/corner-trace.yaml +0 -0
  189. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/border/dual-orbit.yaml +0 -0
  190. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/border/entanglement.yaml +0 -0
  191. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/border/rimrun.yaml +0 -0
  192. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/kinetic/bars.yaml +0 -0
  193. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/kinetic/breach.yaml +0 -0
  194. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/kinetic/broadcast.yaml +0 -0
  195. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/kinetic/cascade.yaml +0 -0
  196. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/kinetic/collapse.yaml +0 -0
  197. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/kinetic/converge.yaml +0 -0
  198. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/kinetic/crash.yaml +0 -0
  199. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/kinetic/drop.yaml +0 -0
  200. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/kinetic/pulse.yaml +0 -0
  201. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/motions/static.yaml +0 -0
  202. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/paradigms/brutalist.yaml +0 -0
  203. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/paradigms/chrome.yaml +0 -0
  204. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/paradigms/default.yaml +0 -0
  205. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/policies/normal.json +0 -0
  206. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/policies/permissive.json +0 -0
  207. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/policies/ungoverned.json +0 -0
  208. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/profiles/brutalist.contract.json +0 -0
  209. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/profiles/brutalist.yaml +0 -0
  210. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/profiles/chrome.contract.json +0 -0
  211. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/profiles/chrome.yaml +0 -0
  212. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/specimens.yaml +0 -0
  213. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/telemetry/model-pricing.yaml +0 -0
  214. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/telemetry/stage-config.yaml +0 -0
  215. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/telemetry/stage-labels.yaml +0 -0
  216. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/telemetry/tool-classes.yaml +0 -0
  217. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/telemetry/tool-colors.yaml +0 -0
  218. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/arrow.json +0 -0
  219. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/aurora.json +0 -0
  220. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/beacon.json +0 -0
  221. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/bracket3d.json +0 -0
  222. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/chevron.json +0 -0
  223. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/crosshair.json +0 -0
  224. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/diamond.json +0 -0
  225. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/rocket.json +0 -0
  226. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/rules/bar.json +0 -0
  227. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/rules/dashed.json +0 -0
  228. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/rules/five-wave.json +0 -0
  229. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/rules/spectral.json +0 -0
  230. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/rules/straight.json +0 -0
  231. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/rules/wave.json +0 -0
  232. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/data/terminals/stijl.json +0 -0
  233. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/kit.py +0 -0
  234. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/mcp/__init__.py +0 -0
  235. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/mcp/__main__.py +0 -0
  236. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/py.typed +0 -0
  237. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/render/__init__.py +0 -0
  238. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/render/fonts.py +0 -0
  239. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/render/glyphs.py +0 -0
  240. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/render/motion.py +0 -0
  241. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/render/templates.py +0 -0
  242. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/serve/__init__.py +0 -0
  243. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/serve/app.py +0 -0
  244. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/telemetry/__init__.py +0 -0
  245. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/telemetry/capture.py +0 -0
  246. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/telemetry/corrections.py +0 -0
  247. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/telemetry/cost.py +0 -0
  248. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/telemetry/models.py +0 -0
  249. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/telemetry/parser.py +0 -0
  250. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/telemetry/stages.py +0 -0
  251. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/filter.svg.j2 +0 -0
  252. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/glyph-inline.svg.j2 +0 -0
  253. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/glyph.svg.j2 +0 -0
  254. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/gradient.svg.j2 +0 -0
  255. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/metadata.svg.j2 +0 -0
  256. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/metric.svg.j2 +0 -0
  257. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/phase-segment.svg.j2 +0 -0
  258. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/rule.svg.j2 +0 -0
  259. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/status.svg.j2 +0 -0
  260. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/components/terminal.svg.j2 +0 -0
  261. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/document.svg.j2 +0 -0
  262. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/error-badge.svg.j2 +0 -0
  263. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/badge/brutalist-content.j2 +0 -0
  264. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/badge/brutalist-defs.j2 +0 -0
  265. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/badge/chrome-content.j2 +0 -0
  266. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/badge/chrome-defs.j2 +0 -0
  267. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/badge/default-content.j2 +0 -0
  268. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/badge/default-defs.j2 +0 -0
  269. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/badge.svg.j2 +0 -0
  270. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/banner/brutalist-defs.j2 +0 -0
  271. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/banner/chrome-content.j2 +0 -0
  272. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/banner/chrome-defs.j2 +0 -0
  273. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/banner/default-content.j2 +0 -0
  274. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/banner/default-defs.j2 +0 -0
  275. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/banner.svg.j2 +0 -0
  276. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/catalog.svg.j2 +0 -0
  277. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/chart/brutalist-defs.j2 +0 -0
  278. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/chart/chrome-defs.j2 +0 -0
  279. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/chart.svg.j2 +0 -0
  280. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/divider.svg.j2 +0 -0
  281. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/icon/brutalist-content.j2 +0 -0
  282. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/icon/brutalist-defs.j2 +0 -0
  283. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/icon/chrome-content.j2 +0 -0
  284. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/icon/chrome-defs.j2 +0 -0
  285. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/icon/default-content.j2 +0 -0
  286. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/icon/default-defs.j2 +0 -0
  287. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/icon.svg.j2 +0 -0
  288. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-counter/brutalist-content.j2 +0 -0
  289. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-counter/brutalist-defs.j2 +0 -0
  290. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-counter/chrome-content.j2 +0 -0
  291. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-counter/chrome-defs.j2 +0 -0
  292. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-counter.svg.j2 +0 -0
  293. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-horizontal/brutalist-content.j2 +0 -0
  294. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-horizontal/brutalist-defs.j2 +0 -0
  295. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-horizontal/chrome-content.j2 +0 -0
  296. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-horizontal/chrome-defs.j2 +0 -0
  297. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-horizontal.svg.j2 +0 -0
  298. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-vertical/brutalist-content.j2 +0 -0
  299. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-vertical/brutalist-defs.j2 +0 -0
  300. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-vertical/chrome-content.j2 +0 -0
  301. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-vertical/chrome-defs.j2 +0 -0
  302. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/marquee-vertical.svg.j2 +0 -0
  303. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/master-card.svg.j2 +0 -0
  304. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/rhythm-strip.svg.j2 +0 -0
  305. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/stats/brutalist-content.j2 +0 -0
  306. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/stats/brutalist-defs.j2 +0 -0
  307. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/stats/chrome-content.j2 +0 -0
  308. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/stats/chrome-defs.j2 +0 -0
  309. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/stats.svg.j2 +0 -0
  310. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/strip/brutalist-content.j2 +0 -0
  311. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/strip/brutalist-defs.j2 +0 -0
  312. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/strip/brutalist-status.j2 +0 -0
  313. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/strip/chrome-content.j2 +0 -0
  314. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/strip/chrome-defs.j2 +0 -0
  315. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/strip/chrome-status.j2 +0 -0
  316. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/strip/default-content.j2 +0 -0
  317. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/strip/default-defs.j2 +0 -0
  318. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/strip/default-status.j2 +0 -0
  319. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/strip.svg.j2 +0 -0
  320. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/timeline/default-content.j2 +0 -0
  321. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/frames/timeline.svg.j2 +0 -0
  322. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/border/chromatic-pulse.svg.j2 +0 -0
  323. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/border/corner-trace.svg.j2 +0 -0
  324. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/border/dual-orbit.svg.j2 +0 -0
  325. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/border/entanglement.svg.j2 +0 -0
  326. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/border/rimrun.svg.j2 +0 -0
  327. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/kinetic/bars.svg.j2 +0 -0
  328. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/kinetic/breach.svg.j2 +0 -0
  329. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/kinetic/broadcast.svg.j2 +0 -0
  330. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/kinetic/cascade.svg.j2 +0 -0
  331. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/kinetic/collapse.svg.j2 +0 -0
  332. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/kinetic/converge.svg.j2 +0 -0
  333. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/kinetic/crash.svg.j2 +0 -0
  334. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/kinetic/drop.svg.j2 +0 -0
  335. {hyperweave-0.2.6 → hyperweave-0.2.7}/src/hyperweave/templates/motions/kinetic/pulse.svg.j2 +0 -0
  336. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/__init__.py +0 -0
  337. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/conftest.py +0 -0
  338. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/fixtures/github_contributions/synthetic.html +0 -0
  339. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/fixtures/session.json +0 -0
  340. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/fixtures/session.jsonl +0 -0
  341. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/helpers.py +0 -0
  342. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_assembler.py +0 -0
  343. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_core.py +0 -0
  344. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_github_scrape.py +0 -0
  345. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_kit.py +0 -0
  346. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_paradigm_dispatch.py +0 -0
  347. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_paradigm_extensibility.py +0 -0
  348. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_proofset.py +0 -0
  349. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_render.py +0 -0
  350. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_serve.py +0 -0
  351. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_serve_live_state.py +0 -0
  352. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_stats_card.py +0 -0
  353. {hyperweave-0.2.6 → hyperweave-0.2.7}/tests/test_timeline.py +0 -0
  354. {hyperweave-0.2.6 → hyperweave-0.2.7}/uv.lock +0 -0
@@ -5,6 +5,30 @@ All notable changes to HyperWeave are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.7] - 2026-04-20
9
+
10
+ ### Fixed
11
+
12
+ - **Star history charts now end at the current date.** On very large repositories (40k+ stars), GitHub caps stargazer pagination at roughly the first 40,000 stars, so the latest sample pulled from the API was often years old and the polyline terminated in the past. The chart now always appends a final data point stamped at the current time, while still reporting the real total star count. Small repos are unaffected.
13
+ - **Session receipt labels are honest again.** The "N corrections" line previously counted every user turn — any pushback, redirect, or elaboration — and rendered them next to tool-failure marks (`✗N`) on the token treemap, which created the impression that the two numbers should reconcile. They're now split into "N user turns" and "N tool errors", with tool-error counts tinted red to match the cell marks. A new legend — `✗N = failed tool calls` — appears above the token map so the red marks are self-explanatory.
14
+ - **Session receipt hero no longer misreports the dominant work phase.** The hero badge previously showed the label of the first detected stage, even when that stage lasted two minutes and a later stage dominated the session. It now uses the stage with the largest share of tool calls, and falls back to `MIXED` when no single stage exceeds 20%.
15
+ - **Rhythm bars stop overflowing their track on long sessions.** On sessions with many stages (~30+), the rightmost rhythm bar on the receipt could render hundreds of pixels past the track's right edge. Receipt and rhythm-strip now share a single layout routine with a proper gap budget and a post-hoc rescale, so bars always fit the track regardless of stage count.
16
+ - **Rhythm bars encode time, not tool-call share.** When the telemetry contract carries start and end timestamps per stage, bar widths are now proportional to stage duration, so the time-axis labels (`0m · 104m · 209m`) actually correspond to bar positions. Bar heights are now uniform; the previous height scaling was effectively noise because most bars hit the minimum-height floor.
17
+ - **Live badges now recognize `building` as a state.** A badge with `value="building"` (or `"rebuilding"` / `"build"`) now renders as the building state instead of falling through to the default `active`. Longer phrases containing the word "build" are not affected — only those three exact tokens.
18
+ - **Deploying `HW_GITHUB_TOKENS` no longer crashes the app.** A vestigial `github_tokens` field on the settings schema was trying to parse the environment variable as JSON on startup, so setting the plain comma-separated secret that the token-rotation code actually expects caused a 500. The field has been removed; the connector reads the secret directly, and plain comma-separated deployments now work as documented.
19
+
20
+ ### Added
21
+
22
+ - **`hw_discover` advertises the `stats`, `chart`, and `timeline` routes.** MCP clients calling `hw_discover(what="url_grammar")` now receive URL patterns, example URLs, and method hints for the three routes that shipped in v0.2.0 but were missing from discovery.
23
+
24
+ ### Changed
25
+
26
+ - **Chart markers render through template partials.** Marker shapes (rect, circle, diamond, and their endpoint variants) now live as Jinja partials under `templates/components/chart-markers/`, matching how the rest of the rendering pipeline handles SVG. No visual change; output is byte-identical for the shipped genomes.
27
+
28
+ ### Dev
29
+
30
+ - GitHub token pool rotation is now covered by unit tests (`HW_GITHUB_TOKENS` comma-separated list, `GITHUB_TOKEN` single-token fallback, whitespace tolerance, empty-env behavior).
31
+
8
32
  ## [0.2.6] - 2026-04-19
9
33
 
10
34
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperweave
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: Headless visual output layer for AI agents. One API call → self-contained SVG.
5
5
  Project-URL: Repository, https://github.com/InnerAura/hyperweave
6
6
  Project-URL: Issues, https://github.com/InnerAura/hyperweave/issues
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.6'
22
- __version_tuple__ = version_tuple = (0, 2, 6)
21
+ __version__ = version = '0.2.7'
22
+ __version_tuple__ = version_tuple = (0, 2, 7)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -341,7 +341,7 @@ def _ctx_chart(spec: ComposeSpec, resolved: ResolvedArtifact, css: dict[str, str
341
341
  ctx["chart_gridlines"] = ""
342
342
  ctx["chart_area"] = ""
343
343
  ctx["chart_polyline"] = ""
344
- ctx["chart_markers"] = ""
344
+ ctx["chart_markers"] = []
345
345
  ctx["chart_milestones"] = ""
346
346
  ctx["data_hw_status"] = "fresh"
347
347
  ctx.update(resolved.frame_context)
@@ -371,7 +371,7 @@ def _ctx_stats(spec: ComposeSpec, resolved: ResolvedArtifact, css: dict[str, str
371
371
  ctx["embedded_chart_defs"] = ""
372
372
  ctx["embedded_chart_area"] = ""
373
373
  ctx["embedded_chart_polyline"] = ""
374
- ctx["embedded_chart_markers"] = ""
374
+ ctx["embedded_chart_markers"] = []
375
375
  ctx.update(resolved.frame_context)
376
376
  return ctx
377
377
 
@@ -6,6 +6,7 @@ from datetime import datetime
6
6
  from pathlib import Path
7
7
  from typing import TYPE_CHECKING, Any
8
8
 
9
+ from hyperweave.compose.rhythm import layout_rhythm_bars
9
10
  from hyperweave.core.enums import (
10
11
  FrameType,
11
12
  GlyphMode,
@@ -1112,7 +1113,7 @@ def resolve_receipt(
1112
1113
  profile_data: dict[str, Any] = tel.get("profile", {})
1113
1114
  tools_raw = tel.get("tools", {})
1114
1115
  stages_raw: list[dict[str, Any]] = tel.get("stages", [])
1115
- corrections: list[dict[str, Any]] = tel.get("corrections", [])
1116
+ user_events: list[dict[str, Any]] = tel.get("user_events", [])
1116
1117
  agents: list[dict[str, Any]] = tel.get("agents", [])
1117
1118
 
1118
1119
  # ── Normalize tools: contract produces dict keyed by name, templates need list ──
@@ -1122,7 +1123,8 @@ def resolve_receipt(
1122
1123
  tools = list(tools_raw)
1123
1124
 
1124
1125
  # ── Normalize stages: contract produces {label, dominant_class, start, end, tools} ──
1125
- # Templates need {name, pct, tool_class} with percentage proportions
1126
+ # Templates need {name, pct, tool_class}; start/end are preserved so
1127
+ # :func:`layout_rhythm_bars` can lay out time-proportional x/w.
1126
1128
  total_stage_tools = sum(s.get("tools", 1) for s in stages_raw) or 1
1127
1129
  stages: list[dict[str, Any]] = [
1128
1130
  {
@@ -1130,6 +1132,8 @@ def resolve_receipt(
1130
1132
  "pct": round(s.get("tools", 1) / total_stage_tools * 100),
1131
1133
  "label": s.get("label", ""),
1132
1134
  "tool_class": s.get("dominant_class", "explore"),
1135
+ "start": s.get("start"),
1136
+ "end": s.get("end"),
1133
1137
  }
1134
1138
  for s in stages_raw
1135
1139
  ]
@@ -1161,21 +1165,43 @@ def resolve_receipt(
1161
1165
  "Send": "coordinate",
1162
1166
  }
1163
1167
 
1168
+ # ── Dominant phase (drives hero badge + bottom-right phase label) ──
1169
+ # Using stages[0] was the old bug — for a session where the first 2-minute
1170
+ # stage classified as "validation" but the dominant (45% of tool calls)
1171
+ # was "implementation", the hero badge lied. When no single stage owns
1172
+ # at least 20% of the tool calls, fall back to "MIXED" to avoid
1173
+ # overclaiming a dominant phase that doesn't exist.
1174
+ dominant = max(stages, key=lambda s: s.get("pct", 0)) if stages else None
1175
+ dominant_label = (dominant.get("label") or dominant.get("name") or "") if dominant else ""
1176
+ dominant_pct = dominant.get("pct", 0) if dominant else 0
1177
+
1164
1178
  # ── Hero row ──
1165
1179
  hero_headline = f"{_fmt_tok(total_tok)} tokens billed · ${total_cost:.2f}"
1166
1180
  dur_label = f"{int(duration_m)}m" if duration_m else "—"
1167
1181
  hero_subline = f"{model} · {dur_label} · {calls} calls"
1168
- hero_profile = stages[0]["label"].upper() if stages else "SESSION"
1169
- hero_tool_class = stages[0]["tool_class"] if stages else "explore"
1170
- n_corrections = len(corrections)
1182
+ if not dominant:
1183
+ hero_profile = "SESSION"
1184
+ elif dominant_pct < 20:
1185
+ hero_profile = "MIXED"
1186
+ else:
1187
+ hero_profile = dominant_label.upper()
1188
+ hero_tool_class = dominant["tool_class"] if dominant else "explore"
1189
+ # Split "pushbacks" into distinct signals so the card stops labeling them
1190
+ # as one opaque "N corrections" lie. user_events counts every non-continuation
1191
+ # user turn (corrections + redirects + elaborations); tool errors count
1192
+ # failing/blocked tool calls (the red ✗N cell marks reconcile to this).
1193
+ n_user_turns = len(user_events)
1194
+ n_tool_errors = sum(t.get("errors", 0) + t.get("blocked", 0) for t in tools)
1171
1195
  n_agents = len(agents)
1172
- hero_right = [
1173
- f"{_fmt_tok(total_input)} in / {_fmt_tok(total_output)} out",
1196
+ hero_right: list[dict[str, str]] = [
1197
+ {"text": f"{_fmt_tok(total_input)} in / {_fmt_tok(total_output)} out"},
1174
1198
  ]
1175
1199
  if total_cache_read or total_cache_create:
1176
- hero_right.append(f"{_fmt_tok(total_cache_read)} cached / {_fmt_tok(total_cache_create)} written")
1177
- if n_corrections:
1178
- hero_right.append(f"{n_corrections} correction{'s' if n_corrections != 1 else ''}")
1200
+ hero_right.append({"text": f"{_fmt_tok(total_cache_read)} cached / {_fmt_tok(total_cache_create)} written"})
1201
+ if n_user_turns:
1202
+ hero_right.append({"text": f"{n_user_turns} user turn{'s' if n_user_turns != 1 else ''}"})
1203
+ if n_tool_errors:
1204
+ hero_right.append({"text": f"{n_tool_errors} tool errors", "accent": "failing"})
1179
1205
 
1180
1206
  # ── Treemap layout (3-tier, 752px wide, token-proportional) ──
1181
1207
  content_w = 752
@@ -1253,29 +1279,16 @@ def resolve_receipt(
1253
1279
  )
1254
1280
  x += min(w, 180) + 4
1255
1281
 
1256
- # ── Rhythm bars (scale stages to 752px) ──
1257
- total_stage_pct = sum(s.get("pct", 0) for s in stages) or 100
1258
- rhythm_bars: list[dict[str, Any]] = []
1259
- rx = 0
1282
+ # ── Rhythm bars ──
1283
+ # Delegated to the shared helper so receipt + rhythm-strip can't drift.
1284
+ # See src/hyperweave/compose/rhythm.py for the two-pass algorithm.
1260
1285
  bar_area_h = 92
1261
- for s in stages:
1262
- pct = s.get("pct", 0)
1263
- w = max(int(content_w * pct / total_stage_pct), 6)
1264
- h = max(int(bar_area_h * (pct / 50)), 8)
1265
- y = bar_area_h - h
1266
- tc = s.get("tool_class", s.get("name", "explore"))
1267
- rhythm_bars.append({"x": rx, "y": y, "w": w, "h": h, "tool_class": tc})
1268
- rx += w + 2
1286
+ rhythm_bars = layout_rhythm_bars(stages, area_w=content_w, area_h=bar_area_h)
1269
1287
 
1270
1288
  # ── Legend entries ──
1271
1289
  used_classes = sorted({c["tool_class"] for c in treemap_cells}) if treemap_cells else ["explore"]
1272
1290
  treemap_legend = [{"tool_class": tc, "label": tc} for tc in used_classes]
1273
1291
 
1274
- # ── Dominant phase ──
1275
- dominant = max(stages, key=lambda s: s.get("pct", 0)) if stages else {"name": "", "pct": 0}
1276
- dominant_label = dominant.get("label", dominant.get("name", ""))
1277
- dominant_pct = dominant.get("pct", 0)
1278
-
1279
1292
  # ── Metadata band (v0.4): provenance row between rhythm legend and footer ──
1280
1293
  session_id = session.get("id", "")
1281
1294
  session_id_short = session_id[:8].rstrip("-") if session_id else ""
@@ -1307,11 +1320,14 @@ def resolve_receipt(
1307
1320
  metadata_right = " · ".join(metadata_right_parts)
1308
1321
 
1309
1322
  # ── Footer: session stats (left) + brand anchor (right) ──
1323
+ # Same split as hero so nothing in the card claims "corrections" anymore.
1310
1324
  footer_parts = []
1311
- if n_corrections:
1312
- footer_parts.append(f"{n_corrections} corrections")
1325
+ if n_user_turns:
1326
+ footer_parts.append(f"{n_user_turns} user turn{'s' if n_user_turns != 1 else ''}")
1327
+ if n_tool_errors:
1328
+ footer_parts.append(f"{n_tool_errors} tool error{'s' if n_tool_errors != 1 else ''}")
1313
1329
  if n_agents:
1314
- footer_parts.append(f"{n_agents} agents")
1330
+ footer_parts.append(f"{n_agents} agent{'s' if n_agents != 1 else ''}")
1315
1331
  footer_left = " · ".join(footer_parts) if footer_parts else ""
1316
1332
  footer_right = "hyperweave.app"
1317
1333
 
@@ -1325,7 +1341,7 @@ def resolve_receipt(
1325
1341
  "hero_tool_class": hero_tool_class,
1326
1342
  "hero_headline": hero_headline,
1327
1343
  "hero_subline": hero_subline,
1328
- "hero_right_stats": [{"text": t} for t in hero_right],
1344
+ "hero_right_stats": hero_right,
1329
1345
  "treemap_legend": treemap_legend,
1330
1346
  "treemap_cells": treemap_cells,
1331
1347
  "stage_count": len(stages),
@@ -1368,12 +1384,16 @@ def resolve_rhythm_strip(
1368
1384
  tools = list(tools_raw)
1369
1385
 
1370
1386
  # ── Normalize stages ──
1387
+ # start/end preserved so :func:`layout_rhythm_bars` can lay out
1388
+ # time-proportional widths when the contract carries them.
1371
1389
  total_stage_tools = sum(s.get("tools", 1) for s in stages_raw) or 1
1372
1390
  stages: list[dict[str, Any]] = [
1373
1391
  {
1374
1392
  "name": s.get("dominant_class", s.get("label", "explore")),
1375
1393
  "pct": round(s.get("tools", 1) / total_stage_tools * 100),
1376
1394
  "tool_class": s.get("dominant_class", "explore"),
1395
+ "start": s.get("start"),
1396
+ "end": s.get("end"),
1377
1397
  }
1378
1398
  for s in stages_raw
1379
1399
  ]
@@ -1389,38 +1409,10 @@ def resolve_rhythm_strip(
1389
1409
 
1390
1410
  # Rhythm bars — must match template bar_w = sw - stats_w - right_w - 16.
1391
1411
  # With sw=800, stats_w=180, right_w=120: bar_area_w = 484.
1392
- # Budget accounts for 2px gaps between bars so many-stage sessions don't
1393
- # overflow into the right-side loop-status panel.
1412
+ # Delegated to the shared helper see src/hyperweave/compose/rhythm.py.
1394
1413
  bar_area_w = 484
1395
1414
  bar_area_h = 42
1396
- gap_px = 2
1397
- n_stages = len(stages)
1398
- gap_budget = gap_px * max(n_stages - 1, 0)
1399
- available_w = max(bar_area_w - gap_budget, bar_area_w // 2)
1400
- min_bar_w = max(2, available_w // max(n_stages, 1) // 3) if n_stages else 6
1401
- total_stage_pct = sum(s.get("pct", 0) for s in stages) or 100
1402
-
1403
- raw_bars: list[dict[str, Any]] = []
1404
- for s in stages:
1405
- pct = s.get("pct", 0)
1406
- w = max(int(available_w * pct / total_stage_pct), min_bar_w)
1407
- h = max(int(bar_area_h * (pct / 50)), 6)
1408
- y = bar_area_h - h
1409
- tc = s.get("tool_class", "explore")
1410
- raw_bars.append({"w": w, "h": h, "y": y, "tool_class": tc})
1411
-
1412
- # Post-hoc uniform scale if floor-pressure still exceeds budget
1413
- raw_total = sum(b["w"] for b in raw_bars)
1414
- if raw_total > available_w and raw_total > 0:
1415
- scale = available_w / raw_total
1416
- for b in raw_bars:
1417
- b["w"] = max(int(b["w"] * scale), 2)
1418
-
1419
- rhythm_bars: list[dict[str, Any]] = []
1420
- rx = 0
1421
- for b in raw_bars:
1422
- rhythm_bars.append({"x": rx, "y": b["y"], "w": b["w"], "h": b["h"], "tool_class": b["tool_class"]})
1423
- rx += b["w"] + gap_px
1415
+ rhythm_bars = layout_rhythm_bars(stages, area_w=bar_area_w, area_h=bar_area_h)
1424
1416
 
1425
1417
  # Velocity estimate
1426
1418
  vel = int(total_tok / max(duration_m, 1)) if duration_m else 0
@@ -0,0 +1,109 @@
1
+ """Rhythm-bar layout: time-proportional x/w with uniform heights.
2
+
3
+ Shared between :func:`resolve_receipt` and :func:`resolve_rhythm_strip`
4
+ so the receipt's "79-stage bars overflow the track" bug can't diverge
5
+ from rhythm-strip's correct two-pass algorithm.
6
+
7
+ Algorithm (two passes):
8
+ 1. Reserve a gap budget up front so ``n_bars x gap_px`` never steals
9
+ from the width budget. Compute a minimum bar width floor based on
10
+ ``n_bars`` so each bar is still visible under extreme stage counts.
11
+ 2. Raw widths: time-proportional when every stage carries ISO
12
+ ``start``/``end`` timestamps (preferred — the time-axis labels
13
+ then agree with the bar positions); fall back to tool-call-share
14
+ pct when timestamps are missing (legacy contract).
15
+ 3. Post-hoc uniform rescale if the raw sum still exceeds the gap
16
+ budget (happens when the min-bar-width floor dominates).
17
+ 4. Emit ``{x, y, w, h, tool_class}`` with ``h = BAR_HEIGHT`` uniform.
18
+
19
+ Uniform height is intentional. The old resolver's
20
+ ``h = max(int(bar_area_h * (pct / 50)), 8)`` made ~76 of 79 bars hit
21
+ the 8px floor, implying a signal dimension the data didn't carry.
22
+ Three channels (time x category x color) is already enough; the
23
+ fourth (height) had no clear semantic and was therefore noise.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from datetime import datetime
29
+ from typing import Any
30
+
31
+ BAR_HEIGHT = 14
32
+ """Uniform rhythm-bar height. Shared by receipt (area_h=92) and rhythm-strip
33
+ (area_h=42): both align the bars flush to the bottom of their track."""
34
+
35
+
36
+ def layout_rhythm_bars(
37
+ stages: list[dict[str, Any]],
38
+ area_w: int,
39
+ area_h: int,
40
+ gap_px: int = 2,
41
+ ) -> list[dict[str, Any]]:
42
+ """Lay out rhythm bars into a bounded track with a gap budget + rescale.
43
+
44
+ Args:
45
+ stages: Normalized stage dicts. Each MAY carry ``start``/``end``
46
+ ISO strings for time-proportional layout; otherwise layout
47
+ falls back to ``pct`` (tool-call share).
48
+ area_w: Track width in pixels.
49
+ area_h: Track height in pixels. Used only for ``y`` (bars align
50
+ bottom-flush); ``h`` is always :data:`BAR_HEIGHT`.
51
+ gap_px: Inter-bar gap in pixels. Reserved upfront in the budget
52
+ so many-stage sessions don't overflow into adjacent panels.
53
+
54
+ Returns:
55
+ One dict per stage: ``{x, y, w, h, tool_class}``. Empty list when
56
+ ``stages`` is empty.
57
+ """
58
+ if not stages:
59
+ return []
60
+
61
+ n = len(stages)
62
+ gap_budget = gap_px * max(n - 1, 0)
63
+ # Cap the worst case: never give up more than half the track to gaps.
64
+ available_w = max(area_w - gap_budget, area_w // 2)
65
+
66
+ has_timestamps = all(s.get("start") and s.get("end") for s in stages)
67
+ if has_timestamps:
68
+ t0 = datetime.fromisoformat(stages[0]["start"])
69
+ t_end = datetime.fromisoformat(stages[-1]["end"])
70
+ total_s = max((t_end - t0).total_seconds(), 1.0)
71
+ raw_w = [
72
+ max(
73
+ int(
74
+ available_w
75
+ * (datetime.fromisoformat(s["end"]) - datetime.fromisoformat(s["start"])).total_seconds()
76
+ / total_s
77
+ ),
78
+ 2,
79
+ )
80
+ for s in stages
81
+ ]
82
+ else:
83
+ total_pct = sum(s.get("pct", 0) for s in stages) or 100
84
+ # Per-bar minimum: 1/3 of the equal-share width so tiny stages
85
+ # stay visible without swallowing the whole budget.
86
+ min_bar_w = max(2, available_w // max(n, 1) // 3)
87
+ raw_w = [max(int(available_w * s.get("pct", 0) / total_pct), min_bar_w) for s in stages]
88
+
89
+ # Post-hoc uniform rescale: if the floor pressure drove the sum over budget.
90
+ raw_total = sum(raw_w)
91
+ if raw_total > available_w and raw_total > 0:
92
+ scale = available_w / raw_total
93
+ raw_w = [max(int(w * scale), 2) for w in raw_w]
94
+
95
+ y = area_h - BAR_HEIGHT
96
+ bars: list[dict[str, Any]] = []
97
+ rx = 0
98
+ for s, w in zip(stages, raw_w, strict=True):
99
+ bars.append(
100
+ {
101
+ "x": rx,
102
+ "y": y,
103
+ "w": w,
104
+ "h": BAR_HEIGHT,
105
+ "tool_class": s.get("tool_class", s.get("dominant_class", "explore")),
106
+ }
107
+ )
108
+ rx += w + gap_px
109
+ return bars
@@ -37,7 +37,11 @@ class HyperWeaveSettings(BaseSettings):
37
37
  data_cache_ttl: int = Field(default=300, description="Data-bound artifact max-age (5 min)")
38
38
 
39
39
  # -- Connectors --
40
- github_tokens: list[str] = Field(default_factory=list, description="GitHub API tokens for rotation")
40
+ # NOTE: GitHub token rotation lives in ``connectors.base._get_github_token``,
41
+ # which reads ``HW_GITHUB_TOKENS`` as a plain comma-separated string directly
42
+ # from ``os.environ``. Do NOT add a ``github_tokens`` Pydantic field here —
43
+ # Pydantic Settings would auto-map the same env var and try to JSON-parse
44
+ # the CSV value, crashing app startup.
41
45
  connect_timeout: float = Field(default=10.0, description="HTTP connect timeout in seconds")
42
46
  total_timeout: float = Field(default=15.0, description="HTTP total timeout in seconds")
43
47
 
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import math
7
7
  import re
8
+ from datetime import UTC, datetime
8
9
  from typing import Any
9
10
 
10
11
  from hyperweave.connectors.base import fetch_json, fetch_text
@@ -134,7 +135,13 @@ async def fetch_metric(identifier: str, metric: str) -> dict[str, Any]:
134
135
  # ── Session 2A+2B: star history sampling ───────────────────────────────────
135
136
 
136
137
 
137
- _STARGAZER_PAGE_SIZE = 30
138
+ _STARGAZER_PAGE_SIZE = 100
139
+ # GitHub hard-caps deep pagination on /stargazers at ~400 pages.
140
+ # With per_page=100 that gives ~40k stargazer visibility per repo; we still
141
+ # report the real total_stars in the "now" point, we just can't sample past
142
+ # this wall. For mega-repos (100k+ stars) we therefore sample within a fixed
143
+ # window and mark the final point with the current timestamp.
144
+ _STARGAZER_PAGE_CAP = 400
138
145
  _STARGAZER_ACCEPT_HEADER = "application/vnd.github.v3.star+json"
139
146
 
140
147
 
@@ -183,8 +190,9 @@ async def fetch_stargazer_history(
183
190
  cache.set(cache_key, empty_result, STARGAZER_HISTORY_TTL)
184
191
  return empty_result
185
192
 
186
- # Step 2: compute total pages
193
+ # Step 2: compute total pages (clamped at GitHub's deep-pagination cap)
187
194
  total_pages = max(1, math.ceil(total_stars / _STARGAZER_PAGE_SIZE))
195
+ effective_pages = min(total_pages, _STARGAZER_PAGE_CAP)
188
196
 
189
197
  # Single-page case: repo has ≤ 30 stars. The "first starred_at of the page"
190
198
  # sampling trick would otherwise collapse to a single aggregated point plus
@@ -211,14 +219,14 @@ async def fetch_stargazer_history(
211
219
  cache.set(cache_key, single_page_result, STARGAZER_HISTORY_TTL)
212
220
  return single_page_result
213
221
 
214
- # Cap the sample count at the number of available pages.
215
- sample_count = min(sample_pages, total_pages)
222
+ # Cap the sample count at the number of reachable pages.
223
+ sample_count = min(sample_pages, effective_pages)
216
224
 
217
225
  # Step 3: pick evenly distributed page numbers (always include first + last).
218
226
  if sample_count == 1:
219
227
  page_numbers: list[int] = [1]
220
228
  else:
221
- step = (total_pages - 1) / (sample_count - 1)
229
+ step = (effective_pages - 1) / (sample_count - 1)
222
230
  page_numbers = sorted({max(1, round(1 + step * i)) for i in range(sample_count)})
223
231
 
224
232
  async def _fetch_page(page: int) -> tuple[int, list[dict[str, Any]]]:
@@ -247,10 +255,13 @@ async def fetch_stargazer_history(
247
255
  cumulative = 1 # first starred timestamp → at least 1 star
248
256
  points.append({"date": starred_at, "count": cumulative})
249
257
 
250
- # Always append the "now" point with the current total.
258
+ # Append an honest "now" point: real current star total at real current
259
+ # timestamp. For mega-repos where sampling is capped at page 400, the
260
+ # deepest reachable starred_at may be years old — using that as the
261
+ # terminal timestamp produced polylines that ended in the past. The count
262
+ # is still the real stargazers_count; only the timestamp becomes "now".
251
263
  if points:
252
- latest_date = max(p["date"] for p in points)
253
- points.append({"date": latest_date, "count": total_stars})
264
+ points.append({"date": datetime.now(UTC).isoformat(), "count": total_stars})
254
265
  points.sort(key=lambda p: p["date"])
255
266
 
256
267
  result: dict[str, Any] = {
@@ -27,6 +27,12 @@ def infer_state(label: str, value: str) -> str:
27
27
  return "failing"
28
28
  if "warn" in value_lower:
29
29
  return "warning"
30
+ # Explicit set match (not substring) so values like "rebuild required"
31
+ # or "build failure" don't get miscategorized as "building" state.
32
+ # Only the three literal tokens below qualify; any richer phrase should
33
+ # route via the percent-threshold or default-active fall-throughs.
34
+ if value_lower in {"building", "rebuilding", "build"}:
35
+ return "building"
30
36
  if "build" in label_lower and "run" in value_lower:
31
37
  return "building"
32
38
 
@@ -297,6 +297,28 @@ async def hw_discover(
297
297
  },
298
298
  "example": "/v1/live/github/eli64s/readme-ai/stars/brutalist-emerald.static",
299
299
  },
300
+ "stats": {
301
+ "pattern": "/v1/stats/{username}/{genome}.{motion}",
302
+ "query_params": {
303
+ "glyph": "Glyph identifier",
304
+ "state": "Semantic state",
305
+ },
306
+ "example": "/v1/stats/jiahongc/chrome-horizon.static",
307
+ },
308
+ "chart": {
309
+ "pattern": "/v1/chart/stars/{owner}/{repo}/{genome}.{motion}",
310
+ "query_params": {
311
+ "state": "Semantic state",
312
+ },
313
+ "example": "/v1/chart/stars/torvalds/linux/brutalist-emerald.static",
314
+ },
315
+ "timeline": {
316
+ "pattern": "/v1/timeline/{genome}.{motion}",
317
+ "method": "POST",
318
+ "body": 'JSON: {"items": [{"title": str, "subtitle": str, "date": str, "status": str}, ...]}',
319
+ "query_params": {},
320
+ "example": "POST /v1/timeline/chrome-horizon.static with items payload",
321
+ },
300
322
  }
301
323
 
302
324
  return result