stackvitals 0.1.0__py3-none-any.whl
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.
- mergescout/__init__.py +1 -0
- mergescout/archive/__init__.py +0 -0
- mergescout/archive/__main__.py +30 -0
- mergescout/archive/convert.py +83 -0
- mergescout/archive/events.py +55 -0
- mergescout/archive/manifest.py +45 -0
- mergescout/archive/stream.py +83 -0
- mergescout/bench/__init__.py +1 -0
- mergescout/bench/__main__.py +135 -0
- mergescout/bench/ci.py +257 -0
- mergescout/bench/deploys.py +115 -0
- mergescout/bench/matched.py +22 -0
- mergescout/bench/prior.py +42 -0
- mergescout/bench/report.py +230 -0
- mergescout/bench/shared.py +112 -0
- mergescout/bench/stars.py +57 -0
- mergescout/cli.py +89 -0
- mergescout/config.py +35 -0
- mergescout/crawl/__init__.py +0 -0
- mergescout/crawl/__main__.py +97 -0
- mergescout/crawl/chain.py +34 -0
- mergescout/crawl/fetch.py +146 -0
- mergescout/crawl/outcomes.py +69 -0
- mergescout/crawl/queries.py +250 -0
- mergescout/crawl/runner.py +176 -0
- mergescout/crawl/seed.py +77 -0
- mergescout/crawl/state.py +169 -0
- mergescout/crawl/writer.py +87 -0
- mergescout/discover/__init__.py +0 -0
- mergescout/discover/__main__.py +58 -0
- mergescout/discover/sweep.py +286 -0
- mergescout/github/__init__.py +0 -0
- mergescout/github/graphql.py +202 -0
- mergescout/github/identities.py +35 -0
- mergescout/github/releases.py +137 -0
- mergescout/labels.py +20 -0
- mergescout/mcp_server.py +228 -0
- mergescout/measure/__init__.py +0 -0
- mergescout/measure/__main__.py +75 -0
- mergescout/measure/api_cost.py +91 -0
- mergescout/measure/count_repos.py +30 -0
- mergescout/measure/jsonl.py +14 -0
- mergescout/measure/report.py +56 -0
- mergescout/measure/sample.py +37 -0
- mergescout/owners.py +22 -0
- mergescout/setup_check.py +64 -0
- mergescout/signals/__init__.py +0 -0
- mergescout/signals/__main__.py +96 -0
- mergescout/signals/bump_ledger.py +258 -0
- mergescout/signals/bumps.py +104 -0
- mergescout/signals/check.py +141 -0
- mergescout/signals/community_bench.py +149 -0
- mergescout/signals/delivery_bench.py +103 -0
- mergescout/signals/deps_map.py +261 -0
- mergescout/signals/deps_trends.py +117 -0
- mergescout/signals/evidence.py +94 -0
- mergescout/signals/exploits.py +43 -0
- mergescout/signals/flow_bench.py +258 -0
- mergescout/signals/identity.py +73 -0
- mergescout/signals/issues.py +76 -0
- mergescout/signals/labels.py +79 -0
- mergescout/signals/osv.py +232 -0
- mergescout/signals/owners_watch.py +80 -0
- mergescout/signals/repo_stats.py +96 -0
- mergescout/signals/rules.py +70 -0
- mergescout/signals/silver.py +33 -0
- mergescout/signals/top_packages.py +26 -0
- mergescout/signals/triage.py +71 -0
- mergescout/signals/triage_math.py +120 -0
- mergescout/signals/walls.py +91 -0
- mergescout/snapshot/__init__.py +0 -0
- mergescout/snapshot/__main__.py +215 -0
- mergescout/snapshot/build.py +147 -0
- mergescout/snapshot/publish.py +70 -0
- mergescout/snapshot/schema.py +34 -0
- mergescout/snapshot/score.py +30 -0
- mergescout/store/__init__.py +0 -0
- mergescout/store/__main__.py +46 -0
- mergescout/store/sync.py +148 -0
- mergescout/timeutil.py +11 -0
- mergescout/tool/__init__.py +0 -0
- mergescout/tool/action_pack.py +62 -0
- mergescout/tool/live.py +201 -0
- mergescout/tool/queries.py +293 -0
- mergescout/tool/refs.py +31 -0
- mergescout/tool/store.py +129 -0
- mergescout/tool/tracking.py +83 -0
- stackvitals/__init__.py +2 -0
- stackvitals/__main__.py +4 -0
- stackvitals/ci/__init__.py +1 -0
- stackvitals/ci/action.py +574 -0
- stackvitals/ci/analyze.py +577 -0
- stackvitals/ci/api.py +218 -0
- stackvitals/ci/automation.py +212 -0
- stackvitals/ci/bench.py +195 -0
- stackvitals/ci/burnin.py +102 -0
- stackvitals/ci/cli.py +570 -0
- stackvitals/ci/collect.py +239 -0
- stackvitals/ci/cost.py +107 -0
- stackvitals/ci/coverage.py +155 -0
- stackvitals/ci/feedback.py +699 -0
- stackvitals/ci/fingerprint.py +220 -0
- stackvitals/ci/gitlab.py +121 -0
- stackvitals/ci/gitlab_action.py +125 -0
- stackvitals/ci/impact.py +125 -0
- stackvitals/ci/investigate.py +59 -0
- stackvitals/ci/kinds.py +52 -0
- stackvitals/ci/monitors.py +105 -0
- stackvitals/ci/owners.py +116 -0
- stackvitals/ci/quarantine_runtime.py +90 -0
- stackvitals/ci/report.py +435 -0
- stackvitals/ci/retry.py +67 -0
- stackvitals/ci/selfheal.py +102 -0
- stackvitals/ci/shards.py +120 -0
- stackvitals/ci/share.py +59 -0
- stackvitals/ci/store.py +93 -0
- stackvitals/ci/testreports.py +351 -0
- stackvitals/ci/tickets.py +106 -0
- stackvitals/ci/track_record.json +11 -0
- stackvitals/ci/triage.py +130 -0
- stackvitals/ci/triggers.py +147 -0
- stackvitals/ci/upload.py +85 -0
- stackvitals/ci/validate.py +91 -0
- stackvitals/cli.py +111 -0
- stackvitals/codeowners.py +69 -0
- stackvitals/community/__init__.py +0 -0
- stackvitals/community/action.py +140 -0
- stackvitals/community/affiliation.py +51 -0
- stackvitals/community/analyze.py +507 -0
- stackvitals/community/build.py +56 -0
- stackvitals/community/chaoss.py +70 -0
- stackvitals/community/cli.py +79 -0
- stackvitals/community/collect.py +240 -0
- stackvitals/community/context.py +54 -0
- stackvitals/community/report.py +250 -0
- stackvitals/connectors/__init__.py +244 -0
- stackvitals/connectors/ai_spend.py +190 -0
- stackvitals/connectors/base.py +82 -0
- stackvitals/connectors/check.py +686 -0
- stackvitals/connectors/copilot.py +104 -0
- stackvitals/connectors/discord.py +103 -0
- stackvitals/connectors/discourse.py +117 -0
- stackvitals/connectors/incident_io.py +106 -0
- stackvitals/connectors/jira.py +210 -0
- stackvitals/connectors/linear.py +140 -0
- stackvitals/connectors/pagerduty.py +96 -0
- stackvitals/connectors/slack.py +110 -0
- stackvitals/connectors/sonar.py +77 -0
- stackvitals/connectors/store.py +120 -0
- stackvitals/connectors/tracker.py +108 -0
- stackvitals/connectors/writeback.py +105 -0
- stackvitals/contribute/__init__.py +1 -0
- stackvitals/contribute/brief.py +297 -0
- stackvitals/contribute/cli.py +64 -0
- stackvitals/core/__init__.py +1 -0
- stackvitals/core/actors.py +103 -0
- stackvitals/core/agent_ledger.py +120 -0
- stackvitals/core/agreements.py +95 -0
- stackvitals/core/ai_outcomes.py +276 -0
- stackvitals/core/alerts.py +224 -0
- stackvitals/core/appdata.py +112 -0
- stackvitals/core/bucket.py +190 -0
- stackvitals/core/bugs.py +43 -0
- stackvitals/core/calendar.py +34 -0
- stackvitals/core/capex.py +57 -0
- stackvitals/core/change_risk.py +181 -0
- stackvitals/core/changes.py +133 -0
- stackvitals/core/chat.py +143 -0
- stackvitals/core/config.py +373 -0
- stackvitals/core/dataflow.py +168 -0
- stackvitals/core/decisions.py +275 -0
- stackvitals/core/delivery.py +954 -0
- stackvitals/core/delivery_peers.py +98 -0
- stackvitals/core/delivery_report.py +429 -0
- stackvitals/core/deliverycli.py +147 -0
- stackvitals/core/devlake_dora.sql +485 -0
- stackvitals/core/digest.py +333 -0
- stackvitals/core/explaincli.py +107 -0
- stackvitals/core/explore.py +296 -0
- stackvitals/core/export.py +342 -0
- stackvitals/core/exportcli.py +117 -0
- stackvitals/core/findings.py +350 -0
- stackvitals/core/health.py +94 -0
- stackvitals/core/identity.py +82 -0
- stackvitals/core/insights.py +365 -0
- stackvitals/core/insightscli.py +43 -0
- stackvitals/core/installdoc.py +69 -0
- stackvitals/core/investment.py +187 -0
- stackvitals/core/joins.py +43 -0
- stackvitals/core/llmsdoc.py +40 -0
- stackvitals/core/merge_queue.py +70 -0
- stackvitals/core/metrics.py +109 -0
- stackvitals/core/model.py +285 -0
- stackvitals/core/projectcli.py +66 -0
- stackvitals/core/propose.py +86 -0
- stackvitals/core/pulse.py +85 -0
- stackvitals/core/recorded.py +145 -0
- stackvitals/core/releases.py +39 -0
- stackvitals/core/requests.py +528 -0
- stackvitals/core/resync.py +35 -0
- stackvitals/core/schema.py +205 -0
- stackvitals/core/services.py +203 -0
- stackvitals/core/settings.py +96 -0
- stackvitals/core/sqlcli.py +111 -0
- stackvitals/core/stats.py +222 -0
- stackvitals/core/store.py +125 -0
- stackvitals/core/summary.py +104 -0
- stackvitals/core/time.py +29 -0
- stackvitals/core/tomledit.py +149 -0
- stackvitals/core/uicli.py +138 -0
- stackvitals/core/webapp.py +79 -0
- stackvitals/core/what_changed.py +76 -0
- stackvitals/core/workspace.py +194 -0
- stackvitals/deps/__init__.py +1 -0
- stackvitals/deps/action.py +299 -0
- stackvitals/deps/actions_vulns.py +89 -0
- stackvitals/deps/cli.py +77 -0
- stackvitals/deps/dossier.py +341 -0
- stackvitals/deps/evidence.py +44 -0
- stackvitals/deps/fixplan.py +40 -0
- stackvitals/deps/guarddog.py +32 -0
- stackvitals/deps/hygiene.py +47 -0
- stackvitals/deps/imports.py +113 -0
- stackvitals/deps/lockfiles.py +198 -0
- stackvitals/deps/lookalike.py +80 -0
- stackvitals/deps/reach.py +59 -0
- stackvitals/deps/release.py +110 -0
- stackvitals/deps/runtimes.py +161 -0
- stackvitals/deps/supply.py +454 -0
- stackvitals/deps/vulns.py +588 -0
- stackvitals/editors/__init__.py +40 -0
- stackvitals/editors/vscode/extension.js +101 -0
- stackvitals/editors/vscode/marks.js +92 -0
- stackvitals/editors/vscode/package.json +26 -0
- stackvitals/flow/__init__.py +1 -0
- stackvitals/flow/action.py +178 -0
- stackvitals/flow/analyze.py +418 -0
- stackvitals/flow/automate.py +72 -0
- stackvitals/flow/build.py +46 -0
- stackvitals/flow/cli.py +93 -0
- stackvitals/flow/code_hosts.py +146 -0
- stackvitals/flow/collect.py +451 -0
- stackvitals/flow/gitlab_mr.py +75 -0
- stackvitals/flow/knowledge.py +202 -0
- stackvitals/flow/peers.py +90 -0
- stackvitals/flow/preflight.py +210 -0
- stackvitals/flow/report.py +253 -0
- stackvitals/flow/rules.py +135 -0
- stackvitals/flow/store.py +108 -0
- stackvitals/flow/survival.py +84 -0
- stackvitals/flow/triage.py +62 -0
- stackvitals/mcp_server.py +670 -0
- stackvitals/portal/__init__.py +0 -0
- stackvitals/portal/action.py +205 -0
- stackvitals/portal/batch.py +80 -0
- stackvitals/portal/campaigns.py +104 -0
- stackvitals/portal/catalog.py +104 -0
- stackvitals/portal/checks.py +63 -0
- stackvitals/portal/cli.py +172 -0
- stackvitals/portal/descriptor.py +105 -0
- stackvitals/portal/export.py +43 -0
- stackvitals/portal/github.py +48 -0
- stackvitals/portal/golden.py +95 -0
- stackvitals/portal/history.py +30 -0
- stackvitals/portal/inventory.py +264 -0
- stackvitals/portal/owners.py +78 -0
- stackvitals/portal/pages.py +153 -0
- stackvitals/portal/rollup.py +32 -0
- stackvitals/portal/scorecard.py +366 -0
- stackvitals/security/__init__.py +2 -0
- stackvitals/security/probes.py +154 -0
- stackvitals/security/repo_checks.py +132 -0
- stackvitals/security/workflows.py +111 -0
- stackvitals/shell.py +107 -0
- stackvitals/site.py +51 -0
- stackvitals/web/app/LICENSES.md +428 -0
- stackvitals/web/app/boot.js +1 -0
- stackvitals/web/app/chunks/AddRepo-KRPZXWCY.js +1 -0
- stackvitals/web/app/chunks/SetupRunning-XWFQAKRZ.js +1 -0
- stackvitals/web/app/chunks/accuracy-data-OQTDXI7Y.js +1 -0
- stackvitals/web/app/chunks/agents-data-BAL6QAOF.js +11 -0
- stackvitals/web/app/chunks/chunk-24LFKFZ6.js +1 -0
- stackvitals/web/app/chunks/chunk-3G4SQNUC.js +1 -0
- stackvitals/web/app/chunks/chunk-3IXWEOIJ.js +154 -0
- stackvitals/web/app/chunks/chunk-3ROQFHPV.js +1 -0
- stackvitals/web/app/chunks/chunk-45PGAQRO.js +8 -0
- stackvitals/web/app/chunks/chunk-4H53OYLG.js +1 -0
- stackvitals/web/app/chunks/chunk-4LUI7O5P.js +1 -0
- stackvitals/web/app/chunks/chunk-4SQPQ5TB.js +1 -0
- stackvitals/web/app/chunks/chunk-4WKQTWUC.js +2 -0
- stackvitals/web/app/chunks/chunk-576YHDEK.js +8 -0
- stackvitals/web/app/chunks/chunk-6BDBXA6V.js +1 -0
- stackvitals/web/app/chunks/chunk-6RLVD2DT.js +1 -0
- stackvitals/web/app/chunks/chunk-763NH7PP.js +1 -0
- stackvitals/web/app/chunks/chunk-76WAVKUG.js +1 -0
- stackvitals/web/app/chunks/chunk-7II2BD5F.js +1 -0
- stackvitals/web/app/chunks/chunk-7TJCBOAY.js +1 -0
- stackvitals/web/app/chunks/chunk-ASYNLVOP.js +1 -0
- stackvitals/web/app/chunks/chunk-CTKDYZMG.js +1 -0
- stackvitals/web/app/chunks/chunk-CVB26NMC.js +1 -0
- stackvitals/web/app/chunks/chunk-G7NGB5UX.js +1 -0
- stackvitals/web/app/chunks/chunk-GPVLJRZD.js +1 -0
- stackvitals/web/app/chunks/chunk-GQYFN4PE.js +75 -0
- stackvitals/web/app/chunks/chunk-GT4EAKAO.js +2 -0
- stackvitals/web/app/chunks/chunk-IYMBFPCV.js +1 -0
- stackvitals/web/app/chunks/chunk-LAAGCPU3.js +1 -0
- stackvitals/web/app/chunks/chunk-MAPYGK3P.js +1 -0
- stackvitals/web/app/chunks/chunk-P5TXDD45.js +1 -0
- stackvitals/web/app/chunks/chunk-PABICGBL.js +1 -0
- stackvitals/web/app/chunks/chunk-RPXHAG6F.js +1 -0
- stackvitals/web/app/chunks/chunk-RQZPSJ2Y.js +1 -0
- stackvitals/web/app/chunks/chunk-S2ZBMEVA.js +1 -0
- stackvitals/web/app/chunks/chunk-S654BBEP.js +6 -0
- stackvitals/web/app/chunks/chunk-T7QVTNXF.js +1 -0
- stackvitals/web/app/chunks/chunk-THFUOCPC.js +273 -0
- stackvitals/web/app/chunks/chunk-TO4VVRZ7.js +1 -0
- stackvitals/web/app/chunks/chunk-VQ5YXO54.js +1 -0
- stackvitals/web/app/chunks/chunk-VWKSMFWJ.js +1 -0
- stackvitals/web/app/chunks/chunk-Y6LUXDM6.js +1 -0
- stackvitals/web/app/chunks/chunk-YTANGYYH.js +1 -0
- stackvitals/web/app/chunks/connect-KONYWYTT.js +1 -0
- stackvitals/web/app/chunks/connections-data-2DYS4UVW.js +32 -0
- stackvitals/web/app/chunks/logos-N5IINUAM.js +1 -0
- stackvitals/web/app/chunks/more-KMJMJQD3.js +10 -0
- stackvitals/web/app/chunks/more-PC6FVS3P.js +1 -0
- stackvitals/web/app/chunks/more-RCIVCUDE.js +2 -0
- stackvitals/web/app/chunks/more-ZKKRY6NO.css +1 -0
- stackvitals/web/app/chunks/sealbox-35YL4EUH.js +3 -0
- stackvitals/web/app/chunks/session-DYYTOE2K.js +1 -0
- stackvitals/web/app/chunks/settingsApply-2HRJEZYM.js +1 -0
- stackvitals/web/app/chunks/settingsReview-GX2FZQ4M.js +3 -0
- stackvitals/web/app/chunks/uPlot.esm-OIWT3AHJ.js +3 -0
- stackvitals/web/app/demo/ci/impact.json +1 -0
- stackvitals/web/app/demo/ci/quarantine.json +1 -0
- stackvitals/web/app/demo/ci/tickets.json +1 -0
- stackvitals/web/app/demo/mobile/alerts.json +1 -0
- stackvitals/web/app/demo/mobile/app.json +1 -0
- stackvitals/web/app/demo/mobile/community.json +1 -0
- stackvitals/web/app/demo/mobile/connectors.json +1 -0
- stackvitals/web/app/demo/mobile/data.json +1 -0
- stackvitals/web/app/demo/mobile/decisions.json +1 -0
- stackvitals/web/app/demo/mobile/delivery.json +1 -0
- stackvitals/web/app/demo/mobile/deps.json +1 -0
- stackvitals/web/app/demo/mobile/flow.json +1 -0
- stackvitals/web/app/demo/mobile/health.json +1 -0
- stackvitals/web/app/demo/mobile/insights.json +1 -0
- stackvitals/web/app/demo/mobile/pulse.json +1 -0
- stackvitals/web/app/demo/mobile/security.json +1 -0
- stackvitals/web/app/demo/mobile/summary/ci_jobs.jsonl +5 -0
- stackvitals/web/app/demo/mobile/summary/devlake_project_pr_metrics.jsonl +14 -0
- stackvitals/web/app/demo/mobile/summary/devlake_pull_requests.jsonl +14 -0
- stackvitals/web/app/demo/mobile/summary/manifest.json +1 -0
- stackvitals/web/app/demo/mobile/summary/prs.jsonl +19 -0
- stackvitals/web/app/demo/mobile/summary/tests.jsonl +1 -0
- stackvitals/web/app/demo/mobile/supply.json +1 -0
- stackvitals/web/app/demo/mobile/vulns.json +1 -0
- stackvitals/web/app/demo/payments-api/alerts.json +1 -0
- stackvitals/web/app/demo/payments-api/app.json +1 -0
- stackvitals/web/app/demo/payments-api/community.json +1 -0
- stackvitals/web/app/demo/payments-api/connectors.json +1 -0
- stackvitals/web/app/demo/payments-api/data.json +1 -0
- stackvitals/web/app/demo/payments-api/decisions.json +1 -0
- stackvitals/web/app/demo/payments-api/delivery.json +1 -0
- stackvitals/web/app/demo/payments-api/deps.json +1 -0
- stackvitals/web/app/demo/payments-api/flow.json +1 -0
- stackvitals/web/app/demo/payments-api/health.json +1 -0
- stackvitals/web/app/demo/payments-api/insights.json +1 -0
- stackvitals/web/app/demo/payments-api/pulse.json +1 -0
- stackvitals/web/app/demo/payments-api/security.json +1 -0
- stackvitals/web/app/demo/payments-api/summary/ci_jobs.jsonl +5 -0
- stackvitals/web/app/demo/payments-api/summary/devlake_project_pr_metrics.jsonl +14 -0
- stackvitals/web/app/demo/payments-api/summary/devlake_pull_requests.jsonl +14 -0
- stackvitals/web/app/demo/payments-api/summary/manifest.json +1 -0
- stackvitals/web/app/demo/payments-api/summary/prs.jsonl +22 -0
- stackvitals/web/app/demo/payments-api/summary/tests.jsonl +3 -0
- stackvitals/web/app/demo/payments-api/supply.json +1 -0
- stackvitals/web/app/demo/payments-api/vulns.json +1 -0
- stackvitals/web/app/demo/portal.json +1 -0
- stackvitals/web/app/demo/settings.json +1 -0
- stackvitals/web/app/demo/web-app/alerts.json +1 -0
- stackvitals/web/app/demo/web-app/app.json +1 -0
- stackvitals/web/app/demo/web-app/community.json +1 -0
- stackvitals/web/app/demo/web-app/connectors.json +1 -0
- stackvitals/web/app/demo/web-app/data.json +1 -0
- stackvitals/web/app/demo/web-app/decisions.json +1 -0
- stackvitals/web/app/demo/web-app/delivery.json +1 -0
- stackvitals/web/app/demo/web-app/deps.json +1 -0
- stackvitals/web/app/demo/web-app/flow.json +1 -0
- stackvitals/web/app/demo/web-app/health.json +1 -0
- stackvitals/web/app/demo/web-app/insights.json +1 -0
- stackvitals/web/app/demo/web-app/pulse.json +1 -0
- stackvitals/web/app/demo/web-app/security.json +1 -0
- stackvitals/web/app/demo/web-app/summary/ci_jobs.jsonl +5 -0
- stackvitals/web/app/demo/web-app/summary/devlake_project_pr_metrics.jsonl +14 -0
- stackvitals/web/app/demo/web-app/summary/devlake_pull_requests.jsonl +14 -0
- stackvitals/web/app/demo/web-app/summary/manifest.json +1 -0
- stackvitals/web/app/demo/web-app/summary/prs.jsonl +18 -0
- stackvitals/web/app/demo/web-app/summary/tests.jsonl +2 -0
- stackvitals/web/app/demo/web-app/supply.json +1 -0
- stackvitals/web/app/demo/web-app/vulns.json +1 -0
- stackvitals/web/app/fonts/plex-mono-400.woff2 +0 -0
- stackvitals/web/app/fonts/plex-sans-400.woff2 +0 -0
- stackvitals/web/app/fonts/plex-sans-600.woff2 +0 -0
- stackvitals/web/app/fonts/plex-sans-condensed-700.woff2 +0 -0
- stackvitals/web/app/help/accuracy.json +1 -0
- stackvitals/web/app/help/connectors.json +1 -0
- stackvitals/web/app/help/getting-started.json +1 -0
- stackvitals/web/app/help/index.json +1 -0
- stackvitals/web/app/help/modules.json +1 -0
- stackvitals/web/app/help/privacy-and-security.json +1 -0
- stackvitals/web/app/help/reference.json +1 -0
- stackvitals/web/app/main.css +1 -0
- stackvitals/web/app/main.js +1 -0
- stackvitals/web/app/pages/about.css +1 -0
- stackvitals/web/app/pages/about.js +1 -0
- stackvitals/web/app/pages/activity.css +1 -0
- stackvitals/web/app/pages/activity.js +1 -0
- stackvitals/web/app/pages/agents.css +1 -0
- stackvitals/web/app/pages/agents.js +2 -0
- stackvitals/web/app/pages/ci.css +1 -0
- stackvitals/web/app/pages/ci.js +1 -0
- stackvitals/web/app/pages/community.css +1 -0
- stackvitals/web/app/pages/community.js +1 -0
- stackvitals/web/app/pages/connections.css +1 -0
- stackvitals/web/app/pages/connections.js +6 -0
- stackvitals/web/app/pages/delivery.css +1 -0
- stackvitals/web/app/pages/delivery.js +45 -0
- stackvitals/web/app/pages/deps.css +1 -0
- stackvitals/web/app/pages/deps.js +7 -0
- stackvitals/web/app/pages/explore.css +1 -0
- stackvitals/web/app/pages/explore.js +7 -0
- stackvitals/web/app/pages/health.css +1 -0
- stackvitals/web/app/pages/health.js +1 -0
- stackvitals/web/app/pages/help.css +1 -0
- stackvitals/web/app/pages/help.js +6 -0
- stackvitals/web/app/pages/insights.css +1 -0
- stackvitals/web/app/pages/insights.js +1 -0
- stackvitals/web/app/pages/investment.css +1 -0
- stackvitals/web/app/pages/investment.js +3 -0
- stackvitals/web/app/pages/overview.css +1 -0
- stackvitals/web/app/pages/overview.js +1 -0
- stackvitals/web/app/pages/portal.css +1 -0
- stackvitals/web/app/pages/portal.js +51 -0
- stackvitals/web/app/pages/prs.css +1 -0
- stackvitals/web/app/pages/prs.js +4 -0
- stackvitals/web/app/pages/settings.css +1 -0
- stackvitals/web/app/pages/settings.js +1 -0
- stackvitals/web/app/pages/setup.css +1 -0
- stackvitals/web/app/pages/setup.js +2 -0
- stackvitals/web/app/pages/standing.css +1 -0
- stackvitals/web/app/pages/standing.js +1 -0
- stackvitals/web/app/pages/welcome-sheet.js +1 -0
- stackvitals/web/app/pages/welcome.js +1 -0
- stackvitals/web/app/settings-workflow.json +1 -0
- stackvitals/web/index.html +34 -0
- stackvitals/web/site/index.html +108 -0
- stackvitals/web/site/integrations.html +15 -0
- stackvitals/web/site/setup.html +15 -0
- stackvitals/web/site/site.css +68 -0
- stackvitals-0.1.0.dist-info/METADATA +134 -0
- stackvitals-0.1.0.dist-info/RECORD +465 -0
- stackvitals-0.1.0.dist-info/WHEEL +4 -0
- stackvitals-0.1.0.dist-info/entry_points.txt +3 -0
- stackvitals-0.1.0.dist-info/licenses/LICENSE +202 -0
- stackvitals-0.1.0.dist-info/licenses/NOTICE +16 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Deployments and incidents pushed from any tool (Argo CD, Jenkins, a shell script, Opsgenie or Rootly webhooks): a
|
|
2
|
+
`repository_dispatch` event, sent by `stackvitals record` or one curl line, is kept on the data branch, and Delivery
|
|
3
|
+
counts it like GitHub's own. GitHub is the receiver, so no server of ours is involved."""
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
PATH = "delivery/recorded.ndjson"
|
|
9
|
+
EVENTS = {"stackvitals-deployment": "deployment", "stackvitals-incident": "incident"}
|
|
10
|
+
STATES = {"success": "SUCCESS", "failure": "FAILURE", "error": "FAILURE"}
|
|
11
|
+
TIME = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$")
|
|
12
|
+
SHA = re.compile(r"^[0-9a-f]{7,40}$")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _utc(value) -> str | None:
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
if not isinstance(value, str) or not TIME.match(value):
|
|
18
|
+
return None
|
|
19
|
+
return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse(kind: str, payload: dict) -> tuple[dict | None, str | None]:
|
|
23
|
+
"""One record from a dispatch payload, or why it was refused (what to fix, said plainly)."""
|
|
24
|
+
p = payload if isinstance(payload, dict) else {}
|
|
25
|
+
rid = str(p.get("id") or "").strip()[:100]
|
|
26
|
+
if not rid:
|
|
27
|
+
return None, "an id is needed (the same id sent again updates the record)"
|
|
28
|
+
if kind == "deployment":
|
|
29
|
+
finished = _utc(p.get("finished_at") or p.get("at"))
|
|
30
|
+
sha = str(p.get("sha") or "").lower()
|
|
31
|
+
if not finished:
|
|
32
|
+
return None, "finished_at must be a time like 2026-10-29T10:00:00Z"
|
|
33
|
+
if sha and not SHA.match(sha):
|
|
34
|
+
return None, "sha must be a commit id (7 to 40 hex characters)"
|
|
35
|
+
state = STATES.get(str(p.get("status") or "success").lower())
|
|
36
|
+
if not state:
|
|
37
|
+
return None, "status must be success or failure"
|
|
38
|
+
return {"kind": "deployment", "id": rid, "env": str(p.get("environment") or "production")[:60], "sha": sha or None,
|
|
39
|
+
"at": _utc(p.get("started_at")) or finished, "done": finished, "state": state,
|
|
40
|
+
"tool": str(p.get("tool") or "")[:40] or None}, None
|
|
41
|
+
created = _utc(p.get("created_at") or p.get("at"))
|
|
42
|
+
if not created:
|
|
43
|
+
return None, "created_at must be a time like 2026-10-29T10:00:00Z"
|
|
44
|
+
resolved = _utc(p.get("resolved_at")) if p.get("resolved_at") else None
|
|
45
|
+
return {"kind": "incident", "id": rid, "created": created, "resolved": resolved,
|
|
46
|
+
"tool": str(p.get("tool") or "")[:40] or None}, None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def from_event(event_name: str | None, payload: dict | None) -> tuple[dict | None, str | None]:
|
|
50
|
+
"""The record a repository_dispatch event carries; (None, None) for any other event."""
|
|
51
|
+
if event_name != "repository_dispatch" or not payload or payload.get("action") not in EVENTS:
|
|
52
|
+
return None, None
|
|
53
|
+
return parse(EVENTS[payload["action"]], payload.get("client_payload") or {})
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def append(root: Path, rec: dict) -> None:
|
|
57
|
+
path = Path(root) / PATH
|
|
58
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
with path.open("a", encoding="utf-8", newline="\n") as f:
|
|
60
|
+
f.write(json.dumps(rec, sort_keys=True, separators=(",", ":")) + "\n")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def load(root: Path) -> dict:
|
|
64
|
+
"""{"deployments": [...], "incidents": [...]}: the last record sent for each id (a later one updates it)."""
|
|
65
|
+
last = {}
|
|
66
|
+
try:
|
|
67
|
+
lines = (Path(root) / PATH).read_text(encoding="utf-8").splitlines()
|
|
68
|
+
except OSError:
|
|
69
|
+
lines = []
|
|
70
|
+
for line in lines:
|
|
71
|
+
try:
|
|
72
|
+
rec = json.loads(line)
|
|
73
|
+
except ValueError:
|
|
74
|
+
continue
|
|
75
|
+
last[(rec.get("kind"), rec.get("id"))] = rec
|
|
76
|
+
return {"deployments": [r for (k, _), r in last.items() if k == "deployment"],
|
|
77
|
+
"incidents": [r for (k, _), r in last.items() if k == "incident"]}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def incidents(recorded: dict | None) -> list[dict]:
|
|
81
|
+
"""Recorded incidents in the shape Delivery's own incidents take."""
|
|
82
|
+
return [{"source": "recorded", "number": r["id"], "created": r["created"], "resolved": r.get("resolved")}
|
|
83
|
+
for r in (recorded or {}).get("incidents") or []]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def record_step(ws, env: dict, now) -> dict:
|
|
87
|
+
"""The Action's step for a repository_dispatch event: keep the record, or say in the summary why it was refused."""
|
|
88
|
+
payload = None
|
|
89
|
+
try:
|
|
90
|
+
payload = json.loads(Path(env["GITHUB_EVENT_PATH"]).read_text(encoding="utf-8"))
|
|
91
|
+
except (KeyError, OSError, ValueError):
|
|
92
|
+
pass
|
|
93
|
+
rec, why = from_event(env.get("GITHUB_EVENT_NAME"), payload)
|
|
94
|
+
if why:
|
|
95
|
+
return {"recorded": None, "refused": why}
|
|
96
|
+
if rec:
|
|
97
|
+
append(ws.path, {**rec, "received": now.strftime("%Y-%m-%dT%H:%M:%SZ")})
|
|
98
|
+
return {"recorded": rec and rec["kind"], "id": rec and rec["id"]}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def dispatch_body(kind: str, fields: dict) -> dict:
|
|
102
|
+
"""What `stackvitals record` sends to POST /repos/{owner}/{repo}/dispatches."""
|
|
103
|
+
return {"event_type": f"stackvitals-{kind}", "client_payload": {k: v for k, v in fields.items() if v is not None}}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main(argv: list[str] | None = None, post=None) -> int:
|
|
107
|
+
"""`stackvitals record deployment|incident owner/repo ...`: checked here first, then sent as a repository_dispatch."""
|
|
108
|
+
import argparse
|
|
109
|
+
import os
|
|
110
|
+
import sys
|
|
111
|
+
from datetime import datetime, timezone
|
|
112
|
+
ap = argparse.ArgumentParser(prog="stackvitals record", description="Send a deployment or incident from any tool. "
|
|
113
|
+
"The token needs write access to the repo's contents (GitHub's rule for dispatch events).")
|
|
114
|
+
ap.add_argument("kind", choices=["deployment", "incident"])
|
|
115
|
+
ap.add_argument("repo", help="owner/name")
|
|
116
|
+
ap.add_argument("--id", required=True, help="your tool's id; sending the same id again updates the record")
|
|
117
|
+
ap.add_argument("--sha", help="the commit deployed")
|
|
118
|
+
ap.add_argument("--environment", default="production")
|
|
119
|
+
ap.add_argument("--status", default="success", choices=["success", "failure"])
|
|
120
|
+
ap.add_argument("--at", help="when it finished (deployment) or began (incident); default now, UTC")
|
|
121
|
+
ap.add_argument("--resolved-at", help="when the incident ended")
|
|
122
|
+
ap.add_argument("--tool", help="what sent it, e.g. argo-cd, jenkins, opsgenie")
|
|
123
|
+
a = ap.parse_args(argv)
|
|
124
|
+
at = a.at or datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
125
|
+
fields = ({"id": a.id, "sha": a.sha, "environment": a.environment, "status": a.status, "finished_at": at, "tool": a.tool}
|
|
126
|
+
if a.kind == "deployment" else {"id": a.id, "created_at": at, "resolved_at": a.resolved_at, "tool": a.tool})
|
|
127
|
+
_rec, why = parse(a.kind, {k: v for k, v in fields.items() if v is not None})
|
|
128
|
+
if why:
|
|
129
|
+
print(f"Not sent: {why}.", file=sys.stderr)
|
|
130
|
+
return 2
|
|
131
|
+
tok = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
132
|
+
if not tok:
|
|
133
|
+
print("A GitHub token is needed in GITHUB_TOKEN.", file=sys.stderr)
|
|
134
|
+
return 1
|
|
135
|
+
if post is None:
|
|
136
|
+
import httpx
|
|
137
|
+
post = lambda url, body: httpx.post(url, json=body, timeout=30, headers={ # noqa: E731
|
|
138
|
+
"Authorization": f"Bearer {tok}", "Accept": "application/vnd.github+json"}).status_code
|
|
139
|
+
from stackvitals.ci.api import API
|
|
140
|
+
status = post(f"{API}/repos/{a.repo}/dispatches", dispatch_body(a.kind, fields))
|
|
141
|
+
if status != 204:
|
|
142
|
+
print(f"GitHub answered {status}: the token may lack write access to {a.repo}'s contents.", file=sys.stderr)
|
|
143
|
+
return 1
|
|
144
|
+
print(f"Sent: the {a.kind} is kept by the StackVitals workflow and counted from its next scheduled run.")
|
|
145
|
+
return 0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Quality per release (DevLake's release quality dashboard, plus what followed): for each recent release, the pull
|
|
2
|
+
requests it shipped and how many were fixes, then what came after it: reverts of its own pull requests, and incidents
|
|
3
|
+
in the two weeks after it went out."""
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
|
|
6
|
+
from stackvitals.core import investment
|
|
7
|
+
from stackvitals.core.ai_outcomes import is_revert, revert_target
|
|
8
|
+
|
|
9
|
+
LAST = 6
|
|
10
|
+
AFTER_DAYS = 14
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _t(s: str) -> datetime:
|
|
14
|
+
return datetime.fromisoformat(s.replace("Z", "+00:00"))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def quality(prs: list[dict], releases: list[dict], incidents: list[dict], now: datetime, last: int = LAST) -> list[dict] | None:
|
|
18
|
+
"""Newest first: {"tag", "at", "prs", "fixes", "fix_share", "reverted", "incidents_after", "settled"}. A release's PRs
|
|
19
|
+
are the ones merged after the release before it and up to it; "settled" once two weeks have passed."""
|
|
20
|
+
rel = sorted((r for r in releases or [] if r.get("at") and not r.get("pre")), key=lambda r: r["at"])
|
|
21
|
+
if len(rel) < 2:
|
|
22
|
+
return None
|
|
23
|
+
merged = [p for p in prs if p.get("mg")]
|
|
24
|
+
by_number = {p["n"]: p for p in merged}
|
|
25
|
+
reverted = {}
|
|
26
|
+
for p in merged:
|
|
27
|
+
if is_revert(p) and (target := revert_target(p, by_number)) is not None:
|
|
28
|
+
reverted[target] = p["mg"]
|
|
29
|
+
out = []
|
|
30
|
+
for prev, cur in list(zip(rel, rel[1:]))[-last:]:
|
|
31
|
+
mine = [p for p in merged if prev["at"] < p["mg"] <= cur["at"] and not is_revert(p)]
|
|
32
|
+
fixes = sum(investment.classify(p)[0] == "fix" for p in mine)
|
|
33
|
+
end = (_t(cur["at"]) + timedelta(days=AFTER_DAYS)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
34
|
+
out.append({"tag": cur.get("tag"), "at": cur["at"], "prs": len(mine), "fixes": fixes,
|
|
35
|
+
"fix_share": round(fixes / len(mine), 4) if mine else None,
|
|
36
|
+
"reverted": sorted(p["n"] for p in mine if p["n"] in reverted),
|
|
37
|
+
"incidents_after": sum(cur["at"] < (i.get("created") or "") <= end for i in incidents or []),
|
|
38
|
+
"settled": _t(cur["at"]) + timedelta(days=AFTER_DAYS) <= now})
|
|
39
|
+
return list(reversed(out))
|