memgineering 0.1.0 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,98 @@ language the reader wants. The bilingual rule the monorepo applies to
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.2.1] — 2026-08-09
15
+
16
+ ### Fixed
17
+
18
+ - **The language warning no longer fires on a query that worked.** On a brain
19
+ holding notes in more than one language, `recall "배포"` returned the Korean
20
+ note it was looking for and was told, above the result, that the query could
21
+ not reach the brain. A query that reached a note you wrote reached the brain,
22
+ whatever script it was in. It now stays quiet in that case, and still speaks
23
+ when everything that came back was `01_BASE/` scaffolding — which `init`
24
+ writes in English for everybody, so matching it is not evidence the query
25
+ landed. Found by using 0.2.0 against a real brain; a single-language fixture
26
+ could not produce it.
27
+
28
+ ## [0.2.0] — 2026-08-09
29
+
30
+ The record can say why, and you can see what a memory is actually worth.
31
+
32
+ ### Added
33
+
34
+ - **`evidence <ref>`** — how much a memory has been used, and why it last
35
+ changed: how often recall surfaced it, how often anyone opened it, when it
36
+ was last read, how many times it has been revised, and the reason attached to
37
+ the most recent change. Every number comes from records that already existed;
38
+ nothing new is collected, and running it records nothing of its own — so
39
+ measuring a memory cannot change where `resurface` ranks it.
40
+ - **A standing for each memory** — `well-used`, `surfaced, never opened`, or
41
+ `never recalled` — computed from usage rather than declared. It is withheld
42
+ entirely when this machine has no recall history yet, since that log is local
43
+ state and a brain copied to a new machine starts empty.
44
+ - **`--reason` on every write verb.** `remember`, `exclude`, `unexclude` and
45
+ `undo` accept one for the first time; `revise`, `retire` and `unretire`
46
+ already did. The reason on an `undo` is usually the most valuable of the set.
47
+ - **`log` shows the reason** without needing `--verbose`.
48
+
49
+ ### Fixed
50
+
51
+ - **`revise --reason` and `retire --reason` were silently discarded.** Both
52
+ flags shipped in 0.1.0, and `retire --help` described the reason as "kept in
53
+ the record". It was not kept anywhere: only creating a brand-new note ever
54
+ read the field, so on the ordinary path — changing a note that already exists
55
+ — the text went nowhere. Reasons given to those commands are now recorded in
56
+ the operation ledger, where `log` and `evidence` can show them.
57
+
58
+ - **`recall` now names the language barrier** when a query cannot reach the
59
+ brain. Search matches text, so an English query never reaches Korean notes
60
+ however it is worded — and the old zero-result footer suggested broader
61
+ wording, the one remedy that cannot work. Four testers hit this and came
62
+ within one command of telling their user the brain held nothing on a subject
63
+ it had notes about. It appears above results too, since the failure is rarely
64
+ a clean zero: a couple of scaffolding notes match and look like an answer.
65
+ - **`use` with no argument now says which brain you are on**, and how it
66
+ resolved — by pointer, by containment, or by being the only one linked. Its
67
+ own help had always promised this; it reported only whether a pointer file
68
+ existed, and three testers went and read the config file by hand instead.
69
+ - **`open` no longer tells you to hand-edit frontmatter** when a note states no
70
+ conclusion. It points at `memgineering revise --claim`, which is the
71
+ sanctioned path — the previous hint contradicted the bolded rule against
72
+ editing brain files directly, and a hand edit is invisible to `undo`.
73
+ - **`evidence` no longer implies a well-used memory is a correct one.** A note
74
+ the repository flatly contradicted was described as backed by the record.
75
+ Usage is attendance, and where a memory contradicts another the output now
76
+ says plainly that usage cannot settle which is right.
77
+ - **`evidence` sees a contradiction declared by either note.** The `contradicts`
78
+ edge lives on whichever note a revise happened to touch, so the warning above
79
+ fired on only one of the two — and stayed silent on the other, which is the
80
+ side a reader is more likely to be trusting by mistake.
81
+ - **`evidence` divides the open rate by the number printed beside it.** It used
82
+ to divide by recalls while `surfaced` also counted resurfacings, so the two
83
+ figures and the percentage reconciled with none of each other.
84
+ - **`remember --reason` help disambiguated** — it records why you are writing
85
+ something down, not why the fact is true. One tester nearly filed the
86
+ substance of a note there, where recall would never show it.
87
+
88
+ ### Changed
89
+
90
+ - A reason that looks like it contains credential material is refused, with the
91
+ pattern named and the value never echoed. The ledger travels with the brain,
92
+ so an unscreened reason would become a permanent synced copy of a secret —
93
+ and the deny layer that screens notes does not look at the ledger. The write
94
+ itself is unaffected: rephrase, or drop the flag.
95
+ - Operation records carry a `rationale` field and `rule_version` is now 2.
96
+ Ledgers written by 0.1.x still read correctly; an older line simply reports
97
+ no reason given, which is what happened.
98
+
99
+ ### Agent guidance
100
+
101
+ The bundled hub and the `memgineering-memory` skill now cover `evidence` and
102
+ ask for `--reason` on every write, including the rule that a newer memory does
103
+ not automatically win. **Restart your agent session after upgrading** so the
104
+ new guidance is loaded.
105
+
14
106
  ## [0.1.0] — 2026-08-09
15
107
 
16
108
  First release. A brain is a folder of your own notes; every agent you use
@@ -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, or tells you something worth keeping. 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.1.0
5
+ version: 0.2.0
6
6
  ---
7
7
 
8
8
  # memgineering
@@ -47,17 +47,33 @@ memgineering open <handle> --detail full # the whole note
47
47
  `open` also takes a note's exact title or its path, so you do not need to
48
48
  recall first when you already know what you want.
49
49
 
50
+ **Check the evidence before trusting an old memory,** or when two of them
51
+ disagree:
52
+
53
+ ```
54
+ memgineering evidence <handle>
55
+ ```
56
+
57
+ How often it has been surfaced, how often anyone read it, when it last changed
58
+ and why. **Newer does not win**: a rule that has stood for eleven months has
59
+ eleven months of nobody contradicting it behind it. Use whether it was actually
60
+ reached for, not its date.
61
+
50
62
  **Remember when you learn something durable** — a decision, a constraint, a
51
63
  correction the user made, how something actually works:
52
64
 
53
65
  ```
54
- memgineering remember "deploys are manual — launchctl by hand, no CD"
66
+ memgineering remember "deploys are manual — launchctl by hand, no CD" --reason "watched it twice"
55
67
  ```
56
68
 
57
69
  No approval step, by design. It is recorded and reversible (`memgineering
58
70
  undo`), so the cost of a wrong entry is one command, not a permanent mistake.
59
71
  Do not ask permission for ordinary observations — write them.
60
72
 
73
+ **Always pass `--reason`.** Every write verb takes it and it goes in the ledger.
74
+ It is the only part of a record that still means anything to whoever reads it
75
+ six months from now, and it is refused if it looks like it carries a credential.
76
+
61
77
  **Revise when a conclusion changes:**
62
78
 
63
79
  ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: memgineering-memory
3
- description: Use when the user asks about something they told you before, refers to a past decision, wants their own notes searched, or says something worth keeping. Triggers include "what did I decide about X", "check my notes", "how do we deploy again", "remember this", "예전에 뭐라고 정했더라", "내 노트에서 찾아줘", "이거 기억해둬", "what do you know about my setup". Covers recall, open, remember, revise, undo, and resurface against the user's own brain folder.
3
+ description: Use when the user asks about something they told you before, refers to a past decision, wants their own notes searched, or says something worth keeping. Triggers include "what did I decide about X", "check my notes", "how do we deploy again", "remember this", "is this still true", "which one do I go with", "예전에 뭐라고 정했더라", "내 노트에서 찾아줘", "이거 기억해둬", "what do you know about my setup". Covers recall, open, evidence, remember, revise, undo, and resurface against the user's own brain folder.
4
4
  type: skill
5
5
  allowed-tools: Bash(memgineering:*)
6
6
  ---
@@ -74,20 +74,70 @@ Say so. Only titles, aliases and summaries are searched, so suggest broader
74
74
  words — but do not go read their folder yourself to compensate. A brain that
75
75
  answers "nothing" is giving you real information.
76
76
 
77
+ ## Deciding which one to trust
78
+
79
+ When two memories disagree, or one looks old enough to doubt, do not settle it
80
+ by date.
81
+
82
+ ```
83
+ memgineering evidence deploy-manual
84
+ ```
85
+
86
+ ```
87
+ surfaced 34× (12× in this folder, 22× elsewhere)
88
+ opened 12× (35% of the recalls that surfaced it)
89
+ last used 3 days ago
90
+ in the ledger since 2026-05-02 · changed 2×
91
+
92
+ **well-used** — it has been reached for and read. The record backs it.
93
+
94
+ why it last changed:
95
+ > CD was removed from the repo
96
+ ```
97
+
98
+ **Newer does not win.** A rule that has stood for eleven months has eleven
99
+ months of nobody contradicting it behind it; a note written last week has a
100
+ week. What separates them is whether either was ever actually used, and what
101
+ the last person to change their mind said. Both are in the output above.
102
+
103
+ Three standings, and each is a fact rather than a grade:
104
+
105
+ - **well-used** — recall returned it and somebody read it
106
+ - **surfaced, never opened** — it keeps coming back and nobody reads it. Either
107
+ it does not answer those questions, or its summary does not say what it knows
108
+ - **never recalled** — nothing has ever surfaced it. It may be perfectly true
109
+ and simply never needed, but nothing supports it either
110
+
111
+ If it says this machine has no recall history, the numbers mean nothing yet —
112
+ that log is local, so a brain synced to a new laptop starts empty. Say that
113
+ rather than reporting the memory as unused.
114
+
77
115
  ## Writing things down
78
116
 
79
117
  ```
80
- memgineering remember "deploys are manual — launchctl by hand, no CD"
118
+ memgineering remember "deploys are manual — launchctl by hand, no CD" \
119
+ --reason "watched the deploy happen by hand twice"
81
120
  ```
82
121
 
83
122
  Lands immediately as its own note and is recallable at once. There is no
84
123
  approval queue: the design trades permission-before for correction-after, and
85
124
  every write records how to reverse it.
86
125
 
126
+ **Pass `--reason` on every write.** It goes in the ledger, and it is the only
127
+ part of the record that survives being read six months later by somebody — the
128
+ user, or you in a new session — who no longer remembers the conversation. Every
129
+ write verb takes it: `remember`, `revise`, `retire`, `exclude`, `undo`. The one
130
+ on `undo` is usually the most valuable of all, because it is the moment an
131
+ earlier conclusion turned out to be wrong.
132
+
133
+ It is refused if it looks like it contains a credential. The ledger travels
134
+ with the brain, so that would be a permanent synced copy — say what changed
135
+ without the value, or drop the flag; the write itself is unaffected.
136
+
87
137
  ```
88
- memgineering undo # the last change
138
+ memgineering undo --reason "that belonged to the other project" # the last change
89
139
  memgineering undo <op_id> # a specific one
90
- memgineering log # what has changed, and what can still be undone
140
+ memgineering log # what changed, why, and what can still be undone
91
141
  ```
92
142
 
93
143
  Write down: decisions and their reasons, constraints, corrections the user
@@ -122,7 +172,7 @@ the user's; neither this command nor you should rewrite it uninvited.
122
172
 
123
173
  ```
124
174
  memgineering retire <ref> --reason "the date moved" # no longer current, still visible
125
- memgineering exclude path/to/note.md # stop reading it entirely
175
+ memgineering exclude path/to/note.md --reason "names an internal host" # stop reading it
126
176
  ```
127
177
 
128
178
  Retiring keeps the note in recall, ranked last and labelled. Excluding takes it
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/program.ts
4
- import { Command as Command16 } from "commander";
4
+ import { Command as Command17 } from "commander";
5
5
 
6
6
  // src/help.ts
7
7
  import { Help } from "commander";
@@ -145,7 +145,7 @@ var COMMAND_GROUPS = [
145
145
  {
146
146
  title: "MEMORY",
147
147
  blurb: "day to day \u2014 what your agent reaches for while working",
148
- commands: ["recall", "open", "remember", "revise", "undo", "resurface"]
148
+ commands: ["recall", "open", "evidence", "remember", "revise", "undo", "resurface"]
149
149
  },
150
150
  {
151
151
  title: "BRAIN",
@@ -2547,12 +2547,36 @@ var OpRecordSchema = z7.object({
2547
2547
  hash_after: z7.string().nullable(),
2548
2548
  /** The frontmatter block region, for reversing a `revise`. */
2549
2549
  patch: z7.object({ before: z7.string(), after: z7.string() }).nullable().default(null),
2550
+ /**
2551
+ * Why this was done, in the words of whoever did it. Never derived.
2552
+ *
2553
+ * Defaulted rather than required so a ledger written by 0.1.x — which had no
2554
+ * such field — still parses. An old line reads as "no reason was given",
2555
+ * which is exactly what happened.
2556
+ */
2557
+ rationale: z7.string().nullable().default(null),
2550
2558
  /** Which generation of the write rules produced this. */
2551
2559
  rule_version: z7.number().int().positive(),
2552
2560
  /** The op this reverses or replaces, when it does. */
2553
2561
  supersedes: z7.string().nullable().default(null)
2554
2562
  });
2555
- var RULE_VERSION = 1;
2563
+ var RULE_VERSION = 2;
2564
+ var RATIONALE_MAX = 500;
2565
+ function sanitizeRationale(raw) {
2566
+ if (raw == null) return null;
2567
+ const clean = stripControl(raw).trim();
2568
+ if (clean === "") return null;
2569
+ const hits = secretContentHits(clean);
2570
+ if (hits.length > 0) {
2571
+ throw memgError(
2572
+ "invalid_input",
2573
+ `the reason looks like it contains credential material (${hits.map((h) => h.code).join(", ")})`,
2574
+ "The ledger travels with the brain, so this would be a permanent synced copy. Say what changed and why without the value itself, or drop --reason \u2014 the write itself is unaffected."
2575
+ );
2576
+ }
2577
+ const capped = clean.length > RATIONALE_MAX ? `${clean.slice(0, RATIONALE_MAX - 1)}\u2026` : clean;
2578
+ return capped;
2579
+ }
2556
2580
  function ledgerDir(vaultRoot) {
2557
2581
  return join6(vaultRoot, BRAND.vaultLedgerDir, "ledger");
2558
2582
  }
@@ -2581,6 +2605,7 @@ async function appendOp(vaultRoot, op) {
2581
2605
  hash_before: op.hashBefore,
2582
2606
  hash_after: op.hashAfter,
2583
2607
  patch: op.patch ?? null,
2608
+ rationale: op.rationale ?? null,
2584
2609
  rule_version: RULE_VERSION,
2585
2610
  supersedes: op.supersedes ?? null
2586
2611
  });
@@ -3499,7 +3524,10 @@ function openCommand() {
3499
3524
  );
3500
3525
  printHuman(
3501
3526
  c.gray(
3502
- "To state one, add frontmatter at the top of the note:\n ---\n memgineering:\n id: kitchen-remodel\n claim: The kitchen work runs until April 15.\n contradicts: [kitchen-remodel-march]\n ---\n`contradicts` is what makes two notes surface as a conflict."
3527
+ `To state one:
3528
+ memgineering revise ${handle} --claim "<the conclusion>" --summary "<one line>"
3529
+ Add --contradicts <id> when it disagrees with another memory; that edge is what
3530
+ makes the two surface together as a conflict.`
3503
3531
  )
3504
3532
  );
3505
3533
  }
@@ -3928,7 +3956,8 @@ Brain: ${root}`));
3928
3956
  function lifecycleCommand(action) {
3929
3957
  return new Command5(action).description(
3930
3958
  action === "retire" ? "mark a memory as no longer current \u2014 the note itself is not changed" : "mark a retired memory as current again"
3931
- ).argument("<ref>", "handle, id, path, or exact title").option("--reason <text>", "why \u2014 kept in the record").option("--vault <path>", "which brain the memory is in").action(async (ref, opts) => {
3959
+ ).argument("<ref>", "handle, id, path, or exact title").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").action(async (ref, opts) => {
3960
+ const rationale = sanitizeRationale(opts.reason);
3932
3961
  const { brain } = await resolveBrain(opts.vault);
3933
3962
  const adapter = await LocalFileCasAdapter.openVault(brain.root, {
3934
3963
  createPrefix: brain.observationsDir
@@ -4003,6 +4032,7 @@ function lifecycleCommand(action) {
4003
4032
  hashBefore: hashContent(before.content),
4004
4033
  hashAfter: hashContent(outcome.content),
4005
4034
  patch: { before: preview.before, after: preview.after },
4035
+ rationale,
4006
4036
  snapshotBefore: before.content
4007
4037
  });
4008
4038
  });
@@ -4030,7 +4060,8 @@ Undo: \`memgineering undo ${record.op_id}\``));
4030
4060
  function ignoreCommand(mode) {
4031
4061
  return new Command5(mode).description(
4032
4062
  mode === "exclude" ? "stop reading a note entirely \u2014 the note itself is not changed" : "read a note again after excluding it"
4033
- ).argument("<path>", "path inside the brain, e.g. decisions/launch-date.md").option("--vault <path>", "which brain the note is in").action(async (path, opts) => {
4063
+ ).argument("<path>", "path inside the brain, e.g. decisions/launch-date.md").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the note is in").action(async (path, opts) => {
4064
+ const rationale = sanitizeRationale(opts.reason);
4034
4065
  const { brain } = await resolveBrain(opts.vault);
4035
4066
  const adapter = await LocalFileCasAdapter.openVault(brain.root, {
4036
4067
  createPrefix: brain.observationsDir
@@ -4054,6 +4085,7 @@ function ignoreCommand(mode) {
4054
4085
  hashBefore: hashContent(base.content),
4055
4086
  hashAfter: hashContent(result.content),
4056
4087
  patch: { before: base.content, after: result.content },
4088
+ rationale,
4057
4089
  snapshotBefore: base.content
4058
4090
  });
4059
4091
  });
@@ -4101,12 +4133,170 @@ var unretireCommand = () => lifecycleCommand("unretire");
4101
4133
  var excludeCommand = () => ignoreCommand("exclude");
4102
4134
  var unexcludeCommand = () => ignoreCommand("unexclude");
4103
4135
 
4136
+ // src/commands/evidence.ts
4137
+ import { Command as Command6 } from "commander";
4138
+ function evidenceCommand() {
4139
+ return new Command6("evidence").description("how much a memory has been used, and why it last changed").argument("<ref>", "handle, id, path, or exact title").option("--vault <path>", "which brain to read").action(async (ref, opts) => {
4140
+ const { brain } = await resolveBrain(opts.vault);
4141
+ const adapter = await openBrainOrExplain(brain.root);
4142
+ const built = await openIndex(adapter, brain.root);
4143
+ const entry = resolveRef(built.entries, ref);
4144
+ const [events, ledger] = await Promise.all([readEvents(brain.root), readOps(brain.root)]);
4145
+ const now = /* @__PURE__ */ new Date();
4146
+ const report = assembleEvidence(entry, events, ledger.ops, {
4147
+ contextDir: process.cwd(),
4148
+ now,
4149
+ brain: built.entries
4150
+ });
4151
+ printDual({
4152
+ json: report,
4153
+ human: () => {
4154
+ printHuman(`## evidence: ${entry.memory.title}
4155
+ `);
4156
+ printHuman(`\`id: ${report.id}\` \xB7 \`${report.path}\`
4157
+ `);
4158
+ if (report.critical) {
4159
+ printHuman(c.yellow("\u26A0 critical \u2014 this note defines how the agent behaves.") + "\n");
4160
+ }
4161
+ if (!report.brain_has_history) {
4162
+ printHuman(
4163
+ c.yellow("This machine has no recall history for this brain yet.") + c.gray(
4164
+ "\nThe usage numbers below are zero because nothing has been recorded here,\nnot because the memory is unused. The log is local state and does not sync.\n"
4165
+ )
4166
+ );
4167
+ }
4168
+ const rows = [
4169
+ [
4170
+ "surfaced",
4171
+ report.surfaced === 0 ? "never" : `${report.surfaced}\xD7 (${report.surfaced_here}\xD7 in this folder, ${report.surfaced - report.surfaced_here}\xD7 elsewhere)`
4172
+ ],
4173
+ [
4174
+ "opened",
4175
+ report.opened === 0 ? "never" : `${report.opened}\xD7${report.open_rate === null ? "" : ` (${report.opened} of the ${report.surfaced} times it surfaced)`}`
4176
+ ],
4177
+ ["last used", report.last_touched ? ago(report.last_touched, now) : "\u2014"],
4178
+ [
4179
+ "in the ledger",
4180
+ report.first_recorded ? `since ${report.first_recorded.slice(0, 10)} \xB7 changed ${report.changed}\xD7${report.undone > 0 ? ` \xB7 undone ${report.undone}\xD7` : ""}` : "nothing recorded \u2014 this note predates the ledger, or was only edited by hand"
4181
+ ]
4182
+ ];
4183
+ if (report.declared_from) {
4184
+ rows.push(["declared", `valid from ${report.declared_from.slice(0, 10)}`]);
4185
+ }
4186
+ if (report.supersedes > 0 || report.contradicts > 0) {
4187
+ rows.push([
4188
+ "relations",
4189
+ [
4190
+ report.supersedes > 0 ? `supersedes ${report.supersedes}` : null,
4191
+ report.contradicts > 0 ? `contradicts ${report.contradicts}` : null
4192
+ ].filter(Boolean).join(" \xB7 ")
4193
+ ]);
4194
+ }
4195
+ const pad = rows.reduce((w, [label]) => Math.max(w, label.length), 0) + 2;
4196
+ for (const [label, value] of rows) printHuman(` ${label.padEnd(pad)}${value}`);
4197
+ if (report.standing) {
4198
+ printHuman(`
4199
+ **${report.standing}** \u2014 ${STANDING_MEANS[report.standing]}`);
4200
+ }
4201
+ if (report.contradicts > 0) {
4202
+ printHuman(
4203
+ c.yellow(
4204
+ `
4205
+ \u26A0 this memory contradicts ${report.contradicts} other${report.contradicts === 1 ? "" : "s"}.`
4206
+ ) + c.gray(
4207
+ "\n Both sides can be well-used. Usage cannot settle which one is right \u2014 compare the\n reasons above, or check something outside the brain."
4208
+ )
4209
+ );
4210
+ }
4211
+ if (report.last_reason) {
4212
+ printHuman(
4213
+ report.last_reason.verb === "remember" ? "\nwhy it was recorded:" : "\nwhy it last changed:"
4214
+ );
4215
+ printHuman(quoteBlock(report.last_reason.text));
4216
+ printHuman(
4217
+ c.gray(` ${report.last_reason.verb}, ${report.last_reason.ts.slice(0, 10)}`)
4218
+ );
4219
+ } else if (report.changed > 0) {
4220
+ printHuman(
4221
+ c.gray(
4222
+ "\nIt has changed, and no reason was recorded. Pass `--reason` when you write and the next one will say why."
4223
+ )
4224
+ );
4225
+ }
4226
+ }
4227
+ });
4228
+ });
4229
+ }
4230
+ var STANDING_MEANS = {
4231
+ "well-used": "it gets reached for and read. That is attendance, not correctness.",
4232
+ "surfaced, never opened": "recall keeps returning it and nobody reads it. Either it does not answer those questions, or its summary does not say what it knows.",
4233
+ "never recalled": "nothing has ever surfaced it. It may be true and simply never needed \u2014 but nothing here supports it either."
4234
+ };
4235
+ function assembleEvidence(entry, events, ops, opts) {
4236
+ const id = entry.memory.id;
4237
+ const surfacing = events.filter(
4238
+ (e) => (e.verb === "recall" || e.verb === "resurface") && e.returned_ids.includes(id)
4239
+ );
4240
+ const opens = events.filter((e) => e.verb === "open" && e.opened_id === id);
4241
+ const touched = [...surfacing, ...opens].map((e) => e.ts).sort().at(-1);
4242
+ const mine = ops.filter((o) => o.target === entry.path || o.memory_id === id);
4243
+ const changed = mine.filter((o) => o.verb !== "undo" && o.verb !== "remember").length;
4244
+ const undone = mine.filter((o) => o.verb === "undo").length;
4245
+ const withReason = mine.filter((o) => o.rationale !== null).at(-1);
4246
+ const relationsOfType = (type) => entry.relations.filter((r) => r.type === type && r.from === id).length;
4247
+ const disputedWith = /* @__PURE__ */ new Set();
4248
+ for (const relation of entry.relations) {
4249
+ if (relation.type === "contradicts" && relation.from === id) disputedWith.add(relation.to);
4250
+ }
4251
+ for (const other of opts.brain ?? []) {
4252
+ if (other.memory.id === id) continue;
4253
+ for (const relation of other.relations) {
4254
+ if (relation.type === "contradicts" && relation.to === id) disputedWith.add(relation.from);
4255
+ }
4256
+ }
4257
+ return {
4258
+ id,
4259
+ path: entry.path,
4260
+ title: entry.memory.title,
4261
+ critical: entry.path.startsWith("01_BASE/"),
4262
+ brain_has_history: events.length > 0,
4263
+ surfaced: surfacing.length,
4264
+ surfaced_here: surfacing.filter((e) => isInside(e.context_dir, opts.contextDir)).length,
4265
+ opened: opens.length,
4266
+ // Divided by the number printed directly above it. It used to be divided by
4267
+ // recalls only, while `surfaced` also counted resurfacings — so a reader saw
4268
+ // two numbers and a percentage that reconciled with neither.
4269
+ open_rate: surfacing.length === 0 ? null : opens.length / surfacing.length,
4270
+ last_touched: touched ?? null,
4271
+ first_recorded: mine[0]?.ts ?? null,
4272
+ declared_from: entry.claims.find((claim) => claim.valid_from)?.valid_from ?? null,
4273
+ changed,
4274
+ undone,
4275
+ supersedes: relationsOfType("supersedes"),
4276
+ contradicts: disputedWith.size,
4277
+ last_reason: withReason ? { verb: withReason.verb, ts: withReason.ts, text: withReason.rationale } : null,
4278
+ // Withheld rather than guessed when the log is empty. "Never recalled" on a
4279
+ // brain that arrived this morning would be a confident wrong answer, which
4280
+ // is the one thing a memory tool must not produce.
4281
+ standing: events.length === 0 ? null : opens.length > 0 ? "well-used" : surfacing.length > 0 ? "surfaced, never opened" : "never recalled"
4282
+ };
4283
+ }
4284
+ function ago(iso, now) {
4285
+ const days = Math.floor((now.getTime() - Date.parse(iso)) / 864e5);
4286
+ if (!Number.isFinite(days)) return iso;
4287
+ if (days <= 0) return "today";
4288
+ if (days === 1) return "yesterday";
4289
+ if (days < 30) return `${days} days ago`;
4290
+ if (days < 365) return `${Math.floor(days / 30)} months ago`;
4291
+ return `${Math.floor(days / 365)} years ago`;
4292
+ }
4293
+
4104
4294
  // src/commands/init.ts
4105
4295
  import { mkdir as mkdir8, readdir as readdir5, writeFile as writeFile6 } from "fs/promises";
4106
4296
  import { join as join13, resolve as resolve6 } from "path";
4107
- import { Command as Command6 } from "commander";
4297
+ import { Command as Command7 } from "commander";
4108
4298
  function initCommand() {
4109
- return new Command6("init").description("create a new brain, laid out and ready to write into").argument("<path>", "where the brain should live").option("--name <name>", "what to call it in the hub", "My brain").action(async (path, opts) => {
4299
+ return new Command7("init").description("create a new brain, laid out and ready to write into").argument("<path>", "where the brain should live").option("--name <name>", "what to call it in the hub", "My brain").action(async (path, opts) => {
4110
4300
  const root = resolve6(path);
4111
4301
  const existing = await readdir5(root).catch(() => null);
4112
4302
  if (existing && existing.filter((e) => !e.startsWith(".")).length > 0) {
@@ -4274,9 +4464,9 @@ function files(name) {
4274
4464
  }
4275
4465
 
4276
4466
  // src/commands/log.ts
4277
- import { Command as Command7 } from "commander";
4467
+ import { Command as Command8 } from "commander";
4278
4468
  function logCommand() {
4279
- return new Command7("log").description("what changed in this brain, and what can still be undone").option("--limit <n>", "how many operations", (v) => parseInt(v, 10), 20).option("--verbose", "include hashes and reverse patches").option("--vault <path>", "which brain to read").action(async (opts) => {
4469
+ return new Command8("log").description("what changed in this brain, and what can still be undone").option("--limit <n>", "how many operations", (v) => parseInt(v, 10), 20).option("--verbose", "include hashes and reverse patches").option("--vault <path>", "which brain to read").action(async (opts) => {
4280
4470
  const { brain } = await resolveBrain(opts.vault);
4281
4471
  const adapter = await openBrainOrExplain(brain.root);
4282
4472
  const { ops, skipped } = await readOps(brain.root);
@@ -4322,6 +4512,7 @@ function logCommand() {
4322
4512
  `- \`${op.op_id}\` ${op.verb} \`${op.target}\`${marks.length ? c.yellow(` (${marks.join(", ")})`) : ""}`
4323
4513
  );
4324
4514
  printHuman(c.gray(` ${op.ts} \xB7 ${op.actor}`));
4515
+ if (op.rationale) printHuman(c.gray(quoteBlock(op.rationale, " > ")));
4325
4516
  if (opts.verbose) {
4326
4517
  printHuman(c.gray(` ${op.hash_before ?? "\u2205"} \u2192 ${op.hash_after ?? "\u2205"}`));
4327
4518
  }
@@ -4357,6 +4548,7 @@ function summarize(op) {
4357
4548
  verb: op.verb,
4358
4549
  target: op.target,
4359
4550
  memory_id: op.memory_id,
4551
+ rationale: op.rationale,
4360
4552
  supersedes: op.supersedes
4361
4553
  };
4362
4554
  }
@@ -4369,9 +4561,9 @@ async function readOrNull(adapter, path) {
4369
4561
  }
4370
4562
 
4371
4563
  // src/commands/recall.ts
4372
- import { Command as Command8 } from "commander";
4564
+ import { Command as Command9 } from "commander";
4373
4565
  function recallCommand() {
4374
- return new Command8("recall").description("recall memory cards for a question").argument("<query>", "what you want to remember").option("--vault <path>", "which brain to search").option("--scope <scope>", "restrict to one scope").option("--limit <n>", "max cards", (v) => parseInt(v, 10), 8).option("--detail <level>", "title | card | summary | chunks | full (default: card)").action(
4566
+ return new Command9("recall").description("recall memory cards for a question").argument("<query>", "what you want to remember").option("--vault <path>", "which brain to search").option("--scope <scope>", "restrict to one scope").option("--limit <n>", "max cards", (v) => parseInt(v, 10), 8).option("--detail <level>", "title | card | summary | chunks | full (default: card)").action(
4375
4567
  async (query, opts) => {
4376
4568
  const detail = parseDetail(opts.detail, "card");
4377
4569
  const { brain } = await resolveBrain(opts.vault);
@@ -4411,10 +4603,30 @@ function recallCommand() {
4411
4603
  conflicts: result.conflicts
4412
4604
  },
4413
4605
  human: () => {
4606
+ const reachedSomethingWritten = rows.some(
4607
+ ({ card }) => !(byId.get(card.id)?.path ?? "").startsWith("01_BASE/")
4608
+ );
4609
+ const otherScript = reachedSomethingWritten ? null : unreachableScript(query, built.entries);
4610
+ const sayWhy = () => {
4611
+ printHuman(
4612
+ c.yellow(
4613
+ `\u26A0 This brain's notes are mostly written in ${otherScript}, and this query is not.`
4614
+ ) + c.gray(
4615
+ `
4616
+ Search matches text, not meaning, so no rewording in another language will reach them.
4617
+ Ask again in ${otherScript}, or run \`memgineering resurface\` to see what is here.
4618
+ `
4619
+ )
4620
+ );
4621
+ };
4414
4622
  if (rows.length === 0) {
4415
4623
  printHuman(`## recall: ${query}
4416
4624
  `);
4417
4625
  printHuman("_Nothing matched._\n");
4626
+ if (otherScript) {
4627
+ sayWhy();
4628
+ return;
4629
+ }
4418
4630
  printHuman(
4419
4631
  c.gray(
4420
4632
  "Only titles, aliases and summaries are searched \u2014 try fewer or broader words, or drop --scope."
@@ -4424,6 +4636,7 @@ function recallCommand() {
4424
4636
  }
4425
4637
  printHuman(`## recall: ${query} (${rows.length} cards)
4426
4638
  `);
4639
+ if (otherScript) sayWhy();
4427
4640
  for (const [i, { card, handle, stage }] of rows.entries()) {
4428
4641
  printHuman(renderCard(i + 1, card, handle, detail, stage));
4429
4642
  }
@@ -4471,11 +4684,62 @@ function renderCard(n, card, handle, detail, stage) {
4471
4684
  out.push("");
4472
4685
  return out.join("\n");
4473
4686
  }
4687
+ function unreachableScript(query, entries) {
4688
+ const queryScripts = scriptsIn(query);
4689
+ if (queryScripts.size === 0) return null;
4690
+ const written = entries.filter((e) => !e.path.startsWith("01_BASE/"));
4691
+ const counts = /* @__PURE__ */ new Map();
4692
+ for (const entry of written) {
4693
+ const script = dominantScriptOf(`${entry.memory.title} ${entry.memory.summary ?? ""}`);
4694
+ if (script) counts.set(script, (counts.get(script) ?? 0) + 1);
4695
+ }
4696
+ if (counts.size === 0) return null;
4697
+ const [dominant, seen] = [...counts].sort((a, b) => b[1] - a[1])[0];
4698
+ if (queryScripts.has(dominant)) return null;
4699
+ return seen >= Math.max(2, written.length / 2) ? dominant : null;
4700
+ }
4701
+ var SCRIPT_RANGES = [
4702
+ ["Korean", /[가-힣ᄀ-ᇿ]/g],
4703
+ ["Kana", /[぀-ヿ]/g],
4704
+ ["Han", /[一-鿿]/g],
4705
+ ["Cyrillic", /[Ѐ-ӿ]/g],
4706
+ ["Arabic", /[؀-ۿ]/g],
4707
+ ["English", /[A-Za-z]/g]
4708
+ ];
4709
+ function scriptCounts(text) {
4710
+ const counts = /* @__PURE__ */ new Map();
4711
+ for (const [name, pattern] of SCRIPT_RANGES) {
4712
+ const n = (text.match(pattern) ?? []).length;
4713
+ if (n > 0) counts.set(name, n);
4714
+ }
4715
+ const han = counts.get("Han") ?? 0;
4716
+ if (counts.has("Kana")) {
4717
+ counts.set("Japanese", (counts.get("Kana") ?? 0) + han);
4718
+ counts.delete("Kana");
4719
+ counts.delete("Han");
4720
+ } else if (han > 0) {
4721
+ counts.set("Chinese", han);
4722
+ counts.delete("Han");
4723
+ }
4724
+ return counts;
4725
+ }
4726
+ function dominantScriptOf(text) {
4727
+ const ranked = [...scriptCounts(text)].sort((a, b) => b[1] - a[1]);
4728
+ if (ranked.length === 0) return null;
4729
+ if (ranked.length > 1 && ranked[0][1] === ranked[1][1]) return null;
4730
+ return ranked[0][0];
4731
+ }
4732
+ function scriptsIn(text) {
4733
+ return new Set(scriptCounts(text).keys());
4734
+ }
4474
4735
 
4475
4736
  // src/commands/remember.ts
4476
- import { Command as Command9 } from "commander";
4737
+ import { Command as Command10 } from "commander";
4477
4738
  function rememberCommand() {
4478
- return new Command9("remember").description("write something down now, with no approval step").argument("<text>", "what to remember").option("--vault <path>", "which brain to write to").option("--scope <scope>", "what this is about \u2014 a project, an area").action(async (text, opts) => {
4739
+ return new Command10("remember").description("write something down now, with no approval step").argument("<text>", "what to remember").option("--vault <path>", "which brain to write to").option("--scope <scope>", "what this is about \u2014 a project, an area").option(
4740
+ "--reason <text>",
4741
+ "why you are recording this \u2014 goes in the ledger, not in the note (the fact itself belongs in <text>)"
4742
+ ).action(async (text, opts) => {
4479
4743
  const body = stripControl(text).trim();
4480
4744
  if (body === "") {
4481
4745
  throw memgError(
@@ -4484,6 +4748,7 @@ function rememberCommand() {
4484
4748
  'Pass the text: `memgineering remember "deploys are manual \u2014 launchctl by hand"`'
4485
4749
  );
4486
4750
  }
4751
+ const rationale = sanitizeRationale(opts.reason);
4487
4752
  const { brain } = await resolveBrain(opts.vault);
4488
4753
  await openBrainOrExplain(brain.root);
4489
4754
  const now = /* @__PURE__ */ new Date();
@@ -4519,6 +4784,7 @@ function rememberCommand() {
4519
4784
  memoryId: id,
4520
4785
  hashBefore: null,
4521
4786
  hashAfter: hashContent(content),
4787
+ rationale,
4522
4788
  now
4523
4789
  });
4524
4790
  });
@@ -4562,9 +4828,9 @@ function yamlString(value) {
4562
4828
  }
4563
4829
 
4564
4830
  // src/commands/resurface.ts
4565
- import { Command as Command10 } from "commander";
4831
+ import { Command as Command11 } from "commander";
4566
4832
  function resurfaceCommand() {
4567
- return new Command10("resurface").description("what is worth having in view right now, without being asked").option("--context-dir <path>", "where the work is happening (default: cwd)").option("--limit <n>", "how many cards", (v) => parseInt(v, 10), 5).option("--vault <path>", "which brain").action(async (opts) => {
4833
+ return new Command11("resurface").description("what is worth having in view right now, without being asked").option("--context-dir <path>", "where the work is happening (default: cwd)").option("--limit <n>", "how many cards", (v) => parseInt(v, 10), 5).option("--vault <path>", "which brain").action(async (opts) => {
4568
4834
  const contextDir = opts.contextDir ?? process.cwd();
4569
4835
  const { brain } = await resolveBrain(opts.vault, contextDir);
4570
4836
  const adapter = await openBrainOrExplain(brain.root);
@@ -4690,9 +4956,9 @@ function rank(entries, events, opts) {
4690
4956
 
4691
4957
  // src/commands/revise.ts
4692
4958
  import { readFile as readFile14 } from "fs/promises";
4693
- import { Command as Command11 } from "commander";
4959
+ import { Command as Command12 } from "commander";
4694
4960
  function reviseCommand() {
4695
- return new Command11("revise").description("change a memory's conclusion, and record how to undo it").argument("<ref>", "handle, id, path, or exact title").option("--claim <text>", "what is now true").option("--summary <text>", "the one line recall will show (defaults to the claim)").option("--action <kind>", "reinforce | supersede | conflict", "supersede").option("--contradicts <ids...>", "memories this disagrees with").option("--reason <text>", "why this change is right").option("--because <text>", "the passage from the note that justifies it").option("--input <json>", "the whole change as JSON (file path, `-`, or inline)").option("--dry-run", "show the diff and write nothing").option("--vault <path>", "which brain to write to").action(async (ref, opts) => {
4961
+ return new Command12("revise").description("change a memory's conclusion, and record how to undo it").argument("<ref>", "handle, id, path, or exact title").option("--claim <text>", "what is now true").option("--summary <text>", "the one line recall will show (defaults to the claim)").option("--action <kind>", "reinforce | supersede | conflict", "supersede").option("--contradicts <ids...>", "memories this disagrees with").option("--reason <text>", "why this change is right").option("--because <text>", "the passage from the note that justifies it").option("--input <json>", "the whole change as JSON (file path, `-`, or inline)").option("--dry-run", "show the diff and write nothing").option("--vault <path>", "which brain to write to").action(async (ref, opts) => {
4696
4962
  const { brain } = await resolveBrain(opts.vault);
4697
4963
  await openBrainOrExplain(brain.root);
4698
4964
  const adapter = await LocalFileCasAdapter.openVault(brain.root, {
@@ -4701,6 +4967,9 @@ function reviseCommand() {
4701
4967
  const built = await openIndex(adapter, brain.root);
4702
4968
  const entry = resolveRef(built.entries, ref);
4703
4969
  const input = await buildCommitInput(entry, opts);
4970
+ const rationale = sanitizeRationale(
4971
+ input.reason === NO_REASON_GIVEN ? null : input.reason ?? null
4972
+ );
4704
4973
  const result = commit(asSingleMatch(entry), input);
4705
4974
  if (result.action === "ignore") {
4706
4975
  throw memgError("invalid_input", "nothing to revise", "Pass --claim, or --input with one.");
@@ -4756,6 +5025,7 @@ function reviseCommand() {
4756
5025
  // would build a permanent second copy of something the author may
4757
5026
  // later delete on purpose.
4758
5027
  patch: { before: preview.before, after: preview.after },
5028
+ rationale,
4759
5029
  snapshotBefore: before.content
4760
5030
  });
4761
5031
  return { record, content: outcome.content };
@@ -4788,6 +5058,7 @@ Undo: \`memgineering undo ${applied.record.op_id}\``));
4788
5058
  });
4789
5059
  });
4790
5060
  }
5061
+ var NO_REASON_GIVEN = "revised directly";
4791
5062
  async function buildCommitInput(entry, opts) {
4792
5063
  if (opts.input) {
4793
5064
  const raw = await readInput(opts.input);
@@ -4823,7 +5094,7 @@ async function buildCommitInput(entry, opts) {
4823
5094
  relates_to: [],
4824
5095
  contradicts: opts.contradicts ?? []
4825
5096
  },
4826
- reason: opts.reason ?? "revised directly",
5097
+ reason: opts.reason ?? NO_REASON_GIVEN,
4827
5098
  // The engine refuses to submit without an excerpt, because approving a
4828
5099
  // diff on an assertion alone is not consent. Nothing here is approved by a
4829
5100
  // person, but the requirement still earns its keep: it forces the record
@@ -4890,10 +5161,10 @@ import { access, mkdir as mkdir9, readFile as readFile15 } from "fs/promises";
4890
5161
  import { homedir as homedir3 } from "os";
4891
5162
  import { dirname as dirname3, join as join14 } from "path";
4892
5163
  import { fileURLToPath } from "url";
4893
- import { Command as Command12 } from "commander";
5164
+ import { Command as Command13 } from "commander";
4894
5165
  import prompts2 from "prompts";
4895
5166
  function setupCommand() {
4896
- return new Command12("setup").description("install into your agents, and point at a brain").option("--agent", "non-interactive: take every answer from flags").option("--human", "interactive: choose from a list").option("--tools <list>", "comma-separated: claude, codex, gemini (default: all detected)").option("--auto-update <choice>", "on | off (default: on, recommended)").option("--hook <choice>", "on | off \u2014 SessionStart resurface, Claude Code only").option("--dry-run", "report what would change, write nothing").option("--silent", "only speak if something changed").action(async (opts) => {
5167
+ return new Command13("setup").description("install into your agents, and point at a brain").option("--agent", "non-interactive: take every answer from flags").option("--human", "interactive: choose from a list").option("--tools <list>", "comma-separated: claude, codex, gemini (default: all detected)").option("--auto-update <choice>", "on | off (default: on, recommended)").option("--hook <choice>", "on | off \u2014 SessionStart resurface, Claude Code only").option("--dry-run", "report what would change, write nothing").option("--silent", "only speak if something changed").action(async (opts) => {
4897
5168
  const mode = await resolveMode(opts);
4898
5169
  const detected = await detectTools();
4899
5170
  if (detected.length === 0) {
@@ -5258,7 +5529,7 @@ async function exists(path) {
5258
5529
  }
5259
5530
 
5260
5531
  // src/commands/update.ts
5261
- import { Command as Command13 } from "commander";
5532
+ import { Command as Command14 } from "commander";
5262
5533
 
5263
5534
  // src/version.ts
5264
5535
  import { readFileSync } from "fs";
@@ -5500,7 +5771,7 @@ function shortMessage(err) {
5500
5771
 
5501
5772
  // src/commands/update.ts
5502
5773
  function updateCommand() {
5503
- return new Command13("update").description("update memgineering itself").option("--check", "report what is available and change nothing").action(async (opts) => {
5774
+ return new Command14("update").description("update memgineering itself").option("--check", "report what is available and change nothing").action(async (opts) => {
5504
5775
  const status = await checkForUpdate(VERSION);
5505
5776
  const provenance = await detectProvenance();
5506
5777
  const previous = await readUpdateState();
@@ -5608,9 +5879,10 @@ function reportBelowMinimum(below) {
5608
5879
  // src/commands/undo.ts
5609
5880
  import { readFile as readFile17, rm as rm8 } from "fs/promises";
5610
5881
  import { join as join18 } from "path";
5611
- import { Command as Command14 } from "commander";
5882
+ import { Command as Command15 } from "commander";
5612
5883
  function undoCommand() {
5613
- return new Command14("undo").description("take back the last change, or a named one").argument("[op]", "operation id (default: the most recent)").option("--vault <path>", "which brain to act on").action(async (opId, opts) => {
5884
+ return new Command15("undo").description("take back the last change, or a named one").argument("[op]", "operation id (default: the most recent)").option("--reason <text>", "why it is being taken back \u2014 kept in the ledger").option("--vault <path>", "which brain to act on").action(async (opId, opts) => {
5885
+ const rationale = sanitizeRationale(opts.reason);
5614
5886
  const { brain } = await resolveBrain(opts.vault);
5615
5887
  await openBrainOrExplain(brain.root);
5616
5888
  const { ops } = await readOps(brain.root);
@@ -5658,6 +5930,7 @@ function undoCommand() {
5658
5930
  memoryId: target.memory_id,
5659
5931
  hashBefore: actualHash,
5660
5932
  hashAfter: reverted === null ? null : hashContent(reverted),
5933
+ rationale,
5661
5934
  supersedes: target.op_id
5662
5935
  });
5663
5936
  });
@@ -5747,25 +6020,32 @@ The text from before that change is still on this machine: ${join18(snapshotDir(
5747
6020
 
5748
6021
  // src/commands/use.ts
5749
6022
  import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve8 } from "path";
5750
- import { Command as Command15 } from "commander";
6023
+ import { Command as Command16 } from "commander";
6024
+ var VIA_MEANS = {
6025
+ flag: "because --vault named it",
6026
+ pointer: `because a \`${BRAND.pointerFileName}\` file here points at it`,
6027
+ cwd: "because this folder is inside it",
6028
+ only: "because it is the only brain linked"
6029
+ };
5751
6030
  function useCommand() {
5752
- return new Command15("use").description("bind this folder (and everything under it) to one brain").argument("[brain]", "path to the brain \u2014 omit to show what this folder resolves to").option("--here <dir>", "write the pointer in this directory instead of the cwd").action(async (brainPath, opts) => {
6031
+ return new Command16("use").description("bind this folder (and everything under it) to one brain").argument("[brain]", "path to the brain \u2014 omit to show what this folder resolves to").option("--here <dir>", "write the pointer in this directory instead of the cwd").action(async (brainPath, opts) => {
5753
6032
  const where = await canonicalize(resolve8(opts.here ?? process.cwd()));
5754
6033
  if (!brainPath) {
5755
6034
  const pointer = await readPointer(where);
6035
+ const resolved = await resolveBrain(void 0, where);
5756
6036
  printDual({
5757
- json: pointer ? { pointer: pointer.from, brain: pointer.root } : { pointer: null },
6037
+ json: {
6038
+ brain: resolved.brain.root,
6039
+ via: resolved.via,
6040
+ pointer: pointer ? pointer.from : null
6041
+ },
5758
6042
  human: () => {
5759
- if (!pointer) {
5760
- printHuman("_No pointer here._\n");
5761
- printHuman(
5762
- c.gray(
5763
- "This folder resolves by containment, or by being the only linked brain.\nBind one explicitly with `memgineering use <brain>`."
5764
- )
5765
- );
5766
- return;
6043
+ printHuman(`Using \`${resolved.brain.root}\`
6044
+ `);
6045
+ printHuman(c.gray(` ${VIA_MEANS[resolved.via]}`));
6046
+ if (resolved.via !== "pointer") {
6047
+ printHuman(c.gray(" Bind this folder explicitly with `memgineering use <brain>`."));
5767
6048
  }
5768
- printHuman(`\`${pointer.from}\` \u2192 \`${pointer.root}\``);
5769
6049
  }
5770
6050
  });
5771
6051
  return;
@@ -5804,13 +6084,14 @@ A pointer to an unlinked folder would fail on every command instead of at this o
5804
6084
 
5805
6085
  // src/program.ts
5806
6086
  function buildProgram() {
5807
- const program = new Command16("memgineering").version(VERSION).description("memgineering \u2014 one memory for the AI you connect").option("--json", "emit JSON to stdout instead of markdown", false).hook("preAction", (thisCommand) => {
6087
+ const program = new Command17("memgineering").version(VERSION).description("memgineering \u2014 one memory for the AI you connect").option("--json", "emit JSON to stdout instead of markdown", false).hook("preAction", (thisCommand) => {
5808
6088
  const opts = thisCommand.optsWithGlobals();
5809
6089
  if (opts.json || process.env[BRAND.jsonEnvVar] === "1") setJsonMode(true);
5810
6090
  });
5811
6091
  configureGroupedHelp(program);
5812
6092
  program.addCommand(recallCommand());
5813
6093
  program.addCommand(openCommand());
6094
+ program.addCommand(evidenceCommand());
5814
6095
  program.addCommand(rememberCommand());
5815
6096
  program.addCommand(reviseCommand());
5816
6097
  program.addCommand(undoCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memgineering",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "description": "One memory for the AI you connect. Recall, remember, and revise a brain your agents share — stored in your own folder.",
6
6
  "license": "Apache-2.0",