@polycode-projects/the-mechanical-code-talker 3.3.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -8
- package/corpus/reference/index.json.gz +0 -0
- package/corpus/reference/manifest.json +8 -8
- package/corpus/reference/shards/ref-00.jsonl.gz +0 -0
- package/corpus/sprites/src/sprite-facts.jsonl +52 -0
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +53 -13
- package/corpus/worlds/shards/greyvale-museum.jsonl.gz +0 -0
- package/corpus/worlds/shards/lantern-cottage.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-garden.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-hollow.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-warren.jsonl.gz +0 -0
- package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
- package/corpus/worlds/src/greyvale-museum.jsonl +136 -0
- package/corpus/worlds/src/lantern-cottage.jsonl +60 -0
- package/corpus/worlds/src/mud-garden.jsonl +34 -1
- package/corpus/worlds/src/mud-hollow.jsonl +82 -0
- package/corpus/worlds/src/mud-warren.jsonl +124 -0
- package/corpus/worlds/src/spider-fly.jsonl +1 -1
- package/data/sprites/animal-icon.toml +11 -5
- package/data/sprites/book-icon.toml +9 -5
- package/data/sprites/cabinet-icon.toml +10 -5
- package/data/sprites/cellar-icon.toml +16 -6
- package/data/sprites/container-icon.toml +7 -3
- package/data/sprites/desk-icon.toml +8 -5
- package/data/sprites/dog-icon.toml +8 -5
- package/data/sprites/dog-with-colour-icon.toml +13 -12
- package/data/sprites/drawing-room-icon.toml +14 -5
- package/data/sprites/egg-icon.toml +6 -3
- package/data/sprites/fly-icon.toml +10 -5
- package/data/sprites/furniture-icon.toml +5 -3
- package/data/sprites/garden-icon.toml +10 -3
- package/data/sprites/key-icon.toml +3 -1
- package/data/sprites/kitchen-icon.toml +15 -6
- package/data/sprites/lamp-icon.toml +9 -4
- package/data/sprites/letter-icon.toml +7 -4
- package/data/sprites/library-icon.toml +14 -3
- package/data/sprites/pan-icon.toml +7 -3
- package/data/sprites/person-icon.toml +6 -2
- package/data/sprites/poodle-icon.toml +1 -1
- package/data/sprites/portable-icon.toml +6 -4
- package/data/sprites/portrait-icon.toml +7 -4
- package/data/sprites/room-icon.toml +11 -2
- package/data/sprites/spider-icon.toml +9 -3
- package/data/sprites/study-icon.toml +10 -2
- package/package.json +5 -4
- package/src/adapters/p2p/webrtc-transport.mjs +146 -0
- package/src/domain/game-config.mjs +26 -5
- package/src/domain/grammar/ace.mjs +40 -4
- package/src/domain/grammar/lexicon-core.json +2 -1
- package/src/domain/grammar/lexicon.mjs +31 -0
- package/src/domain/memory/trust.mjs +6 -4
- package/src/domain/p2p/facts.mjs +81 -0
- package/src/domain/p2p/peer-id.mjs +32 -0
- package/src/domain/p2p/provenance-relabel.mjs +26 -0
- package/src/domain/p2p/sync-filter.mjs +31 -0
- package/src/domain/p2p/wire.mjs +123 -0
- package/src/domain/reference-pack.mjs +31 -7
- package/src/domain/spider-fly-world.mjs +1 -1
- package/src/domain/sprite-map.mjs +10 -2
- package/src/domain/sprite-templates.mjs +8 -6
- package/src/services/adventure-editor.mjs +10 -2
- package/src/services/adventure-viz.mjs +309 -73
- package/src/services/adventure.mjs +1034 -330
- package/src/services/chat-page-viz.mjs +1097 -16
- package/src/services/chat.mjs +99 -18
- package/src/services/code-explorer-viz.mjs +52 -14
- package/src/services/extract-facts.mjs +4 -7
- package/src/services/ingest-viz.mjs +54 -26
- package/src/services/ledger-viz.mjs +105 -56
- package/src/services/memory-panel-viz.mjs +24 -0
- package/src/services/mud-editor.mjs +313 -0
- package/src/services/mud-turn.mjs +264 -87
- package/src/services/mud-viz.mjs +2606 -384
- package/src/services/p2p-room.mjs +559 -0
- package/src/services/plan-viz.mjs +119 -61
- package/src/services/research-viz.mjs +151 -57
- package/src/services/spider-fly-turn.mjs +1 -1
- package/src/services/spider-fly-viz.mjs +67 -43
- package/src/services/sprite-catalog-viz.mjs +175 -51
- package/src/services/viz-theme.mjs +15 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +109 -109
- package/src/surfaces/web/mud-browser-entry.mjs +226 -18
- package/src/surfaces/web/p2p-browser-entry.mjs +39 -0
package/src/services/chat.mjs
CHANGED
|
@@ -7758,6 +7758,13 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
7758
7758
|
// teach frame, in a store written before the spellings converged) is
|
|
7759
7759
|
// found by the curated spelling it means.
|
|
7760
7760
|
let hits = predicate ? subjectHits.filter((f) => normFactPredicate(f.predicate) === predicate) : subjectHits;
|
|
7761
|
+
// A bare "what is X" answered ONLY by corpus-weak ConceptNet association(s)
|
|
7762
|
+
// is the hedged "possibly: X is related to Y" guess, not a definition — treated
|
|
7763
|
+
// as no hit at all so the honest-miss cascade below (teach lane, reference-pack/
|
|
7764
|
+
// Wikipedia fallback) still gets a turn, instead of the hedge silently standing
|
|
7765
|
+
// in as "already answered". A predicate-specific ask ("X used for") is untouched:
|
|
7766
|
+
// scoped to the SAME undifferentiated shape the reference-pack fallback serves.
|
|
7767
|
+
if (!predicate && hits.length && !hits.some(isRealGrounding)) hits = [];
|
|
7761
7768
|
if (!hits.length) {
|
|
7762
7769
|
// The subject itself is known — as a fact subject, or as the standing
|
|
7763
7770
|
// focus a pronoun just resolved to — but not under this specific
|
|
@@ -7777,7 +7784,9 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
7777
7784
|
// ishmael"): surface those reverse relations rather than missing, the same
|
|
7778
7785
|
// facts "what do you know about X" would list.
|
|
7779
7786
|
if (!predicate) {
|
|
7780
|
-
|
|
7787
|
+
let objectHits = rankByBiasThenTrust((await factRows(memoryDir, cache)).filter((f) => variants.has(f.object)), biasByBundle);
|
|
7788
|
+
// Same corpus-weak-only demotion as the subject-side hits above.
|
|
7789
|
+
if (objectHits.length && !objectHits.some(isRealGrounding)) objectHits = [];
|
|
7781
7790
|
if (objectHits.length) {
|
|
7782
7791
|
const objLines = objectHits.map(renderFactLine);
|
|
7783
7792
|
const objShown = objLines.slice(0, FACT_ANSWER_CAP);
|
|
@@ -8343,6 +8352,16 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
8343
8352
|
// besides. The adventure's own where/openness readers answer the legitimate
|
|
8344
8353
|
// in-game questions from the world fold.
|
|
8345
8354
|
hits = hits.filter((f) => !WORLD_INTERNAL_PREDICATES.has(f.predicate));
|
|
8355
|
+
// A corpus-weak-only result set (every hit resolved ONLY through
|
|
8356
|
+
// ConceptNet's /r/RelatedTo tier) still composes its hedged "possibly"
|
|
8357
|
+
// lines below exactly as before — this reader's whole job is showing
|
|
8358
|
+
// those, weak or not, so they're never suppressed from the text. But the
|
|
8359
|
+
// result is flagged `weakOnly` so the shared honest-miss cascade (the
|
|
8360
|
+
// reference-pack/Wikipedia fallback, below in runAsk) still gets a turn
|
|
8361
|
+
// instead of the hedge silently standing in as "already answered" —
|
|
8362
|
+
// mirroring isRealGrounding's other two call sites without breaking this
|
|
8363
|
+
// one's own job.
|
|
8364
|
+
const weakOnly = hits.length > 0 && !hits.some(isRealGrounding);
|
|
8346
8365
|
// A genuinely empty result here is a real miss: "what do you know about
|
|
8347
8366
|
// the last commit" needs a TEACH-OFFER, not a bare wall — added as a LATE
|
|
8348
8367
|
// runTurn-level addition, below, alongside the sibling "what is X" offer,
|
|
@@ -8393,11 +8412,16 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
8393
8412
|
const header = `${hits.length} remembered fact${hits.length === 1 ? "" : "s"} about ${term}`
|
|
8394
8413
|
+ `${viaSubtype ? " (including its known subtypes)" : ""}:`;
|
|
8395
8414
|
const { lines, grouped } = senseSplitFactList(hits, rows, variants, { indent: " " });
|
|
8396
|
-
if (grouped) return { ...grouped, text: `${header}\n${grouped.text}
|
|
8415
|
+
if (grouped) return { ...grouped, text: `${header}\n${grouped.text}`, ...(weakOnly ? { weakOnly: true } : {}) };
|
|
8397
8416
|
const shown = lines.slice(0, FACT_ANSWER_CAP);
|
|
8398
8417
|
const rest = lines.slice(FACT_ANSWER_CAP);
|
|
8399
8418
|
const extra = rest.length ? `\n …and ${rest.length} more — say 'more' to see them.` : "";
|
|
8400
|
-
return {
|
|
8419
|
+
return {
|
|
8420
|
+
text: `${header}\n${shown.join("\n")}${extra}`,
|
|
8421
|
+
replace: true,
|
|
8422
|
+
...(rest.length ? { pending: { items: rest.map((l) => l.trim()), noun: "facts" } } : {}),
|
|
8423
|
+
...(weakOnly ? { weakOnly: true } : {}),
|
|
8424
|
+
};
|
|
8401
8425
|
}
|
|
8402
8426
|
return null;
|
|
8403
8427
|
}
|
|
@@ -10674,14 +10698,37 @@ async function curatedDefinitionAnswer(query, envelope, { memoryDir, lexicon })
|
|
|
10674
10698
|
return { text: `${def} (source: corpus/seon)`, term };
|
|
10675
10699
|
}
|
|
10676
10700
|
|
|
10701
|
+
/** True when `f` carries a source better than the ConceptNet `/r/RelatedTo`
|
|
10702
|
+
* corpus-weak tier — or no recorded source type at all (the conservative,
|
|
10703
|
+
* pre-existing read for legacy rows). A fact resolved ONLY by corpus-weak
|
|
10704
|
+
* source(s) is the hedged "possibly: X is related to Y" guess, not an
|
|
10705
|
+
* actual definition; callers treat it as no grounding at all rather than an
|
|
10706
|
+
* answer, so it can never by itself skip the honest-miss cascade below
|
|
10707
|
+
* (the teach lane, the reference-pack/Wikipedia fallback). Shared by the
|
|
10708
|
+
* bare "what is X" reader, the "what do you know about X" reader (both in
|
|
10709
|
+
* factAnswerReaders) and the learn-on-miss gate (cleanMissPackKey/
|
|
10710
|
+
* cleanMissLiveKey) so none of them can ever disagree. */
|
|
10711
|
+
function isRealGrounding(f) {
|
|
10712
|
+
const types = f.sourceTypes || [];
|
|
10713
|
+
return types.length === 0 || types.some((t) => t !== "corpusWeak");
|
|
10714
|
+
}
|
|
10715
|
+
|
|
10677
10716
|
// ---- learn-on-miss: the shipped child + reference packs behind the cleanest miss ----
|
|
10678
10717
|
|
|
10718
|
+
/** A remembered fact naming `term` is real grounding only when SOME source
|
|
10719
|
+
* behind it outranks the ConceptNet `/r/RelatedTo` corpus-weak tier — see
|
|
10720
|
+
* isRealGrounding just above. */
|
|
10721
|
+
function hasNonWeakGrounding(rows, variants) {
|
|
10722
|
+
return rows.some((f) => (variants.has(f.subject) || variants.has(f.object)) && isRealGrounding(f));
|
|
10723
|
+
}
|
|
10724
|
+
|
|
10679
10725
|
/** The learn-on-miss gate, shared by the child-pack hook, the articled
|
|
10680
10726
|
* reference hook and the bare-form fallback so the three can never disagree.
|
|
10681
10727
|
* Passes only on the CLEANEST miss: a definition-shaped term the lexicon
|
|
10682
|
-
* knows, resolving to no graph entity and no
|
|
10683
|
-
* only then, may a pack provider be
|
|
10684
|
-
* byte-identically to a pack-less
|
|
10728
|
+
* knows, resolving to no graph entity and no fact grounded above the
|
|
10729
|
+
* corpus-weak tier — then, and only then, may a pack provider be
|
|
10730
|
+
* consulted. Null means the turn proceeds byte-identically to a pack-less
|
|
10731
|
+
* run. */
|
|
10685
10732
|
async function cleanMissPackKey(term, { graph, memoryDir, lexicon, cache }) {
|
|
10686
10733
|
if (!term || !memoryDir) return null;
|
|
10687
10734
|
let key = null;
|
|
@@ -10695,7 +10742,7 @@ async function cleanMissPackKey(term, { graph, memoryDir, lexicon, cache }) {
|
|
|
10695
10742
|
const variants = factTermVariants(normFactTerm, term);
|
|
10696
10743
|
variants.add(key);
|
|
10697
10744
|
const rows = await factRows(memoryDir, cache);
|
|
10698
|
-
if (rows
|
|
10745
|
+
if (hasNonWeakGrounding(rows, variants)) return null;
|
|
10699
10746
|
// A taught RULE that owns this term outranks any pack load: surfacing
|
|
10700
10747
|
// unrelated conceptnet content over the user's own taught concept is worse
|
|
10701
10748
|
// than the honest miss the decline leaves standing.
|
|
@@ -10723,10 +10770,10 @@ async function referencePackMissAnswer(term, { graph, memoryDir, lexicon, env, c
|
|
|
10723
10770
|
}
|
|
10724
10771
|
|
|
10725
10772
|
/** The LIVE variant of cleanMissPackKey — the same resolveEntity and
|
|
10726
|
-
*
|
|
10727
|
-
* lexicon-membership wall: a word the lexicon has never met is
|
|
10728
|
-
* the live lookup exists for. Null means the turn proceeds
|
|
10729
|
-
* to a live-off run. */
|
|
10773
|
+
* above-corpus-weak grounding checks, but through cleanMissLiveTerm, which
|
|
10774
|
+
* drops the lexicon-membership wall: a word the lexicon has never met is
|
|
10775
|
+
* exactly what the live lookup exists for. Null means the turn proceeds
|
|
10776
|
+
* byte-identically to a live-off run. */
|
|
10730
10777
|
async function cleanMissLiveKey(term, { graph, memoryDir, lexicon, cache }) {
|
|
10731
10778
|
if (!term || !memoryDir) return null;
|
|
10732
10779
|
let key = null;
|
|
@@ -10738,7 +10785,7 @@ async function cleanMissLiveKey(term, { graph, memoryDir, lexicon, cache }) {
|
|
|
10738
10785
|
const variants = factTermVariants(normFactTerm, term);
|
|
10739
10786
|
variants.add(key);
|
|
10740
10787
|
const rows = await factRows(memoryDir, cache);
|
|
10741
|
-
if (rows
|
|
10788
|
+
if (hasNonWeakGrounding(rows, variants)) return null;
|
|
10742
10789
|
return key;
|
|
10743
10790
|
}
|
|
10744
10791
|
|
|
@@ -12641,6 +12688,13 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
12641
12688
|
let via = "composed";
|
|
12642
12689
|
let recordMiss = miss;
|
|
12643
12690
|
let factPending = null; // a truncated fact listing's held remainder (for "more" paging)
|
|
12691
|
+
// Set when the memory-facts lane (W4, below) answered from a corpus-weak-only
|
|
12692
|
+
// hit set (factAnswer's KNOW_ABOUT_RE reader, `weakOnly`) — the hedged
|
|
12693
|
+
// "possibly" answer still stands as `answer`, but `via` is deliberately left
|
|
12694
|
+
// at "composed" so the honest-miss cascade further down still gets a turn,
|
|
12695
|
+
// and this flag lets the LEARN-ON-MISS PACKS lane resolve a reference-pack
|
|
12696
|
+
// term for THIS query shape (which metaTermOf alone never recognizes).
|
|
12697
|
+
let factLaneWeakOnly = false;
|
|
12644
12698
|
// scm-svf1/cardinality-monotonicity/
|
|
12645
12699
|
// cax-maxc0's LIVE proof chases (factReadBack) have no persisted Fact to
|
|
12646
12700
|
// attach trust.mjs's entailed hook to, so they compute
|
|
@@ -13156,9 +13210,18 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
13156
13210
|
// the empty-graph orientation pointer below stays off it — a pronoun
|
|
13157
13211
|
// decline with an index pointer under it is two answers to one turn.
|
|
13158
13212
|
if (fact.selfContainedMiss) selfContainedMiss = true;
|
|
13159
|
-
|
|
13213
|
+
// A `weakOnly` hit (the "what do you know about X" reader's
|
|
13214
|
+
// corpus-weak-only hedge) is shown as `answer` above like any other
|
|
13215
|
+
// fact-lane hit, but doesn't count as REAL grounding — via/recordMiss
|
|
13216
|
+
// stay untouched so the honest-miss cascade below (the reference-pack
|
|
13217
|
+
// lane in particular) still gets a turn at replacing the hedge with a
|
|
13218
|
+
// real answer, exactly the discipline isRealGrounding's other two call
|
|
13219
|
+
// sites already apply.
|
|
13220
|
+
if (!fact.miss && !fact.weakOnly) {
|
|
13160
13221
|
via = "fact";
|
|
13161
13222
|
recordMiss = false;
|
|
13223
|
+
} else if (fact.weakOnly) {
|
|
13224
|
+
factLaneWeakOnly = true;
|
|
13162
13225
|
}
|
|
13163
13226
|
if (fact.pending) factPending = fact.pending; // a truncated fact list → paginable remainder
|
|
13164
13227
|
if (typeof fact.trust === "number") entailedTrust = fact.trust; // live-chase trust (see the `entailedTrust` declaration above)
|
|
@@ -13465,13 +13528,19 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
13465
13528
|
// pack's article speak, cited as before. Both packs missing leaves the
|
|
13466
13529
|
// honest miss byte-identical.
|
|
13467
13530
|
if (miss && recordMiss && via === "composed" && memoryDir) {
|
|
13468
|
-
|
|
13531
|
+
// metaTermOf only recognizes the "what is X"/"define X" shapes — a
|
|
13532
|
+
// "what do you know about X" query only reaches this lane at all when
|
|
13533
|
+
// its own reader (above) answered with a corpus-weak-only hedge
|
|
13534
|
+
// (factLaneWeakOnly), so the KNOW_ABOUT_RE term is resolved directly the
|
|
13535
|
+
// same way the TEACH-OFFER lane below already does for that shape.
|
|
13536
|
+
const refTerm = metaTermOf(query, envelope)
|
|
13537
|
+
|| (factLaneWeakOnly ? expandContractions(String(query).trim()).match(KNOW_ABOUT_RE)?.[1]?.trim() : null);
|
|
13469
13538
|
const key = refTerm ? await cleanMissPackKey(refTerm, { graph, memoryDir, lexicon, cache }) : null;
|
|
13470
13539
|
const learned = key ? await childPackFactsForKey(key, { memoryDir, env, cache, synthesisBudget }) : null;
|
|
13471
13540
|
if (learned) {
|
|
13472
13541
|
const fact = (await factAnswer(memoryDir, query, envelope, miss, biasByBundle, cache, newFocus?.label))
|
|
13473
13542
|
?? (await factReadBack(memoryDir, query, envelope, miss, graph, newFocus?.label, biasByBundle, cache));
|
|
13474
|
-
if (fact && !fact.miss) {
|
|
13543
|
+
if (fact && !fact.miss && !fact.weakOnly) {
|
|
13475
13544
|
answer = fact.replace ? fact.text : `${answer}\n${fact.text}`;
|
|
13476
13545
|
via = "fact";
|
|
13477
13546
|
recordMiss = false;
|
|
@@ -13558,7 +13627,12 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
13558
13627
|
const ent = knowAboutTerm ? null : await resolveEntity(graph, offerTerm);
|
|
13559
13628
|
if (!ent) {
|
|
13560
13629
|
const variants = factTermVariants(normFactTerm, offerTerm);
|
|
13561
|
-
|
|
13630
|
+
// A corpus-weak-only match (ConceptNet's /r/RelatedTo hedge) is not
|
|
13631
|
+
// real grounding — same isRealGrounding discipline as the bare
|
|
13632
|
+
// "what is X" reader and the learn-on-miss gate, so a term with
|
|
13633
|
+
// nothing but a loose association still gets the "teach me" offer
|
|
13634
|
+
// rather than being silently counted as already known.
|
|
13635
|
+
const known = hasNonWeakGrounding(await factRows(memoryDir, cache), variants);
|
|
13562
13636
|
if (!known) teachOffer = unknownVocabTermOffer(cleanTerm);
|
|
13563
13637
|
}
|
|
13564
13638
|
}
|
|
@@ -14698,11 +14772,18 @@ const LIST_KNOWN_KINDS_RE = /^list\s+(?:the\s+|all\s+(?:the\s+)?)?(?!facts?\b|th
|
|
|
14698
14772
|
// conditional over a NAMED subject or an arbitrary property is a rule, not a
|
|
14699
14773
|
// subclass fact, and stays outside this frame.
|
|
14700
14774
|
const UNIVERSAL_CONDITIONAL_RE = /^if\s+(?:something|somebody|someone|anything)\s+is\s+an?\s+([\w-]+)\s*,?\s*(?:then\s+)?(?:it|they)\s+(?:is|are)\s+an?\s+([\w-]+)[.!?\s]*$/i;
|
|
14701
|
-
function rewriteVocabOpener(line) {
|
|
14775
|
+
function rewriteVocabOpener(line, { adventureLive = false } = {}) {
|
|
14702
14776
|
let m = line.match(DESIRE_ABOUT_RE) || line.match(TELL_ABOUT_VARIANT_RE);
|
|
14703
14777
|
if (m) return `tell me about ${m[1].trim()}`;
|
|
14704
14778
|
m = line.match(UNIVERSAL_CONDITIONAL_RE);
|
|
14705
14779
|
if (m) return `every ${m[1]} is ${indefiniteArticleFor(m[2])} ${m[2]}`;
|
|
14780
|
+
// "what <noun> do you know [about]" shares its surface form with a live
|
|
14781
|
+
// adventure's own personal-knowledge asides ("what food do you know
|
|
14782
|
+
// about") — those ask what the CHARACTER has learned in-world, not for
|
|
14783
|
+
// the taught CLASS's members, so this closed-set enumeration rewrite
|
|
14784
|
+
// stands down while a world is live and leaves the line for the
|
|
14785
|
+
// adventure lane to recognise (or not) on its own terms.
|
|
14786
|
+
if (adventureLive) return null;
|
|
14706
14787
|
m = line.match(KNOWN_KINDS_RE) || line.match(LIST_KNOWN_KINDS_RE);
|
|
14707
14788
|
if (m) {
|
|
14708
14789
|
const noun = teachableSubjectOf(m[1]);
|
|
@@ -14977,7 +15058,7 @@ async function dispatchTurn(input, { config, source = defaultSource, graph = nul
|
|
|
14977
15058
|
const indirectMatch = line.match(INDIRECT_REQUEST_RE);
|
|
14978
15059
|
const indirectLine = indirectMatch ? indirectMatch[1].trim() : line;
|
|
14979
15060
|
const preRewriteLine = rewriteEntryPointQuestion(indirectLine) || rewriteProveThat(indirectLine)
|
|
14980
|
-
|| rewriteVocabOpener(indirectLine) || indirectLine;
|
|
15061
|
+
|| rewriteVocabOpener(indirectLine, { adventureLive: Boolean(planState?.adventure) }) || indirectLine;
|
|
14981
15062
|
// rewriteUsesAsBaseFrame's discontiguous-frame rewrite: applied here, once,
|
|
14982
15063
|
// before ANY dispatch lane sees the text. Null (no-op) for every turn that
|
|
14983
15064
|
// doesn't match one of the four discontiguous shapes.
|
|
@@ -129,6 +129,7 @@ const CLIENT_JS = String.raw`
|
|
|
129
129
|
dockNote: document.getElementById("dock-note"),
|
|
130
130
|
source: document.getElementById("source-name"),
|
|
131
131
|
seedStatus: document.getElementById("seed-status"),
|
|
132
|
+
factTotal: document.getElementById("fact-total-value"),
|
|
132
133
|
};
|
|
133
134
|
var session = null;
|
|
134
135
|
|
|
@@ -143,6 +144,15 @@ const CLIENT_JS = String.raw`
|
|
|
143
144
|
var parts = [s.individuals + " individuals", s.edges + " edges"];
|
|
144
145
|
var cls = s.classes.slice(0, 4).map(function (c) { return c[1] + " " + c[0]; });
|
|
145
146
|
els.stats.textContent = parts.concat(cls).join(" · ");
|
|
147
|
+
renderFactTotal(data);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Everything the chat can answer from: the graph's own edges plus the
|
|
151
|
+
// general-knowledge seed, which arrives later and re-renders this.
|
|
152
|
+
function renderFactTotal(data) {
|
|
153
|
+
if (!els.factTotal) return;
|
|
154
|
+
var edges = (data && data.ledger && data.ledger.stats && data.ledger.stats.edges) || 0;
|
|
155
|
+
els.factTotal.textContent = (edges + seedState.facts).toLocaleString();
|
|
146
156
|
}
|
|
147
157
|
|
|
148
158
|
function renderFocusRows(data) {
|
|
@@ -182,7 +192,13 @@ const CLIENT_JS = String.raw`
|
|
|
182
192
|
renderHints(data);
|
|
183
193
|
}
|
|
184
194
|
|
|
195
|
+
function clearEmptyState() {
|
|
196
|
+
var e = document.getElementById("chat-empty");
|
|
197
|
+
if (e) e.remove();
|
|
198
|
+
}
|
|
199
|
+
|
|
185
200
|
function appendTurn(role, text) {
|
|
201
|
+
clearEmptyState();
|
|
186
202
|
var div = document.createElement("div");
|
|
187
203
|
div.className = "turn turn-" + role;
|
|
188
204
|
div.innerHTML = '<span class="who">' + (role === "you" ? "you" : "tmct") + '</span><span class="said">' + esc(text) + '</span>';
|
|
@@ -190,10 +206,16 @@ const CLIENT_JS = String.raw`
|
|
|
190
206
|
els.log.scrollTop = els.log.scrollHeight;
|
|
191
207
|
}
|
|
192
208
|
|
|
193
|
-
function
|
|
209
|
+
function renderEmptyState() {
|
|
194
210
|
var div = document.createElement("div");
|
|
195
|
-
div.className = "
|
|
196
|
-
div.
|
|
211
|
+
div.className = "chat-empty";
|
|
212
|
+
div.id = "chat-empty";
|
|
213
|
+
div.innerHTML = api
|
|
214
|
+
? '<p class="chat-empty-head">Ask the graph, or ask it anything</p>'
|
|
215
|
+
+ '<p>Ask about the code graph on the left, or about anything its general knowledge covers, like “what is a queue”.</p>'
|
|
216
|
+
+ '<p class="chat-empty-hint">Try one of the questions below, or type your own.</p>'
|
|
217
|
+
: '<p class="chat-empty-head">Static view</p>'
|
|
218
|
+
+ '<p>This page shows a fixed snapshot of the graph. The live chat is not available here.</p>';
|
|
197
219
|
els.log.appendChild(div);
|
|
198
220
|
}
|
|
199
221
|
|
|
@@ -203,6 +225,10 @@ const CLIENT_JS = String.raw`
|
|
|
203
225
|
// readSeed under the desktop shell (a file:// page cannot fetch). The
|
|
204
226
|
// status bar narrates the load; a missing or failing seed leaves the chat
|
|
205
227
|
// graph-only and says so, never broken.
|
|
228
|
+
// The build's own content hash for the seed rides in the URL, so the service
|
|
229
|
+
// worker's cache-first read can only return the copy this page asked for.
|
|
230
|
+
// Empty under the desktop shell, which reads the seed off disk instead.
|
|
231
|
+
var SEED_QUERY = DATA.seedStamp ? "?b=" + DATA.seedStamp : "";
|
|
206
232
|
var seedState = { status: api ? "loading" : "absent", payload: null, facts: 0 };
|
|
207
233
|
function seedNote(text) { if (els.seedStatus) els.seedStatus.textContent = text; }
|
|
208
234
|
function mbText(n) { return (n / 1048576).toFixed(1); }
|
|
@@ -232,7 +258,7 @@ const CLIENT_JS = String.raw`
|
|
|
232
258
|
seedNote("loading general knowledge…");
|
|
233
259
|
text = await window.tmctDesktop.readSeed();
|
|
234
260
|
} else {
|
|
235
|
-
text = await fetchTextWithProgress("./chat-seed.json", function (loaded, total) {
|
|
261
|
+
text = await fetchTextWithProgress("./chat-seed.json" + SEED_QUERY, function (loaded, total) {
|
|
236
262
|
seedNote("loading general knowledge… " + mbText(loaded) + (total ? " of " + mbText(total) : "") + " MB");
|
|
237
263
|
});
|
|
238
264
|
}
|
|
@@ -241,13 +267,14 @@ const CLIENT_JS = String.raw`
|
|
|
241
267
|
seedState.facts = (seedState.payload.individuals || []).filter(function (i) { return i.class === "Fact"; }).length;
|
|
242
268
|
seedState.status = "ready";
|
|
243
269
|
seedNote("general knowledge: " + seedState.facts + " facts");
|
|
270
|
+
renderFactTotal(DATA);
|
|
244
271
|
return;
|
|
245
272
|
}
|
|
246
273
|
} catch (e) {
|
|
247
274
|
console.warn("tmct code explorer: chat-seed unavailable, continuing graph-only", e);
|
|
248
275
|
}
|
|
249
276
|
seedState.status = "absent";
|
|
250
|
-
seedNote("graph-only
|
|
277
|
+
seedNote("graph-only, general knowledge unavailable");
|
|
251
278
|
}
|
|
252
279
|
var seedPromise = api ? loadSeed() : Promise.resolve();
|
|
253
280
|
|
|
@@ -315,6 +342,7 @@ const CLIENT_JS = String.raw`
|
|
|
315
342
|
window.__CODE_EXPLORER__ = DATA;
|
|
316
343
|
if (updateSource && els.source) els.source.textContent = picked.name || "(loaded graph)";
|
|
317
344
|
els.log.innerHTML = "";
|
|
345
|
+
renderEmptyState();
|
|
318
346
|
mountView(DATA);
|
|
319
347
|
}
|
|
320
348
|
} finally { btn.disabled = false; }
|
|
@@ -324,12 +352,11 @@ const CLIENT_JS = String.raw`
|
|
|
324
352
|
wirePicker("open-repo", "openRepo", true);
|
|
325
353
|
|
|
326
354
|
if (!api) {
|
|
327
|
-
if (els.dockNote) els.dockNote.textContent = "static view
|
|
355
|
+
if (els.dockNote) els.dockNote.textContent = "static view, live chat unavailable here.";
|
|
328
356
|
if (els.input) els.input.disabled = true;
|
|
329
357
|
seedNote("static view");
|
|
330
|
-
} else {
|
|
331
|
-
appendNote("Ask about this code graph — or anything its general knowledge covers, like “what is a queue”.");
|
|
332
358
|
}
|
|
359
|
+
renderEmptyState();
|
|
333
360
|
|
|
334
361
|
mountView(DATA);
|
|
335
362
|
})();
|
|
@@ -344,7 +371,7 @@ const CLIENT_JS = String.raw`
|
|
|
344
371
|
* pointing at the desktop app's README section — the desktop shell itself
|
|
345
372
|
* renders this page too and passes `false`, since it has nothing to point at.
|
|
346
373
|
*/
|
|
347
|
-
export function renderCodeExplorerHtml(data, { bundleInline = "", bundleAvailable = false, winkLoaderInline = "", sourceName = "demo code graph", showDesktopLink = false } = {}) {
|
|
374
|
+
export function renderCodeExplorerHtml(data, { bundleInline = "", bundleAvailable = false, winkLoaderInline = "", sourceName = "demo code graph", showDesktopLink = false, seedStamp = "" } = {}) {
|
|
348
375
|
const payloadJson = embedJson(data.payload);
|
|
349
376
|
const dataJson = embedJson({ ledger: data.ledger, hints: data.hints, focus: data.focus, meta: data.meta });
|
|
350
377
|
|
|
@@ -369,6 +396,11 @@ body { margin: 0; overflow: hidden; background: var(--bg); color: var(--ink); fo
|
|
|
369
396
|
.titlebar h1 { font-size: 0.95rem; margin: 0; font-weight: 600; letter-spacing: 0.02em; }
|
|
370
397
|
.titlebar .sub { color: var(--muted); font-size: 0.76rem; font-family: ${MONO_STACK}; }
|
|
371
398
|
.titlebar .sub a { color: var(--corpus); }
|
|
399
|
+
/* the fact count sits in the titlebar, not the footer status bar: it is what
|
|
400
|
+
the chat can actually answer from, and the footer is where a wrong number
|
|
401
|
+
goes unread. */
|
|
402
|
+
.titlebar .factpill { display: inline-flex; align-items: baseline; gap: 0.34rem; font-family: ${MONO_STACK}; font-size: 0.62rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--muted); border: 1px solid var(--entail); border-radius: 99px; padding: 0.14rem 0.7rem; background: var(--entail-soft); white-space: nowrap; }
|
|
403
|
+
.titlebar .factpill-value { font-size: 0.9rem; letter-spacing: 0; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }
|
|
372
404
|
.titlebar .pickers { margin-left: auto; display: flex; gap: 0.45rem; }
|
|
373
405
|
button { font: inherit; cursor: pointer; }
|
|
374
406
|
button:disabled { cursor: default; opacity: 0.5; }
|
|
@@ -397,13 +429,16 @@ ul.rows { list-style: none; margin: 0; padding: 0; }
|
|
|
397
429
|
.turn-you .said { font-family: ${MONO_STACK}; font-size: 0.84rem; }
|
|
398
430
|
.turn-tmct { border-left: 2px solid var(--entail); padding-left: 0.7rem; }
|
|
399
431
|
.turn-tmct .said { white-space: pre-wrap; }
|
|
400
|
-
.
|
|
401
|
-
.
|
|
432
|
+
.chat-empty { margin: auto; max-width: 26rem; text-align: center; color: var(--muted); }
|
|
433
|
+
.chat-empty-head { color: var(--ink); font-weight: 600; font-size: 1.05rem; margin: 0 0 0.5rem; }
|
|
434
|
+
.chat-empty p { margin: 0.3rem 0; line-height: 1.55; font-size: 0.92rem; }
|
|
435
|
+
.chat-empty-hint { font-size: 0.82rem; }
|
|
436
|
+
.suggest { flex: none; border-top: 1px solid var(--line); padding: 0.65rem 1.1rem 0.4rem; }
|
|
402
437
|
.suggest-label { font-family: ${MONO_STACK}; font-size: 0.66rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.09em; color: var(--muted); }
|
|
403
|
-
.hints { display: flex; flex-wrap: wrap; gap: 0.
|
|
438
|
+
.hints { display: flex; flex-wrap: wrap; gap: 0.4rem; max-height: 5.6rem; overflow-y: auto; padding: 0.45rem 0 0.55rem; }
|
|
404
439
|
.hint { background: var(--bg); border: 1px solid var(--line); border-radius: 999px; padding: 0.24rem 0.7rem; font-size: 0.8rem; color: var(--ink); text-align: left; }
|
|
405
440
|
.hint:hover { border-color: var(--entail); }
|
|
406
|
-
#chat-form { flex: none; display: flex; gap: 0.5rem; padding: 0.
|
|
441
|
+
#chat-form { flex: none; display: flex; gap: 0.5rem; padding: 0.75rem 1.1rem 0.9rem; border-top: 1px solid var(--line); }
|
|
407
442
|
#chat-input { flex: 1; min-width: 0; font: inherit; font-size: 0.92rem; padding: 0.5rem 0.65rem; border: 1px solid var(--line); border-radius: 4px; background: var(--bg); color: var(--ink); }
|
|
408
443
|
#chat-form button { background: var(--entail); color: var(--card); font-weight: 600; border: none; border-radius: 4px; padding: 0.5rem 1rem; font-size: 0.9rem; }
|
|
409
444
|
|
|
@@ -422,6 +457,9 @@ ul.rows { list-style: none; margin: 0; padding: 0; }
|
|
|
422
457
|
<svg class="mark" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M8 0l1 2.3a5.8 5.8 0 0 1 1.9.8L13.3 2l.7.7-1.1 2.4c.4.6.6 1.2.8 1.9L16 8l-2.3 1a5.8 5.8 0 0 1-.8 1.9l1.1 2.4-.7.7-2.4-1.1a5.8 5.8 0 0 1-1.9.8L8 16l-1-2.3a5.8 5.8 0 0 1-1.9-.8L2.7 14l-.7-.7 1.1-2.4a5.8 5.8 0 0 1-.8-1.9L0 8l2.3-1c.2-.7.4-1.3.8-1.9L2 2.7l.7-.7 2.4 1.1A5.8 5.8 0 0 1 7 2.3L8 0zm0 5.2A2.8 2.8 0 1 0 8 10.8 2.8 2.8 0 0 0 8 5.2z"/></svg>
|
|
423
458
|
<h1>tmct code explorer</h1>
|
|
424
459
|
<span class="sub">source: <span id="source-name">${escapeHtml(sourceName)}</span></span>
|
|
460
|
+
<span class="factpill" id="fact-total" aria-live="polite" title="every fact this page's chat can draw on — the code graph's own edges plus the general-knowledge seed once it lands">
|
|
461
|
+
<span class="factpill-value" id="fact-total-value">—</span> facts loaded
|
|
462
|
+
</span>
|
|
425
463
|
${showDesktopLink ? `<span class="sub">Also available as a <a href="${DESKTOP_APP_URL}">desktop app</a>.</span>` : ""}
|
|
426
464
|
<div class="pickers">
|
|
427
465
|
<button id="open-graph">Open graph…</button>
|
|
@@ -456,7 +494,7 @@ ul.rows { list-style: none; margin: 0; padding: 0; }
|
|
|
456
494
|
<span id="seed-status"></span>
|
|
457
495
|
</footer>
|
|
458
496
|
</div>
|
|
459
|
-
<script>window.__CODE_EXPLORER__ = Object.assign({ payload: ${payloadJson} }, ${dataJson});</script>
|
|
497
|
+
<script>window.__CODE_EXPLORER__ = Object.assign({ payload: ${payloadJson}, seedStamp: ${JSON.stringify(seedStamp)} }, ${dataJson});</script>
|
|
460
498
|
${winkLoaderInline ? `<script>\n${embedScriptText(winkLoaderInline)}\n</script>` : ""}
|
|
461
499
|
${bundleInline ? `<script>\n${embedScriptText(bundleInline)}\n</script>` : ""}
|
|
462
500
|
${bundleAvailable && !bundleInline ? `<script src="./code-explorer.bundle.js"></script>` : ""}
|
|
@@ -63,6 +63,7 @@ import { normFactTerm } from "../domain/hash.mjs";
|
|
|
63
63
|
import { winkInstance } from "../adapters/wink-model.mjs";
|
|
64
64
|
import {
|
|
65
65
|
loadLexicon, lookupNoun, lookupVerb, lookupAdjective, lookupProperName, predicateOf,
|
|
66
|
+
OF_CLASSIFIER_HEADS, OF_PARTITIVE_HEADS,
|
|
66
67
|
} from "../domain/grammar/lexicon.mjs";
|
|
67
68
|
|
|
68
69
|
export const USAGE = "usage: tmct extract <text-file>|--file <text-file> [--repo <path>] [--out <file.jsonl>] [--optimistic] [--canonical]";
|
|
@@ -111,14 +112,10 @@ const OPTIMISTIC_ENTITY_HOPS = 4;
|
|
|
111
112
|
// read — the noun on the far side belongs to a different clause or to a
|
|
112
113
|
// prepositional complement, not to "X is a Y".
|
|
113
114
|
const COPULA_FRAME_BLOCKERS = new Set(["VERB", "AUX", "ADP", "SCONJ", "CCONJ"]);
|
|
114
|
-
// Of-chain handling on a copula object: classifier heads read through to the
|
|
115
|
-
// real class; partitive containers state composition and yield no isa.
|
|
116
|
-
const COPULA_OF_READ_THROUGH = new Set(["type", "kind", "sort", "form", "class", "variety", "species", "breed", "genus"]);
|
|
117
115
|
// Naming periphrases stay copular: "can be termed as a name", "is known as",
|
|
118
116
|
// "is defined as" — the participle + "as" carries the same class claim the
|
|
119
117
|
// bare copula does, unlike any other verb after "is".
|
|
120
118
|
const COPULA_NAMING_PARTICIPLES = new Set(["termed", "known", "defined", "described", "referred", "called", "classified"]);
|
|
121
|
-
const COPULA_PARTITIVE_HEADS = new Set(["body", "mass", "group", "collection", "set", "series", "number", "amount", "piece", "part", "lot", "pair", "bunch", "pile"]);
|
|
122
119
|
// The relative pronouns that open a clause predicating about the SENTENCE
|
|
123
120
|
// subject: "a mountain that has lava" is a fact about the volcano, so the
|
|
124
121
|
// relative clause's verb binds to the copula's own subject, not to its object.
|
|
@@ -202,7 +199,7 @@ function optimisticTriplesPos(sentence, lexicon, nlp) {
|
|
|
202
199
|
let k = g - 1;
|
|
203
200
|
while (k >= 0 && !isNounish(k) && (ofChainSkip(k) || values[k]?.toLowerCase() === "of")) k -= 1;
|
|
204
201
|
if (k < 0 || !isNounish(k)) return null; // no readable head — abstain
|
|
205
|
-
if (
|
|
202
|
+
if (OF_CLASSIFIER_HEADS.has(String(values[k]).toLowerCase())) return lo; // classifier reads through
|
|
206
203
|
lo = runLoOf(k);
|
|
207
204
|
}
|
|
208
205
|
return lo;
|
|
@@ -271,8 +268,8 @@ function optimisticTriplesPos(sentence, lexicon, nlp) {
|
|
|
271
268
|
const headWord = String(values[hi]).toLowerCase();
|
|
272
269
|
const nextIsOf = values[hi + 1]?.toLowerCase() === "of";
|
|
273
270
|
if (!nextIsOf) return { label: entityRunAt(j), hi };
|
|
274
|
-
if (
|
|
275
|
-
if (
|
|
271
|
+
if (OF_CLASSIFIER_HEADS.has(headWord)) { i = hi + 1; j = hi + 1; continue; }
|
|
272
|
+
if (OF_PARTITIVE_HEADS.has(headWord)) return null;
|
|
276
273
|
return { label: entityRunAt(j), hi };
|
|
277
274
|
}
|
|
278
275
|
return null;
|