mps 0.5.0 → 1.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f0d20f2919cb1ee0ae46a3fe6eaef005af856eabd4650fc5e6e07477b5e7fb3
4
- data.tar.gz: 259a623e9ee4b8edea593ab16a270b11015a55e3e22564359a424610f0510caa
3
+ metadata.gz: 96b0eac30435621ea7ca9d0575d3a3285a074cb2707d453075ced7e1535ca288
4
+ data.tar.gz: 647d49ad9cffd798858318dd712d4653df718e37d05a8f52803be367e423c17c
5
5
  SHA512:
6
- metadata.gz: 4653dca542034d736e5e61c69b2750c5ddb119c141876fac896b3f15e79d3d758f9e299a3ecf75fd17c52c5e7dbd8da9a562be228d760c6e4a7ba290359c83b0
7
- data.tar.gz: 9cf8bf1273e814ed6708561c1f7cd5b82917e804b0e97e867d34c50bb2f35e762067f3df3493cb59eb4eea3ab0e5b1be9e02c38cbc7fffb43c9b9494c4790be3
6
+ metadata.gz: e1204e48c493e45f0dc226b1a7a4de0751b4821774ab5984c54f56cf53e91ac99cb09a17fb5cf777de0cf665ebefd69289913de935baf5655d3afd76f80da9bf
7
+ data.tar.gz: bbb938d0b337b652c07cca9983949a830c13fd9180ab037df87bd75feda68a6e11dfbc091689fa96546edc1e6f5f02e770b98a6d896c32b90de2b95b0fcaae00
@@ -1,29 +1,34 @@
1
- name: Ruby
1
+ name: Ruby CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches:
6
- - master
7
- - release/*
5
+ branches: [master, "release/*"]
8
6
  pull_request:
9
- branches:
10
- - master
11
- - dev
12
- - release/*
7
+ branches: [master, dev, "release/*"]
13
8
 
14
9
  jobs:
15
- build:
10
+ test:
16
11
  runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ # Futureproofing: Test against multiple versions
16
+ # This ensures you catch the "RubyGems 4.0" issue before your users do
17
+ ruby: ["3.1", "3.2", "3.3"]
18
+
17
19
  steps:
18
- - uses: actions/checkout@v2
20
+ - uses: actions/checkout@v4 # Use the latest version
21
+
19
22
  - name: Set up Ruby
20
23
  uses: ruby/setup-ruby@v1
21
24
  with:
22
- ruby-version: 3.0.2
23
- - name: Run the default task
24
- run: |
25
- gem update --system
26
- gem install bundler -v 2.2.13
27
- bundle add rake
28
- bundle install
29
- bundle exec rake
25
+ ruby-version: ${{ matrix.ruby }}
26
+ # AUTOMATIC FUTUREPROOFING:
27
+ # This handles 'bundle install' and caching for you efficiently.
28
+ bundler-cache: true
29
+
30
+ - name: Run tests
31
+ run: bundle exec rake
32
+
33
+ - name: Run with groups tests
34
+ run: bundle exec rake test:with_groups
data/.gitignore CHANGED
@@ -6,8 +6,9 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
- *.lock
10
9
  *.mps
10
+ !test/assets/*.mps
11
11
  /vendor/
12
- **/**/*.gem
13
-
12
+ *.gem
13
+ .claude/
14
+ prompt-for-v1-rollout.txt
data/ARCHITECTURE.md ADDED
@@ -0,0 +1,298 @@
1
+ # MPS — Canonical Architecture Document
2
+
3
+ > Updated for v1.0. Reflects all Phase 1–7 changes.
4
+ > For AI agents and developers evolving the system.
5
+
6
+ ---
7
+
8
+ ## 1. Core Vision and Goals
9
+
10
+ MPS is a **plain-text personal information system**. The file system is the database, the text editor is the UI, and git is the sync layer. The `.mps` DSL is lightweight, human-writable, and machine-parseable without a grammar library.
11
+
12
+ Philosophy: **structured data that looks like prose**. Every design decision flows from keeping files readable in any text editor while remaining richly queryable at the command line.
13
+
14
+ ---
15
+
16
+ ## 2. Load Order
17
+
18
+ ```
19
+ lib/mps.rb
20
+ require mps/version
21
+ require mps/mps ← MPS module methods
22
+ require mps/constants
23
+ require mps/config
24
+ require mps/interpolators/interpolators
25
+ require mps/elements/elements
26
+ require mps/engines/engines
27
+ require mps/ref_resolver ← Phase 3
28
+ require mps/query ← Phase 7
29
+ require mps/presenter ← Phase 7
30
+ require mps/store
31
+ require cli/mps ← thin dispatcher (defines MPS::CLI::MPS)
32
+ require mps/cli/commands ← auto-discovers lib/mps/cli/commands/*.rb
33
+ ```
34
+
35
+ Each layer depends only on layers above it. No circular dependencies.
36
+
37
+ ---
38
+
39
+ ## 3. Macro Architecture
40
+
41
+ ```
42
+ exe/mps
43
+ └─ MPS::CLI::MPS (Thor, thin dispatcher)
44
+ │ shared helpers: init, store, date_range, type_badge,
45
+ │ print_tree, format_duration, auto_git_cmd
46
+
47
+ ├─ lib/mps/cli/commands/*.rb (self-registering via class_eval)
48
+ │ open, list, append, update, done, search, stats,
49
+ │ export, git, autogit, cmd, config, tags, version
50
+
51
+ ├─ MPS::Query ← filter predicate object (Phase 7)
52
+ ├─ MPS::Presenter ← rendering object (Phase 7)
53
+ ├─ MPS::RefResolver ← human↔epoch ref translation (Phase 3)
54
+
55
+ └─ MPS::Store ← all filesystem operations
56
+ └─ MPS::Engines::Parser ← stack machine parser
57
+ └─ MPS::Elements.* ← typed value objects
58
+ ```
59
+
60
+ ---
61
+
62
+ ## 4. Micro Architecture
63
+
64
+ ### 4.1 The `.mps` DSL (unchanged from v0.5)
65
+
66
+ ```
67
+ @type[arg1, key: val]{
68
+ body
69
+ @nested_type{ child }
70
+ }
71
+ ```
72
+
73
+ Optional brackets; arbitrary nesting; comma-separated args where bare words → tags and `key: val` → named attrs.
74
+
75
+ ### 4.2 Element Schema DSL (Phase 2)
76
+
77
+ The core architectural addition of v1. Each element class declares a typed attribute schema using a class macro:
78
+
79
+ ```ruby
80
+ class Task
81
+ include Element
82
+ SIGNATURE_STAMP = "task"
83
+ SIGNATURE_REGEX = /\Atask\z/
84
+
85
+ attribute :status, type: :string, default: "open", flag: "status", aliases: ["-s"]
86
+ end
87
+ ```
88
+
89
+ `Element.included` extends the class with `ClassMethods`, which provides:
90
+ - `attribute(name, type:, default:, flag:, aliases:)` — declares a schema entry
91
+ - `schema` — frozen public view of declared attributes
92
+ - `_schema` — mutable backing store (used internally and in tests)
93
+ - `parse_args(raw)` — schema-driven implementation; subclasses no longer override this
94
+
95
+ **Guiding principle**: adding a new attribute to an element requires exactly one schema declaration. The parser, filter predicates, export output, and future CLI option generation all derive from that single declaration.
96
+
97
+ Current schema per type:
98
+
99
+ | Type | Attributes |
100
+ |------|------------|
101
+ | Task | `:status` (default: "open") |
102
+ | Log | `:start`, `:end` |
103
+ | Reminder | `:at` |
104
+ | Note | — (tags only) |
105
+ | MPS | — (tags only) |
106
+
107
+ ### 4.3 Parser (unchanged algorithm, extended interface)
108
+
109
+ Single-pass stack machine. Position-advancing via `Regexp#match(str, pos)`. Returns a flat `{ "YYYYMMDD.n.m" => element }` hash. Ref-path encoding: date integer (YYYYMMDD) as the epoch base, then sequential child counters.
110
+
111
+ **v1 extension**: accepts `interpolator_classes:` keyword argument. After the main parse loop, `_apply_interpolations` mutates body strings in-place for any registered interpolator patterns.
112
+
113
+ Backward-compat alias: `Engines::MPS = Engines::Parser` retained.
114
+
115
+ ### 4.4 RefResolver (Phase 3)
116
+
117
+ `MPS::RefResolver.new(elements_hash)` builds a bidirectional mapping between epoch refs and human refs at construction time.
118
+
119
+ **Human ref format**:
120
+ - Top-level: `{type}-{n}` where `n` is sequential per type (e.g. `task-1`, `note-2`)
121
+ - Nested: `{parent_human}.{child_index}` (e.g. `mps-1.1`, `task-2.3`)
122
+
123
+ **API**:
124
+ - `to_human(epoch_ref)` → human ref string or nil
125
+ - `to_epoch(human_ref)` → epoch ref string or nil
126
+ - `resolve(ref_str)` → epoch ref (accepts either form)
127
+ - `all_epoch_refs` → sorted list
128
+
129
+ The resolver is ephemeral — instantiated per-request, not cached. Store exposes `resolver_for(date)`.
130
+
131
+ ### 4.5 Store (extended for Phase 3 + Phase 4)
132
+
133
+ `MPS::Store.new(storage_dir)` now also collects `@interpolator_classes` and passes them to every parser call.
134
+
135
+ **New methods**:
136
+ - `resolver_for(date)` → `RefResolver` for that date's elements
137
+ - `rewrite_element(ref_str, new_attrs, date: Date.today)` → boolean
138
+
139
+ `rewrite_element` accepts both epoch refs and human refs. Human refs are resolved using a `RefResolver` for the given date. The rewrite:
140
+ 1. Reads the file into memory
141
+ 2. Parses to find the element and its `raw_args`
142
+ 3. Merges `new_attrs` over existing `parsed_args`, preserving tags
143
+ 4. Builds a new args string (`attr: val` pairs first, then tags)
144
+ 5. Replaces the `@type[old_args]{` opening via `String#sub` with a compiled regex
145
+ 6. Writes to `path.tmp.PID` then `File.rename` (atomic on POSIX)
146
+
147
+ **Known limitation**: if the same type+args combination appears multiple times in a file, `sub` replaces only the first occurrence. For typical personal files this is acceptable.
148
+
149
+ ### 4.6 Query (Phase 7)
150
+
151
+ `MPS::Query.new(opts)` encapsulates all filter logic. No Thor dependency.
152
+
153
+ - `apply(elements_hash)` → filtered hash (MPS containers excluded)
154
+ - `apply_for_tree(elements_hash)` → filtered hash preserving MPS containers when their children are visible
155
+
156
+ Schema-driven attr filters: iterates all element classes' schemas, maps `flag:` → `opts[flag_sym]`, applies as filter predicates. Adding `attribute :location, flag: "location"` to Task automatically makes `Query.new(location: "home")` filter by location.
157
+
158
+ ### 4.7 Presenter (Phase 7)
159
+
160
+ `MPS::Presenter.new(elements_hash, color_fn:, resolver:, with_refs:)` renders to string without Thor dependency.
161
+
162
+ - `render_tree` → [rendered_string, shown_count]
163
+ - `render_element(el, depth:)` → single line string
164
+ - `render_tag_table` → tag frequency table string
165
+
166
+ Colorization is injected via `color_fn:` proc. CLI passes `method(:set_color)` from Thor context. Tests use the default ANSI fallback or plain text (color_fn: nil disables ANSI).
167
+
168
+ ### 4.8 CLI Modularization (Phase 5)
169
+
170
+ `lib/cli/mps.rb` is now a **thin dispatcher**. It defines:
171
+ - Thor class header (class_option, default_task, exit_on_failure)
172
+ - `self.start` override for `default_command` config
173
+ - `version` command (tiny, lives here)
174
+ - All private shared helpers
175
+
176
+ **Each command is a separate file** under `lib/mps/cli/commands/`. Commands self-register by calling `MPS::CLI::MPS.class_eval { ... }` at load time. This works because `cli/mps.rb` (the dispatcher) is loaded before `mps/cli/commands` in `lib/mps.rb`.
177
+
178
+ `lib/mps/cli/commands.rb` auto-discovers and loads all `*.rb` files in the `commands/` directory:
179
+
180
+ ```ruby
181
+ Dir[File.join(__dir__, "commands", "*.rb")].sort.each { |f| require f }
182
+ ```
183
+
184
+ **Adding a command = adding a file. No other changes required.**
185
+
186
+ ---
187
+
188
+ ## 5. Design Patterns
189
+
190
+ | Pattern | Where |
191
+ |---------|-------|
192
+ | Schema DSL (class macro) | `Element.attribute` |
193
+ | Registry via module constants | Elements, Interpolators auto-discovery |
194
+ | Mixin composition | `Element` mixin included by all element types |
195
+ | Command self-registration | `class_eval` on dispatcher |
196
+ | Auto-discovery loader | `mps/cli/commands.rb` globs directory |
197
+ | Dependency injection | `color_fn:` in Presenter |
198
+ | Atomic file write | tmp + rename in `rewrite_element` |
199
+ | Resolver pattern | `RefResolver` translates between ref forms |
200
+ | Query object | `MPS::Query` encapsulates filter predicates |
201
+
202
+ ---
203
+
204
+ ## 6. Dependency Layering
205
+
206
+ ```
207
+ Constants → Config → Elements (schema DSL)
208
+ → Engines::Parser (uses element classes + interpolators)
209
+ → RefResolver (uses elements hash)
210
+ → Query (uses element schemas)
211
+ → Presenter (uses element classes + Query)
212
+ → Store (uses Parser + RefResolver)
213
+ → CLI dispatcher (uses Store + Query + Presenter)
214
+ → command files (CLI layer)
215
+ ```
216
+
217
+ **The CLI never instantiates Elements directly.** Store is the only gateway.
218
+
219
+ ---
220
+
221
+ ## 7. Resolved Technical Debt (v0.5 → v1.0)
222
+
223
+ | Debt item | Resolution |
224
+ |-----------|-----------|
225
+ | `git` / `autogit` code duplication | Extracted `auto_git_cmd` private method |
226
+ | Dead `display_str` / `disp_str` / `PADDING` | Removed from Element mixin |
227
+ | `get_filename_from_date` epoch mismatch | Delegates to `MPS_NEW_FILE_NAME_GEN` |
228
+ | `Engines::MPS` in tests | Updated to `Engines::Parser` |
229
+ | `Config#mps_dir` missing | Added `attr_reader :mps_dir` |
230
+ | `stats` inline duration math | Uses `format_duration` helper |
231
+ | Bespoke `parse_args` per element | Replaced by schema-driven `ClassMethods#parse_args` |
232
+ | `visible?` hardcoded `:status` | Schema-driven attr filters in `Query` |
233
+ | CLI monolith | Modularized into `commands/` directory |
234
+ | Interpolators unconnected | Wired via `interpolator_classes:` param |
235
+ | No mutation path | `Store#rewrite_element` + `update`/`done` commands |
236
+ | No human-readable refs | `RefResolver` + `--refs` flag on `list` |
237
+
238
+ ---
239
+
240
+ ## 8. Remaining Known Issues
241
+
242
+ | Issue | Notes |
243
+ |-------|-------|
244
+ | `rewrite_element` replaces first occurrence only | Acceptable for personal files; needs position tracking for robustness |
245
+ | `Engines::MPS = Parser` alias | Retained for backward compat; can be removed in v1.1 |
246
+ | Human refs for today only | Epoch refs work for any date; human refs require `--date` for non-today |
247
+
248
+ ---
249
+
250
+ ## 9. Extension Points
251
+
252
+ ### Adding a new element type
253
+
254
+ 1. Create `lib/mps/elements/widget.rb` with `class MPS::Elements::Widget; include Element; SIGNATURE_STAMP = "widget"; SIGNATURE_REGEX = /\Awidget\z/`
255
+ 2. Declare attributes: `attribute :foo, type: :string, flag: "foo"`
256
+ 3. Add `require_relative "./widget"` to `elements/elements.rb`
257
+
258
+ Parser, Store, Query, and Presenter all pick it up automatically. The CLI `append` command accepts the type. `update` options are generated from the schema at class-load time.
259
+
260
+ ### Adding a new command
261
+
262
+ 1. Create `lib/mps/cli/commands/mycommand.rb`
263
+ 2. Write `MPS::CLI::MPS.class_eval { desc "mycommand ARGS", "desc"; def mycommand(...) ... end }`
264
+ 3. No other changes required — the auto-discovery loader picks it up
265
+
266
+ ### Adding a new interpolator
267
+
268
+ 1. Create `lib/mps/interpolators/myinterp.rb` with `SIGNATURE_REGEX = /:myinterp/` and `get_str`
269
+ 2. Add `require_relative "./myinterp"` to `interpolators/interpolators.rb`
270
+
271
+ The store passes all discovered interpolators to the parser automatically.
272
+
273
+ ---
274
+
275
+ ## 10. Phase Roadmap Summary
276
+
277
+ | Phase | Scope | Status |
278
+ |-------|-------|--------|
279
+ | 1 | Technical debt cleanup | ✓ Done |
280
+ | 2 | Element attribute schema DSL | ✓ Done |
281
+ | 3 | RefResolver (human-readable refs) | ✓ Done |
282
+ | 4 | Element mutation (`rewrite_element`, `update`, `done`) | ✓ Done |
283
+ | 5 | CLI modularization (self-registering commands) | ✓ Done |
284
+ | 6 | Surface expansion (`tags`, `config`, `default_command`, `aliases`, interpolators) | ✓ Done |
285
+ | 7 | Query + Presenter extraction | ✓ Done |
286
+
287
+ **Out of scope for v1**: interactive TUI, LLM integration, cross-file `@ref` linking, SQLite search index.
288
+
289
+ ---
290
+
291
+ ## 11. Next Architectural Moves (v1.1+)
292
+
293
+ - **Position tracking in parser**: store byte offsets per element for robust rewrite (no collision risk)
294
+ - **SQLite search index**: for sub-second search over large archives
295
+ - **Cross-file `@ref` element**: using epoch refs as stable identifiers
296
+ - **Query language**: `mps query "type:task AND tag:work since:monday"`
297
+ - **Rust parser rewrite**: for performance-critical path (parser + store)
298
+ - **Remove `Engines::MPS` alias** now that all callers use `Engines::Parser`
data/CLAUDE.md CHANGED
@@ -39,30 +39,26 @@ bundle exec exe/mps append TYPE BODY [--tags work,release] [--at 3pm]
39
39
  bundle exec exe/mps autogit
40
40
  ```
41
41
 
42
- Set `MPS_DEBUG=true` to enable verbose `require_relative` tracing via the custom `ir()` loader.
43
-
44
42
  ## Architecture
45
43
 
46
44
  ### Entry point
47
45
  `exe/mps` calls `MPS::CLI::MPS.start(ARGV)` — a Thor-based CLI defined in `lib/cli/mps.rb`. All commands (`open`, `git`, `autogit`, `list`, `append`, `cmd`, `version`) live there and delegate to the library layer.
48
46
 
49
- ### Custom loader (`ir`)
50
- `lib/mps/mps.rb` defines a global helper `ir(relative_path)` that wraps `require_relative` with caller-location resolution. All internal requires use `ir` instead of `require_relative`. This is intentional — don't replace it with standard requires.
51
-
52
47
  ### Load order (`lib/mps.rb`)
53
48
  ```
54
- mps/version → mps/mps (defines ir + MPS module methods) →
55
- mps/constants → mps/config → mps/interpolators → mps/elements → mps/engines → cli/mps
49
+ mps/version → mps/mps (MPS module methods) →
50
+ mps/constants → mps/config → mps/interpolators → mps/elements → mps/engines →
51
+ mps/ref_resolver → mps/query → mps/presenter → mps/store → cli/mps → mps/cli/commands
56
52
  ```
57
53
 
58
54
  ### Parsing pipeline (`lib/mps/engines/mps.rb`)
59
- `Engines::MPS.parse_mps_file_to_elments_hash` reads an `.mps` file and uses `StringScanner` to tokenize it. It wraps the raw file contents in a synthetic `@mps[]{}` root element, then does a single-pass scan driven by two regexes from `Constants`: `AT_REGEXP_LA` (lookahead for `@element[args]{`) and `END_CURLY_REGEXP_LA` (lookahead for `}`). A stack tracks nesting; each closed element is instantiated and stored in a flat hash keyed by a dotted ref path (e.g. `"1234567890.1.2"`).
55
+ `Engines::Parser.parse_mps_file_to_elements_hash` reads an `.mps` file and uses a position-based stack parser with `Regexp#match(str, pos)`. It wraps the raw file contents in a synthetic `@mps[]{}` root element, then iterates using two regexes from `Constants`: `AT_REGEXP` (opening `@element[args]{`) and `END_CURLY_REGEXP` (closing `}`). A stack tracks nesting; each closed element is instantiated and stored in a flat hash keyed by a dotted ref path (e.g. `"1234567890.1.2"`). `Engines::MPS` is a backward-compat alias for `Engines::Parser`.
60
56
 
61
57
  ### Elements (`lib/mps/elements/`)
62
58
  Each element type (Task, Note, Reminder, Log, MPS) is a class that `include`s the `MPS::Element` mixin. The mixin provides `initialize(args:, refs:, body_str:)`, `display_str`, and `attr_reader :body_str`. Each class must define `SIGNATURE_STAMP` (used when generating filenames/wrapping) and `SIGNATURE_REGEX` (matched against the parsed element sign to dispatch). The engine discovers all element classes dynamically via `MPS::Elements.constants`.
63
59
 
64
60
  ### Interpolators (`lib/mps/interpolators/`)
65
- Interpolator classes (e.g. `Interpolators::Time`) are discovered the same way as elements — via `const_get` on the module's `constants`. Each defines `SIGNATURE_REGEX` and `get_str(**ref)`. They are loaded into `Engines::MPS` but the interpolation call-site is not yet wired up in the engine.
61
+ Interpolator classes (e.g. `Interpolators::Time`) are discovered the same way as elements — via `const_get` on the module's `constants`. Each defines `SIGNATURE_REGEX` and `get_str`. The parser applies them to body strings after the main parse loop via `_apply_interpolations`.
66
62
 
67
63
  ### Configuration (`lib/mps/config.rb` + `lib/mps/constants.rb`)
68
64
  `Config.init(path)` writes a default YAML config. `Config.new(**hash)` holds `storage_dir`, `mps_dir`, `log_file`, and a `Logger`. Two additional optional YAML keys are supported: `git_remote` (default `"origin"`) and `git_branch` (default `"master"`); both are exposed as `attr_reader`s and used by `git` and `autogit` commands. The CLI re-reads the config on every invocation via `load_config`; it auto-creates missing directories and the log file.
@@ -97,6 +93,6 @@ File names follow `YYYYMMDD.<epoch>.mps`; the epoch disambiguates multiple files
97
93
 
98
94
  Tests use Minitest with `fakefs` for filesystem isolation. The test helper at `test/test_helper.rb` sets up the load path and does `include MPS`, so all constants and module methods are available directly in test classes.
99
95
 
100
- `test/engine_test.rb` tests the parser directly: it uses a `parse_content(str)` helper that writes a fixture file under `FakeFS` at `/tmp/20260101.mps` and calls `Engines::MPS.parse_mps_file_to_elments_hash` on it. Covers empty files, single and multiple elements, unknown element fallback to `Struct`, nested `@mps{}`, `matched_element_class`, and `look_ahead_pos` edge cases.
96
+ `test/engine_test.rb` tests the parser directly: it uses a `parse_content(str)` helper that writes a fixture file under `FakeFS` at `/tmp/20260101.mps` and calls `Engines::Parser.parse_mps_file_to_elements_hash` on it. Covers empty files, single and multiple elements, unknown element fallback to `Struct`, nested `@mps{}`, `matched_element_class`, and `look_ahead_pos` edge cases.
101
97
 
102
98
  `test/config_test.rb` covers `Config.init`, `Config.load_conf_hash` (including `git_remote`/`git_branch` defaults), logger formatting, and `LoadError` on missing keys.
data/GETTING_STARTED.md CHANGED
@@ -93,6 +93,24 @@ mps list
93
93
 
94
94
  The nested tree is preserved — child elements appear indented under their parent `[@mps]` group. Each type gets its own color in the terminal.
95
95
 
96
+ ### Show human-readable refs
97
+
98
+ Pass `--refs` (or `-r`) to see addressable references alongside each element:
99
+
100
+ ```bash
101
+ mps list --refs
102
+ ```
103
+
104
+ ```
105
+ task-1 [task] (open) Review the API pull request [work]
106
+ reminder-1 [reminder] (10am) Team standup
107
+ note-1 [note] The auth token expiry edge case
108
+ mps-1 [@mps]
109
+ mps-1.1 [task] (open) Nested backend task [backend]
110
+ ```
111
+
112
+ These refs (`task-1`, `note-1`, `mps-1.1`, …) can be passed to `update` and `done`.
113
+
96
114
  ### Filter by type
97
115
 
98
116
  Only want tasks?
@@ -121,8 +139,6 @@ mps list --status open
121
139
  mps list -s done
122
140
  ```
123
141
 
124
- Status filtering only applies to tasks — notes, logs, and reminders are excluded when you use `--status`.
125
-
126
142
  ### Look at a different day
127
143
 
128
144
  ```bash
@@ -197,10 +213,59 @@ The 3h30m duration is computed automatically and shown in `list`, `stats`, and `
197
213
  mps append reminder "Push the hotfix before EOD" --at "5pm"
198
214
  ```
199
215
 
216
+ ### Type aliases
217
+
218
+ If you configure aliases in `~/.mps_config.yaml`:
219
+
220
+ ```yaml
221
+ aliases:
222
+ t: task
223
+ n: note
224
+ r: reminder
225
+ l: log
226
+ ```
227
+
228
+ You can use the short form:
229
+
230
+ ```bash
231
+ mps append t "Quick task via alias"
232
+ ```
233
+
200
234
  All types supported by `append`: `task`, `note`, `log`, `reminder`.
201
235
 
202
236
  ---
203
237
 
238
+ ## Updating elements in-place
239
+
240
+ Found a task you finished? Mark it done without opening Vim:
241
+
242
+ ```bash
243
+ mps done task-1
244
+ ```
245
+
246
+ Need to update an arbitrary attribute?
247
+
248
+ ```bash
249
+ mps update task-1 --status done
250
+ mps update reminder-1 --at "6pm"
251
+ mps update log-1 --end-time 13:00
252
+ ```
253
+
254
+ Refs can be the human-readable form (`task-1`, `note-2`, `mps-1.1`) shown by `mps list --refs`, or the epoch-based form (`20260428.1`) which encodes the date and position.
255
+
256
+ ```bash
257
+ # Epoch ref — works for any date without needing --date
258
+ mps done 20260421.2
259
+
260
+ # Human ref — defaults to today
261
+ mps done task-1
262
+
263
+ # Human ref for another date
264
+ mps done task-1 --date yesterday
265
+ ```
266
+
267
+ ---
268
+
204
269
  ## Search across all your files
205
270
 
206
271
  End of quarter. You vaguely remember logging something about "auth" in March. You don't know which file.
@@ -244,6 +309,28 @@ All filters compose: `mps search "auth" --type task --tag backend --since "last
244
309
 
245
310
  ---
246
311
 
312
+ ## Tag usage summary
313
+
314
+ See which tags you're using most:
315
+
316
+ ```bash
317
+ mps tags
318
+ ```
319
+
320
+ ```
321
+ work (7)
322
+ backend (4)
323
+ personal (2)
324
+ ```
325
+
326
+ Filter by type or date range:
327
+
328
+ ```bash
329
+ mps tags --type task --since monday
330
+ ```
331
+
332
+ ---
333
+
247
334
  ## Your productivity at a glance
248
335
 
249
336
  Friday afternoon. How did your week go?
@@ -300,12 +387,6 @@ CSV format:
300
387
  mps export --format csv
301
388
  ```
302
389
 
303
- ```
304
- date,ref,type,tags,body,status,at,start,end
305
- 2026-04-28,1745000000.1,task,work,Review the API pull request,open,,,
306
- 2026-04-28,1745000000.2,reminder,,Team standup,,10am,,
307
- ```
308
-
309
390
  All the same filters apply:
310
391
 
311
392
  ```bash
@@ -357,6 +438,60 @@ git_branch: main
357
438
 
358
439
  ---
359
440
 
441
+ ## Configuration management
442
+
443
+ View your current configuration:
444
+
445
+ ```bash
446
+ mps config show
447
+ ```
448
+
449
+ ```
450
+ MPS configuration
451
+ config file : /home/you/.mps_config.yaml
452
+ mps_dir : /home/you/.mps
453
+ storage_dir : /home/you/.mps/mps
454
+ log_file : /home/you/.mps/mps.log
455
+ git_remote : origin
456
+ git_branch : main
457
+ default_cmd : open
458
+ ```
459
+
460
+ Open your config file in `$EDITOR`:
461
+
462
+ ```bash
463
+ mps config edit
464
+ ```
465
+
466
+ ### Configuring the default command
467
+
468
+ By default, bare `mps` opens today's file in Vim. To make `mps` list instead:
469
+
470
+ ```yaml
471
+ default_command: list
472
+ ```
473
+
474
+ ### Type aliases
475
+
476
+ Create short names for element types in your config:
477
+
478
+ ```yaml
479
+ aliases:
480
+ t: task
481
+ n: note
482
+ r: reminder
483
+ l: log
484
+ ```
485
+
486
+ Then use them with `append`:
487
+
488
+ ```bash
489
+ mps append t "Quick capture" # same as: mps append task
490
+ mps append n "Remember this" # same as: mps append note
491
+ ```
492
+
493
+ ---
494
+
360
495
  ## Run any shell command in your storage directory
361
496
 
362
497
  Need to see what files exist, or grep across everything raw?
@@ -388,9 +523,13 @@ mps version
388
523
  | `mps` / `mps open [date]` | Open a date's file in Vim (default: today) |
389
524
  | `mps list [date]` | Print elements in tree order (default: today) |
390
525
  | `mps append TYPE BODY` | Add one element to today's file without Vim |
526
+ | `mps update REFPATH [--attr val]` | Update an element's attributes in-place |
527
+ | `mps done REFPATH` | Mark a task as done (shorthand for update --status done) |
391
528
  | `mps search QUERY` | Full-text search across all files |
529
+ | `mps tags [date]` | Show tag usage counts |
392
530
  | `mps stats [date]` | Element counts and log durations for a date |
393
531
  | `mps export [date]` | Export elements as JSON or CSV to stdout |
532
+ | `mps config [show\|edit]` | View or edit configuration |
394
533
  | `mps autogit` | Stage, commit, pull, push in one shot |
395
534
  | `mps git ARGS` | Run any git command inside storage dir |
396
535
  | `mps cmd ARGS` | Run any shell command inside storage dir |
@@ -404,6 +543,7 @@ mps version
404
543
  | `--tag TAG` | `-g` | Filter by tag name |
405
544
  | `--status STATUS` | `-s` | Filter tasks by: `open`, `done` |
406
545
  | `--since DATESIGN` | `-S` | Show elements from SINCE up to DATESIGN |
546
+ | `--refs` | `-r` | Show human-readable ref column |
407
547
 
408
548
  ### append options
409
549
 
@@ -415,6 +555,18 @@ mps version
415
555
  | `--start-time HH:MM` | Start time for logs |
416
556
  | `--end-time HH:MM` | End time for logs |
417
557
 
558
+ ### update options
559
+
560
+ All schema-declared attributes are available as flags. Current built-in:
561
+
562
+ | Option | Description |
563
+ |--------|-------------|
564
+ | `--status STATUS` | New status value |
565
+ | `--at TIME` | New time for reminders |
566
+ | `--start-time HH:MM` | New start time for logs |
567
+ | `--end-time HH:MM` | New end time for logs |
568
+ | `--date DATESIGN` | Date context for human refs (default: today) |
569
+
418
570
  ### search options
419
571
 
420
572
  | Option | Short | Description |
@@ -437,6 +589,14 @@ mps version
437
589
  | `--type TYPE` | `-t` | Filter by element type |
438
590
  | `--since DATESIGN` | `-S` | Export from SINCE up to DATESIGN |
439
591
 
592
+ ### tags options
593
+
594
+ | Option | Short | Description |
595
+ |--------|-------|-------------|
596
+ | `--type TYPE` | `-t` | Filter by element type |
597
+ | `--since DATESIGN` | `-S` | Show tags from SINCE up to DATESIGN |
598
+ | `--status STATUS` | `-s` | Filter tasks by status before counting |
599
+
440
600
  ### Date formats accepted everywhere
441
601
 
442
602
  | Input | Meaning |
@@ -445,3 +605,11 @@ mps version
445
605
  | `monday`, `last friday` | Day of week |
446
606
  | `2 days ago`, `last week` | Natural language |
447
607
  | `20260421` | Explicit YYYYMMDD |
608
+
609
+ ### Ref formats accepted by update and done
610
+
611
+ | Format | Example | Scope |
612
+ |--------|---------|-------|
613
+ | Human top-level | `task-1` | Today's file (or `--date`) |
614
+ | Human nested | `mps-1.1` | Today's file (or `--date`) |
615
+ | Epoch-based | `20260428.2` | Any date (encoded in prefix) |