@polycode-projects/the-mechanical-code-talker 4.0.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 +34 -0
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +49 -9
- 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-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-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/domain/grammar/ace.mjs +40 -4
- package/src/domain/grammar/lexicon-core.json +2 -1
- package/src/domain/grammar/lexicon.mjs +18 -0
- package/src/domain/reference-pack.mjs +31 -7
- package/src/domain/spider-fly-world.mjs +1 -1
- package/src/domain/sprite-templates.mjs +8 -6
- package/src/services/adventure-viz.mjs +130 -47
- package/src/services/adventure.mjs +440 -367
- package/src/services/chat-page-viz.mjs +37 -9
- 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-viz.mjs +940 -21
- 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 +107 -107
- package/src/surfaces/web/mud-browser-entry.mjs +44 -2
|
@@ -32,7 +32,7 @@ import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml } from "./viz-the
|
|
|
32
32
|
import { provBucketFor } from "./ledger-viz.mjs";
|
|
33
33
|
import { createTicker, prefersReducedMotion } from "./viz-ticker.mjs";
|
|
34
34
|
import { sessionLogTimeOfDay, sessionLogHeaderMarkdown, sessionLogTurnMarkdown } from "./session-log-format.mjs";
|
|
35
|
-
import { bandLabelFor, statsSummaryLine, fetchWithProgress, renderStatsPanelInto } from "./memory-panel-viz.mjs";
|
|
35
|
+
import { bandLabelFor, statsSummaryLine, clearSiteAssetCaches, fetchWithProgress, renderStatsPanelInto } from "./memory-panel-viz.mjs";
|
|
36
36
|
|
|
37
37
|
const DEFAULT_TITLE = "the-mechanical-code-talker — talk to it";
|
|
38
38
|
|
|
@@ -379,7 +379,7 @@ export function transcriptMarkdown(turns, meta, headerMd, turnMd) {
|
|
|
379
379
|
* live digest-bank twin (see chat-browser-entry.mjs) rather than to a
|
|
380
380
|
* client-side digest panel of this page's own; an empty list degrades to the
|
|
381
381
|
* flat list exactly as before this page could digest at all. */
|
|
382
|
-
export function renderChatHtml({ title = DEFAULT_TITLE, digestStructures = [] } = {}) {
|
|
382
|
+
export function renderChatHtml({ title = DEFAULT_TITLE, digestStructures = [], seedStamp = "" } = {}) {
|
|
383
383
|
const digestStructuresJson = JSON.stringify(Array.isArray(digestStructures) ? digestStructures : []);
|
|
384
384
|
const legendHtml = PROV_LEGEND.map(
|
|
385
385
|
([key, label]) => `<span class="legend-item"><i class="dot dot-${provKey(key)}"></i>${escapeHtml(label)}</span>`,
|
|
@@ -549,6 +549,13 @@ ${THEME_TOKENS_CSS}
|
|
|
549
549
|
input and a round send button. The chrome's controls take the same shape,
|
|
550
550
|
so the page's networking reads as ordinary chat furniture rather than as
|
|
551
551
|
an instrument bolted on. */
|
|
552
|
+
/* the live fact count, in the topbar rather than the statusline: it is the
|
|
553
|
+
one number that says what this session actually knows, and a small line of
|
|
554
|
+
grey mono under the composer is where a wrong one goes unnoticed. Reads as
|
|
555
|
+
a pill like its neighbours, with the number itself at reading size. */
|
|
556
|
+
.fact-pill { display: inline-flex; align-items: baseline; gap: .34rem; font-family: ${MONO_STACK}; font-size: .68rem; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); border: 1px solid var(--corpus-t1); border-radius: 99px; padding: .2rem .8rem; background: var(--corpus-soft); white-space: nowrap; }
|
|
557
|
+
.fact-pill .fact-pill-value { font-size: .96rem; letter-spacing: 0; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }
|
|
558
|
+
|
|
552
559
|
.chrome { display: flex; align-items: center; gap: .4rem; }
|
|
553
560
|
.chrome-btn { font-family: ${SERIF_STACK}; font-size: .8rem; color: var(--muted); border: 1px solid var(--line); border-radius: 99px; padding: .22rem .75rem; background: var(--card); text-decoration: none; display: inline-flex; align-items: center; gap: .32rem; white-space: nowrap; line-height: 1.35; }
|
|
554
561
|
.chrome-btn:hover { color: var(--ink); border-color: var(--ink); }
|
|
@@ -789,6 +796,10 @@ ${THEME_TOKENS_CSS}
|
|
|
789
796
|
<span class="eyebrow">the-mechanical-code-talker</span>
|
|
790
797
|
</div>
|
|
791
798
|
<div class="chrome">
|
|
799
|
+
<span class="fact-pill" id="factPill" aria-live="polite"
|
|
800
|
+
title="every fact this session's memory holds right now — the starter memory it shipped with plus anything you have taught, researched or ingested">
|
|
801
|
+
<span class="fact-pill-value" id="factPillValue">—</span> facts
|
|
802
|
+
</span>
|
|
792
803
|
<button type="button" class="state-pill" id="statePill" data-tone="idle"
|
|
793
804
|
title="the shared-world connection; click to open the network panel">
|
|
794
805
|
<i class="pill-dot"></i><span id="statePillWord">not shared</span>
|
|
@@ -880,6 +891,7 @@ ${THEME_TOKENS_CSS}
|
|
|
880
891
|
const transcriptMarkdown = ${transcriptMarkdown.toString()};
|
|
881
892
|
const bandLabelFor = ${bandLabelFor.toString()};
|
|
882
893
|
const statsSummaryLine = ${statsSummaryLine.toString()};
|
|
894
|
+
const clearSiteAssetCaches = ${clearSiteAssetCaches.toString()};
|
|
883
895
|
const fetchWithProgress = ${fetchWithProgress.toString()};
|
|
884
896
|
const renderStatsPanelInto = ${renderStatsPanelInto.toString()};
|
|
885
897
|
const createTicker = ${createTicker.toString()};
|
|
@@ -900,6 +912,7 @@ ${THEME_TOKENS_CSS}
|
|
|
900
912
|
const inputEl = el("composerInput");
|
|
901
913
|
const sendBtn = el("composerSend");
|
|
902
914
|
const statusEl = el("status");
|
|
915
|
+
const factPillValueEl = el("factPillValue");
|
|
903
916
|
const statsPanelEl = el("statsPanelStats");
|
|
904
917
|
const researchedPanelEl = el("researchedPanel");
|
|
905
918
|
const wikiModeFieldset = el("wikiMode");
|
|
@@ -1102,11 +1115,18 @@ ${THEME_TOKENS_CSS}
|
|
|
1102
1115
|
}
|
|
1103
1116
|
}
|
|
1104
1117
|
|
|
1118
|
+
// The build's own content hash for the seed. It rides in the URL this page
|
|
1119
|
+
// fetches the seed by, so the service worker's cache-first read can only
|
|
1120
|
+
// ever return the copy this page asked for. Empty in a build that had no
|
|
1121
|
+
// seed to hash (the desktop shell's own render).
|
|
1122
|
+
const SEED_STAMP = ${JSON.stringify(seedStamp)};
|
|
1123
|
+
const SEED_QUERY = SEED_STAMP ? "?b=" + SEED_STAMP : "";
|
|
1124
|
+
|
|
1105
1125
|
let seedPayload = null;
|
|
1106
1126
|
let seedFacts = 0;
|
|
1107
1127
|
async function fetchSeed() {
|
|
1108
1128
|
try {
|
|
1109
|
-
const blob = await fetchWithProgress("./chat-seed.json", (loaded, total) => noteProgress("seed", loaded, total));
|
|
1129
|
+
const blob = await fetchWithProgress("./chat-seed.json" + SEED_QUERY, (loaded, total) => noteProgress("seed", loaded, total));
|
|
1110
1130
|
seedPayload = JSON.parse(await blob.text());
|
|
1111
1131
|
seedFacts = (seedPayload.individuals || []).filter((i) => i.class === "Fact").length;
|
|
1112
1132
|
} catch (err) {
|
|
@@ -1156,8 +1176,9 @@ ${THEME_TOKENS_CSS}
|
|
|
1156
1176
|
// Best-effort IndexedDB (window.tmctChat.openPersistedStore): the whole
|
|
1157
1177
|
// Backend-B payload snapshots after each teach turn, debounced so a burst
|
|
1158
1178
|
// of teaching costs one multi-MB write, not one per fact. The stamp ties a
|
|
1159
|
-
// snapshot to this deploy (site version) AND this seed (fact count
|
|
1160
|
-
// changing discards the snapshot in favour of
|
|
1179
|
+
// snapshot to this deploy (site version) AND this seed (its fact count and
|
|
1180
|
+
// content hash) — any of them changing discards the snapshot in favour of
|
|
1181
|
+
// the fresh seed.
|
|
1161
1182
|
let persist = null;
|
|
1162
1183
|
let saveTimer = null;
|
|
1163
1184
|
let restoredCount = 0;
|
|
@@ -1217,6 +1238,7 @@ ${THEME_TOKENS_CSS}
|
|
|
1217
1238
|
try { stats = await window.tmctChat.memoryStats(window.tmctChatSession.memoryDir); }
|
|
1218
1239
|
catch { return; }
|
|
1219
1240
|
}
|
|
1241
|
+
factPillValueEl.textContent = Number(stats.total || 0).toLocaleString();
|
|
1220
1242
|
renderStatsPanelInto(statsPanelEl, stats, {
|
|
1221
1243
|
bandLabel: bandLabelFor,
|
|
1222
1244
|
onForget: persist ? forgetEverything : null,
|
|
@@ -1598,14 +1620,20 @@ ${THEME_TOKENS_CSS}
|
|
|
1598
1620
|
inputEl.focus();
|
|
1599
1621
|
});
|
|
1600
1622
|
|
|
1601
|
-
// "reset to seed" is the full re-initialisation: drop
|
|
1602
|
-
//
|
|
1603
|
-
//
|
|
1623
|
+
// "reset to seed" is the full re-initialisation: drop everything this device
|
|
1624
|
+
// holds and reload, so boot re-seeds from the page's shipped seed as if on a
|
|
1625
|
+
// first visit. Harder than "forget everything", which only swaps the live
|
|
1604
1626
|
// session — this trusts nothing in memory and re-fetches the seed asset.
|
|
1627
|
+
//
|
|
1628
|
+
// Both stores go, not just the payload: what you taught lives in IndexedDB,
|
|
1629
|
+
// and the seed asset itself lives in the service worker's Cache Storage. A
|
|
1630
|
+
// reset that cleared only the first would re-seed straight back out of a
|
|
1631
|
+
// cached copy of an older seed, which is exactly what it promises not to do.
|
|
1605
1632
|
el("reinitStore").addEventListener("click", async () => {
|
|
1606
1633
|
clearTimeout(saveTimer);
|
|
1607
1634
|
saveTimer = null;
|
|
1608
1635
|
if (persist) await persist.clear();
|
|
1636
|
+
await clearSiteAssetCaches();
|
|
1609
1637
|
window.location.reload();
|
|
1610
1638
|
});
|
|
1611
1639
|
|
|
@@ -2186,7 +2214,7 @@ ${THEME_TOKENS_CSS}
|
|
|
2186
2214
|
progressActive = false;
|
|
2187
2215
|
window.tmctChat.registerReferencePackProvider(fetchPackProvider);
|
|
2188
2216
|
if (window.tmctChat.openPersistedStore) {
|
|
2189
|
-
persist = window.tmctChat.openPersistedStore({ storeKey: "chat", stamp: siteVersion + ":" + seedFacts });
|
|
2217
|
+
persist = window.tmctChat.openPersistedStore({ storeKey: "chat", stamp: siteVersion + ":" + seedFacts + ":" + SEED_STAMP });
|
|
2190
2218
|
}
|
|
2191
2219
|
const savedRecord = persist ? await persist.load() : null;
|
|
2192
2220
|
const initialMode = readWikiMode();
|
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;
|