@polycode-projects/the-mechanical-code-talker 3.0.10 → 3.1.2
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 +2 -0
- package/package.json +5 -2
- package/src/domain/ask-vocab.mjs +23 -0
- package/src/domain/ask.mjs +63 -13
- package/src/domain/codegraph.mjs +25 -17
- package/src/domain/interpret/normalize.mjs +7 -0
- package/src/domain/paraphrase-ing8.mjs +195 -0
- package/src/domain/real-word-collisions.json +1 -1
- package/src/domain/router/resolver.mjs +2 -0
- package/src/services/adventure-viz.mjs +6 -1
- package/src/services/chat-page-viz.mjs +3 -0
- package/src/services/chat-session.mjs +5 -0
- package/src/services/chat.mjs +183 -32
- package/src/services/code-explorer-viz.mjs +3 -0
- package/src/services/finish.mjs +6 -1
- package/src/services/ingest-viz.mjs +3 -0
- package/src/services/ledger-viz.mjs +3 -0
- package/src/services/plan-viz.mjs +3 -0
- package/src/services/research-viz.mjs +3 -0
- package/src/services/spider-fly-viz.mjs +6 -1
- package/src/services/sprite-catalog-viz.mjs +6 -1
- package/src/surfaces/web/memory-ask-browser.bundle.js +113 -113
- package/src/tools/handlers/kit.mjs +5 -3
- package/src/tools/handlers/tmct-context.mjs +3 -2
- package/src/tools/handlers/tmct-export.mjs +9 -3
- package/src/tools/handlers/tmct-members.mjs +1 -1
- package/src/tools/handlers/tmct-search.mjs +1 -0
- package/src/tools/server.mjs +9 -4
package/src/services/chat.mjs
CHANGED
|
@@ -257,7 +257,12 @@ function renderNarration(trace, { record, detail, fallbackGoal }) {
|
|
|
257
257
|
* renderVerbose) see the exact same text a narrate:false run would have
|
|
258
258
|
* produced; narrate is purely additive to what's PRINTED, never to what's
|
|
259
259
|
* REMEMBERED. No-op (returns `result` unchanged, by reference) when `trace`
|
|
260
|
-
* is null (narrate off) or empty (nothing was traced).
|
|
260
|
+
* is null (narrate off) or empty (nothing was traced).
|
|
261
|
+
*
|
|
262
|
+
* The same trace block also rides the result as its own `narration` field, so
|
|
263
|
+
* an embedding caller reads the answer and the trace apart without splitting
|
|
264
|
+
* the printed text on NARRATE_MARKER. `answer` keeps the glued form it always
|
|
265
|
+
* had — the field is additive, and absent on a turn that traced nothing. */
|
|
261
266
|
function withNarration(result, trace, fallbackGoal) {
|
|
262
267
|
if (!trace || !trace.length) return result;
|
|
263
268
|
const narrative = renderNarration(trace, { record: result.record, detail: result.detail, fallbackGoal });
|
|
@@ -265,7 +270,7 @@ function withNarration(result, trace, fallbackGoal) {
|
|
|
265
270
|
const logLines = Array.isArray(result.logLines)
|
|
266
271
|
? result.logLines.map((l) => (l === result.answer ? answer : l))
|
|
267
272
|
: result.logLines;
|
|
268
|
-
return { ...result, answer, logLines };
|
|
273
|
+
return { ...result, answer, narration: narrative, logLines };
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
/** A short "Goal (inferred): …" line appended (never prepended) after every
|
|
@@ -1111,14 +1116,26 @@ const CAPABILITY_PHRASES = [
|
|
|
1111
1116
|
// anything nameable. Answered the same as any other vague opener: sure,
|
|
1112
1117
|
// here's what I can help with.
|
|
1113
1118
|
/^can i ask (?:you\s+)?something(?:\s+random)?\??$/i,
|
|
1119
|
+
// The same orientation request with the SUBJECT flipped to "I" — "what can I
|
|
1120
|
+
// ask you" rather than "what can you do". Every entry above reads "you" as
|
|
1121
|
+
// the subject, so the flipped phrasings matched nothing and the bare "I"
|
|
1122
|
+
// token went to the code-graph lane as a module name. The optional
|
|
1123
|
+
// about-tail reuses META_ORIENT_RE's own closed self-referential noun list,
|
|
1124
|
+
// so "what questions can I ask about <a real module>" is untouched.
|
|
1125
|
+
/^what can (?:i|we) ask(?:\s+(?:you|u))?(?:\s+about (?:this|the)\s+(?:app|codebase|repo|repository|project|code))?\??$/i,
|
|
1126
|
+
/^what questions can (?:i|we) ask(?:\s+(?:you|u))?(?:\s+about (?:this|the)\s+(?:app|codebase|repo|repository|project|code))?\??$/i,
|
|
1127
|
+
/^what (?:kind|kinds|sort|sorts|type|types) of questions can (?:i|we) ask(?:\s+(?:you|u))?(?:\s+about (?:this|the)\s+(?:app|codebase|repo|repository|project|code))?\??$/i,
|
|
1114
1128
|
];
|
|
1115
1129
|
/** IDENTITY questions — "who/what are you", by name, in plain or ESL-ish phrasing.
|
|
1116
1130
|
* Routed to a self-description (identity-self) that works regardless of graph
|
|
1117
1131
|
* state, never the code-graph deflection. */
|
|
1118
1132
|
const IDENTITY_PHRASES = [
|
|
1119
|
-
|
|
1133
|
+
// The optional adverb ("what EVEN is this thing", "what REALLY is this") is
|
|
1134
|
+
// emphasis on the same question — without it the casual spelling missed the
|
|
1135
|
+
// closed set entirely and the bare "this"/"I" token went to the graph lane.
|
|
1136
|
+
/^who are you\??$/i, /^what (?:even |really |actually )?(is|are|r) (this|you)\??$/i,
|
|
1120
1137
|
/^what('?s| is) your name\??$/i, /^what exactly are you\??$/i,
|
|
1121
|
-
/^(tell me about|introduce) yourself\??$/i, /^what is this thing\??$/i,
|
|
1138
|
+
/^(tell me about|introduce) yourself\??$/i, /^what (?:even |really |actually )?is this thing\??$/i,
|
|
1122
1139
|
/^what am i (talking|speaking|chatting) (to|with)\??$/i,
|
|
1123
1140
|
/^you are what\??$/i, /^what thing (are|is) you\??$/i,
|
|
1124
1141
|
// "explain [to me|please]* what (you are|this is)" in EITHER word order — a
|
|
@@ -1248,6 +1265,34 @@ function aiIdentityMatch(raw) {
|
|
|
1248
1265
|
return splitClauses(text).some((clause) => AI_IDENTITY_PHRASES.some((re) => re.test(clause)));
|
|
1249
1266
|
}
|
|
1250
1267
|
|
|
1268
|
+
/** The self-referential tail a casual identity question trails after a comma
|
|
1269
|
+
* ("what even is this thing, like what does it do?"). It names no term at all
|
|
1270
|
+
* — "it"/"this"/"you" is the thing already asked about in the first clause —
|
|
1271
|
+
* so it adds nothing to answer and only has to be RECOGNIZED, not routed. */
|
|
1272
|
+
const SELF_REFERENTIAL_TAIL_RE = /^(?:like\s+|so\s+|and\s+)?what (?:does|do|can) (?:it|this|you|u) do\??$/i;
|
|
1273
|
+
|
|
1274
|
+
/** IDENTITY_PHRASES matched against the raw turn, its preamble-peeled twin, or
|
|
1275
|
+
* a comma-joined identity clause trailed only by self-referential filler.
|
|
1276
|
+
*
|
|
1277
|
+
* The peeled check mirrors what CAPABILITY_PHRASES' own dispatch already does
|
|
1278
|
+
* ("hello there, what am I talking to?" is an exact identity question behind a
|
|
1279
|
+
* greeting frame, and every entry is anchored, so the frame alone sank it).
|
|
1280
|
+
*
|
|
1281
|
+
* The clause check is deliberately narrower than aiIdentityMatch's: it splits
|
|
1282
|
+
* on commas/"and" (conversationalClauses), which a real graph question can sit
|
|
1283
|
+
* on the far side of, so a match requires the FIRST clause to be an identity
|
|
1284
|
+
* question AND every later clause to be self-referential filler. "what is
|
|
1285
|
+
* this, and which modules import walk.mjs" therefore stays a graph query. */
|
|
1286
|
+
function identityPhraseMatch(raw) {
|
|
1287
|
+
const text = String(raw);
|
|
1288
|
+
const anchored = (s) => IDENTITY_PHRASES.some((re) => re.test(s));
|
|
1289
|
+
if (anchored(text) || anchored(applyPreambleFrames(text))) return true;
|
|
1290
|
+
const clauses = conversationalClauses(applyPreambleFrames(text).trim());
|
|
1291
|
+
if (clauses.length < 2) return false;
|
|
1292
|
+
return anchored(clauses[0].replace(/[?.!]+$/, ""))
|
|
1293
|
+
&& clauses.slice(1).every((c) => SELF_REFERENTIAL_TAIL_RE.test(c));
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1251
1296
|
/** "Do you have feelings/emotions" — with no closed-set match, this would
|
|
1252
1297
|
* otherwise misfire into a literal module-name lookup for the bare noun
|
|
1253
1298
|
* ("no module matching 'feelings' found in the index") — a wrong-flavor
|
|
@@ -2009,7 +2054,7 @@ function conversationalTurn(line, ctx) {
|
|
|
2009
2054
|
{ lane: "help" },
|
|
2010
2055
|
);
|
|
2011
2056
|
}
|
|
2012
|
-
if (
|
|
2057
|
+
if (identityPhraseMatch(raw)) {
|
|
2013
2058
|
note(ctx.trace, "goal: identity — who/what tmct is, not a capability listing");
|
|
2014
2059
|
note(ctx.trace, "lane: conversational — identity (IDENTITY_PHRASES closed set)");
|
|
2015
2060
|
return mk(t(T_IDENTITY_SELF), { lane: "help" });
|
|
@@ -2126,7 +2171,12 @@ const ORIENTATION_EMPTY_FALLBACK = "I'm tmct — a deterministic, offline chat a
|
|
|
2126
2171
|
* branch uses, so there is exactly one copy of that wording to keep in sync, not
|
|
2127
2172
|
* two hand-duplicated strings. */
|
|
2128
2173
|
function orientationText(graph, templates, vocabHint) {
|
|
2129
|
-
|
|
2174
|
+
// A null (never-loaded) graph reads as "unknown, not empty" to noCodeGraph,
|
|
2175
|
+
// which is the right call for the greeting/orientation CARD — but there is no
|
|
2176
|
+
// entity count to render from one either, so the empty wording is the only
|
|
2177
|
+
// truthful thing left to say. Without this the entity tally below threw on a
|
|
2178
|
+
// graph-less runTurn.
|
|
2179
|
+
if (!graph || noCodeGraph(graph)) {
|
|
2130
2180
|
return tRender(templates, T_ORIENTATION_EMPTY, { vocabHint }) ?? ORIENTATION_EMPTY_FALLBACK;
|
|
2131
2181
|
}
|
|
2132
2182
|
const by = (cls) => (graph.individuals || []).filter((i) => (i.class || "") === cls).length;
|
|
@@ -2245,7 +2295,14 @@ const TEACH_RE = /^(?:please\s+)?(?:i\s+(?:want|wanted)\s+you\s+to\s+|i(?:'d|\s+
|
|
|
2245
2295
|
// stayed null) before ever trying the unknown-subject/object mint fallbacks
|
|
2246
2296
|
// below — the SAME sentence typed without the period worked. Mirrors
|
|
2247
2297
|
// UNKNOWN_SUBJECT_RE's own identical tolerance, added for the same reason.
|
|
2248
|
-
|
|
2298
|
+
// This is the gate that decides whether a bare declarative becomes a teach
|
|
2299
|
+
// payload at all, so a complement narrower than UNKNOWN_SUBJECT_RE's own object
|
|
2300
|
+
// capture kept a multi-word class name out of the mint fallbacks downstream no
|
|
2301
|
+
// matter how wide their captures were. A multi-word complement is admitted ONLY
|
|
2302
|
+
// behind an article — "is A unit of work" names a class, while the article-less
|
|
2303
|
+
// "is nice today" / "are fast animals" is ordinary prose that happens to carry a
|
|
2304
|
+
// copula, and admitting THAT grounds filler sentences as facts.
|
|
2305
|
+
const BARE_DECLARATIVE_RE = /^(?:every |each |all |a |an )?[\w-]+(?: [\w-]+)? (?:is|are) (?:(?:a |an )[\w-]+(?: [\w-]+){0,2}|[\w-]+)(?: too)?[.!?]*$/i;
|
|
2249
2306
|
/** "X is <comparative> than Y" — the comparative teach/ask surface. The
|
|
2250
2307
|
* comparative slot is closed by SHAPE (-er word, better/worse, or a
|
|
2251
2308
|
* more/less + adjective pair), never a hand-list of adjectives. */
|
|
@@ -2842,7 +2899,11 @@ export { singularizeSurface };
|
|
|
2842
2899
|
* and unknownObjectFallback's own docblocks) — a proper noun that happens to
|
|
2843
2900
|
* end in "s" ("redis") naively strips to "redi" if singularized on an "is"
|
|
2844
2901
|
* sentence, where no such fold is ever needed.
|
|
2845
|
-
* Y (the object) is
|
|
2902
|
+
* Y (the object) is ONE TO THREE tokens, so a natural multi-word class name
|
|
2903
|
+
* ("every Function is a unit of work") reaches the mint instead of failing
|
|
2904
|
+
* the match outright and falling to the grammar wall; the greedy quantifier
|
|
2905
|
+
* plus the end anchor keep a longer trailing phrase ("rex is a dog in the
|
|
2906
|
+
* garden") rejected exactly as before.
|
|
2846
2907
|
* X (the subject) is ONE OR TWO tokens, to cover a natural 2-word noun
|
|
2847
2908
|
* phrase ("vulcan gizmo is a tool"), the same class of gap OWNS_TEACH_RE's
|
|
2848
2909
|
* own object needed widening for, above. The greedy quantifier tries the
|
|
@@ -2867,7 +2928,7 @@ export { singularizeSurface };
|
|
|
2867
2928
|
* captures themselves are unaffected (`[\w-]+` never included the period in
|
|
2868
2929
|
* the first place), so this only widens WHICH sentences reach the match,
|
|
2869
2930
|
* never what gets captured out of one that already did. */
|
|
2870
|
-
const UNKNOWN_SUBJECT_RE = /^(every\s+|each\s+|all\s+|any\s+|a\s+|an\s+)?([\w-]+(?:\s+[\w-]+)?)\s+(is|are)\s+(?:an?\s+)?([\w-]+)[.!?]*$/i;
|
|
2931
|
+
const UNKNOWN_SUBJECT_RE = /^(every\s+|each\s+|all\s+|any\s+|a\s+|an\s+)?([\w-]+(?:\s+[\w-]+)?)\s+(is|are)\s+(?:an?\s+)?([\w-]+(?:\s+[\w-]+){0,2})[.!?]*$/i;
|
|
2871
2932
|
|
|
2872
2933
|
/** ISA-family predicates (mirrors the private ISA_PREDICATES set defined near
|
|
2873
2934
|
* memoryFacts, below, at module scope — both are simple top-level consts
|
|
@@ -3124,6 +3185,12 @@ async function unknownSubjectFallback(payload, { memoryDir, sessionId, lexicon }
|
|
|
3124
3185
|
* tagging surprise, degrades to a null tag treated as "no signal" (never a
|
|
3125
3186
|
* decline) — matching every other optional-adapter path in this file. */
|
|
3126
3187
|
async function objectReadsAsNonNoun(word) {
|
|
3188
|
+
// A MULTI-WORD complement only ever reaches here from behind an article, so
|
|
3189
|
+
// it is a noun phrase by construction and the adjective question doesn't
|
|
3190
|
+
// arise. Asking wink anyway tags the joined string as one opaque token and
|
|
3191
|
+
// reads back nonsense ("key value store" → ADJ), which then diverted a plain
|
|
3192
|
+
// class claim into the property lane.
|
|
3193
|
+
if (/\s/.test(String(word || "").trim())) return false;
|
|
3127
3194
|
try {
|
|
3128
3195
|
const { nlpAdapter } = await import("../adapters/ask-nlp.mjs");
|
|
3129
3196
|
const adapter = nlpAdapter();
|
|
@@ -4766,12 +4833,18 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
4766
4833
|
if (stored) return stored;
|
|
4767
4834
|
}
|
|
4768
4835
|
// PASSIVE ownership — "<X> is owned by <Name>". Same bare-form gate as the
|
|
4769
|
-
// active shape just above
|
|
4770
|
-
//
|
|
4771
|
-
//
|
|
4772
|
-
//
|
|
4836
|
+
// active shape just above, reading EITHER side and requiring no
|
|
4837
|
+
// interrogative lead, so "is TaskController owned by anyone" (a genuine
|
|
4838
|
+
// yes/no QUESTION, handled by factReadBack instead) never lands a bogus
|
|
4839
|
+
// fact here. Reading both sides matters as much as it does for the active
|
|
4840
|
+
// shape, and the SUBJECT side gets the path test too: "src/domain/
|
|
4841
|
+
// lexicon.mjs is owned by antony" states ownership just as plainly as a
|
|
4842
|
+
// capitalized owner does, while "the car is owned by john" still declines.
|
|
4773
4843
|
const ownPassive = ownSrc.match(OWNS_PASSIVE_TEACH_RE);
|
|
4774
|
-
|
|
4844
|
+
const ownPassiveSubject = ownPassive?.[1]?.trim() ?? "";
|
|
4845
|
+
if (ownPassive && memoryDir && !QUESTION_LEAD_RE.test(ownSrc) && !ownSrcMidQuestion
|
|
4846
|
+
&& (wrapped || /^[A-Z]/.test(ownPassive[2]) || /^[A-Z]/.test(ownPassiveSubject)
|
|
4847
|
+
|| MODULE_PATH_RE.test(ownPassiveSubject))) {
|
|
4775
4848
|
const stored = await teachFact(memoryDir, sessionId, {
|
|
4776
4849
|
subject: ownPassive[1], predicate: OWNED_BY_PREDICATE, object: ownPassive[2],
|
|
4777
4850
|
});
|
|
@@ -5469,7 +5542,7 @@ const WHAT_KNOW_RE = /^(?:what\s+(?:do\s+you|d'?you)\s+know(?:\s+so\s+far)?|what
|
|
|
5469
5542
|
// do" needs the noun OPTIONAL after "this" (kept REQUIRED after "the") or it
|
|
5470
5543
|
// falls through to MODULE_ORIENT_RE, which fails to resolve "this" as an
|
|
5471
5544
|
// entity and hits the raw grammar wall.
|
|
5472
|
-
const META_ORIENT_RE = /^(?:what(?:'s| is| are)?\s+this(?:\s+(?:app|codebase|repo|repository|project|code|thing))?|what\s+(?:codebase|repo|repository|project)\s+is\s+this|what\s+does\s+this(?:\s+(?:app|code|codebase|project|repo))?\s+do|what\s+does\s+the\s+(?:app|code|codebase|project|repo)\s+do|what\s+is\s+(?:this|the)\s+app(?:\s+for)?|what\s+am\s+i\s+looking\s+at|what\s+is\s+tmct|how\s+do\s+i\s+(?:start|begin|get\s+started|get\s+going|load\s+(?:my\s+)?code|index\s+(?:my\s+)?(?:code|repo|repository)|use\s+(?:this|you|tmct))|where\s+do\s+i\s+(?:start|begin)(?:\s+reading(?:\s+(?:this\s+)?(?:codebase|code|repo|repository|project))?)?|what\s+should\s+i\s+(?:read|look\s+at)\s+first(?:\s+to\s+understand\s+(?:this\s+)?(?:codebase|code|repo|repository|project))?|where\s+should\s+i\s+start\s+reading(?:\s+(?:this\s+)?(?:codebase|code|repo|repository|project))?|where\s+do\s+i\s+begin\s+reading(?:\s+(?:this\s+)?(?:codebase|code|repo|repository|project))?)$/;
|
|
5545
|
+
const META_ORIENT_RE = /^(?:what(?:'s| is| are)?\s+this(?:\s+(?:app|codebase|repo|repository|project|code|thing))?|what\s+(?:codebase|repo|repository|project)\s+is\s+this|what\s+does\s+this(?:\s+(?:app|code|codebase|project|repo))?\s+do|what\s+does\s+the\s+(?:app|code|codebase|project|repo)\s+do|what\s+is\s+(?:this|the)\s+app(?:\s+for)?|what\s+am\s+i\s+looking\s+at|what\s+is\s+tmct|how\s+do\s+i\s+(?:(?:start|begin|get\s+started|get\s+going)(?:\s+(?:with|on)\s+(?:this|it))?|load\s+(?:my\s+)?code|index\s+(?:my\s+)?(?:code|repo|repository)|use\s+(?:this|you|tmct))|where\s+do\s+i\s+(?:start|begin)(?:\s+reading(?:\s+(?:this\s+)?(?:codebase|code|repo|repository|project))?)?|what\s+should\s+i\s+(?:read|look\s+at)\s+first(?:\s+to\s+understand\s+(?:this\s+)?(?:codebase|code|repo|repository|project))?|where\s+should\s+i\s+start\s+reading(?:\s+(?:this\s+)?(?:codebase|code|repo|repository|project))?|where\s+do\s+i\s+begin\s+reading(?:\s+(?:this\s+)?(?:codebase|code|repo|repository|project))?)$/;
|
|
5473
5546
|
/** A bare "what is in here"/"what's in here"/"whats in here" — the SAME
|
|
5474
5547
|
* orientation intent as META_ORIENT_RE's own
|
|
5475
5548
|
* "what's in this repo"-shaped members, just phrased with the CONTEXT_WORDS
|
|
@@ -5517,17 +5590,19 @@ async function memorySummary(memoryDir, graph) {
|
|
|
5517
5590
|
// #2(e) MODULE-GRAIN OVERVIEW. META_ORIENT_RE (above) can't match a module
|
|
5518
5591
|
// path/symbol name, so "what does app/lib/a.mjs do" needs its own lane.
|
|
5519
5592
|
// CASE-PRESERVING: reads the ORIGINAL query text, never metaLane's lowercased `q`.
|
|
5520
|
-
/**
|
|
5521
|
-
* store module do exactly?", "
|
|
5522
|
-
*
|
|
5523
|
-
*
|
|
5524
|
-
* raw grammar wall even though the
|
|
5525
|
-
* (used elsewhere in the file) never
|
|
5526
|
-
* at all. Mirrors
|
|
5527
|
-
* just below: closed, optional, single-lane blast
|
|
5528
|
-
* X do" still matches with
|
|
5529
|
-
|
|
5530
|
-
|
|
5593
|
+
/** One intensifier/filler adverb sitting either side of "do"/"does" ("what does
|
|
5594
|
+
* the store module do exactly?", "what does tasks.mjs actually do
|
|
5595
|
+
* internally", "what X does really") — the closed-form anchors below pin
|
|
5596
|
+
* "do"/"does" against the term on one side and the optional "?" on the
|
|
5597
|
+
* other, so either extra word would hit the raw grammar wall even though the
|
|
5598
|
+
* shared FILLER_WORDS/normalizeQuery pass (used elsewhere in the file) never
|
|
5599
|
+
* sees this lane's case-preserving text at all. Mirrors
|
|
5600
|
+
* MODULE_ORIENT_POLITENESS_RE just below: closed, optional, single-lane blast
|
|
5601
|
+
* radius — a bare "what does X do" still matches with both slots empty. The
|
|
5602
|
+
* PRE-verb slot also keeps an adverb out of the term capture, so
|
|
5603
|
+
* "tasks.mjs actually" never reaches entity resolution as one name. */
|
|
5604
|
+
const ORIENT_ADVERB_SLOT_RE = "(?:\\s+(?:exactly|really|actually|truly|anyway|internally|properly))?";
|
|
5605
|
+
const MODULE_ORIENT_RE = new RegExp(`^what\\s+does\\s+(.+?)${ORIENT_ADVERB_SLOT_RE}\\s+do${ORIENT_ADVERB_SLOT_RE}\\??$`, "i");
|
|
5531
5606
|
/** The SUBJECT-FIRST word order of the SAME question ("what saveStore does" vs
|
|
5532
5607
|
* "what does saveStore do") — a perfectly natural alternate phrasing of an
|
|
5533
5608
|
* ALREADY-recognized intent that would otherwise hit the raw grammar wall
|
|
@@ -5537,7 +5612,7 @@ const MODULE_ORIENT_RE = new RegExp(`^what\\s+does\\s+(.+?)\\s+do${TRAILING_ADVE
|
|
|
5537
5612
|
* UNIQUE graph entity or this lane declines) is what keeps this loose an
|
|
5538
5613
|
* ending safe — a syntactic match against a term that isn't a real entity
|
|
5539
5614
|
* simply falls through unchanged, same as every other lane in this file. */
|
|
5540
|
-
const MODULE_ORIENT_SVO_RE = new RegExp(`^what\\s+(.+?)\\s+does${
|
|
5615
|
+
const MODULE_ORIENT_SVO_RE = new RegExp(`^what\\s+(.+?)${ORIENT_ADVERB_SLOT_RE}\\s+does${ORIENT_ADVERB_SLOT_RE}\\??$`, "i");
|
|
5541
5616
|
/** "whats X do" / "what's X do" / "what is X do" — the CONTRACTED phrasing of
|
|
5542
5617
|
* "what does X do", where the auxiliary collapses into the "what's"/"whats"
|
|
5543
5618
|
* opener and "do" trails the term. MODULE_ORIENT_RE's own "does BEFORE the
|
|
@@ -5548,7 +5623,7 @@ const MODULE_ORIENT_SVO_RE = new RegExp(`^what\\s+(.+?)\\s+does${TRAILING_ADVERB
|
|
|
5548
5623
|
* that is not a real unique entity (a pronoun subject "whats it do", a
|
|
5549
5624
|
* non-word) simply declines. The "what(?:'s|s|\s+is)" opener mirrors
|
|
5550
5625
|
* MODULE_PURPOSE_RE's tolerance for the apostrophe-less "whats" contraction. */
|
|
5551
|
-
const MODULE_ORIENT_IS_DO_RE = new RegExp(`^what(?:'s|s|\\s+is)\\s+(.+?)\\s+do${
|
|
5626
|
+
const MODULE_ORIENT_IS_DO_RE = new RegExp(`^what(?:'s|s|\\s+is)\\s+(.+?)${ORIENT_ADVERB_SLOT_RE}\\s+do${ORIENT_ADVERB_SLOT_RE}\\??$`, "i");
|
|
5552
5627
|
// Purpose/identity phrasing: "whats X for"/"what's X
|
|
5553
5628
|
// about"/"what is X for", the sibling of "what does X do" that asks for the
|
|
5554
5629
|
// SAME module-grain overview. Deliberately does NOT claim the literal noun
|
|
@@ -10927,9 +11002,24 @@ const STACCATO_PRONOUN_RE = /^(?:and|also|so|then|now)\s+(it|that|this|those|the
|
|
|
10927
11002
|
const DESCRIBE_GRAIN_WORD_RE = new RegExp(
|
|
10928
11003
|
`^(?:(?:the|a|an)\\s+)?(.+?)\\s+(${Object.keys(ENTITY_TO_TYPE).join("|")})$`, "i",
|
|
10929
11004
|
);
|
|
11005
|
+
/** A LEADING noise phrase glued onto the captured term by an outer bridge, the
|
|
11006
|
+
* mirror of the trailing grain word DESCRIBE_GRAIN_WORD_RE strips. "give me
|
|
11007
|
+
* the context for src/core/store.mjs" reaches this lane as "describe context
|
|
11008
|
+
* for src/core/store.mjs" (normalize.mjs's show/give-me frame rewrites the
|
|
11009
|
+
* whole tail into the term), and every resolver downstream then reads
|
|
11010
|
+
* "context" as part of the name. Closed list, and the term still has to
|
|
11011
|
+
* resolve afterwards, so a phrase that leaves nothing resolvable simply
|
|
11012
|
+
* declines to the ordinary miss. */
|
|
11013
|
+
const DESCRIBE_LEADING_NOISE_RE =
|
|
11014
|
+
/^(?:the\s+)?(?:context|details|detail|info|information|background)\s+(?:for|on|about|of|around)\s+/i;
|
|
11015
|
+
function stripDescribeLeadingNoise(term) {
|
|
11016
|
+
const stripped = String(term || "").replace(DESCRIBE_LEADING_NOISE_RE, "").trim();
|
|
11017
|
+
return stripped || String(term || "").trim();
|
|
11018
|
+
}
|
|
11019
|
+
|
|
10930
11020
|
async function describeGrainRescue(graph, term) {
|
|
10931
11021
|
if (!graph) return null;
|
|
10932
|
-
const m =
|
|
11022
|
+
const m = stripDescribeLeadingNoise(term).match(DESCRIBE_GRAIN_WORD_RE);
|
|
10933
11023
|
if (!m) return null;
|
|
10934
11024
|
const [, head, grainWord] = m;
|
|
10935
11025
|
const expectedClass = ENTITY_TO_TYPE[grainWord.toLowerCase()];
|
|
@@ -10959,6 +11049,11 @@ async function describeWrapperAnswer(query, { config, source, focus, graph, tel
|
|
|
10959
11049
|
// "describe about X" (a doubled verb) leaves a redundant leading "about "
|
|
10960
11050
|
// glued to the captured term.
|
|
10961
11051
|
term = term.replace(/^about\s+/i, "");
|
|
11052
|
+
// "give me the context for X" arrives bridged into "describe context for X".
|
|
11053
|
+
// Cleaned once here so the grain rescue, the /describe dispatch and the
|
|
11054
|
+
// focus-setting resolveEntity below all read the same clean term — the
|
|
11055
|
+
// answer was already right without this, but the focus never updated.
|
|
11056
|
+
term = stripDescribeLeadingNoise(term);
|
|
10962
11057
|
// A trailing bare discourse tag ("describe Record then") glued onto the
|
|
10963
11058
|
// captured term, same class of gap stripTrailingDiscourseTag (ask-vocab.mjs)
|
|
10964
11059
|
// already fixes for the meta-whatis vocab lane.
|
|
@@ -14785,7 +14880,60 @@ function vocabAntecedentFrom(last) {
|
|
|
14785
14880
|
return m[1];
|
|
14786
14881
|
}
|
|
14787
14882
|
|
|
14788
|
-
|
|
14883
|
+
/** Commands whose answer is SERVED SOURCE TEXT — lines read byte-for-byte off a
|
|
14884
|
+
* file on disk, which a caller may paste straight into an edit. The prose
|
|
14885
|
+
* grammar rules rewrite real code into code that no longer parses: a spread
|
|
14886
|
+
* (`...base`) and an optional chain (`?.`) both read as runs of terminal
|
|
14887
|
+
* punctuation and collapse to a single character. So the whole answer goes to
|
|
14888
|
+
* finish() as ONE protected `code` span and comes back untouched. */
|
|
14889
|
+
const SOURCE_RENDERING_COMMANDS = new Set(["context", "snippet"]);
|
|
14890
|
+
|
|
14891
|
+
/** finish() a dispatched turn, byte-protecting a served-source answer. A
|
|
14892
|
+
* producer that segmented its own answer keeps its own segments; everything
|
|
14893
|
+
* else is masked and grammar-corrected exactly as before. */
|
|
14894
|
+
function finishTurn(result, ctx) {
|
|
14895
|
+
const preSegmented = Array.isArray(result?.segments) && result.segments.length > 0;
|
|
14896
|
+
if (!preSegmented && typeof result?.answer === "string"
|
|
14897
|
+
&& SOURCE_RENDERING_COMMANDS.has(result?.record?.command)) {
|
|
14898
|
+
return finish({ ...result, segments: [{ type: "code", text: result.answer }] }, ctx);
|
|
14899
|
+
}
|
|
14900
|
+
return finish(result, ctx);
|
|
14901
|
+
}
|
|
14902
|
+
|
|
14903
|
+
/** An uncached readFactRows() snapshot of the memory store — one half of the
|
|
14904
|
+
* before/after pair a turn's `factsTouched` diff is taken over. Deliberately
|
|
14905
|
+
* bypasses the turn's factRowsCache: the cache is invalidated by only some
|
|
14906
|
+
* write paths, and a diff read through it would miss the very writes it exists
|
|
14907
|
+
* to report. Null (rather than []) when there is no store or it won't load, so
|
|
14908
|
+
* the caller can tell "nothing to diff" from "diffed, nothing moved". */
|
|
14909
|
+
async function factRowSnapshot(memoryDir) {
|
|
14910
|
+
if (!memoryDir) return null;
|
|
14911
|
+
try { return readStoredFactRows(await loadMemoryStore(memoryDir)); } catch { return null; }
|
|
14912
|
+
}
|
|
14913
|
+
|
|
14914
|
+
/** The Fact rows this turn wrote, diffed against the snapshot taken before it.
|
|
14915
|
+
* Empty when the turn had no store to write to, or wrote nothing. */
|
|
14916
|
+
async function factsTouchedSince(memoryDir, before) {
|
|
14917
|
+
if (!before) return [];
|
|
14918
|
+
const after = await factRowSnapshot(memoryDir);
|
|
14919
|
+
if (!after) return [];
|
|
14920
|
+
const { touchedFactRows } = await import("../domain/memory/touched-facts.mjs");
|
|
14921
|
+
return touchedFactRows(before, after);
|
|
14922
|
+
}
|
|
14923
|
+
|
|
14924
|
+
/** Run one turn and report which Fact rows it wrote, as `factsTouched` beside
|
|
14925
|
+
* the answer/record/logLines every caller already reads. The dispatch itself
|
|
14926
|
+
* is dispatchTurn, below; this wrapper exists so the field lands on EVERY
|
|
14927
|
+
* return path (dispatched, conversational, multi-sentence) from one place. */
|
|
14928
|
+
export async function runTurn(input, options = {}) {
|
|
14929
|
+
const memoryDir = options?.memoryDir ?? null;
|
|
14930
|
+
const before = await factRowSnapshot(memoryDir);
|
|
14931
|
+
const result = await dispatchTurn(input, options);
|
|
14932
|
+
if (!result || typeof result !== "object") return result;
|
|
14933
|
+
return { ...result, factsTouched: await factsTouchedSince(memoryDir, before) };
|
|
14934
|
+
}
|
|
14935
|
+
|
|
14936
|
+
async function dispatchTurn(input, { config, source = defaultSource, graph = null, focus = null, last = null, memoryDir = null, sessionId = "", env = process.env, lexicon = null, narrate = false, liveReference = false, onLiveLookup = null, vocabHint = null, tel = null, biasByBundle = {}, factRowsCache: injectedFactRowsCache = null, planState = null, gameConfig = null, uiContext = "cli", synthesisBudget = AUTO_SYNTHESIS_BUDGET, researchState = null, researchConfig = null, discourse = null, _noSplit = false } = {}) {
|
|
14789
14937
|
// Every game's tuning knobs (spider-fly's mass economy, guess-the-number's
|
|
14790
14938
|
// bounds, the shared plan lane's search-depth cap) — a caller's own
|
|
14791
14939
|
// gameConfig (chat-session.mjs resolves one per session from tmct.toml)
|
|
@@ -14863,7 +15011,7 @@ export async function runTurn(input, { config, source = defaultSource, graph = n
|
|
|
14863
15011
|
// what the shell prints. The narrate block is applied AFTER `last` is
|
|
14864
15012
|
// captured from the PRE-narration finished result.
|
|
14865
15013
|
const withLast = (result, fallbackGoal = "unclear — no goal signal for this turn type") => {
|
|
14866
|
-
const finished = attachDialogueAct(
|
|
15014
|
+
const finished = attachDialogueAct(finishTurn(result, { graph }), trace);
|
|
14867
15015
|
// The logged transcript echo is ALWAYS the verbatim user line — no dispatch
|
|
14868
15016
|
// path's internal rewrite (the indirect-request wrapper, the vocab-opener /
|
|
14869
15017
|
// cleft / ESL rewrites, a discourse substitution) may leak into what the
|
|
@@ -15154,8 +15302,11 @@ export async function runTurn(input, { config, source = defaultSource, graph = n
|
|
|
15154
15302
|
let f = focus; let l = last; let ps = planHolder.state; let d = discourseHolder.record;
|
|
15155
15303
|
const receipts = [];
|
|
15156
15304
|
let finalRec = null;
|
|
15305
|
+
// dispatchTurn, not runTurn: the OUTER wrapper's before/after snapshot
|
|
15306
|
+
// already spans every sentence, so a per-sentence diff would only pay
|
|
15307
|
+
// for a narrower answer to the same question.
|
|
15157
15308
|
for (const sentence of sentences) {
|
|
15158
|
-
const r = await
|
|
15309
|
+
const r = await dispatchTurn(sentence, {
|
|
15159
15310
|
config, source, graph, focus: f, last: l, memoryDir, sessionId, env, lexicon,
|
|
15160
15311
|
narrate: false, vocabHint, tel, biasByBundle, planState: ps, discourse: d, _noSplit: true,
|
|
15161
15312
|
});
|
|
@@ -354,6 +354,9 @@ export function renderCodeExplorerHtml(data, { bundleInline = "", bundleAvailabl
|
|
|
354
354
|
<meta charset="utf-8">
|
|
355
355
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
356
356
|
<title>tmct code explorer</title>
|
|
357
|
+
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
|
|
358
|
+
<link rel="icon" href="./favicon.ico" sizes="any">
|
|
359
|
+
<link rel="apple-touch-icon" href="./apple-touch-icon.png">
|
|
357
360
|
<style>
|
|
358
361
|
${THEME_TOKENS_CSS}
|
|
359
362
|
* { box-sizing: border-box; }
|
package/src/services/finish.mjs
CHANGED
|
@@ -20,7 +20,12 @@ const GRAMMAR_DIR = dirname(fileURLToPath(import.meta.url));
|
|
|
20
20
|
/** The data-driven grammar-rule table. */
|
|
21
21
|
const GRAMMAR_RULES_FILE = join(GRAMMAR_DIR, "..", "..", "data", "templates", "grammar-rules.toml");
|
|
22
22
|
|
|
23
|
-
/** The segment type vocabulary. `prose` is the only unprotected type.
|
|
23
|
+
/** The segment type vocabulary. `prose` is the only unprotected type.
|
|
24
|
+
*
|
|
25
|
+
* `code` is the one type maskSegments never infers: no regex tells source text
|
|
26
|
+
* from prose reliably enough to be trusted with a caller's edit. A producer
|
|
27
|
+
* that KNOWS it is serving source (chat's /snippet and /context) says so by
|
|
28
|
+
* handing finish() its own `code` span, and gets its bytes back exactly. */
|
|
24
29
|
export const SEGMENT_TYPES = Object.freeze([
|
|
25
30
|
"prose", "entity", "path", "number", "code", "provenance", "receipt",
|
|
26
31
|
]);
|
|
@@ -76,6 +76,9 @@ export function renderIngestHtml({ title = DEFAULT_TITLE } = {}) {
|
|
|
76
76
|
<meta charset="utf-8">
|
|
77
77
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
78
78
|
<title>${escapeHtml(title)}</title>
|
|
79
|
+
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
|
|
80
|
+
<link rel="icon" href="./favicon.ico" sizes="any">
|
|
81
|
+
<link rel="apple-touch-icon" href="./apple-touch-icon.png">
|
|
79
82
|
<!--
|
|
80
83
|
The wink lemma/POS tier loads from ./vendor/wink.js — the site's own shared
|
|
81
84
|
first-party bundle (built by scripts/build-wink-vendor.mjs), one cached copy
|
|
@@ -570,6 +570,9 @@ export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, wo
|
|
|
570
570
|
<meta charset="utf-8">
|
|
571
571
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
572
572
|
<title>${escapeHtml(title)}</title>
|
|
573
|
+
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
|
|
574
|
+
<link rel="icon" href="./favicon.ico" sizes="any">
|
|
575
|
+
<link rel="apple-touch-icon" href="./apple-touch-icon.png">
|
|
573
576
|
<!--
|
|
574
577
|
The wink lemma/POS tier loads from ./vendor/wink.js — the site's own shared
|
|
575
578
|
first-party bundle of wink-nlp + wink-eng-lite-web-model (built by
|
|
@@ -293,6 +293,9 @@ export function renderPlanHtml({ plan, rendersAs = {}, sizeOrder = [], title } =
|
|
|
293
293
|
<meta charset="utf-8">
|
|
294
294
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
295
295
|
<title>${escapeHtml(pageTitle)}</title>
|
|
296
|
+
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
|
|
297
|
+
<link rel="icon" href="./favicon.ico" sizes="any">
|
|
298
|
+
<link rel="apple-touch-icon" href="./apple-touch-icon.png">
|
|
296
299
|
<!--
|
|
297
300
|
The wink lemma/POS tier loads from ./vendor/wink.js — the site's own shared
|
|
298
301
|
first-party bundle of wink-nlp + wink-eng-lite-web-model (built by
|
|
@@ -87,6 +87,9 @@ export function renderResearchHtml({ title = DEFAULT_TITLE, digestStructures = [
|
|
|
87
87
|
<meta charset="utf-8">
|
|
88
88
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
89
89
|
<title>${escapeHtml(title)}</title>
|
|
90
|
+
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
|
|
91
|
+
<link rel="icon" href="./favicon.ico" sizes="any">
|
|
92
|
+
<link rel="apple-touch-icon" href="./apple-touch-icon.png">
|
|
90
93
|
<!--
|
|
91
94
|
The wink lemma/POS tier loads from ./vendor/wink.js — the site's own shared
|
|
92
95
|
first-party bundle (built by scripts/build-wink-vendor.mjs), one cached copy
|
|
@@ -225,7 +225,9 @@ export function nextCorpses(prevCorpses, prevAgents, agents, turn, lingerTurns =
|
|
|
225
225
|
* the engine into the page instead of the sibling `<script src>`, for the
|
|
226
226
|
* CLI's standalone export — one downloadable file that runs from file://
|
|
227
227
|
* with no sibling assets. Default empty keeps the site build's sibling-file
|
|
228
|
-
* arrangement byte-identical
|
|
228
|
+
* arrangement byte-identical, favicon links included; the standalone export
|
|
229
|
+
* drops them too, since a relative ./favicon.svg would be a dangling
|
|
230
|
+
* external reference the "no sibling assets" export can't carry. */
|
|
229
231
|
export function renderSpiderFlyHtml({ title = DEFAULT_TITLE, spriteTemplates = [], engineBundleJs = "" } = {}) {
|
|
230
232
|
const gridData = embedJson({
|
|
231
233
|
gridSize: GRID_SIZE,
|
|
@@ -254,6 +256,9 @@ export function renderSpiderFlyHtml({ title = DEFAULT_TITLE, spriteTemplates = [
|
|
|
254
256
|
<meta charset="utf-8">
|
|
255
257
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
256
258
|
<title>${escapeHtml(title)}</title>
|
|
259
|
+
${engineBundleJs ? "" : `<link rel="icon" href="./favicon.svg" type="image/svg+xml">
|
|
260
|
+
<link rel="icon" href="./favicon.ico" sizes="any">
|
|
261
|
+
<link rel="apple-touch-icon" href="./apple-touch-icon.png">`}
|
|
257
262
|
<style>
|
|
258
263
|
${THEME_TOKENS_CSS}
|
|
259
264
|
:root { --fly: #A6791F; }
|
|
@@ -464,7 +464,9 @@ function sectionHtml(group, entries) {
|
|
|
464
464
|
* same two template sets) and references the sibling
|
|
465
465
|
* ./sprites-browser.bundle.js scripts/build-demo-site.mjs builds alongside
|
|
466
466
|
* it. Left false, the page renders exactly as before — no dock, no bundle
|
|
467
|
-
* reference, nothing extra to 404
|
|
467
|
+
* reference, nothing extra to 404 — including the favicon links, since the
|
|
468
|
+
* CLI's standalone export (also `spritesBundleAvailable: false`) can't carry
|
|
469
|
+
* a dangling relative ./favicon.svg either. */
|
|
468
470
|
export function renderSpriteCatalogHtml({ title = DEFAULT_TITLE, iconTemplates = [], largeTemplates = [], factRows = [], spritesBundleAvailable = false } = {}) {
|
|
469
471
|
const entries = buildSpriteCatalogEntries({ iconTemplates, largeTemplates, factRows });
|
|
470
472
|
const totalSwatches = entries.reduce((n, e) => n + e.iconSwatches.length + e.largeSwatches.length, 0);
|
|
@@ -607,6 +609,9 @@ const SPRITE_CHAT = ${embedJson({ rows: dockRows })};
|
|
|
607
609
|
<meta charset="utf-8">
|
|
608
610
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
609
611
|
<title>${escapeHtml(title)}</title>
|
|
612
|
+
${spritesBundleAvailable ? `<link rel="icon" href="./favicon.svg" type="image/svg+xml">
|
|
613
|
+
<link rel="icon" href="./favicon.ico" sizes="any">
|
|
614
|
+
<link rel="apple-touch-icon" href="./apple-touch-icon.png">` : ""}
|
|
610
615
|
<style>
|
|
611
616
|
${THEME_TOKENS_CSS}
|
|
612
617
|
html { background: var(--bg); }
|