mps 0.5.0
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 +7 -0
- data/.github/workflows/main.yml +29 -0
- data/.gitignore +13 -0
- data/CLAUDE.md +102 -0
- data/GETTING_STARTED.md +447 -0
- data/Gemfile +6 -0
- data/IMPROVEMENTS.md +90 -0
- data/README.md +183 -0
- data/Rakefile +18 -0
- data/bin/console +38 -0
- data/bin/setup +8 -0
- data/exe/mps +5 -0
- data/lib/cli/mps.rb +442 -0
- data/lib/mps/config.rb +64 -0
- data/lib/mps/constants.rb +68 -0
- data/lib/mps/elements/element.rb +49 -0
- data/lib/mps/elements/elements.rb +6 -0
- data/lib/mps/elements/log.rb +32 -0
- data/lib/mps/elements/mps.rb +15 -0
- data/lib/mps/elements/note.rb +15 -0
- data/lib/mps/elements/reminder.rb +16 -0
- data/lib/mps/elements/task.rb +19 -0
- data/lib/mps/engines/engines.rb +1 -0
- data/lib/mps/engines/mps.rb +108 -0
- data/lib/mps/interpolators/interpolators.rb +1 -0
- data/lib/mps/interpolators/time.rb +11 -0
- data/lib/mps/mps.rb +32 -0
- data/lib/mps/store.rb +75 -0
- data/lib/mps/version.rb +5 -0
- data/lib/mps.rb +21 -0
- data/mps.gemspec +49 -0
- data/rust_rollout_spec.md +935 -0
- metadata +258 -0
data/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# MPS (MonoPsyches)
|
|
2
|
+
|
|
3
|
+
MPS is a plain-text personal productivity CLI. Tasks, notes, reminders, and logs live in date-stamped `.mps` files stored in `~/.mps/mps/`. Files are opened in Vim; a git integration handles sync. Everything is plain text — no app, no database, no account.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Date-based files** — one or more `.mps` files per day (`20260428.1745000000.mps`)
|
|
8
|
+
- **Structured elements** — tasks (with status), notes, reminders (with time), logs (with duration)
|
|
9
|
+
- **Nested elements** — `@mps{ @task{ ... } }` for grouping; `list` renders the tree
|
|
10
|
+
- **Typed argument parsing** — tags and named attrs (`status: done`, `at: 5pm`, `start: 09:00, end: 12:30`)
|
|
11
|
+
- **Full command set** — `list`, `append`, `search`, `stats`, `export`, `open`, `git`, `autogit`, `cmd`
|
|
12
|
+
- **Date ranges** — every listing/search/stats/export command accepts `--since` for multi-day views
|
|
13
|
+
- **Git integration** — configurable remote and branch; `autogit` stages, commits, pulls, and pushes in one shot
|
|
14
|
+
- **Plain text storage** — grep it, pipe it, open it in any editor
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
gem install mps
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or add to your Gemfile:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
gem 'mps'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
mps # open today's file in Vim
|
|
32
|
+
mps list # print today's elements (nested tree)
|
|
33
|
+
mps append task "Fix the token bug" --tags backend --status open
|
|
34
|
+
mps search "token" --type task --since "last week"
|
|
35
|
+
mps stats --since monday
|
|
36
|
+
mps export --format csv --since "2026-04-01" > april.csv
|
|
37
|
+
mps autogit # stage + commit + pull + push
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
See [GETTING_STARTED.md](GETTING_STARTED.md) for a full walkthrough with examples.
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
| Command | Description |
|
|
45
|
+
|---------|-------------|
|
|
46
|
+
| `mps [open] [date]` | Open a date's file in Vim (default: today) |
|
|
47
|
+
| `mps list [date]` | Print elements as an indented tree |
|
|
48
|
+
| `mps append TYPE BODY` | Append one element to today's file without Vim |
|
|
49
|
+
| `mps search QUERY` | Full-text search across all `.mps` files |
|
|
50
|
+
| `mps stats [date]` | Element counts and log durations |
|
|
51
|
+
| `mps export [date]` | JSON or CSV to stdout |
|
|
52
|
+
| `mps autogit` | Stage, commit, pull, push |
|
|
53
|
+
| `mps git ARGS` | Any git command inside storage dir |
|
|
54
|
+
| `mps cmd ARGS` | Any shell command inside storage dir |
|
|
55
|
+
| `mps version` | Print version |
|
|
56
|
+
|
|
57
|
+
## File format
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
@task[work, release]{
|
|
61
|
+
Ship the API refactor
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@note{
|
|
65
|
+
The auth token expiry edge case needs a second look
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@reminder[at: 10am]{
|
|
69
|
+
Team standup
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@log[start: 09:00, end: 12:30]{
|
|
73
|
+
Debugging the auth flow
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@mps{
|
|
77
|
+
@task[backend]{
|
|
78
|
+
Nested task inside a sub-block
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Brackets are optional — `@task{ body }` is valid. Elements nest freely.
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
On first run MPS writes `~/.mps_config.yaml`:
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
mps_dir: ~/.mps
|
|
91
|
+
storage_dir: ~/.mps/mps
|
|
92
|
+
log_file: ~/.mps/mps.log
|
|
93
|
+
git_remote: origin
|
|
94
|
+
git_branch: main
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Architecture
|
|
98
|
+
|
|
99
|
+
MPS follows a layered architecture: parser → element types → store → CLI.
|
|
100
|
+
|
|
101
|
+
### Load order
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
mps/version → mps/mps (defines ir()) →
|
|
105
|
+
mps/constants → mps/config → mps/interpolators →
|
|
106
|
+
mps/elements → mps/engines → mps/store → cli/mps
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Parser (`lib/mps/engines/mps.rb`)
|
|
110
|
+
|
|
111
|
+
A single-pass, position-based stack parser. Each iteration finds the nearest `@element[args]{` or `}` from the current position; whichever comes first wins. A stack frame carries the element sign, args, body start offset, child counter, and ref path. Closed frames become element instances, keyed by dotted ref paths (`epoch.1.2` = second child of first top-level element).
|
|
112
|
+
|
|
113
|
+
### Elements (`lib/mps/elements/`)
|
|
114
|
+
|
|
115
|
+
Each type includes the `Element` mixin which provides `split_args`, `parsed_args`, `raw_args`, and `tags`. Type-specific `parse_args` class methods handle named attributes: tasks have `status`, logs have `start`/`end` (from which `duration_minutes` and `duration_str` are derived), reminders have `at`.
|
|
116
|
+
|
|
117
|
+
### Store (`lib/mps/store.rb`)
|
|
118
|
+
|
|
119
|
+
A library class that owns all filesystem work — finding files by date, creating new paths, parsing, appending, searching. The CLI delegates entirely to Store; there are no direct file operations in `lib/cli/mps.rb`.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Before and after: what Claude changed
|
|
124
|
+
|
|
125
|
+
The pre-Claude baseline (commit `66ac095`) had only five commands (`open`, `git`, `autogit`, `cmd`, `version`) and a fragile engine. Here is what changed and why.
|
|
126
|
+
|
|
127
|
+
### Parser
|
|
128
|
+
|
|
129
|
+
| Before | After |
|
|
130
|
+
|--------|-------|
|
|
131
|
+
| Flip-flop `at_first` boolean; double `scan_until` per loop | Position-based stack: `Regexp#match(str, pos)` picks nearer of open/close each iteration |
|
|
132
|
+
| `AT_REGEXP` required brackets — `@task{ }` was invisible | Brackets made optional: `(?:\[(?<args>[^\]]*)\])?` |
|
|
133
|
+
| Partial sign match `/task/` could match `@taskboard` | Exact match `/\Atask\z/` |
|
|
134
|
+
| `eval("::MPS::Elements::#{k}")` for class lookup | `Elements.const_get(k)` — no code evaluation |
|
|
135
|
+
| `instance_eval("attr_accessor :disp_str")` on Unknown | `Unknown = Struct.new(:ecn, :args, :refs, :body_str)` |
|
|
136
|
+
| `rescue Exception` swallowed signals | `rescue StandardError` throughout |
|
|
137
|
+
|
|
138
|
+
### Element types
|
|
139
|
+
|
|
140
|
+
| Before | After |
|
|
141
|
+
|--------|-------|
|
|
142
|
+
| No argument parsing — args string was opaque | `split_args` parses `"work, status: done"` → tags + attrs hash |
|
|
143
|
+
| No status, duration, or time accessors | `done?`, `open?`, `duration_str`, `duration_minutes`, `at` |
|
|
144
|
+
| `# frozen string_literal: true` (space typo, magic comment inactive) | `# frozen_string_literal: true` |
|
|
145
|
+
|
|
146
|
+
### Store layer
|
|
147
|
+
|
|
148
|
+
Before: CLI methods did their own `Dir.glob`, `File.read`, and `File.write`. No shared abstraction.
|
|
149
|
+
|
|
150
|
+
After: `MPS::Store` owns all file operations. One place to fix bugs; CLI is thin orchestration only.
|
|
151
|
+
|
|
152
|
+
A specific fix during Store development: `Dir.glob().grep(MPS_FILE_NAME_REGEXP)` matched against full paths (e.g. `/home/you/.mps/mps/20260428.mps`) while the regexp was anchored to basenames. Changed to `.select { |f| File.basename(f) =~ regexp }`.
|
|
153
|
+
|
|
154
|
+
### CLI
|
|
155
|
+
|
|
156
|
+
| Before | After |
|
|
157
|
+
|--------|-------|
|
|
158
|
+
| `open`, `git`, `autogit`, `cmd`, `version` | + `list`, `append`, `search`, `stats`, `export` |
|
|
159
|
+
| `git pull orign master` typo | `git pull #{git_remote} #{git_branch}` (configurable) |
|
|
160
|
+
| `git_remote`/`git_branch` hardcoded | Read from config YAML |
|
|
161
|
+
| No output formatting | Colorized type badges, status/duration/time extras, nested tree |
|
|
162
|
+
|
|
163
|
+
## Requirements
|
|
164
|
+
|
|
165
|
+
- Ruby >= 2.3.0
|
|
166
|
+
- Vim (for `open` / default command)
|
|
167
|
+
- Git (for `git` / `autogit`)
|
|
168
|
+
|
|
169
|
+
## Dependencies
|
|
170
|
+
|
|
171
|
+
- `thor` — CLI framework
|
|
172
|
+
- `tty-editor` — editor integration
|
|
173
|
+
- `chronic` — natural-language date parsing
|
|
174
|
+
- `cli-ui` — terminal UI (multi-file prompt)
|
|
175
|
+
- `strscan` — string scanning in parser
|
|
176
|
+
|
|
177
|
+
## Contributing
|
|
178
|
+
|
|
179
|
+
Bug reports and pull requests are welcome at https://github.com/mash-97/mps.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT License.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:default_test) do |t|
|
|
7
|
+
t.libs << "test"
|
|
8
|
+
t.libs << "lib"
|
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
task default: :test
|
|
13
|
+
namespace :test do
|
|
14
|
+
desc "Run test task with specified groups of gems bundle"
|
|
15
|
+
task :with_groups do
|
|
16
|
+
Rake::Task[:default_test].invoke
|
|
17
|
+
end
|
|
18
|
+
end
|
data/bin/console
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
ENV["MPS_DEBUG"] = "true"
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "mps"
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require "irb"
|
|
15
|
+
require 'pp'
|
|
16
|
+
|
|
17
|
+
include MPS
|
|
18
|
+
include Constants
|
|
19
|
+
|
|
20
|
+
# write a dummy mps file
|
|
21
|
+
@txt_fp = Pathname.new("E:/mps/test/tests/sig-tests/20240221.mps")
|
|
22
|
+
txt = File.read(@txt_fp.to_path)
|
|
23
|
+
|
|
24
|
+
require 'fakefs'
|
|
25
|
+
FakeFS.activate!
|
|
26
|
+
|
|
27
|
+
FileUtils.mkdir_p(Constants::MPS_DIR)
|
|
28
|
+
FileUtils.mkdir_p(@txt_fp.dirname.to_path)
|
|
29
|
+
File.open(@txt_fp.to_path, "w+") do |f|
|
|
30
|
+
f.write(txt)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@conf = Config.init()
|
|
34
|
+
@me = Engines::MPS.new(@conf)
|
|
35
|
+
|
|
36
|
+
IRB.start(__FILE__)
|
|
37
|
+
|
|
38
|
+
FakeFS.deactivate!
|
data/bin/setup
ADDED