@polycode-projects/the-mechanical-code-talker 3.1.2 → 3.1.4

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.
@@ -7,3 +7,5 @@
7
7
  {"relation":"touches","definition":"A touch is a commit changing a file or a symbol in the codebase.","sense":"software"}
8
8
  {"relation":"cochange","definition":"Change-coupling is two files that tend to be changed together in the same commits.","sense":"software"}
9
9
  {"relation":"reexports","definition":"A re-export is a module passing another module's definition through as part of its own public API.","sense":"software"}
10
+ {"relation":"serves","definition":"A service edge is one part of the codebase providing or backing another, such as a handler serving a route.","sense":"software"}
11
+ {"relation":"denotes","definition":"A denotation is a vocabulary term naming a code entity, linking the word to the thing it refers to.","sense":"software"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "3.1.2",
3
+ "version": "3.1.4",
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.",
@@ -46,6 +46,7 @@ export const MISS_REASONS = Object.freeze({
46
46
  export const EDGE_KINDS = Object.freeze([
47
47
  "imports", "calls", "callsSymbol", "defines", "tests",
48
48
  "touches", "touchesSymbol", "contains", "inherits", "cochange", "reexports",
49
+ "serves", "denotes",
49
50
  ]);
50
51
 
51
52
  /** edge-kind → the `tmct:` object property it realizes (the OWL grounding). */
@@ -61,6 +62,8 @@ export const EDGE_KIND_TO_TMCT = Object.freeze({
61
62
  inherits: "tmct:extends",
62
63
  cochange: "tmct:dependsOn",
63
64
  reexports: "tmct:exports",
65
+ serves: "tmct:serves",
66
+ denotes: "tmct:denotes",
64
67
  });
65
68
 
66
69
  /** The named services, grouped as in the plan's six-group inventory. Names are
@@ -3,14 +3,23 @@
3
3
  //
4
4
  // A static `import "wink-nlp"` would drag the ~1 MB model into the base/viewer bundle.
5
5
  // Instead, `registerWinkModel` lets a browser/bundler entry hand in an already-imported
6
- // `{ winkNLP, model }` pair once; Node instead resolves lazily via `createRequire`.
6
+ // `{ winkNLP, model }` pair once; Node instead resolves lazily via `createRequire`,
7
+ // fetched at call time from `process.getBuiltinModule("node:module")` rather than a
8
+ // top-level `import { createRequire } from "node:module"`. A top-level import declares
9
+ // the `createRequire` binding in this module's own output, and a downstream consumer
10
+ // that bundles tmct alongside other CJS dependencies (esbuild, `--format=esm`) can
11
+ // inject its own auto-generated `createRequire` shim into the same bundle scope,
12
+ // producing a duplicate top-level declaration and a `SyntaxError` at Node module-parse
13
+ // time. Resolving it lazily via `process.getBuiltinModule` keeps the bundled output free
14
+ // of any top-level `createRequire` declaration, so there's nothing left to collide with.
15
+ // Don't "simplify" this back to a top-level import. `process.getBuiltinModule` needs
16
+ // Node 22.3.0/20.16.0+; this package's `engines.node` floor (">=24") already clears
17
+ // that, so lowering the floor later needs to keep clearing it too.
7
18
  //
8
19
  // The loader stays synchronous. Failure is cached as null — a checkout without the
9
20
  // optional deps, or a page that never registered a model, runs adapter-less rather
10
21
  // than throwing.
11
22
 
12
- import { createRequire } from "node:module";
13
-
14
23
  let injected; // browser/bundler-supplied `() => ({ winkNLP, model })`, or undefined
15
24
  let cached; // undefined = not tried yet; null = unavailable (tried once, honestly off)
16
25
 
@@ -40,7 +49,7 @@ function loadWinkModel() {
40
49
  /** Node fallback: resolve wink through the module system (never a guessed path).
41
50
  * CJS deps, so `createRequire`. */
42
51
  function nodeRequireWink() {
43
- const require = createRequire(import.meta.url);
52
+ const require = process.getBuiltinModule("node:module").createRequire(import.meta.url);
44
53
  return {
45
54
  winkNLP: require("wink-nlp"),
46
55
  model: require("wink-eng-lite-web-model"),
@@ -42,6 +42,8 @@ const FORWARD_TEMPLATE = Object.freeze({
42
42
  contains: (f) => `what is in ${f}`,
43
43
  defines: (f) => `what does ${f} define`,
44
44
  inherits: (f) => `what does ${f} inherit from`,
45
+ serves: (f) => `what does ${f} serve`,
46
+ denotes: (f) => `what does ${f} denote`,
45
47
  });
46
48
 
47
49
  // Reverse reading (focus is the object): "what <kind> <focus>".
@@ -51,6 +53,8 @@ const REVERSE_TEMPLATE = Object.freeze({
51
53
  tests: (f) => `what tests ${f}`,
52
54
  inherits: (f) => `what inherits from ${f}`,
53
55
  contains: (f) => `what contains ${f}`,
56
+ serves: (f) => `what serves ${f}`,
57
+ denotes: (f) => `what denotes ${f}`,
54
58
  });
55
59
 
56
60
  /** Index a payload into the counts and adjacency the hints read: class →
@@ -30,6 +30,7 @@ export const ENTITY_CLASSES = Object.freeze([
30
30
  export const EDGE_PREDICATES = Object.freeze([
31
31
  "imports", "calls", "callsSymbol", "defines", "tests",
32
32
  "touches", "touchesSymbol", "contains", "inherits", "cochange", "reexports",
33
+ "serves", "denotes",
33
34
  ]);
34
35
 
35
36
  /** The closed effect vocabulary — every way an operator's declared delta can
@@ -219,6 +219,10 @@ export const RELATION_TERM = Object.freeze({
219
219
  export: "reexports", exports: "reexports", exporting: "reexports", exported: "reexports",
220
220
  reexport: "reexports", reexports: "reexports", reexporting: "reexports",
221
221
  "re-export": "reexports", "re-exports": "reexports", "re-exporting": "reexports",
222
+ // provider-declared kinds: tmct's own indexer emits neither, so a graph it
223
+ // built degrades to the honest two-band "no edges" answer rather than a miss.
224
+ serve: "serves", serves: "serves", serving: "serves", served: "serves",
225
+ denote: "denotes", denotes: "denotes", denoting: "denotes", denoted: "denotes",
222
226
  });
223
227
 
224
228
  /** concept key → the relationKind()s whose edges it enumerates. A concept can span
@@ -234,6 +238,8 @@ const RELATION_KINDS = Object.freeze({
234
238
  touches: ["touches", "touchesSymbol"],
235
239
  cochange: ["cochange"],
236
240
  reexports: ["reexports"],
241
+ serves: ["serves"],
242
+ denotes: ["denotes"],
237
243
  });
238
244
 
239
245
  /** concept key → the verb phrase that renders an edge as an English sentence
@@ -248,6 +254,8 @@ const RELATION_RENDER = Object.freeze({
248
254
  touches: { verb: "touches", edgeNoun: "touch" },
249
255
  cochange: { verb: "changes together with", edgeNoun: "change-coupling" },
250
256
  reexports: { verb: "re-exports", edgeNoun: "re-export" },
257
+ serves: { verb: "serves", edgeNoun: "service" },
258
+ denotes: { verb: "denotes", edgeNoun: "denotation" },
251
259
  });
252
260
 
253
261
  /** Per concept key, the candidate follow-up shapes in priority order — each
@@ -290,6 +298,14 @@ const RELATION_FOLLOWUP_SHAPES = Object.freeze({
290
298
  { side: "subj", make: (x) => `what does ${x} export` },
291
299
  { side: "obj", make: (x) => `where is ${x} defined` },
292
300
  ],
301
+ serves: [
302
+ { side: "subj", make: (x) => `what does ${x} serve` },
303
+ { side: "obj", make: (x) => `what serves ${x}` },
304
+ ],
305
+ denotes: [
306
+ { side: "subj", make: (x) => `what does ${x} denote` },
307
+ { side: "obj", make: (x) => `what denotes ${x}` },
308
+ ],
293
309
  });
294
310
 
295
311
  /** How many example edges the relation force shows before the remainder is held for
@@ -186,6 +186,12 @@ const EMBEDDED_MEANS_RE = /^what\s+((?:an?\s+|the\s+)?[\w'-]+(?:\s+[\w'-]+){0,2}
186
186
  * untouched, a relation/interrogative remainder unwraps to itself, anything
187
187
  * else bridges to "describe <thing>". */
188
188
  const SHOW_GIVE_ME_RE = /^(?:show|give)\s+me\s+(?:the\s+)?(.+?)\??$/i;
189
+ /** An "everything I need [to <verb>|for X]" remainder always bridges to
190
+ * "describe <thing>", even when its purpose clause happens to contain a
191
+ * relation verb ("everything I need to change X") — RELATION_VERB_RE's
192
+ * bag-of-words probe would otherwise misread that verb as making the
193
+ * remainder itself an already-relational clause. */
194
+ const EVERYTHING_I_NEED_RE = /^everything\s+i(?:'d|\s+would)?\s+need\b/i;
189
195
 
190
196
  /** Leading STACCATO connective before an ALREADY well-formed question ("and
191
197
  * what imports it") -> the question alone. Gated on the remainder starting
@@ -254,7 +260,9 @@ export function applyPreambleFrames(text) {
254
260
  if (m) {
255
261
  const rest = m[1].trim();
256
262
  if (!isListingRemainder(rest)) {
257
- q = (RELATION_VERB_RE.test(rest) || INTERROGATIVE_LEAD_RE.test(rest)) ? rest : `describe ${rest}`;
263
+ const isRelationClause = !EVERYTHING_I_NEED_RE.test(rest)
264
+ && (RELATION_VERB_RE.test(rest) || INTERROGATIVE_LEAD_RE.test(rest));
265
+ q = isRelationClause ? rest : `describe ${rest}`;
258
266
  }
259
267
  }
260
268
  m = q.match(LEADING_CONNECTIVE_RE);
@@ -2,8 +2,10 @@
2
2
  // the rankers that read what they build.
3
3
 
4
4
  /** Does this module path (or a lowercased path-shaped label) belong to test code?
5
- * Covers `test/` and `tests/` segments, Python's `test_*.py` convention, and
6
- * .NET's `*.Tests` assembly suffix. Case-sensitive: callers holding mixed-case
7
- * paths lowercase first. */
5
+ * Covers `test/` and `tests/` segments, a hyphen/underscore-prefixed test directory
6
+ * (`behaviour-tests/`, `unit_test/`), Python's `test_*.py` convention, .NET's
7
+ * `*.Tests` assembly suffix, and a `.test`/`.spec` file anywhere in the tree.
8
+ * Case-sensitive: callers holding mixed-case paths lowercase first. */
8
9
  export const isTestPath = (p) =>
9
- /(^|\/)tests?\//.test(p) || /(^|\/)test_[^/]*\.py$/.test(p) || /\.tests(\.|$)/.test(p);
10
+ /(^|\/)tests?\//.test(p) || /(^|\/)test_[^/]*\.py$/.test(p) || /\.tests(\.|$)/.test(p)
11
+ || /(^|\/)[^/]*[-_]tests?\//.test(p) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(p);
@@ -128,6 +128,8 @@ const GOAL_BY_KIND = {
128
128
  touchesSymbol: "understand commit/change history",
129
129
  cochange: "understand change-coupling between modules",
130
130
  reexports: "understand a module's public exports/API surface",
131
+ serves: "understand which component provides or backs another",
132
+ denotes: "understand which vocabulary term names a code entity",
131
133
  };
132
134
  const goalNoun = (entityType) => (entityType ? `${String(entityType).toLowerCase()}(s)` : "entities");
133
135
 
@@ -2240,6 +2242,8 @@ const MISS_EXAMPLES = {
2240
2242
  define: ['"where is <name> defined"', '"where is <name> mentioned"'],
2241
2243
  meaning: ['"what is a <ClassName>"', '"what does <term> mean"'],
2242
2244
  count: ['"how many classes are there"', '"how many modules are there"'],
2245
+ serve: ['"what does <name> serve"', '"what serves <name>"'],
2246
+ denote: ['"what does <name> denote"', '"what denotes <name>"'],
2243
2247
  };
2244
2248
  const MISS_DEFAULT = ['"which modules import <name>"', '"what calls <name>"'];
2245
2249
 
@@ -2251,6 +2255,8 @@ function tailoredExamples(q) {
2251
2255
  const has = (re) => re.test(q);
2252
2256
  if (has(/\bimport/)) return MISS_EXAMPLES.import;
2253
2257
  if (has(/\bexport/)) return MISS_EXAMPLES.export;
2258
+ if (has(/\bserv(?:e|es|ed|ing|ice)\b/)) return MISS_EXAMPLES.serve;
2259
+ if (has(/\bdenot(?:e|es|ed|ing|ation)\b/)) return MISS_EXAMPLES.denote;
2254
2260
  if (has(/\b(?:calls?|caller|callee)\b/)) return MISS_EXAMPLES.call;
2255
2261
  if (has(/\b(?:tests?|cover|covering|tested)\b/)) return MISS_EXAMPLES.test;
2256
2262
  if (has(/\b(?:inherit|subclass|extends?|superclass|hierarchy|base class|parent class)\b/)) return MISS_EXAMPLES.inherit;
@@ -11009,9 +11015,18 @@ const DESCRIBE_GRAIN_WORD_RE = new RegExp(
11009
11015
  * whole tail into the term), and every resolver downstream then reads
11010
11016
  * "context" as part of the name. Closed list, and the term still has to
11011
11017
  * resolve afterwards, so a phrase that leaves nothing resolvable simply
11012
- * declines to the ordinary miss. */
11018
+ * declines to the ordinary miss.
11019
+ *
11020
+ * The "everything i need ..." branch is the same shape with a purpose clause
11021
+ * instead of a bare noun: "give me everything i need to change X"/"...for X"
11022
+ * both reach here as "describe everything i need to change X"/"describe
11023
+ * everything i need for X". The "to <verb...>" purpose clause is never split
11024
+ * word-by-word (a multi-word clause like "to work on X" would mis-split into
11025
+ * a captured verb plus a dangling remainder) — the greedy `.*\s` instead
11026
+ * always backtracks to the LAST whitespace run in the match, so the whole
11027
+ * clause strips as one unit and only the trailing symbol/path survives. */
11013
11028
  const DESCRIBE_LEADING_NOISE_RE =
11014
- /^(?:the\s+)?(?:context|details|detail|info|information|background)\s+(?:for|on|about|of|around)\s+/i;
11029
+ /^(?:(?:the\s+)?(?:context|details|detail|info|information|background)\s+(?:for|on|about|of|around)\s+|everything\s+i(?:'d|\s+would)?\s+need\s+(?:to\s+.*\s|for\s+))/i;
11015
11030
  function stripDescribeLeadingNoise(term) {
11016
11031
  const stripped = String(term || "").replace(DESCRIBE_LEADING_NOISE_RE, "").trim();
11017
11032
  return stripped || String(term || "").trim();
@@ -13355,6 +13370,15 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
13355
13370
  const described = await describeWrapperAnswer(query, { config, source, focus: newFocus, graph, tel });
13356
13371
  if (described) {
13357
13372
  answer = described.text; via = described.miss ? "miss" : "describe"; recordMiss = !!described.miss;
13373
+ // The composed engine's failed parse above (`via` was still "composed"
13374
+ // to reach this lane at all) can have deduced a goal/canonical for a
13375
+ // DIFFERENT reading it never resolved — e.g. "give me everything i need
13376
+ // to change X" bag-of-words-matches "change" as a touches verb and
13377
+ // deduces subject="describe everything i need". This rescue answers a
13378
+ // different question, so that stale interpretation must not survive
13379
+ // into its answer (same staleness the FUZZY-VERB DECLINE lane guards
13380
+ // against, below).
13381
+ canonical = null; deduced = null;
13358
13382
  note(trace, "lane: (4d) DESCRIBE-WRAPPER RESCUE — a polite wrapper around \"describe/tell me about <symbol>\" resolved via /describe, tried last after every other lane declined");
13359
13383
  note(trace, "goal: get a symbol's definition/kind/relations (phrased conversationally)");
13360
13384
  // Carry the resolved entity forward as the new focus, same class-gated