@polycode-projects/the-mechanical-code-talker 1.5.5 → 1.8.4

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 (45) hide show
  1. package/README.md +123 -14
  2. package/ROADMAP.md +233 -1392
  3. package/bin/tmct.mjs +479 -98
  4. package/corpus/README.md +3 -0
  5. package/corpus/generated/README.md +43 -0
  6. package/corpus/generated/ace-surface-variants.jsonl +17 -0
  7. package/corpus/generated/manifest.json +9 -0
  8. package/corpus/tier2/generate.mjs +14668 -0
  9. package/corpus/tier2/human-examples-large.jsonl +1928 -0
  10. package/corpus/tier2/human-examples-medium.jsonl +356 -0
  11. package/corpus/tier2/human-examples.jsonl +120 -0
  12. package/corpus/tier2/human-large.jsonl +12001 -0
  13. package/corpus/tier2/human-medium.jsonl +944 -0
  14. package/corpus/tier2/human.jsonl +664 -0
  15. package/corpus/tier2/manifest.json +42 -0
  16. package/package.json +14 -8
  17. package/src/answer-variants.json +47 -0
  18. package/src/answer-variants.mjs +67 -0
  19. package/src/ask-browser-entry.mjs +34 -0
  20. package/src/ask-browser.bundle.js +5095 -0
  21. package/src/ask-vocab.mjs +93 -8
  22. package/src/ask.mjs +451 -49
  23. package/src/chat.mjs +1273 -137
  24. package/src/cli-args.mjs +164 -0
  25. package/src/codegraph.mjs +170 -32
  26. package/src/extensions.mjs +100 -19
  27. package/src/grammar/ace.mjs +85 -3
  28. package/src/grammar/lexicon-core.json +9531 -63
  29. package/src/grammar/lexicon.mjs +58 -8
  30. package/src/graph-merge.mjs +114 -0
  31. package/src/index.mjs +14 -0
  32. package/src/init.mjs +40 -14
  33. package/src/interpret/normalize.mjs +75 -1
  34. package/src/interpret/strategies/grammar.mjs +10 -0
  35. package/src/interpret/strategies/keywords.mjs +20 -0
  36. package/src/interpret/strategies/noise-strip.mjs +73 -4
  37. package/src/memory/core.mjs +466 -8
  38. package/src/router/goal-reasoner.mjs +41 -7
  39. package/src/router/guardrail.mjs +37 -7
  40. package/src/router/resolver.mjs +50 -4
  41. package/src/sessions.mjs +5 -1
  42. package/src/source.mjs +54 -1
  43. package/src/syllogise.mjs +398 -27
  44. package/src/toml-config.mjs +13 -4
  45. package/src/viz.mjs +541 -0
@@ -39,6 +39,11 @@ import {
39
39
  predicateOf, numberOf, classify,
40
40
  } from "./lexicon.mjs";
41
41
 
42
+ // "a"/"an" are the only ACE determiners that are grammatically SINGULAR-ONLY —
43
+ // "the" and a bare/no determiner are number-neutral (see resolveNP's
44
+ // singularOnly below, and lexicon.mjs's lookupNoun doc for what this prunes).
45
+ const SINGULAR_ONLY_DET = new Set(["a", "an"]);
46
+
42
47
  export const PATTERN_SUB_CLASS_OF = "subClassOf";
43
48
  export const PATTERN_TYPE_ASSERTION = "typeAssertion";
44
49
  export const PATTERN_RELATION = "relation";
@@ -89,22 +94,29 @@ const stripDet = (tokens) =>
89
94
  * Returns { term, individual, extras, unknown } — `term` null on a miss with
90
95
  * the undeclared tokens in `unknown` (empty `unknown` = structurally
91
96
  * unparseable phrase → the caller returns a hard null). `extras` carries the
92
- * pattern-8 adjective triples (subclass axioms / hasValue restriction). */
97
+ * pattern-8 adjective triples (subclass axioms / hasValue restriction).
98
+ *
99
+ * `singularOnly` is grammatical-agreement pruning (see lexicon.mjs's
100
+ * lookupNoun doc): true only when the ORIGINAL (pre-strip) phrase opened
101
+ * with "a"/"an" — the one signal that a singular-plural-fold collision
102
+ * (die/dice, person/people, tooth/teeth) can be resolved by, rather than
103
+ * silently committing to whichever the lexicon happens to fold to first. */
93
104
  function resolveNP(lexicon, tokensIn) {
94
105
  const ns = lexicon.ns;
106
+ const singularOnly = tokensIn.length > 1 && SINGULAR_ONLY_DET.has(tokensIn[0].toLowerCase());
95
107
  const tokens = stripDet(tokensIn);
96
108
  if (tokens.length === 1) {
97
109
  const t = tokens[0];
98
110
  const proper = lookupProperName(lexicon, t);
99
111
  if (proper) return { term: `${ns}${proper}`, individual: true, extras: [], unknown: [] };
100
112
  if (CODE_REF.test(t)) return { term: `${ns}${t}`, individual: true, extras: [], unknown: [] };
101
- const noun = lookupNoun(lexicon, t);
113
+ const noun = lookupNoun(lexicon, t, { singularOnly });
102
114
  if (noun) return { term: `${ns}${noun.lemma}`, individual: false, noun, extras: [], unknown: [] };
103
115
  return { term: null, individual: false, extras: [], unknown: [t] };
104
116
  }
105
117
  if (tokens.length === 2) {
106
118
  const adj = lookupAdjective(lexicon, tokens[0]);
107
- const noun = lookupNoun(lexicon, tokens[1]);
119
+ const noun = lookupNoun(lexicon, tokens[1], { singularOnly });
108
120
  if (adj && noun) {
109
121
  const term = `${ns}${adj.lemma}-${noun.lemma}`;
110
122
  const extras = [
@@ -178,6 +190,76 @@ function parseRelation(lexicon, toks, lower) {
178
190
  return null;
179
191
  }
180
192
 
193
+ // ---- ambiguity: breadth-first candidate parses, dead ends pruned, survivors
194
+ // surfaced rather than guessed (the operator's own framing — see
195
+ // PLAN_DID_YOU_SEE_HER_DUCK.md's Origin section). parseRelation just above is
196
+ // UNCHANGED — it is still the greedy, first-verb-position-wins fast path
197
+ // every existing caller keeps using, so every single-reading sentence (the
198
+ // overwhelming majority) is completely unaffected. parseRelationHits and
199
+ // parseAceAmbiguous below are a separate, ADDITIVE scan that a caller opts
200
+ // into only when it wants to know whether more than one reading survives. ----
201
+
202
+ /** Pattern 3 — EVERY verb-position split, not just the first: for each token
203
+ * index that lookupVerb recognizes, resolve both sides and keep it ONLY if
204
+ * it is a complete, valid parse (a genuine hit — a missOrNull/null split is
205
+ * a dead end, pruned here rather than surfaced as "ambiguity"). Duplicate
206
+ * logic with parseRelation is deliberate: parseRelation must stay byte-for-
207
+ * byte unchanged for every existing caller, so this is a standalone reader,
208
+ * not a refactor of shared internals. */
209
+ function parseRelationHits(lexicon, toks, lower) {
210
+ const hits = [];
211
+ for (let i = 1; i < toks.length - 1; i += 1) {
212
+ const verb = lookupVerb(lexicon, lower[i]);
213
+ if (!verb) continue;
214
+ let objStart = i + 1;
215
+ if (verb.prep) {
216
+ if (lower[objStart] !== verb.prep) continue;
217
+ objStart += 1;
218
+ if (objStart >= toks.length) continue;
219
+ }
220
+ const np1 = resolveNP(lexicon, toks.slice(0, i));
221
+ const np2 = resolveNP(lexicon, toks.slice(objStart));
222
+ if (np1.term == null || np2.term == null) continue; // dead end
223
+ hits.push({
224
+ i,
225
+ verbLemma: verb.lemma,
226
+ subject: np1.term,
227
+ object: np2.term,
228
+ result: hit(PATTERN_RELATION, [np1, np2], [
229
+ { subject: np1.term, predicate: predicateOf(verb, lexicon.ns), object: np2.term, kind: "owl:ObjectProperty" },
230
+ ]),
231
+ });
232
+ }
233
+ return hits;
234
+ }
235
+
236
+ /** Public ambiguity surface: parse `sentence` and, ONLY when more than one
237
+ * independent, COMPLETE relation-pattern reading survives (parseRelationHits
238
+ * above), return them all, each labeled by the token it read as the verb.
239
+ * Returns null for the overwhelming majority of sentences: anything not
240
+ * relation-shaped (mirrors parseAce's own dispatch gate exactly, so this
241
+ * only ever fires on a sentence parseAce would ALSO route to parseRelation),
242
+ * and any relation-shaped sentence with 0 or 1 surviving readings — the
243
+ * ordinary parseAce path is authoritative and untouched either way. */
244
+ export function parseAceAmbiguous(sentence, lexicon = loadLexicon()) {
245
+ const toks = tokenize(sentence);
246
+ if (toks.length < 4) return null; // 3 tokens: exactly one verb position is even possible
247
+ const lower = toks.map((t) => t.toLowerCase());
248
+ if (lower[0] === "every" || lower[0] === "no") return null;
249
+ if (/'s$/.test(lower[0]) && lower[0].length > 2) return null;
250
+ if (lower[0] === "the" && lower.includes("of") && lower.includes("is")) return null;
251
+ if (lower.indexOf("is") > 0) return null;
252
+ const hits = parseRelationHits(lexicon, toks, lower);
253
+ if (hits.length < 2) return null;
254
+ return {
255
+ pattern: PATTERN_RELATION,
256
+ sentence,
257
+ readings: hits.map(({ i, verbLemma, subject, object, result }) => ({
258
+ i, verbLemma, subject, object, ...result,
259
+ })),
260
+ };
261
+ }
262
+
181
263
  /** Pattern 8 (copula arm) — "X is ADJ": data adjective → datatype-property
182
264
  * assertion; subclass adjective → rdf:type (individual) / rdfs:subClassOf. */
183
265
  function adjectiveCopula(lexicon, pattern, np1, adj) {