hyperweave 0.2.7__tar.gz → 0.2.9__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.
- {hyperweave-0.2.7 → hyperweave-0.2.9}/CHANGELOG.md +30 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/PKG-INFO +1 -1
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/_version.py +2 -2
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/context.py +9 -5
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/connectors/base.py +63 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/connectors/github.py +210 -24
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/render/chart_engine.py +198 -124
- hyperweave-0.2.9/src/hyperweave/templates/components/chart-area.svg.j2 +11 -0
- hyperweave-0.2.9/src/hyperweave/templates/components/chart-axes.svg.j2 +2 -0
- hyperweave-0.2.9/src/hyperweave/templates/components/chart-empty-state.svg.j2 +8 -0
- hyperweave-0.2.9/src/hyperweave/templates/components/chart-gridlines.svg.j2 +2 -0
- hyperweave-0.2.9/src/hyperweave/templates/components/chart-milestone.svg.j2 +7 -0
- hyperweave-0.2.9/src/hyperweave/templates/components/chart-polyline.svg.j2 +10 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/chart/brutalist-content.j2 +22 -8
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/chart/chrome-content.j2 +21 -7
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_chart_engine.py +184 -42
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_connectors.py +467 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/.dockerignore +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/.github/workflows/ci.yml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/.github/workflows/deploy.yml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/.github/workflows/publish.yml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/.gitignore +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/.pre-commit-config.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/Dockerfile +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/LICENSE +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/README.md +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/buttons/button-liquid.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/cards/card-butterfly.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/cards/card-python.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/cards/card-sunflower.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/cards/card-waves.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/badge_critical.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/badge_passing.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/badge_warning.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/banner.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/icons/discord.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/icons/notion.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/icons/reddit.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/icons/spotify.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/icons/youtube.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/marquee_counter.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/marquee_horizontal.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/marquee_vertical.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/profile-cards/chart_stars_full.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/profile-cards/stats.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/brutalist-emerald/strip.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/badge_critical.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/badge_passing.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/badge_warning.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/banner.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/icons/bluesky.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/icons/github.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/icons/instagram.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/icons/mastodon.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/icons/x.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/marquee_counter.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/marquee_horizontal.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/marquee_vertical.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/profile-cards/chart_stars_full.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/profile-cards/stats.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/chrome-horizon/strip.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/telemetry/master_card.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/telemetry/receipt.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/examples/telemetry/rhythm_strip.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/footers/inneraura-footer-liquid.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/footers/inneraura-footer-purple.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/footers/inneraura-footer.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/hyperweave-banner.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/icons/cobalt-sapphire-discord.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/icons/cobalt-sapphire-docs.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/icons/cobalt-sapphire-github.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/icons/cobalt-sapphire-instagram.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/icons/cobalt-sapphire-linkedin.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/icons/cobalt-sapphire-tiktok.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/icons/cobalt-sapphire-x.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/icons/cobalt-sapphire-youtube.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/marquees/badge-showcase-triple.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/marquees/genome-marquee-triple.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/marquees/sample-badges.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/mintlify-assets/404.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/mintlify-assets/callout-icons.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/mintlify-assets/divider.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/mintlify-assets/favicon.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/mintlify-assets/hero-banner.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/mintlify-assets/hyperweave-banner-v2.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/mintlify-assets/hyperweave-navbar-logo.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/mintlify-assets/loader.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/mintlify-assets/og-image.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/assets/timelines/hyperweave-roadmap.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/assets/favicon.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/assets/hyperweave-banner.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/assets/hyperweave-navbar-logo.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/assets/og-image.svg +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/explanation/architecture.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/explanation/camo-compatibility.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/explanation/cim-compliance.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/explanation/genome-profile-system.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/explanation/metadata-tiers.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/how-to/add-motion-to-badges.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/how-to/create-session-receipts.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/how-to/set-up-claude-code-hooks.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/how-to/use-live-data-badges.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/introduction.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/mint.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/quickstart.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/reference/cli.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/reference/compose-spec.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/reference/genomes.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/reference/http-api.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/reference/mcp-tools.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/reference/motions.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/reference/telemetry-contract.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/spec/hyperweave-protocol.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/tutorials/build-an-artifact-kit.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/tutorials/compose-your-first-badge.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/docs/tutorials/install-session-telemetry.mdx +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/fly.toml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/hooks/install.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/hooks/session_end.sh +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/justfile +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/pyproject.toml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/scripts/extract_font_metrics.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/scripts/extract_glyphs.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/scripts/generate_hw_compliant.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/scripts/generate_proofset.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/scripts/stress_test.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/__main__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/cli.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/assembler.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/engine.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/lanes.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/resolver.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/resolvers/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/resolvers/chart.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/resolvers/stats.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/resolvers/timeline.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/rhythm.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/compose/validate_paradigms.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/config/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/config/genome_validator.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/config/loader.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/config/registry.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/config/settings.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/connectors/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/connectors/arxiv.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/connectors/cache.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/connectors/rest.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/color.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/contracts.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/enums.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/font_metrics.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/models.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/paradigm.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/schema.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/state.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/text.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/core/thresholds.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/css/accessibility.css +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/css/bridge.css +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/css/expression.css +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/css/phase-colors.css +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/css/status.css +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/css/telemetry.css +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/font-metrics/inter.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/font-metrics/jetbrains-mono.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/font-metrics/orbitron.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/fonts/jetbrains-mono.b64 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/fonts/jetbrains-mono.meta.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/fonts/orbitron.b64 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/fonts/orbitron.meta.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/genomes/brutalist-emerald.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/genomes/chrome-horizon.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/genomes/telemetry-void.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/glyphs.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/border/chromatic-pulse.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/border/corner-trace.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/border/dual-orbit.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/border/entanglement.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/border/rimrun.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/kinetic/bars.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/kinetic/breach.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/kinetic/broadcast.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/kinetic/cascade.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/kinetic/collapse.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/kinetic/converge.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/kinetic/crash.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/kinetic/drop.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/kinetic/pulse.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/motions/static.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/paradigms/brutalist.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/paradigms/chrome.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/paradigms/default.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/policies/normal.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/policies/permissive.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/policies/ungoverned.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/profiles/brutalist.contract.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/profiles/brutalist.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/profiles/chrome.contract.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/profiles/chrome.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/specimens.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/telemetry/model-pricing.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/telemetry/stage-config.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/telemetry/stage-labels.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/telemetry/tool-classes.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/telemetry/tool-colors.yaml +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/arrow.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/aurora.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/beacon.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/bracket3d.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/chevron.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/crosshair.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/diamond.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/rocket.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/rules/bar.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/rules/dashed.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/rules/five-wave.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/rules/spectral.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/rules/straight.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/rules/wave.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/data/terminals/stijl.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/kit.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/mcp/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/mcp/__main__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/mcp/server.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/py.typed +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/render/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/render/fonts.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/render/glyphs.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/render/motion.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/render/templates.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/serve/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/serve/app.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/telemetry/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/telemetry/capture.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/telemetry/contract.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/telemetry/corrections.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/telemetry/cost.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/telemetry/models.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/telemetry/parser.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/telemetry/stages.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/chart-markers/circle.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/chart-markers/diamond.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/chart-markers/endpoint-diamond.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/chart-markers/endpoint-rect.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/chart-markers/rect.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/filter.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/glyph-inline.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/glyph.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/gradient.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/metadata.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/metric.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/phase-segment.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/rule.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/status.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/terminal.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/components/treemap.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/document.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/error-badge.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/badge/brutalist-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/badge/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/badge/chrome-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/badge/chrome-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/badge/default-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/badge/default-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/badge.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/banner/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/banner/chrome-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/banner/chrome-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/banner/default-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/banner/default-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/banner.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/catalog.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/chart/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/chart/chrome-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/chart.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/divider.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/icon/brutalist-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/icon/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/icon/chrome-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/icon/chrome-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/icon/default-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/icon/default-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/icon.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-counter/brutalist-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-counter/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-counter/chrome-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-counter/chrome-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-counter.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-horizontal/brutalist-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-horizontal/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-horizontal/chrome-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-horizontal/chrome-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-horizontal.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-vertical/brutalist-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-vertical/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-vertical/chrome-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-vertical/chrome-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/marquee-vertical.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/master-card.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/receipt.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/rhythm-strip.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/stats/brutalist-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/stats/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/stats/chrome-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/stats/chrome-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/stats.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/strip/brutalist-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/strip/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/strip/brutalist-status.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/strip/chrome-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/strip/chrome-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/strip/chrome-status.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/strip/default-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/strip/default-defs.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/strip/default-status.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/strip.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/timeline/default-content.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/frames/timeline.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/border/chromatic-pulse.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/border/corner-trace.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/border/dual-orbit.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/border/entanglement.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/border/rimrun.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/kinetic/bars.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/kinetic/breach.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/kinetic/broadcast.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/kinetic/cascade.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/kinetic/collapse.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/kinetic/converge.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/kinetic/crash.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/kinetic/drop.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/src/hyperweave/templates/motions/kinetic/pulse.svg.j2 +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/__init__.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/conftest.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/fixtures/github_contributions/synthetic.html +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/fixtures/session.json +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/fixtures/session.jsonl +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/helpers.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_assembler.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_chart_frame.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_core.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_github_scrape.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_kit.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_mcp.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_paradigm_dispatch.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_paradigm_extensibility.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_proofset.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_render.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_resolver_rhythm.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_serve.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_serve_live_state.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_state_inference.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_stats_card.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_telemetry.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_telemetry_integration.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/tests/test_timeline.py +0 -0
- {hyperweave-0.2.7 → hyperweave-0.2.9}/uv.lock +0 -0
|
@@ -5,6 +5,36 @@ 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.9] - 2026-04-21
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Star history charts now render an authentic growth curve for any repo size.** v0.2.8 fetched only the most-recent 2,000 stargazers for any repo with more than ~2,000 stars, which meant both the 361,000-star `openclaw/openclaw` and the 40,000-star `JuliusBrussee/caveman` rendered as flat horizontal lines near their total-star value — the captured window's variation was invisible on the full-range Y-axis. Star history now uses GraphQL cursor-offset sampling to pull 12 real stargazer timestamps evenly distributed across the repository's full lifetime, regardless of whether the repo has 30 stars or 500,000. The resulting curve shows actual adoption shape — slow-early, explosive-middle, tapering-late — rather than either a hockey-stick or a flat line.
|
|
13
|
+
- **X-axis date labels adapt to the repository's lifetime.** A viral repo that went from 0 to 40K stars in two weeks was being labeled with a single "2026" at the center of its axis; a mature repo with 10 years of history showed the same year-only format. The axis now selects granularity from the actual temporal span — daily labels ("Apr 05", "Apr 12") when the span is under two weeks, weekly under 90 days, month-plus-year under two years, yearly up to ten years, and every-other-year beyond. Labels maintain a minimum 48-pixel horizontal gap so they never collide — the "202324" pileup is gone.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **Parallel GraphQL dispatch is now bounded.** Cold fetches fan out twelve cursor-offset queries across four concurrent in-flight requests via `asyncio.Semaphore`. This keeps cold-fetch latency under ~1 second while staying well below per-minute abuse-detection thresholds that bursty same-token parallelism could otherwise trip.
|
|
18
|
+
|
|
19
|
+
## [0.2.8] - 2026-04-20
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Star history charts now render readably on large repositories.** Previously, repos with more than ~40,000 stars produced a flat-then-vertical "hockey stick" because GitHub's stargazer REST endpoint hard-caps deep pagination, so the only data reachable was a small cluster of early-history stars followed by a single jump to today. Star history now sources from GitHub's GraphQL API and adapts its sampling window to the repo: small repos continue to get full lifetime views, large repos get a detailed recent-growth curve similar to the one star-history.com shows.
|
|
24
|
+
- **Star-count milestone labels no longer stack on top of each other.** When sampled points clustered temporally, the milestone labels (`500`, `1K`, `5K`, `10K`, ...) would render in a single illegible pile on top of the crossing point. Milestones now maintain a minimum 40-pixel horizontal gap; any labels that would overlap with an already-placed one are dropped in favor of the first (lowest-threshold) milestone in the cluster.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **All chart rendering now flows through template partials.** The chart engine's remaining rendering paths — axes, gridlines, polyline, area fill, milestones, and the empty-state overlay — joined the marker layer in Jinja. The engine module returns structured Python data; Jinja partials under `templates/components/` produce the final SVG. No visual change; output is byte-identical for the shipped genomes on unchanged inputs.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **`fetch_graphql` connector primitive.** New async helper in the connector layer that handles POST with JSON bodies, GitHub bearer-token rotation, circuit-breaker coordination, and SSRF validation — mirroring the existing `fetch_json` contract. This unlocks future migration of the stats card's six REST sub-fetches to a single GraphQL call, and keeps a single canonical place for authenticated-POST plumbing.
|
|
33
|
+
|
|
34
|
+
### Dev
|
|
35
|
+
|
|
36
|
+
- GraphQL stargazer pipeline is covered by unit tests for the primary path, REST fallback on failure, no-token skip, mega-repo window cap, small-repo history exhaustion, downsampling correctness, and current-UTC now-point stamping.
|
|
37
|
+
|
|
8
38
|
## [0.2.7] - 2026-04-20
|
|
9
39
|
|
|
10
40
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperweave
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.9'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 9)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -337,12 +337,16 @@ def _ctx_chart(spec: ComposeSpec, resolved: ResolvedArtifact, css: dict[str, str
|
|
|
337
337
|
ctx["chart_viewport_w"] = 0
|
|
338
338
|
ctx["chart_viewport_h"] = 0
|
|
339
339
|
ctx["chart_defs"] = ""
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
ctx["
|
|
340
|
+
# Post-v0.2.8: axes / gridlines / milestones / markers all return structured
|
|
341
|
+
# lists; polyline / area / empty_state are dicts or None so templates can
|
|
342
|
+
# use ``{% if %}`` to guard includes without a StrictUndefined trap.
|
|
343
|
+
ctx["chart_axes"] = []
|
|
344
|
+
ctx["chart_gridlines"] = []
|
|
345
|
+
ctx["chart_area"] = None
|
|
346
|
+
ctx["chart_polyline"] = None
|
|
344
347
|
ctx["chart_markers"] = []
|
|
345
|
-
ctx["chart_milestones"] =
|
|
348
|
+
ctx["chart_milestones"] = []
|
|
349
|
+
ctx["chart_empty_state"] = None
|
|
346
350
|
ctx["data_hw_status"] = "fresh"
|
|
347
351
|
ctx.update(resolved.frame_context)
|
|
348
352
|
return ctx
|
|
@@ -216,3 +216,66 @@ async def fetch_text(
|
|
|
216
216
|
merged.update(headers)
|
|
217
217
|
response = await fetch(url, provider=provider, headers=merged)
|
|
218
218
|
return response.text
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# GraphQL POST
|
|
222
|
+
|
|
223
|
+
_GITHUB_GRAPHQL_URL: str = "https://api.github.com/graphql"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
async def fetch_graphql(
|
|
227
|
+
query: str,
|
|
228
|
+
variables: dict[str, Any] | None = None,
|
|
229
|
+
*,
|
|
230
|
+
provider: str = "github",
|
|
231
|
+
url: str = _GITHUB_GRAPHQL_URL,
|
|
232
|
+
) -> dict[str, Any]:
|
|
233
|
+
"""POST a GraphQL query and return the parsed JSON response.
|
|
234
|
+
|
|
235
|
+
Mirrors :func:`fetch_json`'s contract (SSRF validation, per-provider
|
|
236
|
+
circuit breaker, timeouts, GitHub Bearer-token injection) but speaks
|
|
237
|
+
POST with a JSON body. The single GraphQL endpoint for GitHub is
|
|
238
|
+
baked into the default URL so callers don't have to remember it.
|
|
239
|
+
|
|
240
|
+
GraphQL always returns HTTP 200 with a ``data`` field and an optional
|
|
241
|
+
``errors`` list — HTTP-level failures trip the breaker, but query-level
|
|
242
|
+
errors (invalid field, missing perms) come through as response body
|
|
243
|
+
and are the caller's responsibility to inspect.
|
|
244
|
+
"""
|
|
245
|
+
validate_url(url)
|
|
246
|
+
|
|
247
|
+
breaker = get_breaker(provider)
|
|
248
|
+
if not breaker.allow_request():
|
|
249
|
+
raise CircuitOpenError(
|
|
250
|
+
f"Circuit breaker open for provider {provider!r}. Retry after {breaker.recovery_timeout}s."
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
merged_headers: dict[str, str] = {
|
|
254
|
+
"User-Agent": f"HyperWeave/{__version__} (https://hyperweave.app)",
|
|
255
|
+
"Accept": "application/json",
|
|
256
|
+
"Content-Type": "application/json",
|
|
257
|
+
}
|
|
258
|
+
if provider == "github":
|
|
259
|
+
token = _get_github_token()
|
|
260
|
+
if token:
|
|
261
|
+
merged_headers["Authorization"] = f"Bearer {token}"
|
|
262
|
+
|
|
263
|
+
timeout = httpx.Timeout(
|
|
264
|
+
connect=CONNECT_TIMEOUT,
|
|
265
|
+
read=TOTAL_TIMEOUT,
|
|
266
|
+
write=TOTAL_TIMEOUT,
|
|
267
|
+
pool=TOTAL_TIMEOUT,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
body: dict[str, Any] = {"query": query, "variables": variables or {}}
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
274
|
+
response = await client.post(url, headers=merged_headers, json=body)
|
|
275
|
+
response.raise_for_status()
|
|
276
|
+
breaker.record_success()
|
|
277
|
+
data: dict[str, Any] = response.json()
|
|
278
|
+
return data
|
|
279
|
+
except (httpx.HTTPStatusError, httpx.RequestError) as exc:
|
|
280
|
+
breaker.record_failure()
|
|
281
|
+
raise ConnectorError(f"GraphQL fetch failed for {provider!r}: {exc}") from exc
|
|
@@ -3,12 +3,20 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import base64
|
|
6
7
|
import math
|
|
7
8
|
import re
|
|
8
9
|
from datetime import UTC, datetime
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
|
-
from hyperweave.connectors.base import
|
|
12
|
+
from hyperweave.connectors.base import (
|
|
13
|
+
CircuitOpenError,
|
|
14
|
+
ConnectorError,
|
|
15
|
+
_get_github_token,
|
|
16
|
+
fetch_graphql,
|
|
17
|
+
fetch_json,
|
|
18
|
+
fetch_text,
|
|
19
|
+
)
|
|
12
20
|
from hyperweave.connectors.cache import get_cache
|
|
13
21
|
|
|
14
22
|
PROVIDER = "github"
|
|
@@ -144,6 +152,47 @@ _STARGAZER_PAGE_SIZE = 100
|
|
|
144
152
|
_STARGAZER_PAGE_CAP = 400
|
|
145
153
|
_STARGAZER_ACCEPT_HEADER = "application/vnd.github.v3.star+json"
|
|
146
154
|
|
|
155
|
+
# GraphQL cursor-offset sampling constants.
|
|
156
|
+
#
|
|
157
|
+
# GitHub's GraphQL cursors decode to the literal text ``cursor:<N>`` where
|
|
158
|
+
# N is a 0-indexed offset into the stargazer list. By Base64-encoding
|
|
159
|
+
# ``cursor:<N-1>`` we can construct an ``after:`` anchor that lets ``first: 1``
|
|
160
|
+
# return exactly the Nth stargazer. This bypasses GitHub's 400-page REST cap
|
|
161
|
+
# and lets us sample any offset in a 500K-star repo cheaply.
|
|
162
|
+
#
|
|
163
|
+
# 12 offsets * 1 node per call = 12 GraphQL points per cold fetch.
|
|
164
|
+
# ``asyncio.Semaphore(_GRAPHQL_CONCURRENCY)`` caps fan-out so bursty
|
|
165
|
+
# same-token parallelism doesn't trigger GitHub's per-minute abuse detection.
|
|
166
|
+
_DEFAULT_SAMPLE_COUNT = 12
|
|
167
|
+
_GRAPHQL_CONCURRENCY = 4
|
|
168
|
+
|
|
169
|
+
_CURSOR_OFFSET_QUERY = """
|
|
170
|
+
query StargazerAtOffset($owner: String!, $repo: String!, $cursor: String) {
|
|
171
|
+
repository(owner: $owner, name: $repo) {
|
|
172
|
+
stargazerCount
|
|
173
|
+
stargazers(first: 1, after: $cursor) {
|
|
174
|
+
edges { starredAt }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _cursor_for_offset(offset: int) -> str | None:
|
|
182
|
+
"""Return the Base64-encoded cursor for a 1-indexed stargazer position.
|
|
183
|
+
|
|
184
|
+
GitHub's GraphQL cursors decode to ``cursor:<N>`` where N is 0-indexed.
|
|
185
|
+
Constructing them lets us sample any offset within a repo's stargazer
|
|
186
|
+
list, bypassing both the REST 400-page cap and the cost of walking
|
|
187
|
+
every page via cursor pagination.
|
|
188
|
+
|
|
189
|
+
Returns None for ``offset <= 1`` (no cursor needed — this IS the first
|
|
190
|
+
entry; passing ``after: None`` to GraphQL starts from the beginning).
|
|
191
|
+
"""
|
|
192
|
+
if offset <= 1:
|
|
193
|
+
return None
|
|
194
|
+
return base64.b64encode(f"cursor:{offset - 1}".encode()).decode()
|
|
195
|
+
|
|
147
196
|
|
|
148
197
|
async def fetch_stargazer_history(
|
|
149
198
|
owner: str,
|
|
@@ -152,19 +201,14 @@ async def fetch_stargazer_history(
|
|
|
152
201
|
) -> dict[str, Any]:
|
|
153
202
|
"""Fetch sampled star history for ``owner/repo`` as cumulative data points.
|
|
154
203
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
time marker for the cumulative count at ``(page - 1) * 30``.
|
|
164
|
-
5. Cache under ``github:{owner}/{repo}:stargazer-history``.
|
|
165
|
-
|
|
166
|
-
Returns a dict with keys ``points`` (list of ``{date, count}``), ``current_stars``,
|
|
167
|
-
``repo``, ``ttl``. Raises ``ValueError`` on invalid identifier.
|
|
204
|
+
Public dispatcher. Prefers GraphQL when a token is available (unlocks the
|
|
205
|
+
recent-growth window on mega-repos beyond GitHub's 400-page REST cap);
|
|
206
|
+
falls back to REST sampling on GraphQL failure or missing token. Caches
|
|
207
|
+
successful results under ``github:{owner}/{repo}:stargazer-history``.
|
|
208
|
+
|
|
209
|
+
Returns a dict with keys ``points`` (list of ``{date, count}``),
|
|
210
|
+
``current_stars``, ``repo``, ``ttl``. Raises ``ValueError`` on invalid
|
|
211
|
+
identifier.
|
|
168
212
|
"""
|
|
169
213
|
if not owner or not repo:
|
|
170
214
|
raise ValueError("fetch_stargazer_history requires owner and repo")
|
|
@@ -176,25 +220,171 @@ async def fetch_stargazer_history(
|
|
|
176
220
|
if cached is not None:
|
|
177
221
|
return cached # type: ignore[no-any-return]
|
|
178
222
|
|
|
223
|
+
result: dict[str, Any] | None = None
|
|
224
|
+
|
|
225
|
+
# Primary path: GraphQL cursor-offset sampling (requires auth). Constructs
|
|
226
|
+
# cursors for N evenly-distributed stargazer offsets across [1, total_stars]
|
|
227
|
+
# and fetches one stargazer per offset. Bypasses GitHub's 400-page REST cap,
|
|
228
|
+
# so a 361K-star repo gets 12 real points across its full lifetime instead
|
|
229
|
+
# of a hockey-stick or flat line.
|
|
230
|
+
if _get_github_token():
|
|
231
|
+
try:
|
|
232
|
+
result = await _fetch_stargazer_history_graphql(owner, repo, sample_pages)
|
|
233
|
+
except (ConnectorError, CircuitOpenError):
|
|
234
|
+
result = None # fall through to REST
|
|
235
|
+
|
|
236
|
+
if result is None:
|
|
237
|
+
# Fallback path: REST sampling. Works unauth at 60 req/hr but caps at
|
|
238
|
+
# ~40k stars on mega-repos (GitHub's deep-pagination wall).
|
|
239
|
+
result = await _fetch_stargazer_history_rest(owner, repo, sample_pages)
|
|
240
|
+
|
|
241
|
+
cache.set(cache_key, result, STARGAZER_HISTORY_TTL)
|
|
242
|
+
return result
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
async def _fetch_stargazer_history_graphql(
|
|
246
|
+
owner: str,
|
|
247
|
+
repo: str,
|
|
248
|
+
sample_count: int = _DEFAULT_SAMPLE_COUNT,
|
|
249
|
+
) -> dict[str, Any]:
|
|
250
|
+
"""Sample N stargazer timestamps at evenly-distributed cursor offsets.
|
|
251
|
+
|
|
252
|
+
Strategy: given a repo's ``total_stars``, fetch one stargazer at each of
|
|
253
|
+
N evenly-spaced offsets across ``[1, total_stars]``. Each offset maps to
|
|
254
|
+
a constructed cursor (``base64("cursor:<N-1>")``); GraphQL returns that
|
|
255
|
+
stargazer's ``starred_at``. Pair with the known offset to form a
|
|
256
|
+
``{date, count}`` point. Appends a now-point at ``total_stars``.
|
|
257
|
+
|
|
258
|
+
Works for any repo size — 30 stars, 2938 stars, 361k stars — because
|
|
259
|
+
cursor-offset sampling is not subject to GitHub's 400-page REST cap.
|
|
260
|
+
|
|
261
|
+
Concurrency is bounded at ``_GRAPHQL_CONCURRENCY`` to avoid tripping
|
|
262
|
+
per-minute abuse-detection heuristics on bursty parallel fan-out.
|
|
263
|
+
"""
|
|
264
|
+
identifier = f"{owner}/{repo}"
|
|
265
|
+
|
|
266
|
+
# First query: learn total_stars and pick up the first stargazer's
|
|
267
|
+
# timestamp in the same round-trip (offset 1 uses no cursor anyway).
|
|
268
|
+
first_response = await fetch_graphql(
|
|
269
|
+
_CURSOR_OFFSET_QUERY,
|
|
270
|
+
{"owner": owner, "repo": repo, "cursor": None},
|
|
271
|
+
provider=PROVIDER,
|
|
272
|
+
)
|
|
273
|
+
repo_data = (first_response.get("data") or {}).get("repository")
|
|
274
|
+
if not repo_data:
|
|
275
|
+
return _empty_stargazer_result(identifier)
|
|
276
|
+
|
|
277
|
+
total_stars = int(repo_data.get("stargazerCount", 0))
|
|
278
|
+
if total_stars == 0:
|
|
279
|
+
return _empty_stargazer_result(identifier)
|
|
280
|
+
|
|
281
|
+
first_edges = (repo_data.get("stargazers") or {}).get("edges") or []
|
|
282
|
+
first_starred_at: str | None = None
|
|
283
|
+
if first_edges and isinstance(first_edges[0], dict):
|
|
284
|
+
first_starred_at = first_edges[0].get("starredAt")
|
|
285
|
+
|
|
286
|
+
# Clamp sample count to total_stars — small repos get every star sampled
|
|
287
|
+
# (no point requesting 12 offsets from a 5-star repo).
|
|
288
|
+
effective_samples = min(sample_count, total_stars)
|
|
289
|
+
|
|
290
|
+
# Compute evenly-distributed offsets across the full stargazer list.
|
|
291
|
+
# Always includes position 1 (oldest) and position total_stars (newest).
|
|
292
|
+
if effective_samples <= 1:
|
|
293
|
+
offsets = [1]
|
|
294
|
+
else:
|
|
295
|
+
step = (total_stars - 1) / (effective_samples - 1)
|
|
296
|
+
offsets = sorted({max(1, round(1 + step * i)) for i in range(effective_samples)})
|
|
297
|
+
|
|
298
|
+
# Bounded concurrent fan-out: semaphore throttles to _GRAPHQL_CONCURRENCY
|
|
299
|
+
# in-flight requests at a time. For 12 offsets at concurrency 4, that's
|
|
300
|
+
# 3 waves of ≤4 calls each → ~1s cold-fetch latency.
|
|
301
|
+
sem = asyncio.Semaphore(_GRAPHQL_CONCURRENCY)
|
|
302
|
+
|
|
303
|
+
async def _fetch_one(offset: int) -> tuple[int, str | None]:
|
|
304
|
+
# Offset 1 is already in hand from the total_stars lookup.
|
|
305
|
+
if offset == 1 and first_starred_at is not None:
|
|
306
|
+
return offset, first_starred_at
|
|
307
|
+
cursor = _cursor_for_offset(offset)
|
|
308
|
+
async with sem:
|
|
309
|
+
resp = await fetch_graphql(
|
|
310
|
+
_CURSOR_OFFSET_QUERY,
|
|
311
|
+
{"owner": owner, "repo": repo, "cursor": cursor},
|
|
312
|
+
provider=PROVIDER,
|
|
313
|
+
)
|
|
314
|
+
edges_data = (((resp.get("data") or {}).get("repository") or {}).get("stargazers") or {}).get("edges") or []
|
|
315
|
+
starred_at: str | None = None
|
|
316
|
+
if edges_data and isinstance(edges_data[0], dict):
|
|
317
|
+
starred_at = edges_data[0].get("starredAt")
|
|
318
|
+
return offset, starred_at
|
|
319
|
+
|
|
320
|
+
results = await asyncio.gather(
|
|
321
|
+
*[_fetch_one(off) for off in offsets],
|
|
322
|
+
return_exceptions=True,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
points: list[dict[str, Any]] = []
|
|
326
|
+
for item in results:
|
|
327
|
+
if isinstance(item, BaseException):
|
|
328
|
+
continue
|
|
329
|
+
offset, starred_at = item
|
|
330
|
+
if starred_at is None:
|
|
331
|
+
continue
|
|
332
|
+
points.append({"date": starred_at, "count": offset})
|
|
333
|
+
|
|
334
|
+
# Append the honest now-point: current timestamp at the real total count.
|
|
335
|
+
if points:
|
|
336
|
+
points.append({"date": datetime.now(UTC).isoformat(), "count": total_stars})
|
|
337
|
+
points.sort(key=lambda p: p["date"])
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
"points": points,
|
|
341
|
+
"current_stars": total_stars,
|
|
342
|
+
"repo": identifier,
|
|
343
|
+
"ttl": STARGAZER_HISTORY_TTL,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _empty_stargazer_result(identifier: str) -> dict[str, Any]:
|
|
348
|
+
"""Shared empty-state shape for zero-star or missing-repo cases."""
|
|
349
|
+
return {
|
|
350
|
+
"points": [],
|
|
351
|
+
"current_stars": 0,
|
|
352
|
+
"repo": identifier,
|
|
353
|
+
"ttl": STARGAZER_HISTORY_TTL,
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
async def _fetch_stargazer_history_rest(
|
|
358
|
+
owner: str,
|
|
359
|
+
repo: str,
|
|
360
|
+
sample_pages: int = 12,
|
|
361
|
+
) -> dict[str, Any]:
|
|
362
|
+
"""REST-based stargazer sampling — the v0.2.7 behavior preserved as fallback.
|
|
363
|
+
|
|
364
|
+
Used when no ``HW_GITHUB_TOKENS`` is set or when the GraphQL path fails.
|
|
365
|
+
Samples evenly across pages ``[1, min(total_pages, 400)]`` and stamps the
|
|
366
|
+
now-point with the current UTC timestamp. Bounded by GitHub's 400-page
|
|
367
|
+
deep-pagination cap: produces a truthful but limited view of mega-repos.
|
|
368
|
+
"""
|
|
369
|
+
identifier = f"{owner}/{repo}"
|
|
370
|
+
|
|
179
371
|
# Step 1: total stars
|
|
180
372
|
repo_url = f"https://api.github.com/repos/{identifier}"
|
|
181
373
|
repo_data = await fetch_json(repo_url, provider=PROVIDER)
|
|
182
374
|
total_stars = int(repo_data.get("stargazers_count", 0))
|
|
183
375
|
if total_stars == 0:
|
|
184
|
-
|
|
376
|
+
return {
|
|
185
377
|
"points": [],
|
|
186
378
|
"current_stars": 0,
|
|
187
379
|
"repo": identifier,
|
|
188
380
|
"ttl": STARGAZER_HISTORY_TTL,
|
|
189
381
|
}
|
|
190
|
-
cache.set(cache_key, empty_result, STARGAZER_HISTORY_TTL)
|
|
191
|
-
return empty_result
|
|
192
382
|
|
|
193
383
|
# Step 2: compute total pages (clamped at GitHub's deep-pagination cap)
|
|
194
384
|
total_pages = max(1, math.ceil(total_stars / _STARGAZER_PAGE_SIZE))
|
|
195
385
|
effective_pages = min(total_pages, _STARGAZER_PAGE_CAP)
|
|
196
386
|
|
|
197
|
-
# Single-page case: repo has ≤
|
|
387
|
+
# Single-page case: repo has ≤ 100 stars. The "first starred_at of the page"
|
|
198
388
|
# sampling trick would otherwise collapse to a single aggregated point plus
|
|
199
389
|
# a duplicate-date "now" point, producing a zero time-range polyline. Use
|
|
200
390
|
# each stargazer's own timestamp instead — the whole page fits in one call.
|
|
@@ -210,14 +400,12 @@ async def fetch_stargazer_history(
|
|
|
210
400
|
for idx, entry in enumerate(page_payload):
|
|
211
401
|
if isinstance(entry, dict) and entry.get("starred_at"):
|
|
212
402
|
single_page_points.append({"date": entry["starred_at"], "count": idx + 1})
|
|
213
|
-
|
|
403
|
+
return {
|
|
214
404
|
"points": single_page_points,
|
|
215
405
|
"current_stars": total_stars,
|
|
216
406
|
"repo": identifier,
|
|
217
407
|
"ttl": STARGAZER_HISTORY_TTL,
|
|
218
408
|
}
|
|
219
|
-
cache.set(cache_key, single_page_result, STARGAZER_HISTORY_TTL)
|
|
220
|
-
return single_page_result
|
|
221
409
|
|
|
222
410
|
# Cap the sample count at the number of reachable pages.
|
|
223
411
|
sample_count = min(sample_pages, effective_pages)
|
|
@@ -264,14 +452,12 @@ async def fetch_stargazer_history(
|
|
|
264
452
|
points.append({"date": datetime.now(UTC).isoformat(), "count": total_stars})
|
|
265
453
|
points.sort(key=lambda p: p["date"])
|
|
266
454
|
|
|
267
|
-
|
|
455
|
+
return {
|
|
268
456
|
"points": points,
|
|
269
457
|
"current_stars": total_stars,
|
|
270
458
|
"repo": identifier,
|
|
271
459
|
"ttl": STARGAZER_HISTORY_TTL,
|
|
272
460
|
}
|
|
273
|
-
cache.set(cache_key, result, STARGAZER_HISTORY_TTL)
|
|
274
|
-
return result
|
|
275
461
|
|
|
276
462
|
|
|
277
463
|
# ── Session 2A+2B: contribution calendar scraping ──────────────────────────
|