@polycode-projects/the-mechanical-code-talker 0.2.0 → 0.4.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.
Files changed (53) hide show
  1. package/README.md +77 -3
  2. package/ROADMAP.md +416 -3
  3. package/bin/tmct.mjs +308 -12
  4. package/corpus/README.md +52 -0
  5. package/corpus/conceptnet/LICENSE-NOTICE +37 -0
  6. package/corpus/conceptnet/README.md +103 -0
  7. package/corpus/conceptnet/fetch-slice.mjs +136 -0
  8. package/corpus/conceptnet/filter-dump.mjs +89 -0
  9. package/corpus/conceptnet/slice.jsonl +14258 -0
  10. package/data/phrasebook/software-phrases.txt +231 -0
  11. package/data/templates/grammar-rules.toml +89 -0
  12. package/data/templates/responses.jsonl +68 -0
  13. package/package.json +40 -3
  14. package/src/ask-nlp.mjs +22 -10
  15. package/src/ask-vocab.mjs +35 -1
  16. package/src/ask.mjs +171 -494
  17. package/src/chat.mjs +709 -81
  18. package/src/corpus/conceptnet-map.toml +251 -0
  19. package/src/corpus/conceptnet.mjs +167 -0
  20. package/src/corpus/templates.mjs +188 -0
  21. package/src/finish.mjs +443 -0
  22. package/src/grammar/ace.mjs +341 -0
  23. package/src/grammar/assert.mjs +40 -0
  24. package/src/grammar/lexicon-core.json +287 -0
  25. package/src/grammar/lexicon.mjs +202 -0
  26. package/src/hash.mjs +32 -0
  27. package/src/index.mjs +21 -5
  28. package/src/init.mjs +264 -0
  29. package/src/interpret/fuzzy.mjs +89 -0
  30. package/src/interpret/merge.mjs +148 -0
  31. package/src/interpret/normalize.mjs +151 -0
  32. package/src/interpret/pipeline.mjs +112 -0
  33. package/src/interpret/strategies/grammar.mjs +137 -0
  34. package/src/interpret/strategies/keywords.mjs +241 -0
  35. package/src/interpret/strategies/noise-strip.mjs +114 -0
  36. package/src/memory/blocks.mjs +221 -0
  37. package/src/memory/core.mjs +533 -0
  38. package/src/memory/fold.mjs +0 -0
  39. package/src/memory/inspect.mjs +141 -0
  40. package/src/memory/trust.mjs +113 -0
  41. package/src/prose-nlp.mjs +14 -16
  42. package/src/providers/bootstrap.mjs +24 -0
  43. package/src/providers/fixture.mjs +118 -0
  44. package/src/providers/graph-service.mjs +312 -0
  45. package/src/repository-interface.mjs +318 -0
  46. package/src/server.mjs +44 -28
  47. package/src/sessions.mjs +137 -4
  48. package/src/source.mjs +44 -5
  49. package/src/syllogise.mjs +0 -0
  50. package/src/toml-config.mjs +14 -0
  51. package/src/tui/app.mjs +173 -0
  52. package/src/wink-model.mjs +74 -0
  53. package/bin/cli.mjs +0 -226
@@ -0,0 +1,141 @@
1
+ // memory/inspect.mjs — seeing into the memory as TEXT (ROADMAP Phase 4,
2
+ // "Memory inspection"). One renderer serves both surfaces — the `/memory` chat
3
+ // command and the `tmct memory` CLI — in a terse (default) and a verbose form:
4
+ //
5
+ // - the memory graph grouped by OWL superclass (Fact / Utterance / Session,
6
+ // plus any other class present), counts with BALANCED samples scaled
7
+ // log-wise to class size (a 10,000-fact class shows ~8 exemplars, a
8
+ // 3-session class shows all 3);
9
+ // - top facts ranked by PROVENANCE BREADTH (a fact the corpus AND the chat
10
+ // both asserted outranks a single-writer fact), provenance verbatim;
11
+ // - recent Q→A utterance pairs (read off the mgx:inReplyTo edges);
12
+ // - the block-index summary (blocks, indexed tokens, top PageRank blocks).
13
+ //
14
+ // Pure renderers over loaded payloads + one thin I/O wrapper (inspectMemory).
15
+ // Everything degrades honestly: an empty memory renders as the empty story,
16
+ // never an error.
17
+
18
+ import { loadMemory, UTTERANCE_CLASS, IN_REPLY_TO_PROP, readFactRows, findContradictions } from "./core.mjs";
19
+ import { loadBlockIndex } from "./blocks.mjs";
20
+
21
+ /** Log-scaled sample count for a class of `n` individuals: 2·log10(n), floored
22
+ * at 3, never more than n (10,000 → 8; 500 → 5; 3 → 3; 1 → 1). Verbose doubles. */
23
+ export function sampleSize(n, { verbose = false } = {}) {
24
+ const base = Math.max(3, Math.round(2 * Math.log10(Math.max(1, n))));
25
+ return Math.min(n, verbose ? base * 2 : base);
26
+ }
27
+
28
+ /** Evenly-spaced (balanced) deterministic sample of k items — spans the class
29
+ * start to end rather than showing the first k. */
30
+ export function balancedSample(items, k) {
31
+ const n = items.length;
32
+ if (n <= k) return items.slice();
33
+ if (k <= 1) return [items[0]];
34
+ const out = [];
35
+ for (let i = 0; i < k; i += 1) out.push(items[Math.round((i * (n - 1)) / (k - 1))]);
36
+ return out;
37
+ }
38
+
39
+ const attrOf = (ind, key) => (ind?.attributes || []).find((a) => a.key === key)?.value || "";
40
+ const truncate = (s, cap) => {
41
+ const t = String(s ?? "").replace(/\s+/g, " ").trim();
42
+ return t.length > cap ? `${t.slice(0, cap - 1)}…` : t;
43
+ };
44
+
45
+ /** Render a loaded memory payload + block index into the inspection text.
46
+ * Pure. `verbose` widens every cap and stops truncating provenance/text. */
47
+ export function renderMemory({ memory, blocks }, { verbose = false } = {}) {
48
+ const individuals = memory?.individuals || [];
49
+ const lines = [];
50
+ const textCap = verbose ? 400 : 100;
51
+
52
+ if (!individuals.length) {
53
+ lines.push("memory is empty — nothing remembered yet (facts, utterances and sessions land in .tmct/memory/ as you chat).");
54
+ } else {
55
+ // ---- classes: counts + balanced log-scaled samples ----
56
+ const byClass = new Map();
57
+ for (const ind of individuals) {
58
+ const cls = ind?.class || "(unclassified)";
59
+ if (!byClass.has(cls)) byClass.set(cls, []);
60
+ byClass.get(cls).push(ind);
61
+ }
62
+ const classes = [...byClass.entries()].sort((a, b) => b[1].length - a[1].length);
63
+ lines.push(`memory — ${individuals.length} individuals: ${classes.map(([c, of]) => `${of.length} ${c}`).join(", ")}.`);
64
+ for (const [cls, of] of classes) {
65
+ const k = sampleSize(of.length, { verbose });
66
+ lines.push("", `${cls} — ${of.length} (showing ${k})`);
67
+ for (const ind of balancedSample(of, k)) lines.push(` ${truncate(ind.label, textCap)}`);
68
+ }
69
+
70
+ // ---- top facts by COMPUTED TRUST (upgraded from raw provenance breadth) ----
71
+ // Trust folds source-type prior + corroboration + recency, so a corroborated
72
+ // operator-stated fact outranks a lone web scrape by construction; provenance
73
+ // rides along (verbatim in verbose) for the audit trail.
74
+ const ranked = readFactRows(memory)
75
+ .filter((r) => r.sourceIds.length || r.provenance)
76
+ .sort((a, b) => b.trust - a.trust
77
+ || b.sourceIds.length - a.sourceIds.length
78
+ || `${a.subject} ${a.predicate} ${a.object}`.localeCompare(`${b.subject} ${b.predicate} ${b.object}`));
79
+ if (ranked.length) {
80
+ lines.push("", "top facts by trust:");
81
+ for (const r of ranked.slice(0, verbose ? 8 : 3)) {
82
+ const n = r.sourceIds.length || (r.provenance ? r.provenance.split(" | ").filter(Boolean).length : 0);
83
+ const label = `${r.subject} ${r.predicate} ${r.object}`;
84
+ lines.push(` ${truncate(label, textCap)} — trust ${r.trust.toFixed(2)}, ${n} source${n === 1 ? "" : "s"}: ${verbose ? r.provenance : truncate(r.provenance, 80)}`);
85
+ }
86
+ }
87
+
88
+ // ---- contradictions: same (subject,predicate), differing object, both above
89
+ // the trust floor → surface BOTH with provenance, never silently pick ----
90
+ const contradictions = findContradictions(memory);
91
+ if (contradictions.length) {
92
+ lines.push("", `contradictions (${contradictions.length} — both kept, never silently resolved):`);
93
+ for (const group of contradictions.slice(0, verbose ? 8 : 3)) {
94
+ lines.push(` ${group[0].subject} ${group[0].predicate}?`);
95
+ for (const r of group) {
96
+ lines.push(` ${truncate(r.object, textCap)} (trust ${r.trust.toFixed(2)}; ${verbose ? r.provenance : truncate(r.provenance, 60)})`);
97
+ }
98
+ }
99
+ }
100
+
101
+ // ---- recent Q→A pairs (off the inReplyTo edges) ----
102
+ const byId = new Map(individuals.map((i) => [i.id, i]));
103
+ const replyGroup = (memory.objectProperties || []).find((g) => g?.prop === IN_REPLY_TO_PROP);
104
+ const pairs = (replyGroup?.examples || [])
105
+ .map((e) => ({ a: byId.get(e.subject), q: byId.get(e.object) }))
106
+ .filter((p) => p.a && p.q && p.a.class === UTTERANCE_CLASS)
107
+ .sort((x, y) => attrOf(y.a, "ts").localeCompare(attrOf(x.a, "ts")));
108
+ if (pairs.length) {
109
+ lines.push("", `recent Q→A pairs (${pairs.length} recorded):`);
110
+ for (const p of pairs.slice(0, verbose ? 8 : 3)) {
111
+ lines.push(` Q: ${truncate(attrOf(p.q, "text"), textCap)}`);
112
+ lines.push(` A: ${truncate(attrOf(p.a, "text"), textCap)}`);
113
+ }
114
+ }
115
+ }
116
+
117
+ // ---- block-index summary ----
118
+ const entries = Object.entries(blocks?.blocks || {});
119
+ if (entries.length) {
120
+ const tokens = entries.reduce((n, [, b]) => n + (b.tokens?.length || 0), 0);
121
+ const top = entries
122
+ .slice()
123
+ .sort((a, b) => (b[1].rank ?? 0) - (a[1].rank ?? 0) || a[0].localeCompare(b[0]))
124
+ .slice(0, verbose ? 8 : 3);
125
+ lines.push("", `blocks — ${entries.length} folded session block${entries.length === 1 ? "" : "s"}, ${tokens} indexed tokens.`);
126
+ lines.push(` top by rank: ${top.map(([id, b]) => `${String(id).slice(0, 8)} (${(b.rank ?? 0).toFixed(3)})`).join(", ")}`);
127
+ } else {
128
+ lines.push("", "blocks — none folded yet (a session folds when it ends).");
129
+ }
130
+
131
+ return lines.join("\n");
132
+ }
133
+
134
+ /** Load + render a repo's memory (the one thin I/O wrapper both the `/memory`
135
+ * chat command and the `tmct memory` CLI call). Never throws on a missing
136
+ * store — that is the honest empty story. */
137
+ export async function inspectMemory(dir, { verbose = false } = {}) {
138
+ const memory = await loadMemory(dir);
139
+ const blocks = await loadBlockIndex(dir);
140
+ return renderMemory({ memory, blocks }, { verbose });
141
+ }
@@ -0,0 +1,113 @@
1
+ // memory/trust.mjs — deterministic, explainable, auditable trust over a Fact's
2
+ // Sources (PLAN_PROVENANCE_TRUST step (c)).
3
+ //
4
+ // Trust is a COMPUTED attribute of a Fact — never hand-set — a pure function of
5
+ // its Source edges, those Sources' types, and its mgx:createdAt. Three inputs
6
+ // combine:
7
+ // - a Source-TYPE PRIOR (operator > provider > corpus > web > entailed);
8
+ // - CORROBORATION over the fact's distinct Sources by noisy-OR
9
+ // (1 − Π(1 − wᵢ), capped at 1) — two independent web sources (0.4) reach
10
+ // 0.64, a lone operator fact is already 1.0;
11
+ // - a bounded RECENCY nudge in [0.9, 1.0] from createdAt, half-life decayed —
12
+ // the codegraph "capped nudge" philosophy, so recency breaks ties and
13
+ // freshens but never flips a source-type ordering by itself.
14
+ //
15
+ // For ENTAILED facts (tier-5): trust = min(premise trusts) × rule-confidence — a
16
+ // conclusion is only as trustworthy as its weakest premise. Premises may be
17
+ // absent for now, so this is a documented HOOK: pass opts.premiseTrusts (and
18
+ // opts.ruleConfidence) and it engages; otherwise an entailed fact scores off its
19
+ // bare 0.3 prior like any other Source.
20
+ //
21
+ // This module is PURE and import-free of core.mjs (no cycle): it reads Source
22
+ // individuals by their attribute props and returns { score, inputs }. core.mjs
23
+ // materialises the score onto the Fact (mgx:trustScore) plus the inputs it was
24
+ // computed from (mgx:trustInputs), so every score is reproducible and auditable.
25
+
26
+ export const TRUST_SCORE_PROP = "mgx:trustScore";
27
+ export const TRUST_INPUTS_PROP = "mgx:trustInputs";
28
+
29
+ /** Source-type priors — the ordering operator > provider-graph > curated-corpus
30
+ * > web > unverified-entailment. The entailed value is a FLOOR before premise
31
+ * adjustment (see the entailed hook below). */
32
+ export const SOURCE_PRIOR = Object.freeze({
33
+ operator: 1.0,
34
+ provider: 0.9,
35
+ corpus: 0.7,
36
+ web: 0.4,
37
+ entailed: 0.3,
38
+ });
39
+
40
+ export const RECENCY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
41
+ export const RECENCY_FLOOR = 0.9; // recency multiplier stays within [0.9, 1.0]
42
+
43
+ const round = (n, p = 6) => Number(n.toFixed(p));
44
+ const sourceTypeOf = (s) => (s?.attributes || []).find((a) => a.prop === "mgx:sourceType")?.value || "";
45
+
46
+ /**
47
+ * Bounded recency multiplier in [RECENCY_FLOOR, 1] from an ISO-8601 createdAt.
48
+ * A half-life decay: freshly written ≈ 1.0, ancient → RECENCY_FLOOR. An unknown
49
+ * or unparseable timestamp yields 1.0 (no penalty) — recency only ever nudges
50
+ * down from a full score, it never invents one.
51
+ */
52
+ export function recencyNudge(createdAt, now = Date.now(), halfLifeMs = RECENCY_HALF_LIFE_MS) {
53
+ const t = Date.parse(createdAt);
54
+ if (!Number.isFinite(t)) return 1;
55
+ const ageMs = Math.max(0, now - t);
56
+ return RECENCY_FLOOR + (1 - RECENCY_FLOOR) * Math.pow(0.5, ageMs / halfLifeMs);
57
+ }
58
+
59
+ /**
60
+ * Pure trust for one Fact. `fact` supplies `{ sourceIds: [...], createdAt }`;
61
+ * the Source individuals are resolved from `sourcesById` (a plain { id: Source }
62
+ * map — exactly what a memory payload's Source individuals key into). Distinct
63
+ * sources are corroborated by noisy-OR over their type priors and nudged by
64
+ * recency. Deterministic given the same inputs and `opts.now`.
65
+ *
66
+ * opts:
67
+ * - now (ms) reference time for recency; default Date.now()
68
+ * - halfLifeMs recency half-life override
69
+ * - premiseTrusts entailed hook: [trusts] of the conclusion's premise Facts
70
+ * - ruleConfidence entailed hook: the rule's confidence in [0,1] (default 1)
71
+ *
72
+ * Returns { score, inputs } — `inputs` (the source-type multiset, corroboration
73
+ * count, createdAt and the recency multiplier) is stored alongside the score so
74
+ * "why does this rank high?" is answerable from the record.
75
+ */
76
+ export function computeTrust(fact, sourcesById = {}, opts = {}) {
77
+ const now = typeof opts.now === "number" ? opts.now : Date.now();
78
+ const ids = Array.isArray(fact?.sourceIds) ? fact.sourceIds : [];
79
+
80
+ // distinct sources → their type priors
81
+ const seen = new Set();
82
+ const types = [];
83
+ for (const id of ids) {
84
+ if (seen.has(id)) continue;
85
+ seen.add(id);
86
+ const t = sourceTypeOf(sourcesById[id]);
87
+ if (t) types.push(t);
88
+ }
89
+
90
+ // corroboration via noisy-OR over distinct-source priors, capped at 1
91
+ let base = 0;
92
+ let complement = 1;
93
+ for (const t of types) complement *= 1 - (SOURCE_PRIOR[t] ?? 0);
94
+ if (types.length) base = Math.min(1, 1 - complement);
95
+
96
+ // entailed hook (tier-5): a conclusion is only as trustworthy as its weakest
97
+ // premise × the rule confidence. Engages only when premises are supplied;
98
+ // otherwise an entailed fact rides its bare prior through the noisy-OR above.
99
+ if (types.includes("entailed") && Array.isArray(opts.premiseTrusts) && opts.premiseTrusts.length) {
100
+ const rc = typeof opts.ruleConfidence === "number" ? opts.ruleConfidence : 1;
101
+ base = Math.max(0, Math.min(1, Math.min(...opts.premiseTrusts) * rc));
102
+ }
103
+
104
+ const recency = recencyNudge(fact?.createdAt, now, opts.halfLifeMs);
105
+ const score = round(Math.min(1, base * recency));
106
+ const inputs = {
107
+ sourceTypes: types.slice().sort(),
108
+ corroboration: types.length,
109
+ createdAt: fact?.createdAt || "",
110
+ recency: round(recency),
111
+ };
112
+ return { score, inputs };
113
+ }
package/src/prose-nlp.mjs CHANGED
@@ -1,23 +1,23 @@
1
1
  // prose-nlp.mjs — the OPTIONAL wink-nlp lemma loader behind prose.mjs's LEMMA layer.
2
2
  //
3
- // Deliberately a SEPARATE loader from ask-nlp.mjs (same createRequire pattern, same
4
- // deps) rather than an import of it: ask-nlp.mjs belongs to the ask-engine surface
5
- // and is under active concurrent work the prose pre-pass must not couple its
6
- // index-build path to that file's export shape. The ~20 duplicated lines are the
7
- // decoupling fee; the wink model itself is loaded lazily and at most once per
8
- // process either way.
3
+ // Kept SEPARATE from ask-nlp.mjs (its own `proseLemma` export shape) rather than an
4
+ // import of that ask-engine surface but both now share the neutral leaf loader
5
+ // src/wink-model.mjs, so the wink model is resolved in ONE place. The former ~20
6
+ // duplicated createRequire lines are gone; the coupling this file avoids is to
7
+ // ask-nlp.mjs's export shape, not to a leaf model loader.
9
8
  //
10
- // BOUNDARY (same as ask-nlp.mjs, hard): Node-only, never inlined into the viewer
11
- // bundle. prose.mjs is itself never inlined by viz.mjs's askSource(), so nothing
12
- // browser-side can reach this module. wink-nlp + wink-eng-lite-web-model are CJS
13
- // loaded via createRequire, lazily, with failure cached as null: a checkout without
14
- // the optional deps simply builds no lemma layer (honestly absent), it never throws.
9
+ // BOUNDARY (same as ask-nlp.mjs, hard): Node-only path, never inlined into the
10
+ // viewer bundle. prose.mjs is itself never inlined by viz.mjs's askSource(), so
11
+ // nothing browser-side can reach this module. The wink pair is loaded lazily (Node
12
+ // createRequire fallback, or the browser registration seam), failure cached as null:
13
+ // a checkout without the optional deps simply builds no lemma layer (honestly
14
+ // absent), it never throws.
15
15
  //
16
16
  // Determinism: wink's lemmatiser is a fixed trained model with no sampling — the
17
17
  // same token always yields the same lemma across runs and processes, which is what
18
18
  // lets the lemma layer meet the "byte-identical proseIndex across builds" contract.
19
19
 
20
- import { createRequire } from "node:module";
20
+ import { winkInstance } from "./wink-model.mjs";
21
21
 
22
22
  let cached; // undefined = not tried yet; null = unavailable (tried once, honestly off)
23
23
 
@@ -27,10 +27,8 @@ let cached; // undefined = not tried yet; null = unavailable (tried once, honest
27
27
  export function proseLemma() {
28
28
  if (cached !== undefined) return cached;
29
29
  try {
30
- const require = createRequire(import.meta.url);
31
- const winkNLP = require("wink-nlp");
32
- const model = require("wink-eng-lite-web-model");
33
- const nlp = winkNLP(model);
30
+ const nlp = winkInstance();
31
+ if (!nlp) { cached = null; return cached; }
34
32
  const its = nlp.its;
35
33
  const memo = new Map();
36
34
  cached = (word) => {
@@ -0,0 +1,24 @@
1
+ // The BOOTSTRAP reference provider — the empty/degenerate graph a fresh repo
2
+ // "contains" before anything is indexed. PLAN_REPOSITORY_INTERFACE.md deliverable
3
+ // 2: "bootstrap returns honest empties".
4
+ //
5
+ // It implements every Repository-Interface service over the empty bootstrap
6
+ // payload (src/source.mjs emptyEntities): every id-taking service returns
7
+ // miss(UNRESOLVED_TERM) — there are no individuals — and every aggregate returns
8
+ // an honest empty (stats.total = 0, untested.modules = [], …). Nothing throws.
9
+ // This is the other end of the compatibility kit: the provider that has no data
10
+ // must still CONFORM.
11
+
12
+ import { parseEntities } from "../codegraph.mjs";
13
+ import { emptyEntities } from "../source.mjs";
14
+ import { createGraphService } from "./graph-service.mjs";
15
+
16
+ /** The parsed empty bootstrap graph. */
17
+ export function bootstrapGraph() {
18
+ return parseEntities(emptyEntities());
19
+ }
20
+
21
+ /** The bootstrap provider: every service over the empty graph — honest empties. */
22
+ export function bootstrapProvider() {
23
+ return createGraphService(bootstrapGraph());
24
+ }
@@ -0,0 +1,118 @@
1
+ // The FIXTURE reference provider — a small, real, self-contained code graph that
2
+ // implements every Repository-Interface service. PLAN_REPOSITORY_INTERFACE.md
3
+ // deliverable 2: "the executable specification an external producer reads first".
4
+ //
5
+ // It is a degenerate provider in the sense that its graph is tiny and its source
6
+ // bodies are absent (snippet/context answer NO_SOURCE) — but every OTHER service
7
+ // returns real graph truth. The contract suite (test/repository-interface.test.mjs)
8
+ // runs the whole compatibility kit against it.
9
+ //
10
+ // The payload is embedded (not read from test/) so this ships as a runnable spec
11
+ // inside the library. Its shape is exactly a parseEntities() input.
12
+
13
+ import { parseEntities } from "../codegraph.mjs";
14
+ import { createGraphService } from "./graph-service.mjs";
15
+
16
+ /** A compact but type-complete entities payload: modules, a class hierarchy
17
+ * (Base ← Widget ← Button), a method with a full signature, an attribute, a
18
+ * module global, and a commit — wired by one edge of every closed kind. */
19
+ export const FIXTURE_ENTITIES = Object.freeze({
20
+ generated_at: "2026-07-05T00:00:00.000Z",
21
+ bootstrap: false,
22
+ prefixes: { seon: "http://se-on.org/ontologies/seon.owl#", mgx: "urn:tmct:mgx#" },
23
+ classes: [
24
+ { name: "Module", count: 5, sample: ["pkg/core/graph.mjs"] },
25
+ { name: "Class", count: 3, sample: ["Base", "Widget", "Button"] },
26
+ { name: "Method", count: 1, sample: ["Widget.render"] },
27
+ { name: "Attribute", count: 1, sample: ["Widget.name"] },
28
+ { name: "Function", count: 1, sample: ["parseNode"] },
29
+ { name: "Commit", count: 1, sample: ["a1b2c3d"] },
30
+ ],
31
+ vocabulary: [],
32
+ objectProperties: [
33
+ { predicate: "imports", prop: "mgx:importsNamespace", count: 3, examples: [
34
+ { subject: "mod:view.mjs", object: "mod:graph.mjs", subjectLabel: "pkg/ui/view.mjs", objectLabel: "pkg/core/graph.mjs" },
35
+ { subject: "mod:widget.mjs", object: "mod:graph.mjs", subjectLabel: "pkg/ui/widget.mjs", objectLabel: "pkg/core/graph.mjs" },
36
+ { subject: "mod:button.mjs", object: "mod:widget.mjs", subjectLabel: "pkg/ui/button.mjs", objectLabel: "pkg/ui/widget.mjs" },
37
+ ] },
38
+ { predicate: "calls", prop: "mgx:callsCoarse", count: 1, examples: [
39
+ { subject: "mod:script.mjs", object: "mod:graph.mjs", subjectLabel: "scripts/build.mjs", objectLabel: "pkg/core/graph.mjs" },
40
+ ] },
41
+ { predicate: "callsSymbol", prop: "mgx:callsSymbol", count: 1, examples: [
42
+ { subject: "m:render", object: "fn:parseNode", subjectLabel: "Widget.render", objectLabel: "parseNode" },
43
+ ] },
44
+ { predicate: "defines", prop: "seon:declaresMethod", count: 3, examples: [
45
+ { subject: "mod:graph.mjs", object: "fn:parseNode", subjectLabel: "pkg/core/graph.mjs", objectLabel: "parseNode" },
46
+ { subject: "mod:widget.mjs", object: "cls:widget", subjectLabel: "pkg/ui/widget.mjs", objectLabel: "Widget" },
47
+ { subject: "mod:widget.mjs", object: "g:register", subjectLabel: "pkg/ui/widget.mjs", objectLabel: "register" },
48
+ ] },
49
+ { predicate: "tests", prop: "mgx:testsCoverage", count: 1, examples: [
50
+ { subject: "mod:widget.test.mjs", object: "mod:widget.mjs", subjectLabel: "pkg/test/widget.test.mjs", objectLabel: "pkg/ui/widget.mjs" },
51
+ ] },
52
+ { predicate: "touches", prop: "mgx:touchedByCommit", count: 1, examples: [
53
+ { subject: "commit:a1b2c3d", object: "mod:widget.mjs", subjectLabel: "a1b2c3d", objectLabel: "pkg/ui/widget.mjs" },
54
+ ] },
55
+ { predicate: "touchesSymbol", prop: "mgx:touchesSymbol", count: 1, examples: [
56
+ { subject: "commit:a1b2c3d", object: "m:render", subjectLabel: "a1b2c3d", objectLabel: "Widget.render" },
57
+ ] },
58
+ { predicate: "contains", prop: "seon:containsCodeEntity", count: 2, examples: [
59
+ { subject: "cls:widget", object: "m:render", subjectLabel: "Widget", objectLabel: "render" },
60
+ { subject: "cls:widget", object: "a:name", subjectLabel: "Widget", objectLabel: "name" },
61
+ ] },
62
+ { predicate: "inherits", prop: "seon:hasSuperType", count: 2, examples: [
63
+ { subject: "cls:widget", object: "cls:base", subjectLabel: "Widget", objectLabel: "Base" },
64
+ { subject: "cls:button", object: "cls:widget", subjectLabel: "Button", objectLabel: "Widget" },
65
+ ] },
66
+ { predicate: "cochange", prop: "mgx:changeCoupledWith", count: 1, examples: [
67
+ { subject: "mod:widget.mjs", object: "mod:graph.mjs", subjectLabel: "pkg/ui/widget.mjs", objectLabel: "pkg/core/graph.mjs", weight: 3 },
68
+ ] },
69
+ { predicate: "reexports", prop: "mgx:reExports", count: 1, examples: [
70
+ { subject: "mod:widget.mjs", object: "cls:widget", subjectLabel: "pkg/ui/widget.mjs", objectLabel: "Widget" },
71
+ ] },
72
+ ],
73
+ individuals: [
74
+ { id: "mod:graph.mjs", label: "pkg/core/graph.mjs", class: "Module", derived_from: ["git:a1b2c3d"], mentions: [] },
75
+ { id: "mod:view.mjs", label: "pkg/ui/view.mjs", class: "Module", derived_from: [], mentions: [] },
76
+ { id: "mod:widget.mjs", label: "pkg/ui/widget.mjs", class: "Module", derived_from: ["git:a1b2c3d"], mentions: [] },
77
+ { id: "mod:button.mjs", label: "pkg/ui/button.mjs", class: "Module", derived_from: [], mentions: [] },
78
+ { id: "mod:script.mjs", label: "scripts/build.mjs", class: "Module", derived_from: [], mentions: [] },
79
+ { id: "mod:widget.test.mjs", label: "pkg/test/widget.test.mjs", class: "Module", derived_from: [], mentions: [] },
80
+ { id: "fn:parseNode", label: "parseNode", class: "Function", derived_from: [], mentions: [], attributes: [
81
+ { prop: "seon:startsAt", key: "site", value: "pkg/core/graph.mjs:10-24" },
82
+ { prop: "seon:hasParameter", key: "params", value: "node, depth=0" },
83
+ { prop: "seon:hasReturnType", key: "returns", value: "Node" },
84
+ ] },
85
+ { id: "cls:base", label: "Base", class: "Class", derived_from: [], mentions: [], attributes: [{ prop: "seon:startsAt", key: "site", value: "pkg/core/graph.mjs:1-6" }] },
86
+ { id: "cls:widget", label: "Widget", class: "Class", derived_from: [], mentions: [], attributes: [{ prop: "seon:startsAt", key: "site", value: "pkg/ui/widget.mjs:1-40" }] },
87
+ { id: "cls:button", label: "Button", class: "Class", derived_from: [], mentions: [], attributes: [{ prop: "seon:startsAt", key: "site", value: "pkg/ui/button.mjs:1-12" }] },
88
+ { id: "m:render", label: "Widget.render", class: "Method", derived_from: [], mentions: [], attributes: [
89
+ { prop: "seon:startsAt", key: "site", value: "pkg/ui/widget.mjs:8-20" },
90
+ { prop: "mgx:decorator", key: "decorators", value: "property" },
91
+ { prop: "seon:hasParameter", key: "params", value: "self, mode='full'" },
92
+ { prop: "seon:hasReturnType", key: "returns", value: "str" },
93
+ { prop: "seon:throwsException", key: "raises", value: "ValueError" },
94
+ { prop: "seon:accessesField", key: "self_fields", value: "name, size" },
95
+ { prop: "seon:hasDoc", key: "doc", value: "Render the widget." },
96
+ ] },
97
+ { id: "a:name", label: "Widget.name", class: "Attribute", derived_from: [], mentions: [], attributes: [{ prop: "seon:startsAt", key: "site", value: "pkg/ui/widget.mjs:2" }] },
98
+ { id: "g:register", label: "register", class: "GlobalVariable", derived_from: [], mentions: [], attributes: [
99
+ { prop: "seon:startsAt", key: "site", value: "pkg/ui/widget.mjs:1" },
100
+ { prop: "mgx:value", key: "value", value: "Library()" },
101
+ ] },
102
+ { id: "commit:a1b2c3d", label: "a1b2c3d", class: "Commit", derived_from: [], mentions: [], attributes: [
103
+ { prop: "mgx:commitAuthor", key: "author", value: "Grace Hopper" },
104
+ { prop: "mgx:commitDate", key: "date", value: "2026-07-01" },
105
+ { prop: "mgx:commitMessage", key: "message", value: "render the widget in full mode" },
106
+ ] },
107
+ ],
108
+ });
109
+
110
+ /** The parsed fixture graph (shared, immutable truth). */
111
+ export function fixtureGraph() {
112
+ return parseEntities(FIXTURE_ENTITIES);
113
+ }
114
+
115
+ /** The fixture provider: a Repository-Interface service over the small real graph. */
116
+ export function fixtureProvider() {
117
+ return createGraphService(fixtureGraph());
118
+ }