@polycode-projects/the-mechanical-code-talker 1.9.2 → 1.10.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 +441 -202
- package/bin/tmct.mjs +126 -1
- package/package.json +4 -2
- package/src/answer-variants.mjs +8 -36
- package/src/ask-browser-entry.mjs +5 -23
- package/src/ask-browser.bundle.js +1 -2
- package/src/ask-nlp.mjs +9 -23
- package/src/ask-vocab.mjs +139 -589
- package/src/ask.mjs +627 -1729
- package/src/chat.mjs +1684 -2872
- package/src/cli-args.mjs +14 -28
- package/src/codegraph.mjs +236 -644
- package/src/completions/complete.mjs +18 -62
- package/src/completions/graph-adapter.mjs +14 -60
- package/src/completions/group.mjs +12 -68
- package/src/completions/infer.mjs +38 -126
- package/src/completions/prune.mjs +17 -70
- package/src/completions/rank.mjs +16 -69
- package/src/completions/search.mjs +8 -31
- package/src/concept.mjs +32 -88
- package/src/conformance.mjs +11 -15
- package/src/corpus/conceptnet.mjs +31 -89
- package/src/corpus/templates.mjs +19 -45
- package/src/corpus/unknown-ingest.mjs +31 -92
- package/src/embed.mjs +10 -22
- package/src/extensions.mjs +50 -154
- package/src/finish.mjs +35 -91
- package/src/grammar/ace.mjs +16 -40
- package/src/grammar/assert.mjs +1 -1
- package/src/grammar/lexicon-core.json +1 -1
- package/src/grammar/lexicon.mjs +9 -27
- package/src/graph-merge.mjs +2 -3
- package/src/hash.mjs +6 -14
- package/src/index.mjs +6 -10
- package/src/init.mjs +38 -125
- package/src/interpret/fuzzy.mjs +10 -29
- package/src/interpret/merge.mjs +9 -27
- package/src/interpret/normalize.mjs +137 -585
- package/src/interpret/pipeline.mjs +23 -71
- package/src/interpret/strategies/ace.mjs +7 -31
- package/src/interpret/strategies/constructions.mjs +14 -41
- package/src/interpret/strategies/grammar.mjs +21 -60
- package/src/interpret/strategies/keywords.mjs +42 -131
- package/src/interpret/strategies/noise-strip.mjs +18 -89
- package/src/memory/bias.mjs +11 -54
- package/src/memory/blocks.mjs +18 -69
- package/src/memory/core.mjs +171 -591
- package/src/memory/fold.mjs +0 -0
- package/src/memory/inspect.mjs +7 -25
- package/src/memory/shacl.mjs +10 -39
- package/src/memory/trust.mjs +26 -127
- package/src/memory-ask-browser-entry.mjs +7 -30
- package/src/memory-ask-browser.bundle.js +1 -1
- package/src/paraphrase.mjs +20 -53
- package/src/planning.mjs +15 -157
- package/src/prose-nlp.mjs +4 -17
- package/src/prose.mjs +19 -67
- package/src/providers/bootstrap.mjs +1 -2
- package/src/providers/fixture.mjs +1 -2
- package/src/providers/graph-service.mjs +28 -59
- package/src/repository-interface.mjs +6 -8
- package/src/router/drive.mjs +183 -0
- package/src/router/goal-reasoner.mjs +66 -231
- package/src/router/guardrail.mjs +20 -58
- package/src/router/planner.mjs +15 -46
- package/src/router/registry.mjs +13 -43
- package/src/router/resolver.mjs +46 -131
- package/src/router/results.mjs +231 -0
- package/src/schema-docs.mjs +10 -27
- package/src/server-http.mjs +10 -19
- package/src/server.mjs +22 -28
- package/src/sessions.mjs +15 -30
- package/src/source-slice.mjs +5 -7
- package/src/source.mjs +10 -20
- package/src/syllogise.mjs +187 -575
- package/src/telemetry.mjs +3 -3
- package/src/toml-config.mjs +4 -4
- package/src/tui/app.mjs +9 -19
- package/src/viz.mjs +66 -123
- package/src/wink-model.mjs +10 -24
package/bin/tmct.mjs
CHANGED
|
@@ -103,6 +103,14 @@ Usage:
|
|
|
103
103
|
[--host <h>] [--port <n>] (POST /v1/messages) over the graph — a deterministic,
|
|
104
104
|
[--graph <path>] no-LLM "model" a tool-loop client can call; $0 usage.
|
|
105
105
|
[--config <path>] Defaults: host 127.0.0.1, port 8787. Ctrl+C to stop.
|
|
106
|
+
tmct plan "<request>" the capability router: compose/execute read-only graph-
|
|
107
|
+
[--repo <abs>] query tool calls for a compound or maintenance-goal
|
|
108
|
+
[--graph <path>] request ("of the modules impacted by X, which are
|
|
109
|
+
[--config <path>] untested", "what most needs a test") — a real STRIPS/
|
|
110
|
+
[--tools <a,b,...>] PDDL planner (src/router/*), never a guessed call.
|
|
111
|
+
[--json] Prints the grounded step sequence + composed answer,
|
|
112
|
+
or an honest "no plan found". --tools restricts the
|
|
113
|
+
declared toolset; --json prints the full loop result.
|
|
106
114
|
tmct cli <tool> '{…}' invoke a graph tool directly (carry-over, de-emphasized)
|
|
107
115
|
tmct cli digest '{…}' architecture map + per-module context bundles
|
|
108
116
|
tmct --help show this help
|
|
@@ -658,6 +666,22 @@ async function main() {
|
|
|
658
666
|
process.stderr.write(`tmct init: ${e?.message || e}\n`);
|
|
659
667
|
process.exit(2);
|
|
660
668
|
}
|
|
669
|
+
// `init` takes no positional argument at all, so a bare "sqlite"/"memory"/
|
|
670
|
+
// "default" here almost always means `npm run init --memory-backend X`
|
|
671
|
+
// was typed without the `--` separator npm needs — npm silently drops its
|
|
672
|
+
// own unrecognized flag and forwards only the bare word.
|
|
673
|
+
if (!memoryBackendVal) {
|
|
674
|
+
const strayBackendWord = rest.find((a) => ["default", "memory", "sqlite"].includes(a));
|
|
675
|
+
if (strayBackendWord) {
|
|
676
|
+
process.stderr.write(
|
|
677
|
+
`tmct init: read a bare "${strayBackendWord}" as --memory-backend ${strayBackendWord} — ` +
|
|
678
|
+
`npm likely dropped your --memory-backend flag because "npm run init --memory-backend ${strayBackendWord}" ` +
|
|
679
|
+
`needs a "--" before it: npm run init -- --memory-backend ${strayBackendWord} (or npm run init:sqlite, ` +
|
|
680
|
+
`or npx tmct init --memory-backend ${strayBackendWord} directly).\n`,
|
|
681
|
+
);
|
|
682
|
+
memoryBackendVal = strayBackendWord;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
661
685
|
|
|
662
686
|
// Resolve + validate EVERY pluggable input BEFORE touching disk — mirrors
|
|
663
687
|
// `--with-persona`'s own "validate before scaffolding" discipline, and the
|
|
@@ -1079,6 +1103,107 @@ async function main() {
|
|
|
1079
1103
|
return; // the listening server keeps the event loop alive
|
|
1080
1104
|
}
|
|
1081
1105
|
|
|
1106
|
+
if (mode === "plan") {
|
|
1107
|
+
// `tmct plan "<request>"` — the capability router (src/router/*), surfaced
|
|
1108
|
+
// for real: a STRIPS/PDDL-style planner over the SAME read-only graph-query
|
|
1109
|
+
// tools chat/serve dispatch. A single-shot request ("who calls X") resolves
|
|
1110
|
+
// directly; a compound request ("of the modules impacted by X, which are
|
|
1111
|
+
// untested", "assess Y and then check Z") decomposes into an ordered call
|
|
1112
|
+
// sequence with a causal-link proof, then folds the executed results into one
|
|
1113
|
+
// composed answer; a request neither stage grounds escalates to the
|
|
1114
|
+
// closed-world goal-reasoner (a maintenance-invariant deduction — coverage
|
|
1115
|
+
// gaps, change-coupling risk — never a keyword guess). Anything none of the
|
|
1116
|
+
// three grounds is an HONEST "no plan found", never a guessed call — same
|
|
1117
|
+
// "grounded or an honest miss" contract as every other tmct answer path.
|
|
1118
|
+
const rest = process.argv.slice(3);
|
|
1119
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
1120
|
+
process.stdout.write(
|
|
1121
|
+
"tmct plan \"<request>\" — the capability router: compose/execute read-only\n" +
|
|
1122
|
+
"graph-query tool calls for a compound or maintenance-goal request.\n\n" +
|
|
1123
|
+
"Usage:\n" +
|
|
1124
|
+
" tmct plan \"<request>\" [--repo <abs>] [--graph <path>] [--config <path>]\n" +
|
|
1125
|
+
" [--tools <name,name,...>] [--json]\n\n" +
|
|
1126
|
+
" --repo <abs> target a repo's graph (<abs>/.tmct/graph.json); default: git root/cwd\n" +
|
|
1127
|
+
" --graph <path> explicit graph file (repeatable — multiple graphs merge)\n" +
|
|
1128
|
+
" --config <path> an alternate tmct.toml location (a file or a directory)\n" +
|
|
1129
|
+
" --tools <list> restrict the declared toolset (comma-separated capability\n" +
|
|
1130
|
+
" names, e.g. tmct_impact,tmct_untested); default: every\n" +
|
|
1131
|
+
" registered capability\n" +
|
|
1132
|
+
" --json print the full machine-readable loop result instead of\n" +
|
|
1133
|
+
" the human-readable report (a non-zero exit still means\n" +
|
|
1134
|
+
" an honest refusal, not a crash)\n\n" +
|
|
1135
|
+
"Examples:\n" +
|
|
1136
|
+
" tmct plan \"of the modules impacted by src/lib/http.mjs, which are untested\"\n" +
|
|
1137
|
+
" tmct plan \"what most needs a test in this codebase\"\n" +
|
|
1138
|
+
" tmct plan \"who calls createApp\"\n",
|
|
1139
|
+
);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const { strFlag, resolveRuntimeConfig } = await import("../src/cli-args.mjs");
|
|
1143
|
+
const toolsFlag = strFlag(rest, ["--tools"]);
|
|
1144
|
+
const jsonFlag = rest.includes("--json");
|
|
1145
|
+
// The request is every argv token that isn't a recognized flag or its value,
|
|
1146
|
+
// rejoined with single spaces — so both `tmct plan "quoted request"` and a
|
|
1147
|
+
// bare `tmct plan unquoted request words` work.
|
|
1148
|
+
const FLAG_WITH_VALUE = new Set(["--repo", "--graph", "--config", "--tools"]);
|
|
1149
|
+
const requestParts = [];
|
|
1150
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
1151
|
+
const a = rest[i];
|
|
1152
|
+
if (FLAG_WITH_VALUE.has(a)) { i += 1; continue; }
|
|
1153
|
+
if (a === "--json") continue;
|
|
1154
|
+
requestParts.push(a);
|
|
1155
|
+
}
|
|
1156
|
+
const request = requestParts.join(" ").trim();
|
|
1157
|
+
if (!request) {
|
|
1158
|
+
process.stderr.write("tmct plan: needs a request, e.g. `tmct plan \"of the modules impacted by X, which are untested\"`\n");
|
|
1159
|
+
process.exit(2);
|
|
1160
|
+
}
|
|
1161
|
+
const { config } = await resolveRuntimeConfig({ argv: rest });
|
|
1162
|
+
const { buildCapabilityPlanCtx, runCapabilityPlan, declaredCapabilityNames } = await import("../src/router/drive.mjs");
|
|
1163
|
+
const declared = declaredCapabilityNames();
|
|
1164
|
+
let tools = declared;
|
|
1165
|
+
if (toolsFlag) {
|
|
1166
|
+
tools = toolsFlag.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1167
|
+
const unknown = tools.filter((t) => !declared.includes(t));
|
|
1168
|
+
if (unknown.length) {
|
|
1169
|
+
process.stderr.write(`tmct plan: unknown --tools name(s): ${unknown.join(", ")}. Registered capabilities: ${declared.join(", ")}.\n`);
|
|
1170
|
+
process.exit(2);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
let ctx;
|
|
1174
|
+
try {
|
|
1175
|
+
ctx = await buildCapabilityPlanCtx({ config });
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
process.stderr.write(`tmct plan: could not load the graph — ${e?.message || e}\n`);
|
|
1178
|
+
process.exit(1);
|
|
1179
|
+
}
|
|
1180
|
+
const result = await runCapabilityPlan(request, tools, ctx);
|
|
1181
|
+
if (jsonFlag) {
|
|
1182
|
+
process.stdout.write(JSON.stringify({ request, ...result }, null, 2) + "\n");
|
|
1183
|
+
process.exit(result.refused ? 1 : 0);
|
|
1184
|
+
}
|
|
1185
|
+
process.stdout.write(`tmct plan: "${request}"\n`);
|
|
1186
|
+
if (result.refused) {
|
|
1187
|
+
process.stdout.write(`no plan found — ${Array.isArray(result.why) ? result.why.join("; ") : result.why}\n`);
|
|
1188
|
+
if (result.c1Why) {
|
|
1189
|
+
process.stdout.write(`(the direct router also declined: ${Array.isArray(result.c1Why) ? result.c1Why.join("; ") : result.c1Why})\n`);
|
|
1190
|
+
}
|
|
1191
|
+
process.exit(1);
|
|
1192
|
+
}
|
|
1193
|
+
process.stdout.write(`driver: ${result.driver}\n\nsteps:\n`);
|
|
1194
|
+
for (let i = 0; i < result.calls.length; i += 1) {
|
|
1195
|
+
const c = result.calls[i];
|
|
1196
|
+
let text = "";
|
|
1197
|
+
try { text = await ctx.dispatch(c.name, c.input || {}).then((r) => (r.ok ? r.text : `(unresolved: ${r.error})`)); }
|
|
1198
|
+
catch (e) { text = `(error: ${e?.message || e})`; }
|
|
1199
|
+
process.stdout.write(` ${i + 1}. ${c.name} ${JSON.stringify(c.input || {})}\n ${String(text).split("\n").join("\n ")}\n`);
|
|
1200
|
+
}
|
|
1201
|
+
if (result.composed !== undefined && result.composed !== null) {
|
|
1202
|
+
process.stdout.write(`\ncomposed answer (${result.composed.length}): ${result.composed.length ? result.composed.join(", ") : "(empty set)"}\n`);
|
|
1203
|
+
}
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1082
1207
|
if (mode === "cli") {
|
|
1083
1208
|
await runCliMode();
|
|
1084
1209
|
return;
|
|
@@ -1087,7 +1212,7 @@ async function main() {
|
|
|
1087
1212
|
// An unknown mode gets the instructive usage line and exit 2. (A bare invocation
|
|
1088
1213
|
// never lands here — the argv splice above rewrote it to `chat`.)
|
|
1089
1214
|
process.stderr.write(`tmct: unknown invocation "${process.argv.slice(2).join(" ")}". ` +
|
|
1090
|
-
"Use `chat`, `memory`, `init`, `import`, `extend --validate`, `syllogise`, `serve`, `cli digest …`, or `cli <tool> …`.\n");
|
|
1215
|
+
"Use `chat`, `memory`, `init`, `import`, `extend --validate`, `syllogise`, `serve`, `plan`, `cli digest …`, or `cli <tool> …`.\n");
|
|
1091
1216
|
process.exit(2);
|
|
1092
1217
|
}
|
|
1093
1218
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
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.",
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
"./extensions": "./src/extensions.mjs",
|
|
59
59
|
"./toml-config": "./src/toml-config.mjs",
|
|
60
60
|
"./generateCompletion": "./src/completions/complete.mjs",
|
|
61
|
-
"./createCompletionsGraphAdapter": "./src/completions/graph-adapter.mjs"
|
|
61
|
+
"./createCompletionsGraphAdapter": "./src/completions/graph-adapter.mjs",
|
|
62
|
+
"./plan": "./src/router/drive.mjs"
|
|
62
63
|
},
|
|
63
64
|
"files": [
|
|
64
65
|
"bin/",
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
"chat": "node bin/tmct.mjs",
|
|
86
87
|
"chat:repo": "node bin/tmct.mjs chat --repo",
|
|
87
88
|
"init": "node bin/tmct.mjs init",
|
|
89
|
+
"init:sqlite": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --memory-backend sqlite",
|
|
88
90
|
"init:persona:human": "node bin/tmct.mjs init --with-persona human",
|
|
89
91
|
"init:persona:empty": "node bin/tmct.mjs init --with-persona empty",
|
|
90
92
|
"init:large": "node bin/tmct.mjs init && node bin/tmct.mjs import --corpus seon && node bin/tmct.mjs import --corpus conceptnet && node bin/tmct.mjs import --corpus aws && node bin/tmct.mjs import --corpus python && node bin/tmct.mjs import --corpus java",
|
package/src/answer-variants.mjs
CHANGED
|
@@ -1,33 +1,10 @@
|
|
|
1
|
-
// answer-variants.mjs — deterministic, committed answer-phrasing variety
|
|
2
|
-
//
|
|
3
|
-
// What's next item / archive/PLAN_TEMPLATE_COVERAGE.md's stated follow-on).
|
|
1
|
+
// answer-variants.mjs — deterministic, committed answer-phrasing variety for
|
|
2
|
+
// a curated set of answer templates (answer-variants.json).
|
|
4
3
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// rephrase-hint template. Each pool was hand-reviewed for meaning-safety in
|
|
10
|
-
// its SPECIFIC rendered sentence (see answer-variants.json's own "curation"
|
|
11
|
-
// field) -- WordNet was consulted but its sense-1 lookups mostly resolved to
|
|
12
|
-
// the wrong sense for this domain-specific connector vocabulary, so nothing
|
|
13
|
-
// here was auto-accepted from a raw synset match.
|
|
14
|
-
//
|
|
15
|
-
// Selection is a pure hash, never Math.random/Date.now: the SAME (poolId, key)
|
|
16
|
-
// always picks the SAME form, so the same query against the same entity
|
|
17
|
-
// renders byte-identical text every time (pinnable in tests), while different
|
|
18
|
-
// entities/answers spread across the pool's forms. This is the ONE place that
|
|
19
|
-
// hash-and-pick logic lives -- every call site below imports pickPhrase
|
|
20
|
-
// rather than re-deriving its own index arithmetic.
|
|
21
|
-
//
|
|
22
|
-
// Lazy + failure-tolerant load (chat.mjs's own "a turn never crashes" ethos,
|
|
23
|
-
// see chatTemplates()): a missing/corrupt data file, or `node:fs` simply
|
|
24
|
-
// being unavailable, degrades to always returning `base` -- never a throw.
|
|
25
|
-
// That second case is load-bearing, not theoretical: ask.mjs is bundled
|
|
26
|
-
// whole into the browser "Ask the graph" panel (scripts/build-ask-bundle.mjs
|
|
27
|
-
// stubs node:fs's readFileSync as a function that THROWS when called, so the
|
|
28
|
-
// browser bundle still boots; this module's readFileSync call only ever
|
|
29
|
-
// happens lazily inside pickPhrase, wrapped in try/catch, exactly so that
|
|
30
|
-
// throw is caught here and never reaches the caller.
|
|
4
|
+
// Selection is a pure hash of (poolId, key), never Math.random/Date.now, so
|
|
5
|
+
// output is byte-identical across runs (pinnable in tests). Load is lazy and
|
|
6
|
+
// failure-tolerant (missing file, or a stubbed node:fs in the browser bundle,
|
|
7
|
+
// both degrade to returning `base`) rather than throwing.
|
|
31
8
|
|
|
32
9
|
import { readFileSync } from "node:fs";
|
|
33
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -48,13 +25,8 @@ function loadData() {
|
|
|
48
25
|
}
|
|
49
26
|
|
|
50
27
|
/** Deterministically choose `base` or one of `poolId`'s committed variants,
|
|
51
|
-
* keyed on `key
|
|
52
|
-
*
|
|
53
|
-
* stable string, e.g. a graph-summary key, for templates with no single
|
|
54
|
-
* entity). Same (poolId, key) always returns the same string; an unknown
|
|
55
|
-
* pool, a missing/unreadable data file, or a falsy `key` all fall back to
|
|
56
|
-
* `base` unconditionally -- variety is strictly additive, never a way to
|
|
57
|
-
* lose the original wording. */
|
|
28
|
+
* keyed on `key`. Any failure (unknown pool, unreadable data, falsy `key`)
|
|
29
|
+
* falls back to `base`. */
|
|
58
30
|
export function pickPhrase(poolId, key, base) {
|
|
59
31
|
if (!key) return base;
|
|
60
32
|
const data = loadData();
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
// ask-browser-entry.mjs — the esbuild entry for `tmct viz`'s embedded "Ask the
|
|
2
|
-
// graph" chat panel
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// Precedent: seonix's own `src/ask-browser-entry.mjs` (PLAN_CHAT_EXTRACTION.md
|
|
7
|
-
// Stage 5) proved this exact approach in production — esbuild + a Node-builtin
|
|
8
|
-
// stub plugin bundles tmct's real ask() into a single browser IIFE. This entry
|
|
9
|
-
// mirrors that pattern, adapted for tmct bundling its OWN ask.mjs directly
|
|
10
|
-
// (no external package import needed — we're already inside the source tree)
|
|
11
|
-
// and extended with the graph-traversal exports `tmct viz`'s own client-side
|
|
12
|
-
// re-focus/re-walk needs (spiralExpand etc. — seonix's code-graph viewer
|
|
13
|
-
// recomputes depth with its own hand-rolled client-side BFS; tmct's viewer
|
|
14
|
-
// reuses the real spiralExpand instead, so the browser walk is byte-identical
|
|
15
|
-
// to the CLI's).
|
|
2
|
+
// graph" chat panel: a real NL chat running client-side against the embedded
|
|
3
|
+
// graph, via tmct's own JS engine (esbuild + a Node-builtin stub plugin
|
|
4
|
+
// bundles tmct's real ask() into a single browser IIFE).
|
|
16
5
|
//
|
|
17
6
|
import { ask, parseQuery } from "./ask.mjs";
|
|
18
7
|
import {
|
|
@@ -21,15 +10,8 @@ import {
|
|
|
21
10
|
edgeKindsFor, collapseToTopN,
|
|
22
11
|
} from "./codegraph.mjs";
|
|
23
12
|
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// collapseToTopN are exported here (alongside the pre-existing traversal
|
|
27
|
-
// exports) so the viewer page's client-side recentre/edge-kind-toggle/
|
|
28
|
-
// dimension-switcher (a user double-clicking a node, changing which edge
|
|
29
|
-
// kinds the walk follows, or flipping the legend from "split by predicate"
|
|
30
|
-
// to "split by trust source") re-derives the SAME term graph, kind set, and
|
|
31
|
-
// legend the CLI's own generation-time computeVizGraph used — never a second
|
|
32
|
-
// hand-rolled copy that could drift.
|
|
13
|
+
// Exported so the viewer page's client-side recentre/edge-kind-toggle/
|
|
14
|
+
// dimension-switcher reuses the same computation as the CLI, not a second copy.
|
|
33
15
|
globalThis.tmctViz = {
|
|
34
16
|
ask, parseQuery, parseEntities, spiralExpand, mostRecentIndividual, derivedUpdatedAt,
|
|
35
17
|
MEMORY_SPIRAL_EXPAND_KINDS, MEMORY_FACT_LINK_KINDS, buildVizNodesAndEdges,
|
|
@@ -1837,8 +1837,7 @@
|
|
|
1837
1837
|
// (neither recognizes the bare "is in" idiom once "else" sits in front of it).
|
|
1838
1838
|
// The only rescue was the relaxation cascade's drop-unmatched layer — but that
|
|
1839
1839
|
// layer refuses to accept a relaxed reading that still renders an honest EMPTY
|
|
1840
|
-
//
|
|
1841
|
-
// another kind of miss), so a genuinely empty class ("what else is in
|
|
1840
|
+
// , so a genuinely empty class ("what else is in
|
|
1842
1841
|
// Task.complete" — a method, no members) bottomed out at the bare grammar
|
|
1843
1842
|
// wall instead of the specific "no contains edges" receipt. Routing this
|
|
1844
1843
|
// frame onto the SAME direct "what does X contain" path the plain "what is
|
package/src/ask-nlp.mjs
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
// ask-nlp.mjs — the
|
|
2
|
-
//
|
|
3
|
-
// wink-nlp and wink-eng-lite-web-model are loaded through the shared leaf loader
|
|
4
|
-
// src/wink-model.mjs (Node `createRequire` fallback + a browser registration seam),
|
|
5
|
-
// so this file no longer carries its own Node-only load block. The load happens
|
|
6
|
-
// lazily on first use and failure is cached as null: a checkout without the optional
|
|
7
|
-
// deps installed answers exactly like the browser bundle, it never throws.
|
|
1
|
+
// ask-nlp.mjs — the optional wink-nlp adapter behind ask.mjs's lemma/POS tier.
|
|
2
|
+
// Loaded lazily via src/wink-model.mjs; failure is cached as null, never a throw.
|
|
8
3
|
|
|
9
4
|
import { winkInstance } from "./wink-model.mjs";
|
|
10
5
|
|
|
11
|
-
let cached; // undefined = not tried yet; null = unavailable
|
|
6
|
+
let cached; // undefined = not tried yet; null = unavailable
|
|
12
7
|
|
|
13
|
-
/** Lazily build the {lemma, posTags} adapter, or null when wink isn't loadable.
|
|
14
|
-
* Deterministic: wink-nlp's tagger/lemmatiser is a fixed model with no sampling,
|
|
15
|
-
* so the same input always yields the same tags/lemmas across processes. */
|
|
8
|
+
/** Lazily build the {lemma, posTags} adapter, or null when wink isn't loadable. */
|
|
16
9
|
export function nlpAdapter() {
|
|
17
10
|
if (cached !== undefined) return cached;
|
|
18
11
|
try {
|
|
@@ -20,8 +13,7 @@ export function nlpAdapter() {
|
|
|
20
13
|
if (!nlp) { cached = null; return cached; }
|
|
21
14
|
const its = nlp.its;
|
|
22
15
|
cached = {
|
|
23
|
-
/** Lowercase lemma of a single token ("imported" -> "import")
|
|
24
|
-
* itself when wink has nothing better (unknown words come back as-is). */
|
|
16
|
+
/** Lowercase lemma of a single token ("imported" -> "import"). */
|
|
25
17
|
lemma(word) {
|
|
26
18
|
const w = String(word || "");
|
|
27
19
|
try {
|
|
@@ -31,12 +23,7 @@ export function nlpAdapter() {
|
|
|
31
23
|
return w.toLowerCase();
|
|
32
24
|
}
|
|
33
25
|
},
|
|
34
|
-
/** True when wink's lexicon flags the word as an English stop word
|
|
35
|
-
* ("anyway", "well", "also", …). Consulted by the noise-strip strategy
|
|
36
|
-
* (interpret/strategies/noise-strip.mjs) as its wink tier — the strategy's
|
|
37
|
-
* own KEEP set screens out the grammar's load-bearing words (which/what/
|
|
38
|
-
* does/…) BEFORE this is asked, so wink flagging a question word is
|
|
39
|
-
* harmless by construction. False on any surprise, never a throw. */
|
|
26
|
+
/** True when wink's lexicon flags the word as an English stop word. */
|
|
40
27
|
isStopWord(word) {
|
|
41
28
|
try {
|
|
42
29
|
const out = nlp.readDoc(String(word || "")).tokens().out(its.stopWordFlag);
|
|
@@ -45,10 +32,9 @@ export function nlpAdapter() {
|
|
|
45
32
|
return false;
|
|
46
33
|
}
|
|
47
34
|
},
|
|
48
|
-
/** UPOS tags aligned to the
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* sub-token's tag; null per word on any surprise, never a throw. */
|
|
35
|
+
/** UPOS tags aligned to the caller's word array. wink re-tokenizes, so
|
|
36
|
+
* each input word is greedily matched to the run of wink tokens that
|
|
37
|
+
* spell it and takes its first sub-token's tag. */
|
|
52
38
|
posTags(words) {
|
|
53
39
|
try {
|
|
54
40
|
const toks = nlp.readDoc(words.join(" ")).tokens();
|