@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 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.11.6",
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
@@ -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) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[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 mine = LEDGER.rows.filter((r) => focus && touches(r, focus));
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
- const count = mine.filter((r) => passes(r, group) &&
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));