Skip to main content

Daily project hygiene: keep README / CHANGELOG / TODO in sync with reality. LLM-first, algorithmic fallback.

Project description

taskill

AI Cost Tracking

PyPI Version Python License AI Cost Human Time Model

  • 🤖 LLM usage: $1.1082 (17 commits)
  • 👤 Human dev: ~$1213 (12.1h @ $100/h, 30min dedup)

Generated on 2026-07-06 using openrouter/qwen/qwen3-coder-next


Daily project hygiene: keep README.md / CHANGELOG.md / TODO.md in sync with reality. LLM-first, algorithmic fallback. Works standalone, in CI, or via Ansible.

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.

It 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.

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.

Install

pip install taskill                    # core
pip install "taskill[mcp]"             # with Windsurf MCP support
pip install "taskill[mcp,schedule]"    # with built-in scheduler

Quickstart

cd your-project/
taskill init                  # writes taskill.yaml + .env.example
cp .env.example .env          # add OPENROUTER_API_KEY
taskill status                # preview without running
taskill run --dry-run         # see what would change
taskill run                   # do it

Autonomous task execution (koru)

taskill doesn't only keep docs honest — it can do the tasks on your list. Run it with no subcommand and it resolves pending tasks from a list and hands each one to the koru/coru autonomous pipeline, so the work you'd otherwise have to write out or hunt down just gets done.

taskill                 # resolve pending tasks and run them autonomously
taskill koru --dry-run  # preview: show the tasks + the command, run nothing
taskill koru -y         # run without the confirmation prompt
taskill koru --limit 3  # run at most 3 tasks this invocation
taskill koru ./*        # fleet mode: sweep every project folder under ./

Fleet mode — pass directories or globs to run the autopilot across many projects at once (each loads its own taskill.yaml; projects with no pending tasks are skipped):

taskill koru ./*            # every sub-folder with pending tasks
taskill koru ./* --dry-run  # preview the whole fleet plan
taskill koru repoA repoB    # only these projects

Where tasks come from (koru.source, default auto tries them in order):

  1. TODO.md — every unchecked - [ ] item.
  2. planfile.yaml — every open ticket's title.
  3. TASK.md (or koru.task_file) — one task per non-empty, non-comment line.

How each task runs: by default coru text "<task>" --llm, which routes the natural-language task through coru's litellm planner (OpenRouter) and executes the mapped actions. Override the command with koru.command.

taskill lists the resolved tasks and asks one confirmation before running (skipped with -y or koru.auto_confirm, or --dry-run), caps the batch at koru.max_tasks (default 1), continues past per-task failures with an ok/failed summary, and ticks the - [ ] checkbox in TODO.md after a task succeeds (koru.mark_done).

# taskill.yaml
koru:
  enabled: true
  command: ["coru", "text", "{task}", "--llm"]   # {task} ⇒ task text
  source: auto            # auto | todo | planfile | file
  task_file: TASK.md
  max_tasks: 1            # cap per invocation (null = no cap)
  auto_confirm: false     # true ⇒ never prompt (like always passing -y)
  mark_done: true         # tick the TODO checkbox on success

Requires coru on PATH (part of the koru ecosystem) and an OPENROUTER_API_KEY for the litellm planner. taskill koru --dry-run works without either.

What it does

Every run produces three (idempotent) edits:

  1. CHANGELOG.md — appends new entries under ## [Unreleased], grouped by Conventional Commit type (### Added, ### Fixed, ### Performance, etc.). Uses Keep a Changelog layout. Existing entries are deduplicated.
  2. TODO.md — moves completed items to a ## Done (moved to CHANGELOG) section, and appends TODO: / FIXME: markers found in new commit bodies under ## Discovered.
  3. README.md — refreshes only the block between `

Status

Last updated by taskill at 2026-04-25 14:10 UTC

Metric Value
HEAD 11740ba
Coverage
Failing tests
Commits in last cycle 1

Refactored the CLI documentation and interface, with corresponding updates to README and CHANGELOG. Minor packaging/version metadata files were touched alongside various source modules.

` markers (HEAD, coverage, failing tests, summary). Never touches the rest of the file.

Provider chain

The chain runs top-to-bottom. First provider that's available and succeeds wins.

Order Provider Used when
1 windsurf_mcp mcp package installed and a Windsurf endpoint resolves
2 openrouter OPENROUTER_API_KEY is set
3 algorithmic always — pure git-log + Conventional Commits parser

You can reorder, disable, or pass options via taskill.yaml:

providers:
  - name: openrouter         # skip windsurf, go straight to OpenRouter
    enabled: true
  - name: algorithmic
    enabled: true

Triggers

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.

triggers:
  min_hours_since_last_run: 24
  min_commits_since_last_run: 1
  changed_files_threshold: 1
  coverage_change_pct: 2.0       # absolute pp; null to disable
  failed_tests_changed: true
  watch_files: [SUMD.md, SUMR.md]
  require_all: false             # OR by default; set true for AND

taskill run --force ignores triggers entirely.

Running it

Cron / systemd timer

0 6 * * * cd /path/to/project && /usr/local/bin/taskill run >> ~/.taskill.log 2>&1

Bulk run (multiple projects)

For fleet-wide hygiene across many repos:

taskill bulk-run --root ~/github --max-depth 2
taskill bulk-run --root ~/github --max-depth 2 --dry-run  # preview
taskill bulk-run --root ~/github --filter taskill --filter testql  # filter repos

See /daily-docs-update workflow for complete documentation.

GitHub Actions

See examples/github-action.yml. Triggers on push to main, runs taskill run, opens a PR if files changed.

GitLab CI

See examples/gitlab-ci.yml. Same idea, with merge-request creation via the GitLab API.

Ansible

See examples/ansible-playbook.yml. Useful for fleet-wide hygiene across many self-hosted repos.

CLI

taskill                # autonomously run pending tasks via koru/coru
taskill koru           # same, explicit form (--dry-run / -y / --limit N)
taskill init           # generate taskill.yaml + .env.example
taskill status         # show what would happen, no writes
taskill run            # execute (respects triggers)
taskill run --force    # ignore triggers
taskill run --dry-run  # don't write files or state
taskill run --json     # machine-readable output
taskill bulk-run       # run across all git repos in a directory
taskill release X.Y.Z  # promote [Unreleased] → versioned heading
taskill clean-todo     # wipe TODO.md (after a release)

Bulk / fleet-wide runs

When you maintain many small repos (a personal "github" folder, a self-hosted GitLab group, a monorepo of independent packages), taskill bulk-run runs the same hygiene job across all of them with a single command:

# Scan ~/github (depth 2), use a shared taskill.yaml as the base config
taskill bulk-run --root ~/github --shared-config ~/github/taskill.yaml

# Same, but only run on repos whose name matches one of the filters
taskill bulk-run --root ~/github -f oqlos -f semcod

# Process at most 5 projects per run (useful for rate-limited LLMs)
taskill bulk-run --root ~/github --max-projects 5

# Preview without writing anything
taskill bulk-run --root ~/github --dry-run --force

# Machine-readable summary for CI / dashboards
taskill bulk-run --root ~/github --json

Config resolution per repo

Per-repo taskill.yaml (or .taskill.yaml) takes precedence over the shared config, so you can have one default policy plus per-project overrides. Repos without any local config inherit the shared one with their own project_root.

Resolution order for each discovered repo:

  1. <repo>/taskill.yaml — local override (highest priority)
  2. <repo>/.taskill.yaml — alternative local override
  3. Shared config from --shared-config, rebased onto the repo
  4. Built-in defaults

Discovery rules

bulk-run walks the directory tree starting at --root up to --max-depth levels deep (default 2). A directory containing a .git entry is treated as a repo, and bulk-run does not descend into nested repos. Hidden directories and common noise (node_modules, __pycache__, .venv, dist, build, target, .tox, .pytest_cache) are skipped automatically.

Daily-run wrapper

A shell wrapper (daily_update.sh) reads its defaults from .env:

# .env
GITHUB_ROOT=$HOME/github
TASKILL_MAX_DEPTH=2
TASKILL_MAX_PROJECTS=10        # cap per cron tick
TASKILL_DRY_RUN=false
TASKILL_FORCE=false
TASKILL_FILTER=oqlos,semcod    # comma-separated
./daily_update.sh                              # uses .env
./daily_update.sh --dry-run                    # CLI override
./daily_update.sh --root /tmp/my-projects      # different root
./daily_update.sh --filter taskill,testql      # narrow scope

Extending taskill: providers and updaters as plugins

Both providers (LLM/algorithmic backends) and document updaters (CHANGELOG / TODO / README writers) are discovered through Python entry points, so a third-party package can register a new provider or updater without touching this codebase.

Custom provider

# my_pkg/pyproject.toml
[project.entry-points."taskill.providers"]
my_provider = "my_pkg.provider:MyProvider"
# my_pkg/provider.py
from taskill.providers.base import Provider, GeneratedDocs

class MyProvider(Provider):
    name = "my_provider"

    def is_available(self) -> bool:
        return True

    def generate(self, context) -> GeneratedDocs:
        ...

Then reference it from taskill.yaml like any built-in:

providers:
  - name: my_provider
    enabled: true
    options: { ... }
  - name: algorithmic
    enabled: true

Custom updater

[project.entry-points."taskill.updaters"]
wiki = "my_pkg.wiki:WikiUpdater"
from taskill.updaters.base import DocumentUpdater, UpdateResult

class WikiUpdater(DocumentUpdater):
    name = "wiki"

    def apply(self, path, snapshot, docs) -> UpdateResult:
        ...
        return UpdateResult(changed=True, path=path, updater_name=self.name)

Built-in updaters (changelog, todo, readme) are registered the same way, so removing or replacing them is just a matter of the entry-point taking precedence.

Configuration reference

See taskill.yaml at the repo root for the annotated default config.

Reusing existing tools

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:

reuse:
  pyqual: true       # taskill will run `pyqual report --json`
  llx: false         # ...add llx context (planned for v0.2)
  prefact: false     # ...add prefact suggestions (planned for v0.2)

If a tool isn't on PATH, taskill skips it silently — no hard dependency.

How it relates to the wider stack

SUMD (description) ─┐
                    ├─→ taskill ──→ README.md
git log ────────────┤              CHANGELOG.md
pyqual report ──────┤              TODO.md
SUMR (state) ───────┘

taskill reads, never generates code. That's what prefact / llx / pyqual are for.

License

Licensed under Apache-2.0.

Status

Last updated by taskill at 2026-04-25 09:23 UTC

Metric Value
HEAD 4618c29
Coverage
Failing tests
Commits in last cycle 0

No changes were made to the project since the last taskill run.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

taskill-0.1.16.tar.gz (470.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

taskill-0.1.16-py3-none-any.whl (53.4 kB view details)

Uploaded Python 3

File details

Details for the file taskill-0.1.16.tar.gz.

File metadata

  • Download URL: taskill-0.1.16.tar.gz
  • Upload date:
  • Size: 470.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for taskill-0.1.16.tar.gz
Algorithm Hash digest
SHA256 42e9a7bda2976903918475c3065c34b59c3d33a87e03087a1079db38ed3483dd
MD5 8751c0d730867c43109725836f8f6ba5
BLAKE2b-256 672298df5dc5435851781d986907ef20fa91485744cc3bc5f597752e9404aad1

See more details on using hashes here.

File details

Details for the file taskill-0.1.16-py3-none-any.whl.

File metadata

  • Download URL: taskill-0.1.16-py3-none-any.whl
  • Upload date:
  • Size: 53.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for taskill-0.1.16-py3-none-any.whl
Algorithm Hash digest
SHA256 332addbac5a1623811e5b39390f19fd924de105a6970c1933db2b47d3a053150
MD5 5dd2921e0e2c789182713b358c38eb6f
BLAKE2b-256 ebf2b0d5c4f419a67374d5b9304bf1d1a02f8361e0a59ff907d34dd34e9e0afb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page