@polycode-projects/the-mechanical-code-talker 1.11.6 → 1.12.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/ROADMAP.md +2 -2
- package/bin/tmct.mjs +11 -11
- package/package.json +1 -1
- package/src/chat.mjs +33 -0
- package/src/ledger-viz.mjs +35 -4
package/ROADMAP.md
CHANGED
|
@@ -51,12 +51,12 @@ getting silently traded away by inherited caution:
|
|
|
51
51
|
and an NPC turn scheduler. Design-only.
|
|
52
52
|
- **`PLAN_SYLLOGIST.md`** — retraction-aware consistency checking under a hard budget and trust
|
|
53
53
|
tiers, the one open piece of the reasoning engine's research horizon. Design-only.
|
|
54
|
-
- **`PLAN_HANOI.md`** — shipped in full, including the follow-ups: river-crossing (co-travel
|
|
54
|
+
- **`archive/PLAN_HANOI.md`** — shipped in full, including the follow-ups: river-crossing (co-travel
|
|
55
55
|
effects, the forbidden-together constraint frame, 7-crossing oracle) and planner-side
|
|
56
56
|
consumption of `taught:` capability records (`/plan` in chat and bin). See its dated addenda.
|
|
57
57
|
- **`PLAN_GUESS_NUMBER.md`** — the closed-loop (observe-and-replan) planning domain for the same
|
|
58
58
|
kernels. Design-only.
|
|
59
|
-
- **`PLAN_VIZ_LEDGER.md`** — shipped in full, including the follow-ups, all resolved by
|
|
59
|
+
- **`archive/PLAN_VIZ_LEDGER.md`** — shipped in full, including the follow-ups, all resolved by
|
|
60
60
|
operator decision 2026-07-15: the ledger IS the `tmct viz` surface (node-link page removed),
|
|
61
61
|
`factAnswer`/`factReadBack` carry the additive `goal` field the dock renders, multi-valued
|
|
62
62
|
has/can facts are exempt from `findContradictions`, and page weight is budgeted (~561 KB
|
package/bin/tmct.mjs
CHANGED
|
@@ -1235,6 +1235,17 @@ async function main() {
|
|
|
1235
1235
|
}
|
|
1236
1236
|
const { repo, config } = await resolveRuntimeConfig({ argv: rest });
|
|
1237
1237
|
const { buildCapabilityPlanCtx, runCapabilityPlan, declaredCapabilityNames } = await import("../src/router/drive.mjs");
|
|
1238
|
+
let ctx;
|
|
1239
|
+
try {
|
|
1240
|
+
ctx = await buildCapabilityPlanCtx({ config, memoryDir: repo });
|
|
1241
|
+
} catch (e) {
|
|
1242
|
+
process.stderr.write(`tmct plan: could not load the graph — ${e?.message || e}\n`);
|
|
1243
|
+
process.exit(1);
|
|
1244
|
+
}
|
|
1245
|
+
// The toolset is read AFTER the ctx build: the memory store's taught:
|
|
1246
|
+
// capability records only register there, so both the default toolset and
|
|
1247
|
+
// an explicit --tools list (e.g. --tools "taught:move onto") see them —
|
|
1248
|
+
// and a world goal refuses when its taught record is outside the toolset.
|
|
1238
1249
|
const declared = declaredCapabilityNames();
|
|
1239
1250
|
let tools = declared;
|
|
1240
1251
|
if (toolsFlag) {
|
|
@@ -1245,17 +1256,6 @@ async function main() {
|
|
|
1245
1256
|
process.exit(2);
|
|
1246
1257
|
}
|
|
1247
1258
|
}
|
|
1248
|
-
let ctx;
|
|
1249
|
-
try {
|
|
1250
|
-
ctx = await buildCapabilityPlanCtx({ config, memoryDir: repo });
|
|
1251
|
-
} catch (e) {
|
|
1252
|
-
process.stderr.write(`tmct plan: could not load the graph — ${e?.message || e}\n`);
|
|
1253
|
-
process.exit(1);
|
|
1254
|
-
}
|
|
1255
|
-
// The default (no --tools) toolset is re-read AFTER the ctx build: the
|
|
1256
|
-
// memory store's taught: capability records only register there, and a
|
|
1257
|
-
// world goal refuses when its taught record is outside the toolset.
|
|
1258
|
-
if (!toolsFlag) tools = declaredCapabilityNames();
|
|
1259
1259
|
try {
|
|
1260
1260
|
const result = await runCapabilityPlan(request, tools, ctx);
|
|
1261
1261
|
if (jsonFlag) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.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.",
|
package/src/chat.mjs
CHANGED
|
@@ -4252,6 +4252,7 @@ export async function helpText() {
|
|
|
4252
4252
|
["/memory [verbose]", "what tmct remembers: facts, utterances, sessions, folded blocks"],
|
|
4253
4253
|
["/focus <symbol>", "set the current focus (reused by 'it'/'this' and no-arg entity commands)"],
|
|
4254
4254
|
["/plan <request>", "the capability router: plan+execute a compound or maintenance-goal request (\"of the modules impacted by X, which are untested\", \"what most needs a test\")"],
|
|
4255
|
+
["/capabilities", "what /plan can plan over: the built-in graph tools plus your taught actions"],
|
|
4255
4256
|
["/narrate on|off", "verbose developer/debug mode: decision points, matched pattern, results+sources, goal per turn"],
|
|
4256
4257
|
["/help", "this list"],
|
|
4257
4258
|
["/exit", "leave the session (also Ctrl+C / Ctrl+D)"],
|
|
@@ -9263,6 +9264,7 @@ const GOAL_BY_COMMAND = {
|
|
|
9263
9264
|
history: GOAL_BY_KIND.touches,
|
|
9264
9265
|
exports: GOAL_BY_KIND.reexports,
|
|
9265
9266
|
arch: "understand the overall architecture (package/module boundaries)",
|
|
9267
|
+
capabilities: "see what /plan can plan over — built-in query tools and taught actions",
|
|
9266
9268
|
};
|
|
9267
9269
|
|
|
9268
9270
|
/** A slash-command → the mapped tool (or the /help, /focus, /narrate, unknown
|
|
@@ -9326,6 +9328,37 @@ async function runCommand(line, { config, source, graph, focus, memoryDir, trace
|
|
|
9326
9328
|
return mk(`focus set to ${ent.label}.`, { resolvedIds: [ent.id], newFocus: ent });
|
|
9327
9329
|
}
|
|
9328
9330
|
|
|
9331
|
+
// /capabilities — everything a /plan request can plan over: the built-in
|
|
9332
|
+
// read-only graph-query tools, plus the taught action families read straight
|
|
9333
|
+
// from this store (they only live in the registry inside a /plan request,
|
|
9334
|
+
// so listing them means reading the rules, not the registry).
|
|
9335
|
+
if (name === "capabilities") {
|
|
9336
|
+
note(trace, "goal: see what /plan can plan over — built-in query tools and taught actions");
|
|
9337
|
+
const { declaredCapabilityNames } = await import("./router/drive.mjs");
|
|
9338
|
+
const { actionFamilies, capabilityFromActionRules } = await import("./router/taught.mjs");
|
|
9339
|
+
const lines = [`read-only graph tools: ${declaredCapabilityNames().join(", ")}`];
|
|
9340
|
+
let families = new Map();
|
|
9341
|
+
if (memoryDir) {
|
|
9342
|
+
try {
|
|
9343
|
+
const { loadMemory } = await import("./memory/core.mjs");
|
|
9344
|
+
families = actionFamilies(await loadMemory(memoryDir));
|
|
9345
|
+
} catch { /* an unreadable store lists like an empty one */ }
|
|
9346
|
+
}
|
|
9347
|
+
if (!families.size) {
|
|
9348
|
+
lines.push('taught actions: none yet — teach one ("you can move a disk onto a peg.") and /plan can use it.');
|
|
9349
|
+
} else {
|
|
9350
|
+
lines.push("taught actions (planned over, never dispatched):");
|
|
9351
|
+
for (const [familyName, family] of [...families.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
9352
|
+
const cap = capabilityFromActionRules(familyName, family);
|
|
9353
|
+
const sig = cap.parameters
|
|
9354
|
+
.map((p) => `${p.name}: ${p.classes.filter(Boolean).join("|") || "?"}`)
|
|
9355
|
+
.join(", ");
|
|
9356
|
+
lines.push(` taught:${familyName} — ${sig}`);
|
|
9357
|
+
}
|
|
9358
|
+
}
|
|
9359
|
+
return mk(lines.join("\n"));
|
|
9360
|
+
}
|
|
9361
|
+
|
|
9329
9362
|
// /plan <request> — the capability router (src/router/*): plan+execute a
|
|
9330
9363
|
// compound ("of the modules impacted by X, which are untested", "assess X
|
|
9331
9364
|
// and then check Y") or maintenance-goal ("what most needs a test") request
|
package/src/ledger-viz.mjs
CHANGED
|
@@ -225,6 +225,38 @@ export function resolveAnsweredTerm(answerText, questionText, terms, normFn) {
|
|
|
225
225
|
return (terms || []).some((t) => t && t.term === norm) ? norm : null;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/** Per-segment facet counts for the rows touching `focus`: each group's count
|
|
229
|
+
* is computed against the OTHER groups' active filters (its own group's
|
|
230
|
+
* selection is skipped), so a selected family narrows the provenance and
|
|
231
|
+
* recency counts but never its own rail. `sel` holds the active multi-select
|
|
232
|
+
* Sets ({prov, fam, rec}); `now` anchors the recency buckets. Self-contained
|
|
233
|
+
* on purpose: its source is injected into the rendered page verbatim, so it
|
|
234
|
+
* must close over nothing. */
|
|
235
|
+
export function facetCounts(rows, focus, sel, now) {
|
|
236
|
+
const DAY = 86400000;
|
|
237
|
+
const recencyOf = (r) => {
|
|
238
|
+
const t = Date.parse(r.createdAt);
|
|
239
|
+
if (!Number.isFinite(t)) return "older";
|
|
240
|
+
const age = now - t;
|
|
241
|
+
return age < DAY ? "today" : age < 7 * DAY ? "this week" : "older";
|
|
242
|
+
};
|
|
243
|
+
const passes = (r, skip) =>
|
|
244
|
+
(skip === "prov" || !sel.prov.size || sel.prov.has(r.prov)) &&
|
|
245
|
+
(skip === "fam" || !sel.fam.size || sel.fam.has(r.family)) &&
|
|
246
|
+
(skip === "rec" || !sel.rec.size || sel.rec.has(recencyOf(r)));
|
|
247
|
+
const counts = { prov: {}, fam: {}, rec: {} };
|
|
248
|
+
for (const r of rows || []) {
|
|
249
|
+
if (!focus || (r.s !== focus && r.o !== focus)) continue;
|
|
250
|
+
if (passes(r, "prov")) counts.prov[r.prov] = (counts.prov[r.prov] || 0) + 1;
|
|
251
|
+
if (passes(r, "fam")) counts.fam[r.family] = (counts.fam[r.family] || 0) + 1;
|
|
252
|
+
if (passes(r, "rec")) {
|
|
253
|
+
const k = recencyOf(r);
|
|
254
|
+
counts.rec[k] = (counts.rec[k] || 0) + 1;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return counts;
|
|
258
|
+
}
|
|
259
|
+
|
|
228
260
|
/** One complete, self-contained document: the ledger, segment rail,
|
|
229
261
|
* worth-a-look panel, breadcrumb/search, two-hop minimap, and (when the
|
|
230
262
|
* memory-ask bundle is present) the ask-the-graph chat dock, all over the
|
|
@@ -376,6 +408,7 @@ ${hasMemChat ? `<script>\n${bundleStr}\n</script>` : ""}
|
|
|
376
408
|
(function () {
|
|
377
409
|
"use strict";
|
|
378
410
|
const DAY = 86400000;
|
|
411
|
+
const facetCounts = ${facetCounts.toString()};
|
|
379
412
|
const el = (id) => document.getElementById(id);
|
|
380
413
|
const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
381
414
|
const FAMS = ["is-a", "has", "can", "used-for", "rests-on", "role", "other"];
|
|
@@ -418,13 +451,11 @@ ${hasMemChat ? `<script>\n${bundleStr}\n</script>` : ""}
|
|
|
418
451
|
return b;
|
|
419
452
|
}
|
|
420
453
|
function renderSegs() {
|
|
421
|
-
const
|
|
454
|
+
const counts = facetCounts(LEDGER.rows, focus, sel, Date.now());
|
|
422
455
|
const put = (id, group, values, labelOf, clsOf) => {
|
|
423
456
|
const box = el(id); box.innerHTML = "";
|
|
424
457
|
for (const v of values) {
|
|
425
|
-
|
|
426
|
-
(group === "prov" ? r.prov === v : group === "fam" ? r.family === v : recOf(r) === v)).length;
|
|
427
|
-
box.appendChild(segButton(group, v, labelOf(v), count, clsOf ? clsOf(v) : null));
|
|
458
|
+
box.appendChild(segButton(group, v, labelOf(v), counts[group][v] || 0, clsOf ? clsOf(v) : null));
|
|
428
459
|
}
|
|
429
460
|
};
|
|
430
461
|
put("segProv", "prov", PROVS.map((p) => p[0]), (v) => PROVS.find((p) => p[0] === v)[1], (v) => "c-" + provKey(v));
|