{"id":50472423,"url":"https://github.com/semcod/taskill","last_synced_at":"2026-06-01T11:03:38.930Z","repository":{"id":353744307,"uuid":"1220735547","full_name":"semcod/taskill","owner":"semcod","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-13T15:10:40.000Z","size":1733,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-13T17:18:09.621Z","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":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/semcod.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"ROADMAP.md","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-04-25T09:03:56.000Z","updated_at":"2026-05-13T15:25:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/semcod/taskill","commit_stats":null,"previous_names":["semcod/taskill"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/semcod/taskill","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semcod%2Ftaskill","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semcod%2Ftaskill/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semcod%2Ftaskill/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semcod%2Ftaskill/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/semcod","download_url":"https://codeload.github.com/semcod/taskill/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semcod%2Ftaskill/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33771630,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"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-06-01T11:03:33.906Z","updated_at":"2026-06-01T11:03:33.959Z","avatar_url":"https://github.com/semcod.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# taskill\n\n\n## AI Cost Tracking\n\n![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.14-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)\n![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.95-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-7.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)\n\n- 🤖 **LLM usage:** $1.9500 (13 commits)\n- 👤 **Human dev:** ~$789 (7.9h @ $100/h, 30min dedup)\n\nGenerated on 2026-04-25 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)\n\n---\n\n\u003e Daily project hygiene: keep `README.md` / `CHANGELOG.md` / `TODO.md` in sync with reality.\n\u003e LLM-first, algorithmic fallback. Works standalone, in CI, or via Ansible.\n\n`taskill` is the small daemon you stop forgetting to run. Once a day (or whenever metrics drift), it reads your git log, your `SUMD.md` / `SUMR.md`, your coverage report, and updates the three documentation files everyone tells themselves they'll keep current and never do.\n\nIt uses an LLM where one is available — Windsurf MCP first (because you're probably already running it in JetBrains), OpenRouter second — and falls back to a deterministic Conventional Commits parser when no LLM is reachable. The fallback is always available and always runs offline.\n\n`taskill` deliberately doesn't replace `pyqual`, `llx`, or `prefact`. It calls them as subprocesses when configured, picks up their reports, and stays out of the way otherwise.\n\n## Install\n\n```bash\npip install taskill                    # core\npip install \"taskill[mcp]\"             # with Windsurf MCP support\npip install \"taskill[mcp,schedule]\"    # with built-in scheduler\n```\n\n## Quickstart\n\n```bash\ncd your-project/\ntaskill init                  # writes taskill.yaml + .env.example\ncp .env.example .env          # add OPENROUTER_API_KEY\ntaskill status                # preview without running\ntaskill run --dry-run         # see what would change\ntaskill run                   # do it\n```\n\n## What it does\n\nEvery run produces three (idempotent) edits:\n\n1. **`CHANGELOG.md`** — appends new entries under `## [Unreleased]`, grouped by Conventional Commit type (`### Added`, `### Fixed`, `### Performance`, etc.). Uses [Keep a Changelog](https://keepachangelog.com/) layout. Existing entries are deduplicated.\n2. **`TODO.md`** — moves completed items to a `## Done (moved to CHANGELOG)` section, and appends `TODO:` / `FIXME:` markers found in new commit bodies under `## Discovered`.\n3. **`README.md`** — refreshes only the block between `\u003c!-- taskill:status:start --\u003e\n\n## Status\n\n_Last updated by [taskill](https://github.com/oqlos/taskill) at 2026-04-25 14:10 UTC_\n\n| Metric | Value |\n|---|---|\n| HEAD | `11740ba` |\n| Coverage | — |\n| Failing tests | — |\n| Commits in last cycle | 1 |\n\n\u003e Refactored the CLI documentation and interface, with corresponding updates to README and CHANGELOG. Minor packaging/version metadata files were touched alongside various source modules.\n\n\u003c!-- taskill:status:end --\u003e` markers (HEAD, coverage, failing tests, summary). Never touches the rest of the file.\n\n## Provider chain\n\nThe chain runs top-to-bottom. First provider that's available *and* succeeds wins.\n\n| Order | Provider | Used when |\n|---|---|---|\n| 1 | `windsurf_mcp` | `mcp` package installed and a Windsurf endpoint resolves |\n| 2 | `openrouter` | `OPENROUTER_API_KEY` is set |\n| 3 | `algorithmic` | always — pure git-log + Conventional Commits parser |\n\nYou can reorder, disable, or pass options via `taskill.yaml`:\n\n```yaml\nproviders:\n  - name: openrouter         # skip windsurf, go straight to OpenRouter\n    enabled: true\n  - name: algorithmic\n    enabled: true\n```\n\n## Triggers\n\n`taskill run` is a no-op unless one of the configured thresholds is crossed. State lives in `.taskill/state.json` so cron, GitHub Actions, and Ansible all share the same delta logic.\n\n```yaml\ntriggers:\n  min_hours_since_last_run: 24\n  min_commits_since_last_run: 1\n  changed_files_threshold: 1\n  coverage_change_pct: 2.0       # absolute pp; null to disable\n  failed_tests_changed: true\n  watch_files: [SUMD.md, SUMR.md]\n  require_all: false             # OR by default; set true for AND\n```\n\n`taskill run --force` ignores triggers entirely.\n\n## Running it\n\n### Cron / systemd timer\n\n```cron\n0 6 * * * cd /path/to/project \u0026\u0026 /usr/local/bin/taskill run \u003e\u003e ~/.taskill.log 2\u003e\u00261\n```\n\n### Bulk run (multiple projects)\n\nFor fleet-wide hygiene across many repos:\n\n```bash\ntaskill bulk-run --root ~/github --max-depth 2\ntaskill bulk-run --root ~/github --max-depth 2 --dry-run  # preview\ntaskill bulk-run --root ~/github --filter taskill --filter testql  # filter repos\n```\n\nSee `/daily-docs-update` workflow for complete documentation.\n\n### GitHub Actions\n\nSee `examples/github-action.yml`. Triggers on `push` to main, runs `taskill run`, opens a PR if files changed.\n\n### GitLab CI\n\nSee `examples/gitlab-ci.yml`. Same idea, with merge-request creation via the GitLab API.\n\n### Ansible\n\nSee `examples/ansible-playbook.yml`. Useful for fleet-wide hygiene across many self-hosted repos.\n\n## CLI\n\n```\ntaskill init           # generate taskill.yaml + .env.example\ntaskill status         # show what would happen, no writes\ntaskill run            # execute (respects triggers)\ntaskill run --force    # ignore triggers\ntaskill run --dry-run  # don't write files or state\ntaskill run --json     # machine-readable output\ntaskill bulk-run       # run across all git repos in a directory\ntaskill release X.Y.Z  # promote [Unreleased] → versioned heading\ntaskill clean-todo     # wipe TODO.md (after a release)\n```\n\n## Bulk / fleet-wide runs\n\nWhen you maintain many small repos (a personal \"github\" folder, a self-hosted\nGitLab group, a monorepo of independent packages), `taskill bulk-run` runs the\nsame hygiene job across all of them with a single command:\n\n```bash\n# Scan ~/github (depth 2), use a shared taskill.yaml as the base config\ntaskill bulk-run --root ~/github --shared-config ~/github/taskill.yaml\n\n# Same, but only run on repos whose name matches one of the filters\ntaskill bulk-run --root ~/github -f oqlos -f semcod\n\n# Process at most 5 projects per run (useful for rate-limited LLMs)\ntaskill bulk-run --root ~/github --max-projects 5\n\n# Preview without writing anything\ntaskill bulk-run --root ~/github --dry-run --force\n\n# Machine-readable summary for CI / dashboards\ntaskill bulk-run --root ~/github --json\n```\n\n### Config resolution per repo\n\nPer-repo `taskill.yaml` (or `.taskill.yaml`) takes precedence over the shared\nconfig, so you can have one default policy plus per-project overrides. Repos\nwithout any local config inherit the shared one with their own `project_root`.\n\nResolution order for each discovered repo:\n\n1. `\u003crepo\u003e/taskill.yaml` — local override (highest priority)\n2. `\u003crepo\u003e/.taskill.yaml` — alternative local override\n3. Shared config from `--shared-config`, rebased onto the repo\n4. Built-in defaults\n\n### Discovery rules\n\n`bulk-run` walks the directory tree starting at `--root` up to `--max-depth`\nlevels deep (default 2). A directory containing a `.git` entry is treated as a\nrepo, and `bulk-run` does **not** descend into nested repos. Hidden directories\nand common noise (`node_modules`, `__pycache__`, `.venv`, `dist`, `build`,\n`target`, `.tox`, `.pytest_cache`) are skipped automatically.\n\n### Daily-run wrapper\n\nA shell wrapper (`daily_update.sh`) reads its defaults from `.env`:\n\n```bash\n# .env\nGITHUB_ROOT=$HOME/github\nTASKILL_MAX_DEPTH=2\nTASKILL_MAX_PROJECTS=10        # cap per cron tick\nTASKILL_DRY_RUN=false\nTASKILL_FORCE=false\nTASKILL_FILTER=oqlos,semcod    # comma-separated\n```\n\n```bash\n./daily_update.sh                              # uses .env\n./daily_update.sh --dry-run                    # CLI override\n./daily_update.sh --root /tmp/my-projects      # different root\n./daily_update.sh --filter taskill,testql      # narrow scope\n```\n\n## Extending taskill: providers and updaters as plugins\n\nBoth providers (LLM/algorithmic backends) and document updaters (CHANGELOG /\nTODO / README writers) are discovered through Python entry points, so a\nthird-party package can register a new provider or updater without touching\nthis codebase.\n\n### Custom provider\n\n```toml\n# my_pkg/pyproject.toml\n[project.entry-points.\"taskill.providers\"]\nmy_provider = \"my_pkg.provider:MyProvider\"\n```\n\n```python\n# my_pkg/provider.py\nfrom taskill.providers.base import Provider, GeneratedDocs\n\nclass MyProvider(Provider):\n    name = \"my_provider\"\n\n    def is_available(self) -\u003e bool:\n        return True\n\n    def generate(self, context) -\u003e GeneratedDocs:\n        ...\n```\n\nThen reference it from `taskill.yaml` like any built-in:\n\n```yaml\nproviders:\n  - name: my_provider\n    enabled: true\n    options: { ... }\n  - name: algorithmic\n    enabled: true\n```\n\n### Custom updater\n\n```toml\n[project.entry-points.\"taskill.updaters\"]\nwiki = \"my_pkg.wiki:WikiUpdater\"\n```\n\n```python\nfrom taskill.updaters.base import DocumentUpdater, UpdateResult\n\nclass WikiUpdater(DocumentUpdater):\n    name = \"wiki\"\n\n    def apply(self, path, snapshot, docs) -\u003e UpdateResult:\n        ...\n        return UpdateResult(changed=True, path=path, updater_name=self.name)\n```\n\nBuilt-in updaters (`changelog`, `todo`, `readme`) are registered the same way,\nso removing or replacing them is just a matter of the entry-point taking\nprecedence.\n\n## Configuration reference\n\nSee [`taskill.yaml`](./taskill.yaml) at the repo root for the annotated default config.\n\n## Reusing existing tools\n\n`taskill` doesn't try to absorb `pyqual` / `llx` / `prefact`. It calls them by `subprocess` when toggled in `reuse:` and feeds their JSON output to the LLM as extra context:\n\n```yaml\nreuse:\n  pyqual: true       # taskill will run `pyqual report --json`\n  llx: false         # ...add llx context (planned for v0.2)\n  prefact: false     # ...add prefact suggestions (planned for v0.2)\n```\n\nIf a tool isn't on `PATH`, `taskill` skips it silently — no hard dependency.\n\n## How it relates to the wider stack\n\n```\nSUMD (description) ─┐\n                    ├─→ taskill ──→ README.md\ngit log ────────────┤              CHANGELOG.md\npyqual report ──────┤              TODO.md\nSUMR (state) ───────┘\n```\n\n`taskill` reads, never generates code. That's what `prefact` / `llx` / `pyqual` are for.\n\n## License\n\nLicensed under Apache-2.0.\n## Status\n\n_Last updated by [taskill](https://github.com/oqlos/taskill) at 2026-04-25 09:23 UTC_\n\n| Metric | Value |\n|---|---|\n| HEAD | `4618c29` |\n| Coverage | — |\n| Failing tests | — |\n| Commits in last cycle | 0 |\n\n\u003e No changes were made to the project since the last taskill run.\n\n\u003c!-- taskill:status:end --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemcod%2Ftaskill","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsemcod%2Ftaskill","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemcod%2Ftaskill/lists"}