@polycode-projects/the-mechanical-code-talker 3.1.0 → 3.1.3

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.
@@ -0,0 +1,195 @@
1
+ // paraphrase-ing8.mjs — the deterministic ING-8 equivalence checker: the harder
2
+ // whole-document paraphrase shapes ING-7's own checker (verifySubClassParaphrase,
3
+ // ./paraphrase.mjs) doesn't cover — non-isa relations (has/creates/capableOf),
4
+ // multi-sentence documents, and synonym substitution over that closed relation
5
+ // vocabulary.
6
+ //
7
+ // Strategy, mirroring verifySubClassParaphrase's own pattern: wherever the
8
+ // predicate space allows exact re-derivation (a single isa fact on both sides),
9
+ // reuse ING-7's own closure re-derivation directly. Everywhere else — every
10
+ // non-isa relation, and every multi-sentence document — there is no entailment
11
+ // closure to re-derive over, so the fallback is normalization + a closed
12
+ // synonym-template table per relation, recognized and compared as an exact set.
13
+ // A document holding a sentence outside the closed template set is reported
14
+ // unverified, never guessed — the same "verified, never instead of the
15
+ // original" discipline paraphrase.mjs itself states for the isa case.
16
+
17
+ import { recoverSubClassTriple, verifySubClassParaphrase } from "./paraphrase.mjs";
18
+ import { normFactTerm, fnv1aHex } from "./hash.mjs";
19
+
20
+ const articleFor = (word) => (/^[aeiou]/i.test(String(word || "")) ? "an" : "a");
21
+
22
+ // Every template reads "SUBJECT verb OBJECT" left to right, same discipline as
23
+ // paraphrase.mjs's SUBCLASS_TEMPLATES — no passive/reordered form, since that's
24
+ // the shape most likely to invert subject/object under a naive recognizer.
25
+ const HAS_TEMPLATES = [
26
+ (s, o) => `${s} has ${articleFor(o)} ${o}`,
27
+ (s, o) => `${s} possesses ${articleFor(o)} ${o}`,
28
+ (s, o) => `${s} owns ${articleFor(o)} ${o}`,
29
+ (s, o) => `${s} carries ${articleFor(o)} ${o}`,
30
+ ];
31
+ const HAS_RECOGNIZERS = [
32
+ /^(.+?)\s+has\s+an?\s+(.+)$/i,
33
+ /^(.+?)\s+possesses\s+an?\s+(.+)$/i,
34
+ /^(.+?)\s+owns\s+an?\s+(.+)$/i,
35
+ /^(.+?)\s+carries\s+an?\s+(.+)$/i,
36
+ ];
37
+
38
+ const CREATES_TEMPLATES = [
39
+ (s, o) => `${s} creates ${o}`,
40
+ (s, o) => `${s} produces ${o}`,
41
+ (s, o) => `${s} generates ${o}`,
42
+ (s, o) => `${s} causes ${o}`,
43
+ ];
44
+ const CREATES_RECOGNIZERS = [
45
+ /^(.+?)\s+creates\s+(.+)$/i,
46
+ /^(.+?)\s+produces\s+(.+)$/i,
47
+ /^(.+?)\s+generates\s+(.+)$/i,
48
+ /^(.+?)\s+causes\s+(.+)$/i,
49
+ ];
50
+
51
+ const CAPABLEOF_TEMPLATES = [
52
+ (s, o) => `${s} can ${o}`,
53
+ (s, o) => `${s} is able to ${o}`,
54
+ (s, o) => `${s} knows how to ${o}`,
55
+ ];
56
+ const CAPABLEOF_RECOGNIZERS = [
57
+ /^(.+?)\s+can\s+(.+)$/i,
58
+ /^(.+?)\s+is\s+able\s+to\s+(.+)$/i,
59
+ /^(.+?)\s+knows\s+how\s+to\s+(.+)$/i,
60
+ ];
61
+
62
+ // The closed non-isa relation vocabulary this checker recognizes. Each family
63
+ // pairs its templates and recognizers by index, same convention as isa.
64
+ const RELATION_FAMILIES = {
65
+ has: { templates: HAS_TEMPLATES, recognizers: HAS_RECOGNIZERS },
66
+ creates: { templates: CREATES_TEMPLATES, recognizers: CREATES_RECOGNIZERS },
67
+ capableOf: { templates: CAPABLEOF_TEMPLATES, recognizers: CAPABLEOF_RECOGNIZERS },
68
+ };
69
+ export const RELATION_FAMILY_IDS = Object.keys(RELATION_FAMILIES);
70
+
71
+ /** Deterministic template pick — same (subject, object) always picks the same
72
+ * template, spread across the table by a pure hash (paraphrase.mjs's own
73
+ * pickTemplateIndex pattern). `variantIndex`, when given, overrides the hash
74
+ * pick (a corpus generator wanting two DIFFERENT closed phrasings for the same
75
+ * fact passes distinct indices explicitly rather than relying on the hash). */
76
+ function pickTemplateIndex(subject, object, templateCount, variantIndex) {
77
+ if (variantIndex !== null && variantIndex !== undefined) {
78
+ return ((variantIndex % templateCount) + templateCount) % templateCount;
79
+ }
80
+ const h = fnv1aHex(`${subject}\0${object}`);
81
+ return parseInt(h.slice(0, 8), 16) % templateCount;
82
+ }
83
+
84
+ /** Generate a closed-template phrasing of `subject <family> object` — never
85
+ * null, always one of RELATION_FAMILIES[family]'s templates. Rule/template-
86
+ * based only, no LLM, matching paraphrase.mjs's paraphraseSubClass. */
87
+ export function paraphraseRelation(family, subject, object, variantIndex = null) {
88
+ const def = RELATION_FAMILIES[family];
89
+ if (!def) throw new Error(`paraphrase-ing8: unknown relation family "${family}"`);
90
+ const idx = pickTemplateIndex(subject, object, def.templates.length, variantIndex);
91
+ return def.templates[idx](subject, object);
92
+ }
93
+
94
+ /** Recover {subject, object} from text matching one of `family`'s closed
95
+ * recognizers — null if it matches none of them. A plain closed-set regex
96
+ * match, never a fuzzy/NLP parse, the exact inverse of paraphraseRelation. */
97
+ export function recoverRelationTriple(family, text) {
98
+ const def = RELATION_FAMILIES[family];
99
+ if (!def) return null;
100
+ const s = String(text || "").trim();
101
+ for (const re of def.recognizers) {
102
+ const m = s.match(re);
103
+ if (m) return { subject: m[1].trim(), object: m[2].trim() };
104
+ }
105
+ return null;
106
+ }
107
+
108
+ /** Verify one relation-family paraphrase (mirrors verifySubClassParaphrase's
109
+ * single-fact contract, but for a non-isa relation): the paraphrase text must
110
+ * recover to the SAME (subject, object) under `family`'s closed recognizers.
111
+ * No closure to re-derive over for a non-isa relation, so this is a direct
112
+ * normalized compare, never a fuzzy one. */
113
+ export function verifyRelationParaphrase(family, subject, object, paraphraseText) {
114
+ const recovered = recoverRelationTriple(family, paraphraseText);
115
+ if (!recovered) return { verified: false };
116
+ const verified = normFactTerm(recovered.subject) === normFactTerm(subject)
117
+ && normFactTerm(recovered.object) === normFactTerm(object);
118
+ return { verified };
119
+ }
120
+
121
+ // ---- whole-document recognition: isa + every relation family above ----
122
+
123
+ /** Recover a single (family, subject, object) triple from one sentence: isa
124
+ * first (paraphrase.mjs's closed subclass templates), then every relation
125
+ * family in RELATION_FAMILY_IDS order — null if the sentence matches none of
126
+ * the closed templates. Family keywords ("is a kind of" vs "has" vs "creates"
127
+ * vs "can" …) don't overlap, so recognition order never resolves a genuine
128
+ * ambiguity, only which closed family a sentence belongs to. */
129
+ export function recoverAnyTriple(sentence) {
130
+ const isaHit = recoverSubClassTriple(sentence);
131
+ if (isaHit) return { family: "isa", subject: isaHit.subject, object: isaHit.object };
132
+ for (const family of RELATION_FAMILY_IDS) {
133
+ const hit = recoverRelationTriple(family, sentence);
134
+ if (hit) return { family, subject: hit.subject, object: hit.object };
135
+ }
136
+ return null;
137
+ }
138
+
139
+ function splitSentences(text) {
140
+ return String(text || "")
141
+ .split(/(?<=[.!?])\s+/)
142
+ .map((s) => s.trim())
143
+ .filter(Boolean);
144
+ }
145
+
146
+ const tripleKey = (t) => `${t.family}\0${normFactTerm(t.subject)}\0${normFactTerm(t.object)}`;
147
+
148
+ /** Recover EVERY sentence of a document as a closed-template triple — null (the
149
+ * whole document unrecognized) the moment any sentence fails to match, never a
150
+ * partial list standing in for the whole. */
151
+ export function recoverDocumentTriples(text) {
152
+ const sentences = splitSentences(text);
153
+ if (!sentences.length) return null;
154
+ const triples = [];
155
+ for (const sentence of sentences) {
156
+ const t = recoverAnyTriple(sentence.replace(/\.+$/, ""));
157
+ if (!t) return null;
158
+ triples.push(t);
159
+ }
160
+ return triples;
161
+ }
162
+
163
+ /** Verify a whole-document ING-8 paraphrase pair: does `restatementText` say
164
+ * exactly what `inputText` says, no more and no less? A single isa fact on
165
+ * both sides re-derives the subclass closure directly through
166
+ * verifySubClassParaphrase (ING-7's own exact-re-derivation strategy).
167
+ * Everything else — a non-isa relation, or more than one sentence on either
168
+ * side — recovers every sentence of both texts against the closed template
169
+ * set above and requires the exact same (family, subject, object) SET, order
170
+ * irrelevant. Never verified when either side holds a sentence outside the
171
+ * closed templates, or when the two recovered sets differ in size or content
172
+ * (a dropped fact and an invented fact both fail this the same way — the
173
+ * checker declines rather than guesses which). */
174
+ export function verifyIng8Paraphrase(inputText, restatementText) {
175
+ const inputTriples = recoverDocumentTriples(inputText);
176
+ const restatementTriples = recoverDocumentTriples(restatementText);
177
+ if (!inputTriples || !restatementTriples) {
178
+ return { verified: false, reason: "unrecognized", inputTriples, restatementTriples };
179
+ }
180
+ const bothSingleIsa = inputTriples.length === 1 && restatementTriples.length === 1
181
+ && inputTriples[0].family === "isa" && restatementTriples[0].family === "isa";
182
+ if (bothSingleIsa) {
183
+ const { subject, object } = inputTriples[0];
184
+ const restatementSentence = splitSentences(restatementText)[0].replace(/\.+$/, "");
185
+ const { verified } = verifySubClassParaphrase(subject, object, restatementSentence);
186
+ return { verified, method: "isa-closure", inputTriples, restatementTriples };
187
+ }
188
+ if (inputTriples.length !== restatementTriples.length) {
189
+ return { verified: false, reason: "fact count mismatch", inputTriples, restatementTriples };
190
+ }
191
+ const inputKeys = inputTriples.map(tripleKey).sort();
192
+ const restatementKeys = restatementTriples.map(tripleKey).sort();
193
+ const verified = inputKeys.every((k, i) => k === restatementKeys[i]);
194
+ return { verified, method: "closed-template-set", inputTriples, restatementTriples };
195
+ }