{"id":51413601,"url":"https://github.com/YoniRaviv/DocPulse","last_synced_at":"2026-07-23T08:00:56.959Z","repository":{"id":364011854,"uuid":"1265343603","full_name":"YoniRaviv/DocPulse","owner":"YoniRaviv","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-11T08:22:41.000Z","size":170,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T10:14:12.032Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/YoniRaviv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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-06-10T17:29:59.000Z","updated_at":"2026-06-11T08:22:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/YoniRaviv/DocPulse","commit_stats":null,"previous_names":["yoniraviv/docpulse"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/YoniRaviv/DocPulse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YoniRaviv%2FDocPulse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YoniRaviv%2FDocPulse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YoniRaviv%2FDocPulse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YoniRaviv%2FDocPulse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/YoniRaviv","download_url":"https://codeload.github.com/YoniRaviv/DocPulse/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YoniRaviv%2FDocPulse/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35794548,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-23T02:00:06.683Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2026-07-04T17:00:21.536Z","updated_at":"2026-07-23T08:00:56.932Z","avatar_url":"https://github.com/YoniRaviv.png","language":"Python","funding_links":[],"categories":["GitHub Actions"],"sub_categories":[],"readme":"# DocPulse\n\n![DocPulse — docs that stay in sync with the heartbeat of the codebase](https://raw.githubusercontent.com/YoniRaviv/DocPulse/main/.github/assets/banner.png)\n\n\u003e Docs that stay in sync with the heartbeat of the codebase.\n\n[![GitHub Marketplace](https://img.shields.io/badge/GitHub%20Marketplace-DocPulse-purple?logo=github)](https://github.com/marketplace/actions/docpulse-self-healing-docs)\n\nDocPulse detects documentation sections invalidated by a pull request's code\nchanges and commits surgical fixes directly onto the PR branch. A deterministic layer\n(tree-sitter chunking → code↔doc link graph → diff-driven suspect selection)\ndecides *what to check*; a bounded agentic layer (an LLM verifier with read/grep\ntools, then a style-preserving repairer with a validation pass) decides *stale\nor not, and how to fix it*.\n\n## Quickstart (GitHub Action)\n\n```yaml\n# .github/workflows/docpulse.yml\nname: DocPulse\non: pull_request\njobs:\n  docpulse:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write          # repair mode commits doc fixes onto the PR branch\n      pull-requests: write\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.ref }}\n          fetch-depth: 0\n      - uses: YoniRaviv/DocPulse@v1\n        with:\n          mode: repair          # or \"check\" to comment-only\n        env:\n          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\n          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          DOCPULSE_PR_NUMBER: ${{ github.event.pull_request.number }}\n```\n\nEmbeddings improve link recall and need an embedding key (`OPENAI_API_KEY`). To run without one, set the action input `heuristics-only: true` — linking then uses name mentions only.\n\nIn `repair` mode DocPulse commits the doc fix straight onto your PR's branch, so the fix lands in the same PR (same-repo PRs only — DocPulse can't push to a fork, so use `mode: check` for fork-based contributions). It skips itself when the most-recent commit on the branch is already a doc-sync commit, so the auto-fix push never re-triggers an endless loop.\n\nAdd a `docpulse.yml` at your repo root (see [Configuration](#configuration)).\n\n## Quickstart (CLI)\n\n```bash\nuv tool install docpulse                              # or: pipx install docpulse\ndocpulse index --root .                               # build the code\u003c-\u003edocs link index\ndocpulse check  --base origin/main                    # verify docs vs the PR diff (exit 1 on drift)\ndocpulse check  --base origin/main --suspects-only    # keyless: list suspect sections only\ndocpulse repair --base origin/main                    # print proposed fixes + the dry-run commit plan\ndocpulse repair --base origin/main --write            # apply fixes to doc files locally (no push)\ndocpulse repair --base origin/main --push             # commit+push doc fixes onto the current branch (needs GH_TOKEN)\n```\n\n`check` exits 0 (clean), 1 (a doc section is stale at/above `flag_threshold`),\nor 2 (setup/tool error). `unverified` never fails the build. `repair` uses the same\ncodes — it exits 1 if drift was found, even after committing a fix, so the auto-fixed\nPR still flags for human review.\n\n## Eval numbers\n\nMeasured on 12 hand-labeled seed cases (4 per language; 6 stale / 6 accurate)\nwith `anthropic/claude-haiku-4-5`:\n\n| Metric    | Value |\n|-----------|-------|\n| Precision | 1.00  |\n| Recall    | 1.00  |\n\nThe seed cases are deliberately clear-cut; robustness grows as real-world\nfalse-positives are added back as cases. Run `docpulse eval --cases evals/cases`\nto reproduce.\n\n## Architecture\n\n```mermaid\nflowchart LR\n  subgraph Deterministic\n    A[tree-sitter chunker] --\u003e B[code↔doc link graph]\n    B --\u003e C[git diff → suspects]\n  end\n  subgraph Agentic\n    C --\u003e D[verifier\u003cbr/\u003eread/grep tools]\n    D --\u003e E[repairer]\n    E --\u003e F[validator]\n  end\n  F --\u003e G[destination:\u003cbr/\u003ePR comment + doc-sync commit]\n```\n\n## Configuration\n\n```yaml\nmodel: anthropic/claude-sonnet-4-6        # any LiteLLM model string\nembedding_model: openai/text-embedding-3-small\ndocs:\n  - path: \"docs/**/*.md\"\n  - path: \"README.md\"\ncode:\n  include: [\"src/**\"]\n  exclude: [\"**/*.test.*\", \"tests/**\"]\nconfidence:\n  auto_fix_threshold: 0.85\n  flag_threshold: 0.5\nbudget:\n  max_suspects_per_run: 20\n  max_tool_calls_per_suspect: 10\ncontext: [git]\n```\n\nSecrets come only from env vars (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, …).\n`docpulse index --heuristics-only` builds the link graph without embeddings (no key).\n\n## Jenkins / any CI\n\n```bash\n# The image entrypoint is env-driven (it always runs `index` then the chosen\n# mode with --push). Configure it with -e vars, not CLI args:\ndocker run --rm \\\n  -e ANTHROPIC_API_KEY -e GH_TOKEN \\\n  -e DOCPULSE_MODE=check \\\n  -e DOCPULSE_BASE_REF=origin/main \\\n  -e DOCPULSE_PR_NUMBER=\"$PR_NUMBER\" \\\n  -e DOCPULSE_HEURISTICS_ONLY=true \\\n  -v \"$PWD:/work\" -w /work \\\n  ghcr.io/yoniraviv/docpulse:latest\n```\n\nSet `DOCPULSE_PR_NUMBER` to your CI's PR/MR number so the flag comment is posted to\nthe PR (without it, the comment is only printed to the log). Drop\n`DOCPULSE_HEURISTICS_ONLY` and add `-e OPENAI_API_KEY` to use embedding-based linking.\n\n### Kubernetes (no Docker daemon)\n\nA Jenkins agent that is itself a K8s pod has no Docker daemon, so `docker run`\nwon't work. Run DocPulse as a **pod sidecar container** (or `uv tool install\ndocpulse` / `pipx install docpulse` into the agent image) and call the CLI\ndirectly — it self-preps (git `safe.directory`, base-ref fetch on shallow\ncheckouts, and the push loop guard), so no entrypoint wrapper is needed:\n\n```groovy\ncontainer('docpulse') {\n  sh '''\n    docpulse index --root .\n    docpulse check --base \"origin/$CHANGE_TARGET\" --comment-via none --comment-out docpulse-flag.md\n  '''\n}\n// then post docpulse-flag.md with your SCM's Jenkins plugin (GitHub/GitLab/Bitbucket)\n```\n\n`--comment-out` writes the flag comment as portable markdown so **any** host's CI\nposts it natively — DocPulse stays out of the per-vendor comment-API business.\nGitHub users can instead let DocPulse post directly (the default `--comment-via\ngh`, given `gh` + `GH_TOKEN` + `DOCPULSE_PR_NUMBER`); other hosts pass\n`--comment-via none`. `repair --push` works on any host — it commits the doc-sync\nfix with `git -c user.*` (bot-authored, so the loop guard catches it) and pushes\nwith the credentials your checkout already configured.\n\nMap your CI's variables: Jenkins exposes the base branch as `$CHANGE_TARGET` and\nthe PR/MR number as `$CHANGE_ID` (set `DOCPULSE_PR_NUMBER=\"$CHANGE_ID\"` for `gh`\nposting). The bot identity is overridable via `DOCPULSE_BOT_NAME` /\n`DOCPULSE_BOT_EMAIL`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FYoniRaviv%2FDocPulse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FYoniRaviv%2FDocPulse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FYoniRaviv%2FDocPulse/lists"}