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 +226 -0
- package/assets/MEMGINEERING.md +1 -1
- package/assets/memgineering-setup/SKILL.md +38 -1
- package/assets/memgineering-writing/SKILL.md +13 -2
- package/dist/index.js +934 -318
- package/package.json +2 -2
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
|
package/assets/MEMGINEERING.md
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.
|
|
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.
|
|
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
|
-
|
|
144
|
-
|
|
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.
|