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 +55 -0
- package/assets/MEMGINEERING.md +1 -1
- package/assets/memgineering-writing/SKILL.md +15 -6
- package/dist/index.js +166 -23
- package/package.json +1 -1
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
|
package/assets/MEMGINEERING.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: memgineering
|
|
3
3
|
description: Use whenever the user refers to something they told you before, asks what was decided, tells you something worth keeping, or settles something that should hold next time. The memory lives in their own folder and outlives this session; check it before answering from guesswork, and write to it when you learn something durable.
|
|
4
4
|
type: skill
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.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
|
-
|
|
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 "
|
|
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.
|
|
372
|
-
|
|
373
|
-
|
|
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 "<
|
|
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
|
-
|
|
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\`
|
|
11643
|
-
|
|
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
|
-
|
|
11805
|
-
|
|
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(
|
|
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
|
|
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
|
|
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: {
|
|
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
|
|
12328
|
-
const
|
|
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, ...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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