@polycode-projects/seonix 0.9.0 → 0.9.1
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 +1 -1
- package/package.json +55 -7
- package/src/ask-browser.bundle.js +644 -35
package/README.md
CHANGED
|
@@ -325,7 +325,7 @@ set `SEONIX_GRAPH_FORMAT=1` to opt back out to the legacy v1 form.
|
|
|
325
325
|
An optional `seonix.toml` at a repo (or estate) root steers indexing and
|
|
326
326
|
scoring. Absent file = shipped defaults, byte-for-byte today's behaviour.
|
|
327
327
|
`seonix init` (or `seonix init --dotnet` for an estate) seeds a commented
|
|
328
|
-
template — see `
|
|
328
|
+
template — see `templates/seonix.toml` and
|
|
329
329
|
`templates/seonix-dotnet.toml` for the full annotated reference.
|
|
330
330
|
|
|
331
331
|
- **`repositories`** — the repo set for an estate config: an inline array of
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/seonix",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "SEONIX — a deterministic, offline, $0 code-graph tool that makes a cheap coding agent edit like an expensive one. Indexes a repo with Python ast + git (zero model calls) and renders a bounded edit-digest.",
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
"homepage": "https://seonix.polycode.co.uk",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "git+https://gitlab.com/polycode-projects/seonix.git"
|
|
13
|
-
"directory": "packages/seonix"
|
|
12
|
+
"url": "git+https://gitlab.com/polycode-projects/seonix.git"
|
|
14
13
|
},
|
|
15
14
|
"bugs": {
|
|
16
15
|
"url": "https://gitlab.com/polycode-projects/seonix/-/issues"
|
|
@@ -42,9 +41,58 @@
|
|
|
42
41
|
"publishConfig": {
|
|
43
42
|
"access": "public"
|
|
44
43
|
},
|
|
44
|
+
"workspaces": [
|
|
45
|
+
"infra"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"setup": "node scripts/setup.mjs",
|
|
49
|
+
"bench": "node bench/run.mjs",
|
|
50
|
+
"bench:quick": "node bench/run.mjs --instances 1 --runs 1",
|
|
51
|
+
"join:telemetry": "node scripts/join-telemetry.mjs",
|
|
52
|
+
"bench:lite": "node bench/run.mjs --suite lite",
|
|
53
|
+
"bench:seonix": "node bench/run.mjs --suite django-lh --arms seonix,seonix-min --runs 5",
|
|
54
|
+
"bench:b010": "node bench/run.mjs --suite django-lh --arms otb,seonix,seonix-min,seonix-b010 --models claude-opus-4-8,claude-sonnet-4-6 --runs 5 --concurrency 10",
|
|
55
|
+
"bench:b011": "node bench/run.mjs --suite django-lh --arms seonix,seonix-min --models claude-opus-4-8,claude-sonnet-4-6,claude-haiku-4-5 --runs 5 --concurrency 15",
|
|
56
|
+
"bench:b011-haiku-refs": "node bench/run.mjs --suite django-lh --arms otb,seonix-b010 --models claude-haiku-4-5 --runs 5 --concurrency 10",
|
|
57
|
+
"bench:b015": "node scripts/bench-b015.mjs",
|
|
58
|
+
"bench:b016p1": "node scripts/bench-b016-p1.mjs",
|
|
59
|
+
"bench:b016h": "node scripts/bench-b016-h.mjs",
|
|
60
|
+
"bench:b016p2": "node scripts/bench-b016-p2.mjs",
|
|
61
|
+
"bench:baseline": "node scripts/use-baseline.mjs",
|
|
62
|
+
"proof": "node scripts/proof-demo.mjs",
|
|
63
|
+
"smoke": "node bench/smoke.mjs --suite django-lh",
|
|
64
|
+
"report": "node bench/report.mjs",
|
|
65
|
+
"all": "npm run setup && npm run bench && npm run report",
|
|
66
|
+
"clean": "node scripts/clean.mjs",
|
|
67
|
+
"verify:counts": "node scripts/verify-counts.mjs",
|
|
68
|
+
"verify:sanity": "node scripts/verify-sanity.mjs",
|
|
69
|
+
"verify:index": "node scripts/verify-index.mjs",
|
|
70
|
+
"verify:query": "node scripts/verify-query.mjs",
|
|
71
|
+
"verify:review": "node scripts/verify-review.mjs",
|
|
72
|
+
"verify:loop": "node scripts/verify-loop.mjs",
|
|
73
|
+
"refs": "node scripts/fetch-references.mjs",
|
|
74
|
+
"refs:embeddings": "node scripts/fetch-embeddings.mjs",
|
|
75
|
+
"audit": "npm audit --audit-level=high",
|
|
76
|
+
"audit:fix": "npm audit fix",
|
|
77
|
+
"test:root": "node --test \"test/**/*.test.mjs\"",
|
|
78
|
+
"test": "npm run test:root && npm run chrono:test",
|
|
79
|
+
"chrono:build": "node chronograph/build.mjs",
|
|
80
|
+
"chrono:serve": "node chronograph/serve.mjs",
|
|
81
|
+
"chrono:test": "node --test \"chronograph/test/**/*.test.mjs\"",
|
|
82
|
+
"test:all": "npm run test",
|
|
83
|
+
"seonix": "node bin/cli.mjs",
|
|
84
|
+
"seonix:viz": "node bin/cli.mjs viz --",
|
|
85
|
+
"e2e:install": "npx playwright install chromium --with-deps",
|
|
86
|
+
"e2e": "playwright test",
|
|
87
|
+
"e2e:site": "playwright test site.behaviour.test.js",
|
|
88
|
+
"e2e:prod": "SEONIX_E2E_BASE_URL=https://seonix.polycode.co.uk playwright test site.behaviour.test.js",
|
|
89
|
+
"site:prompts": "node scripts/site-prompts.mjs",
|
|
90
|
+
"build:ask-bundle": "node scripts/build-ask-bundle.mjs",
|
|
91
|
+
"site:gen": "npm run build:ask-bundle && node bin/cli.mjs viz --focus buildContextBundle --depth 2 --repo-url https://gitlab.com/polycode-projects/seonix --site-nav --out site/seonix-graph.html --data-out site/seonix-graph-data.json --browser-out site/code-browser.html --browser-data-out site/code-browser-data.json --timeline-out site/timeline.html && npm run site:prompts"
|
|
92
|
+
},
|
|
45
93
|
"dependencies": {
|
|
46
94
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
|
-
"@polycode-projects/the-mechanical-code-talker": "^0.8.
|
|
95
|
+
"@polycode-projects/the-mechanical-code-talker": "^0.8.1",
|
|
48
96
|
"cytoscape": "^3.30.0",
|
|
49
97
|
"smol-toml": "^1.7.0",
|
|
50
98
|
"tree-sitter": "^0.21.1",
|
|
@@ -52,8 +100,8 @@
|
|
|
52
100
|
"tree-sitter-java": "^0.21.0",
|
|
53
101
|
"typescript": "~5.6.2"
|
|
54
102
|
},
|
|
55
|
-
"
|
|
56
|
-
"test": "
|
|
57
|
-
"
|
|
103
|
+
"devDependencies": {
|
|
104
|
+
"@playwright/test": "^1.56.0",
|
|
105
|
+
"esbuild": "^0.28.1"
|
|
58
106
|
}
|
|
59
107
|
}
|
|
@@ -50,6 +50,8 @@
|
|
|
50
50
|
};
|
|
51
51
|
var createRequire2 = unavailable2("createRequire");
|
|
52
52
|
var readFileSync2 = unavailable2("readFileSync");
|
|
53
|
+
var join = (...a) => a.join("/");
|
|
54
|
+
var dirname = (p) => String(p).replace(/\/[^/]*$/, "");
|
|
53
55
|
var randomBytes2 = unavailable2("randomBytes");
|
|
54
56
|
|
|
55
57
|
// node-stub:node:url
|
|
@@ -58,6 +60,7 @@
|
|
|
58
60
|
};
|
|
59
61
|
var createRequire3 = unavailable3("createRequire");
|
|
60
62
|
var readFileSync3 = unavailable3("readFileSync");
|
|
63
|
+
var fileURLToPath = (u) => String(u);
|
|
61
64
|
var randomBytes3 = unavailable3("randomBytes");
|
|
62
65
|
|
|
63
66
|
// node_modules/@polycode-projects/the-mechanical-code-talker/src/codegraph.mjs
|
|
@@ -972,6 +975,92 @@
|
|
|
972
975
|
}
|
|
973
976
|
return text;
|
|
974
977
|
}
|
|
978
|
+
var PHRASING_FRAMES = Object.freeze([
|
|
979
|
+
// MEMBERS-of-class → "what does X contain".
|
|
980
|
+
// "what functions are in Task", "what methods are inside X", "what attributes are in X"
|
|
981
|
+
{ re: /^what\s+(?:functions?|methods?|members?|attributes?|fields?|properties)\s+(?:are|is)\s+(?:in|inside|within)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
|
|
982
|
+
// "what functions does Task have", "what methods does X have"
|
|
983
|
+
{ re: /^what\s+(?:functions?|methods?|members?|attributes?|fields?|properties)\s+(?:does|do)\s+(.+?)\s+have\??$/i, to: (m) => `what does ${m[1]} contain` },
|
|
984
|
+
// "what are the members of X", "what are the methods in X"
|
|
985
|
+
{ re: /^what\s+are\s+(?:the\s+)?(?:functions?|methods?|members?|attributes?|fields?|properties)\s+(?:of|in|inside|within)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
|
|
986
|
+
// "members of X", "methods of X", "contents of X"
|
|
987
|
+
{ re: /^(?:the\s+)?(?:members?|methods?|attributes?|contents)\s+of\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
|
|
988
|
+
// "what's in X" / "what is in X" (contraction already expanded; sha handled above)
|
|
989
|
+
{ re: /^what\s+is\s+(?:in|inside)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
|
|
990
|
+
// WHERE-DEFINED → "where is X defined". PAST TENSE ONLY ("what defined X", "what
|
|
991
|
+
// declared X"): the PRESENT "what defines X" already parses as a reverse-defines
|
|
992
|
+
// query (the module defining symbol X — test/ask.test.mjs pins that), so rewriting
|
|
993
|
+
// it would change that receipt. The past-tense form is the one that hit the wall.
|
|
994
|
+
{ re: /^what\s+(?:defined|declared)\s+(?:the\s+)?(?:function\s+|method\s+|class\s+|module\s+|variable\s+|constant\s+)?(.+?)\??$/i, to: (m) => `where is ${m[1]} defined` },
|
|
995
|
+
// "where's X defined" (the "where's" contraction is not in the contraction table)
|
|
996
|
+
{ re: /^where'?s\s+(?:the\s+)?(.+?)\s+(defined|declared|located|implemented)\??$/i, to: (m) => `where is ${m[1]} ${m[2]}` },
|
|
997
|
+
// PREDICATIVE QUALIFIER → the ATTRIBUTIVE form the grammar already answers. The
|
|
998
|
+
// adjective-qualifier post-filters (ask-vocab.mjs QUALIFIERS: tested/untested,
|
|
999
|
+
// public/private, exported, static/abstract/constant, …) parse in the ATTRIBUTIVE
|
|
1000
|
+
// slot — "untested modules", "public methods" — but a developer just as naturally
|
|
1001
|
+
// asks the PREDICATIVE "which modules are untested" / "what functions are tested",
|
|
1002
|
+
// which hit the grammar wall (and, worse, the wall's own hint SUGGESTED "which
|
|
1003
|
+
// functions are tested" — a shape it could not then answer). Rewriting the
|
|
1004
|
+
// predicative "<which|what> <kind> are <QUALIFIER>" to "<QUALIFIER> <kind>" routes
|
|
1005
|
+
// it onto the working attributive filter. Closed to the known qualifier adjectives
|
|
1006
|
+
// (not a general "… are X" catch), and the QUALIFIER must sit immediately after
|
|
1007
|
+
// are/is, so "which modules are NOT tested" never matches here — that keeps its own
|
|
1008
|
+
// set-complement handler (matchNegationSet, downstream in ask.mjs's parseNegation).
|
|
1009
|
+
{
|
|
1010
|
+
re: /^(?:which|what)\s+(?:the\s+|all\s+)?([a-z][a-z-]*?)\s+(?:are|is)\s+(public|private|protected|static|abstract|constant|exported|re-?exported|tested|covered|untested|uncovered)\??$/i,
|
|
1011
|
+
to: (m) => `${m[2].toLowerCase()} ${m[1].toLowerCase()}`
|
|
1012
|
+
},
|
|
1013
|
+
// BARE COVERAGE SURVEY (no entity kind) → the attributive "<qualifier> modules"
|
|
1014
|
+
// the grammar already answers. Once "what is a test" opens the topic, a developer
|
|
1015
|
+
// asks the survey the plainest way — "what is untested", "what's not tested",
|
|
1016
|
+
// "what isn't covered", "what is covered" — with NO entity noun at all, so the
|
|
1017
|
+
// predicative-qualifier frame above (which needs a KIND between what/which and
|
|
1018
|
+
// are/is) can't catch it, and it fell through to a soft wall ("no module matching
|
|
1019
|
+
// 'not'…" / the "I answer questions…" orientation). Default the surveyed kind to
|
|
1020
|
+
// modules (the same set "which modules are not tested" / "untested modules" return)
|
|
1021
|
+
// and fold the negation into the qualifier (not tested → untested, not covered →
|
|
1022
|
+
// uncovered). Anchored with no object, so "what tests cover X" / "what is a test"
|
|
1023
|
+
// never match here.
|
|
1024
|
+
{
|
|
1025
|
+
re: /^what\s+(?:is|are)\s+(not\s+)?(tested|untested|covered|uncovered)\??$/i,
|
|
1026
|
+
to: (m) => {
|
|
1027
|
+
const q = m[2].toLowerCase();
|
|
1028
|
+
const flipped = m[1] ? q === "tested" ? "untested" : q === "covered" ? "uncovered" : q : q;
|
|
1029
|
+
return `${flipped} modules`;
|
|
1030
|
+
}
|
|
1031
|
+
},
|
|
1032
|
+
// CO-CHANGE → the "co-changes with" canonical the RELATIONS table answers. The
|
|
1033
|
+
// cochange verb synonyms (ask-vocab.mjs) include "co-changes with" / "moves
|
|
1034
|
+
// together with" / "tends to change together with", but NOT the plainest form a
|
|
1035
|
+
// developer types — the one the README itself prints and the relation renders as:
|
|
1036
|
+
// "what does X change together with" / "what changes together with X". Both hit a
|
|
1037
|
+
// dead-end ("couldn't resolve one of the terms" / the grammar wall); rewriting them
|
|
1038
|
+
// onto "what co-changes with X" routes them to the working change-coupling query.
|
|
1039
|
+
{ re: /^what\s+does\s+(.+?)\s+changes?\s+together\s+with\??$/i, to: (m) => `what co-changes with ${m[1]}` },
|
|
1040
|
+
{ re: /^what\s+changes?\s+together\s+with\s+(.+?)\??$/i, to: (m) => `what co-changes with ${m[1]}` },
|
|
1041
|
+
// AUTHORSHIP → the "who touched X" churn query. "who touched X" now names the
|
|
1042
|
+
// commit author beside the sha (the 0.8.1 commit-ref quick-win), which invites the
|
|
1043
|
+
// synonyms a developer reaches for next — "who wrote X", "who authored X", "who is
|
|
1044
|
+
// the author of X" — and every one of them hit the grammar wall. tmct has no
|
|
1045
|
+
// separate authorship edge; "touched" IS the authorship signal (the churn commits
|
|
1046
|
+
// carry the author), so these are true synonyms of "who touched X", not a new
|
|
1047
|
+
// capability. Anaphora rides through untouched ("who wrote it" → "who touched it").
|
|
1048
|
+
{ re: /^who\s+(?:wrote|authored)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `who touched ${m[1]}` },
|
|
1049
|
+
{ re: /^who\s+is\s+the\s+authors?\s+of\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `who touched ${m[1]}` },
|
|
1050
|
+
// NEEDS-TESTS → the untested-module survey. "what needs tests" / "what needs
|
|
1051
|
+
// testing" is the plainest way to ask which modules are uncovered, and it hit the
|
|
1052
|
+
// grammar wall ("no module matching 'needs'…"). Route it onto the same attributive
|
|
1053
|
+
// survey the bare "what is untested" frame lands on. Closed to the tests/coverage
|
|
1054
|
+
// object, so it can't swallow a general "what needs X".
|
|
1055
|
+
{ re: /^what\s+needs\s+(?:to\s+be\s+)?(?:a\s+)?(?:tested|tests?|testing|coverage|covering)\??$/i, to: () => "untested modules" }
|
|
1056
|
+
]);
|
|
1057
|
+
function applyPhrasingFrames(text) {
|
|
1058
|
+
for (const frame of PHRASING_FRAMES) {
|
|
1059
|
+
const m = text.match(frame.re);
|
|
1060
|
+
if (m) return frame.to(m).replace(/\s+/g, " ").trim();
|
|
1061
|
+
}
|
|
1062
|
+
return text;
|
|
1063
|
+
}
|
|
975
1064
|
var NEGATION_SET_RE = new RegExp(
|
|
976
1065
|
"^(?:which|what|who|list|show(?:\\s+me)?|find|give\\s+me)?\\s*(?:the\\s+|all\\s+)?([a-z][a-z-]*)\\s+(?:(?:that|which|who)\\s+)?(?:(?:do|does|did|are|is|was|were|have|has)\\s+)?not\\s+(.+)$",
|
|
977
1066
|
// the negation marker + (2) the predicate
|
|
@@ -1058,17 +1147,17 @@
|
|
|
1058
1147
|
function fuzzyVocabWord(w) {
|
|
1059
1148
|
const bound = fuzzyBound(w);
|
|
1060
1149
|
let best = bound + 1;
|
|
1061
|
-
let
|
|
1150
|
+
let hit2 = null;
|
|
1062
1151
|
let tied = false;
|
|
1063
1152
|
for (const target of FUZZY_TARGET_WORDS) {
|
|
1064
1153
|
const d = editDistance(w, target, Math.min(best, bound));
|
|
1065
1154
|
if (d < best) {
|
|
1066
1155
|
best = d;
|
|
1067
|
-
|
|
1156
|
+
hit2 = target;
|
|
1068
1157
|
tied = false;
|
|
1069
|
-
} else if (d === best && d <= bound && target !==
|
|
1158
|
+
} else if (d === best && d <= bound && target !== hit2) tied = true;
|
|
1070
1159
|
}
|
|
1071
|
-
return best <= bound && !tied ?
|
|
1160
|
+
return best <= bound && !tied ? hit2 : null;
|
|
1072
1161
|
}
|
|
1073
1162
|
|
|
1074
1163
|
// node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/strategies/grammar.mjs
|
|
@@ -1253,8 +1342,8 @@
|
|
|
1253
1342
|
}
|
|
1254
1343
|
}
|
|
1255
1344
|
const consumed = /* @__PURE__ */ new Set();
|
|
1256
|
-
const mark = (
|
|
1257
|
-
if (
|
|
1345
|
+
const mark = (hit2) => {
|
|
1346
|
+
if (hit2) for (let i = hit2.start; i < hit2.end; i += 1) consumed.add(i);
|
|
1258
1347
|
};
|
|
1259
1348
|
mark(verbHit);
|
|
1260
1349
|
const entityHit = findPhrase(canonWords, ENTITY_TO_TYPE, consumed);
|
|
@@ -1356,6 +1445,423 @@
|
|
|
1356
1445
|
}
|
|
1357
1446
|
};
|
|
1358
1447
|
|
|
1448
|
+
// node_modules/@polycode-projects/the-mechanical-code-talker/src/grammar/lexicon.mjs
|
|
1449
|
+
var import_meta = {};
|
|
1450
|
+
var CORE_FILE = join(dirname(fileURLToPath(import_meta.url)), "lexicon-core.json");
|
|
1451
|
+
var DETERMINERS = Object.freeze({
|
|
1452
|
+
every: "universal",
|
|
1453
|
+
a: "indefinite",
|
|
1454
|
+
an: "indefinite",
|
|
1455
|
+
the: "definite",
|
|
1456
|
+
no: "negative"
|
|
1457
|
+
});
|
|
1458
|
+
var QUANTIFIERS = Object.freeze({
|
|
1459
|
+
"at least": "owl:minCardinality",
|
|
1460
|
+
"at most": "owl:maxCardinality",
|
|
1461
|
+
exactly: "owl:cardinality"
|
|
1462
|
+
});
|
|
1463
|
+
var NUMBER_WORDS = Object.freeze({
|
|
1464
|
+
one: 1,
|
|
1465
|
+
two: 2,
|
|
1466
|
+
three: 3,
|
|
1467
|
+
four: 4,
|
|
1468
|
+
five: 5,
|
|
1469
|
+
six: 6,
|
|
1470
|
+
seven: 7,
|
|
1471
|
+
eight: 8,
|
|
1472
|
+
nine: 9,
|
|
1473
|
+
ten: 10
|
|
1474
|
+
});
|
|
1475
|
+
function numberOf(word) {
|
|
1476
|
+
const w = String(word ?? "").trim().toLowerCase();
|
|
1477
|
+
if (/^\d+$/.test(w)) return Number(w);
|
|
1478
|
+
return NUMBER_WORDS[w] ?? null;
|
|
1479
|
+
}
|
|
1480
|
+
function thirdPerson(base) {
|
|
1481
|
+
const b = String(base);
|
|
1482
|
+
if (b === "have") return "has";
|
|
1483
|
+
if (/[^aeiou]y$/.test(b)) return `${b.slice(0, -1)}ies`;
|
|
1484
|
+
if (/(s|x|z|ch|sh)$/.test(b)) return `${b}es`;
|
|
1485
|
+
return `${b}s`;
|
|
1486
|
+
}
|
|
1487
|
+
function predicateOf(verbEntry) {
|
|
1488
|
+
if (verbEntry.predicate) return verbEntry.predicate;
|
|
1489
|
+
const prep = verbEntry.prep ? verbEntry.prep[0].toUpperCase() + verbEntry.prep.slice(1) : "";
|
|
1490
|
+
return `tmct:${thirdPerson(verbEntry.lemma)}${prep}`;
|
|
1491
|
+
}
|
|
1492
|
+
function foldCandidates(word) {
|
|
1493
|
+
const w = String(word);
|
|
1494
|
+
const out = [w];
|
|
1495
|
+
if (w.length > 4 && /[a-z]ies$/.test(w)) out.push(`${w.slice(0, -3)}y`);
|
|
1496
|
+
if (/(ses|xes|zes|ches|shes)$/.test(w)) out.push(w.slice(0, -2));
|
|
1497
|
+
if (/[a-z]s$/.test(w) && !/ss$/.test(w)) out.push(w.slice(0, -1));
|
|
1498
|
+
if (w === "has") out.push("have");
|
|
1499
|
+
return out;
|
|
1500
|
+
}
|
|
1501
|
+
var NOUN_PROPERTY_TYPES = /* @__PURE__ */ new Set(["data", "object"]);
|
|
1502
|
+
var ADJECTIVE_TYPES = /* @__PURE__ */ new Set(["subclass", "data"]);
|
|
1503
|
+
function ingest(lex, raw = {}) {
|
|
1504
|
+
for (const [lemma, e] of Object.entries(raw.nouns || {})) {
|
|
1505
|
+
const entry = { lemma, ...e || {} };
|
|
1506
|
+
if (entry.property && !NOUN_PROPERTY_TYPES.has(entry.property)) {
|
|
1507
|
+
throw new Error(`lexicon noun "${lemma}": property must be "data" or "object", got ${JSON.stringify(entry.property)}`);
|
|
1508
|
+
}
|
|
1509
|
+
lex.nouns.set(lemma, entry);
|
|
1510
|
+
if (entry.plural) lex.nounPlurals.set(entry.plural, lemma);
|
|
1511
|
+
}
|
|
1512
|
+
for (const [lemma, e] of Object.entries(raw.verbs || {})) {
|
|
1513
|
+
lex.verbs.set(lemma, { lemma, ...e || {} });
|
|
1514
|
+
}
|
|
1515
|
+
for (const [lemma, e] of Object.entries(raw.adjectives || {})) {
|
|
1516
|
+
const entry = { lemma, ...e || {} };
|
|
1517
|
+
if (!ADJECTIVE_TYPES.has(entry.type)) {
|
|
1518
|
+
throw new Error(`lexicon adjective "${lemma}": type must be "subclass" or "data", got ${JSON.stringify(entry.type)}`);
|
|
1519
|
+
}
|
|
1520
|
+
lex.adjectives.set(lemma, entry);
|
|
1521
|
+
}
|
|
1522
|
+
for (const name of raw.properNames || []) {
|
|
1523
|
+
lex.properNames.set(String(name).toLowerCase(), String(name));
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
var coreCache = null;
|
|
1527
|
+
function loadLexicon(extra) {
|
|
1528
|
+
if (!extra && coreCache) return coreCache;
|
|
1529
|
+
const raw = JSON.parse(readFileSync(CORE_FILE, "utf8"));
|
|
1530
|
+
const lex = {
|
|
1531
|
+
nouns: /* @__PURE__ */ new Map(),
|
|
1532
|
+
nounPlurals: /* @__PURE__ */ new Map(),
|
|
1533
|
+
verbs: /* @__PURE__ */ new Map(),
|
|
1534
|
+
adjectives: /* @__PURE__ */ new Map(),
|
|
1535
|
+
properNames: /* @__PURE__ */ new Map()
|
|
1536
|
+
// lowercased → canonical spelling
|
|
1537
|
+
};
|
|
1538
|
+
ingest(lex, raw);
|
|
1539
|
+
if (extra) {
|
|
1540
|
+
ingest(lex, extra);
|
|
1541
|
+
return lex;
|
|
1542
|
+
}
|
|
1543
|
+
coreCache = lex;
|
|
1544
|
+
return lex;
|
|
1545
|
+
}
|
|
1546
|
+
function lookupNoun(lexicon, word) {
|
|
1547
|
+
const w = String(word ?? "").toLowerCase();
|
|
1548
|
+
const irregular = lexicon.nounPlurals.get(w);
|
|
1549
|
+
if (irregular) return lexicon.nouns.get(irregular) ?? null;
|
|
1550
|
+
for (const cand of foldCandidates(w)) {
|
|
1551
|
+
const hit2 = lexicon.nouns.get(cand);
|
|
1552
|
+
if (hit2) return hit2;
|
|
1553
|
+
}
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
function lookupVerb(lexicon, word) {
|
|
1557
|
+
const w = String(word ?? "").toLowerCase();
|
|
1558
|
+
for (const cand of foldCandidates(w)) {
|
|
1559
|
+
const hit2 = lexicon.verbs.get(cand);
|
|
1560
|
+
if (hit2) return hit2;
|
|
1561
|
+
}
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
function lookupAdjective(lexicon, word) {
|
|
1565
|
+
return lexicon.adjectives.get(String(word ?? "").toLowerCase()) ?? null;
|
|
1566
|
+
}
|
|
1567
|
+
function lookupProperName(lexicon, word) {
|
|
1568
|
+
return lexicon.properNames.get(String(word ?? "").toLowerCase()) ?? null;
|
|
1569
|
+
}
|
|
1570
|
+
function classify(word, lexicon = loadLexicon()) {
|
|
1571
|
+
const w = String(word ?? "").trim();
|
|
1572
|
+
if (!w) return null;
|
|
1573
|
+
const lower = w.toLowerCase();
|
|
1574
|
+
if (DETERMINERS[lower]) return { pos: "determiner", type: DETERMINERS[lower] };
|
|
1575
|
+
if (QUANTIFIERS[lower]) return { pos: "quantifier", type: QUANTIFIERS[lower] };
|
|
1576
|
+
const n = numberOf(lower);
|
|
1577
|
+
if (n != null) return { pos: "number", type: "cardinal", value: n };
|
|
1578
|
+
const proper = lookupProperName(lexicon, w);
|
|
1579
|
+
if (proper) return { pos: "properName", type: "individual", canonical: proper };
|
|
1580
|
+
const noun = lookupNoun(lexicon, lower);
|
|
1581
|
+
if (noun) {
|
|
1582
|
+
return noun.property ? { pos: "noun", type: `${noun.property}-property`, lemma: noun.lemma, property: noun.property } : { pos: "noun", type: "class", lemma: noun.lemma };
|
|
1583
|
+
}
|
|
1584
|
+
const verb = lookupVerb(lexicon, lower);
|
|
1585
|
+
if (verb) {
|
|
1586
|
+
return { pos: "verb", type: "objectProperty", lemma: verb.lemma, predicate: predicateOf(verb), ...verb.prep ? { prep: verb.prep } : {} };
|
|
1587
|
+
}
|
|
1588
|
+
const adj = lookupAdjective(lexicon, lower);
|
|
1589
|
+
if (adj) return { pos: "adjective", type: adj.type, lemma: adj.lemma };
|
|
1590
|
+
return null;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// node_modules/@polycode-projects/the-mechanical-code-talker/src/grammar/ace.mjs
|
|
1594
|
+
var DET = /* @__PURE__ */ new Set(["a", "an", "the"]);
|
|
1595
|
+
var CODE_REF = /[./\\#:@]/;
|
|
1596
|
+
function tokenize(sentence) {
|
|
1597
|
+
return String(sentence ?? "").replace(/[‘’]/g, "'").replace(/[,;]/g, " ").replace(/[?!.]+\s*$/, "").trim().split(/\s+/).filter(Boolean);
|
|
1598
|
+
}
|
|
1599
|
+
var local = (term) => String(term).replace(/^tmct:/, "");
|
|
1600
|
+
var stripDet = (tokens) => tokens.length > 1 && DET.has(tokens[0].toLowerCase()) ? tokens.slice(1) : tokens;
|
|
1601
|
+
function resolveNP(lexicon, tokensIn) {
|
|
1602
|
+
const tokens = stripDet(tokensIn);
|
|
1603
|
+
if (tokens.length === 1) {
|
|
1604
|
+
const t = tokens[0];
|
|
1605
|
+
const proper = lookupProperName(lexicon, t);
|
|
1606
|
+
if (proper) return { term: `tmct:${proper}`, individual: true, extras: [], unknown: [] };
|
|
1607
|
+
if (CODE_REF.test(t)) return { term: `tmct:${t}`, individual: true, extras: [], unknown: [] };
|
|
1608
|
+
const noun = lookupNoun(lexicon, t);
|
|
1609
|
+
if (noun) return { term: `tmct:${noun.lemma}`, individual: false, noun, extras: [], unknown: [] };
|
|
1610
|
+
return { term: null, individual: false, extras: [], unknown: [t] };
|
|
1611
|
+
}
|
|
1612
|
+
if (tokens.length === 2) {
|
|
1613
|
+
const adj = lookupAdjective(lexicon, tokens[0]);
|
|
1614
|
+
const noun = lookupNoun(lexicon, tokens[1]);
|
|
1615
|
+
if (adj && noun) {
|
|
1616
|
+
const term = `tmct:${adj.lemma}-${noun.lemma}`;
|
|
1617
|
+
const extras = [
|
|
1618
|
+
{ subject: term, predicate: "rdfs:subClassOf", object: `tmct:${noun.lemma}`, kind: "rdfs:subClassOf" }
|
|
1619
|
+
];
|
|
1620
|
+
if (adj.type === "subclass") {
|
|
1621
|
+
extras.push({ subject: term, predicate: "rdfs:subClassOf", object: `tmct:${adj.lemma}`, kind: "rdfs:subClassOf" });
|
|
1622
|
+
} else {
|
|
1623
|
+
const r = `tmct:has-${adj.lemma}`;
|
|
1624
|
+
extras.push(
|
|
1625
|
+
{ subject: r, predicate: "rdf:type", object: "owl:Restriction", kind: "owl:hasValue" },
|
|
1626
|
+
{ subject: r, predicate: "owl:onProperty", object: adj.property || `tmct:${adj.lemma}`, kind: "owl:hasValue" },
|
|
1627
|
+
{ subject: r, predicate: "owl:hasValue", object: adj.value ?? "true", kind: "owl:hasValue" },
|
|
1628
|
+
{ subject: term, predicate: "rdfs:subClassOf", object: r, kind: "owl:hasValue" }
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
return { term, individual: false, noun, extras, unknown: [] };
|
|
1632
|
+
}
|
|
1633
|
+
const unknown = tokens.filter((t) => !classify(t, lexicon));
|
|
1634
|
+
return { term: null, individual: false, extras: [], unknown };
|
|
1635
|
+
}
|
|
1636
|
+
return { term: null, individual: false, extras: [], unknown: tokens.filter((t) => !classify(t, lexicon)) };
|
|
1637
|
+
}
|
|
1638
|
+
function missOrNull(pattern, nps, extraUnknown = []) {
|
|
1639
|
+
const residue = [...extraUnknown, ...nps.flatMap((np) => np.unknown)];
|
|
1640
|
+
return residue.length ? { pattern, triples: [], residue } : null;
|
|
1641
|
+
}
|
|
1642
|
+
var hit = (pattern, nps, triples, more = {}) => ({
|
|
1643
|
+
pattern,
|
|
1644
|
+
triples: [...nps.flatMap((np) => np.extras), ...triples],
|
|
1645
|
+
residue: [],
|
|
1646
|
+
...more
|
|
1647
|
+
});
|
|
1648
|
+
function parseRelation(lexicon, toks, lower) {
|
|
1649
|
+
for (let i = 1; i < toks.length - 1; i += 1) {
|
|
1650
|
+
const verb = lookupVerb(lexicon, lower[i]);
|
|
1651
|
+
if (!verb) continue;
|
|
1652
|
+
let objStart = i + 1;
|
|
1653
|
+
if (verb.prep) {
|
|
1654
|
+
if (lower[objStart] !== verb.prep) continue;
|
|
1655
|
+
objStart += 1;
|
|
1656
|
+
if (objStart >= toks.length) continue;
|
|
1657
|
+
}
|
|
1658
|
+
const np1 = resolveNP(lexicon, toks.slice(0, i));
|
|
1659
|
+
const np2 = resolveNP(lexicon, toks.slice(objStart));
|
|
1660
|
+
if (np1.term == null || np2.term == null) return missOrNull("relation", [np1, np2]);
|
|
1661
|
+
return hit("relation", [np1, np2], [
|
|
1662
|
+
{ subject: np1.term, predicate: predicateOf(verb), object: np2.term, kind: "owl:ObjectProperty" }
|
|
1663
|
+
]);
|
|
1664
|
+
}
|
|
1665
|
+
const content = toks.filter((t) => !DET.has(t.toLowerCase()));
|
|
1666
|
+
if (content.length === 3 && !classify(content[1], lexicon)) {
|
|
1667
|
+
const np1 = resolveNP(lexicon, [content[0]]);
|
|
1668
|
+
const np2 = resolveNP(lexicon, [content[2]]);
|
|
1669
|
+
if (np1.term != null && np2.term != null) return { pattern: "relation", triples: [], residue: [content[1]] };
|
|
1670
|
+
}
|
|
1671
|
+
return null;
|
|
1672
|
+
}
|
|
1673
|
+
function adjectiveCopula(pattern, np1, adj) {
|
|
1674
|
+
if (np1.term == null) return missOrNull(pattern, [np1]);
|
|
1675
|
+
if (adj.type === "data") {
|
|
1676
|
+
return hit(pattern, [np1], [
|
|
1677
|
+
{ subject: np1.term, predicate: adj.property || `tmct:${adj.lemma}`, object: adj.value ?? "true", kind: "owl:DatatypeProperty" }
|
|
1678
|
+
]);
|
|
1679
|
+
}
|
|
1680
|
+
const predicate = np1.individual ? "rdf:type" : "rdfs:subClassOf";
|
|
1681
|
+
return hit(pattern, [np1], [
|
|
1682
|
+
{ subject: np1.term, predicate, object: `tmct:${adj.lemma}`, kind: predicate }
|
|
1683
|
+
]);
|
|
1684
|
+
}
|
|
1685
|
+
function parseRestriction(lexicon, toks, lower, thatIdx) {
|
|
1686
|
+
const isIdx = lower.indexOf("is", thatIdx + 2);
|
|
1687
|
+
if (isIdx < 0 || thatIdx + 1 >= isIdx) return null;
|
|
1688
|
+
const verb = lookupVerb(lexicon, lower[thatIdx + 1]);
|
|
1689
|
+
const np1 = resolveNP(lexicon, toks.slice(1, thatIdx));
|
|
1690
|
+
let objStart = thatIdx + 2;
|
|
1691
|
+
if (verb?.prep) {
|
|
1692
|
+
if (lower[objStart] !== verb.prep) return null;
|
|
1693
|
+
objStart += 1;
|
|
1694
|
+
}
|
|
1695
|
+
const np2 = resolveNP(lexicon, toks.slice(objStart, isIdx));
|
|
1696
|
+
const np3 = resolveNP(lexicon, toks.slice(isIdx + 1));
|
|
1697
|
+
if (!verb) return missOrNull("someValuesFrom", [np1, np2, np3], [toks[thatIdx + 1]]);
|
|
1698
|
+
if (np1.term == null || np2.term == null || np3.term == null) {
|
|
1699
|
+
return missOrNull("someValuesFrom", [np1, np2, np3]);
|
|
1700
|
+
}
|
|
1701
|
+
if (np1.individual || np2.individual || np3.individual) return null;
|
|
1702
|
+
const pred = predicateOf(verb);
|
|
1703
|
+
const k = "owl:someValuesFrom";
|
|
1704
|
+
const r = `tmct:some-${local(pred)}-${local(np2.term)}`;
|
|
1705
|
+
const inter = `tmct:${local(np1.term)}-that-${local(pred)}-${local(np2.term)}`;
|
|
1706
|
+
return hit("someValuesFrom", [np1, np2, np3], [
|
|
1707
|
+
{ subject: r, predicate: "rdf:type", object: "owl:Restriction", kind: k },
|
|
1708
|
+
{ subject: r, predicate: "owl:onProperty", object: pred, kind: k },
|
|
1709
|
+
{ subject: r, predicate: "owl:someValuesFrom", object: np2.term, kind: k },
|
|
1710
|
+
{ subject: inter, predicate: "owl:intersectionOf", object: np1.term, kind: k },
|
|
1711
|
+
{ subject: inter, predicate: "owl:intersectionOf", object: r, kind: k },
|
|
1712
|
+
{ subject: inter, predicate: "rdfs:subClassOf", object: np3.term, kind: k }
|
|
1713
|
+
]);
|
|
1714
|
+
}
|
|
1715
|
+
function parseCardinality(lexicon, toks, lower, hasIdx) {
|
|
1716
|
+
let kind = null;
|
|
1717
|
+
let nIdx = -1;
|
|
1718
|
+
if (lower[hasIdx + 1] === "at" && lower[hasIdx + 2] === "least") {
|
|
1719
|
+
kind = "owl:minCardinality";
|
|
1720
|
+
nIdx = hasIdx + 3;
|
|
1721
|
+
} else if (lower[hasIdx + 1] === "at" && lower[hasIdx + 2] === "most") {
|
|
1722
|
+
kind = "owl:maxCardinality";
|
|
1723
|
+
nIdx = hasIdx + 3;
|
|
1724
|
+
} else if (lower[hasIdx + 1] === "exactly") {
|
|
1725
|
+
kind = "owl:cardinality";
|
|
1726
|
+
nIdx = hasIdx + 2;
|
|
1727
|
+
} else return null;
|
|
1728
|
+
const n = numberOf(lower[nIdx]);
|
|
1729
|
+
if (n == null || nIdx + 1 >= toks.length) return null;
|
|
1730
|
+
const np1 = resolveNP(lexicon, toks.slice(1, hasIdx));
|
|
1731
|
+
const np2 = resolveNP(lexicon, toks.slice(nIdx + 1));
|
|
1732
|
+
if (np1.term == null || np2.term == null) return missOrNull("cardinality", [np1, np2]);
|
|
1733
|
+
if (np1.individual || np2.individual) return null;
|
|
1734
|
+
const tag = { "owl:minCardinality": "min", "owl:maxCardinality": "max", "owl:cardinality": "exactly" }[kind];
|
|
1735
|
+
const r = `tmct:${tag}-${n}-${local(np2.term)}`;
|
|
1736
|
+
return hit("cardinality", [np1, np2], [
|
|
1737
|
+
{ subject: r, predicate: "rdf:type", object: "owl:Restriction", kind },
|
|
1738
|
+
{ subject: r, predicate: "owl:onProperty", object: "tmct:has", kind },
|
|
1739
|
+
{ subject: r, predicate: kind, object: String(n), kind, n },
|
|
1740
|
+
{ subject: r, predicate: "owl:onClass", object: np2.term, kind },
|
|
1741
|
+
{ subject: np1.term, predicate: "rdfs:subClassOf", object: r, kind }
|
|
1742
|
+
], { n });
|
|
1743
|
+
}
|
|
1744
|
+
function parseEvery(lexicon, toks, lower) {
|
|
1745
|
+
const thatIdx = lower.indexOf("that");
|
|
1746
|
+
if (thatIdx > 1) return parseRestriction(lexicon, toks, lower, thatIdx);
|
|
1747
|
+
const hasIdx = lower.indexOf("has");
|
|
1748
|
+
if (hasIdx > 1 && (lower[hasIdx + 1] === "at" || lower[hasIdx + 1] === "exactly")) {
|
|
1749
|
+
return parseCardinality(lexicon, toks, lower, hasIdx);
|
|
1750
|
+
}
|
|
1751
|
+
const isIdx = lower.indexOf("is");
|
|
1752
|
+
if (isIdx <= 1 || isIdx === toks.length - 1) return null;
|
|
1753
|
+
const np1 = resolveNP(lexicon, toks.slice(1, isIdx));
|
|
1754
|
+
const rest = toks.slice(isIdx + 1);
|
|
1755
|
+
if (rest.length === 1) {
|
|
1756
|
+
const adj = lookupAdjective(lexicon, rest[0]);
|
|
1757
|
+
if (adj) return adjectiveCopula("adjective", np1, adj);
|
|
1758
|
+
}
|
|
1759
|
+
const np2 = resolveNP(lexicon, rest);
|
|
1760
|
+
if (np1.term == null || np2.term == null) return missOrNull("subClassOf", [np1, np2]);
|
|
1761
|
+
if (np1.individual || np2.individual) return null;
|
|
1762
|
+
return hit("subClassOf", [np1, np2], [
|
|
1763
|
+
{ subject: np1.term, predicate: "rdfs:subClassOf", object: np2.term, kind: "rdfs:subClassOf" }
|
|
1764
|
+
]);
|
|
1765
|
+
}
|
|
1766
|
+
function parseDisjoint(lexicon, toks, lower) {
|
|
1767
|
+
const isIdx = lower.indexOf("is");
|
|
1768
|
+
if (isIdx <= 1 || isIdx === toks.length - 1) return null;
|
|
1769
|
+
const np1 = resolveNP(lexicon, toks.slice(1, isIdx));
|
|
1770
|
+
const np2 = resolveNP(lexicon, toks.slice(isIdx + 1));
|
|
1771
|
+
if (np1.term == null || np2.term == null) return missOrNull("disjointWith", [np1, np2]);
|
|
1772
|
+
if (np1.individual || np2.individual) return null;
|
|
1773
|
+
return hit("disjointWith", [np1, np2], [
|
|
1774
|
+
{ subject: np1.term, predicate: "owl:disjointWith", object: np2.term, kind: "owl:disjointWith" }
|
|
1775
|
+
]);
|
|
1776
|
+
}
|
|
1777
|
+
function buildPossessive(lexicon, ownerToks, headToks, valueToks) {
|
|
1778
|
+
const owner = resolveNP(lexicon, ownerToks);
|
|
1779
|
+
if (headToks.length !== 1) return null;
|
|
1780
|
+
const head = lookupNoun(lexicon, headToks[0]);
|
|
1781
|
+
if (!head) return missOrNull("possessive", [owner], [headToks[0]]);
|
|
1782
|
+
if (owner.term == null) return missOrNull("possessive", [owner]);
|
|
1783
|
+
if (!valueToks.length) return null;
|
|
1784
|
+
const predicate = `tmct:${head.lemma}`;
|
|
1785
|
+
if ((head.property || "data") === "object") {
|
|
1786
|
+
const value = resolveNP(lexicon, valueToks);
|
|
1787
|
+
if (value.term == null) return missOrNull("possessive", [owner, value]);
|
|
1788
|
+
return hit("possessive", [owner, value], [
|
|
1789
|
+
{ subject: owner.term, predicate, object: value.term, kind: "owl:ObjectProperty" }
|
|
1790
|
+
]);
|
|
1791
|
+
}
|
|
1792
|
+
return hit("possessive", [owner], [
|
|
1793
|
+
{ subject: owner.term, predicate, object: valueToks.join(" "), kind: "owl:DatatypeProperty" }
|
|
1794
|
+
]);
|
|
1795
|
+
}
|
|
1796
|
+
function parsePossessive(lexicon, toks, lower) {
|
|
1797
|
+
const ownerRaw = toks[0].replace(/'s$/i, "");
|
|
1798
|
+
const isIdx = lower.indexOf("is");
|
|
1799
|
+
if (isIdx < 2 || !ownerRaw) return null;
|
|
1800
|
+
return buildPossessive(lexicon, [ownerRaw], toks.slice(1, isIdx), toks.slice(isIdx + 1));
|
|
1801
|
+
}
|
|
1802
|
+
function parseOfForm(lexicon, toks, lower) {
|
|
1803
|
+
const ofIdx = lower.indexOf("of");
|
|
1804
|
+
const isIdx = lower.indexOf("is", ofIdx + 1);
|
|
1805
|
+
if (ofIdx < 2 || isIdx < ofIdx + 2) return null;
|
|
1806
|
+
return buildPossessive(lexicon, toks.slice(ofIdx + 1, isIdx), toks.slice(1, ofIdx), toks.slice(isIdx + 1));
|
|
1807
|
+
}
|
|
1808
|
+
function parseCopula(lexicon, toks, lower, isIdx) {
|
|
1809
|
+
const np1 = resolveNP(lexicon, toks.slice(0, isIdx));
|
|
1810
|
+
const rest = toks.slice(isIdx + 1);
|
|
1811
|
+
if (!rest.length) return null;
|
|
1812
|
+
if (rest.length === 1) {
|
|
1813
|
+
const adj = lookupAdjective(lexicon, rest[0]);
|
|
1814
|
+
if (adj) return adjectiveCopula("adjective", np1, adj);
|
|
1815
|
+
}
|
|
1816
|
+
const np2 = resolveNP(lexicon, rest);
|
|
1817
|
+
if (np1.term == null || np2.term == null) {
|
|
1818
|
+
return missOrNull(np1.individual ? "typeAssertion" : "subClassOf", [np1, np2]);
|
|
1819
|
+
}
|
|
1820
|
+
if (np2.individual) return null;
|
|
1821
|
+
if (np1.individual) {
|
|
1822
|
+
return hit("typeAssertion", [np1, np2], [
|
|
1823
|
+
{ subject: np1.term, predicate: "rdf:type", object: np2.term, kind: "rdf:type" }
|
|
1824
|
+
]);
|
|
1825
|
+
}
|
|
1826
|
+
return hit("subClassOf", [np1, np2], [
|
|
1827
|
+
{ subject: np1.term, predicate: "rdfs:subClassOf", object: np2.term, kind: "rdfs:subClassOf" }
|
|
1828
|
+
]);
|
|
1829
|
+
}
|
|
1830
|
+
function parseAce(sentence, lexicon = loadLexicon()) {
|
|
1831
|
+
const toks = tokenize(sentence);
|
|
1832
|
+
if (toks.length < 3) return null;
|
|
1833
|
+
const lower = toks.map((t) => t.toLowerCase());
|
|
1834
|
+
if (lower[0] === "every") return parseEvery(lexicon, toks, lower);
|
|
1835
|
+
if (lower[0] === "no") return parseDisjoint(lexicon, toks, lower);
|
|
1836
|
+
if (/'s$/.test(lower[0]) && lower[0].length > 2) return parsePossessive(lexicon, toks, lower);
|
|
1837
|
+
if (lower[0] === "the" && lower.includes("of") && lower.includes("is")) {
|
|
1838
|
+
return parseOfForm(lexicon, toks, lower);
|
|
1839
|
+
}
|
|
1840
|
+
const isIdx = lower.indexOf("is");
|
|
1841
|
+
if (isIdx > 0) return parseCopula(lexicon, toks, lower, isIdx);
|
|
1842
|
+
return parseRelation(lexicon, toks, lower);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
// node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/strategies/ace.mjs
|
|
1846
|
+
function runAce(text) {
|
|
1847
|
+
let parsed = null;
|
|
1848
|
+
try {
|
|
1849
|
+
parsed = parseAce(text);
|
|
1850
|
+
} catch {
|
|
1851
|
+
return null;
|
|
1852
|
+
}
|
|
1853
|
+
if (!parsed || !Array.isArray(parsed.triples) || parsed.triples.length === 0) return null;
|
|
1854
|
+
return { strategyId: "ace", class: "ace-fact", candidates: [{ parsed, confidence: 0.85, via: "exact", note: `ACE ${parsed.pattern}` }] };
|
|
1855
|
+
}
|
|
1856
|
+
var aceStrategy = {
|
|
1857
|
+
id: "ace",
|
|
1858
|
+
class: "ace-fact",
|
|
1859
|
+
// eslint-disable-next-line require-await
|
|
1860
|
+
async run(text) {
|
|
1861
|
+
return runAce(text);
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1864
|
+
|
|
1359
1865
|
// node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/merge.mjs
|
|
1360
1866
|
var DEFAULT_CONFIDENCE = 0.5;
|
|
1361
1867
|
var cmpTerm = (s) => String(s || "").trim().toLowerCase().replace(/\s+/g, " ").replace(/^commit\s+(?=[0-9a-f]{7,40}$)/, "");
|
|
@@ -1422,7 +1928,7 @@
|
|
|
1422
1928
|
var randomBytes4 = unavailable4("randomBytes");
|
|
1423
1929
|
|
|
1424
1930
|
// node_modules/@polycode-projects/the-mechanical-code-talker/src/wink-model.mjs
|
|
1425
|
-
var
|
|
1931
|
+
var import_meta2 = {};
|
|
1426
1932
|
var injected;
|
|
1427
1933
|
var cached;
|
|
1428
1934
|
function loadWinkModel() {
|
|
@@ -1436,7 +1942,7 @@
|
|
|
1436
1942
|
return cached;
|
|
1437
1943
|
}
|
|
1438
1944
|
function nodeRequireWink() {
|
|
1439
|
-
const require2 = createRequire4(
|
|
1945
|
+
const require2 = createRequire4(import_meta2.url);
|
|
1440
1946
|
return {
|
|
1441
1947
|
winkNLP: require2("wink-nlp"),
|
|
1442
1948
|
model: require2("wink-eng-lite-web-model")
|
|
@@ -1526,7 +2032,8 @@
|
|
|
1526
2032
|
}
|
|
1527
2033
|
|
|
1528
2034
|
// node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/pipeline.mjs
|
|
1529
|
-
var
|
|
2035
|
+
var OPTIONAL_STRATEGIES = typeof aceStrategy !== "undefined" ? [aceStrategy] : [];
|
|
2036
|
+
var STRATEGIES = [grammarStrategy, keywordSpotStrategy, noiseStripStrategy, ...OPTIONAL_STRATEGIES];
|
|
1530
2037
|
function runStrategiesSync(text, ctx = {}, strategies = STRATEGIES) {
|
|
1531
2038
|
const results = [];
|
|
1532
2039
|
for (const s of strategies) {
|
|
@@ -1577,7 +2084,7 @@
|
|
|
1577
2084
|
const adapter = nlp === void 0 ? defaultNlp() : nlp;
|
|
1578
2085
|
const raw = String(query || "").trim().replace(/\s+/g, " ");
|
|
1579
2086
|
if (!raw) return null;
|
|
1580
|
-
const text = applyNegationFrames(normalizeQuery(raw));
|
|
2087
|
+
const text = applyPhrasingFrames(applyNegationFrames(normalizeQuery(raw)));
|
|
1581
2088
|
if (!text) return null;
|
|
1582
2089
|
const composite = parseComposite(text, adapter);
|
|
1583
2090
|
if (composite) return composite;
|
|
@@ -1596,7 +2103,7 @@
|
|
|
1596
2103
|
function parseComposite(text, nlp) {
|
|
1597
2104
|
const w = splitWords(text);
|
|
1598
2105
|
const lc = w.map((x) => x.toLowerCase());
|
|
1599
|
-
return parseNegation(text, nlp, 0) || parseForwardNegation(w, lc, nlp) || parseAnaphora(w, lc, nlp) || parseAggregate(w, lc, nlp) || parseSuperlative(w, lc, nlp) || parseList(w, lc, nlp, 0) || parseNested(w, lc, nlp, 0) || parseRelationalOrQualified(w, lc, nlp, 0);
|
|
2106
|
+
return parseNegation(text, nlp, 0) || parseForwardNegation(w, lc, nlp) || parseTemporal(w, lc, nlp, 0) || parseAnaphora(w, lc, nlp) || parseAggregate(w, lc, nlp) || parseSuperlative(w, lc, nlp) || parseList(w, lc, nlp, 0) || parseNested(w, lc, nlp, 0) || parseRelationalOrQualified(w, lc, nlp, 0);
|
|
1600
2107
|
}
|
|
1601
2108
|
function complementAst(entityType, diffAtom) {
|
|
1602
2109
|
return {
|
|
@@ -1693,6 +2200,58 @@
|
|
|
1693
2200
|
}
|
|
1694
2201
|
return null;
|
|
1695
2202
|
}
|
|
2203
|
+
var TEMPORAL_AUX = /* @__PURE__ */ new Set(["did", "was", "were", "do", "does", "has", "have", "had"]);
|
|
2204
|
+
var TEMPORAL_TAIL = /* @__PURE__ */ new Set([
|
|
2205
|
+
"change",
|
|
2206
|
+
"changed",
|
|
2207
|
+
"changes",
|
|
2208
|
+
"update",
|
|
2209
|
+
"updated",
|
|
2210
|
+
"updates",
|
|
2211
|
+
"modify",
|
|
2212
|
+
"modified",
|
|
2213
|
+
"modifies",
|
|
2214
|
+
"touch",
|
|
2215
|
+
"touched",
|
|
2216
|
+
"touches",
|
|
2217
|
+
"edit",
|
|
2218
|
+
"edited",
|
|
2219
|
+
"revise",
|
|
2220
|
+
"revised"
|
|
2221
|
+
]);
|
|
2222
|
+
var TEMPORAL_TRAIL_FILLER = /* @__PURE__ */ new Set(["last", "recently", "ever", "get", "got", "been", "then", "now", "already"]);
|
|
2223
|
+
var TEMPORAL_DET = /* @__PURE__ */ new Set(["the", "a", "an", "all", "those", "these", "any"]);
|
|
2224
|
+
function parseTemporal(w, lc, nlp, depth = 0) {
|
|
2225
|
+
if (lc[0] !== "when") return null;
|
|
2226
|
+
let i = 1;
|
|
2227
|
+
if (!TEMPORAL_AUX.has(lc[i])) return null;
|
|
2228
|
+
i += 1;
|
|
2229
|
+
let t = -1;
|
|
2230
|
+
for (let k = lc.length - 1; k >= i; k -= 1) {
|
|
2231
|
+
if (TEMPORAL_TAIL.has(lc[k])) {
|
|
2232
|
+
t = k;
|
|
2233
|
+
break;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
if (t < 0) return null;
|
|
2237
|
+
let subjWords = w.slice(i, t);
|
|
2238
|
+
let subjLc = lc.slice(i, t);
|
|
2239
|
+
while (subjLc.length && TEMPORAL_TRAIL_FILLER.has(subjLc[subjLc.length - 1])) {
|
|
2240
|
+
subjWords = subjWords.slice(0, -1);
|
|
2241
|
+
subjLc = subjLc.slice(0, -1);
|
|
2242
|
+
}
|
|
2243
|
+
while (subjLc.length && TEMPORAL_DET.has(subjLc[0])) {
|
|
2244
|
+
subjWords = subjWords.slice(1);
|
|
2245
|
+
subjLc = subjLc.slice(1);
|
|
2246
|
+
}
|
|
2247
|
+
if (!subjWords.length) return null;
|
|
2248
|
+
if (!subjLc.some((x) => RELATIVE_PRONOUNS.includes(x))) return null;
|
|
2249
|
+
const framed = FRAME_WORDS.has(subjLc[0]) ? subjWords.join(" ") : `which ${subjWords.join(" ")}`;
|
|
2250
|
+
const inner = parseSetPhrase(framed, nlp, depth + 1);
|
|
2251
|
+
if (!inner || inner.node === "miss") return inner ? { node: "miss", reason: inner.reason || "the inner set of the temporal query didn't parse" } : { node: "miss", reason: "the inner set of the temporal query didn't parse" };
|
|
2252
|
+
const noun = entityNoun(subjLc[0]);
|
|
2253
|
+
return { node: "temporal", inner, entityType: noun && noun.entityType || null };
|
|
2254
|
+
}
|
|
1696
2255
|
function parseAnaphora(w, lc, nlp) {
|
|
1697
2256
|
let p = -1;
|
|
1698
2257
|
let viaOf = false;
|
|
@@ -1958,21 +2517,21 @@
|
|
|
1958
2517
|
let start = 0;
|
|
1959
2518
|
let i = 0;
|
|
1960
2519
|
while (i < predLc.length) {
|
|
1961
|
-
let
|
|
2520
|
+
let hit2 = null;
|
|
1962
2521
|
for (const c of conns) {
|
|
1963
2522
|
const cw = c.split(" ");
|
|
1964
2523
|
if (predLc.slice(i, i + cw.length).join(" ") === c) {
|
|
1965
|
-
|
|
2524
|
+
hit2 = { c, len: cw.length };
|
|
1966
2525
|
break;
|
|
1967
2526
|
}
|
|
1968
2527
|
}
|
|
1969
|
-
if (
|
|
2528
|
+
if (hit2 && i > start) {
|
|
1970
2529
|
branches.push(predWords.slice(start, i));
|
|
1971
|
-
ops.push(BOOLEAN_CONNECTIVES[
|
|
1972
|
-
i +=
|
|
2530
|
+
ops.push(BOOLEAN_CONNECTIVES[hit2.c]);
|
|
2531
|
+
i += hit2.len;
|
|
1973
2532
|
start = i;
|
|
1974
|
-
} else if (
|
|
1975
|
-
i +=
|
|
2533
|
+
} else if (hit2) {
|
|
2534
|
+
i += hit2.len;
|
|
1976
2535
|
start = i;
|
|
1977
2536
|
} else i += 1;
|
|
1978
2537
|
}
|
|
@@ -1985,7 +2544,9 @@
|
|
|
1985
2544
|
const edges2 = edgesOfKind(graph, symbolKind).filter((e) => objectIds.has(e.object));
|
|
1986
2545
|
return uniqueById(edges2.map((e) => graph.byId.get(e.subject)).filter((s) => s && s.class === entityType));
|
|
1987
2546
|
}
|
|
1988
|
-
const
|
|
2547
|
+
const objHasFine = !!symbolKind && [...objectIds].some((id) => FINE_ENTITY_TYPES.has(graph.byId.get(id)?.class));
|
|
2548
|
+
const scanKinds = objHasFine ? [...kindsFor(kind), symbolKind] : kindsFor(kind);
|
|
2549
|
+
const edges = scanKinds.flatMap((k) => edgesOfKind(graph, k)).filter((e) => objectIds.has(e.object));
|
|
1989
2550
|
const subjects = uniqueById(edges.map((e) => graph.byId.get(e.subject)).filter(Boolean));
|
|
1990
2551
|
if (!entityType || entityType === "Change") return subjects;
|
|
1991
2552
|
const direct = subjects.filter((s) => s.class === entityType);
|
|
@@ -2165,6 +2726,15 @@
|
|
|
2165
2726
|
}
|
|
2166
2727
|
return n;
|
|
2167
2728
|
}
|
|
2729
|
+
function evalTemporal(graph, ast, opts) {
|
|
2730
|
+
const inner = evalSet(graph, ast.inner, opts);
|
|
2731
|
+
const ids = new Set(inner.map((i) => i.id));
|
|
2732
|
+
if (!ids.size) return { compositeKind: "temporal", matches: [], entityType: ast.entityType, innerCount: 0 };
|
|
2733
|
+
const commits = reverseOverSet(graph, "touches", "Commit", ids);
|
|
2734
|
+
const dateOf = (c) => String((c.attributes || []).find((a) => a.key === "date")?.value || "");
|
|
2735
|
+
commits.sort((a, b) => dateOf(b).localeCompare(dateOf(a)));
|
|
2736
|
+
return { compositeKind: "temporal", matches: commits, entityType: ast.entityType, innerCount: inner.length };
|
|
2737
|
+
}
|
|
2168
2738
|
function evalSuperlative(graph, ast) {
|
|
2169
2739
|
const pool = graph.individuals.filter((i) => i.class === ast.entityType);
|
|
2170
2740
|
const scored = pool.map((ind) => ({ ind, score: degreeMetric(graph, ind, ast.metric) })).sort((a, z) => ast.extreme === "most" ? z.score - a.score : a.score - z.score);
|
|
@@ -2178,6 +2748,7 @@
|
|
|
2178
2748
|
if (ast.node === "count") return { compositeKind: "count", count: evalSet(graph, ast.base, opts).length, entityType: ast.entityType, matches: [] };
|
|
2179
2749
|
if (ast.node === "list") return { compositeKind: "list", matches: evalSet(graph, ast.base, opts), entityType: ast.entityType, scoped: ast.scoped };
|
|
2180
2750
|
if (ast.node === "superlative") return evalSuperlative(graph, ast);
|
|
2751
|
+
if (ast.node === "temporal") return evalTemporal(graph, ast, opts);
|
|
2181
2752
|
if (ast.node === "anaphora") return evalAnaphora(graph, ast, opts);
|
|
2182
2753
|
return { compositeKind: "set", matches: evalSet(graph, ast, opts), entityType: ast.entityType || null };
|
|
2183
2754
|
}
|
|
@@ -2215,6 +2786,31 @@
|
|
|
2215
2786
|
matches: result.matches
|
|
2216
2787
|
};
|
|
2217
2788
|
}
|
|
2789
|
+
if (result.compositeKind === "temporal") {
|
|
2790
|
+
const n = result.innerCount || 0;
|
|
2791
|
+
const setNoun = result.entityType ? nounFor(result.entityType, n || 2) : n === 1 ? "entity" : "entities";
|
|
2792
|
+
const wasWere = n === 1 ? "was" : "were";
|
|
2793
|
+
if (!n) {
|
|
2794
|
+
return { content: `nothing in the index matches the inner set, so there is no change history to date.`, miss: true, ambiguous: false, matches: [] };
|
|
2795
|
+
}
|
|
2796
|
+
if (!result.matches.length) {
|
|
2797
|
+
return { content: `no recorded commit touched the ${n} ${setNoun} in that set in this index.`, miss: true, ambiguous: false, matches: [] };
|
|
2798
|
+
}
|
|
2799
|
+
const newest = result.matches[0];
|
|
2800
|
+
const date = (newest.attributes || []).find((a) => a.key === "date")?.value || "";
|
|
2801
|
+
if (!date) {
|
|
2802
|
+
return { content: `the ${setNoun} in that set ${wasWere} last touched by commit ${newest.label}, but this index records no commit dates \u2014 regenerate the graph to attach mgx:commitDate.`, miss: true, ambiguous: false, matches: result.matches };
|
|
2803
|
+
}
|
|
2804
|
+
const msg = (newest.attributes || []).find((a) => a.key === "message")?.value || "";
|
|
2805
|
+
const day = String(date).slice(0, 10);
|
|
2806
|
+
const more = result.matches.length - 1;
|
|
2807
|
+
return {
|
|
2808
|
+
content: `the ${setNoun} in that set ${wasWere} last touched by commit ${newest.label} on ${day}${msg ? ` ("${msg}")` : ""}${more ? `; ${more} earlier commit${more === 1 ? "" : "s"} recorded` : ""}.`,
|
|
2809
|
+
miss: false,
|
|
2810
|
+
ambiguous: false,
|
|
2811
|
+
matches: result.matches
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2218
2814
|
if (!result.matches.length) {
|
|
2219
2815
|
return { content: `nothing in the index matches that${result.entityType ? ` (${nounFor(result.entityType, 2)})` : ""}.`, miss: true, ambiguous: false, matches: [] };
|
|
2220
2816
|
}
|
|
@@ -2562,6 +3158,11 @@
|
|
|
2562
3158
|
const label = String(ind.label || ind.id || "");
|
|
2563
3159
|
return ["Function", "Method"].includes(ind.class) ? `function ${label}()` : label;
|
|
2564
3160
|
}
|
|
3161
|
+
function commitRefOf(ind) {
|
|
3162
|
+
const sha = String(ind.label || ind.id || "");
|
|
3163
|
+
const author = (ind.attributes || []).find((a) => a.key === "author")?.value;
|
|
3164
|
+
return author ? `${sha} (${author})` : sha;
|
|
3165
|
+
}
|
|
2565
3166
|
function listJoin(syms) {
|
|
2566
3167
|
return syms.length > 1 ? `${syms.slice(0, -1).join(", ")} and ${syms[syms.length - 1]}` : syms[0];
|
|
2567
3168
|
}
|
|
@@ -2738,6 +3339,14 @@
|
|
|
2738
3339
|
ambiguous: false
|
|
2739
3340
|
};
|
|
2740
3341
|
}
|
|
3342
|
+
if (parsed.kind === "tests" && !parsed.entityType) {
|
|
3343
|
+
const obj = String(parsed.object || "").replace(/^cover(?:s|ing)?\s+/i, "").trim();
|
|
3344
|
+
return {
|
|
3345
|
+
content: `No tests cover ${obj}. (traversal: ${result.traversal || "no traversal resolved"})`,
|
|
3346
|
+
miss: true,
|
|
3347
|
+
ambiguous: false
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
2741
3350
|
const entityWord = nounFor(parsed.entityType || "Module", 2);
|
|
2742
3351
|
return {
|
|
2743
3352
|
content: `No ${entityWord} found whose module directly ${verbFor(parsed.kind)} ${parsed.object}. (traversal: ${result.traversal || "no traversal resolved"})`,
|
|
@@ -2746,7 +3355,7 @@
|
|
|
2746
3355
|
};
|
|
2747
3356
|
}
|
|
2748
3357
|
if (parsed.shape === "forward" || parsed.entityType === "Module" || result.matches.every((m) => !FINE_ENTITY_TYPES.has(m.class))) {
|
|
2749
|
-
const shown = result.matches.slice(0, OVERFLOW_CAP).map((m) => m.label);
|
|
3358
|
+
const shown = result.matches.slice(0, OVERFLOW_CAP).map((m) => m.class === "Commit" ? commitRefOf(m) : m.label);
|
|
2750
3359
|
const extra2 = result.matches.length > OVERFLOW_CAP ? `, \u2026and ${result.matches.length - OVERFLOW_CAP} more` : "";
|
|
2751
3360
|
return { content: shown.join(" and ") + extra2 + ".", miss: false, ambiguous: false, matches: result.matches };
|
|
2752
3361
|
}
|
|
@@ -2806,17 +3415,17 @@
|
|
|
2806
3415
|
function fuzzyCascadeWord(w) {
|
|
2807
3416
|
const bound = fuzzyBound(w);
|
|
2808
3417
|
let best = bound + 1;
|
|
2809
|
-
let
|
|
3418
|
+
let hit2 = null;
|
|
2810
3419
|
let tied = false;
|
|
2811
3420
|
for (const target of CASCADE_FUZZY_TARGETS) {
|
|
2812
3421
|
const d = editDistance(w, target, Math.min(best, bound));
|
|
2813
3422
|
if (d < best) {
|
|
2814
3423
|
best = d;
|
|
2815
|
-
|
|
3424
|
+
hit2 = target;
|
|
2816
3425
|
tied = false;
|
|
2817
|
-
} else if (d === best && d <= bound && target !==
|
|
3426
|
+
} else if (d === best && d <= bound && target !== hit2) tied = true;
|
|
2818
3427
|
}
|
|
2819
|
-
return best <= bound && !tied ?
|
|
3428
|
+
return best <= bound && !tied ? hit2 : null;
|
|
2820
3429
|
}
|
|
2821
3430
|
function schemaTypoTrap(resolution, term) {
|
|
2822
3431
|
if (!resolution?.match || resolution.matchedVia !== "fuzzy" || resolution.ambiguous) return false;
|
|
@@ -2875,7 +3484,7 @@
|
|
|
2875
3484
|
const rendered = render(p, traverse(graph, p, { contextId, prev }));
|
|
2876
3485
|
return rendered.miss ? null : { parsed: p, text };
|
|
2877
3486
|
};
|
|
2878
|
-
const done = (
|
|
3487
|
+
const done = (hit2) => ({ parsed: hit2.parsed, from, to: hit2.text, dropped: [...dropped], steps });
|
|
2879
3488
|
let guard = 0;
|
|
2880
3489
|
const hardCap = Math.max(tokens.length, 1) + 12;
|
|
2881
3490
|
for (; guard < hardCap; guard += 1) {
|
|
@@ -2892,8 +3501,8 @@
|
|
|
2892
3501
|
tokens = tokens.filter((_, i) => i !== idx);
|
|
2893
3502
|
dropped.push(removed);
|
|
2894
3503
|
steps.push(`strip noise "${removed}" \u2192 "${tokens.join(" ")}"`);
|
|
2895
|
-
const
|
|
2896
|
-
if (
|
|
3504
|
+
const hit2 = attempt(tokens);
|
|
3505
|
+
if (hit2) return done(hit2);
|
|
2897
3506
|
}
|
|
2898
3507
|
const survivors = [];
|
|
2899
3508
|
const nowDropped = [];
|
|
@@ -2918,8 +3527,8 @@
|
|
|
2918
3527
|
dropped.push(...nowDropped);
|
|
2919
3528
|
if (corrected.length) steps.push(`fuzzy-correct ${JSON.stringify(corrected)} \u2192 "${tokens.join(" ")}"`);
|
|
2920
3529
|
if (nowDropped.length) steps.push(`drop unmatched ${JSON.stringify(nowDropped)} \u2192 "${tokens.join(" ")}"`);
|
|
2921
|
-
const
|
|
2922
|
-
if (
|
|
3530
|
+
const hit2 = attempt(tokens);
|
|
3531
|
+
if (hit2) return done(hit2);
|
|
2923
3532
|
}
|
|
2924
3533
|
let changed = false;
|
|
2925
3534
|
const normed = tokens.map((t) => {
|
|
@@ -2932,8 +3541,8 @@
|
|
|
2932
3541
|
});
|
|
2933
3542
|
if (changed) {
|
|
2934
3543
|
steps.push(`normalise synonyms \u2192 "${normed.join(" ")}"`);
|
|
2935
|
-
const
|
|
2936
|
-
if (
|
|
3544
|
+
const hit2 = attempt(normed);
|
|
3545
|
+
if (hit2) return done(hit2);
|
|
2937
3546
|
}
|
|
2938
3547
|
const bareLc = splitWords(from).map((t) => t.toLowerCase());
|
|
2939
3548
|
const kindWords = [];
|
|
@@ -2945,10 +3554,10 @@
|
|
|
2945
3554
|
else others.push(t);
|
|
2946
3555
|
}
|
|
2947
3556
|
if (kindWords.length === 1 && others.length === 0) {
|
|
2948
|
-
const
|
|
2949
|
-
if (
|
|
3557
|
+
const hit2 = attempt(["count", kindWords[0]]);
|
|
3558
|
+
if (hit2) {
|
|
2950
3559
|
steps.push(`bare kind "${kindWords[0]}" \u2192 count`);
|
|
2951
|
-
return done(
|
|
3560
|
+
return done(hit2);
|
|
2952
3561
|
}
|
|
2953
3562
|
}
|
|
2954
3563
|
return null;
|
|
@@ -3013,7 +3622,7 @@
|
|
|
3013
3622
|
};
|
|
3014
3623
|
}
|
|
3015
3624
|
|
|
3016
|
-
//
|
|
3625
|
+
// src/ask-browser-entry.mjs
|
|
3017
3626
|
function parseEntities(payload) {
|
|
3018
3627
|
const individuals = Array.isArray(payload?.individuals) ? payload.individuals : [];
|
|
3019
3628
|
const byId = /* @__PURE__ */ new Map();
|