@polycode-projects/the-mechanical-code-talker 1.8.20 → 1.9.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 +27 -6
- package/ROADMAP.md +1 -1
- package/bin/tmct.mjs +167 -20
- package/corpus/generated/README.md +2 -2
- package/corpus/namenet/LICENSE-NOTICE +68 -0
- package/corpus/namenet/generate.mjs +309 -0
- package/corpus/namenet/manifest.json +20 -0
- package/corpus/namenet/namenet.jsonl +7260 -0
- package/corpus/wordnet/LICENSE-NOTICE +49 -0
- package/corpus/wordnet/generate.mjs +333 -0
- package/corpus/wordnet/manifest.json +34 -0
- package/corpus/wordnet/wordnet-full.jsonl +192498 -0
- package/corpus/wordnet/wordnet-xl.jsonl +23805 -0
- package/package.json +4 -2
- package/src/ask-browser-entry.mjs +13 -2
- package/src/ask-browser.bundle.js +272 -19
- package/src/chat.mjs +144 -27
- package/src/cli-args.mjs +20 -1
- package/src/codegraph.mjs +306 -1
- package/src/corpus/conceptnet-map.toml +17 -12
- package/src/corpus/conceptnet.mjs +23 -1
- package/src/extensions.mjs +44 -1
- package/src/init.mjs +99 -46
- package/src/interpret/normalize.mjs +1 -3
- package/src/memory/core.mjs +57 -1
- package/src/memory/trust.mjs +27 -6
- package/src/memory-ask-browser-entry.mjs +36 -0
- package/src/memory-ask-browser.bundle.js +5544 -0
- package/src/toml-config.mjs +11 -1
- package/src/viz.mjs +459 -59
package/src/init.mjs
CHANGED
|
@@ -148,6 +148,27 @@ enabled = ${seed.enabled ? "true" : "false"}
|
|
|
148
148
|
# To cap it, uncomment and set a number (definitional band first):
|
|
149
149
|
${seed.limit != null ? `limit = ${Number(seed.limit)}` : "# limit = 500"}
|
|
150
150
|
`;
|
|
151
|
+
// [memory] backend — ONLY emitted when a caller actually supplies it (an
|
|
152
|
+
// explicit `tmct init --memory-backend <...>`, or a manual override); the
|
|
153
|
+
// plain zero-flag `tmct init` output stays BYTE-IDENTICAL to before this
|
|
154
|
+
// knob existed, same discipline as the extras block below. "default" is
|
|
155
|
+
// written out explicitly rather than omitted, so `--memory-backend default`
|
|
156
|
+
// leaves a self-documenting trace of the choice (mirrors --with-persona's
|
|
157
|
+
// own "make the default explicit" behaviour).
|
|
158
|
+
let out = base;
|
|
159
|
+
if (config.memory && config.memory.backend !== undefined) {
|
|
160
|
+
out += `
|
|
161
|
+
[memory]
|
|
162
|
+
# Storage backend for taught facts + the memory graph (PLAN_SEED.md §6).
|
|
163
|
+
# Precedence: --memory-backend flag > TMCT_MEMORY_BACKEND env > this file >
|
|
164
|
+
# "default" (the built-in fallback).
|
|
165
|
+
# "default" — the flat OWL-labelled JSON file under .tmct/memory/. The default.
|
|
166
|
+
# "memory" — in-process only; nothing written to disk (a library caller's option).
|
|
167
|
+
# "sqlite" — a local SQLite file at .tmct/memory/graph.sqlite.
|
|
168
|
+
backend = ${JSON.stringify(config.memory.backend)}
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
|
|
151
172
|
// Extension-pack / bias sections (src/extensions.mjs) — ONLY emitted when a
|
|
152
173
|
// caller actually supplies them (an explicit `--with-persona`, or a manual
|
|
153
174
|
// override); the plain zero-flag `tmct init` output stays BYTE-IDENTICAL to
|
|
@@ -157,8 +178,8 @@ ${seed.limit != null ? `limit = ${Number(seed.limit)}` : "# limit = 500"}
|
|
|
157
178
|
const extras = {};
|
|
158
179
|
if (config.extensions !== undefined) extras.extensions = config.extensions;
|
|
159
180
|
if (config.bias !== undefined) extras.bias = config.bias;
|
|
160
|
-
if (!Object.keys(extras).length) return
|
|
161
|
-
return `${
|
|
181
|
+
if (!Object.keys(extras).length) return out;
|
|
182
|
+
return `${out}
|
|
162
183
|
# Extension packs + bias (src/extensions.mjs) — written by \`tmct init --with-persona\`
|
|
163
184
|
# or a manual edit. Recognized names (human, seon, conceptnet, tier2-aws,
|
|
164
185
|
# tier2-python, tier2-java, tier2-general) override the shipped defaults; any
|
|
@@ -196,13 +217,21 @@ function seedRequested({ optSeed, configEnabled, env }) {
|
|
|
196
217
|
* only ever sees an already-resolved preset object (or nothing). Has no
|
|
197
218
|
* effect when tmct.toml already exists and `force` isn't set (the existing
|
|
198
219
|
* "preserve a user's tmct.toml" rule wins, same as `seed`/`corpus.tier`).
|
|
220
|
+
* @param {string} [opts.memoryBackend] "default" | "memory" | "sqlite" — merged
|
|
221
|
+
* into the FRESH config's `[memory] backend` before it's written (same
|
|
222
|
+
* "fresh write only" rule as `persona`, above; `tmct init --memory-backend
|
|
223
|
+
* <...>` on an ALREADY-initialized repo is bin/tmct.mjs's own job, mirroring
|
|
224
|
+
* how `--graph` amends an existing tmct.toml post-hoc). Also selects which
|
|
225
|
+
* backend the corpus SEED below (step 3) writes into — src/memory/core.mjs's
|
|
226
|
+
* `openMemoryBackend`, the same resolver chat.mjs's createSession uses, so a
|
|
227
|
+
* seeded fact and a later chat-taught fact always land in the same store.
|
|
199
228
|
* @returns {Promise<{
|
|
200
229
|
* created: string[], config: object, seeded: boolean,
|
|
201
230
|
* alreadyInitialized: boolean, seedResult: (object|null), message: string
|
|
202
231
|
* }>} `created` lists the ABSOLUTE paths this call brought into being (empty on a
|
|
203
232
|
* benign no-op re-init). Never throws on a benign re-init or a corpus failure.
|
|
204
233
|
*/
|
|
205
|
-
export async function initRepo(dir, { force = false, seed, env = process.env, persona = null } = {}) {
|
|
234
|
+
export async function initRepo(dir, { force = false, seed, env = process.env, persona = null, memoryBackend = null } = {}) {
|
|
206
235
|
const root = resolve(dir);
|
|
207
236
|
const created = [];
|
|
208
237
|
const paths = {
|
|
@@ -235,6 +264,7 @@ export async function initRepo(dir, { force = false, seed, env = process.env, pe
|
|
|
235
264
|
if (persona.extensions && Object.keys(persona.extensions).length) config.extensions = persona.extensions;
|
|
236
265
|
if (persona.bias && Object.keys(persona.bias).length) config.bias = persona.bias;
|
|
237
266
|
}
|
|
267
|
+
if (memoryBackend) config.memory = { ...(config.memory || {}), backend: memoryBackend };
|
|
238
268
|
const tomlPresent = await exists(paths.toml);
|
|
239
269
|
if (!tomlPresent || force) {
|
|
240
270
|
await writeFile(paths.toml, renderTomlConfig(config));
|
|
@@ -266,50 +296,70 @@ export async function initRepo(dir, { force = false, seed, env = process.env, pe
|
|
|
266
296
|
} else if ((await exists(paths.marker)) && !force) {
|
|
267
297
|
seedNote = "seed skipped (already seeded — marker present)";
|
|
268
298
|
} else {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
299
|
+
// BUG FIX (found in review): this step used to call seedActiveCorpusEntries
|
|
300
|
+
// with the plain `root` string ALWAYS — Backend A only — regardless of
|
|
301
|
+
// `config.memory.backend`. A `tmct init --memory-backend sqlite` repo ended
|
|
302
|
+
// up with its corpus facts trapped in an inert .tmct/memory/graph.json that
|
|
303
|
+
// a sqlite-backend chat session (createSession, which IS backend-aware)
|
|
304
|
+
// createSession resolves it — src/memory/core.mjs's openMemoryBackend — so
|
|
305
|
+
// the seed lands in whichever backend `config.memory.backend` actually
|
|
306
|
+
// names. "memory" is skipped outright: it's an in-process-only store that
|
|
307
|
+
// vanishes the moment this one-shot init process exits, so seeding it is
|
|
308
|
+
// pure wasted work — a later `tmct chat --memory-backend memory` opens a
|
|
309
|
+
// brand new, unrelated in-memory store anyway.
|
|
310
|
+
const backendChoice = String(config.memory?.backend || "").trim().toLowerCase();
|
|
311
|
+
if (backendChoice === "memory") {
|
|
312
|
+
seedNote = "seed skipped (memory backend is in-process only — nothing would persist past this command)";
|
|
313
|
+
} else {
|
|
314
|
+
const { openMemoryBackend } = await import("./memory/core.mjs");
|
|
315
|
+
const { dir: memoryDir, close: closeMemoryStore } = await openMemoryBackend(root, backendChoice);
|
|
316
|
+
try {
|
|
317
|
+
const { resolveExtensions, seedActiveCorpusEntries } = await import("./extensions.mjs");
|
|
318
|
+
const { entries } = await resolveExtensions(root);
|
|
319
|
+
// `tmct.toml`'s `[seed] limit` knob is documented as capping the tier-1
|
|
320
|
+
// ConceptNet band specifically (the curated SEON ontology is small and
|
|
321
|
+
// always seeds whole) — so it overrides ONLY the resolved "conceptnet"
|
|
322
|
+
// entry's limit, exactly like the pre-fix single-corpus seed did.
|
|
323
|
+
if (config.seed?.limit != null && entries.has("conceptnet")) {
|
|
324
|
+
entries.set("conceptnet", { ...entries.get("conceptnet"), limit: Number(config.seed.limit) });
|
|
325
|
+
}
|
|
326
|
+
const { appended, skipped, total, perBundle } = await seedActiveCorpusEntries(memoryDir, entries);
|
|
327
|
+
// seedActiveCorpusEntries is failure-tolerant PER BUNDLE (a bad third-party
|
|
328
|
+
// pack never aborts the others) — but initRepo's own "FAILURE-TOLERANT
|
|
329
|
+
// SEED" contract is about the SEED AS A WHOLE degrading honestly. If every
|
|
330
|
+
// active bundle failed (e.g. the memory graph file itself is unwritable —
|
|
331
|
+
// see test/init.test.mjs "seed failure degrades"), re-throw the first
|
|
332
|
+
// bundle's error so the SAME outer catch below reports the familiar "seed
|
|
333
|
+
// skipped (corpus unavailable: …)" note, rather than claiming success with
|
|
334
|
+
// zero facts actually written.
|
|
335
|
+
const bundleNames = Object.keys(perBundle);
|
|
336
|
+
const allFailed = bundleNames.length > 0 && bundleNames.every((n) => perBundle[n].error);
|
|
337
|
+
if (allFailed) throw new Error(perBundle[bundleNames[0]].error);
|
|
338
|
+
seedResult = {
|
|
339
|
+
appended, skipped, total, perBundle,
|
|
340
|
+
seon: perBundle.seon?.appended || 0,
|
|
341
|
+
conceptnet: perBundle.conceptnet?.appended || 0,
|
|
342
|
+
};
|
|
343
|
+
const markerNew = !(await exists(paths.marker));
|
|
344
|
+
await mkdir(dirname(paths.marker), { recursive: true });
|
|
345
|
+
await writeFile(
|
|
346
|
+
paths.marker,
|
|
347
|
+
JSON.stringify({
|
|
348
|
+
seededAt: new Date().toISOString(),
|
|
349
|
+
limit: config.seed?.limit != null ? Number(config.seed.limit) : SEED_LIMIT,
|
|
350
|
+
appended: seedResult.appended,
|
|
351
|
+
skipped: seedResult.skipped,
|
|
352
|
+
perBundle,
|
|
353
|
+
}) + "\n",
|
|
354
|
+
);
|
|
355
|
+
if (markerNew) created.push(paths.marker);
|
|
356
|
+
seeded = true;
|
|
357
|
+
} catch (err) {
|
|
358
|
+
// Corpus unavailable/broken → an initialised-but-unseeded repo, not a crash.
|
|
359
|
+
seedNote = `seed skipped (corpus unavailable: ${err && err.message ? err.message : err})`;
|
|
360
|
+
} finally {
|
|
361
|
+
await closeMemoryStore();
|
|
278
362
|
}
|
|
279
|
-
const { appended, skipped, total, perBundle } = await seedActiveCorpusEntries(root, entries);
|
|
280
|
-
// seedActiveCorpusEntries is failure-tolerant PER BUNDLE (a bad third-party
|
|
281
|
-
// pack never aborts the others) — but initRepo's own "FAILURE-TOLERANT
|
|
282
|
-
// SEED" contract is about the SEED AS A WHOLE degrading honestly. If every
|
|
283
|
-
// active bundle failed (e.g. the memory graph file itself is unwritable —
|
|
284
|
-
// see test/init.test.mjs "seed failure degrades"), re-throw the first
|
|
285
|
-
// bundle's error so the SAME outer catch below reports the familiar "seed
|
|
286
|
-
// skipped (corpus unavailable: …)" note, rather than claiming success with
|
|
287
|
-
// zero facts actually written.
|
|
288
|
-
const bundleNames = Object.keys(perBundle);
|
|
289
|
-
const allFailed = bundleNames.length > 0 && bundleNames.every((n) => perBundle[n].error);
|
|
290
|
-
if (allFailed) throw new Error(perBundle[bundleNames[0]].error);
|
|
291
|
-
seedResult = {
|
|
292
|
-
appended, skipped, total, perBundle,
|
|
293
|
-
seon: perBundle.seon?.appended || 0,
|
|
294
|
-
conceptnet: perBundle.conceptnet?.appended || 0,
|
|
295
|
-
};
|
|
296
|
-
const markerNew = !(await exists(paths.marker));
|
|
297
|
-
await mkdir(dirname(paths.marker), { recursive: true });
|
|
298
|
-
await writeFile(
|
|
299
|
-
paths.marker,
|
|
300
|
-
JSON.stringify({
|
|
301
|
-
seededAt: new Date().toISOString(),
|
|
302
|
-
limit: config.seed?.limit != null ? Number(config.seed.limit) : SEED_LIMIT,
|
|
303
|
-
appended: seedResult.appended,
|
|
304
|
-
skipped: seedResult.skipped,
|
|
305
|
-
perBundle,
|
|
306
|
-
}) + "\n",
|
|
307
|
-
);
|
|
308
|
-
if (markerNew) created.push(paths.marker);
|
|
309
|
-
seeded = true;
|
|
310
|
-
} catch (err) {
|
|
311
|
-
// Corpus unavailable/broken → an initialised-but-unseeded repo, not a crash.
|
|
312
|
-
seedNote = `seed skipped (corpus unavailable: ${err && err.message ? err.message : err})`;
|
|
313
363
|
}
|
|
314
364
|
}
|
|
315
365
|
|
|
@@ -356,6 +406,9 @@ async function readWrittenConfig(tomlPath, base) {
|
|
|
356
406
|
// normalizeConfig).
|
|
357
407
|
if (raw.extensions !== undefined) cfg.extensions = raw.extensions;
|
|
358
408
|
if (raw.bias !== undefined) cfg.bias = raw.bias;
|
|
409
|
+
if (raw.memory && raw.memory.backend !== undefined) {
|
|
410
|
+
cfg.memory = { ...cfg.memory, backend: raw.memory.backend };
|
|
411
|
+
}
|
|
359
412
|
return cfg;
|
|
360
413
|
} catch {
|
|
361
414
|
return base;
|
|
@@ -119,9 +119,7 @@ const KIND_NOUN_ANAPHORA_RE = /\b(this|that)\s+(class|module|function|method|att
|
|
|
119
119
|
* "file"->"Module" convention every other lane in this grammar already
|
|
120
120
|
* uses)? Returns that class, or null when no such anaphora is present.
|
|
121
121
|
* Deliberately SEPARATE from normalizeQuery's own KIND_NOUN_ANAPHORA_RE
|
|
122
|
-
* replace just above (
|
|
123
|
-
* "this", discarding the kind-noun signal for good, by design — see that
|
|
124
|
-
* replace's own docblock): this never mutates its input and has no effect
|
|
122
|
+
* replace just above (: this never mutates its input and has no effect
|
|
125
123
|
* on normalizeQuery's behavior, signature, or any of its many call sites.
|
|
126
124
|
* A caller that needs BOTH the collapsed pronoun AND the kind it stood for
|
|
127
125
|
* (chat.mjs's runAsk, at its pronoun-reuse site) calls this side-channel on
|
package/src/memory/core.mjs
CHANGED
|
@@ -108,7 +108,7 @@ const MEMORY_VOCABULARY = [
|
|
|
108
108
|
{ prop: DERIVED_FROM_PROP, predicate: "derivedFrom", note: "umbrella: a Fact derived from a Source (or another Fact). ext ref prov:wasDerivedFrom (UNVERIFIED-pending-web-check)" },
|
|
109
109
|
{ prop: STATED_BY_PROP, predicate: "statedBy", note: "subPropertyOf derivedFrom: a Source directly asserts this Fact (one edge per independent source — replaces the factProvenance union)" },
|
|
110
110
|
{ prop: CANONICALISED_FROM_PROP, predicate: "canonicalisedFrom", note: "subPropertyOf derivedFrom: a canonical Fact cleaned from a raw Block/Source, never replacing it" },
|
|
111
|
-
{ prop: "mgx:sourceType", note: "a Source's kind: operator | teach | provider | corpus | web | entailed (the trust-prior key)" },
|
|
111
|
+
{ prop: "mgx:sourceType", note: "a Source's kind: operator | teach | provider | corpus | corpusWeak | extracted | web | entailed (the trust-prior key)" },
|
|
112
112
|
{ prop: "mgx:sourceUrl", note: "a web Source's URL" },
|
|
113
113
|
{ prop: "mgx:sourceRule", note: "an entailed Source's rule id" },
|
|
114
114
|
{ prop: "mgx:sourceReliability", note: "actor-level (session-scoped) trust nudge in [0.5,1.5], neutral 1.0 when absent — materialised by recomputeSourceReliability from a session's asserted-vs-contradicted track record (memory/trust.mjs's sessionReliabilityFrom); folds into computeTrust's per-source prior" },
|
|
@@ -312,6 +312,43 @@ export function closeSqliteMemoryStore(handle) {
|
|
|
312
312
|
if (isSqliteHandle(handle)) handle.db.close();
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Resolve an already-lowercased/trimmed backend token ("memory" | "sqlite" |
|
|
317
|
+
* anything else, including "" or "default") into `{ dir, close }`: the exact
|
|
318
|
+
* `dir` value every dir-taking export in this module (loadMemory, appendFact,
|
|
319
|
+
* appendFacts, seedMemory via src/corpus/conceptnet.mjs, …) already accepts —
|
|
320
|
+
* a plain repo-path string for Backend A, or a live Backend B/C handle — plus
|
|
321
|
+
* an idempotent `close()` cleanup (a no-op for A/B; closes the sqlite
|
|
322
|
+
* connection for C).
|
|
323
|
+
*
|
|
324
|
+
* The ONE shared resolver for the storage-backend seam (PLAN_SEED.md §6):
|
|
325
|
+
* originally inlined only in chat.mjs's createSession, factored out here so
|
|
326
|
+
* `tmct init`'s corpus seed (src/init.mjs) and bin/tmct.mjs's
|
|
327
|
+
* `--corpus`/`--ontology`/`--lexicon` activation seed the SAME backend a
|
|
328
|
+
* later `tmct chat` in that repo will read — a caller resolving "sqlite"
|
|
329
|
+
* independently and picking a different db path (or a different in-memory
|
|
330
|
+
* store) would silently split a repo's memory across two places that can
|
|
331
|
+
* never see each other. `backendChoice` is a precedence result the CALLER
|
|
332
|
+
* already computed (CLI flag > env > tmct.toml > default, or whatever subset
|
|
333
|
+
* applies); this function does no precedence resolution of its own — it only
|
|
334
|
+
* maps the resolved token to a handle. `repoRoot` is used only for Backend C's
|
|
335
|
+
* db path (`<repoRoot>/.tmct/memory/graph.sqlite`); Backend A returns
|
|
336
|
+
* `repoRoot` itself unchanged, byte-identical to every existing plain-string
|
|
337
|
+
* caller.
|
|
338
|
+
*/
|
|
339
|
+
export async function openMemoryBackend(repoRoot, backendChoice) {
|
|
340
|
+
if (backendChoice === BACKEND_MEMORY) {
|
|
341
|
+
return { dir: createInMemoryStore(), close: async () => {} };
|
|
342
|
+
}
|
|
343
|
+
if (backendChoice === BACKEND_SQLITE) {
|
|
344
|
+
const dbPath = join(repoRoot, ".tmct", "memory", "graph.sqlite");
|
|
345
|
+
await mkdir(dirname(dbPath), { recursive: true });
|
|
346
|
+
const handle = await createSqliteMemoryStore(dbPath);
|
|
347
|
+
return { dir: handle, close: async () => closeSqliteMemoryStore(handle) };
|
|
348
|
+
}
|
|
349
|
+
return { dir: repoRoot, close: async () => {} };
|
|
350
|
+
}
|
|
351
|
+
|
|
315
352
|
/** Deep-clone a JSON-safe value. Used two ways here: (1) readSqlitePayload
|
|
316
353
|
* hands every CALLER a clone of the cache, never the live cached object
|
|
317
354
|
* itself, so this backend keeps the same "fresh object every call" contract
|
|
@@ -771,6 +808,11 @@ function sourceIdFor(desc) {
|
|
|
771
808
|
case "teach": return { id: desc.sessionId ? `${TEACH_SOURCE_ID}:${desc.sessionId}` : TEACH_SOURCE_ID, type: "teach" };
|
|
772
809
|
case "provider": return { id: `src:provider:${desc.name}`, type: "provider" };
|
|
773
810
|
case "corpus": return { id: `src:corpus:${desc.name}`, type: "corpus" };
|
|
811
|
+
// One Source per source-file basename (the corpus precedent above), not per
|
|
812
|
+
// extraction run — re-running scripts/extract-facts-from-text.mjs over the
|
|
813
|
+
// SAME file collapses onto the same Source instead of minting a new one
|
|
814
|
+
// every time, matching corpus's "one Source per named dataset" idiom.
|
|
815
|
+
case "extracted": return { id: `src:extracted:${desc.name}`, type: "extracted" };
|
|
774
816
|
case "web": return { id: `src:learned:web:${fnv1aHex(String(desc.url || ""))}`, type: "web", url: String(desc.url || "") };
|
|
775
817
|
case "entailed": return { id: `src:entailed:${desc.rule}`, type: "entailed", rule: String(desc.rule || "") };
|
|
776
818
|
default: return null;
|
|
@@ -821,19 +863,32 @@ function parseChatTagRest(rest) {
|
|
|
821
863
|
* set — the inverse the migration and the live write path both name Sources
|
|
822
864
|
* through. The tag formats are exactly what the writers produce:
|
|
823
865
|
* corpus:conceptnet /r/IsA → { kind:"corpus", name:"conceptnet" }
|
|
866
|
+
* corpus-weak:conceptnet /r/RelatedTo → { kind:"corpusWeak", name:"conceptnet" }
|
|
824
867
|
* ace:chat:<session>@<ts> → { kind:"operator", createdAt:<ts>, sessionId:<session> }
|
|
825
868
|
* teach:chat:<session>@<ts> → { kind:"teach", createdAt:<ts>, sessionId:<session> }
|
|
826
869
|
* web:<url> | url:<url> → { kind:"web", url:<url> }
|
|
870
|
+
* extracted:<file-basename> → { kind:"extracted", name:<file-basename> }
|
|
827
871
|
* entailed:<rule> → { kind:"entailed", rule:<rule> }
|
|
828
872
|
* chat:/session: refs map to the operator; an unknown tag → null (no Source).
|
|
873
|
+
* `corpus-weak:` is the SAME corpus provenance shape as `corpus:`, just naming
|
|
874
|
+
* a lower trust-prior kind (memory/trust.mjs SOURCE_PRIOR.corpusWeak) for
|
|
875
|
+
* facts whose underlying relation is real but low-precision (e.g. ConceptNet's
|
|
876
|
+
* undirected /r/RelatedTo) — trust stays computed from the Source's kind, never
|
|
877
|
+
* hand-set on the Fact.
|
|
829
878
|
* The session-id segment (Part B: session-scoped actor-level trust) feeds
|
|
830
879
|
* sourceIdFor, which mints a PER-SESSION Source id when present, instead of
|
|
831
880
|
* collapsing every session onto one singleton operator/teach Source.
|
|
881
|
+
* `extracted:` is scripts/extract-facts-from-text.mjs's own audit tag, layered
|
|
882
|
+
* ADDITIVELY (via appendFact's provenance union) on top of whatever the
|
|
883
|
+
* runTurn recognizer already wrote (ace:/teach:) — see that script for why:
|
|
884
|
+
* it distinguishes "this document evidenced this fact" from ordinary chat
|
|
885
|
+
* speech, at its own trust-prior tier (memory/trust.mjs SOURCE_PRIOR.extracted).
|
|
832
886
|
*/
|
|
833
887
|
export function provenanceTagToSource(tag) {
|
|
834
888
|
const t = String(tag || "").trim();
|
|
835
889
|
if (!t) return null;
|
|
836
890
|
const head = t.split(/\s+/)[0]; // drop trailing " /r/IsA" etc.
|
|
891
|
+
if (head.startsWith("corpus-weak:")) return { kind: "corpusWeak", name: head.slice("corpus-weak:".length) || "unknown" };
|
|
837
892
|
if (head.startsWith("corpus:")) return { kind: "corpus", name: head.slice("corpus:".length) || "unknown" };
|
|
838
893
|
if (head.startsWith("ace:")) return { kind: "operator", ...parseChatTagRest(head.slice("ace:".length)) };
|
|
839
894
|
if (head.startsWith("teach:")) {
|
|
@@ -842,6 +897,7 @@ export function provenanceTagToSource(tag) {
|
|
|
842
897
|
}
|
|
843
898
|
if (head.startsWith("web:")) return { kind: "web", url: head.slice("web:".length) };
|
|
844
899
|
if (head.startsWith("url:")) return { kind: "web", url: head.slice("url:".length) };
|
|
900
|
+
if (head.startsWith("extracted:")) return { kind: "extracted", name: head.slice("extracted:".length) || "unknown" };
|
|
845
901
|
if (head.startsWith("entailed:")) return { kind: "entailed", rule: head.slice("entailed:".length) };
|
|
846
902
|
if (head.startsWith("chat:") || head.startsWith("session:") || head.startsWith("operator")) return { kind: "operator" };
|
|
847
903
|
return null;
|
package/src/memory/trust.mjs
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// Trust is a COMPUTED attribute of a Fact — never hand-set — a pure function of
|
|
5
5
|
// its Source edges, those Sources' types, and its mgx:createdAt. Three inputs
|
|
6
6
|
// combine:
|
|
7
|
-
// - a Source-TYPE PRIOR (operator > teach > provider > corpus >
|
|
7
|
+
// - a Source-TYPE PRIOR (operator > teach > provider > corpus > corpusWeak >
|
|
8
|
+
// extracted > web > entailed);
|
|
8
9
|
// - CORROBORATION over the fact's distinct Sources by noisy-OR
|
|
9
10
|
// (1 − Π(1 − wᵢ), capped at 1) — two independent web sources (0.4) reach
|
|
10
11
|
// 0.64, a lone operator fact is already 1.0;
|
|
@@ -35,17 +36,37 @@ export const TRUST_SCORE_PROP = "mgx:trustScore";
|
|
|
35
36
|
export const TRUST_INPUTS_PROP = "mgx:trustInputs";
|
|
36
37
|
|
|
37
38
|
/** Source-type priors — the ordering operator > teach > provider-graph >
|
|
38
|
-
* curated-corpus >
|
|
39
|
-
* lane's natural-frame writes
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
39
|
+
* curated-corpus > weak-corpus > extracted-document > web > unverified-
|
|
40
|
+
* entailment. `teach` is the chat teach lane's natural-frame writes
|
|
41
|
+
* ("remember that …", "<Name> owns <X>") — still operator speech, but
|
|
42
|
+
* through a looser recognizer than the ACE-parsed operator assert, so it
|
|
43
|
+
* sits just below the operator prior. The entailed value is a FLOOR before
|
|
44
|
+
* premise adjustment (see the entailed hook below).
|
|
45
|
+
*
|
|
46
|
+
* `corpusWeak` is for corpus-sourced facts whose underlying relation is real
|
|
47
|
+
* but low-precision (ConceptNet's /r/RelatedTo — ambiguous, undirected
|
|
48
|
+
* association, unlike the specific typed relations the plain `corpus` prior
|
|
49
|
+
* covers) — still a curated, committed dataset (above `web`), just not
|
|
50
|
+
* asserting the same strength of claim (below `corpus`).
|
|
51
|
+
*
|
|
52
|
+
* `extracted` is scripts/extract-facts-from-text.mjs's batch reader: it runs
|
|
53
|
+
* the SAME deterministic teach/assert recognizer as `teach`/`operator`, but
|
|
54
|
+
* unattended over an arbitrary document nobody in-session vetted sentence by
|
|
55
|
+
* sentence — the recognizer is exact/closed-set (no guessing), but the
|
|
56
|
+
* SOURCE DOCUMENT is unreviewed, so it sits just above `web` (also an
|
|
57
|
+
* unreviewed external source) and below `corpusWeak`/`corpus` (curated,
|
|
58
|
+
* committed datasets) and `teach` (a human typing into the live chat).
|
|
59
|
+
*
|
|
60
|
+
* Both are computed from the Source's type exactly like every other tier —
|
|
61
|
+
* never hand-set on a Fact directly. */
|
|
43
62
|
export const SOURCE_PRIOR = Object.freeze({
|
|
44
63
|
operator: 1.0,
|
|
45
64
|
teach: 0.95,
|
|
46
65
|
provider: 0.9,
|
|
47
66
|
corpus: 0.7,
|
|
67
|
+
corpusWeak: 0.55,
|
|
48
68
|
web: 0.4,
|
|
69
|
+
extracted: 0.45,
|
|
49
70
|
entailed: 0.3,
|
|
50
71
|
});
|
|
51
72
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// memory-ask-browser-entry.mjs — the esbuild entry for `tmct viz`'s embedded
|
|
2
|
+
// "Ask the graph" panel's MEMORY-graph engine (PLAN_VIZ_MEMORY.md Bug 1 fix).
|
|
3
|
+
//
|
|
4
|
+
// Bug 1: the panel bundled ONLY ask.mjs — tmct's code-graph query engine
|
|
5
|
+
// ("which modules import X"), which has no concept of Facts/corpus data at
|
|
6
|
+
// all, so a memory-graph question like "what is a dog" always missed even
|
|
7
|
+
// though the page's own embedded payload had the answer. This is the SECOND,
|
|
8
|
+
// narrow browser entry point src/ask-browser-entry.mjs's own doc comment
|
|
9
|
+
// anticipates: re-exports just `factAnswer` (src/chat.mjs) — tmct's REAL
|
|
10
|
+
// memory-graph answer engine, the same one `npm run chat` uses — plus
|
|
11
|
+
// `createInMemoryStore` (src/memory/core.mjs), which is how the panel hands
|
|
12
|
+
// `factAnswer` the page's already-embedded PAYLOAD with ZERO fs I/O: a
|
|
13
|
+
// Backend-B handle's `loadMemory` branch returns `handle.payload` directly, no
|
|
14
|
+
// bundle-time module shimming needed (see factAnswer's own doc comment,
|
|
15
|
+
// src/chat.mjs, for the full reasoning — a simpler, more robust mechanism than
|
|
16
|
+
// intercepting loadMemory at bundle time, since it reuses machinery the
|
|
17
|
+
// codebase already ships and tests, rather than a new esbuild-only code path
|
|
18
|
+
// that could drift from the real one).
|
|
19
|
+
//
|
|
20
|
+
// `factAnswer` is called with `envelope: null, miss: true` — the exact,
|
|
21
|
+
// already-documented "no envelope available" bootstrap path (chat.mjs's own
|
|
22
|
+
// comments: "the FIRST turn of a graph-less session... leaves `envelope` null
|
|
23
|
+
// for the rest of THIS turn's processing" — a real, tested code path, not a
|
|
24
|
+
// hack) — which arms factAnswer's own bare-question regex fallbacks
|
|
25
|
+
// (BARE_WHATIS_RE and friends) to parse the query directly, with no
|
|
26
|
+
// dependency on the much larger structural-graph parse pipeline
|
|
27
|
+
// (dispatchTool/loadGraph, server.mjs) that pipeline needs a real --repo code
|
|
28
|
+
// index for and this panel has no use for.
|
|
29
|
+
import { factAnswer } from "./chat.mjs";
|
|
30
|
+
import { createInMemoryStore, normFactTerm } from "./memory/core.mjs";
|
|
31
|
+
|
|
32
|
+
// normFactTerm is re-exported too — viz.mjs's client-side "focus follows the
|
|
33
|
+
// memory-engine's answer" heuristic (guessTermIdFromQuery) needs the SAME
|
|
34
|
+
// term normalization the CLI's own `--term` seed flag and factAnswer's own
|
|
35
|
+
// subject/object matching use, never a second hand-rolled copy.
|
|
36
|
+
globalThis.tmctMemoryAsk = { factAnswer, createInMemoryStore, normFactTerm };
|