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.
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/mps ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "mps"
4
+
5
+ MPS::CLI::MPS.start(ARGV)