@polycode-projects/the-mechanical-code-talker 1.0.9 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ROADMAP.md +2 -1
- package/package.json +1 -1
- package/src/chat.mjs +206 -14
package/ROADMAP.md
CHANGED
|
@@ -20,7 +20,7 @@ Tiers 0, 1, 2, and 4 each closed in one pass. Tier 3 took 7 passes to track down
|
|
|
20
20
|
turned up one genuinely important correctness bug alongside a batch of routing fixes. Full
|
|
21
21
|
per-cycle detail is in `HANDOVER.md`'s "The dialogue-flow playtest loop" section.
|
|
22
22
|
|
|
23
|
-
`npm test` is green at **
|
|
23
|
+
`npm test` is green at **1355** (up from 1258 at the start of this session). v1.0.7 is
|
|
24
24
|
published (0.9.11 → 1.0.0 → 1.0.7 across an earlier session; the exact release chain and
|
|
25
25
|
file:line detail are in `HANDOVER.md`). Nothing has pushed since, so the local version sits at
|
|
26
26
|
1.0.9 per the bump-at-push-time policy.
|
|
@@ -34,6 +34,7 @@ Test count across the session's later stretch:
|
|
|
34
34
|
| Tier 5 (teach + recall + reasoning in dialogue), 5 cycles | 1307 → 1328 |
|
|
35
35
|
| Tier 6 (the messy real user), 5 cycles, run alongside a background test-suite health pass | 1328 → 1345 |
|
|
36
36
|
| Compound-name resolution (multi-word queries to joined-token symbol names) | 1345 → 1352 |
|
|
37
|
+
| Vocabulary-growth mirror fix — known-subject/unknown-object mint (`unknownObjectFallback`), so new terms compound turn over turn | 1352 → 1355 |
|
|
37
38
|
|
|
38
39
|
### Shipped this session
|
|
39
40
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; no codebase index of its own.",
|
package/src/chat.mjs
CHANGED
|
@@ -1474,6 +1474,112 @@ const SOME_A_FEW_RE = /^(some|a few)\s+([\w-]+)\s+are\s+([\w-]+)$/i;
|
|
|
1474
1474
|
* the ambiguity a fully free-form multi-word subject would otherwise have. */
|
|
1475
1475
|
const UNKNOWN_SUBJECT_RE = /^(every\s+|each\s+|all\s+|a\s+|an\s+)?([\w-]+(?:\s+[\w-]+)?)\s+(?:is|are)\s+(?:an?\s+)?([\w-]+)$/i;
|
|
1476
1476
|
|
|
1477
|
+
/** ISA-family predicates (mirrors the private ISA_PREDICATES set defined near
|
|
1478
|
+
* memoryFacts, below, at module scope — both are simple top-level consts
|
|
1479
|
+
* evaluated once at load time, so referencing either from a function defined
|
|
1480
|
+
* earlier in this file is safe: no function here actually RUNS until well
|
|
1481
|
+
* after the whole module has finished loading). Named again here, right by
|
|
1482
|
+
* its one caller, so isGroundedByFact reads standalone. */
|
|
1483
|
+
const MINT_ISA_PREDICATES = new Set(["rdfs:subClassOf", "rdf:type"]);
|
|
1484
|
+
|
|
1485
|
+
/** Small CLOSED set of generic English root nouns that count as always-
|
|
1486
|
+
* grounded anchor terms for the mint-fallbacks below (operator refinement,
|
|
1487
|
+
* 2026-07-09) — deliberately NOT added to lexicon-core.json itself (that
|
|
1488
|
+
* file stays the curated ~180-word CODE vocabulary; these are ordinary-
|
|
1489
|
+
* English root nouns with no code meaning at all, confirmed absent from it
|
|
1490
|
+
* today). Their only job is to give a user who hits the "both sides
|
|
1491
|
+
* ungrounded" decline (groundingSuggestionMiss, below) an honest, guessable
|
|
1492
|
+
* way in: ground one brand-new term via one of THESE words first ("every
|
|
1493
|
+
* zorp is a thing"), then chain the other new term off the now-grounded one. */
|
|
1494
|
+
const GENERIC_ANCHOR_NOUNS = new Set(["thing", "concept", "object", "entity"]);
|
|
1495
|
+
|
|
1496
|
+
/** Shared fact-groundedness primitive (Feature A mint-extension, point 2):
|
|
1497
|
+
* true when `term` already appears as the SUBJECT or OBJECT of a previously
|
|
1498
|
+
* taught isa-family fact (rdfs:subClassOf/rdf:type) in memory. A term minted
|
|
1499
|
+
* by EITHER mint-fallback below (this session, or an earlier one — this
|
|
1500
|
+
* reads persisted memory, not session-scoped state) is exactly as legitimate
|
|
1501
|
+
* an anchor for a NEW fact as a static lexicon-core.json word — the whole
|
|
1502
|
+
* point of this extension is letting new vocabulary compound turn over turn
|
|
1503
|
+
* ("every cache is a store" mints "store"; "every store is a container" then
|
|
1504
|
+
* needs "store" to read as known even though it's not in the static lexicon
|
|
1505
|
+
* at all). Read-only, reuses memoryFacts' plain read path (existence only —
|
|
1506
|
+
* no trust-ranking needed here) and normFactTerm's own normalization, so a
|
|
1507
|
+
* fact-grounded term matches under the EXACT spelling teachFact itself stored
|
|
1508
|
+
* it under. Failure-tolerated: no memory dir / no match → false, never a
|
|
1509
|
+
* guessed "yes". */
|
|
1510
|
+
async function isGroundedByFact(term, memoryDir) {
|
|
1511
|
+
if (!memoryDir) return false;
|
|
1512
|
+
const raw = String(term ?? "").trim();
|
|
1513
|
+
if (!raw) return false;
|
|
1514
|
+
const { normFactTerm } = await import("./memory/core.mjs");
|
|
1515
|
+
const t = normFactTerm(raw);
|
|
1516
|
+
if (!t) return false;
|
|
1517
|
+
// TAUGHT-only (same discipline factReadBack's own cax-sco/scm-sco proof
|
|
1518
|
+
// chase already uses, above, for the identical reason: the bulk background
|
|
1519
|
+
// corpus band (ConceptNet, trust 0.7, seeded by the thousands on a fresh
|
|
1520
|
+
// repo) mentions ordinary English words like "store"/"container" constantly
|
|
1521
|
+
// — treating THOSE as "grounded" would silently reopen the general lexicon
|
|
1522
|
+
// bypass this whole feature is deliberately narrow to avoid. Only what the
|
|
1523
|
+
// OPERATOR actually taught (or a prior `tmct syllogise` entailment) anchors
|
|
1524
|
+
// a term here. factRows (not memoryFacts) is used specifically because it's
|
|
1525
|
+
// the one read path that carries sourceTypes for this filter.
|
|
1526
|
+
const rows = await factRows(memoryDir);
|
|
1527
|
+
const isTaught = (f) => !f.sourceTypes?.includes("corpus") && !f.sourceTypes?.includes("web");
|
|
1528
|
+
return rows.some((f) => MINT_ISA_PREDICATES.has(f.predicate) && isTaught(f) && (f.subject === t || f.object === t));
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/** Shared "is this term grounded in ANY sense" aggregate (Feature A mint-
|
|
1532
|
+
* extension, point 2's named shared helper) — a static lexicon word (any
|
|
1533
|
+
* part of speech, via `classify`), a GENERIC_ANCHOR_NOUNS root, OR a term
|
|
1534
|
+
* already anchored by a previously taught isa-family fact (isGroundedByFact,
|
|
1535
|
+
* above). Used by unknownObjectFallback's subject/object groundedness checks
|
|
1536
|
+
* below, where no part-of-speech branching follows — just "known or not".
|
|
1537
|
+
* (unknownSubjectFallback's own object-known check, above/below, stays
|
|
1538
|
+
* narrower and NOUN-specific — see its own comment — so an object that's
|
|
1539
|
+
* merely a known ADJECTIVE doesn't get misrouted into the class/subClassOf
|
|
1540
|
+
* branch instead of the property branch.) */
|
|
1541
|
+
async function isGroundedTerm(term, lex, memoryDir) {
|
|
1542
|
+
const raw = String(term ?? "").trim();
|
|
1543
|
+
if (!raw) return false;
|
|
1544
|
+
if (GENERIC_ANCHOR_NOUNS.has(raw.toLowerCase())) return true;
|
|
1545
|
+
const { classify } = await import("./grammar/lexicon.mjs");
|
|
1546
|
+
if (classify(raw, lex)) return true;
|
|
1547
|
+
return isGroundedByFact(raw, memoryDir);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/** The "both sides ungrounded" grounding NUDGE (operator refinement,
|
|
1551
|
+
* 2026-07-09): reuses teachSuggestion's own "compute a hint, APPEND it to
|
|
1552
|
+
* the existing honest-miss message, never replace/silently guess" pattern
|
|
1553
|
+
* (see its docblock, above, and the "did"/"why" append-style construction in
|
|
1554
|
+
* teachLane's own final decline, below) for a DIFFERENT decline case —
|
|
1555
|
+
* rather than mint a relationship between two brand-new terms (a real
|
|
1556
|
+
* fabrication risk, unknownObjectFallback's own explicit safety guard,
|
|
1557
|
+
* below), teachLane's final honest-miss text gets an EXTRA appended nudge
|
|
1558
|
+
* whenever the declined payload fit the "X is/are Y" shape
|
|
1559
|
+
* (UNKNOWN_SUBJECT_RE) but NEITHER side is grounded — an honest, actionable
|
|
1560
|
+
* way in: ground one side via a GENERIC_ANCHOR_NOUNS root first ("every zorp
|
|
1561
|
+
* is a thing"), then chain the other off the now-grounded term. Deliberately
|
|
1562
|
+
* APPENDED rather than a replacement/short-circuit: a "both sides
|
|
1563
|
+
* ungrounded" is/are sentence with a KNOWN subject on one side (e.g. "module
|
|
1564
|
+
* is banana") never reaches this at all (isGroundedTerm(subject) is true, so
|
|
1565
|
+
* the very first return below fires) — that stays unknownObjectFallback's
|
|
1566
|
+
* own mint territory, entirely unaffected here. Returns "" (message
|
|
1567
|
+
* unchanged) whenever the payload doesn't fit the shape, or at least one
|
|
1568
|
+
* side IS already grounded — a DIFFERENT, more specific reason it declined,
|
|
1569
|
+
* where this nudge would be actively unhelpful noise. */
|
|
1570
|
+
async function ungroundedPairHint(payload, lexicon, memoryDir) {
|
|
1571
|
+
if (!memoryDir) return "";
|
|
1572
|
+
const m = String(payload).trim().match(UNKNOWN_SUBJECT_RE);
|
|
1573
|
+
if (!m) return "";
|
|
1574
|
+
const [, , subjectRaw, objectRaw] = m;
|
|
1575
|
+
const { loadLexicon } = await import("./grammar/lexicon.mjs");
|
|
1576
|
+
const lex = lexicon || loadLexicon();
|
|
1577
|
+
if (await isGroundedTerm(subjectRaw, lex, memoryDir)) return "";
|
|
1578
|
+
if (await isGroundedTerm(objectRaw, lex, memoryDir)) return "";
|
|
1579
|
+
return ` I don't know "${subjectRaw}" or "${objectRaw}" yet. Try grounding one first, e.g. `
|
|
1580
|
+
+ `"every ${subjectRaw} is a thing", then "every ${objectRaw} is a ${subjectRaw}".`;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1477
1583
|
/** The unknown-SUBJECT direct-write fallback (point 1 + point 2's bare-property
|
|
1478
1584
|
* extension): tried ONLY after the real ACE grammar (assertTurn) has already
|
|
1479
1585
|
* had its turn and declined. Declines itself (returns null, never a guess)
|
|
@@ -1485,18 +1591,25 @@ const UNKNOWN_SUBJECT_RE = /^(every\s+|each\s+|all\s+|a\s+|an\s+)?([\w-]+(?:\s+[
|
|
|
1485
1591
|
* - X is actually a KNOWN lexicon word — then the ACE grammar's own miss was
|
|
1486
1592
|
* a real structural/vocabulary problem elsewhere (e.g. Y itself unknown as
|
|
1487
1593
|
* the WRONG part of speech), never silently reinterpreted through this
|
|
1488
|
-
* narrow exception
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1491
|
-
*
|
|
1492
|
-
*
|
|
1493
|
-
*
|
|
1494
|
-
*
|
|
1495
|
-
*
|
|
1496
|
-
*
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1499
|
-
*
|
|
1594
|
+
* narrow exception. (NOTE: this stays a STATIC-lexicon-only check,
|
|
1595
|
+
* deliberately not widened to isGroundedTerm — a subject that's grounded
|
|
1596
|
+
* only via a PRIOR taught fact, not the static lexicon, is precisely the
|
|
1597
|
+
* case unknownObjectFallback, below, owns instead.)
|
|
1598
|
+
* - Y resolves as NEITHER a known noun NOR a known adjective NOR a term
|
|
1599
|
+
* already grounded by a prior taught fact / a GENERIC_ANCHOR_NOUNS root
|
|
1600
|
+
* (Feature A mint-extension, point 2 — a term minted by
|
|
1601
|
+
* unknownObjectFallback, below, reads exactly as known here as any
|
|
1602
|
+
* lexicon word) — the OBJECT must still be a term tmct actually knows;
|
|
1603
|
+
* an unknown Y stays an honest miss (never a guess), exactly like the
|
|
1604
|
+
* pre-existing "monkey is an animal" case.
|
|
1605
|
+
* Y resolving as a NOUN (or fact-/anchor-grounded) writes rdfs:subClassOf
|
|
1606
|
+
* (mirrors the ACE grammar's own subClassOf/typeAssertion pattern); Y
|
|
1607
|
+
* resolving as an ADJECTIVE (and not also a noun) writes mgx:hasProperty
|
|
1608
|
+
* (mirrors the wrapped "remember that X is deprecated" property frame —
|
|
1609
|
+
* reused here for the bare/unwrapped form too, since the free pass is about
|
|
1610
|
+
* the SUBJECT, not about the "remember that" wrapper). Only the "every"
|
|
1611
|
+
* determiner records a quantifier (point 3: "a"/bare/"your" read as one
|
|
1612
|
+
* specific entity, not a class-level generalization). */
|
|
1500
1613
|
async function unknownSubjectFallback(payload, { memoryDir, sessionId, lexicon }) {
|
|
1501
1614
|
if (!memoryDir) return null;
|
|
1502
1615
|
const m = String(payload).trim().match(UNKNOWN_SUBJECT_RE);
|
|
@@ -1507,7 +1620,12 @@ async function unknownSubjectFallback(payload, { memoryDir, sessionId, lexicon }
|
|
|
1507
1620
|
// A known X's own ACE miss is a real miss — never silently reinterpreted here.
|
|
1508
1621
|
if (classify(subjectRaw, lex)) return null;
|
|
1509
1622
|
const quantifier = /^every$/i.test((det || "").trim()) ? "every" : "";
|
|
1510
|
-
|
|
1623
|
+
// Point 2 (mint-extension): a PRIOR turn's minted term, or a
|
|
1624
|
+
// GENERIC_ANCHOR_NOUNS root, grounds Y just as legitimately as a static
|
|
1625
|
+
// lexicon noun — both are always treated as class-level (never property),
|
|
1626
|
+
// consistent with unknownObjectFallback (below) always minting a CLASS.
|
|
1627
|
+
if (lookupNoun(lex, objectRaw) || GENERIC_ANCHOR_NOUNS.has(String(objectRaw).toLowerCase())
|
|
1628
|
+
|| (await isGroundedByFact(objectRaw, memoryDir))) {
|
|
1511
1629
|
return teachFact(memoryDir, sessionId, {
|
|
1512
1630
|
subject: subjectRaw, predicate: SUBCLASS_PREDICATE, object: objectRaw, quantifier,
|
|
1513
1631
|
});
|
|
@@ -1522,6 +1640,67 @@ async function unknownSubjectFallback(payload, { memoryDir, sessionId, lexicon }
|
|
|
1522
1640
|
return null; // Y unknown too — decline honestly, never guess
|
|
1523
1641
|
}
|
|
1524
1642
|
|
|
1643
|
+
/** The unknown-OBJECT mint fallback (Feature A, 2026-07-09 operator-authorized
|
|
1644
|
+
* vocabulary-growth extension): the MIRROR of unknownSubjectFallback, above —
|
|
1645
|
+
* same "X is/are Y" payload shape (UNKNOWN_SUBJECT_RE, reused verbatim,
|
|
1646
|
+
* never a second regex for the identical shape), tried as a SIBLING call
|
|
1647
|
+
* right after unknownSubjectFallback in teachLane (below), but firing on the
|
|
1648
|
+
* OPPOSITE asymmetry: SUBJECT already grounded (a real lexicon-core.json
|
|
1649
|
+
* word of ANY part of speech, a GENERIC_ANCHOR_NOUNS root, OR a term a PRIOR
|
|
1650
|
+
* turn already minted via either fallback — isGroundedTerm, shared with this
|
|
1651
|
+
* check) and OBJECT completely ungrounded. Mints the object as a new
|
|
1652
|
+
* class-level concept (rdfs:subClassOf, same predicate/quantifier machinery
|
|
1653
|
+
* teachFact/unknownSubjectFallback already use) so ordinary conversation can
|
|
1654
|
+
* build up new vocabulary turn over turn: "every cache is a store" (subject
|
|
1655
|
+
* "cache" grounded via the static lexicon) mints "store"; a LATER "every
|
|
1656
|
+
* store is a container" then finds "store" grounded via the fact just
|
|
1657
|
+
* minted (not the static lexicon at all) and mints "container" the same way.
|
|
1658
|
+
*
|
|
1659
|
+
* GATED ON A GENUINE UNIVERSAL QUANTIFIER ("every"/"each"/"all" — never bare/
|
|
1660
|
+
* "a"/"an"/"your"): minting a NEW CLASS-LEVEL CONCEPT is inherently a general
|
|
1661
|
+
* claim about a class, the same "every"/bare distinction unknownSubjectFallback's
|
|
1662
|
+
* own docblock already draws (point 3) between a class generalization and a
|
|
1663
|
+
* claim about ONE specific entity. This is load-bearing, not cosmetic: a bare
|
|
1664
|
+
* "module is banana" (a KNOWN lexicon subject, an unrecognized bare object,
|
|
1665
|
+
* NO determiner at all) is a pinned regression — it must stay a plain honest
|
|
1666
|
+
* miss, never silently minted — and a WRAPPED "remember that X is <adjective>"
|
|
1667
|
+
* (also determiner-less at the subject) must keep falling through to
|
|
1668
|
+
* TEACH_PROPERTY_RE's own, more permissive arbitrary-adjective path
|
|
1669
|
+
* unimpeded. Requiring the determiner keeps this fallback's mint exactly as
|
|
1670
|
+
* narrow as the vocabulary-growth feature actually needs (every required
|
|
1671
|
+
* test case in this feature's own spec phrases the mint sentence with
|
|
1672
|
+
* "every"), without swallowing either of those pre-existing shapes.
|
|
1673
|
+
*
|
|
1674
|
+
* The critical safety guard (operator's own stated worry, mirrored from
|
|
1675
|
+
* unknownSubjectFallback's docblock): this must NEVER silently mint when
|
|
1676
|
+
* BOTH sides are ungrounded ("every zorp is a florp" — two brand-new,
|
|
1677
|
+
* never-seen terms with no relation to each other tmct actually knows) —
|
|
1678
|
+
* declines here (null), and teachLane's own final honest-miss text picks up
|
|
1679
|
+
* an appended grounding NUDGE for exactly this case (ungroundedPairHint,
|
|
1680
|
+
* above) — never a silent guess, never a silent hard-swallowed decline
|
|
1681
|
+
* either. Any OTHER decline (subject ungrounded + object grounded — not this
|
|
1682
|
+
* fallback's asymmetry; or both grounded — already known, nothing to mint;
|
|
1683
|
+
* or no genuine universal quantifier) falls through as a plain null,
|
|
1684
|
+
* letting the ordinary teachLane cascade (property teach, then the generic
|
|
1685
|
+
* honest-miss text) continue unaffected. */
|
|
1686
|
+
async function unknownObjectFallback(payload, { memoryDir, sessionId, lexicon }) {
|
|
1687
|
+
if (!memoryDir) return null;
|
|
1688
|
+
const m = String(payload).trim().match(UNKNOWN_SUBJECT_RE);
|
|
1689
|
+
if (!m) return null;
|
|
1690
|
+
const [, det, subjectRaw, objectRaw] = m;
|
|
1691
|
+
if (!/^(?:every|each|all)$/i.test((det || "").trim())) return null; // class-level mint needs a real universal quantifier
|
|
1692
|
+
const { loadLexicon } = await import("./grammar/lexicon.mjs");
|
|
1693
|
+
const lex = lexicon || loadLexicon();
|
|
1694
|
+
const subjectGrounded = await isGroundedTerm(subjectRaw, lex, memoryDir);
|
|
1695
|
+
if (!subjectGrounded) return null; // ungrounded subject isn't this fallback's asymmetry — never a guessed mint
|
|
1696
|
+
const objectGrounded = await isGroundedTerm(objectRaw, lex, memoryDir);
|
|
1697
|
+
if (objectGrounded) return null; // object already known — nothing to mint
|
|
1698
|
+
const quantifier = /^every$/i.test((det || "").trim()) ? "every" : "";
|
|
1699
|
+
return teachFact(memoryDir, sessionId, {
|
|
1700
|
+
subject: subjectRaw, predicate: SUBCLASS_PREDICATE, object: objectRaw, quantifier,
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1525
1704
|
// ---- BUG 3 (2026-07-09, operator-authorized generalizing — "I don't know
|
|
1526
1705
|
// where that ban came from, overturn it. build it."): general verb-to-
|
|
1527
1706
|
// predicate teaching. "remember tony has a hat" / "remember margo eats ribs"
|
|
@@ -1922,6 +2101,14 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null }) {
|
|
|
1922
2101
|
// the exact narrowing rules (object must still be known, etc.).
|
|
1923
2102
|
const fallback = await unknownSubjectFallback(payload, { memoryDir, sessionId, lexicon });
|
|
1924
2103
|
if (fallback) return fallback;
|
|
2104
|
+
// MIRROR mint fallback (Feature A, 2026-07-09 operator-authorized vocabulary-
|
|
2105
|
+
// growth extension): the known-subject/unknown-object asymmetry — tried
|
|
2106
|
+
// right after the unknown-subject case declines, so a subject the STATIC
|
|
2107
|
+
// lexicon (or a prior taught fact) already grounds can mint a brand-new
|
|
2108
|
+
// object term. See unknownObjectFallback's own docblock for the exact
|
|
2109
|
+
// narrowing rules (the "both sides ungrounded" safety guard, etc.).
|
|
2110
|
+
const objectFallback = await unknownObjectFallback(payload, { memoryDir, sessionId, lexicon });
|
|
2111
|
+
if (objectFallback) return objectFallback;
|
|
1925
2112
|
// PROPERTY teach — "remember/note that <X> is <adjective>": wrapper-REQUIRED
|
|
1926
2113
|
// (a bare "X is deprecated" is never silently reified), and only after the
|
|
1927
2114
|
// ACE grammar declined (unknown words / not the membership shape), so a
|
|
@@ -1980,9 +2167,14 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null }) {
|
|
|
1980
2167
|
+ "I can only teach facts using tmct's own code-vocabulary nouns (like module, class, function…), "
|
|
1981
2168
|
+ "not arbitrary new terms."
|
|
1982
2169
|
: "";
|
|
2170
|
+
// Grounding NUDGE (operator refinement, 2026-07-09): APPENDED, never a
|
|
2171
|
+
// replacement, exactly like "did" above — see ungroundedPairHint's own
|
|
2172
|
+
// docblock for why this is scoped to the "both sides ungrounded, fits the
|
|
2173
|
+
// X is/are Y shape" case only.
|
|
2174
|
+
const groundingHint = await ungroundedPairHint(payload, lexicon, memoryDir);
|
|
1983
2175
|
return {
|
|
1984
2176
|
text: `I couldn't store that —${why} I remember facts in the shape "every X is a Y", where X and Y are `
|
|
1985
|
-
+ `words I know.${did} Type /memory to see what I already remember.`,
|
|
2177
|
+
+ `words I know.${did}${groundingHint} Type /memory to see what I already remember.`,
|
|
1986
2178
|
via: "teach-miss", miss: true,
|
|
1987
2179
|
};
|
|
1988
2180
|
}
|