@polycode-projects/the-mechanical-code-talker 5.0.12 → 5.0.14

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.
@@ -14071,8 +14071,11 @@ export const CORPUSES = {
14071
14071
  ["humanistic discipline", "/r/IsA", "discipline"],
14072
14072
  ["nub", "/r/IsA", "content"],
14073
14073
  ["vow", "/r/IsA", "assurance"],
14074
- ["beta", "/r/IsA", "letter"],
14075
- ["letter", "/r/IsA", "character"],
14074
+ ["beta", "/r/IsA", "greek letter"],
14075
+ ["letter of the alphabet", "/r/IsA", "character"],
14076
+ ["greek letter", "/r/IsA", "letter of the alphabet"],
14077
+ ["alpha", "/r/IsA", "greek letter"],
14078
+ ["letter", "/r/RelatedTo", "letter of the alphabet"],
14076
14079
  ["written symbol", "/r/IsA", "symbol"],
14077
14080
  ["brag", "/r/IsA", "boast"],
14078
14081
  ["boast", "/r/IsA", "speech act"],
@@ -14119,7 +14122,7 @@ export const CORPUSES = {
14119
14122
  ["dynamical system", "/r/IsA", "phase space"],
14120
14123
  ["phase space", "/r/IsA", "space"],
14121
14124
  ["conga", "/r/IsA", "dance music"],
14122
- ["delta", "/r/IsA", "letter"],
14125
+ ["delta", "/r/IsA", "greek letter"],
14123
14126
  ["demur", "/r/IsA", "objection"],
14124
14127
  ["dread", "/r/IsA", "fear"],
14125
14128
  ["fable", "/r/IsA", "story"],
@@ -14265,7 +14268,7 @@ export const CORPUSES = {
14265
14268
  ["gradual", "/r/IsA", "antiphon"],
14266
14269
  ["antiphon", "/r/IsA", "religious music"],
14267
14270
  ["gravity", "/r/IsA", "feeling"],
14268
- ["initial", "/r/IsA", "letter"],
14271
+ ["initial", "/r/IsA", "letter of the alphabet"],
14269
14272
  ["inquiry", "/r/IsA", "questioning"],
14270
14273
  ["lingual", "/r/IsA", "consonant"],
14271
14274
  ["loyalty", "/r/IsA", "love"],
@@ -14725,8 +14728,8 @@ export const CORPUSES = {
14725
14728
  ["description", "/r/IsA", "speech act"],
14726
14729
  ["predetermination", "/r/IsA", "decision"],
14727
14730
  ["decision", "/r/IsA", "judgment"],
14728
- ["pi", "/r/IsA", "letter"],
14729
- ["xi", "/r/IsA", "letter"],
14731
+ ["pi", "/r/IsA", "greek letter"],
14732
+ ["xi", "/r/IsA", "greek letter"],
14730
14733
  ["boo", "/r/IsA", "cry"],
14731
14734
  ["bye", "/r/IsA", "concession"],
14732
14735
  ["concession", "/r/IsA", "assent"],
@@ -14738,7 +14741,7 @@ export const CORPUSES = {
14738
14741
  ["nay", "/r/IsA", "negative"],
14739
14742
  ["pic", "/r/IsA", "product"],
14740
14743
  ["pic", "/r/HasA", "sequence"],
14741
- ["psi", "/r/IsA", "letter"],
14744
+ ["psi", "/r/IsA", "greek letter"],
14742
14745
  ["pun", "/r/IsA", "fun"],
14743
14746
  ["wai", "/r/IsA", "gesture"],
14744
14747
  ["woe", "/r/IsA", "mournfulness"],
@@ -14754,7 +14757,7 @@ export const CORPUSES = {
14754
14757
  ["grin", "/r/IsA", "facial expression"],
14755
14758
  ["heed", "/r/IsA", "attention"],
14756
14759
  ["idyl", "/r/IsA", "musical composition"],
14757
- ["iota", "/r/IsA", "letter"],
14760
+ ["iota", "/r/IsA", "greek letter"],
14758
14761
  ["jive", "/r/IsA", "jazz"],
14759
14762
  ["moan", "/r/IsA", "utterance"],
14760
14763
  ["muse", "/r/IsA", "source"],
@@ -14812,7 +14815,7 @@ export const CORPUSES = {
14812
14815
  ["emotional arousal", "/r/IsA", "arousal"],
14813
14816
  ["arousal", "/r/IsA", "physical condition"],
14814
14817
  ["frown", "/r/IsA", "facial expression"],
14815
- ["gamma", "/r/IsA", "letter"],
14818
+ ["gamma", "/r/IsA", "greek letter"],
14816
14819
  ["gamut", "/r/IsA", "scale"],
14817
14820
  ["scale", "/r/IsA", "musical notation"],
14818
14821
  ["grief", "/r/IsA", "negative stimulus"],
@@ -14824,7 +14827,7 @@ export const CORPUSES = {
14824
14827
  ["macro", "/r/IsA", "instruction"],
14825
14828
  ["instruction", "/r/IsA", "code"],
14826
14829
  ["odour", "/r/IsA", "sensation"],
14827
- ["omega", "/r/IsA", "letter"],
14830
+ ["omega", "/r/IsA", "greek letter"],
14828
14831
  ["paean", "/r/IsA", "hymn"],
14829
14832
  ["pavan", "/r/IsA", "dance music"],
14830
14833
  ["peeve", "/r/IsA", "temper"],
@@ -11223,8 +11223,11 @@
11223
11223
  {"start":"/c/en/humanistic discipline","rel":"/r/IsA","end":"/c/en/discipline","weight":1,"surfaceText":"[[humanistic discipline]] IsA [[discipline]]"}
11224
11224
  {"start":"/c/en/nub","rel":"/r/IsA","end":"/c/en/content","weight":1,"surfaceText":"[[nub]] IsA [[content]]"}
11225
11225
  {"start":"/c/en/vow","rel":"/r/IsA","end":"/c/en/assurance","weight":1,"surfaceText":"[[vow]] IsA [[assurance]]"}
11226
- {"start":"/c/en/beta","rel":"/r/IsA","end":"/c/en/letter","weight":1,"surfaceText":"[[beta]] IsA [[letter]]"}
11227
- {"start":"/c/en/letter","rel":"/r/IsA","end":"/c/en/character","weight":1,"surfaceText":"[[letter]] IsA [[character]]"}
11226
+ {"start":"/c/en/beta","rel":"/r/IsA","end":"/c/en/greek letter","weight":1,"surfaceText":"[[beta]] IsA [[greek letter]]"}
11227
+ {"start":"/c/en/letter of the alphabet","rel":"/r/IsA","end":"/c/en/character","weight":1,"surfaceText":"[[letter of the alphabet]] IsA [[character]]"}
11228
+ {"start":"/c/en/greek letter","rel":"/r/IsA","end":"/c/en/letter of the alphabet","weight":1,"surfaceText":"[[greek letter]] IsA [[letter of the alphabet]]"}
11229
+ {"start":"/c/en/alpha","rel":"/r/IsA","end":"/c/en/greek letter","weight":1,"surfaceText":"[[alpha]] IsA [[greek letter]]"}
11230
+ {"start":"/c/en/letter","rel":"/r/RelatedTo","end":"/c/en/letter of the alphabet","weight":1,"surfaceText":"[[letter]] RelatedTo [[letter of the alphabet]]"}
11228
11231
  {"start":"/c/en/written symbol","rel":"/r/IsA","end":"/c/en/symbol","weight":1,"surfaceText":"[[written symbol]] IsA [[symbol]]"}
11229
11232
  {"start":"/c/en/brag","rel":"/r/IsA","end":"/c/en/boast","weight":1,"surfaceText":"[[brag]] IsA [[boast]]"}
11230
11233
  {"start":"/c/en/boast","rel":"/r/IsA","end":"/c/en/speech act","weight":1,"surfaceText":"[[boast]] IsA [[speech act]]"}
@@ -11271,7 +11274,7 @@
11271
11274
  {"start":"/c/en/dynamical system","rel":"/r/IsA","end":"/c/en/phase space","weight":1,"surfaceText":"[[dynamical system]] IsA [[phase space]]"}
11272
11275
  {"start":"/c/en/phase space","rel":"/r/IsA","end":"/c/en/space","weight":1,"surfaceText":"[[phase space]] IsA [[space]]"}
11273
11276
  {"start":"/c/en/conga","rel":"/r/IsA","end":"/c/en/dance music","weight":1,"surfaceText":"[[conga]] IsA [[dance music]]"}
11274
- {"start":"/c/en/delta","rel":"/r/IsA","end":"/c/en/letter","weight":1,"surfaceText":"[[delta]] IsA [[letter]]"}
11277
+ {"start":"/c/en/delta","rel":"/r/IsA","end":"/c/en/greek letter","weight":1,"surfaceText":"[[delta]] IsA [[greek letter]]"}
11275
11278
  {"start":"/c/en/demur","rel":"/r/IsA","end":"/c/en/objection","weight":1,"surfaceText":"[[demur]] IsA [[objection]]"}
11276
11279
  {"start":"/c/en/dread","rel":"/r/IsA","end":"/c/en/fear","weight":1,"surfaceText":"[[dread]] IsA [[fear]]"}
11277
11280
  {"start":"/c/en/fable","rel":"/r/IsA","end":"/c/en/story","weight":1,"surfaceText":"[[fable]] IsA [[story]]"}
@@ -11417,7 +11420,7 @@
11417
11420
  {"start":"/c/en/gradual","rel":"/r/IsA","end":"/c/en/antiphon","weight":1,"surfaceText":"[[gradual]] IsA [[antiphon]]"}
11418
11421
  {"start":"/c/en/antiphon","rel":"/r/IsA","end":"/c/en/religious music","weight":1,"surfaceText":"[[antiphon]] IsA [[religious music]]"}
11419
11422
  {"start":"/c/en/gravity","rel":"/r/IsA","end":"/c/en/feeling","weight":1,"surfaceText":"[[gravity]] IsA [[feeling]]"}
11420
- {"start":"/c/en/initial","rel":"/r/IsA","end":"/c/en/letter","weight":1,"surfaceText":"[[initial]] IsA [[letter]]"}
11423
+ {"start":"/c/en/initial","rel":"/r/IsA","end":"/c/en/letter of the alphabet","weight":1,"surfaceText":"[[initial]] IsA [[letter of the alphabet]]"}
11421
11424
  {"start":"/c/en/inquiry","rel":"/r/IsA","end":"/c/en/questioning","weight":1,"surfaceText":"[[inquiry]] IsA [[questioning]]"}
11422
11425
  {"start":"/c/en/lingual","rel":"/r/IsA","end":"/c/en/consonant","weight":1,"surfaceText":"[[lingual]] IsA [[consonant]]"}
11423
11426
  {"start":"/c/en/loyalty","rel":"/r/IsA","end":"/c/en/love","weight":1,"surfaceText":"[[loyalty]] IsA [[love]]"}
@@ -11877,8 +11880,8 @@
11877
11880
  {"start":"/c/en/description","rel":"/r/IsA","end":"/c/en/speech act","weight":1,"surfaceText":"[[description]] IsA [[speech act]]"}
11878
11881
  {"start":"/c/en/predetermination","rel":"/r/IsA","end":"/c/en/decision","weight":1,"surfaceText":"[[predetermination]] IsA [[decision]]"}
11879
11882
  {"start":"/c/en/decision","rel":"/r/IsA","end":"/c/en/judgment","weight":1,"surfaceText":"[[decision]] IsA [[judgment]]"}
11880
- {"start":"/c/en/pi","rel":"/r/IsA","end":"/c/en/letter","weight":1,"surfaceText":"[[pi]] IsA [[letter]]"}
11881
- {"start":"/c/en/xi","rel":"/r/IsA","end":"/c/en/letter","weight":1,"surfaceText":"[[xi]] IsA [[letter]]"}
11883
+ {"start":"/c/en/pi","rel":"/r/IsA","end":"/c/en/greek letter","weight":1,"surfaceText":"[[pi]] IsA [[greek letter]]"}
11884
+ {"start":"/c/en/xi","rel":"/r/IsA","end":"/c/en/greek letter","weight":1,"surfaceText":"[[xi]] IsA [[greek letter]]"}
11882
11885
  {"start":"/c/en/boo","rel":"/r/IsA","end":"/c/en/cry","weight":1,"surfaceText":"[[boo]] IsA [[cry]]"}
11883
11886
  {"start":"/c/en/bye","rel":"/r/IsA","end":"/c/en/concession","weight":1,"surfaceText":"[[bye]] IsA [[concession]]"}
11884
11887
  {"start":"/c/en/concession","rel":"/r/IsA","end":"/c/en/assent","weight":1,"surfaceText":"[[concession]] IsA [[assent]]"}
@@ -11890,7 +11893,7 @@
11890
11893
  {"start":"/c/en/nay","rel":"/r/IsA","end":"/c/en/negative","weight":1,"surfaceText":"[[nay]] IsA [[negative]]"}
11891
11894
  {"start":"/c/en/pic","rel":"/r/IsA","end":"/c/en/product","weight":1,"surfaceText":"[[pic]] IsA [[product]]"}
11892
11895
  {"start":"/c/en/pic","rel":"/r/HasA","end":"/c/en/sequence","weight":1,"surfaceText":"[[pic]] HasA [[sequence]]"}
11893
- {"start":"/c/en/psi","rel":"/r/IsA","end":"/c/en/letter","weight":1,"surfaceText":"[[psi]] IsA [[letter]]"}
11896
+ {"start":"/c/en/psi","rel":"/r/IsA","end":"/c/en/greek letter","weight":1,"surfaceText":"[[psi]] IsA [[greek letter]]"}
11894
11897
  {"start":"/c/en/pun","rel":"/r/IsA","end":"/c/en/fun","weight":1,"surfaceText":"[[pun]] IsA [[fun]]"}
11895
11898
  {"start":"/c/en/wai","rel":"/r/IsA","end":"/c/en/gesture","weight":1,"surfaceText":"[[wai]] IsA [[gesture]]"}
11896
11899
  {"start":"/c/en/woe","rel":"/r/IsA","end":"/c/en/mournfulness","weight":1,"surfaceText":"[[woe]] IsA [[mournfulness]]"}
@@ -11906,7 +11909,7 @@
11906
11909
  {"start":"/c/en/grin","rel":"/r/IsA","end":"/c/en/facial expression","weight":1,"surfaceText":"[[grin]] IsA [[facial expression]]"}
11907
11910
  {"start":"/c/en/heed","rel":"/r/IsA","end":"/c/en/attention","weight":1,"surfaceText":"[[heed]] IsA [[attention]]"}
11908
11911
  {"start":"/c/en/idyl","rel":"/r/IsA","end":"/c/en/musical composition","weight":1,"surfaceText":"[[idyl]] IsA [[musical composition]]"}
11909
- {"start":"/c/en/iota","rel":"/r/IsA","end":"/c/en/letter","weight":1,"surfaceText":"[[iota]] IsA [[letter]]"}
11912
+ {"start":"/c/en/iota","rel":"/r/IsA","end":"/c/en/greek letter","weight":1,"surfaceText":"[[iota]] IsA [[greek letter]]"}
11910
11913
  {"start":"/c/en/jive","rel":"/r/IsA","end":"/c/en/jazz","weight":1,"surfaceText":"[[jive]] IsA [[jazz]]"}
11911
11914
  {"start":"/c/en/moan","rel":"/r/IsA","end":"/c/en/utterance","weight":1,"surfaceText":"[[moan]] IsA [[utterance]]"}
11912
11915
  {"start":"/c/en/muse","rel":"/r/IsA","end":"/c/en/source","weight":1,"surfaceText":"[[muse]] IsA [[source]]"}
@@ -11964,7 +11967,7 @@
11964
11967
  {"start":"/c/en/emotional arousal","rel":"/r/IsA","end":"/c/en/arousal","weight":1,"surfaceText":"[[emotional arousal]] IsA [[arousal]]"}
11965
11968
  {"start":"/c/en/arousal","rel":"/r/IsA","end":"/c/en/physical condition","weight":1,"surfaceText":"[[arousal]] IsA [[physical condition]]"}
11966
11969
  {"start":"/c/en/frown","rel":"/r/IsA","end":"/c/en/facial expression","weight":1,"surfaceText":"[[frown]] IsA [[facial expression]]"}
11967
- {"start":"/c/en/gamma","rel":"/r/IsA","end":"/c/en/letter","weight":1,"surfaceText":"[[gamma]] IsA [[letter]]"}
11970
+ {"start":"/c/en/gamma","rel":"/r/IsA","end":"/c/en/greek letter","weight":1,"surfaceText":"[[gamma]] IsA [[greek letter]]"}
11968
11971
  {"start":"/c/en/gamut","rel":"/r/IsA","end":"/c/en/scale","weight":1,"surfaceText":"[[gamut]] IsA [[scale]]"}
11969
11972
  {"start":"/c/en/scale","rel":"/r/IsA","end":"/c/en/musical notation","weight":1,"surfaceText":"[[scale]] IsA [[musical notation]]"}
11970
11973
  {"start":"/c/en/grief","rel":"/r/IsA","end":"/c/en/negative stimulus","weight":1,"surfaceText":"[[grief]] IsA [[negative stimulus]]"}
@@ -11976,7 +11979,7 @@
11976
11979
  {"start":"/c/en/macro","rel":"/r/IsA","end":"/c/en/instruction","weight":1,"surfaceText":"[[macro]] IsA [[instruction]]"}
11977
11980
  {"start":"/c/en/instruction","rel":"/r/IsA","end":"/c/en/code","weight":1,"surfaceText":"[[instruction]] IsA [[code]]"}
11978
11981
  {"start":"/c/en/odour","rel":"/r/IsA","end":"/c/en/sensation","weight":1,"surfaceText":"[[odour]] IsA [[sensation]]"}
11979
- {"start":"/c/en/omega","rel":"/r/IsA","end":"/c/en/letter","weight":1,"surfaceText":"[[omega]] IsA [[letter]]"}
11982
+ {"start":"/c/en/omega","rel":"/r/IsA","end":"/c/en/greek letter","weight":1,"surfaceText":"[[omega]] IsA [[greek letter]]"}
11980
11983
  {"start":"/c/en/paean","rel":"/r/IsA","end":"/c/en/hymn","weight":1,"surfaceText":"[[paean]] IsA [[hymn]]"}
11981
11984
  {"start":"/c/en/pavan","rel":"/r/IsA","end":"/c/en/dance music","weight":1,"surfaceText":"[[pavan]] IsA [[dance music]]"}
11982
11985
  {"start":"/c/en/peeve","rel":"/r/IsA","end":"/c/en/temper","weight":1,"surfaceText":"[[peeve]] IsA [[temper]]"}
@@ -95,9 +95,9 @@
95
95
  "tool": "corpus/tier2/generate.mjs"
96
96
  },
97
97
  "file": "human-large.jsonl",
98
- "facts": 11999,
99
- "bytes": 1464616,
100
- "sha256": "fbbe8135d4915f2bc041bf577e7b0bd1a8fd1c1bab804c8215b1d05e39db27f9",
98
+ "facts": 12002,
99
+ "bytes": 1465216,
100
+ "sha256": "ac35618d54ae98092220f601f7d73736863d56ed1a5bd44a7cfbde252ec5ea2f",
101
101
  "license": "MPL-2.0"
102
102
  }
103
103
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "5.0.12",
3
+ "version": "5.0.14",
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.",
@@ -12,6 +12,7 @@
12
12
  // the selector stays a function of its inputs.
13
13
 
14
14
  import { subClassParents, ancestryChain } from "../sense-split.mjs";
15
+ import { ANSWER_STOP_SET } from "../hub-terms.mjs";
15
16
 
16
17
  // The predicates the digest groups under the isa family (kept in step with
17
18
  // select.mjs's FAMILY_OF): a subclass edge and a type edge both name a class
@@ -56,7 +57,7 @@ export function chainsForObjects(subClassEdges, objects, cap = 6) {
56
57
  const chains = {};
57
58
  for (const obj of objects || []) {
58
59
  if (obj in chains) continue;
59
- const chain = ancestryChain(obj, parents, { cap });
60
+ const chain = ancestryChain(obj, parents, { cap, stopAt: ANSWER_STOP_SET });
60
61
  if (chain.length > 1) chains[obj] = chain;
61
62
  }
62
63
  return chains;
@@ -0,0 +1,29 @@
1
+ // hub-terms.mjs — the class-hierarchy terms too abstract for a plain-English
2
+ // answer to climb into, whichever caller is walking a superclass chain. A
3
+ // leaf module (no imports) so both the persona-tier curator (a corpus build
4
+ // script, heavy WordNet-walking logic that has no business in a browser
5
+ // bundle) and sense-split.mjs's runtime chain walker (bundled client-side by
6
+ // the digest layer) can depend on the same set without either pulling in
7
+ // the other's weight.
8
+
9
+ // human-base's own category roots, plus every hypernym TARGET term Small's
10
+ // curation already established as a "root" word (generate.mjs's own comment:
11
+ // "category-root nouns used as a hypernym TARGET") — a real hypernym chain
12
+ // walk stops here rather than continuing on to WordNet's ultra-abstract
13
+ // "entity"/"abstraction"/"physical_entity" tops, which would add depth
14
+ // without adding anything a plain-English question would ever ask about.
15
+ export const STOP_SET = new Set([
16
+ "person", "place", "object", "event", "time", "quantity", "organization", "group",
17
+ "animal", "plant", "furniture", "vehicle", "insect", "emotion", "metal", "liquid",
18
+ "weather", "planet", "jewelry", "cutlery", "government", "material", "artifact",
19
+ "location", "structure", "food", "drink", "clothing", "body", "language", "mind",
20
+ "family", "meal", "season", "number", "entity", "abstraction", "physical_entity",
21
+ "attribute", "state", "act", "communication", "cognition", "measure", "unit",
22
+ ]);
23
+
24
+ // A wider hub set for a chat ANSWER's own chain rendering, where the climb
25
+ // walks further than the persona-tier corpus curation ever needs to: without
26
+ // it a plain "what is a letter" chain runs on past "character" into
27
+ // "property → concept → idea → content", each step less concrete than the
28
+ // last. STOP_SET itself stays exactly as the persona-tier curator reads it.
29
+ export const ANSWER_STOP_SET = new Set([...STOP_SET, "property", "concept", "idea", "content"]);
@@ -7,20 +7,13 @@
7
7
  // scripts/lib/wordnet-source.mjs, the fact targets and the run itself in
8
8
  // scripts/build-persona-tiers.mjs.
9
9
 
10
- // human-base's own category roots, plus every hypernym TARGET term Small's
11
- // curation already established as a "root" word (generate.mjs's own comment:
12
- // "category-root nouns used as a hypernym TARGET") a real hypernym chain
13
- // walk stops here rather than continuing on to WordNet's ultra-abstract
14
- // "entity"/"abstraction"/"physical_entity" tops, which would add depth
15
- // without adding anything a plain-English question would ever ask about.
16
- export const STOP_SET = new Set([
17
- "person", "place", "object", "event", "time", "quantity", "organization", "group",
18
- "animal", "plant", "furniture", "vehicle", "insect", "emotion", "metal", "liquid",
19
- "weather", "planet", "jewelry", "cutlery", "government", "material", "artifact",
20
- "location", "structure", "food", "drink", "clothing", "body", "language", "mind",
21
- "family", "meal", "season", "number", "entity", "abstraction", "physical_entity",
22
- "attribute", "state", "act", "communication", "cognition", "measure", "unit",
23
- ]);
10
+ // The hub-word stop set lives in ../hub-terms.mjs (re-exported here for every
11
+ // existing caller of this module) so sense-split.mjs's runtime chain walker
12
+ // can share it without importing this whole build-time curation module
13
+ // this file's other exports (buildClump, stripDenylisted, …) are WordNet-walk
14
+ // logic with no business in the browser bundle the digest layer links into.
15
+ import { STOP_SET } from "../hub-terms.mjs";
16
+ export { STOP_SET };
24
17
 
25
18
  export const BLOCKLIST_RE = /\b(archaic|obsolete|offensive|derogatory|informal|slang|dialect|euphemism|hypothetical|imaginary|mythical|mythology|extraterrestrial|fictional|taxonomic genus|genus of|family [A-Z]|nonstandard|vulgar|disparaging|obscene|coarse|genital|ethnic slur|ethnic epithet|excrement|contemptuous|insulting|trade name|street name|controlled substance|illegal|sexual assault|monoclonal antibody|chemical compound|chemical formula|proprietary name)\b/i;
26
19
 
@@ -16,6 +16,9 @@
16
16
  // 5. otherwise -> not distinct.
17
17
 
18
18
  import { SUBCLASS_PREDICATE } from "./syllogise.mjs";
19
+ import { STOP_SET } from "./hub-terms.mjs";
20
+
21
+ export { STOP_SET };
19
22
 
20
23
  /** child term -> Set of its direct superclasses, from `[child, parent]` edges.
21
24
  * Self-loops and blank endpoints are dropped. */
@@ -29,20 +32,82 @@ export function subClassParents(subClassEdges) {
29
32
  return parents;
30
33
  }
31
34
 
35
+ /** Codepoint string order. Deliberately not localeCompare: the walks below
36
+ * must return the same set on any machine and any ICU version. */
37
+ const compareStrings = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
38
+
39
+ /** parent term -> Set of its direct subclasses, from `[child, parent]` edges.
40
+ * The mirror of subClassParents. Self-loops and blank endpoints are dropped. */
41
+ export function subClassChildren(subClassEdges) {
42
+ const children = new Map();
43
+ for (const [child, parent] of subClassEdges || []) {
44
+ if (!child || !parent || child === parent) continue;
45
+ if (!children.has(parent)) children.set(parent, new Set());
46
+ children.get(parent).add(child);
47
+ }
48
+ return children;
49
+ }
50
+
51
+ /** Every class reachable BELOW `seeds` through subClassOf edges, including the
52
+ * seeds themselves. `seeds` is a string or an iterable of strings.
53
+ * Level-ordered BFS with a sorted frontier, so the `cap` truncation is a pure
54
+ * function of the edge set rather than of the order the edges arrived in.
55
+ * Cycle-safe. */
56
+ export function descendantSet(seeds, children, { cap = 20000 } = {}) {
57
+ const seen = new Set();
58
+ let frontier = [...(typeof seeds === "string" ? [seeds] : (seeds || []))]
59
+ .filter(Boolean).sort(compareStrings);
60
+ for (const s of frontier) seen.add(s);
61
+ while (frontier.length && seen.size < cap) {
62
+ const next = [];
63
+ for (const node of frontier) for (const c of children.get(node) || []) if (!seen.has(c)) next.push(c);
64
+ next.sort(compareStrings);
65
+ frontier = [];
66
+ for (const c of next) {
67
+ if (seen.has(c) || seen.size >= cap) continue;
68
+ seen.add(c);
69
+ frontier.push(c);
70
+ }
71
+ }
72
+ return seen;
73
+ }
74
+
32
75
  /** The single rendering chain above `term` — `[term, parent, grandparent, …]`
33
76
  * — following one deterministic (lowest-sorted) parent per hop, capped at
34
77
  * `cap` nodes, cycle-safe. A branch point still yields one readable line;
35
- * the full-ancestor-set comparisons below never rely on this single path. */
36
- export function ancestryChain(term, parents, { cap = 6 } = {}) {
78
+ * the full-ancestor-set comparisons below never rely on this single path.
79
+ *
80
+ * `stopAt` (e.g. hub-terms.mjs's STOP_SET) names ancestors too abstract for
81
+ * a plain-English chain to climb into — "letter → character → property →
82
+ * concept" reads fine, "…→ cognition" doesn't. The walk halts BEFORE
83
+ * appending a later hub ancestor, so a hub never shows up mid-chain with
84
+ * more abstraction stacked past it. The direct first parent is the one
85
+ * exception: it is always shown even when it is itself a hub, so a term
86
+ * whose only real parent is a hub still renders that one direct parent
87
+ * (matching the plain "term is a kind of parent" sentence already stated)
88
+ * rather than an empty chain — the walk then still stops right there.
89
+ *
90
+ * `toward` steers which parent the walk follows at a branch point: among a
91
+ * node's parents, one that actually reaches `toward` is preferred over the
92
+ * lowest-sorted one, so a class with two parents still renders a chain that
93
+ * reaches the class the reader asked about. `stopAt` and `toward` compose —
94
+ * `toward` only changes which parent is picked, `stopAt` still decides where
95
+ * the picked path halts. */
96
+ export function ancestryChain(term, parents, { cap = 6, stopAt = null, toward = null } = {}) {
37
97
  const chain = [term];
38
98
  const seen = new Set([term]);
39
99
  let node = term;
40
100
  while (chain.length < cap) {
41
- const ups = [...(parents.get(node) || [])].filter((p) => !seen.has(p)).sort();
101
+ if (toward && node === toward) break;
102
+ const ups = [...(parents.get(node) || [])].filter((p) => !seen.has(p)).sort(compareStrings);
42
103
  if (!ups.length) break;
43
- node = ups[0];
104
+ const onPath = toward ? ups.filter((p) => p === toward || ancestorSet(p, parents).has(toward)) : [];
105
+ node = (onPath.length ? onPath : ups)[0];
106
+ const isHub = stopAt && stopAt.has(node);
107
+ if (isHub && chain.length > 1) break;
44
108
  seen.add(node);
45
109
  chain.push(node);
110
+ if (isHub) break;
46
111
  }
47
112
  return chain;
48
113
  }
@@ -80,8 +80,14 @@ function provKey(tier) {
80
80
  *
81
81
  * Multiple citations (a multi-step proof chain) resolve by the same
82
82
  * taught-over-entailed-over-corpus precedence provBucketFor's own header
83
- * documents for one fact's sourceTypes, applied across citations instead:
84
- * a chain resting on any taught premise reads as "taught" overall.
83
+ * documents for one fact's sourceTypes, applied across citations instead
84
+ * but only on a real teach turn (`record.via === "assert"`), where the
85
+ * user's own action IS the taught premise the chip announces. A READ turn
86
+ * (a question answered by citing stored facts) only earns the "taught" chip
87
+ * when EVERY citation is taught: one taught fact among otherwise-corpus or
88
+ * entailed citations is the query resting on background data, not the user
89
+ * teaching anything, so it falls through to the same entailed-over-corpus
90
+ * choice a wholly non-taught answer gets.
85
91
  *
86
92
  * Self-contained (no outer refs beyond the injected `bucketFor`),
87
93
  * `.toString()`-splice safe.
@@ -91,7 +97,8 @@ export function provenanceChipFor(answer, record, bucketFor) {
91
97
  const cites = [...String(answer || "").matchAll(/\(source: ([^)]+)\)/g)].map((m) => m[1]);
92
98
  if (!cites.length) return record.via === "assert" ? "taught" : null;
93
99
  const buckets = cites.map((c) => bucketFor([], c));
94
- if (buckets.includes("taught")) return "taught";
100
+ const allTaught = buckets.every((b) => b === "taught");
101
+ if (buckets.includes("taught") && (record.via === "assert" || allTaught)) return "taught";
95
102
  if (buckets.includes("entailed")) return "entailed";
96
103
  return "corpus";
97
104
  }
@@ -2439,7 +2446,7 @@ ${shareOverlayHtml({ withTape: true })}
2439
2446
  } catch { /* best-effort seeding only — a fresh session has none to seed */ }
2440
2447
  }
2441
2448
  renderResearchedPanel();
2442
- inputEl.placeholder = seedPayload ? 'try "what is a dog" or "list facts"' : window.tmct.page.vocabExampleHint(false);
2449
+ inputEl.placeholder = seedPayload ? 'try "what is a dog" or "list animals"' : window.tmct.page.vocabExampleHint(false);
2443
2450
  renderStatus();
2444
2451
  setBusy(false);
2445
2452
  inputEl.focus();