memgineering 0.1.0 → 0.2.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,85 @@ language the reader wants. The bilingual rule the monorepo applies to
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.2.0] — 2026-08-09
15
+
16
+ The record can say why, and you can see what a memory is actually worth.
17
+
18
+ ### Added
19
+
20
+ - **`evidence <ref>`** — how much a memory has been used, and why it last
21
+ changed: how often recall surfaced it, how often anyone opened it, when it
22
+ was last read, how many times it has been revised, and the reason attached to
23
+ the most recent change. Every number comes from records that already existed;
24
+ nothing new is collected, and running it records nothing of its own — so
25
+ measuring a memory cannot change where `resurface` ranks it.
26
+ - **A standing for each memory** — `well-used`, `surfaced, never opened`, or
27
+ `never recalled` — computed from usage rather than declared. It is withheld
28
+ entirely when this machine has no recall history yet, since that log is local
29
+ state and a brain copied to a new machine starts empty.
30
+ - **`--reason` on every write verb.** `remember`, `exclude`, `unexclude` and
31
+ `undo` accept one for the first time; `revise`, `retire` and `unretire`
32
+ already did. The reason on an `undo` is usually the most valuable of the set.
33
+ - **`log` shows the reason** without needing `--verbose`.
34
+
35
+ ### Fixed
36
+
37
+ - **`revise --reason` and `retire --reason` were silently discarded.** Both
38
+ flags shipped in 0.1.0, and `retire --help` described the reason as "kept in
39
+ the record". It was not kept anywhere: only creating a brand-new note ever
40
+ read the field, so on the ordinary path — changing a note that already exists
41
+ — the text went nowhere. Reasons given to those commands are now recorded in
42
+ the operation ledger, where `log` and `evidence` can show them.
43
+
44
+ - **`recall` now names the language barrier** when a query cannot reach the
45
+ brain. Search matches text, so an English query never reaches Korean notes
46
+ however it is worded — and the old zero-result footer suggested broader
47
+ wording, the one remedy that cannot work. Four testers hit this and came
48
+ within one command of telling their user the brain held nothing on a subject
49
+ it had notes about. The warning also appears above results, because the
50
+ failure is rarely a clean zero: a couple of English scaffolding notes match
51
+ and look like an answer.
52
+ - **`use` with no argument now says which brain you are on**, and how it
53
+ resolved — by pointer, by containment, or by being the only one linked. Its
54
+ own help had always promised this; it reported only whether a pointer file
55
+ existed, and three testers went and read the config file by hand instead.
56
+ - **`open` no longer tells you to hand-edit frontmatter** when a note states no
57
+ conclusion. It points at `memgineering revise --claim`, which is the
58
+ sanctioned path — the previous hint contradicted the bolded rule against
59
+ editing brain files directly, and a hand edit is invisible to `undo`.
60
+ - **`evidence` no longer implies a well-used memory is a correct one.** A note
61
+ the repository flatly contradicted was described as backed by the record.
62
+ Usage is attendance, and where a memory contradicts another the output now
63
+ says plainly that usage cannot settle which is right.
64
+ - **`evidence` sees a contradiction declared by either note.** The `contradicts`
65
+ edge lives on whichever note a revise happened to touch, so the warning above
66
+ fired on only one of the two — and stayed silent on the other, which is the
67
+ side a reader is more likely to be trusting by mistake.
68
+ - **`evidence` divides the open rate by the number printed beside it.** It used
69
+ to divide by recalls while `surfaced` also counted resurfacings, so the two
70
+ figures and the percentage reconciled with none of each other.
71
+ - **`remember --reason` help disambiguated** — it records why you are writing
72
+ something down, not why the fact is true. One tester nearly filed the
73
+ substance of a note there, where recall would never show it.
74
+
75
+ ### Changed
76
+
77
+ - A reason that looks like it contains credential material is refused, with the
78
+ pattern named and the value never echoed. The ledger travels with the brain,
79
+ so an unscreened reason would become a permanent synced copy of a secret —
80
+ and the deny layer that screens notes does not look at the ledger. The write
81
+ itself is unaffected: rephrase, or drop the flag.
82
+ - Operation records carry a `rationale` field and `rule_version` is now 2.
83
+ Ledgers written by 0.1.x still read correctly; an older line simply reports
84
+ no reason given, which is what happened.
85
+
86
+ ### Agent guidance
87
+
88
+ The bundled hub and the `memgineering-memory` skill now cover `evidence` and
89
+ ask for `--reason` on every write, including the rule that a newer memory does
90
+ not automatically win. **Restart your agent session after upgrading** so the
91
+ new guidance is loaded.
92
+
14
93
  ## [0.1.0] — 2026-08-09
15
94
 
16
95
  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,27 @@ function recallCommand() {
4411
4603
  conflicts: result.conflicts
4412
4604
  },
4413
4605
  human: () => {
4606
+ const otherScript = unreachableScript(query, built.entries);
4607
+ const sayWhy = () => {
4608
+ printHuman(
4609
+ c.yellow(
4610
+ `\u26A0 This brain's notes are mostly written in ${otherScript}, and this query is not.`
4611
+ ) + c.gray(
4612
+ `
4613
+ Search matches text, not meaning, so no rewording in another language will reach them.
4614
+ Ask again in ${otherScript}, or run \`memgineering resurface\` to see what is here.
4615
+ `
4616
+ )
4617
+ );
4618
+ };
4414
4619
  if (rows.length === 0) {
4415
4620
  printHuman(`## recall: ${query}
4416
4621
  `);
4417
4622
  printHuman("_Nothing matched._\n");
4623
+ if (otherScript) {
4624
+ sayWhy();
4625
+ return;
4626
+ }
4418
4627
  printHuman(
4419
4628
  c.gray(
4420
4629
  "Only titles, aliases and summaries are searched \u2014 try fewer or broader words, or drop --scope."
@@ -4424,6 +4633,7 @@ function recallCommand() {
4424
4633
  }
4425
4634
  printHuman(`## recall: ${query} (${rows.length} cards)
4426
4635
  `);
4636
+ if (otherScript) sayWhy();
4427
4637
  for (const [i, { card, handle, stage }] of rows.entries()) {
4428
4638
  printHuman(renderCard(i + 1, card, handle, detail, stage));
4429
4639
  }
@@ -4471,11 +4681,62 @@ function renderCard(n, card, handle, detail, stage) {
4471
4681
  out.push("");
4472
4682
  return out.join("\n");
4473
4683
  }
4684
+ function unreachableScript(query, entries) {
4685
+ const queryScripts = scriptsIn(query);
4686
+ if (queryScripts.size === 0) return null;
4687
+ const written = entries.filter((e) => !e.path.startsWith("01_BASE/"));
4688
+ const counts = /* @__PURE__ */ new Map();
4689
+ for (const entry of written) {
4690
+ const script = dominantScriptOf(`${entry.memory.title} ${entry.memory.summary ?? ""}`);
4691
+ if (script) counts.set(script, (counts.get(script) ?? 0) + 1);
4692
+ }
4693
+ if (counts.size === 0) return null;
4694
+ const [dominant, seen] = [...counts].sort((a, b) => b[1] - a[1])[0];
4695
+ if (queryScripts.has(dominant)) return null;
4696
+ return seen >= Math.max(2, written.length / 2) ? dominant : null;
4697
+ }
4698
+ var SCRIPT_RANGES = [
4699
+ ["Korean", /[가-힣ᄀ-ᇿ]/g],
4700
+ ["Kana", /[぀-ヿ]/g],
4701
+ ["Han", /[一-鿿]/g],
4702
+ ["Cyrillic", /[Ѐ-ӿ]/g],
4703
+ ["Arabic", /[؀-ۿ]/g],
4704
+ ["English", /[A-Za-z]/g]
4705
+ ];
4706
+ function scriptCounts(text) {
4707
+ const counts = /* @__PURE__ */ new Map();
4708
+ for (const [name, pattern] of SCRIPT_RANGES) {
4709
+ const n = (text.match(pattern) ?? []).length;
4710
+ if (n > 0) counts.set(name, n);
4711
+ }
4712
+ const han = counts.get("Han") ?? 0;
4713
+ if (counts.has("Kana")) {
4714
+ counts.set("Japanese", (counts.get("Kana") ?? 0) + han);
4715
+ counts.delete("Kana");
4716
+ counts.delete("Han");
4717
+ } else if (han > 0) {
4718
+ counts.set("Chinese", han);
4719
+ counts.delete("Han");
4720
+ }
4721
+ return counts;
4722
+ }
4723
+ function dominantScriptOf(text) {
4724
+ const ranked = [...scriptCounts(text)].sort((a, b) => b[1] - a[1]);
4725
+ if (ranked.length === 0) return null;
4726
+ if (ranked.length > 1 && ranked[0][1] === ranked[1][1]) return null;
4727
+ return ranked[0][0];
4728
+ }
4729
+ function scriptsIn(text) {
4730
+ return new Set(scriptCounts(text).keys());
4731
+ }
4474
4732
 
4475
4733
  // src/commands/remember.ts
4476
- import { Command as Command9 } from "commander";
4734
+ import { Command as Command10 } from "commander";
4477
4735
  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) => {
4736
+ 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(
4737
+ "--reason <text>",
4738
+ "why you are recording this \u2014 goes in the ledger, not in the note (the fact itself belongs in <text>)"
4739
+ ).action(async (text, opts) => {
4479
4740
  const body = stripControl(text).trim();
4480
4741
  if (body === "") {
4481
4742
  throw memgError(
@@ -4484,6 +4745,7 @@ function rememberCommand() {
4484
4745
  'Pass the text: `memgineering remember "deploys are manual \u2014 launchctl by hand"`'
4485
4746
  );
4486
4747
  }
4748
+ const rationale = sanitizeRationale(opts.reason);
4487
4749
  const { brain } = await resolveBrain(opts.vault);
4488
4750
  await openBrainOrExplain(brain.root);
4489
4751
  const now = /* @__PURE__ */ new Date();
@@ -4519,6 +4781,7 @@ function rememberCommand() {
4519
4781
  memoryId: id,
4520
4782
  hashBefore: null,
4521
4783
  hashAfter: hashContent(content),
4784
+ rationale,
4522
4785
  now
4523
4786
  });
4524
4787
  });
@@ -4562,9 +4825,9 @@ function yamlString(value) {
4562
4825
  }
4563
4826
 
4564
4827
  // src/commands/resurface.ts
4565
- import { Command as Command10 } from "commander";
4828
+ import { Command as Command11 } from "commander";
4566
4829
  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) => {
4830
+ 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
4831
  const contextDir = opts.contextDir ?? process.cwd();
4569
4832
  const { brain } = await resolveBrain(opts.vault, contextDir);
4570
4833
  const adapter = await openBrainOrExplain(brain.root);
@@ -4690,9 +4953,9 @@ function rank(entries, events, opts) {
4690
4953
 
4691
4954
  // src/commands/revise.ts
4692
4955
  import { readFile as readFile14 } from "fs/promises";
4693
- import { Command as Command11 } from "commander";
4956
+ import { Command as Command12 } from "commander";
4694
4957
  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) => {
4958
+ 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
4959
  const { brain } = await resolveBrain(opts.vault);
4697
4960
  await openBrainOrExplain(brain.root);
4698
4961
  const adapter = await LocalFileCasAdapter.openVault(brain.root, {
@@ -4701,6 +4964,9 @@ function reviseCommand() {
4701
4964
  const built = await openIndex(adapter, brain.root);
4702
4965
  const entry = resolveRef(built.entries, ref);
4703
4966
  const input = await buildCommitInput(entry, opts);
4967
+ const rationale = sanitizeRationale(
4968
+ input.reason === NO_REASON_GIVEN ? null : input.reason ?? null
4969
+ );
4704
4970
  const result = commit(asSingleMatch(entry), input);
4705
4971
  if (result.action === "ignore") {
4706
4972
  throw memgError("invalid_input", "nothing to revise", "Pass --claim, or --input with one.");
@@ -4756,6 +5022,7 @@ function reviseCommand() {
4756
5022
  // would build a permanent second copy of something the author may
4757
5023
  // later delete on purpose.
4758
5024
  patch: { before: preview.before, after: preview.after },
5025
+ rationale,
4759
5026
  snapshotBefore: before.content
4760
5027
  });
4761
5028
  return { record, content: outcome.content };
@@ -4788,6 +5055,7 @@ Undo: \`memgineering undo ${applied.record.op_id}\``));
4788
5055
  });
4789
5056
  });
4790
5057
  }
5058
+ var NO_REASON_GIVEN = "revised directly";
4791
5059
  async function buildCommitInput(entry, opts) {
4792
5060
  if (opts.input) {
4793
5061
  const raw = await readInput(opts.input);
@@ -4823,7 +5091,7 @@ async function buildCommitInput(entry, opts) {
4823
5091
  relates_to: [],
4824
5092
  contradicts: opts.contradicts ?? []
4825
5093
  },
4826
- reason: opts.reason ?? "revised directly",
5094
+ reason: opts.reason ?? NO_REASON_GIVEN,
4827
5095
  // The engine refuses to submit without an excerpt, because approving a
4828
5096
  // diff on an assertion alone is not consent. Nothing here is approved by a
4829
5097
  // person, but the requirement still earns its keep: it forces the record
@@ -4890,10 +5158,10 @@ import { access, mkdir as mkdir9, readFile as readFile15 } from "fs/promises";
4890
5158
  import { homedir as homedir3 } from "os";
4891
5159
  import { dirname as dirname3, join as join14 } from "path";
4892
5160
  import { fileURLToPath } from "url";
4893
- import { Command as Command12 } from "commander";
5161
+ import { Command as Command13 } from "commander";
4894
5162
  import prompts2 from "prompts";
4895
5163
  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) => {
5164
+ 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
5165
  const mode = await resolveMode(opts);
4898
5166
  const detected = await detectTools();
4899
5167
  if (detected.length === 0) {
@@ -5258,7 +5526,7 @@ async function exists(path) {
5258
5526
  }
5259
5527
 
5260
5528
  // src/commands/update.ts
5261
- import { Command as Command13 } from "commander";
5529
+ import { Command as Command14 } from "commander";
5262
5530
 
5263
5531
  // src/version.ts
5264
5532
  import { readFileSync } from "fs";
@@ -5500,7 +5768,7 @@ function shortMessage(err) {
5500
5768
 
5501
5769
  // src/commands/update.ts
5502
5770
  function updateCommand() {
5503
- return new Command13("update").description("update memgineering itself").option("--check", "report what is available and change nothing").action(async (opts) => {
5771
+ return new Command14("update").description("update memgineering itself").option("--check", "report what is available and change nothing").action(async (opts) => {
5504
5772
  const status = await checkForUpdate(VERSION);
5505
5773
  const provenance = await detectProvenance();
5506
5774
  const previous = await readUpdateState();
@@ -5608,9 +5876,10 @@ function reportBelowMinimum(below) {
5608
5876
  // src/commands/undo.ts
5609
5877
  import { readFile as readFile17, rm as rm8 } from "fs/promises";
5610
5878
  import { join as join18 } from "path";
5611
- import { Command as Command14 } from "commander";
5879
+ import { Command as Command15 } from "commander";
5612
5880
  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) => {
5881
+ 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) => {
5882
+ const rationale = sanitizeRationale(opts.reason);
5614
5883
  const { brain } = await resolveBrain(opts.vault);
5615
5884
  await openBrainOrExplain(brain.root);
5616
5885
  const { ops } = await readOps(brain.root);
@@ -5658,6 +5927,7 @@ function undoCommand() {
5658
5927
  memoryId: target.memory_id,
5659
5928
  hashBefore: actualHash,
5660
5929
  hashAfter: reverted === null ? null : hashContent(reverted),
5930
+ rationale,
5661
5931
  supersedes: target.op_id
5662
5932
  });
5663
5933
  });
@@ -5747,25 +6017,32 @@ The text from before that change is still on this machine: ${join18(snapshotDir(
5747
6017
 
5748
6018
  // src/commands/use.ts
5749
6019
  import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve8 } from "path";
5750
- import { Command as Command15 } from "commander";
6020
+ import { Command as Command16 } from "commander";
6021
+ var VIA_MEANS = {
6022
+ flag: "because --vault named it",
6023
+ pointer: `because a \`${BRAND.pointerFileName}\` file here points at it`,
6024
+ cwd: "because this folder is inside it",
6025
+ only: "because it is the only brain linked"
6026
+ };
5751
6027
  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) => {
6028
+ 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
6029
  const where = await canonicalize(resolve8(opts.here ?? process.cwd()));
5754
6030
  if (!brainPath) {
5755
6031
  const pointer = await readPointer(where);
6032
+ const resolved = await resolveBrain(void 0, where);
5756
6033
  printDual({
5757
- json: pointer ? { pointer: pointer.from, brain: pointer.root } : { pointer: null },
6034
+ json: {
6035
+ brain: resolved.brain.root,
6036
+ via: resolved.via,
6037
+ pointer: pointer ? pointer.from : null
6038
+ },
5758
6039
  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;
6040
+ printHuman(`Using \`${resolved.brain.root}\`
6041
+ `);
6042
+ printHuman(c.gray(` ${VIA_MEANS[resolved.via]}`));
6043
+ if (resolved.via !== "pointer") {
6044
+ printHuman(c.gray(" Bind this folder explicitly with `memgineering use <brain>`."));
5767
6045
  }
5768
- printHuman(`\`${pointer.from}\` \u2192 \`${pointer.root}\``);
5769
6046
  }
5770
6047
  });
5771
6048
  return;
@@ -5804,13 +6081,14 @@ A pointer to an unlinked folder would fail on every command instead of at this o
5804
6081
 
5805
6082
  // src/program.ts
5806
6083
  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) => {
6084
+ 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
6085
  const opts = thisCommand.optsWithGlobals();
5809
6086
  if (opts.json || process.env[BRAND.jsonEnvVar] === "1") setJsonMode(true);
5810
6087
  });
5811
6088
  configureGroupedHelp(program);
5812
6089
  program.addCommand(recallCommand());
5813
6090
  program.addCommand(openCommand());
6091
+ program.addCommand(evidenceCommand());
5814
6092
  program.addCommand(rememberCommand());
5815
6093
  program.addCommand(reviseCommand());
5816
6094
  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.0",
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",