docs(ci): auto-generated workflow status dashboard (#3966)

* docs(ci): add auto-generated workflow status dashboard

Adds `docs/ci/workflow-status.md` — a single page that surfaces every
GitHub Actions workflow in the repo, grouped by role, with action items
(disabled / stale / manual-only) at the top. Live status badges link to
each workflow's runs page. Auto-generated from the workflow YAML files +
the GitHub API by `scripts/generate_workflow_status.py`.

Why: the GitHub Actions tab is chronological-mixed (poor "is anything
red right now?" view), and the static workflow table in
`CI_CD_INFRASTRUCTURE.md` drifts when workflows are added/renamed (PR
#3963 fixed three factually wrong header claims for exactly this
reason). A reference page that mechanically reflects current state +
identifies dormant workflows answers both gaps.

What's surfaced today (verified live):
- **Disabled**: `nuclei.yml` (caller commented out in
  `release-gate.yml:177`).
- **Stale**: `update-precommit-hooks.yml` — its weekly Friday cron has
  been **failing for 10+ consecutive weeks** (since at least 2026-03-06).
  This was discovered by the dashboard, not previously tracked.
- **Manual-only**: `check-config-docs.yml`, `sync-main-to-dev.yml`
  (both intentionally manual; the dashboard shows them so they're not
  forgotten).

Generator design notes:
- Resolves reusable workflows correctly: `gh run list --workflow=X.yml`
  is empty for `workflow_call`-only workflows. The script walks the
  call graph (release.yml → release-gate.yml → semgrep.yml etc.),
  fetches the parent run's job list, and matches by **job key** parsed
  from the caller YAML (not by name heuristic — `gitleaks-scan` ↔
  `gitleaks-main.yml` would otherwise collide with `gitleaks.yml`).
- Picks "primary trigger" per workflow so e.g. `codeql.yml` (PR + push +
  cron + workflow_call) gets its glyph from the gated daily run, not a
  stale PR run.
- Stale check walks the *recent* runs list to find last success — a
  workflow that ran red yesterday and green a week ago is not stale.
- Manual edits outside the `<!-- BEGIN/END GENERATED -->` markers are
  preserved on regeneration; the timestamp lives inside the markers so
  post-marker content is fully user-owned.
- Preflights `gh auth status` and rate limit before any per-workflow
  call — fails fast with actionable message instead of partial output.

CI integration:
- `.github/workflows/check-workflow-status.yml` runs
  `--check-structure` on PRs touching workflows, the dashboard, or the
  generator. Pure structural check (no API calls, no live data) — fast
  and deterministic. Live regeneration stays on demand.

Cost: ~340 GitHub API calls per regeneration, ~45 sec wall-clock,
~6.8% of the 5000/hr authenticated quota.

* fixup(ci): review-pass corrections to workflow status dashboard

Surfaced by three rounds of code-review + correctness + security agents
on the original PR. Four small fixes; no behavioral change to the
generated dashboard's content.

1. **Recognize commented job keys** — `JOB_KEY_RE` now accepts an
   optional `# ` prefix. Previously, when an entire job block was
   commented out (e.g. `release-gate.yml:175-181` for nuclei), the
   commented `uses:` line inherited the *previous* active job's key
   (`gitleaks-scan`) instead of the correct `nuclei-scan`. Latent —
   commented entries are filtered out before reaching gated-run lookup
   — but would misattribute status if someone partially uncommented a
   block (uncommented just the `uses:` line).

2. **Pin pyyaml to ==6.0.3** in the CI workflow. The repo convention is
   exact `==` pins (95% of `pip install` calls in workflows); the only
   floating range was the one introduced by this PR. Matches pdm.lock.

3. **Validate marker order** in `merge_with_existing`. If a manual edit
   leaves the BEGIN/END markers reversed (e.g. mid-merge-conflict), bail
   to a clean overwrite instead of splicing interleaved garbage.

4. **Remove `_coerce_jq_stream`** — unused helper left behind from an
   earlier iteration. Zero call sites; no behavior change.

Verified by re-running the generator + `--check-structure`. The
rendered dashboard's only diff vs prior commit is the regeneration
timestamp and live "Last activity" cells (expected — those reflect
recent runs since the previous regen).

* feat(ci): bucketed activity labels + auto-regen on version bump

Two changes that together make the dashboard's diffs meaningful instead
of noisy.

1. **Coarse activity buckets.** Replace exact UTC timestamps in every
   "Last activity / Last manual run / Last successful run" cell with one
   of: `this week`, `last week`, `2 weeks ago`, `3 weeks ago`,
   `last month`, `2 months ago`, `3+ months ago`, `long ago`, `never`.
   Calendar-day boundaries (no time-of-day jitter) so two regenerations
   on the same date produce **zero diff** when nothing actually drifted.
   Verified: same-day re-runs after stable workflow state → empty diff.

   Also drop the redundant `Days idle` columns from Stale and
   Manual-only tables (the bucket label already says it), and round the
   "Last regenerated" footer to a date.

   Why: a daily-running healthy workflow used to bump its timestamp
   every regen (noise). Now it stays in `this week` indefinitely, and
   the only diffs that land in a version-bump PR are real bucket
   transitions — exactly the "this slipped from last week to last month
   — something might be wrong" signal the dashboard exists for.

2. **Auto-regenerate on version bump.** Add a step to `version_check.yml`
   right after the existing `generate_config_docs.py` regen. Same
   pattern as the config docs precedent — the dashboard refresh rides
   along with each version-bump PR and is reviewable in the same diff.

   Costs ~340 GitHub API calls per run (well under the GITHUB_TOKEN
   1000/hr workflow-runs limit). Adds `actions: read` to the job
   permissions block; uses `pyyaml==6.0.3` matching pdm.lock.

* feat(ci): drop regen timestamp; add health banner; fix in-progress false-stale

Three follow-ups to keep version-bump diffs strictly meaningful, plus
two correctness fixes uncovered by repeated stability testing.

1. **Drop the "Last regenerated" date.** Git history is authoritative
   for "when this snapshot was taken"; embedding a date here forced a
   single-line diff every regeneration even when nothing else drifted.

2. **Aggregated health banner** at the top of the generated region:
   `**63 workflows:** 1 disabled · 1 stale · 2 manual-only · 59 active`
   Counts only change when a workflow shifts between
   {disabled, stale, manual, active} — same level of diff-stability as
   the per-row buckets.

3. **`?event=schedule` for own-cron workflow badges.** Verified
   effective by SHA-comparing badge bodies for workflows with
   multi-event run history. Makes the badge for e.g. `gitleaks.yml`,
   `fuzz.yml`, `osv-scanner.yml` reflect cron health specifically,
   rather than whichever PR ran last. The runs-page link uses the
   matching `?query=event%3Aschedule` so a click lands on the
   filtered run list.

4. **Fix false-stale during in-flight release runs.** Previously,
   when release.yml was running, gates reachable via release.yml
   (puppeteer-e2e-tests, ci-gate, etc.) would briefly flip to "stale"
   because `fetch_last_gated_run` returned the in-progress run first
   and `last_success` couldn't see past it. Now the function walks
   all 5 caller runs and returns both the latest match (for activity)
   and the latest successful match (for staleness), avoiding the flip.

5. **Map all GitHub conclusion enum values.** A `gitleaks.yml` run
   completed with `action_required` between two test regens; the
   glyph table didn't have it and rendered `?`. Added every
   documented value (`neutral`, `timed_out`, `stale`, `action_required`)
   and changed the unknown-fallback from `?` to em-dash, so future
   GitHub-side enum additions don't introduce a false-positive diff.

Verified: two same-day regens after workflow state has settled now
produce **zero diff**.

* ci(version-bump): make workflow-status regen non-blocking

Add `continue-on-error: true` to the dashboard regeneration step in
version_check.yml. The regen calls ~340 GitHub API endpoints and would
otherwise block the entire version-bump PR if any of them transiently
fail (rate-limit hit, GitHub Actions outage, etc.). The failure mode
should be "dashboard stays at the previous snapshot until next
successful regen", not "release pipeline is blocked".

The sibling `generate_config_docs.py` step doesn't need this — it's
purely local with no external API dependency.
This commit is contained in:
LearningCircuit
2026-05-10 15:58:32 +02:00
committed by GitHub
parent ab1b9fc6b8
commit 91b68acafd
6 changed files with 1375 additions and 1 deletions

View File

@@ -0,0 +1,52 @@
name: Check Workflow Status Dashboard
# Fails when a workflow file is added/renamed without a corresponding row
# in docs/ci/workflow-status.md. Pure structural check — no GitHub API
# calls, no live data — so it runs fast and doesn't need any auth.
#
# To fix a failure: regenerate the dashboard with
# `pdm run python scripts/generate_workflow_status.py`
# This requires `gh` authenticated against the repo. If you can't run it
# locally, ping a maintainer to regenerate, or add a temporary placeholder
# `\`<your-new-workflow>.yml\`` mention in the file's manual-edit region
# to unblock the PR.
on:
pull_request:
paths:
- '.github/workflows/**'
- 'docs/ci/workflow-status.md'
- 'scripts/generate_workflow_status.py'
workflow_dispatch:
permissions:
contents: read
jobs:
check-structure:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Install PyYAML
# Pinned to match pdm.lock; the rest of the repo uses exact
# `==` pins for ad-hoc workflow installs (see e.g.
# validate-image-pinning.yml). Floating `~=` ranges can pick up
# yanked / replaced patch versions silently.
run: pip install pyyaml==6.0.3
- name: Verify dashboard structure
run: python scripts/generate_workflow_status.py --check-structure

View File

@@ -43,6 +43,7 @@ jobs:
permissions:
contents: write
pull-requests: write
actions: read # for generate_workflow_status.py to read run history
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
@@ -125,6 +126,29 @@ jobs:
if: steps.check.outputs.needs_bump != 'false'
run: python scripts/generate_config_docs.py
# Refreshes docs/ci/workflow-status.md so the version-bump PR
# carries the current snapshot. Output uses coarse "last week /
# last month" buckets so within-day reruns produce zero diff —
# the diff that lands in this PR only shows workflows whose
# bucket has actually shifted since the previous release.
#
# ~340 GitHub API calls per run (well under the GITHUB_TOKEN
# 1000/hr workflow-runs limit). Needs `actions: read` on the
# job permissions block above.
- name: Regenerate workflow status dashboard
if: steps.check.outputs.needs_bump != 'false'
# Don't block the version bump if the dashboard refresh fails.
# The regen calls ~340 GitHub API endpoints; a transient outage
# or rate-limit hit would otherwise prevent the version-bump PR
# from being created at all. On failure the dashboard just stays
# at its previous snapshot until the next successful run.
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pip install pyyaml==6.0.3
python scripts/generate_workflow_status.py
- name: Generate PR body
if: steps.check.outputs.needs_bump != 'false'
id: pr_body
@@ -157,7 +181,7 @@ jobs:
fi
echo "Approve and merge to trigger a new release."
echo ""
echo "Configuration docs (docs/CONFIGURATION.md) have been regenerated."
echo "Configuration docs (docs/CONFIGURATION.md) and the workflow status dashboard (docs/ci/workflow-status.md) have been regenerated."
} > "$body_file"
echo "body_file=$body_file" >> "$GITHUB_OUTPUT"

View File

@@ -24,6 +24,8 @@
[![🐳 Docker Publish](https://github.com/LearningCircuit/local-deep-research/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/docker-publish.yml)
[![📦 PyPI Publish](https://github.com/LearningCircuit/local-deep-research/actions/workflows/publish.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/publish.yml)
[All workflow status →](docs/ci/workflow-status.md)
[![Discord](https://img.shields.io/discord/1352043059562680370?style=for-the-badge&logo=discord)](https://discord.gg/ttcqQeFcJ3)
[![Reddit](https://img.shields.io/badge/Reddit-r/LocalDeepResearch-FF4500?style=for-the-badge&logo=reddit)](https://www.reddit.com/r/LocalDeepResearch/)
[![YouTube](https://img.shields.io/badge/YouTube-Channel-red?style=for-the-badge&logo=youtube)](https://www.youtube.com/@local-deep-research)

View File

@@ -6,6 +6,8 @@ This document describes the continuous integration, security scanning, and devel
The project uses many GitHub Actions workflows and 20+ pre-commit hooks to ensure code quality, security, and reliability.
> **At-a-glance health**: see [`docs/ci/workflow-status.md`](ci/workflow-status.md) — an auto-generated dashboard with live badges for every workflow, surfacing disabled, manual-only, and stale (silently-failing) ones at the top. Regenerate with `pdm run python scripts/generate_workflow_status.py`.
```
┌─────────────────────────────────────────────────────────────────┐
│ Developer Workflow │

137
docs/ci/workflow-status.md Normal file
View File

@@ -0,0 +1,137 @@
# Workflow Status
> **Live status of every GitHub Actions workflow in this repo.**
> Auto-generated by [`scripts/generate_workflow_status.py`](../../scripts/generate_workflow_status.py).
> Do not edit between the generated markers — regenerate with
> `pdm run python scripts/generate_workflow_status.py`. Anything outside
> the markers is preserved on regeneration.
## How to read this page
- **Live badges** (right column on active gates) re-render on every page
view and reflect the current head-of-default-branch status from
GitHub. Click one to land on that workflow's runs page.
- The **Status** emoji column captures the run conclusion at generation
time. It survives broken badge rendering (corporate proxies, anonymous
viewers).
- **Disabled** = a caller has the `uses:` line commented out, or the
workflow is disabled in the GitHub UI. **Stale** = scheduled trigger
but no successful run within 2× its cron cadence (and ≥60 days). The
three top sections are the action items.
- Reusable workflows (those triggered only by `workflow_call:`) show
their **gated** run — the most recent run of their parent (release.yml,
release-gate.yml, ci-gate.yml) that included them — not their own
empty direct-run history.
<!-- BEGIN GENERATED -->
**63 workflows:** 1 disabled · 1 stale · 2 manual-only · 59 active
## ⚠ Disabled workflows
| Workflow | Disabled where | Last direct run |
|---|---|---|
| `nuclei.yml` | `release-gate.yml:177` (commented) | never |
## ⚠ Stale (scheduled but no recent successful run)
| Workflow | Cron | Last successful run |
|---|---|---|
| `update-precommit-hooks.yml` | `0 8 * * 5` | never |
## Manual-only by design
| Workflow | Last manual run | Trigger |
|---|---|---|
| `check-config-docs.yml` | 2 months ago | manual |
| `sync-main-to-dev.yml` | 2 months ago | manual |
## Release-blocking gates — daily (release-gate cron 02:00 UTC)
| Workflow | Status | Last activity | Trigger | Live badge |
|---|---|---|---|---|
| `backwards-compatibility.yml` | ✅ | this week | workflow_call, PR, push:main, release, schedule(0 2 * * 0), manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/backwards-compatibility.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/backwards-compatibility.yml) |
| `bearer.yml` | ✅ | this week | manual, workflow_call, schedule(0 4 * * *) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/bearer.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/bearer.yml) |
| `checkov.yml` | ✅ | this week | manual, workflow_call | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/checkov.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/checkov.yml) |
| `codeql.yml` | ✅ | this week | push:main, PR, schedule(45 5 * * 0), workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/codeql.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/codeql.yml) |
| `container-security.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/container-security.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/container-security.yml) |
| `devskim.yml` | ✅ | this week | manual, workflow_call, schedule(0 10 * * *) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/devskim.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/devskim.yml) |
| `docker-multiarch-test.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/docker-multiarch-test.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/docker-multiarch-test.yml) |
| `dockle.yml` | ✅ | this week | manual, workflow_call, schedule(0 10 * * 2) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/dockle.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/dockle.yml) |
| `gitleaks-main.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/gitleaks-main.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/gitleaks-main.yml) |
| `grype.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/grype.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/grype.yml) |
| `hadolint.yml` | ✅ | this week | PR, manual, workflow_call, schedule(0 9 * * 2) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/hadolint.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/hadolint.yml) |
| `journal-data-integration.yml` | ✅ | this week | workflow_call, manual, schedule(0 4 * * 1) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/journal-data-integration.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/journal-data-integration.yml) |
| `npm-audit.yml` | ✅ | this week | manual, workflow_call | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/npm-audit.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/npm-audit.yml) |
| `owasp-zap-scan.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/owasp-zap-scan.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/owasp-zap-scan.yml) |
| `retirejs.yml` | ✅ | this week | manual, workflow_call, schedule(0 4 * * 1) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/retirejs.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/retirejs.yml) |
| `security-headers-validation.yml` | ✅ | this week | workflow_call, schedule(0 3 * * *), manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/security-headers-validation.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/security-headers-validation.yml) |
| `security-tests.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/security-tests.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/security-tests.yml) |
| `semgrep.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/semgrep.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/semgrep.yml) |
| `zizmor-security.yml` | ✅ | this week | manual, workflow_call, schedule(0 9 * * 1) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/zizmor-security.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/zizmor-security.yml) |
## Release gates — release-time only
| Workflow | Status | Last activity | Trigger | Live badge |
|---|---|---|---|---|
| `check-env-vars.yml` | ⊘ | this week | PR, workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/check-env-vars.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/check-env-vars.yml) |
| `ci-gate.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/ci-gate.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/ci-gate.yml) |
| `compose-integration-test.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/compose-integration-test.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/compose-integration-test.yml) |
| `docker-tests.yml` | ⊘ | this week | PR, push:main, workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/docker-tests.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/docker-tests.yml) |
| `file-whitelist-check.yml` | ⊘ | this week | PR, workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/file-whitelist-check.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/file-whitelist-check.yml) |
| `mypy-type-check.yml` | ⊘ | this week | PR, workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/mypy-type-check.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/mypy-type-check.yml) |
| `playwright-webkit-tests.yml` | ✅ | this week | workflow_call, manual, schedule(0 2 * * *) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/playwright-webkit-tests.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/playwright-webkit-tests.yml) |
| `pre-commit.yml` | ⊘ | this week | PR, workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/pre-commit.yml) |
| `puppeteer-e2e-tests.yml` | ✅ | this week | PR, workflow_call, manual, schedule(0 2 * * 0) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/puppeteer-e2e-tests.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/puppeteer-e2e-tests.yml) |
| `responsive-ui-tests-enhanced.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/responsive-ui-tests-enhanced.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/responsive-ui-tests-enhanced.yml) |
| `security-file-write-check.yml` | ⊘ | this week | PR, workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/security-file-write-check.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/security-file-write-check.yml) |
| `validate-image-pinning.yml` | ⊘ | this week | PR, workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/validate-image-pinning.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/validate-image-pinning.yml) |
| `vulture-dead-code.yml` | ✅ | this week | workflow_call, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/vulture-dead-code.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/vulture-dead-code.yml) |
## Scheduled (own cron)
| Workflow | Status | Last activity | Trigger | Live badge |
|---|---|---|---|---|
| `compose-published-smoke.yml` | — | never | manual, schedule(0 5 * * 1) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/compose-published-smoke.yml/badge.svg?event=schedule)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/compose-published-smoke.yml?query=event%3Aschedule) |
| `fuzz.yml` | ✅ | this week | schedule(0 0 * * 0), manual, PR | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/fuzz.yml/badge.svg?event=schedule)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/fuzz.yml?query=event%3Aschedule) |
| `gitleaks.yml` | ✅ | this week | PR, manual, schedule(0 3 * * *) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/gitleaks.yml/badge.svg?event=schedule)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/gitleaks.yml?query=event%3Aschedule) |
| `ossf-scorecard.yml` | ✅ | this week | branch_protection_rule, schedule(0 8 * * 1), manual, push:main | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/ossf-scorecard.yml/badge.svg?event=schedule)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/ossf-scorecard.yml?query=event%3Aschedule) |
| `osv-scanner-scheduled.yml` | ✅ | this week | push:main, schedule(41 21 * * 1), manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/osv-scanner-scheduled.yml/badge.svg?event=schedule)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/osv-scanner-scheduled.yml?query=event%3Aschedule) |
| `osv-scanner.yml` | ✅ | this week | PR, merge_group, schedule(39 12 * * 1), manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/osv-scanner.yml/badge.svg?event=schedule)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/osv-scanner.yml?query=event%3Aschedule) |
| `release-gate.yml` | ✅ | this week | workflow_call, manual, schedule(0 2 * * *) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/release-gate.yml/badge.svg?event=schedule)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/release-gate.yml?query=event%3Aschedule) |
| `sbom.yml` | ✅ | this week | manual, schedule(0 10 * * 3), release | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/sbom.yml/badge.svg?event=schedule)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/sbom.yml?query=event%3Aschedule) |
| `update-dependencies.yml` | ✅ | this week | workflow_call, manual, schedule(0 8 * * 3) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/update-dependencies.yml/badge.svg?event=schedule)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/update-dependencies.yml?query=event%3Aschedule) |
| `update-npm-dependencies.yml` | ❌ | this week | workflow_call, manual, schedule(0 8 * * 4) | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/update-npm-dependencies.yml/badge.svg?event=schedule)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/update-npm-dependencies.yml?query=event%3Aschedule) |
## PR / push checks
| Workflow | Status | Last activity | Trigger | Live badge |
|---|---|---|---|---|
| `advanced-search-reminder.yml` | ✅ | this week | PR | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/advanced-search-reminder.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/advanced-search-reminder.yml) |
| `ai-code-reviewer.yml` | ✅ | this week | PR | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/ai-code-reviewer.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/ai-code-reviewer.yml) |
| `check-workflow-status.yml` | ✅ | this week | PR, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/check-workflow-status.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/check-workflow-status.yml) |
| `claude-code-review.yml` | · | this week | PR | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/claude-code-review.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/claude-code-review.yml) |
| `danger-zone-alert.yml` | ✅ | this week | PR | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/danger-zone-alert.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/danger-zone-alert.yml) |
| `dependency-review.yml` | ✅ | this week | PR, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/dependency-review.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/dependency-review.yml) |
| `e2e-research-test.yml` | · | this week | PR | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/e2e-research-test.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/e2e-research-test.yml) |
| `label-fixed-in-dev.yml` | · | 3 weeks ago | PR | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/label-fixed-in-dev.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/label-fixed-in-dev.yml) |
| `labels-sync.yml` | ✅ | this week | push:main, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/labels-sync.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/labels-sync.yml) |
| `mcp-tests.yml` | ✅ | this week | push:main,dev, PR, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/mcp-tests.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/mcp-tests.yml) |
| `pr-triage.yml` | ✅ | this week | PR, pull_request_review | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/pr-triage.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/pr-triage.yml) |
| `release.yml` | ⏳ | this week | push:main, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/release.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/release.yml) |
| `version_check.yml` | ✅ | this week | push:main, manual | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/version_check.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/version_check.yml) |
| `welcome-first-time.yml` | ✅ | this week | PR-target | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/welcome-first-time.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/welcome-first-time.yml) |
## Repository-dispatch publishers
| Workflow | Status | Last activity | Trigger | Live badge |
|---|---|---|---|---|
| `docker-publish.yml` | ✅ | last week | repo_dispatch | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/docker-publish.yml) |
| `prerelease-docker.yml` | ❌ | this week | repo_dispatch | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/prerelease-docker.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/prerelease-docker.yml) |
| `publish.yml` | ✅ | last week | repo_dispatch | [![status](https://github.com/LearningCircuit/local-deep-research/actions/workflows/publish.yml/badge.svg)](https://github.com/LearningCircuit/local-deep-research/actions/workflows/publish.yml) |
## Other
_None._
<!-- END GENERATED -->

File diff suppressed because it is too large Load Diff