@polycode-projects/the-mechanical-code-talker 1.12.0 → 2.0.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.
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} +65 -26
  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,11 @@ 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 };
69
72
  }
70
73
 
71
74
  /**
@@ -106,7 +109,7 @@ export function computeBlocksLayout({ plan, rendersAs = {}, sizeOrder = [] }) {
106
109
  const anchorX = new Map(anchors.map((a) => [a.id, a.x]));
107
110
 
108
111
  const blocks = [...blockSet].sort();
109
- const ranks = rankBySize(sizeOrder, blocks);
112
+ const { ranks, sized } = rankBySize(sizeOrder, blocks);
110
113
  const maxRank = blocks.reduce((m, b) => Math.max(m, ranks[b] ?? 0), 0);
111
114
  const blockClassOf = (label) =>
112
115
  classes.find((cls) => rendersAs[cls] === "block" && (classMembers[cls] || []).includes(label));
@@ -118,21 +121,27 @@ export function computeBlocksLayout({ plan, rendersAs = {}, sizeOrder = [] }) {
118
121
  };
119
122
 
120
123
  const snapshots = (plan?.states || []).map((rows) => {
121
- const supporterOf = new Map(); // object -> subject resting on it
124
+ const restingOn = new Map(); // object -> every subject resting directly on it
122
125
  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
- }
126
+ if (!blockSet.has(r.subject)) continue;
127
+ if (!restingOn.has(r.object)) restingOn.set(r.object, []);
128
+ const above = restingOn.get(r.object);
129
+ if (!above.includes(r.subject)) above.push(r.subject);
126
130
  }
127
131
  const stacks = {};
128
132
  const items = anchors.map((a) => ({ ...a }));
129
133
  for (const a of anchors) {
130
134
  const stack = [];
131
- let top = a.id;
132
- while (supporterOf.has(top)) {
133
- top = supporterOf.get(top);
134
- stack.push(top);
135
- }
135
+ const placed = new Set();
136
+ const pileOnto = (support) => {
137
+ for (const label of [...(restingOn.get(support) || [])].sort()) {
138
+ if (placed.has(label)) continue;
139
+ placed.add(label);
140
+ stack.push(label);
141
+ pileOnto(label);
142
+ }
143
+ };
144
+ pileOnto(a.id);
136
145
  stacks[a.id] = stack;
137
146
  stack.forEach((label, i) => {
138
147
  const w = widthOf(label);
@@ -152,16 +161,24 @@ export function computeBlocksLayout({ plan, rendersAs = {}, sizeOrder = [] }) {
152
161
  return { items, stacks };
153
162
  });
154
163
 
155
- return { board: { w: BOARD_W, h: BOARD_H }, ranks, anchors, snapshots };
164
+ return { board: { w: BOARD_W, h: BOARD_H }, ranks, sized, anchors, snapshots };
156
165
  }
157
166
 
158
167
  /** Phase brackets from the largest block's single move: everything before it
159
168
  * 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);
169
+ function phasesFor(actions, ranks, sized) {
170
+ // free-it / move-it / rebuild-on-it describes ONE shape: a puzzle whose biggest
171
+ // piece is declared biggest and travels once, everything before clearing its way
172
+ // and everything after stacking back on top. Hanoi is that shape. A puzzle that
173
+ // declares no sizes has no biggest piece to pivot on — rankBySize ranks its
174
+ // pieces by label so they can be drawn, and reading a pivot out of that ordering
175
+ // names an arbitrary piece. Say nothing rather than something arbitrary.
176
+ const ordered = [...(sized || [])];
177
+ if (!ordered.length || !actions.length) return [];
178
+ const pivot = ordered.reduce((a, b) => (ranks[a] >= ranks[b] ? a : b));
179
+ const moves = actions.filter((a) => a.subject === pivot);
180
+ if (moves.length !== 1) return []; // travels more than once — not this shape
181
+ const k = actions.indexOf(moves[0]);
165
182
  if (k <= 0 || k >= actions.length - 1) return [];
166
183
  return [
167
184
  { label: `free ${pivot}`, from: 0, to: k },
@@ -198,7 +215,7 @@ export function renderPlanHtml({ plan, rendersAs = {}, sizeOrder = [], title } =
198
215
  anchors: layout.anchors,
199
216
  board: layout.board,
200
217
  facts: factsPerStep,
201
- phases: phasesFor(actions, layout.ranks),
218
+ phases: phasesFor(actions, layout.ranks, layout.sized),
202
219
  });
203
220
  const pageTitle = title || `tmct plan — ${actions.length} move${actions.length === 1 ? "" : "s"}`;
204
221
 
@@ -327,13 +344,35 @@ const PLAN = ${embedded};
327
344
  const a = posIn(before, id), b = posIn(after, id);
328
345
  return a && b && (a.x !== b.x || a.y !== b.y);
329
346
  });
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;
347
+ if (reduced || movers.length === 0) { drawState(i + 1); return; }
348
+ // A step can move several pieces at once (a carrier plus its passengers).
349
+ // Only pieces that change column actually travel; a piece whose column is
350
+ // unchanged is settling into the gap a departing piece left, so it drops
351
+ // in place rather than miming the journey.
352
+ const travellers = movers.filter((id) => posIn(before, id).x !== posIn(after, id).x);
353
+ const settlers = movers.filter((id) => posIn(before, id).x === posIn(after, id).x);
354
+ animating = true;
355
+ // Each traveller rides its own lane so pieces crossing together stay
356
+ // legible instead of stacking up on one another mid-air.
357
+ const riders = travellers.map((id, lane) => ({
358
+ el: blockEls[id],
359
+ to: posIn(after, id),
360
+ liftY: ${LIFT_Y} - lane * ${BLOCK_H + BLOCK_GAP},
361
+ }));
362
+ for (const r of riders) r.el.classList.add("moving");
363
+ for (const r of riders) { r.el.style.transition = "top .18s ease-in"; r.el.style.top = r.liftY + "px"; }
364
+ await wait(190);
365
+ for (const id of settlers) {
366
+ const el = blockEls[id];
367
+ el.style.transition = "top .2s ease-in-out"; el.style.top = posIn(after, id).y + "px";
368
+ }
369
+ for (const r of riders) { r.el.style.transition = "left .26s ease-in-out"; r.el.style.left = r.to.x + "px"; }
370
+ await wait(270);
371
+ for (const r of riders) { r.el.style.transition = "top .18s ease-out"; r.el.style.top = r.to.y + "px"; }
372
+ await wait(200);
373
+ for (const r of riders) r.el.classList.remove("moving");
374
+ drawState(i + 1);
375
+ animating = false;
337
376
  }
338
377
  const phaseFor = (i) => {
339
378
  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
@@ -11,29 +11,24 @@
11
11
  // Every response's `usage` is { input_tokens: 0, output_tokens: 0 } — tmct is the $0
12
12
  // floor, priced as free by the meter.
13
13
  //
14
- // src/server.mjs is the tool-dispatch layer, not an HTTP server; this module is the
14
+ // src/tools/server.mjs is the tool-dispatch layer, not an HTTP server; this module is the
15
15
  // HTTP surface.
16
16
 
17
17
  import { createServer } from "node:http";
18
- import { runTurn, COMMANDS, asBareCommand, isConversational } from "./chat.mjs";
19
- import { TOOLS } from "./server.mjs";
20
- import { parseEntities } from "./codegraph.mjs";
21
- import { uuidv7 } from "./uuid.mjs";
22
- import * as defaultSource from "./source.mjs";
18
+ import { runTurn, selectTool } from "../../services/chat.mjs";
19
+ import { TOOLS } from "../../tools/server.mjs";
20
+ import { parseEntities } from "../../domain/codegraph.mjs";
21
+ import { uuidv7 } from "../../adapters/uuid.mjs";
22
+ import * as defaultSource from "../../adapters/source.mjs";
23
+
24
+ // The shim's deterministic tool selection lives with the chat surface's own
25
+ // command routing (selectTool in chat.mjs); re-exported here so HTTP-side
26
+ // callers keep one import site for the whole shim seam.
27
+ export { selectTool } from "../../services/chat.mjs";
23
28
 
24
29
  /** The zero usage every response carries — the meter prices tmct as the $0 floor. */
25
30
  const ZERO_USAGE = { input_tokens: 0, output_tokens: 0 };
26
31
 
27
- /** The tmct tools dispatchTool can back (the set the shim will emit a tool_use for).
28
- * A declared tool outside this set is ignored for emission (the request falls
29
- * through to a text answer) — the shim never emits a call it cannot ground. The
30
- * COMMANDS map (chat.mjs) names the richer graph tools; TOOLS names the hot
31
- * catalog. Their union is what dispatchTool serves. */
32
- const BACKED_TOOLS = new Set([
33
- ...TOOLS.map((t) => t.name),
34
- ...Object.values(COMMANDS).map((s) => s.tool),
35
- ]);
36
-
37
32
  /** Flatten a message's `content` (a string OR a content-block array) into plain
38
33
  * text — concatenating the `text` blocks. Non-text blocks are ignored here. */
39
34
  function textOfContent(content) {
@@ -74,55 +69,6 @@ function toolResultText(block) {
74
69
  try { return JSON.stringify(c); } catch { return String(c); }
75
70
  }
76
71
 
77
- /**
78
- * Decide whether a user turn maps to a DECLARED, dispatch-backed graph-query
79
- * tool, and bind its arguments. Deterministic, in-ethos (no NL guessing beyond
80
- * the chat surface's own command routing):
81
- *
82
- * 1. A slash/bare command that names a tmct tool ("describe X", "/callers X",
83
- * "untested") → that tool with its argument bound from the exact arg key the
84
- * dispatchTool switch reads (COMMANDS in chat.mjs). Only when the tool is
85
- * declared by the caller.
86
- * 2. Otherwise, a non-conversational structural question → tmct_ask{query:…},
87
- * when tmct_ask is declared. Small-talk (isConversational) never emits a
88
- * call — it falls through to a text answer.
89
- *
90
- * Returns { name, input } or null (→ answer as text).
91
- */
92
- export function selectTool(text, declaredNames) {
93
- const t = String(text || "").trim();
94
- if (!t) return null;
95
-
96
- // 1. explicit command form → a specific tool, argument bound
97
- const cmdLine = t.startsWith("/") ? t : asBareCommand(t);
98
- if (cmdLine) {
99
- const [first, ...restTok] = cmdLine.replace(/^\//, "").split(/\s+/);
100
- const spec = COMMANDS[String(first).toLowerCase()];
101
- if (spec && declaredNames.has(spec.tool) && BACKED_TOOLS.has(spec.tool)) {
102
- const input = {};
103
- if (spec.arg) {
104
- const val = restTok.join(" ").trim();
105
- if (val) input[spec.arg] = val;
106
- // an entity command with no argument can't bind a call — fall through
107
- else if (!spec.optional) return askFallback(t, declaredNames);
108
- }
109
- return { name: spec.tool, input };
110
- }
111
- }
112
-
113
- // 2. structural question → tmct_ask, unless it's small-talk
114
- return askFallback(t, declaredNames);
115
- }
116
-
117
- /** The tmct_ask fallback: emit tmct_ask{query} for a non-conversational line when
118
- * the caller declared tmct_ask; otherwise null (→ text answer). */
119
- function askFallback(text, declaredNames) {
120
- if (declaredNames.has("tmct_ask") && BACKED_TOOLS.has("tmct_ask") && !isConversational(text)) {
121
- return { name: "tmct_ask", input: { query: text } };
122
- }
123
- return null;
124
- }
125
-
126
72
  /** Build the assistant message envelope shared by every branch. */
127
73
  function assistantMessage(model, content, stopReason) {
128
74
  return {