@polycode-projects/the-mechanical-code-talker 2.6.1 → 2.7.0

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.
Files changed (56) hide show
  1. package/corpus/LICENSES.json +14 -0
  2. package/corpus/child/LICENSE-NOTICE +43 -0
  3. package/corpus/child/README.md +79 -0
  4. package/corpus/child/index.json.gz +0 -0
  5. package/corpus/child/manifest.json +313 -0
  6. package/corpus/child/shards/child-00.jsonl.gz +0 -0
  7. package/corpus/child/shards/child-01.jsonl.gz +0 -0
  8. package/corpus/child/shards/child-02.jsonl.gz +0 -0
  9. package/corpus/child/shards/child-03.jsonl.gz +0 -0
  10. package/corpus/child/shards/child-04.jsonl.gz +0 -0
  11. package/corpus/child/shards/child-05.jsonl.gz +0 -0
  12. package/corpus/child/shards/child-06.jsonl.gz +0 -0
  13. package/corpus/child/shards/child-07.jsonl.gz +0 -0
  14. package/corpus/child/shards/child-08.jsonl.gz +0 -0
  15. package/corpus/child/shards/child-09.jsonl.gz +0 -0
  16. package/corpus/child/shards/child-0a.jsonl.gz +0 -0
  17. package/corpus/child/shards/child-0b.jsonl.gz +0 -0
  18. package/corpus/child/shards/child-0c.jsonl.gz +0 -0
  19. package/corpus/child/shards/child-0d.jsonl.gz +0 -0
  20. package/corpus/child/shards/child-0e.jsonl.gz +0 -0
  21. package/corpus/child/shards/child-0f.jsonl.gz +0 -0
  22. package/corpus/child/shards/child-10.jsonl.gz +0 -0
  23. package/corpus/child/shards/child-11.jsonl.gz +0 -0
  24. package/corpus/child/shards/child-12.jsonl.gz +0 -0
  25. package/corpus/child/shards/child-13.jsonl.gz +0 -0
  26. package/corpus/child/shards/child-14.jsonl.gz +0 -0
  27. package/corpus/child/shards/child-15.jsonl.gz +0 -0
  28. package/corpus/child/shards/child-16.jsonl.gz +0 -0
  29. package/corpus/child/shards/child-17.jsonl.gz +0 -0
  30. package/corpus/child/shards/child-18.jsonl.gz +0 -0
  31. package/corpus/child/shards/child-19.jsonl.gz +0 -0
  32. package/corpus/child/shards/child-1a.jsonl.gz +0 -0
  33. package/corpus/child/shards/child-1b.jsonl.gz +0 -0
  34. package/corpus/child/shards/child-1c.jsonl.gz +0 -0
  35. package/corpus/child/shards/child-1d.jsonl.gz +0 -0
  36. package/corpus/child/shards/child-1e.jsonl.gz +0 -0
  37. package/corpus/child/shards/child-1f.jsonl.gz +0 -0
  38. package/corpus/conceptnet/child-seed.mjs +169 -0
  39. package/corpus/conceptnet/filter-dump.mjs +69 -48
  40. package/corpus/worlds/README.md +26 -0
  41. package/corpus/worlds/index.json.gz +0 -0
  42. package/corpus/worlds/manifest.json +33 -0
  43. package/corpus/worlds/shards/ashcombe-hall.jsonl.gz +0 -0
  44. package/corpus/worlds/src/ashcombe-hall.jsonl +64 -0
  45. package/package.json +4 -1
  46. package/src/adapters/corpus/child-pack.mjs +115 -0
  47. package/src/adapters/corpus/conceptnet-map.toml +7 -0
  48. package/src/adapters/corpus/worlds-pack.mjs +122 -0
  49. package/src/domain/child-pack.mjs +79 -0
  50. package/src/domain/grammar/ace.mjs +77 -0
  51. package/src/domain/grammar/lexicon-core.json +6 -0
  52. package/src/domain/memory/trust.mjs +8 -0
  53. package/src/domain/worlds-pack.mjs +71 -0
  54. package/src/services/adventure.mjs +669 -0
  55. package/src/services/chat.mjs +26 -0
  56. package/src/surfaces/web/memory-ask-browser.bundle.js +521 -39
@@ -0,0 +1,169 @@
1
+ // child-seed.mjs — the age-of-acquisition seed for the CHILD triples pack, and
2
+ // the licence decision that shaped it. NOT part of the product path — a
3
+ // maintainer file, read by scripts/fetch-child-corpus.mjs to select a
4
+ // child-concept slice of the full ConceptNet dump. Sits beside
5
+ // fetch-slice.mjs's SEED_TERMS on purpose: same role (the seed the dump is
6
+ // filtered against), different domain (everyday concepts a young child knows,
7
+ // not the software/tech world).
8
+ //
9
+ // THE LICENCE DECISION (settled before any data was fetched, PLAN step 1).
10
+ // The plan proposed seeding from a published age-of-acquisition word list —
11
+ // Kuperman, Stadthagen-Gonzalez & Brysbaert (2012), 30,121 rated English
12
+ // words — thresholded at AoA <= 8. Two candidates were checked for shippable
13
+ // terms, and both were ruled out for a package that ships publicly under
14
+ // MPL-2.0:
15
+ //
16
+ // - Kuperman et al. 2012 (crr.ugent.be / Center for Reading Research, Ghent).
17
+ // The original distribution site was restructured and its file terms could
18
+ // not be verified directly. The aggregator norare.clld.org relabels its own
19
+ // compilation CC-BY-4.0, but that is the aggregate database's label, not a
20
+ // verified grant on the raw Kuperman file. The same lab's sibling AoA norms
21
+ // (Ghyselinck, Custers & Brysbaert, still live at ugent.be) state their
22
+ // terms plainly: "the norms are for non-profit use only" and "can be
23
+ // downloaded and/or consulted for research purposes." That is a
24
+ // NonCommercial, research-only restriction. Shipping a threshold-filtered
25
+ // derivative list inside a public, commercially-usable npm package is
26
+ // exactly what such terms forbid, and a NoDerivatives condition (common on
27
+ // these norms) would forbid the derivative list on its own. Unverifiable +
28
+ // non-commercial => not shippable here.
29
+ //
30
+ // - Dale-Chall 3,000 (the New Dale-Chall list, Chall & Dale, Readability
31
+ // Revisited, Brookline Books, 1995). Widely reproduced online, and an
32
+ // MIT-packaged copy exists (github.com/words/dale-chall). But the 3,000-word
33
+ // list is the selection and arrangement published in a 1995 copyrighted
34
+ // book; "widely reproduced" is not a licence grant from the rights holder,
35
+ // and the clean, verifiable status of the list as distributable data could
36
+ // not be established. Unverifiable => not shippable under the plan's own
37
+ // "do not ship anything whose terms you could not verify" rule.
38
+ //
39
+ // DECISION: author a maintainer-owned original child-concept seed — the plan's
40
+ // named third option, and the honest one. It is hand-curated everyday
41
+ // vocabulary (the same first-party pattern as corpus/tier2/human.jsonl), owned
42
+ // by this repository under MPL-2.0, and it needs no external licence because it
43
+ // copies no external list. It is NOT derived from Kuperman or Dale-Chall: no
44
+ // rated or ranked list was filtered to produce it. It is a filter, never a
45
+ // source (a word list only selects which ConceptNet edges ship) — so the
46
+ // SHIPPED DATA (corpus/child/, the triples) inherits ConceptNet's CC-BY-SA-4.0,
47
+ // exactly like corpus/conceptnet/slice.jsonl, while this seed file stays
48
+ // MPL-2.0. See corpus/child/LICENSE-NOTICE and corpus/LICENSES.json.
49
+
50
+ // The curation target: the concepts a bright ~8-year-old knows. This is the
51
+ // plan's "top-decile knob" made an explicit number instead of a claim about
52
+ // Dutch schooling — the age a maintainer raises to admit an older child's
53
+ // vocabulary (widen CHILD_SEED_TERMS to match). Selection is by hand to this
54
+ // target, not by thresholding a licensed AoA file (see the licence decision
55
+ // above), so the constant documents the curation boundary rather than driving a
56
+ // numeric cut.
57
+ export const CHILD_AOA_TARGET_YEARS = 8;
58
+
59
+ // Everyday concepts, underscored to match ConceptNet's bare term URIs
60
+ // (/c/en/teddy_bear -> "teddy_bear"), grouped by semantic field. An edge ships
61
+ // if EITHER endpoint's bare term is in this set, so a hypernym like "bird"
62
+ // pulls in every "<species> IsA bird" edge and a verb like "fly" pulls in every
63
+ // "<thing> CapableOf fly" edge without the species or the thing being listed.
64
+ // The bird/flight fields are deliberately dense: they are the plan's acceptance
65
+ // probes (kinds of bird, capabilities among them, things that can fly).
66
+ export const CHILD_SEED_TERMS = [
67
+ // animals — the everyday menagerie
68
+ "animal", "pet", "dog", "puppy", "cat", "kitten", "rabbit", "bunny", "mouse",
69
+ "rat", "hamster", "guinea_pig", "horse", "pony", "cow", "calf", "pig", "piglet",
70
+ "sheep", "lamb", "goat", "chicken", "hen", "rooster", "duck", "duckling",
71
+ "goose", "turkey", "fox", "wolf", "bear", "lion", "tiger", "elephant", "giraffe",
72
+ "zebra", "monkey", "gorilla", "kangaroo", "koala", "panda", "deer", "squirrel",
73
+ "hedgehog", "mole", "bat", "frog", "toad", "snake", "lizard", "turtle", "tortoise",
74
+ "crocodile", "dinosaur", "whale", "dolphin", "shark", "seal", "octopus", "crab",
75
+ "lobster", "starfish", "jellyfish", "snail", "worm", "spider", "ant", "bee",
76
+ "wasp", "fly", "ladybug", "butterfly", "caterpillar", "moth", "beetle", "insect",
77
+ "bug", "grasshopper", "cricket", "dragonfly",
78
+ // birds — dense on purpose (kinds of bird, and what they can do)
79
+ "bird", "robin", "sparrow", "swallow", "swift", "wren", "finch", "blackbird",
80
+ "crow", "raven", "magpie", "pigeon", "dove", "seagull", "gull", "duck", "swan",
81
+ "goose", "owl", "eagle", "hawk", "falcon", "kestrel", "vulture", "parrot",
82
+ "budgie", "canary", "peacock", "flamingo", "stork", "heron", "crane", "penguin",
83
+ "ostrich", "emu", "kiwi", "woodpecker", "kingfisher", "hummingbird", "chick",
84
+ "nest", "feather", "wing", "beak", "egg",
85
+ // the body
86
+ "body", "head", "hair", "face", "eye", "ear", "nose", "mouth", "lip", "tooth",
87
+ "tongue", "cheek", "chin", "neck", "shoulder", "arm", "elbow", "hand", "finger",
88
+ "thumb", "nail", "chest", "tummy", "belly", "back", "bottom", "leg", "knee",
89
+ "foot", "toe", "heart", "bone", "skin", "blood", "brain",
90
+ // family & people
91
+ "family", "mum", "mummy", "mother", "dad", "daddy", "father", "parent", "baby",
92
+ "child", "kid", "boy", "girl", "brother", "sister", "grandma", "grandpa",
93
+ "grandmother", "grandfather", "aunt", "uncle", "cousin", "friend", "person",
94
+ "people", "man", "woman", "teacher", "doctor", "nurse", "dentist", "farmer",
95
+ "police_officer", "firefighter", "postman", "driver", "chef", "king", "queen",
96
+ "prince", "princess", "pirate", "giant", "witch", "wizard", "fairy", "monster",
97
+ "ghost", "dragon",
98
+ // food & drink
99
+ "food", "breakfast", "lunch", "dinner", "snack", "meal", "bread", "toast",
100
+ "butter", "jam", "sandwich", "cheese", "egg", "milk", "water", "juice", "tea",
101
+ "coffee", "cake", "biscuit", "cookie", "chocolate", "sweet", "candy", "sugar",
102
+ "honey", "ice_cream", "lolly", "fruit", "apple", "banana", "orange", "pear",
103
+ "grape", "strawberry", "cherry", "lemon", "peach", "plum", "melon", "pineapple",
104
+ "vegetable", "potato", "carrot", "pea", "bean", "tomato", "cucumber", "lettuce",
105
+ "onion", "corn", "mushroom", "pumpkin", "rice", "pasta", "pizza", "soup",
106
+ "chip", "fish", "meat", "chicken", "sausage", "burger", "salt", "pepper",
107
+ // home
108
+ "house", "home", "flat", "room", "door", "window", "wall", "floor", "ceiling",
109
+ "roof", "stair", "chimney", "garden", "fence", "gate", "kitchen", "bathroom",
110
+ "bedroom", "living_room", "table", "chair", "sofa", "bed", "pillow", "blanket",
111
+ "cupboard", "shelf", "drawer", "lamp", "clock", "mirror", "carpet", "curtain",
112
+ "television", "telephone", "fridge", "oven", "cooker", "sink", "bath", "shower",
113
+ "toilet", "soap", "towel", "toothbrush", "cup", "mug", "glass", "plate", "bowl",
114
+ "spoon", "fork", "knife", "pan", "pot", "kettle", "bottle", "box", "bag",
115
+ "basket", "bucket", "brush", "broom", "key", "candle", "toy",
116
+ // clothes
117
+ "clothes", "shirt", "tshirt", "jumper", "sweater", "coat", "jacket", "dress",
118
+ "skirt", "trousers", "jeans", "shorts", "sock", "shoe", "boot", "slipper",
119
+ "hat", "cap", "scarf", "glove", "mitten", "belt", "button", "pocket", "pyjamas",
120
+ "nappy", "apron", "raincoat", "wellington",
121
+ // school & play
122
+ "school", "class", "classroom", "lesson", "teacher", "pupil", "book", "story",
123
+ "page", "word", "letter", "number", "pencil", "pen", "crayon", "paint",
124
+ "brush", "paper", "glue", "scissors", "ruler", "rubber", "chalk", "desk",
125
+ "bag", "lunchbox", "playground", "swing", "slide", "seesaw", "ball", "bat",
126
+ "kite", "balloon", "bubble", "game", "puzzle", "jigsaw", "block", "brick",
127
+ "doll", "teddy", "teddy_bear", "robot", "drum", "whistle", "marble", "skipping_rope",
128
+ // getting around
129
+ "car", "bus", "lorry", "truck", "van", "taxi", "bike", "bicycle", "tricycle",
130
+ "scooter", "motorbike", "train", "tram", "boat", "ship", "sailboat", "canoe",
131
+ "ferry", "plane", "aeroplane", "airplane", "aircraft", "jet", "helicopter",
132
+ "rocket", "spaceship", "balloon", "wheel", "engine", "road", "street", "bridge",
133
+ "tunnel", "station", "airport", "harbour", "traffic_light",
134
+ // the world outside
135
+ "tree", "leaf", "branch", "root", "trunk", "wood", "forest", "flower", "petal",
136
+ "rose", "daisy", "tulip", "grass", "bush", "plant", "seed", "garden", "park",
137
+ "field", "farm", "hill", "mountain", "valley", "cave", "rock", "stone", "sand",
138
+ "mud", "puddle", "pond", "lake", "river", "stream", "sea", "ocean", "beach",
139
+ "island", "wave", "shell", "cliff", "waterfall",
140
+ // sky & weather
141
+ "sky", "sun", "moon", "star", "cloud", "rainbow", "rain", "snow", "snowflake",
142
+ "ice", "frost", "wind", "storm", "thunder", "lightning", "fog", "sunshine",
143
+ "shadow", "day", "night", "morning", "afternoon", "evening", "weather",
144
+ // time, seasons, celebrations
145
+ "time", "hour", "minute", "week", "month", "year", "today", "tomorrow",
146
+ "yesterday", "birthday", "party", "present", "christmas", "holiday", "weekend",
147
+ "spring", "summer", "autumn", "winter", "season",
148
+ // colours & shapes (concepts, not just adjectives)
149
+ "colour", "color", "red", "orange", "yellow", "green", "blue", "purple", "pink",
150
+ "brown", "black", "white", "grey", "gold", "silver", "shape", "circle", "square",
151
+ "triangle", "rectangle", "star", "heart", "line", "dot",
152
+ // everyday verbs (pull in CapableOf / HasSubevent edges)
153
+ "run", "walk", "jump", "hop", "skip", "climb", "crawl", "swim", "fly", "float",
154
+ "dive", "roll", "spin", "dance", "sing", "shout", "laugh", "cry", "smile",
155
+ "sleep", "wake", "eat", "drink", "bite", "chew", "cook", "bake", "wash", "clean",
156
+ "brush", "read", "write", "draw", "paint", "count", "play", "throw", "catch",
157
+ "kick", "push", "pull", "carry", "build", "break", "fix", "cut", "dig", "plant",
158
+ "grow", "melt", "freeze", "burn", "splash", "bounce", "hide", "chase", "hug",
159
+ "kiss", "help", "share", "give", "take", "find", "lose", "open", "close", "ride",
160
+ "drive", "sail", "row", "hear", "see", "look", "listen", "smell", "taste",
161
+ "touch", "feel", "think", "learn", "teach", "talk", "ask", "answer", "whisper",
162
+ // simple properties & feelings (HasProperty)
163
+ "big", "small", "little", "tall", "short", "long", "tiny", "huge", "fast",
164
+ "slow", "hot", "cold", "warm", "cool", "wet", "dry", "hard", "soft", "loud",
165
+ "quiet", "clean", "dirty", "new", "old", "young", "happy", "sad", "angry",
166
+ "scared", "afraid", "tired", "hungry", "thirsty", "sleepy", "kind", "naughty",
167
+ "good", "bad", "nice", "funny", "silly", "brave", "gentle", "heavy", "light",
168
+ "sweet", "sour", "bright", "dark", "sharp", "round", "sick", "poorly", "better",
169
+ ];
@@ -25,6 +25,10 @@
25
25
  // whatever budget remains — they never crowd out seedable facts;
26
26
  // - final order (rel, start, end) for deterministic diffs.
27
27
  // Stats land on stderr; the JSONL lands on stdout.
28
+ //
29
+ // The streaming seed/relation/dedupe pass is exported as scanAssertions so the
30
+ // child-corpus build (scripts/fetch-child-corpus.mjs) can reuse ONE filter over
31
+ // its own seed and its own admitted-relation set, instead of a second copy.
28
32
 
29
33
  import { createInterface } from "node:readline";
30
34
  import { SEED_TERMS, CANONICAL_RELS, FILTERED_RELS, bareEnTerm } from "./fetch-slice.mjs";
@@ -91,56 +95,73 @@ const EXTRA_SEEDS = [
91
95
  const SEEDS = new Set([...SEED_TERMS, ...EXTRA_SEEDS]);
92
96
  const termOf = (uri) => uri.slice("/c/en/".length);
93
97
 
94
- const byKey = new Map(); // "start rel end" -> row
95
- let scanned = 0;
96
- const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
97
- for await (const line of rl) {
98
- scanned += 1;
99
- const cols = line.split("\t");
100
- if (cols.length < 5) continue;
101
- const rel = cols[1];
102
- if (!CANONICAL_RELS.has(rel) || FILTERED_RELS.has(rel)) continue;
103
- const start = bareEnTerm(cols[2]);
104
- const end = bareEnTerm(cols[3]);
105
- if (!start || !end || start === end) continue;
106
- if (!SEEDS.has(termOf(start)) && !SEEDS.has(termOf(end))) continue;
107
- let info = {};
108
- try {
109
- info = JSON.parse(cols[4]);
110
- } catch {
111
- /* a malformed info column loses us weight/surfaceText, not the edge */
98
+ /**
99
+ * Stream one dump (an async iterable of TSV lines) and keep every en→en
100
+ * assertion whose relation is admitted and at least one endpoint's bare term is
101
+ * in `seeds`. Deduped by (start, rel, end), higher weight winning. Pure of any
102
+ * budget or ordering — the caller trims and sorts. Returns { rows, scanned }.
103
+ *
104
+ * `admittedRels` defaults to the 34 canonical relations minus the policy-filtered
105
+ * ones; the child build passes its own set (canonical + /r/NotCapableOf) so the
106
+ * defeasible-negation data survives the filter.
107
+ */
108
+ export async function scanAssertions(lines, { seeds, admittedRels = CANONICAL_RELS, filteredRels = FILTERED_RELS } = {}) {
109
+ const byKey = new Map(); // "start rel end" -> row
110
+ let scanned = 0;
111
+ for await (const line of lines) {
112
+ scanned += 1;
113
+ const cols = line.split("\t");
114
+ if (cols.length < 5) continue;
115
+ const rel = cols[1];
116
+ if (!admittedRels.has(rel) || filteredRels.has(rel)) continue;
117
+ const start = bareEnTerm(cols[2]);
118
+ const end = bareEnTerm(cols[3]);
119
+ if (!start || !end || start === end) continue;
120
+ if (!seeds.has(termOf(start)) && !seeds.has(termOf(end))) continue;
121
+ let info = {};
122
+ try {
123
+ info = JSON.parse(cols[4]);
124
+ } catch {
125
+ /* a malformed info column loses us weight/surfaceText, not the edge */
126
+ }
127
+ const row = { start, rel, end, weight: Number(info.weight) || 1 };
128
+ if (info.surfaceText) row.surfaceText = String(info.surfaceText);
129
+ const key = `${start} ${rel} ${end}`;
130
+ const prev = byKey.get(key);
131
+ if (!prev || row.weight > prev.weight) byKey.set(key, row);
112
132
  }
113
- const row = { start, rel, end, weight: Number(info.weight) || 1 };
114
- if (info.surfaceText) row.surfaceText = String(info.surfaceText);
115
- const key = `${start} ${rel} ${end}`;
116
- const prev = byKey.get(key);
117
- if (!prev || row.weight > prev.weight) byKey.set(key, row);
133
+ return { rows: [...byKey.values()], scanned };
118
134
  }
119
135
 
120
- // budget-trim: mappable relations first, then none-rows each tier
121
- // weight-descending, so the strongest seedable facts always survive
122
- const map = await loadMap();
123
- const byWeight = (a, b) =>
124
- b.weight - a.weight || a.rel.localeCompare(b.rel) || a.start.localeCompare(b.start) || a.end.localeCompare(b.end);
125
- const all = [...byKey.values()];
126
- const mappable = all.filter((r) => map.get(r.rel)?.ace !== "none").sort(byWeight);
127
- const unmappable = all.filter((r) => map.get(r.rel)?.ace === "none").sort(byWeight);
128
- const kept = [];
129
- let bytes = 0;
130
- for (const row of [...mappable, ...unmappable]) {
131
- const line = JSON.stringify(row) + "\n";
132
- if (bytes + line.length > MAX_BYTES) continue;
133
- bytes += line.length;
134
- kept.push(row);
135
- }
136
- kept.sort((a, b) => a.rel.localeCompare(b.rel) || a.start.localeCompare(b.start) || a.end.localeCompare(b.end));
136
+ const isMain = process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href;
137
+ if (isMain) {
138
+ const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
139
+ const { rows: all, scanned } = await scanAssertions(rl, { seeds: SEEDS });
137
140
 
138
- for (const row of kept) process.stdout.write(JSON.stringify(row) + "\n");
141
+ // budget-trim: mappable relations first, then none-rows — each tier
142
+ // weight-descending, so the strongest seedable facts always survive
143
+ const map = await loadMap();
144
+ const byWeight = (a, b) =>
145
+ b.weight - a.weight || a.rel.localeCompare(b.rel) || a.start.localeCompare(b.start) || a.end.localeCompare(b.end);
146
+ const mappable = all.filter((r) => map.get(r.rel)?.ace !== "none").sort(byWeight);
147
+ const unmappable = all.filter((r) => map.get(r.rel)?.ace === "none").sort(byWeight);
148
+ const kept = [];
149
+ let bytes = 0;
150
+ for (const row of [...mappable, ...unmappable]) {
151
+ const line = JSON.stringify(row) + "\n";
152
+ if (bytes + line.length > MAX_BYTES) continue;
153
+ bytes += line.length;
154
+ kept.push(row);
155
+ }
156
+ kept.sort((a, b) => a.rel.localeCompare(b.rel) || a.start.localeCompare(b.start) || a.end.localeCompare(b.end));
139
157
 
140
- const perRel = new Map();
141
- for (const r of kept) perRel.set(r.rel, (perRel.get(r.rel) || 0) + 1);
142
- const keptMappable = kept.filter((r) => map.get(r.rel)?.ace !== "none").length;
143
- console.error(`scanned ${scanned} dump lines; matched ${all.length} unique en→en seed assertions `
144
- + `(${mappable.length} mappable + ${unmappable.length} ace=none); `
145
- + `kept ${kept.length} (${keptMappable} mappable + ${kept.length - keptMappable} none) in ${bytes} bytes`);
146
- for (const [rel, n] of [...perRel.entries()].sort((a, b) => b[1] - a[1])) console.error(` ${rel}: ${n}`);
158
+ for (const row of kept) process.stdout.write(JSON.stringify(row) + "\n");
159
+
160
+ const perRel = new Map();
161
+ for (const r of kept) perRel.set(r.rel, (perRel.get(r.rel) || 0) + 1);
162
+ const keptMappable = kept.filter((r) => map.get(r.rel)?.ace !== "none").length;
163
+ console.error(`scanned ${scanned} dump lines; matched ${all.length} unique en→en seed assertions `
164
+ + `(${mappable.length} mappable + ${unmappable.length} ace=none); `
165
+ + `kept ${kept.length} (${keptMappable} mappable + ${kept.length - keptMappable} none) in ${bytes} bytes`);
166
+ for (const [rel, n] of [...perRel.entries()].sort((a, b) => b[1] - a[1])) console.error(` ${rel}: ${n}`);
167
+ }
@@ -0,0 +1,26 @@
1
+ # corpus/worlds/
2
+
3
+ The shipped worlds pack: the game worlds the chat adventure lane can load on
4
+ request ("play ashcombe hall"), with a gzipped world index consulted first
5
+ and exactly one gzipped JSONL shard loaded per world
6
+ (src/adapters/corpus/worlds-pack.mjs). A world stays on disk until a session
7
+ asks for it; loading one appends its rows into that session's memory store
8
+ under `world:<name>` provenance. Layout:
9
+
10
+ - `src/<world>.jsonl` — the hand-authored source, one per world. Original
11
+ content, written for this repository; no text is copied from any book,
12
+ game or other IP.
13
+ - `index.json.gz` — `{ worldName: { s } }`: the shard holding that world.
14
+ - `shards/<world>.jsonl.gz` — one JSON row per line: `fact` rows (the
15
+ world's graph triples: rooms, exits, placements, cast), `rule` rows (the
16
+ world's pre-built action families, the same four action-Rule kinds the
17
+ live teach frames store), and one `meta` row (the opening line). Shapes in
18
+ src/domain/worlds-pack.mjs.
19
+ - `manifest.json` — counts, budgets, source and file hashes. No build date:
20
+ same sources in, same bytes out.
21
+
22
+ Rebuild with `npm run gen:worlds-pack`. The budgets in the build script are
23
+ caps, not targets — a world that breaks one gets cut, the number stays.
24
+
25
+ Licence: first-party, MPL-2.0 like the rest of the repository (see
26
+ corpus/LICENSES.json).
Binary file
@@ -0,0 +1,33 @@
1
+ {
2
+ "version": 1,
3
+ "generated": "by scripts/build-worlds-pack.mjs from corpus/worlds/src/",
4
+ "counts": {
5
+ "worlds": 1,
6
+ "facts": 51,
7
+ "rules": 12
8
+ },
9
+ "budgets": {
10
+ "sourceBytesPerWorld": 131072,
11
+ "shardGzBytes": 32768,
12
+ "indexGzBytes": 4096
13
+ },
14
+ "sources": [
15
+ {
16
+ "file": "src/ashcombe-hall.jsonl",
17
+ "bytes": 7481,
18
+ "sha256": "776634e8f7db704f2895ad1557cdcb79c28e14fe8fd4202ef1d0db9b4358ca91"
19
+ }
20
+ ],
21
+ "files": [
22
+ {
23
+ "file": "index.json.gz",
24
+ "bytes": 46,
25
+ "sha256": "5c380eaf9cc68c0420edd5a8df9803208070cceb3e717c4e87288d612f6a51a0"
26
+ },
27
+ {
28
+ "file": "shards/ashcombe-hall.jsonl.gz",
29
+ "bytes": 864,
30
+ "sha256": "468ba9ae859fc63094f5030648f07885e1e0785fb4e18e309cc4e6729c878ebf"
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,64 @@
1
+ {"world":"ashcombe-hall","kind":"meta","opening":"the adventure begins. You are in the study of Ashcombe Hall. Somewhere in this house is a letter meant for you. Say \"look\" to look around, \"go north\" (or south, east, west, up, down) to move, and \"stop playing\" to end the game."}
2
+ {"world":"ashcombe-hall","kind":"fact","subject":"player","predicate":"rdf:type","object":"adventurer"}
3
+ {"world":"ashcombe-hall","kind":"fact","subject":"study","predicate":"rdf:type","object":"room"}
4
+ {"world":"ashcombe-hall","kind":"fact","subject":"library","predicate":"rdf:type","object":"room"}
5
+ {"world":"ashcombe-hall","kind":"fact","subject":"drawing-room","predicate":"rdf:type","object":"room"}
6
+ {"world":"ashcombe-hall","kind":"fact","subject":"kitchen","predicate":"rdf:type","object":"room"}
7
+ {"world":"ashcombe-hall","kind":"fact","subject":"cellar","predicate":"rdf:type","object":"room"}
8
+ {"world":"ashcombe-hall","kind":"fact","subject":"garden","predicate":"rdf:type","object":"room"}
9
+ {"world":"ashcombe-hall","kind":"fact","subject":"housekeeper","predicate":"rdf:type","object":"person"}
10
+ {"world":"ashcombe-hall","kind":"fact","subject":"butler","predicate":"rdf:type","object":"person"}
11
+ {"world":"ashcombe-hall","kind":"fact","subject":"cook","predicate":"rdf:type","object":"person"}
12
+ {"world":"ashcombe-hall","kind":"fact","subject":"gardener","predicate":"rdf:type","object":"person"}
13
+ {"world":"ashcombe-hall","kind":"fact","subject":"key","predicate":"rdf:type","object":"portable"}
14
+ {"world":"ashcombe-hall","kind":"fact","subject":"letter","predicate":"rdf:type","object":"portable"}
15
+ {"world":"ashcombe-hall","kind":"fact","subject":"lamp","predicate":"rdf:type","object":"portable"}
16
+ {"world":"ashcombe-hall","kind":"fact","subject":"cabinet","predicate":"rdf:type","object":"furniture"}
17
+ {"world":"ashcombe-hall","kind":"fact","subject":"portrait","predicate":"rdf:type","object":"furniture"}
18
+ {"world":"ashcombe-hall","kind":"fact","subject":"desk","predicate":"rdf:type","object":"furniture"}
19
+ {"world":"ashcombe-hall","kind":"fact","subject":"study","predicate":"mgx:has-exit-north","object":"library"}
20
+ {"world":"ashcombe-hall","kind":"fact","subject":"library","predicate":"mgx:has-exit-south","object":"study"}
21
+ {"world":"ashcombe-hall","kind":"fact","subject":"library","predicate":"mgx:has-exit-north","object":"drawing-room"}
22
+ {"world":"ashcombe-hall","kind":"fact","subject":"drawing-room","predicate":"mgx:has-exit-south","object":"library"}
23
+ {"world":"ashcombe-hall","kind":"fact","subject":"library","predicate":"mgx:has-exit-east","object":"kitchen"}
24
+ {"world":"ashcombe-hall","kind":"fact","subject":"kitchen","predicate":"mgx:has-exit-west","object":"library"}
25
+ {"world":"ashcombe-hall","kind":"fact","subject":"kitchen","predicate":"mgx:has-exit-east","object":"garden"}
26
+ {"world":"ashcombe-hall","kind":"fact","subject":"garden","predicate":"mgx:has-exit-west","object":"kitchen"}
27
+ {"world":"ashcombe-hall","kind":"fact","subject":"study","predicate":"mgx:has-exit-down","object":"cellar"}
28
+ {"world":"ashcombe-hall","kind":"fact","subject":"cellar","predicate":"mgx:has-exit-up","object":"study"}
29
+ {"world":"ashcombe-hall","kind":"fact","subject":"player","predicate":"mgx:currently-in","object":"study"}
30
+ {"world":"ashcombe-hall","kind":"fact","subject":"cabinet","predicate":"mgx:stands-locked-in","object":"study"}
31
+ {"world":"ashcombe-hall","kind":"fact","subject":"desk","predicate":"mgx:fixed-in","object":"study"}
32
+ {"world":"ashcombe-hall","kind":"fact","subject":"lamp","predicate":"mgx:located-in","object":"study"}
33
+ {"world":"ashcombe-hall","kind":"fact","subject":"portrait","predicate":"mgx:fixed-in","object":"drawing-room"}
34
+ {"world":"ashcombe-hall","kind":"fact","subject":"key","predicate":"mgx:hidden-in","object":"portrait"}
35
+ {"world":"ashcombe-hall","kind":"fact","subject":"letter","predicate":"mgx:hidden-in","object":"cabinet"}
36
+ {"world":"ashcombe-hall","kind":"fact","subject":"housekeeper","predicate":"mgx:currently-in","object":"kitchen"}
37
+ {"world":"ashcombe-hall","kind":"fact","subject":"cook","predicate":"mgx:currently-in","object":"kitchen"}
38
+ {"world":"ashcombe-hall","kind":"fact","subject":"butler","predicate":"mgx:currently-in","object":"drawing-room"}
39
+ {"world":"ashcombe-hall","kind":"fact","subject":"gardener","predicate":"mgx:currently-in","object":"garden"}
40
+ {"world":"ashcombe-hall","kind":"fact","subject":"cabinet","predicate":"mgx:is-container","object":"true"}
41
+ {"world":"ashcombe-hall","kind":"fact","subject":"portrait","predicate":"mgx:is-container","object":"true"}
42
+ {"world":"ashcombe-hall","kind":"fact","subject":"cabinet","predicate":"mgx:unlocks-with","object":"key"}
43
+ {"world":"ashcombe-hall","kind":"fact","subject":"housekeeper","predicate":"mgx:is-npc","object":"true"}
44
+ {"world":"ashcombe-hall","kind":"fact","subject":"butler","predicate":"mgx:is-npc","object":"true"}
45
+ {"world":"ashcombe-hall","kind":"fact","subject":"cook","predicate":"mgx:is-npc","object":"true"}
46
+ {"world":"ashcombe-hall","kind":"fact","subject":"gardener","predicate":"mgx:is-npc","object":"true"}
47
+ {"world":"ashcombe-hall","kind":"fact","subject":"housekeeper","predicate":"mgx:acts-on-turn","object":"3"}
48
+ {"world":"ashcombe-hall","kind":"fact","subject":"housekeeper","predicate":"mgx:acts-toward","object":"library"}
49
+ {"world":"ashcombe-hall","kind":"fact","subject":"housekeeper","predicate":"mgx:works-in","object":"kitchen"}
50
+ {"world":"ashcombe-hall","kind":"fact","subject":"cook","predicate":"mgx:works-in","object":"kitchen"}
51
+ {"world":"ashcombe-hall","kind":"fact","subject":"butler","predicate":"mgx:works-in","object":"drawing-room"}
52
+ {"world":"ashcombe-hall","kind":"fact","subject":"gardener","predicate":"mgx:works-in","object":"garden"}
53
+ {"world":"ashcombe-hall","kind":"rule","name":"go","ruleKind":"action-signature","slots":{"subjectClass":"adventurer","targetClass":"room"}}
54
+ {"world":"ashcombe-hall","kind":"rule","name":"go","ruleKind":"action-signature","slots":{"subjectClass":"person","targetClass":"room"}}
55
+ {"world":"ashcombe-hall","kind":"rule","name":"go","ruleKind":"action-effect","slots":{"predicate":"currently-in","subjectRole":"subject","objectRole":"target"}}
56
+ {"world":"ashcombe-hall","kind":"rule","name":"take","ruleKind":"action-signature","slots":{"subjectClass":"portable","targetClass":"adventurer"}}
57
+ {"world":"ashcombe-hall","kind":"rule","name":"take","ruleKind":"action-effect","slots":{"predicate":"located-in","subjectRole":"subject","objectRole":"target"}}
58
+ {"world":"ashcombe-hall","kind":"rule","name":"drop","ruleKind":"action-signature","slots":{"subjectClass":"portable","targetClass":"room"}}
59
+ {"world":"ashcombe-hall","kind":"rule","name":"drop","ruleKind":"action-effect","slots":{"predicate":"located-in","subjectRole":"subject","objectRole":"target"}}
60
+ {"world":"ashcombe-hall","kind":"rule","name":"give","ruleKind":"action-signature","slots":{"subjectClass":"portable","targetClass":"person"}}
61
+ {"world":"ashcombe-hall","kind":"rule","name":"give","ruleKind":"action-effect","slots":{"predicate":"located-in","subjectRole":"subject","objectRole":"target"}}
62
+ {"world":"ashcombe-hall","kind":"rule","name":"open","ruleKind":"action-signature","slots":{"subjectClass":"adventurer","targetClass":"furniture"}}
63
+ {"world":"ashcombe-hall","kind":"rule","name":"unlock","ruleKind":"action-signature","slots":{"subjectClass":"adventurer","targetClass":"furniture"}}
64
+ {"world":"ashcombe-hall","kind":"rule","name":"close","ruleKind":"action-signature","slots":{"subjectClass":"adventurer","targetClass":"furniture"}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
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; no codebase index of its own.",
@@ -143,7 +143,10 @@
143
143
  "gen:collisions": "node scripts/generate-real-word-collisions.mjs",
144
144
  "gen:prose-corpus": "node scripts/fetch-prose-corpus.mjs",
145
145
  "gen:reference-pack": "node scripts/fetch-reference-pack.mjs",
146
+ "gen:child-corpus": "node scripts/fetch-child-corpus.mjs",
147
+ "measure:child-corpus": "node scripts/measure-child-corpus.mjs",
146
148
  "gen:variants": "node scripts/generate-template-variants.mjs",
149
+ "gen:worlds-pack": "node scripts/build-worlds-pack.mjs",
147
150
  "extract": "node bin/tmct.mjs extract",
148
151
  "extract:facts": "node bin/tmct.mjs extract"
149
152
  },
@@ -0,0 +1,115 @@
1
+ // corpus/child-pack.mjs — lazy, failure-tolerated loader for the shipped CHILD
2
+ // triples pack (corpus/child/): a gzipped term index consulted first, then
3
+ // exactly one gzipped JSONL shard per hit. Nothing here ever throws at a
4
+ // caller — an absent, truncated or corrupt pack reads as null, and a null is
5
+ // the ordinary honest miss the clean-miss cascade already knows how to answer.
6
+ //
7
+ // Mirrors src/adapters/corpus/reference-pack.mjs exactly, including its provider
8
+ // seam: registerChildPackProvider swaps the whole lookup behind one async
9
+ // `{ lookup(normTerm) }` contract, so the browser demo (which cannot read this
10
+ // filesystem layout) can register a fetch-backed provider and the chat miss-
11
+ // cascade never knows which one it is talking to. No provider registered = the
12
+ // fs loader below.
13
+
14
+ import { readFileSync } from "node:fs";
15
+ import { gunzipSync } from "node:zlib";
16
+ import { fileURLToPath } from "node:url";
17
+ import { dirname, join } from "node:path";
18
+ import { isChildFactsRow, isChildIndexEntry } from "../../domain/child-pack.mjs";
19
+ import { normFactTerm } from "../../domain/hash.mjs";
20
+
21
+ const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
22
+
23
+ /** The pack directory: TMCT_CHILD_PACK_DIR when set, else the package's own
24
+ * corpus/child/. */
25
+ export function childPackDir(env = process.env) {
26
+ return env?.TMCT_CHILD_PACK_DIR || join(PKG_ROOT, "corpus", "child");
27
+ }
28
+
29
+ const indexCacheByDir = new Map(); // dir -> { term: {s, t, n} } | null
30
+ const shardCacheByKey = new Map(); // `${dir}\0${shard}` -> Map(term -> row) | null
31
+
32
+ /** Drop every cached index/shard — for tests that mutate a pack dir. */
33
+ export function clearChildPackCache() {
34
+ indexCacheByDir.clear();
35
+ shardCacheByKey.clear();
36
+ }
37
+
38
+ function readGunzipped(file) {
39
+ try {
40
+ return gunzipSync(readFileSync(file));
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ /** The pack's term index, lazily read and cached per dir; null (cached) when
47
+ * the pack is absent or unreadable. Never throws. */
48
+ export function loadChildIndex(dir) {
49
+ if (indexCacheByDir.has(dir)) return indexCacheByDir.get(dir);
50
+ let index = null;
51
+ const body = readGunzipped(join(dir, "index.json.gz"));
52
+ if (body) {
53
+ try {
54
+ const parsed = JSON.parse(body.toString("utf8"));
55
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) index = parsed;
56
+ } catch { /* tolerated: a corrupt index is an absent pack */ }
57
+ }
58
+ indexCacheByDir.set(dir, index);
59
+ return index;
60
+ }
61
+
62
+ function loadShard(dir, shardName) {
63
+ const key = `${dir}\0${shardName}`;
64
+ if (shardCacheByKey.has(key)) return shardCacheByKey.get(key);
65
+ let rows = null;
66
+ const body = readGunzipped(join(dir, "shards", `${shardName}.jsonl.gz`));
67
+ if (body) {
68
+ rows = new Map();
69
+ for (const line of body.toString("utf8").split("\n")) {
70
+ if (!line.trim()) continue;
71
+ try {
72
+ const row = JSON.parse(line);
73
+ if (isChildFactsRow(row)) rows.set(row.term, row);
74
+ } catch { /* tolerated: a bad line loses one row, not the shard */ }
75
+ }
76
+ }
77
+ shardCacheByKey.set(key, rows);
78
+ return rows;
79
+ }
80
+
81
+ /** One term's triples row by its (already-normalised) key: index hit -> the one
82
+ * shard the index names (cached) -> the row. The term is normFactTerm-folded
83
+ * here too, so a caller may pass a raw spelling. Null on an index miss, so an
84
+ * unknown term never costs a shard read. Never throws. */
85
+ export function loadChildFacts(dir, term) {
86
+ const index = loadChildIndex(dir);
87
+ if (!index) return null;
88
+ const key = normFactTerm(term);
89
+ const entry = index[key];
90
+ if (!isChildIndexEntry(entry)) return null;
91
+ const rows = loadShard(dir, entry.s);
92
+ return rows?.get(entry.t) ?? null;
93
+ }
94
+
95
+ const fsProvider = {
96
+ lookup: async (normTerm) => loadChildFacts(childPackDir(), normTerm),
97
+ };
98
+
99
+ let registeredProvider = null;
100
+
101
+ /** Swap the pack lookup: provider = { lookup: async (normTerm) => row|null }.
102
+ * Pass null to restore the default fs loader. */
103
+ export function registerChildPackProvider(provider) {
104
+ registeredProvider = provider && typeof provider.lookup === "function" ? provider : null;
105
+ }
106
+
107
+ /** The active provider — the registered one, else the lazy fs loader. An
108
+ * explicit `env` bag (a chat turn's own env, which may carry
109
+ * TMCT_CHILD_PACK_DIR) makes the fs loader resolve the pack dir from that bag
110
+ * instead of process.env; with no argument the behavior is unchanged. */
111
+ export function getChildPackProvider(env) {
112
+ if (registeredProvider) return registeredProvider;
113
+ if (env === undefined) return fsProvider;
114
+ return { lookup: async (normTerm) => loadChildFacts(childPackDir(env), normTerm) };
115
+ }
@@ -182,6 +182,13 @@ ace = "disjointWith"
182
182
  predicate = "owl:disjointWith"
183
183
  note = "mutual exclusion; ACE pattern 6 ('no N1 is a N2')"
184
184
 
185
+ [[relation]]
186
+ rel = "/r/NotCapableOf"
187
+ surface = "a {start} cannot {end}"
188
+ ace = "ObjectProperty"
189
+ predicate = "mgxneg:capableOf"
190
+ note = "typical INcapability — the one ConceptNet negative tmct admits (added for the child corpus). ConceptNet's /r/Not* family is deprecated upstream (docs/references/schemas/conceptnet-relations.md), but NotCapableOf is the one negative the defeasible-negation reader needs data for: it is the corpus source of 'a penguin cannot fly'. It maps onto tmct's OWN polarity, mgxneg:capableOf (src/domain/memory/capability.mjs) — polarity lives in the predicate, so ace stays ObjectProperty and the positive/negative twins hash to distinct facts. This is a DELIBERATE addition to the closed set; the loader's unmapped-relation hard error stays intact for every OTHER /r/Not* term, which never reach a shipped slice. Not part of CANONICAL_RELS (fetch-slice.mjs's tech filter never admits it); the child build admits it explicitly."
191
+
185
192
  # --- unmappable relations: no clean OWL-axiom fit; kept for other consumers ---
186
193
 
187
194
  [[relation]]