{"id":48894663,"url":"https://github.com/phlx0/postmortem","last_synced_at":"2026-04-16T10:06:22.591Z","repository":{"id":342904274,"uuid":"1175490814","full_name":"phlx0/postmortem","owner":"phlx0","description":"Incident Timeline Builder from Git + Logs","archived":false,"fork":false,"pushed_at":"2026-03-07T22:45:32.000Z","size":91,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-08T04:10:19.232Z","etag":null,"topics":["alerts","automation","cli","devops","error-tracking","git","incident-management","microservice","monitoring","observability","open-source","postmortem","productivity","python","sentry","site-reliability-engineering"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phlx0.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-07T19:31:29.000Z","updated_at":"2026-03-07T22:45:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/phlx0/postmortem","commit_stats":null,"previous_names":["phlx0/postmortem"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/phlx0/postmortem","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phlx0%2Fpostmortem","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phlx0%2Fpostmortem/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phlx0%2Fpostmortem/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phlx0%2Fpostmortem/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phlx0","download_url":"https://codeload.github.com/phlx0/postmortem/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phlx0%2Fpostmortem/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31880935,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-16T09:23:21.276Z","status":"ssl_error","status_checked_at":"2026-04-16T09:23:15.028Z","response_time":69,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alerts","automation","cli","devops","error-tracking","git","incident-management","microservice","monitoring","observability","open-source","postmortem","productivity","python","sentry","site-reliability-engineering"],"created_at":"2026-04-16T10:06:22.499Z","updated_at":"2026-04-16T10:06:22.572Z","avatar_url":"https://github.com/phlx0.png","language":"Python","readme":"\u003cdiv align=\"center\"\u003e\n\n# 🔍 postmortem\n\n**When production breaks, stop guessing. Start knowing.**\n\n[![CI](https://img.shields.io/github/actions/workflow/status/phlx0/postmortem/ci.yml?style=flat-square\u0026label=CI)](https://github.com/phlx0/postmortem/actions)\n[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue?style=flat-square\u0026logo=python\u0026logoColor=white)](https://python.org)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)\n\n_Stitches together git history, file hotspots, and Sentry errors  \ninto a shareable incident report — in seconds._\n\n```bash\npostmortem --since 2h --output markdown --out-file incident.md\n```\n\n\u003c/div\u003e\n\n---\n\n## Why postmortem?\n\nProduction is down. You need to know what changed and when — fast.\n\nThat means opening GitHub, scanning commits, cross-referencing deploy times, and asking teammates \"did anyone push anything?\" — all while the clock is ticking.\n\npostmortem does it in one command.\n\n```\n────────────────────────────────────────────────────────────────────────\n  🔍  postmortem  ·  my-api\n  Since 2h  ·  19:58 UTC\n────────────────────────────────────────────────────────────────────────\n\n  🔥  File Hotspots\n\n  ■■■  payments.py     changed 4x  risk 94%\n       coupled: db.py  utils.py\n  ■■  db.py            changed 3x  risk 71%\n\n  ── Sat 07 Mar 2026 ──\n\n  17:58  ⚠  [ERROR] NullPointerException in PaymentService.charge()  ← Sentry\n             payments-api  ·  142 occurrences\n\n  18:21  ●  fix: patch null pointer in payment handler  [3a7f2b1]  ← alice\n             ↳ src/payments/handler.py\n             ↳ tests/test_payments.py\n\n  19:08  ⇄  Merge branch 'feature/stripe-v3' into main  [9c1e4fd]  ← bob\n             ↳ src/stripe/client.py  ↳ src/stripe/webhooks.py  ↳ … +4\n\n────────────────────────────────────────────────────────────────────────\n  6 events  ·  2 authors  ·  1 Sentry error  ·  top hotspot: payments.py\n```\n\n---\n\n## Installation\n\npostmortem installs into an isolated virtualenv at `~/.postmortem` and adds itself to your `$PATH` automatically. Open a new terminal and you're ready.\n\n**Linux / macOS**\n\n```bash\ncurl -sSL https://raw.githubusercontent.com/phlx0/postmortem/main/install.sh | bash\n```\n\n**Windows (PowerShell)**\n\n```powershell\nirm https://raw.githubusercontent.com/phlx0/postmortem/main/install.ps1 | iex\n```\n\n**From source** (edits apply instantly — no reinstall needed)\n\n```bash\ngit clone https://github.com/phlx0/postmortem\ncd postmortem\nbash install.sh       # or: .\\install.ps1 on Windows\n```\n\n**Requires:** Python 3.11+, git\n\n```bash\npostmortem --version   # verify install\nbash install.sh --uninstall   # remove cleanly\n```\n\n---\n\n## Commands\n\n### Basic usage\n\n```bash\npostmortem                                    # last 2 hours, current repo\npostmortem --since 30m                        # last 30 minutes\npostmortem --since 1d                         # last day\npostmortem --since 4h --repo /path/to/repo    # different repo\n```\n\n### Generate a shareable report\n\n```bash\npostmortem --since 2h --output markdown --out-file incident.md\n```\n\nPaste directly into a GitHub issue or Slack. The report includes a TL;DR table, file hotspot rankings, Sentry errors, and the full commit timeline with collapsible file diffs.\n\n### Sentry integration\n\n```bash\nexport SENTRY_TOKEN=sntrys_...\nexport SENTRY_ORG=my-org\nexport SENTRY_PROJECT=api         # optional — searches all projects if omitted\n\npostmortem --since 2h\n```\n\nOr pass inline:\n\n```bash\npostmortem --since 2h --sentry-org my-org --sentry-token sntrys_...\n```\n\n### All flags\n\n| Flag               | Default           | Description                                   |\n| ------------------ | ----------------- | --------------------------------------------- |\n| `--since`, `-s`    | `2h`              | How far back to look: `30m`, `2h`, `1d`, `1w` |\n| `--repo`, `-r`     | `.`               | Path to a git repository                      |\n| `--output`, `-o`   | `terminal`        | Output format: `terminal` or `markdown`       |\n| `--out-file`, `-f` | stdout            | Write output to a file                        |\n| `--no-color`       | false             | Disable ANSI colours                          |\n| `--sentry-token`   | `$SENTRY_TOKEN`   | Sentry auth token                             |\n| `--sentry-org`     | `$SENTRY_ORG`     | Sentry organisation slug                      |\n| `--sentry-project` | `$SENTRY_PROJECT` | Sentry project slug                           |\n| `--version`, `-v`  |                   | Show version and exit                         |\n\n---\n\n## What's in the report\n\n### 🔥 File hotspots\n\nPure git analysis — no config needed. Ranks every file touched during the window by:\n\n- **Change frequency** — how many times it was modified\n- **Recency** — changes in the last 25% of the window score higher\n- **Coupling** — files that always change together are a hidden coordination risk\n\nThe coupling column is often the most useful: if `payments.py` and `db.py` consistently appear in the same commits but aren't directly imported by each other, that's a hidden dependency worth knowing about.\n\n### 🔴 Sentry errors\n\nSurfaces issues whose _last seen_ time falls inside the incident window. Requires a read-scope auth token — see [Sentry setup](#sentry-integration) above.\n\n### 📋 Git timeline\n\nCommits, merges, and tags in chronological order, each with author, SHA, and the list of files changed. Merges are labelled separately so you can spot integration points at a glance.\n\n---\n\n## Project structure\n\n```\npostmortem/\n├── cli.py              Click entry point — stays thin\n├── pipeline.py         Wires collectors → Timeline\n├── models.py           Event, Timeline, HotspotFile — pure data\n├── collectors/\n│   ├── __init__.py     BaseCollector ABC\n│   ├── git.py          Commits, merges, tags\n│   ├── hotspot.py      File frequency + coupling analysis\n│   └── sentry.py       Sentry Issues API\n├── renderers/\n│   ├── __init__.py     BaseRenderer ABC\n│   ├── terminal.py     ANSI terminal output\n│   └── markdown.py     GitHub-flavoured incident report\n└── utils/\n    └── time.py         \"2h\" → datetime\n```\n\n### Adding a collector\n\n```python\n# postmortem/collectors/datadog.py\nfrom postmortem.collectors import BaseCollector\nfrom postmortem.models import Event, EventKind\n\nclass DatadogCollector(BaseCollector):\n    def collect(self) -\u003e list[Event]:\n        # hit the Datadog API, return Events\n        ...\n```\n\nRegister it in `pipeline.py`. That's it.\n\n### Planned collectors\n\n- [ ] GitHub Actions — CI run pass/fail per commit\n- [ ] Datadog / Grafana — annotation and alert events\n- [ ] PagerDuty — on-call alerts in the window\n- [ ] Heroku / Railway — deploy events\n\nPRs welcome.\n\n---\n\n## Development\n\n```bash\ngit clone https://github.com/phlx0/postmortem\ncd postmortem\nbash install.sh          # editable install — changes apply immediately\n\npytest                   # run all tests\nruff check postmortem    # lint\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide.\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\nMade with ☕ · [MIT License](LICENSE)\n\n\u003c/div\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphlx0%2Fpostmortem","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphlx0%2Fpostmortem","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphlx0%2Fpostmortem/lists"}