@polycode-projects/the-mechanical-code-talker 0.6.0 → 0.7.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 CHANGED
@@ -49,6 +49,31 @@ under a guard that proves the protected spans came through byte-for-byte. Today
49
49
  that pass fixes the a/an article defect; broader voice and agreement rules are
50
50
  implemented but parked until they earn their place on the benchmark.
51
51
 
52
+ ## How it guides you
53
+
54
+ When you touch a **concept** without asking a precise question — "what is a
55
+ class", "what about imports", "what calls are there" — tmct answers in three
56
+ bands instead of dead-ending:
57
+
58
+ 1. the **definition** (a plain-English one-liner: *"A class is a template that
59
+ defines the structure and behaviour of objects."* / *"To import is to bring
60
+ another module's definitions into the current one."*);
61
+ 2. **real instances from your graph** — *"In this codebase, for example: Record,
62
+ Task and User (10 classes)"*, or actual edges *"src/core/store.mjs imports
63
+ src/core/model.mjs (18 import edges)"*;
64
+ 3. **guided follow-ups** — two or three concrete next questions, each one
65
+ *pre-checked against your graph* so every suggestion is guaranteed to resolve:
66
+ *"Want to go deeper? Try: which classes inherit from Record / what does Task
67
+ contain / where is User defined"*.
68
+
69
+ It fires for both **noun** concepts (class, module, function, method) and
70
+ **relation** concepts (imports, calls, contains, inherits, tests), and only when
71
+ tmct genuinely knows the concept *and* has instances of it — otherwise the honest
72
+ miss stands. The effect is a conversation that drills down from a vague opener to
73
+ a useful answer without ever hitting a wall. Natural phrasings are routed to the
74
+ capability you meant: *"what functions are in Task"* → its members, *"what
75
+ defined saveStore"* → where it's defined.
76
+
52
77
  ## How it remembers
53
78
 
54
79
  tmct's memory has two layers, both fed by every parsed request and response and
@@ -60,9 +85,12 @@ by cleaned session logs:
60
85
  relevance rather than loaded wholesale.
61
86
 
62
87
  With no graph at all, tmct starts empty and remembers what you tell it — the
63
- `.tmct/` graph is created from the conversation. Committed corpuses seed the
64
- vocabulary; a filtered **ConceptNet slice** (CC-BY-SA 4.0) is planned see
65
- `ROADMAP.md` Phase 2.
88
+ `.tmct/` graph is created from the conversation. On a first run it seeds the
89
+ committed vocabulary so it knows what it's talking about from turn one: a curated
90
+ **SEON** software ontology plus the whole filtered **ConceptNet slice**
91
+ (CC-BY-SA 4.0) — every term carries an English definition, so "what is a cache?"
92
+ answers offline, from disk, on turn one. `--ephemeral` (used by the shipped
93
+ `npm run example:*` demos) reads a graph but writes nothing back.
66
94
 
67
95
  ### Provenance and trust
68
96
 
package/ROADMAP.md CHANGED
@@ -550,6 +550,51 @@ Repository Interface library-surface work (Phase 8) settling the extraction boun
550
550
  `PLAN_OSS_ACE_PARSER.md`. Sibling publish-candidates (the bounded-Damerau fuzzy matcher, the
551
551
  PageRank+IDF block ranker) may follow the same path if there is demand.
552
552
 
553
+ ## Phase 10 — Conversational competence & onboarding (0.6.0 → 0.7.0)
554
+
555
+ *(Operator-directed, from live new-user testing.)* Once a graph is loaded the engine is strong;
556
+ the weak surfaces were the FIRST RUN and the VAGUE question. This phase makes the miss graceful,
557
+ the empty state honest, and the vague touch a guided answer — realising item 1's "tolerant, guides
558
+ you toward precision" promise on the conversational surface.
559
+
560
+ - **Onboarding UX (shipped 0.6.0):** the grammar wall moved behind `/help` (a short, tailored miss
561
+ instead); intent lanes for memory/teach ("remember that X"), meta/self ("what is this codebase",
562
+ "what do you know"), routed only when a graph query would miss; empty/degenerate-graph
563
+ orientation that distinguishes CODE STRUCTURE (needs a `.tmct/graph.json` via a producer or
564
+ `--repo`; tmct reads graphs, it does not index code) from VOCABULARY (`tmct init`/bootstrap seeds
565
+ concepts); `TMCT_GRAPH_FILE` honoured by chat; slash-optional commands (`stats`≡`/stats`);
566
+ `/memory` explore hooks; up/down-arrow prompt history in the TUI.
567
+ - **Knowledge (shipped 0.6.0 → 0.7.0):** the curated `corpus/seon` ontology — a software-sense
568
+ definition for EVERY lexicon term, language-neutral (Java/C#/Python `class` → one concept); the
569
+ ConceptNet slice quality-filtered (word-sense noise cut) and regrown to ~40k facts; tier-2
570
+ specialised corpuses (aws/python/java) with `tmct init --corpus`; batched `appendFacts` (one
571
+ write, 419s→2.5s) enabling **seed-all** so a fresh repo knows the whole curated vocabulary.
572
+ - **The concept force (shipped 0.7.0):** a vague touch on a concept X, where tmct knows X and has instances,
573
+ answers in three bands — **the definition** (from `corpus/seon`), **the examples** (real code-graph
574
+ + memory instances of X), and **a soft guided follow-up** ("Want to go deeper?" + 2–3 questions
575
+ built from the real instances × the query shapes valid for that kind, each pre-validated to
576
+ resolve). Applies to NOUN concepts (`what is a class` → define + Base/Widget/Button + drill-ins)
577
+ AND **RELATION concepts** (`what about imports` / `what calls are there` → the verb definition +
578
+ example edges + guided queries), fixing the vague-query dead-ends. Fact rendering is
579
+ de-anthropomorphised (no first-person "i learned:" over-claim — corpus facts read as data +
580
+ provenance; `you told me` stays for operator-asserted facts); listings cap at 32 with a "say
581
+ 'more'" pagination that holds the remainder in session state.
582
+ - **Dead-end routing + read-only demos (shipped 0.7.0):** natural drill-down phrasings are routed
583
+ onto the canonical shapes they mean — `what functions are in X` → members-of-class, `what defined
584
+ X` → where-is-X-defined, a no-context `what about X` → the concept/relation force (the discourse
585
+ continuation still wins when there IS a prior answer). `tmct chat --ephemeral` (and the
586
+ `npm run example:*` demos) reads a graph but writes nothing back, so a checked-in example is never
587
+ dirtied by a demo run.
588
+ - **The dialogue-flow loop (`SKILL_CHAT_PLAYTEST.md`, 0.7.0):** a fast, qualitative tuning loop that
589
+ complements the LLM-judge benchmark — Claude plays a curious user, hunts *dead-ends* (walls,
590
+ "unknown qualifier", phrasing-misses, invited follow-ups the engine can't take), fixes them by
591
+ ROUTING to existing capabilities, replays the same conversations until they flow, freezes them as
592
+ regression transcripts, then ratchets the complexity tier. The drill-down transcript above is its
593
+ first frozen fixture (`test/chatflow-drilldown.test.mjs`).
594
+ - **Measured** by the version-matched benchmark (`CHATBENCH_<version>` per `SKILL_TUNING_CYCLE.md`),
595
+ with new graded cells for the miss / empty-graph / concept-touch surfaces so these become
596
+ regression-protected levers, not one-off polish.
597
+
553
598
  ## Phase LATER — recognized, deferred, not now
554
599
 
555
600
  Features we have deliberately shaped seams for but will not build until the phases above have
package/bin/tmct.mjs CHANGED
@@ -34,6 +34,7 @@ software repository. No model calls; no codebase index of its own.
34
34
  Usage:
35
35
  tmct interactive chat (the headline surface)
36
36
  tmct chat [--repo <abs>] chat over a specific repo's graph
37
+ [--ephemeral] read the graph but write nothing back (demo/read-only)
37
38
  [--plain] force the plain readline shell (the default when
38
39
  stdin/stdout is not a terminal)
39
40
  tmct memory [--repo <abs>] what tmct remembers: facts, utterances, sessions,
@@ -267,16 +268,21 @@ async function main() {
267
268
  const rest = process.argv.slice(3);
268
269
  const i = rest.indexOf("--repo");
269
270
  const repoPath = i !== -1 ? rest[i + 1] : undefined;
271
+ // `--ephemeral` (used by the shipped `npm run example:*` scripts): read the
272
+ // target graph but write nothing back — no session folded into the committed
273
+ // code graph, no .tmct/memory dropped under it. A demo you can run repeatedly
274
+ // on a checked-in example without ever dirtying it.
275
+ const ephemeral = rest.includes("--ephemeral");
270
276
  // The shell gate: a real terminal gets the full-screen Ink TUI; `--plain` or a
271
277
  // non-TTY stream (pipes, scripts, the test suite) gets the readline shell. Both
272
278
  // drive the same createSession sink — only the drawing differs.
273
279
  const plain = rest.includes("--plain") || !process.stdin.isTTY || !process.stdout.isTTY;
274
280
  if (plain) {
275
281
  const { runChat } = await import("../src/chat.mjs");
276
- await runChat({ repoPath });
282
+ await runChat({ repoPath, ephemeral });
277
283
  } else {
278
284
  const { runTui } = await import("../src/tui/app.mjs");
279
- await runTui({ repoPath });
285
+ await runTui({ repoPath, ephemeral });
280
286
  }
281
287
  return;
282
288
  }
@@ -15,7 +15,9 @@ see `LICENSE-NOTICE` in this directory for the full attribution.
15
15
  - **Source:** ConceptNet 5.7.0 assertions dump,
16
16
  `https://s3.amazonaws.com/conceptnet/downloads/2019/edges/conceptnet-assertions-5.7.0.csv.gz`
17
17
  (published 2019-07-03).
18
- - **Retrieved + filtered:** 2026-07-04.
18
+ - **Retrieved + filtered:** 2026-07-04; **regrown to the ~40k tier-1 target on
19
+ 2026-07-05** (same dump, same licence) by widening the tech-seed domain and
20
+ raising the size budget — see "Growing the slice" below.
19
21
  - **Why the dump, not the API:** the public API (`api.conceptnet.io`) was
20
22
  hard-down (HTTP 502 from its nginx front-end on every request across ~15
21
23
  attempts over 10+ minutes on 2026-07-04), so the slice was stream-filtered
@@ -33,12 +35,16 @@ see `LICENSE-NOTICE` in this directory for the full attribution.
33
35
  policy: `/r/EtymologicallyRelatedTo`, `/r/EtymologicallyDerivedFrom`,
34
36
  `/r/ExternalURL` (etymology noise and link-outs — no consumer in tmct).
35
37
  3. **Tech-domain seed terms**: at least one endpoint's bare term is in the
36
- 90-term software/tech seed list exported as `SEED_TERMS` from
38
+ tech seed list the ~90-term base `SEED_TERMS` exported from
37
39
  `fetch-slice.mjs` (software, computer, program, code, module, function,
38
40
  database, server, network, bug, test, file, memory, algorithm, keyboard,
39
- programmer, repository, commit, …).
41
+ programmer, repository, commit, …) **plus** the ~230-term `EXTRA_SEEDS`
42
+ growth list in `filter-dump.mjs` (programming languages, frameworks, data
43
+ structures, cloud/infra, protocols, tools, ML — python, java, docker,
44
+ kubernetes, git, neural_network, tcp, kernel, hashtable, …), added
45
+ 2026-07-05 to reach the ~40k tier-1 target while staying in the tech domain.
40
46
  4. **Dedupe** by `(start, rel, end)`, keeping the higher weight.
41
- 5. **Size budget** (committed slice ≤ 1.5 MB; target ~1.4 MB), **two-tier**:
47
+ 5. **Size budget** (committed slice ≤ 5 MB; target ~4.3 MB), **two-tier**:
42
48
  assertions whose relation maps to an ACE-OWL pattern (`ace != "none"` in
43
49
  `conceptnet-map.toml`) are kept first, weight-descending; `ace = "none"`
44
50
  relations (`RelatedTo`, `Synonym`, …) fill the remaining budget — they
@@ -46,7 +52,7 @@ see `LICENSE-NOTICE` in this directory for the full attribution.
46
52
  facts.
47
53
  6. Deterministic output order: `(rel, start, end)`.
48
54
 
49
- ## Quality-filter pass (2026-07-05)
55
+ ## Quality-filter pass (regrown slice, 2026-07-05)
50
56
 
51
57
  `filter-dump.mjs` keeps the DATA honest (tech-seed match, canonical relations,
52
58
  budget) but not the SEMANTICS: ConceptNet's crowd-sourced "Verbosity"/Open-Mind
@@ -66,66 +72,66 @@ row):
66
72
  evidence-based set (`elegance, evil, gloom, unreality, universalism, dumb,
67
73
  free, junk`) — never a class
68
74
 
69
- **Result: 14,25813,880 rows (378 cut), 1,399,979 1,348,361 bytes.** Cuts by
70
- reason: sentence-fragment 328, single-char 27, numeric 11, opinion-object 8,
71
- definitional-phrase 4. Of the 4,170 seedable (mapped, `ace≠none`) facts, 286
72
- noise facts were removed, leaving **3,884 clean seedable facts**. No relation
73
- disappeared entirely (the drift guard stays satisfied). Re-run any time with:
75
+ **Result (2026-07-05 regrow): 45,63344,947 rows (686 cut), 4,308,850
76
+ 4,220,629 bytes.** Cuts by reason: sentence-fragment 613, single-char 48,
77
+ numeric 11, opinion-object 8, definitional-phrase 6. Of the seedable (mapped,
78
+ `ace≠none`) facts, the clean slice carries **6,255** up from 3,884 in the
79
+ 1.35 MB slice. No relation disappeared entirely (the drift guard stays
80
+ satisfied). Re-run any time with:
74
81
 
75
82
  ```bash
76
83
  node corpus/conceptnet/quality-filter.mjs --in-place corpus/conceptnet/slice.jsonl
77
84
  ```
78
85
 
79
- ## Growing the slice toward ~40k facts the budget blocker
86
+ ## Growing the slice toward the ~40k tier-1 target (done 2026-07-05)
80
87
 
81
- Growing tier-1 to the operator's ~40k-fact target is **feasible data-wise but
82
- blocked by the committed budget**: the ConceptNet dump S3 endpoint is reachable,
83
- but `test/corpus-conceptnet.test.mjs` asserts `size <= 1_500_000`, and the clean
84
- slice is already 1.35 MB (13,880 rows). 40k facts is ≈ 4 MB — 2.6× over the cap.
85
- Reaching it needs a **Wave-2 policy change** (raise `MAX_BYTES` in
86
- `filter-dump.mjs` AND the budget assertion in the test), which is out of scope
87
- for a data-only pass. When the budget is raised, regrow with a bigger seed list
88
- + budget, then re-run the quality filter:
88
+ The operator's ~40k-fact tier-1 target is **shipped**: the slice was regrown
89
+ from the same ConceptNet dump by (a) widening the tech domain with the
90
+ `EXTRA_SEEDS` list in `filter-dump.mjs` (~230 tech terms languages,
91
+ frameworks, data structures, cloud/infra, protocols, tools, ML) and (b) raising
92
+ `MAX_BYTES` in `filter-dump.mjs` from 1.4 MB to 4.5 MB with the matching test
93
+ budget assertion (`test/corpus-conceptnet.test.mjs`) raised from 1.5 MB to
94
+ 5 MB. The widened seed set matched **45,633 unique en→en assertions** (all under
95
+ budget, so no tier-trimming was needed this pass), and the quality filter
96
+ trimmed them to the committed **44,947 clean facts**. Regenerate with:
89
97
 
90
98
  ```bash
91
- # 1. widen the domain and the budget (edit SEED_TERMS in fetch-slice.mjs, MAX_BYTES in filter-dump.mjs)
99
+ # 1. stream-filter the dump (widened seeds + raised budget already live in filter-dump.mjs)
92
100
  curl -s https://s3.amazonaws.com/conceptnet/downloads/2019/edges/conceptnet-assertions-5.7.0.csv.gz \
93
101
  | gunzip -c | node corpus/conceptnet/filter-dump.mjs > corpus/conceptnet/slice.jsonl
94
102
  # 2. re-apply the semantic quality filter
95
103
  node corpus/conceptnet/quality-filter.mjs --in-place corpus/conceptnet/slice.jsonl
96
- # 3. raise the test's budget assertion to match
97
104
  ```
98
105
 
99
- ## Row counts (pre-filter baseline, 2026-07-04)
106
+ ## Row counts (regrown slice, 2026-07-05)
100
107
 
101
- **14,258 assertions, 1,399,979 bytes** — the raw filter-dump output, BEFORE the
102
- 2026-07-05 quality-filter pass above trimmed it to 13,880 rows / 1,348,361 bytes
103
- (34,074,917 dump lines scanned;
104
- 28,802 unique en→en seed assertions matched = 4,170 mappable + 24,632
105
- `ace="none"`; ALL 4,170 mappable kept, 10,088 none-rows fill the budget).
106
- 29 of the 31 non-filtered canonical relations are present:
108
+ **44,947 clean assertions, 4,220,629 bytes** — the quality-filtered committed
109
+ slice (raw filter-dump output was 45,633 rows / 4,308,850 bytes; 34,074,917 dump
110
+ lines scanned; 45,633 unique en→en seed assertions matched = 6,670 mappable +
111
+ 38,963 `ace="none"`; ALL kept under the 4.5 MB budget, then 686 noise rows cut).
112
+ 30 of the 31 non-filtered canonical relations are present; **6,255 seedable
113
+ (`ace≠none`) facts**:
107
114
 
108
115
  | Relation | Rows | | Relation | Rows |
109
116
  |---|---|---|---|---|
110
- | `/r/RelatedTo` | 4911 | | `/r/HasA` | 39 |
111
- | `/r/HasContext` | 2634 | | `/r/HasProperty` | 27 |
112
- | `/r/IsA` | 2594 | | `/r/Antonym` | 25 |
113
- | `/r/DerivedFrom` | 1906 | | `/r/MotivatedByGoal` | 25 |
114
- | `/r/AtLocation` | 459 | | `/r/SimilarTo` | 20 |
115
- | `/r/Synonym` | 368 | | `/r/DistinctFrom` | 19 |
116
- | `/r/UsedFor` | 312 | | `/r/DefinedAs` | 16 |
117
- | `/r/FormOf` | 224 | | `/r/MadeOf` | 14 |
118
- | `/r/CapableOf` | 164 | | `/r/CausesDesire` | 12 |
119
- | `/r/MannerOf` | 141 | | `/r/HasLastSubevent` | 12 |
120
- | `/r/PartOf` | 115 | | `/r/Causes` | 11 |
121
- | `/r/HasPrerequisite` | 103 | | `/r/CreatedBy` | 10 |
122
- | `/r/HasSubevent` | 42 | | `/r/Desires` | 7 |
123
- | `/r/ReceivesAction` | 40 | | `/r/HasFirstSubevent` | 7 |
124
- | | | | `/r/LocatedNear` | 1 |
125
-
126
- Absent from the slice (nothing matched the seed terms): `/r/ObstructedBy`,
127
- `/r/SymbolOf` both still have mapping rows, so a regenerated slice that
128
- surfaces them stays covered.
117
+ | `/r/RelatedTo` | 29016 | | `/r/HasSubevent` | 42 |
118
+ | `/r/HasContext` | 4376 | | `/r/DistinctFrom` | 32 |
119
+ | `/r/IsA` | 4173 | | `/r/HasProperty` | 30 |
120
+ | `/r/DerivedFrom` | 3388 | | `/r/ReceivesAction` | 27 |
121
+ | `/r/Synonym` | 1126 | | `/r/MotivatedByGoal` | 26 |
122
+ | `/r/AtLocation` | 642 | | `/r/MadeOf` | 20 |
123
+ | `/r/FormOf` | 528 | | `/r/Causes` | 18 |
124
+ | `/r/UsedFor` | 378 | | `/r/CreatedBy` | 15 |
125
+ | `/r/CapableOf` | 231 | | `/r/CausesDesire` | 13 |
126
+ | `/r/MannerOf` | 230 | | `/r/Desires` | 9 |
127
+ | `/r/PartOf` | 196 | | `/r/HasLastSubevent` | 8 |
128
+ | `/r/Antonym` | 155 | | `/r/HasFirstSubevent` | 7 |
129
+ | `/r/HasPrerequisite` | 108 | | `/r/LocatedNear` | 3 |
130
+ | `/r/SimilarTo` | 102 | | `/r/DefinedAs` | 2 |
131
+ | `/r/HasA` | 45 | | `/r/SymbolOf` | 1 |
132
+
133
+ Absent from the slice (nothing matched the seed terms): `/r/ObstructedBy` — it
134
+ still has a mapping row, so a regenerated slice that surfaces it stays covered.
129
135
 
130
136
  ## How to regenerate / extend
131
137
 
@@ -139,11 +145,12 @@ curl -s https://s3.amazonaws.com/conceptnet/downloads/2019/edges/conceptnet-asse
139
145
  | node corpus/conceptnet/filter-dump.mjs > corpus/conceptnet/slice.jsonl
140
146
  ```
141
147
 
142
- To extend the domain, add seed terms to `SEED_TERMS` in `fetch-slice.mjs` and
143
- re-run. `npm test` guards the contract: every relation present in the slice
144
- must have a row in `src/corpus/conceptnet-map.toml` (drift guard), en→en shape
145
- and the 1.5 MB budget are asserted, and the seeding path is exercised
146
- end-to-end.
148
+ To extend the domain, add seed terms to `EXTRA_SEEDS` in `filter-dump.mjs` (the
149
+ dump route that produces the committed slice) or `SEED_TERMS` in
150
+ `fetch-slice.mjs` (the API route) and re-run. `npm test` guards the contract:
151
+ every relation present in the slice must have a row in
152
+ `src/corpus/conceptnet-map.toml` (drift guard), en→en shape and the ≤ 5 MB
153
+ budget are asserted, and the seeding path is exercised end-to-end.
147
154
 
148
155
  ## Consumers
149
156
 
@@ -30,8 +30,65 @@ import { createInterface } from "node:readline";
30
30
  import { SEED_TERMS, CANONICAL_RELS, FILTERED_RELS, bareEnTerm } from "./fetch-slice.mjs";
31
31
  import { loadMap } from "../../src/corpus/conceptnet.mjs";
32
32
 
33
- const MAX_BYTES = 1_400_000; // committed-slice budget (hard cap 1.5 MB)
34
- const SEEDS = new Set(SEED_TERMS);
33
+ const MAX_BYTES = 4_500_000; // committed-slice budget (hard cap 5 MB), grown for the ~40k tier-1 target
34
+
35
+ // Tier-1 growth seeds (2026-07-05): the original SEED_TERMS in fetch-slice.mjs
36
+ // stay the canonical ~90-term base; this list WIDENS the tech domain toward the
37
+ // ~40k-fact target without leaving the software/tech world — languages,
38
+ // frameworks, data structures, cloud/infra, protocols, tools, ML. Kept here (an
39
+ // owned maintainer file) rather than in fetch-slice.mjs so the API route's seed
40
+ // contract is untouched. Union with SEED_TERMS below.
41
+ const EXTRA_SEEDS = [
42
+ // programming languages & ecosystems
43
+ "python", "java", "javascript", "typescript", "ruby", "perl", "php", "golang",
44
+ "rust", "kotlin", "swift", "scala", "haskell", "lisp", "clojure", "erlang",
45
+ "fortran", "cobol", "pascal", "assembly", "sql", "html", "css", "json", "xml",
46
+ "yaml", "markdown", "bash", "powershell",
47
+ // paradigms & concepts
48
+ "programming", "coding", "recursion", "iteration", "inheritance",
49
+ "polymorphism", "abstraction", "encapsulation", "concurrency", "parallelism",
50
+ "multithreading", "asynchronous", "synchronization", "serialization",
51
+ "optimization", "refactoring", "debugging", "compilation", "runtime",
52
+ "object_oriented", "functional_programming", "computer_science",
53
+ "software_engineering", "computer_programming",
54
+ // data structures & algorithms
55
+ "hash", "hashtable", "tree", "graph", "list", "tuple", "dictionary", "heap",
56
+ "matrix", "vector", "node", "recursion", "sorting", "searching", "linked_list",
57
+ "binary_tree", "hash_table", "data_type", "datatype",
58
+ // frameworks, tools & platforms
59
+ "git", "github", "docker", "kubernetes", "jenkins", "react", "angular",
60
+ "nodejs", "django", "flask", "spring", "rails", "tensorflow", "pytorch",
61
+ "hadoop", "kafka", "redis", "mongodb", "mysql", "postgresql", "sqlite",
62
+ "elasticsearch", "apache", "nginx", "wordpress", "eclipse", "vim", "emacs",
63
+ // hardware & systems
64
+ "kernel", "driver", "register", "transistor", "semiconductor", "motherboard",
65
+ "microprocessor", "microcontroller", "gpu", "ram", "rom", "ssd", "firmware",
66
+ "bootloader", "filesystem", "partition", "peripheral", "microchip",
67
+ "circuit_board", "integrated_circuit", "hard_drive", "graphics_card",
68
+ // networking & protocols
69
+ "packet", "bandwidth", "latency", "router", "firewall", "gateway", "proxy",
70
+ "dns", "tcp", "ftp", "smtp", "ssh", "ssl", "vpn", "ethernet", "wifi",
71
+ "bluetooth", "socket", "port", "packet_switching", "ip_address",
72
+ // web & the net
73
+ "url", "cookie", "session", "webpage", "hyperlink", "frontend", "backend",
74
+ "webserver", "webservice", "hosting", "domain", "html5", "ajax",
75
+ // cloud, infra & devops
76
+ "cloud_computing", "container", "virtualization", "serverless",
77
+ "microservice", "deployment", "devops", "pipeline", "infrastructure",
78
+ "datacenter", "cluster", "scalability", "availability", "redundancy",
79
+ // security
80
+ "authentication", "authorization", "cryptography", "hashing", "firewall",
81
+ "malware", "virus", "vulnerability", "cybersecurity", "cipher",
82
+ // data & ml
83
+ "dataset", "database", "datastore", "query", "index", "schema", "transaction",
84
+ "neural_network", "deep_learning", "classification", "regression",
85
+ "clustering", "training", "inference", "algorithm", "computation",
86
+ "big_data", "data_mining", "data_science", "analytics",
87
+ // concurrency & os primitives
88
+ "semaphore", "mutex", "deadlock", "scheduler", "interrupt", "syscall",
89
+ "daemon", "multitasking", "buffer", "pipe",
90
+ ];
91
+ const SEEDS = new Set([...SEED_TERMS, ...EXTRA_SEEDS]);
35
92
  const termOf = (uri) => uri.slice("/c/en/".length);
36
93
 
37
94
  const byKey = new Map(); // "start rel end" -> row