Skip to content

docs: add post-MVP backlog tasks and milestone - #1

Merged
stephen-cox merged 11 commits into
mainfrom
claude/what-more-can-be-done-ay3ylt
Aug 23, 2026
Merged

stephen-cox merged 11 commits into
mainfrom
claude/what-more-can-be-done-ay3ylt

Conversation

@stephen-cox

Copy link
Copy Markdown
Owner

Add milestone m-1 (Post-MVP Hardening) and TASK-10 through TASK-20
covering reliability fixes, CI gaps, advisory coverage, and feed and
site improvements identified after the MVP shipped.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB

claude added 11 commits August 22, 2026 05:43
Add milestone m-1 (Post-MVP Hardening) and TASK-10 through TASK-20
covering reliability fixes, CI gaps, advisory coverage, and feed and
site improvements identified after the MVP shipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
Add a shared `checks` job running ruff and pytest, which build-index and
update-feed now depend on. Add a pull_request trigger so PRs are checked;
only `checks` runs on a PR, with build-index, update-feed and deploy all
skipping.

Scope permissions to match: top-level drops to contents:read, with write
granted only to the jobs that push and pages/id-token only to deploy.

Fix a pre-existing test failure this exposed: generate_feed emitted en
dashes where the tests and design spec both specify em dashes. The suite
had never run in CI so the drift went unnoticed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
Fetching made one unpaginated request with no timeout and no retries, so
GitHub's default per_page=30 silently truncated the feed for every busy
repo — six of twelve were pinned at exactly 30.

Request per_page=100 and follow the Link rel="next" URL until exhausted,
bounded by MAX_PAGES. Pass an explicit (connect, read) timeout on every
request. Retry 429/5xx and connection errors with exponential backoff over
four attempts, honouring Retry-After; 404 and other client errors fail
immediately rather than backing off.

Add tests/test_integration.py serving the endpoint over real HTTP with real
Link headers, since mocking requests.get stubs out the Link parsing that
pagination depends on. It retrieves 250 advisories across 3 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
A single failing repo propagated out of main()'s loop and aborted the whole
run, so one renamed or private entry in config.yaml silently froze the
published feed.

Extract collect_advisories(), which catches per-repo failures, records them,
and continues. Add logging throughout — per-repo counts, per-repo errors,
and a run summary. main() now returns an exit code, non-zero only when every
repo failed, and returns before writing in that case so the existing feed is
left intact rather than replaced with an empty one.

Zero advisories across succeeding repos stays exit 0 with a warning; the
current config already has a repo that legitimately returns none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
The feed kept everything forever — 233 items and 588 KB back to 2019, all
re-downloaded on every subscriber poll — and retracted advisories never left
it. A null published_at would also raise TypeError while sorting.

Drop advisories with a truthy withdrawn_at (checked truthily, since the API
sends withdrawn_at: null on live ones). Add an optional feed: config section
with max_items and max_age_days, defaulting to 100 items and no age limit so
existing fork configs are unaffected; both apply after sorting so the newest
survive. Guard the sort key so null, missing, or unparseable timestamps sort
last with a warning instead of raising.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
The API returns cve_id, cvss, cwes and per-package version ranges; none of
it reached the feed. Add a metadata block ahead of each advisory body, the
CVE in the item title, and category elements for severity and repo.

Kept as markdown rather than HTML to match the body GitHub already returns,
avoiding a conversion dependency. Every field is optional and inconsistently
shaped, so CVSS falls back through cvss_severities, patched versions are
read from either patched_versions or first_patched_version, and an advisory
with none of them renders as just its body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
main() hardcoded the only source with an if/elif-style guard. Extract the
sources/ package the design spec called for: http.py holds the shared retry,
timeout and pagination plumbing, github.py holds repository advisories, and
__init__.py holds the registry plus the normalised advisory shape every
source must produce.

Sources return a SourceResult rather than a bare list, so each isolates
failures across its own targets and TASK-12's per-repo isolation survives
having several sources. An unsupported source: value now logs an error
naming it instead of being silently skipped.

Extend the CLI with --config, --output, --dry-run and --verbose.

Output-neutral: rendered through the real config with a 274-advisory
fixture, before and after are byte-identical excluding lastBuildDate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
The repository endpoint returns only advisories a project filed in its own
security tab. Anything filed against its published packages by third parties,
ecosystem maintainers or CVE assigners was invisible.

Add sources/ghsa.py querying GET /advisories with ecosystem and affects
filters — REST rather than GraphQL, since it reuses the existing Link-header
pagination unchanged. Normalise the payload by setting the package as the
origin label; the shape otherwise already matches.

Extend dedup beyond GHSA ID: one vulnerability can now arrive from both
sources under different GHSA IDs, so collapse by CVE too, keeping whichever
record scores higher on populated detail. Withdrawn records are filtered
before dedup so a retracted richer record cannot win a CVE and vanish.

A misspelled package returns an empty list rather than a 404, so log a
warning naming it instead of silently contributing nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
The published page listed repo names and linked the feed but showed no
advisories, had no RSS autodiscovery, and was built from a 70-line f-string
with every CSS brace doubled.

Move the markup to templates/index.html rendered with string.Template (no
new dependency). Render each advisory with a severity badge, origin, linked
summary, date, CVE and CVSS, above a last-updated line. Add the alternate
link tag, a dark-mode palette and a mobile breakpoint. All advisory text is
escaped.

Fix two problems this surfaced: --index-only runs on every push and would
have wiped the advisory list, so it now recovers the published set from the
committed feed; and RSS pubDate is RFC 2822, which _published_at could not
parse, so every round-tripped date read as unknown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
The deploy job checked out main and pulled to pick up the commit the build
job had just pushed, racing the content it was meant to publish. Upload
public/ as a Pages artifact from whichever job generated it and reduce
deploy to a single deploy-pages step, so what deploys is what was built.

Add a concurrency group so a manual dispatch overlapping the nightly run
serialises instead of failing on a non-fast-forward push.

Correct CLAUDE.md, which claimed public/feed.xml was gitignored — it is
tracked and committed daily. Keep committing it: --index-only reads it to
rebuild the page without fetching, and it is the fallback if a run fails.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
Nothing was bumping the exact-pinned requirements or the pinned action
versions, in a project whose purpose is tracking vulnerabilities. Add
weekly Dependabot for pip and github-actions, and a CodeQL workflow for
Python on push, PR and weekly.

Add mypy to the CI checks job, scoped to vulnfeed.py and sources/ — running
it over tests produced 45 ElementTree union-attr errors of no diagnostic
value. It found one real issue: get_source passed a possibly-None key to
dict.get. Extend ruff with bugbear and pyupgrade, both already clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Comment thread tests/test_integration.py Dismissed
@stephen-cox
stephen-cox merged commit e50e120 into main Aug 23, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants