@polycode-projects/the-mechanical-code-talker 2.11.0 → 2.11.1

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.
@@ -1082,6 +1082,11 @@ const CAPABILITY_PHRASES = [
1082
1082
  // — closed to the same self-referential noun set META_ORIENT_RE uses, so a
1083
1083
  // real module/class named literally "repo"/"codebase" is never at risk.
1084
1084
  /^what(?:'s|s|\s+is) in (?:this|the)\s+(?:app|codebase|repo|repository|project|code)\??$/i,
1085
+ // "can I ask you something random" — an ordinary conversational preamble
1086
+ // asking permission before a real question, not itself a question about
1087
+ // anything nameable. Answered the same as any other vague opener: sure,
1088
+ // here's what I can help with.
1089
+ /^can i ask (?:you\s+)?something(?:\s+random)?\??$/i,
1085
1090
  ];
1086
1091
  /** IDENTITY questions — "who/what are you", by name, in plain or ESL-ish phrasing.
1087
1092
  * Routed to a self-description (identity-self) that works regardless of graph
@@ -1132,6 +1137,25 @@ const AI_IDENTITY_PHRASES = [
1132
1137
  // product path (no-LLM constitution, deterministic offline reasoning), so
1133
1138
  // this is a real "no", not the generic capability listing.
1134
1139
  /^can (?:you|u) (?:browse|access|use|go on|connect to) the internet\??$/i,
1140
+ // "can you look things up on the internet" / "browse the web to check
1141
+ // something" — the same offline-capability question as the entry just
1142
+ // above, worded around "web"/"look up" instead of "browse the internet".
1143
+ // With no closed match, this fell into a code-graph module-name search for
1144
+ // the literal words instead of the plain "no, I'm offline" answer.
1145
+ /^can (?:you|u) look (?:things?\s+)?up on the internet\??$/i,
1146
+ /^can (?:you|u) (?:browse|search|check) the web(?:\s+to\s+check\s+(?:something|this|that))?\??$/i,
1147
+ // "are you like chatgpt or gemini or something" — the comparison phrasing
1148
+ // of the "are you an AI" question, a named model or two followed by a
1149
+ // trailing "or something" hedge.
1150
+ /^(?:are you|r u)\s+like\s+(?:chatgpt|gpt|claude|gemini|llama)(?:\s+or\s+(?:chatgpt|gpt|claude|gemini|llama))*\s+or\s+something\??$/i,
1151
+ // "what model are you, gpt-4 or claude or something else" — the SAME
1152
+ // open-pick question "what model are you built on" already answers, just
1153
+ // without the "built/based/running on" bridge phrase.
1154
+ /^what model are you,?\s*(?:gpt-?\d(?:\.\d)?|chatgpt|claude|gemini|llama)(?:\s+or\s+(?:gpt-?\d(?:\.\d)?|chatgpt|claude|gemini|llama|something\s+else))*\??$/i,
1155
+ // "can you use an LLM to answer this" — asks the identical no-LLM question
1156
+ // as "do you use ai", just with "use an LLM to answer this" as the verb
1157
+ // phrase instead of a bare "use ai".
1158
+ /^can (?:you|u) use (?:an? )?(?:llm|ai|gpt|chatgpt) to answer (?:this|that|it)\??$/i,
1135
1159
  ];
1136
1160
 
1137
1161
  /** META-COMMAND/SESSION questions — a RETURNING USER checking whether a
@@ -1219,10 +1243,25 @@ const FEELINGS_PHRASES = [
1219
1243
  // "keeps growing as new natural phrasings surface" precedent.
1220
1244
  /^how (?:are|r) (?:you|u) doing(?:\s+today)?\??$/i,
1221
1245
  /^what(?:'s|s|\s+is) your (?:favou?rite\s+(?:colou?r|food|movie|book|band|song|number)|name)\??$/i,
1222
- /^do you (?:get|ever get) bored\??$/i,
1246
+ /^do you (?:get|ever get) (?:bored|tired)\??$/i,
1223
1247
  /^what do you do for fun\??$/i,
1224
1248
  /^can you (?:tell|make)\s+(?:me\s+)?(?:a\s+)?jokes?\??$/i,
1225
1249
  /^do you (?:know|know anything|know much)\s+about\s+(?:movies?|sports?|music|tv|television)(?:\s+or\s+(?:movies?|sports?|music|tv|television))?\??$/i,
1250
+ // "do you think dogs are smarter than cats" — a personal-opinion comparison,
1251
+ // same family as the direct personal questions above: closed to a small
1252
+ // comparative-adjective vocabulary, open on the two compared things (the
1253
+ // SAME discipline PHRASING_FRAMES' object captures use elsewhere).
1254
+ /^do you think .+?\s+(?:is|are)\s+(?:smarter|dumber|better|worse|nicer|cooler|cuter|friendlier|stronger|faster)\s+than\s+.+\??$/i,
1255
+ ];
1256
+ /** "can you make up an answer if you don't actually know" — a direct probe of
1257
+ * the product's own headline promise (the honest miss: a query it can't
1258
+ * ground gets a refusal, never a guess). With no closed match this fell to
1259
+ * the plain grammar wall instead of confirming the very thing it asked
1260
+ * about. Same family/placement as FEELINGS_PHRASES just above. */
1261
+ const HONEST_MISS_PHRASES = [
1262
+ /^can (?:you|u) make up an answer if you (?:don'?t|do\s+not) (?:actually\s+|really\s+)?know\??$/i,
1263
+ /^(?:do|would) you (?:ever\s+)?(?:make (?:something|stuff) up|make up an answer)(?:\s+if you (?:don'?t|do\s+not) know)?\??$/i,
1264
+ /^what if you (?:don'?t|do\s+not) know (?:the\s+)?answer\??$/i,
1226
1265
  ];
1227
1266
  /** "whats 2+2" — a bare arithmetic expression, not a code/vocabulary question
1228
1267
  * at all. With no closed-set match of its own, this fell into the SAME
@@ -1235,6 +1274,15 @@ const FEELINGS_PHRASES = [
1235
1274
  * structural answer, never this decline. "+"/"*"/"/" have no such
1236
1275
  * collision in tmct's own vocabulary. */
1237
1276
  const ARITHMETIC_RE = /\d+\s*[+*/]\s*\d+/;
1277
+ /** A bare SQL data-definition/manipulation statement typed at the prompt
1278
+ * ("DROP TABLE users;") — nonsense as a code-graph question, and with no
1279
+ * closed match of its own this fell to the ≤3-word catch-all's identity/
1280
+ * orientation blurb, the same wrong-flavor miss ARITHMETIC_RE exists to
1281
+ * avoid for arithmetic. Each alternative requires the FULL SQL clause shape
1282
+ * (not just the bare leading verb), so an ordinary English imperative
1283
+ * ("update the readme") is never caught here — "table"/"from"/"into"/"set"
1284
+ * are the words that make this unambiguously SQL rather than English. */
1285
+ const SQL_STATEMENT_RE = /^(?:(?:drop|truncate|alter)\s+table|delete\s+from|insert\s+into|update\s+[a-z0-9_.]+\s+set)\s+[a-z0-9_.]+\b/i;
1238
1286
  /** The structural verbs/nouns that mark a near-miss code question (→ keep the
1239
1287
  * precise grammar hint, not the friendly nudge). */
1240
1288
  const STRUCT_WORDS = new Set([
@@ -1428,6 +1476,10 @@ const T_ORIENTATION_EMPTY = "orientation-empty";
1428
1476
  const T_IDENTITY_SELF = "identity-self";
1429
1477
  const T_IDENTITY_NOT_LLM = "identity-not-an-llm";
1430
1478
  const T_IDENTITY_NO_FEELINGS = "identity-no-feelings";
1479
+ /** Confirms the honest-miss promise itself when a user asks about it directly
1480
+ * ("can you make up an answer if you don't know"). Same "works regardless of
1481
+ * graph state" family as the identity answers just above. */
1482
+ const T_IDENTITY_HONEST_MISS = "identity-honest-miss";
1431
1483
  /** THE CONCEPT FORCE (concept.mjs): the three-band answer to a vague "what is a X"
1432
1484
  * that names a known concept WITH instances — {definition}/{examples}/{followups}. */
1433
1485
  const T_CONCEPT = "concept-force";
@@ -1497,6 +1549,13 @@ const BYE = new Set([
1497
1549
  "bye", "goodbye", "quit", "exit", "see ya", "see you", "cya", "later", "farewell",
1498
1550
  "peace", "peace out", "im off", "i'm off", "gtg", "gotta go", "catch you later",
1499
1551
  "farewell then",
1552
+ // "gtg thx" — the SAME "gtg" farewell above, immediately followed by a
1553
+ // thanks word with no delimiter between them (so farewellOrThanksSignal's
1554
+ // comma/semicolon clause split never sees two clauses to work with).
1555
+ // Whole-phrase entry rather than a general "bye word + thanks word, no
1556
+ // delimiter" mechanism — closed and hand-curated, this exact reported
1557
+ // phrasing only.
1558
+ "gtg thx",
1500
1559
  // "good day to you" deliberately does NOT live here: it's a formal-register
1501
1560
  // GREETING, not a farewell. foldedBye is checked before GREET in
1502
1561
  // conversationalTurn, so having it here would silently end the session on
@@ -1514,6 +1573,11 @@ const WHY = new Set([
1514
1573
  const OK_ACK = new Set([
1515
1574
  "ok", "okay", "cool", "aight", "fair enough", "got it", "gotcha", "noted",
1516
1575
  "sounds good", "sure", "cool cool", "right",
1576
+ // "haha ok fair enough" — a laughter beat leading a two-word ack ("fair
1577
+ // enough") that dismissalSignal's own peeling can't reach: it peels
1578
+ // single-WORD fluff off each end, but "fair enough" is itself two words.
1579
+ // Whole-phrase entry, closed and hand-curated, this exact reported phrasing.
1580
+ "haha ok fair enough",
1517
1581
  ]);
1518
1582
  /** Dismissals — "drop it, no question here" beats. Routed to a warm dismissal
1519
1583
  * template, never the identity/orientation blurb (which reads like the tool
@@ -1620,6 +1684,11 @@ const CLOSING_FILLER_CLAUSES = new Set([
1620
1684
  "that's everything for now", "that's all for now",
1621
1685
  "that's everything i needed", "that's all i needed",
1622
1686
  "that's everything for today", "that's all for today",
1687
+ // "thats enough for now" — the same closing-filler shape, worded around
1688
+ // "enough" instead of "everything"/"all", and without the apostrophe a
1689
+ // casual typer routinely drops (conversationalTurn's own `q` never runs an
1690
+ // apostrophe/contraction pass, so both spellings are curated explicitly).
1691
+ "that's enough for now", "thats enough for now",
1623
1692
  ]);
1624
1693
  /** Strip a hedging lead ("i think that's everything for today" → "that's
1625
1694
  * everything for today") so a hedged closing clause still matches the closed
@@ -1876,6 +1945,20 @@ function conversationalTurn(line, ctx) {
1876
1945
  note(ctx.trace, "lane: conversational — identity/feelings (FEELINGS_PHRASES closed set)");
1877
1946
  return mk(t(T_IDENTITY_NO_FEELINGS), { lane: "help" });
1878
1947
  }
1948
+ if (HONEST_MISS_PHRASES.some((re) => re.test(raw))) {
1949
+ note(ctx.trace, "goal: identity — a direct probe of the honest-miss promise itself");
1950
+ note(ctx.trace, "lane: conversational — identity/honest-miss (HONEST_MISS_PHRASES closed set)");
1951
+ return mk(t(T_IDENTITY_HONEST_MISS), { lane: "help" });
1952
+ }
1953
+ if (SQL_STATEMENT_RE.test(raw)) {
1954
+ note(ctx.trace, "goal: nonsense input shaped like a SQL statement — a targeted decline, not the identity blurb");
1955
+ note(ctx.trace, "lane: conversational — SQL-statement decline (SQL_STATEMENT_RE)");
1956
+ return mk(
1957
+ "That reads like a SQL statement, not a question about a code graph or taught facts. "
1958
+ + "Try \"what is a dog\" for vocabulary, or point me at a repo with --repo <path>.",
1959
+ { lane: "help" },
1960
+ );
1961
+ }
1879
1962
  if (ARITHMETIC_RE.test(raw)) {
1880
1963
  note(ctx.trace, "goal: arithmetic — not a code/vocabulary question, an honest decline");
1881
1964
  note(ctx.trace, "lane: conversational — arithmetic decline (ARITHMETIC_RE)");
@@ -2113,7 +2196,7 @@ const WALL_MISS_ANYWHERE_RE = /couldn't parse this as a graph question\. Try:/;
2113
2196
  // bare "X is a Y" declarative the graph parser couldn't handle → route to the
2114
2197
  // assert/memory path; when it can't be stored, say what CAN be remembered
2115
2198
  // instead of the grammar wall or a silent data loss.
2116
- const TEACH_RE = /^(?:please\s+)?(?:i\s+(?:want|wanted)\s+you\s+to\s+|i(?:'d|\s+would)\s+like\s+you\s+to\s+)?(?:remember|note|keep in mind|jot down|for the record|fyi|learn)\b(?:\s+(?:this|that|also))?[:,]?\s*(?:that\s+)?(.+?)[.?!]*$/i;
2199
+ const TEACH_RE = /^(?:please\s+)?(?:i\s+(?:want|wanted)\s+you\s+to\s+|i(?:'d|\s+would)\s+like\s+you\s+to\s+)?(?:remember|note|keep in mind|jot down|for the record|fyi|learn|teach(?:\s+me)?)\b(?:\s+(?:this|that|also))?[:,]?\s*(?:that\s+)?(.+?)[.?!]*$/i;
2117
2200
  // A trailing sentence-final mark ([.!?]*) is tolerated at the very end: an
2118
2201
  // ordinary first turn typed as a full sentence ("every dog is a mammal.")
2119
2202
  // otherwise failed this shape test by one character whenever neither ACE nor
@@ -2862,8 +2945,20 @@ async function unknownSubjectFallback(payload, { memoryDir, sessionId, lexicon }
2862
2945
  const [, det, subjectRaw, verb, objectRaw] = m;
2863
2946
  const { loadLexicon, lookupNoun, lookupAdjective, classify } = await import("../domain/grammar/lexicon.mjs");
2864
2947
  const lex = lexicon || loadLexicon();
2865
- // A known X's own ACE miss is a real miss — never silently reinterpreted here.
2866
- if (classify(subjectRaw, lex)) return null;
2948
+ // A known X's own ACE miss is a real miss — never silently reinterpreted
2949
+ // here. EXCEPT: classify() folds a trailing "-s" the same way resolveNP
2950
+ // does (lexicon.mjs's lookupNoun), so a bare proper name that happens to
2951
+ // end in "s" and collide with an unrelated dictionary noun ("whiskers" ->
2952
+ // "whisker") reads as "already known" under a SINGULAR "is" sentence, where
2953
+ // no plural evidence supports the fold at all (the genuinely-plural "are"
2954
+ // case is handled correctly a few lines down). Refuse only the fold's own
2955
+ // contribution here — an EXACT noun hit (no fold), or any non-noun
2956
+ // classification (a real verb/adjective/proper name/determiner), still
2957
+ // blocks this fallback exactly as before.
2958
+ const subjectClass = classify(subjectRaw, lex);
2959
+ const subjectFoldedNounOnly = subjectClass?.pos === "noun" && !/^are$/i.test(verb)
2960
+ && (lookupNoun(lex, subjectRaw)?.lemma || "").toLowerCase() !== String(subjectRaw).toLowerCase();
2961
+ if (subjectClass && !subjectFoldedNounOnly) return null;
2867
2962
  const quantifier = /^every$/i.test((det || "").trim()) ? "every" : "";
2868
2963
  // Singularize the SUBJECT before storage, but ONLY on a genuinely PLURAL
2869
2964
  // phrasing ("all men ARE mortal", verb "are"). This
@@ -3221,12 +3316,26 @@ function participleObject(raw) {
3221
3316
  /** Does a bare (unwrapped) sentence fit one of the relational teach frames —
3222
3317
  * including its negated twin, read through splitTeachNegation the same way the
3223
3318
  * general-verb and capability frames read theirs? Used only to admit the
3224
- * sentence as a teach payload; the dispatch below re-matches and stores. */
3319
+ * sentence as a teach payload; the dispatch below re-matches and stores.
3320
+ *
3321
+ * Each regex's own subject capture is a bare word/short NP with no pronoun
3322
+ * exclusion of its own (unlike generalVerbTeach's GENERAL_VERB_NOT_A_VERB_RE
3323
+ * check) — this frame used to be reached only once the bare-sentence pronoun
3324
+ * guard (TEACH_PRONOUN_BARE_RE) had already declined, but that guard is
3325
+ * deliberately narrow (copula + a SHORT complement only), so a pronoun
3326
+ * subject with a longer relational complement ("it is closely connected
3327
+ * with hunting" — the ingest pronoun-carry mechanism's own first, expected-
3328
+ * to-fail attempt) reaches here unprotected. isTeachPronoun is the same
3329
+ * closed check every other mint fallback in this lane uses. */
3225
3330
  function matchesRelationalTeachFrame(sentence) {
3226
3331
  const { payload } = splitTeachNegation(String(sentence || "").trim());
3227
- return PARTICIPLE_PREP_TEACH_RE.test(payload)
3228
- || COPULA_NP_PARTICIPLE_TEACH_RE.test(payload)
3229
- || SAME_NOUN_TEACH_RE.test(payload);
3332
+ const pp = payload.match(PARTICIPLE_PREP_TEACH_RE);
3333
+ if (pp) return !isTeachPronoun(pp[1]);
3334
+ const np = payload.match(COPULA_NP_PARTICIPLE_TEACH_RE);
3335
+ if (np) return !isTeachPronoun(np[1]);
3336
+ const same = payload.match(SAME_NOUN_TEACH_RE);
3337
+ if (same) return !isTeachPronoun(same[1]) && !isTeachPronoun(same[2]);
3338
+ return false;
3230
3339
  }
3231
3340
  /** Does a relational-frame sentence name a code-graph entity? "the Router is
3232
3341
  * used by every handler" reads as a passive uses-CLAIM the ask engine verifies
@@ -3343,7 +3452,17 @@ const GENERAL_VERB_ANYWHERE_EXCLUDE_RE = /\b(?:is|are|am|owns|maintains)\b/i;
3343
3452
  * the legit "mentors" as NOUN, so a POS gate would have regressed a real
3344
3453
  * teach; a closed list (templates over general grammar rules) is both more
3345
3454
  * reliable here and, being closed, can never widen recognition the way a
3346
- * probabilistic POS heuristic could. */
3455
+ * probabilistic POS heuristic could.
3456
+ *
3457
+ * The interrogative pronouns (what/who/which/where/when/why/how) join the
3458
+ * list for the same reason: a dropped-copula casual fragment ("k what abt
3459
+ * users.mjs", missing the "is" QUESTION_LEAD_RE/hasMidSentenceInterrogative
3460
+ * both expect) leaves the WH-word sitting in the verb slot exactly like "if"
3461
+ * or "in" above — subject "k", verb "what", object "abt users.mjs" — and
3462
+ * the SAME nonsense -s fold ("k WHATs abt users.mjs") mints it as a fact. A
3463
+ * WH-word is never a genuine general-verb-frame verb, so this is the same
3464
+ * pure narrowing the block above already describes, just closing the one
3465
+ * closed class it left out. */
3347
3466
  const GENERAL_VERB_NOT_A_VERB_RE = new RegExp(
3348
3467
  "^(?:"
3349
3468
  // personal/possessive/demonstrative pronouns + determiners (mirrors, and
@@ -3356,6 +3475,9 @@ const GENERAL_VERB_NOT_A_VERB_RE = new RegExp(
3356
3475
  + "|up|down|off|out|above|below|between|among|against|without|within|along|across|behind|beyond|upon|toward|towards|per"
3357
3476
  // conjunctions/subordinators
3358
3477
  + "|and|but|or|if|because|although|though|while|when|since|unless|until|whether|so|nor|than|as"
3478
+ // interrogative pronouns/adverbs — never a real verb, only ever the
3479
+ // fronted question-word of a copula-dropped fragment
3480
+ + "|what|who|whom|whose|which|where|why|how"
3359
3481
  + ")$",
3360
3482
  "i",
3361
3483
  );
@@ -3911,20 +4033,38 @@ function habitualGroundingHintText(line, habitual) {
3911
4033
  * a demonstrated entity ("that is a bug", pointing at something real) is a
3912
4034
  * much closer call than "every you is a womble".
3913
4035
  *
3914
- * The verb slot matches ANY word, not just the is/are/am copula: a pronoun
4036
+ * The verb slot matches ANY word, not just the is/are/am copula a pronoun
3915
4037
  * is just as invalid a fact subject under a general verb ("remember you has
3916
- * a hat", "remember he eats ribs") as it is under "is" — this is a
3917
- * grammatical category error regardless of the verb — keeping the guard
3918
- * ahead of every teach recognizer (copula AND general-verb alike) the same
3919
- * way it already stood ahead of teachSuggestion/unknownSubjectFallback. */
3920
- // The contracted forms ("i'm new here …") join the bare pronouns: a leading
3921
- // pronoun+copula contraction is exactly as invalid a fact subject, and it
3922
- // slipped past this guard into the general-verb mint as a one-token "i'm".
3923
- const TEACH_PRONOUNS = Object.freeze([
3924
- "you're", "i'm", "it's", "they're", "he's", "she's", "we're",
3925
- "you", "i", "it", "they", "he", "she", "we",
3926
- ]);
3927
- const TEACH_PRONOUN_RE = new RegExp(`^(?:every\\s+|each\\s+|all\\s+|some\\s+|a few\\s+|a\\s+|an\\s+)?(${TEACH_PRONOUNS.join("|")})\\s+\\S+`, "i");
4038
+ * a hat", "remember he eats ribs") as it is under "is" — but ONLY once an
4039
+ * explicit "remember"/"note"/"teach me"-style wrapper (TEACH_RE) already
4040
+ * named this an unambiguous teach attempt (TEACH_PRONOUN_WRAPPED_RE, tried
4041
+ * first when `wrapped` is set). A BARE sentence with no such signal gets the
4042
+ * narrower TEACH_PRONOUN_BARE_RE instead: pronoun + copula + a SHORT
4043
+ * complement (one word, one optional leading article) the exact shape
4044
+ * TEACH_PROPERTY_RE/BARE_DECLARATIVE_RE would themselves recognize from a
4045
+ * legal subject. Without that narrowing, an ordinary opener that merely
4046
+ * STARTS with "I" ("I am new here", "I want to know X") reached this guard
4047
+ * too (teachLane is the LAST lane tried, after every query strategy already
4048
+ * missed) and fired the copula-specific decline over a sentence no frame
4049
+ * would ever have stored regardless of subject — a confusing answer to a
4050
+ * question nobody asked. Those now fall to teachExclusionReason's existing
4051
+ * self-referential exclusion (a standalone "i"/"me" is already a
4052
+ * TEACH_EXCLUDE_META_TOKEN_RE hit) or to generalVerbTeach's own silent
4053
+ * decline (GENERAL_VERB_NOT_A_VERB_RE covers the same closed pronoun set) —
4054
+ * the same clean stand-down a non-pronoun subject already gets. */
4055
+ const TEACH_PRONOUNS_BARE = Object.freeze(["you", "i", "it", "they", "he", "she", "we"]);
4056
+ // The contracted forms ("i'm new here …") bake the copula into one token, so
4057
+ // they get their own branch below rather than a separate copula match.
4058
+ const TEACH_PRONOUNS_CONTRACTED = Object.freeze(["you're", "i'm", "it's", "they're", "he's", "she's", "we're"]);
4059
+ const TEACH_PRONOUNS = Object.freeze([...TEACH_PRONOUNS_CONTRACTED, ...TEACH_PRONOUNS_BARE]);
4060
+ const TEACH_PRONOUN_WRAPPED_RE = new RegExp(`^(?:every\\s+|each\\s+|all\\s+|some\\s+|a few\\s+|a\\s+|an\\s+)?(${TEACH_PRONOUNS.join("|")})\\s+\\S+`, "i");
4061
+ const TEACH_PRONOUN_BARE_RE = new RegExp(
4062
+ "^(?:every\\s+|each\\s+|all\\s+|some\\s+|a few\\s+|a\\s+|an\\s+)?"
4063
+ + `(?:(${TEACH_PRONOUNS_BARE.join("|")})\\s+(?:is|are|am|was|were)`
4064
+ + `|(${TEACH_PRONOUNS_CONTRACTED.join("|")}))`
4065
+ + "\\s+(?:an?\\s+)?[\\w'-]+[.!?]*$",
4066
+ "i",
4067
+ );
3928
4068
  /** The same closed set, read as a whole-word membership test: a pronoun is no
3929
4069
  * more a legal fact subject when a reader LIFTS one out of a prior answer than
3930
4070
  * when a teach frame offers one. */
@@ -4080,7 +4220,7 @@ async function teachExclusionReason(sentence) {
4080
4220
  const unpunctuated = s.replace(/[.!?]+\s*$/, "");
4081
4221
  if (TEACH_EXCLUDE_IMPERATIVE_LEAD_RE.test(s)
4082
4222
  && !RETRACT_FORGET_RE.test(unpunctuated) && !RETRACT_FORGET_LOCATIVE_RE.test(unpunctuated)) return "imperative";
4083
- if (TEACH_EXCLUDE_META_TOKEN_RE.test(s) && !TEACH_PRONOUN_RE.test(s)) return "self-referential";
4223
+ if (TEACH_EXCLUDE_META_TOKEN_RE.test(s) && !TEACH_PRONOUN_BARE_RE.test(s)) return "self-referential";
4084
4224
  return null;
4085
4225
  }
4086
4226
  export { teachExclusionReason };
@@ -4257,10 +4397,10 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
4257
4397
  // wrapped; trailing punctuation stripped the same way the OWNS/SOME_A_FEW
4258
4398
  // lanes below do) before anything else in this function, so a pronoun
4259
4399
  // subject NEVER reaches teachSuggestion's "did you mean" hint or
4260
- // unknownSubjectFallback's direct-write path — see TEACH_PRONOUN_RE's own
4261
- // docblock above for why.
4400
+ // unknownSubjectFallback's direct-write path — see TEACH_PRONOUN_WRAPPED_RE/
4401
+ // TEACH_PRONOUN_BARE_RE's own shared docblock above for why the two differ.
4262
4402
  const pronounSrc = (wrapped ?? raw).replace(/[.!?]+\s*$/, "");
4263
- const pronounMatch = pronounSrc.match(TEACH_PRONOUN_RE);
4403
+ const pronounMatch = pronounSrc.match(wrapped != null ? TEACH_PRONOUN_WRAPPED_RE : TEACH_PRONOUN_BARE_RE);
4264
4404
  // A pronoun-led sentence that's ALSO a mid-sentence question ("it uses
4265
4405
  // which controller as its base") isn't a pronoun-classification problem at
4266
4406
  // all — "it" was never going to be storable either way, so naming the
@@ -4276,7 +4416,7 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
4276
4416
  // declining right here.
4277
4417
  if (pronounMatch && !ACTION_SIGNATURE_TEACH_RE.test(pronounSrc)
4278
4418
  && !(await hasMidSentenceInterrogative(pronounSrc))) {
4279
- const pronoun = pronounMatch[1];
4419
+ const pronoun = pronounMatch[1] || pronounMatch[2];
4280
4420
  return {
4281
4421
  text: `I can't store a fact about "${pronoun}" as a class — pronouns aren't things I can classify. `
4282
4422
  + `I remember facts in the shape "every X is a Y", where X is a specific noun, not a pronoun. `
@@ -4288,10 +4428,14 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
4288
4428
  // NEGATION ("X is not a Y") and RETRACTION ("forget that X is a Y"). Two
4289
4429
  // sentences, two intents, and the split is the point: a negative is a source
4290
4430
  // DISAGREEING, never an instruction to destroy. Each branch documents itself
4291
- // below; both are tried here, right after the pronoun guard, so a pronoun
4292
- // subject ("it is not an animal") still falls to that guard's own decline
4293
- // first (TEACH_PRONOUN_RE matches ANY verb after the pronoun, including
4294
- // "is not"), never reaching this block.
4431
+ // below; both are tried here, right after the pronoun guard. A WRAPPED
4432
+ // pronoun subject ("remember that it is not an animal") still falls to that
4433
+ // guard's own decline first. A BARE one ("it is not an animal") reaches this
4434
+ // block instead, but stays safe either way: retractNotMatch's own "gated on
4435
+ // the positive existing" rule below finds no stored "it ⊑ …" fact to
4436
+ // disagree with (a pronoun was never a legal mint subject anywhere in this
4437
+ // lane), so it falls through to the ordinary cascade unstored, same as
4438
+ // today — just without this guard's more specific wording.
4295
4439
  const retractSrc = (wrapped ?? raw).replace(/[.!?]+\s*$/, "");
4296
4440
  const retractSrcMidQuestion = memoryDir && !QUESTION_LEAD_RE.test(retractSrc)
4297
4441
  ? await hasMidSentenceInterrogative(retractSrc) : false;
@@ -8095,13 +8239,14 @@ const IS_ADJECTIVE_PRONOUN_RE = /^(?:it|this|that)$/i;
8095
8239
  * SUBJECT capture — factReadBack then treats "you"/"you like"/"you secretly
8096
8240
  * chatgpt or" as a literal fact subject and offers to teach a fact ABOUT the
8097
8241
  * pronoun ("remember that you is happy"), exactly the grammatical category
8098
- * error TEACH_PRONOUN_RE (above) was already built to reject
8099
- * on the teach-lane side. Same pronoun set (you|i|they|he|she|we) reused
8100
- * here, checked at the START of the subject capture only (not anchored to
8101
- * the whole capture — a pronoun subject can carry trailing words, "you
8102
- * like"/"you secretly … or", the same way TEACH_PRONOUN_RE's own `\s+\S+`
8103
- * tail allows). "it" is deliberately EXCLUDED from this set, unlike
8104
- * TEACH_PRONOUN_RE IS_ADJECTIVE_PRONOUN_RE (just above) already gives "it"
8242
+ * error TEACH_PRONOUN_WRAPPED_RE/TEACH_PRONOUN_BARE_RE (above) were already
8243
+ * built to reject on the teach-lane side. Same pronoun set (you|i|they|he|
8244
+ * she|we) reused here, checked at the START of the subject capture only
8245
+ * (not anchored to the whole capture — a pronoun subject can carry trailing
8246
+ * words, "you like"/"you secretly … or", the same class of category error
8247
+ * those two regexes reject on the teach-lane side). "it" is deliberately
8248
+ * EXCLUDED from this set, unlike TEACH_PRONOUN_WRAPPED_RE/
8249
+ * TEACH_PRONOUN_BARE_RE — IS_ADJECTIVE_PRONOUN_RE (just above) already gives "it"
8105
8250
  * its own correct, wanted behavior (anaphoric resolution against the
8106
8251
  * session's current FOCUS, "is it deprecated" → resolves off focusLabel),
8107
8252
  * which this guard must not shadow. Every call site below is expected to
@@ -10609,9 +10754,13 @@ async function compareAnswer(query, { graph, config, source }) {
10609
10754
  * "give me a detailed overview of X", wired to src/domain/completions/'s extractive
10610
10755
  * multi-sentence pipeline below. Two closed shapes (DETAILED_HOW_WORKS_RE
10611
10756
  * tried first, more specific); distinct from DESCRIBE_WRAPPER_RE, which
10612
- * neither anchors on "give me"/"explain ... in detail". */
10757
+ * neither anchors on "give me"/"explain ... in detail". The article before
10758
+ * "detailed" and the "of" before "how" are both OPTIONAL: a non-native
10759
+ * speaker's "give me detailed summary how this application works" carries
10760
+ * the identical intent as the fully-articled form, and dropping either word
10761
+ * changes nothing the pipeline downstream reads — same shape, same term. */
10613
10762
  const DETAILED_HOW_WORKS_RE =
10614
- /^(?:(?:can|could|would)\s+you\s+(?:please\s+)?|please\s+)?(?:give\s+me\s+a\s+detailed\s+(?:summary|overview|explanation)\s+of\s+how|explain\s+(?:to\s+me\s+)?in\s+detail\s+how)\s+(.+?)\s+works\s*\??$/i;
10763
+ /^(?:(?:can|could|would)\s+you\s+(?:please\s+)?|please\s+)?(?:give\s+me\s+(?:an?\s+)?detailed\s+(?:summary|overview|explanation)\s+(?:of\s+)?how|explain\s+(?:to\s+me\s+)?in\s+detail\s+how)\s+(.+?)\s+works\s*\??$/i;
10615
10764
 
10616
10765
  const DETAILED_OVERVIEW_RE =
10617
10766
  /^(?:(?:can|could|would)\s+you\s+(?:please\s+)?|please\s+)?give\s+me\s+a\s+detailed\s+(?:overview|summary|explanation)\s+of\s+(.+?)\s*\??$/i;