@polycode-projects/the-mechanical-code-talker 2.2.0 → 2.3.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/bin/tmct.mjs +4 -5
- package/corpus/wordnet/generate.mjs +6 -7
- package/package.json +30 -2
- package/src/adapters/corpus/conceptnet.mjs +1 -1
- package/src/adapters/graph-build.mjs +3 -3
- package/src/adapters/memory/blocks.mjs +2 -2
- package/src/adapters/memory/core.mjs +5 -5
- package/src/adapters/providers/bootstrap.mjs +1 -1
- package/src/adapters/providers/fixture.mjs +1 -1
- package/src/adapters/wink-model.mjs +1 -1
- package/src/adapters/wordnet-source.mjs +70 -0
- package/src/domain/answer-variants.json +1 -1
- package/src/domain/ask-vocab.mjs +2 -2
- package/src/domain/ask.mjs +4 -4
- package/src/domain/codegraph.mjs +3 -3
- package/src/domain/corpus-matrix.mjs +87 -0
- package/src/domain/grammar/ace.mjs +11 -11
- package/src/domain/grammar/lexicon.mjs +3 -3
- package/src/domain/inflect.mjs +67 -0
- package/src/domain/interpret/fuzzy.mjs +1 -1
- package/src/domain/interpret/merge.mjs +1 -1
- package/src/domain/interpret/normalize.mjs +1 -1
- package/src/domain/licences.mjs +68 -0
- package/src/domain/memory/capability.mjs +1 -1
- package/src/domain/memory/trust.mjs +2 -2
- package/src/domain/persona/codegen.mjs +123 -0
- package/src/domain/persona/examples.mjs +26 -0
- package/src/domain/persona/tiers.mjs +270 -0
- package/src/domain/publish-gate.mjs +41 -0
- package/src/domain/router/call-validator.mjs +1 -1
- package/src/domain/router/drive.mjs +3 -4
- package/src/domain/router/registry.mjs +12 -13
- package/src/domain/router/resolver.mjs +18 -5
- package/src/domain/router/results.mjs +3 -3
- package/src/domain/router/taught.mjs +4 -3
- package/src/domain/schemaorg/turtle.mjs +25 -0
- package/src/domain/semcor/parse.mjs +87 -0
- package/src/domain/syllogise.mjs +6 -6
- package/src/domain/version-stamp.mjs +36 -0
- package/src/domain/wordnet/yaml.mjs +133 -0
- package/src/services/chat-session.mjs +2 -2
- package/src/services/chat.mjs +2 -2
- package/src/services/cli-args.mjs +4 -4
- package/src/services/finish.mjs +1 -1
- package/src/services/ledger-viz.mjs +2 -3
- package/src/services/sessions.mjs +4 -4
- package/src/services/viz-theme.mjs +3 -4
- package/src/surfaces/web/memory-ask-browser.bundle.js +1 -18
- package/src/domain/router/guardrail.mjs +0 -116
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// src/domain/router/registry.mjs — the capability registry.
|
|
2
2
|
//
|
|
3
3
|
// Each tmct tool is modelled as a STRIPS/PDDL operator declared as DATA: a `Capability` with
|
|
4
|
-
// typed `Parameter`s, `Precondition`s, and `Effect`s (add-list/delete-list).
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// typed `Parameter`s, `Precondition`s, and `Effect`s (add-list/delete-list). resolver.mjs
|
|
5
|
+
// backward-chains from a goal to a capability whose add-list achieves it, and proves the
|
|
6
|
+
// preconditions bind before the call fires.
|
|
7
7
|
//
|
|
8
8
|
// Plain data + pure accessors, no I/O. Tool names + parameter arg keys are the
|
|
9
9
|
// exact ones src/tools/server.mjs `dispatchTool` reads, so a bound call this registry validates is
|
|
10
10
|
// directly dispatchable.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
const PREFIXES = Object.freeze({
|
|
13
13
|
cap: "urn:tmct:cap#", // the capability/operator vocabulary (this module)
|
|
14
14
|
mgx: "urn:tmct:mgx#", // tmct's code-graph predicates (imports/calls/tests/…)
|
|
15
15
|
seon: "http://se-on.org/ontologies/seon.owl#", // software-evolution ontology classes
|
|
@@ -25,7 +25,7 @@ export const VOCAB = Object.freeze({
|
|
|
25
25
|
|
|
26
26
|
// Parameter entity-KINDS — the seon/mgx classes a slot ranges over. `Query` and
|
|
27
27
|
// `Kind`/`Package` are free-text / enum slots (no graph resolution); the rest
|
|
28
|
-
// name a graph entity the
|
|
28
|
+
// name a graph entity the resolver must prove RESOLVES before the call fires.
|
|
29
29
|
export const KINDS = Object.freeze({
|
|
30
30
|
Symbol: "seon:CodeEntity", // any code symbol: function/method/class/module/attribute
|
|
31
31
|
Module: "seon:Module",
|
|
@@ -196,11 +196,10 @@ function deepFreeze(value) {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/** Register a capability at runtime (e.g. a taught action family bridged in by
|
|
199
|
-
* src/domain/router/taught.mjs). `readOnly` must be an explicit boolean
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* disposer. */
|
|
199
|
+
* src/domain/router/taught.mjs). `readOnly` must be an explicit boolean: it is
|
|
200
|
+
* what resolver.mjs's dispatch gate reads, and a `readOnly: false` record is
|
|
201
|
+
* never dispatched. `dispatchable` is derived from it for callers that want the
|
|
202
|
+
* record to state the conclusion. Returns an `unregister()` disposer. */
|
|
204
203
|
export function registerCapability(cap) {
|
|
205
204
|
const name = cap && typeof cap.name === "string" ? cap.name.trim() : "";
|
|
206
205
|
if (!name) throw new Error("registerCapability: a non-empty name is required");
|
|
@@ -260,14 +259,14 @@ export function isCapability(n) { return Boolean(byName[n]); }
|
|
|
260
259
|
/** The parameter slots of capability `n` (empty array if unknown/no-arg). */
|
|
261
260
|
export function parametersOf(n) { return byName[n]?.parameters ?? []; }
|
|
262
261
|
|
|
263
|
-
/** The preconditions of capability `n` (the safety gate the
|
|
262
|
+
/** The preconditions of capability `n` (the safety gate the resolver checks). */
|
|
264
263
|
export function preconditionsOf(n) { return byName[n]?.preconditions ?? []; }
|
|
265
264
|
|
|
266
265
|
/** The effects of capability `n` — `{ add, del }` (the proof-chain contribution). */
|
|
267
266
|
export function effectsOf(n) { return byName[n]?.effects ?? { add: [], del: [] }; }
|
|
268
267
|
|
|
269
|
-
/** The set of arg keys capability `n` accepts (for
|
|
270
|
-
* check). Returns a Set of strings. */
|
|
268
|
+
/** The set of arg keys capability `n` accepts (for call-validator.mjs's
|
|
269
|
+
* unknown-arg check). Returns a Set of strings. */
|
|
271
270
|
export function argKeysOf(n) {
|
|
272
271
|
return new Set(parametersOf(n).map((p) => p.arg));
|
|
273
272
|
}
|
|
@@ -200,7 +200,7 @@ export function commandCapability(request, declaredNames, selectTool) {
|
|
|
200
200
|
|
|
201
201
|
/** Build the glass-box proof chain for a grounded single call: its preconditions then the
|
|
202
202
|
* epistemic add-effect. Dispatch has succeeded, so `resolves` steps are ok. */
|
|
203
|
-
|
|
203
|
+
function proofFor(name, input) {
|
|
204
204
|
const steps = [];
|
|
205
205
|
for (const pre of preconditionsOf(name)) {
|
|
206
206
|
if (pre.pred === PRECOND.graphLoaded) steps.push({ step: "precondition", pred: pre.pred, ok: true });
|
|
@@ -213,11 +213,21 @@ export function proofFor(name, input) {
|
|
|
213
213
|
|
|
214
214
|
const REFUSE = (why, extra) => ({ selected: null, refused: true, reason: why, ...(extra || {}) });
|
|
215
215
|
|
|
216
|
-
/**
|
|
217
|
-
*
|
|
218
|
-
*
|
|
216
|
+
/** The gate both dispatch sites below go through. Dispatching is an OBSERVATION, so it
|
|
217
|
+
* may only ever run a capability whose own record says it performs no writes. A
|
|
218
|
+
* world-mutating record (the ones src/domain/router/taught.mjs registers carry
|
|
219
|
+
* `readOnly: false`) is planned over and simulated, never fired here. */
|
|
220
|
+
function dispatchPerformsNoWrites(capName) {
|
|
221
|
+
return capabilityByName(capName)?.readOnly === true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Breadth-first ambiguity: dispatches the SAME tool once per tied candidate, which only
|
|
225
|
+
* stays safe while the tool writes nothing. Returns `[{candidate, result}, ...]`, or
|
|
226
|
+
* undefined when there is no dispatcher to run it with — or when the capability is not
|
|
227
|
+
* read-only. */
|
|
219
228
|
async function dispatchEachCandidate(pool, capName, arg, ctx, execute) {
|
|
220
229
|
if (!execute || !ctx.dispatch) return undefined;
|
|
230
|
+
if (!dispatchPerformsNoWrites(capName)) return undefined;
|
|
221
231
|
const results = [];
|
|
222
232
|
for (const c of pool) {
|
|
223
233
|
const res = await ctx.dispatch(capName, { [arg]: c.label });
|
|
@@ -283,6 +293,9 @@ export async function resolveOne(request, declaredNames, ctx, { execute = true }
|
|
|
283
293
|
if (problems.length) return REFUSE(`bound call did not validate: ${problems.map((p) => p.reason).join(",")}`);
|
|
284
294
|
|
|
285
295
|
if (execute && ctx.dispatch) {
|
|
296
|
+
if (!dispatchPerformsNoWrites(pick.name)) {
|
|
297
|
+
return REFUSE(`${pick.name} is not read-only; the resolver observes, it never fires a world-mutating capability`);
|
|
298
|
+
}
|
|
286
299
|
const res = await ctx.dispatch(pick.name, input);
|
|
287
300
|
if (!res.ok) return REFUSE(`unresolvable at dispatch: ${res.error}`);
|
|
288
301
|
return { selected: call, proof: proofFor(pick.name, input), why, resolved: res.resolved ?? resolved, observed: String(res.text ?? "").slice(0, 240) };
|
|
@@ -293,7 +306,7 @@ export async function resolveOne(request, declaredNames, ctx, { execute = true }
|
|
|
293
306
|
// ---- reachability (used by the bidirectional conformance test + docs) ---------
|
|
294
307
|
|
|
295
308
|
/** The epistemic topics some NL intent or imperative frame can reach. */
|
|
296
|
-
|
|
309
|
+
function nlReachableTopics() {
|
|
297
310
|
const topics = new Set();
|
|
298
311
|
for (const v of Object.values(NL_INTENTS)) topics.add(v.topic);
|
|
299
312
|
for (const f of FRAMES) topics.add(f.topic);
|
|
@@ -147,7 +147,7 @@ export function callersLabels(graph, ind) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/** Callees of a symbol (mirrors renderCallees). */
|
|
150
|
-
|
|
150
|
+
function calleesLabels(graph, ind) {
|
|
151
151
|
if (CALL_SYMBOL_CLASSES.has(ind.class)) {
|
|
152
152
|
return uniqSort(edgesOfKind(graph, "callsSymbol").filter((e) => e.subject === ind.id).map((e) => e.objectLabel || e.object));
|
|
153
153
|
}
|
|
@@ -157,7 +157,7 @@ export function calleesLabels(graph, ind) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/** Subclasses (transitive) of a class (mirrors renderSubclasses closure). */
|
|
160
|
-
|
|
160
|
+
function subclassesLabels(graph, ind) {
|
|
161
161
|
const inherits = edgesOfKind(graph, "inherits");
|
|
162
162
|
const childrenOf = new Map();
|
|
163
163
|
for (const e of inherits) {
|
|
@@ -198,7 +198,7 @@ export function cochangesLabels(graph, ind) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/** A module's public exports (mirrors renderExports — the `reexports` edge). */
|
|
201
|
-
|
|
201
|
+
function exportsLabels(graph, ind) {
|
|
202
202
|
const modId = moduleIdOf(graph, ind);
|
|
203
203
|
if (!modId) return [];
|
|
204
204
|
return uniqSort(edgesOfKind(graph, "reexports").filter((e) => e.subject === modId).map((e) => e.objectLabel || e.object));
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// A taught game action ("you can move a disk onto a peg" + its preconditions
|
|
4
4
|
// and effect) becomes a registered capability record so the router's operator
|
|
5
5
|
// model covers taught actions and built-in query tools alike. Registered
|
|
6
|
-
// records carry readOnly: false, so the
|
|
7
|
-
//
|
|
8
|
-
// `knows` add-effects and these
|
|
6
|
+
// records carry readOnly: false, so the resolver's dispatch gate refuses to
|
|
7
|
+
// fire them — and it never selects one on its own anyway, because it
|
|
8
|
+
// backward-chains over `knows` add-effects and these carry world-triple
|
|
9
|
+
// effects instead. They are planned over and simulated.
|
|
9
10
|
|
|
10
11
|
import { capabilityByName, registerCapability } from "./registry.mjs";
|
|
11
12
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// turtle.mjs — a very small Turtle reader for schema.ttl's OWN regular shape:
|
|
2
|
+
// each class is one `:Name a rdfs:Class ;` block terminated by a line ending
|
|
3
|
+
// in ` .`, with `rdfs:label`/`rdfs:comment`/`rdfs:subClassOf` as `;`-separated
|
|
4
|
+
// predicate lines. Not a general Turtle parser — schema.ttl's own generator
|
|
5
|
+
// emits a single, very regular style (confirmed by direct inspection).
|
|
6
|
+
//
|
|
7
|
+
// Pure: text in, Map out, no imports.
|
|
8
|
+
|
|
9
|
+
/** Every rdfs:Class in `text`, as name -> { name, label, comment, subClassOf }.
|
|
10
|
+
* A class with no rdfs:label falls back to its own name; no rdfs:comment
|
|
11
|
+
* yields "". Blocks that are not classes (properties, say) are skipped. */
|
|
12
|
+
export function parseSchemaClasses(text) {
|
|
13
|
+
const classes = new Map();
|
|
14
|
+
const blocks = text.split(/\n(?=:[A-Za-z])/); // each class/property starts a new top-level block
|
|
15
|
+
for (const block of blocks) {
|
|
16
|
+
const head = /^:([A-Za-z0-9_]+)\s+a\s+rdfs:Class\s*;/.exec(block);
|
|
17
|
+
if (!head) continue;
|
|
18
|
+
const name = head[1];
|
|
19
|
+
const label = /rdfs:label\s+"([^"]*)"/.exec(block)?.[1] || name;
|
|
20
|
+
const comment = /rdfs:comment\s+"([^"]*)"/.exec(block)?.[1] || "";
|
|
21
|
+
const subClassOf = [...block.matchAll(/rdfs:subClassOf\s+:([A-Za-z0-9_]+)/g)].map((m) => m[1]);
|
|
22
|
+
classes.set(name, { name, label, comment, subClassOf });
|
|
23
|
+
}
|
|
24
|
+
return classes;
|
|
25
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// parse.mjs — a targeted reader for SemCor's own regular YAML shape:
|
|
2
|
+
// flow-style lemmas/pos arrays and a folded single-quoted `text` scalar, one
|
|
3
|
+
// record per sentence. Not a general YAML parser (this repo has no YAML
|
|
4
|
+
// dependency), and not the same shape as the WordNet dump's reader in
|
|
5
|
+
// src/domain/wordnet/yaml.mjs — SemCor's flow style is JSON-compatible once
|
|
6
|
+
// isolated, which the WordNet subset never is.
|
|
7
|
+
//
|
|
8
|
+
// Pure: text in, arrays/strings out, no imports, so it runs with no SemCor
|
|
9
|
+
// clone present.
|
|
10
|
+
|
|
11
|
+
/** Split a SemCor YAML file into per-sentence record blocks (top-level
|
|
12
|
+
* "<key>:" lines, skipping the leading "_meta:" schema block). */
|
|
13
|
+
export function splitRecords(text) {
|
|
14
|
+
const lines = text.split("\n");
|
|
15
|
+
const blocks = [];
|
|
16
|
+
let i = 0;
|
|
17
|
+
while (i < lines.length && lines[i] !== "_meta:") i++;
|
|
18
|
+
i += 1;
|
|
19
|
+
while (i < lines.length && (lines[i].startsWith(" ") || lines[i].trim() === "")) i++; // skip rest of _meta
|
|
20
|
+
while (i < lines.length) {
|
|
21
|
+
if (/^[A-Za-z0-9_]+:$/.test(lines[i])) {
|
|
22
|
+
let j = i + 1;
|
|
23
|
+
const block = [];
|
|
24
|
+
while (j < lines.length && !/^[A-Za-z0-9_]+:$/.test(lines[j])) {
|
|
25
|
+
block.push(lines[j]);
|
|
26
|
+
j += 1;
|
|
27
|
+
}
|
|
28
|
+
blocks.push(block.join("\n"));
|
|
29
|
+
i = j;
|
|
30
|
+
} else {
|
|
31
|
+
i += 1;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return blocks;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Extract a flow-style JSON-compatible array value for `key` from one
|
|
38
|
+
* record block (lemmas/pos are double-quoted string arrays — valid JSON
|
|
39
|
+
* once isolated), balancing brackets across a line wrap if one occurs. */
|
|
40
|
+
export function extractArray(block, key) {
|
|
41
|
+
const re = new RegExp(`^\\s*${key}:\\s*(\\[.*)$`, "m");
|
|
42
|
+
const m = re.exec(block);
|
|
43
|
+
if (!m) return null;
|
|
44
|
+
let buf = m[1];
|
|
45
|
+
let depth = (buf.match(/\[/g) || []).length - (buf.match(/\]/g) || []).length;
|
|
46
|
+
const afterIdx = block.indexOf(m[0]) + m[0].length;
|
|
47
|
+
const rest = block.slice(afterIdx).split("\n");
|
|
48
|
+
let ri = 0;
|
|
49
|
+
while (depth > 0 && ri < rest.length) {
|
|
50
|
+
buf += `\n${rest[ri]}`;
|
|
51
|
+
depth += (rest[ri].match(/\[/g) || []).length - (rest[ri].match(/\]/g) || []).length;
|
|
52
|
+
ri += 1;
|
|
53
|
+
}
|
|
54
|
+
try { return JSON.parse(buf); } catch { return null; }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Extract the `text:` folded single-quoted scalar (YAML's own `''` ->
|
|
58
|
+
* literal `'` escape; line breaks folded to spaces). */
|
|
59
|
+
export function extractText(block) {
|
|
60
|
+
const m = /^\s*text:\s*'/m.exec(block);
|
|
61
|
+
if (!m) return null;
|
|
62
|
+
const start = block.indexOf("'", m.index);
|
|
63
|
+
let i = start + 1;
|
|
64
|
+
let raw = "";
|
|
65
|
+
while (i < block.length) {
|
|
66
|
+
if (block[i] === "'") {
|
|
67
|
+
if (block[i + 1] === "'") { raw += "'"; i += 2; continue; }
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
raw += block[i];
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
return raw.replace(/\s+/g, " ").trim();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const NOUN_POS = new Set(["NN", "NNS"]);
|
|
77
|
+
|
|
78
|
+
/** Simple-grammar filter: short, no semicolons/colons, no embedded quotes
|
|
79
|
+
* (which signal reported speech), no more than one comma — a rough proxy for
|
|
80
|
+
* "no complex embedded clauses". */
|
|
81
|
+
export function isSimpleSentence(text, wordCount) {
|
|
82
|
+
if (wordCount > 18) return false;
|
|
83
|
+
if (/[;:]/.test(text)) return false;
|
|
84
|
+
if ((text.match(/,/g) || []).length > 1) return false;
|
|
85
|
+
if (/"/.test(text)) return false;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
package/src/domain/syllogise.mjs
CHANGED
|
@@ -42,13 +42,13 @@ function requireStore(store, needed, caller) {
|
|
|
42
42
|
/** scm-sco: the subClassOf-transitivity rule, and the provenance tag its
|
|
43
43
|
* conclusions carry. */
|
|
44
44
|
export const SUBCLASS_PREDICATE = "rdfs:subClassOf";
|
|
45
|
-
|
|
45
|
+
const SYLLOGISE_RULE = "subClassOf";
|
|
46
46
|
export const ENTAILED_PROVENANCE = `entailed:${SYLLOGISE_RULE}`;
|
|
47
47
|
|
|
48
48
|
/** cax-sco: the type-propagation rule, and the provenance tag its conclusions
|
|
49
49
|
* carry. */
|
|
50
50
|
export const TYPE_PREDICATE = "rdf:type";
|
|
51
|
-
|
|
51
|
+
const CAX_SCO_RULE = "type";
|
|
52
52
|
export const ENTAILED_TYPE_PROVENANCE = `entailed:${CAX_SCO_RULE}`;
|
|
53
53
|
|
|
54
54
|
/** cax-dw: x rdf:type C1, C1 owl:disjointWith C2 |= x is NOT of type C2 — a
|
|
@@ -396,7 +396,7 @@ export function deriveSomeValuesFromApplication(propertyEdges, typeEdges, subCla
|
|
|
396
396
|
// deriveSomeValuesFromApplication reconstructs someValuesFrom restrictions.
|
|
397
397
|
const HAS_PROPERTY_KEY = "has"; // synthetic marker parseCardinality always mints, never a real taught verb
|
|
398
398
|
const CARDINALITY_KIND_OF = { "owl:cardinality": "exactly", "owl:mincardinality": "min", "owl:maxcardinality": "max" };
|
|
399
|
-
|
|
399
|
+
const ON_CLASS_PREDICATE = "owl:onClass";
|
|
400
400
|
|
|
401
401
|
/** Reconstructs pattern-5 cardinality restriction records from raw stored
|
|
402
402
|
* rows touching a restriction node. A restriction is only admitted when its
|
|
@@ -525,7 +525,7 @@ function findOwnCardinalityRestriction(subClassEdges, cardinalityRestrictionEdge
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
// ---- cardinality monotonicity (outside OWL 2 RL's own decidable profile) ----
|
|
528
|
-
|
|
528
|
+
const SCM_CARD_RULE = "cardinalityMonotonicity";
|
|
529
529
|
/** Same sub-1 discount as CAX_DW_RULE_CONFIDENCE. No `syllogise()` call site
|
|
530
530
|
* (this rule is query-rooted, never an enumerable Fact) — defined anyway so
|
|
531
531
|
* chat.mjs's live proof chase can attach an auditable confidence figure
|
|
@@ -557,7 +557,7 @@ export function proveCardinalityAtLeast(subClassEdges, cardinalityRestrictionEdg
|
|
|
557
557
|
// generalization — see this file's header comment; `cax-` prefix per this
|
|
558
558
|
// ladder's "produces a provable no" naming convention, same epistemic status
|
|
559
559
|
// as cax-dw) ----
|
|
560
|
-
|
|
560
|
+
const CAX_MAXC0_RULE = "maxCardinalityZero";
|
|
561
561
|
/** Same sub-1 discount and query-rooted caveat as CARDINALITY_RULE_CONFIDENCE. */
|
|
562
562
|
export const CAX_MAXC0_RULE_CONFIDENCE = 0.95;
|
|
563
563
|
|
|
@@ -989,7 +989,7 @@ function buildSurvivorDerivabilityCheck(rows) {
|
|
|
989
989
|
* A survivor keeps its stale, still-single justification as-is; a later
|
|
990
990
|
* retraction of its OTHER supporting path therefore won't re-examine it.
|
|
991
991
|
* Re-grounding survivors — or tracking every alternate justification set —
|
|
992
|
-
* is the ATMS horizon
|
|
992
|
+
* is the ATMS horizon, not this bounded slice.
|
|
993
993
|
*
|
|
994
994
|
* Returns { retracted, count, budget, depth, truncated, found } — `found` is
|
|
995
995
|
* false when `subject ⊑ object` was never a stored fact.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// version-stamp.mjs — the home page's #pkg-version element, written and read
|
|
2
|
+
// from one place. Pure: strings in, strings out, no imports, so the deploy
|
|
3
|
+
// smoke check can reach it without npm ci.
|
|
4
|
+
//
|
|
5
|
+
// This existed three times and the copies had already drifted: the writer
|
|
6
|
+
// matched [^<]*, the smoke check demanded \d+\.\d+\.\d+, and the estate test
|
|
7
|
+
// accepted [^<\s]*. A writer that accepts what its reader rejects is a green
|
|
8
|
+
// build and a failed deploy, so the pattern lives here and all three call it.
|
|
9
|
+
|
|
10
|
+
/** The element that carries the version, and the value inside it. */
|
|
11
|
+
const STAMP = /(id="pkg-version"[^>]*>)\s*v?([^<]*?)\s*(<)/;
|
|
12
|
+
|
|
13
|
+
/** A semver core, which is what the deploy smoke check is entitled to expect. */
|
|
14
|
+
const SEMVER = /^\d+\.\d+\.\d+$/;
|
|
15
|
+
|
|
16
|
+
/** True iff `html` carries an element the stamp can be written into. */
|
|
17
|
+
export function hasVersionStamp(html) {
|
|
18
|
+
return STAMP.test(html);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The version `html` displays, or null when the element is absent or holds
|
|
22
|
+
* something that is not a semver core (an unstamped placeholder, say). */
|
|
23
|
+
export function parseVersionStamp(html) {
|
|
24
|
+
const found = STAMP.exec(html);
|
|
25
|
+
if (!found) return null;
|
|
26
|
+
const value = found[2].trim();
|
|
27
|
+
return SEMVER.test(value) ? value : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** `html` with the stamp set to `version`. Throws when there is nothing to
|
|
31
|
+
* stamp — a page that lost its element would otherwise publish blank. */
|
|
32
|
+
export function stampVersion(html, version) {
|
|
33
|
+
if (!SEMVER.test(version)) throw new Error(`not a stampable version: "${version}"`);
|
|
34
|
+
if (!hasVersionStamp(html)) throw new Error("no #pkg-version element to stamp");
|
|
35
|
+
return html.replace(STAMP, `$1${version}$3`);
|
|
36
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// yaml.mjs — a reader for the small YAML subset the Open English WordNet dump
|
|
2
|
+
// uses: 2-space-indented block mappings/sequences, quoted or bare scalars, and
|
|
3
|
+
// long scalar list-items that simply WRAP onto a further-indented continuation
|
|
4
|
+
// line. No block scalars, no anchors, no flow style — confirmed by direct
|
|
5
|
+
// inspection of the dump. This reads exactly that subset; it is not a general
|
|
6
|
+
// YAML parser.
|
|
7
|
+
//
|
|
8
|
+
// Pure: text in, object out, no imports, so it is testable without the WordNet
|
|
9
|
+
// clone the scripts that call it need.
|
|
10
|
+
|
|
11
|
+
/** Non-greedy key group so a MULTI-WORD entry key ("M-1 rifle", "ice cream")
|
|
12
|
+
* still matches — the first ": "/end-of-line colon wins, exactly as real
|
|
13
|
+
* YAML's block-mapping key/value split works. A plain wrapped scalar
|
|
14
|
+
* continuation line (a definition/example fragment) only coincidentally
|
|
15
|
+
* matches this if it ALSO happens to contain a bare "word: " sequence — rare
|
|
16
|
+
* in this corpus's prose, and this feeds a maintainer worksheet whose output
|
|
17
|
+
* is hand-reviewed, not a correctness-critical parser. */
|
|
18
|
+
const KEY_RE = /^(.+?):(\s+(.*)|)$/;
|
|
19
|
+
|
|
20
|
+
const isDash = (t) => t === "-" || t.startsWith("- ");
|
|
21
|
+
|
|
22
|
+
function parseScalar(s) {
|
|
23
|
+
const t = s.trim();
|
|
24
|
+
if ((t.startsWith("'") && t.endsWith("'") && t.length >= 2) || (t.startsWith('"') && t.endsWith('"') && t.length >= 2)) {
|
|
25
|
+
return t.slice(1, -1);
|
|
26
|
+
}
|
|
27
|
+
return t;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function parseYaml(text) {
|
|
31
|
+
const rawLines = text.split("\n");
|
|
32
|
+
const lines = [];
|
|
33
|
+
for (const line of rawLines) {
|
|
34
|
+
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
35
|
+
const indent = line.length - line.trimStart().length;
|
|
36
|
+
lines.push({ indent, text: line.trimStart() });
|
|
37
|
+
}
|
|
38
|
+
let pos = 0;
|
|
39
|
+
|
|
40
|
+
// A scalar that may continue on subsequent MORE-indented lines with no
|
|
41
|
+
// "key:"/"- " marker of their own (WordNet's definition-wrapping style). A
|
|
42
|
+
// QUOTED scalar ('...' or "...") is handled separately: WordNet definitions
|
|
43
|
+
// routinely contain a literal ": " inside the quoted text itself (e.g. "…
|
|
44
|
+
// Matthew, Mark, Luke, and John" split across a line boundary right after a
|
|
45
|
+
// colon) — the bare-scalar heuristic below would misread that continuation
|
|
46
|
+
// line as a new "key:" line and truncate the string. Once inside an open
|
|
47
|
+
// quote, EVERY line is a continuation until one ends with the matching
|
|
48
|
+
// closing quote, full stop — the key/dash heuristic never applies inside it.
|
|
49
|
+
function parseScalarOrContinue(first, minContinIndent) {
|
|
50
|
+
const trimmed = first.trim();
|
|
51
|
+
const quote = trimmed[0] === "'" || trimmed[0] === '"' ? trimmed[0] : null;
|
|
52
|
+
if (quote) {
|
|
53
|
+
const closes = (s) => s.length >= 2 && s.endsWith(quote);
|
|
54
|
+
let buf = trimmed;
|
|
55
|
+
while (!closes(buf) && pos < lines.length && lines[pos].indent >= minContinIndent) {
|
|
56
|
+
buf += " " + lines[pos].text.trim();
|
|
57
|
+
pos += 1;
|
|
58
|
+
}
|
|
59
|
+
return closes(buf) ? buf.slice(1, -1) : buf;
|
|
60
|
+
}
|
|
61
|
+
let s = parseScalar(first);
|
|
62
|
+
while (pos < lines.length && lines[pos].indent >= minContinIndent
|
|
63
|
+
&& !isDash(lines[pos].text) && !KEY_RE.test(lines[pos].text)) {
|
|
64
|
+
s += " " + lines[pos].text.trim();
|
|
65
|
+
pos += 1;
|
|
66
|
+
}
|
|
67
|
+
return s;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The value that follows a "key:" (bare, no inline scalar) — peeks at the
|
|
71
|
+
* next line to decide whether it's a nested sequence (which YAML allows to
|
|
72
|
+
* sit at the SAME indent as the key itself, not just deeper) or a nested
|
|
73
|
+
* mapping (which must be deeper) or simply absent (null). `parentIndent`
|
|
74
|
+
* is the indent of the "key:" line whose value this resolves. */
|
|
75
|
+
function parseValue(parentIndent) {
|
|
76
|
+
if (pos >= lines.length || lines[pos].indent < parentIndent) return null;
|
|
77
|
+
const line = lines[pos];
|
|
78
|
+
if (isDash(line.text)) return parseSeq(line.indent);
|
|
79
|
+
if (line.indent > parentIndent && KEY_RE.test(line.text)) return parseMap(line.indent);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseSeq(indent) {
|
|
84
|
+
const arr = [];
|
|
85
|
+
while (pos < lines.length && lines[pos].indent === indent && isDash(lines[pos].text)) {
|
|
86
|
+
const dashIndent = indent;
|
|
87
|
+
const rest = lines[pos].text === "-" ? "" : lines[pos].text.slice(2);
|
|
88
|
+
pos += 1;
|
|
89
|
+
if (rest === "") {
|
|
90
|
+
arr.push(parseValue(dashIndent));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// A quoted scalar is classified FIRST, unconditionally — WordNet
|
|
94
|
+
// definitions routinely contain a literal ": " (or a colon at the very
|
|
95
|
+
// end of a wrapped line, e.g. "…including:\n whales, …") inside quoted
|
|
96
|
+
// prose, which KEY_RE would otherwise misread as an inline "- key:"
|
|
97
|
+
// mapping. Only an UNQUOTED rest is even considered for that shape.
|
|
98
|
+
const quoted = rest[0] === "'" || rest[0] === '"';
|
|
99
|
+
const m = quoted ? null : KEY_RE.exec(rest);
|
|
100
|
+
if (m) {
|
|
101
|
+
// "- key: value" or "- key:" — an inline mapping for this list item;
|
|
102
|
+
// sibling keys of the SAME item are indented +2 from the dash.
|
|
103
|
+
const obj = {};
|
|
104
|
+
obj[m[1]] = m[3] !== undefined && m[3] !== "" ? parseScalarOrContinue(m[3], dashIndent + 2) : parseValue(dashIndent + 2);
|
|
105
|
+
while (pos < lines.length && lines[pos].indent === dashIndent + 2 && KEY_RE.test(lines[pos].text)) {
|
|
106
|
+
const mm = KEY_RE.exec(lines[pos].text);
|
|
107
|
+
pos += 1;
|
|
108
|
+
obj[mm[1]] = mm[3] !== undefined && mm[3] !== "" ? parseScalarOrContinue(mm[3], dashIndent + 4) : parseValue(dashIndent + 2);
|
|
109
|
+
}
|
|
110
|
+
arr.push(obj);
|
|
111
|
+
} else {
|
|
112
|
+
// a plain (or quoted) scalar list item — may wrap onto continuation lines
|
|
113
|
+
arr.push(parseScalarOrContinue(rest, dashIndent + 2));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return arr;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseMap(indent) {
|
|
120
|
+
const obj = {};
|
|
121
|
+
while (pos < lines.length && lines[pos].indent === indent && KEY_RE.test(lines[pos].text)) {
|
|
122
|
+
const m = KEY_RE.exec(lines[pos].text);
|
|
123
|
+
const key = parseScalar(m[1]);
|
|
124
|
+
pos += 1;
|
|
125
|
+
obj[key] = m[3] !== undefined && m[3] !== "" ? parseScalarOrContinue(m[3], indent + 2) : parseValue(indent);
|
|
126
|
+
// (parseValue(indent) — not indent+2 — so a same-indent sequence value
|
|
127
|
+
// is recognized; parseValue itself accepts child indent >= indent.)
|
|
128
|
+
}
|
|
129
|
+
return obj;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return parseMap(0);
|
|
133
|
+
}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
//
|
|
10
10
|
// createSession(…) is the SESSION SINK every shell shares (runChat's
|
|
11
11
|
// readline loop below, src/surfaces/tui/app.mjs's Ink shell). chat.mjs re-exports
|
|
12
|
-
// createSession/runChat
|
|
13
|
-
//
|
|
12
|
+
// createSession/runChat and the session constants so existing import sites
|
|
13
|
+
// keep working.
|
|
14
14
|
|
|
15
15
|
import { join, resolve } from "node:path";
|
|
16
16
|
import { createWriteStream } from "node:fs";
|
package/src/services/chat.mjs
CHANGED
|
@@ -62,8 +62,8 @@ export { uuidv7 };
|
|
|
62
62
|
// in the session layer so runTurn and the fact engine below stay free of
|
|
63
63
|
// node:fs/child_process/os/readline. Re-exported here (services → services) so
|
|
64
64
|
// every existing import site — bin, tui, server-http, index, tests — keeps
|
|
65
|
-
// importing createSession/runChat/
|
|
66
|
-
export { createSession, runChat,
|
|
65
|
+
// importing createSession/runChat/SESSION_LOG_DIR/PROMPT from chat.mjs.
|
|
66
|
+
export { createSession, runChat, SESSION_LOG_DIR, PROMPT } from "./chat-session.mjs";
|
|
67
67
|
|
|
68
68
|
/** dispatchTool("tmct_ask", …) returns the prose answer plus a delimited
|
|
69
69
|
* machine-readable envelope; the TUI shows the prose only. Reused verbatim
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
// 3. tmct.toml's `graph_file` / `graph_files`
|
|
16
16
|
// 4. <repo>/.tmct/graph.json, where repo is --repo, else git root, else cwd
|
|
17
17
|
//
|
|
18
|
-
// Deliberately does NOT import chat.mjs (would be circular) — the git-root lookup
|
|
19
|
-
// is a small, self-contained copy of chat.mjs's own gitToplevel().
|
|
18
|
+
// Deliberately does NOT import chat-session.mjs (would be circular) — the git-root lookup
|
|
19
|
+
// below is a small, self-contained copy of chat-session.mjs's own gitToplevel().
|
|
20
20
|
|
|
21
21
|
import { spawnSync } from "node:child_process";
|
|
22
22
|
import { dirname, resolve } from "node:path";
|
|
@@ -26,8 +26,8 @@ import { loadTomlConfig, normalizeConfig, mergeEffective, CONFIG_FILE } from "..
|
|
|
26
26
|
import { DEFAULT_GRAPH_REL } from "../adapters/config.mjs";
|
|
27
27
|
|
|
28
28
|
/** The git top-level for `cwd`, or null if not in a repo (or git is
|
|
29
|
-
* unavailable). A deliberate re-declaration of chat.mjs's gitToplevel
|
|
30
|
-
* import) — see the file docblock for why. */
|
|
29
|
+
* unavailable). A deliberate re-declaration of chat-session.mjs's gitToplevel
|
|
30
|
+
* (not an import) — see the file docblock for why. */
|
|
31
31
|
function defaultGitRoot(cwd) {
|
|
32
32
|
try {
|
|
33
33
|
const r = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
|
package/src/services/finish.mjs
CHANGED
|
@@ -18,7 +18,7 @@ export { flatten };
|
|
|
18
18
|
|
|
19
19
|
const GRAMMAR_DIR = dirname(fileURLToPath(import.meta.url));
|
|
20
20
|
/** The data-driven grammar-rule table. */
|
|
21
|
-
|
|
21
|
+
const GRAMMAR_RULES_FILE = join(GRAMMAR_DIR, "..", "..", "data", "templates", "grammar-rules.toml");
|
|
22
22
|
|
|
23
23
|
/** The segment type vocabulary. `prose` is the only unprotected type. */
|
|
24
24
|
export const SEGMENT_TYPES = Object.freeze([
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// ledger-viz.mjs — `tmct viz`: the memory graph as a readable ledger of
|
|
2
|
-
// fact-sentences around one focus term, with the in-page chat dock
|
|
3
|
-
// (PLAN_VIZ_LEDGER.md).
|
|
2
|
+
// fact-sentences around one focus term, with the in-page chat dock.
|
|
4
3
|
//
|
|
5
4
|
// Three pure/impure-separated pieces:
|
|
6
5
|
// - computeLedgerData(repoDir, opts) — I/O (loadMemory) + derivation
|
|
@@ -16,7 +15,7 @@ import { readFile } from "node:fs/promises";
|
|
|
16
15
|
import { fileURLToPath } from "node:url";
|
|
17
16
|
import { dirname, join } from "node:path";
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
const LEDGER_ROW_LIMIT_DEFAULT = 20000;
|
|
20
19
|
|
|
21
20
|
/** Read the checked-in browser memory-ask-engine bundle
|
|
22
21
|
* (`src/surfaces/web/memory-ask-browser.bundle.js`) — the real memory-graph answer engine
|
|
@@ -24,7 +24,7 @@ import { turnKey } from "../domain/memory/session-turns.mjs";
|
|
|
24
24
|
export const SESSIONS_DIR_REL = join(".tmct", "sessions");
|
|
25
25
|
|
|
26
26
|
export const SESSION_CLASS = "Session";
|
|
27
|
-
|
|
27
|
+
const ASKS_ABOUT_PREDICATE = "asksAbout";
|
|
28
28
|
export const ASKS_ABOUT_PROP = "mgx:asksAbout";
|
|
29
29
|
|
|
30
30
|
const QUERIES_ATTR_CAP = 500; // joined-queries attribute cap (mirrors commitMessage's cap idea)
|
|
@@ -111,9 +111,9 @@ export function upsertSession(entities, record) {
|
|
|
111
111
|
attributes: [
|
|
112
112
|
// referenced via the imported constant (single-sourced from memory/core.mjs)
|
|
113
113
|
{ prop: CREATED_AT_PROP, key: "createdAt", value: priorCreatedAt || started || new Date().toISOString() },
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
114
|
+
// a Session really is mutated — re-written every chat turn — so stamp updatedAt
|
|
115
|
+
// explicitly: the derived "max over edges" rule cannot see an own-attribute rewrite
|
|
116
|
+
// that touches no edge.
|
|
117
117
|
{ prop: UPDATED_AT_PROP, key: "updatedAt", value: new Date().toISOString() },
|
|
118
118
|
{ prop: "mgx:sessionStarted", key: "started", value: started },
|
|
119
119
|
{ prop: "mgx:sessionEnded", key: "ended", value: ended },
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// viz-theme.mjs — the shared assets for tmct's generated HTML pages
|
|
2
|
-
// (the ledger explorer and the plan player): the visual token table
|
|
3
|
-
//
|
|
4
|
-
// page builder needs.
|
|
2
|
+
// (the ledger explorer and the plan player): the visual token table plus the
|
|
3
|
+
// escaping helpers every page builder needs.
|
|
5
4
|
//
|
|
6
5
|
// Trust tiers are precomputed rgba() values per provenance color so pages
|
|
7
6
|
// render identically on browsers without color-mix() support.
|
|
@@ -30,7 +29,7 @@ function rgba(hex, alpha) {
|
|
|
30
29
|
return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
const TOKENS = Object.freeze({
|
|
34
33
|
light: Object.freeze({
|
|
35
34
|
bg: "#F7F6F2", ink: "#23272B", muted: "#6E7168", line: "#DDD9D0", card: "#FFFFFF",
|
|
36
35
|
taught: "#2E7D4F", corpus: "#5A80AC", entail: "#B07C2E", alert: "#B0503F",
|