@polycode-projects/the-mechanical-code-talker 1.0.8 → 1.0.9
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/README.md +51 -32
- package/ROADMAP.md +211 -92
- package/package.json +1 -1
- package/src/ask-vocab.mjs +125 -1
- package/src/ask.mjs +510 -17
- package/src/chat.mjs +1010 -64
- package/src/codegraph.mjs +1 -1
- package/src/interpret/normalize.mjs +137 -3
- package/src/interpret/strategies/grammar.mjs +44 -11
- package/src/interpret/strategies/keywords.mjs +15 -1
- package/src/memory/core.mjs +15 -1
- package/src/syllogise.mjs +0 -0
package/src/codegraph.mjs
CHANGED
|
@@ -1408,7 +1408,7 @@ export function renderUntested(graph) {
|
|
|
1408
1408
|
return `${untested.length} source module(s) with no covering test module:\n ${capJoin(untested, COVERAGE_CAP, "\n ")}`;
|
|
1409
1409
|
}
|
|
1410
1410
|
|
|
1411
|
-
const HISTORY_CAP = 15;
|
|
1411
|
+
export const HISTORY_CAP = 15;
|
|
1412
1412
|
|
|
1413
1413
|
/** Recent commits that touched a symbol's module — from `touches` (seon:history).
|
|
1414
1414
|
* Replaces `git log -- <file>`. Commits are listed newest-first (git-log order). */
|
|
@@ -146,7 +146,19 @@ const isListingRemainder = (rest) => {
|
|
|
146
146
|
// splice — "." joins the delimiter class for exactly this reason; a bare
|
|
147
147
|
// greeting alone ("hi.") still can't match since the regex also requires a
|
|
148
148
|
// non-empty remainder AFTER the delimiter.
|
|
149
|
-
|
|
149
|
+
// "yeah nah" (AU/NZ informal opener, §3b, Tier 6 playtest): a soft discourse
|
|
150
|
+
// filler AU/NZ speakers lead a sentence with, distinct from an actual "no" —
|
|
151
|
+
// chat.mjs's own GREET closed set already recognizes the BARE phrase, but a
|
|
152
|
+
// fused lead-in ("yeah nah, what does the router do") had no preamble form at
|
|
153
|
+
// all and fell straight to the raw grammar wall, the exact failure g'day's own
|
|
154
|
+
// fix (just above) closed for that dialect's greeting word.
|
|
155
|
+
// "howdy pardner" (Tier 6 playtest, §3b dialect axis): "howdy" alone already
|
|
156
|
+
// matched, but a VOCATIVE word right after it ("pardner", a US-Western/cowboy
|
|
157
|
+
// register touch) sat between the greeting and its delimiter, where only the
|
|
158
|
+
// literal word "there" was tolerated — "howdy pardner, remember that X" fell
|
|
159
|
+
// straight to the raw grammar wall. A small closed vocative set, same
|
|
160
|
+
// discipline as the greeting word list itself.
|
|
161
|
+
const GREETING_PREAMBLE_RE = /^(?:hi|hiya|hello|hey|yo|howdy|g'?day|yeah\s+nah|good\s+(?:morning|afternoon|evening|day)|greetings|salutations)(?:\s+(?:there|pardner|folks|friend|mate))?\s*[,.—–-]\s*(?:(?:just\s+a\s+)?quick\s+question\s*[,:—–-]?\s*)?(.+)$/i;
|
|
150
162
|
/** Thanks lead-in with a delimiter (+ optional "quick question" bridge), the
|
|
151
163
|
* sibling of GREETING_PREAMBLE_RE for the "thanks" word family (Bug B2, 0.8.2
|
|
152
164
|
* follow-up): "thanks, <Q>" / "thanks so much, <Q>" -> "<Q>". chat.mjs's
|
|
@@ -158,6 +170,45 @@ const GREETING_PREAMBLE_RE = /^(?:hi|hiya|hello|hey|yo|howdy|g'?day|good\s+(?:mo
|
|
|
158
170
|
* non-empty-remainder-REQUIRED discipline as the greeting frame, so a bare
|
|
159
171
|
* "thanks so much" (no delimiter, no question) stays small-talk. */
|
|
160
172
|
const THANKS_PREAMBLE_RE = /^(?:thanks|thank\s+you|many\s+thanks|thx|ty|cheers)(?:\s+(?:so\s+much|a\s+lot|very\s+much|a\s+bunch))?\s*[,—–-]\s*(?:(?:just\s+a\s+)?quick\s+question\s*[,:—–-]?\s*)?(.+)$/i;
|
|
173
|
+
/** An ACKNOWLEDGEMENT lead-in with a delimiter, the sibling of GREETING_PREAMBLE_RE/
|
|
174
|
+
* THANKS_PREAMBLE_RE for the "ok"/"cool" word family (Tier 6 playtest: "ok cool,
|
|
175
|
+
* what about the TaskController" — a drill-down continuation politely
|
|
176
|
+
* acknowledging the PREVIOUS answer before asking the next question). chat.mjs's
|
|
177
|
+
* OK_ACK set already treats a BARE "ok"/"cool"/"sounds good" as small-talk, but a
|
|
178
|
+
* CHAINED lead-in ("ok cool, X") left "cool"/"," debris ahead of the real
|
|
179
|
+
* question — same failure class GREETING/THANKS' own multi-word lead-ins had.
|
|
180
|
+
* The marker group repeats (`+`) so a stack of ack-words peels in one pass ("ok
|
|
181
|
+
* cool" both go), each followed by whitespace/comma; same delimiter- and
|
|
182
|
+
* non-empty-remainder-REQUIRED discipline as the two frames above. */
|
|
183
|
+
// "no worries" (Tier 6 playtest, §3b dialect axis, AU/NZ): a casual "that's
|
|
184
|
+
// fine"/"no problem" opener that also, like the other ack words, sometimes
|
|
185
|
+
// leads straight into the NEXT question rather than standing alone. "aight"
|
|
186
|
+
// (cycle 5, §3b typo/elongation axis): the further-dropped texting-register
|
|
187
|
+
// contraction of "alright", chainable with the others just like "ok cool,".
|
|
188
|
+
const ACK_PREAMBLE_RE = /^(?:(?:ok(?:ay)?|aight|cool|alright|sure|right|fine|great|nice|got it|gotcha|sounds good|no worries|no problem)[\s,]+)+(.+)$/i;
|
|
189
|
+
/** A SELF-ORIENTATION lead-in with a delimiter — "just poking around, what's
|
|
190
|
+
* in this repo", "just browsing, X", "just exploring, X" (Tier 6 playtest,
|
|
191
|
+
* §3: the vague-opener family a genuine first-time stranger types). Same
|
|
192
|
+
* delimiter-required discipline as GREETING/THANKS/ACK_PREAMBLE_RE above —
|
|
193
|
+
* a bare "just poking around" with no question stays small-talk (this file
|
|
194
|
+
* never claims a turn that has no remainder to hand back). */
|
|
195
|
+
const BROWSING_PREAMBLE_RE = /^just\s+(?:poking\s+around|looking\s+around|browsing|exploring|checking\s+(?:this|it)\s+out)\s*[,.—–-]\s*(.+)$/i;
|
|
196
|
+
/** A repeated leading HEDGE ADVERB ("maybe", "possibly", "perhaps") ahead of a
|
|
197
|
+
* polite request verb — the sibling of ACK_PREAMBLE_RE for HEDGING rather than
|
|
198
|
+
* acknowledging (Tier 6 playtest §3's own stacked-politeness example: "could
|
|
199
|
+
* you maybe possibly tell me... what saveStore does"). Unlike ACK_PREAMBLE_RE,
|
|
200
|
+
* no delimiter is required — a hedge adverb modifies the verb it precedes
|
|
201
|
+
* directly ("maybe possibly tell me"), so requiring a comma would miss the
|
|
202
|
+
* common case; unconditional strip, same as the other preamble frames (none
|
|
203
|
+
* of these three words is grammar-owned vocabulary). Deliberately a narrow,
|
|
204
|
+
* closed three-word set. */
|
|
205
|
+
const HEDGE_ADVERB_PREAMBLE_RE = /^(?:(?:maybe|possibly|perhaps)\s+)+(.+)$/i;
|
|
206
|
+
/** A floating "if it's not too much trouble"/"if that's not too much bother"
|
|
207
|
+
* aside — a genuine mid-sentence PARENTHETICAL (unlike every other frame
|
|
208
|
+
* here, this is NOT anchored to the start of the string), stripped wherever
|
|
209
|
+
* it appears, comma-bounded on either side. Tier 6 playtest's own example
|
|
210
|
+
* phrase: "...tell me, if its not too much trouble, what saveStore does". */
|
|
211
|
+
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;
|
|
161
212
|
/** Modal politeness wrapper: "can/could/would/will you [please] <Q>[, please][?]"
|
|
162
213
|
* -> "<Q>". FILLER_WORDS already ate "can you"/"please" as words; this frame
|
|
163
214
|
* removes them as a WRAPPER so the ", please" comma never survives into the
|
|
@@ -176,6 +227,16 @@ const MODAL_WRAPPER_RE = /^(?:can|could|would|will)\s+you\s+(?:please\s+)?(.+?)(
|
|
|
176
227
|
* "explain" elaboration request (WHY set) are matched on the RAW turn text
|
|
177
228
|
* before normalizeQuery ever runs, so neither is touched by this frame. */
|
|
178
229
|
const EXPLAIN_WRAPPER_RE = /^explain\s+(?:to\s+me\s+|please\s+)*(.+?)\??$/i;
|
|
230
|
+
/** "tell me <Q>" (bare, no "about") -> "<Q>" — the sibling of EXPLAIN_WRAPPER_RE
|
|
231
|
+
* for the "tell me" verb family when what follows is already a full WH-question
|
|
232
|
+
* rather than a bare noun. "tell me about X" is vagueTouchTermOf's own separate
|
|
233
|
+
* territory (chat.mjs) and is untouched here: this frame's interrogative-lead
|
|
234
|
+
* gate can only ever fire on a remainder that "about X" never satisfies (it
|
|
235
|
+
* starts with "about", not a WH-word). Found live (Tier 6 playtest): "could you
|
|
236
|
+
* maybe possibly tell me... what saveStore does" left "tell me what saveStore
|
|
237
|
+
* does" needing one more unwrap after HEDGE_ADVERB_PREAMBLE_RE and
|
|
238
|
+
* MODAL_WRAPPER_RE peeled their own layers. */
|
|
239
|
+
const TELL_ME_WRAPPER_RE = /^tell\s+me\s+(.+?)\??$/i;
|
|
179
240
|
/** show/give-me presentation bridge: "show me [the] <thing>". Three-way:
|
|
180
241
|
* a KIND-listing remainder is left untouched (the compositional list grammar
|
|
181
242
|
* owns "show me untested modules"); a remainder carrying a relation verb or an
|
|
@@ -204,21 +265,63 @@ const SHOW_GIVE_ME_RE = /^(?:show|give)\s+me\s+(?:the\s+)?(.+?)\??$/i;
|
|
|
204
265
|
const LEADING_CONNECTIVE_RE = /^(?:and|also|so|then|now|but)\s+(.+)$/i;
|
|
205
266
|
const QUESTION_AUX_LEAD_RE = /^(?:does|do|did|is|are|was|were|has|have|had|can|could|will|would|should)\b/i;
|
|
206
267
|
|
|
268
|
+
/** A TOPIC-SWITCH / self-interruption preamble — "actually never mind, what
|
|
269
|
+
* calls X", "no wait, I meant what calls Y", "hold on, where is Z defined"
|
|
270
|
+
* (Tier 6 playtest, §3: "no wait", mid-conversation topic switches). A real
|
|
271
|
+
* user abandoning whatever they were about to say and asking something else
|
|
272
|
+
* instead — same species as LEADING_CONNECTIVE_RE just above (a discourse
|
|
273
|
+
* marker carrying no query content of its own, gated on a real question
|
|
274
|
+
* following it), just a richer closed marker set than a single connective
|
|
275
|
+
* word, and CHAINABLE ("actually" + "never mind" + "i meant" can all stack —
|
|
276
|
+
* the fixpoint loop below peels one per pass). Deliberately NOT the same
|
|
277
|
+
* mechanism as SELF_CORRECTION_RE (this file, further down): that shape
|
|
278
|
+
* requires an explicit "sorry"/"i mean" marker WITH a mandatory trailing
|
|
279
|
+
* delimiter, modeling a mid-sentence restart of the SAME clause with real
|
|
280
|
+
* text on both sides; this one is a STANDALONE marker at the very start of
|
|
281
|
+
* the turn with nothing meaningful before it, and the delimiter (a comma) is
|
|
282
|
+
* optional — colloquial speech routinely drops it ("no wait i meant …"). The
|
|
283
|
+
* marker group REPEATS (`+`, mirroring ACK_PREAMBLE_RE just above) so a stack
|
|
284
|
+
* of markers peels in ONE pass ("actually" + "never mind," both go) — unlike
|
|
285
|
+
* LEADING_CONNECTIVE_RE's single-word frame, gating on the remainder after
|
|
286
|
+
* only the FIRST marker would reject the strip before later markers ever get
|
|
287
|
+
* a chance to peel (found live: "actually never mind, X" left "never mind,
|
|
288
|
+
* X" as the gated remainder, which itself never looks like a question lead,
|
|
289
|
+
* so the whole frame silently declined). None of these markers is grammar-
|
|
290
|
+
* owned vocabulary (VERB_TO_KIND/ENTITY_TO_TYPE), so — same as GREETING_
|
|
291
|
+
* PREAMBLE_RE/THANKS_PREAMBLE_RE/ACK_PREAMBLE_RE above — the strip is
|
|
292
|
+
* unconditional; no interrogative-lead gate needed. */
|
|
293
|
+
const TOPIC_SWITCH_PREAMBLE_RE =
|
|
294
|
+
/^(?:(?:actually|no\s+wait|wait|hold\s+on|never\s+mind|scratch\s+that|on\s+second\s+thought|i\s+mean(?:t)?)[\s,.]+)+(.+)$/i;
|
|
295
|
+
|
|
207
296
|
/** Apply the closed preamble frames in order (greeting -> modal -> show/give-me),
|
|
208
297
|
* repeated to a small fixpoint so stacked wrappers ("hey, can you show me X
|
|
209
298
|
* please") peel fully. Pure and idempotent; unmatched text passes through. */
|
|
210
299
|
export function applyPreambleFrames(text) {
|
|
211
300
|
let q = String(text || "");
|
|
301
|
+
// The trouble-aside is a mid-sentence parenthetical, not a start-anchored
|
|
302
|
+
// wrapper — stripped unconditionally, once, before the fixpoint loop (it
|
|
303
|
+
// never interacts with the other frames' anchoring).
|
|
304
|
+
q = q.replace(TROUBLE_ASIDE_RE, " ").replace(/\s+/g, " ").trim();
|
|
212
305
|
for (let pass = 0; pass < 3; pass++) {
|
|
213
306
|
const before = q;
|
|
214
307
|
let m = q.match(GREETING_PREAMBLE_RE);
|
|
215
308
|
if (m) q = m[1].trim();
|
|
216
309
|
m = q.match(THANKS_PREAMBLE_RE);
|
|
217
310
|
if (m) q = m[1].trim();
|
|
311
|
+
m = q.match(ACK_PREAMBLE_RE);
|
|
312
|
+
if (m) q = m[1].trim();
|
|
313
|
+
m = q.match(BROWSING_PREAMBLE_RE);
|
|
314
|
+
if (m) q = m[1].trim();
|
|
315
|
+
m = q.match(HEDGE_ADVERB_PREAMBLE_RE);
|
|
316
|
+
if (m) q = m[1].trim();
|
|
317
|
+
m = q.match(TOPIC_SWITCH_PREAMBLE_RE);
|
|
318
|
+
if (m) q = m[1].trim();
|
|
218
319
|
m = q.match(MODAL_WRAPPER_RE);
|
|
219
320
|
if (m) q = m[1].trim();
|
|
220
321
|
m = q.match(EXPLAIN_WRAPPER_RE);
|
|
221
322
|
if (m && INTERROGATIVE_LEAD_RE.test(m[1].trim())) q = m[1].trim();
|
|
323
|
+
m = q.match(TELL_ME_WRAPPER_RE);
|
|
324
|
+
if (m && INTERROGATIVE_LEAD_RE.test(m[1].trim())) q = m[1].trim();
|
|
222
325
|
m = q.match(SHOW_GIVE_ME_RE);
|
|
223
326
|
if (m) {
|
|
224
327
|
const rest = m[1].trim();
|
|
@@ -229,7 +332,22 @@ export function applyPreambleFrames(text) {
|
|
|
229
332
|
m = q.match(LEADING_CONNECTIVE_RE);
|
|
230
333
|
if (m) {
|
|
231
334
|
const rest = m[1].trim();
|
|
232
|
-
|
|
335
|
+
// Tier 6 playtest cycle 5: a leading connective ("so") sandwiched
|
|
336
|
+
// BETWEEN two other discourse markers ("aight cool, so actually wait,
|
|
337
|
+
// what calls X") used to block the WHOLE fixpoint — the remainder right
|
|
338
|
+
// after "so" ("actually wait, X") isn't ITSELF a question yet (it still
|
|
339
|
+
// has its own marker prefix), so the original interrogative-only gate
|
|
340
|
+
// rejected the strip, and TOPIC_SWITCH_PREAMBLE_RE (anchored to start)
|
|
341
|
+
// never got a chance at "actually" while "so " still sat in front of
|
|
342
|
+
// it. Also accepting a remainder that matches one of this file's OWN
|
|
343
|
+
// other closed preamble frames is safe by the same logic those frames
|
|
344
|
+
// already rely on: each is anchored + closed-vocabulary, so a match
|
|
345
|
+
// here guarantees the NEXT pass strips it too, not a guess.
|
|
346
|
+
if (
|
|
347
|
+
INTERROGATIVE_LEAD_RE.test(rest) || QUESTION_AUX_LEAD_RE.test(rest)
|
|
348
|
+
|| TOPIC_SWITCH_PREAMBLE_RE.test(rest) || ACK_PREAMBLE_RE.test(rest)
|
|
349
|
+
|| HEDGE_ADVERB_PREAMBLE_RE.test(rest) || BROWSING_PREAMBLE_RE.test(rest)
|
|
350
|
+
) q = rest;
|
|
233
351
|
}
|
|
234
352
|
if (q === before) break;
|
|
235
353
|
}
|
|
@@ -298,7 +416,23 @@ export function applySubordinationFrames(text) {
|
|
|
298
416
|
// NOT match on a bare dash alone with no marker word: an ordinary em-dash aside
|
|
299
417
|
// ("modules — like Base — that inherit from X") is common prose, not a restart, and
|
|
300
418
|
// treating every dash as a delimiter would be a guess this file's discipline forbids
|
|
301
|
-
// everywhere else.
|
|
419
|
+
// everywhere else.
|
|
420
|
+
//
|
|
421
|
+
// Tier 6 playtest tried making the trailing delimiter optional too (to catch
|
|
422
|
+
// "what calls listTasks -- oh wait, i mean createTask" — no comma after "i
|
|
423
|
+
// mean") and reverted it live: this regex's `.+?` prefix discards EVERYTHING
|
|
424
|
+
// before the marker, which is correct for a FULL-CLAUSE restart (the remainder
|
|
425
|
+
// is a complete new question, verb included, e.g. "which classes inherit from
|
|
426
|
+
// Base") but wrong for an OBJECT-ONLY restart, where the verb clause ("what
|
|
427
|
+
// calls") must survive and only the object swaps. Without the delimiter, this
|
|
428
|
+
// object-only shape reduced to the bare noun "createTask" alone (no verb at
|
|
429
|
+
// all) — a genuine regression from the honest "did you mean listTasks or
|
|
430
|
+
// createTask?" ambiguity nudge the UNCHANGED regex already gives (a real
|
|
431
|
+
// candidate list including the correct answer is an acceptable FLOW under
|
|
432
|
+
// SKILL_CHAT_PLAYTEST.md §0/§2, not a dead end) to a hard wall. Fixing the
|
|
433
|
+
// object-only case properly needs verb-clause-preserving logic this closed,
|
|
434
|
+
// delimiter-anchored frame mechanism isn't shaped for — left as a genuine,
|
|
435
|
+
// narrower ceiling rather than risk widening this proven, tested regex. ----
|
|
302
436
|
const SELF_CORRECTION_RE =
|
|
303
437
|
/^.+?(?:\s*(?:--|—|-)\s*)?\b(?:sorry|i\s+mean)\b\s*(?:--|—|-|,|:)\s*(.+)$/i;
|
|
304
438
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import {
|
|
8
8
|
VERB_TO_KIND, ENTITY_TO_TYPE, MODIFIER_TO_KIND,
|
|
9
9
|
META_MEANING_VERBS, WHERE_MARKERS, MENTION_MARKERS,
|
|
10
|
+
INHERITS_REVERSE_VERBS, stripTrailingScopeFiller,
|
|
10
11
|
} from "../../ask-vocab.mjs";
|
|
11
12
|
import { escapeRegex } from "../normalize.mjs";
|
|
12
13
|
|
|
@@ -20,13 +21,28 @@ const TEMPLATES = [
|
|
|
20
21
|
// does/is/do/did, which the reverse/forward templates below never match (those start with
|
|
21
22
|
// which/what), so precedence between T1 and the rest is structural, not a tie-break guess.
|
|
22
23
|
// "did" joins does/do for the past-tense commit forms ("did commit <sha> touch X").
|
|
24
|
+
// REVERSE VERB SWAP (Seonix Batch 2 Fix 2): this template fixes subject/object by regex
|
|
25
|
+
// capture POSITION, not by the verb's semantic direction — fine for every forward verb
|
|
26
|
+
// ("subclass of", "imports", …), but "is X a superclass of Y" MEANS the reverse of "is X
|
|
27
|
+
// a subclass of Y" (Y inherits from X, not X from Y). INHERITS_REVERSE_VERBS (ask-vocab.mjs)
|
|
28
|
+
// is the closed set of such reverse phrasings; when the matched verb is one of them,
|
|
29
|
+
// subject/object are swapped here, once, at parse time — so downstream evaluation (ask.mjs)
|
|
30
|
+
// sees "is Y a subclass of X" and needs zero changes of its own. (In practice this exact
|
|
31
|
+
// regex only ever matches a does/do/did lead, so "is …" phrasings actually reach the "ask"
|
|
32
|
+
// shape via keywords.mjs's decomposition strategy instead — that strategy applies the same
|
|
33
|
+
// swap for the same reason; this branch is kept for any does/do/did-led phrasing that names
|
|
34
|
+
// a reverse verb, and for structural symmetry with that sibling strategy.)
|
|
23
35
|
{
|
|
24
36
|
name: "ask",
|
|
25
37
|
re: new RegExp(`^(?:does|do|did)\\s+(.+?)\\s+(${VERB_ALT})\\s+(.+?)\\??$`, "i"),
|
|
26
|
-
build: (m) =>
|
|
27
|
-
|
|
28
|
-
kind
|
|
29
|
-
|
|
38
|
+
build: (m) => {
|
|
39
|
+
const verb = m[2].toLowerCase();
|
|
40
|
+
const kind = VERB_TO_KIND[verb];
|
|
41
|
+
let subject = m[1].trim();
|
|
42
|
+
let object = m[3].trim();
|
|
43
|
+
if (INHERITS_REVERSE_VERBS.includes(verb)) [subject, object] = [object, subject];
|
|
44
|
+
return { shape: "ask", entityType: null, modifier: "direct", kind, subject, object };
|
|
45
|
+
},
|
|
30
46
|
},
|
|
31
47
|
// T2 reverse: "which <entity> [<modifier>] <verb> <object>" — the operator's own example shape.
|
|
32
48
|
{
|
|
@@ -63,15 +79,32 @@ const TEMPLATES = [
|
|
|
63
79
|
build: (m) => ({ shape: "meta", entityType: null, modifier: "direct", kind: "meta", object: m[1].trim() }),
|
|
64
80
|
},
|
|
65
81
|
// T5 meta: "what is a/an <term>" — the OTHER worked phrasing ("what is a Commit").
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
82
|
+
// Seonix Batch 2 Fix 1: the indefinite article is now OPTIONAL, but the BARE
|
|
83
|
+
// (no-article) form is restricted to the CLOSED vocabulary ENTITY_TO_TYPE already
|
|
84
|
+
// imported above (function/method/class/module/attribute/variable/change/commit,
|
|
85
|
+
// singular and plural) — build() returns null (same "this template didn't actually
|
|
86
|
+
// match, keep scanning" contract T8/"when" below already relies on — see
|
|
87
|
+
// parseAnchored's own docblock) when the bare form's object isn't one of those
|
|
88
|
+
// closed terms, so the scan falls through exactly as if this template had not
|
|
89
|
+
// matched at all. This keeps "what is a doohickey"/"widget"/"gizmo" (still routed
|
|
90
|
+
// here via the WITH-article, fully unrestricted `(.+?)` path — pinned to resolve as
|
|
91
|
+
// an honest meta miss downstream, ask-combo.test.mjs/chat-readback.test.mjs) working
|
|
92
|
+
// unmodified, while ALSO keeping the two pinned bare-form honest misses intact:
|
|
93
|
+
// "what is the meaning of this codebase" (ask.test.mjs/ask-dual-strategy.test.mjs)
|
|
94
|
+
// and "what is exposed" (ask.test.mjs:840) both have bare objects absent from
|
|
95
|
+
// ENTITY_TO_TYPE, so build() still rejects them and they still fall through to null.
|
|
96
|
+
// Fix 3: stripTrailingScopeFiller (ask-vocab.mjs) trims a curated trailing clause
|
|
97
|
+
// ("what is a Module in this graph" -> "Module") off the object before it's
|
|
98
|
+
// returned, so a scoping tail never corrupts the lookup term either the bare-form
|
|
99
|
+
// check above or downstream resolution/rendering perform.
|
|
71
100
|
{
|
|
72
101
|
name: "meta-whatis",
|
|
73
|
-
re: new RegExp(`^what\\s+(?:is|are)\\s+(?:an?)\\s+(.+?)\\??$`, "i"),
|
|
74
|
-
build: (m) =>
|
|
102
|
+
re: new RegExp(`^what\\s+(?:is|are)\\s+(?:(an?)\\s+)?(.+?)\\??$`, "i"),
|
|
103
|
+
build: (m) => {
|
|
104
|
+
const object = m[2].trim();
|
|
105
|
+
if (!m[1] && !ENTITY_TO_TYPE[object.toLowerCase()]) return null; // bare form: closed-set only
|
|
106
|
+
return { shape: "meta", entityType: null, modifier: "direct", kind: "meta", object: stripTrailingScopeFiller(object) };
|
|
107
|
+
},
|
|
75
108
|
},
|
|
76
109
|
// T6 mention: "where is <term> mentioned/referenced" — the prose/mentions surface
|
|
77
110
|
// (2026-07-02 query families). Tried BEFORE T7: T7's trailing marker is optional,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import {
|
|
9
9
|
VERB_TO_KIND, ENTITY_TO_TYPE, MODIFIER_TO_KIND,
|
|
10
10
|
WHERE_MARKERS, MENTION_MARKERS, PLACEHOLDER_NOUNS, PASSIVE_PARTICIPLE_TO_KIND,
|
|
11
|
+
INHERITS_REVERSE_VERBS,
|
|
11
12
|
} from "../../ask-vocab.mjs";
|
|
12
13
|
import { STOPWORDS } from "../normalize.mjs";
|
|
13
14
|
import { VOCAB_WORDS, eligibleForCanon, fuzzyVocabWord } from "../fuzzy.mjs";
|
|
@@ -227,7 +228,20 @@ export function parseKeywordSpot(text, nlp = null) {
|
|
|
227
228
|
}
|
|
228
229
|
}
|
|
229
230
|
|
|
230
|
-
if (beforeText && afterText)
|
|
231
|
+
if (beforeText && afterText) {
|
|
232
|
+
// REVERSE VERB SWAP (Seonix Batch 2 Fix 2): this decomposition assigns subject/
|
|
233
|
+
// object by POSITION (before the verb / after it), not by the verb's semantic
|
|
234
|
+
// direction — the same structural fact grammar.mjs's T1 "ask" template comment
|
|
235
|
+
// explains. "is X a superclass of Y" (or its bare "superclass" stem — see
|
|
236
|
+
// INHERITS_REVERSE_VERBS's own comment in ask-vocab.mjs) means the REVERSE of
|
|
237
|
+
// "is X a subclass of Y": swap once, here, at parse time, so evaluation always
|
|
238
|
+
// sees the equivalent forward-phrased question.
|
|
239
|
+
const verbPhrase = canonWords.slice(verbHit.start, verbHit.end).join(" ");
|
|
240
|
+
let subject = beforeText;
|
|
241
|
+
let object = afterText;
|
|
242
|
+
if (INHERITS_REVERSE_VERBS.includes(verbPhrase)) [subject, object] = [object, subject];
|
|
243
|
+
return { shape: "ask", entityType: null, modifier: "direct", kind, subject, object };
|
|
244
|
+
}
|
|
231
245
|
if (afterText) return { shape: "reverse", entityType, modifier, kind, object: afterText };
|
|
232
246
|
// forward keeps the spotted entityType ("which modules did commit <sha> touch" is a
|
|
233
247
|
// forward decomposition — subject before the verb — whose asked grain would otherwise
|
package/src/memory/core.mjs
CHANGED
|
@@ -424,12 +424,26 @@ export async function appendUtterances(dir, utterances) {
|
|
|
424
424
|
* spelling and the graph stays queryable: ConceptNet's /c/en/foo_bar, a
|
|
425
425
|
* grammar's tmct:Foo_bar and a bare "Foo bar" all become "foo bar". The
|
|
426
426
|
* PREDICATE is deliberately NOT normalized this way - it is a controlled
|
|
427
|
-
* vocabulary term (rdfs:subClassOf) whose casing is meaningful.
|
|
427
|
+
* vocabulary term (rdfs:subClassOf) whose casing is meaningful.
|
|
428
|
+
*
|
|
429
|
+
* Tier-5 playtest fix (2026-07-09): also strips a leading "the"/"a"/"an" —
|
|
430
|
+
* found live via "remember that THE logger module is deprecated" (teach-side
|
|
431
|
+
* already stripped it before this ran, so storage was unaffected) followed by
|
|
432
|
+
* "what do you know about THE logger module" / "who maintains THE tasks
|
|
433
|
+
* handler" (recall-side queries, which do NOT pre-strip their own captured
|
|
434
|
+
* term before calling this) genuinely missing the just-taught fact — every
|
|
435
|
+
* recall regex in chat.mjs (KNOW_ABOUT_RE, WHO_OWNS_RE, ISA_ASK_RE, …) calls
|
|
436
|
+
* factTermVariants -> normFactTerm on the raw captured term, so fixing it
|
|
437
|
+
* ONCE here closes the gap for all of them instead of patching each site.
|
|
438
|
+
* Safe for storage too (idempotent — an already-stripped subject is
|
|
439
|
+
* unaffected); the article is a determiner, never semantically distinguishing
|
|
440
|
+
* for a code-entity or common-noun term in this domain. */
|
|
428
441
|
export function normFactTerm(t) {
|
|
429
442
|
let s = normText(t);
|
|
430
443
|
s = s.replace(/^\/c\/[a-z]{2,3}\//i, "");
|
|
431
444
|
s = s.replace(/^[a-z][\w.-]*:/i, "");
|
|
432
445
|
s = s.replace(/_/g, " ").replace(/\s+/g, " ").trim();
|
|
446
|
+
s = s.replace(/^(?:the|an?)\s+/i, "");
|
|
433
447
|
return s.toLowerCase();
|
|
434
448
|
}
|
|
435
449
|
|
package/src/syllogise.mjs
CHANGED
|
Binary file
|