@polycode-projects/the-mechanical-code-talker 1.10.14 → 1.11.5
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 +121 -114
- package/ROADMAP.md +14 -4
- package/bin/tmct.mjs +167 -77
- package/data/games/crates.txt +24 -0
- package/data/games/hanoi-3.txt +30 -0
- package/data/games/river.txt +33 -0
- package/package.json +1 -1
- package/src/ask.mjs +119 -6
- package/src/chat.mjs +1515 -52
- package/src/codegraph.mjs +24 -11
- package/src/domain.mjs +350 -0
- package/src/import-file.mjs +86 -0
- package/src/init.mjs +46 -1
- package/src/interpret/normalize.mjs +46 -0
- package/src/interpret/strategies/keywords.mjs +13 -9
- package/src/ledger-viz.mjs +635 -0
- package/src/memory/core.mjs +100 -17
- package/src/memory/shacl.mjs +20 -7
- package/src/memory-ask-browser-entry.mjs +9 -7
- package/src/memory-ask-browser.bundle.js +5648 -1177
- package/src/plan-viz.mjs +409 -0
- package/src/router/drive.mjs +122 -13
- package/src/router/guardrail.mjs +5 -0
- package/src/router/registry.mjs +55 -11
- package/src/router/resolver.mjs +13 -0
- package/src/router/taught.mjs +84 -0
- package/src/sentences.mjs +19 -0
- package/src/syllogise.mjs +154 -38
- package/src/viz-theme.mjs +66 -0
- package/src/wink-model.mjs +12 -6
- package/src/ask-browser-entry.mjs +0 -19
- package/src/ask-browser.bundle.js +0 -5411
- package/src/viz.mjs +0 -959
package/src/memory/core.mjs
CHANGED
|
@@ -1023,7 +1023,7 @@ export async function appendFact(dir, { subject, predicate, object, provenance =
|
|
|
1023
1023
|
const p = normText(predicate);
|
|
1024
1024
|
const o = normFactTerm(object);
|
|
1025
1025
|
if (!s || !p || !o) throw new Error("a fact needs subject, predicate and object");
|
|
1026
|
-
const id = `fact:${fnv1aHex(`${s}
|
|
1026
|
+
const id = `fact:${fnv1aHex(`${s}\0${p}\0${o}`)}`;
|
|
1027
1027
|
const text = `${s} ${p} ${o}`;
|
|
1028
1028
|
const tokens = proseTokensFor({ doc: text });
|
|
1029
1029
|
const q = normText(quantifier);
|
|
@@ -1166,31 +1166,64 @@ export async function appendFacts(dir, facts) {
|
|
|
1166
1166
|
export const RULE_KIND_COMPOSE2 = "compose2";
|
|
1167
1167
|
export const RULE_KIND_FILTER = "filter";
|
|
1168
1168
|
export const RULE_KIND_RECURSIVE = "recursive";
|
|
1169
|
-
|
|
1169
|
+
// The action family: one taught sentence = one Rule individual, the family
|
|
1170
|
+
// sharing one mgx:ruleName. A single flat kind can't hold several
|
|
1171
|
+
// preconditions under content addressing (one value per slot), so signature,
|
|
1172
|
+
// precondition and effect are sibling kinds collected by name at plan time.
|
|
1173
|
+
export const RULE_KIND_ACTION_SIGNATURE = "action-signature";
|
|
1174
|
+
export const RULE_KIND_ACTION_PRECOND = "action-precond";
|
|
1175
|
+
export const RULE_KIND_ACTION_EFFECT = "action-effect";
|
|
1176
|
+
export const RULE_KIND_ACTION_CONSTRAINT = "action-constraint";
|
|
1177
|
+
export const RULE_KINDS = Object.freeze([
|
|
1178
|
+
RULE_KIND_COMPOSE2, RULE_KIND_FILTER, RULE_KIND_RECURSIVE,
|
|
1179
|
+
RULE_KIND_ACTION_SIGNATURE, RULE_KIND_ACTION_PRECOND, RULE_KIND_ACTION_EFFECT,
|
|
1180
|
+
RULE_KIND_ACTION_CONSTRAINT,
|
|
1181
|
+
]);
|
|
1170
1182
|
|
|
1171
1183
|
export const RULE_NAME_PROP = "mgx:ruleName";
|
|
1172
1184
|
export const RULE_KIND_PROP = "mgx:ruleKind";
|
|
1173
1185
|
|
|
1174
1186
|
// Per-kind slot contract: JS slot key -> the mgx: attribute it's written under.
|
|
1175
|
-
// filter's "base" slot
|
|
1176
|
-
//
|
|
1177
|
-
//
|
|
1178
|
-
//
|
|
1179
|
-
//
|
|
1187
|
+
// filter's "base" slot reuses ruleBase1 — the same attribute name compose2's
|
|
1188
|
+
// first hop already uses, since both play the identical "base relation this
|
|
1189
|
+
// rule builds on" role. Order within each array is the content-address hash
|
|
1190
|
+
// order below — fixed and load-bearing. Predicate slots store BARE values
|
|
1191
|
+
// ("rest-on", "smaller-than"): normFactTerm strips a leading CURIE prefix, so
|
|
1192
|
+
// an mgx:-prefixed value could never round-trip; readers re-attach mgx:.
|
|
1180
1193
|
const RULE_SLOT_SPEC = {
|
|
1181
1194
|
[RULE_KIND_COMPOSE2]: [["base1", "mgx:ruleBase1"], ["base2", "mgx:ruleBase2"]],
|
|
1182
1195
|
[RULE_KIND_FILTER]: [["base", "mgx:ruleBase1"], ["property", "mgx:ruleFilterProperty"]],
|
|
1183
1196
|
[RULE_KIND_RECURSIVE]: [["baseCase", "mgx:ruleBaseCase"], ["recStep", "mgx:ruleRecStep"]],
|
|
1197
|
+
[RULE_KIND_ACTION_SIGNATURE]: [
|
|
1198
|
+
["subjectClass", "mgx:ruleActionSubjectClass"], ["targetClass", "mgx:ruleActionTargetClass"],
|
|
1199
|
+
],
|
|
1200
|
+
[RULE_KIND_ACTION_PRECOND]: [
|
|
1201
|
+
["shape", "mgx:ruleActionPrecondShape"], ["predicate", "mgx:ruleActionPrecondPredicate"],
|
|
1202
|
+
["role", "mgx:ruleActionPrecondRole"], ["scope", "mgx:ruleActionPrecondScope"],
|
|
1203
|
+
],
|
|
1204
|
+
[RULE_KIND_ACTION_EFFECT]: [
|
|
1205
|
+
["predicate", "mgx:ruleActionEffectPredicate"], ["subjectRole", "mgx:ruleActionEffectSubject"],
|
|
1206
|
+
["objectRole", "mgx:ruleActionEffectObject"],
|
|
1207
|
+
],
|
|
1208
|
+
// "the <left> may not be with the <right> without the <guard>" — each slot
|
|
1209
|
+
// names a class whose sole member src/domain.mjs resolves at compile time.
|
|
1210
|
+
[RULE_KIND_ACTION_CONSTRAINT]: [
|
|
1211
|
+
["left", "mgx:ruleActionConstraintLeft"], ["right", "mgx:ruleActionConstraintRight"],
|
|
1212
|
+
["guard", "mgx:ruleActionConstraintGuard"],
|
|
1213
|
+
],
|
|
1184
1214
|
};
|
|
1185
1215
|
|
|
1186
|
-
// Content-addressed over (kind, name,
|
|
1187
|
-
// NUL-delimited discipline: identical rules upsert,
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1216
|
+
// Content-addressed over (kind, name, ...slots in RULE_SLOT_SPEC order),
|
|
1217
|
+
// mirroring factIdFor's NUL-delimited discipline: identical rules upsert,
|
|
1218
|
+
// different ones coexist. For 2-slot kinds the joined string is byte-identical
|
|
1219
|
+
// to the historical (kind, name, slot1, slot2) template, so pre-existing rule
|
|
1220
|
+
// ids never change (pinned by test/memory-rules-action.test.mjs).
|
|
1221
|
+
const ruleIdFor = (kind, name, slotValues) => `rule:${fnv1aHex([kind, name, ...slotValues].join("\0"))}`;
|
|
1222
|
+
|
|
1223
|
+
/** Append one taught RULE — a sibling of appendFact storing a `Rule`
|
|
1224
|
+
* individual, same upsert/provenance/trust/SHACL discipline (neither
|
|
1225
|
+
* pipeline ever checks `individual.class`). `slots` is the matching
|
|
1226
|
+
* per-kind object (RULE_SLOT_SPEC above). Returns { id }. */
|
|
1194
1227
|
export async function appendRule(dir, { name, kind, slots, provenance = "", createdAt = "" } = {}) {
|
|
1195
1228
|
const spec = RULE_SLOT_SPEC[kind];
|
|
1196
1229
|
if (!spec) throw new Error(`a rule kind must be one of ${RULE_KINDS.join(", ")}, got ${JSON.stringify(kind)}`);
|
|
@@ -1200,7 +1233,7 @@ export async function appendRule(dir, { name, kind, slots, provenance = "", crea
|
|
|
1200
1233
|
if (slotValues.some((v) => !v)) {
|
|
1201
1234
|
throw new Error(`a ${kind} rule needs ${spec.map(([slotKey]) => slotKey).join(" + ")}`);
|
|
1202
1235
|
}
|
|
1203
|
-
const id = ruleIdFor(kind, n, slotValues
|
|
1236
|
+
const id = ruleIdFor(kind, n, slotValues);
|
|
1204
1237
|
const label = labelOf(`${n} = ${kind}(${slotValues.join(", ")})`);
|
|
1205
1238
|
await mutateMemory(dir, async (payload) => {
|
|
1206
1239
|
const prior = payload.individuals.find((x) => x?.id === id);
|
|
@@ -1244,6 +1277,45 @@ export function findRuleByName(memory, name) {
|
|
|
1244
1277
|
);
|
|
1245
1278
|
}
|
|
1246
1279
|
|
|
1280
|
+
/** Every Rule individual sharing `name` — an action family's members live as
|
|
1281
|
+
* sibling individuals (one per taught sentence), so consumers collect them
|
|
1282
|
+
* all. Sorted by kind then id for deterministic iteration. */
|
|
1283
|
+
export function findRulesByName(memory, name) {
|
|
1284
|
+
const n = normFactTerm(name);
|
|
1285
|
+
const kindOf = (i) => (i.attributes || []).find((a) => a?.prop === RULE_KIND_PROP)?.value || "";
|
|
1286
|
+
return (memory?.individuals || [])
|
|
1287
|
+
.filter((i) => i?.class === RULE_CLASS
|
|
1288
|
+
&& (i.attributes || []).find((a) => a?.prop === RULE_NAME_PROP)?.value === n)
|
|
1289
|
+
.sort((a, b) => kindOf(a).localeCompare(kindOf(b)) || String(a.id).localeCompare(String(b.id)));
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/** Every taught Rule as a plain row {id, name, kind, slots, provenance} —
|
|
1293
|
+
* the sibling of readFactRows, so consumers (src/domain.mjs) never touch
|
|
1294
|
+
* raw individuals. Rules whose kind has no RULE_SLOT_SPEC entry are
|
|
1295
|
+
* skipped (unreadable without a slot contract). Sorted by name, kind, id. */
|
|
1296
|
+
export function readRuleRows(memory) {
|
|
1297
|
+
const rows = [];
|
|
1298
|
+
for (const ind of memory?.individuals || []) {
|
|
1299
|
+
if (ind?.class !== RULE_CLASS) continue;
|
|
1300
|
+
const attr = (prop) => (ind.attributes || []).find((a) => a?.prop === prop)?.value;
|
|
1301
|
+
const kind = attr(RULE_KIND_PROP);
|
|
1302
|
+
const spec = RULE_SLOT_SPEC[kind];
|
|
1303
|
+
if (!spec) continue;
|
|
1304
|
+
const slots = {};
|
|
1305
|
+
for (const [slotKey, prop] of spec) slots[slotKey] = attr(prop) ?? "";
|
|
1306
|
+
rows.push({
|
|
1307
|
+
id: ind.id,
|
|
1308
|
+
name: attr(RULE_NAME_PROP) || "",
|
|
1309
|
+
kind,
|
|
1310
|
+
slots,
|
|
1311
|
+
provenance: attr("mgx:factProvenance") || "",
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
rows.sort((a, b) => a.name.localeCompare(b.name)
|
|
1315
|
+
|| a.kind.localeCompare(b.kind) || String(a.id).localeCompare(String(b.id)));
|
|
1316
|
+
return rows;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1247
1319
|
// ---- Relation chase ---------------------------------------------------------
|
|
1248
1320
|
//
|
|
1249
1321
|
// `resolveRelationChase` and `resolveRelationChaseReverse` are standalone,
|
|
@@ -1504,13 +1576,24 @@ export async function removeFacts(dir, ids) {
|
|
|
1504
1576
|
* contradiction (below it the fact is too weak to contradict anything). */
|
|
1505
1577
|
export const CONTRADICTION_TRUST_FLOOR = 0.5;
|
|
1506
1578
|
|
|
1579
|
+
export const HAS_A_PREDICATE = "mgx:hasA";
|
|
1580
|
+
export const CAPABLE_OF_PREDICATE = "mgx:capableOf";
|
|
1581
|
+
|
|
1582
|
+
/** Predicates whose real-world semantics allow many objects at once ("a dog
|
|
1583
|
+
* has legs" AND "a dog has a tail"; "a bird can fly" AND "a bird can sing"),
|
|
1584
|
+
* so a second object is a second fact, never a disagreement. A closed list:
|
|
1585
|
+
* every predicate outside it keeps the full contradiction contract. */
|
|
1586
|
+
export const MULTI_VALUED_PREDICATES = new Set([HAS_A_PREDICATE, CAPABLE_OF_PREDICATE]);
|
|
1587
|
+
|
|
1507
1588
|
/** Facts that CONTRADICT: same (subject, predicate), different object, each
|
|
1508
1589
|
* above the trust floor. Returns groups (trust-desc) so callers surface both,
|
|
1509
|
-
* never silently pick one. Same (s,p,o) is corroboration, not contradiction
|
|
1590
|
+
* never silently pick one. Same (s,p,o) is corroboration, not contradiction,
|
|
1591
|
+
* and a MULTI_VALUED_PREDICATES predicate never contradicts on object count. */
|
|
1510
1592
|
export function findContradictions(memory, { floor = CONTRADICTION_TRUST_FLOOR } = {}) {
|
|
1511
1593
|
const rows = readFactRows(memory).filter((r) => r.trust >= floor);
|
|
1512
1594
|
const byKey = new Map();
|
|
1513
1595
|
for (const r of rows) {
|
|
1596
|
+
if (MULTI_VALUED_PREDICATES.has(r.predicate)) continue;
|
|
1514
1597
|
const key = `${r.subject} ${r.predicate}`;
|
|
1515
1598
|
if (!byKey.has(key)) byKey.set(key, []);
|
|
1516
1599
|
byKey.get(key).push(r);
|
package/src/memory/shacl.mjs
CHANGED
|
@@ -6,15 +6,29 @@
|
|
|
6
6
|
// only fires on genuine malformation, never a sparse-but-valid write.
|
|
7
7
|
|
|
8
8
|
const MEMORY_CLASSES = new Set(["Utterance", "Fact", "Session", "Source", "Rule"]);
|
|
9
|
-
const RULE_KINDS = new Set([
|
|
9
|
+
const RULE_KINDS = new Set([
|
|
10
|
+
"compose2", "filter", "recursive",
|
|
11
|
+
"action-signature", "action-precond", "action-effect", "action-constraint",
|
|
12
|
+
]);
|
|
10
13
|
|
|
11
14
|
// Mirrors core.mjs's own (unexported) RULE_SLOT_SPEC exactly — the single
|
|
12
|
-
// source of truth for the closed
|
|
13
|
-
//
|
|
15
|
+
// source of truth for the closed rule-kind shapes; kept in sync by hand
|
|
16
|
+
// (both describe the same kinds' slots).
|
|
14
17
|
const RULE_SLOT_PROPS = {
|
|
15
18
|
compose2: ["mgx:ruleBase1", "mgx:ruleBase2"],
|
|
16
19
|
filter: ["mgx:ruleBase1", "mgx:ruleFilterProperty"],
|
|
17
20
|
recursive: ["mgx:ruleBaseCase", "mgx:ruleRecStep"],
|
|
21
|
+
"action-signature": ["mgx:ruleActionSubjectClass", "mgx:ruleActionTargetClass"],
|
|
22
|
+
"action-precond": [
|
|
23
|
+
"mgx:ruleActionPrecondShape", "mgx:ruleActionPrecondPredicate",
|
|
24
|
+
"mgx:ruleActionPrecondRole", "mgx:ruleActionPrecondScope",
|
|
25
|
+
],
|
|
26
|
+
"action-effect": [
|
|
27
|
+
"mgx:ruleActionEffectPredicate", "mgx:ruleActionEffectSubject", "mgx:ruleActionEffectObject",
|
|
28
|
+
],
|
|
29
|
+
"action-constraint": [
|
|
30
|
+
"mgx:ruleActionConstraintLeft", "mgx:ruleActionConstraintRight", "mgx:ruleActionConstraintGuard",
|
|
31
|
+
],
|
|
18
32
|
};
|
|
19
33
|
|
|
20
34
|
function attrValue(ind, prop) {
|
|
@@ -45,14 +59,13 @@ function checkFact(ind, violations) {
|
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
/** RuleShape (mgx:RuleShape): a non-empty name; a kind from the closed
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* above). */
|
|
62
|
+
* vocabulary; and the matching slots for that declared kind, each present
|
|
63
|
+
* and non-empty (RULE_SLOT_PROPS above). */
|
|
51
64
|
function checkRule(ind, violations) {
|
|
52
65
|
if (!nonEmpty(attrValue(ind, "mgx:ruleName"))) violations.push("a Rule needs a non-empty mgx:ruleName");
|
|
53
66
|
const kind = attrValue(ind, "mgx:ruleKind");
|
|
54
67
|
if (!kind || !RULE_KINDS.has(kind)) {
|
|
55
|
-
violations.push(`a Rule's mgx:ruleKind must be one of
|
|
68
|
+
violations.push(`a Rule's mgx:ruleKind must be one of ${[...RULE_KINDS].join(" | ")} (got ${JSON.stringify(kind)})`);
|
|
56
69
|
return; // no declared kind to check slots against
|
|
57
70
|
}
|
|
58
71
|
for (const prop of RULE_SLOT_PROPS[kind]) {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// memory-ask-browser-entry.mjs — the esbuild entry for
|
|
2
|
-
//
|
|
1
|
+
// memory-ask-browser-entry.mjs — the esbuild entry for the ledger page's
|
|
2
|
+
// chat dock (`tmct viz`, src/ledger-viz.mjs).
|
|
3
3
|
//
|
|
4
4
|
// Re-exports `factAnswer` (src/chat.mjs) and `createInMemoryStore`
|
|
5
|
-
// (src/memory/core.mjs), which lets the
|
|
5
|
+
// (src/memory/core.mjs), which lets the dock hand `factAnswer` the page's
|
|
6
6
|
// already-embedded payload with zero fs I/O. Called with `envelope: null,
|
|
7
7
|
// miss: true` to arm factAnswer's bare-question regex fallbacks directly,
|
|
8
|
-
// bypassing the structural-graph parse pipeline this
|
|
9
|
-
import { factAnswer } from "./chat.mjs";
|
|
8
|
+
// bypassing the structural-graph parse pipeline this dock has no use for.
|
|
9
|
+
import { factAnswer, factReadBack } from "./chat.mjs";
|
|
10
10
|
import { createInMemoryStore, normFactTerm } from "./memory/core.mjs";
|
|
11
11
|
|
|
12
|
-
// normFactTerm is re-exported too, for
|
|
13
|
-
|
|
12
|
+
// normFactTerm is re-exported too, for the page's client-side term normalization.
|
|
13
|
+
// factReadBack carries the taught-relation chases (grandfather-style questions)
|
|
14
|
+
// that factAnswer's own lanes don't reach.
|
|
15
|
+
globalThis.tmctMemoryAsk = { factAnswer, factReadBack, createInMemoryStore, normFactTerm };
|