@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,231 @@
1
+ # software-phrases.txt — the SE phrase book (ROADMAP items 4+7).
2
+ #
3
+ # One phrase pattern per line; {x}/{y} are entity slots the matcher binds to
4
+ # names in the graph. Lines starting with `~` declare a SYNONYM FAMILY: words
5
+ # and short phrases the matcher may treat as interchangeable when normalizing
6
+ # input. `#` starts a comment; blank lines are ignored.
7
+ # Parsed by src/corpus/templates.mjs loadPhrasebook().
8
+
9
+ # --- synonym families: the interchangeable vocabulary of asking about code ---
10
+ ~ call, invoke, use, execute, run, trigger, talk to
11
+ ~ import, include, require, pull in, depend on, load
12
+ ~ module, file, source file, unit
13
+ ~ function, fn, func, routine, procedure, method
14
+ ~ class, type, struct, record, interface
15
+ ~ test, spec, unit test, test case, check
16
+ ~ bug, defect, fault, issue, problem, glitch
17
+ ~ fix, repair, patch, resolve, correct
18
+ ~ change, modify, edit, touch, update, alter, rework
19
+ ~ delete, remove, drop, kill, strip out, get rid of
20
+ ~ create, add, make, introduce, write, define
21
+ ~ rename, relabel, move, relocate
22
+ ~ commit, change set, checkin, revision
23
+ ~ author, committer, contributor, developer, dev, engineer, programmer
24
+ ~ big, large, huge, long, giant
25
+ ~ small, tiny, short, little, lean
26
+ ~ recent, latest, newest, last, fresh
27
+ ~ old, oldest, ancient, stale, legacy
28
+ ~ broken, failing, red, busted, dead
29
+ ~ working, passing, green, healthy, fine
30
+ ~ central, core, important, key, load-bearing, critical
31
+ ~ unused, dead, orphaned, abandoned, unreferenced
32
+ ~ caller, call site, consumer, client
33
+ ~ callee, target, dependency
34
+ ~ parent, superclass, base class, ancestor
35
+ ~ child, subclass, derived class, descendant
36
+ ~ member, field, attribute, property
37
+ ~ argument, parameter, param, arg, input
38
+ ~ return, result, output, yield
39
+ ~ error, exception, failure, crash, panic
40
+ ~ codebase, repo, repository, project, source tree, code
41
+
42
+ # --- call graph: what calls what ---
43
+ what calls {x}
44
+ what does {x} call
45
+ who calls {x}
46
+ who is calling {x}
47
+ which functions call {x}
48
+ which modules call {x}
49
+ what invokes {x}
50
+ callers of {x}
51
+ call sites of {x}
52
+ where is {x} called
53
+ where is {x} called from
54
+ is {x} called anywhere
55
+ is {x} ever called
56
+ does anything call {x}
57
+ does {x} call {y}
58
+ what talks to {x}
59
+ what does {x} talk to
60
+ what depends on {x}
61
+ what does {x} depend on
62
+ show me the call graph around {x}
63
+
64
+ # --- imports and dependencies ---
65
+ who imports {x}
66
+ what imports {x}
67
+ which modules import {x}
68
+ what does {x} import
69
+ where is {x} imported
70
+ is {x} imported anywhere
71
+ does {x} import {y}
72
+ what requires {x}
73
+ what uses {x}
74
+ what does {x} use
75
+ what pulls in {x}
76
+ dependencies of {x}
77
+ dependents of {x}
78
+ what breaks if {x} changes
79
+ what would be affected by changing {x}
80
+ impact of changing {x}
81
+ blast radius of {x}
82
+
83
+ # --- definition and location: where things live ---
84
+ where is {x}
85
+ where is {x} defined
86
+ where does {x} live
87
+ which file defines {x}
88
+ which module defines {x}
89
+ what is defined in {x}
90
+ what does {x} define
91
+ what lives in {x}
92
+ find {x}
93
+ show me {x}
94
+ is there a {x}
95
+ do we have a {x}
96
+ does {x} exist
97
+ where is {x} importable from
98
+ what does {x} export
99
+ what is the public api of {x}
100
+ what is in the {x} package
101
+
102
+ # --- description: what things are ---
103
+ what is {x}
104
+ what is {x} for
105
+ what does {x} do
106
+ describe {x}
107
+ tell me about {x}
108
+ explain {x}
109
+ what kind of thing is {x}
110
+ is {x} a module or a function
111
+ what type is {x}
112
+ what does {x} return
113
+ what arguments does {x} take
114
+ what parameters does {x} take
115
+ what is the signature of {x}
116
+ what exceptions does {x} throw
117
+ what does {x} raise
118
+ what does {x} catch
119
+ is {x} public or private
120
+ is {x} deprecated
121
+
122
+ # --- structure: classes, members, inheritance ---
123
+ what does {x} contain
124
+ what members does {x} have
125
+ what methods does {x} have
126
+ what fields does {x} have
127
+ what is inside {x}
128
+ what does {x} inherit from
129
+ what extends {x}
130
+ what subclasses {x}
131
+ subclasses of {x}
132
+ superclass of {x}
133
+ parents of {x}
134
+ children of {x}
135
+ is {x} a subclass of {y}
136
+ what implements {x}
137
+ implementations of {x}
138
+
139
+ # --- tests ---
140
+ is {x} tested
141
+ does {x} have tests
142
+ what tests {x}
143
+ what tests cover {x}
144
+ which tests exercise {x}
145
+ where are the tests for {x}
146
+ test coverage of {x}
147
+ what does {x} test
148
+ is there a test for {x}
149
+ how well tested is {x}
150
+ what has no tests
151
+ which modules are untested
152
+ do the tests pass
153
+ are the tests green
154
+
155
+ # --- history: commits, authors, change ---
156
+ who wrote {x}
157
+ who authored {x}
158
+ who last touched {x}
159
+ who changed {x}
160
+ when was {x} last changed
161
+ when was {x} added
162
+ what changed recently
163
+ what changed in the last commit
164
+ what did {x} change
165
+ what commits touched {x}
166
+ history of {x}
167
+ how often does {x} change
168
+ what changes together with {x}
169
+ what usually changes with {x}
170
+ is {x} new
171
+ is {x} stale
172
+ who knows about {x}
173
+ who owns {x}
174
+
175
+ # --- size, count, shape ---
176
+ how many modules are there
177
+ how many functions are there
178
+ how many classes are there
179
+ how many tests are there
180
+ how big is {x}
181
+ how big is the codebase
182
+ how many lines is {x}
183
+ what is the biggest module
184
+ what is the biggest function
185
+ what is the most complex module
186
+ what is the most central module
187
+ what is the most imported module
188
+ what is the most called function
189
+ what has the most dependencies
190
+ what has no dependencies
191
+ what is unused
192
+ what is dead code
193
+ list the modules
194
+ list the classes
195
+ list everything you know about {x}
196
+
197
+ # --- bugs, quality, health ---
198
+ is {x} broken
199
+ why is {x} failing
200
+ what is wrong with {x}
201
+ does {x} have known bugs
202
+ is {x} safe to change
203
+ is {x} safe to delete
204
+ what is risky about {x}
205
+ what smells in {x}
206
+ is {x} too big
207
+ does {x} do too much
208
+
209
+ # --- memory: what tmct itself was told ---
210
+ what did i tell you about {x}
211
+ what do you know about {x}
212
+ what do you remember about {x}
213
+ what have i told you
214
+ what did i ask you before
215
+ did i mention {x}
216
+ remember that {x}
217
+ forget about {x}
218
+ what did you just say
219
+ what was your last answer
220
+ what did we talk about
221
+ summarise this session
222
+
223
+ # --- meta: about the graph and tmct ---
224
+ what can you answer
225
+ what do you know
226
+ how much do you know
227
+ what is in your graph
228
+ when was your graph built
229
+ where does your graph come from
230
+ what can i ask you
231
+ help me ask a better question
@@ -0,0 +1,89 @@
1
+ # grammar-rules.toml — the data-driven grammar-rule table (Phase 7, lever 2)
2
+ # (PLAN_RESPONSE_FINISHING.md, "The grammar pass (lever 2)").
3
+ #
4
+ # Each [[rule]] is a corrective grammar rule applied by applyGrammar() in
5
+ # src/finish.mjs over the PROSE spans of a segmented answer — NEVER the flat
6
+ # string, and NEVER a protected span (entity / path / number / code / provenance
7
+ # / receipt). The engine reads STRUCTURE, never guesses from surface: an article
8
+ # rule that would touch the word inside the following protected span refuses to
9
+ # fire, agreement reads the following number's value, and so on.
10
+ #
11
+ # CONTRACT (the plan's law): a rule's NEUTRAL behaviour is BYTE-STABLE. The only
12
+ # byte changes a rule may introduce are GENUINE fixes to defects tmct itself
13
+ # generates ("a artifact"). `kind` selects the built-in handler; `enabled=false`
14
+ # PARKS a rule out of the live answer path; the remaining keys are that handler's
15
+ # applicability conditions + parameters. Rules apply in file order; the set is
16
+ # chosen to commute so finish() is idempotent: finish(finish(x)) === finish(x).
17
+ #
18
+ # SEQUENCING (PLAN_RESPONSE_FINISHING.md, "one grammar rule per tuning cycle"):
19
+ # only ARTICLE-SELECTION is live this cycle — it fixes a genuine defect with a
20
+ # narrow, safe blast radius. AGREEMENT, CAPITALISATION, LIST and TERMINAL are
21
+ # fully implemented and golden-tested IN ISOLATION, but PARKED (enabled=false):
22
+ # each rewrites established product bytes (tmct's lowercase openers and repeated
23
+ # "and" joins are an intentional VOICE, not a grammar defect), so activating them
24
+ # is a per-rule tuning-cycle decision with its own bench + showcase reconcile,
25
+ # not a blanket flip. `enabled=false` keeps them inert in finish(); the goldens
26
+ # force-enable each rule to prove its behaviour independent of the live flag.
27
+
28
+ # 1. Article selection — a/an by the following word's phonetic onset. The live
29
+ # defect this fixes: the assert echo "every module is a artifact" -> "an
30
+ # artifact". Reads the next word (whether in-span or the leading token of the
31
+ # following protected span); refuses at a boundary it cannot read safely.
32
+ [[rule]]
33
+ id = "article-selection"
34
+ kind = "article"
35
+ enabled = true
36
+ registers = [] # [] = every register
37
+ description = "a/an agreement with the following word's phonetic onset"
38
+ # Spelling-vowel words that begin with a CONSONANT sound (take 'a').
39
+ consonant_sound_vowels = ["uni", "use", "user", "usa", "usu", "ubi", "eu", "ewe", "one", "once"]
40
+ # Spelling-consonant words that begin with a VOWEL sound (take 'an'): silent h.
41
+ vowel_sound_consonants = ["hour", "honest", "honour", "honor", "heir", "herb"]
42
+
43
+ # 2. Subject–verb agreement — an existential copula agrees with the count that
44
+ # follows it ("there is 3 classes" -> "there are 3 classes"; "there are 1
45
+ # class" -> "there is 1 class"). Structure-driven: the plurality is READ from
46
+ # the following number span's value (or a protected span's explicit `plural`
47
+ # flag), never guessed. Neutral on already-correct agreement.
48
+ [[rule]]
49
+ id = "subject-verb-agreement"
50
+ kind = "agreement"
51
+ enabled = false # PARKED — implemented + golden-tested, not live this cycle
52
+ registers = []
53
+ description = "existential copula agrees with the following count/plurality"
54
+ singular = ["is", "was", "has"]
55
+ plural = ["are", "were", "have"]
56
+
57
+ # 3. Sentence capitalisation — capitalise the first alphabetic character of a
58
+ # sentence-initial prose span. Never fires when the answer opens on a
59
+ # protected span (a path/entity opener is left exactly as grounded).
60
+ [[rule]]
61
+ id = "sentence-capitalisation"
62
+ kind = "capitalise"
63
+ enabled = false # PARKED — implemented + golden-tested, not live this cycle
64
+ registers = []
65
+ description = "capitalise the first alphabetic of a prose-initial span"
66
+
67
+ # 4. List punctuation — a series joined by repeated " and " connectives becomes
68
+ # a comma series with a single terminal conjunction ("a and b and c" -> "a, b
69
+ # and c"). Operates ONLY on the prose connective spans, never the entity spans
70
+ # they join; a two-item list ("a and b") is already correct and untouched.
71
+ [[rule]]
72
+ id = "list-punctuation"
73
+ kind = "list"
74
+ enabled = false # PARKED — implemented + golden-tested, not live this cycle
75
+ registers = []
76
+ description = "repeated 'and' joins in a 3+ item series become a comma series"
77
+ connective = " and "
78
+ separator = ", "
79
+
80
+ # 5. Terminal punctuation — exactly one sentence-final stop: a run of 2+ trailing
81
+ # stops in the final prose span collapses to one ("done.." -> "done."). Adds
82
+ # nothing where a fragment/list answer legitimately ends without a stop.
83
+ [[rule]]
84
+ id = "terminal-punctuation"
85
+ kind = "terminal"
86
+ enabled = false # PARKED — implemented + golden-tested, not live this cycle
87
+ registers = []
88
+ description = "collapse a run of trailing sentence stops to a single stop"
89
+ stops = [".", "!", "?"]
@@ -0,0 +1,68 @@
1
+ {"id":"describe-entity-terse","class":"describe","register":"terse","template":"{subject}: {kind} in {location}."}
2
+ {"id":"describe-entity-friendly","class":"describe","register":"friendly","template":"{subject} is a {kind} living in {location}."}
3
+ {"id":"describe-entity-with-doc","class":"describe","register":"friendly","template":"{subject} is a {kind} in {location} — \"{doc}\"."}
4
+ {"id":"describe-module-imports","class":"describe","register":"friendly","template":"{subject} imports {objects}."}
5
+ {"id":"describe-module-imported-by","class":"describe","register":"friendly","template":"{subject} is imported by {objects}."}
6
+ {"id":"describe-callers","class":"describe","register":"friendly","template":"{subject} is called by {objects}."}
7
+ {"id":"describe-callers-terse","class":"describe","register":"terse","template":"{subject} <- {objects}."}
8
+ {"id":"describe-callees","class":"describe","register":"friendly","template":"{subject} calls {objects}."}
9
+ {"id":"describe-members","class":"describe","register":"friendly","template":"{subject} contains {objects}."}
10
+ {"id":"describe-superclass","class":"describe","register":"friendly","template":"{subject} inherits from {object}."}
11
+ {"id":"describe-tested-by","class":"describe","register":"friendly","template":"{subject} is exercised by {objects}."}
12
+ {"id":"describe-untested","class":"describe","register":"friendly","template":"I can't see any test that imports {subject} — that may just mean the coverage is indirect."}
13
+ {"id":"describe-history","class":"describe","register":"friendly","template":"{subject} was last touched by {commit} ({when}): \"{message}\"."}
14
+ {"id":"describe-relation-fact","class":"describe","register":"terse","template":"{subject} {predicate} {object}."}
15
+ {"id":"describe-isa-fact","class":"describe","register":"friendly","template":"As I understand it, a {subject} is a kind of {object}."}
16
+ {"id":"count-plain","class":"count","register":"terse","template":"{count} {noun}."}
17
+ {"id":"count-sentence","class":"count","register":"friendly","template":"There are {count} {noun} in {scope}."}
18
+ {"id":"count-one","class":"count","register":"friendly","template":"There is exactly one {noun} in {scope}: {subject}."}
19
+ {"id":"count-zero","class":"count","register":"friendly","template":"I can't find any {noun} in {scope}."}
20
+ {"id":"count-with-sample","class":"count","register":"friendly","template":"{count} {noun} in {scope} — for example {examples}."}
21
+ {"id":"count-compared","class":"count","register":"friendly","template":"{count} {noun} — {comparison} for a codebase of this size."}
22
+ {"id":"miss-plain","class":"miss","register":"terse","template":"No match for \"{query}\"."}
23
+ {"id":"miss-honest","class":"miss","register":"friendly","template":"I couldn't ground \"{query}\" in anything I know. I'd rather say so than guess."}
24
+ {"id":"miss-rephrase-name","class":"miss","register":"friendly","template":"I don't recognise \"{query}\". If you name a module, function, or class I know about, I can work from there."}
25
+ {"id":"miss-rephrase-pattern","class":"miss","register":"friendly","template":"\"{query}\" didn't match anything. Try a shape like \"{example}\" — that's a question I can answer precisely."}
26
+ {"id":"miss-nearest","class":"miss","register":"friendly","template":"Nothing matches \"{query}\" exactly. The nearest name I know is {nearest} — is that what you meant?"}
27
+ {"id":"miss-nearest-terse","class":"miss","register":"terse","template":"No \"{query}\". Nearest: {nearest}."}
28
+ {"id":"miss-empty-graph","class":"miss","register":"friendly","template":"My graph is empty right now, so I can't answer \"{query}\" yet. Tell me things and I'll remember them."}
29
+ {"id":"miss-out-of-domain","class":"miss","register":"friendly","template":"That sounds like it's outside my patch — I only really know about {scope}. Ask me about the code and I'm much more useful."}
30
+ {"id":"ambiguity-two","class":"ambiguity","register":"friendly","template":"That could go two ways. If you mean {a} then {aAnswer}; if you mean {b} then {bAnswer}."}
31
+ {"id":"ambiguity-two-terse","class":"ambiguity","register":"terse","template":"{a}: {aAnswer} / {b}: {bAnswer}."}
32
+ {"id":"ambiguity-three","class":"ambiguity","register":"friendly","template":"I see three readings. If you mean {a} then {aAnswer}; if you mean {b} then {bAnswer}; if you mean {c} then {cAnswer}."}
33
+ {"id":"ambiguity-name-clash","class":"ambiguity","register":"friendly","template":"\"{query}\" names more than one thing here: {candidates}. Which one do you mean?"}
34
+ {"id":"ambiguity-name-clash-terse","class":"ambiguity","register":"terse","template":"\"{query}\" is ambiguous: {candidates}."}
35
+ {"id":"ambiguity-class-split","class":"ambiguity","register":"friendly","template":"Reading \"{query}\" as a {a} gives one answer, reading it as a {b} gives another. Say which and I'll commit."}
36
+ {"id":"assume-scope","class":"assumption","register":"friendly","template":"I'll assume you're asking about {assumed} — say otherwise and I'll drop that."}
37
+ {"id":"assume-scope-terse","class":"assumption","register":"terse","template":"Assuming {assumed}."}
38
+ {"id":"assume-spelling","class":"assumption","register":"friendly","template":"I read \"{query}\" as {assumed} (closest name I know). {answer}"}
39
+ {"id":"assume-this-repo","class":"assumption","register":"friendly","template":"I'm assuming \"{query}\" is about this repository. {answer}"}
40
+ {"id":"assume-latest","class":"assumption","register":"friendly","template":"You didn't say which {noun}, so I took the most recent one: {assumed}."}
41
+ {"id":"assume-singular","class":"assumption","register":"friendly","template":"Several things match; I picked the strongest, {assumed}. The runners-up were {others}."}
42
+ {"id":"recall-plain","class":"recall","register":"friendly","template":"You told me earlier that {fact}."}
43
+ {"id":"recall-plain-terse","class":"recall","register":"terse","template":"Earlier: {fact}."}
44
+ {"id":"recall-with-when","class":"recall","register":"friendly","template":"Earlier in this session ({when}) you said \"{utterance}\"."}
45
+ {"id":"recall-prior-session","class":"recall","register":"friendly","template":"In a previous session you told me that {fact} — I'm going on that."}
46
+ {"id":"recall-corpus","class":"recall","register":"friendly","template":"My corpus says {fact} — that's background knowledge, not something you told me."}
47
+ {"id":"recall-conflict","class":"recall","register":"friendly","template":"Careful — you told me earlier that {fact}, which doesn't sit well with \"{query}\". Which should I keep?"}
48
+ {"id":"recall-similar-question","class":"recall","register":"friendly","template":"You asked something similar before (\"{utterance}\") and the answer was: {answer}"}
49
+ {"id":"recall-nothing","class":"recall","register":"friendly","template":"You haven't told me anything about {subject} yet."}
50
+ {"id":"stored-fact","class":"stored","register":"friendly","template":"Noted: {subject} {predicate} {object}. I'll remember that."}
51
+ {"id":"stored-fact-terse","class":"stored","register":"terse","template":"Stored: {subject} {predicate} {object}."}
52
+ {"id":"stored-duplicate","class":"stored","register":"friendly","template":"I already knew that {subject} {predicate} {object} — nothing new to store."}
53
+ {"id":"nudge-precision","class":"nudge","register":"friendly","template":"The closer you get to a shape like \"{example}\", the sharper my answer gets."}
54
+ {"id":"nudge-commands","class":"nudge","register":"friendly","template":"If prose fails you, the slash commands always work — try {command}."}
55
+ {"id":"nudge-narrower","class":"nudge","register":"friendly","template":"That matched {count} things — too many to be useful. Narrow it with a module or class name."}
56
+ {"id":"conversational-greeting","class":"conversational","register":"friendly","template":"Hi. Ask me about this codebase — imports, calls, definitions, history — or /help."}
57
+ {"id":"conversational-greeting-hello-there","class":"conversational","register":"friendly","template":"Hello there. (A hollow voice says, \"fool.\") Ask me about this codebase, or /help."}
58
+ {"id":"conversational-greeting-good-morning","class":"conversational","register":"friendly","template":"Good morning. Ask me about this codebase, or /help."}
59
+ {"id":"conversational-greeting-good-afternoon","class":"conversational","register":"friendly","template":"Good afternoon. Ask me about this codebase, or /help."}
60
+ {"id":"conversational-greeting-good-evening","class":"conversational","register":"friendly","template":"Good evening. Ask me about this codebase, or /help."}
61
+ {"id":"conversational-thanks","class":"conversational","register":"friendly","template":"Any time. Ask another, or /help for what I can do."}
62
+ {"id":"conversational-farewell","class":"conversational","register":"friendly","template":"Bye — flushing the session log. Come back with a question any time."}
63
+ {"id":"orientation-friendly","class":"orientation","register":"friendly","template":"I answer questions about THIS codebase's structure — imports, calls, definitions,\nhistory and counts. For example:\n which modules import walk.mjs\n what calls buildContextBundle\n how many classes are there\n/help for commands, /stats for an overview of the graph."}
64
+ {"id":"miss-no-previous-answer","class":"miss","register":"friendly","template":"No previous answer to expand yet — ask me a question first, then say \"why\" or \"say more\"."}
65
+ {"id":"technical-density","class":"count","register":"technical","template":"{subject} carries {count} {noun} across {scope} — a concentration well above what a codebase of this size typically sustains ({provenance})."}
66
+ {"id":"technical-comparison","class":"count","register":"technical","template":"At {count} {noun}, {subject} sits {comparison} the comparable-project baseline, a divergence that reflects deliberate structure rather than measurement noise ({provenance})."}
67
+ {"id":"technical-superlative","class":"count","register":"technical","template":"No {noun} in {scope} is more {metric} than {subject}; it leads the next candidate by a clear margin of {count} ({provenance})."}
68
+ {"id":"technical-ratio","class":"count","register":"technical","template":"{subject} sustains a ratio of {count} {noun} per {unit}, placing it in the upper band for projects of comparable {scope} ({provenance})."}
package/package.json CHANGED
@@ -1,12 +1,38 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
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; no codebase index of its own.",
7
+ "keywords": [
8
+ "chatbot",
9
+ "no-llm",
10
+ "offline",
11
+ "deterministic",
12
+ "eliza",
13
+ "parry",
14
+ "nlp",
15
+ "wink-nlp",
16
+ "owl",
17
+ "rdf",
18
+ "ontology",
19
+ "controlled-natural-language",
20
+ "ace",
21
+ "knowledge-graph",
22
+ "provenance",
23
+ "code-navigation",
24
+ "cli"
25
+ ],
7
26
  "license": "MPL-2.0",
8
27
  "author": "Polycode Limited",
9
28
  "homepage": "https://polycode-projects.gitlab.io/the-mechanical-code-talker/",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://gitlab.com/polycode-projects/the-mechanical-code-talker.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://gitlab.com/polycode-projects/the-mechanical-code-talker/-/issues"
35
+ },
10
36
  "engines": {
11
37
  "node": ">=24"
12
38
  },
@@ -29,18 +55,29 @@
29
55
  "src/",
30
56
  "README.md",
31
57
  "ROADMAP.md",
32
- "LICENSE"
58
+ "LICENSE",
59
+ "corpus/",
60
+ "data/"
33
61
  ],
34
62
  "publishConfig": {
35
63
  "access": "public"
36
64
  },
37
65
  "dependencies": {
66
+ "ink": "^7.1.0",
67
+ "react": "^19.2.7",
38
68
  "smol-toml": "^1.7.0",
39
69
  "wink-eng-lite-web-model": "^1.8.1",
40
70
  "wink-nlp": "^2.4.0"
41
71
  },
42
72
  "scripts": {
43
73
  "test": "node --test \"test/**/*.test.mjs\"",
44
- "chat": "node bin/tmct.mjs"
74
+ "chat": "node bin/tmct.mjs",
75
+ "chatbench:run": "node chatbench/run.mjs",
76
+ "chatbench:judge": "node chatbench/judge.mjs",
77
+ "audit": "npm audit --audit-level=high",
78
+ "audit:fix": "npm audit fix"
79
+ },
80
+ "devDependencies": {
81
+ "ink-testing-library": "^4.0.0"
45
82
  }
46
83
  }
package/src/ask-nlp.mjs CHANGED
@@ -9,13 +9,13 @@
9
9
  // the bounded edit-distance tier still work, browser and Node alike). Keeping the
10
10
  // ~1MB CJS model out of the page is the point of the split.
11
11
  //
12
- // wink-nlp and wink-eng-lite-web-model are CJS loaded via createRequire, the
13
- // same Node-module-resolution approach viz.mjs uses to locate cytoscape (resolve
14
- // through the module system, never a guessed path). The require happens lazily on
15
- // first use and failure is cached as null: a checkout without the optional deps
16
- // installed answers exactly like the browser bundle, it never throws.
12
+ // wink-nlp and wink-eng-lite-web-model are loaded through the shared leaf loader
13
+ // src/wink-model.mjs (Node `createRequire` fallback + a browser registration seam),
14
+ // so this file no longer carries its own Node-only load block. The load happens
15
+ // lazily on first use and failure is cached as null: a checkout without the optional
16
+ // deps installed answers exactly like the browser bundle, it never throws.
17
17
 
18
- import { createRequire } from "node:module";
18
+ import { winkInstance } from "./wink-model.mjs";
19
19
 
20
20
  let cached; // undefined = not tried yet; null = unavailable (tried once, honestly off)
21
21
 
@@ -25,10 +25,8 @@ let cached; // undefined = not tried yet; null = unavailable (tried once, honest
25
25
  export function nlpAdapter() {
26
26
  if (cached !== undefined) return cached;
27
27
  try {
28
- const require = createRequire(import.meta.url);
29
- const winkNLP = require("wink-nlp");
30
- const model = require("wink-eng-lite-web-model");
31
- const nlp = winkNLP(model);
28
+ const nlp = winkInstance();
29
+ if (!nlp) { cached = null; return cached; }
32
30
  const its = nlp.its;
33
31
  cached = {
34
32
  /** Lowercase lemma of a single token ("imported" -> "import"); the word
@@ -42,6 +40,20 @@ export function nlpAdapter() {
42
40
  return w.toLowerCase();
43
41
  }
44
42
  },
43
+ /** True when wink's lexicon flags the word as an English stop word
44
+ * ("anyway", "well", "also", …). Consulted by the noise-strip strategy
45
+ * (interpret/strategies/noise-strip.mjs) as its wink tier — the strategy's
46
+ * own KEEP set screens out the grammar's load-bearing words (which/what/
47
+ * does/…) BEFORE this is asked, so wink flagging a question word is
48
+ * harmless by construction. False on any surprise, never a throw. */
49
+ isStopWord(word) {
50
+ try {
51
+ const out = nlp.readDoc(String(word || "")).tokens().out(its.stopWordFlag);
52
+ return out[0] === true;
53
+ } catch {
54
+ return false;
55
+ }
56
+ },
45
57
  /** UPOS tags aligned to the CALLER's word array. wink re-tokenizes (it
46
58
  * splits "walk.mjs" into three tokens), so each input word is greedily
47
59
  * matched to the run of wink tokens that spell it and takes its FIRST
package/src/ask-vocab.mjs CHANGED
@@ -220,6 +220,28 @@ export const MODIFIER_TO_KIND = Object.freeze({
220
220
  transitively: "transitive", indirectly: "transitive",
221
221
  });
222
222
 
223
+ // ---- reversible-passive participles (Cycle 6, PLAN_CYCLE_4.md) — past participles ->
224
+ // relation kind, for the agent-marked passive "X is <participle> by Y". Kept SEPARATE
225
+ // from VERB_TO_KIND on purpose: these forms are NOT standalone active verbs in this
226
+ // grammar ("defined" belongs to the multi-word "is defined in" and to the WHERE_MARKERS
227
+ // location routing; bare "inherited" has no active key), so folding them into
228
+ // VERB_TO_KIND would silently re-route "where is X defined" and other queries. This
229
+ // table is consulted ONLY by the keyword strategy's passive path, which has already
230
+ // confirmed a passive auxiliary AND an agent-marking "by" — so an active query is never
231
+ // affected. Most common participles ("imported"/"tested"/"called"/"covered") already
232
+ // reach VERB_TO_KIND via the lemma tier; this table backfills the two families the lemma
233
+ // tier can't (defines/inherits) plus the obvious siblings, so the passive works
234
+ // adapter-free too. ----
235
+ export const PASSIVE_PARTICIPLE_TO_KIND = Object.freeze({
236
+ imported: "imports", called: "calls", used: "uses",
237
+ tested: "tests", covered: "tests", verified: "tests", exercised: "tests", checked: "tests",
238
+ defined: "defines", declared: "defines",
239
+ inherited: "inherits", extended: "inherits", subclassed: "inherits",
240
+ contained: "contains",
241
+ exported: "reexports", "re-exported": "reexports", exposed: "reexports",
242
+ touched: "touches", changed: "touches", modified: "touches", edited: "touches", updated: "touches",
243
+ });
244
+
223
245
  // ---- §3.5 normalization — contractions/informal spellings that would otherwise
224
246
  // block a match, expanded BEFORE parsing (BOTH the anchored-template strategy
225
247
  // and the independent keyword-spotting strategy see the same normalized text —
@@ -295,7 +317,13 @@ export const MISSPELLINGS = Object.freeze({
295
317
  "tets": "tests",
296
318
  // grammar anchor words
297
319
  "whcih": "which", "wich": "which", "whihc": "which",
298
- "waht": "what",
320
+ // "wat" (chatbench cycle 2, tf-wat-calls): the internet-casual spelling of
321
+ // "what" — neither curated noise nor a restorable trigger typo, so "wat calls
322
+ // fnAlpha" used to die as "couldn't resolve one of the terms". Restored here
323
+ // so BOTH parse strategies and the relaxation cascade see the canonical
324
+ // anchor; the correction regex's dotted-extension guard keeps a module
325
+ // literally named "wat.mjs" untouched, same residual trade as every entry.
326
+ "waht": "what", "wat": "what",
299
327
  "dose": "does", "doess": "does",
300
328
  "teh": "the",
301
329
  // aggregate/list TRIGGER words (2026-07-02, trigger-typo work) — a typo of a count
@@ -626,6 +654,12 @@ export const CASCADE_NOISE = Object.freeze([
626
654
  // vocatives / terms of address (the "matey" of the worked example, and its kin)
627
655
  "matey", "mate", "buddy", "pal", "dude", "man", "bro", "bru", "fam",
628
656
  "friend", "sir", "maam", "folks", "guys", "everyone", "dear",
657
+ // the product's OWN name used as an address (chatbench cycle 2, ns-hey-tmct:
658
+ // "hey tmct, what calls fnAlpha thanks") — a vocative like "matey", stripped
659
+ // by the same rules: relaxParse's resolvesExact guard still protects a module
660
+ // literally named "tmct", and noise-strip's template/keyword-spot acceptance
661
+ // bounds the cost of a mid-question strip to an honest object-miss.
662
+ "tmct",
629
663
  // presentation frames — the keyword-spotting strategy's blind spot: the
630
664
  // compositional grammar skips these as FRAME_WORDS, but "show me what imports X"
631
665
  // otherwise decomposes (via keyword-spot) to ask{subject:"show me"}. Stripping