memgineering 0.12.0 → 0.13.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,61 @@ language the reader wants. The bilingual rule the monorepo applies to
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.13.0] — 2026-08-28
15
+
16
+ ### Added
17
+
18
+ - **Widening a boundary is one word, not a retyped list.**
19
+ `revise <ref> --claim "…" --never-store-add "therapy"` adds words to a
20
+ note's declared boundary while keeping every word already there —
21
+ `--never-store` REPLACES the list, so the old remedy was to retype it
22
+ and one transcription slip silently narrowed a boundary. Only the new
23
+ words are validated; the declared ones are grandfathered, so a list an
24
+ older rule set accepted can still be widened. Refused: a note that
25
+ declares no boundary (start one with `--never-store`), the flag beside
26
+ `--never-store` / `--no-never-store` / `--input`, and an addition
27
+ where every word is already held. After a hosted widening the CLI
28
+ reads the note back and warns loudly — on the human line and as
29
+ `never_store_dropped` in `--json` — if the words did not land; honest
30
+ framing: on today's servers that read is drift-defense (a server that
31
+ rewrites or partially accepts the list), not version-skew protection,
32
+ because a server too old for boundaries makes the no-declaration
33
+ refusal fire first. If the read-back itself fails after the write
34
+ landed, the command reports "could not confirm" instead of failing a
35
+ revision the server already holds.
36
+ - **The semantic suggestion now recommends the additive form.** The
37
+ remedy command printed after a near-boundary write — human line and
38
+ `--json` hint alike — is built by the CLI and says `--never-store-add
39
+ "<the word>"`, so following it can no longer delete the words already
40
+ declared. The regression pin is a negative match, because
41
+ `--never-store-add` contains `--never-store` and a substring check
42
+ would pass on the destructive form.
43
+
44
+ ### Fixed
45
+
46
+ - **`proposals approve|reject|rebase --vault <path>` acts on the brain it
47
+ names.** The parent `proposals` command declares `--vault`/`--local`
48
+ for its own listing, and commander parses a parent's options out of
49
+ the whole argv — so the subcommands received nothing at ANY flag
50
+ position and fell through to the linked brain: on a machine signed in
51
+ to a hosted brain, an approval aimed at a folder went to the cloud.
52
+ The subcommands now hear the flags wherever they are written (the last
53
+ occurrence on the line binds), and the undo hint printed after an
54
+ approval names the brain the write actually landed on.
55
+ - **The local `revise --input` door screens `claim.scope`.** It was the
56
+ one user-prose field reachable through `--input` that the boundary
57
+ screen did not see. Honest framing: the engine currently discards
58
+ scope on this path, so this shuts a door rather than plugs a leak —
59
+ it guards against that internal list ceasing to be true.
60
+
61
+ ### Agent guidance
62
+
63
+ - The `memgineering-writing` skill's widening section changed: add a
64
+ word with `--never-store-add`, replace or narrow a list with
65
+ `--never-store`, and a repeated did-not-land warning means stop and
66
+ tell the user. Restart your agent session after upgrading so the new
67
+ guidance is what gets injected.
68
+
14
69
  ## [0.12.0] — 2026-08-28
15
70
 
16
71
  ### Added
@@ -2,7 +2,7 @@
2
2
  name: memgineering
3
3
  description: Use whenever the user refers to something they told you before, asks what was decided, tells you something worth keeping, or settles something that should hold next time. The memory lives in their own folder and outlives this session; check it before answering from guesswork, and write to it when you learn something durable.
4
4
  type: skill
5
- version: 0.12.0
5
+ version: 0.13.0
6
6
  ---
7
7
 
8
8
  # memgineering
@@ -358,19 +358,28 @@ what they have declared.
358
358
 
359
359
  ### Widening, narrowing, lifting
360
360
 
361
- `--never-store` **REPLACES** the list; it does not add to it. So to add a word,
362
- read the declaration first and name every word it should still hold:
361
+ To add a word, name ONLY the new word the note's words stay:
363
362
 
364
363
  ```bash
365
364
  memgineering revise <ref> \
366
365
  --claim "<the boundary, restated to cover the new word>" \
367
- --never-store "health, hospital, clinic, diagnosis, symptom, medication, therapy"
366
+ --never-store-add "therapy"
368
367
  ```
369
368
 
370
369
  `--claim` is required, and an unchanged claim is refused — restate the boundary
371
- rather than copying the line already there. **Naming only the new word deletes
372
- the words already declared**, which is the opposite of what anybody asking for
373
- this wants.
370
+ rather than copying the line already there. A note that declares no boundary
371
+ refuses `-add` and points you at `--never-store` that is also what you will
372
+ see for every note on a hosted brain older than boundaries, where there is
373
+ nothing to widen yet. After a hosted widening the CLI reads the note back and
374
+ warns out loud if the words did not land; if that warning repeats on a retry,
375
+ the server is not storing what it accepts — stop and tell the user.
376
+
377
+ To narrow or rewrite the list, `--never-store` **REPLACES** it wholesale — name
378
+ every word the note should still hold, because a word you leave out is a word
379
+ the boundary loses. That is what makes it the narrowing verb and the wrong verb
380
+ for adding: **with `--never-store`, naming only the new word deletes the words
381
+ already declared.** The CLI refuses the two flags together rather than guessing
382
+ which you meant.
374
383
 
375
384
  To lift a boundary entirely: `memgineering retire <the declaration>`. That is
376
385
  the un-declare path, and it is the user's decision, never yours.
package/dist/index.js CHANGED
@@ -220,7 +220,10 @@ function undoHint(opId, vault) {
220
220
  return vault ? `memgineering undo ${opId} --vault ${quoteArg(vault)}` : `memgineering undo ${opId}`;
221
221
  }
222
222
  function widenBoundaryHint(path) {
223
- return `memgineering revise ${quoteArg(oneLineWhole(path))} --claim "<the boundary, restated to cover the new word>" --never-store "<every word it should hold, including the new one>"`;
223
+ return `memgineering revise ${quoteArg(oneLineWhole(path))} --claim "<the boundary, restated to cover the new word>" --never-store-add "<the word>"`;
224
+ }
225
+ function boundarySuggestionHint(path) {
226
+ return `This WAS stored and nothing was refused \u2014 no boundary of yours was broken. It only reads as though it might belong with the boundary declared in \`${oneLineWhole(path)}\`, which lists no word this note carries. Tell the person whose brain this is and let them decide, rather than acting on it: if it should not have been stored, \`undo\` on the \`op_id\` in this response takes it back. To close the gap for next time, add the missing word to that declaration \u2014 this keeps the words it already holds: \`${widenBoundaryHint(path)}\`. The word list is what refuses; this check only notices SOME of what it misses, so silence from it is not permission.`;
224
227
  }
225
228
  function quoteArg(value) {
226
229
  return /^[A-Za-z0-9._\/-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
@@ -11584,7 +11587,21 @@ function rememberCommand() {
11584
11587
  // always, and is also what a server too old to send it looks
11585
11588
  // like. Those two are not worth telling apart: neither one
11586
11589
  // means anything was dropped.
11587
- ...written.boundary_suggestion == null ? {} : { boundary_suggestion: written.boundary_suggestion }
11590
+ //
11591
+ // The hint is REBUILT here, never forwarded. The server's own
11592
+ // sentence embeds a remedy command, and the flags in it belong
11593
+ // to this binary — a command travelling as a string between two
11594
+ // separately deployed programs goes stale with nothing on
11595
+ // either side able to see it, and that one already had. What
11596
+ // comes from the server is the declaration and the score;
11597
+ // neither is a sentence. `boundarySuggestionHint` has the rest.
11598
+ ...written.boundary_suggestion == null ? {} : {
11599
+ boundary_suggestion: {
11600
+ declared_in: written.boundary_suggestion.declared_in,
11601
+ similarity: written.boundary_suggestion.similarity,
11602
+ hint: boundarySuggestionHint(written.boundary_suggestion.declared_in)
11603
+ }
11604
+ }
11588
11605
  },
11589
11606
  human: () => {
11590
11607
  printHuman(`Remembered in ${oneLine(target.name, WIDTH.title)}.
@@ -11639,8 +11656,8 @@ function rememberCommand() {
11639
11656
  c.gray(
11640
11657
  `close: this might belong with a boundary you declared \u2014 \`${oneLine(suggestion.declared_in, WIDTH.path)}\`
11641
11658
  Nothing was refused; none of the words you listed are in it. Take this note back
11642
- with the undo above \u2014 or widen that boundary. \`--never-store\` REPLACES the whole
11643
- list, so read that note first and name every word it should keep:
11659
+ with the undo above \u2014 or widen that boundary. \`--never-store-add\` keeps the words
11660
+ that note already declares, so only the missing one is named here:
11644
11661
  \`${widenBoundaryHint(suggestion.declared_in)}\``
11645
11662
  )
11646
11663
  );
@@ -11783,7 +11800,7 @@ function parseKind(raw) {
11783
11800
  `Nothing was written. Use one of: ${MemoryKindSchema.options.join(", ")} \u2014 or leave --kind off, which is fine and common. Your own word for it belongs in the note's \`type\`.`
11784
11801
  );
11785
11802
  }
11786
- function parseNeverStore(raw) {
11803
+ function parseNeverStore(raw, flag = "--never-store") {
11787
11804
  if (raw === void 0) return [];
11788
11805
  const out = [];
11789
11806
  for (const piece of raw.split(",")) {
@@ -11801,8 +11818,8 @@ function parseNeverStore(raw) {
11801
11818
  if (out.length === 0) {
11802
11819
  throw memgError(
11803
11820
  "invalid_input",
11804
- "--never-store was given no usable words",
11805
- 'Nothing was written. Pass the words you mean \u2014 `--never-store "\uBCD1\uC6D0, \uC6B0\uC6B8\uC99D"`. To REMOVE a declaration instead, that is `revise <ref> --no-never-store`, which is a different flag on purpose.'
11821
+ `${flag} was given no usable words`,
11822
+ `Nothing was written. Pass the words you mean \u2014 \`${flag} "\uBCD1\uC6D0, \uC6B0\uC6B8\uC99D"\`. To REMOVE a declaration instead, that is \`revise <ref> --no-never-store\`, which is a different flag on purpose.`
11806
11823
  );
11807
11824
  }
11808
11825
  return out;
@@ -11824,7 +11841,10 @@ function reviseCommand() {
11824
11841
  return new Command6("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("--title <text>", "the heading recall shows above it (unchanged when omitted)").option(
11825
11842
  "--valid-from <iso>",
11826
11843
  "when this became true (defaults: supersede = now, otherwise unchanged)"
11827
- ).option("--action <kind>", "reinforce | supersede | conflict", "supersede").option("--rule", "this memory is now a decision that binds, not a fact to look up").option("--no-rule", "it no longer binds \u2014 an ordinary memory again").option("--never-store <words>", "replace this note\u2019s boundary words (comma-separated)").option("--no-never-store", "this note no longer declares a boundary").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").option("--local", "use the brain on this machine, even when signed in to a hosted one").addHelpText(
11844
+ ).option("--action <kind>", "reinforce | supersede | conflict", "supersede").option("--rule", "this memory is now a decision that binds, not a fact to look up").option("--no-rule", "it no longer binds \u2014 an ordinary memory again").option("--never-store <words>", "replace this note\u2019s boundary words (comma-separated)").option("--no-never-store", "this note no longer declares a boundary").option(
11845
+ "--never-store-add <words>",
11846
+ "add words to this note\u2019s boundary, keeping the ones already there"
11847
+ ).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").option("--local", "use the brain on this machine, even when signed in to a hosted one").addHelpText(
11828
11848
  "after",
11829
11849
  [
11830
11850
  "",
@@ -11840,12 +11860,13 @@ function reviseCommand() {
11840
11860
  "always true, you have only just been told it. `supersede` would date it today."
11841
11861
  ].join("\n")
11842
11862
  ).action(async (ref, opts) => {
11863
+ const added = boundaryAddition(opts);
11843
11864
  const where = await resolveTarget({
11844
11865
  ...opts.local === void 0 ? {} : { local: opts.local },
11845
11866
  ...opts.vault === void 0 ? {} : { vault: opts.vault }
11846
11867
  });
11847
11868
  if (where.kind === "cloud") {
11848
- await reviseHosted(where, ref, opts);
11869
+ await reviseHosted(where, ref, opts, added);
11849
11870
  return;
11850
11871
  }
11851
11872
  const { brain } = await resolveBrain(opts.vault);
@@ -11855,7 +11876,8 @@ function reviseCommand() {
11855
11876
  });
11856
11877
  const built = await openIndex(adapter, brain.root);
11857
11878
  const entry = resolveRef(built.entries, ref);
11858
- const input = await buildCommitInput(entry, opts);
11879
+ const widened = widenedBoundary(entry.memory.never_store, added, ref);
11880
+ const input = await buildCommitInput(entry, opts, widened);
11859
11881
  const rationale = sanitizeRationale(
11860
11882
  input.reason === NO_REASON_GIVEN ? null : input.reason ?? null
11861
11883
  );
@@ -11865,6 +11887,28 @@ function reviseCommand() {
11865
11887
  input.claim?.summary,
11866
11888
  input.claim?.title,
11867
11889
  input.claim?.valid_from,
11890
+ // Free prose, and the last field on this door that was not read.
11891
+ // No flag sets it — only `--input` can.
11892
+ //
11893
+ // What this screens is what the write CARRIES, not what today's
11894
+ // patch happens to keep: `computePatch` currently drops `scope` via
11895
+ // `NEVER_WRITTEN_KEYS`, so on this path the word reaches neither the
11896
+ // note nor the ledger. Screening it anyway, because that list is not
11897
+ // a boundary and has never been treated as one — `title` moved off
11898
+ // it into `PATCHABLE_KEYS` once already — and `scope` is search-
11899
+ // scored by design, so the day it becomes writable is the day the
11900
+ // user's word is stored and findable by the phrase they asked never
11901
+ // to keep. A boundary that holds only while an unrelated list stays
11902
+ // the way it is, is one the user tested once and is wrong about
11903
+ // afterwards.
11904
+ //
11905
+ // Guarded on the type rather than trusted, because `readInput` is a
11906
+ // bare `JSON.parse` with no schema in front of it: a malformed
11907
+ // `--input` puts whatever it likes here, and a screen that threw a
11908
+ // TypeError on it would refuse to say what was actually wrong.
11909
+ // Anything that is not a string falls through to `commit`, which
11910
+ // parses this input properly and reports it as malformed.
11911
+ ...typeof input.claim?.scope === "string" ? [input.claim.scope] : [],
11868
11912
  input.source_excerpt
11869
11913
  ],
11870
11914
  ...input.claim?.never_store != null && input.claim.never_store.length > 0 ? { declares: input.claim.never_store } : (entry.memory.never_store?.length ?? 0) > 0 ? { declares: entry.memory.never_store } : {}
@@ -12025,7 +12069,7 @@ Undo: \`${undoHint(applied.record.op_id, opts.vault)}\``));
12025
12069
  });
12026
12070
  });
12027
12071
  }
12028
- async function reviseHosted(where, ref, opts) {
12072
+ async function reviseHosted(where, ref, opts, added) {
12029
12073
  if (opts.input) {
12030
12074
  throw memgError(
12031
12075
  "invalid_input",
@@ -12040,8 +12084,11 @@ async function reviseHosted(where, ref, opts) {
12040
12084
  'Pass `--claim "<the new conclusion>"`.'
12041
12085
  );
12042
12086
  }
12043
- const boundary = boundaryIntent(opts.neverStore);
12087
+ const replaced = boundaryIntent(opts.neverStore);
12044
12088
  const brain = await openCloudBrain(where);
12089
+ const declared = added === void 0 ? void 0 : declaredWords(await cloudOpen(brain, ref));
12090
+ const widened = widenedBoundary(declared, added, ref);
12091
+ const boundary = widened ?? replaced;
12045
12092
  const result = await cloudRevise(brain, {
12046
12093
  ref,
12047
12094
  claim: opts.claim,
@@ -12062,8 +12109,22 @@ async function reviseHosted(where, ref, opts) {
12062
12109
  ...boundary === void 0 ? {} : { neverStore: boundary },
12063
12110
  ...opts.dryRun === true ? { dryRun: true } : {}
12064
12111
  });
12112
+ let dropped = [];
12113
+ let unconfirmed = false;
12114
+ if (widened !== void 0 && result.dry_run !== true) {
12115
+ try {
12116
+ dropped = missingWords(widened, declaredWords(await cloudOpen(brain, ref)));
12117
+ } catch {
12118
+ unconfirmed = true;
12119
+ }
12120
+ }
12065
12121
  printDual({
12066
- json: { brain: where.name, ...result },
12122
+ json: {
12123
+ brain: where.name,
12124
+ ...result,
12125
+ ...dropped.length === 0 ? {} : { never_store_dropped: dropped },
12126
+ ...unconfirmed ? { never_store_unconfirmed: true } : {}
12127
+ },
12067
12128
  human: () => {
12068
12129
  if (result.dry_run) {
12069
12130
  printHuman(`## revise (dry run) \u2014 ${oneLine(where.name, WIDTH.title)}
@@ -12099,6 +12160,25 @@ async function reviseHosted(where, ref, opts) {
12099
12160
  )
12100
12161
  );
12101
12162
  }
12163
+ if (dropped.length > 0) {
12164
+ printHuman(
12165
+ c.yellow("\n\u26A0 this brain's server did not store the widened boundary") + c.gray(
12166
+ `
12167
+ Missing from the declaration: ${dropped.map((t) => `\`${t}\``).join(", ")}
12168
+ The revision itself is saved, but the note read back without these words, so
12169
+ it refuses exactly what it refused before. Run this again; if it repeats,
12170
+ the server is not storing what it accepts \u2014 check \`memgineering open\` and
12171
+ tell your owner. Nothing here needs undoing first.`
12172
+ )
12173
+ );
12174
+ }
12175
+ if (unconfirmed) {
12176
+ printHuman(
12177
+ c.gray(
12178
+ "\nCould not read the note back to confirm the widened boundary \u2014 the revision\nitself is saved. Check with `memgineering open` when the connection recovers."
12179
+ )
12180
+ );
12181
+ }
12102
12182
  if (result.critical_target) {
12103
12183
  printHuman(
12104
12184
  c.yellow(
@@ -12123,6 +12203,67 @@ ${oneLine(result.reverse.hint, WIDTH.text)}`));
12123
12203
  }
12124
12204
  });
12125
12205
  }
12206
+ function boundaryAddition(opts) {
12207
+ if (opts.neverStoreAdd === void 0) return void 0;
12208
+ if (opts.neverStore === false) {
12209
+ throw memgError(
12210
+ "invalid_input",
12211
+ "--never-store-add and --no-never-store contradict each other",
12212
+ "Nothing was written. One widens this note\u2019s boundary and the other removes it entirely; there is no order in which both are what you meant. Pass whichever one you did."
12213
+ );
12214
+ }
12215
+ if (opts.neverStore !== void 0) {
12216
+ throw memgError(
12217
+ "invalid_input",
12218
+ "--never-store-add and --never-store contradict each other",
12219
+ 'Nothing was written. `--never-store` REPLACES the whole list and `--never-store-add` keeps it, so together they decide the same field twice. To widen the declaration: `--never-store-add "<the new word>"`. To set the list outright: `--never-store "<every word it should hold>"`.'
12220
+ );
12221
+ }
12222
+ if (opts.input !== void 0) {
12223
+ throw memgError(
12224
+ "invalid_input",
12225
+ "--never-store-add cannot be combined with --input",
12226
+ "Nothing was written. An `--input` document carries its own `claim.never_store`, and that field REPLACES the list \u2014 so the JSON and the flag would be answering the same question differently. Put the whole list in the JSON, or drop `--input` and widen with the flag."
12227
+ );
12228
+ }
12229
+ return parseNeverStore(opts.neverStoreAdd, "--never-store-add");
12230
+ }
12231
+ function widenedBoundary(declared, added, ref) {
12232
+ if (added === void 0) return void 0;
12233
+ const existing = [...declared ?? []];
12234
+ if (existing.length === 0) {
12235
+ throw memgError(
12236
+ "invalid_input",
12237
+ `${ref} declares no boundary to add to`,
12238
+ `Nothing was written. \`--never-store-add\` keeps the words a note already declares, and this one declares none. Start the declaration with the flag that sets it:
12239
+ memgineering revise ${ref} --claim "<what this brain must never hold>" --never-store "${added.join(", ")}"
12240
+ To see what this note says now: \`memgineering open ${ref}\``
12241
+ );
12242
+ }
12243
+ const fresh = added.filter((term) => !existing.some((word) => sameBoundaryWord(word, term)));
12244
+ if (fresh.length === 0) {
12245
+ const words = added.map((term) => `\`${term}\``).join(", ");
12246
+ throw memgError(
12247
+ "nothing_to_change",
12248
+ `${ref} already declares ${words} \u2014 nothing was written`,
12249
+ `Nothing was added and nothing was changed: every word passed is already on this note\u2019s list, so the write would have recorded an operation that moved nothing and an \`undo\` that reversed nothing.
12250
+ To see the declaration as it stands: \`memgineering open ${ref}\`
12251
+ To change what the note CONCLUDES without touching the words, leave \`--never-store-add\` off.`
12252
+ );
12253
+ }
12254
+ return [...existing, ...fresh];
12255
+ }
12256
+ function sameBoundaryWord(a, b) {
12257
+ const shape = (value) => value.normalize("NFC").replace(/\s+/gu, " ").trim().toLowerCase();
12258
+ return shape(a) === shape(b);
12259
+ }
12260
+ function missingWords(intended, stored) {
12261
+ return intended.filter((term) => !stored.some((word) => sameBoundaryWord(word, term)));
12262
+ }
12263
+ function declaredWords(note) {
12264
+ const raw = note.memory["never_store"];
12265
+ return Array.isArray(raw) ? raw.filter((term) => typeof term === "string") : [];
12266
+ }
12126
12267
  function boundaryIntent(raw) {
12127
12268
  if (raw === void 0) return void 0;
12128
12269
  return raw === false ? [] : parseNeverStore(raw);
@@ -12140,7 +12281,7 @@ function checkedTerms(terms2) {
12140
12281
  return checked.term;
12141
12282
  });
12142
12283
  }
12143
- async function buildCommitInput(entry, opts) {
12284
+ async function buildCommitInput(entry, opts, widened) {
12144
12285
  if (opts.input) {
12145
12286
  const raw = await readInput(opts.input);
12146
12287
  return {
@@ -12164,7 +12305,7 @@ async function buildCommitInput(entry, opts) {
12164
12305
  'Pass `--claim "<the new conclusion>"`, or `--input` with the whole change as JSON.'
12165
12306
  );
12166
12307
  }
12167
- const boundary = boundaryIntent(opts.neverStore);
12308
+ const boundary = widened ?? boundaryIntent(opts.neverStore);
12168
12309
  const action = opts.action;
12169
12310
  if (action !== "reinforce" && action !== "supersede" && action !== "conflict") {
12170
12311
  throw memgError(
@@ -12323,12 +12464,13 @@ function renderSummary(p) {
12323
12464
  function decisionCommand(decision) {
12324
12465
  return new Command7(decision).description(
12325
12466
  decision === "approve" ? "apply a proposal to the note \u2014 recorded in the ledger, reversible with undo" : "decline a proposal; the note is not touched"
12326
- ).argument("<id>", "proposal id, or enough of it to be unambiguous").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the proposal belongs to").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(async (id, opts) => {
12327
- const rationale = sanitizeRationale(opts.reason);
12328
- const where = await whichBrain(opts);
12467
+ ).argument("<id>", "proposal id, or enough of it to be unambiguous").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the proposal belongs to").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(async (id, opts, cmd) => {
12468
+ const merged = { ...cmd.parent?.opts() ?? {}, ...opts };
12469
+ const rationale = sanitizeRationale(merged.reason);
12470
+ const where = await whichBrain(merged);
12329
12471
  if (where.kind === "cloud") {
12330
12472
  const brain2 = await openCloudBrain(where);
12331
- const args = { id, ...opts.reason === void 0 ? {} : { reason: opts.reason } };
12473
+ const args = { id, ...merged.reason === void 0 ? {} : { reason: merged.reason } };
12332
12474
  if (decision === "reject") {
12333
12475
  const rejected = await cloudReject(brain2, args);
12334
12476
  renderRejection(rejected.proposal_id, rejected.state, rejected.target);
@@ -12346,7 +12488,7 @@ function decisionCommand(decision) {
12346
12488
  before: applied2.before,
12347
12489
  after: applied2.after
12348
12490
  },
12349
- opts.vault
12491
+ merged.vault
12350
12492
  );
12351
12493
  return;
12352
12494
  }
@@ -12421,7 +12563,7 @@ function decisionCommand(decision) {
12421
12563
  before: preview.before,
12422
12564
  after: preview.after
12423
12565
  },
12424
- opts.vault
12566
+ merged.vault
12425
12567
  );
12426
12568
  });
12427
12569
  }
@@ -12459,8 +12601,9 @@ function renderRejection(id, state, target) {
12459
12601
  });
12460
12602
  }
12461
12603
  function rebaseCommand() {
12462
- return new Command7("rebase").description("recompute a stale proposal against the note as it is now").argument("<id>", "proposal id, or enough of it to be unambiguous").option("--vault <path>", "which brain the proposal belongs to").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(async (id, opts) => {
12463
- const where = await whichBrain(opts);
12604
+ return new Command7("rebase").description("recompute a stale proposal against the note as it is now").argument("<id>", "proposal id, or enough of it to be unambiguous").option("--vault <path>", "which brain the proposal belongs to").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(async (id, opts, cmd) => {
12605
+ const merged = { ...cmd.parent?.opts() ?? {}, ...opts };
12606
+ const where = await whichBrain(merged);
12464
12607
  if (where.kind === "cloud") {
12465
12608
  const rebased = await cloudRebase(await openCloudBrain(where), { id });
12466
12609
  renderRebase(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memgineering",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "private": false,
5
5
  "description": "One memory for the AI you connect. Recall, remember, and revise a brain your agents share \u2014 stored in your own folder.",
6
6
  "license": "Apache-2.0",