@polycode-projects/the-mechanical-code-talker 3.0.5 → 3.0.7
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 +41 -16
- package/bin/tmct.mjs +1 -1
- package/corpus/child/README.md +2 -2
- package/corpus/child/index.json.gz +0 -0
- package/corpus/child/manifest.json +74 -74
- package/corpus/child/shards/child-00.jsonl.gz +0 -0
- package/corpus/child/shards/child-01.jsonl.gz +0 -0
- package/corpus/child/shards/child-02.jsonl.gz +0 -0
- package/corpus/child/shards/child-03.jsonl.gz +0 -0
- package/corpus/child/shards/child-04.jsonl.gz +0 -0
- package/corpus/child/shards/child-05.jsonl.gz +0 -0
- package/corpus/child/shards/child-06.jsonl.gz +0 -0
- package/corpus/child/shards/child-07.jsonl.gz +0 -0
- package/corpus/child/shards/child-08.jsonl.gz +0 -0
- package/corpus/child/shards/child-09.jsonl.gz +0 -0
- package/corpus/child/shards/child-0a.jsonl.gz +0 -0
- package/corpus/child/shards/child-0b.jsonl.gz +0 -0
- package/corpus/child/shards/child-0c.jsonl.gz +0 -0
- package/corpus/child/shards/child-0d.jsonl.gz +0 -0
- package/corpus/child/shards/child-0e.jsonl.gz +0 -0
- package/corpus/child/shards/child-0f.jsonl.gz +0 -0
- package/corpus/child/shards/child-10.jsonl.gz +0 -0
- package/corpus/child/shards/child-11.jsonl.gz +0 -0
- package/corpus/child/shards/child-12.jsonl.gz +0 -0
- package/corpus/child/shards/child-13.jsonl.gz +0 -0
- package/corpus/child/shards/child-14.jsonl.gz +0 -0
- package/corpus/child/shards/child-15.jsonl.gz +0 -0
- package/corpus/child/shards/child-16.jsonl.gz +0 -0
- package/corpus/child/shards/child-17.jsonl.gz +0 -0
- package/corpus/child/shards/child-18.jsonl.gz +0 -0
- package/corpus/child/shards/child-19.jsonl.gz +0 -0
- package/corpus/child/shards/child-1a.jsonl.gz +0 -0
- package/corpus/child/shards/child-1b.jsonl.gz +0 -0
- package/corpus/child/shards/child-1c.jsonl.gz +0 -0
- package/corpus/child/shards/child-1d.jsonl.gz +0 -0
- package/corpus/child/shards/child-1e.jsonl.gz +0 -0
- package/corpus/child/shards/child-1f.jsonl.gz +0 -0
- package/corpus/conceptnet/quality-filter.mjs +28 -4
- package/corpus/tier2/generate.mjs +4 -8
- package/corpus/tier2/human-large.jsonl +0 -2
- package/corpus/tier2/human-medium.jsonl +0 -2
- package/corpus/tier2/manifest.json +6 -6
- package/data/templates/constructions/digest-sentence-structures.toml +146 -1
- package/package.json +18 -15
- package/src/adapters/corpus/digest-bank.mjs +15 -3
- package/src/domain/answer-variants.json +1 -1
- package/src/domain/ask-vocab.mjs +4 -0
- package/src/domain/ask.mjs +101 -12
- package/src/domain/codegraph.mjs +25 -8
- package/src/domain/digest/compose.mjs +142 -10
- package/src/domain/digest/config.json +5 -0
- package/src/domain/digest/structures.mjs +39 -14
- package/src/domain/interpret/normalize.mjs +2 -2
- package/src/domain/reference-pack.mjs +12 -3
- package/src/domain/router/call-validator.mjs +1 -1
- package/src/domain/router/drive.mjs +2 -2
- package/src/domain/router/set-algebra.mjs +1 -1
- package/src/services/adventure.mjs +34 -19
- package/src/services/chat-page-viz.mjs +16 -5
- package/src/services/chat.mjs +290 -88
- package/src/services/extract-facts.mjs +46 -4
- package/src/services/index.mjs +6 -3
- package/src/services/ledger-viz.mjs +1 -1
- package/src/services/research.mjs +80 -3
- package/src/surfaces/web/chat-browser-entry.mjs +23 -3
- package/src/surfaces/web/digest-client.mjs +8 -3
- package/src/surfaces/web/graph-ask-browser-entry.mjs +41 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +113 -113
|
@@ -53,7 +53,8 @@ import { readFile, writeFile, mkdtemp, rm } from "node:fs/promises";
|
|
|
53
53
|
import { tmpdir } from "node:os";
|
|
54
54
|
import { basename, join, resolve } from "node:path";
|
|
55
55
|
|
|
56
|
-
import { runTurn, uuidv7 } from "./chat.mjs";
|
|
56
|
+
import { runTurn, uuidv7, stripLeadingDiscourseAdverb } from "./chat.mjs";
|
|
57
|
+
import { beginsWithVowelSound, grammarRules } from "./finish.mjs";
|
|
57
58
|
import { splitSentencesPreservingPaths, stripCitationResidue } from "./sentences.mjs";
|
|
58
59
|
import { loadMemory, readFactRows, appendFact } from "../adapters/memory/core.mjs";
|
|
59
60
|
import { loadConfig } from "../adapters/config.mjs";
|
|
@@ -125,6 +126,12 @@ const RELATIVE_PRONOUNS = new Set(["that", "which", "who", "whom", "whose"]);
|
|
|
125
126
|
// At most this many triples from one sentence — a bound so a run-on can never
|
|
126
127
|
// shatter into noise, not a first-wins cap.
|
|
127
128
|
const MAX_TRIPLES_PER_SENTENCE = 4;
|
|
129
|
+
// How far a copula object scan walks past an attributive-adjective compound
|
|
130
|
+
// (wink tokenizes "medium-sized" as NOUN + "-" + VERB and never re-fuses it) to
|
|
131
|
+
// reach the real head noun through a coordinate modifier list
|
|
132
|
+
// (", burrowing, nocturnal mammal"). A small, explicit bound: past it the object
|
|
133
|
+
// abstains rather than guess, so a long noun pile never mints a stray class.
|
|
134
|
+
const ATTRIBUTIVE_CHAIN_MAX_HOPS = 8;
|
|
128
135
|
|
|
129
136
|
/** Fold an entity surface to its stored key: a lexicon noun's lemma, else the
|
|
130
137
|
* word's own normFactTerm (the optimistic tier mints unlisted content nouns
|
|
@@ -242,6 +249,25 @@ function optimisticTriplesPos(sentence, lexicon, nlp) {
|
|
|
242
249
|
if (!isNounish(j)) continue;
|
|
243
250
|
let hi = j;
|
|
244
251
|
while (hi + 1 < values.length && isNounish(hi + 1)) hi += 1;
|
|
252
|
+
// A NOUN immediately followed by "-" then a VERB or ADJ is the left half of
|
|
253
|
+
// an attributive-adjective compound wink never re-fused ("medium-sized"),
|
|
254
|
+
// not the class. Walk forward through the coordinate modifier list (hyphens,
|
|
255
|
+
// commas, "and", further ADJ/VERB tokens) to the real head noun and re-point
|
|
256
|
+
// there; abstain if none appears within the bound, never mint the modifier.
|
|
257
|
+
if (values[hi + 1] === "-" && (pos[hi + 2] === "VERB" || pos[hi + 2] === "ADJ")) {
|
|
258
|
+
let head = null;
|
|
259
|
+
let k = hi + 1;
|
|
260
|
+
for (let hop = 0; hop < ATTRIBUTIVE_CHAIN_MAX_HOPS && k < values.length; hop += 1, k += 1) {
|
|
261
|
+
if (isNounish(k)) { head = k; break; }
|
|
262
|
+
const w = values[k]?.toLowerCase();
|
|
263
|
+
if (w === "-" || w === "," || w === "and" || pos[k] === "ADJ" || pos[k] === "VERB" || pos[k] === "CCONJ") continue;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
if (head === null) return null;
|
|
267
|
+
j = head;
|
|
268
|
+
hi = head;
|
|
269
|
+
while (hi + 1 < values.length && isNounish(hi + 1)) hi += 1;
|
|
270
|
+
}
|
|
245
271
|
const headWord = String(values[hi]).toLowerCase();
|
|
246
272
|
const nextIsOf = values[hi + 1]?.toLowerCase() === "of";
|
|
247
273
|
if (!nextIsOf) return { label: entityRunAt(j), hi };
|
|
@@ -434,6 +460,17 @@ export function clauseCandidates(sentence, { nlp } = {}) {
|
|
|
434
460
|
// live focus, never a stale paragraph carry.
|
|
435
461
|
const PRONOUN_LEAD_RE = /^(?:they|it|these|those|this)\b\s*/i;
|
|
436
462
|
|
|
463
|
+
/** Re-article a bare carried subject so the retried sentence is a grammatical
|
|
464
|
+
* habitual surface the recognizer accepts: "cell" → "a cell", "orbit" → "an
|
|
465
|
+
* orbit". Uses the same vowel-sound-aware article rule (grammar-rules.toml)
|
|
466
|
+
* the chat recognizer's own capability rewrite uses, rather than a hardcoded
|
|
467
|
+
* "a". */
|
|
468
|
+
function articledSubject(subject) {
|
|
469
|
+
const articleRule = grammarRules().find((r) => r.kind === "article");
|
|
470
|
+
const article = articleRule && beginsWithVowelSound(subject, articleRule) ? "an" : "a";
|
|
471
|
+
return `${article} ${subject}`;
|
|
472
|
+
}
|
|
473
|
+
|
|
437
474
|
/** A readable predicate for canonical output: the local part of an rdfs:/ace:
|
|
438
475
|
* CURIE, otherwise the predicate verbatim. */
|
|
439
476
|
const readablePredicate = (predicate) => String(predicate).replace(/^[a-z]+:/i, "");
|
|
@@ -532,9 +569,14 @@ export async function ingestText(text, {
|
|
|
532
569
|
}
|
|
533
570
|
// Bounded pronoun carry: a "they/it/these/those/this …" sentence the
|
|
534
571
|
// recognizer skipped is retried once with the paragraph's last grounded
|
|
535
|
-
// subject in the pronoun's place.
|
|
536
|
-
|
|
537
|
-
|
|
572
|
+
// subject in the pronoun's place. A leading ordinal/temporal discourse
|
|
573
|
+
// adverb ("Then it splits.") is stripped first so the pronoun reaches
|
|
574
|
+
// the sentence front; the carried subject is re-articled ("a cell") so
|
|
575
|
+
// the retry is a grammatical habitual surface. Never a chat turn —
|
|
576
|
+
// ingest only.
|
|
577
|
+
const threaded = stripLeadingDiscourseAdverb(cleaned);
|
|
578
|
+
if (!rows && carrySubject && PRONOUN_LEAD_RE.test(threaded)) {
|
|
579
|
+
rows = await strictRows(threaded.replace(PRONOUN_LEAD_RE, `${articledSubject(carrySubject)} `));
|
|
538
580
|
}
|
|
539
581
|
if (rows) {
|
|
540
582
|
recognizedSentences += 1;
|
package/src/services/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// ask.mjs keeps the core primitives (resolveObject, traverse, render) and
|
|
7
7
|
// the ask() orchestration.
|
|
8
8
|
|
|
9
|
-
// Chat surface
|
|
9
|
+
// Chat surface.
|
|
10
10
|
// createSession is the library's session sink — the same focus/last/planState
|
|
11
11
|
// threading and memory-backend seam every shell shares; runTurn is the pure
|
|
12
12
|
// single-turn engine underneath it.
|
|
@@ -35,6 +35,9 @@ setConstructionBanks(readConstructionFiles);
|
|
|
35
35
|
// Graph traversal primitives.
|
|
36
36
|
export { relationKind, impactClosure } from "../domain/codegraph.mjs";
|
|
37
37
|
|
|
38
|
+
// Graph-service construction for a provider-supplied code graph.
|
|
39
|
+
export { createGraphService } from "../adapters/providers/graph-service.mjs";
|
|
40
|
+
|
|
38
41
|
// Tool dispatch (slash-commands and CLI tool calls route through here).
|
|
39
42
|
export { dispatchTool } from "../tools/server.mjs";
|
|
40
43
|
|
|
@@ -49,13 +52,13 @@ export { foldSessionLogs } from "./fold.mjs";
|
|
|
49
52
|
// fetchEntities() is the one read path.
|
|
50
53
|
export { fetchEntities, registerProvider } from "../adapters/source.mjs";
|
|
51
54
|
|
|
52
|
-
// `tmct init` onboarding
|
|
55
|
+
// `tmct init` onboarding.
|
|
53
56
|
// init.mjs and toml-config.mjs each export a same-named `CONFIG_FILE`
|
|
54
57
|
// constant ("tmct.toml") — aliased here so both can ride the one `.` entry
|
|
55
58
|
// point without colliding.
|
|
56
59
|
export { initRepo, defaultConfig, renderTomlConfig, PERSONA_PRESETS, CONFIG_FILE as INIT_CONFIG_FILE } from "./init.mjs";
|
|
57
60
|
|
|
58
|
-
// tmct.toml loading
|
|
61
|
+
// tmct.toml loading.
|
|
59
62
|
export { CONFIG_FILE as TOML_CONFIG_FILE } from "../adapters/toml-config.mjs";
|
|
60
63
|
|
|
61
64
|
// The "detailed answer" completions pipeline (also reachable as the
|
|
@@ -795,7 +795,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
795
795
|
// LEDGER (declared "let" in the embed script above this one — a classic,
|
|
796
796
|
// non-module script's top-level bindings are reachable as bare identifiers
|
|
797
797
|
// by every later script tag in the document, never as window.LEDGER; see
|
|
798
|
-
// e2e/pages-ledger.test.mjs's own note on this) and its indexes start as
|
|
798
|
+
// test-e2e/pages-ledger.test.mjs's own note on this) and its indexes start as
|
|
799
799
|
// the server-rendered snapshot but are REASSIGNED wholesale after a live
|
|
800
800
|
// teach (applyLedgerData, below) — a successful teach through the dock
|
|
801
801
|
// changes the underlying graph, and the page has to show that, not keep
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { normFactTerm } from "../domain/hash.mjs";
|
|
27
27
|
import { loadLexicon, lookupNoun } from "../domain/grammar/lexicon.mjs";
|
|
28
|
+
import { defaultNlp } from "../domain/interpret/nlp-registry.mjs";
|
|
28
29
|
|
|
29
30
|
/** The search key a topic folds to: normFactTerm, then the lexicon lemma
|
|
30
31
|
* when the noun is known ("owls" → "owl") — the same fold the live
|
|
@@ -210,12 +211,87 @@ function queuedFolds(state) {
|
|
|
210
211
|
return seen;
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
// The lexical-token split a fan-out candidate and the seed topic both fold
|
|
215
|
+
// through before comparison — lowercased, split on anything that isn't a
|
|
216
|
+
// letter or digit, empty pieces dropped.
|
|
217
|
+
const LEXICAL_SPLIT_RE = /[^a-z0-9]+/i;
|
|
218
|
+
function lexicalTokens(text) {
|
|
219
|
+
return String(text || "").toLowerCase().split(LEXICAL_SPLIT_RE).filter(Boolean);
|
|
220
|
+
}
|
|
221
|
+
function sharesLexicalToken(title, seedTokens) {
|
|
222
|
+
if (!seedTokens.size) return false;
|
|
223
|
+
for (const tok of lexicalTokens(title)) if (seedTokens.has(tok)) return true;
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// A citation-shaped candidate: an ISBN/DOI prefix, a bare year, or a
|
|
228
|
+
// "Nth century" phrase — the shape raw Wikipedia link lists carry for their
|
|
229
|
+
// source apparatus rather than a kin article.
|
|
230
|
+
const CITATION_PREFIX_RE = /^(isbn|doi)[\s:.-]/i;
|
|
231
|
+
const CITATION_BARE_YEAR_RE = /^\d{3,4}$/;
|
|
232
|
+
const CITATION_CENTURY_RE = /\b\d{1,2}(?:st|nd|rd|th)\s+century\b/i;
|
|
233
|
+
function isCitationShaped(title) {
|
|
234
|
+
const t = String(title || "").trim();
|
|
235
|
+
if (!t) return false;
|
|
236
|
+
return CITATION_PREFIX_RE.test(t) || CITATION_BARE_YEAR_RE.test(t) || CITATION_CENTURY_RE.test(t);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const SINGLE_WORD_TITLE_RE = /^[A-Za-z][A-Za-z'-]*$/;
|
|
240
|
+
// A short, neutral sentence to tag a bare candidate word inside — wink-nlp
|
|
241
|
+
// reads an isolated capitalized word as PROPN with no sentence context to
|
|
242
|
+
// tell it otherwise, so the word is lowercased and read at this fixed slot
|
|
243
|
+
// inside real subject/verb/object context instead.
|
|
244
|
+
const NOUN_CARRIER_PREFIX = ["this", "is", "about", "the"];
|
|
245
|
+
const NOUN_CARRIER_SUFFIX = ["and", "its", "history"];
|
|
246
|
+
const NOUN_CARRIER_WORD_INDEX = NOUN_CARRIER_PREFIX.length;
|
|
247
|
+
|
|
248
|
+
/** True when `title` is a single word a general-English POS tagger reads as
|
|
249
|
+
* a common noun (hub articles like "Earth"/"Geology") rather than a proper
|
|
250
|
+
* noun (kin articles like "Hawaii") — false whenever `nlp` is unavailable,
|
|
251
|
+
* never a throw. */
|
|
252
|
+
function readsAsCommonNoun(title, nlp) {
|
|
253
|
+
if (!nlp || typeof nlp.posTags !== "function") return false;
|
|
254
|
+
const t = String(title || "").trim();
|
|
255
|
+
if (!SINGLE_WORD_TITLE_RE.test(t)) return false;
|
|
256
|
+
const carrier = [...NOUN_CARRIER_PREFIX, t.toLowerCase(), ...NOUN_CARRIER_SUFFIX];
|
|
257
|
+
let tags;
|
|
258
|
+
try { tags = nlp.posTags(carrier); } catch { return false; }
|
|
259
|
+
return Array.isArray(tags) && tags[NOUN_CARRIER_WORD_INDEX] === "NOUN";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Stable-sorts fan-out candidates into relevance tiers — never reorders
|
|
263
|
+
* within a tier, only reprioritizes between them — from information the
|
|
264
|
+
* runtime actually has (title strings, the seed topic, an optional POS
|
|
265
|
+
* tagger), never bench-only ground truth:
|
|
266
|
+
* 0. shares a lexical token with `seedTopic` ("Active volcano" ~ "Volcano");
|
|
267
|
+
* 1. everything else, in original document order (the fallback tier);
|
|
268
|
+
* 2. a single-word title a POS tagger reads as a common noun rather than a
|
|
269
|
+
* proper noun — the hub-article signal ("Earth", "Geology");
|
|
270
|
+
* 3. a citation-shaped title (ISBN/DOI prefix, bare year, "Nth century").
|
|
271
|
+
* With no `nlp` adapter registered, tier 2 never fires (everything that
|
|
272
|
+
* would have landed there stays in tier 1) — degrades gracefully, never
|
|
273
|
+
* throws, never drops a candidate. */
|
|
274
|
+
export function relevanceOrder(titles, seedTopic, nlp = null) {
|
|
275
|
+
const list = Array.isArray(titles) ? titles : [];
|
|
276
|
+
const seedTokens = new Set(lexicalTokens(seedTopic));
|
|
277
|
+
const tiers = [[], [], [], []];
|
|
278
|
+
for (const title of list) {
|
|
279
|
+
if (sharesLexicalToken(title, seedTokens)) { tiers[0].push(title); continue; }
|
|
280
|
+
if (isCitationShaped(title)) { tiers[3].push(title); continue; }
|
|
281
|
+
if (readsAsCommonNoun(title, nlp)) { tiers[2].push(title); continue; }
|
|
282
|
+
tiers[1].push(title);
|
|
283
|
+
}
|
|
284
|
+
return [...tiers[0], ...tiers[1], ...tiers[2], ...tiers[3]];
|
|
285
|
+
}
|
|
286
|
+
|
|
213
287
|
/** Queue `article`'s lead-section links at `fromDepth + 1`, subject to the run's
|
|
214
288
|
* depth ceiling, its per-fan-out cap and — crucially — its TOTAL node budget:
|
|
215
289
|
* the number added never pushes grounded+pending past `maxTopics`. Sets
|
|
216
290
|
* `state.nodeCapReached` when the budget (not the depth, not a lack of links)
|
|
217
|
-
* is what stopped the fan-out, so the progress line can say so.
|
|
218
|
-
*
|
|
291
|
+
* is what stopped the fan-out, so the progress line can say so. Candidates are
|
|
292
|
+
* relevance-ordered (relevanceOrder) before the fan-out cap truncates them, so
|
|
293
|
+
* a capped fetch keeps kin articles over generic hubs. Returns the titles it
|
|
294
|
+
* enqueued. */
|
|
219
295
|
async function enqueueFrom(state, article, fromDepth, provider) {
|
|
220
296
|
const childDepth = fromDepth + 1;
|
|
221
297
|
if (childDepth > runMaxDepth(state)) return [];
|
|
@@ -226,10 +302,11 @@ async function enqueueFrom(state, article, fromDepth, provider) {
|
|
|
226
302
|
if (want <= 0) { state.nodeCapReached = true; return []; }
|
|
227
303
|
let linked = null;
|
|
228
304
|
try { linked = await provider.linkedTitles(article.title, { limit: want + 2 }); } catch { linked = null; }
|
|
305
|
+
const ordered = relevanceOrder(linked || [], article.title, defaultNlp());
|
|
229
306
|
const seen = queuedFolds(state);
|
|
230
307
|
if (!state.depths) state.depths = {};
|
|
231
308
|
const added = [];
|
|
232
|
-
for (const title of
|
|
309
|
+
for (const title of ordered) {
|
|
233
310
|
const folded = normFactTerm(title);
|
|
234
311
|
if (!folded || seen.has(folded)) continue;
|
|
235
312
|
seen.add(folded);
|
|
@@ -10,12 +10,21 @@
|
|
|
10
10
|
// built seed payload (scripts/build-chat-seed.mjs), so teach turns, recall,
|
|
11
11
|
// proof chains and the honest miss all run client-side with zero I/O.
|
|
12
12
|
//
|
|
13
|
-
//
|
|
13
|
+
// Three browser traps this file owns so no caller can fall into them:
|
|
14
14
|
// - runTurn defaults `env` to process.env, and a browser has no `process`
|
|
15
15
|
// global — every turn here passes `env: {}` explicitly;
|
|
16
16
|
// - the uuid adapter needs node:crypto — the session id comes from the
|
|
17
17
|
// Web Crypto API instead, with a Date.now fallback for contexts
|
|
18
|
-
// without it
|
|
18
|
+
// without it;
|
|
19
|
+
// - setDigestStructures is imported from adapters/corpus/digest-bank.mjs.
|
|
20
|
+
// Under the bundle build, scripts/build-chat-bundle.mjs swaps that import
|
|
21
|
+
// for a live in-memory twin, and this call actually feeds it the page's
|
|
22
|
+
// embedded structure rows; this file is ALSO imported directly by plain
|
|
23
|
+
// Node (test-e2e/web-chat-memory.test.mjs, exercising the same engine contract
|
|
24
|
+
// without a browser), where the import resolves to the real fs+TOML
|
|
25
|
+
// adapter — its own setDigestStructures is a documented no-op there
|
|
26
|
+
// (see that module's header), so the call is harmless either way, never
|
|
27
|
+
// a load error and never a behavior change on the Node side.
|
|
19
28
|
import { runTurn, vocabExampleHint } from "../../services/chat.mjs";
|
|
20
29
|
import { createInMemoryStore, normFactTerm, loadMemory, readFactRows } from "../../adapters/memory/core.mjs";
|
|
21
30
|
import { serializeFactsJsonl } from "../../adapters/memory/export-jsonl.mjs";
|
|
@@ -24,6 +33,7 @@ import { provenanceTagToSource } from "../../domain/memory/trust.mjs";
|
|
|
24
33
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
25
34
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
26
35
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
36
|
+
import { setDigestStructures } from "../../adapters/corpus/digest-bank.mjs";
|
|
27
37
|
// The reference-pack provider seam: the page registers a fetch-backed
|
|
28
38
|
// provider over public/reference-pack/ so the engine's pack lookups work
|
|
29
39
|
// where the gzipped fs layout cannot (the module's own fs loader degrades to
|
|
@@ -50,11 +60,21 @@ import { openPersistedStore } from "./idb-persist.mjs";
|
|
|
50
60
|
* payload carries the starter vocabulary, so an unseeded session offers the
|
|
51
61
|
* teach pointer instead of an example it cannot answer.
|
|
52
62
|
*
|
|
63
|
+
* `digestStructures` (optional) is the page's build-time-embedded
|
|
64
|
+
* [[structure]] rows from the digest sentence-structure bank (chat-page-viz.mjs's
|
|
65
|
+
* DIGEST_STRUCTURES) — fed once to the bundle's live digest-bank twin so a
|
|
66
|
+
* long "what is X" answer leads with a composed digest instead of always
|
|
67
|
+
* falling back to the flat fact list (the bundle's stub used to return null
|
|
68
|
+
* unconditionally). Harmless to call more than once per page load: the table
|
|
69
|
+
* is module-scope, last write wins, and every session created after this one
|
|
70
|
+
* shares it.
|
|
71
|
+
*
|
|
53
72
|
* Returns { memoryDir, sessionId, turn }. `turn(line)` resolves to
|
|
54
73
|
* { answer, end, record, plan } and threads focus/last/planState between
|
|
55
74
|
* calls exactly as the CLI session does.
|
|
56
75
|
*/
|
|
57
|
-
export function createChatSession({ seedPayload = null, vocabSeeded = false, liveReference = false, onLiveLookup = null, synthesisBudget = 12 } = {}) {
|
|
76
|
+
export function createChatSession({ seedPayload = null, vocabSeeded = false, liveReference = false, onLiveLookup = null, synthesisBudget = 12, digestStructures = null } = {}) {
|
|
77
|
+
setDigestStructures(digestStructures || []);
|
|
58
78
|
const memoryDir = createInMemoryStore();
|
|
59
79
|
// Spread onto the store's own empty payload so a partial seed (individuals
|
|
60
80
|
// and objectProperties only) still carries the classes/prefixes scaffolding
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import { buildStructureTable, digestTerm } from "../../domain/digest/index.mjs";
|
|
16
16
|
import { digestStoreStats, chainsForObjects, isaObjectsOf } from "../../domain/digest/store-stats.mjs";
|
|
17
|
-
import { readFactRows } from "../../adapters/memory/core.mjs";
|
|
17
|
+
import { readFactRows, normFactTerm } from "../../adapters/memory/core.mjs";
|
|
18
|
+
import { factTermVariants } from "../../services/chat.mjs";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Digest one term end to end from fact rows and a pre-parsed structure table.
|
|
@@ -40,14 +41,18 @@ export function digestTermFromRowsBrowser(term, termRows, allRows, structures, o
|
|
|
40
41
|
* in-browser surface already holds (the ledger's embedded PAYLOAD, the ledger
|
|
41
42
|
* dock's live memoryDir.payload, the research session's store). Scans the
|
|
42
43
|
* payload once for the term's own rows and the whole-store statistics, so a
|
|
43
|
-
* caller never has to run readFactRows itself.
|
|
44
|
+
* caller never has to run readFactRows itself. Matches `term` against a fact's
|
|
45
|
+
* subject through the same spelling-variant fold `factTermVariants` gives the
|
|
46
|
+
* "what is X" fact reader (plural/irregular-plural folding), so a term seeded
|
|
47
|
+
* as "dog" is still found when queried as "dogs".
|
|
44
48
|
*
|
|
45
49
|
* Returns the render-ready view (see digestViewFromArticle) or null when the
|
|
46
50
|
* term has no rows, no structures were supplied, or the selector kept nothing.
|
|
47
51
|
*/
|
|
48
52
|
export function digestTermFromPayloadBrowser(payload, term, structures, opts = {}) {
|
|
49
53
|
const rows = readFactRows(payload || { individuals: [], objectProperties: [] });
|
|
50
|
-
const
|
|
54
|
+
const variants = factTermVariants(normFactTerm, term);
|
|
55
|
+
const termRows = rows.filter((r) => variants.has(r.subject));
|
|
51
56
|
if (!termRows.length) return null;
|
|
52
57
|
const article = digestTermFromRowsBrowser(term, termRows, rows, structures, opts);
|
|
53
58
|
return digestViewFromArticle(article);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// graph-ask-browser-entry.mjs — the public, browser-safe entry point for
|
|
2
|
+
// tmct's CODE-graph query engine (`./ask-browser` package export).
|
|
3
|
+
//
|
|
4
|
+
// A plain re-export, zero side effects, zero `globalThis` assignment: unlike
|
|
5
|
+
// this directory's other *-browser-entry.mjs files (built into an IIFE and
|
|
6
|
+
// inlined into a specific tmct page), this one is meant to be imported by a
|
|
7
|
+
// DOWNSTREAM consumer's own bundler (e.g. seonix's website chat panel) as a
|
|
8
|
+
// genuine ESM module, not inlined by tmct's own build scripts.
|
|
9
|
+
//
|
|
10
|
+
// It exists because the package root (`src/services/index.mjs`, and its
|
|
11
|
+
// `.`/exports subpath) also runs Node-side composition wiring at module-load
|
|
12
|
+
// time — `setDefaultNlpAdapter(nlpAdapter)` from `../adapters/ask-nlp.mjs`
|
|
13
|
+
// (wink-nlp, a ~4MB model) and `setConstructionBanks(readConstructionFiles)`
|
|
14
|
+
// from `../adapters/corpus/construction-banks.mjs` (fs reads) — neither of
|
|
15
|
+
// which can run or bundle in a browser. This file imports only ask.mjs and
|
|
16
|
+
// graph-service.mjs directly, so none of that wiring is reachable from here.
|
|
17
|
+
//
|
|
18
|
+
// Verified with `esbuild --bundle --platform=browser --format=esm`
|
|
19
|
+
// (scripts/verify-ask-browser-entry.mjs). ask()'s optional strategies
|
|
20
|
+
// (ace.mjs, constructions.mjs) and the wink lemma/POS adapter are all reached
|
|
21
|
+
// through nlp-registry.mjs's `defaultNlp()` registry seam and each call
|
|
22
|
+
// site's own `typeof X !== "undefined"` guard, not a static import, so a
|
|
23
|
+
// consumer bundling this entry point alone never links wink-nlp or any
|
|
24
|
+
// fs-reading corpus module — none of the ask-nlp.mjs/ace.mjs/
|
|
25
|
+
// constructions.mjs/construction-banks.mjs/digest-bank.mjs family of stubs
|
|
26
|
+
// scripts/build-ask-bundle.mjs needs for chat.mjs is required here.
|
|
27
|
+
//
|
|
28
|
+
// One real gap remains: `createGraphService` pulls in source-slice.mjs,
|
|
29
|
+
// which imports `node:path` (join/resolve/sep) for span math, so a browser
|
|
30
|
+
// bundler with no Node shims will fail on that one specifier. Apply the
|
|
31
|
+
// same generic `stubNodeBuiltins` plugin scripts/lib/browser-bundle.mjs
|
|
32
|
+
// already exports for tmct's own bundles (or an equivalent node:path shim —
|
|
33
|
+
// e.g. esbuild's own `--define`/polyfill options, or a bundler that already
|
|
34
|
+
// ships a node:path shim, as most do). Re-run
|
|
35
|
+
// `node scripts/verify-ask-browser-entry.mjs` (clean) and
|
|
36
|
+
// `node scripts/verify-ask-browser-entry.mjs --with-stubs` (with the shim)
|
|
37
|
+
// after touching ask.mjs's or graph-service.mjs's import graph — if the
|
|
38
|
+
// no-stub run ever passes clean, or a new specifier shows up, update this
|
|
39
|
+
// comment to match what esbuild actually reports.
|
|
40
|
+
export { ask, resolveObject } from "../../domain/ask.mjs";
|
|
41
|
+
export { createGraphService } from "../../adapters/providers/graph-service.mjs";
|