@polycode-projects/the-mechanical-code-talker 6.0.20 → 6.0.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "6.0.20",
3
+ "version": "6.0.21",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; indexes a repo on request (tmct index) or reads any producer's graph.",
@@ -29,6 +29,8 @@ export const MANIFEST_SORT_KEY = "manifest";
29
29
 
30
30
  /** The bands this repo ships a build pipeline and a loader for. */
31
31
  export const FIRST_CLASS_BANDS = Object.freeze([
32
+ "child",
33
+ "conceptnet",
32
34
  "wordnet-complete",
33
35
  ]);
34
36
 
@@ -38,6 +40,10 @@ export const FIRST_CLASS_BANDS = Object.freeze([
38
40
  * `notice` is a repo-relative path to the human-readable attribution file;
39
41
  * null when the licence carries no attribution burden. */
40
42
  export const BAND_LICENSES = Object.freeze({
43
+ // The child pack is ConceptNet-derived, so it carries ConceptNet's own
44
+ // share-alike terms rather than the maintainer-owned seed script's.
45
+ child: Object.freeze({ license: "CC-BY-SA-4.0", notice: "corpus/child/LICENSE-NOTICE" }),
46
+ conceptnet: Object.freeze({ license: "CC-BY-SA-4.0", notice: "corpus/conceptnet/LICENSE-NOTICE" }),
41
47
  "wordnet-complete": Object.freeze({ license: "CC-BY-4.0", notice: "corpus/wordnet/LICENSE-NOTICE" }),
42
48
  });
43
49
 
@@ -953,47 +953,67 @@ async function ingestResearchArticle(ctx, term, provider, article) {
953
953
  return { facts: distinct.size, derived };
954
954
  }
955
955
 
956
- // The one band this fallback reads. WordNet is dictionary content — real
957
- // words, not news entities — so it grounds the everyday nouns a headline
958
- // mentions in passing ("harbor", "senator") without ever costing a KB round
959
- // trip on those.
960
- const BAND_TERM_LOOKUP_BAND = "wordnet-complete";
956
+ // The bands this fallback reads. All three are reference content — dictionary
957
+ // senses and everyday commonsense edges, not news entities — so they ground
958
+ // the ordinary nouns a headline mentions in passing ("harbor", "senator",
959
+ // "penguin") without ever costing a KB round trip on those.
960
+ const BAND_TERM_LOOKUP_BANDS = Object.freeze(["child", "conceptnet", "wordnet-complete"]);
961
961
  const BAND_TERM_LOOKUP_LIMIT = 50;
962
962
  // A local DynamoDB Query has no courtesy throttle and nothing external to
963
963
  // protect, but it still has to return: the enrich cycle shares the same wall
964
964
  // budget every other phase does, and this runs once per candidate term. A
965
965
  // slow or hung Query loses the race and reads as a miss — a timeout is a
966
966
  // miss, never a guess — rather than stalling the whole cycle behind it.
967
+ // The bands are read in parallel against one deadline, so the budget buys
968
+ // every band at once: each is a single-partition begins_with read of its own,
969
+ // and the worst case stays one timeout however many bands there are.
967
970
  const BAND_TERM_LOOKUP_TIMEOUT_MS = 750;
968
971
 
969
- /** `term`'s rows from the wordnet-complete band, as `{subject, predicate,
970
- * object, provenance}` triples ready for `appendFacts` — or `[]` when the
971
- * band carries nothing for it, the query errors, or it doesn't return in
972
- * time. `ctx.providers.queryBandTerm` is absent on the browser surface and
973
- * in most tests, so this is a no-op there by construction, not a special
974
- * case here. */
975
- async function groundTermFromBand(ctx, term) {
976
- const queryBandTerm = ctx.providers?.queryBandTerm;
977
- if (typeof queryBandTerm !== "function") return [];
978
- const timeout = new Promise((resolve) => { setTimeout(() => resolve(null), BAND_TERM_LOOKUP_TIMEOUT_MS); });
972
+ /** `term`'s rows from one band, as `{subject, predicate, object, provenance}`
973
+ * triples — or `[]` when the band carries nothing for it or the Query
974
+ * throws. A band that fails is a band with nothing to say, so the others'
975
+ * rows survive it. */
976
+ async function bandTermFacts(queryBandTerm, band, term) {
979
977
  let response;
980
978
  try {
981
- response = await Promise.race([
982
- queryBandTerm({ band: BAND_TERM_LOOKUP_BAND, term, limit: BAND_TERM_LOOKUP_LIMIT }),
983
- timeout,
984
- ]);
979
+ response = await queryBandTerm({ band, term, limit: BAND_TERM_LOOKUP_LIMIT });
985
980
  } catch {
986
981
  return [];
987
982
  }
988
- if (!response) return [];
989
983
  const facts = [];
990
- for (const row of response.rows || []) {
984
+ for (const row of response?.rows || []) {
991
985
  const fact = factFromBandRow(row);
992
986
  if (fact) facts.push(fact);
993
987
  }
994
988
  return facts;
995
989
  }
996
990
 
991
+ /** `term`'s rows from every band, as `{subject, predicate, object,
992
+ * provenance}` triples ready for `appendFacts` — or `[]` when no band
993
+ * carries it. Each band's Query races the SAME deadline, so a band that
994
+ * hangs costs the cycle one timeout rather than one per band, and the bands
995
+ * that answered inside it keep their rows. A fact both a band and the seed
996
+ * already hold arrives with the band's own provenance, so it corroborates
997
+ * rather than duplicating. `ctx.providers.queryBandTerm` is absent on the
998
+ * browser surface and in most tests, so this is a no-op there by
999
+ * construction, not a special case here. */
1000
+ async function groundTermFromBand(ctx, term) {
1001
+ const queryBandTerm = ctx.providers?.queryBandTerm;
1002
+ if (typeof queryBandTerm !== "function") return [];
1003
+ let deadlineTimer;
1004
+ const deadline = new Promise((resolve) => {
1005
+ deadlineTimer = setTimeout(() => resolve(null), BAND_TERM_LOOKUP_TIMEOUT_MS);
1006
+ });
1007
+ try {
1008
+ const perBand = await Promise.all(BAND_TERM_LOOKUP_BANDS.map(
1009
+ (band) => Promise.race([bandTermFacts(queryBandTerm, band, term), deadline]),
1010
+ ));
1011
+ return perBand.flatMap((facts) => facts || []);
1012
+ } finally {
1013
+ clearTimeout(deadlineTimer);
1014
+ }
1015
+ }
1016
+
997
1017
  /** Grounds `term` from `facts` — a band's own rows, already resolved
998
1018
  * `{subject, predicate, object, provenance}` triples with no article prose
999
1019
  * to walk, so this skips straight to append + syllogise rather than
@@ -1084,10 +1104,10 @@ export async function enrichTopTerms(ctx, { limit } = {}) {
1084
1104
  if (aborted) { markTerm(ledger, entry.term, "pending", nowVal); break; }
1085
1105
  if (!hit) {
1086
1106
  // Every configured KB source came back empty (or none is configured) —
1087
- // try the wordnet-complete band before giving up. It's another source
1088
- // the resolver can reach, not a replacement for the KB walk above: a
1107
+ // try the corpus bands before giving up. They're another source the
1108
+ // resolver can reach, not a replacement for the KB walk above: a
1089
1109
  // headline's proper nouns still need a KB lookup, but its everyday
1090
- // vocabulary is often already sitting in the band.
1110
+ // vocabulary is often already sitting in a band.
1091
1111
  const bandFacts = await groundTermFromBand(ctx, entry.term);
1092
1112
  if (bandFacts.length) {
1093
1113
  const res = await ingestBandFacts(ctx, bandFacts);