@polycode-projects/the-mechanical-code-talker 2.0.3 → 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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/ROADMAP.md +27 -4
  3. package/bin/tmct.mjs +4 -5
  4. package/corpus/generated/ace-surface-variants.jsonl +1 -0
  5. package/corpus/generated/manifest.json +3 -3
  6. package/corpus/wordnet/generate.mjs +6 -7
  7. package/package.json +30 -2
  8. package/src/adapters/corpus/conceptnet.mjs +1 -1
  9. package/src/adapters/graph-build.mjs +3 -3
  10. package/src/adapters/memory/blocks.mjs +2 -2
  11. package/src/adapters/memory/core.mjs +5 -5
  12. package/src/adapters/providers/bootstrap.mjs +1 -1
  13. package/src/adapters/providers/fixture.mjs +1 -1
  14. package/src/adapters/toml-config.mjs +0 -1
  15. package/src/adapters/wink-model.mjs +1 -1
  16. package/src/adapters/wordnet-source.mjs +70 -0
  17. package/src/domain/answer-variants.json +1 -1
  18. package/src/domain/ask-vocab.mjs +2 -2
  19. package/src/domain/ask.mjs +4 -4
  20. package/src/domain/codegraph.mjs +7 -71
  21. package/src/domain/corpus-matrix.mjs +87 -0
  22. package/src/domain/grammar/ace.mjs +11 -11
  23. package/src/domain/grammar/lexicon.mjs +3 -3
  24. package/src/domain/inflect.mjs +67 -0
  25. package/src/domain/interpret/fuzzy.mjs +1 -1
  26. package/src/domain/interpret/merge.mjs +1 -1
  27. package/src/domain/interpret/normalize.mjs +1 -1
  28. package/src/domain/licences.mjs +68 -0
  29. package/src/domain/markdown-links.mjs +55 -0
  30. package/src/domain/memory/capability.mjs +1 -1
  31. package/src/domain/memory/trust.mjs +2 -2
  32. package/src/domain/persona/codegen.mjs +123 -0
  33. package/src/domain/persona/examples.mjs +26 -0
  34. package/src/domain/persona/tiers.mjs +270 -0
  35. package/src/domain/publish-gate.mjs +41 -0
  36. package/src/domain/router/call-validator.mjs +1 -1
  37. package/src/domain/router/drive.mjs +3 -4
  38. package/src/domain/router/registry.mjs +12 -13
  39. package/src/domain/router/resolver.mjs +18 -5
  40. package/src/domain/router/results.mjs +3 -3
  41. package/src/domain/router/taught.mjs +4 -3
  42. package/src/domain/schemaorg/turtle.mjs +25 -0
  43. package/src/domain/semcor/parse.mjs +87 -0
  44. package/src/domain/syllogise.mjs +6 -6
  45. package/src/domain/version-stamp.mjs +36 -0
  46. package/src/domain/wordnet/yaml.mjs +133 -0
  47. package/src/services/chat-session.mjs +2 -2
  48. package/src/services/chat.mjs +2 -2
  49. package/src/services/cli-args.mjs +4 -4
  50. package/src/services/finish.mjs +1 -1
  51. package/src/services/ledger-viz.mjs +2 -3
  52. package/src/services/sessions.mjs +4 -4
  53. package/src/services/viz-theme.mjs +3 -4
  54. package/src/surfaces/web/memory-ask-browser.bundle.js +4 -94
  55. package/src/adapters/embed.mjs +0 -169
  56. package/src/domain/router/guardrail.mjs +0 -116
  57. package/src/domain/vector.mjs +0 -12
@@ -1,169 +0,0 @@
1
- // embed.mjs — deterministic static-embedding lookup (model2vec potion-base-8M,
2
- // 29,528 WordPiece subwords × 256 fp32 dims). Pure table lookup + float
3
- // arithmetic, no ONNX runtime, no network after the one-time fetch — the same
4
- // text always embeds to the same vector.
5
- //
6
- // The safetensors reader and WordPiece tokenizer below are hand-rolled: both
7
- // formats are simple enough to parse directly, avoiding an ONNX/HF tokenizer
8
- // dependency for it.
9
- //
10
- // Weights are gitignored (vendor/, fetched by scripts/fetch-embeddings.mjs)
11
- // and never in the npm package; loadEmbedder() returns null when absent so
12
- // CI/tests never require the download.
13
-
14
- import { readFileSync, existsSync } from "node:fs";
15
- import { join, dirname } from "node:path";
16
- import { fileURLToPath } from "node:url";
17
-
18
- const MODEL_FILE = "model.safetensors";
19
- const TOKENIZER_FILE = "tokenizer.json";
20
- const CONFIG_FILE = "config.json";
21
-
22
- /** Default artifact dir: $TMCT_EMBED_DIR, else <repo>/vendor/embeddings/potion-base-8M
23
- * (gitignored via vendor/ — the location scripts/fetch-embeddings.mjs writes). */
24
- export function defaultEmbeddingsDir() {
25
- if (process.env.TMCT_EMBED_DIR) return process.env.TMCT_EMBED_DIR;
26
- const here = dirname(fileURLToPath(import.meta.url)); // packages/tmct/src
27
- return join(here, "..", "..", "..", "..", "vendor", "embeddings", "potion-base-8M");
28
- }
29
-
30
- // ---- safetensors (hand-rolled: u64le header length + JSON header + raw tensors) -------
31
-
32
- /** Read the single 2-D F32 embedding tensor from a safetensors file →
33
- * { matrix: Float32Array (row-major), rows, dim }. model2vec exports exactly one
34
- * tensor named "embeddings"; any lone 2-D F32 tensor is accepted for test fixtures. */
35
- function readSafetensors(file) {
36
- const buf = readFileSync(file);
37
- const headerLen = Number(buf.readBigUInt64LE(0));
38
- const header = JSON.parse(buf.subarray(8, 8 + headerLen).toString("utf8"));
39
- const name = header.embeddings
40
- ? "embeddings"
41
- : Object.keys(header).find((k) => k !== "__metadata__" && header[k]?.shape?.length === 2);
42
- const t = name && header[name];
43
- if (!t) throw new Error(`no 2-D tensor in ${file}`);
44
- if (t.dtype !== "F32") throw new Error(`unsupported dtype ${t.dtype} in ${file} (only F32)`);
45
- const [rows, dim] = t.shape;
46
- const [start, end] = t.data_offsets;
47
- const bytes = buf.subarray(8 + headerLen + start, 8 + headerLen + end);
48
- if (bytes.byteLength !== rows * dim * 4) throw new Error(`tensor size mismatch in ${file}`);
49
- // Copy into a fresh ArrayBuffer: the slice's byteOffset inside the file buffer is not
50
- // guaranteed 4-byte aligned, and Float32Array requires alignment.
51
- const matrix = new Float32Array(rows * dim);
52
- new Uint8Array(matrix.buffer).set(bytes);
53
- return { matrix, rows, dim };
54
- }
55
-
56
- // ---- Bert-style WordPiece tokenizer (from tokenizer.json) -----------------------------
57
-
58
- const isPunct = (ch) => {
59
- const c = ch.codePointAt(0);
60
- // ASCII punctuation ranges (Bert treats these as standalone tokens) + general unicode P/S.
61
- return (c >= 33 && c <= 47) || (c >= 58 && c <= 64) || (c >= 91 && c <= 96) || (c >= 123 && c <= 126) ||
62
- /[\p{P}\p{S}]/u.test(ch);
63
- };
64
- const isCjk = (ch) => {
65
- const c = ch.codePointAt(0);
66
- return (c >= 0x4e00 && c <= 0x9fff) || (c >= 0x3400 && c <= 0x4dbf) ||
67
- (c >= 0xf900 && c <= 0xfaff) || (c >= 0x20000 && c <= 0x2ffff);
68
- };
69
-
70
- function makeTokenizer(spec) {
71
- if (spec?.model?.type !== "WordPiece") {
72
- throw new Error(`unsupported tokenizer model "${spec?.model?.type}" (only WordPiece)`);
73
- }
74
- const vocab = spec.model.vocab; // token -> id
75
- const contPrefix = spec.model.continuing_subword_prefix ?? "##";
76
- const maxChars = spec.model.max_input_chars_per_word ?? 100;
77
- const unkId = vocab[spec.model.unk_token] ?? null;
78
- const lowercase = spec.normalizer?.lowercase !== false;
79
-
80
- // BertNormalizer: clean control chars, pad CJK, lowercase (+ strip accents when lowercasing).
81
- const normalize = (text) => {
82
- let s = String(text).replace(/[\u0000\ufffd]/g, "").replace(/[\u0001-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
83
- s = [...s].map((ch) => (isCjk(ch) ? ` ${ch} ` : ch)).join("");
84
- if (lowercase) s = s.toLowerCase().normalize("NFD").replace(/\p{Mn}/gu, "");
85
- return s;
86
- };
87
- // BertPreTokenizer: split on whitespace; every punctuation char is its own word.
88
- const preTokenize = (s) => {
89
- const words = [];
90
- for (const chunk of s.split(/\s+/)) {
91
- if (!chunk) continue;
92
- let cur = "";
93
- for (const ch of chunk) {
94
- if (isPunct(ch)) {
95
- if (cur) { words.push(cur); cur = ""; }
96
- words.push(ch);
97
- } else cur += ch;
98
- }
99
- if (cur) words.push(cur);
100
- }
101
- return words;
102
- };
103
- // Greedy longest-match WordPiece per word; a word with no valid segmentation → [UNK].
104
- const wordPiece = (word) => {
105
- if (word.length > maxChars) return unkId == null ? [] : [unkId];
106
- const ids = [];
107
- let start = 0;
108
- while (start < word.length) {
109
- let end = word.length;
110
- let id = null;
111
- while (end > start) {
112
- const piece = (start > 0 ? contPrefix : "") + word.slice(start, end);
113
- if (vocab[piece] !== undefined) { id = vocab[piece]; break; }
114
- end--;
115
- }
116
- if (id == null) return unkId == null ? [] : [unkId];
117
- ids.push(id);
118
- start = end;
119
- }
120
- return ids;
121
- };
122
- // No [CLS]/[SEP] template — model2vec pools content subwords only.
123
- return (text) => preTokenize(normalize(text)).flatMap(wordPiece);
124
- }
125
-
126
- // ---- embedder ---------------------------------------------------------------------------
127
-
128
- const LOADED = new Map(); // dir -> embedder | null (per-process cache; weights load once)
129
-
130
- /** Load tokenizer + embedding matrix from `dir` (default: defaultEmbeddingsDir()).
131
- * Returns null — silently — when the artifacts are absent (the one-time
132
- * `npm run refs:embeddings` fetch has not run): callers no-op, never fail.
133
- * The returned embedder is { dim, dir, embed(text) → L2-normalised Float32Array }. */
134
- export function loadEmbedder({ dir = defaultEmbeddingsDir() } = {}) {
135
- if (LOADED.has(dir)) return LOADED.get(dir);
136
- const modelFile = join(dir, MODEL_FILE);
137
- const tokFile = join(dir, TOKENIZER_FILE);
138
- if (!existsSync(modelFile) || !existsSync(tokFile)) {
139
- LOADED.set(dir, null);
140
- return null;
141
- }
142
- const { matrix, rows, dim } = readSafetensors(modelFile);
143
- const tokenize = makeTokenizer(JSON.parse(readFileSync(tokFile, "utf8")));
144
- let cfg = {};
145
- try { cfg = JSON.parse(readFileSync(join(dir, CONFIG_FILE), "utf8")); } catch { /* optional */ }
146
- const doNormalize = cfg.normalize !== false;
147
-
148
- const embed = (text) => {
149
- const ids = tokenize(text);
150
- const v = new Float32Array(dim);
151
- if (!ids.length) return v; // zero vector: cosine 0 against everything
152
- for (const id of ids) {
153
- if (id < 0 || id >= rows) continue;
154
- const off = id * dim;
155
- for (let j = 0; j < dim; j++) v[j] += matrix[off + j];
156
- }
157
- for (let j = 0; j < dim; j++) v[j] /= ids.length; // mean pool
158
- if (doNormalize) {
159
- let norm = 0;
160
- for (let j = 0; j < dim; j++) norm += v[j] * v[j];
161
- norm = Math.sqrt(norm);
162
- if (norm > 0) for (let j = 0; j < dim; j++) v[j] /= norm;
163
- }
164
- return v;
165
- };
166
- const embedder = { dim, dir, embed };
167
- LOADED.set(dir, embedder);
168
- return embedder;
169
- }
@@ -1,116 +0,0 @@
1
- // src/domain/router/guardrail.mjs — the guardrail. Validate an
2
- // EXTERNALLY-proposed `tool_use` against the registry's declared preconditions.
3
- //
4
- // Proves RESOLVABILITY (the tool is registered/declared, args are well-formed, every
5
- // `resolves(param, as)` precondition binds to a real graph entity) — NOT antecedent
6
- // correctness: a cross-turn mis-binding ("it" -> the wrong Commit) still resolves to a
7
- // real entity and passes. "This symbol denotes something real and the call is
8
- // well-formed", never "this is the right something".
9
- //
10
- // Pure over its inputs + ctx.resolve (the binding oracle). No network, no Date.now.
11
-
12
- import { capabilityByName, preconditionsOf, PRECOND } from "./registry.mjs";
13
- import { hallucinationsIn } from "./call-validator.mjs";
14
-
15
- /** The same read-only breadth-first enrichment as resolver.mjs's `dispatchEachCandidate`:
16
- * dispatching the SAME tool once per tied candidate is safe for `readOnly` capabilities
17
- * (dispatch performs no writes). Returns `[{candidate, result}, ...]`, or
18
- * undefined when there is no dispatcher to run it with. */
19
- async function dispatchEachCandidate(pool, capName, arg, ctx) {
20
- if (!ctx.dispatch) return undefined;
21
- // Only readOnly capabilities may be dispatched here: the enrichment runs the
22
- // SAME tool once per tied candidate, which is only safe when dispatch
23
- // performs no writes. A registered world-mutating capability is planned
24
- // over, never dispatched.
25
- if (capabilityByName(capName)?.readOnly !== true) return undefined;
26
- const results = [];
27
- for (const c of pool) {
28
- const res = await ctx.dispatch(capName, { [arg]: c.label });
29
- results.push({ candidate: c.label, result: res });
30
- }
31
- return results;
32
- }
33
-
34
- /** Validate a proposed tool_use. Returns a glass-box verdict:
35
- * { ok, tool, denied:[{reason,detail}], steps:[{pred,ok,...}], provenance, candidateResults? }
36
- * ok=false with a default-deny/undeclared/unknown-arg/missing-arg denial is structural (no
37
- * graph needed); an `unresolved` step is a binding rejection, and an ambiguous `resolves`
38
- * term additionally carries `candidateResults` (the tool dispatched once per tied candidate)
39
- * when `ctx.dispatch` is wired. `declaredNames=null` skips the declared-set check.
40
- * `ctx.resolve(term)` is the resolveObject oracle; omit it for structural-only validation. */
41
- export async function guard(toolUse, declaredNames = null, ctx = {}) {
42
- const name = toolUse?.name;
43
- const input = toolUse && typeof toolUse.input === "object" && toolUse.input ? toolUse.input : {};
44
- const denied = [];
45
- const steps = [];
46
-
47
- // Default-deny: unknown/unregistered tool is an automatic reject.
48
- const declaredList = declaredNames ? [...declaredNames] : null;
49
- const cap = capabilityByName(name);
50
- if (!cap) {
51
- denied.push({ reason: "default-deny", detail: `"${name ?? "(none)"}" is not a registered capability` });
52
- return { ok: false, tool: name ?? null, denied, steps, provenance: "registry default-deny" };
53
- }
54
- const structural = hallucinationsIn({ name, input }, declaredList ?? [name]);
55
- for (const p of structural) {
56
- // no declared set supplied => "undeclared" isn't a real denial (we synthesised [name]).
57
- if (!declaredList && p.reason === "undeclared") continue;
58
- denied.push(p);
59
- }
60
-
61
- // Precondition check — the STRIPS safety gate, step by step.
62
- let candidateResults;
63
- for (const pre of preconditionsOf(name)) {
64
- if (pre.pred === PRECOND.graphLoaded) {
65
- steps.push({ step: "precondition", pred: pre.pred, ok: true });
66
- } else if (pre.pred === PRECOND.anyPresent) {
67
- const ok = pre.params.some((k) => input[k] !== undefined && input[k] !== null && String(input[k]).trim() !== "");
68
- steps.push({ step: "precondition", pred: pre.pred, params: pre.params, ok });
69
- if (!ok) denied.push({ reason: "missing-arg", detail: `${name} needs one of ${pre.params.join("|")}` });
70
- } else if (pre.pred === PRECOND.resolves) {
71
- const term = input[pre.param];
72
- const present = term !== undefined && term !== null && String(term).trim() !== "";
73
- if (!present) {
74
- steps.push({ step: "precondition", pred: pre.pred, param: pre.param, value: null, ok: false });
75
- continue;
76
- }
77
- // No oracle wired: assert the arg is PRESENT only, not that it binds.
78
- if (!ctx.resolve) {
79
- steps.push({ step: "precondition", pred: pre.pred, param: pre.param, value: term, ok: true, note: "structural-only (no resolver wired)" });
80
- continue;
81
- }
82
- const r = ctx.resolve(String(term));
83
- const resolvedOk = Boolean(r && r.match && !r.ambiguous);
84
- steps.push({
85
- step: "precondition", pred: pre.pred, param: pre.param, value: term,
86
- ok: resolvedOk,
87
- ...(r && r.match ? { boundTo: r.match.label, boundClass: r.match.class ?? null, tier: r.tier ?? null } : {}),
88
- ...(r && r.ambiguous ? { ambiguous: true } : {}),
89
- });
90
- if (!resolvedOk) {
91
- denied.push({
92
- reason: "unresolved",
93
- detail: r && r.ambiguous
94
- ? `${name}.${pre.param}="${term}" is ambiguous (narrow it)`
95
- : `${name}.${pre.param}="${term}" resolves to no graph entity`,
96
- });
97
- if (r && r.ambiguous) {
98
- const pool = [r.match, ...(r.candidates || [])].slice(0, 4);
99
- const dispatched = await dispatchEachCandidate(pool, name, pre.param, ctx);
100
- if (dispatched) candidateResults = dispatched;
101
- }
102
- }
103
- }
104
- }
105
-
106
- const ok = denied.length === 0;
107
- return {
108
- ok, tool: name, denied, steps, provenance: ok ? "resolvable (NOT proven antecedent-correct)" : "denied",
109
- ...(candidateResults ? { candidateResults } : {}),
110
- };
111
- }
112
-
113
- /** Convenience boolean: does a proposed tool_use PASS the guardrail? */
114
- export async function admits(toolUse, declaredNames = null, ctx = {}) {
115
- return (await guard(toolUse, declaredNames, ctx)).ok;
116
- }
@@ -1,12 +0,0 @@
1
- // vector.mjs — pure vector arithmetic over embedding vectors. No model, no fs:
2
- // the loader that reads weights off disk lives in src/adapters/embed.mjs.
3
-
4
- /** Cosine similarity. Over L2-normalised vectors this is just the dot product, but the
5
- * full form is kept so unnormalised test fixtures behave. 0 when either vector is zero. */
6
- export function cosine(a, b) {
7
- let dot = 0, na = 0, nb = 0;
8
- const n = Math.min(a.length, b.length);
9
- for (let i = 0; i < n; i++) { dot += a[i] * b[i]; na += a[i] * a[i]; nb += b[i] * b[i]; }
10
- if (na === 0 || nb === 0) return 0;
11
- return dot / (Math.sqrt(na) * Math.sqrt(nb));
12
- }