@polycode-projects/the-mechanical-code-talker 0.8.2 → 0.9.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.
@@ -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 - …"), the modal
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/politeness words stripped.
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",