@polycode-projects/the-mechanical-code-talker 1.10.14 → 1.11.5

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/bin/tmct.mjs CHANGED
@@ -38,6 +38,11 @@ Usage:
38
38
  see src/graph-merge.mjs); wins over --repo/TMCT_GRAPH_FILE/tmct.toml
39
39
  [--config <path>] an alternate tmct.toml location (a file or a directory)
40
40
  [--ephemeral] read the graph but write nothing back (demo/read-only)
41
+ [--prompt "<text>"] one-shot: run the prompt's sentences as turns and print
42
+ the final answer (teach state first, trigger last)
43
+ [--render blocks] with --prompt: when the final turn produced a plan,
44
+ write it as a self-contained animated page
45
+ [--output <path>] the rendered page's path (default plan.html)
41
46
  [--narrate] start with narrate mode on — a verbose, developer-facing
42
47
  trace of decision points/matched pattern/results/goal per
43
48
  turn, appended under a "--- narrate ---" marker (also
@@ -77,6 +82,9 @@ Usage:
77
82
  [--ontology <name|path>] DIFFERENT operation from the others: it APPENDS to
78
83
  [--lexicon <name|path>] tmct.toml's graph_files array (multi-graph growth),
79
84
  [--graph <path>] never an extensions-bundle activation.
85
+ [--file <definition.txt>] teach a plain-text definition file sentence by
86
+ sentence (# lines are comments); any declined
87
+ sentence exits non-zero with the sentence named
80
88
  [--memory-backend <default|memory|sqlite>] same knob as \`tmct init\`
81
89
  [--config <path>]
82
90
  tmct extend --validate <dir> validate a third-party extension pack's declared
@@ -85,20 +93,14 @@ Usage:
85
93
  tmct syllogise [--repo <abs>] speculative inference (offline maintenance job): forward-
86
94
  [--depth <n>] [--budget <n>] chain the memory's rdfs:subClassOf closure, materialising
87
95
  [--config <path>] bounded, low-trust, retractable entailed facts (never on the chat path)
88
- tmct viz [--repo <abs>] write one self-contained, navigable HTML file rendering the
89
- [--focus <id>] memory graph: pan/zoom, click a node for its label/class/
90
- [--term <word>] timestamps. Seeds from the most recently created individual
91
- [--depth <n>] by default (--focus <id> or --term <word> override it);
92
- [--limit <n>] --output defaults to graph.html in the cwd.
93
- [--hub-degree <n>] --depth = max arcs (hops) from the focus node (default 3);
94
- [--edge-kind <mode>] --limit = spiral length, total nodes walked (default 300);
95
- [--output <path>] --hub-degree = stop expanding THROUGH a node above N
96
- [--config <path>] connections, still shows it (default 40); --edge-kind =
97
- meta|relation|both (default both) — which edge kinds the
98
- walk follows (provenance-only, concept-relations-only, or
99
- both — see the page's own edge-kind toggle to change this
100
- live); --term <word> resolves to the Fact(s) whose subject/
101
- object normalizes to that word and seeds from there.
96
+ tmct viz [--repo <abs>] write one self-contained HTML page: the memory graph as a
97
+ [--focus <term>] readable ledger of fact-sentences around one focus term,
98
+ [--term <word>] with segments, a two-hop minimap, and an in-page chat dock
99
+ [--limit <n>] that answers from the embedded graph. Focuses on the newest
100
+ [--output <path>] taught fact's subject by default (--focus <term> or
101
+ [--config <path>] --term <word> override it); --output defaults to
102
+ ledger.html in the cwd; --limit caps the embedded fact
103
+ rows; --term resolves via the same normalization chat uses.
102
104
  tmct serve [--repo <abs>] run the Anthropic Messages API-compatible endpoint
103
105
  [--host <h>] [--port <n>] (POST /v1/messages) over the graph — a deterministic,
104
106
  [--graph <path>] no-LLM "model" a tool-loop client can call; $0 usage.
@@ -596,6 +598,65 @@ async function main() {
596
598
  if (graphPaths.length) extra.graphPaths = graphPaths;
597
599
  if (configPath) extra.configPath = configPath;
598
600
  if (memoryBackend) extra.memoryBackend = memoryBackend;
601
+ // `--prompt "<text>"` — one-shot mode: each sentence of the prompt runs as
602
+ // its own turn (state teaches first, the goal/solve trigger last), the
603
+ // FINAL turn's answer prints to stdout, and the process exits. Piped stdin
604
+ // stays the interactive fallback.
605
+ const prompt = strFlag(rest, ["--prompt"]);
606
+ // `--render <archetype> [--output <path>]` — after a one-shot --prompt whose
607
+ // final turn produced a plan, write the self-contained animated plan page
608
+ // (src/plan-viz.mjs). Requires --prompt: interactive chat has no single
609
+ // final turn to render.
610
+ let renderArchetype;
611
+ try {
612
+ renderArchetype = enumFlag(rest, ["--render"], ["blocks"]);
613
+ } catch (e) {
614
+ process.stderr.write(`tmct: ${e?.message || e}\n`);
615
+ process.exit(2);
616
+ }
617
+ if (renderArchetype && !prompt) {
618
+ process.stderr.write("tmct: --render needs --prompt (a one-shot turn whose plan it renders)\n");
619
+ process.exit(1);
620
+ }
621
+ if (prompt) {
622
+ const { createSession } = await import("../src/chat.mjs");
623
+ const { splitSentences } = await import("../src/sentences.mjs");
624
+ const session = await createSession({ repoPath, ephemeral, narrate, ...extra });
625
+ let finalAnswer = "";
626
+ let finalPlan = null;
627
+ let parts;
628
+ try { parts = splitSentences(prompt); } catch { parts = null; }
629
+ const sentences = parts && parts.length ? parts : [prompt];
630
+ for (const s of sentences) {
631
+ const t = await session.turn(s);
632
+ finalAnswer = t?.answer ?? "";
633
+ finalPlan = t?.plan ?? null;
634
+ }
635
+ await session.close();
636
+ process.stdout.write(finalAnswer + "\n");
637
+ if (renderArchetype) {
638
+ if (!finalPlan) {
639
+ process.stderr.write("the final turn produced no plan — nothing to render\n");
640
+ process.exitCode = 1;
641
+ return;
642
+ }
643
+ const { renderPlanHtml } = await import("../src/plan-viz.mjs");
644
+ const { writeFile } = await import("node:fs/promises");
645
+ const { resolve: resolvePath } = await import("node:path");
646
+ const outPath = resolvePath(process.cwd(), strFlag(rest, ["--output", "--out"], "plan.html"));
647
+ const rendersAs = finalPlan.domain?.renderHints ?? {};
648
+ const sizeOrder = (finalPlan.domain?.ordering ?? [])
649
+ .filter((row) => /-than$/.test(String(row.predicate || "")))
650
+ .map((row) => [row.subject, row.object]);
651
+ const html = renderPlanHtml({
652
+ plan: finalPlan, rendersAs, sizeOrder,
653
+ title: finalPlan.goal?.text || "tmct plan",
654
+ });
655
+ await writeFile(outPath, html, "utf8");
656
+ process.stdout.write(`wrote ${outPath} (${finalPlan.actions.length} moves, ${finalPlan.states.length} snapshots)\n`);
657
+ }
658
+ return;
659
+ }
599
660
  // The shell gate: a real terminal gets the full-screen Ink TUI; `--plain` or a
600
661
  // non-TTY stream (pipes, scripts, the test suite) gets the readline shell. Both
601
662
  // drive the same createSession sink — only the drawing differs.
@@ -867,8 +928,9 @@ async function main() {
867
928
  process.exit(2);
868
929
  }
869
930
 
870
- if (!corpusVal && !ontologyVal && !lexiconVal && !graphFlags.length && !memoryBackendVal) {
871
- process.stderr.write("tmct import: needs at least one of --corpus/--ontology/--lexicon/--graph/--memory-backend\n");
931
+ const fileVal = strFlag(rest, ["--file"]);
932
+ if (!corpusVal && !ontologyVal && !lexiconVal && !graphFlags.length && !memoryBackendVal && !fileVal) {
933
+ process.stderr.write("tmct import: needs at least one of --corpus/--ontology/--lexicon/--graph/--memory-backend/--file\n");
872
934
  process.exit(2);
873
935
  }
874
936
 
@@ -914,6 +976,22 @@ async function main() {
914
976
  process.exit(1);
915
977
  }
916
978
  }
979
+
980
+ // `--file <definition.txt>` — teach a plain-text definition file, one
981
+ // sentence per turn, through the same recognizers the live chat uses.
982
+ // Any declined sentence exits non-zero: a half-taught game plans wrongly
983
+ // or not at all with no visible cause otherwise.
984
+ if (fileVal) {
985
+ const { importDefinitionFile } = await import("../src/import-file.mjs");
986
+ try {
987
+ const result = await importDefinitionFile(repoRoot, fileVal);
988
+ process.stdout.write(result.report + "\n");
989
+ if (result.declined.length > 0) process.exit(1);
990
+ } catch (e) {
991
+ process.stderr.write(`tmct import: ${e?.message || e}\n`);
992
+ process.exit(1);
993
+ }
994
+ }
917
995
  return;
918
996
  }
919
997
 
@@ -1006,53 +1084,50 @@ async function main() {
1006
1084
  }
1007
1085
 
1008
1086
  if (mode === "viz") {
1009
- // `tmct viz` — one self-contained, navigable HTML file rendering the
1010
- // memory graph (PLAN_BREADTH_FIRST_NLU.md §5, PLAN_VIZ.md's design):
1011
- // pan/zoom, click-a-node, a concentric ring layout keyed on hop with a
1012
- // depth/age falloff. Same repo resolution as `memory`/`syllogise`
1013
- // resolveRuntimeConfig: --repo > git root > cwd.
1087
+ // `tmct viz` — the ledger explorer: one self-contained HTML page rendering
1088
+ // the memory graph as readable fact-sentences around a focus term, with
1089
+ // the in-browser chat dock (PLAN_VIZ_LEDGER.md). Same repo resolution as
1090
+ // `memory`/`syllogise` resolveRuntimeConfig: --repo > git root > cwd.
1091
+ // `--ledger` is accepted as a no-op: the ledger IS the viz surface now.
1014
1092
  const rest = process.argv.slice(3);
1093
+ const retiredFlags = ["--depth", "--hub-degree", "--edge-kind"].filter((f) => rest.includes(f));
1094
+ if (retiredFlags.length) {
1095
+ process.stderr.write(
1096
+ `tmct viz: ${retiredFlags.join(", ")} belonged to the retired node-link graph page and no longer exist${retiredFlags.length === 1 ? "s" : ""}.\n`
1097
+ + "The ledger view takes: --repo <abs>, --focus <term>, --term <word>, --limit <n>, --output <path>.\n",
1098
+ );
1099
+ process.exitCode = 1;
1100
+ return;
1101
+ }
1015
1102
  const { strFlag, resolveRuntimeConfig } = await import("../src/cli-args.mjs");
1016
- const { computeVizGraph, renderVizHtml, readAskBundle, readMemoryAskBundle } = await import("../src/viz.mjs");
1103
+ const { computeLedgerData, renderLedgerHtml, readMemoryAskBundle } = await import("../src/ledger-viz.mjs");
1017
1104
  const { writeFile } = await import("node:fs/promises");
1018
1105
  const { resolve } = await import("node:path");
1019
- const numFlag = (name) => {
1020
- const j = rest.indexOf(name);
1021
- const v = j !== -1 ? Number(rest[j + 1]) : NaN;
1022
- return Number.isFinite(v) ? v : undefined;
1023
- };
1106
+ const limitIdx = rest.indexOf("--limit");
1107
+ const limitRaw = limitIdx !== -1 ? Number(rest[limitIdx + 1]) : NaN;
1108
+ const rowLimit = Number.isFinite(limitRaw) ? limitRaw : undefined;
1024
1109
  const focus = strFlag(rest, ["--focus"]);
1025
- const term = strFlag(rest, ["--term"]); // PLAN_VIZ_MEMORY.md: seed via normFactTerm-matched Fact(s), alongside --focus
1026
- const depth = numFlag("--depth"); // max arcs (hops) from the focus node
1027
- const nodeLimit = numFlag("--limit"); // spiral length: total nodes walked
1028
- const hubDegree = numFlag("--hub-degree"); // stop expanding THROUGH a node above N connections
1029
- const edgeKindModeRaw = strFlag(rest, ["--edge-kind"]);
1030
- const edgeKindMode = ["meta", "relation", "both"].includes(edgeKindModeRaw) ? edgeKindModeRaw : undefined;
1031
- const outPath = resolve(process.cwd(), strFlag(rest, ["--output", "--out"], "graph.html"));
1110
+ const term = strFlag(rest, ["--term"]); // seeds via normFactTerm; --focus wins when both are given
1111
+ const outPath = resolve(process.cwd(), strFlag(rest, ["--output", "--out"], "ledger.html"));
1032
1112
  const { repo } = await resolveRuntimeConfig({ argv: rest });
1033
- const vizGraph = await computeVizGraph(repo, {
1113
+ const data = await computeLedgerData(repo, {
1034
1114
  ...(focus ? { focus } : {}),
1035
- ...(!focus && term ? { term } : {}), // --focus takes precedence when both are given
1036
- ...(depth != null ? { depth } : {}),
1037
- ...(nodeLimit != null ? { nodeLimit } : {}),
1038
- ...(hubDegree != null ? { hubDegree } : {}),
1039
- ...(edgeKindMode ? { edgeKindMode } : {}),
1115
+ ...(!focus && term ? { term } : {}),
1116
+ ...(rowLimit != null ? { rowLimit } : {}),
1040
1117
  });
1041
- // The embedded "Ask the graph" chat panels TWO real engines, bundled for
1042
- // the browser (scripts/build-ask-bundle.mjs's checked-in output): the
1043
- // code-graph ask.mjs engine, and (PLAN_VIZ_MEMORY.md Bug 1 fix) the
1044
- // memory-graph factAnswer engine. Neither read*AskBundle() ever throws; an
1045
- // empty string degrades that ONE engine gracefully rather than breaking
1046
- // the page (e.g. a fresh checkout before the bundles' first build).
1047
- const [askBundle, memoryAskBundle] = await Promise.all([readAskBundle(), readMemoryAskBundle()]);
1048
- const html = renderVizHtml({ ...vizGraph, askBundle, memoryAskBundle });
1049
- await writeFile(outPath, html, "utf8");
1050
- const chatNote = askBundle || memoryAskBundle
1051
- ? ` (with the embedded ask-the-graph chat panel${askBundle && memoryAskBundle ? "s" : ""})`
1052
- : " (no chat panel — run `npm run build:ask-bundle` first)";
1118
+ // readMemoryAskBundle never throws; an empty string renders the page with
1119
+ // an honest "chat unavailable" note instead of the dock (e.g. a fresh
1120
+ // checkout before the bundle's first build).
1121
+ const memoryAskBundle = await readMemoryAskBundle();
1122
+ await writeFile(outPath, renderLedgerHtml({ ...data, memoryAskBundle }), "utf8");
1053
1123
  process.stdout.write(
1054
- `tmct viz — wrote ${vizGraph.nodes.length} node(s), ${vizGraph.edges.length} edge(s) to ${outPath}${chatNote}\n`,
1124
+ `tmct viz — wrote ${data.meta.shown} fact row(s) around ${data.focus ? `'${data.focus}'` : "no focus"} to ${outPath}\n`,
1055
1125
  );
1126
+ if (data.meta.truncated) {
1127
+ process.stdout.write(
1128
+ `showing ${data.meta.shown} of ${data.meta.total} rows — narrow with --focus <term> or raise --limit\n`,
1129
+ );
1130
+ }
1056
1131
  return;
1057
1132
  }
1058
1133
 
@@ -1158,7 +1233,7 @@ async function main() {
1158
1233
  process.stderr.write("tmct plan: needs a request, e.g. `tmct plan \"of the modules impacted by X, which are untested\"`\n");
1159
1234
  process.exit(2);
1160
1235
  }
1161
- const { config } = await resolveRuntimeConfig({ argv: rest });
1236
+ const { repo, config } = await resolveRuntimeConfig({ argv: rest });
1162
1237
  const { buildCapabilityPlanCtx, runCapabilityPlan, declaredCapabilityNames } = await import("../src/router/drive.mjs");
1163
1238
  const declared = declaredCapabilityNames();
1164
1239
  let tools = declared;
@@ -1172,34 +1247,49 @@ async function main() {
1172
1247
  }
1173
1248
  let ctx;
1174
1249
  try {
1175
- ctx = await buildCapabilityPlanCtx({ config });
1250
+ ctx = await buildCapabilityPlanCtx({ config, memoryDir: repo });
1176
1251
  } catch (e) {
1177
1252
  process.stderr.write(`tmct plan: could not load the graph — ${e?.message || e}\n`);
1178
1253
  process.exit(1);
1179
1254
  }
1180
- const result = await runCapabilityPlan(request, tools, ctx);
1181
- if (jsonFlag) {
1182
- process.stdout.write(JSON.stringify({ request, ...result }, null, 2) + "\n");
1183
- process.exit(result.refused ? 1 : 0);
1184
- }
1185
- process.stdout.write(`tmct plan: "${request}"\n`);
1186
- if (result.refused) {
1187
- process.stdout.write(`no plan found — ${Array.isArray(result.why) ? result.why.join("; ") : result.why}\n`);
1188
- if (result.c1Why) {
1189
- process.stdout.write(`(the direct router also declined: ${Array.isArray(result.c1Why) ? result.c1Why.join("; ") : result.c1Why})\n`);
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
+ try {
1260
+ const result = await runCapabilityPlan(request, tools, ctx);
1261
+ if (jsonFlag) {
1262
+ process.stdout.write(JSON.stringify({ request, ...result }, null, 2) + "\n");
1263
+ process.exit(result.refused ? 1 : 0);
1190
1264
  }
1191
- process.exit(1);
1192
- }
1193
- process.stdout.write(`driver: ${result.driver}\n\nsteps:\n`);
1194
- for (let i = 0; i < result.calls.length; i += 1) {
1195
- const c = result.calls[i];
1196
- let text = "";
1197
- try { text = await ctx.dispatch(c.name, c.input || {}).then((r) => (r.ok ? r.text : `(unresolved: ${r.error})`)); }
1198
- catch (e) { text = `(error: ${e?.message || e})`; }
1199
- process.stdout.write(` ${i + 1}. ${c.name} ${JSON.stringify(c.input || {})}\n ${String(text).split("\n").join("\n ")}\n`);
1200
- }
1201
- if (result.composed !== undefined && result.composed !== null) {
1202
- process.stdout.write(`\ncomposed answer (${result.composed.length}): ${result.composed.length ? result.composed.join(", ") : "(empty set)"}\n`);
1265
+ process.stdout.write(`tmct plan: "${request}"\n`);
1266
+ if (result.refused) {
1267
+ process.stdout.write(`no plan found — ${Array.isArray(result.why) ? result.why.join("; ") : result.why}\n`);
1268
+ if (result.c1Why) {
1269
+ process.stdout.write(`(the direct router also declined: ${Array.isArray(result.c1Why) ? result.c1Why.join("; ") : result.c1Why})\n`);
1270
+ }
1271
+ process.exit(1);
1272
+ }
1273
+ process.stdout.write(`driver: ${result.driver}\n\nsteps:\n`);
1274
+ for (let i = 0; i < result.calls.length; i += 1) {
1275
+ const c = result.calls[i];
1276
+ let text = "";
1277
+ // taught: records are simulated, never dispatchable — dispatching one
1278
+ // would print a misleading "unknown tool" under an honest plan step.
1279
+ if (c.name.startsWith("taught:")) text = "(simulated over the taught rules — execute it in chat with \"next\")";
1280
+ else {
1281
+ try { text = await ctx.dispatch(c.name, c.input || {}).then((r) => (r.ok ? r.text : `(unresolved: ${r.error})`)); }
1282
+ catch (e) { text = `(error: ${e?.message || e})`; }
1283
+ }
1284
+ process.stdout.write(` ${i + 1}. ${c.name} ${JSON.stringify(c.input || {})}\n ${String(text).split("\n").join("\n ")}\n`);
1285
+ }
1286
+ if (result.composed !== undefined && result.composed !== null) {
1287
+ process.stdout.write(`\ncomposed answer (${result.composed.length}): ${result.composed.length ? result.composed.join(", ") : "(empty set)"}\n`);
1288
+ } else if (result.observed) {
1289
+ process.stdout.write(`\n${result.observed}\n`);
1290
+ }
1291
+ } finally {
1292
+ for (const dispose of ctx.disposers || []) dispose();
1203
1293
  }
1204
1294
  return;
1205
1295
  }
@@ -0,0 +1,24 @@
1
+ # crates — a second taught game, same closed frames as hanoi-3.
2
+ # Lines starting with # are skipped by import.
3
+ # No size order here: stacking is legal onto any clear crate or pallet, so this
4
+ # domain exercises the comparator precondition being optional. Goals can be a
5
+ # conjunction — teach several goal sentences before "solve it".
6
+ #
7
+ # After `tmct import --file crates.txt`, try this in `tmct chat` (or via --prompt):
8
+ #
9
+ # crate-c rests on crate-a. crate-a rests on pallet-1. crate-b rests on pallet-2.
10
+ # the goal is that crate-a rests on crate-b. the goal is that crate-b rests on pallet-2.
11
+ # solve it.
12
+ #
13
+ # (2 moves: clear crate-a, then stack it — the solver may route crate-c to
14
+ # pallet-3 or onto crate-b; both are optimal.)
15
+ a crate is a kind of container.
16
+ a pallet is a kind of place.
17
+ crate-a is a crate. crate-b is a crate. crate-c is a crate.
18
+ pallet-1 is a pallet. pallet-2 is a pallet. pallet-3 is a pallet.
19
+ you can stack a crate onto a pallet.
20
+ you can stack a crate onto a crate.
21
+ to stack a crate onto a target, nothing may rest on the crate.
22
+ to stack a crate onto a target, nothing may rest on the target.
23
+ stacking a crate onto a target makes the crate rest on the target.
24
+ a crate renders as a block. a pallet renders as a slot.
@@ -0,0 +1,30 @@
1
+ # hanoi-3 — a taught game definition. Lines starting with # are skipped by import.
2
+ # After `tmct import --file hanoi-3.txt`, try this in `tmct chat` (or via --prompt):
3
+ #
4
+ # disk-1 rests on disk-2. disk-2 rests on disk-3. disk-3 rests on peg-a.
5
+ # the goal is that every disk rests on peg-c. solve it.
6
+ #
7
+ # Variations, each stretching a different direction:
8
+ # scale — first teach: "disk-4 is a disk. disk-3 is smaller than disk-4."
9
+ # start all four on peg-a, same goal. 15 moves (2^4 - 1).
10
+ # any start — disk-1 rests on peg-b. disk-2 rests on peg-c. disk-3 rests on peg-a.
11
+ # the goal is that every disk rests on peg-c. solve it.
12
+ # other goal — same start, "the goal is that every disk rests on peg-b. solve it."
13
+ # partial — "the goal is that disk-3 rests on peg-c. solve it."
14
+ # (a one-fact goal; exercises the non-universal goal frame)
15
+ # legality — "what moves are legal now?" (findReachableSet, one ply, no plan)
16
+ # read-back — "what rests on disk-2?" / "is disk-1 clear?" (plain fact questions,
17
+ # no planning involved)
18
+ a disk is a kind of game piece.
19
+ a peg is a kind of place.
20
+ disk-1 is a disk. disk-2 is a disk. disk-3 is a disk.
21
+ peg-a is a peg. peg-b is a peg. peg-c is a peg.
22
+ disk-1 is smaller than disk-2. disk-1 is smaller than disk-3.
23
+ disk-2 is smaller than disk-3.
24
+ you can move a disk onto a peg.
25
+ you can move a disk onto a disk.
26
+ to move a disk onto a target, nothing may rest on the disk.
27
+ to move a disk onto a target, nothing may rest on the target.
28
+ to move a disk onto a disk, the disk must be smaller than the target.
29
+ moving a disk onto a target makes the disk rest on the target.
30
+ a disk renders as a block. a peg renders as a slot.
@@ -0,0 +1,33 @@
1
+ # river — the wolf/goat/cabbage crossing, taught with the same closed frames
2
+ # as hanoi-3 plus two new families: a co-travel effect (the farmer rides every
3
+ # crossing) and the "may not be with ... without ..." constraint.
4
+ # Lines starting with # are skipped by import.
5
+ #
6
+ # After `tmct import --file river.txt`, try this in `tmct chat` (or via --prompt):
7
+ #
8
+ # wolf-1 stands on bank-east. goat-1 stands on bank-east.
9
+ # cabbage-1 stands on bank-east. farmer-1 stands on bank-east.
10
+ # the goal is that every passenger stands on bank-west. solve it.
11
+ #
12
+ # The classic optimum is 7 crossings: goat over, farmer back, wolf over, goat
13
+ # back, cabbage over, farmer back, goat over. From the opening position exactly
14
+ # 1 move is legal (ferry the goat) — every other crossing leaves the wolf with
15
+ # the goat, or the goat with the cabbage, without the farmer.
16
+ #
17
+ # Other things to try:
18
+ # legality — "what moves are legal now?" (one ply, no plan; shows the
19
+ # constraint pruning: 1 legal move at the start)
20
+ # read-back — "what stands on bank-east?" (plain fact question, no planning)
21
+ a passenger is a kind of game piece.
22
+ a bank is a kind of place.
23
+ wolf-1 is a wolf. wolf-1 is a passenger.
24
+ goat-1 is a goat. goat-1 is a passenger.
25
+ cabbage-1 is a cabbage. cabbage-1 is a passenger.
26
+ farmer-1 is a farmer.
27
+ bank-east is a bank. bank-west is a bank.
28
+ you can ferry a passenger onto a bank.
29
+ you can ferry a farmer onto a bank.
30
+ ferrying a passenger onto a bank makes the passenger stand on the target.
31
+ ferrying a passenger onto a bank makes the farmer stand on the target.
32
+ to ferry a passenger onto a bank, the wolf may not be with the goat without the farmer.
33
+ to ferry a passenger onto a bank, the goat may not be with the cabbage without the farmer.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "1.10.14",
3
+ "version": "1.11.5",
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.",