memgineering 0.7.4 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,232 @@ language the reader wants. The bilingual rule the monorepo applies to
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.8.0] — 2026-08-23
15
+
16
+ ### Added
17
+
18
+ - **`memgineering unlink --brain` stops reading the hosted brain.** There was no
19
+ way to clear that pointer — `use --brain ""` is refused and `logout` leaves it
20
+ in place on purpose — so a machine-wide setting had no way back to the state it
21
+ started in. Nothing on the server is touched, and it says so.
22
+ - **`memgineering brains` names whose account it is and which server**, so
23
+ "what is this computer connected to" takes one command instead of three.
24
+ - **`memgineering brains` lists the hosted brains on your account**, with a note
25
+ count and a creation date, and marks the one this machine reads. There was no
26
+ listing at all: the only way to learn the names was to pass one you knew was
27
+ wrong and read the refusal — and the sub-skill documented that as the
28
+ technique, so the docs taught guessing. `--json` carries `current` at the top
29
+ level. An absent count is `null` rather than `0`, because zero notes is an
30
+ empty brain and no count is a server that does not send one.
31
+
32
+ - **`memgineering use --brain <name>` points this machine at a hosted brain.**
33
+ `push --brain` and `pull --brain` already took one by name and nothing could
34
+ point at one, so switching which brain you read meant pushing a folder into it
35
+ — an upload as the price of changing a setting. Takes a name or an id, says
36
+ which brain it stopped reading, and `--json` carries `changed` so "already
37
+ there" and "switched" are told apart. It does NOT create: pointing at a brain
38
+ that does not exist would leave every recall answering from an empty one, which
39
+ from the outside looks exactly like having lost the notes, so an unknown name is
40
+ refused with the account's list. A folder path, `--default` and `--here` are
41
+ refused alongside it rather than ignored — they bind a folder, and this is one
42
+ setting for the whole machine.
43
+
44
+ ### Fixed
45
+
46
+ - **A note's own words can no longer start a line of memgineering's output.**
47
+ A title, a summary, a brain name — anything the tool prints inside a line —
48
+ is now folded onto one line first. It was not, and a value carrying a line
49
+ break ended the tool's sentence and began one of its own. In `recall` that
50
+ looked like an extra result card; whatever it said appeared to come from
51
+ memgineering rather than from the note. The tools reading this output are
52
+ agents, so that distinction is the whole point. Roughly 120 places across
53
+ every command, plus a test that fails if a new one is added. `--json` is
54
+ unchanged and still carries the full value, line breaks included.
55
+ - **A hosted brain's name is stored as one line.** The server accepted a line
56
+ break in a name, so the same forged line reached every client, not just this
57
+ one. Names are folded rather than refused — a slash is still refused, because
58
+ a synced brain becomes a folder and a slash would make it two.
59
+
60
+ - **`logout` now says your notes are untouched**, and that this machine keeps
61
+ which brain it was reading so a later `login` resumes. It said only that a
62
+ token was revoked, which is not the sentence somebody signing out is waiting
63
+ for.
64
+ - **An unknown command is answered with commands.** `memgineering disconnect`
65
+ used to get "memgineering takes `--version` `--json`" — true, useless, and it
66
+ reads as though the tool has two features.
67
+ - **`logout`'s description says it signs out of your account**, not "of a hosted
68
+ brain". That wording made three disconnect-shaped verbs indistinguishable.
69
+ - **Unlinking the last brain no longer leaves a default naming it.**
70
+ - **`MEMGINEERING_AGENT_HOME` is listed in `--help`.** It is what keeps `setup`
71
+ off a real machine, and it was the only one of five missing.
72
+
73
+ - **A signed-out machine, and one whose token was rejected, are both told to sign
74
+ in rather than to make a new brain.** The refusal that names the account's
75
+ brains treated any failure the same way and fell back to "link a folder · or
76
+ start a new brain" — including for a 401, where the server has just confirmed
77
+ the account exists and the notes are on it. Only a server that cannot be
78
+ reached falls back now.
79
+ - **That refusal cannot stall a command any more.** It makes one request with no
80
+ retry. It had inherited the ordinary 429 ladder — three retries sleeping the
81
+ server's `retry-after`, capped at a minute each — which on a rate-limited
82
+ server turned an instant offline answer into minutes and, past an agent's
83
+ command timeout, into a killed command rather than a refusal.
84
+ - **`use --brain` on a machine with no account no longer points at `--local`,**
85
+ which it does not accept. It names `memgineering login --emit-only` instead.
86
+ - **`use --brain <name>` refuses a flag written where the name goes.**
87
+ `use --brain --default` bound `--default` as the value, so the
88
+ cannot-combine check never ran, the command asked the server for a brain by
89
+ that name, and the refusal recommended `push --brain <name>` — which creates.
90
+ Following it produced a hosted brain literally called `--default`.
91
+ - **`use --brain` moves the pointer when only the SERVER differs**, and prefers
92
+ an id over a name that merely looks like one. Both were silent wrong answers:
93
+ the first left the machine on the old host reporting "Already using", the
94
+ second resolved a different brain through the handle this command advertises
95
+ as unambiguous.
96
+ - **`use --brain` says when the brain it just pointed at is empty.** The count
97
+ was in the response already. Pointing at an existing-but-empty brain produced
98
+ the same experience as pointing at one that does not exist — which is the thing
99
+ the no-create refusal exists to prevent.
100
+ - **`push` no longer tells you there is no way to point back without uploading.**
101
+ 0.8.0 added exactly that verb; the sentence now names it.
102
+ - **The "not set up yet" notice stays quiet only for a pointer this machine can
103
+ actually read.** It checked that a hosted brain was named, not that it was on
104
+ the server currently configured — so a pointer left behind by a different host
105
+ silenced the notice on a machine that could answer nothing.
106
+ - **"No brain is linked" no longer sends somebody whose notes are on their
107
+ account to create an empty one.** Signed in, holding hosted brains, and pointed
108
+ at none of them, the refusal offered `link` and `init` — and an agent that
109
+ follows `init` makes a third empty notebook and reports that the notes are
110
+ still gone. It now names the account's brains and `use --brain`, and says in as
111
+ many words not to run `init` for this. Found by fresh-context agents: three of
112
+ ten hit it, one on the ask "i think my notes are gone", which is exactly when
113
+ it fired. The offline refusal still stands unchanged when the account cannot be
114
+ reached, holds nothing, or `--local` was asked for.
115
+ - **The "not set up yet" notice stops firing on a machine that is working.**
116
+ It reads `system.installed`, which records whether `setup` ran and says nothing
117
+ about whether this machine can read and write — so it printed above successful
118
+ output on every command. Worse, it tells the reading agent to run
119
+ `setup --agent`, which re-points the brain: the one action it demands would
120
+ undo a `use --brain` the user had just asked for. Ten of ten fresh agents saw
121
+ it over working output; four named running it as the trap they nearly walked
122
+ into. It now stands down whenever a hosted brain is pointed at.
123
+
124
+ ### Fixed
125
+
126
+ - **Your standing rules now reach an agent before it edits a file on a machine
127
+ that reads a hosted brain.** `--rule` exists so a decision arrives before the
128
+ work rather than after somebody asks, and the hook that delivers it read only a
129
+ brain in a folder — so on a signed-in machine it delivered nothing. Measured on
130
+ a real account: `rules` answered seven and the hook answered none.
131
+ - **A rule can no longer put its own line into an agent's instructions.** Rule
132
+ text lands there verbatim, and a newline in one produced a second line with no
133
+ bullet in front of it — from inside that context, indistinguishable from
134
+ something the tool wrote.
135
+ - **Unlinking a folder no longer stops this machine reading its hosted brain.**
136
+ The config write rebuilt one object and dropped every key it did not name, and
137
+ the account pointer was one of them — so `unlink <folder>` sent every later
138
+ recall somewhere else with nothing on screen to explain it.
139
+ - **`unlink` no longer says "no brain is linked yet" while `use` names a hosted
140
+ brain.** It asked only the resolver that knows about folders, so the two
141
+ commands answered a single state two opposite ways — and its advice was to
142
+ create a brain, which for somebody whose notes are already in one ends in a
143
+ third empty brain.
144
+ - **`setup --dry-run` tells "nothing to change" apart from "no tools found".**
145
+ Both printed `Would change 0 file(s):` over an empty list. The second is what
146
+ an ordinary re-run looks like, so the common case read as the alarming one.
147
+
148
+ ### Agent guidance
149
+
150
+ `memgineering-setup` documents the new verb. Restart your agent session after
151
+ upgrading so it picks it up.
152
+
153
+ ## [0.7.5] — 2026-08-23
154
+
155
+ ### Fixed
156
+
157
+ - **The approval list now shows the claim — what the note would say — above the
158
+ reason it is being changed.** `proposals approve` has no confirmation step: it
159
+ applies the change and shows the diff afterwards, so the list is the only
160
+ place a decision is made, and it was carrying the argument for a change while
161
+ withholding the change itself. Cut to one line on screen; whole under
162
+ `--json`. Null for `retire`, `unretire` and `reclassify`, which assert no
163
+ conclusion. Showing it for a **hosted** brain needs the brain server deployed
164
+ as well; against an older server the row says the claim is unavailable rather
165
+ than rendering as though the action had none. Brains in a folder need nothing.
166
+ - **`consolidate` candidates name their notes.** A row gave an id and a path,
167
+ which say which FILE a candidate is about and nothing about what it says — so
168
+ you could not tell which notes a candidate was even about without opening both.
169
+ The row still does not carry their claims: deciding which of a pair is right
170
+ means reading them, and the row is what tells you which two to read.
171
+ Titles are cut at 72 characters with an ellipsis, so a short title and a
172
+ truncated one are never confusable. (Characters, not columns — a Korean title
173
+ is about twice as wide on screen.)
174
+ - **`reindex` reports what it left out.** It listed notes refused for their name
175
+ or their content and said nothing about ones a `.memgignore` rule or a symlink
176
+ kept out of the index, so "✓ rebuilt 3 note(s)" could not answer "where is my
177
+ note". It now prints the same four-rule block `push` prints. `--json` gains
178
+ `not_indexed`, `denied_explicitly`, `excluded_by_rule` and `skipped_symlinks`
179
+ — the count is `not_indexed` rather than push's `not_sent`, because nothing is
180
+ sent from here.
181
+ - **A title or claim cut mid-emoji no longer comes out as broken text.** The
182
+ truncation counted UTF-16 units, so a cut landing between the halves of a
183
+ surrogate pair emitted a lone surrogate. It counts characters now.
184
+ - **A rule that excludes a FOLDER now names the folder.** `push` and `reindex`
185
+ both stop walking at an excluded directory, so the notes inside are never
186
+ enumerated — and both reported a held-back count of zero and printed nothing
187
+ else, so a brain whose entire archive was excluded looked exactly like a brain
188
+ that lost nothing. The count is still an honest floor; `excluded_folders` in
189
+ `--json` and a named line on screen say what it is a floor of.
190
+ - **Note text is stripped of C1 controls and bidi format characters, on both
191
+ sides.** Only the `ESC`-introduced escape sequences were removed. A terminal in
192
+ 8-bit mode reads U+009B and U+009D as the same two introducers with no `ESC`
193
+ byte; U+0085 is a line break, which broke the one-line guarantee the new title
194
+ and claim rows depend on; and a bidi override can make a line display in an
195
+ order its bytes do not have. U+2028 and U+2029 go too — `quoteBlock` splits on
196
+ `\n` alone, so a note containing one could resume outside the quote that marks
197
+ it as the note's words rather than the tool's. Escape sequences are read by
198
+ ECMA-48's grammar now rather than run to the next plausible-looking byte, so a
199
+ stray marker costs a few characters instead of the rest of the line. Measured:
200
+ one character before pure Hangul, two before `launchctl 로 한다`, five before
201
+ `21 August`, six before `123 abc` — against the whole remainder before. U+0085,
202
+ U+2028 and U+2029 are translated to a newline rather than dropped, so a break
203
+ in a stored note stays a break instead of welding the words on either side.
204
+ - **Security: text pasted out of a terminal or a chat window can now be refused,
205
+ and a hidden character can no longer sneak a password past the check that looks
206
+ for one.** If a proposal or a `--reason` is rejected for carrying invisible
207
+ formatting, nothing was saved — retype the words as plain text and run it again.
208
+ What was wrong: the check read normalised text, and normalising could not
209
+ win: deleting the marker left its parameters between a label and its value,
210
+ and consuming the sequence deleted label characters instead — each fix opened
211
+ the other hole. The screen now refuses a proposal whose prose contains an
212
+ escape or control introducer at all, which has no such seam. The same rule now
213
+ guards `--reason` on every write verb: that text goes into the ledger, which is
214
+ permanent and travels with the brain. Note text itself is still stored rather
215
+ than refused — blocking a save is the worse trade on the main write path, where
216
+ the commonest source of a stray control byte is a mis-decoded file — and is
217
+ protected instead by the credential detector, which now reads the text with
218
+ control introducers removed as well as with them left in, so a label split by a
219
+ hidden byte is still seen as a label. That widening cuts both ways and the
220
+ second half is worth knowing: a note ALREADY on disk whose text hides a label
221
+ this way is now refused on READ, where 0.7.4 read it fine. Both codes involved
222
+ are weak ones, so `.memgallow` overrules them. The two copies of the normaliser are
223
+ hardened and a parity test compares them as source, because they had already
224
+ drifted once.
225
+
226
+ ### Changed
227
+
228
+ - `reindex` lists markdown only where it used to list every denied path, on
229
+ screen and in `denied_by_name` alike, matching `push`. A denied `.env` was
230
+ never a note anybody expected in a note index.
231
+ - `push`'s nothing-to-upload error no longer tells someone to check their path
232
+ when the path was right and their own folder rule emptied the brain.
233
+
234
+ ### Agent guidance
235
+
236
+ `memgineering-writing` and `memgineering-setup` describe the claim row and what
237
+ `reindex` now reports. Restart your agent session after upgrading so it picks
238
+ them up.
239
+
14
240
  ## [0.7.4] — 2026-08-19
15
241
 
16
242
  ### Changed
@@ -2,7 +2,7 @@
2
2
  name: memgineering
3
3
  description: Use whenever the user refers to something they told you before, asks what was decided, tells you something worth keeping, or settles something that should hold next time. The memory lives in their own folder and outlives this session; check it before answering from guesswork, and write to it when you learn something durable.
4
4
  type: skill
5
- version: 0.7.4
5
+ version: 0.8.0
6
6
  ---
7
7
 
8
8
  # memgineering
@@ -182,6 +182,31 @@ memgineering use --default ~/brains/work
182
182
  Inside a repository `use` writes a relative path, so it can be committed and
183
183
  resolves for a teammate who links the same brain.
184
184
 
185
+ **An account can hold several hosted brains. `brains` lists them and
186
+ `use --brain <name>` switches.**
187
+
188
+ ```
189
+ memgineering brains # what the account holds, and which one this machine reads
190
+ memgineering use --brain work # name or id; nothing is uploaded
191
+ ```
192
+
193
+ **Run `brains` before you switch, not after a guess.** It carries a note count
194
+ and a creation date, which is what tells two brains apart when their names do
195
+ not — and it marks the one this machine currently reads. `--json` puts that id
196
+ at the top level as `current`, so you do not have to scan the array to answer
197
+ the question you most often have. An absent `note_count` is `null` and not `0`:
198
+ zero notes is an empty brain, and no count is a server too old to send one.
199
+
200
+ It moves the one hosted brain this machine reads and touches no notes — `push`
201
+ and `pull` are what move those. It does not create: an unknown name is refused
202
+ with the account's list, because pointing at a brain that does not exist would
203
+ have every recall answer from an empty one, and from the outside that looks like
204
+ the notes are gone. Before this, `push --brain <name>` was the only thing that
205
+ could move the pointer, so switching meant uploading a folder first.
206
+
207
+ It refuses a folder path, `--default` or `--here` in the same call. Those bind a
208
+ folder; this is one setting for the whole machine.
209
+
185
210
  **Do not give up on a write because the choice is ambiguous.** A refused
186
211
  `remember` means the thing they asked you to keep was not kept — ask which
187
212
  brain, or pass `--vault` for the write and settle the default afterwards.
@@ -282,7 +307,19 @@ and needs no model anywhere; that is a decision, not a gap.
282
307
  by hand in the ordinary case — writes keep the index in step on their own.
283
308
  `memgineering reindex` reaches either kind of brain: on a hosted one it backfills
284
309
  what is missing and clears stored rows for notes the brain has since been told
285
- not to read, reporting both; `--local` forces the folder brain. `--rebuild`
310
+ not to read, reporting both; `--local` forces the folder brain. On a brain in a
311
+ FOLDER it also names what it left out — notes a deny name, a `.memgignore` rule
312
+ or a symlink kept from the index, and any folder a rule excluded whole — so "3
313
+ notes rebuilt" is never the whole answer when a fourth was dropped. A hosted
314
+ reindex cannot say that: the rules are applied on the machine that pushed, so
315
+ the server only ever saw what arrived.
316
+
317
+ Under `--json` that is `not_indexed` with `denied_by_name`, `denied_explicitly`,
318
+ `excluded_by_rule`, `skipped_symlinks` and `excluded_folders`. **Read the count
319
+ together with `excluded_folders`**: a rule that names a folder stops the walk at
320
+ the folder, so nothing inside it is ever counted — `not_indexed: 0` beside a
321
+ non-empty `excluded_folders` means the count is a floor, not the answer. `push`
322
+ reports the same set with its total under `not_sent`. `--rebuild`
286
323
  exists for one situation only, where an embedding model changed the shape of its
287
324
  answers without changing its name and the index has to be laid down again.
288
325
 
@@ -140,8 +140,11 @@ only the last of them touches a note.
140
140
  memgineering consolidate # what looks worth merging or reclassifying — reads only
141
141
  ```
142
142
 
143
- It returns pairs with the evidence that caught them and the action it would
144
- suggest, under four rules: `similar_title`; `shared_context`, one scope naming
143
+ Each candidate names its notes id, title, path so you can tell WHICH notes
144
+ it is about without opening them. It does not carry their claims, so deciding
145
+ which of a pair is right still means reading them: two notes can share a title
146
+ and hold opposite conclusions, which is exactly the case a proposal exists for. It returns them with the evidence that caught them and the action it
147
+ would suggest, under four rules: `similar_title`; `shared_context`, one scope naming
145
148
  the same thing; `contradiction`, a disagreement nobody settled; and
146
149
  `kind_promotion`, a note marked as binding but filed as something other than a
147
150
  decision.
@@ -179,6 +182,14 @@ memgineering proposals reject <id>
179
182
  memgineering proposals rebase <id>
180
183
  ```
181
184
 
185
+ **Read the list before you approve.** A row carries the claim — what the note
186
+ would say — above the reason it is being changed. Three actions assert no
187
+ conclusion and so have none: `retire`, `unretire` and `reclassify`. A row with no
188
+ claim line is one of those, not a row that lost something. There is no confirmation step:
189
+ `approve` applies the change and shows you the diff afterwards, so the list is
190
+ where the decision is actually made. The claim is cut to one line on screen and
191
+ whole under `--json`.
192
+
182
193
  `approve` is the only command in this flow that writes to a note, and it goes
183
194
  through the ledger like every other write — one `undo` away from not having
184
195
  happened. `approve` and `reject` each take `--reason`, and it is kept.