pyworklog 0.3.0__tar.gz → 0.5.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.
- pyworklog-0.5.0/CHANGELOG.md +119 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/DESIGN.md +38 -5
- {pyworklog-0.3.0 → pyworklog-0.5.0}/DESIGN.zh.md +38 -5
- {pyworklog-0.3.0 → pyworklog-0.5.0}/PKG-INFO +4 -1
- {pyworklog-0.3.0 → pyworklog-0.5.0}/README.md +3 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/README.zh.md +3 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/pyproject.toml +1 -1
- {pyworklog-0.3.0 → pyworklog-0.5.0}/skills/worklog-cli/SKILL.md +76 -11
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/cli.py +163 -36
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/commands/__init__.py +7 -2
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/commands/bulk.py +76 -58
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/commands/meta.py +123 -58
- pyworklog-0.5.0/src/worklog/commands/metric.py +337 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/commands/query.py +222 -113
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/commands/state.py +268 -133
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/commands/views.py +295 -91
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/completion.py +11 -5
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/db.py +16 -6
- pyworklog-0.5.0/src/worklog/db_table.py +174 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/helpers.py +23 -11
- pyworklog-0.5.0/src/worklog/migrations/0002_metric_and_log_tag.sql +106 -0
- pyworklog-0.5.0/src/worklog/migrations/0003_backfill_checkin_metrics.sql +20 -0
- pyworklog-0.5.0/src/worklog/migrations/0004_meta_props_to_typed_logs.sql +35 -0
- pyworklog-0.5.0/src/worklog/migrations/0005_clock_table.sql +59 -0
- pyworklog-0.5.0/src/worklog/migrations/0006_rename_log_type_to_tag.sql +14 -0
- pyworklog-0.5.0/src/worklog/migrations/0007_utc_timestamps.sql +46 -0
- pyworklog-0.5.0/src/worklog/queries.py +314 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/render.py +2 -2
- pyworklog-0.5.0/src/worklog/timeutil.py +159 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/conftest.py +9 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_add.py +38 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_brief.py +47 -27
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_checkin.py +8 -16
- pyworklog-0.5.0/tests/test_clock.py +324 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_completion.py +15 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_dateinfo.py +3 -3
- pyworklog-0.5.0/tests/test_day.py +237 -0
- pyworklog-0.5.0/tests/test_db_table.py +197 -0
- pyworklog-0.5.0/tests/test_filters.py +186 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_find.py +16 -16
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_focus.py +27 -3
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_import_apply.py +63 -38
- pyworklog-0.5.0/tests/test_link_set.py +121 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_log.py +13 -23
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_log_format.py +1 -1
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_logs.py +20 -0
- pyworklog-0.5.0/tests/test_meta.py +120 -0
- pyworklog-0.5.0/tests/test_metric.py +678 -0
- pyworklog-0.5.0/tests/test_migrations.py +372 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_projects.py +4 -4
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_render.py +16 -16
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_sched.py +112 -27
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_show.py +19 -1
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_state.py +61 -17
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_summary.py +1 -1
- pyworklog-0.5.0/tests/test_timeutil.py +136 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_tree.py +5 -3
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_ux.py +27 -27
- {pyworklog-0.3.0 → pyworklog-0.5.0}/uv.lock +1 -1
- pyworklog-0.3.0/CHANGELOG.md +0 -66
- pyworklog-0.3.0/src/worklog/queries.py +0 -216
- pyworklog-0.3.0/tests/test_clock.py +0 -191
- pyworklog-0.3.0/tests/test_day.py +0 -148
- pyworklog-0.3.0/tests/test_link_set.py +0 -51
- pyworklog-0.3.0/tests/test_meta.py +0 -59
- pyworklog-0.3.0/tests/test_migrations.py +0 -136
- {pyworklog-0.3.0 → pyworklog-0.5.0}/.github/workflows/release.yml +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/.github/workflows/test.yml +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/.gitignore +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/AGENTS.md +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/CONTRIBUTING.md +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/LICENSE +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/Makefile +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/pytest.ini +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/__init__.py +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/migrations/0001_initial_schema.sql +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/src/worklog/xdg.py +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/__init__.py +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_aliases.py +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_cascade.py +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_changes.py +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_config.py +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_init.py +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_ls.py +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_sample.py +0 -0
- {pyworklog-0.3.0 → pyworklog-0.5.0}/tests/test_xdg.py +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
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.5.0] — 2026-06-05
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Shared `--tag` / `--kind` / `--status` filter across `wl ls` / `tree` / `day` / `logs` / `agenda`** — one filter with the same meaning everywhere (wired through a parent parser, like the existing time-window flags), instead of each command rolling its own. `-t` / `--tag` is comma-separated **AND** (the node must carry every listed tag); `--kind` / `--status` are equality. So `wl day -t work` shows only the work bucket (empty buckets/groups aren't rendered, and the summary line counts the filtered set), `wl tree -t work` prunes the tree to matching nodes plus the ancestor paths that lead to them, and `ls` / `logs` / `agenda` filter identically. An explicit `--status DONE/CANCELED` overrides the default terminal-status hide. `-t` is now the short form on all five (matching `wl add -t`).
|
|
13
|
+
- **`wl day` header states the day's nature** — every day now reads at a glance: `workday` (Mon–Fri) or `weekend` (Sat/Sun) by default, refined to `holiday` / `leave` / `workday` when a `wl dateinfo` label says so, with the label appended for context — e.g. `2026-06-01 Mon · Children's Day holiday`, `2026-06-13 Sat · makeup workday`, `2026-05-21 Thu · workday (Grain Buds solar term)`. Previously the header showed a label only when one existed, so a bare day told you nothing about its working status.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **Timestamps follow an `at`/`date` convention — precise instants stored UTC, literal dates kept local.** A column named `*_at` (or bare `at`: `created_at` / `closed_at` / `logged_at` / `metric.at` / `clock.start_at` / `end_at` / `sched.created_at`) is a precise instant, now stored as UTC and rendered in local time; a column holding a calendar *day* is named `*date` and kept verbatim as a local date. As part of this, `node.scheduled_at` → **`scheduled_date`** and `node.deadline_at` → **`deadline_date`** (they hold a planned/deadline day, plus fuzzy `@2026-06` pins — not instants). "Local" is the machine zone by default; `$WORKLOG_TZ` pins a fixed offset (e.g. `+08:00`) for reproducibility / overriding. Day-grouping (`wl day` / `summary` / `logs --date` / habit month-to-date) converts UTC→local before bucketing, so a log near local midnight lands on the right day. **Migration 0007** converts pre-existing rows from local (+08:00) to UTC (bare `YYYY-MM-DD` values left untouched) and renames the two date columns; new installs are unaffected.
|
|
17
|
+
- **Habit month-to-date label is English** — `(本月 N/M)` → `(this month N/M)` in `wl day` / `wl tree`, matching the rest of the output.
|
|
18
|
+
|
|
19
|
+
### Internal
|
|
20
|
+
- **A zero-dependency single-table CRUD layer (`db_table`).** All hand-written single-table SQL — inserts, updates, deletes, selects, dynamically-assembled `WHERE` / `SET`, and `INSERT OR IGNORE/REPLACE` — now goes through a small helper (`insert` / `update` / `delete` / `query` / `query_one` / `get` / `exists` / `count` / `clause`) with identifier validation and parameter binding throughout; SQL `datetime('now')` is gone (all timestamps flow through `timeutil`). No behavior change — a maintainability/safety refactor, codified as design goal **G3** (few deps: stdlib + `rich`; simple explicit SQL over query-builder DSLs; small helpers over an ORM). Genuinely-complex queries (display JOINs, `GROUP BY`, correlated subqueries) stay raw by design. The test fixtures were also de-Chinese'd to English (the relative-date input aliases like `明天` / `下周` remain — they're a feature).
|
|
21
|
+
|
|
22
|
+
## [0.4.0] — 2026-06-05
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **`wl sched` is now idempotent** — scheduling a task to a date/recurrence it already has no longer inserts a duplicate `sched` row; it reports `= already scheduled` instead. Previously repeated `wl sched 42 2026-06-02` accumulated duplicate rows.
|
|
26
|
+
- **`wl day` no longer tags DONE/CANCELED tasks `«planned·not-done»`** — a terminal-status task scheduled on a day with no logs is done, not pending. Both the per-row hint and the footer `planned·not-done N` count now skip terminal-status tasks (removes the contradictory `[x] … «planned·not-done»`).
|
|
27
|
+
- **`wl focus <day>` expands that day's activity** — focusing a day node now shows the nodes with a log / schedule on that date (same as `wl tree` / `wl day`), not just the handful of nodes whose `parent_id` is the day. Previously focusing a date dropped a dozen tasks that hung under their projects/habit groups; `focus` was the only path bypassing the day special-case.
|
|
28
|
+
- **Auto-created day builds its full time-ancestor chain** — `wl goal` / `wl recap` on a date whose week/month doesn't exist yet (e.g. the first of a new month) no longer dangles the new day with `parent_id NULL`; it builds the `year→quarter→month→week` skeleton on demand (lenient lookup reuses an existing `2026` or `2026 年` year). Fixes broken per-month aggregation.
|
|
29
|
+
- **`wl apply` gives an actionable error for an un-indented field op** — a flush-left `parent 6` (which must be indented under its `~ #id` lock line) now says "field op must be indented under `~ #id`" instead of the opaque "cannot parse 'parent 6'" that read as "parent isn't supported".
|
|
30
|
+
- **Shell completion no longer offers `someday` for concrete-date commands** — `wl day <tab>` (like `log` / `logs` / `unlog`) only suggests concrete dates now; `someday` is offered only for `sched` / `defer`, which actually accept fuzzy granularities. Previously picking `someday` for `wl day` just errored.
|
|
31
|
+
- **Flat `wl logs` row stays on one line with CJK titles** — the row now budgets by terminal display width (CJK = 2 columns) instead of slicing the title to 30 characters, so a wide CJK title no longer overruns the line and wraps the body.
|
|
32
|
+
- **`wl day` plan grouping collapsed to planned / unplanned** — dropped the confusing third `unplanned (untagged)` bucket; now that planned/unplanned is derived from `sched`, "has no tag" and "has the unplanned tag" both just mean "not scheduled".
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **`wl agenda <start> <end>`** — cross-time-range scheduling overview. Lists every node scheduled within a date range across all granularities, ordered by anchor date, reading **both** schedule sources: the `sched` table's concrete one-off days and `node.scheduled_at` fuzzy pins (`@2026-06` month / week / `someday`). A per-month tree view misses month- and someday-pinned tasks; `agenda` surfaces them in one list (the duplicate-check companion to the `wl add` warning below). `--all` includes DONE/CANCELED; `--someday` appends fuzzy pins.
|
|
36
|
+
- **`wl add` warns when a similar open task/project exists** — a best-effort title-similarity check prints a non-blocking warning listing suspected duplicates (the node is still created, so the operator can reuse via `wl sched` / `wl link` instead). Match is on normalized titles (lowercased, spaces/punctuation stripped), identical or containment with the shorter ≥4 chars; scope is task/project, DONE/CANCELED excluded.
|
|
37
|
+
- **`wl tag <id> +work -planned`** — edit a node's real tags directly (`+`/bare word adds idempotently, `-` removes, no args lists). Closes a footgun: `wl set <id> tags X` used to silently create a shadow `tags` prop while the real tag field — which bucketing/grouping reads — stayed untouched; that path is now rejected with a pointer to `wl tag` (same for import `update {tags:…}` → `add_tags`/`remove_tags`).
|
|
38
|
+
- **`wl unlink <id> "doc"`** — remove a single vault-doc link, symmetric with `wl link`; previously removing one mistaken link meant wiping them all. Multiple ids supported; a no-op prints a notice rather than failing.
|
|
39
|
+
- **`wl recap --date YYYY-MM-DD`** — read or back-fill a past day's recap (also `today`/`yesterday`/CJK aliases). Unlike the old `wl set <day> summary`, it stamps `summary_at` so the "changes after recap" staleness warning clears correctly, and creates the day node (+ its time-ancestor chain) on demand.
|
|
40
|
+
- **Habit lines show month-to-date completion** — in `wl day` / `wl tree`, a scheduled habit shows `(本月 N/M)`: N = distinct days this month (≤ the viewed day) with a check-in, M = days its schedule fires in that window. No schedule → no rate.
|
|
41
|
+
- **Metrics fold under their node/log in `wl show` / `wl day` / `wl tree`** — datapoints render indented (`↳ [glucose] 5.4 mmol/L`); in `wl show` an empty `tag='metric'` carrier log shows its datapoint directly as a `📊 metric` line (no blank log row); `wl day`/`wl tree` show a node's that-day datapoints under it (the `checkin` marker is skipped — it's reflected by `[x]`); >5 on one node/log are elided (`↳ … N more datapoints`).
|
|
42
|
+
- **`node → log → metric` data model** — a new `metric` table holds structured datapoints (check-in / number / measurement) that hang off a log; `log` gains a `tag` column for the log's role (note/goal/summary/clock/…), unifying the classification field across all three layers: node-level `tag` (a label set), `metric.tag` (a datapoint's kind), and `log.tag` (a log's role) — one word, one mental model. Foundation for querying/aggregating what was previously stuffed into log text (migrations 0002 + 0006, two-round cross-model reviewed). A metric must hang off a log; `metric.node_id` is denormalized (no FK, trigger-kept consistent); markers store `value_num=1`.
|
|
43
|
+
- **`wl metric add/ls/edit/rm`** — full CRUD for datapoints. `add` creates a carrier log (or `--on-log #L<id>`); numeric vs text autodetected (`--text` forces text); `--unit/--note/--at`; `ls` defaults to this week (`--all`, `--tag`, `--since/--until/--week/--month`); `rm` also cleans up an emptied auto-carrier log. Metric ids display/parse as `#M<id>`.
|
|
44
|
+
- **`--metric` shortcut on `wl log` / `wl add`** — attach datapoints in the same command: `wl log 42 "..." --metric 'glucose 5.4 mmol/L' --metric checkin` (repeatable; on `add`, reuses the `--log` carrier or makes one).
|
|
45
|
+
- **Habit check-in is now a structured signal** — `wl tick` / `wl checkin` attach a `checkin` metric (idempotent per day); "habit done today" (in `wl day` / `wl tree` / `wl checkin`) reads that metric instead of the old "did any log exist that day", so a stray note no longer marks a habit done. Migration 0003 backfills historical habit completions.
|
|
46
|
+
- **`wl import` supports metrics** — a log entry may carry `"metrics": [...]`, and a node may carry node-level `"metrics": [...]` attached to one carrier log (1 carrier → N datapoints, e.g. importing a day of CGM readings without 288 separate logs).
|
|
47
|
+
- **Structured time tracking (`clock` table)** — `wl start` / `stop` / `spent` / `wait` now read/write a real `clock(node_id, start_at, end_at, elapsed_sec)` interval table instead of the `CLOCK_IN` / `CLOCK_OUT` log-body convention; durations are summed from a typed column (G1), not parsed out of text. `wl active` lists open intervals; `wl show` renders intervals as `start→end (Nmin)`; `wl day` / `wl summary` totals come from the table. Migration 0005 pairs existing CLOCK_IN/OUT logs into clock rows and removes them. (`wl unlog`/`wl relog` no longer need CLOCK guards — clock is no longer a log.)
|
|
48
|
+
- **Day/week/month meta fields keep history** — `goal` / `summary` (recap) / `overview` / `top5` moved from single-value `prop`s to history-preserving typed logs (`log.tag`): each `wl goal` / `wl recap` / `wl set <node> overview|top5` write appends a new log, and the latest is the current value, so you can see how a goal or recap evolved. The recap "written at" + stale-recap warning now come from the summary log's own timestamp (the `summary_at` prop is gone). Migration 0004 converts existing props; `prop` returns to truly-static attributes.
|
|
49
|
+
- **`wl show` now displays the schedule** — a `schedule:` line lists the task's one-off dates and recurring rules (previously only visible via raw SQL on the `sched` table). First-hand info when debugging why a task shows on multiple days.
|
|
50
|
+
- **`-q`/`--brief` is accepted after the subcommand too** (`wl day -q`), not only globally before it (`wl -q day`). Implemented by injecting the flag into every subparser with `default=SUPPRESS` so it never clobbers the global value.
|
|
51
|
+
- **`wl done` warns on recurring tasks** — running `wl done` on a task with a recurring rule prints a hint that `wl done` retires the whole task (marks it done on every scheduled day) and that `wl tick <id>` is the way to record just today's occurrence. Mitigates accidentally finishing a recurring habit/meta-task.
|
|
52
|
+
|
|
53
|
+
## [0.3.0] — 2026-06-01
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
- `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.
|
|
57
|
+
- **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).
|
|
58
|
+
- **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.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
- `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`).
|
|
62
|
+
- `__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.
|
|
63
|
+
- `--cov` target switched from `wl` to `worklog`; coverage gate stays at 95%.
|
|
64
|
+
|
|
65
|
+
### Removed
|
|
66
|
+
- Hand-edited `__version__` in `wl.py`. (Use `pyproject.toml`.)
|
|
67
|
+
|
|
68
|
+
### Migration notes for existing users
|
|
69
|
+
- 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`.
|
|
70
|
+
- Re-run `make install` (or `make setup`) once so the `~/bin/wl` wrapper points at the new `.venv/bin/wl`.
|
|
71
|
+
|
|
72
|
+
## [0.2.0] — 2026-05-31
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
- **`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).
|
|
76
|
+
- **`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.
|
|
77
|
+
- **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.
|
|
78
|
+
- CI matrix on GitHub Actions (`ubuntu-latest` + `macos-latest` × Python 3.11 / 3.12 / 3.13) + Codecov coverage upload + status badges in README.
|
|
79
|
+
- **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.
|
|
80
|
+
- **`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.
|
|
81
|
+
- **`CONTRIBUTING.md`** — full dev setup, TDD + DRY conventions, local Makefile overrides, release process.
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
- 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`.
|
|
85
|
+
- **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.
|
|
86
|
+
- `_setup_aliases` test fixture now also clears `XDG_CONFIG_HOME` / `XDG_DATA_HOME` — CI runners preset these and otherwise leak into tests.
|
|
87
|
+
|
|
88
|
+
### Removed
|
|
89
|
+
- `requirements.txt` / `requirements-dev.txt` — replaced by `pyproject.toml` + `uv.lock`.
|
|
90
|
+
- Pre-XDG path fallbacks (`~/.worklog/wl.db`) — v0.1.0 was the only release that wrote there.
|
|
91
|
+
|
|
92
|
+
## [0.1.0] — 2026-05-31
|
|
93
|
+
|
|
94
|
+
Initial open-source release of `worklog-cli` (`wl`).
|
|
95
|
+
|
|
96
|
+
### Added
|
|
97
|
+
- **Single-table execution-system model** — one `node` table holds the whole hierarchy across 13 kinds (`lifetime` / `decade` / `year` / `quarter` / `month` / `week` / `day` / `area` / `project` / `task` / `habit` / `signal` / `meetlog`), sharing one id space and tree-linked via a `parent_id` self-reference. Six sibling tables hang off it: `log` (timestamped entries), `tag` (labels), `sched` (scheduling), `prop` (key-value attributes), `link` (vault-doc references), `date_meta`.
|
|
98
|
+
- **Seven-state task lifecycle** — `TODO` / `DOING` / `LATER` / `WAIT` / `DONE` / `DEFERRED` / `CANCELED`, a superset of the markdown `[ ]` / `[/]` / `[>]` / `[x]` four-state set that adds `LATER` (deferred to the future) vs. `WAIT` (blocked on someone). Plus `A` / `B` / `C` priority (rendered `[#A]` = P0) and auto-stamped `closed_at`.
|
|
99
|
+
- **37 subcommands** — create/edit (`add` / `set` / `link` / `init`), logging (`log` / `unlog` / `relog`), state transitions (`done` / `defer` / `start` / `stop` / `spent` / `active` / `wait` / `reopen` / `cancel` / `tick` / `checkin`), views (`show` / `ls` / `tree` / `focus` / `ancestors` / `descendants` / `projects` / `changes` / `summary` / `day`), meta fields (`goal` / `recap`), scheduling (`sched` / `dateinfo`), bulk I/O (`import` / `apply`), search (`find` / `logs`), and display (`themes` / `print-completion`). Most carry short aliases.
|
|
100
|
+
- **Scheduling with recurrence** — the `sched` table records both one-off dates (`on_date`) and recurrence rules (`rrule`: `daily`, `weekly:Mon,Wed,Fri`); a task scheduled for a given day counts as planned, derived from `sched` rather than a manual tag.
|
|
101
|
+
- **Time tracking** — `wl start` / `stop` / `spent` / `active` / `wait` record work intervals and report durations per node.
|
|
102
|
+
- **Project association via shared tags** — `wl projects` rolls a project's members up by structural children plus shared semantic tags, with a `GENERIC_TAGS` set (work / personal / planned / unplanned / P0–P2 / habit / meeting / …) excluded so generic dimension tags don't cross-link unrelated work.
|
|
103
|
+
- **Bulk loaders** — `wl import` (JSON) and `wl apply` (wl-diff format, symmetric with the rendered tree output) for scripted/AI-driven batch entry.
|
|
104
|
+
- **Compound flags on `wl add` / `done` / `cancel`** — create + log + close + timestamp + vault-link + schedule in one shot.
|
|
105
|
+
- **Date aliases** — `today` / `yesterday` / `tomorrow` / `day-before-yesterday` / … and Chinese equivalents resolve to concrete dates wherever a date is accepted.
|
|
106
|
+
- **Brief mode `-q`** on every command (skip log body / timeline / detail; ~50–90% character savings for AI consumers).
|
|
107
|
+
- **`rich`-based highlighting** with `dark` / `light` / `mono` themes, auto-detected via `$COLORFGBG` / OSC 11 terminal-background probe; manual override via `--theme` or `$WL_COLOR` / `$WL_THEME` / `$NO_COLOR`.
|
|
108
|
+
- **Shell completion** (fish / bash / zsh) generated at runtime by `wl print-completion`; loaded via init-load (the starship / direnv / zoxide pattern).
|
|
109
|
+
- **Vault integration** — `wl link` associates a node with one or more Obsidian vault docs (`[[doc]]`), kept in the `link` table for a two-way knowledge graph.
|
|
110
|
+
- Bilingual project documentation (English + Chinese for README / DESIGN / SKILL).
|
|
111
|
+
- MIT License.
|
|
112
|
+
|
|
113
|
+
[Unreleased]: https://github.com/xyb/worklog/compare/v0.5.0...HEAD
|
|
114
|
+
[0.5.0]: https://github.com/xyb/worklog/compare/v0.4.0...v0.5.0
|
|
115
|
+
[0.4.0]: https://github.com/xyb/worklog/compare/v0.3.0...v0.4.0
|
|
116
|
+
[0.3.0]: https://github.com/xyb/worklog/compare/v0.2.0...v0.3.0
|
|
117
|
+
|
|
118
|
+
[0.2.0]: https://github.com/xyb/worklog/compare/v0.1.0...v0.2.0
|
|
119
|
+
[0.1.0]: https://github.com/xyb/worklog/releases/tag/v0.1.0
|
|
@@ -5,6 +5,34 @@
|
|
|
5
5
|
> Every command must follow the unified conventions in this document. Read this before adding a new command or changing an old one, to keep things consistent.
|
|
6
6
|
> If conventions change, update this document, all affected commands, and the tests together.
|
|
7
7
|
|
|
8
|
+
## 0. Design goals (north star, override every specific convention)
|
|
9
|
+
|
|
10
|
+
These goals are foundational. Pass them before adding or changing any feature; when they conflict with a specific convention below, they win.
|
|
11
|
+
|
|
12
|
+
### G1 Structured-first (no string matching)
|
|
13
|
+
|
|
14
|
+
worklog's whole job is to **structure** the work log so information can be precisely queried / aggregated / analyzed. Any semantics that "must be searchable/countable later" has to live in a **structured field (its own column + type)** — never via string matching / prefix conventions on the body text. That is not structured; it can't be queried or counted reliably.
|
|
15
|
+
|
|
16
|
+
- For a new feature, ask first: "what is its structured carrier?" (which table, which column, what type) — not "which blob of text do I stuff it into".
|
|
17
|
+
- Anti-patterns (to be phased out): CLOCK events detected via `body LIKE 'CLOCK_%'`; habit completion inferred from "is there a log that day". Both are string conventions — fragile and unaggregatable.
|
|
18
|
+
|
|
19
|
+
### G2 Minimal & self-evident (usable without docs, by humans and AI alike)
|
|
20
|
+
|
|
21
|
+
The tool must be simple enough to **use without reading the manual**, for both humans and AI. AI faces a plain-text interface; complexity makes it err.
|
|
22
|
+
|
|
23
|
+
- Keep the **kinds of records hung under a node** restrained (fewer is better): each extra kind is one more concept to remember and one more block to render — heavier, harder to grasp.
|
|
24
|
+
- Self-check any new design with three questions: (1) how many new concepts does it add? (2) does using it force AI/humans to make a choice ("A or B?")? (3) can you guess it right without docs? If any answer is poor, simplify further.
|
|
25
|
+
- When G1 and G2 conflict, find the "structured AND fewest-concepts" answer — don't pile on new tables / fields / commands just to be structured.
|
|
26
|
+
|
|
27
|
+
### G3 Few dependencies, simple logic, easy to maintain
|
|
28
|
+
|
|
29
|
+
worklog deliberately stays **dependency-light and low-abstraction**: runtime is **stdlib + `rich` only**, and new features prefer **zero new dependencies**. Logic stays **explicit and direct** — plain SQL over query-builder DSLs, small purpose-built helpers over frameworks/ORMs that hide what is happening. The bar: a maintainer (human or AI) can read any code path top-to-bottom without first learning a hidden layer.
|
|
30
|
+
|
|
31
|
+
- **Borrow the technique, not the library.** Prefer writing a tens-of-lines, zero-dependency helper over pulling in a package (e.g. a dict→`INSERT` helper in the spirit of sqlite-utils, *not* an ORM; a `field__op`→`WHERE` helper, *not* a query builder).
|
|
32
|
+
- **No ORM / no query builder.** Wrap only the uniform ~80 % (single-table CRUD + existence/count) in thin helpers that map transparently to one SQL statement; keep the complex ~20 % (JOIN / CTE / CASE / time-window) as explicit SQL. Don't reinvent SQL as a kwargs DSL.
|
|
33
|
+
- **Each table / module maintainable on its own.** Minimize forced coupling (hard FK cascades, cross-cutting magic, triggers that hide intent) so one part can change — or be migrated / synced — without rippling everywhere. Lean toward avoiding irreversible operations (prefer soft-delete / status over `DELETE`) where they create cross-table consistency burdens.
|
|
34
|
+
- When a "clever" abstraction conflicts with G3, choose the boringly-simple version. Fewer moving parts beats elegance.
|
|
35
|
+
|
|
8
36
|
## 1. Command style (todo.sh school)
|
|
9
37
|
|
|
10
38
|
- Verb first: `wl <verb> [args] [--flags]`
|
|
@@ -17,13 +45,18 @@
|
|
|
17
45
|
|
|
18
46
|
A single `node` table carries everything; the `kind` field discriminates type; `parent_id` self-reference builds the tree. The schema is delivered as numbered SQL migrations under `src/worklog/migrations/NNNN_*.sql`; `PRAGMA user_version` tracks the highest applied migration. `ensure_db()` auto-applies pending migrations on every command; `wl migrate` is the explicit form. See `src/worklog/migrations/0001_initial_schema.sql` for the initial layout and `README.md` for the high-level picture.
|
|
19
47
|
|
|
48
|
+
> **Migration-authoring rule**: the runner wraps each file in one `BEGIN/COMMIT` (so a mid-script failure rolls the whole file back). Migration files must therefore **not** contain their own `BEGIN`/`COMMIT`.
|
|
49
|
+
|
|
20
50
|
- **kind values**: `lifetime / decade / year / quarter / month / week / day / area / project / task / meetlog / habit / signal` (extensible, but new kinds should have a clear place in tree / projects / summary classification)
|
|
21
51
|
- **status only applies to task / habit / meetlog**; time-hierarchy kinds (year/month/...) and project kind leave status NULL
|
|
22
|
-
- Tables: `node / tag / log / prop / link / sched / date_meta` + derived view `v_node_path`
|
|
52
|
+
- Tables: `node / tag / log / metric / clock / prop / link / sched / date_meta` + derived view `v_node_path`
|
|
53
|
+
- **`node → log → metric` spine** (the log-centric core): a `node` has many `log`s; a `log` (carrying a `tag` — `note`/`goal`/`summary`/`overview`/`top5`/`clock`(carrier)/… , NULL = plain note) has 0..N `metric`s. A `metric` is a structured datapoint (`tag` = what it is e.g. `glucose`/`pullups`/`checkin`; `value_num`/`value_text`/`unit`/`note`/`at`). **`tag` is the uniform classification field across all three** — node (a multi-value label set), log (its role, single-value), metric (its kind, single-value); same word, different scopes, SQL-unambiguous. A metric **must hang off a log** (`metric.log_id` NOT NULL) — so every datapoint has a log carrier; a `CHECK` requires a value (pure markers store `value_num=1`). `metric.node_id` is denormalized for join-free per-node queries (no FK; triggers keep it equal to the carrier log's node). CRUD surface: `wl metric add/ls/edit/rm` (`add` without `--on-log` creates a carrier log; a value-less marker is stored as `value_num=1`); `--metric` on `wl log`/`wl add` and `metrics` in `wl import` attach datapoints inline. Habit "done today" = a `tag=checkin` metric that day (written by `wl tick`/`wl checkin`), not "any log exists".
|
|
54
|
+
- **Meta fields are history-preserving typed logs**: day `goal`/`summary`, week `overview`, month `top5` are `log.tag` logs (latest = current; each edit appends), written by `wl goal`/`wl recap`/`wl set <node> <key>`. (`prop` is back to truly-static single-value attributes.)
|
|
55
|
+
- **`clock` is structured time tracking**: `clock(node_id, start_at, end_at, elapsed_sec)`, written by `wl start`/`stop`/`spent`/`wait` — replaces the old `CLOCK_IN`/`CLOCK_OUT` log-body convention. Durations are summed from `elapsed_sec`, not parsed from text.
|
|
23
56
|
- **Two parallel trees, both hung under lifetime**:
|
|
24
57
|
- **Responsibility line**: `lifetime → area → project → task` (PARA model: area is a cross-time responsibility domain, projects belong to areas, tasks belong to projects)
|
|
25
|
-
- **Time line**: `lifetime → year → quarter → month → week → day` (carries
|
|
26
|
-
- `
|
|
58
|
+
- **Time line**: `lifetime → year → quarter → month → week → day` (carries the time skeleton + meta typed logs: day's `goal`/`summary`, month's `top5`/`goal`, week's `overview`)
|
|
59
|
+
- The recap (`summary` log) carries its own `logged_at` = write time. `wl day` shows "(written ...)" and, if there are later plain-note logs that day (`tag IS NULL`), warns "⚠ N changes after recap, consider rewriting". (Replaces the old `summary_at` prop.)
|
|
27
60
|
- **Projects no longer hang under months** (legacy design did; migrated to areas). Day/month/week views derive from **log's `logged_at`** (time dimension) and **kind/tag/ancestor chain** (domain dimension) — so moving projects under areas does not affect any per-day or per-project view.
|
|
28
61
|
|
|
29
62
|
## 3. State machine (#+TODO style)
|
|
@@ -372,7 +405,7 @@ Goal: reproduce the day-dimension view of "all projects + progress on a given da
|
|
|
372
405
|
- **`wl day [date]` is log-date driven**, no longer requires a day node to exist (historical data lists by day too): query logs with `date(logged_at)=target` (excluding `CLOCK_*` accounting rows, restricted to task/habit/meetlog), render as **bucket → secondary group → task → indented logs**, with footer stats "N tasks made progress + status distribution + CLOCK"
|
|
373
406
|
- **Bucket = `work`/`personal` tag** → `Work` / `Personal` / `Other` (`_node_bucket`), order in `_BUCKET_ORDER`
|
|
374
407
|
- **Secondary group `--by`** (`_sec_group` / `_sec_sort_key`):
|
|
375
|
-
- `plan` (**`wl day` default** — closest to markdown day structure): `planned` tag → `Planned`;
|
|
408
|
+
- `plan` (**`wl day` default** — closest to markdown day structure): scheduled that day (or migration-era `planned` tag) → `Planned`; everything else → `Unplanned`. (The old separate `Unplanned (untagged)` bucket was merged into `Unplanned` — now that planned/unplanned derives from sched, the tag distinction has no value and the label misled.)
|
|
376
409
|
- `project`: project ancestor (`_node_project` picks the `kind=project` ancestor). `wl logs --group day` still defaults to `project`
|
|
377
410
|
- `priority`: `A/B/C → P0/P1/P2`, no priority → `—` (defaults to unplanned but flagged unconfirmed). ⚠️ **Planned/unplanned is fundamentally per-day (per-log)** (a task may be planned today, unplanned tomorrow); hanging the mark on the task itself is an approximation after merging migration. Precise modeling would push the mark down to log rows (schema not yet done, decision pending).
|
|
378
411
|
- **`wl logs --group day [--by ...]`** reuses `_render_day_group`: groups by date header, each day has the same structure as `wl day`
|
|
@@ -391,7 +424,7 @@ The decision model: **schedule (forward planning, calendar-like) and log (retros
|
|
|
391
424
|
- **`wl sched <id> [when] [--recur R] [--clear]`**: schedule / recur / clear / no-arg = list. `when` goes through `_resolve_concrete_date` (YYYY-MM-DD / today / tomorrow / day-after-tomorrow).
|
|
392
425
|
- **`wl day` derivation (`_node_plan(con, nid, sched_ids)`)**:
|
|
393
426
|
- Planned = `nid in sched_ids` (hit by schedule, scheduled in advance) **or** migration-period `planned` tag
|
|
394
|
-
- Unplanned =
|
|
427
|
+
- Unplanned = not in sched_ids and no `planned` tag (a logged-but-unscheduled task is unplanned; the former `unplanned` tag and the no-tag case are now one bucket)
|
|
395
428
|
- **Tasks scheduled but not yet logged are also listed** (`wl day` merges sched-only nodes into items, marked `«planned · not yet done»`), implementing "plan visible in advance, log added when actually doing"
|
|
396
429
|
- **Division of labor with `scheduled_at` (§20)**: `scheduled_at` = fuzzy todo time (someday / 2026-06, backlog hint); `sched` = specific calendar placement (drives planned). The two complement each other and do not auto-sync.
|
|
397
430
|
- **Migration transition**: legacy data uses `planned`/`unplanned` tags for planned/unplanned (no sched), and `_node_plan` falls back to tags; future real scheduling goes through sched, gradually dropping the tags.
|
|
@@ -5,6 +5,34 @@
|
|
|
5
5
|
> 所有命令必须遵守本文档的统一约定。加新命令 / 改老命令前先读这里,保持一致。
|
|
6
6
|
> 改了约定 → 同步更新本文档 + 所有受影响命令 + 测试。
|
|
7
7
|
|
|
8
|
+
## 0. 设计目标(北极星,凌驾于一切具体约定之上)
|
|
9
|
+
|
|
10
|
+
下面几条是地基。加任何功能、改任何设计前先过这几关;跟下文具体约定冲突时,以这几条为准。
|
|
11
|
+
|
|
12
|
+
### G1 结构化优先(不靠字符串匹配)
|
|
13
|
+
|
|
14
|
+
worklog 的整个职责就是把工作记录**结构化**,让信息能被精确查询 / 聚合 / 分析。任何"以后要能搜 / 能统计"的语义,都必须落在**结构化字段(独立的列 + 类型)**里,绝不靠对正文做字符串匹配 / 前缀约定。那不叫结构化,查不准也统计不了。
|
|
15
|
+
|
|
16
|
+
- 加新功能先问:"它的结构化载体是什么?"(哪张表、哪个列、什么类型)——而不是"塞进哪段文本里"。
|
|
17
|
+
- 反模式(要逐步淘汰):靠 `body LIKE 'CLOCK_%'` 检测 CLOCK 事件;靠"那天有没有 log"推断习惯完成。这两个都是字符串约定——脆弱且无法聚合。
|
|
18
|
+
|
|
19
|
+
### G2 极简自明(不读文档也能用,人和 AI 都一样)
|
|
20
|
+
|
|
21
|
+
工具必须简单到**不看手册就能用**,对人和 AI 都成立。AI 面对的是纯文本接口,复杂度会让它出错。
|
|
22
|
+
|
|
23
|
+
- **挂在一个节点下的记录种类**要克制(越少越好):每多一种就多一个要记的概念、多一块要渲染的内容——更重、更难把握。
|
|
24
|
+
- 任何新设计自检三问:(1) 它新增了几个概念?(2) 用它要不要逼人 / AI 做选择("用 A 还是 B?")?(3) 不看文档能不能猜对用法?任一条答得不好,就再简化。
|
|
25
|
+
- G1 和 G2 冲突时,找"既结构化又最少概念"的答案——别为了结构化就堆新表 / 新字段 / 新命令。
|
|
26
|
+
|
|
27
|
+
### G3 依赖少、逻辑简单、易维护
|
|
28
|
+
|
|
29
|
+
worklog 刻意保持**少依赖、低抽象**:运行时只有 **stdlib + `rich`**,新功能优先**零新依赖**。逻辑保持**显式直白**——宁可裸 SQL 也不上 query-builder DSL,宁可写小而专的 helper 也不引入藏掉细节的框架 / ORM。底线:维护者(人或 AI)能把任一条代码路径从头读到尾,不必先学一层隐藏机制。
|
|
30
|
+
|
|
31
|
+
- **借技法,不借库**:宁可写几十行零依赖 helper,也别拉一个包(例如照 sqlite-utils 思路写 dict→`INSERT` helper,而**不是** ORM;写 `field__op`→`WHERE` helper,而**不是** query builder)。
|
|
32
|
+
- **不上 ORM / 不上 query builder**:只把均匀的 ~80%(单表 CRUD + 存在/计数)包成**透明映射到一条 SQL**的薄 helper;复杂的 ~20%(JOIN / CTE / CASE / 时间窗)留显式 SQL。别把 SQL 重新发明成一套 kwargs DSL。
|
|
33
|
+
- **每张表 / 每个模块能各自维护**:尽量减少强制耦合(硬 FK 级联、跨切面魔法、藏意图的 trigger),让一处能改动 / 迁移 / 同步而不牵连全局。在会造成跨表一致性负担的地方,倾向**避免不可逆操作**(用软删除 / 状态标记替代 `DELETE`)。
|
|
34
|
+
- "聪明"的抽象跟 G3 冲突时,选无聊但简单的版本。零件少胜过优雅。
|
|
35
|
+
|
|
8
36
|
## 1. 命令风格(todo.sh 派)
|
|
9
37
|
|
|
10
38
|
- 动词在前:`wl <verb> [args] [--flags]`
|
|
@@ -17,13 +45,18 @@
|
|
|
17
45
|
|
|
18
46
|
单 `node` 表承载一切,`kind` 字段区分类型,`parent_id` 自引用建树。schema 以编号 SQL migrations 的形式发布,放在 `src/worklog/migrations/NNNN_*.sql`,`PRAGMA user_version` 记录最高已应用版本。`ensure_db()` 每条命令都自动 apply pending migrations,显式形式是 `wl migrate`。初始版本见 `src/worklog/migrations/0001_initial_schema.sql`,整体概览见 `README.md`。
|
|
19
47
|
|
|
48
|
+
> **migration 编写规则**:runner 把每个文件包进一个 `BEGIN/COMMIT`(中途失败整文件回滚),所以 migration 文件本身**不要**写 `BEGIN`/`COMMIT`。
|
|
49
|
+
|
|
20
50
|
- **kind 取值**:`lifetime / decade / year / quarter / month / week / day / area / project / task / meetlog / habit / signal`(可扩展,但加新 kind 要想清楚它在 tree / projects / summary 里怎么归类)
|
|
21
51
|
- **status 只在 task / habit / meetlog 类用**;时间层级类(year/month/...)跟 project 类 status 留 NULL
|
|
22
|
-
- 表:`node / tag / log / prop / link / sched / date_meta` + 派生 view `v_node_path`
|
|
52
|
+
- 表:`node / tag / log / metric / clock / prop / link / sched / date_meta` + 派生 view `v_node_path`
|
|
53
|
+
- **`node → log → metric` 主干**(log 为中心的核心):一个 `node` 挂多条 `log`;一条 `log`(带 `tag`——`note`/`goal`/`summary`/`overview`/`top5`/`clock`(载体)/…,NULL = 普通笔记)下挂 0..N 条 `metric`。`metric` 是结构化数据点(`tag` = 它是什么,如 `glucose`/`pullups`/`checkin`;外加 `value_num`/`value_text`/`unit`/`note`/`at`)。**`tag` 是三处统一的分类字段**——node(多值标签集)、log(角色,单值)、metric(种类,单值);同一个词、不同范围、SQL 不混。metric **必须挂在一条 log 上**(`metric.log_id` NOT NULL)——所以每个数据点都有 log 载体;`CHECK` 要求有值(纯标记如打卡存 `value_num=1`)。`metric.node_id` 是反范式冗余(免 join 查某 node 的数据点,无 FK;trigger 保证它始终等于载体 log 的 node)。CRUD 入口:`wl metric add/ls/edit/rm`(`add` 不带 `--on-log` 时建载体 log;无值标记存 `value_num=1`);`wl log`/`wl add` 的 `--metric` 和 `wl import` 的 `metrics` 可内联挂数据点。habit「今天做没做」= 当天有没有 `tag=checkin` 的 metric(`wl tick`/`wl checkin` 写),不再是「那天有没有 log」。
|
|
54
|
+
- **元信息 = 历史保留的 typed log**:day 的 `goal`/`summary`、week 的 `overview`、month 的 `top5` 都是 `log.tag` 化的 log(最新一条=当前值,每次改追加一条),由 `wl goal`/`wl recap`/`wl set <node> <key>` 写。(`prop` 回归只放真正静态的单值属性。)
|
|
55
|
+
- **`clock` = 结构化计时**:`clock(node_id, start_at, end_at, elapsed_sec)`,由 `wl start`/`stop`/`spent`/`wait` 读写——替代旧的 `CLOCK_IN`/`CLOCK_OUT` log-body 约定;时长从 `elapsed_sec` 求和,不再从文本解析。
|
|
23
56
|
- **两条并列树(都挂 lifetime 下)**(2026-05-29 起):
|
|
24
57
|
- **责任领域线**:`lifetime → area → project → task`(PARA:area 是跨时间的责任领域,project 归 area,task 归 project)
|
|
25
|
-
- **时间线**:`lifetime → year → quarter → month → week → day
|
|
26
|
-
- `
|
|
58
|
+
- **时间线**:`lifetime → year → quarter → month → week → day`(承载时间骨架 + 元信息 typed log:day 的 goal/summary、month 的 top5/goal、week 的 overview)
|
|
59
|
+
- 小结(`summary` log)自带 `logged_at` = 写入时间。`wl day` 显示「(写于 …)」,若当天小结后还有普通笔记 log(`tag IS NULL`)就提示「⚠ 小结后又有 N 条变更, 建议重写 recap」。(替代旧的 `summary_at` prop。)
|
|
27
60
|
- **project 不再挂 month**(旧设计曾挂月,已迁到 area)。日/月/周视图靠 **log 的 logged_at**(时间维度)跟 **kind/tag/祖先链**(领域维度)解耦:`wl day` 按 log 日期驱动、project 经祖先链解析、bucket 经 work/personal tag——所以 project 移到 area 下不影响任何按天/按项目视图
|
|
28
61
|
|
|
29
62
|
## 3. 状态机(#+TODO 风格)
|
|
@@ -372,7 +405,7 @@ out(_c("✓", "done") + " " + _c(f"#{id}", "id") + " " + _c(title))
|
|
|
372
405
|
- **`wl day [date]` 基于 log 日期驱动**,不再依赖 day 节点存在(历史数据也能按天罗列):查 `date(logged_at)=target` 的 log(排除 `CLOCK_*` 记账行 + 限 task/habit/meetlog),按 **桶 → 次级分组 → 任务 → 缩进 log** 渲染,底部统计「N 任务有进展 + 状态分布 + CLOCK」
|
|
373
406
|
- **桶(bucket)= `work`/`personal` tag** → `工作`/`个人`/`其他`(`_node_bucket`),顺序 `_BUCKET_ORDER`
|
|
374
407
|
- **次级分组 `--by`**(`_sec_group` / `_sec_sort_key`):
|
|
375
|
-
- `plan`(**`wl day` 默认**,设计决策 2026-05-29——最贴近 markdown
|
|
408
|
+
- `plan`(**`wl day` 默认**,设计决策 2026-05-29——最贴近 markdown 日结构):当天有排期(或迁移期 `planned` tag)→ `计划内`;其余 → `计划外`。(原来单独的 `计划外(未标)` 一档已并入 `计划外`——现在计划内外靠 sched 推导,那个 tag 区分没价值且 `(未标)` 文案误导,会被当成"没打 work/personal tag"。)
|
|
376
409
|
- `project`:项目祖先(`_node_project` 取 kind=project 的 ancestor)。`wl logs --group day` 仍默认 `project`
|
|
377
410
|
- `priority`:`A/B/C → P0/P1/P2`,无优先级 `—`(默认当计划外,但标注未显式确认,设计决策 2026-05-29)。⚠️ **计划内/计划外本质是 per-day(per-log)属性**(同一任务今天计划内、明天可能计划外),归并迁移后挂在任务上只是近似;要精确需把标记下沉到 log 行(schema 未做,留待决策)
|
|
378
411
|
- **`wl logs --group day [--by ...]`** 复用 `_render_day_group`:先按日期 header 分组,每天内同 `wl day` 结构
|
|
@@ -391,7 +424,7 @@ out(_c("✓", "done") + " " + _c(f"#{id}", "id") + " " + _c(title))
|
|
|
391
424
|
- **`wl sched <id> [when] [--recur R] [--clear]`**:排期 / 重复 / 清除 / 无参列出。`when` 走 `_resolve_concrete_date`(YYYY-MM-DD / 今天 / 明天 / 后天)。
|
|
392
425
|
- **`wl day` 推导(`_node_plan(con, nid, sched_ids)`)**:
|
|
393
426
|
- 计划内 = `nid in sched_ids`(被 schedule 命中,事先排的)**或** 迁移期 `planned` tag。
|
|
394
|
-
- 计划外 =
|
|
427
|
+
- 计划外 = 没命中 sched 且无 `planned` tag(有 log 但没排期的就是计划外;原 `unplanned` tag 和"没打标"两种情况现在合并成一档)。
|
|
395
428
|
- **被 schedule 命中但当天还没 log 的任务也列出**(`wl day` 把 sched-only node 并进 items,标 `«计划·未做»`),实现"计划提前可见,真正做时再加 log"。
|
|
396
429
|
- **跟 `scheduled_at`(§20)的分工**:`scheduled_at` = 模糊待办时间(someday / 2026-06,backlog 提示);`sched` = 具体日历落位(驱动计划内)。两者互补,不互相同步。
|
|
397
430
|
- **迁移期过渡**:历史数据用 `planned`/`unplanned` tag 表达计划内外(无 sched),`_node_plan` 把 tag 作 fallback;将来真实排期走 sched 后逐步去 tag。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyworklog
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: SQLite-backed worklog tool with a todo.sh-style CLI
|
|
5
5
|
Project-URL: Homepage, https://github.com/xyb/worklog
|
|
6
6
|
Project-URL: Repository, https://github.com/xyb/worklog
|
|
@@ -29,8 +29,11 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
|
|
30
30
|
# worklog
|
|
31
31
|
|
|
32
|
+
[](https://pypi.org/project/pyworklog/)
|
|
33
|
+
[](https://pypi.org/project/pyworklog/)
|
|
32
34
|
[](https://github.com/xyb/worklog/actions/workflows/test.yml)
|
|
33
35
|
[](https://codecov.io/gh/xyb/worklog)
|
|
36
|
+
[](https://github.com/xyb/worklog/blob/main/LICENSE)
|
|
34
37
|
|
|
35
38
|
> **Changelog**: see [CHANGELOG.md](CHANGELOG.md) for a curated highlight reel of every release.
|
|
36
39
|
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# worklog
|
|
4
4
|
|
|
5
|
+
[](https://pypi.org/project/pyworklog/)
|
|
6
|
+
[](https://pypi.org/project/pyworklog/)
|
|
5
7
|
[](https://github.com/xyb/worklog/actions/workflows/test.yml)
|
|
6
8
|
[](https://codecov.io/gh/xyb/worklog)
|
|
9
|
+
[](https://github.com/xyb/worklog/blob/main/LICENSE)
|
|
7
10
|
|
|
8
11
|
> **Changelog**: see [CHANGELOG.md](CHANGELOG.md) for a curated highlight reel of every release.
|
|
9
12
|
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# worklog
|
|
4
4
|
|
|
5
|
+
[](https://pypi.org/project/pyworklog/)
|
|
6
|
+
[](https://pypi.org/project/pyworklog/)
|
|
5
7
|
[](https://github.com/xyb/worklog/actions/workflows/test.yml)
|
|
6
8
|
[](https://codecov.io/gh/xyb/worklog)
|
|
9
|
+
[](https://github.com/xyb/worklog/blob/main/LICENSE)
|
|
7
10
|
|
|
8
11
|
SQLite 后端的 worklog 工具,`todo.sh` 风格 CLI。完整执行体系层级建模在单个 `node` 表里 —— lifetime / decade / year / quarter / month / week / day / project / task / habit / signal / meetlog —— 共享同一个 id 空间,通过 `parent_id` 自引用形成树状结构。
|
|
9
12
|
|
|
@@ -9,25 +9,37 @@ SQLite execution-system tool. A single `node` table carries lifetime/year/quarte
|
|
|
9
9
|
|
|
10
10
|
**Full design conventions in repo `DESIGN.md`** (18 sections; required reading before adding commands or changing formats). This skill only covers how AI uses `wl`.
|
|
11
11
|
|
|
12
|
+
## ⚠️ Check for duplicates before adding a task/project (hard rule)
|
|
13
|
+
|
|
14
|
+
Before `wl add`-ing any task or project, **first check whether a related entry already exists** — if so, reuse it (`wl sched` it onto the new day) instead of creating a duplicate. **Search across time ranges**, because an entry may be scheduled at `@2026-06` (month level) / `@someday` / earlier or later, so looking only at the current month misses it:
|
|
15
|
+
|
|
16
|
+
- `wl find <keyword>` — full-text search (always run this first)
|
|
17
|
+
- `wl agenda <start> <end> [--someday]` — everything scheduled in a date range across all granularities (day/week/month pins), the cross-range view that catches `@2026-06` / `@someday` items a per-month tree misses
|
|
18
|
+
- `wl ls --parent <proj> --all` — entries already under that project
|
|
19
|
+
|
|
20
|
+
Likewise, before adding a dev todo under a project, check that project's existing entries first.
|
|
21
|
+
|
|
12
22
|
## When to use `wl` (scenario → command)
|
|
13
23
|
|
|
14
24
|
| User says | Command |
|
|
15
25
|
|---|---|
|
|
16
|
-
| **Daily three** (the daily flow) | `wl goal "deliver X today"` (read = `wl goal`) / `wl recap "end-of-day summary..."` (read = `wl recap`) / `wl tick <id> [--note "..."] [--done]` to check in. **Auto-creates today's day node** (hung under the current ISO week), no need to manually `wl add ... -k day`. `wl recap` write
|
|
26
|
+
| **Daily three** (the daily flow) | `wl goal "deliver X today"` (read = `wl goal`) / `wl recap "end-of-day summary..."` (read = `wl recap`) / `wl tick <id> [--note "..."] [--done]` to check in. **Auto-creates today's day node** (hung under the current ISO week), no need to manually `wl add ... -k day`. `wl recap`/`wl goal` write a history-preserving `log.tag` log (latest = current); `wl day` shows "(written MM-DD HH:MM)" from the recap log's own time, and if more plain-note logs land after it, warns "⚠ N changes after recap, consider rewriting". Back-fill a past day with `wl recap --date YYYY-MM-DD "..."` (`goal --date` is not available — recap only) |
|
|
17
27
|
| Add a task / project / habit | `wl add "..." -k task -p A -t work,P0 --parent N` |
|
|
18
28
|
| Add a task with scheduled time (precise or fuzzy) | `wl add "..." --scheduled 2026-06-15` / `--scheduled 2026-06` / `next-week` / `next-month` / `someday` |
|
|
19
29
|
| Log progress on a task | `wl log <id> "..."` (backfill old logs with `--date 2026-05-06`, or use `import` with body `"2026-05-06 content"` so logs land on the original day, not today) |
|
|
30
|
+
| Record a number / measurement / check-in | `wl metric add <id> glucose 5.4 --unit mmol/L` / `wl metric ls <id>` / `wl metric edit #M7` / `wl metric rm #M7`; inline: `wl log <id> "..." --metric 'pullups 8'`. Habit done-today = a `checkin` metric (`wl tick`/`wl checkin`) |
|
|
20
31
|
| Mark done / defer (fuzzy time ok) / start clock | `wl done <id>` / `wl defer <id> next-month` (also accepts `2026-Q3` / `someday` / precise date) / `wl start <id>` `wl stop <id>` |
|
|
21
32
|
| Schedule task to a date / repeat (drives "planned") | `wl sched <id> 2026-06-15` (also accepts `tomorrow` / `day-after-tomorrow`) / `--clear`. `--recur` supports period start / end: `daily` / `weekly:Mon,Fri` (also 1-7 / -1..-7) / `monthly:1` (month start) · `monthly:-1` (month end) / `quarterly:1-1` (quarter start) · `quarterly:-1` (quarter end) / `yearly:01-01` (year start) · `yearly:-1` (year end); `-1` always means period end. A task scheduled to a day shows up in `wl day` as "planned · not yet logged" even with no log |
|
|
22
|
-
| Meta info (end-of-day summary / Top5 / today's goal) |
|
|
33
|
+
| Meta info (end-of-day summary / Top5 / today's goal) | History-preserving typed logs (`log.tag`), not props: `wl goal "..."` (day) / `wl recap "..."` (day summary) / `wl set <month> top5 "..."` / `wl set <week> overview "..."`. Each write appends; the latest is current. `wl day` shows them at the top as a blockquote. Prefer `wl recap`/`wl goal` over `wl set` for day fields (read-back + stale-warning) |
|
|
23
34
|
| Date context (holidays / vacation / makeup days) | `wl dateinfo 2026-05-01 Labor-Day-holiday` / `wl dateinfo --import holidays.json` (`{"YYYY-MM-DD":"label"}`) / `wl dateinfo <date> --clear`. Weekday auto-computed; `wl day` header shows "date weekday · label" |
|
|
24
|
-
| Reproduce a day's progress (like markdown worklog) | `wl day [YYYY-MM-DD]` (log-date based: work/personal split → secondary group → task → indented logs + stats). **Default `--by plan`** (planned / unplanned
|
|
35
|
+
| Reproduce a day's progress (like markdown worklog) | `wl day [YYYY-MM-DD]` (log-date based: work/personal split → secondary group → task → indented logs + stats). **Default `--by plan`** (planned / unplanned). **Planned = has a `sched`/recur entry firing that day (source of truth, set via `wl sched`); everything else = unplanned.** The legacy `:planned:` tag is honored only as a transitional fallback — do NOT add it to new tasks, use `wl sched`. Switch dimensions with `--by project` / `--by priority` (P0/P1/P2) |
|
|
25
36
|
| List all active projects | `wl projects` |
|
|
26
37
|
| Tree view | `wl tree` (**default = overview: timeline expanded to today [year→quarter→month→week→today + today's tasks] + areas only listed as names, ~30 lines to avoid flooding**) / `wl tree --root <area>` (projects + tasks under that area) / `wl tree --root <week/month>` (per-day activity) / `wl tree --depth N` (fully expanded from lifetime) / `wl tree --by project/tag/direction` (switch dimension). Time nodes sorted by date; **day node expansion = tasks that have logs that day + only their logs from that day** |
|
|
27
38
|
| Time-range progress (weekly report input) | `wl changes --week 2026-W22` / `wl summary --week ... --by project/day` |
|
|
28
39
|
| Full info + timeline for a single task | `wl show <id>` |
|
|
29
40
|
| Focus a node's parents/children | `wl focus <id>` / `wl ancestors <id>` / `wl descendants <id>` |
|
|
30
|
-
| Link a vault doc | `wl link <id> "doc name"` (no `.md` suffix) |
|
|
41
|
+
| Link a vault doc | `wl link <id> "doc name"` (no `.md` suffix); remove one with `wl unlink <id> "doc name"` |
|
|
42
|
+
| Edit a node's tags | `wl tag <id> +work -planned` (bare word = add; no ops = list). **Edits the real tag field** — do NOT `wl set <id> tags ...` (rejected; it would create a misleading shadow prop). For bulk, `apply ~ #id / +tag` or `import add_tags/remove_tags` |
|
|
31
43
|
| List log stream | `wl logs` (**default: last 7 days only**, to avoid flooding); `--since/--until/--date` for explicit range; `--group day [--by project/priority/plan]` for daily replay |
|
|
32
44
|
|
|
33
45
|
## UX v2 (iteratively converged)
|
|
@@ -75,26 +87,77 @@ wl unlog --node 39 --date yesterday # delete most-recent 1 by node + date
|
|
|
75
87
|
wl relog #L282 "corrected content" # edit body
|
|
76
88
|
wl relog #L282 --at 14:30 # edit time only
|
|
77
89
|
wl relog #L282 # no body/--at → opens $EDITOR
|
|
78
|
-
# CLOCK_IN/OUT logs are immutable here (use `wl stop --at` to fix times)
|
|
79
90
|
```
|
|
80
91
|
|
|
81
|
-
### Time backfill: `start
|
|
92
|
+
### Time tracking / backfill: `start` / `stop` / `spent` (structured `clock` table)
|
|
93
|
+
|
|
94
|
+
Time is a structured interval in the `clock` table, not log text. `wl start` opens
|
|
95
|
+
an interval; `wl stop`/`wl wait` close it; `wl spent` writes a closed one. `wl active`
|
|
96
|
+
lists open intervals; `wl show` renders them as `start→end (Nmin)`; `wl day`/`wl
|
|
97
|
+
summary` totals sum elapsed.
|
|
82
98
|
|
|
83
99
|
```fish
|
|
84
|
-
wl start <id> --at 09:00 # backfill
|
|
85
|
-
wl stop <id> --at 11:30 #
|
|
86
|
-
wl spent <id> 90m # given duration, write a
|
|
100
|
+
wl start <id> --at 09:00 # open an interval (backfill start)
|
|
101
|
+
wl stop <id> --at 11:30 # close it (must be after start)
|
|
102
|
+
wl spent <id> 90m # given duration, write a closed interval directly
|
|
87
103
|
wl spent <id> 1h30m --at 14:00 # 14:00 as end, backs out 12:30 as start
|
|
104
|
+
# wl start refuses a 2nd open interval on an already-running node (wl stop first)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### ⭐ Structured datapoints: `wl metric` (node → log → metric)
|
|
108
|
+
|
|
109
|
+
A `metric` is a structured datapoint (number / measurement / check-in) that hangs
|
|
110
|
+
off a log. Use it for anything you'll want to query/trend later (glucose, weight,
|
|
111
|
+
reps, …) instead of stuffing numbers into log text.
|
|
112
|
+
|
|
113
|
+
```fish
|
|
114
|
+
wl metric add 42 glucose 5.4 --unit mmol/L # numeric datapoint (auto creates a carrier log)
|
|
115
|
+
wl metric add 42 pullups 8 # numeric, no unit
|
|
116
|
+
wl metric add 42 mood good --text # text value
|
|
117
|
+
wl metric add 42 checkin # a pure marker (stored as value 1)
|
|
118
|
+
wl metric add 42 glucose 6.1 --on-log #L99 # attach to an existing log instead of a new carrier
|
|
119
|
+
wl metric ls 42 # list (default this week; --all / --tag / --since/--until/--week/--month)
|
|
120
|
+
wl metric edit #M7 --value 5.6 --note "post-meal"
|
|
121
|
+
wl metric rm #M7 #M8 # delete (also removes an emptied auto-carrier log)
|
|
88
122
|
```
|
|
89
123
|
|
|
90
|
-
|
|
124
|
+
Inline shortcut — attach datapoints in the same command as a log/task (repeatable):
|
|
91
125
|
|
|
92
126
|
```fish
|
|
127
|
+
wl log 42 "morning reading" --metric 'glucose 5.4 mmol/L' --metric checkin
|
|
128
|
+
wl add "weigh-in" -k task --metric 'weight 70 kg'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`wl import` too: a log entry can carry `"metrics":[{tag,value,unit}]`, and a node can
|
|
132
|
+
carry node-level `"metrics":[...]` on ONE carrier log (1 carrier → N points, e.g. a
|
|
133
|
+
day of CGM readings without 288 separate logs).
|
|
134
|
+
|
|
135
|
+
`wl show` folds a log's metrics beneath it (`↳ [glucose] 5.4 mmol/L`); an empty
|
|
136
|
+
metric-carrier log shows its datapoint directly as a `📊 metric` line. `wl day` /
|
|
137
|
+
`wl tree` day-expansion fold a node's that-day datapoints under it (the `checkin`
|
|
138
|
+
marker is skipped — it's reflected by `[x]`); >5 are elided. A scheduled habit also
|
|
139
|
+
shows `(本月 N/M)` = month-to-date check-ins / scheduled days.
|
|
140
|
+
|
|
141
|
+
### Multi-habit interactive check-in: `wl checkin` / `wl tick`
|
|
142
|
+
|
|
143
|
+
Habit "done today" is a structured `tag=checkin` metric (idempotent per day), NOT
|
|
144
|
+
"any log exists that day" — so a stray note no longer marks a habit done. `wl tick`
|
|
145
|
+
and `wl checkin` write that metric.
|
|
146
|
+
|
|
147
|
+
```fish
|
|
148
|
+
wl tick 39 # check in one habit today (writes a checkin metric)
|
|
149
|
+
wl tick 39 40 41 --note "…" # bulk check-in
|
|
93
150
|
wl checkin # default multi-select (↑↓ space enter)
|
|
94
151
|
wl checkin --per-item # alt: one-by-one y/n/note/q prompt
|
|
95
152
|
wl checkin --all-kinds # not limited to habit kind
|
|
96
153
|
```
|
|
97
154
|
|
|
155
|
+
### Meta fields keep history (typed logs, not props)
|
|
156
|
+
|
|
157
|
+
`wl goal` / `wl recap` (day), `wl set <week> overview` / `wl set <month> top5` are
|
|
158
|
+
stored as `log.tag` logs — each write appends, the latest is current, so edit
|
|
159
|
+
history is kept (`prop` is only for truly-static single-value attributes).
|
|
160
|
+
|
|
98
161
|
### Recurrence rules (`--recur`), every variant supports `-1` for "last day of period"
|
|
99
162
|
|
|
100
163
|
```fish
|
|
@@ -154,13 +217,14 @@ echo '{
|
|
|
154
217
|
```
|
|
155
218
|
|
|
156
219
|
- `children` nesting (parent id auto-propagates) + `ref`/`parent_ref` (in-batch reference)
|
|
220
|
+
- a log entry may carry `"metrics":[{"tag":"glucose","value":5.4,"unit":"mmol/L"}]`; a node may carry node-level `"metrics":[...]` (one carrier log → N datapoints)
|
|
157
221
|
- `--dry-run` to preview first
|
|
158
222
|
|
|
159
223
|
### `wl apply <file|->` (wl-diff, same format as `wl` output — lightweight edits for humans/AI)
|
|
160
224
|
|
|
161
225
|
```
|
|
162
226
|
#6 [day] 2026-05-29 Friday ← anchor: locate existing node as parent, don't modify
|
|
163
|
-
+ [x] [#A] morning-check
|
|
227
|
+
+ [x] [#A] morning-check ← add (indent = child), [x]=DONE; for planned use `wl sched`, not a :planned: tag
|
|
164
228
|
+ @log check key points
|
|
165
229
|
~ [x] #14 ← change #14 status (single-line shorthand)
|
|
166
230
|
~ #20 ← complex update: lock + field operations
|
|
@@ -177,6 +241,7 @@ Prefixes: `+` add / `~` update / `-` delete / ` ` anchor. `--dry-run` validates
|
|
|
177
241
|
|
|
178
242
|
- Single-line shorthand: `~ [x] #14` only changes status; `~ [#A] #14` only changes priority (without a marker, status is untouched); `~ #14 new name` only changes title
|
|
179
243
|
- Field operations: `status DONE` / `priority -` (clear) / `parent 6` (move) / `+tag` / `-tag` / `prop k=v` / `-prop k` / `+log`
|
|
244
|
+
- ⚠️ Each field op **must be indented** (2 spaces) under its `~ #id` lock line. A flush-left `parent 6` is a separate top-level line, not part of the update — the parser will reject it and tell you to indent.
|
|
180
245
|
- Illegal values (priority∉ABC, illegal status, parent missing) caught by validator — **bad data never lands**
|
|
181
246
|
|
|
182
247
|
## ⭐ Brief / token-saving mode (REQUIRED reading for AI usage)
|