@polycode-projects/the-mechanical-code-talker 2.7.10 → 2.7.12
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/corpus/worlds/manifest.json +5 -5
- package/corpus/worlds/shards/ashcombe-hall.jsonl.gz +0 -0
- package/corpus/worlds/src/ashcombe-hall.jsonl +5 -0
- package/package.json +1 -1
- package/src/adapters/memory/core.mjs +42 -6
- package/src/adapters/memory/shacl.mjs +8 -6
- package/src/domain/domain.mjs +51 -6
- package/src/domain/grammar/ace.mjs +42 -17
- package/src/services/adventure.mjs +103 -35
- package/src/services/chat.mjs +19 -1
- package/src/surfaces/web/memory-ask-browser.bundle.js +42 -13
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"counts": {
|
|
5
5
|
"worlds": 2,
|
|
6
6
|
"facts": 527,
|
|
7
|
-
"rules":
|
|
7
|
+
"rules": 19
|
|
8
8
|
},
|
|
9
9
|
"budgets": {
|
|
10
10
|
"sourceBytesPerWorld": 131072,
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"sources": [
|
|
15
15
|
{
|
|
16
16
|
"file": "src/ashcombe-hall.jsonl",
|
|
17
|
-
"bytes":
|
|
18
|
-
"sha256": "
|
|
17
|
+
"bytes": 8352,
|
|
18
|
+
"sha256": "f141da008edb6abfe025ddf4753fc374ccf44bdf23e3a414741f33f7246a3c9a"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"file": "src/spider-fly.jsonl",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"file": "shards/ashcombe-hall.jsonl.gz",
|
|
34
|
-
"bytes":
|
|
35
|
-
"sha256": "
|
|
34
|
+
"bytes": 948,
|
|
35
|
+
"sha256": "49bbdf7bd0008c916145e866cd174d334c97ec449bd8ec850d10985468653c25"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"file": "shards/spider-fly.jsonl.gz",
|
|
Binary file
|
|
@@ -60,5 +60,10 @@
|
|
|
60
60
|
{"world":"ashcombe-hall","kind":"rule","name":"give","ruleKind":"action-signature","slots":{"subjectClass":"portable","targetClass":"person"}}
|
|
61
61
|
{"world":"ashcombe-hall","kind":"rule","name":"give","ruleKind":"action-effect","slots":{"predicate":"located-in","subjectRole":"subject","objectRole":"target"}}
|
|
62
62
|
{"world":"ashcombe-hall","kind":"rule","name":"open","ruleKind":"action-signature","slots":{"subjectClass":"adventurer","targetClass":"furniture"}}
|
|
63
|
+
{"world":"ashcombe-hall","kind":"rule","name":"open","ruleKind":"action-precond","slots":{"shape":"fact-value","predicate":"stands-locked-in","role":"target","scope":"any","negate":"true"}}
|
|
64
|
+
{"world":"ashcombe-hall","kind":"rule","name":"open","ruleKind":"action-precond","slots":{"shape":"fact-value","predicate":"is-open","role":"target","scope":"any","value":"true","negate":"true"}}
|
|
65
|
+
{"world":"ashcombe-hall","kind":"rule","name":"open","ruleKind":"action-effect","slots":{"predicate":"is-open","subjectRole":"target","value":"true"}}
|
|
63
66
|
{"world":"ashcombe-hall","kind":"rule","name":"unlock","ruleKind":"action-signature","slots":{"subjectClass":"adventurer","targetClass":"furniture"}}
|
|
64
67
|
{"world":"ashcombe-hall","kind":"rule","name":"close","ruleKind":"action-signature","slots":{"subjectClass":"adventurer","targetClass":"furniture"}}
|
|
68
|
+
{"world":"ashcombe-hall","kind":"rule","name":"close","ruleKind":"action-precond","slots":{"shape":"fact-value","predicate":"is-open","role":"target","scope":"any","value":"true"}}
|
|
69
|
+
{"world":"ashcombe-hall","kind":"rule","name":"close","ruleKind":"action-effect","slots":{"predicate":"is-open","subjectRole":"target","value":"false"}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.12",
|
|
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.",
|
|
@@ -1287,10 +1287,18 @@ const RULE_SLOT_SPEC = {
|
|
|
1287
1287
|
[RULE_KIND_ACTION_PRECOND]: [
|
|
1288
1288
|
["shape", "mgx:ruleActionPrecondShape"], ["predicate", "mgx:ruleActionPrecondPredicate"],
|
|
1289
1289
|
["role", "mgx:ruleActionPrecondRole"], ["scope", "mgx:ruleActionPrecondScope"],
|
|
1290
|
+
// value/negate: the "fact-value" shape's literal-match and negation
|
|
1291
|
+
// slots. Optional (RULE_SLOT_OPTIONAL below) — no-incoming/comparator
|
|
1292
|
+
// preconds never set them.
|
|
1293
|
+
["value", "mgx:ruleActionPrecondValue"], ["negate", "mgx:ruleActionPrecondNegate"],
|
|
1290
1294
|
],
|
|
1291
1295
|
[RULE_KIND_ACTION_EFFECT]: [
|
|
1292
1296
|
["predicate", "mgx:ruleActionEffectPredicate"], ["subjectRole", "mgx:ruleActionEffectSubject"],
|
|
1293
|
-
|
|
1297
|
+
// objectRole/value: exactly one of these two must be set (enforced
|
|
1298
|
+
// below, not by this per-slot spec) — a role-bound effect (Hanoi's
|
|
1299
|
+
// "rest-on") supplies objectRole, a literal datatype effect (Ashcombe's
|
|
1300
|
+
// is-open = "true") supplies value instead.
|
|
1301
|
+
["objectRole", "mgx:ruleActionEffectObject"], ["value", "mgx:ruleActionEffectValue"],
|
|
1294
1302
|
],
|
|
1295
1303
|
// "the <left> may not be with the <right> without the <guard>" — each slot
|
|
1296
1304
|
// names a class whose sole member src/domain/domain.mjs resolves at compile time.
|
|
@@ -1300,25 +1308,47 @@ const RULE_SLOT_SPEC = {
|
|
|
1300
1308
|
],
|
|
1301
1309
|
};
|
|
1302
1310
|
|
|
1311
|
+
// Slots a kind's RULE_SLOT_SPEC lists but does NOT require non-empty — the
|
|
1312
|
+
// literal-effect/fact-value extension's slots, added after the original
|
|
1313
|
+
// four-kind design. A pre-existing rule never sets them, so it reads back
|
|
1314
|
+
// with these keys simply absent (readRuleRows defaults an absent slot to
|
|
1315
|
+
// ""), the same "not supplied" signal domain.mjs's compileDomain gives an
|
|
1316
|
+
// explicit "" — see optionalTerm there.
|
|
1317
|
+
const RULE_SLOT_OPTIONAL = {
|
|
1318
|
+
[RULE_KIND_ACTION_PRECOND]: new Set(["value", "negate"]),
|
|
1319
|
+
[RULE_KIND_ACTION_EFFECT]: new Set(["objectRole", "value"]),
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1303
1322
|
// Content-addressed over (kind, name, ...slots in RULE_SLOT_SPEC order),
|
|
1304
1323
|
// mirroring factIdFor's NUL-delimited discipline: identical rules upsert,
|
|
1305
1324
|
// different ones coexist. For 2-slot kinds the joined string is byte-identical
|
|
1306
1325
|
// to the historical (kind, name, slot1, slot2) template, so pre-existing rule
|
|
1307
|
-
// ids never change (pinned by test/adapters/memory-rules-action.test.mjs)
|
|
1326
|
+
// ids never change (pinned by test/adapters/memory-rules-action.test.mjs);
|
|
1327
|
+
// action-precond/action-effect ids shifted when their optional slots joined
|
|
1328
|
+
// the spec above (their own round-trip test asserts dedup behavior, never a
|
|
1329
|
+
// specific hash, so this was never a promise for those two kinds).
|
|
1308
1330
|
const ruleIdFor = (kind, name, slotValues) => `rule:${fnv1aHex([kind, name, ...slotValues].join("\0"))}`;
|
|
1309
1331
|
|
|
1310
1332
|
/** Append one taught RULE — a sibling of appendFact storing a `Rule`
|
|
1311
1333
|
* individual, same upsert/provenance/trust/SHACL discipline (neither
|
|
1312
1334
|
* pipeline ever checks `individual.class`). `slots` is the matching
|
|
1313
|
-
* per-kind object (RULE_SLOT_SPEC above)
|
|
1335
|
+
* per-kind object (RULE_SLOT_SPEC above); a slot named in RULE_SLOT_OPTIONAL
|
|
1336
|
+
* may be omitted. Returns { id }. */
|
|
1314
1337
|
export async function appendRule(dir, { name, kind, slots, provenance = "", createdAt = "" } = {}) {
|
|
1315
1338
|
const spec = RULE_SLOT_SPEC[kind];
|
|
1316
1339
|
if (!spec) throw new Error(`a rule kind must be one of ${RULE_KINDS.join(", ")}, got ${JSON.stringify(kind)}`);
|
|
1317
1340
|
const n = normFactTerm(name);
|
|
1318
1341
|
if (!n) throw new Error("a rule needs a name");
|
|
1342
|
+
const optional = RULE_SLOT_OPTIONAL[kind] || new Set();
|
|
1319
1343
|
const slotValues = spec.map(([slotKey]) => normFactTerm(slots?.[slotKey]));
|
|
1320
|
-
|
|
1321
|
-
|
|
1344
|
+
const missing = spec.filter(([slotKey], i) => !optional.has(slotKey) && !slotValues[i]);
|
|
1345
|
+
if (missing.length) {
|
|
1346
|
+
throw new Error(`a ${kind} rule needs ${missing.map(([slotKey]) => slotKey).join(" + ")}`);
|
|
1347
|
+
}
|
|
1348
|
+
if (kind === RULE_KIND_ACTION_EFFECT) {
|
|
1349
|
+
const objectRole = slotValues[spec.findIndex(([k]) => k === "objectRole")];
|
|
1350
|
+
const value = slotValues[spec.findIndex(([k]) => k === "value")];
|
|
1351
|
+
if (!objectRole && !value) throw new Error(`a ${kind} rule needs an objectRole or a value`);
|
|
1322
1352
|
}
|
|
1323
1353
|
const id = ruleIdFor(kind, n, slotValues);
|
|
1324
1354
|
const label = labelOf(`${n} = ${kind}(${slotValues.join(", ")})`);
|
|
@@ -1336,7 +1366,13 @@ export async function appendRule(dir, { name, kind, slots, provenance = "", crea
|
|
|
1336
1366
|
{ prop: "rdf:type", key: "type", value: "owl:NamedIndividual" },
|
|
1337
1367
|
{ prop: RULE_NAME_PROP, key: "ruleName", value: n },
|
|
1338
1368
|
{ prop: RULE_KIND_PROP, key: "ruleKind", value: kind },
|
|
1339
|
-
|
|
1369
|
+
// An optional slot left empty stores no attribute at all — the same
|
|
1370
|
+
// "never supplied" shape a pre-extension Rule individual already has,
|
|
1371
|
+
// rather than a wasted always-"" one.
|
|
1372
|
+
...spec
|
|
1373
|
+
.map(([slotKey, prop], i) => ({ prop, key: slotKey, value: slotValues[i], skip: !slotValues[i] && optional.has(slotKey) }))
|
|
1374
|
+
.filter((attr) => !attr.skip)
|
|
1375
|
+
.map(({ skip, ...attr }) => attr),
|
|
1340
1376
|
{ prop: CREATED_AT_PROP, key: "createdAt", value: createdAtVal },
|
|
1341
1377
|
...(provs.length ? [{ prop: "mgx:factProvenance", key: "provenance", value: provs.join(" | ") }] : []),
|
|
1342
1378
|
],
|
|
@@ -11,9 +11,13 @@ const RULE_KINDS = new Set([
|
|
|
11
11
|
"action-signature", "action-precond", "action-effect", "action-constraint",
|
|
12
12
|
]);
|
|
13
13
|
|
|
14
|
-
// Mirrors core.mjs's own (unexported) RULE_SLOT_SPEC
|
|
15
|
-
//
|
|
16
|
-
// (
|
|
14
|
+
// Mirrors the REQUIRED subset of core.mjs's own (unexported) RULE_SLOT_SPEC —
|
|
15
|
+
// kept in sync by hand. Two kinds there also carry optional slots
|
|
16
|
+
// (RULE_SLOT_OPTIONAL: action-precond's value/negate, action-effect's
|
|
17
|
+
// objectRole/value) that deliberately do NOT appear here: this gate only
|
|
18
|
+
// checks genuine malformation (the comment above), and "at least one of
|
|
19
|
+
// objectRole/value" is a joint constraint core.mjs's own appendRule already
|
|
20
|
+
// enforces at write time, not a per-slot presence check this shape can express.
|
|
17
21
|
const RULE_SLOT_PROPS = {
|
|
18
22
|
compose2: ["mgx:ruleBase1", "mgx:ruleBase2"],
|
|
19
23
|
filter: ["mgx:ruleBase1", "mgx:ruleFilterProperty"],
|
|
@@ -23,9 +27,7 @@ const RULE_SLOT_PROPS = {
|
|
|
23
27
|
"mgx:ruleActionPrecondShape", "mgx:ruleActionPrecondPredicate",
|
|
24
28
|
"mgx:ruleActionPrecondRole", "mgx:ruleActionPrecondScope",
|
|
25
29
|
],
|
|
26
|
-
"action-effect": [
|
|
27
|
-
"mgx:ruleActionEffectPredicate", "mgx:ruleActionEffectSubject", "mgx:ruleActionEffectObject",
|
|
28
|
-
],
|
|
30
|
+
"action-effect": ["mgx:ruleActionEffectPredicate", "mgx:ruleActionEffectSubject"],
|
|
29
31
|
"action-constraint": [
|
|
30
32
|
"mgx:ruleActionConstraintLeft", "mgx:ruleActionConstraintRight", "mgx:ruleActionConstraintGuard",
|
|
31
33
|
],
|
package/src/domain/domain.mjs
CHANGED
|
@@ -19,6 +19,15 @@ const attachPrefix = (predicate) => {
|
|
|
19
19
|
return p.includes(":") ? p : `mgx:${p}`;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
/** A genuinely-supplied optional slot (a literal precond/effect value): `""`,
|
|
23
|
+
* `null` and `undefined` all read as "not supplied", the same signal a
|
|
24
|
+
* never-taught slot and a readRuleRows-defaulted `""` both give. */
|
|
25
|
+
const optionalTerm = (value) => {
|
|
26
|
+
if (value == null) return undefined;
|
|
27
|
+
const t = normTerm(value);
|
|
28
|
+
return t === "" ? undefined : t;
|
|
29
|
+
};
|
|
30
|
+
|
|
22
31
|
const rowSort = (a, b) =>
|
|
23
32
|
a.subject.localeCompare(b.subject) ||
|
|
24
33
|
a.predicate.localeCompare(b.predicate) ||
|
|
@@ -55,17 +64,32 @@ export function compileDomain(factRows, ruleRows) {
|
|
|
55
64
|
targetClass: normTerm(slots.targetClass),
|
|
56
65
|
});
|
|
57
66
|
} else if (rule.kind === "action-precond") {
|
|
67
|
+
// value/negate are optional: absent in every no-incoming/comparator row
|
|
68
|
+
// taught so far, so their key is omitted rather than written as "" —
|
|
69
|
+
// an omitted key keeps a pre-existing precond's JSON.stringify sort
|
|
70
|
+
// form (and its precondHolds behavior) byte-identical to before this
|
|
71
|
+
// shape existed. normFactTerm/readRuleRows read a genuinely absent
|
|
72
|
+
// slot back as "", the same signal an explicit "" would give.
|
|
73
|
+
const value = optionalTerm(slots.value);
|
|
74
|
+
const negate = String(slots.negate) === "true" ? true : undefined;
|
|
58
75
|
family.preconds.push({
|
|
59
76
|
shape: normTerm(slots.shape),
|
|
60
77
|
predicate: attachPrefix(slots.predicate),
|
|
61
78
|
role: normTerm(slots.role),
|
|
62
79
|
scope: normTerm(slots.scope),
|
|
80
|
+
...(value !== undefined ? { value } : {}),
|
|
81
|
+
...(negate !== undefined ? { negate } : {}),
|
|
63
82
|
});
|
|
64
83
|
} else if (rule.kind === "action-effect") {
|
|
84
|
+
// value is the literal-effect alternative to objectRole (a datatype
|
|
85
|
+
// write, e.g. mgx:is-open = "true", rather than a role binding) — same
|
|
86
|
+
// omit-when-absent discipline as the precond fields above.
|
|
87
|
+
const value = optionalTerm(slots.value);
|
|
65
88
|
family.effects.push({
|
|
66
89
|
predicate: attachPrefix(slots.predicate),
|
|
67
90
|
subjectRole: normTerm(slots.subjectRole),
|
|
68
91
|
objectRole: normTerm(slots.objectRole),
|
|
92
|
+
...(value !== undefined ? { value } : {}),
|
|
69
93
|
});
|
|
70
94
|
} else if (rule.kind === "action-constraint") {
|
|
71
95
|
family.constraints.push({
|
|
@@ -111,7 +135,11 @@ export function compileDomain(factRows, ruleRows) {
|
|
|
111
135
|
};
|
|
112
136
|
for (const action of actions) {
|
|
113
137
|
for (const effect of action.effects) {
|
|
114
|
-
|
|
138
|
+
// A literal-valued effect (effect.value set) has no objectRole to bind
|
|
139
|
+
// — the literal IS the object, so objectRole (unused, "" when the
|
|
140
|
+
// teaching row never set it) never enters the class-membership check.
|
|
141
|
+
const roles = effect.value !== undefined ? [effect.subjectRole] : [effect.subjectRole, effect.objectRole];
|
|
142
|
+
for (const role of roles) {
|
|
115
143
|
if (role !== "subject" && role !== "target") requireSoleMember(role, `an effect role of "${action.name}"`);
|
|
116
144
|
}
|
|
117
145
|
}
|
|
@@ -187,7 +215,13 @@ export function stateKeyFor(state) {
|
|
|
187
215
|
const precondApplies = (precond, target, domain) =>
|
|
188
216
|
precond.scope === "any" || (domain.classMembers[precond.scope] || []).includes(target);
|
|
189
217
|
|
|
190
|
-
|
|
218
|
+
/** A precondition on a fact ABOUT the role term itself (roleTerm as subject),
|
|
219
|
+
* as opposed to "no-incoming"'s fact pointing AT the role term (roleTerm as
|
|
220
|
+
* object). Datatype/state checks — a lock, an open/closed flag — read this
|
|
221
|
+
* way: "does (roleTerm, predicate, value) exist", with `value` an existence
|
|
222
|
+
* wildcard when omitted and `negate` flipping the sense ("must NOT exist"
|
|
223
|
+
* covers "must not be locked", "must not already be open"). */
|
|
224
|
+
export function precondHolds(precond, subject, target, state, domain) {
|
|
191
225
|
const roleTerm = precond.role === "target" ? target : subject;
|
|
192
226
|
if (precond.shape === "no-incoming") {
|
|
193
227
|
return !state.some((r) => r.predicate === precond.predicate && r.object === roleTerm);
|
|
@@ -198,13 +232,21 @@ function precondHolds(precond, subject, target, state, domain) {
|
|
|
198
232
|
return domain.ordering.some((r) =>
|
|
199
233
|
r.subject === left && r.predicate === precond.predicate && r.object === right);
|
|
200
234
|
}
|
|
235
|
+
if (precond.shape === "fact-value") {
|
|
236
|
+
const exists = state.some((r) => r.subject === roleTerm && r.predicate === precond.predicate
|
|
237
|
+
&& (precond.value == null || r.object === precond.value));
|
|
238
|
+
return precond.negate ? !exists : exists;
|
|
239
|
+
}
|
|
201
240
|
return false;
|
|
202
241
|
}
|
|
203
242
|
|
|
204
243
|
/** Ground an effect/constraint role word: "subject"/"target" bind the
|
|
205
244
|
* grounding pair; any other word is class-bound and binds the class's sole
|
|
206
|
-
* member — its companion semantics (compileDomain guarantees exactly one).
|
|
207
|
-
|
|
245
|
+
* member — its companion semantics (compileDomain guarantees exactly one).
|
|
246
|
+
* Exported so a caller consulting one grounded precond/effect directly,
|
|
247
|
+
* rather than running a full movesFromRules search, can still resolve an
|
|
248
|
+
* effect's subject/object the same way the planner does. */
|
|
249
|
+
export const roleBinding = (role, subject, target, domain) => {
|
|
208
250
|
if (role === "subject") return subject;
|
|
209
251
|
if (role === "target") return target;
|
|
210
252
|
return (domain.classMembers[role] || [])[0];
|
|
@@ -213,12 +255,15 @@ const roleBinding = (role, subject, target, domain) => {
|
|
|
213
255
|
const positionIn = (rows, term, predicate) =>
|
|
214
256
|
rows.find((r) => r.subject === term && r.predicate === predicate)?.object;
|
|
215
257
|
|
|
216
|
-
function applyEffects(effects, subject, target, state, domain) {
|
|
258
|
+
export function applyEffects(effects, subject, target, state, domain) {
|
|
217
259
|
let rows = state;
|
|
218
260
|
let changed = false;
|
|
219
261
|
for (const effect of effects) {
|
|
220
262
|
const effSubject = roleBinding(effect.subjectRole, subject, target, domain);
|
|
221
|
-
|
|
263
|
+
// A literal-valued effect (a taught datatype flag) writes its value
|
|
264
|
+
// directly; a role-valued effect resolves objectRole through the
|
|
265
|
+
// grounding pair / class-bound member instead.
|
|
266
|
+
const effObject = effect.value !== undefined ? effect.value : roleBinding(effect.objectRole, subject, target, domain);
|
|
222
267
|
const already = rows.some((r) =>
|
|
223
268
|
r.subject === effSubject && r.predicate === effect.predicate && r.object === effObject);
|
|
224
269
|
if (already) continue;
|
|
@@ -502,7 +502,15 @@ const VERB_SYNONYMS = new Map([
|
|
|
502
502
|
// consumed whole before any shorter prefix inside it gets a chance to match.
|
|
503
503
|
const VERB_SYNONYM_MAX_PREFIX = Math.max(...[...VERB_SYNONYMS.keys()].map((k) => k.split(" ").length));
|
|
504
504
|
|
|
505
|
-
|
|
505
|
+
// Every leading SURFACE word the exact tiers below recognise as the start of
|
|
506
|
+
// a command: a bare IMPERATIVE_VERBS word, or the first token of any
|
|
507
|
+
// VERB_SYNONYMS key (single- or multi-word alike — "pick", "have", "chat" are
|
|
508
|
+
// each only ever a key's own first word, never a key on their own, so they'd
|
|
509
|
+
// otherwise have nothing to fuzzy-repair a typo onto). Corrects the SURFACE
|
|
510
|
+
// word, not the canonical verb, so the existing exact-match tiers below (in
|
|
511
|
+
// particular the multi-word prefix loop) still resolve the rest of the phrase
|
|
512
|
+
// after repair — "hav a look at" -> "have a look at" -> examine, not just "look".
|
|
513
|
+
const VERB_SURFACE_WORDS = [...new Set([...IMPERATIVE_VERBS, ...[...VERB_SYNONYMS.keys()].map((k) => k.split(" ")[0])])];
|
|
506
514
|
const IMPERATIVE_FUZZY_BOUND = 1; // this project's existing distance-1 tolerance elsewhere (interpret/fuzzy.mjs)
|
|
507
515
|
|
|
508
516
|
// A small, hand-checked exemption (same idiom as this file's own
|
|
@@ -513,31 +521,48 @@ const IMPERATIVE_FUZZY_BOUND = 1; // this project's existing distance-1 toleranc
|
|
|
513
521
|
// Checked against this repo's own WordNet corpus, not guessed.
|
|
514
522
|
const NEVER_FUZZY_VERB = new Set(["walk", "wake", "make"]);
|
|
515
523
|
|
|
516
|
-
/**
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
*
|
|
522
|
-
|
|
523
|
-
* for a two-word synonym phrase); `corrected` is non-null only for the
|
|
524
|
-
* fuzzy tier. Null when nothing at all recognises the leading token(s). */
|
|
525
|
-
function resolveImperativeVerb(toks) {
|
|
524
|
+
/** The three deterministic (non-fuzzy) tiers: a 2+-token VERB_SYNONYMS prefix
|
|
525
|
+
* FIRST (so "talk to"/"look at" resolve as themselves rather than as the
|
|
526
|
+
* bare exact verbs "talk"/"look" they'd otherwise short-circuit on, leaving
|
|
527
|
+
* their "to"/"at" stranded in the object phrase), then an exact
|
|
528
|
+
* IMPERATIVE_VERBS match, then the 1-token VERB_SYNONYMS form. `consumed` is
|
|
529
|
+
* how many leading tokens the match ate. Null when none of the three match. */
|
|
530
|
+
function exactImperativeVerb(toks) {
|
|
526
531
|
const first = toks[0].toLowerCase();
|
|
527
532
|
const lower = toks.map((t) => t.toLowerCase());
|
|
528
533
|
for (let n = Math.min(VERB_SYNONYM_MAX_PREFIX, toks.length); n >= 2; n -= 1) {
|
|
529
534
|
const hit = VERB_SYNONYMS.get(lower.slice(0, n).join(" "));
|
|
530
|
-
if (hit) return { verb: hit, consumed: n
|
|
535
|
+
if (hit) return { verb: hit, consumed: n };
|
|
531
536
|
}
|
|
532
|
-
if (IMPERATIVE_VERBS.has(first)) return { verb: first, consumed: 1
|
|
537
|
+
if (IMPERATIVE_VERBS.has(first)) return { verb: first, consumed: 1 };
|
|
533
538
|
const oneHit = VERB_SYNONYMS.get(first);
|
|
534
|
-
if (oneHit) return { verb: oneHit, consumed: 1
|
|
535
|
-
if (NEVER_FUZZY_VERB.has(first)) return null;
|
|
536
|
-
const fuzzyHit = fuzzyMatchInSet(first, VERB_FUZZY_CANDIDATES, IMPERATIVE_FUZZY_BOUND);
|
|
537
|
-
if (fuzzyHit) return { verb: fuzzyHit, consumed: 1, corrected: { from: first, to: fuzzyHit } };
|
|
539
|
+
if (oneHit) return { verb: oneHit, consumed: 1 };
|
|
538
540
|
return null;
|
|
539
541
|
}
|
|
540
542
|
|
|
543
|
+
/** Resolve the leading verb token(s) to a canonical, recognised verb: the
|
|
544
|
+
* three exact tiers above, failing that a bounded fuzzy TYPO repair
|
|
545
|
+
* (distance <= 1, via the existing interpret/fuzzy.mjs matcher) of the
|
|
546
|
+
* leading token against every recognised surface word, single- or
|
|
547
|
+
* multi-word idiom alike, then the exact tiers retried against the
|
|
548
|
+
* corrected token — so a typo'd idiom lead word ("hav a look at") is
|
|
549
|
+
* repaired to its real word first ("have a look at") and still resolves the
|
|
550
|
+
* whole idiom, not just a bare-verb guess. Only the fuzzy tier is a
|
|
551
|
+
* correction of an ERROR rather than a recognised alternate wording, so only
|
|
552
|
+
* it is reported back on the command (`corrected`) for the caller to name in
|
|
553
|
+
* its response — a synonym executes silently, a typo fix does not. */
|
|
554
|
+
function resolveImperativeVerb(toks) {
|
|
555
|
+
const exact = exactImperativeVerb(toks);
|
|
556
|
+
if (exact) return { ...exact, corrected: null };
|
|
557
|
+
const first = toks[0].toLowerCase();
|
|
558
|
+
if (NEVER_FUZZY_VERB.has(first)) return null;
|
|
559
|
+
const fixedFirst = fuzzyMatchInSet(first, VERB_SURFACE_WORDS, IMPERATIVE_FUZZY_BOUND);
|
|
560
|
+
if (!fixedFirst) return null;
|
|
561
|
+
const retried = exactImperativeVerb([fixedFirst, ...toks.slice(1)]);
|
|
562
|
+
if (!retried) return null;
|
|
563
|
+
return { ...retried, corrected: { from: first, to: fixedFirst } };
|
|
564
|
+
}
|
|
565
|
+
|
|
541
566
|
/** Resolve one imperative object phrase to its bare lexicon term. */
|
|
542
567
|
function imperativeNP(lexicon, tokens) {
|
|
543
568
|
const np = resolveNP(lexicon, tokens);
|
|
@@ -12,6 +12,7 @@ import { worldProvenanceTag } from "../domain/worlds-pack.mjs";
|
|
|
12
12
|
import { parseImperative } from "../domain/grammar/ace.mjs";
|
|
13
13
|
import { createCompletionsGraphAdapter } from "../domain/completions/graph-adapter.mjs";
|
|
14
14
|
import { actionFamilies } from "../domain/router/taught.mjs";
|
|
15
|
+
import { compileDomain, precondHolds, roleBinding } from "../domain/domain.mjs";
|
|
15
16
|
import { getWorldsPackProvider } from "../adapters/corpus/worlds-pack.mjs";
|
|
16
17
|
import { appendFacts, appendRule, loadMemory, normFactTerm, readFactRows, readRuleRows } from "../adapters/memory/core.mjs";
|
|
17
18
|
import { COMPLETIONS_STORE, generateCompletion } from "./completions.mjs";
|
|
@@ -78,7 +79,12 @@ async function openAdventure(opening, { planHolder, memoryDir, sessionId, env, c
|
|
|
78
79
|
world = names[0];
|
|
79
80
|
} else if (!names.includes(world)) {
|
|
80
81
|
// An unknown "play X" is not necessarily an adventure ask at all ("play
|
|
81
|
-
//
|
|
82
|
+
// spider" is spider-fly's own opener, not a broken adventure request) —
|
|
83
|
+
// fall through so a sibling game's opener keeps first refusal.
|
|
84
|
+
// unclaimedAdventureOpening below is chat's LAST-RESORT check: once
|
|
85
|
+
// every "play X" lane (this one included) has had its turn and none
|
|
86
|
+
// claimed the line, THAT'S when an unrecognized name gets named and
|
|
87
|
+
// declined honestly, never silently.
|
|
82
88
|
return null;
|
|
83
89
|
}
|
|
84
90
|
|
|
@@ -125,6 +131,31 @@ async function openAdventure(opening, { planHolder, memoryDir, sessionId, env, c
|
|
|
125
131
|
};
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
/** chat's LAST-RESORT check for a named opener no lane claimed — "play
|
|
135
|
+
* atlantis" when the pack has worlds but none is called that. Called AFTER
|
|
136
|
+
* every other "play X"-shaped lane (spider-fly's own opener among them) has
|
|
137
|
+
* already had its chance, so this never steals a name a sibling game
|
|
138
|
+
* recognizes as its own (openAdventure's own fallthrough above stays
|
|
139
|
+
* silent for exactly that reason). Only once nothing else wanted the line
|
|
140
|
+
* does it get named and declined, rather than answered with an unrelated
|
|
141
|
+
* generic non-answer. Null when the line isn't a named opener at all, or
|
|
142
|
+
* the pack has no worlds (openAdventure's own first pass already gave that
|
|
143
|
+
* case its honest missingPackAnswer, before any lane got a turn), or the
|
|
144
|
+
* name IS one of the pack's — never re-decides a real hit. */
|
|
145
|
+
export async function unclaimedAdventureOpening(line, { env }) {
|
|
146
|
+
const opening = matchAdventureOpening(line);
|
|
147
|
+
if (!opening?.world) return null;
|
|
148
|
+
const provider = getWorldsPackProvider(env);
|
|
149
|
+
let names = null;
|
|
150
|
+
try { names = await provider.list(); } catch { names = null; }
|
|
151
|
+
if (!names || !names.length || names.includes(opening.world)) return null;
|
|
152
|
+
return {
|
|
153
|
+
text: `I don't know a world called "${spokenNameOf(opening.world)}" — the pack has: ${names.map(spokenNameOf).join(", ")}.`,
|
|
154
|
+
lane: "game-inform",
|
|
155
|
+
note: `ADVENTURE — last-resort opening decline: "${opening.world}" names no world in the pack (has: ${names.join(", ")}), and no other lane claimed the line either`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
128
159
|
/** A resumed game's current room: non-null only when earlier @turnN
|
|
129
160
|
* snapshots exist (the world was already played in this store), folded the
|
|
130
161
|
* same way every other reader folds them. A fresh world returns null and
|
|
@@ -264,8 +295,10 @@ const affordanceSuffix = (actions) => (actions.length ? ` You can: ${actions.joi
|
|
|
264
295
|
|
|
265
296
|
/** The effect predicate a family writes (its action-effect row's slot, with
|
|
266
297
|
* the mgx: prefix rule readers re-attach). Null when the family carries no
|
|
267
|
-
* effect row —
|
|
268
|
-
*
|
|
298
|
+
* effect row — unlock's family stays signature-only (its instrument match
|
|
299
|
+
* needs a third, externally-supplied binding no shipped rule shape covers)
|
|
300
|
+
* and rides the hand-written logic below; go/take/drop/give/open/close all
|
|
301
|
+
* carry real effect rows and never hit this null. */
|
|
269
302
|
function familyEffectPredicate(family) {
|
|
270
303
|
const effect = (family || []).find((r) => r.kind === "action-effect");
|
|
271
304
|
if (!effect?.slots?.predicate) return null;
|
|
@@ -449,6 +482,24 @@ function containerStatusPhrase(object, { state }) {
|
|
|
449
482
|
: `the ${object} is open. It's empty.`;
|
|
450
483
|
}
|
|
451
484
|
|
|
485
|
+
/** `object`'s own datatype facts (its placement predicate, its open/closed
|
|
486
|
+
* flag), as the tiny {subject,predicate,object} row set a "fact-value"
|
|
487
|
+
* precond needs — read from the already-folded CURRENT truth (state.
|
|
488
|
+
* placements/state.openness), never raw @turnN rows, so a superseded
|
|
489
|
+
* snapshot can never look current. domain.mjs's own stateFromFacts can't
|
|
490
|
+
* serve this: it keys "current" off an @stepN suffix (this world writes
|
|
491
|
+
* @turnN) and restricts rows to individuals typed into some action's
|
|
492
|
+
* SUBJECT class, which "furniture" (the container's own class) never is —
|
|
493
|
+
* a container's own facts about itself would silently vanish through it. */
|
|
494
|
+
function containerDatatypeState(state, object) {
|
|
495
|
+
const rows = [];
|
|
496
|
+
const place = state.placements.get(object);
|
|
497
|
+
if (place) rows.push({ subject: object, predicate: place.predicate, object: place.object });
|
|
498
|
+
const openness = state.openness.get(object);
|
|
499
|
+
if (openness) rows.push({ subject: object, predicate: OPEN_PREDICATE, object: openness.open ? "true" : "false" });
|
|
500
|
+
return rows;
|
|
501
|
+
}
|
|
502
|
+
|
|
452
503
|
async function runWorldCommand(cmd, { world, memoryDir, env, graph, cache }) {
|
|
453
504
|
const memory = await loadMemory(memoryDir);
|
|
454
505
|
const rows = readFactRows(memory);
|
|
@@ -512,7 +563,8 @@ async function runWorldCommand(cmd, { world, memoryDir, env, graph, cache }) {
|
|
|
512
563
|
);
|
|
513
564
|
}
|
|
514
565
|
|
|
515
|
-
const
|
|
566
|
+
const ruleRows = readRuleRows(memory);
|
|
567
|
+
const families = actionFamilies(ruleRows);
|
|
516
568
|
const family = families.get(cmd.verb);
|
|
517
569
|
if (!family) {
|
|
518
570
|
return answer(
|
|
@@ -619,9 +671,15 @@ async function runWorldCommand(cmd, { world, memoryDir, env, graph, cache }) {
|
|
|
619
671
|
);
|
|
620
672
|
}
|
|
621
673
|
|
|
622
|
-
// open / unlock / close — the container verbs.
|
|
623
|
-
//
|
|
624
|
-
//
|
|
674
|
+
// open / unlock / close — the container verbs. presence and container-ness
|
|
675
|
+
// stay hand-checked here (visibility gating, not a state precondition);
|
|
676
|
+
// unlock's instrument match stays fully hand-written below it too — it
|
|
677
|
+
// needs a third, externally-supplied binding beyond subject/target, which
|
|
678
|
+
// this retrofit does not attempt. open/close's lock-state and open/closed
|
|
679
|
+
// checks, and their mgx:is-open write, are now taught "fact-value"
|
|
680
|
+
// precond/effect rows consulted through domain.mjs below; only the
|
|
681
|
+
// hidden-contents reveal (a variable-arity effect over a discovered set)
|
|
682
|
+
// stays hand-written JS, since no shipped rule shape covers that either.
|
|
625
683
|
const presentHere = place && place.predicate !== "mgx:hidden-in" && place.predicate !== "mgx:currently-in" && place.object === here;
|
|
626
684
|
if (!presentHere) {
|
|
627
685
|
return answer(`I don't see a ${object} here.`, noteFor(`${cmd.verb} — ${object} isn't in the ${here}; declined`), { miss: true });
|
|
@@ -629,40 +687,50 @@ async function runWorldCommand(cmd, { world, memoryDir, env, graph, cache }) {
|
|
|
629
687
|
if (!isContainer(rows, object)) {
|
|
630
688
|
return answer(`the ${object} doesn't open.`, noteFor(`${cmd.verb} — no mgx:is-container fact on ${object}; declined by name`), { miss: true });
|
|
631
689
|
}
|
|
632
|
-
const open = !!state.openness.get(object)?.open;
|
|
633
690
|
|
|
634
|
-
if (cmd.verb === "open") {
|
|
635
|
-
|
|
636
|
-
|
|
691
|
+
if (cmd.verb === "open" || cmd.verb === "close") {
|
|
692
|
+
const domain = compileDomain(rows, ruleRows);
|
|
693
|
+
const taughtAction = domain.actions.find((a) => a.name === cmd.verb);
|
|
694
|
+
const effect = taughtAction?.effects.find((e) => e.predicate === OPEN_PREDICATE);
|
|
695
|
+
if (!effect) {
|
|
696
|
+
return answer(
|
|
697
|
+
`this world doesn't teach how ${cmd.verb === "open" ? "opening" : "closing"} changes the ${object}.`,
|
|
698
|
+
noteFor(`${cmd.verb} — the taught "${cmd.verb}" family carries no ${OPEN_PREDICATE} effect; honest decline`),
|
|
699
|
+
{ miss: true },
|
|
700
|
+
);
|
|
637
701
|
}
|
|
638
|
-
|
|
639
|
-
|
|
702
|
+
const factState = containerDatatypeState(state, object);
|
|
703
|
+
const failed = taughtAction.preconds.find((p) => !precondHolds(p, "player", object, factState, domain));
|
|
704
|
+
if (failed) {
|
|
705
|
+
const text = failed.predicate === "mgx:stands-locked-in"
|
|
706
|
+
? `the ${object} is locked.`
|
|
707
|
+
: cmd.verb === "open" ? `the ${object} is already open.` : `the ${object} isn't open.`;
|
|
708
|
+
return answer(text, noteFor(`${cmd.verb} — the taught "${cmd.verb}" family's ${failed.predicate} precondition declined by name`), { miss: true });
|
|
640
709
|
}
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
return answer(`the ${object} isn't open.`, noteFor("close — not open; declined"), { miss: true });
|
|
710
|
+
const effSubject = roleBinding(effect.subjectRole, "player", object, domain);
|
|
711
|
+
const writeIsOpen = { subject: `${effSubject}@turn${k}`, predicate: effect.predicate, object: effect.value };
|
|
712
|
+
|
|
713
|
+
if (cmd.verb === "open") {
|
|
714
|
+
const revealed = [...state.placements]
|
|
715
|
+
.filter(([, p]) => p.predicate === "mgx:hidden-in" && p.object === object)
|
|
716
|
+
.map(([thing]) => thing)
|
|
717
|
+
.sort();
|
|
718
|
+
return commit(
|
|
719
|
+
[
|
|
720
|
+
writeIsOpen,
|
|
721
|
+
...revealed.map((thing) => ({ subject: `${thing}@turn${k}`, predicate: "mgx:located-in", object })),
|
|
722
|
+
],
|
|
723
|
+
revealed.length
|
|
724
|
+
? `you open the ${object} — inside: the ${revealed.join(", the ")}.`
|
|
725
|
+
: `you open the ${object}. It's empty.`,
|
|
726
|
+
`open — ${object} opens${revealed.length ? `, revealing ${revealed.join(", ")}` : ""} via the taught "open" family's effect`,
|
|
727
|
+
`open the ${object}`,
|
|
728
|
+
);
|
|
661
729
|
}
|
|
662
730
|
return commit(
|
|
663
|
-
[
|
|
731
|
+
[writeIsOpen],
|
|
664
732
|
`you close the ${object}.`,
|
|
665
|
-
`close — ${object} closes`,
|
|
733
|
+
`close — ${object} closes via the taught "close" family's effect`,
|
|
666
734
|
`close the ${object}`,
|
|
667
735
|
);
|
|
668
736
|
}
|
package/src/services/chat.mjs
CHANGED
|
@@ -52,7 +52,7 @@ import { CHILD_PACK_NAME, childProvenanceTag } from "../domain/child-pack.mjs";
|
|
|
52
52
|
import { getChildPackProvider } from "../adapters/corpus/child-pack.mjs";
|
|
53
53
|
import { dialogueActForLane } from "../domain/dialogue-acts.mjs";
|
|
54
54
|
import { relatedForTerm } from "../domain/skos-view.mjs";
|
|
55
|
-
import { adventureTurn } from "./adventure.mjs";
|
|
55
|
+
import { adventureTurn, unclaimedAdventureOpening } from "./adventure.mjs";
|
|
56
56
|
import { spiderFlyTurn } from "./spider-fly-turn.mjs";
|
|
57
57
|
|
|
58
58
|
// Composition: the chat surface supplies the domain parser's default lemma/POS
|
|
@@ -12646,6 +12646,24 @@ export async function runTurn(input, { config, source = defaultSource, graph = n
|
|
|
12646
12646
|
}
|
|
12647
12647
|
}
|
|
12648
12648
|
|
|
12649
|
+
// A "play X" naming no world EITHER game lane above claimed — last resort,
|
|
12650
|
+
// checked only once the adventure lane's own fallthrough and spider-fly's
|
|
12651
|
+
// own opener have both passed on the line, so this never outguesses a
|
|
12652
|
+
// sibling game's own recognized phrasing. Honest by name ("I don't know a
|
|
12653
|
+
// world called…") rather than the generic non-answer an unclaimed opener
|
|
12654
|
+
// fell into before.
|
|
12655
|
+
{
|
|
12656
|
+
const unclaimed = await unclaimedAdventureOpening(workingLine, { env });
|
|
12657
|
+
if (unclaimed) {
|
|
12658
|
+
note(trace, `lane: ${unclaimed.note}`);
|
|
12659
|
+
const result = plainTurn(workingLine, unclaimed.text, { via: "game", miss: true, focus });
|
|
12660
|
+
result.lane = unclaimed.lane;
|
|
12661
|
+
const rec = withLast(result, "play the adventure");
|
|
12662
|
+
rec.planState = planHolder.state;
|
|
12663
|
+
return rec;
|
|
12664
|
+
}
|
|
12665
|
+
}
|
|
12666
|
+
|
|
12649
12667
|
// Conversational layer next (greetings, thanks, help, bye, why/say-more) — these
|
|
12650
12668
|
// resolve no entity and carry their own preserved `last`. Bypasses withLast (a
|
|
12651
12669
|
// conversational turn is never finish()'d / never becomes a new `last`), so the
|
|
@@ -9131,11 +9131,7 @@ ${bodyText}` : graphText, tier });
|
|
|
9131
9131
|
"mgx:ruleActionPrecondRole",
|
|
9132
9132
|
"mgx:ruleActionPrecondScope"
|
|
9133
9133
|
],
|
|
9134
|
-
"action-effect": [
|
|
9135
|
-
"mgx:ruleActionEffectPredicate",
|
|
9136
|
-
"mgx:ruleActionEffectSubject",
|
|
9137
|
-
"mgx:ruleActionEffectObject"
|
|
9138
|
-
],
|
|
9134
|
+
"action-effect": ["mgx:ruleActionEffectPredicate", "mgx:ruleActionEffectSubject"],
|
|
9139
9135
|
"action-constraint": [
|
|
9140
9136
|
"mgx:ruleActionConstraintLeft",
|
|
9141
9137
|
"mgx:ruleActionConstraintRight",
|
|
@@ -10088,9 +10084,16 @@ ${bodyText}` : graphText, tier });
|
|
|
10088
10084
|
if (!spec) throw new Error(`a rule kind must be one of ${RULE_KINDS2.join(", ")}, got ${JSON.stringify(kind)}`);
|
|
10089
10085
|
const n = normFactTerm(name);
|
|
10090
10086
|
if (!n) throw new Error("a rule needs a name");
|
|
10087
|
+
const optional = RULE_SLOT_OPTIONAL[kind] || /* @__PURE__ */ new Set();
|
|
10091
10088
|
const slotValues = spec.map(([slotKey]) => normFactTerm(slots?.[slotKey]));
|
|
10092
|
-
|
|
10093
|
-
|
|
10089
|
+
const missing = spec.filter(([slotKey], i) => !optional.has(slotKey) && !slotValues[i]);
|
|
10090
|
+
if (missing.length) {
|
|
10091
|
+
throw new Error(`a ${kind} rule needs ${missing.map(([slotKey]) => slotKey).join(" + ")}`);
|
|
10092
|
+
}
|
|
10093
|
+
if (kind === RULE_KIND_ACTION_EFFECT) {
|
|
10094
|
+
const objectRole = slotValues[spec.findIndex(([k]) => k === "objectRole")];
|
|
10095
|
+
const value = slotValues[spec.findIndex(([k]) => k === "value")];
|
|
10096
|
+
if (!objectRole && !value) throw new Error(`a ${kind} rule needs an objectRole or a value`);
|
|
10094
10097
|
}
|
|
10095
10098
|
const id = ruleIdFor(kind, n, slotValues);
|
|
10096
10099
|
const label = labelOf(`${n} = ${kind}(${slotValues.join(", ")})`);
|
|
@@ -10109,7 +10112,10 @@ ${bodyText}` : graphText, tier });
|
|
|
10109
10112
|
{ prop: "rdf:type", key: "type", value: "owl:NamedIndividual" },
|
|
10110
10113
|
{ prop: RULE_NAME_PROP, key: "ruleName", value: n },
|
|
10111
10114
|
{ prop: RULE_KIND_PROP, key: "ruleKind", value: kind },
|
|
10112
|
-
|
|
10115
|
+
// An optional slot left empty stores no attribute at all — the same
|
|
10116
|
+
// "never supplied" shape a pre-extension Rule individual already has,
|
|
10117
|
+
// rather than a wasted always-"" one.
|
|
10118
|
+
...spec.map(([slotKey, prop], i) => ({ prop, key: slotKey, value: slotValues[i], skip: !slotValues[i] && optional.has(slotKey) })).filter((attr) => !attr.skip).map(({ skip, ...attr }) => attr),
|
|
10113
10119
|
{ prop: CREATED_AT_PROP, key: "createdAt", value: createdAtVal },
|
|
10114
10120
|
...provs.length ? [{ prop: "mgx:factProvenance", key: "provenance", value: provs.join(" | ") }] : []
|
|
10115
10121
|
]
|
|
@@ -10376,7 +10382,7 @@ ${bodyText}` : graphText, tier });
|
|
|
10376
10382
|
}
|
|
10377
10383
|
return out.sort((a, b) => `${a[0].subject} ${a[0].predicate}`.localeCompare(`${b[0].subject} ${b[0].predicate}`));
|
|
10378
10384
|
}
|
|
10379
|
-
var MEMORY_DIR_REL, MEMORY_GRAPH_REL, UTTERANCE_CLASS, FACT_CLASS, MEMORY_SESSION_CLASS, SOURCE_CLASS, RULE_CLASS, SAID_IN_SESSION_PROP, IN_REPLY_TO_PROP, DERIVED_FROM_PROP, STATED_BY_PROP, CANONICALISED_FROM_PROP, SOURCE_RELIABILITY_PROP, OPERATOR_SOURCE_ID, TEACH_SOURCE_ID, ROLES, LABEL_CAP, MEMORY_VOCABULARY, memoryGraphFile, BACKEND_MEMORY, BACKEND_SQLITE, SQLITE_DDL, STD_EDGE_KEYS, cloneJson, MEMORY_MANIFEST_REL, DEFAULT_RETENTION, resolveManifestFile, LEGACY_FACT_ID_RE, SYLLOGISE_STATE_REL, SQLITE_SYLLOGISE_STATE_KEY, MEMORY_INDEX, memoryIndexOf, labelOf, nowIso, sourceLabel, PROV_CLASS_BY_SOURCE_TYPE, isSessionScopedSourceId, RULE_KIND_COMPOSE2, RULE_KIND_FILTER, RULE_KIND_RECURSIVE, RULE_KIND_ACTION_SIGNATURE, RULE_KIND_ACTION_PRECOND, RULE_KIND_ACTION_EFFECT, RULE_KIND_ACTION_CONSTRAINT, RULE_KINDS2, RULE_NAME_PROP, RULE_KIND_PROP, RULE_SLOT_SPEC, ruleIdFor, CONTRADICTION_TRUST_FLOOR, HAS_A_PREDICATE, CAPABLE_OF_PREDICATE2, MULTI_VALUED_PREDICATES;
|
|
10385
|
+
var MEMORY_DIR_REL, MEMORY_GRAPH_REL, UTTERANCE_CLASS, FACT_CLASS, MEMORY_SESSION_CLASS, SOURCE_CLASS, RULE_CLASS, SAID_IN_SESSION_PROP, IN_REPLY_TO_PROP, DERIVED_FROM_PROP, STATED_BY_PROP, CANONICALISED_FROM_PROP, SOURCE_RELIABILITY_PROP, OPERATOR_SOURCE_ID, TEACH_SOURCE_ID, ROLES, LABEL_CAP, MEMORY_VOCABULARY, memoryGraphFile, BACKEND_MEMORY, BACKEND_SQLITE, SQLITE_DDL, STD_EDGE_KEYS, cloneJson, MEMORY_MANIFEST_REL, DEFAULT_RETENTION, resolveManifestFile, LEGACY_FACT_ID_RE, SYLLOGISE_STATE_REL, SQLITE_SYLLOGISE_STATE_KEY, MEMORY_INDEX, memoryIndexOf, labelOf, nowIso, sourceLabel, PROV_CLASS_BY_SOURCE_TYPE, isSessionScopedSourceId, RULE_KIND_COMPOSE2, RULE_KIND_FILTER, RULE_KIND_RECURSIVE, RULE_KIND_ACTION_SIGNATURE, RULE_KIND_ACTION_PRECOND, RULE_KIND_ACTION_EFFECT, RULE_KIND_ACTION_CONSTRAINT, RULE_KINDS2, RULE_NAME_PROP, RULE_KIND_PROP, RULE_SLOT_SPEC, RULE_SLOT_OPTIONAL, ruleIdFor, CONTRADICTION_TRUST_FLOOR, HAS_A_PREDICATE, CAPABLE_OF_PREDICATE2, MULTI_VALUED_PREDICATES;
|
|
10380
10386
|
var init_core = __esm({
|
|
10381
10387
|
"src/adapters/memory/core.mjs"() {
|
|
10382
10388
|
init_promises();
|
|
@@ -10506,12 +10512,22 @@ CREATE INDEX IF NOT EXISTS edges_by_prop ON edges(prop);
|
|
|
10506
10512
|
["shape", "mgx:ruleActionPrecondShape"],
|
|
10507
10513
|
["predicate", "mgx:ruleActionPrecondPredicate"],
|
|
10508
10514
|
["role", "mgx:ruleActionPrecondRole"],
|
|
10509
|
-
["scope", "mgx:ruleActionPrecondScope"]
|
|
10515
|
+
["scope", "mgx:ruleActionPrecondScope"],
|
|
10516
|
+
// value/negate: the "fact-value" shape's literal-match and negation
|
|
10517
|
+
// slots. Optional (RULE_SLOT_OPTIONAL below) — no-incoming/comparator
|
|
10518
|
+
// preconds never set them.
|
|
10519
|
+
["value", "mgx:ruleActionPrecondValue"],
|
|
10520
|
+
["negate", "mgx:ruleActionPrecondNegate"]
|
|
10510
10521
|
],
|
|
10511
10522
|
[RULE_KIND_ACTION_EFFECT]: [
|
|
10512
10523
|
["predicate", "mgx:ruleActionEffectPredicate"],
|
|
10513
10524
|
["subjectRole", "mgx:ruleActionEffectSubject"],
|
|
10514
|
-
|
|
10525
|
+
// objectRole/value: exactly one of these two must be set (enforced
|
|
10526
|
+
// below, not by this per-slot spec) — a role-bound effect (Hanoi's
|
|
10527
|
+
// "rest-on") supplies objectRole, a literal datatype effect (Ashcombe's
|
|
10528
|
+
// is-open = "true") supplies value instead.
|
|
10529
|
+
["objectRole", "mgx:ruleActionEffectObject"],
|
|
10530
|
+
["value", "mgx:ruleActionEffectValue"]
|
|
10515
10531
|
],
|
|
10516
10532
|
// "the <left> may not be with the <right> without the <guard>" — each slot
|
|
10517
10533
|
// names a class whose sole member src/domain/domain.mjs resolves at compile time.
|
|
@@ -10521,6 +10537,10 @@ CREATE INDEX IF NOT EXISTS edges_by_prop ON edges(prop);
|
|
|
10521
10537
|
["guard", "mgx:ruleActionConstraintGuard"]
|
|
10522
10538
|
]
|
|
10523
10539
|
};
|
|
10540
|
+
RULE_SLOT_OPTIONAL = {
|
|
10541
|
+
[RULE_KIND_ACTION_PRECOND]: /* @__PURE__ */ new Set(["value", "negate"]),
|
|
10542
|
+
[RULE_KIND_ACTION_EFFECT]: /* @__PURE__ */ new Set(["objectRole", "value"])
|
|
10543
|
+
};
|
|
10524
10544
|
ruleIdFor = (kind, name, slotValues) => `rule:${fnv1aHex([kind, name, ...slotValues].join("\0"))}`;
|
|
10525
10545
|
CONTRADICTION_TRUST_FLOOR = 0.5;
|
|
10526
10546
|
HAS_A_PREDICATE = "mgx:hasA";
|
|
@@ -21709,7 +21729,7 @@ ${codeblock}`, options);
|
|
|
21709
21729
|
});
|
|
21710
21730
|
|
|
21711
21731
|
// src/domain/grammar/ace.mjs
|
|
21712
|
-
var PATTERN_SUB_CLASS_OF, PATTERN_TYPE_ASSERTION, PATTERN_RELATION, PATTERN_SOME_VALUES_FROM, PATTERN_CARDINALITY, PATTERN_DISJOINT_WITH, PATTERN_POSSESSIVE, PATTERN_ADJECTIVE, PATTERN_CAPABILITY, PATTERNS, IMPERATIVE_VERBS, VERB_SYNONYMS, VERB_SYNONYM_MAX_PREFIX,
|
|
21732
|
+
var PATTERN_SUB_CLASS_OF, PATTERN_TYPE_ASSERTION, PATTERN_RELATION, PATTERN_SOME_VALUES_FROM, PATTERN_CARDINALITY, PATTERN_DISJOINT_WITH, PATTERN_POSSESSIVE, PATTERN_ADJECTIVE, PATTERN_CAPABILITY, PATTERNS, IMPERATIVE_VERBS, VERB_SYNONYMS, VERB_SYNONYM_MAX_PREFIX, VERB_SURFACE_WORDS;
|
|
21713
21733
|
var init_ace2 = __esm({
|
|
21714
21734
|
"src/domain/grammar/ace.mjs"() {
|
|
21715
21735
|
init_lexicon();
|
|
@@ -21758,7 +21778,7 @@ ${codeblock}`, options);
|
|
|
21758
21778
|
["shut", "close"]
|
|
21759
21779
|
]);
|
|
21760
21780
|
VERB_SYNONYM_MAX_PREFIX = Math.max(...[...VERB_SYNONYMS.keys()].map((k) => k.split(" ").length));
|
|
21761
|
-
|
|
21781
|
+
VERB_SURFACE_WORDS = [.../* @__PURE__ */ new Set([...IMPERATIVE_VERBS, ...[...VERB_SYNONYMS.keys()].map((k) => k.split(" ")[0])])];
|
|
21762
21782
|
}
|
|
21763
21783
|
});
|
|
21764
21784
|
|
|
@@ -22011,6 +22031,14 @@ ${codeblock}`, options);
|
|
|
22011
22031
|
}
|
|
22012
22032
|
});
|
|
22013
22033
|
|
|
22034
|
+
// src/domain/domain.mjs
|
|
22035
|
+
var SEP2;
|
|
22036
|
+
var init_domain = __esm({
|
|
22037
|
+
"src/domain/domain.mjs"() {
|
|
22038
|
+
SEP2 = String.fromCharCode(0);
|
|
22039
|
+
}
|
|
22040
|
+
});
|
|
22041
|
+
|
|
22014
22042
|
// src/adapters/memory/blocks.mjs
|
|
22015
22043
|
var BLOCKS_DIR_REL;
|
|
22016
22044
|
var init_blocks = __esm({
|
|
@@ -23449,6 +23477,7 @@ ${JSON.stringify(envelope, null, 2)}`;
|
|
|
23449
23477
|
init_ace2();
|
|
23450
23478
|
init_graph_adapter();
|
|
23451
23479
|
init_taught();
|
|
23480
|
+
init_domain();
|
|
23452
23481
|
|
|
23453
23482
|
// src/adapters/corpus/worlds-pack.mjs
|
|
23454
23483
|
init_node_fs();
|