@polycode-projects/the-mechanical-code-talker 0.8.2 → 0.9.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.
- package/README.md +14 -0
- package/ROADMAP.md +90 -57
- package/corpus/seon/concepts.jsonl +42 -0
- package/package.json +2 -1
- package/src/ask.mjs +460 -13
- package/src/chat.mjs +516 -31
- package/src/grammar/lexicon-core.json +7 -0
- package/src/interpret/normalize.mjs +139 -3
|
@@ -168,6 +168,13 @@
|
|
|
168
168
|
"status": { "property": "data" },
|
|
169
169
|
"language": { "property": "data" },
|
|
170
170
|
"extension": { "property": "data" },
|
|
171
|
+
"churn": { "property": "data" },
|
|
172
|
+
"impact": { "property": "data" },
|
|
173
|
+
"complexity": { "property": "data" },
|
|
174
|
+
"latency": { "property": "data" },
|
|
175
|
+
"duration": { "property": "data" },
|
|
176
|
+
"frequency": { "property": "data" },
|
|
177
|
+
"severity": { "property": "data" },
|
|
171
178
|
"owner": { "property": "object" },
|
|
172
179
|
"maintainer": { "property": "object" },
|
|
173
180
|
"author": { "property": "object" },
|
|
@@ -46,7 +46,8 @@ const WRONG_WORD_RE = correctionRe(WRONG_WORDS);
|
|
|
46
46
|
|
|
47
47
|
// ---- closed PREAMBLE frames (0.8.2 feel wave, PLAN_CHAT_FEEL item 2) — the
|
|
48
48
|
// conversational wrapping a developer puts AROUND a real question: a greeting
|
|
49
|
-
// lead-in with a delimiter ("hey there, quick question - …"),
|
|
49
|
+
// lead-in with a delimiter ("hey there, quick question - …"), a thanks lead-in
|
|
50
|
+
// with a delimiter ("thanks so much, …" — Bug B2, 0.8.2 follow-up), the modal
|
|
50
51
|
// politeness wrapper ("can you … please"), and the show/give-me presentation
|
|
51
52
|
// bridge. These are DELIMITER- and PHRASE-anchored, so they must run BEFORE the
|
|
52
53
|
// FILLER-strip pass below: FILLER_WORDS strips "hey"/"can you" as bare words, so
|
|
@@ -88,6 +89,17 @@ const isListingRemainder = (rest) => {
|
|
|
88
89
|
* conversational lane, and "hey tmct, …" (a vocative, no delimiter after the
|
|
89
90
|
* greeting word) is left for the noise-strip tier that already owns it. */
|
|
90
91
|
const GREETING_PREAMBLE_RE = /^(?:hi|hiya|hello|hey|yo|howdy)(?:\s+there)?\s*[,—–-]\s*(?:(?:just\s+a\s+)?quick\s+question\s*[,:—–-]?\s*)?(.+)$/i;
|
|
92
|
+
/** Thanks lead-in with a delimiter (+ optional "quick question" bridge), the
|
|
93
|
+
* sibling of GREETING_PREAMBLE_RE for the "thanks" word family (Bug B2, 0.8.2
|
|
94
|
+
* follow-up): "thanks, <Q>" / "thanks so much, <Q>" -> "<Q>". chat.mjs's
|
|
95
|
+
* GREETINGS set already treats a BARE "thanks"/"thank you"/"cheers" as
|
|
96
|
+
* small-talk, and noise-strip.mjs's CASCADE_NOISE strips a single bare
|
|
97
|
+
* token — but a multi-word lead-in ("thanks so much, X", "thanks a lot, X")
|
|
98
|
+
* left "so"/"much"/"a"/"lot" debris after the noise strip that corrupted
|
|
99
|
+
* re-parse (the object term inherited the debris). Same delimiter- and
|
|
100
|
+
* non-empty-remainder-REQUIRED discipline as the greeting frame, so a bare
|
|
101
|
+
* "thanks so much" (no delimiter, no question) stays small-talk. */
|
|
102
|
+
const THANKS_PREAMBLE_RE = /^(?:thanks|thank\s+you|many\s+thanks|thx|ty|cheers)(?:\s+(?:so\s+much|a\s+lot|very\s+much|a\s+bunch))?\s*[,—–-]\s*(?:(?:just\s+a\s+)?quick\s+question\s*[,:—–-]?\s*)?(.+)$/i;
|
|
91
103
|
/** Modal politeness wrapper: "can/could/would/will you [please] <Q>[, please][?]"
|
|
92
104
|
* -> "<Q>". FILLER_WORDS already ate "can you"/"please" as words; this frame
|
|
93
105
|
* removes them as a WRAPPER so the ", please" comma never survives into the
|
|
@@ -113,6 +125,8 @@ export function applyPreambleFrames(text) {
|
|
|
113
125
|
const before = q;
|
|
114
126
|
let m = q.match(GREETING_PREAMBLE_RE);
|
|
115
127
|
if (m) q = m[1].trim();
|
|
128
|
+
m = q.match(THANKS_PREAMBLE_RE);
|
|
129
|
+
if (m) q = m[1].trim();
|
|
116
130
|
m = q.match(MODAL_WRAPPER_RE);
|
|
117
131
|
if (m) q = m[1].trim();
|
|
118
132
|
m = q.match(SHOW_GIVE_ME_RE);
|
|
@@ -127,9 +141,122 @@ export function applyPreambleFrames(text) {
|
|
|
127
141
|
return q;
|
|
128
142
|
}
|
|
129
143
|
|
|
144
|
+
// ---- ADVANCED_GRAMMAR track (a) (PLAN_ADVANCED_GRAMMAR.md §2): closed-frame
|
|
145
|
+
// subordination + conditionals — the proven 0.8.2 preamble-frame method (closed,
|
|
146
|
+
// delimiter-anchored, first-match-wins, unmatched text passes through
|
|
147
|
+
// byte-unchanged) at its next size up. Two families:
|
|
148
|
+
// SUBORDINATION_FRAMES strippable leading framing clauses ("since we're
|
|
149
|
+
// refactoring, which modules import x?" -> "which modules import x?") —
|
|
150
|
+
// the clause carries no query content, it's conversational scaffolding
|
|
151
|
+
// around a real question, same species as the greeting/thanks preambles.
|
|
152
|
+
// CONDITIONAL frames "if <clause>, is it <qualifier>?" compiles to the
|
|
153
|
+
// EXISTING compositional boolean-qualifier shape the grammar already
|
|
154
|
+
// answers ("<kind> <relation-gerund> <object> and <qualifier>" — proven by
|
|
155
|
+
// test/ask-compositional.test.mjs's "classes inheriting from Base and
|
|
156
|
+
// tested"), and the counterfactual "if X were deleted, what would break"
|
|
157
|
+
// compiles to the existing transitive-modifier reverse-dependency closure
|
|
158
|
+
// ("which modules transitively import X" — proven by
|
|
159
|
+
// test/ask.test.mjs's transitive-modifier suite). Both frame families are
|
|
160
|
+
// wired INSIDE normalizeQuery (not as a separate call site) because this
|
|
161
|
+
// agent's scope is normalize.mjs only — ask.mjs/interpret/pipeline.mjs
|
|
162
|
+
// call normalizeQuery already, so embedding here reaches every strategy
|
|
163
|
+
// for free, with no new call site required. A conditional shape NOT
|
|
164
|
+
// covered by these two closed patterns is deliberately left unmatched —
|
|
165
|
+
// the honest-miss discipline PLAN_ADVANCED_GRAMMAR §2a states explicitly
|
|
166
|
+
// ("refuse any conditional whose consequent isn't a computable
|
|
167
|
+
// traversal"): only rewrite to a shape independently verified correct,
|
|
168
|
+
// never a plausible-looking guess. ----
|
|
169
|
+
|
|
170
|
+
/** Strippable leading framing clause: "since/although/though/while/because/
|
|
171
|
+
* whereas/given that/now that <clause>, <Q>" -> "<Q>". Delimiter- (comma-)
|
|
172
|
+
* anchored and non-empty-remainder-required, same discipline as
|
|
173
|
+
* GREETING_PREAMBLE_RE — a bare "since when do you know that" (no comma
|
|
174
|
+
* splitting a framing clause from a real question) is NOT a subordination
|
|
175
|
+
* wrapper and is left alone; "since" as an ordinary temporal content word
|
|
176
|
+
* ("modules changed since last week", no leading comma-delimited clause)
|
|
177
|
+
* never matches either. */
|
|
178
|
+
const SUBORDINATION_FRAMES_RE =
|
|
179
|
+
/^(?:since|although|though|while|because|whereas|given\s+that|now\s+that)\s+.+?,\s*(.+)$/i;
|
|
180
|
+
|
|
181
|
+
/** Apply the subordination-frame strip to a small fixpoint (a doubly-wrapped
|
|
182
|
+
* "well, since X, although Y, <Q>" peels fully — rare, but the same
|
|
183
|
+
* discipline applyPreambleFrames already uses). Pure; unmatched text passes
|
|
184
|
+
* through byte-unchanged. */
|
|
185
|
+
export function applySubordinationFrames(text) {
|
|
186
|
+
let q = String(text || "");
|
|
187
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
188
|
+
const m = q.match(SUBORDINATION_FRAMES_RE);
|
|
189
|
+
if (!m) break;
|
|
190
|
+
q = m[1].trim();
|
|
191
|
+
}
|
|
192
|
+
return q;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** relation-verb (bare 3rd-person singular, the RELATIONS table's own primary
|
|
196
|
+
* form) -> gerund, the shape the compositional grammar's proven
|
|
197
|
+
* "<kind> <gerund> <object> and <qualifier>" pattern needs. A small, closed,
|
|
198
|
+
* hand-curated table (not a generic morphological rule) — the same
|
|
199
|
+
* "no guessing" discipline as every other closed vocabulary in this file. */
|
|
200
|
+
const CONDITIONAL_VERB_GERUND = Object.freeze({
|
|
201
|
+
imports: "importing", calls: "calling", touches: "touching", tests: "testing",
|
|
202
|
+
exports: "exporting", contains: "containing", defines: "defining", uses: "using",
|
|
203
|
+
"inherits from": "inheriting from",
|
|
204
|
+
});
|
|
205
|
+
/** entity kind noun (singular) -> plural, the LISTABLE_KINDS the compositional
|
|
206
|
+
* grammar's subject slot takes. Regular English pluralization covers every
|
|
207
|
+
* entry (no irregulars in this closed set), so a flat table beats a
|
|
208
|
+
* morphological rule for the same "no guessing" reason as the verb table. */
|
|
209
|
+
const CONDITIONAL_KIND_PLURAL = Object.freeze({
|
|
210
|
+
module: "modules", class: "classes", function: "functions", method: "methods",
|
|
211
|
+
attribute: "attributes", variable: "variables", commit: "commits", file: "files",
|
|
212
|
+
});
|
|
213
|
+
const CONDITIONAL_QUALIFIER_SRC =
|
|
214
|
+
"public|private|protected|static|abstract|constant|re-?exported|exported|tested|covered|untested|uncovered";
|
|
215
|
+
/** "if a/the <kind> <relation-verb> <object>, is/are it/that/they/this
|
|
216
|
+
* <qualifier>?" -> "<kind plural> <relation-gerund> <object> and <qualifier>".
|
|
217
|
+
* Closed to the two small tables above (both ends validated), so an
|
|
218
|
+
* unrecognized kind/verb/qualifier simply doesn't match — falls through to
|
|
219
|
+
* the ordinary grammar, which honestly misses on the untransformed "if …"
|
|
220
|
+
* text rather than risk a wrong composition. */
|
|
221
|
+
const CONDITIONAL_QUALIFIER_RE = new RegExp(
|
|
222
|
+
"^if\\s+(?:a|an|the)?\\s*(" + Object.keys(CONDITIONAL_KIND_PLURAL).join("|") + ")\\s+"
|
|
223
|
+
+ "(" + Object.keys(CONDITIONAL_VERB_GERUND).join("|") + ")\\s+"
|
|
224
|
+
+ "(.+?),\\s*(?:is|are)\\s+(?:it|that|they|this)\\s+"
|
|
225
|
+
+ "(" + CONDITIONAL_QUALIFIER_SRC + ")\\??$",
|
|
226
|
+
"i",
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
/** Counterfactual deletion: "if <X> were/was deleted/removed(,)? what
|
|
230
|
+
* would/might break/fail/be affected?" -> "which modules transitively import
|
|
231
|
+
* <X>" — the EXISTING reverse-dependency closure (impactClosure via the
|
|
232
|
+
* transitive modifier, ask.mjs/codegraph.mjs), proven correct by
|
|
233
|
+
* test/ask.test.mjs's transitive-modifier suite. Exported so chat.mjs can
|
|
234
|
+
* independently recognize the SAME raw query shape and prepend a
|
|
235
|
+
* hypothetical marker to the rendered answer (a hypothetical consequent must
|
|
236
|
+
* never be presented as an unqualified fact) — normalize.mjs only rewrites
|
|
237
|
+
* the QUESTION text, it never touches the answer. */
|
|
238
|
+
export const COUNTERFACTUAL_RE =
|
|
239
|
+
/^if\s+(.+?)\s+(?:were|was)\s+(?:deleted|removed),?\s*what\s+(?:would|might|could)\s+(?:break|fail|be\s+affected)\??$/i;
|
|
240
|
+
|
|
241
|
+
/** Apply the two closed CONDITIONAL frames, first-match-wins (qualifier
|
|
242
|
+
* composition tried first — it is the more specific shape). Pure; unmatched
|
|
243
|
+
* text passes through byte-unchanged. */
|
|
244
|
+
export function applyConditionalFrames(text) {
|
|
245
|
+
const q = String(text || "");
|
|
246
|
+
const qual = q.match(CONDITIONAL_QUALIFIER_RE);
|
|
247
|
+
if (qual) {
|
|
248
|
+
const kind = CONDITIONAL_KIND_PLURAL[qual[1].toLowerCase()];
|
|
249
|
+
const gerund = CONDITIONAL_VERB_GERUND[qual[2].toLowerCase()];
|
|
250
|
+
return `${kind} ${gerund} ${qual[3].trim()} and ${qual[4].toLowerCase()}`;
|
|
251
|
+
}
|
|
252
|
+
const cf = q.match(COUNTERFACTUAL_RE);
|
|
253
|
+
if (cf) return `which modules transitively import ${cf[1].trim()}`;
|
|
254
|
+
return q;
|
|
255
|
+
}
|
|
256
|
+
|
|
130
257
|
/** Free-text -> normalized free-text: contractions expanded, g-dropped words
|
|
131
|
-
* restored, closed preamble frames peeled, filler/
|
|
132
|
-
* Idempotent and pure — the same
|
|
258
|
+
* restored, closed preamble/subordination/conditional frames peeled, filler/
|
|
259
|
+
* politeness words stripped. Idempotent and pure — the same
|
|
133
260
|
* input always normalizes the same way, so both parsing strategies see
|
|
134
261
|
* identical text and their outputs are directly comparable. Deliberately
|
|
135
262
|
* does NOT force lowercase: object/subject terms (module names like
|
|
@@ -147,6 +274,15 @@ export function normalizeQuery(text) {
|
|
|
147
274
|
// bridge) — AFTER the correction tables (a repaired "give me"/"show me" still
|
|
148
275
|
// feeds the bridge) but BEFORE the filler strip erases their anchor words.
|
|
149
276
|
q = applyPreambleFrames(q);
|
|
277
|
+
// subordination (strip a leading framing clause) THEN conditional (compile
|
|
278
|
+
// "if …" to an existing working shape) — subordination first so a stacked
|
|
279
|
+
// "since we're refactoring, if a module imports X, is it tested" peels its
|
|
280
|
+
// outer wrapper before the conditional frame ever sees it. Both run BEFORE
|
|
281
|
+
// the filler strip for the same reason the preamble frames do: their
|
|
282
|
+
// anchors ("since", "if", "is it") would otherwise be eaten as bare words,
|
|
283
|
+
// leaving punctuation/clause debris that poisons the parse.
|
|
284
|
+
q = applySubordinationFrames(q);
|
|
285
|
+
q = applyConditionalFrames(q);
|
|
150
286
|
if (FILLER_WORDS.length) {
|
|
151
287
|
const fillerRe = new RegExp(
|
|
152
288
|
"\\b(" + [...FILLER_WORDS].sort((a, b) => b.length - a.length).map(escapeRegex).join("|") + ")\\b",
|