@polycode-projects/the-mechanical-code-talker 1.10.14 → 1.11.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.
- package/README.md +115 -101
- package/ROADMAP.md +17 -4
- package/bin/tmct.mjs +115 -9
- package/data/games/crates.txt +24 -0
- package/data/games/hanoi-3.txt +30 -0
- package/package.json +1 -1
- package/src/ask-browser.bundle.js +129 -398
- package/src/chat.mjs +564 -12
- package/src/domain.mjs +271 -0
- package/src/import-file.mjs +86 -0
- package/src/init.mjs +46 -1
- package/src/ledger-viz.mjs +613 -0
- package/src/memory/core.mjs +80 -16
- package/src/memory/shacl.mjs +17 -7
- package/src/memory-ask-browser-entry.mjs +4 -2
- package/src/memory-ask-browser.bundle.js +5157 -1165
- package/src/plan-viz.mjs +410 -0
- package/src/router/guardrail.mjs +5 -0
- package/src/router/registry.mjs +55 -11
- package/src/router/taught.mjs +73 -0
- package/src/sentences.mjs +19 -0
- package/src/viz-theme.mjs +50 -0
- package/src/viz.mjs +2 -2
- package/src/wink-model.mjs +12 -6
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,56 @@ 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_KINDS = Object.freeze([
|
|
1177
|
+
RULE_KIND_COMPOSE2, RULE_KIND_FILTER, RULE_KIND_RECURSIVE,
|
|
1178
|
+
RULE_KIND_ACTION_SIGNATURE, RULE_KIND_ACTION_PRECOND, RULE_KIND_ACTION_EFFECT,
|
|
1179
|
+
]);
|
|
1170
1180
|
|
|
1171
1181
|
export const RULE_NAME_PROP = "mgx:ruleName";
|
|
1172
1182
|
export const RULE_KIND_PROP = "mgx:ruleKind";
|
|
1173
1183
|
|
|
1174
1184
|
// Per-kind slot contract: JS slot key -> the mgx: attribute it's written under.
|
|
1175
|
-
// filter's "base" slot
|
|
1176
|
-
//
|
|
1177
|
-
//
|
|
1178
|
-
//
|
|
1179
|
-
//
|
|
1185
|
+
// filter's "base" slot reuses ruleBase1 — the same attribute name compose2's
|
|
1186
|
+
// first hop already uses, since both play the identical "base relation this
|
|
1187
|
+
// rule builds on" role. Order within each array is the content-address hash
|
|
1188
|
+
// order below — fixed and load-bearing. Predicate slots store BARE values
|
|
1189
|
+
// ("rest-on", "smaller-than"): normFactTerm strips a leading CURIE prefix, so
|
|
1190
|
+
// an mgx:-prefixed value could never round-trip; readers re-attach mgx:.
|
|
1180
1191
|
const RULE_SLOT_SPEC = {
|
|
1181
1192
|
[RULE_KIND_COMPOSE2]: [["base1", "mgx:ruleBase1"], ["base2", "mgx:ruleBase2"]],
|
|
1182
1193
|
[RULE_KIND_FILTER]: [["base", "mgx:ruleBase1"], ["property", "mgx:ruleFilterProperty"]],
|
|
1183
1194
|
[RULE_KIND_RECURSIVE]: [["baseCase", "mgx:ruleBaseCase"], ["recStep", "mgx:ruleRecStep"]],
|
|
1195
|
+
[RULE_KIND_ACTION_SIGNATURE]: [
|
|
1196
|
+
["subjectClass", "mgx:ruleActionSubjectClass"], ["targetClass", "mgx:ruleActionTargetClass"],
|
|
1197
|
+
],
|
|
1198
|
+
[RULE_KIND_ACTION_PRECOND]: [
|
|
1199
|
+
["shape", "mgx:ruleActionPrecondShape"], ["predicate", "mgx:ruleActionPrecondPredicate"],
|
|
1200
|
+
["role", "mgx:ruleActionPrecondRole"], ["scope", "mgx:ruleActionPrecondScope"],
|
|
1201
|
+
],
|
|
1202
|
+
[RULE_KIND_ACTION_EFFECT]: [
|
|
1203
|
+
["predicate", "mgx:ruleActionEffectPredicate"], ["subjectRole", "mgx:ruleActionEffectSubject"],
|
|
1204
|
+
["objectRole", "mgx:ruleActionEffectObject"],
|
|
1205
|
+
],
|
|
1184
1206
|
};
|
|
1185
1207
|
|
|
1186
|
-
// Content-addressed over (kind, name,
|
|
1187
|
-
// NUL-delimited discipline: identical rules upsert,
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1208
|
+
// Content-addressed over (kind, name, ...slots in RULE_SLOT_SPEC order),
|
|
1209
|
+
// mirroring factIdFor's NUL-delimited discipline: identical rules upsert,
|
|
1210
|
+
// different ones coexist. For 2-slot kinds the joined string is byte-identical
|
|
1211
|
+
// to the historical (kind, name, slot1, slot2) template, so pre-existing rule
|
|
1212
|
+
// ids never change (pinned by test/memory-rules-action.test.mjs).
|
|
1213
|
+
const ruleIdFor = (kind, name, slotValues) => `rule:${fnv1aHex([kind, name, ...slotValues].join("\0"))}`;
|
|
1214
|
+
|
|
1215
|
+
/** Append one taught RULE — a sibling of appendFact storing a `Rule`
|
|
1216
|
+
* individual, same upsert/provenance/trust/SHACL discipline (neither
|
|
1217
|
+
* pipeline ever checks `individual.class`). `slots` is the matching
|
|
1218
|
+
* per-kind object (RULE_SLOT_SPEC above). Returns { id }. */
|
|
1194
1219
|
export async function appendRule(dir, { name, kind, slots, provenance = "", createdAt = "" } = {}) {
|
|
1195
1220
|
const spec = RULE_SLOT_SPEC[kind];
|
|
1196
1221
|
if (!spec) throw new Error(`a rule kind must be one of ${RULE_KINDS.join(", ")}, got ${JSON.stringify(kind)}`);
|
|
@@ -1200,7 +1225,7 @@ export async function appendRule(dir, { name, kind, slots, provenance = "", crea
|
|
|
1200
1225
|
if (slotValues.some((v) => !v)) {
|
|
1201
1226
|
throw new Error(`a ${kind} rule needs ${spec.map(([slotKey]) => slotKey).join(" + ")}`);
|
|
1202
1227
|
}
|
|
1203
|
-
const id = ruleIdFor(kind, n, slotValues
|
|
1228
|
+
const id = ruleIdFor(kind, n, slotValues);
|
|
1204
1229
|
const label = labelOf(`${n} = ${kind}(${slotValues.join(", ")})`);
|
|
1205
1230
|
await mutateMemory(dir, async (payload) => {
|
|
1206
1231
|
const prior = payload.individuals.find((x) => x?.id === id);
|
|
@@ -1244,6 +1269,45 @@ export function findRuleByName(memory, name) {
|
|
|
1244
1269
|
);
|
|
1245
1270
|
}
|
|
1246
1271
|
|
|
1272
|
+
/** Every Rule individual sharing `name` — an action family's members live as
|
|
1273
|
+
* sibling individuals (one per taught sentence), so consumers collect them
|
|
1274
|
+
* all. Sorted by kind then id for deterministic iteration. */
|
|
1275
|
+
export function findRulesByName(memory, name) {
|
|
1276
|
+
const n = normFactTerm(name);
|
|
1277
|
+
const kindOf = (i) => (i.attributes || []).find((a) => a?.prop === RULE_KIND_PROP)?.value || "";
|
|
1278
|
+
return (memory?.individuals || [])
|
|
1279
|
+
.filter((i) => i?.class === RULE_CLASS
|
|
1280
|
+
&& (i.attributes || []).find((a) => a?.prop === RULE_NAME_PROP)?.value === n)
|
|
1281
|
+
.sort((a, b) => kindOf(a).localeCompare(kindOf(b)) || String(a.id).localeCompare(String(b.id)));
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/** Every taught Rule as a plain row {id, name, kind, slots, provenance} —
|
|
1285
|
+
* the sibling of readFactRows, so consumers (src/domain.mjs) never touch
|
|
1286
|
+
* raw individuals. Rules whose kind has no RULE_SLOT_SPEC entry are
|
|
1287
|
+
* skipped (unreadable without a slot contract). Sorted by name, kind, id. */
|
|
1288
|
+
export function readRuleRows(memory) {
|
|
1289
|
+
const rows = [];
|
|
1290
|
+
for (const ind of memory?.individuals || []) {
|
|
1291
|
+
if (ind?.class !== RULE_CLASS) continue;
|
|
1292
|
+
const attr = (prop) => (ind.attributes || []).find((a) => a?.prop === prop)?.value;
|
|
1293
|
+
const kind = attr(RULE_KIND_PROP);
|
|
1294
|
+
const spec = RULE_SLOT_SPEC[kind];
|
|
1295
|
+
if (!spec) continue;
|
|
1296
|
+
const slots = {};
|
|
1297
|
+
for (const [slotKey, prop] of spec) slots[slotKey] = attr(prop) ?? "";
|
|
1298
|
+
rows.push({
|
|
1299
|
+
id: ind.id,
|
|
1300
|
+
name: attr(RULE_NAME_PROP) || "",
|
|
1301
|
+
kind,
|
|
1302
|
+
slots,
|
|
1303
|
+
provenance: attr("mgx:factProvenance") || "",
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
rows.sort((a, b) => a.name.localeCompare(b.name)
|
|
1307
|
+
|| a.kind.localeCompare(b.kind) || String(a.id).localeCompare(String(b.id)));
|
|
1308
|
+
return rows;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1247
1311
|
// ---- Relation chase ---------------------------------------------------------
|
|
1248
1312
|
//
|
|
1249
1313
|
// `resolveRelationChase` and `resolveRelationChaseReverse` are standalone,
|
package/src/memory/shacl.mjs
CHANGED
|
@@ -6,15 +6,26 @@
|
|
|
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",
|
|
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
|
+
],
|
|
18
29
|
};
|
|
19
30
|
|
|
20
31
|
function attrValue(ind, prop) {
|
|
@@ -45,14 +56,13 @@ function checkFact(ind, violations) {
|
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
/** RuleShape (mgx:RuleShape): a non-empty name; a kind from the closed
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* above). */
|
|
59
|
+
* vocabulary; and the matching slots for that declared kind, each present
|
|
60
|
+
* and non-empty (RULE_SLOT_PROPS above). */
|
|
51
61
|
function checkRule(ind, violations) {
|
|
52
62
|
if (!nonEmpty(attrValue(ind, "mgx:ruleName"))) violations.push("a Rule needs a non-empty mgx:ruleName");
|
|
53
63
|
const kind = attrValue(ind, "mgx:ruleKind");
|
|
54
64
|
if (!kind || !RULE_KINDS.has(kind)) {
|
|
55
|
-
violations.push(`a Rule's mgx:ruleKind must be one of
|
|
65
|
+
violations.push(`a Rule's mgx:ruleKind must be one of ${[...RULE_KINDS].join(" | ")} (got ${JSON.stringify(kind)})`);
|
|
56
66
|
return; // no declared kind to check slots against
|
|
57
67
|
}
|
|
58
68
|
for (const prop of RULE_SLOT_PROPS[kind]) {
|
|
@@ -6,8 +6,10 @@
|
|
|
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
8
|
// bypassing the structural-graph parse pipeline this panel has no use for.
|
|
9
|
-
import { factAnswer } from "./chat.mjs";
|
|
9
|
+
import { factAnswer, factReadBack } from "./chat.mjs";
|
|
10
10
|
import { createInMemoryStore, normFactTerm } from "./memory/core.mjs";
|
|
11
11
|
|
|
12
12
|
// normFactTerm is re-exported too, for viz.mjs's client-side term normalization.
|
|
13
|
-
|
|
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 };
|