pyworklog 0.5.0__tar.gz → 0.6.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 (158) hide show
  1. {pyworklog-0.5.0 → pyworklog-0.6.0}/CHANGELOG.md +216 -0
  2. {pyworklog-0.5.0 → pyworklog-0.6.0}/CONTRIBUTING.md +41 -0
  3. {pyworklog-0.5.0 → pyworklog-0.6.0}/DESIGN.md +181 -1
  4. {pyworklog-0.5.0 → pyworklog-0.6.0}/PKG-INFO +1 -1
  5. {pyworklog-0.5.0 → pyworklog-0.6.0}/pyproject.toml +1 -1
  6. {pyworklog-0.5.0 → pyworklog-0.6.0}/skills/worklog-cli/SKILL.md +8 -4
  7. pyworklog-0.6.0/src/worklog/cli.py +1672 -0
  8. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/commands/__init__.py +34 -0
  9. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/commands/bulk.py +42 -16
  10. pyworklog-0.6.0/src/worklog/commands/help.py +493 -0
  11. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/commands/meta.py +233 -31
  12. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/commands/metric.py +1 -0
  13. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/commands/query.py +60 -37
  14. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/commands/state.py +329 -60
  15. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/commands/views.py +71 -29
  16. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/completion.py +57 -10
  17. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/db.py +5 -2
  18. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/db_table.py +66 -26
  19. pyworklog-0.6.0/src/worklog/help/en/active.md +10 -0
  20. pyworklog-0.6.0/src/worklog/help/en/add.md +27 -0
  21. pyworklog-0.6.0/src/worklog/help/en/admin.md +15 -0
  22. pyworklog-0.6.0/src/worklog/help/en/agenda.md +12 -0
  23. pyworklog-0.6.0/src/worklog/help/en/alias.md +15 -0
  24. pyworklog-0.6.0/src/worklog/help/en/ancestors.md +9 -0
  25. pyworklog-0.6.0/src/worklog/help/en/apply.md +20 -0
  26. pyworklog-0.6.0/src/worklog/help/en/bulk.md +16 -0
  27. pyworklog-0.6.0/src/worklog/help/en/cancel.md +10 -0
  28. pyworklog-0.6.0/src/worklog/help/en/changes.md +14 -0
  29. pyworklog-0.6.0/src/worklog/help/en/checkin.md +16 -0
  30. pyworklog-0.6.0/src/worklog/help/en/clock.md +19 -0
  31. pyworklog-0.6.0/src/worklog/help/en/config.md +8 -0
  32. pyworklog-0.6.0/src/worklog/help/en/date.md +11 -0
  33. pyworklog-0.6.0/src/worklog/help/en/dateinfo.md +14 -0
  34. pyworklog-0.6.0/src/worklog/help/en/day.md +27 -0
  35. pyworklog-0.6.0/src/worklog/help/en/defer.md +16 -0
  36. pyworklog-0.6.0/src/worklog/help/en/delete.md +15 -0
  37. pyworklog-0.6.0/src/worklog/help/en/descendants.md +9 -0
  38. pyworklog-0.6.0/src/worklog/help/en/done.md +18 -0
  39. pyworklog-0.6.0/src/worklog/help/en/find.md +18 -0
  40. pyworklog-0.6.0/src/worklog/help/en/focus.md +13 -0
  41. pyworklog-0.6.0/src/worklog/help/en/goal.md +14 -0
  42. pyworklog-0.6.0/src/worklog/help/en/ids.md +13 -0
  43. pyworklog-0.6.0/src/worklog/help/en/import.md +18 -0
  44. pyworklog-0.6.0/src/worklog/help/en/index.md +22 -0
  45. pyworklog-0.6.0/src/worklog/help/en/init.md +8 -0
  46. pyworklog-0.6.0/src/worklog/help/en/kind.md +16 -0
  47. pyworklog-0.6.0/src/worklog/help/en/link.md +17 -0
  48. pyworklog-0.6.0/src/worklog/help/en/log.md +20 -0
  49. pyworklog-0.6.0/src/worklog/help/en/logs.md +16 -0
  50. pyworklog-0.6.0/src/worklog/help/en/ls.md +20 -0
  51. pyworklog-0.6.0/src/worklog/help/en/meta.md +19 -0
  52. pyworklog-0.6.0/src/worklog/help/en/metric.md +16 -0
  53. pyworklog-0.6.0/src/worklog/help/en/migrate.md +8 -0
  54. pyworklog-0.6.0/src/worklog/help/en/node.md +28 -0
  55. pyworklog-0.6.0/src/worklog/help/en/para.md +38 -0
  56. pyworklog-0.6.0/src/worklog/help/en/planned.md +18 -0
  57. pyworklog-0.6.0/src/worklog/help/en/planning.md +23 -0
  58. pyworklog-0.6.0/src/worklog/help/en/print-completion.md +12 -0
  59. pyworklog-0.6.0/src/worklog/help/en/priority.md +17 -0
  60. pyworklog-0.6.0/src/worklog/help/en/projects.md +13 -0
  61. pyworklog-0.6.0/src/worklog/help/en/prop.md +17 -0
  62. pyworklog-0.6.0/src/worklog/help/en/recap.md +14 -0
  63. pyworklog-0.6.0/src/worklog/help/en/relog.md +10 -0
  64. pyworklog-0.6.0/src/worklog/help/en/reopen.md +9 -0
  65. pyworklog-0.6.0/src/worklog/help/en/reporting.md +18 -0
  66. pyworklog-0.6.0/src/worklog/help/en/sched.md +25 -0
  67. pyworklog-0.6.0/src/worklog/help/en/set.md +11 -0
  68. pyworklog-0.6.0/src/worklog/help/en/shortcuts.md +15 -0
  69. pyworklog-0.6.0/src/worklog/help/en/show.md +15 -0
  70. pyworklog-0.6.0/src/worklog/help/en/spent.md +10 -0
  71. pyworklog-0.6.0/src/worklog/help/en/start.md +10 -0
  72. pyworklog-0.6.0/src/worklog/help/en/status.md +24 -0
  73. pyworklog-0.6.0/src/worklog/help/en/stop.md +9 -0
  74. pyworklog-0.6.0/src/worklog/help/en/summary.md +18 -0
  75. pyworklog-0.6.0/src/worklog/help/en/tag.md +20 -0
  76. pyworklog-0.6.0/src/worklog/help/en/themes.md +8 -0
  77. pyworklog-0.6.0/src/worklog/help/en/tick.md +11 -0
  78. pyworklog-0.6.0/src/worklog/help/en/time.md +18 -0
  79. pyworklog-0.6.0/src/worklog/help/en/tracking.md +15 -0
  80. pyworklog-0.6.0/src/worklog/help/en/tree.md +18 -0
  81. pyworklog-0.6.0/src/worklog/help/en/unlink.md +9 -0
  82. pyworklog-0.6.0/src/worklog/help/en/unlog.md +10 -0
  83. pyworklog-0.6.0/src/worklog/help/en/unset.md +10 -0
  84. pyworklog-0.6.0/src/worklog/help/en/wait.md +10 -0
  85. pyworklog-0.6.0/src/worklog/help/en/window.md +20 -0
  86. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/helpers.py +17 -7
  87. pyworklog-0.6.0/src/worklog/migrations/0008_soft_delete_tombstones.sql +40 -0
  88. pyworklog-0.6.0/src/worklog/migrations/0009_tombstone_node_path_view.sql +18 -0
  89. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/queries.py +78 -20
  90. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/render.py +43 -1
  91. pyworklog-0.6.0/tests/test_alias.py +64 -0
  92. pyworklog-0.6.0/tests/test_cascade.py +152 -0
  93. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_clock.py +7 -5
  94. pyworklog-0.6.0/tests/test_cycle_safety.py +60 -0
  95. pyworklog-0.6.0/tests/test_dateinfo.py +111 -0
  96. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_day.py +12 -1
  97. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_db_table.py +35 -2
  98. pyworklog-0.6.0/tests/test_default_verb.py +386 -0
  99. pyworklog-0.6.0/tests/test_help.py +363 -0
  100. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_import_apply.py +7 -7
  101. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_link_set.py +47 -5
  102. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_meta.py +71 -0
  103. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_metric.py +11 -11
  104. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_migrations.py +16 -10
  105. pyworklog-0.6.0/tests/test_node.py +121 -0
  106. pyworklog-0.6.0/tests/test_prop_clock.py +139 -0
  107. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_show.py +46 -0
  108. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_tree.py +48 -0
  109. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_ux.py +135 -0
  110. {pyworklog-0.5.0 → pyworklog-0.6.0}/uv.lock +1 -1
  111. pyworklog-0.5.0/src/worklog/cli.py +0 -1287
  112. pyworklog-0.5.0/tests/test_cascade.py +0 -27
  113. pyworklog-0.5.0/tests/test_dateinfo.py +0 -42
  114. {pyworklog-0.5.0 → pyworklog-0.6.0}/.github/workflows/release.yml +0 -0
  115. {pyworklog-0.5.0 → pyworklog-0.6.0}/.github/workflows/test.yml +0 -0
  116. {pyworklog-0.5.0 → pyworklog-0.6.0}/.gitignore +0 -0
  117. {pyworklog-0.5.0 → pyworklog-0.6.0}/AGENTS.md +0 -0
  118. {pyworklog-0.5.0 → pyworklog-0.6.0}/DESIGN.zh.md +0 -0
  119. {pyworklog-0.5.0 → pyworklog-0.6.0}/LICENSE +0 -0
  120. {pyworklog-0.5.0 → pyworklog-0.6.0}/Makefile +0 -0
  121. {pyworklog-0.5.0 → pyworklog-0.6.0}/README.md +0 -0
  122. {pyworklog-0.5.0 → pyworklog-0.6.0}/README.zh.md +0 -0
  123. {pyworklog-0.5.0 → pyworklog-0.6.0}/pytest.ini +0 -0
  124. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/__init__.py +0 -0
  125. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/migrations/0001_initial_schema.sql +0 -0
  126. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/migrations/0002_metric_and_log_tag.sql +0 -0
  127. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/migrations/0003_backfill_checkin_metrics.sql +0 -0
  128. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/migrations/0004_meta_props_to_typed_logs.sql +0 -0
  129. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/migrations/0005_clock_table.sql +0 -0
  130. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/migrations/0006_rename_log_type_to_tag.sql +0 -0
  131. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/migrations/0007_utc_timestamps.sql +0 -0
  132. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/timeutil.py +0 -0
  133. {pyworklog-0.5.0 → pyworklog-0.6.0}/src/worklog/xdg.py +0 -0
  134. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/__init__.py +0 -0
  135. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/conftest.py +0 -0
  136. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_add.py +0 -0
  137. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_aliases.py +0 -0
  138. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_brief.py +0 -0
  139. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_changes.py +0 -0
  140. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_checkin.py +0 -0
  141. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_completion.py +0 -0
  142. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_config.py +0 -0
  143. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_filters.py +0 -0
  144. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_find.py +0 -0
  145. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_focus.py +0 -0
  146. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_init.py +0 -0
  147. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_log.py +0 -0
  148. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_log_format.py +0 -0
  149. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_logs.py +0 -0
  150. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_ls.py +0 -0
  151. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_projects.py +0 -0
  152. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_render.py +0 -0
  153. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_sample.py +0 -0
  154. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_sched.py +0 -0
  155. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_state.py +0 -0
  156. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_summary.py +0 -0
  157. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_timeutil.py +0 -0
  158. {pyworklog-0.5.0 → pyworklog-0.6.0}/tests/test_xdg.py +0 -0
@@ -6,6 +6,222 @@ The autogenerated `Commits since vX.Y.Z` block on each GitHub Release captures e
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.0] - 2026-06-07
10
+
11
+ ### Added
12
+ - **`wl show` annotates a recurring schedule with its next occurrence.** The `schedule:` line
13
+ now reads e.g. `recur weekly:Mon,Fri (next 2026-06-08 Mon); on 2026-06-20, 2026-06-25` — the
14
+ `(next …)` is computed by the same `_sched_fires` predicate `wl day` / `agenda` use, so it's
15
+ exactly when the task will reappear (the actionable bit, the way TaskWarrior / calendars surface
16
+ a recurrence's next due). One-off-only schedules list their dates as before (no `(next …)`).
17
+ - **`wl help` — an info-style topic browser (DESIGN §25).** `wl help` shows an index of
18
+ topics by category; `wl help <topic>` renders a fuller explanation of a command / concept /
19
+ parameter / workflow than `<command> -h` gives, with a "See also" footer linking related
20
+ topics. Topics are **repo-managed Markdown docs** under `src/worklog/help/<lang>/<topic>.md`
21
+ (shipped in the wheel, **i18n-ready** — language resolves `--lang` > `$WORKLOG_LANG` >
22
+ `$LANG` > `en`, falling back to `en` per-topic). Seeded with index + node / status /
23
+ priority / para / planning / add / day; `--help` stays the quick per-command reference and
24
+ now points into `wl help` for the deeper read (a doc-integrity test enforces no dangling
25
+ see-also links). Runs without a DB (safe before `wl init`). Each command's `--help`
26
+ **auto-gains a `More: wl help <cmd>` pointer** when a topic doc exists for it (no per-command
27
+ epilog edits), `wl help <topic>` is **tab-completable** (fish/bash/zsh), and the bare-`wl`
28
+ splash now points to it. Topic set expanded to 18 — added log / tag / sched / done / defer /
29
+ tree / ls / find / link / meta / prop alongside the seed concepts/guides.
30
+ - **`wl node` entity group — `node add / ls / show / edit / rm / reparent`.** The start of
31
+ reshaping the command surface to the metric-style `<entity> <verb>` form (WL#486). The
32
+ high-frequency `add` / `ls` / `show` are reachable both as `wl node <verb>` and as the
33
+ existing top-level shortcut (`wl add` == `wl node add`), one handler each, args defined
34
+ once so the two forms can't drift. Three new primitives close node's CRUD to the metric
35
+ bar: `node edit <id>` (edit a node's own fields — title / priority / kind / body /
36
+ scheduled / deadline; rejects an empty title), `node reparent <id> <parent|none|0>`
37
+ (change the real `parent_id`, with cycle / self-parent guards — WL#485), and `node rm <id>`
38
+ (soft-delete a node + subtree, reversible). Every current command keeps working unchanged.
39
+ - **`wl prop` and `wl clock` entity groups (WL#486), filling two CRUD gaps.** `wl prop
40
+ set / ls / rm` — `set` keeps its top-level shortcut and `rm` gains the new shortcut
41
+ `wl unset <id> <key>` (you can finally *remove* a custom prop, not just overwrite it).
42
+ `wl clock ls / edit / rm` — list / fix (edit start-end, recomputing the duration) /
43
+ remove a time interval; intervals are still created by `wl start` / `stop` / `spent`.
44
+ - **`wl link` entity group + a default-verb mechanism for name-collision entities (WL#486).**
45
+ `wl link add / ls / rm` — `link add` keeps the top-level shortcut, `rm` gains the
46
+ `wl unlink <id> <doc>` shortcut, and `link ls` is a new primitive (list a node's links).
47
+ Because the entity name `link` is itself the everyday verb, the parser learned a
48
+ default-verb rule: when the token after `link` isn't one of its verbs it's taken as an id,
49
+ so `wl link 42 doc` still means `wl link add 42 doc` while `wl link ls / rm / -h` route to
50
+ the group. Leaf ids are always integers, so id-vs-verb is unambiguous. This unblocks the
51
+ same treatment for the other collision entities (`log` / `tag` / `sched`).
52
+ - **`wl tag` reshaped into a metric-style group (WL#486), the second default-verb entity.**
53
+ `wl tag add / ls / rm`. `add` is the default verb, so the everyday `wl tag <id> +work
54
+ -planned` keeps working unchanged (full +add / -remove / bare-add / empty-list grammar);
55
+ `tag ls` lists, `tag rm` removes by plain name. `tag` joins `link` in the default-verb
56
+ table — `wl tag 42 +work` → `wl tag add 42 +work`, while `wl tag ls / rm / -h` route to
57
+ the group; id-first disambiguation means `wl tag 1 ls` adds the tag `ls` whereas `wl tag
58
+ ls 1` lists. Tag update stays atomic (no `edit` verb).
59
+ - **`wl log` reshaped into a metric-style group (WL#486), the third default-verb entity.**
60
+ `wl log add / ls / edit / rm`. `add` is the default verb, so the everyday `wl log <id>
61
+ "body"` keeps working unchanged (auto TODO→DOING, `--date` / `--time` / `--metric` /
62
+ `--keep-status`). `log edit` and `log rm` reuse the `relog` / `unlog` handlers — both keep
63
+ their top-level shortcuts — and `log ls <id>` is a new node-scoped lister (the full
64
+ filterable / windowed stream view stays at `wl logs`). id-first disambiguation: `wl log 1
65
+ ls` adds a log bodied `ls`, whereas `wl log ls 1` lists.
66
+ - **`wl sched` reshaped into a metric-style group (WL#486), the fourth (and final collision)
67
+ default-verb entity.** `wl sched add / ls / rm`. `add` is the default verb, so `wl sched
68
+ <id> <when>` / `wl sched <id> --recur …` / `wl sched <id> --clear` all keep working
69
+ unchanged (full when / `--recur` / `--clear` / list-when-empty grammar). `sched ls` lists,
70
+ `sched rm` clears; `wl defer` (status=LATER + rough hint) stays its own command. With this,
71
+ every collision entity (`link` / `tag` / `log` / `sched`) is reshaped.
72
+ - **`date_meta` reshaped into a clean `wl date` group (WL#486) — completing the reshape.**
73
+ `wl date set / ls / rm / import`. Unlike the collision entities this is a *clean* group
74
+ (no default verb — `date` doesn't collide with any leaf); `wl dateinfo` stays as the
75
+ polymorphic everyday shortcut (sets when given a label, lists when not, `--clear` /
76
+ `--import` variants) over the same table. **With this, every entity now has a full
77
+ `<entity> <verb>` group.**
78
+ - **`wl meta` group for the history-preserving typed-log fields (WL#486).** `wl meta set / ls
79
+ / rm <node> <field>` for `goal` / `summary` / `overview` / `top5` (each edit appends a typed
80
+ log, latest = current — distinct from props, which are static single-value). `wl set` / `wl
81
+ unset` are now *key-routed* shortcuts: a meta key routes to `wl meta set` / `wl meta rm`,
82
+ any other key to `wl prop set` / `wl prop rm` (the same way `wl add` is `wl node add`). `wl
83
+ goal` / `wl recap` stay as the today-auto shortcuts. Behavior of `wl set <node>
84
+ overview/top5/goal/summary` is unchanged — it's just documented and now has a canonical
85
+ group form. (Known limitation: a prop literally named goal/summary/overview/top5 can't be
86
+ set via `wl set` since the key routes to meta — a future `--prop`/`--meta` disambiguator.)
87
+ - **`wl alias add / ls / rm`** manages `~/.config/worklog/aliases.ini` (e.g. `wl alias add d
88
+ day` → `wl d` == `wl day`). The target must be a real command and an alias can't shadow one;
89
+ changes take effect on the next invocation. Alias names are now case-preserving.
90
+ - **Newcomer-friendlier `--help`.** Top-level `wl -h` now opens with a one-line what-it-is +
91
+ a `wl init → add → log → day` quickstart, replaces the unreadable 50-command brace blob in
92
+ the usage line with `<command>`, and ends with a **commands-grouped-by-purpose** map (track
93
+ work / time / see it / organize / plan-reflect / bulk-setup), a **Concepts** glossary
94
+ (node / log / tag / prop / meta / metric / link / sched / clock in one line each), and a
95
+ "good to know" tips block (id forms, fuzzy dates, planned/DOING behavior, completion,
96
+ shortcuts, **PARA** area▸project▸task). `wl add -h` gained help text on every option
97
+ (`title`, `-p` = A/B/C priority, …), a PARA organizing explainer, and leads with simple
98
+ relatable examples instead of work-jargon. Every user-facing arg now has help text (no more
99
+ blank options). `wl day -h` gained an (explicitly optional) "Time levels" section explaining
100
+ the year▸quarter▸month▸week▸day skeleton + a suggested planning cadence; `wl goal -h` / `wl
101
+ recap -h` now show the morning→evening→weekly→monthly planning rhythm — and their stale
102
+ pre-reshape wording ("prop 'goal'", "summary_at") was corrected (they're history-preserving
103
+ meta fields, not props).
104
+ - **Single-letter short flags for the daily-workflow args (WL#487):** `-d` for `--date`
105
+ (`wl log 1 "…" -d yesterday`, also on `logs` / `recap` / `unlog`) and `-n` for `--note`
106
+ (`wl tick 1 -n "6 pullups"`, also on `wait` / `metric`).
107
+ - **Help cross-references the shortcut ↔ canonical pair both ways** (now a documented
108
+ convention, DESIGN §1.2): e.g. `wl node -h` lists its shortcuts, `wl node add -h` names
109
+ the `wl add` shortcut, `wl add -h` names the canonical `wl node add` (same for set ↔
110
+ prop set, unset ↔ prop rm).
111
+
112
+ ### Changed
113
+ - **`wl -h` / `wl <command> -h` now colorize to match `wl help`.** argparse's `--help` output
114
+ is post-processed into the same 3-tier scheme: dim-grey body prose, bright-cyan references
115
+ (option flags, `wl <command>` invocations, subcommand-choice names, inline `code`), bold
116
+ bright-white section headings + the `usage:` label, and status/priority markers (`[x]`/`[/]`/
117
+ `[#A]`…) in their real `wl ls` colors. It's a no-op when color is off (non-TTY, `--color never`,
118
+ `$NO_COLOR`, mono theme), and runs *after* argparse's column math so the zero-width ANSI never
119
+ shifts alignment. `--help` fires before the console is built, so color/theme are re-resolved
120
+ from `--color`/`--theme`/env at format time. Epilogs/descriptions may use the **same restricted
121
+ Markdown as `wl help` topics** (`` `code` `` / `**bold**` / `*italic*` / `[text](url)`) for
122
+ explicit styling — markers render with color on and are *stripped* with color off (so piped
123
+ `-h` stays clean). The top-level `wl -h` now styles the Concepts glossary names and the
124
+ Commands-by-purpose lists this way (command names like `set`/`log`/`show` collide with English,
125
+ so explicit backticks beat any heuristic). Per-command `help=` one-line summaries stay plain
126
+ text — argparse line-wraps them, which would split a Markdown span across lines.
127
+ - **`wl help <topic>` slimming of `--help` epilogs.** With the topic browser holding the full
128
+ detail, verbose per-command epilogs are trimmed to a summary + key examples + a `wl help <cmd>`
129
+ pointer; anything cut is first verified present in (or added to) the matching topic. Covered
130
+ ~23 commands (add / ls / day / logs / sched / tree / node / summary / log / tag / show / active /
131
+ link / unset / projects / changes / find / defer / set / prop / import / apply); multi-line
132
+ Differences / Shortcuts / format-spec blocks moved into topics, concise one-line notes kept.
133
+ - **`--help` now wraps the raw epilog/description with a hanging indent + caps its width.**
134
+ Previously the hand-formatted epilog (Concepts glossary, Commands-by-purpose) hard-wrapped at
135
+ the terminal edge with no indent on a narrow window. A `_WlHelpFormatter` (subclass of
136
+ `RawDescriptionHelpFormatter`) overrides `_fill_text` to wrap the description/epilog argparse-
137
+ style — a two-column `label description` row hangs its continuations under the description
138
+ column, bullets under their text — measured on *visible* width so a markdown span
139
+ (`` `wl day` ``) is never split across the boundary. Option/choice help is left to argparse's
140
+ own `_split_lines` (we never re-wrap it), and the formatter caps the wrap width on wide
141
+ terminals (`render.HELP_MAX_WIDTH`, 100 cols) so help stays readable; everything shares the one
142
+ `render.help_width()`, so columns line up.
143
+ - **Removals are now reversible — soft-delete tombstones instead of `DELETE`, foreign keys
144
+ off (WL#501).** Every table gained a `deleted_at` tombstone; deleting a tag / link / log /
145
+ node sets it instead of removing the row, and reads filter it out, so an accidental
146
+ `wl unlog` / `wl tag -x` / `wl apply '- #id'` is recoverable (clear `deleted_at`). FK
147
+ enforcement is turned off and the app maintains consistency (app-level cascade), decoupling
148
+ the tables — preparation for multi-end / CRDT sync (#57). Re-adding a removed tag / link
149
+ revives its tombstone (a new `upsert` replaces `INSERT OR IGNORE/REPLACE`). Migration 0008
150
+ is additive (nullable column, no data touched); 0009 makes the `v_node_path` view
151
+ tombstone-aware. Internally this also moved ~19 single-table reads still on raw SQL onto
152
+ the `db_table` helper, so they filter tombstones for free.
153
+
154
+ ### Fixed
155
+ - **Help/timeline width handling, hardened via a GPT-5.5 / Kimi / Codex cross-model review.**
156
+ (1) Width is now measured in real display columns everywhere (one `east_asian_width`-based
157
+ `_cw`, shared by `_display_width` / `_truncate_log_body` / the help wrapper's `_vis`): CJK = 2,
158
+ but the narrow `·`/`→`/`…`/`—` separators = 1 (a crude non-ASCII→2 rule wrongly doubled them).
159
+ (2) The help wrapper hard-breaks a spaceless over-long token (CJK run / long URL) by character.
160
+ (3) `--theme mono` no longer leaks `\x1b[3m` — `*italic*` / links route through the palette.
161
+ (4) `_truncate_log_body`'s available-width floor was `max(20, …)`, which *forced* the `wl show`
162
+ timeline line to overflow on any terminal < ~61 cols (prefix ~39 + 20); floored at 1 now, so it
163
+ fits narrow terminals (body degrades to `…`). (5) The help wrapper falls back to a base-indent
164
+ wrap when the hanging (description) column would be wider than a very narrow terminal.
165
+ - **`wl show` timeline log lines no longer overflow to a second line.** The one-line truncation
166
+ budgeted against a hardcoded indent guess (32) while the real prefix (` <ts> #L<id> ✎ log `)
167
+ is ~39 cols, so a long body spilled past the terminal width by exactly the prefix gap. It now
168
+ measures the actual prefix via `_display_width`. (Audited the other `_truncate_log_body` callers
169
+ — `wl logs --by-task` / `--id` / `wl log ls` already budget correctly.)
170
+ - **`wl help <topic>` rendered a small Markdown subset + stopped crashing on literal brackets.**
171
+ A dependency-free renderer now styles ATX headings, ```fenced``` code, and inline `**bold**` /
172
+ `*italic*` / `` `code` `` / `[text](url)` / bare URLs; with color off the markers are stripped.
173
+ Crucially it escapes literal text first, so topic bodies containing `[ ]` / `[x]` / `[/]` / `#L42`
174
+ (e.g. `wl help status`) no longer crash rich markup parsing under `--color always`. The supported
175
+ subset is documented for topic authors in CONTRIBUTING.md. `wl help para` also gained a PARA
176
+ concept intro + a link to Tiago Forte's canonical article. The renderer now uses a 3-tier
177
+ contrast scheme (Claude-Code-style): dim-grey body prose, bright-cyan references (inline
178
+ `code`, `[links]`, See-also entries, the index's topic names, and `wl <subcommand>`
179
+ invocations — colored as commands only when the verb is real), and bold bright-white
180
+ headings/`**strong**`. Status markers in backticks (`` `[x]` `` etc.) render in their real
181
+ `wl ls` / `day` status colors, so the `wl help status` legend matches the rest of wl. The topic
182
+ title is now underlined (bold bright-white) instead of spending a whole row on a `───` rule, and
183
+ the index's topic-name column is padded to the longest name + 2 so a long id (`print-completion`)
184
+ keeps a gap before its description instead of running into it.
185
+ - **`wl tree --root <month>` / `wl focus <month>` now show tasks fuzzy-pinned at that time
186
+ node** (#436). A task pinned at `@2026-06` (via `scheduled_date`) hangs under its project,
187
+ not the month node, so the tree/focus parent_id walk missed it — which made a "what's
188
+ already scheduled this month?" check come up empty and a duplicate get created. They now
189
+ surface such pins (the filtered `--tag`/`--kind` path too); #413 had fixed only day nodes.
190
+ - **Shell completion for default-verb group leaf args.** When a top-level command became a
191
+ metric-style group (`log` / `sched`), its everyday args moved under the `add` subparser,
192
+ which the fish / bash / zsh completion walk skipped — so `wl sched <id> --recur` lost its
193
+ suggestions and `wl log <id> --date` its date completion. The generators now descend into
194
+ a default-verb group's leaf (`_default_verb_leaf`) and emit those completions under the
195
+ bare group name.
196
+ - **`parent_id` cycle safety (FK-off hardening).** With foreign-key enforcement off
197
+ (WL#501), `parent_id` integrity isn't DB-guaranteed and the bulk `wl apply` / `wl import`
198
+ update path set a parent with only an existence check (the interactive `wl reparent` had a
199
+ cycle guard; the bulk path didn't) — a cycle would then make the ancestor/descendant
200
+ walks loop forever and hang the CLI. The bulk path now refuses a self/descendant parent
201
+ (parity with `reparent`), and `_collect_descendants` / `_ancestors_chain` are visited-set
202
+ guarded so any pre-existing cycle degrades gracefully instead of hanging.
203
+ - **`wl link` strips an outer `[[ ]]` wrapper on input (WL#462).** `[[X]]` and `X` now store
204
+ identically (no more `[[[[X]]]]` double-wrap), re-linking either form dedups via the natural
205
+ key, and `wl unlink`/`apply -link` match regardless of wrapping. All link writes go through a
206
+ single normalizing chokepoint (`_upsert_link`/`_delete_link`). (Dedup + `unlink` already
207
+ existed; this closes the last part of #462.)
208
+ - **`wl day` drops the redundant per-row `«planned·not-done»` hint under the default `--by
209
+ plan` (WL#500).** The `▸ planned` group header + the `[ ]` status marker already say it; the
210
+ hint stays under `--by project`/`priority`, and the footer count is unchanged.
211
+
212
+ ### Internal
213
+ - **Command taxonomy documented (DESIGN §1.1):** every command is a primitive entity CRUD
214
+ (the `metric add/ls/edit/rm` subsystem is the template), a composite helper, or a read-only
215
+ view; with the per-entity CRUD matrix and remaining gaps (`prop` unset, `clock` edit/rm).
216
+ - **Further raw-SQL reduction (DESIGN G3).** `_insert_log` returns the new log id (dropping 6
217
+ `SELECT last_insert_rowid()` follow-ups — also more correct than the connection-global
218
+ lastrowid), and 5 more single-table reads/writes moved onto `db_table`. Remaining raw SQL is
219
+ all genuinely complex (JOIN / time-window / aggregate / OR-LIKE / conditional / dynamic).
220
+ - **Raw-SQL 余债 cleared (WL#526):** `_get_or_make`'s 4 time-skeleton lookups (single-table
221
+ `LIKE`/`=` probes) and `_node_clock_min`'s all-time clock-SUM + log-span-DISTINCT moved onto
222
+ `db_table` (`cols=` carries the aggregate). Only the *day-scoped* aggregates (SUM/DISTINCT +
223
+ `local_day_sql` in WHERE) stay raw — genuinely complex.
224
+
9
225
  ## [0.5.0] — 2026-06-05
10
226
 
11
227
  ### Added
@@ -48,6 +48,47 @@ wl --db /tmp/scratch.db add "experiment"
48
48
 
49
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
50
 
51
+ ## Writing `wl help` topics
52
+
53
+ `wl help <topic>` is an info-style browser over Markdown docs in `src/worklog/help/<lang>/<topic>.md` (architecture: DESIGN §25). `en` is the source language and the per-topic fallback; other languages mirror the same topic ids. To add or edit a topic, just add/edit a `.md` file — the loader, the index, shell completion, and each command's `--help` pointer pick it up automatically.
54
+
55
+ **File format** — minimal frontmatter between `---` fences, then a Markdown body:
56
+
57
+ ```
58
+ ---
59
+ title: tag — labels on a node # "<name> — <one-line description>"; the index shows the part after the dash
60
+ category: command # one of: guide | concept | command | param
61
+ see_also: node, day, ls, add # comma/space-separated topic ids
62
+ ---
63
+ <body>
64
+ ```
65
+
66
+ - The **topic id is the filename stem** (`tag.md` → `wl help tag`). For a command topic, name it after the command so its `--help` auto-gains a `More: wl help <cmd>` pointer (commands without a same-named topic fall back to a family topic via `_HELP_FAMILY` in `cli.py`).
67
+ - Every `see_also` id **must resolve to a real topic** — a test (`test_all_see_also_targets_resolve`) fails on a dangling link, since a "See also" is meant to be runnable as `wl help <x>`.
68
+
69
+ **Supported Markdown is a small, fixed subset** (rendered by a dependency-free renderer in `commands/help.py` — no Markdown/`rich.markdown` engine):
70
+
71
+ | Syntax | Renders as |
72
+ |---|---|
73
+ | `# Heading` … `###### ` | a styled heading (the `#`s are stripped) |
74
+ | ` ```fence ` … ` ``` ` | a code block (dimmed; the fence lines are dropped; no inline parsing inside) |
75
+ | `**bold**` | bold |
76
+ | `*italic*` | italic |
77
+ | `` `code` `` | inline code (cyan) |
78
+ | `[text](url)` | underlined text + the url |
79
+ | `http://…` / `https://…` (bare) | underlined link |
80
+
81
+ Everything else is printed verbatim, so **lay the body out as readable plain text** (it must read fine with color off, where the markers above are simply stripped). Notes:
82
+
83
+ - **`_italic_` is intentionally NOT supported** — bare underscores are too common in identifiers (`node_id`, `closed_at`, `$WORKLOG_LANG`). Use `*italic*`.
84
+ - Literal brackets (`[ ]`, `[x]`, `[#A]`, `#L42`) are safe — the renderer escapes them; don't avoid them.
85
+ - Preview both ways before committing: `wl help <topic>` and `wl help <topic> --color always`.
86
+
87
+ **The same subset also styles `--help`** — `argparse` epilogs and `description=` strings are colorized by `colorize_help` (in `commands/help.py`) into the identical scheme, so you may use `` `code` `` / `**bold**` / `*italic*` / `[text](url)` there too. Two rules:
88
+
89
+ - **Markdown only in `epilog=` / `description=`** (which argparse prints raw/un-wrapped). **Never in a per-command `help=` one-line summary** — argparse line-wraps those, which splits a Markdown span across lines (the marker then leaks instead of styling). Keep `help=` plain prose; `wl <command>` references in it still get colored by the heuristic.
90
+ - A `wl <command>` reference colors itself (the heuristic recognizes real subcommand names) — reserve backticks for the cases the heuristic can't catch: bare command names that collide with English (`` `set` `` / `` `log` `` / `` `show` ``), key=value bits, flags-in-prose.
91
+
51
92
  ## TDD: red → green → refactor
52
93
 
53
94
  Every change touching behavior follows the [Red-Green-Refactor cycle](https://brennanbrown.github.io/notes/programming/python-tdd/):
@@ -41,6 +41,117 @@ worklog deliberately stays **dependency-light and low-abstraction**: runtime is
41
41
  - Long flags `--xxx`; short flags only for the highest-frequency ones (`-k` kind / `-p` priority / `-t` tag)
42
42
  - Missing node: always `sys.exit(f"✗ ... #{id} not found")` with non-zero exit code
43
43
 
44
+ ### 1.1 Command taxonomy (primitive CRUD / composite helper / view)
45
+
46
+ Every command falls into exactly one of three buckets. Knowing which keeps the
47
+ surface coherent and tells you where a new command belongs.
48
+
49
+ **A. Primitive entity CRUD** — directly create/read/update/delete one underlying
50
+ table row. The **`metric` subsystem is the template**: `metric add / ls / edit / rm`,
51
+ a complete, consistently-named CRUD set. Every entity should reach that completeness;
52
+ naming/behaviour stays uniform across entities. Current state:
53
+
54
+ | entity | create | read | update | delete |
55
+ |---|---|---|---|---|
56
+ | **metric** | `metric add` | `metric ls` | `metric edit` | `metric rm` |
57
+ | **node** | `node add` | `node ls` / `node show` | `node edit` / `reparent` | `node rm` |
58
+ | **prop** | `prop set` (= `set` w/ a non-meta key) | `prop ls` / `show` | `prop set` | `prop rm` (= `unset`) |
59
+ | **meta** (typed-log fields) | `meta set` (= `set` w/ a meta key; `goal`/`recap` for today) | `meta ls` / `show` | `meta set` (appends; latest = current) | `meta rm` (= `unset` w/ a meta key) |
60
+ | **clock** | `start` / `spent` | `clock ls` / `active` | `clock edit` | `clock rm` |
61
+ | **link** | `link add` (= `link`) | `link ls` / `show` | — (atomic) | `link rm` (= `unlink`) |
62
+ | **log** | `log add` (= `log`) | `log ls` / `logs` / `show` | `log edit` (= `relog`) | `log rm` (= `unlog`) |
63
+ | **sched** | `sched add` (= `sched`) | `sched ls` / `show` | `sched add` / `defer` | `sched rm` (= `sched --clear`) |
64
+ | **tag** | `tag add` (= `tag +x`) | `tag ls` / `show` | — (atomic) | `tag rm` (= `tag -x`) |
65
+ | **date_meta** | `date set` (= `dateinfo`) | `date ls` / `dateinfo` | `date set` | `date rm` (= `dateinfo --clear`); `date import` |
66
+
67
+ Every entity now has a full metric-style `<entity> <verb>` group. Entities reshaped: **metric**
68
+ (template), **node**, **prop**, **meta**, **clock**, **link**, **tag**, **log**, **sched**, **date** (the `date_meta` table). High-frequency verbs keep a top-level
69
+ shortcut onto the same handler (`wl add` == `wl node add`, `wl unlink` == `wl link rm`,
70
+ `wl relog` == `wl log edit`, `wl unlog` == `wl log rm`, `wl dateinfo` == polymorphic over
71
+ `wl date set/ls/rm/import`), args defined once via a shared adder so the two forms can't
72
+ drift. `clock` intervals are CREATED by the composite helpers (`start`/`stop`/`spent`).
73
+ Removal is soft-delete (reversible tombstone, § soft-delete).
74
+
75
+ **`wl set` / `wl unset` are *key-routed* shortcuts** (the same way `wl add` is `node add`):
76
+ a meta key (`goal`/`summary`/`overview`/`top5`) routes to `wl meta set` / `wl meta rm`
77
+ (history-preserving typed log); any other key routes to `wl prop set` / `wl prop rm`
78
+ (static single-value UDA prop). So one `set`/`unset` surface fronts two group-set verbs by
79
+ key — `prop` and `meta` are distinct stores (prop = overwrite single-value; meta = append
80
+ typed log, latest = current), but share the everyday `set`/`unset` entry. `wl goal` / `wl
81
+ recap` additionally auto-target today's day node for the goal / summary fields.
82
+
83
+ > Known limitation: the four meta keys are reserved, so a *prop* literally named
84
+ > `goal`/`summary`/`overview`/`top5` can't be set through `wl set` (the key routes to meta).
85
+ > Acceptable for now (nobody needs a prop with those names); a future fix would be a
86
+ > `--prop`/`--meta` disambiguator flag on `set`, or `wl prop set` (which is unambiguous) as
87
+ > the escape hatch.
88
+
89
+ **`wl alias add/ls/rm`** manages `~/.config/worklog/aliases.ini` (maps a short name to a
90
+ command, e.g. `wl d` == `wl day`); aliases are wired into the parser at startup, so a change
91
+ takes effect on the next invocation. The target must be a real command and an alias may not
92
+ shadow one.
93
+
94
+ **Default-verb dispatch (collision entities).** When the group name equals the old leaf
95
+ command (`link` / `tag` / `log` / `sched`), a custom parser (`_WlParser` /
96
+ `_expand_default_verb`) inserts the group's *default verb* when the token after the entity
97
+ isn't a known verb — so the legacy leaf keeps working: `wl link 42 doc` → `wl link add 42
98
+ doc`, `wl tag 42 +work` → `wl tag add 42 +work`, `wl log 42 "body"` → `wl log add 42
99
+ "body"`, `wl sched 42 2026-06-15` → `wl sched add 42 2026-06-15`, while `wl sched ls 42` /
100
+ `wl sched -h` are left alone. The leaf's first positional is always an int id, never a verb
101
+ word, so the test is unambiguous. For `tag` the `add` default verb keeps the full `+add` /
102
+ `-remove` / bare-add / empty-list grammar (`cmd_tag`); `tag ls` / `tag rm` are
103
+ single-purpose convenience verbs. For `log`, `log edit` / `log rm` reuse the `relog` /
104
+ `unlog` handlers (which keep their shortcuts), and `log ls` is a node-scoped lister (the
105
+ full filterable stream view stays at `wl logs`). For `sched`, the `add` default verb keeps
106
+ the full when / `--recur` / `--clear` / list-when-empty grammar (`cmd_sched`); `sched ls`
107
+ lists and `sched rm` clears, while `wl defer` (status=LATER + rough hint) stays its own
108
+ composite command.
109
+
110
+ A note for the completion generators: a default-verb group's leaf args (`sched`'s
111
+ `--recur` / `when`, `log`'s `--date`) live under the nested `add` subparser, so the
112
+ per-subcommand walk (which skips `_SubParsersAction`) must also descend into the
113
+ default-verb leaf (`_default_verb_leaf`) and emit those completions under the bare group
114
+ condition — otherwise `wl sched <id> --recur<TAB>` silently loses its suggestions.
115
+
116
+ The one **clean group** (no collision, so no default verb) is **`date`** (the `date_meta`
117
+ table): `date set / ls / rm / import`, with `wl dateinfo` as the polymorphic everyday
118
+ shortcut (sets when given a label, lists when not, `--clear` / `--import` variants).
119
+
120
+ The `<entity> <verb>` reshape (WL#486) is now **complete** for every entity.
121
+
122
+ **B. Composite helper** — a one-step shortcut that wraps one or more primitives for
123
+ the common path; never the *only* way to do something (the primitive stays). Status
124
+ verbs `done` / `cancel` / `reopen` / `wait` (set `node.status` + side effects),
125
+ `defer` (sched + LATER), `start` / `stop` (clock + DOING), `tick` (habit check-in
126
+ metric + done), and the compound `add --log/--done/--at/--link/--sched/--metric`
127
+ (add + several primitives in one transaction).
128
+
129
+ **C. View** — read-only cross-entity rendering, no single-row CRUD: `ls` / `tree` /
130
+ `day` / `agenda` / `projects` / `changes` / `summary` / `focus` / `ancestors` /
131
+ `descendants` / `find` / `logs` / `show`. These share the unified filter (§ shared
132
+ `--tag/--kind/--status`), time-window (§8) and `--by` (§9) conventions.
133
+
134
+ System commands (`migrate` / `config` / `init` / `import` / `apply` / `themes` /
135
+ `print-completion`) sit outside the three buckets.
136
+
137
+ ### 1.2 Help system (battery-included + shortcut ↔ canonical cross-reference)
138
+
139
+ Every command's `--help` is self-sufficient (§35 "battery-included"): a one-line intro,
140
+ a few scenario examples, and a "Differences from related commands" block. On top of that,
141
+ **wherever a shortcut and its canonical entity-verb both exist, both helps must name the
142
+ relationship — this is mandatory, never omitted**, so a reader landing on either form
143
+ learns the other:
144
+
145
+ - The **canonical entity-verb** help (e.g. `wl node add -h`) states it's also reachable as
146
+ the shortcut (`also: wl add`).
147
+ - The **shortcut** help (e.g. `wl add -h`) states it's the shortcut for the canonical form
148
+ (`canonical: wl node add`).
149
+ - The **entity group** help (e.g. `wl node -h`) carries a dedicated **Shortcuts** section
150
+ mapping each shortcut-able verb to its top-level name (`add → wl add`, `ls → wl ls`, …).
151
+
152
+ This applies to every entity as the `<entity> <verb>` + shortcut rollout proceeds, so the
153
+ two ways to invoke the same operation never drift in discoverability, only in keystrokes.
154
+
44
155
  ## 2. Data model (`src/worklog/migrations/`)
45
156
 
46
157
  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.
@@ -51,7 +162,7 @@ A single `node` table carries everything; the `kind` field discriminates type; `
51
162
  - **status only applies to task / habit / meetlog**; time-hierarchy kinds (year/month/...) and project kind leave status NULL
52
163
  - Tables: `node / tag / log / metric / clock / prop / link / sched / date_meta` + derived view `v_node_path`
53
164
  - **`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.)
165
+ - **Meta fields are history-preserving typed logs**: day `goal`/`summary`, week `overview`, month `top5` are `log.tag` logs (latest = current; each edit appends), with their own `wl meta set/ls/rm` CRUD group; also written by `wl goal`/`wl recap` (today-auto) and the key-routed `wl set <node> <key>` shortcut. (`prop` is back to truly-static single-value attributes — a separate store.)
55
166
  - **`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.
56
167
  - **Two parallel trees, both hung under lifetime**:
57
168
  - **Responsibility line**: `lifetime → area → project → task` (PARA model: area is a cross-time responsibility domain, projects belong to areas, tasks belong to projects)
@@ -431,6 +542,75 @@ The decision model: **schedule (forward planning, calendar-like) and log (retros
431
542
 
432
543
  When changing sched / day derivation, keep `_sched_fires` / `_scheduled_node_ids` / `_node_plan` / `_sec_group` / `_render_day_group` / `cmd_day` / `cmd_logs(--group)` / `cmd_sched` in sync.
433
544
 
545
+ ## 25. Help topic system (`wl help` — info-style)
546
+
547
+ Beyond per-command `--help` (§1.2), `wl help` is a standalone, **info-style topic browser**:
548
+ a repo-managed set of docs — one per command / concept / common-param / guide — navigable by
549
+ "see also" links. This keeps `--help` short (summary + a pointer) while giving each topic room
550
+ for a full explanation, and makes the help content **i18n-able and reviewable as plain files**.
551
+
552
+ ### Docs live in the repo (packaged, i18n-ready)
553
+ - Location: `src/worklog/help/<lang>/<topic>.md`, shipped in the wheel like `migrations/`.
554
+ `en` is the source language and the fallback; other langs mirror the same topic ids.
555
+ - One Markdown file per topic; topic id = filename stem (`node.md` → `wl help node`).
556
+ - Minimal frontmatter (no YAML dep), `key: value` lines between `---` fences:
557
+ ```
558
+ ---
559
+ title: node — the single unit of everything
560
+ category: concept # concept | command | param | guide
561
+ see_also: log, tag, status, add
562
+ ---
563
+ <markdown body>
564
+ ```
565
+ - `index.md` is the root (`wl help` with no topic): the overview + topic list grouped by category.
566
+
567
+ ### `wl help` behavior
568
+ - `wl help` → render `index.md` + list topics by category.
569
+ - `wl help <topic>` → title, body, then a "See also: …" footer resolved from `see_also`.
570
+ - Unknown topic → closest-match suggestions (never a stack trace).
571
+ - Language: `--lang` > `$WORKLOG_LANG` > `$LANG` prefix > `en`; a missing translation falls back
572
+ to `en` per-topic, never a hard error.
573
+ - Rendering: a **dependency-free renderer** (`commands/help.py`) handles a small fixed Markdown
574
+ subset — ATX headings, ```fenced``` code blocks, and inline `**bold**` / `*italic*` /
575
+ `` `code` `` / `[text](url)` / bare URLs (no `_italic_` — underscores collide with identifiers;
576
+ no Markdown engine, no `rich.markdown`). It **escapes literal text** before injecting style
577
+ markup, so bodies with `[ ]` / `[x]` / `[/]` / `#L42` render safely (a raw `[/]` previously
578
+ crashed rich); with color off the markers are simply stripped. The exact subset + frontmatter
579
+ spec for topic authors lives in **CONTRIBUTING.md** ("Writing `wl help` topics").
580
+
581
+ ### Relationship to `--help` (the slimming policy)
582
+ - `--help` stays the *quick reference at the point of use*: usage, one-line intro, a couple of
583
+ examples, and — when a fuller topic exists — a closing `More: wl help <topic>`.
584
+ - `wl help <topic>` is the *teaching* layer: concepts, rationale, cross-links, the planning
585
+ rhythm, PARA, etc. Content that would bloat `--help` lives here, keeping `--help` scannable.
586
+ - The top-level `wl -h` Concepts glossary stays a one-line teaser per concept, pointing to
587
+ `wl help <concept>` for the full entry.
588
+
589
+ ### `--help` rendering: color, Markdown, wrapping (shared with `wl help`)
590
+ - **Color is post-processed, not native.** argparse has no color before Py 3.14 (and 3.14's uses
591
+ its own palette), so `colorize_help` injects ANSI into argparse's *already-formatted* output.
592
+ The ANSI is zero-width, so argparse's column math is untouched. Color/theme are re-resolved from
593
+ `--color`/`--theme`/env at format time, because `--help` fires inside `parse_args`, before
594
+ `main()` builds the console. It's the same 3-tier scheme as `wl help` (dim body / bright-cyan
595
+ references / bold-white headings).
596
+ - **`--help` may use the topic Markdown subset — but only in `epilog`/`description`.** Markers
597
+ render with color on and are stripped with color off. `help=` one-line summaries stay **plain
598
+ text**: argparse line-wraps them, which would split a Markdown span across lines. The point of
599
+ Markdown here is explicit styling where a heuristic can't help — a command name like `set` /
600
+ `log` / `show` collides with the English word, so it's backticked to mark it as code.
601
+ - **Wrapping lives in the formatter, not the post-processor.** A `RawDescriptionHelpFormatter`
602
+ subclass overrides `_fill_text` (the hook argparse calls *only* for epilog/description) to wrap
603
+ the raw epilog with a hanging indent; option/choice help is still wrapped by argparse's own
604
+ `_split_lines`. Re-wrapping argparse's whole formatted output as a post-process was the wrong
605
+ layer — it re-wraps already-wrapped rows and misaligns them. The formatter caps the width on
606
+ wide terminals (`HELP_MAX_WIDTH`) and shares one `render.help_width()` with the `wl help`
607
+ topic-body wrapper, so every help surface lines up to a single width.
608
+
609
+ ### Rollout (incremental)
610
+ Land the loader + `wl help` command + a handful of seed topics first; then author one topic per
611
+ command / concept / param and trim each `--help` to summary + pointer. Topics land piecemeal —
612
+ a missing topic simply isn't offered; nothing breaks.
613
+
434
614
  ## 24+. Additional sections (24–37)
435
615
 
436
616
  Sections 24 onward cover finer-grained extensions: metadata at the top of `wl day` (goal / summary / top5 / overview blockquote), date context (`date_meta` for holidays / makeups), `--by-task` per-task aggregation in `wl logs`, `wl active` (active CLOCKs with elapsed), the `compound flag` semantics on `add` / `done` / `cancel` (`--log` / `--at` / `--link` / `--sched` / `--done` in one shot), `unlog` / `relog` (log editing through `#L<id>` references), time backfill (`start --at` / `stop --at` / `spent`), the multi-habit interactive `wl checkin`, recurrence rules including `-1` for "last day of period", the `wl ls` multi-dimension query model, default-tail-N to prevent screen-blast, shell completion via `print-completion` init-load (matching the starship / direnv / zoxide pattern), and the **battery-included command help design philosophy** (every `--help` carries a one-line intro + scenario + diff-from-neighbors).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyworklog
3
- Version: 0.5.0
3
+ Version: 0.6.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyworklog"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  description = "SQLite-backed worklog tool with a todo.sh-style CLI"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -30,7 +30,8 @@ Likewise, before adding a dev todo under a project, check that project's existin
30
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`) |
31
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>` |
32
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 |
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) |
33
+ | Meta info (end-of-day summary / Top5 / today's goal) | History-preserving typed logs (`log.tag`), **a distinct store from props** — own group `wl meta set/ls/rm <node> <field>` (`field` ∈ goal/summary/overview/top5; each write appends, latest = current). Shortcuts onto it: `wl goal "..."` / `wl recap "..."` (auto-target today's day; preferred — read-back + stale-warning), and the key-routed `wl set <node> <field>` (= `wl meta set`; `wl unset <node> <field>` = `wl meta rm`). e.g. `wl meta set <week> overview "..."` / `wl meta set <month> top5 "..."`. `wl day` shows them at the top as a blockquote |
34
+ | Command aliases | `wl alias add d day` (→ `wl d` == `wl day`) / `wl alias ls` / `wl alias rm d`. Stored in `~/.config/worklog/aliases.ini`; target must be a real command, can't shadow one; takes effect next run |
34
35
  | 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" |
35
36
  | 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) |
36
37
  | List all active projects | `wl projects` |
@@ -154,9 +155,12 @@ wl checkin --all-kinds # not limited to habit kind
154
155
 
155
156
  ### Meta fields keep history (typed logs, not props)
156
157
 
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).
158
+ The four meta fields (goal/summary/overview/top5) have their own group `wl meta
159
+ set/ls/rm <node> <field>`, stored as `log.tag` logs — each write appends, the latest is
160
+ current, so edit history is kept (`prop` is a separate store, only for truly-static
161
+ single-value attributes). `wl goal` / `wl recap` are today-auto shortcuts; `wl set <node>
162
+ <field>` / `wl unset <node> <field>` are key-routed shortcuts onto `wl meta set` / `wl meta
163
+ rm` (so `wl set` fronts both `prop set` and `meta set` by key, like `wl add` is `node add`).
160
164
 
161
165
  ### Recurrence rules (`--recur`), every variant supports `-1` for "last day of period"
162
166