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 +4 -4
- data/.github/workflows/main.yml +23 -18
- data/.gitignore +4 -3
- data/ARCHITECTURE.md +298 -0
- data/CLAUDE.md +6 -10
- data/GETTING_STARTED.md +176 -8
- data/Gemfile.lock +46 -0
- data/Rakefile +6 -5
- data/lib/cli/mps.rb +88 -333
- data/lib/mps/cli/commands/append.rb +32 -0
- data/lib/mps/cli/commands/config_cmd.rb +28 -0
- data/lib/mps/cli/commands/export.rb +51 -0
- data/lib/mps/cli/commands/git.rb +44 -0
- data/lib/mps/cli/commands/list.rb +56 -0
- data/lib/mps/cli/commands/open.rb +25 -0
- data/lib/mps/cli/commands/search.rb +34 -0
- data/lib/mps/cli/commands/stats.rb +77 -0
- data/lib/mps/cli/commands/tags.rb +57 -0
- data/lib/mps/cli/commands/update.rb +57 -0
- data/lib/mps/cli/commands.rb +5 -0
- data/lib/mps/config.rb +8 -3
- data/lib/mps/constants.rb +7 -5
- data/lib/mps/elements/element.rb +41 -11
- data/lib/mps/elements/elements.rb +8 -6
- data/lib/mps/elements/log.rb +2 -4
- data/lib/mps/elements/mps.rb +1 -4
- data/lib/mps/elements/note.rb +1 -4
- data/lib/mps/elements/reminder.rb +1 -4
- data/lib/mps/elements/task.rb +1 -4
- data/lib/mps/engines/engines.rb +3 -1
- data/lib/mps/engines/mps.rb +20 -10
- data/lib/mps/interpolators/interpolators.rb +3 -1
- data/lib/mps/mps.rb +11 -19
- data/lib/mps/presenter.rb +128 -0
- data/lib/mps/query.rb +71 -0
- data/lib/mps/ref_resolver.rb +77 -0
- data/lib/mps/store.rb +95 -6
- data/lib/mps/version.rb +1 -1
- data/lib/mps.rb +11 -9
- data/mps.gemspec +15 -24
- data/prompt.txt +64 -0
- metadata +28 -90
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 96b0eac30435621ea7ca9d0575d3a3285a074cb2707d453075ced7e1535ca288
|
|
4
|
+
data.tar.gz: 647d49ad9cffd798858318dd712d4653df718e37d05a8f52803be367e423c17c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e1204e48c493e45f0dc226b1a7a4de0751b4821774ab5984c54f56cf53e91ac99cb09a17fb5cf777de0cf665ebefd69289913de935baf5655d3afd76f80da9bf
|
|
7
|
+
data.tar.gz: bbb938d0b337b652c07cca9983949a830c13fd9180ab037df87bd75feda68a6e11dfbc091689fa96546edc1e6f5f02e770b98a6d896c32b90de2b95b0fcaae00
|
data/.github/workflows/main.yml
CHANGED
|
@@ -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
|
-
|
|
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@
|
|
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:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
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 (
|
|
55
|
-
mps/constants → mps/config → mps/interpolators → mps/elements → mps/engines →
|
|
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::
|
|
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
|
|
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::
|
|
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) |
|