memgineering 0.8.0 → 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 +52 -0
- package/assets/MEMGINEERING.md +1 -1
- package/assets/memgineering-recall/SKILL.md +40 -7
- package/assets/memgineering-writing/SKILL.md +16 -5
- package/dist/index.js +837 -103
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -181,10 +181,29 @@ function quoteBlock(text, prefix = "> ") {
|
|
|
181
181
|
return stripControl(text).split("\n").map((line) => prefix + line).join("\n");
|
|
182
182
|
}
|
|
183
183
|
function oneLine(text, max) {
|
|
184
|
-
const flat =
|
|
184
|
+
const flat = oneLineWhole(text);
|
|
185
185
|
const chars = [...flat];
|
|
186
186
|
if (chars.length <= max) return flat;
|
|
187
|
-
|
|
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("")}`;
|
|
188
207
|
}
|
|
189
208
|
function printBlock(text) {
|
|
190
209
|
printHuman(stripControl(text));
|
|
@@ -216,7 +235,7 @@ var init_ui = __esm({
|
|
|
216
235
|
/** A vault path. Longer, because the tail is the part that identifies it. */
|
|
217
236
|
path: 120,
|
|
218
237
|
/** An id or an `open:` handle. */
|
|
219
|
-
handle:
|
|
238
|
+
handle: 128,
|
|
220
239
|
/** A claim, a summary, a server hint — a sentence rather than a label. */
|
|
221
240
|
text: 200
|
|
222
241
|
};
|
|
@@ -247,11 +266,12 @@ var init_errors = __esm({
|
|
|
247
266
|
|
|
248
267
|
// ../../packages/memory-engine/src/schema.ts
|
|
249
268
|
import { z } from "zod";
|
|
250
|
-
var MappingModeSchema, ProvenanceSchema, MemoryStatusSchema, ClaimConfidenceSchema, MemoryKindSchema, ClaimSchema, MemorySchema, RelationTypeSchema, RelationSourceSchema, RelationSchema;
|
|
269
|
+
var MappingModeSchema, ScopeSourceSchema, ProvenanceSchema, MemoryStatusSchema, ClaimConfidenceSchema, MemoryKindSchema, ClaimSchema, MemorySchema, RelationTypeSchema, RelationSourceSchema, RelationSchema;
|
|
251
270
|
var init_schema = __esm({
|
|
252
271
|
"../../packages/memory-engine/src/schema.ts"() {
|
|
253
272
|
"use strict";
|
|
254
273
|
MappingModeSchema = z.enum(["explicit", "heuristic"]);
|
|
274
|
+
ScopeSourceSchema = z.enum(["explicit", "path", "none"]);
|
|
255
275
|
ProvenanceSchema = z.object({
|
|
256
276
|
/** Vault-relative path of the note this was mapped from. */
|
|
257
277
|
sourcePath: z.string().min(1),
|
|
@@ -264,7 +284,9 @@ var init_schema = __esm({
|
|
|
264
284
|
* here would be the engine learning one backend's internals.
|
|
265
285
|
*/
|
|
266
286
|
sourceRevision: z.unknown(),
|
|
267
|
-
mapping: MappingModeSchema
|
|
287
|
+
mapping: MappingModeSchema,
|
|
288
|
+
/** Field-level provenance needed before scope may lower a semantic threshold. */
|
|
289
|
+
scopeSource: ScopeSourceSchema.optional()
|
|
268
290
|
});
|
|
269
291
|
MemoryStatusSchema = z.enum(["active", "superseded", "retired"]);
|
|
270
292
|
ClaimConfidenceSchema = z.enum(["stated", "observed", "inferred"]);
|
|
@@ -644,6 +666,8 @@ function mapNoteToMemory(input) {
|
|
|
644
666
|
const ns = explicit ? nsRaw : {};
|
|
645
667
|
const id = asString(ns["id"]) ?? path;
|
|
646
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);
|
|
647
671
|
const summary = asString(ns["summary"]) ?? outline.description ?? firstParagraph(outline.intro) ?? firstSectionParagraph(outline.sections) ?? null;
|
|
648
672
|
const memory = {
|
|
649
673
|
id,
|
|
@@ -658,7 +682,7 @@ function mapNoteToMemory(input) {
|
|
|
658
682
|
})(),
|
|
659
683
|
title,
|
|
660
684
|
summary,
|
|
661
|
-
scope
|
|
685
|
+
scope,
|
|
662
686
|
current_claim: asString(ns["current_claim"]),
|
|
663
687
|
status: statusOf(ns["status"], issues),
|
|
664
688
|
// Only the author's own `binding: true`. Never inferred: guessing that a
|
|
@@ -676,7 +700,8 @@ function mapNoteToMemory(input) {
|
|
|
676
700
|
provenance: {
|
|
677
701
|
sourcePath: path,
|
|
678
702
|
sourceRevision: revision,
|
|
679
|
-
mapping: explicit ? "explicit" : "heuristic"
|
|
703
|
+
mapping: explicit ? "explicit" : "heuristic",
|
|
704
|
+
scopeSource: declaredScope !== null ? "explicit" : scope === null ? "none" : "path"
|
|
680
705
|
}
|
|
681
706
|
};
|
|
682
707
|
const claims = [];
|
|
@@ -790,7 +815,7 @@ var init_cache_io = __esm({
|
|
|
790
815
|
"../../packages/memory-engine/src/view/cache-io.ts"() {
|
|
791
816
|
"use strict";
|
|
792
817
|
init_schema();
|
|
793
|
-
SCHEMA_VERSION =
|
|
818
|
+
SCHEMA_VERSION = 6;
|
|
794
819
|
POLICY_VERSION = 1;
|
|
795
820
|
CacheEntrySchema = z2.object({
|
|
796
821
|
path: z2.string().min(1),
|
|
@@ -1397,18 +1422,40 @@ var init_conflicts = __esm({
|
|
|
1397
1422
|
});
|
|
1398
1423
|
|
|
1399
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
|
+
}
|
|
1400
1428
|
function terms(text) {
|
|
1401
|
-
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);
|
|
1402
1444
|
}
|
|
1403
1445
|
function startsWithFieldTerm(query, fieldTerms) {
|
|
1404
|
-
if (query.length <= SUBSTRING_MIN) return false;
|
|
1446
|
+
if (query.length <= SUBSTRING_MIN || !HANGUL.test(query)) return false;
|
|
1405
1447
|
for (const f of fieldTerms) {
|
|
1406
|
-
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;
|
|
1407
1450
|
}
|
|
1408
1451
|
return false;
|
|
1409
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
|
+
}
|
|
1410
1457
|
function variantsOf(term) {
|
|
1411
|
-
if (!HANGUL.test(term)) return [term];
|
|
1458
|
+
if (exactOnly(term) || !HANGUL.test(term)) return [term];
|
|
1412
1459
|
for (const particle of PARTICLES) {
|
|
1413
1460
|
if (!term.endsWith(particle)) continue;
|
|
1414
1461
|
const stem = term.slice(0, -particle.length);
|
|
@@ -1416,19 +1463,22 @@ function variantsOf(term) {
|
|
|
1416
1463
|
}
|
|
1417
1464
|
return [term];
|
|
1418
1465
|
}
|
|
1419
|
-
function fieldScore(queryTerms, field, weight) {
|
|
1466
|
+
function fieldScore(queryTerms, field, weight, allowLeadingOneSyllable = false) {
|
|
1420
1467
|
if (field === null || field === "") return 0;
|
|
1421
1468
|
const normalized = field.normalize("NFC").toLowerCase();
|
|
1422
|
-
const
|
|
1469
|
+
const orderedFieldTerms = indexTerms(field);
|
|
1470
|
+
const fieldTerms = new Set(orderedFieldTerms);
|
|
1423
1471
|
let score = 0;
|
|
1424
|
-
for (const q of queryTerms) {
|
|
1472
|
+
for (const [queryIndex, q] of queryTerms.entries()) {
|
|
1425
1473
|
let best = 0;
|
|
1426
1474
|
for (const variant of variantsOf(q)) {
|
|
1427
1475
|
if (fieldTerms.has(variant)) {
|
|
1428
1476
|
best = Math.max(best, weight);
|
|
1429
|
-
} 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)) {
|
|
1430
1480
|
best = Math.max(best, weight / 2);
|
|
1431
|
-
} else if (startsWithFieldTerm(variant, fieldTerms)) {
|
|
1481
|
+
} else if (!exactOnly(variant) && startsWithFieldTerm(variant, fieldTerms)) {
|
|
1432
1482
|
best = Math.max(best, weight / 2);
|
|
1433
1483
|
}
|
|
1434
1484
|
}
|
|
@@ -1441,7 +1491,7 @@ function searchMemories(memories, query) {
|
|
|
1441
1491
|
if (q.length === 0) return [];
|
|
1442
1492
|
const scored = [];
|
|
1443
1493
|
for (const memory of memories) {
|
|
1444
|
-
let score = fieldScore(q, memory.title, WEIGHTS.title);
|
|
1494
|
+
let score = fieldScore(q, memory.title, WEIGHTS.title, true);
|
|
1445
1495
|
for (const alias of memory.aliases) score += fieldScore(q, alias, WEIGHTS.alias);
|
|
1446
1496
|
for (const entity of memory.entities) score += fieldScore(q, entity, WEIGHTS.entity);
|
|
1447
1497
|
score += fieldScore(q, memory.summary, WEIGHTS.summary);
|
|
@@ -1452,11 +1502,15 @@ function searchMemories(memories, query) {
|
|
|
1452
1502
|
(a, b) => b.score !== a.score ? b.score - a.score : a.memory.id < b.memory.id ? -1 : 1
|
|
1453
1503
|
);
|
|
1454
1504
|
}
|
|
1455
|
-
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;
|
|
1456
1506
|
var init_search = __esm({
|
|
1457
1507
|
"../../packages/memory-engine/src/recall/search.ts"() {
|
|
1458
1508
|
"use strict";
|
|
1459
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");
|
|
1460
1514
|
SUBSTRING_MIN = 2;
|
|
1461
1515
|
PARTICLES = [
|
|
1462
1516
|
"\uC774\uB77C\uB3C4",
|
|
@@ -1494,6 +1548,7 @@ var init_search = __esm({
|
|
|
1494
1548
|
"\uAED8"
|
|
1495
1549
|
];
|
|
1496
1550
|
HANGUL = /[\uac00-\ud7a3]/;
|
|
1551
|
+
ONE_SYLLABLE_TOPIC_TERMS = /* @__PURE__ */ new Set(["\uD0A4"]);
|
|
1497
1552
|
}
|
|
1498
1553
|
});
|
|
1499
1554
|
|
|
@@ -1511,7 +1566,7 @@ var init_chunks = __esm({
|
|
|
1511
1566
|
avgLength;
|
|
1512
1567
|
constructor(refs) {
|
|
1513
1568
|
for (const ref of refs) {
|
|
1514
|
-
const tokens =
|
|
1569
|
+
const tokens = indexTerms(ref.text);
|
|
1515
1570
|
if (tokens.length === 0) continue;
|
|
1516
1571
|
const counts = /* @__PURE__ */ new Map();
|
|
1517
1572
|
for (const token of tokens) counts.set(token, (counts.get(token) ?? 0) + 1);
|
|
@@ -1573,6 +1628,34 @@ var init_chunks = __esm({
|
|
|
1573
1628
|
function isRecallDepth(value) {
|
|
1574
1629
|
return typeof value === "string" && RECALL_DEPTHS.includes(value);
|
|
1575
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
|
+
}
|
|
1576
1659
|
function freshnessOf(memory, claims, now) {
|
|
1577
1660
|
if (memory.status === "superseded") return "superseded";
|
|
1578
1661
|
if (memory.status === "retired") return "retired";
|
|
@@ -1599,12 +1682,14 @@ function recall(entries, query, opts = {}) {
|
|
|
1599
1682
|
const claimsById = new Map(inScope.map((e) => [e.memory.id, e.claims]));
|
|
1600
1683
|
const relations = inScope.flatMap((e) => e.relations);
|
|
1601
1684
|
const allMemories = inScope.map((e) => e.memory);
|
|
1602
|
-
const
|
|
1685
|
+
const allMemoryIds = new Set(allMemories.map((memory) => memory.id));
|
|
1686
|
+
const queryShape = shapeRecallQuery(query);
|
|
1687
|
+
const cardHits = searchMemories(allMemories, queryShape.lexical);
|
|
1603
1688
|
const chunkHits = depth === "exact" ? [] : new ChunkIndex(
|
|
1604
1689
|
inScope.flatMap(
|
|
1605
1690
|
(e) => (e.chunks ?? []).map((c2) => ({ memoryId: e.memory.id, label: c2.label, text: c2.text }))
|
|
1606
1691
|
)
|
|
1607
|
-
).search(
|
|
1692
|
+
).search(queryShape.lexical);
|
|
1608
1693
|
const semanticHits = depth === "exact" ? [] : opts.semantic ?? [];
|
|
1609
1694
|
const scoreById = /* @__PURE__ */ new Map();
|
|
1610
1695
|
for (const hit of cardHits) scoreById.set(hit.memory.id, hit.score);
|
|
@@ -1620,14 +1705,26 @@ function recall(entries, query, opts = {}) {
|
|
|
1620
1705
|
const memory = memoryById.get(id);
|
|
1621
1706
|
return memory === void 0 ? [] : [{ memory, score }];
|
|
1622
1707
|
}).sort((a, b) => b.score !== a.score ? b.score - a.score : a.memory.id < b.memory.id ? -1 : 1);
|
|
1623
|
-
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);
|
|
1624
1716
|
const scored = hits.map((hit) => ({
|
|
1625
1717
|
memory: hit.memory,
|
|
1626
1718
|
currency: CURRENCY_RANK[freshnessOf(hit.memory, claimsById.get(hit.memory.id) ?? [], now)],
|
|
1627
|
-
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)
|
|
1628
1725
|
}));
|
|
1629
1726
|
const ranked = scored.sort(
|
|
1630
|
-
(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
|
|
1631
1728
|
);
|
|
1632
1729
|
const selected = ranked.slice(0, limit).map((s) => s.memory);
|
|
1633
1730
|
const byId = new Map(allMemories.map((m) => [m.id, m]));
|
|
@@ -1712,7 +1809,7 @@ function depthOf(value) {
|
|
|
1712
1809
|
function boostFor(memory) {
|
|
1713
1810
|
return (memory.binding === true ? BINDING_BOOST : 0) + (memory.kind === void 0 ? 0 : KIND_BOOST[memory.kind] ?? 0);
|
|
1714
1811
|
}
|
|
1715
|
-
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;
|
|
1716
1813
|
var init_planner = __esm({
|
|
1717
1814
|
"../../packages/memory-engine/src/recall/planner.ts"() {
|
|
1718
1815
|
"use strict";
|
|
@@ -1742,6 +1839,60 @@ var init_planner = __esm({
|
|
|
1742
1839
|
decision: BINDING_BOOST / 2,
|
|
1743
1840
|
preference: BINDING_BOOST / 2
|
|
1744
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
|
+
]);
|
|
1745
1896
|
CURRENCY_RANK = {
|
|
1746
1897
|
current: 0,
|
|
1747
1898
|
expired: 1,
|
|
@@ -1984,18 +2135,21 @@ function detectConsolidation(entries, opts = {}) {
|
|
|
1984
2135
|
scanned: 0,
|
|
1985
2136
|
truncated: false
|
|
1986
2137
|
};
|
|
1987
|
-
const
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
//
|
|
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),
|
|
1993
2146
|
...declaredContradictions(live),
|
|
1994
|
-
...kindPromotions(live)
|
|
2147
|
+
...kindPromotions(live),
|
|
2148
|
+
...semantic,
|
|
2149
|
+
...context,
|
|
2150
|
+
...titles
|
|
1995
2151
|
];
|
|
1996
|
-
out
|
|
1997
|
-
(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 ?? "")
|
|
1998
|
-
);
|
|
2152
|
+
const out = rankCandidates(scored);
|
|
1999
2153
|
return {
|
|
2000
2154
|
candidates: out.slice(0, opts.limit ?? DEFAULT_LIMIT),
|
|
2001
2155
|
scanned: budget.scanned,
|
|
@@ -2017,24 +2171,63 @@ function overlap(a, b) {
|
|
|
2017
2171
|
}
|
|
2018
2172
|
function similarTitles(entries, budget) {
|
|
2019
2173
|
const termSets = entries.map((e) => titleTerms(e.memory.title));
|
|
2174
|
+
const cyclicKeys = entries.map((entry) => cyclicTitleKey(entry.memory.title));
|
|
2020
2175
|
const buckets = /* @__PURE__ */ new Map();
|
|
2176
|
+
const byCyclicTitle = /* @__PURE__ */ new Map();
|
|
2021
2177
|
termSets.forEach((set, i) => {
|
|
2178
|
+
const cyclicKey = cyclicKeys[i];
|
|
2179
|
+
if (cyclicKey !== null && cyclicKey !== void 0) addToBucket(byCyclicTitle, cyclicKey, i);
|
|
2022
2180
|
for (const term of set) {
|
|
2023
2181
|
const bucket = buckets.get(term);
|
|
2024
2182
|
if (bucket) bucket.push(i);
|
|
2025
2183
|
else buckets.set(term, [i]);
|
|
2026
2184
|
}
|
|
2027
2185
|
});
|
|
2028
|
-
const out =
|
|
2186
|
+
const out = cyclicTitleSpanningCandidates(entries, byCyclicTitle);
|
|
2029
2187
|
for (const [i, j] of pairsFrom(buckets, budget)) {
|
|
2030
2188
|
const score = overlap(termSets[i], termSets[j]);
|
|
2031
2189
|
if (score < TITLE_OVERLAP) continue;
|
|
2032
|
-
out.push(
|
|
2033
|
-
pairCandidate("similar_title", entries[i], entries[j], {
|
|
2190
|
+
out.push({
|
|
2191
|
+
candidate: pairCandidate("similar_title", entries[i], entries[j], {
|
|
2034
2192
|
evidence: `titles overlap ${Math.round(score * 100)}%: "${entries[i].memory.title}" / "${entries[j].memory.title}"`,
|
|
2035
2193
|
suggested_action: "supersede"
|
|
2036
|
-
})
|
|
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)
|
|
2037
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
|
+
}
|
|
2038
2231
|
}
|
|
2039
2232
|
return out;
|
|
2040
2233
|
}
|
|
@@ -2064,12 +2257,221 @@ function sharedContext(entries, budget) {
|
|
|
2064
2257
|
}
|
|
2065
2258
|
for (const [i, j] of pairsFrom(byEntity, budget)) {
|
|
2066
2259
|
const shared = [...entitySets.get(i)].filter((e) => entitySets.get(j).has(e));
|
|
2067
|
-
out.push(
|
|
2068
|
-
pairCandidate("shared_context", entries[i], entries[j], {
|
|
2260
|
+
out.push({
|
|
2261
|
+
candidate: pairCandidate("shared_context", entries[i], entries[j], {
|
|
2069
2262
|
evidence: `both in scope "${scope}" and both about: ${shared.sort().join(", ")}`,
|
|
2070
2263
|
suggested_action: "supersede"
|
|
2071
|
-
})
|
|
2072
|
-
|
|
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));
|
|
2073
2475
|
}
|
|
2074
2476
|
}
|
|
2075
2477
|
return out;
|
|
@@ -2083,15 +2485,16 @@ function declaredContradictions(entries) {
|
|
|
2083
2485
|
if (relation.type !== "contradicts") continue;
|
|
2084
2486
|
const other = byId.get(relation.to);
|
|
2085
2487
|
if (!other || other.memory.id === entry.memory.id) continue;
|
|
2086
|
-
const key2 =
|
|
2488
|
+
const key2 = memoryPairKey(entry.memory.id, other.memory.id);
|
|
2087
2489
|
if (seen.has(key2)) continue;
|
|
2088
2490
|
seen.add(key2);
|
|
2089
|
-
out.push(
|
|
2090
|
-
pairCandidate("contradiction", entry, other, {
|
|
2491
|
+
out.push({
|
|
2492
|
+
candidate: pairCandidate("contradiction", entry, other, {
|
|
2091
2493
|
evidence: `"${entry.memory.title}" declares it contradicts "${other.memory.title}" \u2014 recorded, never settled`,
|
|
2092
2494
|
suggested_action: "supersede"
|
|
2093
|
-
})
|
|
2094
|
-
|
|
2495
|
+
}),
|
|
2496
|
+
confidence: 5
|
|
2497
|
+
});
|
|
2095
2498
|
}
|
|
2096
2499
|
}
|
|
2097
2500
|
return out;
|
|
@@ -2102,20 +2505,88 @@ function kindPromotions(entries) {
|
|
|
2102
2505
|
if (entry.memory.binding !== true) continue;
|
|
2103
2506
|
if (entry.memory.kind === "decision") continue;
|
|
2104
2507
|
out.push({
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
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
|
|
2115
2521
|
});
|
|
2116
2522
|
}
|
|
2117
2523
|
return out;
|
|
2118
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
|
+
}
|
|
2119
2590
|
function pairCandidate(rule, a, b, rest) {
|
|
2120
2591
|
return {
|
|
2121
2592
|
rule,
|
|
@@ -2151,20 +2622,54 @@ function* pairsFrom(buckets, budget) {
|
|
|
2151
2622
|
}
|
|
2152
2623
|
}
|
|
2153
2624
|
}
|
|
2154
|
-
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;
|
|
2155
2626
|
var init_detect = __esm({
|
|
2156
2627
|
"../../packages/memory-engine/src/proposal/detect.ts"() {
|
|
2157
2628
|
"use strict";
|
|
2158
2629
|
init_search();
|
|
2159
2630
|
CONSOLIDATION_RULES = [
|
|
2160
|
-
"
|
|
2161
|
-
"shared_context",
|
|
2631
|
+
"declared_supersession",
|
|
2162
2632
|
"contradiction",
|
|
2633
|
+
"semantic_similarity",
|
|
2634
|
+
"shared_context",
|
|
2635
|
+
"similar_title",
|
|
2163
2636
|
"kind_promotion"
|
|
2164
2637
|
];
|
|
2165
2638
|
DEFAULT_LIMIT = 50;
|
|
2166
2639
|
DEFAULT_PAIR_BUDGET = 2e5;
|
|
2167
|
-
|
|
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
|
+
};
|
|
2168
2673
|
}
|
|
2169
2674
|
});
|
|
2170
2675
|
|
|
@@ -2686,6 +3191,86 @@ function restoreUntouchedLines(original, rendered, changed) {
|
|
|
2686
3191
|
return before.get(key2) ?? line;
|
|
2687
3192
|
}).join("\n");
|
|
2688
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
|
+
}
|
|
2689
3274
|
function renderBlock(fields, existingText) {
|
|
2690
3275
|
let body;
|
|
2691
3276
|
if (existingText === null || existingText.trim() === "") {
|
|
@@ -2693,6 +3278,7 @@ function renderBlock(fields, existingText) {
|
|
|
2693
3278
|
} else {
|
|
2694
3279
|
try {
|
|
2695
3280
|
const doc = parseDocument(existingText);
|
|
3281
|
+
if (doc.errors[0]) throw doc.errors[0];
|
|
2696
3282
|
const changed = /* @__PURE__ */ new Set();
|
|
2697
3283
|
for (const [key2, value] of Object.entries(fields)) {
|
|
2698
3284
|
const current = doc.get(key2);
|
|
@@ -2706,14 +3292,21 @@ function renderBlock(fields, existingText) {
|
|
|
2706
3292
|
}
|
|
2707
3293
|
body = restoreUntouchedLines(
|
|
2708
3294
|
existingText,
|
|
2709
|
-
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$/, ""),
|
|
2710
3306
|
changed
|
|
2711
3307
|
);
|
|
2712
3308
|
} catch (err) {
|
|
2713
|
-
throw
|
|
2714
|
-
`the memgineering block in this note could not be rewritten: ${err.message}`,
|
|
2715
|
-
"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."
|
|
2716
|
-
);
|
|
3309
|
+
throw yamlFieldRefusal(existingText, err, NAMESPACE2);
|
|
2717
3310
|
}
|
|
2718
3311
|
}
|
|
2719
3312
|
return [`${NAMESPACE2}:`, ...body.split("\n").map((l) => l === "" ? l : ` ${l}`)];
|
|
@@ -2743,6 +3336,7 @@ ${block.join("\n")}
|
|
|
2743
3336
|
};
|
|
2744
3337
|
}
|
|
2745
3338
|
const fenceBody = fenceMatch[1] ?? "";
|
|
3339
|
+
assertReadableFrontmatter(fenceBody);
|
|
2746
3340
|
const fenceLines = splitLines(fenceBody);
|
|
2747
3341
|
const span = findBlock(fenceLines);
|
|
2748
3342
|
const existing = readNamespace(noteContent);
|
|
@@ -2908,7 +3502,7 @@ function reversePatch(content, patch) {
|
|
|
2908
3502
|
}
|
|
2909
3503
|
return null;
|
|
2910
3504
|
}
|
|
2911
|
-
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;
|
|
2912
3506
|
var init_patch = __esm({
|
|
2913
3507
|
"../../packages/memory-engine/src/proposal/patch.ts"() {
|
|
2914
3508
|
"use strict";
|
|
@@ -2926,6 +3520,7 @@ var init_patch = __esm({
|
|
|
2926
3520
|
}
|
|
2927
3521
|
};
|
|
2928
3522
|
BLOCK_KEY_RE = /^([^\s#][^:]*?)\s*:/;
|
|
3523
|
+
ANY_INDENTED_KEY_RE = /^(\s*)([^\s#][^:]*?)\s*:/;
|
|
2929
3524
|
}
|
|
2930
3525
|
});
|
|
2931
3526
|
|
|
@@ -4460,11 +5055,13 @@ Nothing was indexed. This is what WOULD be stored:
|
|
|
4460
5055
|
printHuman(
|
|
4461
5056
|
c.gray(` ${oneLine(r.summary ?? "(no summary \u2014 title only)", WIDTH.text)}`)
|
|
4462
5057
|
);
|
|
4463
|
-
printHuman(c.gray(` from ${
|
|
5058
|
+
printHuman(c.gray(` from ${middleLine(r.path, WIDTH.path)}`));
|
|
4464
5059
|
}
|
|
4465
5060
|
printHuman(
|
|
4466
|
-
c.gray(
|
|
4467
|
-
|
|
5061
|
+
c.gray(
|
|
5062
|
+
`
|
|
5063
|
+
${rows.length} note(s). After the person approves this preview, run again with --yes to index them.`
|
|
5064
|
+
)
|
|
4468
5065
|
);
|
|
4469
5066
|
if (refusedWhilePreviewing.length > 0) {
|
|
4470
5067
|
printHuman(
|
|
@@ -8350,6 +8947,9 @@ var cloudRules = (brain, args = {}) => call(brain, "POST", "/rules", {
|
|
|
8350
8947
|
var cloudReindex = (brain, args = {}) => call(brain, "POST", "/reindex", {
|
|
8351
8948
|
...args.rebuild === true ? { rebuild: true } : {}
|
|
8352
8949
|
});
|
|
8950
|
+
function findCloudBrain(brains, named) {
|
|
8951
|
+
return brains.find((brain) => brain.id === named) ?? brains.find((brain) => brain.name === named);
|
|
8952
|
+
}
|
|
8353
8953
|
async function listCloudBrains(token, baseUrl, opts = {}) {
|
|
8354
8954
|
const res = await apiRequest({
|
|
8355
8955
|
method: "GET",
|
|
@@ -8529,13 +9129,13 @@ function isOpaqueId(id) {
|
|
|
8529
9129
|
return id.length === OPAQUE_ID_LENGTH && /^[0-9ABCDEFGHJKMNPQRSTVWXYZ]+$/.test(id);
|
|
8530
9130
|
}
|
|
8531
9131
|
function displayHandle(id, allIds) {
|
|
8532
|
-
if (!isOpaqueId(id)) return
|
|
9132
|
+
if (!isOpaqueId(id)) return oneLineWhole(id);
|
|
8533
9133
|
for (let len = MIN_HANDLE_LENGTH; len < id.length; len++) {
|
|
8534
9134
|
const prefix = id.slice(0, len);
|
|
8535
9135
|
const matches = allIds.filter((other) => other.startsWith(prefix));
|
|
8536
9136
|
if (matches.length === 1) return prefix;
|
|
8537
9137
|
}
|
|
8538
|
-
return
|
|
9138
|
+
return oneLineWhole(id);
|
|
8539
9139
|
}
|
|
8540
9140
|
function matchIds(ref, allIds) {
|
|
8541
9141
|
const exact = allIds.filter((id) => id === ref);
|
|
@@ -8765,14 +9365,15 @@ init_config();
|
|
|
8765
9365
|
init_ui();
|
|
8766
9366
|
init_vault();
|
|
8767
9367
|
async function resolveTarget(opts = {}) {
|
|
9368
|
+
if (opts.brain !== void 0) return namedCloudTarget({ ...opts, brain: opts.brain });
|
|
8768
9369
|
if (opts.local !== true) {
|
|
8769
9370
|
const cloud = await cloudTarget();
|
|
8770
9371
|
if (cloud && cloud.kind === "cloud") {
|
|
8771
9372
|
if (opts.vault !== void 0) {
|
|
8772
9373
|
throw memgError(
|
|
8773
9374
|
"invalid_input",
|
|
8774
|
-
`\`--vault\` names a folder on this machine, and this machine reads the hosted brain \`${oneLine(cloud.name,
|
|
8775
|
-
`Nothing was read from \`${oneLine(opts.vault,
|
|
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.
|
|
8776
9377
|
|
|
8777
9378
|
To read that folder just this once: add \`--local\`
|
|
8778
9379
|
To change which hosted brain this machine reads: \`memgineering use --brain <name>\`
|
|
@@ -8792,6 +9393,49 @@ brain silently reported another.`
|
|
|
8792
9393
|
throw await accountAwareRefusal() ?? err;
|
|
8793
9394
|
}
|
|
8794
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
|
+
}
|
|
8795
9439
|
var isNoVaultLinked = (err) => typeof err === "object" && err !== null && err.code === "no_vault_linked";
|
|
8796
9440
|
async function accountAwareRefusal() {
|
|
8797
9441
|
try {
|
|
@@ -8802,13 +9446,13 @@ async function accountAwareRefusal() {
|
|
|
8802
9446
|
if (!cloud || cloud.api_url !== base) return null;
|
|
8803
9447
|
return memgError(
|
|
8804
9448
|
"not_signed_in",
|
|
8805
|
-
`this machine is signed out of \`${oneLine(cloud.name,
|
|
9449
|
+
`this machine is signed out of \`${oneLine(cloud.name, WIDTH.title)}\``,
|
|
8806
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."
|
|
8807
9451
|
);
|
|
8808
9452
|
}
|
|
8809
9453
|
const brains = await listCloudBrains(credentials.token, base, { retries: 0 });
|
|
8810
9454
|
if (brains.length === 0) return null;
|
|
8811
|
-
const names = brains.map((b) => ` ${oneLine(b.name,
|
|
9455
|
+
const names = brains.map((b) => ` ${oneLine(b.name, WIDTH.title)}`).join("\n");
|
|
8812
9456
|
return memgError(
|
|
8813
9457
|
"no_vault_linked",
|
|
8814
9458
|
"this machine is not pointed at any brain yet",
|
|
@@ -9468,10 +10112,14 @@ async function assembleStage(adapter, entry, detail, sectionName) {
|
|
|
9468
10112
|
init_vault();
|
|
9469
10113
|
init_ui();
|
|
9470
10114
|
function openCommand() {
|
|
9471
|
-
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(
|
|
9472
10119
|
async (ref, opts) => {
|
|
9473
10120
|
const detail = parseDetail(opts.detail, opts.section ? "chunks" : "card");
|
|
9474
10121
|
const target = await resolveTarget({
|
|
10122
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
9475
10123
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
9476
10124
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
9477
10125
|
});
|
|
@@ -10813,9 +11461,11 @@ function consolidateCommand() {
|
|
|
10813
11461
|
[
|
|
10814
11462
|
"",
|
|
10815
11463
|
"What it looks for:",
|
|
10816
|
-
"
|
|
10817
|
-
" shared_context two notes in one scope naming the same thing",
|
|
11464
|
+
" declared_supersession a replaced note that is still active",
|
|
10818
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",
|
|
10819
11469
|
" kind_promotion a note marked as binding but filed as something other",
|
|
10820
11470
|
" than a decision",
|
|
10821
11471
|
"",
|
|
@@ -10844,7 +11494,7 @@ function consolidateCommand() {
|
|
|
10844
11494
|
// Present on every answer, not only the empty one. A full list still
|
|
10845
11495
|
// leaves the reader one step short — the candidates are evidence, and
|
|
10846
11496
|
// the next command is the thing they cannot derive from evidence.
|
|
10847
|
-
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."
|
|
10848
11498
|
},
|
|
10849
11499
|
human: () => {
|
|
10850
11500
|
if (candidates.length === 0) {
|
|
@@ -10871,7 +11521,7 @@ function consolidateCommand() {
|
|
|
10871
11521
|
}
|
|
10872
11522
|
printHuman(
|
|
10873
11523
|
c.gray(
|
|
10874
|
-
'\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.'
|
|
10875
11525
|
)
|
|
10876
11526
|
);
|
|
10877
11527
|
}
|
|
@@ -10886,7 +11536,7 @@ function noteLines(id, title, path) {
|
|
|
10886
11536
|
// Backticked, as it was before the title moved onto the row above it. This
|
|
10887
11537
|
// output is read by agents as markdown, and a path outside code formatting
|
|
10888
11538
|
// is a path they have to guess the extent of.
|
|
10889
|
-
c.gray(` \`${
|
|
11539
|
+
c.gray(` \`${middleLine(path, WIDTH.path)}\``)
|
|
10890
11540
|
];
|
|
10891
11541
|
}
|
|
10892
11542
|
function renderCandidate(candidate) {
|
|
@@ -11532,8 +12182,12 @@ init_index_build();
|
|
|
11532
12182
|
init_vault();
|
|
11533
12183
|
init_ui();
|
|
11534
12184
|
function evidenceCommand() {
|
|
11535
|
-
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) => {
|
|
11536
12189
|
const target = await resolveTarget({
|
|
12190
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
11537
12191
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
11538
12192
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
11539
12193
|
});
|
|
@@ -11542,7 +12196,11 @@ function evidenceCommand() {
|
|
|
11542
12196
|
const report2 = await cloudEvidence(cloud, ref);
|
|
11543
12197
|
printDual({
|
|
11544
12198
|
json: { ...report2, brain: { hosted: true, name: target.name } },
|
|
11545
|
-
human: () => renderEvidence(viewFromCloud(report2), {
|
|
12199
|
+
human: () => renderEvidence(viewFromCloud(report2), {
|
|
12200
|
+
now: /* @__PURE__ */ new Date(),
|
|
12201
|
+
hosted: true,
|
|
12202
|
+
brain: target.name
|
|
12203
|
+
})
|
|
11546
12204
|
});
|
|
11547
12205
|
return;
|
|
11548
12206
|
}
|
|
@@ -11564,7 +12222,8 @@ function evidenceCommand() {
|
|
|
11564
12222
|
});
|
|
11565
12223
|
}
|
|
11566
12224
|
function renderEvidence(report, opts) {
|
|
11567
|
-
|
|
12225
|
+
const brainHeading = opts.brain === void 0 ? "" : ` \u2014 ${oneLine(opts.brain, WIDTH.title)}`;
|
|
12226
|
+
printHuman(`## evidence: ${oneLine(report.title, WIDTH.title)}${brainHeading}
|
|
11568
12227
|
`);
|
|
11569
12228
|
printHuman(
|
|
11570
12229
|
`\`id: ${oneLine(report.id, WIDTH.handle)}\` \xB7 \`${oneLine(report.path, WIDTH.path)}\`
|
|
@@ -11987,8 +12646,12 @@ import { Command as Command12 } from "commander";
|
|
|
11987
12646
|
init_vault();
|
|
11988
12647
|
init_ui();
|
|
11989
12648
|
function logCommand() {
|
|
11990
|
-
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) => {
|
|
11991
12653
|
const target = await resolveTarget({
|
|
12654
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
11992
12655
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
11993
12656
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
11994
12657
|
});
|
|
@@ -12835,7 +13498,10 @@ init_errors();
|
|
|
12835
13498
|
init_vault();
|
|
12836
13499
|
init_ui();
|
|
12837
13500
|
function recallCommand() {
|
|
12838
|
-
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(
|
|
12839
13505
|
"--detail <level>",
|
|
12840
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>`"
|
|
12841
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(
|
|
@@ -12844,10 +13510,13 @@ function recallCommand() {
|
|
|
12844
13510
|
const depth = parseDepth(opts.depth);
|
|
12845
13511
|
const budget = parseBudget(opts.budget) ?? DEFAULT_TOKEN_BUDGET;
|
|
12846
13512
|
const target = await resolveTarget({
|
|
13513
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
12847
13514
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
12848
13515
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
12849
13516
|
});
|
|
13517
|
+
const answeredBy = target.kind === "cloud" ? target.name : null;
|
|
12850
13518
|
let rows;
|
|
13519
|
+
let unsearchedBrains = [];
|
|
12851
13520
|
let isScaffolding;
|
|
12852
13521
|
let reasons;
|
|
12853
13522
|
let pathOf;
|
|
@@ -12875,6 +13544,9 @@ function recallCommand() {
|
|
|
12875
13544
|
budget
|
|
12876
13545
|
});
|
|
12877
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
|
+
}
|
|
12878
13550
|
isScaffolding = () => false;
|
|
12879
13551
|
reasons = /* @__PURE__ */ new Map();
|
|
12880
13552
|
pathOf = () => null;
|
|
@@ -12939,6 +13611,7 @@ function recallCommand() {
|
|
|
12939
13611
|
json: {
|
|
12940
13612
|
query,
|
|
12941
13613
|
detail,
|
|
13614
|
+
...answeredBy === null ? {} : { brain: { hosted: true, name: answeredBy } },
|
|
12942
13615
|
// Echoed only when it was asked for. `detail` above is resolved
|
|
12943
13616
|
// here because this CLI is what assembles the stages, but the depth
|
|
12944
13617
|
// is the searching brain's to default — and on a hosted brain this
|
|
@@ -12971,6 +13644,7 @@ function recallCommand() {
|
|
|
12971
13644
|
...result.truncated === void 0 ? {} : { truncated: result.truncated }
|
|
12972
13645
|
},
|
|
12973
13646
|
human: () => {
|
|
13647
|
+
const brainHeading = answeredBy === null ? "" : ` \u2014 ${oneLine(answeredBy, WIDTH.title)}`;
|
|
12974
13648
|
const reachedSomethingWritten = rows.some(({ card }) => !isScaffolding(card));
|
|
12975
13649
|
const otherScript = reachedSomethingWritten ? null : entries ? unreachableScript(query, entries) : null;
|
|
12976
13650
|
const sayWhy = () => {
|
|
@@ -12994,7 +13668,7 @@ function recallCommand() {
|
|
|
12994
13668
|
);
|
|
12995
13669
|
};
|
|
12996
13670
|
if (rows.length === 0) {
|
|
12997
|
-
printHuman(`## recall: ${query}
|
|
13671
|
+
printHuman(`## recall: ${query}${brainHeading}
|
|
12998
13672
|
`);
|
|
12999
13673
|
printHuman("_Nothing matched._\n");
|
|
13000
13674
|
sayDegraded();
|
|
@@ -13002,14 +13676,38 @@ function recallCommand() {
|
|
|
13002
13676
|
sayWhy();
|
|
13003
13677
|
return;
|
|
13004
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
|
+
}
|
|
13005
13695
|
printHuman(
|
|
13006
13696
|
c.gray(
|
|
13007
|
-
|
|
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." : ".")
|
|
13008
13706
|
)
|
|
13009
13707
|
);
|
|
13010
13708
|
return;
|
|
13011
13709
|
}
|
|
13012
|
-
printHuman(`## recall: ${query} (${rows.length} cards)
|
|
13710
|
+
printHuman(`## recall: ${query}${brainHeading} (${rows.length} cards)
|
|
13013
13711
|
`);
|
|
13014
13712
|
sayDegraded();
|
|
13015
13713
|
if (!reachedSomethingWritten) {
|
|
@@ -13025,7 +13723,22 @@ function recallCommand() {
|
|
|
13025
13723
|
);
|
|
13026
13724
|
for (const [i, { card, handle: handle2, stage }] of rows.entries()) {
|
|
13027
13725
|
const mark = reachedSomethingWritten && isScaffolding(card);
|
|
13028
|
-
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
|
+
);
|
|
13029
13742
|
}
|
|
13030
13743
|
if (result.conflicts.length > 0) {
|
|
13031
13744
|
printHuman(`### \u26A0 contradictions (${result.conflicts.length})
|
|
@@ -13130,7 +13843,7 @@ async function stageFor(adapter, entry, detail) {
|
|
|
13130
13843
|
if (!entry) return {};
|
|
13131
13844
|
return assembleStage(adapter, entry, detail);
|
|
13132
13845
|
}
|
|
13133
|
-
function renderCard(n, card, handle2, detail, stage, glossHere = false, scaffolding = false) {
|
|
13846
|
+
function renderCard(n, card, handle2, detail, stage, glossHere = false, scaffolding = false, qualifyWith) {
|
|
13134
13847
|
const out = [];
|
|
13135
13848
|
const standing = card.freshness === "current" ? "" : ` \xB7 ${card.freshness}`;
|
|
13136
13849
|
const age = Number.isFinite(card.age_days) ? ` \xB7 ${ago2(Math.floor(card.age_days))}` : "";
|
|
@@ -13149,7 +13862,8 @@ function renderCard(n, card, handle2, detail, stage, glossHere = false, scaffold
|
|
|
13149
13862
|
}
|
|
13150
13863
|
if (detail !== "title") {
|
|
13151
13864
|
if (card.summary) out.push("", quoteBlock(card.summary));
|
|
13152
|
-
|
|
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}\``);
|
|
13153
13867
|
}
|
|
13154
13868
|
if (stage.outline && stage.outline.length > 0) {
|
|
13155
13869
|
out.push("", ...stage.outline.map((h) => `- ${h}`));
|
|
@@ -13414,6 +14128,9 @@ init_vault();
|
|
|
13414
14128
|
init_ui();
|
|
13415
14129
|
function resurfaceCommand() {
|
|
13416
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(
|
|
13417
14134
|
"--deadline <ms>",
|
|
13418
14135
|
"give up and print nothing after this long",
|
|
13419
14136
|
(v) => parseInt(v, 10)
|
|
@@ -13423,11 +14140,16 @@ function resurfaceCommand() {
|
|
|
13423
14140
|
await bounded(async () => {
|
|
13424
14141
|
const contextDir = opts.contextDir ?? process.cwd();
|
|
13425
14142
|
const where = await resolveTarget({
|
|
14143
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
13426
14144
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
13427
14145
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
13428
14146
|
});
|
|
13429
14147
|
if (where.kind === "cloud") {
|
|
13430
|
-
await resurfaceHosted(
|
|
14148
|
+
await resurfaceHosted(
|
|
14149
|
+
where,
|
|
14150
|
+
opts.limit,
|
|
14151
|
+
opts.brain === void 0 ? void 0 : where.name
|
|
14152
|
+
);
|
|
13431
14153
|
return;
|
|
13432
14154
|
}
|
|
13433
14155
|
const { brain, unreachableDefault } = await resolveBrain(opts.vault, contextDir);
|
|
@@ -13542,7 +14264,7 @@ ${quoteBlock(entry.memory.summary)}`);
|
|
|
13542
14264
|
}
|
|
13543
14265
|
);
|
|
13544
14266
|
}
|
|
13545
|
-
async function resurfaceHosted(where, limit) {
|
|
14267
|
+
async function resurfaceHosted(where, limit, qualifyWith) {
|
|
13546
14268
|
const brain = await openCloudBrain(where);
|
|
13547
14269
|
const result = await cloudResurface(brain, { limit });
|
|
13548
14270
|
printDual({
|
|
@@ -13570,9 +14292,13 @@ async function resurfaceHosted(where, limit) {
|
|
|
13570
14292
|
else if (card.why.length > 0) printHuman(oneLine(card.why.join(" \xB7 "), WIDTH.text));
|
|
13571
14293
|
if (card.summary) printHuman(`
|
|
13572
14294
|
${quoteBlock(card.summary)}`);
|
|
13573
|
-
printHuman(
|
|
14295
|
+
printHuman(
|
|
14296
|
+
qualifyWith === void 0 ? `
|
|
13574
14297
|
\`open: ${oneLine(card.handle, WIDTH.handle)}\`
|
|
13575
|
-
`
|
|
14298
|
+
` : `
|
|
14299
|
+
\`memgineering open ${oneLine(card.handle, WIDTH.handle)} --brain ${JSON.stringify(qualifyWith)}\`
|
|
14300
|
+
`
|
|
14301
|
+
);
|
|
13576
14302
|
}
|
|
13577
14303
|
printBlock(
|
|
13578
14304
|
c.gray(
|
|
@@ -13589,8 +14315,12 @@ exists only for a brain on this machine.`
|
|
|
13589
14315
|
import { Command as Command21 } from "commander";
|
|
13590
14316
|
init_ui();
|
|
13591
14317
|
function rulesCommand() {
|
|
13592
|
-
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) => {
|
|
13593
14322
|
const where = await resolveTarget({
|
|
14323
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
13594
14324
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
13595
14325
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
13596
14326
|
});
|
|
@@ -14252,6 +14982,7 @@ The text from before that change is still on this machine: ${join23(snapshotDir(
|
|
|
14252
14982
|
init_errors();
|
|
14253
14983
|
init_api_client();
|
|
14254
14984
|
import { Command as Command25 } from "commander";
|
|
14985
|
+
import stringWidth from "string-width";
|
|
14255
14986
|
init_config();
|
|
14256
14987
|
init_ui();
|
|
14257
14988
|
var NAME_WIDTH = 48;
|
|
@@ -14305,8 +15036,11 @@ function brainsCommand() {
|
|
|
14305
15036
|
`## ${rows.length} brain${rows.length === 1 ? "" : "s"} on ${credentials.display_name === null ? "this account" : `${oneLine(credentials.display_name, NAME_WIDTH)}\u2019s account`}
|
|
14306
15037
|
`
|
|
14307
15038
|
);
|
|
14308
|
-
|
|
14309
|
-
|
|
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));
|
|
14310
15044
|
const notes = row.note_count === null ? "" : `${String(row.note_count).padStart(6)} note(s)`;
|
|
14311
15045
|
const made = row.created_at === null ? "" : ` made ${row.created_at.slice(0, 10)}`;
|
|
14312
15046
|
printHuman(
|
|
@@ -14596,7 +15330,7 @@ async function pointAtHosted(named, opts, path) {
|
|
|
14596
15330
|
"Nothing was changed. `memgineering push --brain <name>` creates one and fills it from a folder on this machine."
|
|
14597
15331
|
);
|
|
14598
15332
|
}
|
|
14599
|
-
const found =
|
|
15333
|
+
const found = findCloudBrain(brains, named);
|
|
14600
15334
|
if (!found) {
|
|
14601
15335
|
throw memgError(
|
|
14602
15336
|
"invalid_input",
|