pyworklog 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. pyworklog-0.3.0/.github/workflows/release.yml +94 -0
  2. pyworklog-0.3.0/.github/workflows/test.yml +51 -0
  3. pyworklog-0.3.0/.gitignore +225 -0
  4. pyworklog-0.3.0/AGENTS.md +72 -0
  5. pyworklog-0.3.0/CHANGELOG.md +66 -0
  6. pyworklog-0.3.0/CONTRIBUTING.md +116 -0
  7. pyworklog-0.3.0/DESIGN.md +409 -0
  8. pyworklog-0.3.0/DESIGN.zh.md +744 -0
  9. pyworklog-0.3.0/LICENSE +21 -0
  10. pyworklog-0.3.0/Makefile +125 -0
  11. pyworklog-0.3.0/PKG-INFO +136 -0
  12. pyworklog-0.3.0/README.md +109 -0
  13. pyworklog-0.3.0/README.zh.md +96 -0
  14. pyworklog-0.3.0/pyproject.toml +49 -0
  15. pyworklog-0.3.0/pytest.ini +20 -0
  16. pyworklog-0.3.0/skills/worklog-cli/SKILL.md +294 -0
  17. pyworklog-0.3.0/src/worklog/__init__.py +8 -0
  18. pyworklog-0.3.0/src/worklog/cli.py +1160 -0
  19. pyworklog-0.3.0/src/worklog/commands/__init__.py +89 -0
  20. pyworklog-0.3.0/src/worklog/commands/bulk.py +512 -0
  21. pyworklog-0.3.0/src/worklog/commands/meta.py +579 -0
  22. pyworklog-0.3.0/src/worklog/commands/query.py +854 -0
  23. pyworklog-0.3.0/src/worklog/commands/state.py +651 -0
  24. pyworklog-0.3.0/src/worklog/commands/views.py +558 -0
  25. pyworklog-0.3.0/src/worklog/completion.py +624 -0
  26. pyworklog-0.3.0/src/worklog/db.py +92 -0
  27. pyworklog-0.3.0/src/worklog/helpers.py +288 -0
  28. pyworklog-0.3.0/src/worklog/migrations/0001_initial_schema.sql +90 -0
  29. pyworklog-0.3.0/src/worklog/queries.py +216 -0
  30. pyworklog-0.3.0/src/worklog/render.py +209 -0
  31. pyworklog-0.3.0/src/worklog/xdg.py +42 -0
  32. pyworklog-0.3.0/tests/__init__.py +0 -0
  33. pyworklog-0.3.0/tests/conftest.py +63 -0
  34. pyworklog-0.3.0/tests/test_add.py +183 -0
  35. pyworklog-0.3.0/tests/test_aliases.py +96 -0
  36. pyworklog-0.3.0/tests/test_brief.py +153 -0
  37. pyworklog-0.3.0/tests/test_cascade.py +27 -0
  38. pyworklog-0.3.0/tests/test_changes.py +46 -0
  39. pyworklog-0.3.0/tests/test_checkin.py +315 -0
  40. pyworklog-0.3.0/tests/test_clock.py +191 -0
  41. pyworklog-0.3.0/tests/test_completion.py +183 -0
  42. pyworklog-0.3.0/tests/test_config.py +48 -0
  43. pyworklog-0.3.0/tests/test_dateinfo.py +42 -0
  44. pyworklog-0.3.0/tests/test_day.py +148 -0
  45. pyworklog-0.3.0/tests/test_find.py +99 -0
  46. pyworklog-0.3.0/tests/test_focus.py +95 -0
  47. pyworklog-0.3.0/tests/test_import_apply.py +615 -0
  48. pyworklog-0.3.0/tests/test_init.py +21 -0
  49. pyworklog-0.3.0/tests/test_link_set.py +51 -0
  50. pyworklog-0.3.0/tests/test_log.py +321 -0
  51. pyworklog-0.3.0/tests/test_log_format.py +166 -0
  52. pyworklog-0.3.0/tests/test_logs.py +99 -0
  53. pyworklog-0.3.0/tests/test_ls.py +203 -0
  54. pyworklog-0.3.0/tests/test_meta.py +59 -0
  55. pyworklog-0.3.0/tests/test_migrations.py +136 -0
  56. pyworklog-0.3.0/tests/test_projects.py +49 -0
  57. pyworklog-0.3.0/tests/test_render.py +265 -0
  58. pyworklog-0.3.0/tests/test_sample.py +50 -0
  59. pyworklog-0.3.0/tests/test_sched.py +566 -0
  60. pyworklog-0.3.0/tests/test_show.py +61 -0
  61. pyworklog-0.3.0/tests/test_state.py +151 -0
  62. pyworklog-0.3.0/tests/test_summary.py +88 -0
  63. pyworklog-0.3.0/tests/test_tree.py +265 -0
  64. pyworklog-0.3.0/tests/test_ux.py +727 -0
  65. pyworklog-0.3.0/tests/test_xdg.py +48 -0
  66. pyworklog-0.3.0/uv.lock +319 -0
@@ -0,0 +1,94 @@
1
+ name: Release
2
+
3
+ # Pushing a vX.Y.Z tag:
4
+ # 1. verifies the tag matches pyproject.toml version,
5
+ # 2. runs the test suite (release gate),
6
+ # 3. builds the wheel + sdist with uv,
7
+ # 4. publishes to PyPI via OIDC Trusted Publisher (no token needed),
8
+ # 5. creates a GitHub Release with auto-generated notes + the dist/ files attached.
9
+ on:
10
+ push:
11
+ tags:
12
+ - "v*"
13
+
14
+ jobs:
15
+ release:
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: write # for creating the GitHub Release
19
+ id-token: write # for PyPI Trusted Publisher (OIDC)
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0 # need full history for auto-generated release notes
24
+
25
+ - name: Verify tag matches pyproject version
26
+ run: |
27
+ tag="${GITHUB_REF_NAME#v}"
28
+ proj=$(grep -m1 '^version = ' pyproject.toml | sed 's/.*"\(.*\)".*/\1/')
29
+ echo "git tag=$tag · pyproject=$proj"
30
+ if [ "$tag" != "$proj" ]; then
31
+ echo "::error::version mismatch — git tag must equal pyproject.toml version (which __version__ reads via importlib.metadata)"
32
+ exit 1
33
+ fi
34
+
35
+ - name: Install uv
36
+ uses: astral-sh/setup-uv@v6
37
+ with:
38
+ python-version: '3.12'
39
+ enable-cache: true
40
+
41
+ - name: Sync deps
42
+ run: uv sync --all-groups
43
+
44
+ - name: Run tests (release gate)
45
+ run: uv run pytest -q --no-cov
46
+
47
+ - name: Build wheel + sdist
48
+ run: uv build
49
+
50
+ - name: Publish to PyPI
51
+ uses: pypa/gh-action-pypi-publish@release/v1
52
+ # No `with: password:` — uses OIDC Trusted Publisher.
53
+ # One-time setup on PyPI: https://pypi.org/manage/account/publishing/
54
+ # PyPI Project Name: pyworklog (worklog/worklog-cli taken; hyphenated names avoided)
55
+ # Owner: xyb · Repository: worklog
56
+ # Workflow filename: release.yml · Environment name: (leave empty)
57
+
58
+ - name: Build commit list since previous tag
59
+ id: commits
60
+ run: |
61
+ prev=$(git describe --tags --abbrev=0 "${GITHUB_REF_NAME}^" 2>/dev/null || echo "")
62
+ {
63
+ if [ -n "$prev" ]; then
64
+ echo "## Commits since $prev"
65
+ echo ""
66
+ git log --pretty=format:'- %s (%h)' "$prev..$GITHUB_REF_NAME"
67
+ echo ""
68
+ else
69
+ echo "## Commits"
70
+ echo ""
71
+ git log --pretty=format:'- %s (%h)' "$GITHUB_REF_NAME"
72
+ echo ""
73
+ fi
74
+ echo ""
75
+ echo "## Install"
76
+ echo ""
77
+ echo '```'
78
+ echo "pip install pyworklog==${GITHUB_REF_NAME#v}"
79
+ echo '```'
80
+ } > release-body.md
81
+ {
82
+ echo 'body<<EOF'
83
+ cat release-body.md
84
+ echo 'EOF'
85
+ } >> "$GITHUB_OUTPUT"
86
+
87
+ - name: Create GitHub Release
88
+ uses: softprops/action-gh-release@v2
89
+ with:
90
+ body: ${{ steps.commits.outputs.body }}
91
+ generate_release_notes: true # keeps the auto "Full Changelog" link and any PR list
92
+ files: |
93
+ dist/*.whl
94
+ dist/*.tar.gz
@@ -0,0 +1,51 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest]
16
+ python-version: ['3.11', '3.12', '3.13']
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v6
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+ enable-cache: true
26
+
27
+ - name: Sync deps
28
+ run: uv sync --all-groups
29
+
30
+ - name: Run tests with coverage
31
+ run: uv run pytest --cov-report=xml --cov-report=term-missing
32
+
33
+ - name: Upload coverage to Codecov
34
+ if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
35
+ uses: codecov/codecov-action@v5
36
+ with:
37
+ token: ${{ secrets.CODECOV_TOKEN }}
38
+ files: ./coverage.xml
39
+ fail_ci_if_error: false
40
+
41
+ - name: Smoke test CLI
42
+ run: |
43
+ export WORKLOG_DB=$(mktemp -d)/worklog.db
44
+ uv run wl init
45
+ uv run wl --version
46
+ uv run wl add "ci smoke task" -k task -p A
47
+ uv run wl log 1 "hello from CI"
48
+ uv run wl done 1
49
+ uv run wl show 1
50
+ uv run wl tree
51
+ uv run wl migrate
@@ -0,0 +1,225 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ # ── worklog project specific ──
221
+ *.db
222
+ *.db-journal
223
+ wl.py.bak
224
+ TODO.local.md
225
+ local/
@@ -0,0 +1,72 @@
1
+ # AGENTS.md
2
+
3
+ Primary operating guide for AI coding agents (Claude Code, Cursor, Aider, etc.) working in this repository. Keep it under ~200 lines — anything longer stops getting read in full.
4
+
5
+ ## Authoritative docs (read these first)
6
+
7
+ - **`DESIGN.md`** is canonical for every shared convention (command style, state machine, marker symbols, time-window flags, rendering, schema, import/apply formats, color theming, scheduled time, planned/unplanned derivation, etc.). Read the relevant section before adding a command or changing a format — keeping `src/worklog/cli.py`, the tests, the completion strings, and DESIGN in sync is the project's hardest rule.
8
+ - **`CONTRIBUTING.md`** holds the full dev setup, TDD + DRY conventions, local Makefile overrides, and release process. Do not duplicate that content here.
9
+ - **`skills/worklog-cli/SKILL.md`** is the Claude Code skill (AI-facing usage guide: when to use `wl`, scenario→command table, bulk `import` / `apply` patterns, `-q` brief mode for token savings). Update this when usage patterns change.
10
+ - `README.md` is the project overview + install pointer only; `tests/test_wl.py` is the de-facto contract for every command.
11
+
12
+ ## Common commands
13
+
14
+ ```fish
15
+ make setup # first-time: uv sync + install ~/bin/wl wrapper into project .venv
16
+ make test # pytest in parallel (-n auto) with the 95% cov gate from pytest.ini
17
+ make test-v # sequential, no cov — debug noisy output
18
+ make test-fast # parallel, no cov gate — quick dev loop
19
+ make cov # detailed term-missing coverage report
20
+ make demo # reset DB + populate sample tree (irreversible)
21
+ make reset # interactive: drop current DB + re-init
22
+ make ship # test then push (only pushes if green)
23
+ ```
24
+
25
+ Single-test invocation:
26
+
27
+ ```fish
28
+ uv run pytest tests/test_wl.py::TestApply::test_apply_delete_cascades_subtree -v --no-cov
29
+ ```
30
+
31
+ Ad-hoc DB for scratch work without touching `~/.local/share/worklog/worklog.db`:
32
+
33
+ ```fish
34
+ wl --db /tmp/scratch.db init
35
+ wl --db /tmp/scratch.db add "..."
36
+ ```
37
+
38
+ `wl config` prints the resolved DB path + source (`--db flag` / `$WORKLOG_DB` / XDG default) plus all env vars — start there when debugging a path/env confusion.
39
+
40
+ ## Architecture (the big picture)
41
+
42
+ **Single-package CLI.** `src/worklog/` holds everything: `cli.py` (~5000 lines) is the implementation, `migrations/NNNN_*.sql` is the schema (one initial file at v0.3.0, more added per schema change), `__init__.py` re-exports `__version__`. No further submodule split inside `cli.py` — kept this way intentionally; reconsider only when a feature truly cannot be co-located.
43
+
44
+ **One polymorphic `node` table.** The `kind` column distinguishes `lifetime / decade / year / quarter / month / week / day / area / project / task / meetlog / habit / signal`; `parent_id` self-reference builds the tree. Two parallel sub-trees hang under `lifetime`: a **responsibility line** (`area → project → task`, PARA-style) and a **time line** (`year → quarter → month → week → day`, time skeleton + metadata props). Day/week/month views are **log-driven** (`logged_at` + ancestor chain + tags), not via fixed parent-of-task — moving projects between areas does not break per-day views. Tables: `node / tag / log / prop / link / sched / date_meta` + recursive CTE view `v_node_path`.
45
+
46
+ **Path resolution.** Priority: `wl --db PATH` flag → `$WORKLOG_DB` env → `$XDG_DATA_HOME/worklog/worklog.db` (default `~/.local/share/worklog/worklog.db`). Config is `$XDG_CONFIG_HOME/worklog/aliases.ini`. `_resolve_db_path(args)` is the only resolver; `cmd_config` displays which branch fired.
47
+
48
+ **Subcommand dispatch.** `build_parser()` constructs the argparse tree (one `sub.add_parser(...)` block per command, with `help` + `description` + `epilog`). `HANDLERS = {"cmd": cmd_handler}` maps the parsed name to `cmd_<name>(args, con) -> None`. `main()` resolves user aliases (`~/.config/worklog/aliases.ini`), routes meta-commands (`print-completion`, `config`) that bypass `ensure_db()`, applies `--db` override, then calls the handler. Tests invoke `HANDLERS[cmd](args, con)` directly through the `cli` fixture to bypass `main()`.
49
+
50
+ **Rendering pipeline.** All output goes through `out()` (rich-aware) — never bare `print()` for user-visible rows. `_c(text, style)` is the single coloring entry: when `_CONSOLE` is set, it escapes content via `rich.markup.escape` (so `[x]`, `[[doc]]`, `[#A]` aren't eaten as rich markup) and wraps with `[style]…[/style]`; when console is plain, it returns the string. **Any fragment that may contain `[ ]` must go through `_c`** — direct insertion triggers `rich.MarkupError` or silent eating. `_node_line(con, n, ...)` is the **only node renderer**; reuse it everywhere listing nodes, including new commands, to inherit highlighting + search-hit emphasis for free. `_init_console` runs once in `main()`; tests run in plain mode (no TTY).
51
+
52
+ **Shell completion is generated, not hand-written.** `wl print-completion {fish,bash,zsh}` walks the argparse tree at runtime and emits a script tailored to that shell. Each shell template contains a `__wl_db_path[_bash|_zsh]` helper that queries SQLite directly for dynamic node-id / tag completions — **no Python startup**, so Tab is <50ms. When you add a subcommand or flag, completion is automatic; **but** if it takes a node id or tag, register it in `_FISH_POSITIONAL_NODE` / `_FISH_HELPERS` / `_BASH_DYN_HELPERS` so dynamic completion fires.
53
+
54
+ **Coverage gate is hard (95%).** `pytest.ini` enforces `--cov-fail-under=95`; the CI runs the same. The two pragma-no-cover regions are TTY/escape-sequence probes in `_detect_bg_is_dark` and the bare `main()` entrypoint (tests bypass it).
55
+
56
+ ## Core principles
57
+
58
+ - **TDD (Red → Green → Refactor).** Behavioral changes start with a failing test in `tests/test_wl.py` that reproduces the bug or pins the new behavior. Confirm it fails for the right reason, then write the minimum code to make it pass, then refactor. Tests stay green at every step. A PR that adds behavior without adding the test that drives it is rejected. See CONTRIBUTING.md "TDD" for the full loop.
59
+ - **DRY (Don't Repeat Yourself).** The codebase has a small set of single-source helpers — `_status_marker`, `_node_line`, `out`/`_c`, `_resolve_window`, `_resolve_db_path`, `_project_members`, `_node_clock_min`, `_collect_descendants` (see DESIGN §12 for the full list). New code reuses them; re-implementing one of them in a fresh form is a review block. Same rule for docs: install / dev / release info is in CONTRIBUTING.md and only there — link to it, don't restate it.
60
+
61
+ ## Hard rules
62
+
63
+ - **DESIGN.md is the source of truth.** If a convention changes (statuses, markers, time-window flag set, project↔task linkage rule, `--by` aggregation dim, theme key set, schema), update DESIGN + `src/worklog/cli.py` + tests + completion strings in the same commit. Drift between them is the failure mode this project guards against.
64
+ - **Status / priority / theme key names** are enumerated in DESIGN §3/§5/§19; add to `_THEME_KEYS` when introducing a new palette entry — `test_themes_have_same_keys` catches misses.
65
+ - **No bare `print()` for renderable content**; all rows go through `out()` + `_c()`.
66
+ - **Bulk writes default to `--dry-run`.** `wl apply` and `wl import` validate first, then execute as a single transaction (`_collect_descendants` recurses for the `-` delete prefix because the FK is `ON DELETE SET NULL`, not cascade). Update flags strictly: only fields that appear in the diff are touched — see DESIGN §18.2 "anti-wipe" rule.
67
+ - **i18n layout.** README + DESIGN are bilingual (`*.zh.md`), `src/worklog/cli.py` strings + tests + SKILL.md are English. CJK fixtures in tests are intentional (they exercise unicode width / sort / title handling).
68
+ - **`tmp_path_home` test fixture pattern.** When a test changes `$HOME` to assert path resolution, it must also `monkeypatch.delenv("XDG_CONFIG_HOME")` + `delenv("XDG_DATA_HOME")` — CI runners preset XDG vars and otherwise leak through. See `TestUserAliasesIni._setup_aliases` for the working pattern.
69
+
70
+ ## Local overrides + release
71
+
72
+ Both processes are documented end-to-end in [CONTRIBUTING.md](CONTRIBUTING.md) — local `local/*.mk` Makefile injection and the three-anchor version-bump workflow (`__version__` in `src/worklog/cli.py` + `pyproject.toml` + git tag, all verified by `.github/workflows/release.yml`). Don't duplicate them here.
@@ -0,0 +1,66 @@
1
+ # Changelog
2
+
3
+ All notable changes to **worklog** are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
+
5
+ The autogenerated `Commits since vX.Y.Z` block on each GitHub Release captures every commit; this file is the human-curated highlight reel.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.3.0] — 2026-06-01
10
+
11
+ ### Added
12
+ - `pip install pyworklog` now works. The project ships as a proper Python package (`src/worklog/`) with a `wl` console-script entry point; `uv build` produces a wheel + sdist with `worklog/migrations/0001_initial_schema.sql` packaged in. The PyPI **distribution** name is `pyworklog` (the short names `worklog` and `worklog-cli` were already taken on PyPI, and hyphenated names like `worklog-py` were avoided); the import name and `wl` command are unchanged.
13
+ - **SQL migrations runner** — schema is delivered as `src/worklog/migrations/NNNN_*.sql`; `PRAGMA user_version` tracks the highest applied. `ensure_db()` auto-applies pending migrations on every command. New `wl migrate` is the explicit form (and the one place that bypasses `ensure_db()` so the handler has work to do on a fresh DB).
14
+ - **Downgrade guard** — if `user_version` is ahead of the migrations shipped with the running build, the tool aborts with an upgrade prompt instead of corrupting newer schema with stale logic.
15
+
16
+ ### Changed
17
+ - `wl.py` moved to `src/worklog/cli.py`; `schema.sql` moved to `src/worklog/migrations/0001_initial_schema.sql`. Internal imports rewritten throughout; tests, CI, and the `~/bin/wl` Makefile wrapper now go through the entry-point (`.venv/bin/wl`).
18
+ - `__version__` reads from `importlib.metadata.version("pyworklog")` — `pyproject.toml` is now the single in-repo source of truth for version, and `uv.lock` pins it deterministically. `release.yml` verifies tag against `pyproject.toml` only.
19
+ - `--cov` target switched from `wl` to `worklog`; coverage gate stays at 95%.
20
+
21
+ ### Removed
22
+ - Hand-edited `__version__` in `wl.py`. (Use `pyproject.toml`.)
23
+
24
+ ### Migration notes for existing users
25
+ - The DB path is unchanged (`$WORKLOG_DB` or `$XDG_DATA_HOME/worklog/worklog.db`). Existing DBs upgrade transparently on the first command after install: `_run_migrations()` no-ops on existing tables (the bootstrap migration uses `CREATE TABLE IF NOT EXISTS`) and stamps `user_version=1`. Verified on a 311 KB production-DB backup: row counts unchanged, `PRAGMA integrity_check = ok`.
26
+ - Re-run `make install` (or `make setup`) once so the `~/bin/wl` wrapper points at the new `.venv/bin/wl`.
27
+
28
+ ## [0.2.0] — 2026-05-31
29
+
30
+ ### Added
31
+ - **`wl config`** command — prints resolved DB path, aliases path, XDG directories, environment, runtime info; read-only (does not create the DB just to inspect paths).
32
+ - **`wl --db PATH`** global flag — per-invocation DB override (handy for testing or running against multiple worklogs); takes precedence over `$WORKLOG_DB` and the XDG default.
33
+ - **XDG Base Directory spec** — DB defaults to `$XDG_DATA_HOME/worklog/worklog.db` (`~/.local/share/worklog/worklog.db`); aliases at `$XDG_CONFIG_HOME/worklog/aliases.ini`. Paths resolved lazily at call time so `pytest`'s `monkeypatch` works without reload tricks.
34
+ - CI matrix on GitHub Actions (`ubuntu-latest` + `macos-latest` × Python 3.11 / 3.12 / 3.13) + Codecov coverage upload + status badges in README.
35
+ - **Release workflow** — push a `v*` tag → verify version anchors agree, run the test suite, publish a GitHub Release with auto-generated `Commits since vX.Y.Z` notes.
36
+ - **`AGENTS.md`** — vendor-neutral operating guide for AI coding agents (Claude Code, Cursor, Aider). Codifies TDD (Red-Green-Refactor) and DRY (single-source helpers) as project rules.
37
+ - **`CONTRIBUTING.md`** — full dev setup, TDD + DRY conventions, local Makefile overrides, release process.
38
+
39
+ ### Changed
40
+ - Project namespace fully renamed from `wl` to `worklog` (paths and env vars): `~/.local/share/wl/wl.db` → `~/.local/share/worklog/worklog.db`; `~/.config/wl/aliases.ini` → `~/.config/worklog/aliases.ini`; `$WL_DB` / `$WL_COLOR` / `$WL_THEME` → `$WORKLOG_*`. The CLI command stays `wl`.
41
+ - **uv** replaces `venv` + `pip` + `requirements*.txt`. `pyproject.toml` (PEP 621) + `dependency-groups` (PEP 735) + `uv.lock` is the dependency single-source. `make setup` runs `uv sync` and installs the `~/bin/wl` wrapper.
42
+ - `_setup_aliases` test fixture now also clears `XDG_CONFIG_HOME` / `XDG_DATA_HOME` — CI runners preset these and otherwise leak into tests.
43
+
44
+ ### Removed
45
+ - `requirements.txt` / `requirements-dev.txt` — replaced by `pyproject.toml` + `uv.lock`.
46
+ - Pre-XDG path fallbacks (`~/.worklog/wl.db`) — v0.1.0 was the only release that wrote there.
47
+
48
+ ## [0.1.0] — 2026-05-31
49
+
50
+ Initial open-source release of `worklog-cli` (`wl`).
51
+
52
+ ### Added
53
+ - 37 subcommands covering create / log / done / state transitions / tree / projects / day / summary / changes / find / focus / show.
54
+ - Bulk loaders: `wl import` (JSON) and `wl apply` (wl-diff format, symmetric with the rendered tree output).
55
+ - Compound flags on `wl add` / `done` / `cancel` — create + log + close + timestamp + vault-link + schedule in one shot.
56
+ - Brief mode `-q` on every command (skip log body / timeline / detail; ~50–90% character savings for AI consumers).
57
+ - `rich`-based highlighting with `dark` / `light` / `mono` themes, auto-detected via `$COLORFGBG` / OSC 11 terminal background probe.
58
+ - Shell completion (fish / bash / zsh) generated at runtime by `wl print-completion`; loaded via init-load (the starship / direnv / zoxide pattern).
59
+ - Bilingual project documentation (English + Chinese for README / DESIGN / SKILL).
60
+ - MIT License.
61
+
62
+ [Unreleased]: https://github.com/xyb/worklog/compare/v0.3.0...HEAD
63
+ [0.3.0]: https://github.com/xyb/worklog/compare/v0.2.0...v0.3.0
64
+
65
+ [0.2.0]: https://github.com/xyb/worklog/compare/v0.1.0...v0.2.0
66
+ [0.1.0]: https://github.com/xyb/worklog/releases/tag/v0.1.0
@@ -0,0 +1,116 @@
1
+ # Contributing to worklog
2
+
3
+ Thanks for considering a contribution! This guide covers the local setup, the development loop, and the two coding principles every change is held to: **TDD** and **DRY**.
4
+
5
+ ## Prerequisites
6
+
7
+ - [uv](https://docs.astral.sh/uv/) — install via `brew install uv` or `pipx install uv`.
8
+ - Python ≥ 3.11. `uv` handles the interpreter — no system Python juggling.
9
+
10
+ ## Quick start
11
+
12
+ ```fish
13
+ git clone https://github.com/xyb/worklog.git ~/projects/worklog
14
+ cd ~/projects/worklog
15
+ make setup # uv sync (creates .venv) + installs ~/bin/wl wrapper
16
+ make test # run the full suite (parallel, 95% cov gate)
17
+ ```
18
+
19
+ `make setup` does two things: `uv sync --all-groups` materializes `.venv/` from `pyproject.toml` + `uv.lock`, and `make install` writes `~/bin/wl` pointing into that venv. From there, every command (`uv run pytest …`, `make test`, etc.) uses the same locked deps.
20
+
21
+ ## Day-to-day development loop
22
+
23
+ ```fish
24
+ make test # parallel, cov, 95% gate — pre-commit baseline
25
+ make test-fast # parallel, no cov, no gate — quick iteration
26
+ make test-v # sequential, verbose, no cov — debug a single failure
27
+
28
+ # single test by node id
29
+ uv run pytest tests/test_wl.py::TestApply::test_apply_delete_cascades_subtree -v --no-cov
30
+
31
+ # coverage gate already lives in pytest.ini (--cov-fail-under=95); `make cov`
32
+ # just re-runs `make test` with term-missing output for inspection.
33
+ make cov
34
+ ```
35
+
36
+ Run `wl` against a throwaway DB without touching `~/.local/share/worklog/worklog.db`:
37
+
38
+ ```fish
39
+ wl --db /tmp/scratch.db init
40
+ wl --db /tmp/scratch.db add "experiment"
41
+ ```
42
+
43
+ `wl config` shows which DB is currently resolved (helpful when an env / flag mix is confusing).
44
+
45
+ ## Architecture
46
+
47
+ `DESIGN.md` is canonical for every shared convention — command style, state machine, marker symbols, time-window flags, render pipeline, schema, `import` / `apply` formats, theme keys, scheduled-time resolution, planned/unplanned derivation. Read the relevant section before adding a command or changing a format. If your change touches a convention, the same commit must update **DESIGN.md + `src/worklog/cli.py` + `tests/test_wl.py` + completion strings together** — drift between them is the failure mode this project guards against.
48
+
49
+ `AGENTS.md` is the operating guide for AI coding agents (Claude Code, Cursor, Aider). Skim it once even if you code by hand; it concentrates the hard-rules into one page.
50
+
51
+ ## TDD: red → green → refactor
52
+
53
+ Every change touching behavior follows the [Red-Green-Refactor cycle](https://brennanbrown.github.io/notes/programming/python-tdd/):
54
+
55
+ 1. **Red** — add a failing test in `tests/test_wl.py` first. Run it; confirm it fails for the reason you expect (not an import error). A test that never fails is not a test.
56
+ 2. **Green** — write the minimum production code to make that test pass. Resist the urge to also fix neighboring code; one cycle, one concern.
57
+ 3. **Refactor** — clean up. Extract helpers, remove duplication, rename. Tests still green at every step.
58
+
59
+ Bug fixes follow the same path: write a regression test that reproduces the bug first, then fix.
60
+
61
+ Tests don't ship without docstrings explaining what behavior they pin down (see existing `TestXDGPaths`, `TestConfig` for the style). One `Test<Command>` class per command. Cover happy path + boundaries (missing id, empty DB, illegal flag values) — the boundaries are where DESIGN drift surfaces first.
62
+
63
+ ## DRY: there is exactly one of each
64
+
65
+ The codebase has a small set of single-source helpers; new code must reuse them, never re-implement. The full list lives in DESIGN §12, but the load-bearing ones:
66
+
67
+ - `_status_marker(status)` — status → `[ ]/[x]/[/]/...` marker. Never hard-code the marker elsewhere.
68
+ - `_node_line(con, n, ...)` — the **only** node renderer. Any place listing nodes reuses it and inherits highlighting + search emphasis for free. Hand-rolling a node line is rejected in review.
69
+ - `out()` / `_c(text, style)` — the single output pipe. Bare `print()` for renderable content is rejected; any fragment that might contain `[ ]` must go through `_c` so `rich.markup` doesn't eat it.
70
+ - `_resolve_window(args)` — time-window flag resolution. Commands must not parse `--since` / `--until` / `--week` / `--month` themselves.
71
+ - `_resolve_db_path(args)` — DB path resolution (`--db` flag > `$WORKLOG_DB` > XDG default). Mirrored by `__wl_db_path[_bash|_zsh]` helpers in the generated shell completion — keep them in sync.
72
+
73
+ Same rule for documentation: install / dev / release info lives here, not duplicated in README. Sections in README that need this content link to it instead of restating it.
74
+
75
+ ## Local Makefile overrides
76
+
77
+ The Makefile loads any `local/*.mk` files at the end via `-include local/*.mk`. The `local/` directory is gitignored, so site-specific variables, private remotes, or extra targets go there without touching the shipped Makefile. Missing is fine — make won't complain.
78
+
79
+ Example `local/private.mk`:
80
+
81
+ ```makefile
82
+ GITEA_REMOTE := git@your-private-host:user/worklog.git
83
+
84
+ push-gitea: ## push current branch to private remote
85
+ @$(GIT) -c commit.gpgsign=false push $(GITEA_REMOTE) $$($(GIT) branch --show-current)
86
+ ```
87
+
88
+ After saving, `make help` lists `push-gitea` alongside the built-in targets.
89
+
90
+ ## Release process
91
+
92
+ Version has a **single** in-repo source: `version = "X.Y.Z"` in `pyproject.toml`. `__version__` in `src/worklog/cli.py` reads it via `importlib.metadata.version("worklog")` — never edit it by hand. The git tag `vX.Y.Z` must match the pyproject version; the release workflow enforces that.
93
+
94
+ The `Release` workflow (`.github/workflows/release.yml`) is triggered by pushing a `v*` tag. It verifies tag == pyproject version, re-runs the test suite, then calls `softprops/action-gh-release@v2` to publish a GitHub Release with auto-generated notes (commits since the previous tag).
95
+
96
+ To cut a release:
97
+
98
+ ```fish
99
+ # 1. bump version in pyproject.toml only
100
+ # 2. refresh the lock entry so uv.lock pins the new version
101
+ uv lock --upgrade-package worklog
102
+ # 3. commit + tag + push
103
+ git commit -am "chore: bump version to X.Y.Z"
104
+ git tag vX.Y.Z
105
+ git push origin main
106
+ git push origin vX.Y.Z
107
+ ```
108
+
109
+ The workflow fails the release if any of the three version anchors disagree, so a typo can't ship.
110
+
111
+ ## Pull requests
112
+
113
+ - Branch from `main`. PRs target `main`.
114
+ - Each PR moves the test count up: a refactor adds no tests but keeps coverage ≥95%; a feature/fix adds the test that exercises it.
115
+ - Run `make test` locally before pushing. CI runs the same parallel pytest + 95% gate across ubuntu + macos × Python 3.11/3.12/3.13.
116
+ - DESIGN.md / AGENTS.md drift is the most common review block — when in doubt, update them in the same PR.