@polycode-projects/the-mechanical-code-talker 2.11.3 → 2.11.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "2.11.3",
3
+ "version": "2.11.5",
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.",
@@ -140,6 +140,10 @@ const BROWSING_PREAMBLE_RE = /^(?:just\s+(?:poking\s+around|looking\s+around|bro
140
140
  /** Repeated leading hedge adverb before a polite request verb ("maybe
141
141
  * possibly tell me <Q>"). No delimiter required, unlike ACK_PREAMBLE_RE. */
142
142
  const HEDGE_ADVERB_PREAMBLE_RE = /^(?:(?:maybe|possibly|perhaps)\s+)+(.+)$/i;
143
+ /** Leading courtesy marker ("please tell me <Q>", "please, what is a dog").
144
+ * MODAL_WRAPPER_RE only strips a "please" INSIDE its own frame, so a
145
+ * sentence that leads with one never reached any wrapper. */
146
+ const PLEASE_PREAMBLE_RE = /^(?:please[\s,]+)+(.+)$/i;
143
147
  /** A floating "if it's not too much trouble" aside — a mid-sentence
144
148
  * parenthetical, stripped wherever it appears (not start-anchored). */
145
149
  const TROUBLE_ASIDE_RE = /,?\s*if\s+(?:it'?s|it\s+is|that'?s|that\s+is)\s+not\s+too\s+much\s+(?:trouble|bother|hassle)\s*,?\s*/i;
@@ -148,9 +152,14 @@ const MODAL_WRAPPER_RE = /^(?:can|could|would|will)\s+you\s+(?:please\s+)?(.+?)(
148
152
  /** "explain [to me|please]* <Q>" -> "<Q>", gated on an interrogative
149
153
  * remainder so it only unwraps a real WH-question underneath. */
150
154
  const EXPLAIN_WRAPPER_RE = /^explain\s+(?:to\s+me\s+|please\s+)*(.+?)\??$/i;
151
- /** "tell me <Q>" (bare, no "about") -> "<Q>"; "tell me about X" is a
152
- * different, untouched territory (chat.mjs's vagueTouchTermOf). */
153
- const TELL_ME_WRAPPER_RE = /^tell\s+me\s+(.+?)\??$/i;
155
+ /** "tell me [please] <Q>" (bare, no "about") -> "<Q>"; "tell me about X" is a
156
+ * different, untouched territory (chat.mjs's vagueTouchTermOf). The optional
157
+ * infix "please" mirrors MODAL_WRAPPER_RE's own. */
158
+ const TELL_ME_WRAPPER_RE = /^tell\s+me\s+(?:please\s+)?(.+?)\??$/i;
159
+ /** "[would/do you] mind telling me <Q>" -> "<Q>" — the gerund sibling of
160
+ * TELL_ME_WRAPPER_RE (MODAL_WRAPPER_RE peels a leading "would you", leaving
161
+ * the bare "mind telling me" form this also matches directly). */
162
+ const MIND_TELLING_WRAPPER_RE = /^(?:(?:would|do)\s+you\s+)?mind\s+telling\s+(?:me|us)\s+(?:please\s+)?(.+?)\??$/i;
154
163
  /** "do you know <Q>" -> "<Q>", gated on an interrogative remainder — so
155
164
  * "do you know anything about movies" (small-talk, no embedded question)
156
165
  * passes through untouched. */
@@ -213,6 +222,8 @@ export function applyPreambleFrames(text) {
213
222
  if (m) q = m[1].trim();
214
223
  m = q.match(HEDGE_ADVERB_PREAMBLE_RE);
215
224
  if (m) q = m[1].trim();
225
+ m = q.match(PLEASE_PREAMBLE_RE);
226
+ if (m) q = m[1].trim();
216
227
  m = q.match(TOPIC_SWITCH_PREAMBLE_RE);
217
228
  if (m) q = m[1].trim();
218
229
  m = q.match(MODAL_WRAPPER_RE);
@@ -221,6 +232,8 @@ export function applyPreambleFrames(text) {
221
232
  if (m && INTERROGATIVE_LEAD_RE.test(m[1].trim())) q = m[1].trim();
222
233
  m = q.match(TELL_ME_WRAPPER_RE);
223
234
  if (m && INTERROGATIVE_LEAD_RE.test(m[1].trim())) q = m[1].trim();
235
+ m = q.match(MIND_TELLING_WRAPPER_RE);
236
+ if (m && INTERROGATIVE_LEAD_RE.test(m[1].trim())) q = m[1].trim();
224
237
  m = q.match(KNOW_WRAPPER_RE);
225
238
  if (m && INTERROGATIVE_LEAD_RE.test(m[1].trim())) q = m[1].trim();
226
239
  m = q.match(WANT_KNOW_WRAPPER_RE);
@@ -6737,9 +6737,27 @@ const RELATION_WHO_ASK_RE =
6737
6737
  * ([\w-]+) so the 's split is unambiguous. */
6738
6738
  const GENITIVE_WHO_ASK_RE =
6739
6739
  /^(?:who|what)\s+(?:is|are|was|were)\s+([\w-]+(?:\s+[A-Z][\w-]*)?)'s\s+([a-z][\w-]*)[?.!\s]*$/i;
6740
- function matchGenitiveWhoAsk(q) {
6741
- const g = String(q).match(GENITIVE_WHO_ASK_RE);
6742
- return g ? [g[0], g[2], g[1]] : null;
6740
+ /** The fact lane reads the raw surface, so the ask path's contraction table
6741
+ * never reaches these readers — expand just the interrogative lead
6742
+ * ("who's"/"whos"/"what's"/"whats") so both relation-ask surfaces accept it.
6743
+ * "whose" never matches (the trailing "e" fails the boundary). */
6744
+ const WHO_WHAT_LEAD_CONTRACTION_RE = /^(who|what)'?s\s+/i;
6745
+ const expandWhoWhatLead = (q) => String(q).replace(WHO_WHAT_LEAD_CONTRACTION_RE, (full, w) => `${w} is `);
6746
+ /** The apostrophe-less genitive ("who is petes father") — accepted ONLY when
6747
+ * the stripped possessor is already a term some stored fact names, so a
6748
+ * plain plural or a pronoun ("who is his father") can never be mis-split
6749
+ * into a claimed-then-missed relation ask; this reader's own miss text is
6750
+ * definitive, never a fall-through, so the gate must sit at match time. */
6751
+ const GENITIVE_WHO_ASK_BARE_RE =
6752
+ /^(?:who|what)\s+(?:is|are|was|were)\s+([\w-]{2,}?)s\s+([a-z][\w-]*)[?.!\s]*$/i;
6753
+ function matchGenitiveWhoAsk(q, isKnownFactTerm = null) {
6754
+ const expanded = expandWhoWhatLead(q);
6755
+ const g = expanded.match(GENITIVE_WHO_ASK_RE);
6756
+ if (g) return [g[0], g[2], g[1]];
6757
+ if (!isKnownFactTerm) return null;
6758
+ const b = expanded.match(GENITIVE_WHO_ASK_BARE_RE);
6759
+ if (!b) return null;
6760
+ return isKnownFactTerm(b[1]) ? [b[0], b[2], b[1]] : null;
6743
6761
  }
6744
6762
 
6745
6763
  /** "list the descendants of ahab" — the REACHABILITY-SET list query: a
@@ -7184,7 +7202,7 @@ function withDeducedGoal(res, envelope, query) {
7184
7202
  if (!goal && res.generalVerbQuery) goal = TAUGHT_FACT_LOOKUP_GOAL;
7185
7203
  if (!goal) {
7186
7204
  const yesNo = q.match(RELATION_FACT_YESNO_RE);
7187
- const whoAsk = yesNo ? null : (q.match(RELATION_WHO_ASK_RE) || matchGenitiveWhoAsk(q));
7205
+ const whoAsk = yesNo ? null : (expandWhoWhatLead(q).match(RELATION_WHO_ASK_RE) || matchGenitiveWhoAsk(q));
7188
7206
  const role = yesNo ? yesNo[2] : whoAsk ? whoAsk[1] : null;
7189
7207
  if (role && !ISA_IDIOM_ROLE_WORDS.has(role.toLowerCase())) goal = TAUGHT_FACT_LOOKUP_GOAL;
7190
7208
  }
@@ -8624,7 +8642,10 @@ async function factReadBackReaders(memoryDir, query, envelope, miss, graph = nul
8624
8642
  // sharing it with (a0): RELATION_WHO_ASK_RE and RELATION_FACT_YESNO_RE never
8625
8643
  // both match the same query (one starts with "who", the other with
8626
8644
  // "is/are/was/were"), so the two blocks never run in the same call.
8627
- const whoAsk = qHedge.match(RELATION_WHO_ASK_RE) || matchGenitiveWhoAsk(qHedge);
8645
+ const whoAsk = expandWhoWhatLead(qHedge).match(RELATION_WHO_ASK_RE) || matchGenitiveWhoAsk(qHedge, (base) => {
8646
+ const known = factTermVariants(normFactTerm, base);
8647
+ return rows.some((f) => known.has(f.subject) || known.has(f.object));
8648
+ });
8628
8649
  if (whoAsk) {
8629
8650
  const relationName = whoAsk[1].trim().toLowerCase();
8630
8651
  const rawObject = whoAsk[2].trim();
@@ -8684,7 +8705,7 @@ async function factReadBackReaders(memoryDir, query, envelope, miss, graph = nul
8684
8705
  // oddly as "I don't know ANYONE who is the capital…" — the neutral
8685
8706
  // "nothing/anyone" split below matches whichever interrogative word the
8686
8707
  // query actually used.
8687
- const isWhatAsk = /^what\b/i.test(qHedge);
8708
+ const isWhatAsk = /^what\b/i.test(expandWhoWhatLead(qHedge));
8688
8709
  return {
8689
8710
  text: isWhatAsk
8690
8711
  ? `I don't know what the ${relationName} of ${object} is from what you've told me.`