@polycode-projects/the-mechanical-code-talker 1.12.0 → 2.0.2

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.
Files changed (148) hide show
  1. package/README.md +244 -48
  2. package/ROADMAP.md +23 -34
  3. package/bin/tmct.mjs +107 -71
  4. package/corpus/LICENSES.json +118 -0
  5. package/corpus/README.md +17 -13
  6. package/corpus/conceptnet/README.md +5 -5
  7. package/corpus/conceptnet/fetch-slice.mjs +1 -1
  8. package/corpus/conceptnet/filter-dump.mjs +1 -1
  9. package/corpus/generated/README.md +9 -10
  10. package/corpus/namenet/README.md +39 -0
  11. package/corpus/seon/README.md +2 -2
  12. package/corpus/tier2/generate.mjs +58 -9
  13. package/corpus/tier2/manifest.json +44 -0
  14. package/corpus/wordnet/README.md +37 -0
  15. package/corpus/wordnet/generate.mjs +1 -1
  16. package/data/templates/constructions/agent-noun-relations.toml +2 -2
  17. package/data/templates/grammar-rules.toml +1 -1
  18. package/package.json +13 -22
  19. package/src/{ask-nlp.mjs → adapters/ask-nlp.mjs} +1 -1
  20. package/src/{config.mjs → adapters/config.mjs} +1 -1
  21. package/src/{corpus → adapters/corpus}/conceptnet-map.toml +4 -4
  22. package/src/{corpus → adapters/corpus}/conceptnet.mjs +3 -3
  23. package/src/adapters/corpus/construction-banks.mjs +43 -0
  24. package/src/{corpus → adapters/corpus}/templates.mjs +1 -1
  25. package/src/{embed.mjs → adapters/embed.mjs} +1 -11
  26. package/src/{graph-build.mjs → adapters/graph-build.mjs} +6 -6
  27. package/src/{memory → adapters/memory}/blocks.mjs +2 -2
  28. package/src/{memory → adapters/memory}/core.mjs +38 -94
  29. package/src/adapters/prose-tokens.mjs +98 -0
  30. package/src/{providers → adapters/providers}/bootstrap.mjs +2 -2
  31. package/src/{providers → adapters/providers}/fixture.mjs +3 -3
  32. package/src/{providers → adapters/providers}/graph-service.mjs +9 -4
  33. package/src/{source-slice.mjs → adapters/source-slice.mjs} +2 -2
  34. package/src/{source.mjs → adapters/source.mjs} +1 -1
  35. package/src/{toml-config.mjs → adapters/toml-config.mjs} +1 -1
  36. package/src/{answer-variants.json → domain/answer-variants.json} +1 -1
  37. package/src/domain/answer-variants.mjs +23 -0
  38. package/src/{ask-vocab.mjs → domain/ask-vocab.mjs} +37 -5
  39. package/src/{ask.mjs → domain/ask.mjs} +216 -52
  40. package/src/{codegraph.mjs → domain/codegraph.mjs} +31 -315
  41. package/src/{completions → domain/completions}/complete.mjs +16 -10
  42. package/src/{completions → domain/completions}/graph-adapter.mjs +10 -4
  43. package/src/{completions → domain/completions}/group.mjs +14 -6
  44. package/src/{completions → domain/completions}/infer.mjs +57 -39
  45. package/src/domain/completions/injected.mjs +21 -0
  46. package/src/{completions → domain/completions}/rank.mjs +15 -8
  47. package/src/{completions → domain/completions}/search.mjs +4 -2
  48. package/src/{grammar → domain/grammar}/ace.mjs +3 -3
  49. package/src/{grammar → domain/grammar}/assert.mjs +12 -8
  50. package/src/{grammar → domain/grammar}/lexicon-core.json +1 -1
  51. package/src/{grammar → domain/grammar}/lexicon.mjs +4 -6
  52. package/src/domain/hash.mjs +147 -0
  53. package/src/{interpret → domain/interpret}/fuzzy.mjs +42 -4
  54. package/src/domain/interpret/nlp-registry.mjs +20 -0
  55. package/src/{interpret → domain/interpret}/normalize.mjs +35 -5
  56. package/src/{interpret → domain/interpret}/pipeline.mjs +1 -5
  57. package/src/{interpret → domain/interpret}/strategies/ace.mjs +1 -1
  58. package/src/{interpret → domain/interpret}/strategies/constructions.mjs +30 -52
  59. package/src/{interpret → domain/interpret}/strategies/keywords.mjs +48 -26
  60. package/src/domain/memory/capability.mjs +235 -0
  61. package/src/domain/memory/fold.mjs +54 -0
  62. package/src/domain/memory/session-turns.mjs +7 -0
  63. package/src/{memory → domain/memory}/trust.mjs +48 -0
  64. package/src/{paraphrase.mjs → domain/paraphrase.mjs} +2 -2
  65. package/src/{prose.mjs → domain/prose.mjs} +1 -1
  66. package/src/domain/real-word-collisions.json +1 -0
  67. package/src/{router → domain/router}/call-validator.mjs +1 -1
  68. package/src/{router → domain/router}/drive.mjs +34 -25
  69. package/src/{router → domain/router}/goal-reasoner.mjs +1 -1
  70. package/src/{router → domain/router}/guardrail.mjs +1 -1
  71. package/src/{router → domain/router}/planner.mjs +1 -1
  72. package/src/{router → domain/router}/registry.mjs +5 -5
  73. package/src/{router → domain/router}/resolver.mjs +16 -13
  74. package/src/{router → domain/router}/results.mjs +1 -1
  75. package/src/{router → domain/router}/set-algebra.mjs +1 -1
  76. package/src/{router → domain/router}/taught.mjs +10 -9
  77. package/src/{syllogise.mjs → domain/syllogise.mjs} +21 -4
  78. package/src/domain/vector.mjs +12 -0
  79. package/src/services/chat-session.mjs +451 -0
  80. package/src/{chat.mjs → services/chat.mjs} +1209 -684
  81. package/src/{cli-args.mjs → services/cli-args.mjs} +2 -2
  82. package/src/services/completions.mjs +55 -0
  83. package/src/{extensions.mjs → services/extensions.mjs} +7 -7
  84. package/src/{finish.mjs → services/finish.mjs} +2 -2
  85. package/src/{memory → services}/fold.mjs +0 -0
  86. package/src/{import-file.mjs → services/import-file.mjs} +3 -3
  87. package/src/{index.mjs → services/index.mjs} +21 -12
  88. package/src/{init.mjs → services/init.mjs} +9 -9
  89. package/src/{ledger-viz.mjs → services/ledger-viz.mjs} +3 -3
  90. package/src/{plan-viz.mjs → services/plan-viz.mjs} +98 -28
  91. package/src/{sentences.mjs → services/sentences.mjs} +1 -1
  92. package/src/{sessions.mjs → services/sessions.mjs} +4 -5
  93. package/src/{telemetry.mjs → services/telemetry.mjs} +1 -1
  94. package/src/{server-http.mjs → surfaces/http/server-http.mjs} +11 -65
  95. package/src/{tui → surfaces/tui}/app.mjs +3 -3
  96. package/src/{memory-ask-browser-entry.mjs → surfaces/web/memory-ask-browser-entry.mjs} +5 -5
  97. package/src/{memory-ask-browser.bundle.js → surfaces/web/memory-ask-browser.bundle.js} +9465 -6366
  98. package/src/tools/catalog.mjs +29 -0
  99. package/src/{conformance.mjs → tools/conformance.mjs} +2 -2
  100. package/src/tools/definitions.mjs +288 -0
  101. package/src/tools/graph-load.mjs +20 -0
  102. package/src/tools/handlers/index.mjs +54 -0
  103. package/src/tools/handlers/kit.mjs +33 -0
  104. package/src/tools/handlers/tmct-architecture.mjs +7 -0
  105. package/src/tools/handlers/tmct-ask.mjs +14 -0
  106. package/src/tools/handlers/tmct-callees.mjs +6 -0
  107. package/src/tools/handlers/tmct-callers.mjs +6 -0
  108. package/src/tools/handlers/tmct-calls.mjs +6 -0
  109. package/src/tools/handlers/tmct-class-history.mjs +6 -0
  110. package/src/tools/handlers/tmct-cochanges.mjs +6 -0
  111. package/src/tools/handlers/tmct-context-more.mjs +9 -0
  112. package/src/tools/handlers/tmct-context.mjs +163 -0
  113. package/src/tools/handlers/tmct-describe.mjs +15 -0
  114. package/src/tools/handlers/tmct-exports.mjs +9 -0
  115. package/src/tools/handlers/tmct-file-history.mjs +6 -0
  116. package/src/tools/handlers/tmct-history.mjs +6 -0
  117. package/src/tools/handlers/tmct-impact.mjs +9 -0
  118. package/src/tools/handlers/tmct-members.mjs +16 -0
  119. package/src/tools/handlers/tmct-method-history.mjs +6 -0
  120. package/src/tools/handlers/tmct-search.mjs +22 -0
  121. package/src/tools/handlers/tmct-signature.mjs +6 -0
  122. package/src/tools/handlers/tmct-snippet.mjs +37 -0
  123. package/src/tools/handlers/tmct-subclasses.mjs +16 -0
  124. package/src/tools/handlers/tmct-tests-for.mjs +6 -0
  125. package/src/tools/handlers/tmct-untested.mjs +7 -0
  126. package/src/tools/memory-fallthrough.mjs +65 -0
  127. package/src/{schema-docs.mjs → tools/schema-docs.mjs} +1 -1
  128. package/src/tools/server.mjs +61 -0
  129. package/src/answer-variants.mjs +0 -39
  130. package/src/hash.mjs +0 -24
  131. package/src/server.mjs +0 -501
  132. /package/src/{corpus → adapters/corpus}/unknown-ingest.mjs +0 -0
  133. /package/src/{graph-merge.mjs → adapters/graph-merge.mjs} +0 -0
  134. /package/src/{memory → adapters/memory}/inspect.mjs +0 -0
  135. /package/src/{memory → adapters/memory}/shacl.mjs +0 -0
  136. /package/src/{prose-nlp.mjs → adapters/prose-nlp.mjs} +0 -0
  137. /package/src/{repository-interface.mjs → adapters/repository-interface.mjs} +0 -0
  138. /package/src/{uuid.mjs → adapters/uuid.mjs} +0 -0
  139. /package/src/{wink-model.mjs → adapters/wink-model.mjs} +0 -0
  140. /package/src/{completions → domain/completions}/prune.mjs +0 -0
  141. /package/src/{concept.mjs → domain/concept.mjs} +0 -0
  142. /package/src/{domain.mjs → domain/domain.mjs} +0 -0
  143. /package/src/{interpret → domain/interpret}/merge.mjs +0 -0
  144. /package/src/{interpret → domain/interpret}/strategies/grammar.mjs +0 -0
  145. /package/src/{interpret → domain/interpret}/strategies/noise-strip.mjs +0 -0
  146. /package/src/{memory → domain/memory}/bias.mjs +0 -0
  147. /package/src/{planning.mjs → domain/planning.mjs} +0 -0
  148. /package/src/{viz-theme.mjs → services/viz-theme.mjs} +0 -0
@@ -22,8 +22,8 @@ import { spawnSync } from "node:child_process";
22
22
  import { dirname, resolve } from "node:path";
23
23
  import { stat } from "node:fs/promises";
24
24
  import { join } from "node:path";
25
- import { loadTomlConfig, normalizeConfig, mergeEffective, CONFIG_FILE } from "./toml-config.mjs";
26
- import { DEFAULT_GRAPH_REL } from "./config.mjs";
25
+ import { loadTomlConfig, normalizeConfig, mergeEffective, CONFIG_FILE } from "../adapters/toml-config.mjs";
26
+ import { DEFAULT_GRAPH_REL } from "../adapters/config.mjs";
27
27
 
28
28
  /** The git top-level for `cwd`, or null if not in a repo (or git is
29
29
  * unavailable). A deliberate re-declaration of chat.mjs's gitToplevel (not an
@@ -0,0 +1,55 @@
1
+ // completions.mjs — the composition root for src/domain/completions/'s pipeline. The stages
2
+ // there are pure: they read the memory store, the block store, the graph service and the
3
+ // prose finisher through an explicit `store`/`finisher` handle, and import none of them. This
4
+ // file builds the real handles from the adapters and re-exports both entry points with the
5
+ // signatures package.json's `./generateCompletion` and `./createCompletionsGraphAdapter`
6
+ // subpaths publish, so an external caller never passes a store of its own.
7
+
8
+ import {
9
+ buildNeighbours, degreeOf, OVERLAP_MIN, rankBlocks, retrieveBlocks, tokenizeBlock,
10
+ } from "../adapters/memory/blocks.mjs";
11
+ import { loadMemory, normFactTerm, readFactRows, resolveRelationChase } from "../adapters/memory/core.mjs";
12
+ import { createGraphService } from "../adapters/providers/graph-service.mjs";
13
+ import { finish, grammarRules } from "./finish.mjs";
14
+ import { generateCompletion as generateCompletionOver } from "../domain/completions/complete.mjs";
15
+ import { createCompletionsGraphAdapter as createCompletionsGraphAdapterOver } from "../domain/completions/graph-adapter.mjs";
16
+
17
+ /** Every store handle the six stages read through, in one bag: the memory store's fact
18
+ * readers, the block store's retrieval/clustering/ranking helpers, and the graph service
19
+ * factory graph-adapter.mjs wraps. */
20
+ export const COMPLETIONS_STORE = {
21
+ loadMemory,
22
+ readFactRows,
23
+ normFactTerm,
24
+ resolveRelationChase,
25
+ retrieveBlocks,
26
+ buildNeighbours,
27
+ rankBlocks,
28
+ degreeOf,
29
+ tokenizeBlock,
30
+ OVERLAP_MIN,
31
+ createGraphService,
32
+ };
33
+
34
+ /** finish.mjs reads its grammar rules from TOML on disk, so it stays in services and reaches
35
+ * the pipeline's Stage 6 through this handle. */
36
+ export const COMPLETIONS_FINISHER = { finish, grammarRules };
37
+
38
+ /**
39
+ * The full mechanical-text-generation pipeline over the real store. Same signature and
40
+ * behaviour as src/domain/completions/complete.mjs's generateCompletion(); see its docblock
41
+ * for the options. A caller may still override any handle through `opts.store`/`opts.finisher`.
42
+ */
43
+ export function generateCompletion(dir, prompt, opts = {}) {
44
+ return generateCompletionOver(dir, prompt, {
45
+ store: COMPLETIONS_STORE, finisher: COMPLETIONS_FINISHER, ...opts,
46
+ });
47
+ }
48
+
49
+ /**
50
+ * A graphService-shaped adapter for broadSearch() over the real store. Same signature and
51
+ * behaviour as src/domain/completions/graph-adapter.mjs's createCompletionsGraphAdapter().
52
+ */
53
+ export function createCompletionsGraphAdapter(graph, memory = null) {
54
+ return createCompletionsGraphAdapterOver(graph, memory, { store: COMPLETIONS_STORE });
55
+ }
@@ -12,7 +12,7 @@
12
12
  // A `tmct.toml` `[extensions]` table-of-tables may override a recognized builtin, or
13
13
  // declare a new host entry with its own `kind` (corpus | lexicon | templates | pack |
14
14
  // ontology). A separate flat `[bias]` table (bundle-name → number) feeds
15
- // src/memory/bias.mjs's ranking.
15
+ // src/domain/memory/bias.mjs's ranking.
16
16
  //
17
17
  // Entries are returned in a fixed order: `seon` first, then `conceptnet`, then the rest
18
18
  // sorted by name (so seon's curated facts win idempotency races over ConceptNet noise).
@@ -20,7 +20,7 @@
20
20
  import { isAbsolute, join, resolve, dirname } from "node:path";
21
21
  import { readFile } from "node:fs/promises";
22
22
  import { fileURLToPath } from "node:url";
23
- import { loadTomlConfig } from "./toml-config.mjs";
23
+ import { loadTomlConfig } from "../adapters/toml-config.mjs";
24
24
  import {
25
25
  SEON_CONCEPTS_FILE,
26
26
  SLICE_FILE as CONCEPTNET_SLICE_FILE,
@@ -30,10 +30,10 @@ import {
30
30
  loadSlice,
31
31
  loadMap,
32
32
  toFacts,
33
- } from "./corpus/conceptnet.mjs";
33
+ } from "../adapters/corpus/conceptnet.mjs";
34
34
 
35
35
  // corpus/namenet/generate.mjs's output — a single small top-up bundle.
36
- const NAMENET_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "corpus", "namenet");
36
+ const NAMENET_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "corpus", "namenet");
37
37
 
38
38
  export const EXTENSION_KINDS = Object.freeze(["corpus", "lexicon", "templates", "pack", "ontology"]);
39
39
 
@@ -258,7 +258,7 @@ export async function resolveExtensions(repoRoot, { configFile } = {}) {
258
258
  * recorded as `perBundle[name].error` while every other bundle still seeds normally.
259
259
  * Returns `{ appended, skipped, total, perBundle: { name: {appended,skipped,total,error?} } }`. */
260
260
  export async function seedActiveCorpusEntries(repo, entries) {
261
- const { seedMemory } = await import("./corpus/conceptnet.mjs");
261
+ const { seedMemory } = await import("../adapters/corpus/conceptnet.mjs");
262
262
  const perBundle = {};
263
263
  let appended = 0;
264
264
  let skipped = 0;
@@ -347,7 +347,7 @@ export async function validateExtensionPack(dir, candidate) {
347
347
  if (candidate.lexiconPath) {
348
348
  const path = abs(candidate.lexiconPath);
349
349
  try {
350
- const { loadLexicon } = await import("./grammar/lexicon.mjs");
350
+ const { loadLexicon } = await import("../domain/grammar/lexicon.mjs");
351
351
  const raw = JSON.parse(await readFile(path, "utf8"));
352
352
  const lex = loadLexicon(raw);
353
353
  results.push({
@@ -362,7 +362,7 @@ export async function validateExtensionPack(dir, candidate) {
362
362
  if (candidate.templatesPath) {
363
363
  const path = abs(candidate.templatesPath);
364
364
  try {
365
- const { loadTemplates } = await import("./corpus/templates.mjs");
365
+ const { loadTemplates } = await import("../adapters/corpus/templates.mjs");
366
366
  const templates = await loadTemplates(path);
367
367
  const unnamespaced = [...templates.keys()].filter((id) => !id.includes(":"));
368
368
  if (unnamespaced.length) {
@@ -12,13 +12,13 @@ import { fileURLToPath } from "node:url";
12
12
  import { join, dirname } from "node:path";
13
13
  import { parse as parseToml } from "smol-toml";
14
14
 
15
- import { flatten } from "./corpus/templates.mjs";
15
+ import { flatten } from "../adapters/corpus/templates.mjs";
16
16
 
17
17
  export { flatten };
18
18
 
19
19
  const GRAMMAR_DIR = dirname(fileURLToPath(import.meta.url));
20
20
  /** The data-driven grammar-rule table. */
21
- export const GRAMMAR_RULES_FILE = join(GRAMMAR_DIR, "..", "data", "templates", "grammar-rules.toml");
21
+ export const GRAMMAR_RULES_FILE = join(GRAMMAR_DIR, "..", "..", "data", "templates", "grammar-rules.toml");
22
22
 
23
23
  /** The segment type vocabulary. `prose` is the only unprotected type. */
24
24
  export const SEGMENT_TYPES = Object.freeze([
Binary file
@@ -13,8 +13,8 @@ import { readFile } from "node:fs/promises";
13
13
  import { basename, resolve } from "node:path";
14
14
 
15
15
  import { runTurn, uuidv7 } from "./chat.mjs";
16
- import { loadMemory, readFactRows, appendFact, openMemoryBackend } from "./memory/core.mjs";
17
- import { loadConfig } from "./config.mjs";
16
+ import { loadMemory, readFactRows, appendFact, openMemoryBackend } from "../adapters/memory/core.mjs";
17
+ import { loadConfig } from "../adapters/config.mjs";
18
18
  import { splitSentences } from "./sentences.mjs";
19
19
 
20
20
  /**
@@ -36,7 +36,7 @@ export async function importDefinitionFile(repoRoot, filePath, { env = process.e
36
36
  const body = lines.filter((l) => !l.trim().startsWith("#")).join("\n");
37
37
  const sentences = splitSentences(body).map((s) => s.trim()).filter(Boolean);
38
38
 
39
- const { loadTomlConfig } = await import("./toml-config.mjs");
39
+ const { loadTomlConfig } = await import("../adapters/toml-config.mjs");
40
40
  const raw = await loadTomlConfig(root).catch(() => null);
41
41
  const backend = String(raw?.memory?.backend || "default").trim().toLowerCase();
42
42
  const { dir: memoryDir, close } = await openMemoryBackend(root, backend);
@@ -1,7 +1,7 @@
1
1
  // @polycode-projects/the-mechanical-code-talker (tmct) — library entry point.
2
2
  //
3
3
  // This entry re-exports the adapter primitives a library consumer needs. The
4
- // movable conversational grammar lives in src/interpret/ (normalization
4
+ // movable conversational grammar lives in src/domain/interpret/ (normalization
5
5
  // pre-pass, the registered parsing strategies, the merge rule), while
6
6
  // ask.mjs keeps the core primitives (resolveObject, traverse, render) and
7
7
  // the ask() orchestration.
@@ -10,31 +10,41 @@
10
10
  export { runChat, COMMANDS, answerCount, renderStats } from "./chat.mjs";
11
11
 
12
12
  // Grammar / NL-over-graph primitives.
13
- export { ask, resolveObject } from "./ask.mjs";
13
+ export { ask, resolveObject } from "../domain/ask.mjs";
14
14
 
15
15
  // The interpretation pipeline: normalize once, run every
16
16
  // registered strategy (grammar, keyword-spot, …) over the text, merge same-class
17
17
  // results, surround distinct-class results — no graph access; pair it with ask()
18
18
  // or the primitives to answer. `interpret(text, ctx)` returns the full record
19
19
  // ({raw, normalized, normalizationChanged, results, parsed, class, alternates}).
20
- export { interpret } from "./interpret/pipeline.mjs";
20
+ export { interpret } from "../domain/interpret/pipeline.mjs";
21
+
22
+ // Composition: the library entry wires the domain parser's default lemma/POS
23
+ // adapter and the construction-grammar banks (lazy loader), same as the chat
24
+ // and tool surfaces do for themselves.
25
+ import { setDefaultNlpAdapter } from "../domain/interpret/nlp-registry.mjs";
26
+ import { setConstructionBanks } from "../domain/interpret/strategies/constructions.mjs";
27
+ import { nlpAdapter } from "../adapters/ask-nlp.mjs";
28
+ import { readConstructionFiles } from "../adapters/corpus/construction-banks.mjs";
29
+ setDefaultNlpAdapter(nlpAdapter);
30
+ setConstructionBanks(readConstructionFiles);
21
31
 
22
32
  // Graph traversal primitives.
23
- export { relationKind, impactClosure } from "./codegraph.mjs";
33
+ export { relationKind, impactClosure } from "../domain/codegraph.mjs";
24
34
 
25
35
  // Tool dispatch (slash-commands and CLI tool calls route through here).
26
- export { dispatchTool } from "./server.mjs";
36
+ export { dispatchTool } from "../tools/server.mjs";
27
37
 
28
38
  // Conversational memory — tmct's OWN OWL-labelled graph under
29
39
  // .tmct/memory/, distinct from any provider-supplied code graph.
30
- export { loadMemory, appendUtterance, appendFact } from "./memory/core.mjs";
31
- export { retrieveBlocks, saveBlock, rankBlocks } from "./memory/blocks.mjs";
32
- export { foldSessionLogs } from "./memory/fold.mjs";
40
+ export { loadMemory, appendUtterance, appendFact } from "../adapters/memory/core.mjs";
41
+ export { retrieveBlocks, saveBlock, rankBlocks } from "../adapters/memory/blocks.mjs";
42
+ export { foldSessionLogs } from "./fold.mjs";
33
43
 
34
44
  // The single graph-load choke point — the adapter's data-provider seam
35
45
  // (docs/adapter-contract.md): registerProvider() plugs a producer in;
36
46
  // fetchEntities() is the one read path.
37
- export { fetchEntities, registerProvider } from "./source.mjs";
47
+ export { fetchEntities, registerProvider } from "../adapters/source.mjs";
38
48
 
39
49
  // `tmct init` onboarding (also reachable as the `./init` subpath export).
40
50
  // init.mjs and toml-config.mjs each export a same-named `CONFIG_FILE`
@@ -43,9 +53,8 @@ export { fetchEntities, registerProvider } from "./source.mjs";
43
53
  export { initRepo, defaultConfig, renderTomlConfig, PERSONA_PRESETS, CONFIG_FILE as INIT_CONFIG_FILE } from "./init.mjs";
44
54
 
45
55
  // tmct.toml loading (also reachable as the `./toml-config` subpath export).
46
- export { CONFIG_FILE as TOML_CONFIG_FILE } from "./toml-config.mjs";
56
+ export { CONFIG_FILE as TOML_CONFIG_FILE } from "../adapters/toml-config.mjs";
47
57
 
48
58
  // The "detailed answer" completions pipeline (also reachable as the
49
59
  // `./generateCompletion` and `./createCompletionsGraphAdapter` subpath exports).
50
- export { generateCompletion } from "./completions/complete.mjs";
51
- export { createCompletionsGraphAdapter } from "./completions/graph-adapter.mjs";
60
+ export { generateCompletion, createCompletionsGraphAdapter } from "./completions.mjs";
@@ -57,7 +57,7 @@ export const PERSONA_PRESETS = Object.freeze({
57
57
  /** Read this package's version (best-effort, for provenance). */
58
58
  async function tmctVersion() {
59
59
  try {
60
- const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
60
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
61
61
  const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
62
62
  return pkg.version || null;
63
63
  } catch {
@@ -92,7 +92,7 @@ export function renderTomlConfig(config = defaultConfig()) {
92
92
  graph_file = ${JSON.stringify(c.graphFile)}
93
93
  ${Array.isArray(c.graphFiles) && c.graphFiles.length ? `
94
94
  # graph_files — additional graph artifacts (multi-graph). Merged with
95
- # graph_file at read time (src/graph-merge.mjs); an id that collides
95
+ # graph_file at read time (src/adapters/graph-merge.mjs); an id that collides
96
96
  # across graphs is auto-prefixed with its graph's name, everything else
97
97
  # passes through unchanged. Written by \`tmct init --graph\`/\`tmct import
98
98
  # --graph\`.
@@ -136,12 +136,12 @@ backend = ${JSON.stringify(config.memory.backend)}
136
136
  if (config.bias !== undefined) extras.bias = config.bias;
137
137
  if (!Object.keys(extras).length) return out;
138
138
  return `${out}
139
- # Extension packs + bias (src/extensions.mjs) — written by \`tmct init --with-persona\`
139
+ # Extension packs + bias (src/services/extensions.mjs) — written by \`tmct init --with-persona\`
140
140
  # or a manual edit. Recognized names (human, seon, conceptnet, tier2-aws,
141
141
  # tier2-python, tier2-java, tier2-general) override the shipped defaults; any
142
142
  # other name declares a new host-supplied bundle (needs its own "kind").
143
143
  # [bias] is a flat bundle-name -> weight table consumed by
144
- # src/memory/bias.mjs's ranking.
144
+ # src/domain/memory/bias.mjs's ranking.
145
145
  ${stringifyToml(extras)}`;
146
146
  }
147
147
 
@@ -168,7 +168,7 @@ function seedRequested({ optSeed, configEnabled, env }) {
168
168
  * merged into a FRESH config only; name resolution is the caller's job (bin/tmct.mjs).
169
169
  * @param {string} [opts.memoryBackend] "default" | "memory" | "sqlite" — merged into a
170
170
  * FRESH config's `[memory] backend`, and selects which backend the corpus seed writes
171
- * into (via src/memory/core.mjs's openMemoryBackend).
171
+ * into (via src/adapters/memory/core.mjs's openMemoryBackend).
172
172
  * @returns {Promise<{
173
173
  * created: string[], config: object, seeded: boolean,
174
174
  * alreadyInitialized: boolean, seedResult: (object|null), message: string
@@ -206,7 +206,7 @@ export async function initRepo(dir, { force = false, seed, env = process.env, pe
206
206
  {
207
207
  const importsDir = join(paths.tmct, "imports");
208
208
  const gamesDir = join(importsDir, "games");
209
- const shippedGames = join(dirname(fileURLToPath(import.meta.url)), "..", "data", "games");
209
+ const shippedGames = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "data", "games");
210
210
  if (!(await exists(gamesDir))) {
211
211
  await mkdir(gamesDir, { recursive: true });
212
212
  created.push(gamesDir);
@@ -277,7 +277,7 @@ export async function initRepo(dir, { force = false, seed, env = process.env, pe
277
277
  if (backendChoice === "memory") {
278
278
  seedNote = "seed skipped (memory backend is in-process only — nothing would persist past this command)";
279
279
  } else {
280
- const { openMemoryBackend } = await import("./memory/core.mjs");
280
+ const { openMemoryBackend } = await import("../adapters/memory/core.mjs");
281
281
  const { dir: memoryDir, close: closeMemoryStore } = await openMemoryBackend(root, backendChoice);
282
282
  try {
283
283
  const { resolveExtensions, seedActiveCorpusEntries } = await import("./extensions.mjs");
@@ -348,7 +348,7 @@ export async function initRepo(dir, { force = false, seed, env = process.env, pe
348
348
  * (toml-config.mjs) is where a bad file surfaces its error. */
349
349
  async function readWrittenConfig(tomlPath, base) {
350
350
  try {
351
- const { loadTomlConfig } = await import("./toml-config.mjs");
351
+ const { loadTomlConfig } = await import("../adapters/toml-config.mjs");
352
352
  const raw = await loadTomlConfig(dirname(tomlPath));
353
353
  if (!raw) return base;
354
354
  const cfg = { ...base };
@@ -359,7 +359,7 @@ async function readWrittenConfig(tomlPath, base) {
359
359
  if (raw.seed.enabled !== undefined) cfg.seed.enabled = Boolean(raw.seed.enabled);
360
360
  if (raw.seed.limit !== undefined) cfg.seed.limit = Number(raw.seed.limit);
361
361
  }
362
- // Sparse pass-through — src/extensions.mjs validates; this layer just carries the
362
+ // Sparse pass-through — src/services/extensions.mjs validates; this layer just carries the
363
363
  // raw tables through unmodified.
364
364
  if (raw.extensions !== undefined) cfg.extensions = raw.extensions;
365
365
  if (raw.bias !== undefined) cfg.bias = raw.bias;
@@ -10,7 +10,7 @@
10
10
  // readMemoryAskBundle() is the one extra bit of I/O renderLedgerHtml itself
11
11
  // doesn't do: it reads the checked-in chat-dock engine bundle.
12
12
 
13
- import { loadMemory, readFactRows, findContradictions, normFactTerm } from "./memory/core.mjs";
13
+ import { loadMemory, readFactRows, findContradictions, normFactTerm } from "../adapters/memory/core.mjs";
14
14
  import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson } from "./viz-theme.mjs";
15
15
  import { readFile } from "node:fs/promises";
16
16
  import { fileURLToPath } from "node:url";
@@ -19,14 +19,14 @@ import { dirname, join } from "node:path";
19
19
  export const LEDGER_ROW_LIMIT_DEFAULT = 20000;
20
20
 
21
21
  /** Read the checked-in browser memory-ask-engine bundle
22
- * (`src/memory-ask-browser.bundle.js`) — the real memory-graph answer engine
22
+ * (`src/surfaces/web/memory-ask-browser.bundle.js`) — the real memory-graph answer engine
23
23
  * (chat.mjs's factAnswer/factReadBack) the chat dock runs on. Returns `""`,
24
24
  * never throws, if the bundle hasn't been built — the page then renders with
25
25
  * an honest "chat unavailable" note instead of a dock. */
26
26
  export async function readMemoryAskBundle() {
27
27
  try {
28
28
  const here = dirname(fileURLToPath(import.meta.url));
29
- return await readFile(join(here, "memory-ask-browser.bundle.js"), "utf8");
29
+ return await readFile(join(here, "..", "surfaces", "web", "memory-ask-browser.bundle.js"), "utf8");
30
30
  } catch {
31
31
  return "";
32
32
  }
@@ -31,7 +31,9 @@ function darken(hex, fraction) {
31
31
  }
32
32
 
33
33
  /** Topological rank over [smaller, larger] pairs, label tiebreak; members
34
- * absent from every pair are appended in label order. */
34
+ * absent from every pair are appended in label order. `sized` names the members
35
+ * a pair actually ordered — the appended rest carry a rank so they can be drawn,
36
+ * which says nothing about their size. */
35
37
  function rankBySize(sizeOrder, members) {
36
38
  const pairs = Array.isArray(sizeOrder) ? sizeOrder : [];
37
39
  const inPairs = new Set();
@@ -62,10 +64,29 @@ function rankBySize(sizeOrder, members) {
62
64
  }
63
65
  ready.sort();
64
66
  }
67
+ const sized = new Set(Object.keys(ranks));
65
68
  for (const m of [...members].sort()) {
66
69
  if (!(m in ranks)) ranks[m] = next++;
67
70
  }
68
- return ranks;
71
+ return { ranks, sized };
72
+ }
73
+
74
+ /** Every label the plan itself names — across its states and its actions. The
75
+ * board's undeclared anchors are drawn from this rather than from the whole
76
+ * domain, which is the entire memory. */
77
+ function namesInPlan(plan) {
78
+ const named = new Set();
79
+ for (const rows of plan?.states || []) {
80
+ for (const r of rows || []) {
81
+ if (r?.subject != null) named.add(r.subject);
82
+ if (r?.object != null) named.add(r.object);
83
+ }
84
+ }
85
+ for (const a of plan?.actions || []) {
86
+ if (a?.subject != null) named.add(a.subject);
87
+ if (a?.target != null) named.add(a.target);
88
+ }
89
+ return named;
69
90
  }
70
91
 
71
92
  /**
@@ -73,7 +94,8 @@ function rankBySize(sizeOrder, members) {
73
94
  *
74
95
  * plan: { states: [[{subject, predicate, object}], …], domain: { classMembers } }
75
96
  * rendersAs: { className: "block" | "slot" } — classes absent from the map
76
- * fall back to labeled circles.
97
+ * fall back to labeled circles, and contribute only the members the
98
+ * plan names (see the loop below: the domain is the whole memory).
77
99
  * sizeOrder: [[smallerLabel, largerLabel], …]
78
100
  *
79
101
  * Returns { board, ranks, anchors, snapshots: [{ items, stacks }] } —
@@ -82,6 +104,7 @@ function rankBySize(sizeOrder, members) {
82
104
  export function computeBlocksLayout({ plan, rendersAs = {}, sizeOrder = [] }) {
83
105
  const classMembers = plan?.domain?.classMembers || {};
84
106
  const classes = Object.keys(classMembers).sort();
107
+ const named = namesInPlan(plan);
85
108
  const blockSet = new Set();
86
109
  const anchorDefs = [];
87
110
  const classHue = {};
@@ -94,7 +117,18 @@ export function computeBlocksLayout({ plan, rendersAs = {}, sizeOrder = [] }) {
94
117
  for (const m of members) blockSet.add(m);
95
118
  } else {
96
119
  const kind = archetype === "slot" ? "slot" : "circle";
97
- for (const m of members) anchorDefs.push({ id: m, kind });
120
+ // A DECLARED class is the board's own furniture: every member is drawn,
121
+ // empty or not (an unused peg is still a peg). An UNDECLARED class only
122
+ // falls back to circles, and the domain it comes from is the whole
123
+ // memory, not just this puzzle — one that has been taught a vocabulary
124
+ // carries hundreds of individuals that have nothing to do with the game.
125
+ // Drawing those puts every noun the memory knows on the board, which
126
+ // squeezes the real anchors into a few pixels at the left edge. So an
127
+ // undeclared class contributes only what the plan actually names.
128
+ for (const m of members) {
129
+ if (!archetype && !named.has(m)) continue;
130
+ anchorDefs.push({ id: m, kind });
131
+ }
98
132
  }
99
133
  }
100
134
  anchorDefs.sort((a, b) => (a.kind === b.kind ? (a.id < b.id ? -1 : 1) : a.kind === "slot" ? -1 : 1));
@@ -106,7 +140,7 @@ export function computeBlocksLayout({ plan, rendersAs = {}, sizeOrder = [] }) {
106
140
  const anchorX = new Map(anchors.map((a) => [a.id, a.x]));
107
141
 
108
142
  const blocks = [...blockSet].sort();
109
- const ranks = rankBySize(sizeOrder, blocks);
143
+ const { ranks, sized } = rankBySize(sizeOrder, blocks);
110
144
  const maxRank = blocks.reduce((m, b) => Math.max(m, ranks[b] ?? 0), 0);
111
145
  const blockClassOf = (label) =>
112
146
  classes.find((cls) => rendersAs[cls] === "block" && (classMembers[cls] || []).includes(label));
@@ -118,21 +152,27 @@ export function computeBlocksLayout({ plan, rendersAs = {}, sizeOrder = [] }) {
118
152
  };
119
153
 
120
154
  const snapshots = (plan?.states || []).map((rows) => {
121
- const supporterOf = new Map(); // object -> subject resting on it
155
+ const restingOn = new Map(); // object -> every subject resting directly on it
122
156
  for (const r of [...rows].sort((a, b) => (a.subject < b.subject ? -1 : 1))) {
123
- if (blockSet.has(r.subject) && !supporterOf.has(r.object)) {
124
- supporterOf.set(r.object, r.subject);
125
- }
157
+ if (!blockSet.has(r.subject)) continue;
158
+ if (!restingOn.has(r.object)) restingOn.set(r.object, []);
159
+ const above = restingOn.get(r.object);
160
+ if (!above.includes(r.subject)) above.push(r.subject);
126
161
  }
127
162
  const stacks = {};
128
163
  const items = anchors.map((a) => ({ ...a }));
129
164
  for (const a of anchors) {
130
165
  const stack = [];
131
- let top = a.id;
132
- while (supporterOf.has(top)) {
133
- top = supporterOf.get(top);
134
- stack.push(top);
135
- }
166
+ const placed = new Set();
167
+ const pileOnto = (support) => {
168
+ for (const label of [...(restingOn.get(support) || [])].sort()) {
169
+ if (placed.has(label)) continue;
170
+ placed.add(label);
171
+ stack.push(label);
172
+ pileOnto(label);
173
+ }
174
+ };
175
+ pileOnto(a.id);
136
176
  stacks[a.id] = stack;
137
177
  stack.forEach((label, i) => {
138
178
  const w = widthOf(label);
@@ -152,16 +192,24 @@ export function computeBlocksLayout({ plan, rendersAs = {}, sizeOrder = [] }) {
152
192
  return { items, stacks };
153
193
  });
154
194
 
155
- return { board: { w: BOARD_W, h: BOARD_H }, ranks, anchors, snapshots };
195
+ return { board: { w: BOARD_W, h: BOARD_H }, ranks, sized, anchors, snapshots };
156
196
  }
157
197
 
158
198
  /** Phase brackets from the largest block's single move: everything before it
159
199
  * frees the piece, the move itself is the pivot, the rest rebuilds. */
160
- function phasesFor(actions, ranks) {
161
- const ranked = Object.keys(ranks);
162
- if (!ranked.length || !actions.length) return [];
163
- const pivot = ranked.reduce((a, b) => (ranks[a] >= ranks[b] ? a : b));
164
- const k = actions.findIndex((a) => a.subject === pivot);
200
+ function phasesFor(actions, ranks, sized) {
201
+ // free-it / move-it / rebuild-on-it describes ONE shape: a puzzle whose biggest
202
+ // piece is declared biggest and travels once, everything before clearing its way
203
+ // and everything after stacking back on top. Hanoi is that shape. A puzzle that
204
+ // declares no sizes has no biggest piece to pivot on — rankBySize ranks its
205
+ // pieces by label so they can be drawn, and reading a pivot out of that ordering
206
+ // names an arbitrary piece. Say nothing rather than something arbitrary.
207
+ const ordered = [...(sized || [])];
208
+ if (!ordered.length || !actions.length) return [];
209
+ const pivot = ordered.reduce((a, b) => (ranks[a] >= ranks[b] ? a : b));
210
+ const moves = actions.filter((a) => a.subject === pivot);
211
+ if (moves.length !== 1) return []; // travels more than once — not this shape
212
+ const k = actions.indexOf(moves[0]);
165
213
  if (k <= 0 || k >= actions.length - 1) return [];
166
214
  return [
167
215
  { label: `free ${pivot}`, from: 0, to: k },
@@ -198,7 +246,7 @@ export function renderPlanHtml({ plan, rendersAs = {}, sizeOrder = [], title } =
198
246
  anchors: layout.anchors,
199
247
  board: layout.board,
200
248
  facts: factsPerStep,
201
- phases: phasesFor(actions, layout.ranks),
249
+ phases: phasesFor(actions, layout.ranks, layout.sized),
202
250
  });
203
251
  const pageTitle = title || `tmct plan — ${actions.length} move${actions.length === 1 ? "" : "s"}`;
204
252
 
@@ -327,13 +375,35 @@ const PLAN = ${embedded};
327
375
  const a = posIn(before, id), b = posIn(after, id);
328
376
  return a && b && (a.x !== b.x || a.y !== b.y);
329
377
  });
330
- if (reduced || movers.length !== 1) { drawState(i + 1); return; }
331
- const id = movers[0], el = blockEls[id], to = posIn(after, id);
332
- animating = true; el.classList.add("moving");
333
- el.style.transition = "top .18s ease-in"; el.style.top = "${LIFT_Y}px"; await wait(190);
334
- el.style.transition = "left .26s ease-in-out"; el.style.left = to.x + "px"; await wait(270);
335
- el.style.transition = "top .18s ease-out"; el.style.top = to.y + "px"; await wait(200);
336
- el.classList.remove("moving"); animating = false;
378
+ if (reduced || movers.length === 0) { drawState(i + 1); return; }
379
+ // A step can move several pieces at once (a carrier plus its passengers).
380
+ // Only pieces that change column actually travel; a piece whose column is
381
+ // unchanged is settling into the gap a departing piece left, so it drops
382
+ // in place rather than miming the journey.
383
+ const travellers = movers.filter((id) => posIn(before, id).x !== posIn(after, id).x);
384
+ const settlers = movers.filter((id) => posIn(before, id).x === posIn(after, id).x);
385
+ animating = true;
386
+ // Each traveller rides its own lane so pieces crossing together stay
387
+ // legible instead of stacking up on one another mid-air.
388
+ const riders = travellers.map((id, lane) => ({
389
+ el: blockEls[id],
390
+ to: posIn(after, id),
391
+ liftY: ${LIFT_Y} - lane * ${BLOCK_H + BLOCK_GAP},
392
+ }));
393
+ for (const r of riders) r.el.classList.add("moving");
394
+ for (const r of riders) { r.el.style.transition = "top .18s ease-in"; r.el.style.top = r.liftY + "px"; }
395
+ await wait(190);
396
+ for (const id of settlers) {
397
+ const el = blockEls[id];
398
+ el.style.transition = "top .2s ease-in-out"; el.style.top = posIn(after, id).y + "px";
399
+ }
400
+ for (const r of riders) { r.el.style.transition = "left .26s ease-in-out"; r.el.style.left = r.to.x + "px"; }
401
+ await wait(270);
402
+ for (const r of riders) { r.el.style.transition = "top .18s ease-out"; r.el.style.top = r.to.y + "px"; }
403
+ await wait(200);
404
+ for (const r of riders) r.el.classList.remove("moving");
405
+ drawState(i + 1);
406
+ animating = false;
337
407
  }
338
408
  const phaseFor = (i) => {
339
409
  if (i >= N) return "done";
@@ -1,7 +1,7 @@
1
1
  // sentences.mjs — sentence-boundary splitting, shared by the extract-facts
2
2
  // script, the chat one-shot CLI, and runTurn's multi-sentence pre-split.
3
3
 
4
- import { winkInstance } from "./wink-model.mjs";
4
+ import { winkInstance } from "../adapters/wink-model.mjs";
5
5
 
6
6
  /** Split text into trimmed, non-empty sentences via wink-nlp's own
7
7
  * sentence-boundary detection — never a naive regex split, matching the
@@ -18,7 +18,8 @@
18
18
 
19
19
  import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
20
20
  import { basename, dirname, join } from "node:path";
21
- import { appendUtterances, CREATED_AT_PROP, UPDATED_AT_PROP } from "./memory/core.mjs";
21
+ import { appendUtterances, CREATED_AT_PROP, UPDATED_AT_PROP } from "../adapters/memory/core.mjs";
22
+ import { turnKey } from "../domain/memory/session-turns.mjs";
22
23
 
23
24
  export const SESSIONS_DIR_REL = join(".tmct", "sessions");
24
25
 
@@ -236,7 +237,7 @@ async function recordSessionMemory(graphFile, record, repoDirOverride = null) {
236
237
  ended = Boolean(parseSessionJsonl(sidecar)?.ended);
237
238
  } catch { /* no sidecar — nothing to fold from */ }
238
239
  if (ended) {
239
- const { foldSessionLogs } = await import("./memory/fold.mjs"); // lazy: fold imports this module
240
+ const { foldSessionLogs } = await import("./fold.mjs"); // lazy: fold imports this module
240
241
  await foldSessionLogs(repoDir, { sessionId: record.id });
241
242
  }
242
243
  }
@@ -277,9 +278,7 @@ export function parseSessionJsonl(text) {
277
278
  // echoed "> <query>" line (chat.mjs's logLines shape).
278
279
  const LOG_TS_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
279
280
 
280
- /** Key a transcript answer by its turn: ts + query (ts alone can collide when
281
- * two instant turns land in the same millisecond). */
282
- export const turnKey = (ts, query) => `${ts}${query}`;
281
+ export { turnKey };
283
282
 
284
283
  /**
285
284
  * Parse a human-readable session transcript (.tmct/session-<id>.log) into a
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { appendFile } from "node:fs/promises";
14
14
  import { dirname, join } from "node:path";
15
- import { uuidv7 } from "./uuid.mjs";
15
+ import { uuidv7 } from "../adapters/uuid.mjs";
16
16
 
17
17
  /** Field names whose VALUES are (or embed) raw source and must never be logged. `body` is
18
18
  * the Repository Interface's own field name for real source text (snippet()/context()'s