@polycode-projects/the-mechanical-code-talker 1.10.14 → 1.11.5
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 +121 -114
- package/ROADMAP.md +14 -4
- package/bin/tmct.mjs +167 -77
- package/data/games/crates.txt +24 -0
- package/data/games/hanoi-3.txt +30 -0
- package/data/games/river.txt +33 -0
- package/package.json +1 -1
- package/src/ask.mjs +119 -6
- package/src/chat.mjs +1515 -52
- package/src/codegraph.mjs +24 -11
- package/src/domain.mjs +350 -0
- package/src/import-file.mjs +86 -0
- package/src/init.mjs +46 -1
- package/src/interpret/normalize.mjs +46 -0
- package/src/interpret/strategies/keywords.mjs +13 -9
- package/src/ledger-viz.mjs +635 -0
- package/src/memory/core.mjs +100 -17
- package/src/memory/shacl.mjs +20 -7
- package/src/memory-ask-browser-entry.mjs +9 -7
- package/src/memory-ask-browser.bundle.js +5648 -1177
- package/src/plan-viz.mjs +409 -0
- package/src/router/drive.mjs +122 -13
- package/src/router/guardrail.mjs +5 -0
- package/src/router/registry.mjs +55 -11
- package/src/router/resolver.mjs +13 -0
- package/src/router/taught.mjs +84 -0
- package/src/sentences.mjs +19 -0
- package/src/syllogise.mjs +154 -38
- package/src/viz-theme.mjs +66 -0
- package/src/wink-model.mjs +12 -6
- package/src/ask-browser-entry.mjs +0 -19
- package/src/ask-browser.bundle.js +0 -5411
- package/src/viz.mjs +0 -959
package/src/ask.mjs
CHANGED
|
@@ -31,7 +31,7 @@ import { parseAnchored } from "./interpret/strategies/grammar.mjs";
|
|
|
31
31
|
import { parseKeywordSpot, findPhrase } from "./interpret/strategies/keywords.mjs";
|
|
32
32
|
import { runStrategiesSync } from "./interpret/pipeline.mjs";
|
|
33
33
|
import { mergeStrategyResults, alternateLines } from "./interpret/merge.mjs";
|
|
34
|
-
import { lookupByProseTokens } from "./prose.mjs";
|
|
34
|
+
import { lookupByProseTokens, splitIdentifierWords } from "./prose.mjs";
|
|
35
35
|
import { pickPhrase } from "./answer-variants.mjs";
|
|
36
36
|
|
|
37
37
|
// Normalization stays importable from its original site (tests + chat surface).
|
|
@@ -88,6 +88,20 @@ function nounFor(entityType, n) {
|
|
|
88
88
|
return n === 1 ? s : p;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/** A class enum rendered as prose words ("GlobalVariable" -> "global
|
|
92
|
+
* variable"), lowercase to match nounFor's own convention. For the render
|
|
93
|
+
* sites that must name the enum itself rather than a curated PLURAL_FORMS
|
|
94
|
+
* noun. The typeof guard is the same viewer-bundle boundary the tier-4 prose
|
|
95
|
+
* fallback documents: viz.mjs's askSource strips the prose.mjs import. */
|
|
96
|
+
export function classDisplayName(cls) {
|
|
97
|
+
const s = String(cls || "");
|
|
98
|
+
if (typeof splitIdentifierWords === "function") {
|
|
99
|
+
const words = splitIdentifierWords(s).join(" ");
|
|
100
|
+
if (words) return words;
|
|
101
|
+
}
|
|
102
|
+
return s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
// Every relation kind is already the correct 3rd-person-singular verb form
|
|
92
106
|
// except "cochange" ("X cochanges Y") and "reexports" (human word "export").
|
|
93
107
|
const REVERSE_MISS_VERB = { cochange: "cochanges", reexports: "export" };
|
|
@@ -2342,6 +2356,43 @@ function commitTouches(graph, commit, entityType, extra = {}) {
|
|
|
2342
2356
|
};
|
|
2343
2357
|
}
|
|
2344
2358
|
|
|
2359
|
+
// ---- entry-point survey for the where-defined shape: "where is the (main)
|
|
2360
|
+
// entry point defined" names a ROLE, not a label, so resolveObject can only
|
|
2361
|
+
// miss it (or accidentally substring-match something unrelated). A closed
|
|
2362
|
+
// basename vocabulary stands in for the role; every candidate is ranked
|
|
2363
|
+
// deterministically and the ranking is disclosed in the answer, never a
|
|
2364
|
+
// silently picked single winner. ----
|
|
2365
|
+
|
|
2366
|
+
/** The where-defined object phrasings that ask for the entry-point role. */
|
|
2367
|
+
const ENTRY_POINT_QUERY_RE = /^(?:the\s+)?(?:main\s+|primary\s+)?entry[\s-]?points?(?:\s+(?:of|to|for)\s+(?:this|the)\s+(?:codebase|code|repo|repository|project|app))?$/i;
|
|
2368
|
+
/** Module basenames conventionally used as a program's entry point. */
|
|
2369
|
+
const ENTRY_POINT_BASENAMES = new Set(["index", "main", "app", "server", "cli", "__main__"]);
|
|
2370
|
+
/** Directory segments marking test/fixture territory — ranked below real code. */
|
|
2371
|
+
const TEST_FIXTURE_PATH_SEGMENTS = new Set(["test", "tests", "__tests__", "fixture", "fixtures", "spec", "specs", "testdata"]);
|
|
2372
|
+
|
|
2373
|
+
const moduleStemOf = (label) => String(label).toLowerCase().split("/").pop().replace(/\.[a-z0-9]+$/, "");
|
|
2374
|
+
const isTestFixturePath = (label) => String(label).toLowerCase().split("/").slice(0, -1)
|
|
2375
|
+
.some((seg) => TEST_FIXTURE_PATH_SEGMENTS.has(seg));
|
|
2376
|
+
|
|
2377
|
+
/** All entry-point-basename Modules, best first: a basename the query itself
|
|
2378
|
+
* names ("MAIN entry point" -> main.mjs) beats root proximity (fewer path
|
|
2379
|
+
* segments), which beats a non-test path over a test/fixture one; a full tie
|
|
2380
|
+
* falls back to label order so the ranking is stable. */
|
|
2381
|
+
function rankEntryPointModules(graph, term) {
|
|
2382
|
+
const queryWords = new Set(String(term || "").toLowerCase().split(/[\s-]+/).filter(Boolean));
|
|
2383
|
+
return (graph.individuals || [])
|
|
2384
|
+
.filter((i) => i.class === "Module" && ENTRY_POINT_BASENAMES.has(moduleStemOf(i.label)))
|
|
2385
|
+
.map((ind) => ({
|
|
2386
|
+
ind,
|
|
2387
|
+
named: queryWords.has(moduleStemOf(ind.label)) ? 1 : 0,
|
|
2388
|
+
depth: String(ind.label).split("/").length,
|
|
2389
|
+
fixture: isTestFixturePath(ind.label) ? 1 : 0,
|
|
2390
|
+
}))
|
|
2391
|
+
.sort((a, b) => (b.named - a.named) || (a.depth - b.depth) || (a.fixture - b.fixture)
|
|
2392
|
+
|| String(a.ind.label).localeCompare(String(b.ind.label)))
|
|
2393
|
+
.map((x) => x.ind);
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2345
2396
|
/** Safety net: a {shape, kind, entityType} combination must be explicitly
|
|
2346
2397
|
* listed here to receive real non-"direct" modifier behavior; anything else
|
|
2347
2398
|
* gets an honest "not supported yet" response, never a silent fallback to
|
|
@@ -2414,6 +2465,18 @@ export function traverse(graph, parsed, { contextId = null, prev = null, pinnedO
|
|
|
2414
2465
|
};
|
|
2415
2466
|
}
|
|
2416
2467
|
|
|
2468
|
+
// where + an entry-point ROLE phrasing: checked before object resolution,
|
|
2469
|
+
// which could only miss the role term or accidentally land on an unrelated
|
|
2470
|
+
// partial match.
|
|
2471
|
+
if (shape === "where" && ENTRY_POINT_QUERY_RE.test(String(parsed.object || "").trim())) {
|
|
2472
|
+
const ranked = rankEntryPointModules(graph, parsed.object);
|
|
2473
|
+
return {
|
|
2474
|
+
matches: ranked, objMatch: ranked[0] || null, candidates: ranked.slice(1, 5),
|
|
2475
|
+
ambiguous: false, entryPointShape: true,
|
|
2476
|
+
traversal: `Module individuals with an entry-point basename (${[...ENTRY_POINT_BASENAMES].join("/")}), ranked query-named basename first, then shallower path, then non-test path`,
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2417
2480
|
// Checked before object resolution, so an unsupported modifier+kind
|
|
2418
2481
|
// combination gets its own honest capability-gap message.
|
|
2419
2482
|
if (parsed.modifier && parsed.modifier !== "direct" && !modifierIsWired(shape, kind, entityType)) {
|
|
@@ -2598,7 +2661,7 @@ export function traverse(graph, parsed, { contextId = null, prev = null, pinnedO
|
|
|
2598
2661
|
return {
|
|
2599
2662
|
matches: [], objMatch, candidates, ambiguous, matchedVia,
|
|
2600
2663
|
forwardGrainMiss: true, wantClasses: [...wantClasses],
|
|
2601
|
-
traversal: `${fwdKinds.join("+")} edges where subject = ${objMatch.label} (grain mismatch: this "${kind}" relation never targets a ${entityType})`,
|
|
2664
|
+
traversal: `${fwdKinds.join("+")} edges where subject = ${objMatch.label} (grain mismatch: this "${kind}" relation never targets a ${classDisplayName(entityType)})`,
|
|
2602
2665
|
};
|
|
2603
2666
|
}
|
|
2604
2667
|
}
|
|
@@ -2705,7 +2768,7 @@ export function traverse(graph, parsed, { contextId = null, prev = null, pinnedO
|
|
|
2705
2768
|
return {
|
|
2706
2769
|
matches: [], objMatch: gObjMatch, candidates: gCandidates, ambiguous: gAmbiguous, matchedVia: gMatchedVia,
|
|
2707
2770
|
wrongGrainMiss: true, wantClass,
|
|
2708
|
-
traversal: `"${parsed.object}" resolved to ${gObjMatch.class} ${gObjMatch.label} (grain mismatch: this "${kind}" question needs a ${wantClass}, and no containing module could be found to refine to)`,
|
|
2771
|
+
traversal: `"${parsed.object}" resolved to ${classDisplayName(gObjMatch.class)} ${gObjMatch.label} (grain mismatch: this "${kind}" question needs a ${classDisplayName(wantClass)}, and no containing module could be found to refine to)`,
|
|
2709
2772
|
};
|
|
2710
2773
|
}
|
|
2711
2774
|
} else {
|
|
@@ -2714,7 +2777,7 @@ export function traverse(graph, parsed, { contextId = null, prev = null, pinnedO
|
|
|
2714
2777
|
return {
|
|
2715
2778
|
matches: [], objMatch: gObjMatch, candidates: gCandidates, ambiguous: gAmbiguous, matchedVia: gMatchedVia,
|
|
2716
2779
|
wrongGrainMiss: true, wantClass,
|
|
2717
|
-
traversal: `"${parsed.object}" resolved to ${gObjMatch.class} ${gObjMatch.label} (grain mismatch: this "${kind}" question needs a ${wantClass})`,
|
|
2780
|
+
traversal: `"${parsed.object}" resolved to ${classDisplayName(gObjMatch.class)} ${gObjMatch.label} (grain mismatch: this "${kind}" question needs a ${classDisplayName(wantClass)})`,
|
|
2718
2781
|
};
|
|
2719
2782
|
}
|
|
2720
2783
|
}
|
|
@@ -2764,7 +2827,7 @@ export function traverse(graph, parsed, { contextId = null, prev = null, pinnedO
|
|
|
2764
2827
|
} else if (entityType !== "Module" && subjects.some((s) => s.class === "Module")) {
|
|
2765
2828
|
const moduleIds = new Set(subjects.filter((s) => s.class === "Module").map((s) => s.id));
|
|
2766
2829
|
matches = refineToEntities(graph, moduleIds, entityType);
|
|
2767
|
-
grainNote = `, then ${entityType} defined in the matched module(s)`;
|
|
2830
|
+
grainNote = `, then ${classDisplayName(entityType)} defined in the matched module(s)`;
|
|
2768
2831
|
} else {
|
|
2769
2832
|
matches = [];
|
|
2770
2833
|
}
|
|
@@ -2883,6 +2946,7 @@ function renderCore(parsed, result) {
|
|
|
2883
2946
|
return {
|
|
2884
2947
|
content: `this could mean more than one thing:\n${options}\n(ask one of these directly, or try rephrasing more specifically, to get just that reading)`,
|
|
2885
2948
|
miss: false, ambiguous: true, candidates: parsed.candidates.map(describeParse),
|
|
2949
|
+
candidateParses: parsed.candidates,
|
|
2886
2950
|
};
|
|
2887
2951
|
}
|
|
2888
2952
|
// Fallback (no `result.branches` — e.g. a caller invoking render() directly
|
|
@@ -2891,6 +2955,7 @@ function renderCore(parsed, result) {
|
|
|
2891
2955
|
return {
|
|
2892
2956
|
content: `this could mean more than one thing: ${options} — try rephrasing more specifically.`,
|
|
2893
2957
|
miss: false, ambiguous: true, candidates: parsed.candidates.map(describeParse),
|
|
2958
|
+
candidateParses: parsed.candidates,
|
|
2894
2959
|
};
|
|
2895
2960
|
}
|
|
2896
2961
|
if (result.unresolvedPronoun) {
|
|
@@ -2967,6 +3032,25 @@ function renderCore(parsed, result) {
|
|
|
2967
3032
|
miss: false, ambiguous: false, matches: result.matches,
|
|
2968
3033
|
};
|
|
2969
3034
|
}
|
|
3035
|
+
// entry-point survey: every candidate is disclosed with the rank order,
|
|
3036
|
+
// never a silently picked single winner; zero candidates is an honest miss
|
|
3037
|
+
// naming the closed basename vocabulary that was searched.
|
|
3038
|
+
if (result.entryPointShape) {
|
|
3039
|
+
if (!result.matches.length) {
|
|
3040
|
+
return {
|
|
3041
|
+
content: `no entry-point module found in the index — no module basename matches ${listJoin([...ENTRY_POINT_BASENAMES])}.`,
|
|
3042
|
+
miss: true, ambiguous: false, candidates: [],
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
const [top, ...rest] = result.matches;
|
|
3046
|
+
const shownRest = rest.slice(0, OVERFLOW_CAP).map((i) => i.label);
|
|
3047
|
+
const extra = rest.length > OVERFLOW_CAP ? `, …and ${rest.length - OVERFLOW_CAP} more` : "";
|
|
3048
|
+
const also = rest.length ? ` — also matched: ${listJoin(shownRest)}${extra}` : "";
|
|
3049
|
+
return {
|
|
3050
|
+
content: `ranked ${result.matches.length} entry-point match${result.matches.length === 1 ? "" : "es"}; top: ${top.label}${also}.`,
|
|
3051
|
+
miss: false, ambiguous: false, matches: result.matches,
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
2970
3054
|
if (!result.objMatch && (!result.candidates || result.candidates.length === 0) && parsed.shape !== "ask") {
|
|
2971
3055
|
// Name what kind of thing was looked for: a sha-shaped term reads as
|
|
2972
3056
|
// "commit", a dotted slash-free term as "symbol" (both keep priority over
|
|
@@ -3491,6 +3575,12 @@ function dynamicClassQuery(graph, query) {
|
|
|
3491
3575
|
// extract the term for the article-insertion fallback rather than duplicating it.
|
|
3492
3576
|
const BARE_META_WHATIS_RE = /^what\s+(?:is|are)\s+(?:an?\s+)?(.+?)[?.!\s]*$/i;
|
|
3493
3577
|
|
|
3578
|
+
// "what is X for" / "what is X used for" with a bare entity term — the lead
|
|
3579
|
+
// refuses a/an articles and pronouns so the vocabulary phrasings ("what is a
|
|
3580
|
+
// horse for", "what is it for") never read as an entity term; a leading "the"
|
|
3581
|
+
// is entity-term noise (resolveObject's own article strip) and is dropped.
|
|
3582
|
+
const WHATIS_FOR_FALLBACK_RE = /^what\s+is\s+(?:the\s+)?(?!(?:an?|it|this|that|these|those)\s)(.+?)\s+(?:used\s+)?for[?.!\s]*$/i;
|
|
3583
|
+
|
|
3494
3584
|
export function ask(graph, query, { contextId = null, nlp = undefined, prev = null } = {}) {
|
|
3495
3585
|
if (isHelpRequest(query)) {
|
|
3496
3586
|
return {
|
|
@@ -3540,6 +3630,29 @@ export function ask(graph, query, { contextId = null, nlp = undefined, prev = nu
|
|
|
3540
3630
|
}
|
|
3541
3631
|
}
|
|
3542
3632
|
}
|
|
3633
|
+
// "what is X for" — the purpose paraphrase of the same bare-whatis intent,
|
|
3634
|
+
// excluded from the block above (and from the phrasing frames) because
|
|
3635
|
+
// chat.mjs's module-overview lane owns this phrasing and gates on an ask()
|
|
3636
|
+
// miss. The meta reading is adopted ONLY when it actually answers (a unique
|
|
3637
|
+
// metaFallback entity); on a miss, every byte — parsed, canonical, the miss
|
|
3638
|
+
// text — stays exactly as the lane cascade expects. Article/pronoun-led
|
|
3639
|
+
// terms ("what is a horse for") belong to the memory-facts readers and are
|
|
3640
|
+
// never claimed.
|
|
3641
|
+
if (parsed === null && rendered.miss && !rendered.ambiguous) {
|
|
3642
|
+
const forM = normalizeQuery(String(query || "")).match(WHATIS_FOR_FALLBACK_RE);
|
|
3643
|
+
const forTerm = forM?.[1]?.trim();
|
|
3644
|
+
if (forTerm) {
|
|
3645
|
+
const forParsed = parseQuery(`what is a ${forTerm}`, { nlp });
|
|
3646
|
+
if (forParsed?.shape === "meta") {
|
|
3647
|
+
const forResult = traverse(graph, forParsed, { contextId, prev });
|
|
3648
|
+
const forRendered = render(forParsed, forResult);
|
|
3649
|
+
if (!forRendered.miss && !forRendered.ambiguous) {
|
|
3650
|
+
result = forResult;
|
|
3651
|
+
rendered = forRendered;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3543
3656
|
// If relaxation materially rewrote the query and produced a real answer,
|
|
3544
3657
|
// note it lightly so the reader knows how the question was read.
|
|
3545
3658
|
let content = (relaxed && !rendered.miss && relaxed.to !== relaxed.from)
|
|
@@ -3586,7 +3699,7 @@ export function ask(graph, query, { contextId = null, nlp = undefined, prev = nu
|
|
|
3586
3699
|
// edit-distance, announced in the content as "assuming you meant …");
|
|
3587
3700
|
// null for every literal-identifier tier.
|
|
3588
3701
|
matchedVia: result.matchedVia || null,
|
|
3589
|
-
...(rendered.ambiguous ? { candidates: rendered.candidates } : {}),
|
|
3702
|
+
...(rendered.ambiguous ? { candidates: rendered.candidates, candidateParses: rendered.candidateParses } : {}),
|
|
3590
3703
|
},
|
|
3591
3704
|
};
|
|
3592
3705
|
}
|