@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
|
@@ -1,75 +1,32 @@
|
|
|
1
|
-
// interpret/pipeline.mjs — the multi-strategy interpretation pipeline
|
|
2
|
-
//
|
|
3
|
-
// with each class's own strategy, then merge same-class results and surround
|
|
1
|
+
// interpret/pipeline.mjs — the multi-strategy interpretation pipeline: run
|
|
2
|
+
// the request through every strategy, merge same-class results, and surround
|
|
4
3
|
// distinct-class results with "if you mean X then …" (interpret/merge.mjs).
|
|
5
4
|
//
|
|
6
|
-
// A STRATEGY is
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// `
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// `run` may be sync or async (Promise-returning); a strategy that THROWS or
|
|
13
|
-
// rejects is dropped for that request — one broken strategy never takes the
|
|
14
|
-
// pipeline down. Strategies are registered in the STRATEGIES array below in
|
|
15
|
-
// PRECEDENCE order (earlier wins same-class dedupe ties and confidence ties) —
|
|
16
|
-
// a new strategy (e.g. the Phase-2 ACE grammar, interpret/strategies/ace.mjs)
|
|
17
|
-
// joins by pushing an entry here, not by editing the pipeline.
|
|
18
|
-
//
|
|
19
|
-
// NORMALIZATION PRE-PASS (ROADMAP item 10): interpret() normalizes the input
|
|
20
|
-
// once (normalizeInput below — the same §3.5 pipeline + rhetorical frames
|
|
21
|
-
// ask.mjs always ran) and hands every strategy the normalized text; the raw
|
|
22
|
-
// text rides along in ctx.raw, and the returned record says whether
|
|
23
|
-
// normalization changed the input (`normalizationChanged`), so a repaired
|
|
24
|
-
// spelling/contraction is on the record, never silent.
|
|
5
|
+
// A STRATEGY is { id, class, run(text, ctx) -> {strategyId, class,
|
|
6
|
+
// candidates:[{parsed, confidence?, note?, via?}]} | null }. `via` marks an
|
|
7
|
+
// APPROXIMATE reading ("fuzzy"/"lemma"/"spell"), discarded whenever anything
|
|
8
|
+
// parsed exactly. `run` may be sync or async; a throwing/rejecting strategy
|
|
9
|
+
// is dropped rather than taking the pipeline down. Registered in STRATEGIES
|
|
10
|
+
// below, in precedence order.
|
|
25
11
|
|
|
26
12
|
import { normalizeQuery, applyNegationFrames, applyPhrasingFrames } from "./normalize.mjs";
|
|
27
13
|
import { grammarStrategy } from "./strategies/grammar.mjs";
|
|
28
14
|
import { keywordSpotStrategy } from "./strategies/keywords.mjs";
|
|
29
15
|
import { noiseStripStrategy } from "./strategies/noise-strip.mjs";
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// this import; the `typeof` guard where STRATEGIES is built then degrades to an
|
|
34
|
-
// ace-less registry instead of throwing over an undeclared identifier. (ACE is
|
|
35
|
-
// async-only anyway, so the sync parseQuery path the viewer uses never ran it.)
|
|
16
|
+
// The next three are Node-only (fs/path-reading tooling); an inlining viewer
|
|
17
|
+
// bundle strips them, and the `typeof` guards below degrade to a leaner
|
|
18
|
+
// registry instead of throwing on an undeclared identifier.
|
|
36
19
|
import { aceStrategy } from "./strategies/ace.mjs";
|
|
37
|
-
// Optional Node-only construction-grammar bank (PLAN_ADVANCED_GRAMMAR.md track
|
|
38
|
-
// (d)) — same viewer-bundle boundary as ACE and the wink adapter: the loader
|
|
39
|
-
// reads its committed TOML data via Node fs/path (interpret/strategies/
|
|
40
|
-
// constructions.mjs), so an inlining viewer bundle strips this import too; the
|
|
41
|
-
// same `typeof` guard below degrades to a constructions-less registry instead
|
|
42
|
-
// of throwing over an undeclared identifier (test/ask-nlp.test.mjs's "viewer
|
|
43
|
-
// bundle without wink" test proves the boundary — its bundled file list never
|
|
44
|
-
// includes this strategy file, matching ace.mjs's own exclusion there).
|
|
45
20
|
import { constructionsStrategy } from "./strategies/constructions.mjs";
|
|
46
21
|
import { mergeStrategyResults } from "./merge.mjs";
|
|
47
|
-
// Optional Node-only wink adapter — same viewer-bundle boundary as ask.mjs: an
|
|
48
|
-
// inlining bundle strips this import and the `typeof` read below degrades to
|
|
49
|
-
// adapter-less parsing instead of throwing over an undeclared identifier.
|
|
50
22
|
import { nlpAdapter } from "../ask-nlp.mjs";
|
|
51
23
|
|
|
52
|
-
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* the ACE-OWL controlled-fragment grammar, registered here as an ADDITIVE, own-
|
|
59
|
-
* class ("ace-fact") strategy. It is ASYNC on purpose: runStrategiesSync (the
|
|
60
|
-
* parseQuery / CHATBENCH-facing path) SKIPS Promise-returning strategies, so ACE
|
|
61
|
-
* adds declarative-fragment reach to interpret() while leaving the sync spine
|
|
62
|
-
* byte-stable (see strategies/ace.mjs for the full rationale). The `typeof` guard
|
|
63
|
-
* mirrors the nlpAdapter degradation: a stripped ACE import (viewer bundle) leaves
|
|
64
|
-
* the identifier undeclared, so the registry is ace-less there instead of a crash.
|
|
65
|
-
* interpret/strategies/constructions.mjs (PLAN_ADVANCED_GRAMMAR.md track (d)) is
|
|
66
|
-
* the construction-grammar template bank — data-driven (data/templates/
|
|
67
|
-
* constructions/*.toml), registered as its own additive, own-class
|
|
68
|
-
* ("construction") strategy at grammar-level confidence (0.9) so it outranks a
|
|
69
|
-
* same-text keyword-spot guess outright instead of colliding with it (see that
|
|
70
|
-
* file's header for why: keyword-spot mis-parses the genitive/compound surface
|
|
71
|
-
* forms this bank exists to fix). Sync (unlike ACE), so it participates on the
|
|
72
|
-
* parseQuery path too, not just interpret(). */
|
|
24
|
+
/** Registered strategies, in precedence order: grammar + keyword-spot (shared
|
|
25
|
+
* "graph-query" class), noise-strip (fires only when the anchored grammar
|
|
26
|
+
* misses), then the optional ACE and construction-grammar strategies, each
|
|
27
|
+
* in its own class so it surfaces as an alternate rather than colliding with
|
|
28
|
+
* a graph-query guess. ACE is async-only, so it participates via interpret()
|
|
29
|
+
* only, not the sync parseQuery path. */
|
|
73
30
|
// eslint-disable-next-line no-undef
|
|
74
31
|
const OPTIONAL_STRATEGIES = [
|
|
75
32
|
...(typeof aceStrategy !== "undefined" ? [aceStrategy] : []),
|
|
@@ -78,9 +35,8 @@ const OPTIONAL_STRATEGIES = [
|
|
|
78
35
|
];
|
|
79
36
|
export const STRATEGIES = [grammarStrategy, keywordSpotStrategy, noiseStripStrategy, ...OPTIONAL_STRATEGIES];
|
|
80
37
|
|
|
81
|
-
/**
|
|
82
|
-
*
|
|
83
|
-
* before any strategy runs. Returns {raw, text, changed}. */
|
|
38
|
+
/** Whitespace-collapse + normalization pipeline + rhetorical-frame rewrites,
|
|
39
|
+
* applied once before any strategy runs. Returns {raw, text, changed}. */
|
|
84
40
|
export function normalizeInput(input) {
|
|
85
41
|
const raw = String(input || "").trim().replace(/\s+/g, " ");
|
|
86
42
|
const text = raw ? applyPhrasingFrames(applyNegationFrames(normalizeQuery(raw))) : "";
|
|
@@ -91,10 +47,8 @@ function defaultNlp() {
|
|
|
91
47
|
return typeof nlpAdapter === "function" ? nlpAdapter() : null;
|
|
92
48
|
}
|
|
93
49
|
|
|
94
|
-
/** Synchronous strategy run
|
|
95
|
-
*
|
|
96
|
-
* strategy can only participate via interpret()); a throwing strategy is
|
|
97
|
-
* dropped, never a crash. Returns the strategy results in precedence order. */
|
|
50
|
+
/** Synchronous strategy run (ask.mjs's parseQuery path). Skips a Promise-
|
|
51
|
+
* returning strategy and drops a throwing one; returns results in precedence order. */
|
|
98
52
|
export function runStrategiesSync(text, ctx = {}, strategies = STRATEGIES) {
|
|
99
53
|
const results = [];
|
|
100
54
|
for (const s of strategies) {
|
|
@@ -117,10 +71,8 @@ export function runStrategiesSync(text, ctx = {}, strategies = STRATEGIES) {
|
|
|
117
71
|
* // {ambiguousParse, candidates} tie), or null
|
|
118
72
|
* alternates } // distinct-class runners-up for the
|
|
119
73
|
* // "if you mean X then …" surround
|
|
120
|
-
* `ctx.strategies`
|
|
121
|
-
*
|
|
122
|
-
* given (text, strategies, adapter) — no graph access here: resolving terms
|
|
123
|
-
* against a graph stays ask.mjs's job downstream. */
|
|
74
|
+
* `ctx.strategies`/`ctx.nlp` override the registry/lemma adapter. No graph
|
|
75
|
+
* access here — resolving terms against a graph is ask.mjs's job. */
|
|
124
76
|
export async function interpret(text, ctx = {}) {
|
|
125
77
|
const strategies = ctx.strategies || STRATEGIES;
|
|
126
78
|
const { raw, text: normalized, changed } = normalizeInput(text);
|
|
@@ -1,34 +1,11 @@
|
|
|
1
|
-
// interpret/strategies/ace.mjs — the ACE-OWL controlled-fragment grammar
|
|
2
|
-
// into the
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// ADAPTER was the "real and empty" seam the pipeline header names (interpret/
|
|
6
|
-
// pipeline.mjs). This file fills it. The contract is strictly ADD-ONLY:
|
|
7
|
-
//
|
|
8
|
-
// · Its own class, "ace-fact" — DISJOINT from the graph-query strategies, so a
|
|
9
|
-
// clean ACE parse is a distinct-class ALTERNATE ("if you mean X then …"), never
|
|
10
|
-
// a same-class competitor that could displace a graph-query winner.
|
|
11
|
-
// · It emits a candidate ONLY on a CLEAN parse (parseAce returns triples). A
|
|
12
|
-
// structural-fit-with-residue (empty triples) or a total miss returns null, so
|
|
13
|
-
// a query sentence that merely LOOKS relation-shaped ("which modules import X",
|
|
14
|
-
// whose ACE residue is the "which") contributes nothing — fitting the grammar
|
|
15
|
-
// is a strong signal; missing it is a FEATURE and the tolerant strategies win.
|
|
16
|
-
//
|
|
17
|
-
// WHY ASYNC — the byte-stability guarantee. ask.mjs's parseQuery (the CHATBENCH
|
|
18
|
-
// chat-facing path) runs strategies through runStrategiesSync, which — by the
|
|
19
|
-
// pipeline's documented contract — SKIPS any Promise-returning strategy ("an async
|
|
20
|
-
// strategy can only participate via interpret()"). Registering ACE async therefore
|
|
21
|
-
// makes the sync parseQuery path PROVABLY untouched (CHATBENCH neutral, byte-for-
|
|
22
|
-
// byte) while interpret() — the async pipeline — gains the declarative-fragment
|
|
23
|
-
// reach. The work parseAce does is synchronous; the async wrapper is deliberate,
|
|
24
|
-
// the mechanical seam that keeps the chat spine frozen. (grammar/ace.mjs itself is
|
|
25
|
-
// imported UNCHANGED — no chat-facing edit.)
|
|
1
|
+
// interpret/strategies/ace.mjs — the ACE-OWL controlled-fragment grammar
|
|
2
|
+
// (src/grammar/ace.mjs) wired into the pipeline as an additive strategy: its
|
|
3
|
+
// own class ("ace-fact"), so a clean parse surfaces as an alternate, never
|
|
4
|
+
// displacing a graph-query winner.
|
|
26
5
|
|
|
27
6
|
import { parseAce } from "../../grammar/ace.mjs";
|
|
28
7
|
|
|
29
|
-
/**
|
|
30
|
-
* (residue-only structural fit, or a hard miss) -> null. `via:"exact"` — a
|
|
31
|
-
* controlled-grammar fit is exact evidence, never an approximate rewrite. */
|
|
8
|
+
/** A clean ACE parse -> one candidate; a residue-only fit or a miss -> null. */
|
|
32
9
|
export function runAce(text) {
|
|
33
10
|
let parsed = null;
|
|
34
11
|
try { parsed = parseAce(text); } catch { return null; }
|
|
@@ -36,9 +13,8 @@ export function runAce(text) {
|
|
|
36
13
|
return { strategyId: "ace", class: "ace-fact", candidates: [{ parsed, confidence: 0.85, via: "exact", note: `ACE ${parsed.pattern}` }] };
|
|
37
14
|
}
|
|
38
15
|
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
41
|
-
* so parseQuery — and the CHATBENCH spine it feeds — is byte-stable. */
|
|
16
|
+
/** Registered ASYNC so runStrategiesSync (the sync parseQuery path) skips it,
|
|
17
|
+
* keeping that path byte-stable. */
|
|
42
18
|
export const aceStrategy = {
|
|
43
19
|
id: "ace",
|
|
44
20
|
class: "ace-fact",
|
|
@@ -1,30 +1,12 @@
|
|
|
1
|
-
// interpret/strategies/constructions.mjs —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// OWN additive class ("construction") — the same "own-class strategy" pattern
|
|
7
|
-
// interpret/strategies/ace.mjs and noise-strip.mjs already use, so a construction
|
|
8
|
-
// match outranks a same-text keyword-spot GUESS outright (interpret/merge.mjs
|
|
9
|
-
// picks the highest-confidence CLASS; within-class disagreement is the honest
|
|
10
|
-
// {ambiguousParse} tie, which this strategy deliberately avoids triggering
|
|
11
|
-
// against keyword-spot by living in its own class) rather than colliding with it.
|
|
1
|
+
// interpret/strategies/constructions.mjs — construction-grammar template
|
|
2
|
+
// banks, loaded as DATA from data/templates/constructions/*.toml (pattern ->
|
|
3
|
+
// AST skeleton, slot types validated against RELATIONS/ENTITY_TO_TYPE),
|
|
4
|
+
// registered as its own additive class ("construction") so a match outranks
|
|
5
|
+
// a same-text keyword-spot guess rather than colliding with it.
|
|
12
6
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// and data/templates/responses.jsonl already work this way. Continues
|
|
17
|
-
// grammar.mjs's T1-T10 numbering (T11+, see the TOML file's own [[construction]]
|
|
18
|
-
// `id` fields) without renumbering anything grammar.mjs already owns.
|
|
19
|
-
//
|
|
20
|
-
// Loader discipline (mirrors src/finish.mjs's loadGrammarRules/grammarRules
|
|
21
|
-
// pattern exactly): synchronous (the pipeline is sync-capable), cached once per
|
|
22
|
-
// process, and DEFENSIVE — a missing directory, unparseable TOML, or an entry
|
|
23
|
-
// that fails validation (an unrecognized `kind`/`entityType`, a malformed
|
|
24
|
-
// pattern) is silently DROPPED, never thrown and never guessed into the nearest
|
|
25
|
-
// match. "One broken strategy/entry never takes the pipeline down"
|
|
26
|
-
// (interpret/pipeline.mjs's own file-header discipline) extends here to one
|
|
27
|
-
// broken DATA ROW never taking the strategy down.
|
|
7
|
+
// Loader is synchronous, cached once per process, and defensive: a missing
|
|
8
|
+
// directory, unparseable TOML, or invalid entry is silently dropped, never
|
|
9
|
+
// thrown.
|
|
28
10
|
|
|
29
11
|
import { readFileSync, readdirSync } from "node:fs";
|
|
30
12
|
import { fileURLToPath } from "node:url";
|
|
@@ -72,8 +54,7 @@ export function readConstructionFiles(dir = CONSTRUCTIONS_DIR) {
|
|
|
72
54
|
}
|
|
73
55
|
|
|
74
56
|
/** Validate + index the raw [[relation]] rows into noun -> {kind, entityType}.
|
|
75
|
-
* Closed-vocabulary validation
|
|
76
|
-
* validated against ENTITY_TO_TYPE/VERB_TO_KIND" deliverable): `kind` MUST be
|
|
57
|
+
* Closed-vocabulary validation: `kind` MUST be
|
|
77
58
|
* one of RELATIONS' own keys and `entityType` (when present) MUST be one of
|
|
78
59
|
* ENTITY_TO_TYPE's canonical class names — an entry failing either check is
|
|
79
60
|
* dropped, never coerced to the nearest-looking valid value. First occurrence
|
|
@@ -92,13 +73,8 @@ export function buildAgentNounTable(relations) {
|
|
|
92
73
|
return table;
|
|
93
74
|
}
|
|
94
75
|
|
|
95
|
-
/** Compile one pattern string ("<AGENT> of <TERM>")
|
|
96
|
-
*
|
|
97
|
-
* doesn't carry exactly one <AGENT> and one <TERM> token (a malformed pattern
|
|
98
|
-
* — dropped, not guessed at). Literal text is escaped and whitespace-
|
|
99
|
-
* normalized (\s+), matching every other anchored-template regex in this
|
|
100
|
-
* codebase (grammar.mjs's own TEMPLATES). Case-insensitive; tolerates one
|
|
101
|
-
* optional trailing "?", same as grammar.mjs's own templates. */
|
|
76
|
+
/** Compile one pattern string ("<AGENT> of <TERM>") into {re, agentIndex,
|
|
77
|
+
* termIndex}, or null when it doesn't carry exactly one of each token. */
|
|
102
78
|
function compilePattern(pattern, agentNouns) {
|
|
103
79
|
if (typeof pattern !== "string" || !pattern.trim() || !agentNouns.length) return null;
|
|
104
80
|
const agentAlt = agentNouns.slice().sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
|
|
@@ -189,12 +165,9 @@ export function parseConstruction(text, bank = constructionBank()) {
|
|
|
189
165
|
return null;
|
|
190
166
|
}
|
|
191
167
|
|
|
192
|
-
/** Pipeline registration (
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* pattern match), so it outright outranks a same-text "graph-query"-class
|
|
196
|
-
* keyword-spot guess (0.7) instead of triggering a same-class {ambiguousParse}
|
|
197
|
-
* tie against it (see this file's header). */
|
|
168
|
+
/** Pipeline registration: own class ("construction"), confidence 0.9 — same
|
|
169
|
+
* weight as grammar.mjs's anchored templates, so it outranks a same-text
|
|
170
|
+
* keyword-spot guess (0.7) rather than tying against it. */
|
|
198
171
|
export const constructionsStrategy = {
|
|
199
172
|
id: "constructions",
|
|
200
173
|
class: "construction",
|
|
@@ -18,21 +18,9 @@ const MODIFIER_ALT = Object.keys(MODIFIER_TO_KIND).sort((a, b) => b.length - a.l
|
|
|
18
18
|
const META_ALT = META_MEANING_VERBS.slice().sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
|
|
19
19
|
|
|
20
20
|
const TEMPLATES = [
|
|
21
|
-
// T1 ASK: "does X import Y"
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// "did" joins does/do for the past-tense commit forms ("did commit <sha> touch X").
|
|
25
|
-
// REVERSE VERB SWAP (Seonix Batch 2 Fix 2): this template fixes subject/object by regex
|
|
26
|
-
// capture POSITION, not by the verb's semantic direction — fine for every forward verb
|
|
27
|
-
// ("subclass of", "imports", …), but "is X a superclass of Y" MEANS the reverse of "is X
|
|
28
|
-
// a subclass of Y" (Y inherits from X, not X from Y). INHERITS_REVERSE_VERBS (ask-vocab.mjs)
|
|
29
|
-
// is the closed set of such reverse phrasings; when the matched verb is one of them,
|
|
30
|
-
// subject/object are swapped here, once, at parse time — so downstream evaluation (ask.mjs)
|
|
31
|
-
// sees "is Y a subclass of X" and needs zero changes of its own. (In practice this exact
|
|
32
|
-
// regex only ever matches a does/do/did lead, so "is …" phrasings actually reach the "ask"
|
|
33
|
-
// shape via keywords.mjs's decomposition strategy instead — that strategy applies the same
|
|
34
|
-
// swap for the same reason; this branch is kept for any does/do/did-led phrasing that names
|
|
35
|
-
// a reverse verb, and for structural symmetry with that sibling strategy.)
|
|
21
|
+
// T1 ASK: "does X import Y" -> Yes/No. REVERSE VERB SWAP: a semantically-reverse
|
|
22
|
+
// verb ("superclass of") means the opposite of its forward counterpart, so
|
|
23
|
+
// INHERITS_REVERSE_VERBS (ask-vocab.mjs) swaps subject/object here at parse time.
|
|
36
24
|
{
|
|
37
25
|
name: "ask",
|
|
38
26
|
re: new RegExp(`^(?:does|do|did)\\s+(.+?)\\s+(${VERB_ALT})\\s+(.+?)\\??$`, "i"),
|
|
@@ -45,7 +33,7 @@ const TEMPLATES = [
|
|
|
45
33
|
return { shape: "ask", entityType: null, modifier: "direct", kind, subject, object };
|
|
46
34
|
},
|
|
47
35
|
},
|
|
48
|
-
// T2 reverse: "which <entity> [<modifier>] <verb> <object>"
|
|
36
|
+
// T2 reverse: "which <entity> [<modifier>] <verb> <object>".
|
|
49
37
|
{
|
|
50
38
|
name: "reverse",
|
|
51
39
|
re: new RegExp(`^which\\s+(${ENTITY_ALT})\\s+(?:(${MODIFIER_ALT})\\s+)?(${VERB_ALT})\\s+(.+?)\\??$`, "i"),
|
|
@@ -67,51 +55,24 @@ const TEMPLATES = [
|
|
|
67
55
|
kind: VERB_TO_KIND[m[2].toLowerCase()], object: m[1].trim(),
|
|
68
56
|
}),
|
|
69
57
|
},
|
|
70
|
-
// T4 meta: "what does <term> mean" — a question about the
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// starts "what does/do", but T3 only fires when the tail is a relation VERB_ALT
|
|
74
|
-
// phrase ("import"/"calls"/…), which "mean"/"means"/etc never are (disjoint tables —
|
|
75
|
-
// ask-vocab.mjs's file comment explains why they're kept separate), so the two never
|
|
76
|
-
// actually compete for the same input.
|
|
58
|
+
// T4 meta: "what does <term> mean" — a question about the graph's own vocabulary,
|
|
59
|
+
// not a graph traversal. VERB_ALT and META_ALT are disjoint tables, so this never
|
|
60
|
+
// competes with T3 for the same input.
|
|
77
61
|
{
|
|
78
62
|
name: "meta-mean",
|
|
79
63
|
re: new RegExp(`^what\\s+(?:does|do|is|are)\\s+(.+?)\\s+(?:${META_ALT})\\??$`, "i"),
|
|
80
64
|
build: (m) => ({ shape: "meta", entityType: null, modifier: "direct", kind: "meta", object: m[1].trim() }),
|
|
81
65
|
},
|
|
82
|
-
// T5 meta: "what is a/an <term>" — the
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// imported above (function/method/class/module/attribute/variable/change/commit,
|
|
86
|
-
// singular and plural) — build() returns null (same "this template didn't actually
|
|
87
|
-
// match, keep scanning" contract T8/"when" below already relies on — see
|
|
88
|
-
// parseAnchored's own docblock) when the bare form's object isn't one of those
|
|
89
|
-
// closed terms, so the scan falls through exactly as if this template had not
|
|
90
|
-
// matched at all. This keeps "what is a doohickey"/"widget"/"gizmo" (still routed
|
|
91
|
-
// here via the WITH-article, fully unrestricted `(.+?)` path — pinned to resolve as
|
|
92
|
-
// an honest meta miss downstream, ask-combo.test.mjs/chat-readback.test.mjs) working
|
|
93
|
-
// unmodified, while ALSO keeping the two pinned bare-form honest misses intact:
|
|
94
|
-
// "what is the meaning of this codebase" (ask.test.mjs/ask-dual-strategy.test.mjs)
|
|
95
|
-
// and "what is exposed" (ask.test.mjs:840) both have bare objects absent from
|
|
96
|
-
// ENTITY_TO_TYPE, so build() still rejects them and they still fall through to null.
|
|
97
|
-
// Fix 3: stripTrailingScopeFiller (ask-vocab.mjs) trims a curated trailing clause
|
|
98
|
-
// ("what is a Module in this graph" -> "Module") off the object before it's
|
|
99
|
-
// returned, so a scoping tail never corrupts the lookup term either the bare-form
|
|
100
|
-
// check above or downstream resolution/rendering perform. HANDOVER.md 2026-07-10
|
|
101
|
-
// item 8: stripTrailingDiscourseTag trims a bare trailing "then"/"though" the
|
|
102
|
-
// same way ("what is a component then" -> "component") — applied first, since a
|
|
103
|
-
// discourse tag sits outermost when both happen to stack.
|
|
66
|
+
// T5 meta: "what is a/an <term>" — the bare (no-article) form is restricted to
|
|
67
|
+
// the closed ENTITY_TO_TYPE vocabulary (build() -> null otherwise, falling
|
|
68
|
+
// through); the WITH-article form is unrestricted.
|
|
104
69
|
{
|
|
105
70
|
name: "meta-whatis",
|
|
106
71
|
re: new RegExp(`^what\\s+(?:is|are)\\s+(?:(an?)\\s+)?(.+?)\\??$`, "i"),
|
|
107
72
|
build: (m) => {
|
|
108
73
|
const object = stripTrailingDiscourseTag(m[2].trim());
|
|
109
74
|
if (!m[1] && !ENTITY_TO_TYPE[object.toLowerCase()]) return null; // bare form: closed-set only
|
|
110
|
-
// "what is a kind of X"
|
|
111
|
-
// ask-vocab.mjs): the captured object is itself the tail of a registered inherits
|
|
112
|
-
// verb's own "is a .../are a ..." phrasing, not a term to define — reject so this
|
|
113
|
-
// template yields no candidate at all and only keyword-spot's (unambiguous) reverse-
|
|
114
|
-
// inherits reading survives, instead of a spurious meta/inherits {ambiguousParse} tie.
|
|
75
|
+
// "what is a kind/subclass of X" is an inherits phrasing, not a term to define.
|
|
115
76
|
const objLower = object.toLowerCase();
|
|
116
77
|
if (m[1] && ARTICLE_RELATION_CONTINUATIONS.some(
|
|
117
78
|
(c) => objLower === c || objLower.startsWith(`${c} `),
|
|
@@ -119,8 +80,8 @@ const TEMPLATES = [
|
|
|
119
80
|
return { shape: "meta", entityType: null, modifier: "direct", kind: "meta", object: stripTrailingScopeFiller(object) };
|
|
120
81
|
},
|
|
121
82
|
},
|
|
122
|
-
// T6 mention: "where is <term> mentioned/referenced" — the prose/mentions surface
|
|
123
|
-
//
|
|
83
|
+
// T6 mention: "where is <term> mentioned/referenced" — the prose/mentions surface.
|
|
84
|
+
// Tried BEFORE T7: T7's trailing marker is optional,
|
|
124
85
|
// so without this ordering it would swallow the mention question and lose the
|
|
125
86
|
// marker that distinguishes "locate the definition" from "list the prose mentions".
|
|
126
87
|
{
|
|
@@ -148,19 +109,19 @@ const TEMPLATES = [
|
|
|
148
109
|
? { shape: "when", entityType: null, modifier: "direct", kind: "touches", object: m[1].trim() }
|
|
149
110
|
: null),
|
|
150
111
|
},
|
|
151
|
-
// T9 commit-history NP
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
112
|
+
// T9 commit-history NP: "the commit history of X" / "commit history for X" —
|
|
113
|
+
// an NP form of T8's SAME "when did X change" intent; reuses shape="when"
|
|
114
|
+
// verbatim so evaluation/rendering are byte-identical, only the recognizer
|
|
115
|
+
// surface differs.
|
|
155
116
|
{
|
|
156
117
|
name: "commit-history",
|
|
157
118
|
re: /^(?:the\s+)?commit\s+history\s+(?:of|for)\s+(.+?)\??$/i,
|
|
158
119
|
build: (m) => ({ shape: "when", entityType: null, modifier: "direct", kind: "touches", object: m[1].trim() }),
|
|
159
120
|
},
|
|
160
|
-
// T10 cochange-partners NP
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
121
|
+
// T10 cochange-partners NP: "cochange partners of X" — an NP form of the
|
|
122
|
+
// existing "which modules cochange with X" verb-phrase shape (ask-vocab.mjs's
|
|
123
|
+
// cochange verb table); reuses shape="reverse"/kind="cochange" so evaluation
|
|
124
|
+
// is byte-identical.
|
|
164
125
|
{
|
|
165
126
|
name: "cochange-partners",
|
|
166
127
|
re: /^co-?change\s+partners\s+(?:of|for|with)\s+(.+?)\??$/i,
|