@polycode-projects/the-mechanical-code-talker 5.0.5 → 5.0.6
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 +72 -19
- package/package.json +1 -1
- package/src/domain/ask.mjs +2 -4
- package/src/domain/codegraph.mjs +23 -9
- package/src/domain/game-config.mjs +12 -0
- package/src/services/adventure-viz.mjs +5 -2
- package/src/services/chat-page-viz.mjs +5 -2
- package/src/services/chat.mjs +104 -16
- package/src/services/code-explorer-viz.mjs +3 -2
- package/src/services/ingest-viz.mjs +5 -4
- package/src/services/ledger-viz.mjs +5 -2
- package/src/services/mud-viz.mjs +4 -3
- package/src/services/mudiii-scene.mjs +136 -12
- package/src/services/mudiii-viz.mjs +540 -64
- package/src/services/p2p-room.mjs +1 -1
- package/src/services/plan-viz.mjs +3 -2
- package/src/services/predator-prey.mjs +65 -10
- package/src/services/research-viz.mjs +5 -4
- package/src/services/spider-fly-viz.mjs +3 -2
- package/src/services/sprite-catalog-viz.mjs +3 -2
- package/src/services/viz-theme.mjs +20 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +86 -86
- package/src/surfaces/web/mudiii-browser-entry.mjs +70 -34
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ derive by rule from both. Every answer is either grounded or an honest miss.
|
|
|
27
27
|
| `data/` | seed data assets: sprites, phrasebook, games, response templates |
|
|
28
28
|
| `ontology/` | the software ontology (`tmct-core.ttl`) and memory shapes, in Turtle |
|
|
29
29
|
| `scripts/` | build, check, and maintenance scripts (`npm run` targets live here) |
|
|
30
|
+
| `infra/` | the AWS CDK app that provisions the deployed site and its OIDC/deploy roles |
|
|
30
31
|
| `examples/` | runnable example scripts and fixture repos used by the README's own examples and the test suite |
|
|
31
32
|
| `demo/` | standalone demo scripts (e.g. the agentic-loop demo) |
|
|
32
33
|
| `electron/` | the Electron desktop app wrapper |
|
|
@@ -37,7 +38,7 @@ derive by rule from both. Every answer is either grounded or an honest miss.
|
|
|
37
38
|
| `reports/` | benchmark write-ups (`BENCHMARK_*.md`) and `PAGE_WEIGHTS.md` — see the root `STATUS.md` for the one-page summary these feed |
|
|
38
39
|
| `playtests/` | numbered playtest session logs, one edge found and fixed per entry |
|
|
39
40
|
| `archive/` | delivered `PLAN_*.md`/`BENCHMARK_*.md` docs, kept for history |
|
|
40
|
-
| `public/` | the
|
|
41
|
+
| `public/` | the demo site: the hand-written home page, the eleven about pages, the shared stylesheet and the model/screenshot assets. The demo pages and browser bundles beside them are gitignored build outputs of `npm run demo:build` |
|
|
41
42
|
|
|
42
43
|
`node_modules/` (dependencies) and dotfiles/hidden tooling directories are omitted above.
|
|
43
44
|
|
|
@@ -319,16 +320,19 @@ tmct> /exit
|
|
|
319
320
|
|
|
320
321
|
**[Try it live in your browser →](https://tmct.polycode.co.uk/)**
|
|
321
322
|
runs the actual query engine client-side. No server, no install. The landing
|
|
322
|
-
page answers codebase questions live, and
|
|
323
|
+
page answers codebase questions live, and eleven more pages each ground their
|
|
323
324
|
own domain: a full chat seeded with 63,470 facts (the same nine bands as
|
|
324
325
|
`npm run init:xl`), the
|
|
325
326
|
**memory ledger** (every fact as a readable sentence; drill by clicking the
|
|
326
327
|
terms inside), and the **code explorer** (the same ledger UI refocused on a
|
|
327
328
|
code graph, with a hint rail of suggested next questions). An **ingest
|
|
328
329
|
page** turns pasted or dropped text into grounded facts and downloads them
|
|
329
|
-
as canonical JSONL
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
as canonical JSONL, and a **research page** walks a stub wiki graph link by
|
|
331
|
+
link. The rest are a Towers-of-Hanoi plan replayed move by move, the
|
|
332
|
+
spider-and-fly and text-adventure games, a sprite gallery whose chat dock
|
|
333
|
+
answers from 1,480 generated sprite facts, and two multi-agent worlds: a
|
|
334
|
+
burrow that shares one world across browsers over WebRTC, and a Three.js
|
|
335
|
+
town square where a wolf hunts goblins. The chat page and
|
|
332
336
|
the ledger take the same paste-or-drop text in place; every page that holds
|
|
333
337
|
a fact store exports it as JSONL.
|
|
334
338
|
The site hosts its own copy of wink-nlp, ships its assets precompressed,
|
|
@@ -373,8 +377,8 @@ functions". Every suggestion resolves to a real answer.
|
|
|
373
377
|
The identical page also runs as a plain hosted page over the demo code
|
|
374
378
|
graph, with nothing to install:
|
|
375
379
|
**[try it live →](https://tmct.polycode.co.uk/code.html)**.
|
|
376
|
-
The desktop build below is for exploring your own repo or graph
|
|
377
|
-
hosted page
|
|
380
|
+
The desktop build below is for exploring your own repo or graph: a browser page
|
|
381
|
+
gets no filesystem access, so the hosted page reads the demo graph it ships with.
|
|
378
382
|
|
|
379
383
|
Electron is a dev-only dependency and never ships in the npm package. Because
|
|
380
384
|
`.npmrc` sets `ignore-scripts=true`, installing it does not fetch the runtime
|
|
@@ -659,7 +663,7 @@ now consults two shipped, lazily-loaded packs before giving up:
|
|
|
659
663
|
triples into memory (provenance `child:conceptnet:kettle`, ranked below
|
|
660
664
|
anything you teach) and answers from them; the next ask answers from
|
|
661
665
|
memory directly.
|
|
662
|
-
- `corpus/reference/`: 3,
|
|
666
|
+
- `corpus/reference/`: 3,888 Simple English Wikipedia summaries. When the
|
|
663
667
|
triples cannot answer, a matching article answers as a cited read-out
|
|
664
668
|
(`source: reference article "Otter"…, CC BY-SA 4.0`).
|
|
665
669
|
|
|
@@ -712,7 +716,7 @@ the shipped `npm run example:*` demos) reads a graph but writes nothing back.
|
|
|
712
716
|
|
|
713
717
|
The default persona also comes in three sizes: Small (~664 facts, the
|
|
714
718
|
default), Medium (~1,608, `tmct init --persona-size medium`) and Large
|
|
715
|
-
(~13,
|
|
719
|
+
(~13,600, `--persona-size large`, deep enough to chain real multi-hop
|
|
716
720
|
reasoning).
|
|
717
721
|
|
|
718
722
|
### Memory backends
|
|
@@ -935,6 +939,17 @@ provenance record. Most of its flags choose what gets seeded and where config is
|
|
|
935
939
|
in this repo picks it up with no flag needed
|
|
936
940
|
```
|
|
937
941
|
|
|
942
|
+
`tmct index` is the producer side of the graph seam. It walks a repo's own source
|
|
943
|
+
and writes the `.tmct/graph.json` that chat, serve and the CLI then read.
|
|
944
|
+
|
|
945
|
+
```output:help:index
|
|
946
|
+
tmct index [--repo <abs>] produce a code graph from a repo's OWN source (default: cwd):
|
|
947
|
+
[--no-history] walk the tree, parse JS/TS with the TypeScript compiler
|
|
948
|
+
API, read git history, and write <repo>/.tmct/graph.json —
|
|
949
|
+
the artifact chat/serve/cli then read. --no-history skips
|
|
950
|
+
the git passes (no commit/touches/cochange edges)
|
|
951
|
+
```
|
|
952
|
+
|
|
938
953
|
`tmct import` does the same activation as `tmct init`, but against a repo that is
|
|
939
954
|
already set up. Its `--graph` flag works differently from the others: it appends to
|
|
940
955
|
`tmct.toml`'s `graph_files` array instead of activating a bundle.
|
|
@@ -1055,6 +1070,15 @@ kill $SERVE_PID
|
|
|
1055
1070
|
[--graph <path>] the same thing. --graph names the graph file outright
|
|
1056
1071
|
[--config <path>] (repeatable), --config an alternate tmct.toml
|
|
1057
1072
|
tmct cli digest '{…}' architecture map + per-module context bundles
|
|
1073
|
+
tmct --help show this help
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
The help closes with two notes on where a chat session runs and what it leaves
|
|
1077
|
+
behind:
|
|
1078
|
+
|
|
1079
|
+
```output:help:notes
|
|
1080
|
+
On a terminal, chat opens the full-screen TUI; piped input gets the plain shell.
|
|
1081
|
+
In chat: /help lists slash-commands; /exit leaves. Session log → <repo>/.tmct/session-<id>.md.
|
|
1058
1082
|
```
|
|
1059
1083
|
|
|
1060
1084
|
Two precedence chains apply across every command above, in this order:
|
|
@@ -1116,6 +1140,12 @@ graph_file = ".tmct/graph.json"
|
|
|
1116
1140
|
# Extra graphs, merged alongside graph_file (ids that collide are auto-prefixed).
|
|
1117
1141
|
graph_files = [".tmct/graph.json", ".tmct/legacy-graph.json"]
|
|
1118
1142
|
|
|
1143
|
+
[graph]
|
|
1144
|
+
# A chat session reads this repo's graph and writes nothing back into its
|
|
1145
|
+
# .tmct/: no session upsert, no transcript, no memory. The committed example
|
|
1146
|
+
# fixtures set it so `tmct chat --repo examples/<x>` can't rewrite them.
|
|
1147
|
+
read_only = false
|
|
1148
|
+
|
|
1119
1149
|
[corpus]
|
|
1120
1150
|
# "tier1" (committed slice only, $0/offline, the default), "tier2" (also fetch
|
|
1121
1151
|
# growable corpora at seed time), or "tier3" (also consult live sources per query).
|
|
@@ -1124,6 +1154,8 @@ tier = "tier1"
|
|
|
1124
1154
|
[seed]
|
|
1125
1155
|
enabled = true # seed the committed corpus into .tmct/memory during init
|
|
1126
1156
|
limit = 500 # cap the seeded fact count (definitional band first); unset = no cap
|
|
1157
|
+
capture_unknown_context = true # keep the sentence around an unrecognized term
|
|
1158
|
+
unknown_context_limit = 200 # cap how many of those contexts are kept
|
|
1127
1159
|
|
|
1128
1160
|
# One [extensions.<name>] table per bundle. A recognized name (human, seon,
|
|
1129
1161
|
# conceptnet, human-medium, human-large, tier2-aws, tier2-python, tier2-java,
|
|
@@ -1177,7 +1209,6 @@ call_adjacency = true # boost callers/callees of a matched symbol
|
|
|
1177
1209
|
impl_of_interface = true # boost an interface's implementations
|
|
1178
1210
|
beam_search = true # use beam search over the graph walk
|
|
1179
1211
|
beam_width = 8
|
|
1180
|
-
embed_rank = false # rerank by embedding similarity (off by default)
|
|
1181
1212
|
prose_layers = 2 # how many prose-generation passes to run
|
|
1182
1213
|
|
|
1183
1214
|
[tune.expansion]
|
|
@@ -1186,6 +1217,17 @@ nodes = 50 # node budget for the walk
|
|
|
1186
1217
|
q = 0.5 # expansion breadth parameter
|
|
1187
1218
|
depth = 3 # max hops
|
|
1188
1219
|
|
|
1220
|
+
# Research-lane knobs (src/services/research.mjs).
|
|
1221
|
+
[research]
|
|
1222
|
+
fanout_limit = 8 # links followed per page
|
|
1223
|
+
max_depth = 3 # hops from the starting topic
|
|
1224
|
+
max_topics = 40 # topics visited in one crawl
|
|
1225
|
+
min_interval_ms = 1000 # politeness delay between fetches
|
|
1226
|
+
|
|
1227
|
+
# Discourse record (src/domain/discourse.mjs).
|
|
1228
|
+
[discourse]
|
|
1229
|
+
max_referents = 12 # how many referents stay resolvable across turns
|
|
1230
|
+
|
|
1189
1231
|
[telemetry]
|
|
1190
1232
|
enabled = false # local-only counters; never phones home
|
|
1191
1233
|
|
|
@@ -1202,10 +1244,13 @@ spider_initial_mass = 15
|
|
|
1202
1244
|
spider_mass_decrement_per_turn = 0.5 # lower = slower to starve
|
|
1203
1245
|
fly_initial_mass = 10
|
|
1204
1246
|
fly_mass_decrement_per_turn = 1
|
|
1205
|
-
|
|
1247
|
+
spider_vision_radius = 4 # Chebyshev radius a spider sees other agents within
|
|
1248
|
+
fly_vision_radius = 4 # the same radius for a fly
|
|
1206
1249
|
egg_hatch_delay_turns = 3 # turns between a lay and its hatch
|
|
1207
1250
|
fly_spawn_interval_turns = 3 # a new fly arrives every Nth turn
|
|
1208
|
-
|
|
1251
|
+
egg_lay_mass_threshold = 25 # mass a spider must reach before it lays
|
|
1252
|
+
egg_hatch_count = 2 # hatchlings per egg
|
|
1253
|
+
min_hatchling_mass = 3 # mass a hatchling starts with
|
|
1209
1254
|
web_duration_turns = 10 # turns a spider-built web stays active
|
|
1210
1255
|
|
|
1211
1256
|
[games.guess-number]
|
|
@@ -1219,8 +1264,9 @@ max_depth = 300 # the "solve it" plan lane's search-depth cap (hanoi, river-cro
|
|
|
1219
1264
|
|
|
1220
1265
|
### Try it on an example graph
|
|
1221
1266
|
|
|
1222
|
-
tmct
|
|
1223
|
-
|
|
1267
|
+
tmct reads a code graph at `<repo>/.tmct/graph.json`. `tmct index` builds one
|
|
1268
|
+
from a repo's own source, and any other producer can write the same file. Two
|
|
1269
|
+
ready-made example graphs live in `examples/` in this repo (clone the
|
|
1224
1270
|
repo to use them; they are not in the published npm package), so you can see
|
|
1225
1271
|
it answer real questions with no setup:
|
|
1226
1272
|
|
|
@@ -1402,7 +1448,7 @@ and transcripts are in the linked write-ups.
|
|
|
1402
1448
|
| Determinism | Byte-identical on rerun: a 379-case `--replay` clean across 2 runs, no LLM, no network, $0 per turn | A property of the no-model pipeline. | `archive/BENCHMARK_INFERENCE_2.7.12.md` |
|
|
1403
1449
|
| Dialogue robustness (persona sweep) | A 6-persona sweep (textbook logician, casual newcomer, new developer, adversarial sceptic, returning user, planning user) fixed 25 of the prior cycle's 29 routed findings (21 clean, 4 with a residual noted); 4 remain broken, 2 in a shape distinct from the original complaint | Free exploration across all six personas surfaced roughly 60 fresh findings beyond the ratchet check. The single highest-signal pattern: tmct's own suggested repair text was itself frequently broken when followed verbatim (since fixed, see `NEXT.md`). | `archive/BENCHMARK_CONVERSATION_2.7.11.md` |
|
|
1404
1450
|
|
|
1405
|
-
|
|
1451
|
+
Seven offline benchmark rigs live in a clone (they are not in the npm
|
|
1406
1452
|
package). Each replays a committed case set through the real product and
|
|
1407
1453
|
writes graded rows you can diff between runs:
|
|
1408
1454
|
|
|
@@ -1412,7 +1458,14 @@ writes graded rows you can diff between runs:
|
|
|
1412
1458
|
- `npm run infbench` generates inference cases, then runs each through both
|
|
1413
1459
|
drive points, the reasoning kernel and the chat surface;
|
|
1414
1460
|
- `npm run agentbench:run` measures the tool-loop behaviour, and every
|
|
1415
|
-
verdict carries a hallucination axis
|
|
1461
|
+
verdict carries a hallucination axis;
|
|
1462
|
+
- `npm run idxbench:run` grades how faithfully the code-index producer
|
|
1463
|
+
restates source as a graph;
|
|
1464
|
+
- `npm run ingestbench:run` (with `ingestbench:judge`) grades text-to-facts
|
|
1465
|
+
extraction;
|
|
1466
|
+
- `npm run researchbench:run` grades the research lane's link traversal
|
|
1467
|
+
against a frozen stub wiki graph;
|
|
1468
|
+
- `npm run synthbench:code` grades deterministic code synthesis.
|
|
1416
1469
|
|
|
1417
1470
|
The smallest real slice of each, the same invocations the test suite's
|
|
1418
1471
|
bench-smoke lane replays:
|
|
@@ -1457,7 +1510,7 @@ tmct's vocabulary is grounded in published standards where they exist, and says
|
|
|
1457
1510
|
Each alignment below is a triple in `ontology/tmct-core.ttl` and a test in
|
|
1458
1511
|
`test/adapters/grammar-ontology.test.mjs`. `docs/references/` holds an entry per source: the
|
|
1459
1512
|
edition, the retrieval date, the terms tmct uses, and what could not be verified.
|
|
1460
|
-
`PLAN_NORMATIVE.md` holds the reconciliation, one verdict per term.
|
|
1513
|
+
`archive/PLAN_NORMATIVE.md` holds the reconciliation, one verdict per term.
|
|
1461
1514
|
|
|
1462
1515
|
### The data model
|
|
1463
1516
|
|
|
@@ -1476,7 +1529,7 @@ edition, the retrieval date, the terms tmct uses, and what could not be verified
|
|
|
1476
1529
|
| [Attempto Controlled English](http://attempto.ifi.uzh.ch/site/docs/) | ACE 6.7, 2013 | The controlled-English fragment. tmct implements 9 of ACE's declarative sentence patterns. |
|
|
1477
1530
|
| Kuhn, "A Survey and Classification of Controlled Natural Languages" | *Computational Linguistics* 40(1), 2014 | Where ACE sits among controlled languages. |
|
|
1478
1531
|
| [ConceptNet](https://github.com/commonsense/conceptnet5/wiki/Relations) | slice pins 5.7.0 | The commonsense corpus. 25 relations are mirrored into `mgx:` and each cites its `/r/` origin. |
|
|
1479
|
-
| Damerau, *CACM* 7(3), 1964 · Levenshtein, *Soviet Physics Doklady* 10(8), 1966 | — | Fuzzy matching. `fuzzy.mjs` implements **Optimal String Alignment** — restricted Damerau-Levenshtein, which allows adjacent transposition but edits no substring twice. |
|
|
1532
|
+
| Damerau, *CACM* 7(3), 1964 · Levenshtein, *Soviet Physics Doklady* 10(8), 1966 | — | Fuzzy matching. `src/domain/interpret/fuzzy.mjs` implements **Optimal String Alignment** — restricted Damerau-Levenshtein, which allows adjacent transposition but edits no substring twice. |
|
|
1480
1533
|
|
|
1481
1534
|
### Reasoning and planning
|
|
1482
1535
|
|
|
@@ -1492,7 +1545,7 @@ edition, the retrieval date, the terms tmct uses, and what could not be verified
|
|
|
1492
1545
|
|
|
1493
1546
|
| source | edition | what tmct uses it for |
|
|
1494
1547
|
|---|---|---|
|
|
1495
|
-
| Jensen et al., "A Consensus Glossary of Temporal Database Concepts" | *SIGMOD Record* 23(1), 1994 | The time vocabulary. `mgx:utteranceTs` is valid time; `mgx:createdAt` is a transaction-time start. tmct is **not** bitemporal: `mgx:updatedAt` is an audit stamp, so
|
|
1548
|
+
| Jensen et al., "A Consensus Glossary of Temporal Database Concepts" | *SIGMOD Record* 23(1), 1994 | The time vocabulary. `mgx:utteranceTs` is valid time; `mgx:createdAt` is a transaction-time start. tmct is **not** bitemporal: `mgx:updatedAt` is an audit stamp, so "what did it believe last Tuesday" lands on the honest miss wall. |
|
|
1496
1549
|
| RFC 9923, "The FNV Non-Cryptographic Hash Algorithm" | Informational, 2026 | FNV hashes the narrow non-fact-id pools (paraphrase keys, per-URL source ids, corpus dedupe). Fact ids are content-addressed with a **64-bit truncation of SHA-256**, so a fact id is collision-resistant at tmct's corpus sizes; tmct is still **not** a Merkle tree and offers no tamper-evidence. |
|
|
1497
1550
|
| Green, Karvounarakis, Tannen, "Provenance Semirings" | PODS 2007 | The distinction tmct's docs keep: it records source annotation and PROV-style attribution, not how-provenance. |
|
|
1498
1551
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; indexes a repo on request (tmct index) or reads any producer's graph.",
|
package/src/domain/ask.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// ask.mjs's own `touches`/`cochange` verbs answer one-hop structural edges
|
|
16
16
|
// (mgx:touchedByCommit / mgx:changeCoupledWith).
|
|
17
17
|
|
|
18
|
-
import { relationKind, impactClosure, moduleCountOf, normPath, packageCounts, modulesOf, HISTORY_CAP } from "./codegraph.mjs";
|
|
18
|
+
import { relationKind, impactClosure, moduleCountOf, normPath, packageCounts, modulesOf, HISTORY_CAP, NO_CODE_INDEX_NOTE } from "./codegraph.mjs";
|
|
19
19
|
import { isTestPath } from "./module-paths.mjs";
|
|
20
20
|
import {
|
|
21
21
|
RELATIONS,
|
|
@@ -2491,9 +2491,7 @@ export function rephraseHint() {
|
|
|
2491
2491
|
* graph is UNKNOWN, not empty (see chat.mjs's noCodeGraph), so it keeps the
|
|
2492
2492
|
* index-shaped advice. */
|
|
2493
2493
|
function touchesRephraseHint(graph = null) {
|
|
2494
|
-
if (graph && moduleCountOf(graph) === 0)
|
|
2495
|
-
return "This store holds no code index, so it records no modules or commits to look through.";
|
|
2496
|
-
}
|
|
2494
|
+
if (graph && moduleCountOf(graph) === 0) return NO_CODE_INDEX_NOTE;
|
|
2497
2495
|
return 'Try "who touched <a module that actually has commits>" or "/describe <module>" to see what\'s in the index.';
|
|
2498
2496
|
}
|
|
2499
2497
|
|
package/src/domain/codegraph.mjs
CHANGED
|
@@ -57,6 +57,10 @@ export function moduleCountOf(graph) {
|
|
|
57
57
|
return graph.individuals.filter((i) => (i.class || "") === "Module").length;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/** What a store with zero modules actually holds, said once so every lane that
|
|
61
|
+
* has to decline for that reason declines in the same words. */
|
|
62
|
+
export const NO_CODE_INDEX_NOTE = "This store holds no code index, so it records no modules or commits to look through.";
|
|
63
|
+
|
|
60
64
|
// ---- relation-kind classifier (for impact + tests-coverage) -------------------
|
|
61
65
|
|
|
62
66
|
const KINDS = ["imports", "calls", "defines", "tests", "touches", "contains", "inherits", "callsSymbol", "touchesSymbol"];
|
|
@@ -1360,7 +1364,13 @@ export function renderTestsFor(graph, ind) {
|
|
|
1360
1364
|
}
|
|
1361
1365
|
|
|
1362
1366
|
/** Source modules with no covering test module — a coverage gap view. Test
|
|
1363
|
-
* modules (subjects of test edges, or test-named paths) are excluded.
|
|
1367
|
+
* modules (subjects of test edges, or test-named paths) are excluded.
|
|
1368
|
+
*
|
|
1369
|
+
* "Nothing is uncovered" and "I hold nothing to look at" are opposite
|
|
1370
|
+
* statements, so the empty survey has to say which one it means: with no
|
|
1371
|
+
* source module recorded there is no coverage claim to make at all, and the
|
|
1372
|
+
* full-coverage sentence would be an assertion about a repo this store has
|
|
1373
|
+
* never seen. */
|
|
1364
1374
|
export function renderUntested(graph) {
|
|
1365
1375
|
const covered = new Set();
|
|
1366
1376
|
const testModules = new Set();
|
|
@@ -1368,14 +1378,18 @@ export function renderUntested(graph) {
|
|
|
1368
1378
|
covered.add(e.object);
|
|
1369
1379
|
testModules.add(e.subject);
|
|
1370
1380
|
}
|
|
1371
|
-
const
|
|
1372
|
-
.
|
|
1373
|
-
(i)
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1381
|
+
const sourceModules = graph.individuals.filter(
|
|
1382
|
+
(i) => (i.class || "") === "Module"
|
|
1383
|
+
&& !testModules.has(i.id)
|
|
1384
|
+
&& !isTestPath(String(i.label).toLowerCase()),
|
|
1385
|
+
);
|
|
1386
|
+
if (!sourceModules.length) {
|
|
1387
|
+
return moduleCountOf(graph) === 0
|
|
1388
|
+
? `no modules to check for test coverage in this index. ${NO_CODE_INDEX_NOTE}`
|
|
1389
|
+
: "no source modules to check for test coverage in this index — only test modules are recorded.";
|
|
1390
|
+
}
|
|
1391
|
+
const untested = sourceModules
|
|
1392
|
+
.filter((i) => !covered.has(i.id))
|
|
1379
1393
|
.map((i) => i.label)
|
|
1380
1394
|
.sort();
|
|
1381
1395
|
if (!untested.length) return "every source module has at least one covering test module.";
|
|
@@ -90,6 +90,16 @@ export const DEFAULT_GAME_CONFIG = Object.freeze({
|
|
|
90
90
|
// this is the one switch that turns that off (visionRadius: Infinity for
|
|
91
91
|
// the food-only belief call) without any new belief machinery.
|
|
92
92
|
foodVisionGated: true,
|
|
93
|
+
// Whether prey weigh the predator and the crumb in one score instead of
|
|
94
|
+
// running the evade rung ahead of the forage one. Off by default: strict
|
|
95
|
+
// priority is what ships, and the blend is the alternative a comparison
|
|
96
|
+
// run measures against it (scripts/compare-prey-decision.mjs).
|
|
97
|
+
blendPreyDecision: false,
|
|
98
|
+
// How much of that score belongs to keeping away from the predator, with
|
|
99
|
+
// the rest going to closing on food. 1 evades exactly as the priority
|
|
100
|
+
// chain does, 0 ignores the predator, and the middle takes a crumb that
|
|
101
|
+
// costs little distance. Read only when blendPreyDecision is on.
|
|
102
|
+
preyThreatWeight: 0.5,
|
|
93
103
|
// Whether the town-square lane accepts teaching. Its own knob rather than
|
|
94
104
|
// a share of the adventure one below: the board's sentence table is a
|
|
95
105
|
// different vocabulary, and a page checkbox on the town square should not
|
|
@@ -166,6 +176,8 @@ const MUDIII_KEY_MAP = Object.freeze({
|
|
|
166
176
|
max_prey_population: "maxPreyPopulation",
|
|
167
177
|
max_food_items: "maxFoodItems",
|
|
168
178
|
food_vision_gated: "foodVisionGated",
|
|
179
|
+
blend_prey_decision: "blendPreyDecision",
|
|
180
|
+
prey_threat_weight: "preyThreatWeight",
|
|
169
181
|
teach: "teach",
|
|
170
182
|
});
|
|
171
183
|
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
// an edit implies run through the browser bundle's own `session.applyEdit`
|
|
93
93
|
// (adventure-browser-entry.mjs), never here — this module only renders and
|
|
94
94
|
// reads.
|
|
95
|
-
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embedScriptText, scenarioLabel, rowsForWorld, wordBeforeCursor } from "./viz-theme.mjs";
|
|
95
|
+
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embedScriptText, scenarioLabel, rowsForWorld, wordBeforeCursor, demoEyebrowHtml, EYEBROW_LINKS_CSS } from "./viz-theme.mjs";
|
|
96
96
|
import { createTicker, createSerialQueue } from "./viz-ticker.mjs";
|
|
97
97
|
import { directedGridLayout } from "./viz-room-graph.mjs";
|
|
98
98
|
import { worldDigestRows, roomAffordances, foldWorldState } from "./adventure.mjs";
|
|
@@ -628,7 +628,9 @@ ${THEME_TOKENS_CSS}
|
|
|
628
628
|
body { margin: 0; background: var(--bg); color: var(--ink); font-family: ${SERIF_STACK}; font-size: 16px; line-height: 1.5; }
|
|
629
629
|
.mono { font-family: ${MONO_STACK}; }
|
|
630
630
|
main { max-width: 920px; margin: 0 auto; padding: 1.4rem 1.2rem 2.2rem; }
|
|
631
|
+
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
|
|
631
632
|
.eyebrow { font-family: ${MONO_STACK}; font-size: .7rem; letter-spacing: .12em; text-transform: uppercase; color: var(--gilt); }
|
|
633
|
+
${EYEBROW_LINKS_CSS}
|
|
632
634
|
.titlebar { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin: .3rem 0 1rem; }
|
|
633
635
|
button { font: inherit; color: inherit; background: none; cursor: pointer; }
|
|
634
636
|
button:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
|
|
@@ -993,8 +995,9 @@ ${THEME_TOKENS_CSS}
|
|
|
993
995
|
</head>
|
|
994
996
|
<body>
|
|
995
997
|
<main>
|
|
998
|
+
<h1 class="visually-hidden">the adventure</h1>
|
|
996
999
|
<div class="titlebar">
|
|
997
|
-
<div class="eyebrow"
|
|
1000
|
+
<div class="eyebrow">${demoEyebrowHtml("adventure", "the adventure")}</div>
|
|
998
1001
|
<button id="editModeBtn" type="button" class="mode-toggle" disabled>edit the world</button>
|
|
999
1002
|
</div>
|
|
1000
1003
|
<p class="page-note" id="pageNote">${escapeHtml(scenarioList[0].worldPayload.opening || "")}</p>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// input. scripts/build-demo-site.mjs calls it directly and writes the result
|
|
29
29
|
// to public/chat.html, after chat-browser.bundle.js/chat-seed.json already
|
|
30
30
|
// exist (both built earlier in that same script, for the embedded widget).
|
|
31
|
-
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml } from "./viz-theme.mjs";
|
|
31
|
+
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, demoEyebrowHtml, EYEBROW_LINKS_CSS } from "./viz-theme.mjs";
|
|
32
32
|
import {
|
|
33
33
|
SHARE_OVERLAY_CSS, shareOverlayHtml, shareStepStates, activeWaves, offerBlobIn, peerTerm,
|
|
34
34
|
shareMessageFor, replyMessageFor, whatsAppShareUrl, isProbablyMobile, copyTextToClipboard, flashCopyTip,
|
|
@@ -498,7 +498,9 @@ ${THEME_TOKENS_CSS}
|
|
|
498
498
|
get folded onto a second line by a wide one. */
|
|
499
499
|
.topbar { flex: 0 0 auto; display: flex; align-items: center; gap: 1rem; padding: .55rem 1.1rem; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
|
|
500
500
|
.brand { display: flex; align-items: baseline; gap: .55rem; }
|
|
501
|
+
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
|
|
501
502
|
.eyebrow { font-family: ${MONO_STACK}; font-size: .78rem; letter-spacing: .08em; color: var(--muted); }
|
|
503
|
+
${EYEBROW_LINKS_CSS}
|
|
502
504
|
.legend { display: flex; gap: .8rem; margin-left: auto; font-family: ${MONO_STACK}; font-size: .68rem; color: var(--muted); }
|
|
503
505
|
.legend-item { display: inline-flex; align-items: center; gap: .32rem; white-space: nowrap; }
|
|
504
506
|
.dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
|
|
@@ -691,9 +693,10 @@ ${SHARE_OVERLAY_CSS}
|
|
|
691
693
|
<body>
|
|
692
694
|
${shareOverlayHtml({ withTape: true })}
|
|
693
695
|
<div class="chatCol">
|
|
696
|
+
<h1 class="visually-hidden">talk to it</h1>
|
|
694
697
|
<header class="topbar">
|
|
695
698
|
<div class="brand">
|
|
696
|
-
<span class="eyebrow"
|
|
699
|
+
<span class="eyebrow">${demoEyebrowHtml("chat", "chat")}</span>
|
|
697
700
|
</div>
|
|
698
701
|
<div class="chrome">
|
|
699
702
|
<span class="fact-pill" id="factPill" aria-live="polite"
|
package/src/services/chat.mjs
CHANGED
|
@@ -1413,6 +1413,35 @@ export function isConversational(query) {
|
|
|
1413
1413
|
return expandContractions(q).split(/\s+/).filter(Boolean).length <= 3 && !codeish;
|
|
1414
1414
|
}
|
|
1415
1415
|
|
|
1416
|
+
/** The words that, in the slot of a short "what is X" / "who is X", name the
|
|
1417
|
+
* SESSION rather than a term to look up ("what is this", "what is up").
|
|
1418
|
+
* Closed and hand-curated, the same discipline every small-talk set in this
|
|
1419
|
+
* file uses: a word listed here keeps today's orientation card, and anything
|
|
1420
|
+
* else is a question about a term. */
|
|
1421
|
+
const SESSION_REFERENT_TERMS = new Set([
|
|
1422
|
+
"this", "that", "it", "they", "them", "you", "u", "yours", "tmct",
|
|
1423
|
+
"up", "new", "next", "now", "here", "there", "left", "wrong", "more", "else",
|
|
1424
|
+
"all", "everything", "anything", "something", "nothing",
|
|
1425
|
+
"going", "happening", "possible", "available", "supported", "included",
|
|
1426
|
+
]);
|
|
1427
|
+
|
|
1428
|
+
/** The term a short "what is X" / "who is X" asks about, or null when the line
|
|
1429
|
+
* isn't that shape or names the session itself.
|
|
1430
|
+
*
|
|
1431
|
+
* isConversational's catch-all counts words, so "what is grelb" (three) took
|
|
1432
|
+
* the orientation card while "what is a grelb" (four, one article apart)
|
|
1433
|
+
* refused — the same question answered two different ways, and the shorter
|
|
1434
|
+
* one answered with a paragraph about tmct that reads as if the term had been
|
|
1435
|
+
* looked up. A term question that resolves to nothing belongs on the miss
|
|
1436
|
+
* wall; only the closed set above is really about the product. */
|
|
1437
|
+
function shortTermQuestionTerm(query) {
|
|
1438
|
+
const m = String(query).trim().replace(/[?.!]+\s*$/, "")
|
|
1439
|
+
.match(/^(?:what|who)\s+(?:is|are|was|were)\s+([a-z][\w'-]*)$/i);
|
|
1440
|
+
if (!m) return null;
|
|
1441
|
+
const term = m[1].toLowerCase();
|
|
1442
|
+
return SESSION_REFERENT_TERMS.has(term) ? null : term;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1416
1445
|
/** The tmct tools dispatchTool can back (the set a tool-emitting caller may use).
|
|
1417
1446
|
* A declared tool outside this set is never emitted — the request falls through
|
|
1418
1447
|
* to a text answer. The COMMANDS map names the richer graph tools; TOOLS names
|
|
@@ -2959,6 +2988,23 @@ function singularizeSurface(word) {
|
|
|
2959
2988
|
|
|
2960
2989
|
export { singularizeSurface };
|
|
2961
2990
|
|
|
2991
|
+
/** Does the complement of this payload wear its own article ("all foxes are A
|
|
2992
|
+
* SPECIES")? An article marks the complement singular already, so the plural
|
|
2993
|
+
* fold below must leave it alone. */
|
|
2994
|
+
const objectCarriesArticle = (payload) => /\s(?:is|are)\s+(?:an?\s+)/i.test(String(payload || ""));
|
|
2995
|
+
|
|
2996
|
+
/** The complement as it should be STORED. The subject of a plural membership
|
|
2997
|
+
* sentence has always folded ("all foxes are mammals" stores "fox"); the
|
|
2998
|
+
* object had not, so the pair landed as fox ⊑ mammals and the next turn's
|
|
2999
|
+
* "is a fox a mammal" looked up a class spelled a different way and missed
|
|
3000
|
+
* the fact it had just been told. Gated exactly as the subject fold is: only
|
|
3001
|
+
* a genuinely plural "are" phrasing, and only where no article already marks
|
|
3002
|
+
* the complement singular. */
|
|
3003
|
+
function storedObjectTerm(objectRaw, { verb, payload, lex, lookupNoun }) {
|
|
3004
|
+
if (!/^are$/i.test(verb) || objectCarriesArticle(payload)) return objectRaw;
|
|
3005
|
+
return lookupNoun(lex, objectRaw)?.lemma || singularizeSurface(objectRaw);
|
|
3006
|
+
}
|
|
3007
|
+
|
|
2962
3008
|
/** "(every|each|all|a|an )?X is/are (a|an )?Y" — the shape the unknown-subject
|
|
2963
3009
|
* fallback recognizes (group 2 = X, group 4 = Y); group 1 (when present)
|
|
2964
3010
|
* names the determiner, so the caller can tell a genuine "every" universal
|
|
@@ -3103,19 +3149,25 @@ async function ungroundedPairHint(payload, lexicon, memoryDir, cache = null, gra
|
|
|
3103
3149
|
if (!memoryDir) return "";
|
|
3104
3150
|
const m = String(payload).trim().match(UNKNOWN_SUBJECT_RE);
|
|
3105
3151
|
if (!m) return "";
|
|
3106
|
-
const [, , subjectRaw, , objectRaw] = m;
|
|
3107
|
-
const { loadLexicon } = await import("../domain/grammar/lexicon.mjs");
|
|
3152
|
+
const [, , subjectRaw, verb, objectRaw] = m;
|
|
3153
|
+
const { loadLexicon, lookupNoun } = await import("../domain/grammar/lexicon.mjs");
|
|
3108
3154
|
const lex = lexicon || loadLexicon();
|
|
3109
3155
|
if (await isGroundedTerm(subjectRaw, lex, memoryDir, cache, graph)) return "";
|
|
3110
3156
|
if (await isGroundedTerm(objectRaw, lex, memoryDir, cache, graph)) return "";
|
|
3157
|
+
// The sentences suggested here have to be the ones that actually store, so
|
|
3158
|
+
// both sides fold to the singular a plural surface named ("all zorps are
|
|
3159
|
+
// florbs" → "every zorp is a thing"). Following the plural verbatim teaches
|
|
3160
|
+
// a second class under a spelling nothing else uses.
|
|
3161
|
+
const subject = /^are$/i.test(verb) ? singularOf(subjectRaw, lex, lookupNoun) : subjectRaw;
|
|
3162
|
+
const object = storedObjectTerm(objectRaw, { verb, payload, lex, lookupNoun });
|
|
3111
3163
|
// Chaining the second term UNDER the first's now-grounded proper name
|
|
3112
3164
|
// ("every man is a john") is technically accepted by the grammar (once
|
|
3113
3165
|
// "john" is grounded, ANY term can be taught as a kind of it), but reads as
|
|
3114
3166
|
// nonsense to a human, since a proper name is never a category. Ground both
|
|
3115
3167
|
// sides independently instead — two clear, parallel, semantically sane
|
|
3116
3168
|
// suggestions, not a confusing chain through an arbitrary first term.
|
|
3117
|
-
return ` I don't know "${
|
|
3118
|
-
+ `"every ${
|
|
3169
|
+
return ` I don't know "${subject}" or "${object}" yet. Try grounding each one first, e.g. `
|
|
3170
|
+
+ `"every ${subject} is a thing" and "every ${object} is a thing", then re-teach the`
|
|
3119
3171
|
+ ` original fact.`;
|
|
3120
3172
|
}
|
|
3121
3173
|
|
|
@@ -3192,7 +3244,12 @@ async function unknownSubjectFallback(payload, { memoryDir, sessionId, lexicon,
|
|
|
3192
3244
|
if (lookupNoun(lex, objectRaw) || GENERIC_ANCHOR_NOUNS.has(String(objectRaw).toLowerCase())
|
|
3193
3245
|
|| (await isGroundedByFact(objectRaw, memoryDir, cache))) {
|
|
3194
3246
|
return teachFact(memoryDir, sessionId, {
|
|
3195
|
-
subject,
|
|
3247
|
+
subject,
|
|
3248
|
+
predicate: SUBCLASS_PREDICATE,
|
|
3249
|
+
object: storedObjectTerm(objectRaw, { verb, payload, lex, lookupNoun }),
|
|
3250
|
+
quantifier,
|
|
3251
|
+
observedAt,
|
|
3252
|
+
dateText,
|
|
3196
3253
|
});
|
|
3197
3254
|
}
|
|
3198
3255
|
if (lookupAdjective(lex, objectRaw)) {
|
|
@@ -3321,7 +3378,12 @@ async function unknownObjectFallback(payload, { memoryDir, sessionId, lexicon, c
|
|
|
3321
3378
|
? (lookupNoun(lex, subjectRaw)?.lemma || singularizeSurface(subjectRaw))
|
|
3322
3379
|
: subjectRaw;
|
|
3323
3380
|
return teachFact(memoryDir, sessionId, {
|
|
3324
|
-
subject,
|
|
3381
|
+
subject,
|
|
3382
|
+
predicate: SUBCLASS_PREDICATE,
|
|
3383
|
+
object: storedObjectTerm(objectRaw, { verb, payload, lex, lookupNoun }),
|
|
3384
|
+
quantifier,
|
|
3385
|
+
observedAt,
|
|
3386
|
+
dateText,
|
|
3325
3387
|
});
|
|
3326
3388
|
}
|
|
3327
3389
|
|
|
@@ -4160,9 +4222,20 @@ function matchBareCanTeach(text) {
|
|
|
4160
4222
|
function teachSuggestion(payload) {
|
|
4161
4223
|
const m = String(payload).match(/^(?:every |each |all |a |an )?([\w-]+) (is|are) (a |an )?([\w-]+)[.!?]*$/i);
|
|
4162
4224
|
if (!m) return null;
|
|
4163
|
-
const
|
|
4164
|
-
const
|
|
4165
|
-
|
|
4225
|
+
const plural = /^are$/i.test(m[2]);
|
|
4226
|
+
const subject = (plural ? singularizeSurface(m[1]) : m[1]).toLowerCase();
|
|
4227
|
+
let object = m[4].toLowerCase();
|
|
4228
|
+
let articled = !!m[3];
|
|
4229
|
+
// A plural complement with no article of its own ("all lynxes are mammals")
|
|
4230
|
+
// folds like the subject does, and the fold itself is what proves it was a
|
|
4231
|
+
// plural NOUN rather than a bare adjective — so it earns the article the
|
|
4232
|
+
// singular membership sentence needs. Without this the repair suggested the
|
|
4233
|
+
// very spelling the same sentence had just called unrecognized.
|
|
4234
|
+
if (plural && !articled) {
|
|
4235
|
+
const folded = singularizeSurface(object);
|
|
4236
|
+
if (folded !== object) { object = folded; articled = true; }
|
|
4237
|
+
}
|
|
4238
|
+
if (!articled) return `every ${subject} is ${object}`;
|
|
4166
4239
|
const articleRule = grammarRules().find((r) => r.kind === "article");
|
|
4167
4240
|
const article = articleRule && beginsWithVowelSound(object, articleRule) ? "an" : "a";
|
|
4168
4241
|
return `every ${subject} is ${article} ${object}`;
|
|
@@ -4799,9 +4872,11 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
4799
4872
|
// the SHAPE of a retraction sentence — it says nothing about whether
|
|
4800
4873
|
// subject⊑object was ever actually taught, or taught by THIS session.
|
|
4801
4874
|
// retractSubClassOf is asked for real and is the only thing that decides:
|
|
4802
|
-
// - found:false → subject⊑object was never a stored fact,
|
|
4803
|
-
//
|
|
4804
|
-
//
|
|
4875
|
+
// - found:false → subject⊑object was never a stored fact, so there is
|
|
4876
|
+
// nothing to withdraw and the turn says exactly that. It must NOT
|
|
4877
|
+
// fall through to the rest of the cascade: the teach frames below
|
|
4878
|
+
// read the unconsumed sentence from the top, take "forget bertha" as
|
|
4879
|
+
// a two-word subject, and confirm a fact the user asked to destroy.
|
|
4805
4880
|
// - found:true, ownRecord:false → some OTHER source taught it; this
|
|
4806
4881
|
// session has nothing of its own to withdraw.
|
|
4807
4882
|
// - found:true, stillStands:true → this session's own record is gone,
|
|
@@ -4849,7 +4924,10 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
4849
4924
|
via: "retract", miss: false,
|
|
4850
4925
|
};
|
|
4851
4926
|
}
|
|
4852
|
-
|
|
4927
|
+
return {
|
|
4928
|
+
text: `"${retractSubject} is a kind of ${retractObject}" isn't stored, so there's nothing to forget.`,
|
|
4929
|
+
via: "retract", miss: true,
|
|
4930
|
+
};
|
|
4853
4931
|
}
|
|
4854
4932
|
}
|
|
4855
4933
|
|
|
@@ -13336,9 +13414,11 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
13336
13414
|
dialogueLaneOverride = "game-inform";
|
|
13337
13415
|
note(trace, "lane: (2) MID-GAME NUDGE — an unparsed short turn stayed inside the live game frame instead of the identity card");
|
|
13338
13416
|
note(trace, "goal: keep the running guess-the-number game on track");
|
|
13339
|
-
} else if (isConversationalCandidate) {
|
|
13417
|
+
} else if (isConversationalCandidate && !shortTermQuestionTerm(gateQuery) && !shortTermQuestionTerm(query)) {
|
|
13340
13418
|
// A conversational miss (a greeting, "what can you do", a very short non-code
|
|
13341
13419
|
// line) gets the friendly orientation (module-aware: empty → --repo/tmct init).
|
|
13420
|
+
// A short question naming a TERM is excluded: nothing above resolved it, so
|
|
13421
|
+
// it belongs on the miss wall its four-word twin already reaches.
|
|
13342
13422
|
// This branch carries via:"template" and never reaches the composed-only
|
|
13343
13423
|
// wall-shortening gate below, so it needs its own repeat collapse,
|
|
13344
13424
|
// mirroring WALL_REPEAT_ONELINER.
|
|
@@ -13530,7 +13610,10 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
13530
13610
|
// all, so it would otherwise be confidently WRONG or silently absent.
|
|
13531
13611
|
// Every successfully-recognized teach attempt gets this consistent goal
|
|
13532
13612
|
// line instead.
|
|
13533
|
-
|
|
13613
|
+
// A retraction reaches this lane too, and its goal is the opposite one:
|
|
13614
|
+
// saying "teach/remember a new fact" under a sentence asking for a fact
|
|
13615
|
+
// to go describes the turn backwards.
|
|
13616
|
+
deduced = taught.via === "retract" ? "withdraw a remembered fact" : "teach/remember a new fact";
|
|
13534
13617
|
note(trace, `goal: ${deduced} (revised — the teach lane recognized this shape where the raw structural parse never should have)`);
|
|
13535
13618
|
// A canonical whose verb only matched through the fuzzy edit-distance
|
|
13536
13619
|
// tier ("disk-1 rests on peg-a." read as an ask about "tests") restates
|
|
@@ -13791,7 +13874,12 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
13791
13874
|
// does. Both shapes below anchor on the written-out copula.
|
|
13792
13875
|
const offerSrc = expandContractions(String(query).trim());
|
|
13793
13876
|
const knowAboutTerm = offerSrc.match(KNOW_ABOUT_RE)?.[1]?.trim();
|
|
13794
|
-
|
|
13877
|
+
// "who is grelb" asks about a term the same way "what is grelb" does, and
|
|
13878
|
+
// a name is exactly what a person teaches next — without this the who-form
|
|
13879
|
+
// dead-ends on the bare grammar wall while its what-form twin offers the
|
|
13880
|
+
// teach phrasing.
|
|
13881
|
+
const whoTerm = shortTermQuestionTerm(offerSrc) ? offerSrc.match(WHO_IS_BARE_RE)?.[1]?.trim() : null;
|
|
13882
|
+
const offerTerm = knowAboutTerm || metaTermOf(offerSrc, envelope) || whoTerm;
|
|
13795
13883
|
// A term that LEADS with a bindable anaphor ("it used for", from an
|
|
13796
13884
|
// unresolved "what is it used for") is a pronoun that failed to bind,
|
|
13797
13885
|
// not a teachable subject — offering to learn facts about it would echo
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// sidebar/centre grid, so a later panel is one more <section class="panel">,
|
|
20
20
|
// not a re-architecture.
|
|
21
21
|
|
|
22
|
-
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embedScriptText } from "./viz-theme.mjs";
|
|
22
|
+
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embedScriptText, demoEyebrowHtml, EYEBROW_LINKS_CSS } from "./viz-theme.mjs";
|
|
23
23
|
import { generateCodeHints } from "../domain/code-explorer-hints.mjs";
|
|
24
24
|
import { phraseForRelation } from "../domain/ask-vocab.mjs";
|
|
25
25
|
import { fetchWithProgress } from "./memory-panel-viz.mjs";
|
|
@@ -483,6 +483,7 @@ body { margin: 0; overflow: hidden; background: var(--bg); color: var(--ink); fo
|
|
|
483
483
|
.titlebar { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; padding: 0.5rem 0.9rem; background: var(--card); border-bottom: 1px solid var(--line); box-shadow: inset 0 -2px 0 var(--entail-soft); }
|
|
484
484
|
.titlebar .mark { width: 15px; height: 15px; color: var(--entail); flex: none; }
|
|
485
485
|
.titlebar h1 { font-size: 0.95rem; margin: 0; font-weight: 600; letter-spacing: 0.02em; }
|
|
486
|
+
${EYEBROW_LINKS_CSS}
|
|
486
487
|
.titlebar .sub { color: var(--muted); font-size: 0.76rem; font-family: ${MONO_STACK}; }
|
|
487
488
|
.titlebar .sub a { color: var(--corpus); }
|
|
488
489
|
/* the fact count sits in the titlebar, not the footer status bar: it is what
|
|
@@ -544,7 +545,7 @@ ul.rows { list-style: none; margin: 0; padding: 0; }
|
|
|
544
545
|
<div class="shell">
|
|
545
546
|
<header class="titlebar">
|
|
546
547
|
<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>
|
|
547
|
-
<h1
|
|
548
|
+
<h1>${demoEyebrowHtml("code", "code explorer")}</h1>
|
|
548
549
|
<span class="sub">source: <span id="source-name">${escapeHtml(sourceName)}</span></span>
|
|
549
550
|
<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">
|
|
550
551
|
<span class="factpill-value" id="fact-total-value">—</span> facts loaded
|