@polycode-projects/the-mechanical-code-talker 2.6.0 → 2.6.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.
- package/data/templates/responses.jsonl +1 -1
- package/package.json +1 -1
- package/src/domain/ask.mjs +66 -10
- package/src/domain/codegraph.mjs +1 -1
- package/src/domain/interpret/normalize.mjs +1 -1
- package/src/domain/router/registry.mjs +17 -4
- package/src/domain/router/resolver.mjs +4 -2
- package/src/services/chat.mjs +626 -43
- package/src/services/sessions.mjs +10 -2
- package/src/surfaces/web/memory-ask-browser.bundle.js +86 -18
|
@@ -103,7 +103,9 @@ export function upsertSession(entities, record) {
|
|
|
103
103
|
|
|
104
104
|
const started = record.started || "";
|
|
105
105
|
const ended = record.ended || turns.at(-1)?.ts || started;
|
|
106
|
-
|
|
106
|
+
// The session's recorded questions quote the user VERBATIM (`input`) when
|
|
107
|
+
// the turn carried a rewrite; `query` is the fallback for older sidecars.
|
|
108
|
+
const queries = turns.map((t) => String(t?.input || t?.query || "")).filter(Boolean);
|
|
107
109
|
const label = sessionLabel(record.id);
|
|
108
110
|
entities.individuals.push({
|
|
109
111
|
id: sid, label, class: SESSION_CLASS,
|
|
@@ -206,6 +208,9 @@ async function recordSessionMemory(graphFile, record, repoDirOverride = null) {
|
|
|
206
208
|
const utterances = [];
|
|
207
209
|
for (const t of record.turns || []) {
|
|
208
210
|
const query = String(t?.query || "");
|
|
211
|
+
// The visitor utterance quotes the user VERBATIM; `query` (possibly a
|
|
212
|
+
// rewrite) stays the transcript-matching key below, unchanged.
|
|
213
|
+
const spoken = String(t?.input || t?.query || "");
|
|
209
214
|
const ts = String(t?.ts || "");
|
|
210
215
|
if (!query || !ts) continue;
|
|
211
216
|
// the structured parse the turn produced — stored on the visitor utterance
|
|
@@ -216,7 +221,7 @@ async function recordSessionMemory(graphFile, record, repoDirOverride = null) {
|
|
|
216
221
|
if (t.miss) parsed.miss = true;
|
|
217
222
|
if (t.via) parsed.via = t.via; // answer provenance (W1) — carried into memory
|
|
218
223
|
utterances.push({
|
|
219
|
-
role: "visitor", text:
|
|
224
|
+
role: "visitor", text: spoken, ts, sessionId: record.id, sessionStarted: record.started || "",
|
|
220
225
|
...(Object.keys(parsed).length ? { parsed } : {}),
|
|
221
226
|
});
|
|
222
227
|
const answer = answers.get(turnKey(ts, query));
|
|
@@ -258,6 +263,9 @@ export function parseSessionJsonl(text) {
|
|
|
258
263
|
else if (rec?.type === "turn") {
|
|
259
264
|
turns.push({
|
|
260
265
|
ts: String(rec.ts || ""), query: String(rec.query || ""),
|
|
266
|
+
// the VERBATIM user line, recorded beside any rewritten query — the
|
|
267
|
+
// recall surfaces quote this form, never the rewrite.
|
|
268
|
+
...(rec.input ? { input: String(rec.input) } : {}),
|
|
261
269
|
resolvedIds: arr(rec.resolvedIds), answeredIds: arr(rec.answeredIds), miss: !!rec.miss,
|
|
262
270
|
// preserved for the memory fold (memory/fold.mjs): slash-command turns and
|
|
263
271
|
// conversational filler are recorded but never folded into the corpus.
|
|
@@ -1399,7 +1399,7 @@
|
|
|
1399
1399
|
});
|
|
1400
1400
|
if (truncatedStructural.length) {
|
|
1401
1401
|
lines.push(
|
|
1402
|
-
"warning: partial edge lists (" + truncatedStructural.map((t) => `${t.predicate}: ${t.shown}/${t.count}`).join(", ") + ") \u2014 this closure may be missing edges. Cross-check critical results with
|
|
1402
|
+
"warning: partial edge lists (" + truncatedStructural.map((t) => `${t.predicate}: ${t.shown}/${t.count}`).join(", ") + ") \u2014 this closure may be missing edges. Cross-check critical results with a lexical search (/find <term>)."
|
|
1403
1403
|
);
|
|
1404
1404
|
}
|
|
1405
1405
|
return lines.join("\n");
|
|
@@ -2877,7 +2877,7 @@ ${shown.join("\n")}${tail}`;
|
|
|
2877
2877
|
GREETING_PREAMBLE_RE = /^(?:hi|hiya|hello|hey|yo|howdy|g'?day|yeah\s+nah|good\s+(?:morning|afternoon|evening|day)|greetings|salutations)(?:\s+(?:there|pardner|folks|friend|mate))?\s*[,.—–-]\s*(?:(?:just\s+a\s+)?quick\s+question\s*[,:—–-]?\s*)?(.+)$/i;
|
|
2878
2878
|
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;
|
|
2879
2879
|
ACK_PREAMBLE_RE = /^(?:(?:ok(?:ay)?|aight|cool|alright|sure|right|fine|great|nice|got it|gotcha|sounds good|no worries|no problem)[\s,]+)+(.+)$/i;
|
|
2880
|
-
BROWSING_PREAMBLE_RE = /^(?:just\s+(?:poking\s+around|looking\s+around|browsing|exploring|checking\s+(?:this|it)\s+out)|first\s+time\s+(?:trying\s+this\s+out|using\s+this|here))\s*[,.—–-]\s*(.+)$/i;
|
|
2880
|
+
BROWSING_PREAMBLE_RE = /^(?:just\s+(?:poking\s+around|looking\s+around|browsing|exploring|checking\s+(?:this|it)\s+out)|first\s+time\s+(?:trying\s+this\s+out|using\s+this|here)|i'?m\s+new\s+(?:here|around\s+here|to\s+(?:this|all\s+this)(?:\s+(?:repo|codebase|project|app|tool|thing))?))\s*[,.—–-]\s*(.+)$/i;
|
|
2881
2881
|
HEDGE_ADVERB_PREAMBLE_RE = /^(?:(?:maybe|possibly|perhaps)\s+)+(.+)$/i;
|
|
2882
2882
|
TROUBLE_ASIDE_RE = /,?\s*if\s+(?:it'?s|it\s+is|that'?s|that\s+is)\s+not\s+too\s+much\s+(?:trouble|bother|hassle)\s*,?\s*/i;
|
|
2883
2883
|
MODAL_WRAPPER_RE = /^(?:can|could|would|will)\s+you\s+(?:please\s+)?(.+?)(?:[,\s]+please)?\??$/i;
|
|
@@ -3783,7 +3783,7 @@ ${shown.join("\n")}${tail}`;
|
|
|
3783
3783
|
function parseComposite(text, nlp) {
|
|
3784
3784
|
const w = splitWords(text);
|
|
3785
3785
|
const lc = w.map((x) => x.toLowerCase());
|
|
3786
|
-
return parseExistence(w, lc) || parseQualifierCheck(w, lc) || parseUniversal(w, lc, nlp) || parseNegation(text, nlp, 0) || parseNegatedAsk(w, lc) || parseForwardNegation(w, lc, nlp) || parseTemporal(w, lc, nlp, 0) || parseCommitFilter(w, lc) || parseAnaphora(w, lc, nlp) || parseAggregate(w, lc, nlp) || parseSuperlative(w, lc, nlp) || parseFind(w, lc, nlp, 0) || parseList(w, lc, nlp, 0) || parseNested(w, lc, nlp, 0) || parsePluralAnaphoraObject(w, lc, nlp) || parseRelationalOrQualified(w, lc, nlp, 0);
|
|
3786
|
+
return parseExistence(w, lc) || parseQualifierCheck(w, lc) || parseUniversal(w, lc, nlp) || parseNegation(text, nlp, 0) || parseNegatedAsk(w, lc, nlp) || parseForwardNegation(w, lc, nlp) || parseTemporal(w, lc, nlp, 0) || parseCommitFilter(w, lc) || parseAnaphora(w, lc, nlp) || parseAggregate(w, lc, nlp) || parseSuperlative(w, lc, nlp) || parseFind(w, lc, nlp, 0) || parseList(w, lc, nlp, 0) || parseNested(w, lc, nlp, 0) || parsePluralAnaphoraObject(w, lc, nlp) || parseRelationalOrQualified(w, lc, nlp, 0);
|
|
3787
3787
|
}
|
|
3788
3788
|
function complementAst(entityType, diffAtom) {
|
|
3789
3789
|
return {
|
|
@@ -4110,11 +4110,11 @@ ${shown.join("\n")}${tail}`;
|
|
|
4110
4110
|
if (!term) return { node: "miss", reason: `"is/are <qualifier>" needs a named thing to check first` };
|
|
4111
4111
|
return { node: "qualCheck", term, qualifier: lc[qualIdx], negated, scope: tail?.scope || null };
|
|
4112
4112
|
}
|
|
4113
|
-
function parseNegatedAsk(w, lc) {
|
|
4114
|
-
if (
|
|
4113
|
+
function parseNegatedAsk(w, lc, nlp) {
|
|
4114
|
+
if (!["do", "does", "did", "is", "are", "was", "were"].includes(lc[0])) return null;
|
|
4115
4115
|
const notIdx = lc.indexOf("not", 1);
|
|
4116
4116
|
if (notIdx < 0) return null;
|
|
4117
|
-
const positive =
|
|
4117
|
+
const positive = parseSimpleClause(w.filter((_, i) => i !== notIdx).join(" "), nlp);
|
|
4118
4118
|
if (!positive || positive.shape !== "ask") return null;
|
|
4119
4119
|
return { ...positive, negated: true };
|
|
4120
4120
|
}
|
|
@@ -4917,7 +4917,11 @@ ${shown.join("\n")}${tail}`;
|
|
|
4917
4917
|
return { compositeKind: "temporal", matches: commits, entityType: ast.entityType, innerCount: inner.length };
|
|
4918
4918
|
}
|
|
4919
4919
|
function evalSuperlative(graph, ast) {
|
|
4920
|
-
|
|
4920
|
+
let pool = graph.individuals.filter((i) => i.class === ast.entityType);
|
|
4921
|
+
if (ast.metric?.kind === "tests" && ast.entityType === "Module") {
|
|
4922
|
+
const testSubjects = new Set(edgesOfKind2(graph, "tests").map((e) => e.subject));
|
|
4923
|
+
pool = pool.filter((i) => !testSubjects.has(i.id) && !isTestPath(String(i.label).toLowerCase()));
|
|
4924
|
+
}
|
|
4921
4925
|
const scored = pool.map((ind) => ({ ind, score: degreeMetric(graph, ind, ast.metric) })).sort((a, z) => ast.extreme === "most" ? z.score - a.score : a.score - z.score);
|
|
4922
4926
|
if (!scored.length) return { compositeKind: "superlative", entityType: ast.entityType, matches: [] };
|
|
4923
4927
|
const best = scored[0].score;
|
|
@@ -5498,7 +5502,7 @@ ${shown.join("\n")}${tail}`;
|
|
|
5498
5502
|
}
|
|
5499
5503
|
return declineOnUnplacedWords(resolveObjectCore(graph, term, opts), term);
|
|
5500
5504
|
}
|
|
5501
|
-
return resolveObjectCore(graph, term, opts);
|
|
5505
|
+
return declineOnUnplacedWords(resolveObjectCore(graph, term, opts), term);
|
|
5502
5506
|
}
|
|
5503
5507
|
function resolveTermOrContext(graph, term, contextId) {
|
|
5504
5508
|
if (CONTEXT_PRONOUNS.includes(String(term || "").trim().toLowerCase())) {
|
|
@@ -5771,7 +5775,7 @@ ${shown.join("\n")}${tail}`;
|
|
|
5771
5775
|
if (entityType && entityType !== "Change") {
|
|
5772
5776
|
const wantClasses = classesForKinds(graph, fwdKinds);
|
|
5773
5777
|
const siblingClass = FINE_CLASS_SIBLING[entityType];
|
|
5774
|
-
if (!wantClasses.has(entityType) && !(siblingClass && wantClasses.has(siblingClass))) {
|
|
5778
|
+
if (wantClasses.size && !wantClasses.has(entityType) && !(siblingClass && wantClasses.has(siblingClass))) {
|
|
5775
5779
|
return {
|
|
5776
5780
|
matches: [],
|
|
5777
5781
|
objMatch,
|
|
@@ -6144,13 +6148,44 @@ ${options2}
|
|
|
6144
6148
|
};
|
|
6145
6149
|
}
|
|
6146
6150
|
if (result.ambiguous) {
|
|
6147
|
-
|
|
6151
|
+
let pool = [result.objMatch, ...result.candidates || []].filter(Boolean);
|
|
6152
|
+
let branches = result.branches;
|
|
6153
|
+
if (parsed.kind && parsed.kind !== "touches" && !pool.every((i) => i.class === "Commit")) {
|
|
6154
|
+
const grainOk = (i) => !["Commit", "Session", "Source", "Utterance"].includes(i?.class);
|
|
6155
|
+
const kept = pool.filter(grainOk);
|
|
6156
|
+
if (kept.length) {
|
|
6157
|
+
pool = kept;
|
|
6158
|
+
if (branches?.length) branches = branches.filter((b) => grainOk(b.candidate));
|
|
6159
|
+
}
|
|
6160
|
+
}
|
|
6161
|
+
if (graph && !/[/.]/.test(String(parsed.object || ""))) {
|
|
6162
|
+
const tLc = String(parsed.object || "").toLowerCase();
|
|
6163
|
+
const termWords = new Set(splitIdentifierWords(String(parsed.object || "")).filter((w) => w.length >= 3));
|
|
6164
|
+
const already = new Set(pool.map((i) => i.id));
|
|
6165
|
+
let nearest = null;
|
|
6166
|
+
let bestShared = 0;
|
|
6167
|
+
let bestD = Infinity;
|
|
6168
|
+
if (termWords.size) {
|
|
6169
|
+
for (const i of graph.individuals) {
|
|
6170
|
+
if (!["Function", "Method", "Class", "GlobalVariable", "Attribute"].includes(i.class) || already.has(i.id)) continue;
|
|
6171
|
+
const shared = splitIdentifierWords(String(i.label)).filter((w) => termWords.has(w)).length;
|
|
6172
|
+
if (!shared || shared < bestShared) continue;
|
|
6173
|
+
const d = editDistance(String(i.label).toLowerCase(), tLc, 8);
|
|
6174
|
+
if (shared > bestShared || d < bestD) {
|
|
6175
|
+
nearest = i;
|
|
6176
|
+
bestShared = shared;
|
|
6177
|
+
bestD = d;
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
if (nearest) pool = [...pool, nearest];
|
|
6182
|
+
}
|
|
6148
6183
|
const noun = pool.length && pool.every((i) => i.class === "Commit") ? "commit" : "module";
|
|
6149
6184
|
const shown = pool.slice(0, OVERFLOW_CAP).map((i) => i.label);
|
|
6150
6185
|
const extra2 = pool.length > OVERFLOW_CAP ? `, \u2026and ${pool.length - OVERFLOW_CAP} more` : "";
|
|
6151
6186
|
const lead = `"${parsed.object}" matches more than one ${noun} ambiguously \u2014 did you mean ${listJoin(shown)}${extra2}? Try one of those. If you're not sure, narrow it to one name.`;
|
|
6152
|
-
const content =
|
|
6153
|
-
${
|
|
6187
|
+
const content = branches && branches.length ? `${lead}
|
|
6188
|
+
${branches.map((b, i) => `${i + 1}) ${b.candidate.label}: ${b.rendered.content}`).join("\n")}` : lead;
|
|
6154
6189
|
return {
|
|
6155
6190
|
content,
|
|
6156
6191
|
miss: false,
|
|
@@ -23094,7 +23129,7 @@ ${JSON.stringify(envelope, null, 2)}`;
|
|
|
23094
23129
|
signature: { tool: "tmct_signature", arg: "symbol", help: "a symbol's signature only" },
|
|
23095
23130
|
members: { tool: "tmct_members", arg: "class", help: "the methods/attributes of a class" },
|
|
23096
23131
|
subclasses: { tool: "tmct_subclasses", arg: "class", help: "the subclasses of a class" },
|
|
23097
|
-
impact: { tool: "tmct_impact", arg: "module", help: "what a change to this module reaches (impact closure)" },
|
|
23132
|
+
impact: { tool: "tmct_impact", arg: "module", help: "what a change to this module or symbol reaches (impact closure)" },
|
|
23098
23133
|
callers: { tool: "tmct_callers", arg: "symbol", help: "functions that call this symbol" },
|
|
23099
23134
|
callees: { tool: "tmct_callees", arg: "symbol", help: "functions this symbol calls" },
|
|
23100
23135
|
tests: { tool: "tmct_tests_for", arg: "symbol", help: "the tests covering this symbol" },
|
|
@@ -23391,8 +23426,25 @@ ${JSON.stringify(envelope, null, 2)}`;
|
|
|
23391
23426
|
}
|
|
23392
23427
|
return { predicate, object: String(objectRaw || "").trim() };
|
|
23393
23428
|
}
|
|
23394
|
-
var TEACH_PRONOUNS = Object.freeze([
|
|
23429
|
+
var TEACH_PRONOUNS = Object.freeze([
|
|
23430
|
+
"you're",
|
|
23431
|
+
"i'm",
|
|
23432
|
+
"it's",
|
|
23433
|
+
"they're",
|
|
23434
|
+
"he's",
|
|
23435
|
+
"she's",
|
|
23436
|
+
"we're",
|
|
23437
|
+
"you",
|
|
23438
|
+
"i",
|
|
23439
|
+
"it",
|
|
23440
|
+
"they",
|
|
23441
|
+
"he",
|
|
23442
|
+
"she",
|
|
23443
|
+
"we"
|
|
23444
|
+
]);
|
|
23395
23445
|
var TEACH_PRONOUN_RE = new RegExp(`^(?:every\\s+|each\\s+|all\\s+|some\\s+|a few\\s+|a\\s+|an\\s+)?(${TEACH_PRONOUNS.join("|")})\\s+\\S+`, "i");
|
|
23446
|
+
var BOARD_TEACH_LOCATIVE_RE = new RegExp(`^([\\w-]+)\\s+([a-z]+)s\\s+(${PREP_SRC})\\s+([\\w-]+)$`, "i");
|
|
23447
|
+
var RETRACT_FORGET_LOCATIVE_RE = new RegExp(`^forget\\s+(?:that\\s+)?([\\w-]+)\\s+([a-z]+)s\\s+(${PREP_SRC})\\s+([\\w-]+)$`, "i");
|
|
23396
23448
|
var TRAILING_ADVERB_RE = "(?:\\s+(?:exactly|really|actually|anyway))?";
|
|
23397
23449
|
var MODULE_ORIENT_RE = new RegExp(`^what\\s+does\\s+(.+?)\\s+do${TRAILING_ADVERB_RE}\\??$`, "i");
|
|
23398
23450
|
var MODULE_ORIENT_SVO_RE = new RegExp(`^what\\s+(.+?)\\s+does${TRAILING_ADVERB_RE}\\??$`, "i");
|
|
@@ -23520,6 +23572,7 @@ ${JSON.stringify(envelope, null, 2)}`;
|
|
|
23520
23572
|
if (f.provenance.includes("ace:chat") || f.provenance.includes("teach:chat")) return `you told me: ${factPhrase(f)}${cite}`;
|
|
23521
23573
|
if (f.provenance.includes("corpus-weak:")) return `possibly: ${factPhrase(f)}${cite}`;
|
|
23522
23574
|
if (f.provenance.includes("corpus:")) return `${factPhrase(f)}${cite}`;
|
|
23575
|
+
if (f.provenance.includes("reference:")) return `${factPhrase(f)}${cite}`;
|
|
23523
23576
|
return `i learned: ${factPhrase(f)}${cite}`;
|
|
23524
23577
|
}
|
|
23525
23578
|
function indefiniteArticleFor(term) {
|
|
@@ -24125,12 +24178,19 @@ ${shown.map(renderFactLine).join("\n")}`,
|
|
|
24125
24178
|
);
|
|
24126
24179
|
const hit2 = hasHit(subj);
|
|
24127
24180
|
if (hit2) return { text: `yes \u2014 ${renderFactLine(hit2)}`, replace: true };
|
|
24128
|
-
|
|
24129
|
-
|
|
24130
|
-
|
|
24181
|
+
let liftFrontier = subj;
|
|
24182
|
+
const liftChain = [];
|
|
24183
|
+
const liftSeen = /* @__PURE__ */ new Set();
|
|
24184
|
+
for (let hop = 0; hop < 4; hop += 1) {
|
|
24185
|
+
const step = facts.find((f) => ISA_PREDICATES2.has(f.predicate) && liftFrontier.has(f.subject) && !liftSeen.has(f.object));
|
|
24186
|
+
if (!step) break;
|
|
24187
|
+
liftSeen.add(step.object);
|
|
24188
|
+
liftChain.push(step);
|
|
24189
|
+
const lifted = hasHit(factTermVariants(normFactTerm2, step.object));
|
|
24131
24190
|
if (lifted) {
|
|
24132
|
-
return { text: `yes \u2014 ${renderFactLine
|
|
24191
|
+
return { text: `yes \u2014 ${[...liftChain.map(renderFactLine), renderFactLine(lifted)].join("; ")}`, replace: true };
|
|
24133
24192
|
}
|
|
24193
|
+
liftFrontier = factTermVariants(normFactTerm2, step.object);
|
|
24134
24194
|
}
|
|
24135
24195
|
return null;
|
|
24136
24196
|
}
|
|
@@ -24390,6 +24450,14 @@ ${shown.join("\n")}${extra}`, replace: true, ...rest.length ? { pending: { items
|
|
|
24390
24450
|
const directObjVariants = factTermVariants(normFactTerm2, stripTrailingDiscourseTag(directIsaAsk[2]));
|
|
24391
24451
|
const directSup = inheritsChain(graph, ent.id).find((sup) => [...factTermVariants(normFactTerm2, sup.label)].some((v) => directObjVariants.has(v)));
|
|
24392
24452
|
if (directSup) return { text: `yes \u2014 the code graph says ${ent.label} inherits ${directSup.label}.`, replace: true };
|
|
24453
|
+
const objEnt = await resolveEntity(graph, stripTrailingDiscourseTag(directIsaAsk[2]));
|
|
24454
|
+
if (objEnt && inheritsChain(graph, objEnt.id).some((sup) => sup.id === ent.id)) {
|
|
24455
|
+
return {
|
|
24456
|
+
text: `I can't confirm that \u2014 the code graph's stored direction runs the other way: ${objEnt.label} inherits ${ent.label}. An inheritance doesn't reverse.`,
|
|
24457
|
+
replace: true,
|
|
24458
|
+
miss: true
|
|
24459
|
+
};
|
|
24460
|
+
}
|
|
24393
24461
|
}
|
|
24394
24462
|
}
|
|
24395
24463
|
}
|