memgineering 0.7.5 → 0.9.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 +191 -0
- package/assets/MEMGINEERING.md +1 -1
- package/assets/memgineering-recall/SKILL.md +40 -7
- package/assets/memgineering-setup/SKILL.md +25 -0
- package/assets/memgineering-writing/SKILL.md +16 -5
- package/dist/index.js +1524 -300
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -15,6 +15,9 @@ import { join } from "path";
|
|
|
15
15
|
function brandHome() {
|
|
16
16
|
return process.env[BRAND.homeEnvVar] ?? join(homedir(), BRAND.homeDirName);
|
|
17
17
|
}
|
|
18
|
+
function agentHome() {
|
|
19
|
+
return process.env[BRAND.agentHomeEnvVar] ?? homedir();
|
|
20
|
+
}
|
|
18
21
|
var BRAND;
|
|
19
22
|
var init_brand = __esm({
|
|
20
23
|
"src/brand.ts"() {
|
|
@@ -28,6 +31,16 @@ var init_brand = __esm({
|
|
|
28
31
|
* real `~/.memgineering`.
|
|
29
32
|
*/
|
|
30
33
|
homeEnvVar: "MEMGINEERING_HOME",
|
|
34
|
+
/**
|
|
35
|
+
* Where `setup` installs its guidance — the AGENT tools' homes, not ours.
|
|
36
|
+
*
|
|
37
|
+
* A separate variable from `MEMGINEERING_HOME` because they are separate
|
|
38
|
+
* things: that one moves this tool's own state, and this one moves the files
|
|
39
|
+
* it writes into somebody else's `~/.claude` and `~/.codex`. Conflating them
|
|
40
|
+
* would mean anyone relocating their state dir silently stopped installing
|
|
41
|
+
* into their real agents.
|
|
42
|
+
*/
|
|
43
|
+
agentHomeEnvVar: "MEMGINEERING_AGENT_HOME",
|
|
31
44
|
/** Force `--json` framing without passing the flag (used by wrappers). */
|
|
32
45
|
jsonEnvVar: "MEMGINEERING_JSON",
|
|
33
46
|
/**
|
|
@@ -168,10 +181,32 @@ function quoteBlock(text, prefix = "> ") {
|
|
|
168
181
|
return stripControl(text).split("\n").map((line) => prefix + line).join("\n");
|
|
169
182
|
}
|
|
170
183
|
function oneLine(text, max) {
|
|
171
|
-
const flat =
|
|
184
|
+
const flat = oneLineWhole(text);
|
|
172
185
|
const chars = [...flat];
|
|
173
186
|
if (chars.length <= max) return flat;
|
|
174
|
-
|
|
187
|
+
const budget = Math.max(1, max - 1);
|
|
188
|
+
let shown = chars.slice(0, budget).join("").trimEnd();
|
|
189
|
+
const next = chars[budget];
|
|
190
|
+
const cutsWord = next !== void 0 && /\S/.test(next) && /\S/.test(shown.at(-1) ?? "");
|
|
191
|
+
const lastSpace = shown.lastIndexOf(" ");
|
|
192
|
+
if (cutsWord && lastSpace >= Math.floor(budget * 0.6))
|
|
193
|
+
shown = shown.slice(0, lastSpace).trimEnd();
|
|
194
|
+
return `${shown}\u2026`;
|
|
195
|
+
}
|
|
196
|
+
function oneLineWhole(text) {
|
|
197
|
+
return stripControl(text).replace(/\s+/g, " ").trim();
|
|
198
|
+
}
|
|
199
|
+
function middleLine(text, max) {
|
|
200
|
+
const chars = [...oneLineWhole(text)];
|
|
201
|
+
if (chars.length <= max) return chars.join("");
|
|
202
|
+
if (max <= 1) return "\u2026";
|
|
203
|
+
const budget = max - 1;
|
|
204
|
+
const headLength = Math.max(1, Math.floor(budget * 0.4));
|
|
205
|
+
const tailLength = Math.max(1, budget - headLength);
|
|
206
|
+
return `${chars.slice(0, headLength).join("")}\u2026${chars.slice(-tailLength).join("")}`;
|
|
207
|
+
}
|
|
208
|
+
function printBlock(text) {
|
|
209
|
+
printHuman(stripControl(text));
|
|
175
210
|
}
|
|
176
211
|
function progress(text) {
|
|
177
212
|
if (isInteractive()) process.stderr.write(kleur.gray(` ${text}
|
|
@@ -187,13 +222,23 @@ function undoHint(opId, vault) {
|
|
|
187
222
|
function quoteArg(value) {
|
|
188
223
|
return /^[A-Za-z0-9._\/-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
189
224
|
}
|
|
190
|
-
var jsonMode, CONTROL_INTRODUCER, c;
|
|
225
|
+
var jsonMode, CONTROL_INTRODUCER, WIDTH, c;
|
|
191
226
|
var init_ui = __esm({
|
|
192
227
|
"src/ui.ts"() {
|
|
193
228
|
"use strict";
|
|
194
229
|
init_brand();
|
|
195
230
|
jsonMode = false;
|
|
196
231
|
CONTROL_INTRODUCER = /[\u001b\u009b\u009d]/;
|
|
232
|
+
WIDTH = {
|
|
233
|
+
/** A brain name, a note title — a heading-shaped thing. */
|
|
234
|
+
title: 72,
|
|
235
|
+
/** A vault path. Longer, because the tail is the part that identifies it. */
|
|
236
|
+
path: 120,
|
|
237
|
+
/** An id or an `open:` handle. */
|
|
238
|
+
handle: 128,
|
|
239
|
+
/** A claim, a summary, a server hint — a sentence rather than a label. */
|
|
240
|
+
text: 200
|
|
241
|
+
};
|
|
197
242
|
c = kleur;
|
|
198
243
|
}
|
|
199
244
|
});
|
|
@@ -221,11 +266,12 @@ var init_errors = __esm({
|
|
|
221
266
|
|
|
222
267
|
// ../../packages/memory-engine/src/schema.ts
|
|
223
268
|
import { z } from "zod";
|
|
224
|
-
var MappingModeSchema, ProvenanceSchema, MemoryStatusSchema, ClaimConfidenceSchema, MemoryKindSchema, ClaimSchema, MemorySchema, RelationTypeSchema, RelationSourceSchema, RelationSchema;
|
|
269
|
+
var MappingModeSchema, ScopeSourceSchema, ProvenanceSchema, MemoryStatusSchema, ClaimConfidenceSchema, MemoryKindSchema, ClaimSchema, MemorySchema, RelationTypeSchema, RelationSourceSchema, RelationSchema;
|
|
225
270
|
var init_schema = __esm({
|
|
226
271
|
"../../packages/memory-engine/src/schema.ts"() {
|
|
227
272
|
"use strict";
|
|
228
273
|
MappingModeSchema = z.enum(["explicit", "heuristic"]);
|
|
274
|
+
ScopeSourceSchema = z.enum(["explicit", "path", "none"]);
|
|
229
275
|
ProvenanceSchema = z.object({
|
|
230
276
|
/** Vault-relative path of the note this was mapped from. */
|
|
231
277
|
sourcePath: z.string().min(1),
|
|
@@ -238,7 +284,9 @@ var init_schema = __esm({
|
|
|
238
284
|
* here would be the engine learning one backend's internals.
|
|
239
285
|
*/
|
|
240
286
|
sourceRevision: z.unknown(),
|
|
241
|
-
mapping: MappingModeSchema
|
|
287
|
+
mapping: MappingModeSchema,
|
|
288
|
+
/** Field-level provenance needed before scope may lower a semantic threshold. */
|
|
289
|
+
scopeSource: ScopeSourceSchema.optional()
|
|
242
290
|
});
|
|
243
291
|
MemoryStatusSchema = z.enum(["active", "superseded", "retired"]);
|
|
244
292
|
ClaimConfidenceSchema = z.enum(["stated", "observed", "inferred"]);
|
|
@@ -618,6 +666,8 @@ function mapNoteToMemory(input) {
|
|
|
618
666
|
const ns = explicit ? nsRaw : {};
|
|
619
667
|
const id = asString(ns["id"]) ?? path;
|
|
620
668
|
const title = asString(ns["title"]) ?? asString(frontmatter?.["title"]) ?? h1Of(outline.intro) ?? titleFromPath(path);
|
|
669
|
+
const declaredScope = asString(ns["scope"]);
|
|
670
|
+
const scope = declaredScope ?? scopeFromPath(path);
|
|
621
671
|
const summary = asString(ns["summary"]) ?? outline.description ?? firstParagraph(outline.intro) ?? firstSectionParagraph(outline.sections) ?? null;
|
|
622
672
|
const memory = {
|
|
623
673
|
id,
|
|
@@ -632,7 +682,7 @@ function mapNoteToMemory(input) {
|
|
|
632
682
|
})(),
|
|
633
683
|
title,
|
|
634
684
|
summary,
|
|
635
|
-
scope
|
|
685
|
+
scope,
|
|
636
686
|
current_claim: asString(ns["current_claim"]),
|
|
637
687
|
status: statusOf(ns["status"], issues),
|
|
638
688
|
// Only the author's own `binding: true`. Never inferred: guessing that a
|
|
@@ -650,7 +700,8 @@ function mapNoteToMemory(input) {
|
|
|
650
700
|
provenance: {
|
|
651
701
|
sourcePath: path,
|
|
652
702
|
sourceRevision: revision,
|
|
653
|
-
mapping: explicit ? "explicit" : "heuristic"
|
|
703
|
+
mapping: explicit ? "explicit" : "heuristic",
|
|
704
|
+
scopeSource: declaredScope !== null ? "explicit" : scope === null ? "none" : "path"
|
|
654
705
|
}
|
|
655
706
|
};
|
|
656
707
|
const claims = [];
|
|
@@ -764,7 +815,7 @@ var init_cache_io = __esm({
|
|
|
764
815
|
"../../packages/memory-engine/src/view/cache-io.ts"() {
|
|
765
816
|
"use strict";
|
|
766
817
|
init_schema();
|
|
767
|
-
SCHEMA_VERSION =
|
|
818
|
+
SCHEMA_VERSION = 6;
|
|
768
819
|
POLICY_VERSION = 1;
|
|
769
820
|
CacheEntrySchema = z2.object({
|
|
770
821
|
path: z2.string().min(1),
|
|
@@ -1371,18 +1422,40 @@ var init_conflicts = __esm({
|
|
|
1371
1422
|
});
|
|
1372
1423
|
|
|
1373
1424
|
// ../../packages/memory-engine/src/recall/search.ts
|
|
1425
|
+
function splitMixedScript(token) {
|
|
1426
|
+
return token.split(LATIN_HANGUL_BOUNDARY).filter((part) => part.length > 0);
|
|
1427
|
+
}
|
|
1374
1428
|
function terms(text) {
|
|
1375
|
-
return text.normalize("NFC").toLowerCase().
|
|
1429
|
+
return [...text.normalize("NFC").toLowerCase().matchAll(TOKEN)].flatMap(
|
|
1430
|
+
(match) => splitMixedScript(match[0])
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
function indexTerms(text) {
|
|
1434
|
+
return [...text.normalize("NFC").toLowerCase().matchAll(TOKEN)].flatMap((match) => {
|
|
1435
|
+
const token = match[0];
|
|
1436
|
+
const exact = splitMixedScript(token);
|
|
1437
|
+
if (!HAS_COMPONENT_SEPARATOR.test(token)) return exact;
|
|
1438
|
+
const components = token.split(COMPONENT_SEPARATOR).filter((part) => part.length > 0).flatMap(splitMixedScript);
|
|
1439
|
+
return [...exact, ...components];
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
function exactOnly(term) {
|
|
1443
|
+
return HAS_COMPONENT_SEPARATOR.test(term) || new RegExp("\\p{N}", "u").test(term);
|
|
1376
1444
|
}
|
|
1377
1445
|
function startsWithFieldTerm(query, fieldTerms) {
|
|
1378
|
-
if (query.length <= SUBSTRING_MIN) return false;
|
|
1446
|
+
if (query.length <= SUBSTRING_MIN || !HANGUL.test(query)) return false;
|
|
1379
1447
|
for (const f of fieldTerms) {
|
|
1380
|
-
if (f.length >= SUBSTRING_MIN && f.length < query.length && query.startsWith(f))
|
|
1448
|
+
if (HANGUL.test(f) && f.length >= SUBSTRING_MIN && f.length < query.length && query.startsWith(f))
|
|
1449
|
+
return true;
|
|
1381
1450
|
}
|
|
1382
1451
|
return false;
|
|
1383
1452
|
}
|
|
1453
|
+
function particleSuffixedFieldTerm(query, firstFieldTerm) {
|
|
1454
|
+
if (!ONE_SYLLABLE_TOPIC_TERMS.has(query)) return false;
|
|
1455
|
+
return PARTICLES.some((particle) => firstFieldTerm === `${query}${particle}`);
|
|
1456
|
+
}
|
|
1384
1457
|
function variantsOf(term) {
|
|
1385
|
-
if (!HANGUL.test(term)) return [term];
|
|
1458
|
+
if (exactOnly(term) || !HANGUL.test(term)) return [term];
|
|
1386
1459
|
for (const particle of PARTICLES) {
|
|
1387
1460
|
if (!term.endsWith(particle)) continue;
|
|
1388
1461
|
const stem = term.slice(0, -particle.length);
|
|
@@ -1390,19 +1463,22 @@ function variantsOf(term) {
|
|
|
1390
1463
|
}
|
|
1391
1464
|
return [term];
|
|
1392
1465
|
}
|
|
1393
|
-
function fieldScore(queryTerms, field, weight) {
|
|
1466
|
+
function fieldScore(queryTerms, field, weight, allowLeadingOneSyllable = false) {
|
|
1394
1467
|
if (field === null || field === "") return 0;
|
|
1395
1468
|
const normalized = field.normalize("NFC").toLowerCase();
|
|
1396
|
-
const
|
|
1469
|
+
const orderedFieldTerms = indexTerms(field);
|
|
1470
|
+
const fieldTerms = new Set(orderedFieldTerms);
|
|
1397
1471
|
let score = 0;
|
|
1398
|
-
for (const q of queryTerms) {
|
|
1472
|
+
for (const [queryIndex, q] of queryTerms.entries()) {
|
|
1399
1473
|
let best = 0;
|
|
1400
1474
|
for (const variant of variantsOf(q)) {
|
|
1401
1475
|
if (fieldTerms.has(variant)) {
|
|
1402
1476
|
best = Math.max(best, weight);
|
|
1403
|
-
} else if (
|
|
1477
|
+
} else if (allowLeadingOneSyllable && queryIndex === 0 && particleSuffixedFieldTerm(variant, orderedFieldTerms[0])) {
|
|
1478
|
+
best = Math.max(best, weight);
|
|
1479
|
+
} else if (!exactOnly(variant) && variant.length >= SUBSTRING_MIN && normalized.includes(variant)) {
|
|
1404
1480
|
best = Math.max(best, weight / 2);
|
|
1405
|
-
} else if (startsWithFieldTerm(variant, fieldTerms)) {
|
|
1481
|
+
} else if (!exactOnly(variant) && startsWithFieldTerm(variant, fieldTerms)) {
|
|
1406
1482
|
best = Math.max(best, weight / 2);
|
|
1407
1483
|
}
|
|
1408
1484
|
}
|
|
@@ -1415,7 +1491,7 @@ function searchMemories(memories, query) {
|
|
|
1415
1491
|
if (q.length === 0) return [];
|
|
1416
1492
|
const scored = [];
|
|
1417
1493
|
for (const memory of memories) {
|
|
1418
|
-
let score = fieldScore(q, memory.title, WEIGHTS.title);
|
|
1494
|
+
let score = fieldScore(q, memory.title, WEIGHTS.title, true);
|
|
1419
1495
|
for (const alias of memory.aliases) score += fieldScore(q, alias, WEIGHTS.alias);
|
|
1420
1496
|
for (const entity of memory.entities) score += fieldScore(q, entity, WEIGHTS.entity);
|
|
1421
1497
|
score += fieldScore(q, memory.summary, WEIGHTS.summary);
|
|
@@ -1426,11 +1502,15 @@ function searchMemories(memories, query) {
|
|
|
1426
1502
|
(a, b) => b.score !== a.score ? b.score - a.score : a.memory.id < b.memory.id ? -1 : 1
|
|
1427
1503
|
);
|
|
1428
1504
|
}
|
|
1429
|
-
var WEIGHTS, SUBSTRING_MIN, PARTICLES, HANGUL;
|
|
1505
|
+
var WEIGHTS, TOKEN, COMPONENT_SEPARATOR, HAS_COMPONENT_SEPARATOR, LATIN_HANGUL_BOUNDARY, SUBSTRING_MIN, PARTICLES, HANGUL, ONE_SYLLABLE_TOPIC_TERMS;
|
|
1430
1506
|
var init_search = __esm({
|
|
1431
1507
|
"../../packages/memory-engine/src/recall/search.ts"() {
|
|
1432
1508
|
"use strict";
|
|
1433
1509
|
WEIGHTS = { title: 6, alias: 4, entity: 3, summary: 2, scope: 1 };
|
|
1510
|
+
TOKEN = /[./]?[\p{L}\p{N}]+(?:[._/-][\p{L}\p{N}]+)*/gu;
|
|
1511
|
+
COMPONENT_SEPARATOR = /[._/-]+/u;
|
|
1512
|
+
HAS_COMPONENT_SEPARATOR = /[._/-]/u;
|
|
1513
|
+
LATIN_HANGUL_BOUNDARY = new RegExp("(?<=\\p{Script=Latin})(?=\\p{Script=Hangul})|(?<=\\p{Script=Hangul})(?=\\p{Script=Latin})", "u");
|
|
1434
1514
|
SUBSTRING_MIN = 2;
|
|
1435
1515
|
PARTICLES = [
|
|
1436
1516
|
"\uC774\uB77C\uB3C4",
|
|
@@ -1468,6 +1548,7 @@ var init_search = __esm({
|
|
|
1468
1548
|
"\uAED8"
|
|
1469
1549
|
];
|
|
1470
1550
|
HANGUL = /[\uac00-\ud7a3]/;
|
|
1551
|
+
ONE_SYLLABLE_TOPIC_TERMS = /* @__PURE__ */ new Set(["\uD0A4"]);
|
|
1471
1552
|
}
|
|
1472
1553
|
});
|
|
1473
1554
|
|
|
@@ -1485,7 +1566,7 @@ var init_chunks = __esm({
|
|
|
1485
1566
|
avgLength;
|
|
1486
1567
|
constructor(refs) {
|
|
1487
1568
|
for (const ref of refs) {
|
|
1488
|
-
const tokens =
|
|
1569
|
+
const tokens = indexTerms(ref.text);
|
|
1489
1570
|
if (tokens.length === 0) continue;
|
|
1490
1571
|
const counts = /* @__PURE__ */ new Map();
|
|
1491
1572
|
for (const token of tokens) counts.set(token, (counts.get(token) ?? 0) + 1);
|
|
@@ -1547,6 +1628,34 @@ var init_chunks = __esm({
|
|
|
1547
1628
|
function isRecallDepth(value) {
|
|
1548
1629
|
return typeof value === "string" && RECALL_DEPTHS.includes(value);
|
|
1549
1630
|
}
|
|
1631
|
+
function isStrongDecisionMarker(term) {
|
|
1632
|
+
return /^(?:정하|정했|정한|정해|결정하|결정했|결정한|합의하|합의했|합의한|지키|지켜|따르|골랐|고른)/u.test(
|
|
1633
|
+
term
|
|
1634
|
+
) || /^(?:decide[ds]?|deciding|settle[ds]?|settling|agree[ds]?|agreeing|choose[ds]?|choosing|chose|chosen|follow(?:s|ed|ing)?)$/u.test(
|
|
1635
|
+
term
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
function variantsInclude(term, candidates) {
|
|
1639
|
+
return variantsOf(term).some((variant) => candidates.has(variant));
|
|
1640
|
+
}
|
|
1641
|
+
function isDecisionContext(term) {
|
|
1642
|
+
return variantsInclude(term, DECISION_CONTEXT) || term.startsWith("\uC720\uD6A8");
|
|
1643
|
+
}
|
|
1644
|
+
function shapeRecallQuery(query) {
|
|
1645
|
+
const queryTerms = terms(query);
|
|
1646
|
+
const strong = queryTerms.some(isStrongDecisionMarker);
|
|
1647
|
+
const noun = queryTerms.some((term) => variantsInclude(term, DECISION_NOUNS));
|
|
1648
|
+
const contextual = queryTerms.some(isDecisionContext);
|
|
1649
|
+
const decisionIntent = strong || noun && contextual;
|
|
1650
|
+
if (!decisionIntent) return { lexical: query, decisionIntent: false };
|
|
1651
|
+
const topical = queryTerms.filter(
|
|
1652
|
+
(term) => !isStrongDecisionMarker(term) && !variantsInclude(term, DECISION_NOUNS) && !variantsInclude(term, DECISION_FILLERS) && !term.startsWith("\uC720\uD6A8")
|
|
1653
|
+
);
|
|
1654
|
+
return {
|
|
1655
|
+
lexical: topical.length === 0 ? query : topical.join(" "),
|
|
1656
|
+
decisionIntent
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1550
1659
|
function freshnessOf(memory, claims, now) {
|
|
1551
1660
|
if (memory.status === "superseded") return "superseded";
|
|
1552
1661
|
if (memory.status === "retired") return "retired";
|
|
@@ -1573,12 +1682,14 @@ function recall(entries, query, opts = {}) {
|
|
|
1573
1682
|
const claimsById = new Map(inScope.map((e) => [e.memory.id, e.claims]));
|
|
1574
1683
|
const relations = inScope.flatMap((e) => e.relations);
|
|
1575
1684
|
const allMemories = inScope.map((e) => e.memory);
|
|
1576
|
-
const
|
|
1685
|
+
const allMemoryIds = new Set(allMemories.map((memory) => memory.id));
|
|
1686
|
+
const queryShape = shapeRecallQuery(query);
|
|
1687
|
+
const cardHits = searchMemories(allMemories, queryShape.lexical);
|
|
1577
1688
|
const chunkHits = depth === "exact" ? [] : new ChunkIndex(
|
|
1578
1689
|
inScope.flatMap(
|
|
1579
1690
|
(e) => (e.chunks ?? []).map((c2) => ({ memoryId: e.memory.id, label: c2.label, text: c2.text }))
|
|
1580
1691
|
)
|
|
1581
|
-
).search(
|
|
1692
|
+
).search(queryShape.lexical);
|
|
1582
1693
|
const semanticHits = depth === "exact" ? [] : opts.semantic ?? [];
|
|
1583
1694
|
const scoreById = /* @__PURE__ */ new Map();
|
|
1584
1695
|
for (const hit of cardHits) scoreById.set(hit.memory.id, hit.score);
|
|
@@ -1594,14 +1705,26 @@ function recall(entries, query, opts = {}) {
|
|
|
1594
1705
|
const memory = memoryById.get(id);
|
|
1595
1706
|
return memory === void 0 ? [] : [{ memory, score }];
|
|
1596
1707
|
}).sort((a, b) => b.score !== a.score ? b.score - a.score : a.memory.id < b.memory.id ? -1 : 1);
|
|
1597
|
-
const
|
|
1708
|
+
const authoritativeSupersedes = queryShape.decisionIntent ? relations.filter(
|
|
1709
|
+
(relation) => relation.type === "supersedes" && relation.source === "explicit" && relation.weight > 0 && allMemoryIds.has(relation.from) && allMemoryIds.has(relation.to)
|
|
1710
|
+
) : [];
|
|
1711
|
+
const supersededByAnother = new Set(authoritativeSupersedes.map((relation) => relation.to));
|
|
1712
|
+
const supersedingIds = new Set(
|
|
1713
|
+
authoritativeSupersedes.map((relation) => relation.from).filter((id) => !supersededByAnother.has(id))
|
|
1714
|
+
);
|
|
1715
|
+
const queryTerms = Math.max(1, terms(queryShape.lexical).length);
|
|
1598
1716
|
const scored = hits.map((hit) => ({
|
|
1599
1717
|
memory: hit.memory,
|
|
1600
1718
|
currency: CURRENCY_RANK[freshnessOf(hit.memory, claimsById.get(hit.memory.id) ?? [], now)],
|
|
1601
|
-
score: hit.score + boostFor(hit.memory) / queryTerms
|
|
1719
|
+
score: hit.score + boostFor(hit.memory) / queryTerms,
|
|
1720
|
+
// Only a final tie-break, and only when the question asks for a standing
|
|
1721
|
+
// decision. A valid outgoing supersedes edge says this note is the declared
|
|
1722
|
+
// replacement; it does not say an off-topic replacement beats a better
|
|
1723
|
+
// topical match.
|
|
1724
|
+
superseding: supersedingIds.has(hit.memory.id)
|
|
1602
1725
|
}));
|
|
1603
1726
|
const ranked = scored.sort(
|
|
1604
|
-
(a, b) => a.currency !== b.currency ? a.currency - b.currency : b.score !== a.score ? b.score - a.score : a.memory.id < b.memory.id ? -1 : 1
|
|
1727
|
+
(a, b) => a.currency !== b.currency ? a.currency - b.currency : b.score !== a.score ? b.score - a.score : a.superseding !== b.superseding ? a.superseding ? -1 : 1 : a.memory.id < b.memory.id ? -1 : 1
|
|
1605
1728
|
);
|
|
1606
1729
|
const selected = ranked.slice(0, limit).map((s) => s.memory);
|
|
1607
1730
|
const byId = new Map(allMemories.map((m) => [m.id, m]));
|
|
@@ -1686,7 +1809,7 @@ function depthOf(value) {
|
|
|
1686
1809
|
function boostFor(memory) {
|
|
1687
1810
|
return (memory.binding === true ? BINDING_BOOST : 0) + (memory.kind === void 0 ? 0 : KIND_BOOST[memory.kind] ?? 0);
|
|
1688
1811
|
}
|
|
1689
|
-
var RECALL_DEPTHS, RecallOptionError, DEFAULTS, BODY_WEIGHT, SEMANTIC_WEIGHT, BINDING_BOOST, KIND_BOOST, CURRENCY_RANK, WIDEST_ESTIMATE;
|
|
1812
|
+
var RECALL_DEPTHS, RecallOptionError, DEFAULTS, BODY_WEIGHT, SEMANTIC_WEIGHT, BINDING_BOOST, KIND_BOOST, DECISION_NOUNS, DECISION_CONTEXT, DECISION_FILLERS, CURRENCY_RANK, WIDEST_ESTIMATE;
|
|
1690
1813
|
var init_planner = __esm({
|
|
1691
1814
|
"../../packages/memory-engine/src/recall/planner.ts"() {
|
|
1692
1815
|
"use strict";
|
|
@@ -1716,6 +1839,60 @@ var init_planner = __esm({
|
|
|
1716
1839
|
decision: BINDING_BOOST / 2,
|
|
1717
1840
|
preference: BINDING_BOOST / 2
|
|
1718
1841
|
};
|
|
1842
|
+
DECISION_NOUNS = /* @__PURE__ */ new Set([
|
|
1843
|
+
"\uACB0\uC815",
|
|
1844
|
+
"\uADDC\uCE59",
|
|
1845
|
+
"\uB8F0",
|
|
1846
|
+
"\uD569\uC758",
|
|
1847
|
+
"decision",
|
|
1848
|
+
"decisions",
|
|
1849
|
+
"rule",
|
|
1850
|
+
"rules",
|
|
1851
|
+
"agreement"
|
|
1852
|
+
]);
|
|
1853
|
+
DECISION_CONTEXT = /* @__PURE__ */ new Set([
|
|
1854
|
+
"\uC9C0\uAE08",
|
|
1855
|
+
"\uD604\uC7AC",
|
|
1856
|
+
"\uC544\uC9C1",
|
|
1857
|
+
"\uC608\uC804\uC5D0",
|
|
1858
|
+
"\uBB58",
|
|
1859
|
+
"\uBB50",
|
|
1860
|
+
"\uBB34\uC5C7",
|
|
1861
|
+
"\uC5B4\uB5BB\uAC8C",
|
|
1862
|
+
"what",
|
|
1863
|
+
"which",
|
|
1864
|
+
"did",
|
|
1865
|
+
"now",
|
|
1866
|
+
"current",
|
|
1867
|
+
"currently",
|
|
1868
|
+
"still"
|
|
1869
|
+
]);
|
|
1870
|
+
DECISION_FILLERS = /* @__PURE__ */ new Set([
|
|
1871
|
+
...DECISION_CONTEXT,
|
|
1872
|
+
"\uB0B4\uAC00",
|
|
1873
|
+
"\uC6B0\uB9AC\uAC00",
|
|
1874
|
+
"\uC6B0\uB9AC",
|
|
1875
|
+
"\uB300\uD574",
|
|
1876
|
+
"\uAD00\uD574",
|
|
1877
|
+
"\uD558\uB294",
|
|
1878
|
+
"\uD588\uB358",
|
|
1879
|
+
"\uD560",
|
|
1880
|
+
"\uAC83",
|
|
1881
|
+
"\uBC18\uB4DC\uC2DC",
|
|
1882
|
+
"\uC911",
|
|
1883
|
+
"about",
|
|
1884
|
+
"on",
|
|
1885
|
+
"for",
|
|
1886
|
+
"to",
|
|
1887
|
+
"do",
|
|
1888
|
+
"does",
|
|
1889
|
+
"is",
|
|
1890
|
+
"the",
|
|
1891
|
+
"we",
|
|
1892
|
+
"i",
|
|
1893
|
+
"our",
|
|
1894
|
+
"my"
|
|
1895
|
+
]);
|
|
1719
1896
|
CURRENCY_RANK = {
|
|
1720
1897
|
current: 0,
|
|
1721
1898
|
expired: 1,
|
|
@@ -1958,18 +2135,21 @@ function detectConsolidation(entries, opts = {}) {
|
|
|
1958
2135
|
scanned: 0,
|
|
1959
2136
|
truncated: false
|
|
1960
2137
|
};
|
|
1961
|
-
const
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
//
|
|
2138
|
+
const semanticExcluded = semanticGlobalExclusions(live, []);
|
|
2139
|
+
const semantic = opts.semantic === void 0 ? [] : boundedSemanticSimilarities(live, budget, opts.semantic, semanticExcluded);
|
|
2140
|
+
const titles = similarTitles(live, budget);
|
|
2141
|
+
const context = sharedContext(live, budget);
|
|
2142
|
+
const scored = [
|
|
2143
|
+
// Declared edges enumerate no pairs and spend no budget. Supersession is
|
|
2144
|
+
// first because it names both the winner and the still-active loser.
|
|
2145
|
+
...declaredSupersessions(live),
|
|
1967
2146
|
...declaredContradictions(live),
|
|
1968
|
-
...kindPromotions(live)
|
|
2147
|
+
...kindPromotions(live),
|
|
2148
|
+
...semantic,
|
|
2149
|
+
...context,
|
|
2150
|
+
...titles
|
|
1969
2151
|
];
|
|
1970
|
-
out
|
|
1971
|
-
(a, b) => CONSOLIDATION_RULES.indexOf(a.rule) - CONSOLIDATION_RULES.indexOf(b.rule) || a.memory_id.localeCompare(b.memory_id) || (a.other_id ?? "").localeCompare(b.other_id ?? "")
|
|
1972
|
-
);
|
|
2152
|
+
const out = rankCandidates(scored);
|
|
1973
2153
|
return {
|
|
1974
2154
|
candidates: out.slice(0, opts.limit ?? DEFAULT_LIMIT),
|
|
1975
2155
|
scanned: budget.scanned,
|
|
@@ -1991,24 +2171,63 @@ function overlap(a, b) {
|
|
|
1991
2171
|
}
|
|
1992
2172
|
function similarTitles(entries, budget) {
|
|
1993
2173
|
const termSets = entries.map((e) => titleTerms(e.memory.title));
|
|
2174
|
+
const cyclicKeys = entries.map((entry) => cyclicTitleKey(entry.memory.title));
|
|
1994
2175
|
const buckets = /* @__PURE__ */ new Map();
|
|
2176
|
+
const byCyclicTitle = /* @__PURE__ */ new Map();
|
|
1995
2177
|
termSets.forEach((set, i) => {
|
|
2178
|
+
const cyclicKey = cyclicKeys[i];
|
|
2179
|
+
if (cyclicKey !== null && cyclicKey !== void 0) addToBucket(byCyclicTitle, cyclicKey, i);
|
|
1996
2180
|
for (const term of set) {
|
|
1997
2181
|
const bucket = buckets.get(term);
|
|
1998
2182
|
if (bucket) bucket.push(i);
|
|
1999
2183
|
else buckets.set(term, [i]);
|
|
2000
2184
|
}
|
|
2001
2185
|
});
|
|
2002
|
-
const out =
|
|
2186
|
+
const out = cyclicTitleSpanningCandidates(entries, byCyclicTitle);
|
|
2003
2187
|
for (const [i, j] of pairsFrom(buckets, budget)) {
|
|
2004
2188
|
const score = overlap(termSets[i], termSets[j]);
|
|
2005
2189
|
if (score < TITLE_OVERLAP) continue;
|
|
2006
|
-
out.push(
|
|
2007
|
-
pairCandidate("similar_title", entries[i], entries[j], {
|
|
2190
|
+
out.push({
|
|
2191
|
+
candidate: pairCandidate("similar_title", entries[i], entries[j], {
|
|
2008
2192
|
evidence: `titles overlap ${Math.round(score * 100)}%: "${entries[i].memory.title}" / "${entries[j].memory.title}"`,
|
|
2009
2193
|
suggested_action: "supersede"
|
|
2010
|
-
})
|
|
2194
|
+
}),
|
|
2195
|
+
// Exact lexical identity is a little stronger than the broad
|
|
2196
|
+
// scope+entity rule. This changes only ordering/evidence selection — the
|
|
2197
|
+
// same pair was already detected at the 2/3 threshold.
|
|
2198
|
+
confidence: score === 1 && cyclicKeys[i] !== null && cyclicKeys[i] === cyclicKeys[j] ? 2.01 : 1 + score
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
return out;
|
|
2202
|
+
}
|
|
2203
|
+
function cyclicTitleKey(title) {
|
|
2204
|
+
const sequence = terms(title).map((term) => term.normalize("NFC").toLowerCase());
|
|
2205
|
+
if (sequence.length === 0) return null;
|
|
2206
|
+
if (sequence.length < 3) return sequence.join("\0");
|
|
2207
|
+
let best = null;
|
|
2208
|
+
for (let offset = 0; offset < sequence.length; offset += 1) {
|
|
2209
|
+
const key2 = [...sequence.slice(offset), ...sequence.slice(0, offset)].join("\0");
|
|
2210
|
+
if (best === null || key2 < best) best = key2;
|
|
2211
|
+
}
|
|
2212
|
+
return best;
|
|
2213
|
+
}
|
|
2214
|
+
function cyclicTitleSpanningCandidates(entries, buckets) {
|
|
2215
|
+
const out = [];
|
|
2216
|
+
for (const indices of buckets.values()) {
|
|
2217
|
+
if (indices.length < 2) continue;
|
|
2218
|
+
const ordered = [...indices].sort(
|
|
2219
|
+
(a, b) => entries[a].memory.id.localeCompare(entries[b].memory.id) || entries[a].path.localeCompare(entries[b].path)
|
|
2011
2220
|
);
|
|
2221
|
+
const anchor = ordered[0];
|
|
2222
|
+
for (const other of ordered.slice(1)) {
|
|
2223
|
+
out.push({
|
|
2224
|
+
candidate: pairCandidate("similar_title", entries[anchor], entries[other], {
|
|
2225
|
+
evidence: `titles overlap 100%: "${entries[anchor].memory.title}" / "${entries[other].memory.title}"`,
|
|
2226
|
+
suggested_action: "supersede"
|
|
2227
|
+
}),
|
|
2228
|
+
confidence: 2.01
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2012
2231
|
}
|
|
2013
2232
|
return out;
|
|
2014
2233
|
}
|
|
@@ -2038,12 +2257,221 @@ function sharedContext(entries, budget) {
|
|
|
2038
2257
|
}
|
|
2039
2258
|
for (const [i, j] of pairsFrom(byEntity, budget)) {
|
|
2040
2259
|
const shared = [...entitySets.get(i)].filter((e) => entitySets.get(j).has(e));
|
|
2041
|
-
out.push(
|
|
2042
|
-
pairCandidate("shared_context", entries[i], entries[j], {
|
|
2260
|
+
out.push({
|
|
2261
|
+
candidate: pairCandidate("shared_context", entries[i], entries[j], {
|
|
2043
2262
|
evidence: `both in scope "${scope}" and both about: ${shared.sort().join(", ")}`,
|
|
2044
2263
|
suggested_action: "supersede"
|
|
2045
|
-
})
|
|
2046
|
-
|
|
2264
|
+
}),
|
|
2265
|
+
confidence: 2
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
return out;
|
|
2270
|
+
}
|
|
2271
|
+
function boundedSemanticSimilarities(entries, budget, source, globalExcluded) {
|
|
2272
|
+
const semanticBudget = {
|
|
2273
|
+
left: Math.min(budget.left, SEMANTIC_PAIR_BUDGET),
|
|
2274
|
+
scanned: 0,
|
|
2275
|
+
truncated: false
|
|
2276
|
+
};
|
|
2277
|
+
const out = semanticSimilarities(entries, semanticBudget, source, globalExcluded);
|
|
2278
|
+
budget.left -= semanticBudget.scanned;
|
|
2279
|
+
budget.scanned += semanticBudget.scanned;
|
|
2280
|
+
budget.truncated ||= semanticBudget.truncated;
|
|
2281
|
+
return out;
|
|
2282
|
+
}
|
|
2283
|
+
function semanticSimilarities(entries, budget, source, globalExcluded) {
|
|
2284
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
2285
|
+
const scopes = entries.map(declaredSemanticScope);
|
|
2286
|
+
const entities = entries.map(
|
|
2287
|
+
(entry) => new Set(entry.memory.entities.map((entity) => normalizeContext(entity)).filter(isPresent))
|
|
2288
|
+
);
|
|
2289
|
+
entries.forEach((entry, i) => {
|
|
2290
|
+
if (!source.has(entry.memory.id)) return;
|
|
2291
|
+
const scope = scopes[i];
|
|
2292
|
+
if (scope !== null) addToBucket(buckets, `scope:${scope}`, i);
|
|
2293
|
+
for (const entity of entities[i]) addToBucket(buckets, `entity:${entity}`, i);
|
|
2294
|
+
if (scope === null && entities[i].size === 0 && !globalExcluded.has(entry.memory.id)) {
|
|
2295
|
+
addToBucket(buckets, "global:unanchored", i);
|
|
2296
|
+
}
|
|
2297
|
+
});
|
|
2298
|
+
const observations = [];
|
|
2299
|
+
const byIndex = /* @__PURE__ */ new Map();
|
|
2300
|
+
for (const [i, j] of pairsFrom(buckets, budget)) {
|
|
2301
|
+
const sharedScope = scopes[i] !== null && scopes[i] === scopes[j] ? scopes[i] : null;
|
|
2302
|
+
const sharedEntities = [...entities[i]].filter((entity) => entities[j].has(entity)).sort();
|
|
2303
|
+
const contextual = sharedScope !== null || sharedEntities.length > 0;
|
|
2304
|
+
const raw = source.compare(entries[i].memory.id, entries[j].memory.id);
|
|
2305
|
+
if (raw === null || raw === void 0 || !Number.isFinite(raw)) continue;
|
|
2306
|
+
const score = Math.max(-1, Math.min(1, raw));
|
|
2307
|
+
const threshold = sharedScope !== null ? SAME_SCOPE_SEMANTIC_SIMILARITY : sharedEntities.length > 0 ? SHARED_ENTITY_SEMANTIC_SIMILARITY : GLOBAL_SEMANTIC_SIMILARITY;
|
|
2308
|
+
if (score < threshold) continue;
|
|
2309
|
+
const observation = {
|
|
2310
|
+
i,
|
|
2311
|
+
j,
|
|
2312
|
+
key: indexPairKey(i, j),
|
|
2313
|
+
score,
|
|
2314
|
+
context: !contextual ? "with no lexical context, corroborated by a three-note semantic cluster" : sharedScope === null ? `inside shared entity ${sharedEntities.join(", ")}` : `inside scope "${entries[i].memory.scope ?? sharedScope}"`,
|
|
2315
|
+
contextual
|
|
2316
|
+
};
|
|
2317
|
+
observations.push(observation);
|
|
2318
|
+
addObservation(byIndex, i, observation);
|
|
2319
|
+
addObservation(byIndex, j, observation);
|
|
2320
|
+
}
|
|
2321
|
+
if (budget.truncated) return [];
|
|
2322
|
+
const nearest = /* @__PURE__ */ new Map();
|
|
2323
|
+
for (const [i, adjacent] of byIndex) {
|
|
2324
|
+
const ranked = adjacent.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key)).slice(0, RECIPROCAL_NEIGHBOURS);
|
|
2325
|
+
nearest.set(i, new Set(ranked.map((observation) => observation.key)));
|
|
2326
|
+
}
|
|
2327
|
+
const reciprocal = observations.filter(
|
|
2328
|
+
(observation) => nearest.get(observation.i)?.has(observation.key) === true && nearest.get(observation.j)?.has(observation.key) === true
|
|
2329
|
+
);
|
|
2330
|
+
const corroborated = triangleEdges(reciprocal);
|
|
2331
|
+
return reciprocal.filter((observation) => corroborated.has(observation.key)).map((observation) => ({
|
|
2332
|
+
candidate: pairCandidate(
|
|
2333
|
+
"semantic_similarity",
|
|
2334
|
+
entries[observation.i],
|
|
2335
|
+
entries[observation.j],
|
|
2336
|
+
{
|
|
2337
|
+
evidence: `indexed meaning is ${Math.round(observation.score * 100)}% similar ${observation.context} (reciprocal top-${RECIPROCAL_NEIGHBOURS}, three-note cluster)`,
|
|
2338
|
+
suggested_action: "supersede"
|
|
2339
|
+
}
|
|
2340
|
+
),
|
|
2341
|
+
// Unanchored embedding evidence deliberately waits behind authored
|
|
2342
|
+
// context and strict lexical matches even after triangle corroboration.
|
|
2343
|
+
confidence: observation.contextual ? 3 + observation.score : 1.9
|
|
2344
|
+
}));
|
|
2345
|
+
}
|
|
2346
|
+
function triangleEdges(observations) {
|
|
2347
|
+
const adjacent = /* @__PURE__ */ new Map();
|
|
2348
|
+
for (const observation of observations) {
|
|
2349
|
+
addNeighbour(adjacent, observation.i, observation.j);
|
|
2350
|
+
addNeighbour(adjacent, observation.j, observation.i);
|
|
2351
|
+
}
|
|
2352
|
+
const out = /* @__PURE__ */ new Set();
|
|
2353
|
+
for (const observation of observations) {
|
|
2354
|
+
const left = adjacent.get(observation.i) ?? /* @__PURE__ */ new Set();
|
|
2355
|
+
const right = adjacent.get(observation.j) ?? /* @__PURE__ */ new Set();
|
|
2356
|
+
if ([...left].some((index) => index !== observation.j && right.has(index))) {
|
|
2357
|
+
out.add(observation.key);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
return out;
|
|
2361
|
+
}
|
|
2362
|
+
function addNeighbour(values, from, to) {
|
|
2363
|
+
const adjacent = values.get(from);
|
|
2364
|
+
if (adjacent) adjacent.add(to);
|
|
2365
|
+
else values.set(from, /* @__PURE__ */ new Set([to]));
|
|
2366
|
+
}
|
|
2367
|
+
function semanticGlobalExclusions(entries, titleCandidates) {
|
|
2368
|
+
const out = /* @__PURE__ */ new Set();
|
|
2369
|
+
const liveIds = new Set(entries.map((entry) => entry.memory.id));
|
|
2370
|
+
for (const entry of entries) {
|
|
2371
|
+
if (entry.relations.length === 0) continue;
|
|
2372
|
+
out.add(entry.memory.id);
|
|
2373
|
+
for (const relation of entry.relations) if (liveIds.has(relation.to)) out.add(relation.to);
|
|
2374
|
+
}
|
|
2375
|
+
for (const { candidate } of titleCandidates) {
|
|
2376
|
+
out.add(candidate.memory_id);
|
|
2377
|
+
if (candidate.other_id !== null) out.add(candidate.other_id);
|
|
2378
|
+
}
|
|
2379
|
+
return out;
|
|
2380
|
+
}
|
|
2381
|
+
function addObservation(byIndex, index, observation) {
|
|
2382
|
+
const values = byIndex.get(index);
|
|
2383
|
+
if (values) values.push(observation);
|
|
2384
|
+
else byIndex.set(index, [observation]);
|
|
2385
|
+
}
|
|
2386
|
+
function declaredSupersessions(entries) {
|
|
2387
|
+
const byId = new Map(entries.map((entry) => [entry.memory.id, entry]));
|
|
2388
|
+
const cyclicEdges = cyclicSupersessionEdges(entries, byId);
|
|
2389
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2390
|
+
const out = [];
|
|
2391
|
+
for (const winner of entries) {
|
|
2392
|
+
for (const relation of winner.relations) {
|
|
2393
|
+
if (relation.type !== "supersedes") continue;
|
|
2394
|
+
const loser = byId.get(relation.to);
|
|
2395
|
+
if (!loser || loser.memory.id === winner.memory.id) continue;
|
|
2396
|
+
if (cyclicEdges.has(directedPairKey(winner.memory.id, loser.memory.id))) continue;
|
|
2397
|
+
const key2 = memoryPairKey(winner.memory.id, loser.memory.id);
|
|
2398
|
+
if (seen.has(key2)) continue;
|
|
2399
|
+
seen.add(key2);
|
|
2400
|
+
out.push({
|
|
2401
|
+
candidate: pairCandidate("declared_supersession", loser, winner, {
|
|
2402
|
+
evidence: `"${winner.memory.title}" already declares it supersedes "${loser.memory.title}", but the replaced note is still active`,
|
|
2403
|
+
suggested_action: "retire"
|
|
2404
|
+
}),
|
|
2405
|
+
confidence: 6
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
return out;
|
|
2410
|
+
}
|
|
2411
|
+
function cyclicSupersessionEdges(entries, byId) {
|
|
2412
|
+
const adjacent = /* @__PURE__ */ new Map();
|
|
2413
|
+
const reversed = /* @__PURE__ */ new Map();
|
|
2414
|
+
for (const id of byId.keys()) {
|
|
2415
|
+
adjacent.set(id, []);
|
|
2416
|
+
reversed.set(id, []);
|
|
2417
|
+
}
|
|
2418
|
+
for (const entry of entries) {
|
|
2419
|
+
for (const relation of entry.relations) {
|
|
2420
|
+
if (relation.type !== "supersedes" || relation.to === entry.memory.id) continue;
|
|
2421
|
+
if (!byId.has(relation.to)) continue;
|
|
2422
|
+
adjacent.get(entry.memory.id).push(relation.to);
|
|
2423
|
+
reversed.get(relation.to).push(entry.memory.id);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2427
|
+
const finished = [];
|
|
2428
|
+
for (const start of byId.keys()) {
|
|
2429
|
+
if (visited.has(start)) continue;
|
|
2430
|
+
visited.add(start);
|
|
2431
|
+
const stack = [{ id: start, next: 0 }];
|
|
2432
|
+
while (stack.length > 0) {
|
|
2433
|
+
const frame = stack[stack.length - 1];
|
|
2434
|
+
const neighbours = adjacent.get(frame.id) ?? [];
|
|
2435
|
+
if (frame.next < neighbours.length) {
|
|
2436
|
+
const neighbour = neighbours[frame.next];
|
|
2437
|
+
frame.next += 1;
|
|
2438
|
+
if (!visited.has(neighbour)) {
|
|
2439
|
+
visited.add(neighbour);
|
|
2440
|
+
stack.push({ id: neighbour, next: 0 });
|
|
2441
|
+
}
|
|
2442
|
+
} else {
|
|
2443
|
+
finished.push(frame.id);
|
|
2444
|
+
stack.pop();
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
const componentById = /* @__PURE__ */ new Map();
|
|
2449
|
+
const componentSizes = /* @__PURE__ */ new Map();
|
|
2450
|
+
let component = 0;
|
|
2451
|
+
for (let index = finished.length - 1; index >= 0; index -= 1) {
|
|
2452
|
+
const start = finished[index];
|
|
2453
|
+
if (componentById.has(start)) continue;
|
|
2454
|
+
const stack = [start];
|
|
2455
|
+
componentById.set(start, component);
|
|
2456
|
+
let size = 0;
|
|
2457
|
+
while (stack.length > 0) {
|
|
2458
|
+
const id = stack.pop();
|
|
2459
|
+
size += 1;
|
|
2460
|
+
for (const neighbour of reversed.get(id) ?? []) {
|
|
2461
|
+
if (componentById.has(neighbour)) continue;
|
|
2462
|
+
componentById.set(neighbour, component);
|
|
2463
|
+
stack.push(neighbour);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
componentSizes.set(component, size);
|
|
2467
|
+
component += 1;
|
|
2468
|
+
}
|
|
2469
|
+
const out = /* @__PURE__ */ new Set();
|
|
2470
|
+
for (const [from, targets] of adjacent) {
|
|
2471
|
+
const fromComponent = componentById.get(from);
|
|
2472
|
+
if (fromComponent === void 0 || (componentSizes.get(fromComponent) ?? 0) < 2) continue;
|
|
2473
|
+
for (const to of targets) {
|
|
2474
|
+
if (componentById.get(to) === fromComponent) out.add(directedPairKey(from, to));
|
|
2047
2475
|
}
|
|
2048
2476
|
}
|
|
2049
2477
|
return out;
|
|
@@ -2057,15 +2485,16 @@ function declaredContradictions(entries) {
|
|
|
2057
2485
|
if (relation.type !== "contradicts") continue;
|
|
2058
2486
|
const other = byId.get(relation.to);
|
|
2059
2487
|
if (!other || other.memory.id === entry.memory.id) continue;
|
|
2060
|
-
const key2 =
|
|
2488
|
+
const key2 = memoryPairKey(entry.memory.id, other.memory.id);
|
|
2061
2489
|
if (seen.has(key2)) continue;
|
|
2062
2490
|
seen.add(key2);
|
|
2063
|
-
out.push(
|
|
2064
|
-
pairCandidate("contradiction", entry, other, {
|
|
2491
|
+
out.push({
|
|
2492
|
+
candidate: pairCandidate("contradiction", entry, other, {
|
|
2065
2493
|
evidence: `"${entry.memory.title}" declares it contradicts "${other.memory.title}" \u2014 recorded, never settled`,
|
|
2066
2494
|
suggested_action: "supersede"
|
|
2067
|
-
})
|
|
2068
|
-
|
|
2495
|
+
}),
|
|
2496
|
+
confidence: 5
|
|
2497
|
+
});
|
|
2069
2498
|
}
|
|
2070
2499
|
}
|
|
2071
2500
|
return out;
|
|
@@ -2076,20 +2505,88 @@ function kindPromotions(entries) {
|
|
|
2076
2505
|
if (entry.memory.binding !== true) continue;
|
|
2077
2506
|
if (entry.memory.kind === "decision") continue;
|
|
2078
2507
|
out.push({
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2508
|
+
candidate: {
|
|
2509
|
+
rule: "kind_promotion",
|
|
2510
|
+
memory_id: entry.memory.id,
|
|
2511
|
+
path: entry.path,
|
|
2512
|
+
title: entry.memory.title,
|
|
2513
|
+
other_id: null,
|
|
2514
|
+
other_path: null,
|
|
2515
|
+
other_title: null,
|
|
2516
|
+
evidence: entry.memory.kind === void 0 ? "marked as binding but left unclassified \u2014 a memory that binds is a decision" : `marked as binding but classified as ${entry.memory.kind} \u2014 a memory that binds is a decision`,
|
|
2517
|
+
suggested_action: "reclassify",
|
|
2518
|
+
suggested_kind: "decision"
|
|
2519
|
+
},
|
|
2520
|
+
confidence: 4
|
|
2089
2521
|
});
|
|
2090
2522
|
}
|
|
2091
2523
|
return out;
|
|
2092
2524
|
}
|
|
2525
|
+
function normalizeContext(value) {
|
|
2526
|
+
if (value === null) return null;
|
|
2527
|
+
const normalized = value.normalize("NFC").trim().toLowerCase();
|
|
2528
|
+
return normalized === "" ? null : normalized;
|
|
2529
|
+
}
|
|
2530
|
+
function declaredSemanticScope(entry) {
|
|
2531
|
+
const scope = normalizeContext(entry.memory.scope);
|
|
2532
|
+
if (scope === null) return null;
|
|
2533
|
+
if (entry.memory.provenance.scopeSource === "explicit") return scope;
|
|
2534
|
+
if (entry.memory.provenance.scopeSource === "path") return null;
|
|
2535
|
+
const parts = entry.path.split("/");
|
|
2536
|
+
const parent = parts.length >= 2 ? normalizeContext(parts[parts.length - 2] ?? null) : null;
|
|
2537
|
+
return scope === parent ? null : scope;
|
|
2538
|
+
}
|
|
2539
|
+
function isPresent(value) {
|
|
2540
|
+
return value !== null;
|
|
2541
|
+
}
|
|
2542
|
+
function addToBucket(buckets, key2, index) {
|
|
2543
|
+
const bucket = buckets.get(key2);
|
|
2544
|
+
if (bucket) bucket.push(index);
|
|
2545
|
+
else buckets.set(key2, [index]);
|
|
2546
|
+
}
|
|
2547
|
+
function memoryPairKey(left, right) {
|
|
2548
|
+
return left < right ? `${left}\0${right}` : `${right}\0${left}`;
|
|
2549
|
+
}
|
|
2550
|
+
function directedPairKey(from, to) {
|
|
2551
|
+
return `${from}\0${to}`;
|
|
2552
|
+
}
|
|
2553
|
+
function indexPairKey(left, right) {
|
|
2554
|
+
return left < right ? `${left}:${right}` : `${right}:${left}`;
|
|
2555
|
+
}
|
|
2556
|
+
function rankCandidates(scored) {
|
|
2557
|
+
const unique = /* @__PURE__ */ new Map();
|
|
2558
|
+
for (const item of scored) {
|
|
2559
|
+
const candidate = item.candidate;
|
|
2560
|
+
const key2 = candidate.other_id === null ? `single:${candidate.rule}:${candidate.memory_id}` : `pair:${memoryPairKey(candidate.memory_id, candidate.other_id)}`;
|
|
2561
|
+
const current = unique.get(key2);
|
|
2562
|
+
if (current === void 0 || compareScored(item, current) < 0) unique.set(key2, item);
|
|
2563
|
+
}
|
|
2564
|
+
const ordered = [...unique.values()].sort(compareScored);
|
|
2565
|
+
const tiers = /* @__PURE__ */ new Map();
|
|
2566
|
+
for (const item of ordered) addScored(tiers, Math.floor(item.confidence), item);
|
|
2567
|
+
const components = new CandidateComponents();
|
|
2568
|
+
const out = [];
|
|
2569
|
+
for (const tier of [...tiers.keys()].sort((a, b) => b - a)) {
|
|
2570
|
+
const useful = [];
|
|
2571
|
+
const cycles = [];
|
|
2572
|
+
for (const item of tiers.get(tier) ?? []) {
|
|
2573
|
+
const other = item.candidate.other_id;
|
|
2574
|
+
if (other === null || components.join(item.candidate.memory_id, other)) useful.push(item);
|
|
2575
|
+
else cycles.push(item);
|
|
2576
|
+
}
|
|
2577
|
+
for (const item of useful) out.push(item.candidate);
|
|
2578
|
+
for (const item of cycles) out.push(item.candidate);
|
|
2579
|
+
}
|
|
2580
|
+
return out;
|
|
2581
|
+
}
|
|
2582
|
+
function addScored(tiers, tier, item) {
|
|
2583
|
+
const values = tiers.get(tier);
|
|
2584
|
+
if (values) values.push(item);
|
|
2585
|
+
else tiers.set(tier, [item]);
|
|
2586
|
+
}
|
|
2587
|
+
function compareScored(a, b) {
|
|
2588
|
+
return b.confidence - a.confidence || CONSOLIDATION_RULES.indexOf(a.candidate.rule) - CONSOLIDATION_RULES.indexOf(b.candidate.rule) || a.candidate.memory_id.localeCompare(b.candidate.memory_id) || (a.candidate.other_id ?? "").localeCompare(b.candidate.other_id ?? "");
|
|
2589
|
+
}
|
|
2093
2590
|
function pairCandidate(rule, a, b, rest) {
|
|
2094
2591
|
return {
|
|
2095
2592
|
rule,
|
|
@@ -2125,20 +2622,54 @@ function* pairsFrom(buckets, budget) {
|
|
|
2125
2622
|
}
|
|
2126
2623
|
}
|
|
2127
2624
|
}
|
|
2128
|
-
var CONSOLIDATION_RULES, DEFAULT_LIMIT, DEFAULT_PAIR_BUDGET, TITLE_OVERLAP;
|
|
2625
|
+
var CONSOLIDATION_RULES, DEFAULT_LIMIT, DEFAULT_PAIR_BUDGET, SEMANTIC_PAIR_BUDGET, TITLE_OVERLAP, SAME_SCOPE_SEMANTIC_SIMILARITY, SHARED_ENTITY_SEMANTIC_SIMILARITY, GLOBAL_SEMANTIC_SIMILARITY, RECIPROCAL_NEIGHBOURS, CandidateComponents;
|
|
2129
2626
|
var init_detect = __esm({
|
|
2130
2627
|
"../../packages/memory-engine/src/proposal/detect.ts"() {
|
|
2131
2628
|
"use strict";
|
|
2132
2629
|
init_search();
|
|
2133
2630
|
CONSOLIDATION_RULES = [
|
|
2134
|
-
"
|
|
2135
|
-
"shared_context",
|
|
2631
|
+
"declared_supersession",
|
|
2136
2632
|
"contradiction",
|
|
2633
|
+
"semantic_similarity",
|
|
2634
|
+
"shared_context",
|
|
2635
|
+
"similar_title",
|
|
2137
2636
|
"kind_promotion"
|
|
2138
2637
|
];
|
|
2139
2638
|
DEFAULT_LIMIT = 50;
|
|
2140
2639
|
DEFAULT_PAIR_BUDGET = 2e5;
|
|
2141
|
-
|
|
2640
|
+
SEMANTIC_PAIR_BUDGET = 5e4;
|
|
2641
|
+
TITLE_OVERLAP = 2 / 3;
|
|
2642
|
+
SAME_SCOPE_SEMANTIC_SIMILARITY = 0.6;
|
|
2643
|
+
SHARED_ENTITY_SEMANTIC_SIMILARITY = 0.75;
|
|
2644
|
+
GLOBAL_SEMANTIC_SIMILARITY = 0.72;
|
|
2645
|
+
RECIPROCAL_NEIGHBOURS = 2;
|
|
2646
|
+
CandidateComponents = class {
|
|
2647
|
+
parent = /* @__PURE__ */ new Map();
|
|
2648
|
+
root(id) {
|
|
2649
|
+
const trail = [];
|
|
2650
|
+
let current = id;
|
|
2651
|
+
for (; ; ) {
|
|
2652
|
+
const parent = this.parent.get(current);
|
|
2653
|
+
if (parent === void 0) {
|
|
2654
|
+
this.parent.set(current, current);
|
|
2655
|
+
break;
|
|
2656
|
+
}
|
|
2657
|
+
if (parent === current) break;
|
|
2658
|
+
trail.push(current);
|
|
2659
|
+
current = parent;
|
|
2660
|
+
}
|
|
2661
|
+
for (const member of trail) this.parent.set(member, current);
|
|
2662
|
+
return current;
|
|
2663
|
+
}
|
|
2664
|
+
/** True when this edge connected two components rather than closing a cycle. */
|
|
2665
|
+
join(left, right) {
|
|
2666
|
+
const a = this.root(left);
|
|
2667
|
+
const b = this.root(right);
|
|
2668
|
+
if (a === b) return false;
|
|
2669
|
+
this.parent.set(b, a);
|
|
2670
|
+
return true;
|
|
2671
|
+
}
|
|
2672
|
+
};
|
|
2142
2673
|
}
|
|
2143
2674
|
});
|
|
2144
2675
|
|
|
@@ -2660,6 +3191,86 @@ function restoreUntouchedLines(original, rendered, changed) {
|
|
|
2660
3191
|
return before.get(key2) ?? line;
|
|
2661
3192
|
}).join("\n");
|
|
2662
3193
|
}
|
|
3194
|
+
function yamlErrorLine(error, text) {
|
|
3195
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3196
|
+
let positioned = null;
|
|
3197
|
+
if (error && typeof error === "object") {
|
|
3198
|
+
const linePos = error.linePos;
|
|
3199
|
+
const line = linePos?.[0]?.line;
|
|
3200
|
+
if (typeof line === "number" && Number.isFinite(line) && line >= 1) positioned = line - 1;
|
|
3201
|
+
}
|
|
3202
|
+
const quote = /Missing closing (["'])quote/i.exec(message)?.[1];
|
|
3203
|
+
if (quote !== void 0) {
|
|
3204
|
+
const lines2 = text.split("\n");
|
|
3205
|
+
const end = positioned ?? lines2.length - 1;
|
|
3206
|
+
for (let i = 0; i <= end; i += 1) {
|
|
3207
|
+
const line = lines2[i] ?? "";
|
|
3208
|
+
const colon = line.indexOf(":");
|
|
3209
|
+
if (colon === -1) continue;
|
|
3210
|
+
const value = line.slice(colon + 1);
|
|
3211
|
+
let count = 0;
|
|
3212
|
+
for (let at = 0; at < value.length; at += 1) {
|
|
3213
|
+
if (value[at] !== quote) continue;
|
|
3214
|
+
let slashes = 0;
|
|
3215
|
+
for (let before = at - 1; before >= 0 && value[before] === "\\"; before -= 1) slashes += 1;
|
|
3216
|
+
if (slashes % 2 === 0) count += 1;
|
|
3217
|
+
}
|
|
3218
|
+
if (count % 2 === 1) return i;
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
if (positioned !== null) return positioned;
|
|
3222
|
+
const namedLine = /\bat line (\d+)\b/i.exec(message)?.[1];
|
|
3223
|
+
if (namedLine !== void 0) return Math.max(0, Number(namedLine) - 1);
|
|
3224
|
+
const alias = /alias[^:]*:\s*([^\s]+)/i.exec(message)?.[1];
|
|
3225
|
+
if (alias !== void 0) {
|
|
3226
|
+
const found = text.split("\n").findIndex((line) => line.includes(`*${alias}`));
|
|
3227
|
+
if (found !== -1) return found;
|
|
3228
|
+
}
|
|
3229
|
+
return 0;
|
|
3230
|
+
}
|
|
3231
|
+
function yamlFieldAt(text, line, fallback) {
|
|
3232
|
+
const stack = [];
|
|
3233
|
+
const lines2 = text.split("\n");
|
|
3234
|
+
const end = Math.min(Math.max(line, 0), Math.max(lines2.length - 1, 0));
|
|
3235
|
+
for (let i = 0; i <= end; i += 1) {
|
|
3236
|
+
const match = ANY_INDENTED_KEY_RE.exec(lines2[i] ?? "");
|
|
3237
|
+
if (!match) continue;
|
|
3238
|
+
const whitespace = match[1] ?? "";
|
|
3239
|
+
const indent = whitespace.length;
|
|
3240
|
+
while ((stack.at(-1)?.indent ?? -1) >= indent) stack.pop();
|
|
3241
|
+
stack.push({ indent, key: (match[2] ?? "").trim() });
|
|
3242
|
+
}
|
|
3243
|
+
return stack.length > 0 ? stack.map(({ key: key2 }) => key2).join(".") : fallback;
|
|
3244
|
+
}
|
|
3245
|
+
function yamlFieldRefusal(text, error, fallback) {
|
|
3246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3247
|
+
const localField = yamlFieldAt(text, yamlErrorLine(error, text), fallback);
|
|
3248
|
+
const field = fallback === NAMESPACE2 && localField !== NAMESPACE2 ? `${NAMESPACE2}.${localField}` : localField;
|
|
3249
|
+
const code = error && typeof error === "object" && typeof error.code === "string" ? error.code : "";
|
|
3250
|
+
let change;
|
|
3251
|
+
if (code === "TAB_AS_INDENT" || /tab/i.test(message)) {
|
|
3252
|
+
change = `Replace the tab indentation in ${field} with spaces`;
|
|
3253
|
+
} else if (/alias|anchor/i.test(message)) {
|
|
3254
|
+
change = `Replace the anchor or alias used by ${field} with its value inline`;
|
|
3255
|
+
} else if (code === "DUPLICATE_KEY" || /keys? must be unique/i.test(message)) {
|
|
3256
|
+
change = `Remove or rename the duplicate ${field} field`;
|
|
3257
|
+
} else {
|
|
3258
|
+
change = `Fix ${field} so it is valid YAML (close its quotes or brackets, and quote any value containing a colon)`;
|
|
3259
|
+
}
|
|
3260
|
+
return new PatchError(
|
|
3261
|
+
`the ${field} field could not be rewritten safely: ${message}`,
|
|
3262
|
+
`${change}, then try again. Nothing was written.`
|
|
3263
|
+
);
|
|
3264
|
+
}
|
|
3265
|
+
function assertReadableFrontmatter(frontmatter) {
|
|
3266
|
+
try {
|
|
3267
|
+
const doc = parseDocument(frontmatter);
|
|
3268
|
+
if (doc.errors[0]) throw doc.errors[0];
|
|
3269
|
+
doc.toJS();
|
|
3270
|
+
} catch (error) {
|
|
3271
|
+
throw yamlFieldRefusal(frontmatter, error, "frontmatter");
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
2663
3274
|
function renderBlock(fields, existingText) {
|
|
2664
3275
|
let body;
|
|
2665
3276
|
if (existingText === null || existingText.trim() === "") {
|
|
@@ -2667,6 +3278,7 @@ function renderBlock(fields, existingText) {
|
|
|
2667
3278
|
} else {
|
|
2668
3279
|
try {
|
|
2669
3280
|
const doc = parseDocument(existingText);
|
|
3281
|
+
if (doc.errors[0]) throw doc.errors[0];
|
|
2670
3282
|
const changed = /* @__PURE__ */ new Set();
|
|
2671
3283
|
for (const [key2, value] of Object.entries(fields)) {
|
|
2672
3284
|
const current = doc.get(key2);
|
|
@@ -2680,14 +3292,21 @@ function renderBlock(fields, existingText) {
|
|
|
2680
3292
|
}
|
|
2681
3293
|
body = restoreUntouchedLines(
|
|
2682
3294
|
existingText,
|
|
2683
|
-
doc.toString({
|
|
3295
|
+
doc.toString({
|
|
3296
|
+
lineWidth: 0,
|
|
3297
|
+
// `yaml` turns a double-quoted scalar containing `\n` into three
|
|
3298
|
+
// physical lines once its JSON representation reaches 40 chars.
|
|
3299
|
+
// `restoreUntouchedLines` then sees the blank middle line, mistakes
|
|
3300
|
+
// the first one for a complete scalar, and restores it while
|
|
3301
|
+
// leaving the continuation behind. Keeping quoted newlines escaped
|
|
3302
|
+
// makes an untouched field one physical line on both sides, so the
|
|
3303
|
+
// byte-preserving restore and the YAML renderer agree on its span.
|
|
3304
|
+
doubleQuotedMinMultiLineLength: Number.POSITIVE_INFINITY
|
|
3305
|
+
}).replace(/\n$/, ""),
|
|
2684
3306
|
changed
|
|
2685
3307
|
);
|
|
2686
3308
|
} catch (err) {
|
|
2687
|
-
throw
|
|
2688
|
-
`the memgineering block in this note could not be rewritten: ${err.message}`,
|
|
2689
|
-
"Its YAML uses something this tool cannot rewrite safely \u2014 most often a tab indent, or an anchor/alias defined outside the block. Re-indent with spaces or inline the value, then try again. Nothing was written."
|
|
2690
|
-
);
|
|
3309
|
+
throw yamlFieldRefusal(existingText, err, NAMESPACE2);
|
|
2691
3310
|
}
|
|
2692
3311
|
}
|
|
2693
3312
|
return [`${NAMESPACE2}:`, ...body.split("\n").map((l) => l === "" ? l : ` ${l}`)];
|
|
@@ -2717,6 +3336,7 @@ ${block.join("\n")}
|
|
|
2717
3336
|
};
|
|
2718
3337
|
}
|
|
2719
3338
|
const fenceBody = fenceMatch[1] ?? "";
|
|
3339
|
+
assertReadableFrontmatter(fenceBody);
|
|
2720
3340
|
const fenceLines = splitLines(fenceBody);
|
|
2721
3341
|
const span = findBlock(fenceLines);
|
|
2722
3342
|
const existing = readNamespace(noteContent);
|
|
@@ -2882,7 +3502,7 @@ function reversePatch(content, patch) {
|
|
|
2882
3502
|
}
|
|
2883
3503
|
return null;
|
|
2884
3504
|
}
|
|
2885
|
-
var NAMESPACE2, FENCE_RE, LOOKS_FENCED_RE, PatchError, BLOCK_KEY_RE;
|
|
3505
|
+
var NAMESPACE2, FENCE_RE, LOOKS_FENCED_RE, PatchError, BLOCK_KEY_RE, ANY_INDENTED_KEY_RE;
|
|
2886
3506
|
var init_patch = __esm({
|
|
2887
3507
|
"../../packages/memory-engine/src/proposal/patch.ts"() {
|
|
2888
3508
|
"use strict";
|
|
@@ -2900,6 +3520,7 @@ var init_patch = __esm({
|
|
|
2900
3520
|
}
|
|
2901
3521
|
};
|
|
2902
3522
|
BLOCK_KEY_RE = /^([^\s#][^:]*?)\s*:/;
|
|
3523
|
+
ANY_INDENTED_KEY_RE = /^(\s*)([^\s#][^:]*?)\s*:/;
|
|
2903
3524
|
}
|
|
2904
3525
|
});
|
|
2905
3526
|
|
|
@@ -4430,13 +5051,17 @@ function linkCommand() {
|
|
|
4430
5051
|
Nothing was indexed. This is what WOULD be stored:
|
|
4431
5052
|
`));
|
|
4432
5053
|
for (const r of rows) {
|
|
4433
|
-
printHuman(` ${c.bold(
|
|
4434
|
-
printHuman(
|
|
4435
|
-
|
|
5054
|
+
printHuman(` ${c.bold(oneLine(r.title, WIDTH.title))}`);
|
|
5055
|
+
printHuman(
|
|
5056
|
+
c.gray(` ${oneLine(r.summary ?? "(no summary \u2014 title only)", WIDTH.text)}`)
|
|
5057
|
+
);
|
|
5058
|
+
printHuman(c.gray(` from ${middleLine(r.path, WIDTH.path)}`));
|
|
4436
5059
|
}
|
|
4437
5060
|
printHuman(
|
|
4438
|
-
c.gray(
|
|
4439
|
-
|
|
5061
|
+
c.gray(
|
|
5062
|
+
`
|
|
5063
|
+
${rows.length} note(s). After the person approves this preview, run again with --yes to index them.`
|
|
5064
|
+
)
|
|
4440
5065
|
);
|
|
4441
5066
|
if (refusedWhilePreviewing.length > 0) {
|
|
4442
5067
|
printHuman(
|
|
@@ -4705,8 +5330,8 @@ Brain: ${root}`));
|
|
|
4705
5330
|
if (samples.length > 0) {
|
|
4706
5331
|
printHuman(c.gray("\n For example, these exact lines would be stored:"));
|
|
4707
5332
|
for (const r of samples) {
|
|
4708
|
-
printHuman(c.gray(` ${
|
|
4709
|
-
printHuman(c.gray(` ${
|
|
5333
|
+
printHuman(c.gray(` ${oneLine(r.title, WIDTH.title)}`));
|
|
5334
|
+
printHuman(c.gray(` ${oneLine(r.summary ?? "(no summary \u2014 title only)", WIDTH.text)}`));
|
|
4710
5335
|
}
|
|
4711
5336
|
if (notes.length > samples.length) {
|
|
4712
5337
|
printHuman(
|
|
@@ -4826,7 +5451,7 @@ function loginCommand() {
|
|
|
4826
5451
|
},
|
|
4827
5452
|
human: () => {
|
|
4828
5453
|
printHuman(
|
|
4829
|
-
`Already signed in${already.display_name ? ` as ${c.bold(
|
|
5454
|
+
`Already signed in${already.display_name ? ` as ${c.bold(oneLine(already.display_name, WIDTH.title))}` : ""}.`
|
|
4830
5455
|
);
|
|
4831
5456
|
printHuman(c.gray(" `memgineering logout` first if you want a different account."));
|
|
4832
5457
|
}
|
|
@@ -4899,13 +5524,13 @@ function emit(pending, ctx) {
|
|
|
4899
5524
|
},
|
|
4900
5525
|
human: () => {
|
|
4901
5526
|
printHuman("Show this code to whoever is signing in:\n");
|
|
4902
|
-
printHuman(` ${c.bold().yellow(
|
|
5527
|
+
printHuman(` ${c.bold().yellow(oneLine(pending.user_code, WIDTH.handle))}
|
|
4903
5528
|
`);
|
|
4904
5529
|
if (ctx.opened) {
|
|
4905
5530
|
printHuman("Opened the approval page in your browser.");
|
|
4906
5531
|
} else {
|
|
4907
5532
|
printHuman("Open this to approve:\n");
|
|
4908
|
-
printHuman(` ${c.cyan(
|
|
5533
|
+
printHuman(` ${c.cyan(oneLine(pending.verification_url, WIDTH.path))}`);
|
|
4909
5534
|
}
|
|
4910
5535
|
printHuman(
|
|
4911
5536
|
c.gray(
|
|
@@ -4913,7 +5538,7 @@ function emit(pending, ctx) {
|
|
|
4913
5538
|
)
|
|
4914
5539
|
);
|
|
4915
5540
|
if (ctx.opened && ctx.showUrl) {
|
|
4916
|
-
printHuman(c.gray(` ${
|
|
5541
|
+
printHuman(c.gray(` ${oneLine(pending.verification_url, WIDTH.path)}`));
|
|
4917
5542
|
}
|
|
4918
5543
|
printHuman(
|
|
4919
5544
|
c.gray(
|
|
@@ -4955,7 +5580,7 @@ async function waitForApproval(pending, base) {
|
|
|
4955
5580
|
human: () => {
|
|
4956
5581
|
printHuman(
|
|
4957
5582
|
`
|
|
4958
|
-
Signed in${claimed.display_name ? ` as ${c.bold(
|
|
5583
|
+
Signed in${claimed.display_name ? ` as ${c.bold(oneLine(claimed.display_name, WIDTH.title))}` : ""}.`
|
|
4959
5584
|
);
|
|
4960
5585
|
printHuman(c.gray(" The token is stored on this machine only, readable by you alone."));
|
|
4961
5586
|
}
|
|
@@ -5056,17 +5681,19 @@ function printSignInStep(step) {
|
|
|
5056
5681
|
switch (step.status) {
|
|
5057
5682
|
case "signed-in":
|
|
5058
5683
|
printHuman(
|
|
5059
|
-
c.gray(
|
|
5060
|
-
`)
|
|
5684
|
+
c.gray(
|
|
5685
|
+
` signed in${step.displayName ? ` as ${oneLine(step.displayName, WIDTH.title)}` : ""}
|
|
5686
|
+
`
|
|
5687
|
+
)
|
|
5061
5688
|
);
|
|
5062
5689
|
return;
|
|
5063
5690
|
case "started":
|
|
5064
5691
|
printHuman("Show this code to whoever is signing in:\n");
|
|
5065
|
-
printHuman(` ${c.bold().yellow(
|
|
5692
|
+
printHuman(` ${c.bold().yellow(oneLine(step.userCode, WIDTH.handle))}
|
|
5066
5693
|
`);
|
|
5067
5694
|
printHuman(
|
|
5068
5695
|
step.opened ? c.gray(" The approval page is open in the browser.") : `Open this to approve:
|
|
5069
|
-
${c.cyan(
|
|
5696
|
+
${c.cyan(oneLine(step.verificationUrl, WIDTH.path))}`
|
|
5070
5697
|
);
|
|
5071
5698
|
printHuman(
|
|
5072
5699
|
c.gray(" Not waiting \u2014 setup carries on, and the next command collects the token.\n")
|
|
@@ -5074,12 +5701,14 @@ function printSignInStep(step) {
|
|
|
5074
5701
|
return;
|
|
5075
5702
|
case "pending":
|
|
5076
5703
|
printHuman(
|
|
5077
|
-
c.gray(
|
|
5078
|
-
`)
|
|
5704
|
+
c.gray(
|
|
5705
|
+
` a sign-in is waiting for approval \u2014 code ${oneLine(step.userCode, WIDTH.handle)}
|
|
5706
|
+
`
|
|
5707
|
+
)
|
|
5079
5708
|
);
|
|
5080
5709
|
return;
|
|
5081
5710
|
case "unavailable":
|
|
5082
|
-
printHuman(c.yellow(` Could not start a sign-in: ${
|
|
5711
|
+
printHuman(c.yellow(` Could not start a sign-in: ${oneLine(step.because, WIDTH.text)}`));
|
|
5083
5712
|
printHuman(c.gray(" Setup continues \u2014 an account is not needed for any of it.\n"));
|
|
5084
5713
|
return;
|
|
5085
5714
|
case "would-start":
|
|
@@ -7387,11 +8016,11 @@ var init_setup_web = __esm({
|
|
|
7387
8016
|
// src/commands/setup.ts
|
|
7388
8017
|
import { createHash as createHash8 } from "crypto";
|
|
7389
8018
|
import { access, mkdir as mkdir13, readFile as readFile19 } from "fs/promises";
|
|
7390
|
-
import { homedir as homedir4 } from "os";
|
|
7391
8019
|
import { dirname as dirname5, join as join19 } from "path";
|
|
7392
8020
|
import { fileURLToPath } from "url";
|
|
7393
8021
|
import { Command as Command22 } from "commander";
|
|
7394
8022
|
import prompts3 from "prompts";
|
|
8023
|
+
import { homedir as homedir4 } from "os";
|
|
7395
8024
|
function setupCommand() {
|
|
7396
8025
|
return new Command22("setup").description("install into your agents, and point at a brain (--dry-run to only report)").option("--agent", "non-interactive: take every answer from flags").option("--human", "interactive: choose from a list").option("--web", "open a screen in the browser and answer it there").option("--print-url", "with --web: print the URL instead of opening a browser").option(
|
|
7397
8026
|
"--tools <list>",
|
|
@@ -7463,6 +8092,18 @@ function setupCommand() {
|
|
|
7463
8092
|
...signInStepJson(signIn),
|
|
7464
8093
|
defaults_used: defaultsTaken,
|
|
7465
8094
|
dry_run: opts.dryRun === true,
|
|
8095
|
+
/*
|
|
8096
|
+
* The home these paths are relative TO, and it is only here when it
|
|
8097
|
+
* is not the ordinary one.
|
|
8098
|
+
*
|
|
8099
|
+
* Every path in `changes` is reported relative to the home being
|
|
8100
|
+
* installed into, so `.claude/skills/…` names a file without saying
|
|
8101
|
+
* whose. That ambiguity is why an agent writing into a real machine
|
|
8102
|
+
* from inside a sandbox had nothing in front of it that looked
|
|
8103
|
+
* wrong. Present only when scoped, so the common case stays quiet
|
|
8104
|
+
* and the unusual one cannot be mistaken for it.
|
|
8105
|
+
*/
|
|
8106
|
+
...agentHome() !== homedir4() ? { agent_home: agentHome() } : {},
|
|
7466
8107
|
tools: choice.tools.map((t) => t.id),
|
|
7467
8108
|
auto_update: choice.autoUpdate,
|
|
7468
8109
|
hook: choice.hook,
|
|
@@ -7486,9 +8127,27 @@ function setupCommand() {
|
|
|
7486
8127
|
if (opts.silent && changed.length === 0) return;
|
|
7487
8128
|
if (opts.dryRun) {
|
|
7488
8129
|
printSignInStep(signIn);
|
|
7489
|
-
|
|
8130
|
+
if (changed.length === 0) {
|
|
8131
|
+
printHuman(
|
|
8132
|
+
`Nothing to change \u2014 ${choice.tools.map((t) => t.label).join(", ")} ${choice.tools.length === 1 ? "is" : "are"} already up to date.
|
|
8133
|
+
`
|
|
8134
|
+
);
|
|
8135
|
+
printHuman(
|
|
8136
|
+
c.gray(
|
|
8137
|
+
` ${results.length} file(s) checked. Run without \`--dry-run\` to reinstall them anyway.`
|
|
8138
|
+
)
|
|
8139
|
+
);
|
|
8140
|
+
return;
|
|
8141
|
+
}
|
|
8142
|
+
printHuman(`Would change ${changed.length} of ${results.length} file(s):
|
|
7490
8143
|
`);
|
|
7491
8144
|
for (const r of changed) printHuman(c.gray(` ${r.tool} \xB7 ${r.what}`));
|
|
8145
|
+
printHuman(
|
|
8146
|
+
c.gray(
|
|
8147
|
+
`
|
|
8148
|
+
The rest are already up to date. Tools found: ${choice.tools.map((t) => t.label).join(", ")}.`
|
|
8149
|
+
)
|
|
8150
|
+
);
|
|
7492
8151
|
return;
|
|
7493
8152
|
}
|
|
7494
8153
|
printSignInStep(signIn);
|
|
@@ -7589,7 +8248,7 @@ async function applySetup(choice, dryRun) {
|
|
|
7589
8248
|
const hub = await loadAsset(BRAND.hubFileName);
|
|
7590
8249
|
const installed = { ...(await loadConfig()).system.installed };
|
|
7591
8250
|
const mark = (path, hash) => {
|
|
7592
|
-
const key2 = path.startsWith(
|
|
8251
|
+
const key2 = path.startsWith(agentHome()) ? path.slice(agentHome().length + 1) : path;
|
|
7593
8252
|
installed[key2] = hash;
|
|
7594
8253
|
};
|
|
7595
8254
|
const persist = async () => {
|
|
@@ -7601,11 +8260,11 @@ async function applySetup(choice, dryRun) {
|
|
|
7601
8260
|
});
|
|
7602
8261
|
};
|
|
7603
8262
|
const wasEdited = (path, onDisk) => {
|
|
7604
|
-
const key2 = path.startsWith(
|
|
8263
|
+
const key2 = path.startsWith(agentHome()) ? path.slice(agentHome().length + 1) : path;
|
|
7605
8264
|
const known = installed[key2];
|
|
7606
8265
|
return known !== void 0 && onDisk !== null && fingerprint(onDisk) !== known;
|
|
7607
8266
|
};
|
|
7608
|
-
const keyOf = (path) => path.startsWith(
|
|
8267
|
+
const keyOf = (path) => path.startsWith(agentHome()) ? path.slice(agentHome().length + 1) : path;
|
|
7609
8268
|
try {
|
|
7610
8269
|
return await write();
|
|
7611
8270
|
} finally {
|
|
@@ -7613,7 +8272,7 @@ async function applySetup(choice, dryRun) {
|
|
|
7613
8272
|
}
|
|
7614
8273
|
async function write() {
|
|
7615
8274
|
for (const tool of choice.tools) {
|
|
7616
|
-
const hubPath = join19(
|
|
8275
|
+
const hubPath = join19(agentHome(), tool.hubFile);
|
|
7617
8276
|
const written = await injectBlock(
|
|
7618
8277
|
hubPath,
|
|
7619
8278
|
hub + tailFor(tool),
|
|
@@ -7629,7 +8288,7 @@ async function applySetup(choice, dryRun) {
|
|
|
7629
8288
|
if (!dryRun && written.after !== null) mark(hubPath, written.after);
|
|
7630
8289
|
for (const skill of BRAND.subSkills) {
|
|
7631
8290
|
const content = await loadAsset(join19(skill, "SKILL.md"));
|
|
7632
|
-
const path = join19(
|
|
8291
|
+
const path = join19(agentHome(), tool.skillsDir, skill, "SKILL.md");
|
|
7633
8292
|
const out = await writeIfDifferent(path, content, dryRun);
|
|
7634
8293
|
results.push({
|
|
7635
8294
|
tool: tool.label,
|
|
@@ -7699,7 +8358,7 @@ async function resolveMode(opts) {
|
|
|
7699
8358
|
return { kind: mode ?? "agent", chosen: mode !== void 0 };
|
|
7700
8359
|
}
|
|
7701
8360
|
async function detectTools() {
|
|
7702
|
-
const home =
|
|
8361
|
+
const home = agentHome();
|
|
7703
8362
|
const found = [];
|
|
7704
8363
|
for (const tool of TOOLS) {
|
|
7705
8364
|
if (await exists2(join19(home, tool.dir))) found.push(tool);
|
|
@@ -7832,7 +8491,7 @@ async function writeIfDifferent(filePath, content, dryRun) {
|
|
|
7832
8491
|
return { outcome: existing === null ? "injected" : "updated", before: existing, after };
|
|
7833
8492
|
}
|
|
7834
8493
|
async function installHook(spec, tool, dryRun) {
|
|
7835
|
-
const settingsPath = join19(
|
|
8494
|
+
const settingsPath = join19(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
7836
8495
|
const raw = await readFile19(settingsPath, "utf8").catch(() => null);
|
|
7837
8496
|
let settings = {};
|
|
7838
8497
|
if (raw !== null) {
|
|
@@ -7882,7 +8541,7 @@ async function installHook(spec, tool, dryRun) {
|
|
|
7882
8541
|
return raw === null ? "injected" : "updated";
|
|
7883
8542
|
}
|
|
7884
8543
|
async function removeHook(spec, tool, dryRun) {
|
|
7885
|
-
const settingsPath = join19(
|
|
8544
|
+
const settingsPath = join19(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
7886
8545
|
const raw = await readFile19(settingsPath, "utf8").catch(() => null);
|
|
7887
8546
|
if (raw === null) return "unchanged";
|
|
7888
8547
|
let parsed;
|
|
@@ -8093,7 +8752,7 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
|
|
|
8093
8752
|
});
|
|
8094
8753
|
|
|
8095
8754
|
// src/program.ts
|
|
8096
|
-
import { Command as
|
|
8755
|
+
import { Command as Command28 } from "commander";
|
|
8097
8756
|
|
|
8098
8757
|
// src/help.ts
|
|
8099
8758
|
init_ui();
|
|
@@ -8117,7 +8776,7 @@ var COMMAND_GROUPS = [
|
|
|
8117
8776
|
{
|
|
8118
8777
|
title: "ACCOUNT",
|
|
8119
8778
|
blurb: "optional \u2014 connect a hosted brain; everything above works without it",
|
|
8120
|
-
commands: ["login", "logout", "whoami", "push", "pull"]
|
|
8779
|
+
commands: ["login", "logout", "whoami", "brains", "push", "pull"]
|
|
8121
8780
|
},
|
|
8122
8781
|
{
|
|
8123
8782
|
title: "SYSTEM",
|
|
@@ -8130,7 +8789,12 @@ var ENV_ROWS = [
|
|
|
8130
8789
|
["MEMGINEERING_HOME=<dir>", "state + derived index (default: ~/.memgineering)"],
|
|
8131
8790
|
["MEMGINEERING_JSON=1", "force --json framing without the flag"],
|
|
8132
8791
|
["MEMGINEERING_NO_UPDATE=1", "skip the update check for this run"],
|
|
8133
|
-
["MEMGINEERING_API_URL=<url>", "hosted brain server (default: https://api.memgineering.com)"]
|
|
8792
|
+
["MEMGINEERING_API_URL=<url>", "hosted brain server (default: https://api.memgineering.com)"],
|
|
8793
|
+
// The one that keeps `setup` off a real machine, and it was missing from this
|
|
8794
|
+
// list while the other four were here. An agent testing this CLI in a sandbox
|
|
8795
|
+
// had no documented handle for where `setup` writes — which is how one of them
|
|
8796
|
+
// overwrote the live skill files of the session testing it.
|
|
8797
|
+
["MEMGINEERING_AGENT_HOME=<dir>", "where `setup` installs (default: your home)"]
|
|
8134
8798
|
];
|
|
8135
8799
|
function commandLabel(sub) {
|
|
8136
8800
|
const args = sub.registeredArguments.map((a) => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(" ");
|
|
@@ -8283,12 +8947,16 @@ var cloudRules = (brain, args = {}) => call(brain, "POST", "/rules", {
|
|
|
8283
8947
|
var cloudReindex = (brain, args = {}) => call(brain, "POST", "/reindex", {
|
|
8284
8948
|
...args.rebuild === true ? { rebuild: true } : {}
|
|
8285
8949
|
});
|
|
8286
|
-
|
|
8950
|
+
function findCloudBrain(brains, named) {
|
|
8951
|
+
return brains.find((brain) => brain.id === named) ?? brains.find((brain) => brain.name === named);
|
|
8952
|
+
}
|
|
8953
|
+
async function listCloudBrains(token, baseUrl, opts = {}) {
|
|
8287
8954
|
const res = await apiRequest({
|
|
8288
8955
|
method: "GET",
|
|
8289
8956
|
path: "/v1/brains",
|
|
8290
8957
|
token,
|
|
8291
|
-
baseUrl
|
|
8958
|
+
baseUrl,
|
|
8959
|
+
...opts.retries === void 0 ? {} : { retries: opts.retries }
|
|
8292
8960
|
});
|
|
8293
8961
|
return res.body?.brains ?? [];
|
|
8294
8962
|
}
|
|
@@ -8360,11 +9028,11 @@ function describeHeldBack(held) {
|
|
|
8360
9028
|
return [
|
|
8361
9029
|
...heldBackGroups(held).flatMap(
|
|
8362
9030
|
({ label, rows }) => rows.map(
|
|
8363
|
-
(r) => `${
|
|
9031
|
+
(r) => `${oneLine(r.path, WIDTH.path)} \u2014 ${label}${r.detail === null ? "" : ` (${oneLine(r.detail, WIDTH.text)})`}`
|
|
8364
9032
|
)
|
|
8365
9033
|
),
|
|
8366
9034
|
...held.folders.map(
|
|
8367
|
-
(dir) => `${
|
|
9035
|
+
(dir) => `${oneLine(dir, WIDTH.path)}/ \u2014 an excluded folder, whose contents were never walked`
|
|
8368
9036
|
)
|
|
8369
9037
|
];
|
|
8370
9038
|
}
|
|
@@ -8384,7 +9052,7 @@ ${chosenCount} note(s) are not in ${wording.where}, because your rules say so:`
|
|
|
8384
9052
|
for (const { label, rows } of chosen) {
|
|
8385
9053
|
if (rows.length === 0) continue;
|
|
8386
9054
|
printHuman(c.gray(` ${rows.length} \u2014 ${label}:`));
|
|
8387
|
-
for (const r of rows.slice(0, 10)) printHuman(c.gray(` ${
|
|
9055
|
+
for (const r of rows.slice(0, 10)) printHuman(c.gray(` ${oneLine(r.path, WIDTH.path)}`));
|
|
8388
9056
|
if (rows.length > 10) printHuman(c.gray(` \u2026 and ${rows.length - 10} more`));
|
|
8389
9057
|
}
|
|
8390
9058
|
if (held.folders.length > 0) {
|
|
@@ -8394,7 +9062,8 @@ ${chosenCount} note(s) are not in ${wording.where}, because your rules say so:`
|
|
|
8394
9062
|
inside them are not counted above:`
|
|
8395
9063
|
)
|
|
8396
9064
|
);
|
|
8397
|
-
for (const dir of held.folders.slice(0, 10))
|
|
9065
|
+
for (const dir of held.folders.slice(0, 10))
|
|
9066
|
+
printHuman(c.gray(` ${oneLine(dir, WIDTH.path)}/`));
|
|
8398
9067
|
if (held.folders.length > 10) {
|
|
8399
9068
|
printHuman(c.gray(` \u2026 and ${held.folders.length - 10} more`));
|
|
8400
9069
|
}
|
|
@@ -8408,7 +9077,11 @@ ${chosenCount} note(s) are not in ${wording.where}, because your rules say so:`
|
|
|
8408
9077
|
)
|
|
8409
9078
|
);
|
|
8410
9079
|
for (const d of guessed.slice(0, 10)) {
|
|
8411
|
-
printHuman(
|
|
9080
|
+
printHuman(
|
|
9081
|
+
c.gray(
|
|
9082
|
+
` ${oneLine(d.path, WIDTH.path)} (matched "${oneLine(d.term ?? "?", WIDTH.title)}")`
|
|
9083
|
+
)
|
|
9084
|
+
);
|
|
8412
9085
|
}
|
|
8413
9086
|
if (guessed.length > 10) printHuman(c.gray(` \u2026 and ${guessed.length - 10} more`));
|
|
8414
9087
|
printHuman(
|
|
@@ -8456,13 +9129,13 @@ function isOpaqueId(id) {
|
|
|
8456
9129
|
return id.length === OPAQUE_ID_LENGTH && /^[0-9ABCDEFGHJKMNPQRSTVWXYZ]+$/.test(id);
|
|
8457
9130
|
}
|
|
8458
9131
|
function displayHandle(id, allIds) {
|
|
8459
|
-
if (!isOpaqueId(id)) return
|
|
9132
|
+
if (!isOpaqueId(id)) return oneLineWhole(id);
|
|
8460
9133
|
for (let len = MIN_HANDLE_LENGTH; len < id.length; len++) {
|
|
8461
9134
|
const prefix = id.slice(0, len);
|
|
8462
9135
|
const matches = allIds.filter((other) => other.startsWith(prefix));
|
|
8463
9136
|
if (matches.length === 1) return prefix;
|
|
8464
9137
|
}
|
|
8465
|
-
return
|
|
9138
|
+
return oneLineWhole(id);
|
|
8466
9139
|
}
|
|
8467
9140
|
function matchIds(ref, allIds) {
|
|
8468
9141
|
const exact = allIds.filter((id) => id === ref);
|
|
@@ -8689,14 +9362,127 @@ init_api_client();
|
|
|
8689
9362
|
init_credentials();
|
|
8690
9363
|
init_pending_login();
|
|
8691
9364
|
init_config();
|
|
9365
|
+
init_ui();
|
|
8692
9366
|
init_vault();
|
|
8693
9367
|
async function resolveTarget(opts = {}) {
|
|
9368
|
+
if (opts.brain !== void 0) return namedCloudTarget({ ...opts, brain: opts.brain });
|
|
8694
9369
|
if (opts.local !== true) {
|
|
8695
9370
|
const cloud = await cloudTarget();
|
|
8696
|
-
if (cloud
|
|
9371
|
+
if (cloud && cloud.kind === "cloud") {
|
|
9372
|
+
if (opts.vault !== void 0) {
|
|
9373
|
+
throw memgError(
|
|
9374
|
+
"invalid_input",
|
|
9375
|
+
`\`--vault\` names a folder on this machine, and this machine reads the hosted brain \`${oneLine(cloud.name, WIDTH.title)}\``,
|
|
9376
|
+
`Nothing was read from \`${oneLine(opts.vault, WIDTH.path)}\`, and nothing was changed.
|
|
9377
|
+
|
|
9378
|
+
To read that folder just this once: add \`--local\`
|
|
9379
|
+
To change which hosted brain this machine reads: \`memgineering use --brain <name>\`
|
|
9380
|
+
|
|
9381
|
+
This used to answer from the hosted brain without saying so, so a check of one
|
|
9382
|
+
brain silently reported another.`
|
|
9383
|
+
);
|
|
9384
|
+
}
|
|
9385
|
+
return cloud;
|
|
9386
|
+
}
|
|
9387
|
+
}
|
|
9388
|
+
try {
|
|
9389
|
+
const { brain, via } = await resolveBrain(opts.vault);
|
|
9390
|
+
return { kind: "local", brain, via };
|
|
9391
|
+
} catch (err) {
|
|
9392
|
+
if (!isNoVaultLinked(err) || opts.local === true) throw err;
|
|
9393
|
+
throw await accountAwareRefusal() ?? err;
|
|
9394
|
+
}
|
|
9395
|
+
}
|
|
9396
|
+
async function namedCloudTarget(opts) {
|
|
9397
|
+
const named = opts.brain;
|
|
9398
|
+
if (named.startsWith("--")) {
|
|
9399
|
+
throw memgError(
|
|
9400
|
+
"invalid_input",
|
|
9401
|
+
`\`--brain\` was given the flag \`${oneLine(named, WIDTH.title)}\` as its name`,
|
|
9402
|
+
"Nothing was read or changed. Put the hosted brain name or id immediately after `--brain`, then pass the other flag separately."
|
|
9403
|
+
);
|
|
9404
|
+
}
|
|
9405
|
+
const clashes = [
|
|
9406
|
+
opts.local === true ? "`--local`" : null,
|
|
9407
|
+
opts.vault !== void 0 ? "`--vault`" : null
|
|
9408
|
+
].filter((value) => value !== null);
|
|
9409
|
+
if (clashes.length > 0) {
|
|
9410
|
+
throw memgError(
|
|
9411
|
+
"invalid_input",
|
|
9412
|
+
`\`--brain\` cannot be combined with ${clashes.join(" and ")}`,
|
|
9413
|
+
"`--brain <name>` reads an existing brain on the account. `--local` and `--vault <path>` read a folder on this machine. Nothing was read or changed; choose one target kind."
|
|
9414
|
+
);
|
|
9415
|
+
}
|
|
9416
|
+
const base = apiUrl();
|
|
9417
|
+
const credentials = await requireCredentials(base);
|
|
9418
|
+
const brains = await listCloudBrains(credentials.token, base);
|
|
9419
|
+
if (brains.length === 0) {
|
|
9420
|
+
throw memgError(
|
|
9421
|
+
"invalid_input",
|
|
9422
|
+
"this account has no hosted brains",
|
|
9423
|
+
"Nothing was read or changed. `--brain` only names an existing hosted brain; it never creates one."
|
|
9424
|
+
);
|
|
9425
|
+
}
|
|
9426
|
+
const found = findCloudBrain(brains, named);
|
|
9427
|
+
if (!found) {
|
|
9428
|
+
const available = brains.map((brain) => ` ${oneLine(brain.name, WIDTH.title)} (${oneLine(brain.id, WIDTH.handle)})`).join("\n");
|
|
9429
|
+
throw memgError(
|
|
9430
|
+
"invalid_input",
|
|
9431
|
+
`no hosted brain called "${oneLine(named, WIDTH.title)}"`,
|
|
9432
|
+
`Nothing was read or changed. This account has:
|
|
9433
|
+
${available}
|
|
9434
|
+
Use one of those names or ids. No brain was created.`
|
|
9435
|
+
);
|
|
9436
|
+
}
|
|
9437
|
+
return { kind: "cloud", brainId: found.id, name: found.name, apiUrl: base };
|
|
9438
|
+
}
|
|
9439
|
+
var isNoVaultLinked = (err) => typeof err === "object" && err !== null && err.code === "no_vault_linked";
|
|
9440
|
+
async function accountAwareRefusal() {
|
|
9441
|
+
try {
|
|
9442
|
+
const base = apiUrl();
|
|
9443
|
+
const credentials = await readCredentials();
|
|
9444
|
+
if (!credentials || credentials.api_url !== base) {
|
|
9445
|
+
const cloud = (await loadConfig()).brain.cloud;
|
|
9446
|
+
if (!cloud || cloud.api_url !== base) return null;
|
|
9447
|
+
return memgError(
|
|
9448
|
+
"not_signed_in",
|
|
9449
|
+
`this machine is signed out of \`${oneLine(cloud.name, WIDTH.title)}\``,
|
|
9450
|
+
"Your notes are on that account \u2014 nothing is lost, and nothing here needs creating.\n\nSign back in: `memgineering login`\n\nDo NOT run `init` or `link` to fix this. They make a NEW empty brain on this\nmachine and leave the notes where they are."
|
|
9451
|
+
);
|
|
9452
|
+
}
|
|
9453
|
+
const brains = await listCloudBrains(credentials.token, base, { retries: 0 });
|
|
9454
|
+
if (brains.length === 0) return null;
|
|
9455
|
+
const names = brains.map((b) => ` ${oneLine(b.name, WIDTH.title)}`).join("\n");
|
|
9456
|
+
return memgError(
|
|
9457
|
+
"no_vault_linked",
|
|
9458
|
+
"this machine is not pointed at any brain yet",
|
|
9459
|
+
`Your notes are on your account \u2014 nothing is lost, and nothing here needs creating.
|
|
9460
|
+
This account holds:
|
|
9461
|
+
${names}
|
|
9462
|
+
|
|
9463
|
+
Point at one: \`memgineering use --brain <name>\`
|
|
9464
|
+
|
|
9465
|
+
Do NOT run \`init\` or \`link\` to fix this. They make a NEW empty brain on this
|
|
9466
|
+
machine, which leaves the notes above exactly where they are and adds an empty
|
|
9467
|
+
notebook that looks like the answer.`
|
|
9468
|
+
);
|
|
9469
|
+
} catch (err) {
|
|
9470
|
+
if (err instanceof MemgError && err.code !== "server_unreachable") {
|
|
9471
|
+
return memgError(
|
|
9472
|
+
"not_signed_in",
|
|
9473
|
+
"this machine cannot read the account it is signed in to",
|
|
9474
|
+
`The server refused: ${oneLine(err.message, 120)}
|
|
9475
|
+
|
|
9476
|
+
Your notes are on that account \u2014 nothing is lost, and nothing here needs creating.
|
|
9477
|
+
|
|
9478
|
+
Sign in again: \`memgineering login\`
|
|
9479
|
+
|
|
9480
|
+
Do NOT run \`init\` or \`link\` to fix this. They make a NEW empty brain on this
|
|
9481
|
+
machine and leave the notes where they are.`
|
|
9482
|
+
);
|
|
9483
|
+
}
|
|
9484
|
+
return null;
|
|
8697
9485
|
}
|
|
8698
|
-
const { brain, via } = await resolveBrain(opts.vault);
|
|
8699
|
-
return { kind: "local", brain, via };
|
|
8700
9486
|
}
|
|
8701
9487
|
async function hostedPointer() {
|
|
8702
9488
|
const base = apiUrl();
|
|
@@ -8788,7 +9574,9 @@ function reindexCommand() {
|
|
|
8788
9574
|
)
|
|
8789
9575
|
);
|
|
8790
9576
|
for (const d of byContent.slice(0, 10)) {
|
|
8791
|
-
printHuman(
|
|
9577
|
+
printHuman(
|
|
9578
|
+
c.gray(` ${oneLine(d.path, WIDTH.path)} (${oneLine(d.detail, WIDTH.text)})`)
|
|
9579
|
+
);
|
|
8792
9580
|
}
|
|
8793
9581
|
if (byContent.length > 10) {
|
|
8794
9582
|
printHuman(c.gray(` \u2026 and ${byContent.length - 10} more`));
|
|
@@ -8809,7 +9597,7 @@ async function reindexHosted(target, rebuild) {
|
|
|
8809
9597
|
// route documents, spelled the way the route spells it.
|
|
8810
9598
|
json: { brain: { hosted: true, name: target.name }, ...answered },
|
|
8811
9599
|
human: () => {
|
|
8812
|
-
printHuman(`Reindexed ${
|
|
9600
|
+
printHuman(`Reindexed ${oneLine(target.name, WIDTH.title)}.
|
|
8813
9601
|
`);
|
|
8814
9602
|
if (lexical === void 0) {
|
|
8815
9603
|
printHuman(c.yellow("\u26A0 this brain\u2019s server did not report the card index"));
|
|
@@ -8847,7 +9635,7 @@ async function reindexHosted(target, rebuild) {
|
|
|
8847
9635
|
if (answered.usable === false) {
|
|
8848
9636
|
printHuman(
|
|
8849
9637
|
c.yellow(
|
|
8850
|
-
` \u26A0 vector search is off for this brain (${
|
|
9638
|
+
` \u26A0 vector search is off for this brain (${oneLine(answered.unusable_reason ?? "reason not given", WIDTH.text)})`
|
|
8851
9639
|
)
|
|
8852
9640
|
);
|
|
8853
9641
|
}
|
|
@@ -8866,7 +9654,7 @@ function printFailures(failed, failures) {
|
|
|
8866
9654
|
for (const failure of (failures ?? []).slice(0, 3)) {
|
|
8867
9655
|
printHuman(
|
|
8868
9656
|
c.gray(
|
|
8869
|
-
` ${
|
|
9657
|
+
` ${oneLine(failure.path, WIDTH.path)} (${oneLine(failure.kind, WIDTH.title)}: ${oneLine(failure.detail, WIDTH.text)})`
|
|
8870
9658
|
)
|
|
8871
9659
|
);
|
|
8872
9660
|
}
|
|
@@ -8883,18 +9671,39 @@ function nextStep(stopReason) {
|
|
|
8883
9671
|
case "unusable_index":
|
|
8884
9672
|
return "This brain holds vectors of more than one width, so none of them can be compared. `memgineering reindex --rebuild` re-derives them from the model as it is configured now.";
|
|
8885
9673
|
default:
|
|
8886
|
-
return `The server stopped for \`${
|
|
9674
|
+
return `The server stopped for \`${oneLine(stopReason, WIDTH.title)}\`, which this version has no next step for. Running \`memgineering reindex\` again is safe.`;
|
|
8887
9675
|
}
|
|
8888
9676
|
}
|
|
8889
9677
|
function unlinkCommand() {
|
|
8890
|
-
return new Command2("unlink").description("stop reading a brain \u2014 your notes are not touched").argument("[path]", "which brain (default: the one this folder resolves to)").option("--purge", "also delete the local index, event log and undo snapshots").action(async (path, opts) => {
|
|
8891
|
-
|
|
9678
|
+
return new Command2("unlink").description("stop reading a brain \u2014 your notes are not touched").argument("[path]", "which brain (default: the one this folder resolves to)").option("--brain", "stop reading the HOSTED brain instead of a folder on this disk").option("--purge", "also delete the local index, event log and undo snapshots").action(async (path, opts) => {
|
|
9679
|
+
if (opts.brain === true) {
|
|
9680
|
+
await stopReadingHosted(path);
|
|
9681
|
+
return;
|
|
9682
|
+
}
|
|
9683
|
+
const hostedNow = await hostedPointer();
|
|
9684
|
+
const linkedNow = (await loadConfig()).brain.brains;
|
|
9685
|
+
if (hostedNow && linkedNow.length === 0) {
|
|
9686
|
+
throw memgError(
|
|
9687
|
+
"invalid_input",
|
|
9688
|
+
`nothing is linked on this disk \u2014 this machine reads the hosted brain "${oneLine(hostedNow.name, 72)}"`,
|
|
9689
|
+
"Nothing was changed, and nothing here creates a brain.\n`memgineering unlink --brain` stops reading the hosted one.\n`memgineering link <folder>` adds notes on this disk alongside it."
|
|
9690
|
+
);
|
|
9691
|
+
}
|
|
9692
|
+
const { brain } = await resolveBrain(path).catch(async (err) => {
|
|
9693
|
+
await resolveTarget({});
|
|
9694
|
+
throw err;
|
|
9695
|
+
});
|
|
8892
9696
|
const cfg = await loadConfig();
|
|
8893
9697
|
const remaining = cfg.brain.brains.filter((b) => b.root !== brain.root);
|
|
8894
9698
|
const nextDefault = cfg.brain.default === brain.root ? remaining[0]?.root : cfg.brain.default;
|
|
9699
|
+
const { default: _dropped, ...restOfBrain } = cfg.brain;
|
|
8895
9700
|
await saveConfig({
|
|
8896
9701
|
...cfg,
|
|
8897
|
-
brain: {
|
|
9702
|
+
brain: {
|
|
9703
|
+
...restOfBrain,
|
|
9704
|
+
brains: remaining,
|
|
9705
|
+
...nextDefault === void 0 ? {} : { default: nextDefault }
|
|
9706
|
+
}
|
|
8898
9707
|
});
|
|
8899
9708
|
if (opts.purge) {
|
|
8900
9709
|
await clearCache(cacheDirFor(brain.root, brandHome()));
|
|
@@ -8982,6 +9791,54 @@ async function readLines(root, name) {
|
|
|
8982
9791
|
const raw = await readFile10(join10(root, name), "utf8").catch(() => "");
|
|
8983
9792
|
return raw.split("\n").map((l) => l.replace(/\r$/, "").trim()).filter((l) => l !== "" && !l.startsWith("#"));
|
|
8984
9793
|
}
|
|
9794
|
+
async function stopReadingHosted(path) {
|
|
9795
|
+
if (path !== void 0) {
|
|
9796
|
+
throw memgError(
|
|
9797
|
+
"invalid_input",
|
|
9798
|
+
"`unlink --brain` does not take a path",
|
|
9799
|
+
"It clears the one hosted brain this machine reads, which is a single setting for the whole machine. To unlink a folder on this disk, run `memgineering unlink <path>` without `--brain`."
|
|
9800
|
+
);
|
|
9801
|
+
}
|
|
9802
|
+
const cfg = await loadConfig();
|
|
9803
|
+
const previous = cfg.brain.cloud ?? null;
|
|
9804
|
+
if (previous === null) {
|
|
9805
|
+
throw memgError(
|
|
9806
|
+
"invalid_input",
|
|
9807
|
+
"this machine is not reading a hosted brain",
|
|
9808
|
+
"Nothing was changed. `memgineering brains` lists what the account holds, and `memgineering use --brain <name>` points at one."
|
|
9809
|
+
);
|
|
9810
|
+
}
|
|
9811
|
+
await saveConfig({
|
|
9812
|
+
...cfg,
|
|
9813
|
+
// Rebuilt without `cloud` rather than set to undefined, so what lands on
|
|
9814
|
+
// disk is a config with no such key rather than one asserting null.
|
|
9815
|
+
brain: {
|
|
9816
|
+
brains: cfg.brain.brains,
|
|
9817
|
+
...cfg.brain.default === void 0 ? {} : { default: cfg.brain.default }
|
|
9818
|
+
}
|
|
9819
|
+
});
|
|
9820
|
+
printDual({
|
|
9821
|
+
json: {
|
|
9822
|
+
stopped_reading: { id: previous.id, name: previous.name, api_url: previous.api_url },
|
|
9823
|
+
remaining_local: cfg.brain.brains.length
|
|
9824
|
+
},
|
|
9825
|
+
human: () => {
|
|
9826
|
+
printHuman(`No longer reading \`${oneLine(previous.name, 72)}\`.
|
|
9827
|
+
`);
|
|
9828
|
+
printHuman(
|
|
9829
|
+
c.gray(
|
|
9830
|
+
" Nothing on the server was touched \u2014 the brain, its notes and its ledger\n are exactly where they were. `memgineering use --brain <name>` points back."
|
|
9831
|
+
)
|
|
9832
|
+
);
|
|
9833
|
+
if (cfg.brain.brains.length === 0) {
|
|
9834
|
+
printHuman(
|
|
9835
|
+
c.yellow("\n No brain is linked on this disk either, so nothing answers here now.")
|
|
9836
|
+
);
|
|
9837
|
+
printHuman(c.gray(" `memgineering link <folder>` connects notes you already have."));
|
|
9838
|
+
}
|
|
9839
|
+
}
|
|
9840
|
+
});
|
|
9841
|
+
}
|
|
8985
9842
|
|
|
8986
9843
|
// src/commands/consolidate.ts
|
|
8987
9844
|
init_src();
|
|
@@ -9255,10 +10112,14 @@ async function assembleStage(adapter, entry, detail, sectionName) {
|
|
|
9255
10112
|
init_vault();
|
|
9256
10113
|
init_ui();
|
|
9257
10114
|
function openCommand() {
|
|
9258
|
-
return new Command4("open").description("open one memory, as deeply as you ask").argument("<ref>", "handle, id, path, or exact title").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
|
|
10115
|
+
return new Command4("open").description("open one memory, as deeply as you ask").argument("<ref>", "handle, id, path, or exact title").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
|
|
10116
|
+
"--brain <name>",
|
|
10117
|
+
"read from one existing hosted brain by name or id, without changing the pointer"
|
|
10118
|
+
).option("--detail <level>", "card | summary | chunks | full (default: card)").option("--section <name>", "one section (implies --detail chunks)").action(
|
|
9259
10119
|
async (ref, opts) => {
|
|
9260
10120
|
const detail = parseDetail(opts.detail, opts.section ? "chunks" : "card");
|
|
9261
10121
|
const target = await resolveTarget({
|
|
10122
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
9262
10123
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
9263
10124
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
9264
10125
|
});
|
|
@@ -9279,17 +10140,23 @@ function openCommand() {
|
|
|
9279
10140
|
brain: { hosted: true, name: target.name }
|
|
9280
10141
|
},
|
|
9281
10142
|
human: () => {
|
|
9282
|
-
printHuman(
|
|
9283
|
-
|
|
10143
|
+
printHuman(
|
|
10144
|
+
`## ${oneLine(String(found.memory["title"] ?? found.path), WIDTH.title)}
|
|
10145
|
+
`
|
|
10146
|
+
);
|
|
9284
10147
|
const summary = found.memory["summary"];
|
|
9285
10148
|
if (typeof summary === "string" && summary !== "") {
|
|
9286
|
-
printHuman(`> ${
|
|
10149
|
+
printHuman(`> ${oneLine(summary, WIDTH.text)}
|
|
9287
10150
|
`);
|
|
9288
10151
|
}
|
|
9289
|
-
printHuman(
|
|
10152
|
+
printHuman(
|
|
10153
|
+
c.gray(
|
|
10154
|
+
`${oneLine(found.path, WIDTH.path)} \xB7 ${oneLine(target.name, WIDTH.title)}`
|
|
10155
|
+
)
|
|
10156
|
+
);
|
|
9290
10157
|
if (detail !== "card" && found.body !== "") {
|
|
9291
|
-
printHuman(
|
|
9292
|
-
|
|
10158
|
+
printHuman("\n");
|
|
10159
|
+
printBlock(found.body);
|
|
9293
10160
|
}
|
|
9294
10161
|
}
|
|
9295
10162
|
});
|
|
@@ -9326,7 +10193,7 @@ ${stripControl(found.body)}`);
|
|
|
9326
10193
|
...stage
|
|
9327
10194
|
},
|
|
9328
10195
|
human: () => {
|
|
9329
|
-
printHuman(`## ${
|
|
10196
|
+
printHuman(`## ${oneLine(entry.memory.title, WIDTH.title)}
|
|
9330
10197
|
`);
|
|
9331
10198
|
if (scaffolding) {
|
|
9332
10199
|
printHuman(
|
|
@@ -9339,13 +10206,17 @@ ${stripControl(found.body)}`);
|
|
|
9339
10206
|
}
|
|
9340
10207
|
printHuman(
|
|
9341
10208
|
`
|
|
9342
|
-
\`id: ${
|
|
10209
|
+
\`id: ${oneLine(entry.memory.id, WIDTH.handle)}\` \xB7 \`${oneLine(entry.path, WIDTH.path)}\`
|
|
9343
10210
|
`
|
|
9344
10211
|
);
|
|
9345
10212
|
if (entry.memory.summary) printHuman(quoteBlock(entry.memory.summary) + "\n");
|
|
9346
10213
|
for (const claim of claims) {
|
|
9347
|
-
printHuman(`- ${
|
|
9348
|
-
printHuman(
|
|
10214
|
+
printHuman(`- ${oneLine(claim.text, WIDTH.text)}`);
|
|
10215
|
+
printHuman(
|
|
10216
|
+
c.gray(
|
|
10217
|
+
` ${oneLine(claim.confidence, WIDTH.title)} \xB7 ${oneLine(claim.status, WIDTH.title)}`
|
|
10218
|
+
)
|
|
10219
|
+
);
|
|
9349
10220
|
}
|
|
9350
10221
|
if (claims.length === 0 && scaffolding) {
|
|
9351
10222
|
printHuman(
|
|
@@ -9376,13 +10247,14 @@ makes the two surface together as a conflict.`
|
|
|
9376
10247
|
}
|
|
9377
10248
|
if (stage.outline && stage.outline.length > 0) {
|
|
9378
10249
|
printHuman("\n### sections\n");
|
|
9379
|
-
for (const heading of stage.outline)
|
|
10250
|
+
for (const heading of stage.outline)
|
|
10251
|
+
printHuman(`- ${oneLine(heading, WIDTH.title)}`);
|
|
9380
10252
|
printHuman("");
|
|
9381
10253
|
}
|
|
9382
10254
|
if (stage.sections) {
|
|
9383
10255
|
for (const section of stage.sections) {
|
|
9384
10256
|
printHuman(`
|
|
9385
|
-
### ${
|
|
10257
|
+
### ${oneLine(section.label, WIDTH.title)}
|
|
9386
10258
|
`);
|
|
9387
10259
|
printHuman(quoteBlock(section.body));
|
|
9388
10260
|
}
|
|
@@ -9942,11 +10814,11 @@ To see what is there now: \`memgineering open ` + ref + "`"
|
|
|
9942
10814
|
after: preview.after
|
|
9943
10815
|
},
|
|
9944
10816
|
human: () => {
|
|
9945
|
-
printHuman(`## revise (dry run): ${
|
|
10817
|
+
printHuman(`## revise (dry run): ${oneLine(entry.memory.title, WIDTH.title)}
|
|
9946
10818
|
`);
|
|
9947
|
-
printHuman(`\`${
|
|
10819
|
+
printHuman(`\`${oneLine(entry.path, WIDTH.path)}\`
|
|
9948
10820
|
`);
|
|
9949
|
-
|
|
10821
|
+
printBlock(renderDiff(preview.before, preview.after));
|
|
9950
10822
|
printHuman(c.gray("\nNothing was written. Drop --dry-run to apply."));
|
|
9951
10823
|
}
|
|
9952
10824
|
});
|
|
@@ -10006,10 +10878,10 @@ To see what is there now: \`memgineering open ` + ref + "`"
|
|
|
10006
10878
|
human: () => {
|
|
10007
10879
|
if (proposal.action === "conflict") {
|
|
10008
10880
|
printHuman(
|
|
10009
|
-
`Recorded a disagreement about **${
|
|
10881
|
+
`Recorded a disagreement about **${oneLine(entry.memory.title, WIDTH.title)}**.
|
|
10010
10882
|
`
|
|
10011
10883
|
);
|
|
10012
|
-
printHuman(`\`${
|
|
10884
|
+
printHuman(`\`${oneLine(entry.path, WIDTH.path)}\`
|
|
10013
10885
|
`);
|
|
10014
10886
|
printHuman(
|
|
10015
10887
|
c.gray(
|
|
@@ -10017,12 +10889,12 @@ To see what is there now: \`memgineering open ` + ref + "`"
|
|
|
10017
10889
|
)
|
|
10018
10890
|
);
|
|
10019
10891
|
} else {
|
|
10020
|
-
printHuman(`Revised **${
|
|
10892
|
+
printHuman(`Revised **${oneLine(entry.memory.title, WIDTH.title)}**.
|
|
10021
10893
|
`);
|
|
10022
|
-
printHuman(`\`${
|
|
10894
|
+
printHuman(`\`${oneLine(entry.path, WIDTH.path)}\`
|
|
10023
10895
|
`);
|
|
10024
10896
|
}
|
|
10025
|
-
|
|
10897
|
+
printBlock(renderDiff(preview.before, preview.after));
|
|
10026
10898
|
if (drift) {
|
|
10027
10899
|
printHuman(
|
|
10028
10900
|
c.yellow(`
|
|
@@ -10089,18 +10961,18 @@ async function reviseHosted(where, ref, opts) {
|
|
|
10089
10961
|
json: { brain: where.name, ...result },
|
|
10090
10962
|
human: () => {
|
|
10091
10963
|
if (result.dry_run) {
|
|
10092
|
-
printHuman(`## revise (dry run) \u2014 ${
|
|
10964
|
+
printHuman(`## revise (dry run) \u2014 ${oneLine(where.name, WIDTH.title)}
|
|
10093
10965
|
`);
|
|
10094
|
-
printHuman(`\`${
|
|
10966
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
10095
10967
|
`);
|
|
10096
|
-
|
|
10968
|
+
printBlock(renderDiff(result.before, result.after));
|
|
10097
10969
|
printHuman(c.gray("\nNothing was written. Drop --dry-run to apply."));
|
|
10098
10970
|
return;
|
|
10099
10971
|
}
|
|
10100
10972
|
if (opts.action === "conflict") {
|
|
10101
|
-
printHuman(`Recorded a disagreement in **${
|
|
10973
|
+
printHuman(`Recorded a disagreement in **${oneLine(where.name, WIDTH.title)}**.
|
|
10102
10974
|
`);
|
|
10103
|
-
printHuman(`\`${
|
|
10975
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
10104
10976
|
`);
|
|
10105
10977
|
printHuman(
|
|
10106
10978
|
c.gray(
|
|
@@ -10108,16 +10980,16 @@ async function reviseHosted(where, ref, opts) {
|
|
|
10108
10980
|
)
|
|
10109
10981
|
);
|
|
10110
10982
|
} else {
|
|
10111
|
-
printHuman(`Revised in **${
|
|
10983
|
+
printHuman(`Revised in **${oneLine(where.name, WIDTH.title)}**.
|
|
10112
10984
|
`);
|
|
10113
|
-
printHuman(`\`${
|
|
10985
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
10114
10986
|
`);
|
|
10115
10987
|
}
|
|
10116
|
-
|
|
10988
|
+
printBlock(renderDiff(result.before, result.after));
|
|
10117
10989
|
if (result.body_drift) {
|
|
10118
10990
|
printHuman(
|
|
10119
10991
|
c.yellow(`
|
|
10120
|
-
\u26A0 the body still reads "${
|
|
10992
|
+
\u26A0 the body still reads "${oneLine(result.body_drift, WIDTH.text)}"`) + c.gray(
|
|
10121
10993
|
"\n \u2014 the frontmatter now concludes differently. The prose is yours and was not\n touched; edit it yourself if it should agree."
|
|
10122
10994
|
)
|
|
10123
10995
|
);
|
|
@@ -10141,7 +11013,7 @@ To put it back: \`memgineering revise ${ref} --claim ${JSON.stringify(
|
|
|
10141
11013
|
);
|
|
10142
11014
|
} else if (result.reverse?.hint) {
|
|
10143
11015
|
printHuman(c.gray(`
|
|
10144
|
-
${
|
|
11016
|
+
${oneLine(result.reverse.hint, WIDTH.text)}`));
|
|
10145
11017
|
}
|
|
10146
11018
|
}
|
|
10147
11019
|
});
|
|
@@ -10185,7 +11057,7 @@ function nothingToChange(path, ref, verb = "revise") {
|
|
|
10185
11057
|
memgineering revise ${ref} --action reinforce --claim "<the conclusion>" --reason "<what confirms it>"` : `It is already ${verb === "retire" ? "retired" : "current"}. \`memgineering ${verb === "retire" ? "unretire" : "retire"} ${ref}\` is what changes that.`;
|
|
10186
11058
|
return memgError(
|
|
10187
11059
|
"nothing_to_change",
|
|
10188
|
-
`${
|
|
11060
|
+
`${oneLine(path, WIDTH.path)} already says exactly this`,
|
|
10189
11061
|
`Nothing was written. ${way}
|
|
10190
11062
|
A \`--reason\` does not make it a change either \u2014 it would record an operation that changed nothing, and one whose \`undo\` would reverse nothing while saying it had.
|
|
10191
11063
|
To see what the note says now: \`memgineering open ${ref}\``
|
|
@@ -10293,20 +11165,22 @@ function renderSummary(p) {
|
|
|
10293
11165
|
const claim = p.claim === void 0 || p.claim === null ? null : oneLine(p.claim, CLAIM_WIDTH);
|
|
10294
11166
|
const unavailable = p.claim === void 0 && !claimless;
|
|
10295
11167
|
const lines2 = [
|
|
10296
|
-
`- **${
|
|
11168
|
+
`- **${oneLine(p.action, WIDTH.title)}**${p.proposed_kind ? ` \u2192 ${p.proposed_kind}` : ""} \`${oneLine(p.target, WIDTH.path)}\``,
|
|
10297
11169
|
` ${p.id} ${p.state}`
|
|
10298
11170
|
];
|
|
10299
11171
|
if (claim !== null && claim !== "") lines2.push(` \u2192 ${claim}`);
|
|
10300
11172
|
else if (unavailable) {
|
|
10301
11173
|
lines2.push(
|
|
10302
11174
|
c.gray(
|
|
10303
|
-
` \u2192 (claim unavailable: this brain's server is older than the CLI. \`memgineering open ${
|
|
11175
|
+
` \u2192 (claim unavailable: this brain's server is older than the CLI. \`memgineering open ${oneLine(p.memory_id ?? p.target, WIDTH.handle)}\` shows what the note says NOW; upgrading the server restores the claim.)`
|
|
10304
11176
|
)
|
|
10305
11177
|
);
|
|
10306
11178
|
}
|
|
10307
11179
|
lines2.push(quoteBlock(p.reason, " > "));
|
|
10308
11180
|
if (p.supersedes.length > 0) {
|
|
10309
|
-
lines2.push(
|
|
11181
|
+
lines2.push(
|
|
11182
|
+
c.gray(` replaces: ${p.supersedes.map((id) => oneLine(id, WIDTH.handle)).join(", ")}`)
|
|
11183
|
+
);
|
|
10310
11184
|
}
|
|
10311
11185
|
return lines2.join("\n");
|
|
10312
11186
|
}
|
|
@@ -10422,10 +11296,10 @@ function renderApproval(applied, vault) {
|
|
|
10422
11296
|
},
|
|
10423
11297
|
human: () => {
|
|
10424
11298
|
printHuman(
|
|
10425
|
-
`Applied **${
|
|
11299
|
+
`Applied **${oneLine(applied.action, WIDTH.title)}** to \`${oneLine(applied.target, WIDTH.path)}\`.
|
|
10426
11300
|
`
|
|
10427
11301
|
);
|
|
10428
|
-
|
|
11302
|
+
printBlock(renderDiff(applied.before, applied.after));
|
|
10429
11303
|
printHuman(c.gray(`
|
|
10430
11304
|
Undo: \`${undoHint(applied.opId, vault)}\``));
|
|
10431
11305
|
}
|
|
@@ -10435,7 +11309,7 @@ function renderRejection(id, state, target) {
|
|
|
10435
11309
|
printDual({
|
|
10436
11310
|
json: { proposal_id: id, state, target },
|
|
10437
11311
|
human: () => {
|
|
10438
|
-
printHuman(`Declined \`${
|
|
11312
|
+
printHuman(`Declined \`${oneLine(target, WIDTH.path)}\`.
|
|
10439
11313
|
`);
|
|
10440
11314
|
printHuman(c.gray("The note was not touched."));
|
|
10441
11315
|
}
|
|
@@ -10493,9 +11367,9 @@ function renderRebase(id, state, target, before, after) {
|
|
|
10493
11367
|
hint: `This is the change as it applies to the note TODAY, and it has not been approved. Read the diff, then \`memgineering proposals approve ${id}\` \u2014 or reject it if the note moving on made it wrong.`
|
|
10494
11368
|
},
|
|
10495
11369
|
human: () => {
|
|
10496
|
-
printHuman(`Recomputed against \`${
|
|
11370
|
+
printHuman(`Recomputed against \`${oneLine(target, WIDTH.path)}\`.
|
|
10497
11371
|
`);
|
|
10498
|
-
|
|
11372
|
+
printBlock(renderDiff(before, after));
|
|
10499
11373
|
printHuman(
|
|
10500
11374
|
c.gray(
|
|
10501
11375
|
`
|
|
@@ -10587,9 +11461,11 @@ function consolidateCommand() {
|
|
|
10587
11461
|
[
|
|
10588
11462
|
"",
|
|
10589
11463
|
"What it looks for:",
|
|
10590
|
-
"
|
|
10591
|
-
" shared_context two notes in one scope naming the same thing",
|
|
11464
|
+
" declared_supersession a replaced note that is still active",
|
|
10592
11465
|
" contradiction a disagreement somebody declared and nobody settled",
|
|
11466
|
+
" semantic_similarity calibrated indexed meaning agrees reciprocally",
|
|
11467
|
+
" shared_context two notes in one scope naming the same thing",
|
|
11468
|
+
" similar_title two notes titled with mostly the same words",
|
|
10593
11469
|
" kind_promotion a note marked as binding but filed as something other",
|
|
10594
11470
|
" than a decision",
|
|
10595
11471
|
"",
|
|
@@ -10618,7 +11494,7 @@ function consolidateCommand() {
|
|
|
10618
11494
|
// Present on every answer, not only the empty one. A full list still
|
|
10619
11495
|
// leaves the reader one step short — the candidates are evidence, and
|
|
10620
11496
|
// the next command is the thing they cannot derive from evidence.
|
|
10621
|
-
hint: truncated ? `This brain is large enough that detection stopped after ${report.scanned} pairs, so these are candidates rather than all of them. Narrow it \u2014 \`--vault\` a smaller brain, or run again after acting on these \u2014 and treat the list as a sample. Proposals are still written one at a time: \`memgineering propose <action> --memory <id> --claim "\u2026" --reason "\u2026" --excerpt "\u2026"\`.` : candidates.length ? 'None of these is a proposal yet. Decide what each pair means, then: `memgineering propose <action> --memory <id> --claim "\u2026" --reason "\u2026" --excerpt "\u2026"`.
|
|
11497
|
+
hint: truncated ? `This brain is large enough that detection stopped after ${report.scanned} pairs, so these are candidates rather than all of them. Narrow it \u2014 \`--vault\` a smaller brain, or run again after acting on these \u2014 and treat the list as a sample. Proposals are still written one at a time: \`memgineering propose <action> --memory <id> --claim "\u2026" --reason "\u2026" --excerpt "\u2026"\`.` : candidates.length ? 'None of these is a proposal yet. Decide what each pair means, then: `memgineering propose <action> --memory <id> --claim "\u2026" --reason "\u2026" --excerpt "\u2026"`. An inferred duplicate is two proposals \u2014 supersede the winner and retire the loser. `declared_supersession` already has the winner edge, so only the suggested loser retirement remains.' : "Nothing looked duplicated, contradicted, or misfiled. Detection reads titles, scope, entities and declared edges; a hosted brain also reuses a complete calibrated vector generation, with stricter corroboration when context is absent. `memgineering recall <topic>` remains the broader semantic search."
|
|
10622
11498
|
},
|
|
10623
11499
|
human: () => {
|
|
10624
11500
|
if (candidates.length === 0) {
|
|
@@ -10645,7 +11521,7 @@ function consolidateCommand() {
|
|
|
10645
11521
|
}
|
|
10646
11522
|
printHuman(
|
|
10647
11523
|
c.gray(
|
|
10648
|
-
'\nNone of these is a proposal yet \u2014 nothing has been written.\nDecide what a pair means, then say so:\n memgineering propose <action> --memory <id> --claim "\u2026" --reason "\u2026" --excerpt "\u2026"\
|
|
11524
|
+
'\nNone of these is a proposal yet \u2014 nothing has been written.\nDecide what a pair means, then say so:\n memgineering propose <action> --memory <id> --claim "\u2026" --reason "\u2026" --excerpt "\u2026"\nAn inferred duplicate is two proposals: supersede the winner and retire the loser.\nA declared_supersession already has its winner edge; only retire the named loser.'
|
|
10649
11525
|
)
|
|
10650
11526
|
);
|
|
10651
11527
|
}
|
|
@@ -10656,16 +11532,16 @@ var TITLE_WIDTH = 72;
|
|
|
10656
11532
|
function noteLines(id, title, path) {
|
|
10657
11533
|
const shown = oneLine(title, TITLE_WIDTH);
|
|
10658
11534
|
return [
|
|
10659
|
-
shown === "" ? ` ${
|
|
11535
|
+
shown === "" ? ` ${oneLine(id, WIDTH.handle)}` : ` ${oneLine(id, WIDTH.handle)} ${shown}`,
|
|
10660
11536
|
// Backticked, as it was before the title moved onto the row above it. This
|
|
10661
11537
|
// output is read by agents as markdown, and a path outside code formatting
|
|
10662
11538
|
// is a path they have to guess the extent of.
|
|
10663
|
-
c.gray(` \`${
|
|
11539
|
+
c.gray(` \`${middleLine(path, WIDTH.path)}\``)
|
|
10664
11540
|
];
|
|
10665
11541
|
}
|
|
10666
11542
|
function renderCandidate(candidate) {
|
|
10667
11543
|
const lines2 = [
|
|
10668
|
-
`- **${
|
|
11544
|
+
`- **${oneLine(candidate.rule, WIDTH.title)}** \u2014 ${oneLine(candidate.evidence, WIDTH.text)}`,
|
|
10669
11545
|
...noteLines(candidate.memory_id, candidate.title, candidate.path)
|
|
10670
11546
|
];
|
|
10671
11547
|
if (candidate.other_id !== null) {
|
|
@@ -10707,7 +11583,7 @@ function proposeCommand() {
|
|
|
10707
11583
|
},
|
|
10708
11584
|
human: () => {
|
|
10709
11585
|
printHuman(
|
|
10710
|
-
`Proposed **${
|
|
11586
|
+
`Proposed **${oneLine(filed.action, WIDTH.title)}** on \`${oneLine(filed.target, WIDTH.path)}\`.
|
|
10711
11587
|
`
|
|
10712
11588
|
);
|
|
10713
11589
|
printHuman(c.gray(` ${filed.id}`));
|
|
@@ -11142,14 +12018,14 @@ function lifecycleCommand(action) {
|
|
|
11142
12018
|
json: { action, memory_id: entry.memory.id, target: entry.path, op_id: record.op_id },
|
|
11143
12019
|
human: () => {
|
|
11144
12020
|
printHuman(
|
|
11145
|
-
action === "retire" ? `Retired **${
|
|
11146
|
-
` : `**${
|
|
12021
|
+
action === "retire" ? `Retired **${oneLine(entry.memory.title, WIDTH.title)}**.
|
|
12022
|
+
` : `**${oneLine(entry.memory.title, WIDTH.title)}** is current again.
|
|
11147
12023
|
`
|
|
11148
12024
|
);
|
|
11149
12025
|
if (action === "retire") {
|
|
11150
12026
|
printHuman(
|
|
11151
12027
|
c.gray(
|
|
11152
|
-
"It still appears in recall \u2014 ranked last and labelled. Nothing was removed.\nTo stop reading the note entirely: `memgineering exclude " +
|
|
12028
|
+
"It still appears in recall \u2014 ranked last and labelled. Nothing was removed.\nTo stop reading the note entirely: `memgineering exclude " + oneLine(entry.path, WIDTH.path) + "`"
|
|
11153
12029
|
)
|
|
11154
12030
|
);
|
|
11155
12031
|
}
|
|
@@ -11207,8 +12083,10 @@ function ignoreCommand(mode) {
|
|
|
11207
12083
|
mode === "exclude" ? (
|
|
11208
12084
|
// The path is argv here, but argv came from a listing of the
|
|
11209
12085
|
// user's own notes — and a filename is as authored as a title.
|
|
11210
|
-
c.gray(
|
|
11211
|
-
|
|
12086
|
+
c.gray(
|
|
12087
|
+
`\`${oneLine(target, WIDTH.path)}\` was already excluded \u2014 nothing changed.`
|
|
12088
|
+
)
|
|
12089
|
+
) : c.gray(`\`${oneLine(target, WIDTH.path)}\` was not excluded \u2014 nothing changed.`)
|
|
11212
12090
|
);
|
|
11213
12091
|
return;
|
|
11214
12092
|
}
|
|
@@ -11238,13 +12116,13 @@ async function lifecycleHosted(where, action, ref, reason) {
|
|
|
11238
12116
|
json: { brain: where.name, ...result },
|
|
11239
12117
|
human: () => {
|
|
11240
12118
|
printHuman(
|
|
11241
|
-
action === "retire" ? `Retired in **${
|
|
11242
|
-
` : `Current again in **${
|
|
12119
|
+
action === "retire" ? `Retired in **${oneLine(where.name, WIDTH.title)}**.
|
|
12120
|
+
` : `Current again in **${oneLine(where.name, WIDTH.title)}**.
|
|
11243
12121
|
`
|
|
11244
12122
|
);
|
|
11245
|
-
printHuman(`\`${
|
|
12123
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
11246
12124
|
`);
|
|
11247
|
-
printHuman(c.gray(
|
|
12125
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
11248
12126
|
printHuman(
|
|
11249
12127
|
c.gray(
|
|
11250
12128
|
`
|
|
@@ -11264,15 +12142,15 @@ async function ignoreHosted(where, mode, path, reason) {
|
|
|
11264
12142
|
json: { brain: where.name, ...result },
|
|
11265
12143
|
human: () => {
|
|
11266
12144
|
if (!result.changed) {
|
|
11267
|
-
printHuman(c.gray(
|
|
12145
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
11268
12146
|
return;
|
|
11269
12147
|
}
|
|
11270
12148
|
printHuman(
|
|
11271
|
-
mode === "exclude" ? `No longer reading \`${
|
|
11272
|
-
` : `Reading \`${
|
|
12149
|
+
mode === "exclude" ? `No longer reading \`${oneLine(result.path, WIDTH.path)}\` in **${oneLine(where.name, WIDTH.title)}**.
|
|
12150
|
+
` : `Reading \`${oneLine(result.path, WIDTH.path)}\` again in **${oneLine(where.name, WIDTH.title)}**.
|
|
11273
12151
|
`
|
|
11274
12152
|
);
|
|
11275
|
-
printHuman(c.gray(
|
|
12153
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
11276
12154
|
if (result.op_id) printHuman(c.gray(`
|
|
11277
12155
|
Undo: \`memgineering ${opposite(mode)} ${path}\``));
|
|
11278
12156
|
}
|
|
@@ -11304,8 +12182,12 @@ init_index_build();
|
|
|
11304
12182
|
init_vault();
|
|
11305
12183
|
init_ui();
|
|
11306
12184
|
function evidenceCommand() {
|
|
11307
|
-
return new Command10("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").option("--local", "read the brain on this machine, even when signed in to a hosted one").
|
|
12185
|
+
return new Command10("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").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
|
|
12186
|
+
"--brain <name>",
|
|
12187
|
+
"read from one existing hosted brain by name or id, without changing the pointer"
|
|
12188
|
+
).action(async (ref, opts) => {
|
|
11308
12189
|
const target = await resolveTarget({
|
|
12190
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
11309
12191
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
11310
12192
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
11311
12193
|
});
|
|
@@ -11314,7 +12196,11 @@ function evidenceCommand() {
|
|
|
11314
12196
|
const report2 = await cloudEvidence(cloud, ref);
|
|
11315
12197
|
printDual({
|
|
11316
12198
|
json: { ...report2, brain: { hosted: true, name: target.name } },
|
|
11317
|
-
human: () => renderEvidence(viewFromCloud(report2), {
|
|
12199
|
+
human: () => renderEvidence(viewFromCloud(report2), {
|
|
12200
|
+
now: /* @__PURE__ */ new Date(),
|
|
12201
|
+
hosted: true,
|
|
12202
|
+
brain: target.name
|
|
12203
|
+
})
|
|
11318
12204
|
});
|
|
11319
12205
|
return;
|
|
11320
12206
|
}
|
|
@@ -11336,10 +12222,13 @@ function evidenceCommand() {
|
|
|
11336
12222
|
});
|
|
11337
12223
|
}
|
|
11338
12224
|
function renderEvidence(report, opts) {
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
printHuman(`\`id: ${stripControl(report.id)}\` \xB7 \`${stripControl(report.path)}\`
|
|
12225
|
+
const brainHeading = opts.brain === void 0 ? "" : ` \u2014 ${oneLine(opts.brain, WIDTH.title)}`;
|
|
12226
|
+
printHuman(`## evidence: ${oneLine(report.title, WIDTH.title)}${brainHeading}
|
|
11342
12227
|
`);
|
|
12228
|
+
printHuman(
|
|
12229
|
+
`\`id: ${oneLine(report.id, WIDTH.handle)}\` \xB7 \`${oneLine(report.path, WIDTH.path)}\`
|
|
12230
|
+
`
|
|
12231
|
+
);
|
|
11343
12232
|
if (report.critical) {
|
|
11344
12233
|
printHuman(c.yellow("\u26A0 critical \u2014 this note defines how the agent behaves.") + "\n");
|
|
11345
12234
|
}
|
|
@@ -11389,11 +12278,12 @@ function renderEvidence(report, opts) {
|
|
|
11389
12278
|
]);
|
|
11390
12279
|
}
|
|
11391
12280
|
const pad = rows.reduce((w, [label]) => Math.max(w, label.length), 0) + 2;
|
|
11392
|
-
for (const [label, value] of rows)
|
|
12281
|
+
for (const [label, value] of rows)
|
|
12282
|
+
printHuman(` ${label.padEnd(pad)}${oneLine(value, WIDTH.text)}`);
|
|
11393
12283
|
if (report.standing) {
|
|
11394
12284
|
const means = STANDING_MEANS[report.standing];
|
|
11395
12285
|
printHuman(`
|
|
11396
|
-
**${
|
|
12286
|
+
**${oneLine(report.standing, WIDTH.title)}**${means ? ` \u2014 ${means}` : ""}`);
|
|
11397
12287
|
}
|
|
11398
12288
|
if (report.contradicts > 0) {
|
|
11399
12289
|
printHuman(
|
|
@@ -11414,7 +12304,7 @@ function renderEvidence(report, opts) {
|
|
|
11414
12304
|
report.last_reason.verb,
|
|
11415
12305
|
report.last_reason.ts === null ? null : report.last_reason.ts.slice(0, 10)
|
|
11416
12306
|
].filter((part) => part !== null && part !== "").join(", ");
|
|
11417
|
-
if (provenance !== "") printHuman(c.gray(` ${
|
|
12307
|
+
if (provenance !== "") printHuman(c.gray(` ${oneLine(provenance, WIDTH.text)}`));
|
|
11418
12308
|
} else if (report.changed > 0) {
|
|
11419
12309
|
printHuman(
|
|
11420
12310
|
c.gray(
|
|
@@ -11550,6 +12440,7 @@ import { Command as Command11 } from "commander";
|
|
|
11550
12440
|
// src/lib/rules.ts
|
|
11551
12441
|
init_src();
|
|
11552
12442
|
init_index_build();
|
|
12443
|
+
init_ui();
|
|
11553
12444
|
init_vault();
|
|
11554
12445
|
var RULE_LIMIT = 8;
|
|
11555
12446
|
async function collectRules(vault, contextDir, now = /* @__PURE__ */ new Date()) {
|
|
@@ -11578,11 +12469,12 @@ async function collectRules(vault, contextDir, now = /* @__PURE__ */ new Date())
|
|
|
11578
12469
|
};
|
|
11579
12470
|
}
|
|
11580
12471
|
function renderRuleLines(rules) {
|
|
11581
|
-
return rules.map(({ entry }) => `- ${entry.memory.summary ?? entry.memory.title}`);
|
|
12472
|
+
return rules.map(({ entry }) => `- ${oneLine(entry.memory.summary ?? entry.memory.title, 200)}`);
|
|
11582
12473
|
}
|
|
11583
12474
|
|
|
11584
12475
|
// src/commands/guard.ts
|
|
11585
12476
|
init_deadline();
|
|
12477
|
+
init_ui();
|
|
11586
12478
|
function guardCommand() {
|
|
11587
12479
|
return new Command11("guard").description("hook entry \u2014 show the user\u2019s rules before the first edit of a session").option("--context-dir <path>", "where the work is happening, when stdin does not say").option("--at <event>", "pre-tool-use | stop", "pre-tool-use").action(async (opts) => {
|
|
11588
12480
|
try {
|
|
@@ -11622,7 +12514,7 @@ async function runGuard(payload, contextDirFlag) {
|
|
|
11622
12514
|
return JSON.stringify({
|
|
11623
12515
|
hookSpecificOutput: {
|
|
11624
12516
|
hookEventName: "PreToolUse",
|
|
11625
|
-
additionalContext: contextFor(claimed.
|
|
12517
|
+
additionalContext: contextFor(claimed.lines, claimed.total)
|
|
11626
12518
|
}
|
|
11627
12519
|
});
|
|
11628
12520
|
}
|
|
@@ -11636,16 +12528,22 @@ async function runStopGuard(payload, contextDirFlag) {
|
|
|
11636
12528
|
return JSON.stringify({
|
|
11637
12529
|
hookSpecificOutput: {
|
|
11638
12530
|
hookEventName: "Stop",
|
|
11639
|
-
additionalContext: stopContextFor(claimed.
|
|
12531
|
+
additionalContext: stopContextFor(claimed.lines, claimed.total)
|
|
11640
12532
|
}
|
|
11641
12533
|
});
|
|
11642
12534
|
}
|
|
11643
12535
|
async function claimRules(sessionId, contextDir) {
|
|
12536
|
+
const hosted = await hostedRuleLines().catch(() => null);
|
|
12537
|
+
if (hosted !== null) {
|
|
12538
|
+
if (hosted.lines.length === 0) return null;
|
|
12539
|
+
if (!await claimSession(sessionId, hosted.brainId)) return null;
|
|
12540
|
+
return { lines: hosted.lines, total: hosted.total };
|
|
12541
|
+
}
|
|
11644
12542
|
const collected = await collectRules(void 0, contextDir).catch(() => null);
|
|
11645
12543
|
if (collected === null) return null;
|
|
11646
12544
|
const { brainRoot, rules, total } = collected;
|
|
11647
12545
|
if (rules.length === 0) return null;
|
|
11648
|
-
if (!await claimSession(sessionId)) return null;
|
|
12546
|
+
if (!await claimSession(sessionId, brainRoot)) return null;
|
|
11649
12547
|
await recordEvent(brainRoot, {
|
|
11650
12548
|
verb: "guard",
|
|
11651
12549
|
contextDir,
|
|
@@ -11653,22 +12551,20 @@ async function claimRules(sessionId, contextDir) {
|
|
|
11653
12551
|
limit: RULE_LIMIT,
|
|
11654
12552
|
returnedIds: rules.map((r) => r.entry.memory.id)
|
|
11655
12553
|
});
|
|
11656
|
-
return { rules, total };
|
|
12554
|
+
return { lines: renderRuleLines(rules), total };
|
|
11657
12555
|
}
|
|
11658
|
-
function contextFor(
|
|
11659
|
-
const
|
|
11660
|
-
|
|
11661
|
-
(${total - rules.length} more \u2014 \`memgineering rules\` for all of them.)` : "";
|
|
12556
|
+
function contextFor(lines2, total) {
|
|
12557
|
+
const more = total > lines2.length ? `
|
|
12558
|
+
(${total - lines2.length} more \u2014 \`memgineering rules\` for all of them.)` : "";
|
|
11662
12559
|
return `Decisions this user has already made. They are theirs, not suggestions, and they hold whether or not this task mentions them:
|
|
11663
12560
|
|
|
11664
12561
|
${lines2.join("\n")}${more}
|
|
11665
12562
|
|
|
11666
12563
|
Follow them. If what you are about to do goes against one, say so in a sentence instead of doing it quietly.`;
|
|
11667
12564
|
}
|
|
11668
|
-
function stopContextFor(
|
|
11669
|
-
const
|
|
11670
|
-
|
|
11671
|
-
(${total - rules.length} more \u2014 \`memgineering rules\` for all of them.)` : "";
|
|
12565
|
+
function stopContextFor(lines2, total) {
|
|
12566
|
+
const more = total > lines2.length ? `
|
|
12567
|
+
(${total - lines2.length} more \u2014 \`memgineering rules\` for all of them.)` : "";
|
|
11672
12568
|
return `Decisions this user has already made. They are theirs, not suggestions, and they hold whether or not this task mentions them:
|
|
11673
12569
|
|
|
11674
12570
|
${lines2.join("\n")}${more}
|
|
@@ -11699,10 +12595,10 @@ async function readPayload() {
|
|
|
11699
12595
|
}
|
|
11700
12596
|
}
|
|
11701
12597
|
var guardDir = () => join16(brandHome(), "guard");
|
|
11702
|
-
async function claimSession(sessionId) {
|
|
12598
|
+
async function claimSession(sessionId, brain) {
|
|
11703
12599
|
const marker = join16(
|
|
11704
12600
|
guardDir(),
|
|
11705
|
-
`${createHash7("sha256").update(sessionId).digest("hex").slice(0, 32)}`
|
|
12601
|
+
`${createHash7("sha256").update(`${sessionId}\0${brain}`).digest("hex").slice(0, 32)}`
|
|
11706
12602
|
);
|
|
11707
12603
|
try {
|
|
11708
12604
|
await mkdir11(guardDir(), { recursive: true, mode: 448 });
|
|
@@ -11727,6 +12623,17 @@ async function pruneOldMarkers() {
|
|
|
11727
12623
|
} catch {
|
|
11728
12624
|
}
|
|
11729
12625
|
}
|
|
12626
|
+
var RULE_WIDTH = 200;
|
|
12627
|
+
async function hostedRuleLines() {
|
|
12628
|
+
const where = await resolveTarget({});
|
|
12629
|
+
if (where.kind !== "cloud") return null;
|
|
12630
|
+
const { rules, total } = await cloudRules(await openCloudBrain(where));
|
|
12631
|
+
return {
|
|
12632
|
+
lines: rules.map((r) => `- ${oneLine(r.summary ?? r.title, RULE_WIDTH)}`),
|
|
12633
|
+
total,
|
|
12634
|
+
brainId: where.brainId
|
|
12635
|
+
};
|
|
12636
|
+
}
|
|
11730
12637
|
|
|
11731
12638
|
// src/program.ts
|
|
11732
12639
|
init_init();
|
|
@@ -11739,8 +12646,12 @@ import { Command as Command12 } from "commander";
|
|
|
11739
12646
|
init_vault();
|
|
11740
12647
|
init_ui();
|
|
11741
12648
|
function logCommand() {
|
|
11742
|
-
return new Command12("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").option("--local", "read the brain on this machine, even when signed in to a hosted one").
|
|
12649
|
+
return new Command12("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").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
|
|
12650
|
+
"--brain <name>",
|
|
12651
|
+
"read one existing hosted brain by name or id, without changing the pointer"
|
|
12652
|
+
).action(async (opts) => {
|
|
11743
12653
|
const target = await resolveTarget({
|
|
12654
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
11744
12655
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
11745
12656
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
11746
12657
|
});
|
|
@@ -11769,7 +12680,7 @@ function logCommand() {
|
|
|
11769
12680
|
human: () => {
|
|
11770
12681
|
const diverged2 = Array.isArray(history["divergence"]) ? history["divergence"] : [];
|
|
11771
12682
|
const total = typeof history["total"] === "number" ? history["total"] : rows.length;
|
|
11772
|
-
printHuman(`## log \u2014 ${
|
|
12683
|
+
printHuman(`## log \u2014 ${oneLine(target.name, WIDTH.title)} (${total} operations)
|
|
11773
12684
|
`);
|
|
11774
12685
|
if (rows.length === 0) {
|
|
11775
12686
|
printHuman("_This brain has no recorded operations._\n");
|
|
@@ -11783,7 +12694,8 @@ function logCommand() {
|
|
|
11783
12694
|
);
|
|
11784
12695
|
}
|
|
11785
12696
|
const hint = history["hint"];
|
|
11786
|
-
if (typeof hint === "string" && hint !== "")
|
|
12697
|
+
if (typeof hint === "string" && hint !== "")
|
|
12698
|
+
printHuman(c.gray(oneLine(hint, WIDTH.text)));
|
|
11787
12699
|
}
|
|
11788
12700
|
});
|
|
11789
12701
|
return;
|
|
@@ -11905,15 +12817,19 @@ function renderOps(ops, opts) {
|
|
|
11905
12817
|
op.diverged && !op.undone ? "note has changed since" : null
|
|
11906
12818
|
].filter(Boolean);
|
|
11907
12819
|
printHuman(
|
|
11908
|
-
`- ${
|
|
12820
|
+
`- ${oneLine(op.verb, WIDTH.title)} \`${oneLine(op.target, WIDTH.path)}\`${marks.length ? c.yellow(` (${marks.join(", ")})`) : ""}`
|
|
11909
12821
|
);
|
|
11910
12822
|
printHuman(
|
|
11911
|
-
c.gray(
|
|
12823
|
+
c.gray(
|
|
12824
|
+
` ${oneLine(op.ts, WIDTH.handle)}${op.actor ? ` \xB7 ${oneLine(op.actor, WIDTH.title)}` : ""}`
|
|
12825
|
+
) + ` ${c.gray(undoable(op, opts.hosted) ? "undo:" : "op:")} ${oneLine(op.op_id, WIDTH.handle)}`
|
|
11912
12826
|
);
|
|
11913
12827
|
if (op.rationale) printHuman(c.gray(quoteBlock(op.rationale, " > ")));
|
|
11914
12828
|
if (opts.verbose) {
|
|
11915
12829
|
printHuman(
|
|
11916
|
-
c.gray(
|
|
12830
|
+
c.gray(
|
|
12831
|
+
` ${oneLine(op.hash_before ?? "\u2205", WIDTH.handle)} \u2192 ${oneLine(op.hash_after ?? "\u2205", WIDTH.handle)}`
|
|
12832
|
+
)
|
|
11917
12833
|
);
|
|
11918
12834
|
}
|
|
11919
12835
|
}
|
|
@@ -11969,7 +12885,7 @@ init_pending_login();
|
|
|
11969
12885
|
init_ui();
|
|
11970
12886
|
import { Command as Command14 } from "commander";
|
|
11971
12887
|
function logoutCommand() {
|
|
11972
|
-
return new Command14("logout").description("sign out of
|
|
12888
|
+
return new Command14("logout").description("sign out of your account \u2014 the pointer and your notes stay").option("--all", "revoke every token on the account, not just this machine\u2019s").option(
|
|
11973
12889
|
"--local",
|
|
11974
12890
|
"forget the token here without telling the server \u2014 only for a server you cannot reach"
|
|
11975
12891
|
).action(async (opts) => {
|
|
@@ -12052,6 +12968,11 @@ function logoutCommand() {
|
|
|
12052
12968
|
opts.all ? " Every token on the account is dead. `memgineering login` starts a new one." : " This machine\u2019s token is revoked. Other machines are unaffected."
|
|
12053
12969
|
)
|
|
12054
12970
|
);
|
|
12971
|
+
printHuman(
|
|
12972
|
+
c.gray(
|
|
12973
|
+
" Your notes are untouched: signing out takes a token, not a brain.\n This machine still remembers which brain it was reading, so `memgineering\n login` picks up where you left off. `memgineering unlink --brain` forgets it."
|
|
12974
|
+
)
|
|
12975
|
+
);
|
|
12055
12976
|
}
|
|
12056
12977
|
});
|
|
12057
12978
|
});
|
|
@@ -12135,12 +13056,12 @@ init_ui();
|
|
|
12135
13056
|
function pullCommand() {
|
|
12136
13057
|
return new Command16("pull").description("download your hosted brain to a folder on this machine").argument("[destination]", "folder to write into (created if missing)").option("--brain <name>", "which hosted brain to download").option("--dry-run", "say what would be written and write nothing").action(async (destination, opts) => {
|
|
12137
13058
|
const { brain: source, others } = await resolveSource(opts.brain);
|
|
12138
|
-
const target = resolve8(destination ?? `./${
|
|
13059
|
+
const target = resolve8(destination ?? `./${oneLine(source.name, WIDTH.title)}`);
|
|
12139
13060
|
const first = await cloudListNotes(source, { limit: 1 });
|
|
12140
13061
|
if (first.total === 0) {
|
|
12141
13062
|
throw memgError(
|
|
12142
13063
|
"invalid_input",
|
|
12143
|
-
`\u201C${
|
|
13064
|
+
`\u201C${oneLine(source.name, WIDTH.title)}\u201D has no notes to download`,
|
|
12144
13065
|
"Nothing was written. `memgineering push` carries a brain on this disk up to the hosted one, and `remember` writes a first note into it."
|
|
12145
13066
|
);
|
|
12146
13067
|
}
|
|
@@ -12161,13 +13082,13 @@ function pullCommand() {
|
|
|
12161
13082
|
printHuman(`## pull (dry run)
|
|
12162
13083
|
`);
|
|
12163
13084
|
printHuman(
|
|
12164
|
-
`${first.total} note(s) from \u201C${
|
|
13085
|
+
`${first.total} note(s) from \u201C${oneLine(source.name, WIDTH.title)}\u201D into \`${target}\`
|
|
12165
13086
|
`
|
|
12166
13087
|
);
|
|
12167
13088
|
for (const note of preview.slice(0, 20)) {
|
|
12168
13089
|
printHuman(
|
|
12169
13090
|
c.gray(
|
|
12170
|
-
` ${
|
|
13091
|
+
` ${oneLine(note.path, WIDTH.path)}${note.excluded === true ? " (excluded)" : ""}`
|
|
12171
13092
|
)
|
|
12172
13093
|
);
|
|
12173
13094
|
}
|
|
@@ -12182,7 +13103,7 @@ function pullCommand() {
|
|
|
12182
13103
|
});
|
|
12183
13104
|
return;
|
|
12184
13105
|
}
|
|
12185
|
-
progress(`downloading ${first.total} note(s) from \u201C${
|
|
13106
|
+
progress(`downloading ${first.total} note(s) from \u201C${oneLine(source.name, WIDTH.title)}\u201D\u2026`);
|
|
12186
13107
|
const written = [];
|
|
12187
13108
|
const already = [];
|
|
12188
13109
|
const excluded = [];
|
|
@@ -12224,7 +13145,7 @@ function pullCommand() {
|
|
|
12224
13145
|
human: () => {
|
|
12225
13146
|
printHuman(
|
|
12226
13147
|
c.green(
|
|
12227
|
-
`\u2713 ${written.length} note(s) from \u201C${
|
|
13148
|
+
`\u2713 ${written.length} note(s) from \u201C${oneLine(source.name, WIDTH.title)}\u201D written to \`${target}\``
|
|
12228
13149
|
)
|
|
12229
13150
|
);
|
|
12230
13151
|
if (already.length > 0) {
|
|
@@ -12276,8 +13197,8 @@ async function resolveSource(named) {
|
|
|
12276
13197
|
if (!found) {
|
|
12277
13198
|
throw memgError(
|
|
12278
13199
|
"invalid_input",
|
|
12279
|
-
named ? `no hosted brain called \u201C${
|
|
12280
|
-
`Pass one of: ${brains.map((b) =>
|
|
13200
|
+
named ? `no hosted brain called \u201C${oneLine(named, WIDTH.title)}\u201D` : "more than one hosted brain \u2014 say which",
|
|
13201
|
+
`Pass one of: ${brains.map((b) => oneLine(b.name, WIDTH.title)).join(", ")} (e.g. \`memgineering pull --brain ${oneLine(brains[0]?.name ?? "", WIDTH.title)}\`)`
|
|
12281
13202
|
);
|
|
12282
13203
|
}
|
|
12283
13204
|
return {
|
|
@@ -12294,7 +13215,7 @@ async function resolveSource(named) {
|
|
|
12294
13215
|
}
|
|
12295
13216
|
function sayWhatIsElsewhere(others, into) {
|
|
12296
13217
|
if (others.length === 0) return;
|
|
12297
|
-
const names = others.map(
|
|
13218
|
+
const names = others.map((name) => oneLine(name, WIDTH.title));
|
|
12298
13219
|
printHuman(
|
|
12299
13220
|
c.yellow(
|
|
12300
13221
|
`
|
|
@@ -12392,7 +13313,7 @@ If your notes are in one of those, that rule is why \u2014 nothing here can see
|
|
|
12392
13313
|
return;
|
|
12393
13314
|
}
|
|
12394
13315
|
const destination = await resolveDestination(opts.brain, local.root);
|
|
12395
|
-
progress(`uploading ${paths.length} note(s) to \u201C${
|
|
13316
|
+
progress(`uploading ${paths.length} note(s) to \u201C${oneLine(destination.name, WIDTH.title)}\u201D\u2026`);
|
|
12396
13317
|
const uploaded = [];
|
|
12397
13318
|
const already = [];
|
|
12398
13319
|
const refused = [];
|
|
@@ -12462,7 +13383,9 @@ If your notes are in one of those, that rule is why \u2014 nothing here can see
|
|
|
12462
13383
|
},
|
|
12463
13384
|
human: () => {
|
|
12464
13385
|
printHuman(
|
|
12465
|
-
c.green(
|
|
13386
|
+
c.green(
|
|
13387
|
+
`\u2713 ${uploaded.length} note(s) uploaded to \u201C${oneLine(destination.name, WIDTH.title)}\u201D`
|
|
13388
|
+
)
|
|
12466
13389
|
);
|
|
12467
13390
|
if (already.length > 0) {
|
|
12468
13391
|
printHuman(c.gray(` ${already.length} were already there \u2014 left as they are`));
|
|
@@ -12480,7 +13403,9 @@ If your notes are in one of those, that rule is why \u2014 nothing here can see
|
|
|
12480
13403
|
\u26A0 ${refused.length} note(s) were NOT uploaded:`) + c.gray("\n they are untouched on this machine and can be fixed and pushed again")
|
|
12481
13404
|
);
|
|
12482
13405
|
for (const r of refused.slice(0, 10)) {
|
|
12483
|
-
printHuman(
|
|
13406
|
+
printHuman(
|
|
13407
|
+
c.gray(` ${oneLine(r.path, WIDTH.path)} (${oneLine(r.code, WIDTH.title)})`)
|
|
13408
|
+
);
|
|
12484
13409
|
}
|
|
12485
13410
|
if (refused.length > 10) {
|
|
12486
13411
|
printHuman(c.gray(` \u2026 and ${refused.length - 10} more`));
|
|
@@ -12502,14 +13427,14 @@ Nothing was deleted from \`${local.root}\` \u2014
|
|
|
12502
13427
|
function printRepoint(repoint, destination) {
|
|
12503
13428
|
if (repoint === null) {
|
|
12504
13429
|
printHuman(c.gray(`
|
|
12505
|
-
This machine still reads \u201C${
|
|
13430
|
+
This machine still reads \u201C${oneLine(destination.name, WIDTH.title)}\u201D.`));
|
|
12506
13431
|
return;
|
|
12507
13432
|
}
|
|
12508
13433
|
if (repoint.from === null) {
|
|
12509
13434
|
printHuman(
|
|
12510
13435
|
c.gray(
|
|
12511
13436
|
`
|
|
12512
|
-
This machine now reads \u201C${
|
|
13437
|
+
This machine now reads \u201C${oneLine(destination.name, WIDTH.title)}\u201D \u2014 it was not reading a hosted
|
|
12513
13438
|
brain before. \`memgineering recall --local\` still reads the folder.`
|
|
12514
13439
|
)
|
|
12515
13440
|
);
|
|
@@ -12518,9 +13443,9 @@ brain before. \`memgineering recall --local\` still reads the folder.`
|
|
|
12518
13443
|
printHuman(
|
|
12519
13444
|
c.yellow(
|
|
12520
13445
|
`
|
|
12521
|
-
\u26A0 THIS MACHINE NOW READS \u201C${
|
|
13446
|
+
\u26A0 THIS MACHINE NOW READS \u201C${oneLine(destination.name, WIDTH.title)}\u201D \u2014 it was reading \u201C${oneLine(repoint.from.name, WIDTH.title)}\u201D.`
|
|
12522
13447
|
) + c.gray(
|
|
12523
|
-
"\n That is a side effect of `--brain`, and it outlives this command: `recall`,\n `remember` and the memories an agent session starts with all come from the\n brain named here. Nothing in \u201C" +
|
|
13448
|
+
"\n That is a side effect of `--brain`, and it outlives this command: `recall`,\n `remember` and the memories an agent session starts with all come from the\n brain named here. Nothing in \u201C" + oneLine(repoint.from.name, WIDTH.title) + "\u201D was changed or deleted.\n To point back without uploading: `memgineering use --brain " + oneLine(repoint.from.name, 72) + "`."
|
|
12524
13449
|
)
|
|
12525
13450
|
);
|
|
12526
13451
|
}
|
|
@@ -12573,7 +13498,10 @@ init_errors();
|
|
|
12573
13498
|
init_vault();
|
|
12574
13499
|
init_ui();
|
|
12575
13500
|
function recallCommand() {
|
|
12576
|
-
return new Command18("recall").description("recall memory cards for a question").argument("<query>", "what you want to remember").option("--vault <path>", "which brain to search").option("--local", "search the brain on this machine, even when signed in to a hosted one").option(
|
|
13501
|
+
return new Command18("recall").description("recall memory cards for a question").argument("<query>", "what you want to remember").option("--vault <path>", "which brain to search").option("--local", "search the brain on this machine, even when signed in to a hosted one").option(
|
|
13502
|
+
"--brain <name>",
|
|
13503
|
+
"search one existing hosted brain by name or id, without changing the pointer"
|
|
13504
|
+
).option("--scope <scope>", "restrict to one scope").option("--limit <n>", "max cards", (v) => parseInt(v, 10), 8).option(
|
|
12577
13505
|
"--detail <level>",
|
|
12578
13506
|
"title | card | summary | chunks | full (default: card) \u2014 a hosted brain answers title and card only; deeper needs a brain in a folder, or `open <handle>`"
|
|
12579
13507
|
).option("--depth <depth>", "exact | standard (default: standard, which also searches bodies)").option("--budget <tokens>", "max estimated size of the answer", (v) => parseInt(v, 10)).action(
|
|
@@ -12582,10 +13510,13 @@ function recallCommand() {
|
|
|
12582
13510
|
const depth = parseDepth(opts.depth);
|
|
12583
13511
|
const budget = parseBudget(opts.budget) ?? DEFAULT_TOKEN_BUDGET;
|
|
12584
13512
|
const target = await resolveTarget({
|
|
13513
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
12585
13514
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
12586
13515
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
12587
13516
|
});
|
|
13517
|
+
const answeredBy = target.kind === "cloud" ? target.name : null;
|
|
12588
13518
|
let rows;
|
|
13519
|
+
let unsearchedBrains = [];
|
|
12589
13520
|
let isScaffolding;
|
|
12590
13521
|
let reasons;
|
|
12591
13522
|
let pathOf;
|
|
@@ -12613,6 +13544,9 @@ function recallCommand() {
|
|
|
12613
13544
|
budget
|
|
12614
13545
|
});
|
|
12615
13546
|
rows = answered.memory_cards.map((card) => ({ card, handle: card.handle, stage: {} }));
|
|
13547
|
+
if (rows.length === 0) {
|
|
13548
|
+
unsearchedBrains = await listCloudBrains(cloud.token, cloud.apiUrl, { retries: 0 }).then((all) => all.filter((b) => b.id !== cloud.brainId)).catch(() => []);
|
|
13549
|
+
}
|
|
12616
13550
|
isScaffolding = () => false;
|
|
12617
13551
|
reasons = /* @__PURE__ */ new Map();
|
|
12618
13552
|
pathOf = () => null;
|
|
@@ -12677,6 +13611,7 @@ function recallCommand() {
|
|
|
12677
13611
|
json: {
|
|
12678
13612
|
query,
|
|
12679
13613
|
detail,
|
|
13614
|
+
...answeredBy === null ? {} : { brain: { hosted: true, name: answeredBy } },
|
|
12680
13615
|
// Echoed only when it was asked for. `detail` above is resolved
|
|
12681
13616
|
// here because this CLI is what assembles the stages, but the depth
|
|
12682
13617
|
// is the searching brain's to default — and on a hosted brain this
|
|
@@ -12709,6 +13644,7 @@ function recallCommand() {
|
|
|
12709
13644
|
...result.truncated === void 0 ? {} : { truncated: result.truncated }
|
|
12710
13645
|
},
|
|
12711
13646
|
human: () => {
|
|
13647
|
+
const brainHeading = answeredBy === null ? "" : ` \u2014 ${oneLine(answeredBy, WIDTH.title)}`;
|
|
12712
13648
|
const reachedSomethingWritten = rows.some(({ card }) => !isScaffolding(card));
|
|
12713
13649
|
const otherScript = reachedSomethingWritten ? null : entries ? unreachableScript(query, entries) : null;
|
|
12714
13650
|
const sayWhy = () => {
|
|
@@ -12727,12 +13663,12 @@ function recallCommand() {
|
|
|
12727
13663
|
if (semantic.state !== "skipped" || semantic.hint === void 0) return;
|
|
12728
13664
|
printHuman(
|
|
12729
13665
|
c.yellow("\u26A0 Meaning was not searched \u2014 this answer matched words only.") + c.gray(`
|
|
12730
|
-
${
|
|
13666
|
+
${oneLine(semantic.hint, WIDTH.text)}
|
|
12731
13667
|
`)
|
|
12732
13668
|
);
|
|
12733
13669
|
};
|
|
12734
13670
|
if (rows.length === 0) {
|
|
12735
|
-
printHuman(`## recall: ${query}
|
|
13671
|
+
printHuman(`## recall: ${query}${brainHeading}
|
|
12736
13672
|
`);
|
|
12737
13673
|
printHuman("_Nothing matched._\n");
|
|
12738
13674
|
sayDegraded();
|
|
@@ -12740,14 +13676,38 @@ function recallCommand() {
|
|
|
12740
13676
|
sayWhy();
|
|
12741
13677
|
return;
|
|
12742
13678
|
}
|
|
13679
|
+
if (unsearchedBrains.length > 0) {
|
|
13680
|
+
printHuman(
|
|
13681
|
+
c.yellow(
|
|
13682
|
+
`\u26A0 Only \`${oneLine(answeredBy ?? "", WIDTH.title)}\` was searched \u2014 this account has ${unsearchedBrains.length + 1} brains.`
|
|
13683
|
+
) + c.gray(
|
|
13684
|
+
`
|
|
13685
|
+
${unsearchedBrains.map(
|
|
13686
|
+
(brain) => ` memgineering recall ${JSON.stringify(query)} --brain ${JSON.stringify(brain.name)}`
|
|
13687
|
+
).join(
|
|
13688
|
+
"\n"
|
|
13689
|
+
)}
|
|
13690
|
+
Safe to run \u2014 reading another brain does not change which one this machine points at.
|
|
13691
|
+
`
|
|
13692
|
+
)
|
|
13693
|
+
);
|
|
13694
|
+
}
|
|
12743
13695
|
printHuman(
|
|
12744
13696
|
c.gray(
|
|
12745
|
-
|
|
13697
|
+
// "Within this brain" is load-bearing when the warning above
|
|
13698
|
+
// has just said another brain went unsearched. The enumeration
|
|
13699
|
+
// reads as the whole surface — a recheck tester called this
|
|
13700
|
+
// the last line on screen and a false all-clear, sitting below
|
|
13701
|
+
// the one line that says otherwise.
|
|
13702
|
+
(depth === "exact" ? "Within this brain, `--depth exact` searches the card only \u2014 titles, aliases, entities, summaries and scope \u2014 try `--depth standard` to search note bodies too, or fewer words" : "Within this brain, titles, aliases, summaries and note bodies are all searched \u2014 try fewer or broader words") + // Only when the reader actually passed one. Telling somebody
|
|
13703
|
+
// to drop an argument they never supplied cost one tester a
|
|
13704
|
+
// whole command to `--help` to find out it was not their bug.
|
|
13705
|
+
(opts.scope ? ", or drop --scope." : ".")
|
|
12746
13706
|
)
|
|
12747
13707
|
);
|
|
12748
13708
|
return;
|
|
12749
13709
|
}
|
|
12750
|
-
printHuman(`## recall: ${query} (${rows.length} cards)
|
|
13710
|
+
printHuman(`## recall: ${query}${brainHeading} (${rows.length} cards)
|
|
12751
13711
|
`);
|
|
12752
13712
|
sayDegraded();
|
|
12753
13713
|
if (!reachedSomethingWritten) {
|
|
@@ -12763,7 +13723,22 @@ function recallCommand() {
|
|
|
12763
13723
|
);
|
|
12764
13724
|
for (const [i, { card, handle: handle2, stage }] of rows.entries()) {
|
|
12765
13725
|
const mark = reachedSomethingWritten && isScaffolding(card);
|
|
12766
|
-
printHuman(
|
|
13726
|
+
printHuman(
|
|
13727
|
+
renderCard(
|
|
13728
|
+
i + 1,
|
|
13729
|
+
card,
|
|
13730
|
+
handle2,
|
|
13731
|
+
detail,
|
|
13732
|
+
stage,
|
|
13733
|
+
i === glossOn,
|
|
13734
|
+
mark,
|
|
13735
|
+
// Only when `--brain` aimed this elsewhere. A recall of the
|
|
13736
|
+
// brain this machine already points at mints handles `open`
|
|
13737
|
+
// resolves by default, and repeating the name there would be
|
|
13738
|
+
// noise on the common path.
|
|
13739
|
+
opts.brain === void 0 ? void 0 : answeredBy ?? void 0
|
|
13740
|
+
)
|
|
13741
|
+
);
|
|
12767
13742
|
}
|
|
12768
13743
|
if (result.conflicts.length > 0) {
|
|
12769
13744
|
printHuman(`### \u26A0 contradictions (${result.conflicts.length})
|
|
@@ -12775,13 +13750,11 @@ function recallCommand() {
|
|
|
12775
13750
|
for (const id of conflict.between) {
|
|
12776
13751
|
const reason = reasons.get(id);
|
|
12777
13752
|
if (!reason) {
|
|
12778
|
-
printHuman(c.gray(` ${
|
|
13753
|
+
printHuman(c.gray(` ${oneLine(id, WIDTH.handle)} \u2014 no reason was recorded.`));
|
|
12779
13754
|
continue;
|
|
12780
13755
|
}
|
|
12781
13756
|
printHuman(
|
|
12782
|
-
|
|
12783
|
-
` ${id} \u2014 ${reason.verb === "remember" ? "why it was recorded" : "why it last changed"} (${reason.ts.slice(0, 10)}):`
|
|
12784
|
-
)
|
|
13757
|
+
` ${oneLine(id, WIDTH.handle)} \u2014 ${reason.verb === "remember" ? "why it was recorded" : "why it last changed"} (${reason.ts.slice(0, 10)}):`
|
|
12785
13758
|
);
|
|
12786
13759
|
printHuman(quoteBlock(reason.text, " > "));
|
|
12787
13760
|
}
|
|
@@ -12797,8 +13770,10 @@ function recallCommand() {
|
|
|
12797
13770
|
);
|
|
12798
13771
|
}
|
|
12799
13772
|
if (result.related.length > 0) {
|
|
12800
|
-
printHuman(
|
|
12801
|
-
`)
|
|
13773
|
+
printHuman(
|
|
13774
|
+
`_related:_ ${result.related.map((id) => oneLine(id, WIDTH.handle)).join(", ")}
|
|
13775
|
+
`
|
|
13776
|
+
);
|
|
12802
13777
|
}
|
|
12803
13778
|
if (result.truncated) {
|
|
12804
13779
|
const { withheld, estimated_tokens, budget_tokens } = result.truncated;
|
|
@@ -12852,9 +13827,8 @@ function asksInSentences(query) {
|
|
|
12852
13827
|
return query.trim().split(/\s+/).filter(Boolean).length >= 4;
|
|
12853
13828
|
}
|
|
12854
13829
|
function side(id, status, validFrom2) {
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
);
|
|
13830
|
+
const date = validFrom2 === null ? "" : ` \xB7 ${oneLine(validFrom2.slice(0, 10), WIDTH.handle)}`;
|
|
13831
|
+
return `${oneLine(id, WIDTH.handle)} (${oneLine(status, WIDTH.title)}${date})`;
|
|
12858
13832
|
}
|
|
12859
13833
|
async function conflictReasons(vaultRoot, conflicts, byId) {
|
|
12860
13834
|
const { ops } = await readOps(vaultRoot);
|
|
@@ -12869,11 +13843,11 @@ async function stageFor(adapter, entry, detail) {
|
|
|
12869
13843
|
if (!entry) return {};
|
|
12870
13844
|
return assembleStage(adapter, entry, detail);
|
|
12871
13845
|
}
|
|
12872
|
-
function renderCard(n, card, handle2, detail, stage, glossHere = false, scaffolding = false) {
|
|
13846
|
+
function renderCard(n, card, handle2, detail, stage, glossHere = false, scaffolding = false, qualifyWith) {
|
|
12873
13847
|
const out = [];
|
|
12874
13848
|
const standing = card.freshness === "current" ? "" : ` \xB7 ${card.freshness}`;
|
|
12875
13849
|
const age = Number.isFinite(card.age_days) ? ` \xB7 ${ago2(Math.floor(card.age_days))}` : "";
|
|
12876
|
-
out.push(`### ${n}. ${
|
|
13850
|
+
out.push(`### ${n}. ${oneLine(card.title, WIDTH.title)}`);
|
|
12877
13851
|
if (scaffolding) {
|
|
12878
13852
|
out.push(c.yellow("not filled in yet") + c.gray(" \u2014 still the template `init` wrote"));
|
|
12879
13853
|
} else {
|
|
@@ -12881,14 +13855,15 @@ function renderCard(n, card, handle2, detail, stage, glossHere = false, scaffold
|
|
|
12881
13855
|
card.binding ? c.bold("a standing decision") : null,
|
|
12882
13856
|
`${card.confidence}${standing}${age}`
|
|
12883
13857
|
].filter(Boolean);
|
|
12884
|
-
out.push(
|
|
13858
|
+
out.push(oneLine(marks.join(c.gray(" \xB7 ")), WIDTH.text));
|
|
12885
13859
|
}
|
|
12886
13860
|
if (glossHere && !scaffolding) {
|
|
12887
13861
|
out.push(c.gray("stated = these fields came from the note, not from inference"));
|
|
12888
13862
|
}
|
|
12889
13863
|
if (detail !== "title") {
|
|
12890
13864
|
if (card.summary) out.push("", quoteBlock(card.summary));
|
|
12891
|
-
|
|
13865
|
+
const openLine = qualifyWith === void 0 ? `open: ${oneLine(handle2, WIDTH.handle)}` : `memgineering open ${oneLine(handle2, WIDTH.handle)} --brain ${JSON.stringify(qualifyWith)}`;
|
|
13866
|
+
out.push("", `\`${openLine}\``);
|
|
12892
13867
|
}
|
|
12893
13868
|
if (stage.outline && stage.outline.length > 0) {
|
|
12894
13869
|
out.push("", ...stage.outline.map((h) => `- ${h}`));
|
|
@@ -13019,10 +13994,10 @@ function rememberCommand() {
|
|
|
13019
13994
|
...kindLanded ? {} : { kind_dropped: kind }
|
|
13020
13995
|
},
|
|
13021
13996
|
human: () => {
|
|
13022
|
-
printHuman(`Remembered in ${
|
|
13997
|
+
printHuman(`Remembered in ${oneLine(target.name, WIDTH.title)}.
|
|
13023
13998
|
`);
|
|
13024
13999
|
printHuman(
|
|
13025
|
-
`note: \`${
|
|
14000
|
+
`note: \`${oneLine(written.id, WIDTH.handle)}\` \xB7 \`${oneLine(written.path, WIDTH.path)}\``
|
|
13026
14001
|
);
|
|
13027
14002
|
if (opts.rule === true) {
|
|
13028
14003
|
printHuman("rule: shown before you write a file here, once a session");
|
|
@@ -13034,7 +14009,9 @@ function rememberCommand() {
|
|
|
13034
14009
|
)
|
|
13035
14010
|
);
|
|
13036
14011
|
}
|
|
13037
|
-
printHuman(
|
|
14012
|
+
printHuman(
|
|
14013
|
+
c.gray(`undo: \`memgineering undo ${oneLine(written.op_id, WIDTH.handle)}\``)
|
|
14014
|
+
);
|
|
13038
14015
|
}
|
|
13039
14016
|
});
|
|
13040
14017
|
return;
|
|
@@ -13151,6 +14128,9 @@ init_vault();
|
|
|
13151
14128
|
init_ui();
|
|
13152
14129
|
function resurfaceCommand() {
|
|
13153
14130
|
return new Command20("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").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
|
|
14131
|
+
"--brain <name>",
|
|
14132
|
+
"read one existing hosted brain by name or id, without changing the pointer"
|
|
14133
|
+
).option(
|
|
13154
14134
|
"--deadline <ms>",
|
|
13155
14135
|
"give up and print nothing after this long",
|
|
13156
14136
|
(v) => parseInt(v, 10)
|
|
@@ -13160,11 +14140,16 @@ function resurfaceCommand() {
|
|
|
13160
14140
|
await bounded(async () => {
|
|
13161
14141
|
const contextDir = opts.contextDir ?? process.cwd();
|
|
13162
14142
|
const where = await resolveTarget({
|
|
14143
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
13163
14144
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
13164
14145
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
13165
14146
|
});
|
|
13166
14147
|
if (where.kind === "cloud") {
|
|
13167
|
-
await resurfaceHosted(
|
|
14148
|
+
await resurfaceHosted(
|
|
14149
|
+
where,
|
|
14150
|
+
opts.limit,
|
|
14151
|
+
opts.brain === void 0 ? void 0 : where.name
|
|
14152
|
+
);
|
|
13168
14153
|
return;
|
|
13169
14154
|
}
|
|
13170
14155
|
const { brain, unreachableDefault } = await resolveBrain(opts.vault, contextDir);
|
|
@@ -13235,9 +14220,7 @@ function resurfaceCommand() {
|
|
|
13235
14220
|
`);
|
|
13236
14221
|
if (unreachableDefault) {
|
|
13237
14222
|
printHuman(
|
|
13238
|
-
c.yellow(
|
|
13239
|
-
`Your default brain is not on disk right now: ${unreachableDefault}`
|
|
13240
|
-
)
|
|
14223
|
+
c.yellow(`Your default brain is not on disk right now: ${unreachableDefault}`)
|
|
13241
14224
|
);
|
|
13242
14225
|
printHuman(
|
|
13243
14226
|
c.gray(
|
|
@@ -13251,7 +14234,7 @@ function resurfaceCommand() {
|
|
|
13251
14234
|
);
|
|
13252
14235
|
for (const [i, { entry, why }] of rankedShown.entries()) {
|
|
13253
14236
|
const blank = untouched.has(entry.path);
|
|
13254
|
-
printHuman(`### ${i + 1}. ${
|
|
14237
|
+
printHuman(`### ${i + 1}. ${oneLine(entry.memory.title, WIDTH.title)}`);
|
|
13255
14238
|
printHuman(blank ? c.yellow("not filled in yet") : why.join(" \xB7 "));
|
|
13256
14239
|
if (blank) {
|
|
13257
14240
|
const hasWritten = rankedShown.some((r) => !untouched.has(r.entry.path));
|
|
@@ -13281,7 +14264,7 @@ ${quoteBlock(entry.memory.summary)}`);
|
|
|
13281
14264
|
}
|
|
13282
14265
|
);
|
|
13283
14266
|
}
|
|
13284
|
-
async function resurfaceHosted(where, limit) {
|
|
14267
|
+
async function resurfaceHosted(where, limit, qualifyWith) {
|
|
13285
14268
|
const brain = await openCloudBrain(where);
|
|
13286
14269
|
const result = await cloudResurface(brain, { limit });
|
|
13287
14270
|
printDual({
|
|
@@ -13293,31 +14276,35 @@ async function resurfaceHosted(where, limit) {
|
|
|
13293
14276
|
},
|
|
13294
14277
|
human: () => {
|
|
13295
14278
|
if (result.cards.length === 0) {
|
|
13296
|
-
printHuman(`## resurface \u2014 ${
|
|
14279
|
+
printHuman(`## resurface \u2014 ${oneLine(where.name, WIDTH.title)}
|
|
13297
14280
|
`);
|
|
13298
14281
|
printHuman("_Nothing to bring up yet._\n");
|
|
13299
|
-
if (result.hint) printHuman(c.gray(
|
|
14282
|
+
if (result.hint) printHuman(c.gray(oneLine(result.hint, WIDTH.text)));
|
|
13300
14283
|
return;
|
|
13301
14284
|
}
|
|
13302
|
-
printHuman(
|
|
13303
|
-
|
|
14285
|
+
printHuman(
|
|
14286
|
+
`## resurface \u2014 ${oneLine(where.name, WIDTH.title)} (${result.cards.length} cards)
|
|
14287
|
+
`
|
|
14288
|
+
);
|
|
13304
14289
|
for (const [i, card] of result.cards.entries()) {
|
|
13305
|
-
printHuman(`### ${i + 1}. ${
|
|
14290
|
+
printHuman(`### ${i + 1}. ${oneLine(card.title, WIDTH.title)}`);
|
|
13306
14291
|
if (card.untouched === true) printHuman(c.yellow("not filled in yet"));
|
|
13307
|
-
else if (card.why.length > 0) printHuman(
|
|
14292
|
+
else if (card.why.length > 0) printHuman(oneLine(card.why.join(" \xB7 "), WIDTH.text));
|
|
13308
14293
|
if (card.summary) printHuman(`
|
|
13309
14294
|
${quoteBlock(card.summary)}`);
|
|
13310
|
-
printHuman(
|
|
13311
|
-
|
|
13312
|
-
|
|
14295
|
+
printHuman(
|
|
14296
|
+
qualifyWith === void 0 ? `
|
|
14297
|
+
\`open: ${oneLine(card.handle, WIDTH.handle)}\`
|
|
14298
|
+
` : `
|
|
14299
|
+
\`memgineering open ${oneLine(card.handle, WIDTH.handle)} --brain ${JSON.stringify(qualifyWith)}\`
|
|
14300
|
+
`
|
|
14301
|
+
);
|
|
13313
14302
|
}
|
|
13314
|
-
|
|
14303
|
+
printBlock(
|
|
13315
14304
|
c.gray(
|
|
13316
|
-
|
|
13317
|
-
`Ranked by ${result.ranked_by.join(", ")}. A hosted brain does not record where a
|
|
14305
|
+
`Ranked by ${oneLine(result.ranked_by.join(", "), WIDTH.text)}. A hosted brain does not record where a
|
|
13318
14306
|
question was asked from, so "in this folder" is not one of them \u2014 that signal
|
|
13319
14307
|
exists only for a brain on this machine.`
|
|
13320
|
-
)
|
|
13321
14308
|
)
|
|
13322
14309
|
);
|
|
13323
14310
|
}
|
|
@@ -13328,8 +14315,12 @@ exists only for a brain on this machine.`
|
|
|
13328
14315
|
import { Command as Command21 } from "commander";
|
|
13329
14316
|
init_ui();
|
|
13330
14317
|
function rulesCommand() {
|
|
13331
|
-
return new Command21("rules").description("the user\u2019s standing decisions \u2014 what binds you here").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").
|
|
14318
|
+
return new Command21("rules").description("the user\u2019s standing decisions \u2014 what binds you here").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
|
|
14319
|
+
"--brain <name>",
|
|
14320
|
+
"read one existing hosted brain by name or id, without changing the pointer"
|
|
14321
|
+
).action(async (opts) => {
|
|
13332
14322
|
const where = await resolveTarget({
|
|
14323
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
13333
14324
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
13334
14325
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
13335
14326
|
});
|
|
@@ -13338,7 +14329,7 @@ function rulesCommand() {
|
|
|
13338
14329
|
printDual({
|
|
13339
14330
|
json: { brain: where.name, hosted: true, rules: rules2, total: total2 },
|
|
13340
14331
|
human: () => {
|
|
13341
|
-
printHuman(`## rules \u2014 ${
|
|
14332
|
+
printHuman(`## rules \u2014 ${oneLine(where.name, WIDTH.title)} (${total2})
|
|
13342
14333
|
`);
|
|
13343
14334
|
if (rules2.length === 0) {
|
|
13344
14335
|
printHuman("_Nothing is marked as a rule in this brain._\n");
|
|
@@ -13350,8 +14341,8 @@ function rulesCommand() {
|
|
|
13350
14341
|
return;
|
|
13351
14342
|
}
|
|
13352
14343
|
for (const rule of rules2) {
|
|
13353
|
-
printHuman(`- ${
|
|
13354
|
-
printHuman(c.gray(` \`open: ${
|
|
14344
|
+
printHuman(`- ${oneLine(rule.summary ?? rule.title, WIDTH.text)}`);
|
|
14345
|
+
printHuman(c.gray(` \`open: ${oneLine(rule.handle, WIDTH.handle)}\``));
|
|
13355
14346
|
}
|
|
13356
14347
|
if (total2 > rules2.length) {
|
|
13357
14348
|
printHuman(c.gray(`
|
|
@@ -13403,10 +14394,10 @@ This answered from \`${brainRoot}\` instead, so rules you wrote in the missing o
|
|
|
13403
14394
|
printHuman(`## rules (${rules.length}${total > rules.length ? ` of ${total}` : ""})
|
|
13404
14395
|
`);
|
|
13405
14396
|
for (const { entry } of rules) {
|
|
13406
|
-
printHuman(`- ${
|
|
14397
|
+
printHuman(`- ${oneLine(entry.memory.summary ?? entry.memory.title, WIDTH.text)}`);
|
|
13407
14398
|
printHuman(
|
|
13408
14399
|
c.gray(
|
|
13409
|
-
` \`open: ${displayHandle(entry.memory.id, allIds)}\`${entry.memory.scope ? ` \xB7 ${
|
|
14400
|
+
` \`open: ${displayHandle(entry.memory.id, allIds)}\`${entry.memory.scope ? ` \xB7 ${oneLine(entry.memory.scope, WIDTH.title)}` : ""}`
|
|
13410
14401
|
)
|
|
13411
14402
|
);
|
|
13412
14403
|
}
|
|
@@ -13768,7 +14759,7 @@ async function reportPreviousAttempt() {
|
|
|
13768
14759
|
function reportNotice(text) {
|
|
13769
14760
|
if (!text) return;
|
|
13770
14761
|
printHuman(c.gray(`
|
|
13771
|
-
note from memgineering.com: "${
|
|
14762
|
+
note from memgineering.com: "${oneLine(text, WIDTH.text)}"`));
|
|
13772
14763
|
}
|
|
13773
14764
|
function reportBelowMinimum(below) {
|
|
13774
14765
|
if (!below) return;
|
|
@@ -13806,11 +14797,11 @@ function undoCommand() {
|
|
|
13806
14797
|
json: { ...undone, brain: { hosted: true, name: where.name } },
|
|
13807
14798
|
human: () => {
|
|
13808
14799
|
printHuman(
|
|
13809
|
-
`Took back \`${
|
|
14800
|
+
`Took back \`${oneLine(undone.undone, WIDTH.handle)}\` \u2014 a ${oneLine(undone.verb, WIDTH.title)}.
|
|
13810
14801
|
`
|
|
13811
14802
|
);
|
|
13812
|
-
printHuman(c.gray(` ${
|
|
13813
|
-
printHuman(c.gray(` recorded as \`${
|
|
14803
|
+
printHuman(c.gray(` ${oneLine(undone.target, WIDTH.path)}`));
|
|
14804
|
+
printHuman(c.gray(` recorded as \`${oneLine(undone.op_id, WIDTH.handle)}\``));
|
|
13814
14805
|
}
|
|
13815
14806
|
});
|
|
13816
14807
|
return;
|
|
@@ -13896,7 +14887,7 @@ ${matches.slice(0, 8).map((id) => ` ${id}`).join("\n")}${matches.length > 8 ? "
|
|
|
13896
14887
|
},
|
|
13897
14888
|
human: () => {
|
|
13898
14889
|
printHuman(
|
|
13899
|
-
`Undone: ${
|
|
14890
|
+
`Undone: ${oneLine(target.verb, WIDTH.title)} on \`${oneLine(target.target, WIDTH.path)}\`
|
|
13900
14891
|
`
|
|
13901
14892
|
);
|
|
13902
14893
|
if (opId === void 0) {
|
|
@@ -13987,13 +14978,95 @@ Read what it says now, then decide what should stay.`;
|
|
|
13987
14978
|
The text from before that change is still on this machine: ${join23(snapshotDir(vaultRoot), `${op.op_id}.txt`)}`;
|
|
13988
14979
|
}
|
|
13989
14980
|
|
|
14981
|
+
// src/commands/brains.ts
|
|
14982
|
+
init_errors();
|
|
14983
|
+
init_api_client();
|
|
14984
|
+
import { Command as Command25 } from "commander";
|
|
14985
|
+
import stringWidth from "string-width";
|
|
14986
|
+
init_config();
|
|
14987
|
+
init_ui();
|
|
14988
|
+
var NAME_WIDTH = 48;
|
|
14989
|
+
function brainsCommand() {
|
|
14990
|
+
return new Command25("brains").description("list the hosted brains on your account, and which one this machine reads").addHelpText(
|
|
14991
|
+
"after",
|
|
14992
|
+
"\nReads only. `memgineering use --brain <name>` points this machine at one of\nthem; `memgineering push --brain <name>` is what creates a new one."
|
|
14993
|
+
).action(async () => {
|
|
14994
|
+
const base = apiUrl();
|
|
14995
|
+
const credentials = await requireCredentials(base);
|
|
14996
|
+
const brains = await listCloudBrains(credentials.token, base);
|
|
14997
|
+
const pointer = (await loadConfig()).brain.cloud;
|
|
14998
|
+
const currentId = pointer?.api_url === base ? pointer.id : null;
|
|
14999
|
+
if (brains.length === 0) {
|
|
15000
|
+
throw memgError(
|
|
15001
|
+
"invalid_input",
|
|
15002
|
+
"this account has no hosted brains",
|
|
15003
|
+
"Nothing is wrong \u2014 none has been made yet. `memgineering push --brain <name>` creates one and fills it from a folder on this machine."
|
|
15004
|
+
);
|
|
15005
|
+
}
|
|
15006
|
+
const rows = brains.map((b) => ({
|
|
15007
|
+
id: b.id,
|
|
15008
|
+
name: b.name,
|
|
15009
|
+
// Passed through rather than defaulted to 0. A server that does not send
|
|
15010
|
+
// a count and a brain that holds no notes are different facts, and the
|
|
15011
|
+
// second is alarming.
|
|
15012
|
+
note_count: b.note_count ?? null,
|
|
15013
|
+
created_at: b.created_at ?? null,
|
|
15014
|
+
current: b.id === currentId
|
|
15015
|
+
}));
|
|
15016
|
+
printDual({
|
|
15017
|
+
json: {
|
|
15018
|
+
// WHOSE ACCOUNT, on the command that lists what the account holds.
|
|
15019
|
+
//
|
|
15020
|
+
// "What is this computer connected to?" took three commands to answer:
|
|
15021
|
+
// `whoami` for the identity, this for the brains, `use` for the folder
|
|
15022
|
+
// — and `whoami`, the one named after the account, was the only one
|
|
15023
|
+
// that answered none of the rest. Two round-test agents ran all three.
|
|
15024
|
+
// The name is free here; it came back with the token.
|
|
15025
|
+
account: { id: credentials.account_id, name: credentials.display_name },
|
|
15026
|
+
server: base,
|
|
15027
|
+
brains: rows,
|
|
15028
|
+
count: rows.length,
|
|
15029
|
+
// Named at the top level too, so a caller does not have to scan the
|
|
15030
|
+
// array to answer the question it most often has.
|
|
15031
|
+
current: currentId,
|
|
15032
|
+
hint: currentId === null ? "This machine is not pointed at any of them. `memgineering use --brain <name>` picks one \u2014 it moves the pointer and uploads nothing." : "Switch with `memgineering use --brain <name>`, which moves the pointer and uploads nothing. `push --brain <name>` is what creates a new brain."
|
|
15033
|
+
},
|
|
15034
|
+
human: () => {
|
|
15035
|
+
printHuman(
|
|
15036
|
+
`## ${rows.length} brain${rows.length === 1 ? "" : "s"} on ${credentials.display_name === null ? "this account" : `${oneLine(credentials.display_name, NAME_WIDTH)}\u2019s account`}
|
|
15037
|
+
`
|
|
15038
|
+
);
|
|
15039
|
+
const shownNames = rows.map((row) => oneLine(row.name, NAME_WIDTH));
|
|
15040
|
+
const shownNameWidth = Math.max(...shownNames.map((name) => stringWidth(name)));
|
|
15041
|
+
for (const [index, row] of rows.entries()) {
|
|
15042
|
+
const shownName = shownNames[index] ?? "";
|
|
15043
|
+
const name = shownName + " ".repeat(shownNameWidth - stringWidth(shownName));
|
|
15044
|
+
const notes = row.note_count === null ? "" : `${String(row.note_count).padStart(6)} note(s)`;
|
|
15045
|
+
const made = row.created_at === null ? "" : ` made ${row.created_at.slice(0, 10)}`;
|
|
15046
|
+
printHuman(
|
|
15047
|
+
` ${name}${c.gray(notes)}${c.gray(made)}` + (row.current ? c.yellow(" \u2190 this machine reads this one") : "")
|
|
15048
|
+
);
|
|
15049
|
+
}
|
|
15050
|
+
printHuman("");
|
|
15051
|
+
printHuman(
|
|
15052
|
+
c.gray(
|
|
15053
|
+
currentId === null ? " This machine is not pointed at any of them.\n `memgineering use --brain <name>` picks one \u2014 nothing is uploaded." : " `memgineering use --brain <name>` switches \u2014 nothing is uploaded.\n `memgineering push --brain <name>` is what creates a new one."
|
|
15054
|
+
)
|
|
15055
|
+
);
|
|
15056
|
+
}
|
|
15057
|
+
});
|
|
15058
|
+
});
|
|
15059
|
+
}
|
|
15060
|
+
|
|
13990
15061
|
// src/commands/use.ts
|
|
13991
15062
|
init_atomic();
|
|
13992
15063
|
init_brand();
|
|
13993
15064
|
init_errors();
|
|
13994
|
-
|
|
15065
|
+
init_api_client();
|
|
15066
|
+
init_credentials();
|
|
13995
15067
|
import { isAbsolute as isAbsolute5, relative as relative3, resolve as resolve11 } from "path";
|
|
13996
|
-
import { Command as
|
|
15068
|
+
import { Command as Command26 } from "commander";
|
|
15069
|
+
init_config();
|
|
13997
15070
|
init_vault();
|
|
13998
15071
|
init_ui();
|
|
13999
15072
|
function lostDefaultLines(missing) {
|
|
@@ -14012,16 +15085,24 @@ var VIA_MEANS = {
|
|
|
14012
15085
|
default: "because it is your default brain (`memgineering use --default <path>` changes it)"
|
|
14013
15086
|
};
|
|
14014
15087
|
function useCommand() {
|
|
14015
|
-
return new
|
|
15088
|
+
return new Command26("use").description(
|
|
15089
|
+
"say which brain answers here \u2014 `--brain <name>` for the whole machine, or a path to bind this folder"
|
|
15090
|
+
).argument("[brain]", "path to the brain \u2014 omit to show what this folder resolves to").option("--brain <name>", "point this machine at a hosted brain, by its name or id").option("--here <dir>", "write the pointer in this directory instead of the cwd").option(
|
|
14016
15091
|
"--default",
|
|
14017
|
-
"
|
|
15092
|
+
"for a FOLDER on this machine: make it the one used wherever no pointer applies. Not for hosted brains \u2014 `--brain <name>` is that"
|
|
14018
15093
|
).action(async (brainPath, opts) => {
|
|
15094
|
+
if (opts.brain !== void 0) {
|
|
15095
|
+
await pointAtHosted(opts.brain, opts, brainPath);
|
|
15096
|
+
return;
|
|
15097
|
+
}
|
|
14019
15098
|
const where = await canonicalize(resolve11(opts.here ?? process.cwd()));
|
|
14020
15099
|
if (!brainPath) {
|
|
14021
15100
|
const pointer = await readPointer(where);
|
|
14022
15101
|
const resolved = await resolveBrain(void 0, where).catch((err) => err);
|
|
14023
15102
|
const stuck = resolved instanceof Error;
|
|
14024
15103
|
const hosted = await hostedPointer();
|
|
15104
|
+
const stored = await readCredentials().catch(() => null);
|
|
15105
|
+
const signedIn = stored !== null && stored.api_url === apiUrl();
|
|
14025
15106
|
printDual({
|
|
14026
15107
|
json: {
|
|
14027
15108
|
// The brain that answers `recall` and takes `remember` — the same
|
|
@@ -14056,14 +15137,24 @@ function useCommand() {
|
|
|
14056
15137
|
brain: stuck ? null : resolved.brain.root,
|
|
14057
15138
|
via: stuck ? null : resolved.via,
|
|
14058
15139
|
pointer: pointer ? pointer.from : null,
|
|
14059
|
-
|
|
15140
|
+
// ONLY WHEN THE LOCAL FOLDER IS THE ANSWER. `stuck` describes the
|
|
15141
|
+
// FOLDER resolver, and on a machine reading a hosted brain that
|
|
15142
|
+
// resolver having nothing is the expected state, not a problem —
|
|
15143
|
+
// but it was reported unqualified beside `destination: "hosted"`,
|
|
15144
|
+
// so a successful switch produced `problem: "no_vault_linked"` and
|
|
15145
|
+
// `message: "no brain is linked yet"` in the same document that
|
|
15146
|
+
// named the brain it had just moved to. Two agents read `problem`
|
|
15147
|
+
// first and would have told the user the switch failed.
|
|
15148
|
+
//
|
|
15149
|
+
// `--local` is how somebody asks what the folder side would do.
|
|
15150
|
+
...stuck && hosted?.signedIn !== true ? {
|
|
14060
15151
|
problem: resolved.code ?? "unresolved",
|
|
14061
15152
|
message: resolved.message
|
|
14062
15153
|
} : {}
|
|
14063
15154
|
},
|
|
14064
15155
|
human: () => {
|
|
14065
15156
|
if (hosted?.signedIn) {
|
|
14066
|
-
printHuman(`Using the hosted brain \`${
|
|
15157
|
+
printHuman(`Using the hosted brain \`${oneLine(hosted.name, NAME_WIDTH2)}\`
|
|
14067
15158
|
`);
|
|
14068
15159
|
printHuman(
|
|
14069
15160
|
c.gray(
|
|
@@ -14084,7 +15175,7 @@ function useCommand() {
|
|
|
14084
15175
|
);
|
|
14085
15176
|
printHuman(
|
|
14086
15177
|
c.yellow(
|
|
14087
|
-
` A sign-in to \`${
|
|
15178
|
+
` A sign-in to \`${oneLine(hosted.name, NAME_WIDTH2)}\` is waiting to be approved.`
|
|
14088
15179
|
)
|
|
14089
15180
|
);
|
|
14090
15181
|
printHuman(
|
|
@@ -14101,7 +15192,9 @@ function useCommand() {
|
|
|
14101
15192
|
printHuman(`No brain resolves here.
|
|
14102
15193
|
`);
|
|
14103
15194
|
printHuman(
|
|
14104
|
-
c.yellow(
|
|
15195
|
+
c.yellow(
|
|
15196
|
+
` Pointed at the hosted brain \`${oneLine(hosted.name, NAME_WIDTH2)}\`,`
|
|
15197
|
+
) + c.gray(" with no sign-in on this machine that matches it,\n") + c.gray(" and no brain linked here either. `memgineering login` connects it.")
|
|
14105
15198
|
);
|
|
14106
15199
|
return;
|
|
14107
15200
|
}
|
|
@@ -14110,7 +15203,7 @@ function useCommand() {
|
|
|
14110
15203
|
printHuman(c.gray(` ${VIA_MEANS[resolved.via]}`));
|
|
14111
15204
|
printHuman(
|
|
14112
15205
|
c.yellow(
|
|
14113
|
-
` This machine is pointed at the hosted brain \`${
|
|
15206
|
+
` This machine is pointed at the hosted brain \`${oneLine(hosted.name, NAME_WIDTH2)}\` and is not signed in to it,`
|
|
14114
15207
|
) + c.gray("\n so reads and writes are landing here instead.")
|
|
14115
15208
|
);
|
|
14116
15209
|
printHuman(
|
|
@@ -14121,6 +15214,14 @@ function useCommand() {
|
|
|
14121
15214
|
if (stuck) {
|
|
14122
15215
|
printHuman(`No brain resolves here.
|
|
14123
15216
|
`);
|
|
15217
|
+
if (signedIn) {
|
|
15218
|
+
printHuman(
|
|
15219
|
+
c.yellow(" This machine is signed in, and pointed at no brain.") + c.gray(
|
|
15220
|
+
"\n Your notes may be on the account rather than missing.\n `memgineering brains` lists them; `memgineering use --brain <name>` points here.\n `init` and `link` make a NEW empty brain on this machine \u2014 not what you want\n if the notes are already on the account."
|
|
15221
|
+
)
|
|
15222
|
+
);
|
|
15223
|
+
return;
|
|
15224
|
+
}
|
|
14124
15225
|
printHuman(c.gray(` ${resolved.message}`));
|
|
14125
15226
|
const hint = resolved.hint;
|
|
14126
15227
|
if (hint) printHuman(c.gray(`
|
|
@@ -14187,13 +15288,114 @@ A pointer to an unlinked folder would fail on every command instead of at this o
|
|
|
14187
15288
|
});
|
|
14188
15289
|
});
|
|
14189
15290
|
}
|
|
15291
|
+
var NAME_WIDTH2 = 72;
|
|
15292
|
+
function describeSize(count) {
|
|
15293
|
+
if (count === void 0) return "";
|
|
15294
|
+
if (count === 0)
|
|
15295
|
+
return " \u2014 which is EMPTY. Nothing here has been lost; that brain has no notes yet";
|
|
15296
|
+
return ` \u2014 ${count} note(s)`;
|
|
15297
|
+
}
|
|
15298
|
+
async function pointAtHosted(named, opts, path) {
|
|
15299
|
+
if (named.startsWith("--")) {
|
|
15300
|
+
throw memgError(
|
|
15301
|
+
"invalid_input",
|
|
15302
|
+
`\`--brain\` was given the flag \`${oneLine(named, 32)}\` as its name`,
|
|
15303
|
+
"Nothing was changed. `--brain` takes the name or id of a hosted brain, and the\nflag written after it was read as that name. Put the name first \u2014 `memgineering\nuse --brain <name>` \u2014 and run the other flag as its own command."
|
|
15304
|
+
);
|
|
15305
|
+
}
|
|
15306
|
+
const clash = path !== void 0 ? "a folder path" : opts.default === true ? "`--default`" : opts.here !== void 0 ? "`--here`" : null;
|
|
15307
|
+
if (clash !== null) {
|
|
15308
|
+
throw memgError(
|
|
15309
|
+
"invalid_input",
|
|
15310
|
+
`\`--brain\` cannot be combined with ${clash}`,
|
|
15311
|
+
"They point different things. `--brain <name>` moves the one hosted brain this machine reads, which is a single setting for the whole machine; a path, `--default` and `--here` all bind a FOLDER on this disk. Run them separately."
|
|
15312
|
+
);
|
|
15313
|
+
}
|
|
15314
|
+
const base = apiUrl();
|
|
15315
|
+
const credentials = await requireCredentials(base).catch((err) => {
|
|
15316
|
+
if (err instanceof MemgError && err.code === "not_signed_in") {
|
|
15317
|
+
throw memgError(
|
|
15318
|
+
"not_signed_in",
|
|
15319
|
+
"this machine is not signed in to any account",
|
|
15320
|
+
"Nothing was changed. `--brain` names a brain on an ACCOUNT, so there has to be one:\n\n `memgineering login --emit-only` then show the user the code, and re-run this\n\nA brain in a folder on this machine is a different thing and `use <path>` binds one."
|
|
15321
|
+
);
|
|
15322
|
+
}
|
|
15323
|
+
throw err;
|
|
15324
|
+
});
|
|
15325
|
+
const brains = await listCloudBrains(credentials.token, base);
|
|
15326
|
+
if (brains.length === 0) {
|
|
15327
|
+
throw memgError(
|
|
15328
|
+
"invalid_input",
|
|
15329
|
+
"this account has no hosted brains",
|
|
15330
|
+
"Nothing was changed. `memgineering push --brain <name>` creates one and fills it from a folder on this machine."
|
|
15331
|
+
);
|
|
15332
|
+
}
|
|
15333
|
+
const found = findCloudBrain(brains, named);
|
|
15334
|
+
if (!found) {
|
|
15335
|
+
throw memgError(
|
|
15336
|
+
"invalid_input",
|
|
15337
|
+
`no hosted brain called "${oneLine(named, NAME_WIDTH2)}"`,
|
|
15338
|
+
"Nothing was changed. This account has:\n" + brains.map((b) => ` ${oneLine(b.name, NAME_WIDTH2)} (${oneLine(b.id, NAME_WIDTH2)})`).join("\n") + "\nIf one of those is the one they meant, use it. If they genuinely want a NEW\nnotebook, ASK THEM FIRST and say what it costs: `memgineering push --brain\n<name>` creates one by UPLOADING a folder into it, so a new brain arrives\nholding whatever that folder held \u2014 which on a fresh one is five empty\ntemplate files, not their notes. Creating one is not a way to find\nnotes that already exist."
|
|
15339
|
+
);
|
|
15340
|
+
}
|
|
15341
|
+
const config = await loadConfig();
|
|
15342
|
+
const previous = config.brain.cloud ?? null;
|
|
15343
|
+
const unchanged = previous?.id === found.id && previous.api_url === base && previous.name === found.name;
|
|
15344
|
+
if (!unchanged) {
|
|
15345
|
+
await saveConfig({
|
|
15346
|
+
...config,
|
|
15347
|
+
brain: { ...config.brain, cloud: { id: found.id, name: found.name, api_url: base } }
|
|
15348
|
+
});
|
|
15349
|
+
}
|
|
15350
|
+
printDual({
|
|
15351
|
+
json: {
|
|
15352
|
+
brain: {
|
|
15353
|
+
id: found.id,
|
|
15354
|
+
name: found.name,
|
|
15355
|
+
api_url: base,
|
|
15356
|
+
// Carried because the human line says it. A caller that has to ask a
|
|
15357
|
+
// second time to learn the destination is empty is a caller that will
|
|
15358
|
+
// not ask.
|
|
15359
|
+
...found.note_count === void 0 ? {} : { note_count: found.note_count }
|
|
15360
|
+
},
|
|
15361
|
+
previous: previous ? { id: previous.id, name: previous.name } : null,
|
|
15362
|
+
// Present even when nothing moved, so a caller can tell "already there"
|
|
15363
|
+
// from "switched" without comparing two names itself.
|
|
15364
|
+
changed: !unchanged
|
|
15365
|
+
},
|
|
15366
|
+
human: () => {
|
|
15367
|
+
if (unchanged) {
|
|
15368
|
+
printHuman(`Already using \`${oneLine(found.name, NAME_WIDTH2)}\`.
|
|
15369
|
+
`);
|
|
15370
|
+
printHuman(c.gray(" Nothing changed."));
|
|
15371
|
+
return;
|
|
15372
|
+
}
|
|
15373
|
+
printHuman(
|
|
15374
|
+
`Now using the hosted brain \`${oneLine(found.name, NAME_WIDTH2)}\`${describeSize(found.note_count)}.
|
|
15375
|
+
`
|
|
15376
|
+
);
|
|
15377
|
+
if (previous) {
|
|
15378
|
+
printHuman(
|
|
15379
|
+
c.yellow(
|
|
15380
|
+
` This machine was reading \`${oneLine(previous.name, NAME_WIDTH2)}\` and now is not.`
|
|
15381
|
+
)
|
|
15382
|
+
);
|
|
15383
|
+
}
|
|
15384
|
+
printHuman(
|
|
15385
|
+
c.gray(
|
|
15386
|
+
" Reads and writes go there from now on. Nothing was uploaded and nothing\n was downloaded \u2014 `push` and `pull` are what move notes."
|
|
15387
|
+
)
|
|
15388
|
+
);
|
|
15389
|
+
}
|
|
15390
|
+
});
|
|
15391
|
+
}
|
|
14190
15392
|
|
|
14191
15393
|
// src/commands/whoami.ts
|
|
14192
15394
|
init_api_client();
|
|
14193
15395
|
init_ui();
|
|
14194
|
-
import { Command as
|
|
15396
|
+
import { Command as Command27 } from "commander";
|
|
14195
15397
|
function whoamiCommand() {
|
|
14196
|
-
return new
|
|
15398
|
+
return new Command27("whoami").description("show which account this machine is signed in as").action(async () => {
|
|
14197
15399
|
const base = apiUrl();
|
|
14198
15400
|
const credentials = await requireCredentials(base);
|
|
14199
15401
|
const res = await apiRequest({
|
|
@@ -14219,9 +15421,11 @@ function whoamiCommand() {
|
|
|
14219
15421
|
},
|
|
14220
15422
|
human: () => {
|
|
14221
15423
|
printHuman(
|
|
14222
|
-
c.bold(
|
|
15424
|
+
c.bold(
|
|
15425
|
+
oneLine(account.display_name ?? account.id ?? credentials.account_id, WIDTH.title)
|
|
15426
|
+
)
|
|
14223
15427
|
);
|
|
14224
|
-
if (account.email) printHuman(c.gray(` ${
|
|
15428
|
+
if (account.email) printHuman(c.gray(` ${oneLine(account.email, WIDTH.title)}`));
|
|
14225
15429
|
printHuman(c.gray(` on ${base}`));
|
|
14226
15430
|
if (live !== null) {
|
|
14227
15431
|
printHuman(
|
|
@@ -14236,6 +15440,8 @@ function whoamiCommand() {
|
|
|
14236
15440
|
}
|
|
14237
15441
|
|
|
14238
15442
|
// src/lib/next-step.ts
|
|
15443
|
+
init_api_client();
|
|
15444
|
+
init_credentials();
|
|
14239
15445
|
init_config();
|
|
14240
15446
|
function isSetUp(cfg) {
|
|
14241
15447
|
return Object.keys(cfg.system.installed).length > 0;
|
|
@@ -14289,7 +15495,16 @@ Then run \`memgineering onboard\`: it prints what to ask so the brain knows whos
|
|
|
14289
15495
|
it is. A brain whose \`01_BASE/\` is still template text answers almost nothing.
|
|
14290
15496
|
|
|
14291
15497
|
Read \`memgineering-setup\` for accounts and which of their tools this reaches.`;
|
|
14292
|
-
|
|
15498
|
+
var POINT_AT_A_BRAIN = `## Signed in, and this machine reads no brain yet.
|
|
15499
|
+
|
|
15500
|
+
\`memgineering brains\` lists what is on the account.
|
|
15501
|
+
\`memgineering use --brain <name>\` points this machine at one.
|
|
15502
|
+
`;
|
|
15503
|
+
function nextStepFor(cfg, signedIn = false) {
|
|
15504
|
+
if (signedIn && cfg.brain.cloud === void 0) {
|
|
15505
|
+
return { id: "point_at_a_brain", markdown: POINT_AT_A_BRAIN };
|
|
15506
|
+
}
|
|
15507
|
+
if (cfg.brain.cloud !== void 0 && cfg.brain.cloud.api_url === apiUrl()) return null;
|
|
14293
15508
|
if (!isSetUp(cfg)) return { id: "finish_setup", markdown: FINISH_SETUP };
|
|
14294
15509
|
if (cfg.brain.brains.length === 0 && cfg.brain.cloud === void 0) {
|
|
14295
15510
|
return { id: "connect_a_brain", markdown: CONNECT_A_BRAIN };
|
|
@@ -14298,7 +15513,8 @@ function nextStepFor(cfg) {
|
|
|
14298
15513
|
}
|
|
14299
15514
|
async function currentNextStep() {
|
|
14300
15515
|
try {
|
|
14301
|
-
|
|
15516
|
+
const stored = await readCredentials().catch(() => null);
|
|
15517
|
+
return nextStepFor(await loadConfig(), stored !== null && stored.api_url === apiUrl());
|
|
14302
15518
|
} catch {
|
|
14303
15519
|
return null;
|
|
14304
15520
|
}
|
|
@@ -14308,7 +15524,7 @@ async function currentNextStep() {
|
|
|
14308
15524
|
init_ui();
|
|
14309
15525
|
init_brand();
|
|
14310
15526
|
function buildProgram() {
|
|
14311
|
-
const program = new
|
|
15527
|
+
const program = new Command28("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", async (thisCommand, actionCommand) => {
|
|
14312
15528
|
const opts = thisCommand.optsWithGlobals();
|
|
14313
15529
|
if (opts.json || process.env[BRAND.jsonEnvVar] === "1") setJsonMode(true);
|
|
14314
15530
|
await announceNextStep(actionCommand.name());
|
|
@@ -14329,6 +15545,7 @@ function buildProgram() {
|
|
|
14329
15545
|
program.addCommand(initCommand());
|
|
14330
15546
|
program.addCommand(linkCommand());
|
|
14331
15547
|
program.addCommand(useCommand());
|
|
15548
|
+
program.addCommand(brainsCommand());
|
|
14332
15549
|
program.addCommand(unlinkCommand());
|
|
14333
15550
|
program.addCommand(reindexCommand());
|
|
14334
15551
|
program.addCommand(syncRulesCommand());
|
|
@@ -14368,8 +15585,15 @@ function makeParseErrorsActionable(program) {
|
|
|
14368
15585
|
outputError: (str, write) => {
|
|
14369
15586
|
write(str);
|
|
14370
15587
|
if (!/unknown option|unknown command|too many arguments/i.test(str)) return;
|
|
14371
|
-
const flags = cmd.options.map((o) => o.long ?? o.short).filter(Boolean);
|
|
14372
15588
|
const path = cmd.parent === null ? cmd.name() : `${cmd.parent.name()} ${cmd.name()}`;
|
|
15589
|
+
if (/unknown command/i.test(str) && cmd.commands.length > 0) {
|
|
15590
|
+
write(
|
|
15591
|
+
`hint: ${path} has ${cmd.commands.map((c2) => c2.name()).sort().join(" ")}
|
|
15592
|
+
`
|
|
15593
|
+
);
|
|
15594
|
+
return;
|
|
15595
|
+
}
|
|
15596
|
+
const flags = cmd.options.map((o) => o.long ?? o.short).filter(Boolean);
|
|
14373
15597
|
write(
|
|
14374
15598
|
flags.length > 0 ? `hint: ${path} takes ${flags.join(" ")}
|
|
14375
15599
|
` : `hint: \`${path} --help\` lists what this takes.
|
|
@@ -14397,7 +15621,7 @@ async function handleError(err) {
|
|
|
14397
15621
|
if (isJsonMode()) {
|
|
14398
15622
|
printJson({ error: { code: err.code, message: err.message, hint: err.hint } });
|
|
14399
15623
|
} else {
|
|
14400
|
-
printHuman(c.red(`\u2717 ${
|
|
15624
|
+
printHuman(c.red(`\u2717 ${oneLine(err.message, WIDTH.text)}`));
|
|
14401
15625
|
for (const line of stripControl(err.hint).split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
14402
15626
|
}
|
|
14403
15627
|
process.exit(1);
|
|
@@ -14414,7 +15638,7 @@ async function handleError(err) {
|
|
|
14414
15638
|
}
|
|
14415
15639
|
});
|
|
14416
15640
|
} else {
|
|
14417
|
-
printHuman(c.red(`\u2717 ${
|
|
15641
|
+
printHuman(c.red(`\u2717 ${oneLine(err.message, WIDTH.text)}`));
|
|
14418
15642
|
if (hintStr) {
|
|
14419
15643
|
for (const line of stripControl(hintStr).split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
14420
15644
|
}
|
|
@@ -14422,7 +15646,7 @@ async function handleError(err) {
|
|
|
14422
15646
|
process.exit(1);
|
|
14423
15647
|
}
|
|
14424
15648
|
if (isJsonMode()) printJson({ error: { code: "generic_failure", message: String(err) } });
|
|
14425
|
-
else printHuman(c.red(`\u2717 ${
|
|
15649
|
+
else printHuman(c.red(`\u2717 ${oneLine(String(err), WIDTH.text)}`));
|
|
14426
15650
|
process.exit(1);
|
|
14427
15651
|
}
|
|
14428
15652
|
|