@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.
- package/README.md +244 -48
- package/ROADMAP.md +23 -34
- package/bin/tmct.mjs +107 -71
- package/corpus/LICENSES.json +118 -0
- package/corpus/README.md +17 -13
- package/corpus/conceptnet/README.md +5 -5
- package/corpus/conceptnet/fetch-slice.mjs +1 -1
- package/corpus/conceptnet/filter-dump.mjs +1 -1
- package/corpus/generated/README.md +9 -10
- package/corpus/namenet/README.md +39 -0
- package/corpus/seon/README.md +2 -2
- package/corpus/tier2/generate.mjs +58 -9
- package/corpus/tier2/manifest.json +44 -0
- package/corpus/wordnet/README.md +37 -0
- package/corpus/wordnet/generate.mjs +1 -1
- package/data/templates/constructions/agent-noun-relations.toml +2 -2
- package/data/templates/grammar-rules.toml +1 -1
- package/package.json +13 -22
- package/src/{ask-nlp.mjs → adapters/ask-nlp.mjs} +1 -1
- package/src/{config.mjs → adapters/config.mjs} +1 -1
- package/src/{corpus → adapters/corpus}/conceptnet-map.toml +4 -4
- package/src/{corpus → adapters/corpus}/conceptnet.mjs +3 -3
- package/src/adapters/corpus/construction-banks.mjs +43 -0
- package/src/{corpus → adapters/corpus}/templates.mjs +1 -1
- package/src/{embed.mjs → adapters/embed.mjs} +1 -11
- package/src/{graph-build.mjs → adapters/graph-build.mjs} +6 -6
- package/src/{memory → adapters/memory}/blocks.mjs +2 -2
- package/src/{memory → adapters/memory}/core.mjs +38 -94
- package/src/adapters/prose-tokens.mjs +98 -0
- package/src/{providers → adapters/providers}/bootstrap.mjs +2 -2
- package/src/{providers → adapters/providers}/fixture.mjs +3 -3
- package/src/{providers → adapters/providers}/graph-service.mjs +9 -4
- package/src/{source-slice.mjs → adapters/source-slice.mjs} +2 -2
- package/src/{source.mjs → adapters/source.mjs} +1 -1
- package/src/{toml-config.mjs → adapters/toml-config.mjs} +1 -1
- package/src/{answer-variants.json → domain/answer-variants.json} +1 -1
- package/src/domain/answer-variants.mjs +23 -0
- package/src/{ask-vocab.mjs → domain/ask-vocab.mjs} +37 -5
- package/src/{ask.mjs → domain/ask.mjs} +216 -52
- package/src/{codegraph.mjs → domain/codegraph.mjs} +31 -315
- package/src/{completions → domain/completions}/complete.mjs +16 -10
- package/src/{completions → domain/completions}/graph-adapter.mjs +10 -4
- package/src/{completions → domain/completions}/group.mjs +14 -6
- package/src/{completions → domain/completions}/infer.mjs +57 -39
- package/src/domain/completions/injected.mjs +21 -0
- package/src/{completions → domain/completions}/rank.mjs +15 -8
- package/src/{completions → domain/completions}/search.mjs +4 -2
- package/src/{grammar → domain/grammar}/ace.mjs +3 -3
- package/src/{grammar → domain/grammar}/assert.mjs +12 -8
- package/src/{grammar → domain/grammar}/lexicon-core.json +1 -1
- package/src/{grammar → domain/grammar}/lexicon.mjs +4 -6
- package/src/domain/hash.mjs +147 -0
- package/src/{interpret → domain/interpret}/fuzzy.mjs +42 -4
- package/src/domain/interpret/nlp-registry.mjs +20 -0
- package/src/{interpret → domain/interpret}/normalize.mjs +35 -5
- package/src/{interpret → domain/interpret}/pipeline.mjs +1 -5
- package/src/{interpret → domain/interpret}/strategies/ace.mjs +1 -1
- package/src/{interpret → domain/interpret}/strategies/constructions.mjs +30 -52
- package/src/{interpret → domain/interpret}/strategies/keywords.mjs +48 -26
- package/src/domain/memory/capability.mjs +235 -0
- package/src/domain/memory/fold.mjs +54 -0
- package/src/domain/memory/session-turns.mjs +7 -0
- package/src/{memory → domain/memory}/trust.mjs +48 -0
- package/src/{paraphrase.mjs → domain/paraphrase.mjs} +2 -2
- package/src/{prose.mjs → domain/prose.mjs} +1 -1
- package/src/domain/real-word-collisions.json +1 -0
- package/src/{router → domain/router}/call-validator.mjs +1 -1
- package/src/{router → domain/router}/drive.mjs +34 -25
- package/src/{router → domain/router}/goal-reasoner.mjs +1 -1
- package/src/{router → domain/router}/guardrail.mjs +1 -1
- package/src/{router → domain/router}/planner.mjs +1 -1
- package/src/{router → domain/router}/registry.mjs +5 -5
- package/src/{router → domain/router}/resolver.mjs +16 -13
- package/src/{router → domain/router}/results.mjs +1 -1
- package/src/{router → domain/router}/set-algebra.mjs +1 -1
- package/src/{router → domain/router}/taught.mjs +10 -9
- package/src/{syllogise.mjs → domain/syllogise.mjs} +21 -4
- package/src/domain/vector.mjs +12 -0
- package/src/services/chat-session.mjs +451 -0
- package/src/{chat.mjs → services/chat.mjs} +1209 -684
- package/src/{cli-args.mjs → services/cli-args.mjs} +2 -2
- package/src/services/completions.mjs +55 -0
- package/src/{extensions.mjs → services/extensions.mjs} +7 -7
- package/src/{finish.mjs → services/finish.mjs} +2 -2
- package/src/{memory → services}/fold.mjs +0 -0
- package/src/{import-file.mjs → services/import-file.mjs} +3 -3
- package/src/{index.mjs → services/index.mjs} +21 -12
- package/src/{init.mjs → services/init.mjs} +9 -9
- package/src/{ledger-viz.mjs → services/ledger-viz.mjs} +3 -3
- package/src/{plan-viz.mjs → services/plan-viz.mjs} +65 -26
- package/src/{sentences.mjs → services/sentences.mjs} +1 -1
- package/src/{sessions.mjs → services/sessions.mjs} +4 -5
- package/src/{telemetry.mjs → services/telemetry.mjs} +1 -1
- package/src/{server-http.mjs → surfaces/http/server-http.mjs} +11 -65
- package/src/{tui → surfaces/tui}/app.mjs +3 -3
- package/src/{memory-ask-browser-entry.mjs → surfaces/web/memory-ask-browser-entry.mjs} +5 -5
- package/src/{memory-ask-browser.bundle.js → surfaces/web/memory-ask-browser.bundle.js} +9465 -6366
- package/src/tools/catalog.mjs +29 -0
- package/src/{conformance.mjs → tools/conformance.mjs} +2 -2
- package/src/tools/definitions.mjs +288 -0
- package/src/tools/graph-load.mjs +20 -0
- package/src/tools/handlers/index.mjs +54 -0
- package/src/tools/handlers/kit.mjs +33 -0
- package/src/tools/handlers/tmct-architecture.mjs +7 -0
- package/src/tools/handlers/tmct-ask.mjs +14 -0
- package/src/tools/handlers/tmct-callees.mjs +6 -0
- package/src/tools/handlers/tmct-callers.mjs +6 -0
- package/src/tools/handlers/tmct-calls.mjs +6 -0
- package/src/tools/handlers/tmct-class-history.mjs +6 -0
- package/src/tools/handlers/tmct-cochanges.mjs +6 -0
- package/src/tools/handlers/tmct-context-more.mjs +9 -0
- package/src/tools/handlers/tmct-context.mjs +163 -0
- package/src/tools/handlers/tmct-describe.mjs +15 -0
- package/src/tools/handlers/tmct-exports.mjs +9 -0
- package/src/tools/handlers/tmct-file-history.mjs +6 -0
- package/src/tools/handlers/tmct-history.mjs +6 -0
- package/src/tools/handlers/tmct-impact.mjs +9 -0
- package/src/tools/handlers/tmct-members.mjs +16 -0
- package/src/tools/handlers/tmct-method-history.mjs +6 -0
- package/src/tools/handlers/tmct-search.mjs +22 -0
- package/src/tools/handlers/tmct-signature.mjs +6 -0
- package/src/tools/handlers/tmct-snippet.mjs +37 -0
- package/src/tools/handlers/tmct-subclasses.mjs +16 -0
- package/src/tools/handlers/tmct-tests-for.mjs +6 -0
- package/src/tools/handlers/tmct-untested.mjs +7 -0
- package/src/tools/memory-fallthrough.mjs +65 -0
- package/src/{schema-docs.mjs → tools/schema-docs.mjs} +1 -1
- package/src/tools/server.mjs +61 -0
- package/src/answer-variants.mjs +0 -39
- package/src/hash.mjs +0 -24
- package/src/server.mjs +0 -501
- /package/src/{corpus → adapters/corpus}/unknown-ingest.mjs +0 -0
- /package/src/{graph-merge.mjs → adapters/graph-merge.mjs} +0 -0
- /package/src/{memory → adapters/memory}/inspect.mjs +0 -0
- /package/src/{memory → adapters/memory}/shacl.mjs +0 -0
- /package/src/{prose-nlp.mjs → adapters/prose-nlp.mjs} +0 -0
- /package/src/{repository-interface.mjs → adapters/repository-interface.mjs} +0 -0
- /package/src/{uuid.mjs → adapters/uuid.mjs} +0 -0
- /package/src/{wink-model.mjs → adapters/wink-model.mjs} +0 -0
- /package/src/{completions → domain/completions}/prune.mjs +0 -0
- /package/src/{concept.mjs → domain/concept.mjs} +0 -0
- /package/src/{domain.mjs → domain/domain.mjs} +0 -0
- /package/src/{interpret → domain/interpret}/merge.mjs +0 -0
- /package/src/{interpret → domain/interpret}/strategies/grammar.mjs +0 -0
- /package/src/{interpret → domain/interpret}/strategies/noise-strip.mjs +0 -0
- /package/src/{memory → domain/memory}/bias.mjs +0 -0
- /package/src/{planning.mjs → domain/planning.mjs} +0 -0
- /package/src/{viz-theme.mjs → services/viz-theme.mjs} +0 -0
package/src/server.mjs
DELETED
|
@@ -1,501 +0,0 @@
|
|
|
1
|
-
// tmct tool layer — query-only tools over the deterministic typed code-graph
|
|
2
|
-
// artifact (<repo>/.tmct/graph.json). The graph source is a LOCAL file
|
|
3
|
-
// (src/source.mjs) and tmct_search is a LOCAL lexical lookup (no remote API,
|
|
4
|
-
// no LLM, no model calls anywhere). dispatchTool is the single internal switch
|
|
5
|
-
// the chat surface and the `cli <tool>` route call into.
|
|
6
|
-
//
|
|
7
|
-
// Tools (all query-only, bounded output): tmct_search, tmct_describe, tmct_snippet,
|
|
8
|
-
// tmct_impact, plus the read-replacing tools tmct_members, tmct_subclasses,
|
|
9
|
-
// tmct_architecture, tmct_tests_for, tmct_untested, tmct_history, tmct_callers,
|
|
10
|
-
// tmct_callees. Each answers one question in ONE compact call so the caller need not
|
|
11
|
-
// Read/Grep. Errors reach the caller as clean tool errors — message only, never a stack.
|
|
12
|
-
//
|
|
13
|
-
// tmct_ask (hot tool): a mechanical, zero-model-call NL query over the graph —
|
|
14
|
-
// collapses the search+describe+traversal composition loop a caller would
|
|
15
|
-
// otherwise hand-compose into one deterministic round-trip. See ask.mjs.
|
|
16
|
-
|
|
17
|
-
import { readFile } from "node:fs/promises";
|
|
18
|
-
import { dirname } from "node:path";
|
|
19
|
-
import { ToolError } from "./config.mjs";
|
|
20
|
-
import { sliceSpan, readSpanSafe } from "./source-slice.mjs";
|
|
21
|
-
import * as defaultSource from "./source.mjs";
|
|
22
|
-
import {
|
|
23
|
-
parseEntities,
|
|
24
|
-
resolveSymbol,
|
|
25
|
-
renderDescribe,
|
|
26
|
-
renderImpact,
|
|
27
|
-
renderSearch,
|
|
28
|
-
siteOf,
|
|
29
|
-
renderMembers,
|
|
30
|
-
renderSubclasses,
|
|
31
|
-
renderArchitecture,
|
|
32
|
-
renderTestsFor,
|
|
33
|
-
renderUntested,
|
|
34
|
-
renderHistory,
|
|
35
|
-
renderCallers,
|
|
36
|
-
renderCallees,
|
|
37
|
-
renderCochanges,
|
|
38
|
-
renderExports,
|
|
39
|
-
renderSignature,
|
|
40
|
-
contextPlan,
|
|
41
|
-
sizeBundle,
|
|
42
|
-
bundleMask,
|
|
43
|
-
trimBundleMask,
|
|
44
|
-
renderContextMore,
|
|
45
|
-
renderCalls,
|
|
46
|
-
callHint,
|
|
47
|
-
renderFileHistory,
|
|
48
|
-
renderMethodHistory,
|
|
49
|
-
renderClassHistory,
|
|
50
|
-
} from "./codegraph.mjs";
|
|
51
|
-
import { ask } from "./ask.mjs";
|
|
52
|
-
import { createGraphService } from "./providers/graph-service.mjs";
|
|
53
|
-
// Read-only consumers of the conversational-memory graph (corpus facts, separate from
|
|
54
|
-
// the code-map graph.json). Used by the fall-through bridge below: when the code-map
|
|
55
|
-
// resolves nothing for a concept query, answer from the reified isa-family facts instead.
|
|
56
|
-
import { loadMemory, readFactRows, normFactTerm } from "./memory/core.mjs";
|
|
57
|
-
|
|
58
|
-
const SNIPPET_MAX_LINES = 200;
|
|
59
|
-
|
|
60
|
-
// The reified isa-family predicates a memory Fact carries ("<subject> rdfs:subClassOf
|
|
61
|
-
// <object>" / "rdf:type"): subject IS-A object. Subclasses of X = facts whose OBJECT is X;
|
|
62
|
-
// superclasses of X = facts whose SUBJECT is X. (Matches chat.mjs's ISA_PREDICATES.)
|
|
63
|
-
const ISA_PREDICATES = new Set(["rdfs:subClassOf", "rdf:type"]);
|
|
64
|
-
const MEMORY_LIST_CAP = 40;
|
|
65
|
-
|
|
66
|
-
/** Load the conversational-memory Facts as trust-bearing rows, failure-tolerant (no memory
|
|
67
|
-
* store / unreadable → [], so the tool still returns its honest code-map miss). repoRoot is
|
|
68
|
-
* the dir that CONTAINS .tmct/ (graphFile = <repo>/.tmct/graph.json), which is exactly the
|
|
69
|
-
* `dir` loadMemory joins MEMORY_GRAPH_REL onto. */
|
|
70
|
-
async function memoryFactRows(config) {
|
|
71
|
-
try {
|
|
72
|
-
return readFactRows(await loadMemory(dirname(dirname(config.graphFile))));
|
|
73
|
-
} catch {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** A short provenance receipt for a set of memory rows — distinct source strings, capped. */
|
|
79
|
-
function memoryProvenance(rows) {
|
|
80
|
-
const provs = [...new Set(rows.map((r) => r.provenance).filter(Boolean))];
|
|
81
|
-
if (!provs.length) return "provenance: memory/corpus facts";
|
|
82
|
-
const shown = provs.slice(0, 2).join("; ");
|
|
83
|
-
return `provenance: ${shown}${provs.length > 2 ? `, +${provs.length - 2} more source(s)` : ""}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** FALL-THROUGH: subclasses of a concept from the reified isa-family facts (subjects of
|
|
87
|
-
* "<subj> subClassOf <term>"). Null when the term names no such facts (so the caller can
|
|
88
|
-
* keep the honest code-map miss). Provenance is always cited. */
|
|
89
|
-
function renderMemorySubclasses(rows, term) {
|
|
90
|
-
const t = normFactTerm(term);
|
|
91
|
-
const hits = rows.filter((r) => ISA_PREDICATES.has(r.predicate) && r.object === t);
|
|
92
|
-
if (!hits.length) return null;
|
|
93
|
-
const labels = [...new Set(hits.map((r) => r.subject))].sort();
|
|
94
|
-
const shown = labels.slice(0, MEMORY_LIST_CAP);
|
|
95
|
-
const tail = labels.length > MEMORY_LIST_CAP ? `\n …+${labels.length - MEMORY_LIST_CAP} more` : "";
|
|
96
|
-
return `"${term}" is not a code-map entity — answering from memory/corpus facts. ` +
|
|
97
|
-
`${labels.length} known subclass(es):\n ${shown.join("\n ")}${tail}\n(${memoryProvenance(hits)})`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** FALL-THROUGH: a concept's DEFINITION from the isa-family facts — its superclasses ("is
|
|
101
|
-
* a …") plus a count/sample of its known subclasses. Null when the term names no facts. */
|
|
102
|
-
function renderMemoryDefinition(rows, term) {
|
|
103
|
-
const t = normFactTerm(term);
|
|
104
|
-
const isa = rows.filter((r) => ISA_PREDICATES.has(r.predicate) && (r.subject === t || r.object === t));
|
|
105
|
-
if (!isa.length) return null;
|
|
106
|
-
const supers = [...new Set(isa.filter((r) => r.subject === t).map((r) => r.object))];
|
|
107
|
-
const subs = [...new Set(isa.filter((r) => r.object === t).map((r) => r.subject))].sort();
|
|
108
|
-
const lines = [`"${term}" is not a code-map entity — answering from memory/corpus facts.`];
|
|
109
|
-
if (supers.length) lines.push(`is a: ${supers.slice(0, MEMORY_LIST_CAP).join(", ")}`);
|
|
110
|
-
if (subs.length) {
|
|
111
|
-
const tail = subs.length > MEMORY_LIST_CAP ? `, +${subs.length - MEMORY_LIST_CAP} more` : "";
|
|
112
|
-
lines.push(`known subclasses (${subs.length}): ${subs.slice(0, MEMORY_LIST_CAP).join(", ")}${tail}`);
|
|
113
|
-
}
|
|
114
|
-
lines.push(`(${memoryProvenance(isa)})`);
|
|
115
|
-
return lines.join("\n");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Tiered tool surface: the hot tools carry full descriptions/schemas in this
|
|
119
|
-
// catalog; every COLD tool (describe/members/impact/history/…) is still served
|
|
120
|
-
// by dispatchTool below and is reachable via the CLI `cli <tool>` route +
|
|
121
|
-
// the generated <repo>/.tmct/TOOLS.md catalog (renderToolsCatalog).
|
|
122
|
-
export const TOOLS = [
|
|
123
|
-
{
|
|
124
|
-
name: "tmct_context",
|
|
125
|
-
// Lean resident schema (re-billed every turn): the minimum that still steers the agent to
|
|
126
|
-
// ONE call → write, not Read.
|
|
127
|
-
description:
|
|
128
|
-
"START HERE to add/modify code: ONE call returns a sized edit bundle (exemplar source, sibling signatures, registration, insertion region) — then write directly, don't Read.",
|
|
129
|
-
inputSchema: {
|
|
130
|
-
type: "object",
|
|
131
|
-
required: ["symbol"],
|
|
132
|
-
properties: {
|
|
133
|
-
symbol: { type: "string", description: "Module path (e.g. path/to/module) or a sibling function/class name defined in it." },
|
|
134
|
-
depth: { type: "string", enum: ["min", "auto", "full"], default: "auto", description: "auto (sized to the task) | min (leanest) | full (every section)." },
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
name: "tmct_snippet",
|
|
140
|
-
description: "EXACT source of one function/class/Class.method by name (its line span only) + a one-line in-repo call hint. Prefer over Read for a single symbol.",
|
|
141
|
-
inputSchema: {
|
|
142
|
-
type: "object",
|
|
143
|
-
required: ["symbol"],
|
|
144
|
-
properties: {
|
|
145
|
-
symbol: { type: "string", description: "function/class name, Class.method, or fn:<path>#name." },
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
name: "tmct_ask",
|
|
151
|
-
description:
|
|
152
|
-
"Ask a structural question in plain English: \"which functions call X\", \"what uses X\", \"where is X defined\", \"when did X change\". One call, no model. A clean miss beats a guess.",
|
|
153
|
-
inputSchema: {
|
|
154
|
-
type: "object",
|
|
155
|
-
required: ["query"],
|
|
156
|
-
properties: {
|
|
157
|
-
query: { type: "string", description: "A free-text question, e.g. \"which functions explicitly couple to logging\"." },
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
];
|
|
162
|
-
|
|
163
|
-
// Exported so chat.mjs's compare lane can load the SAME graph dispatchTool's
|
|
164
|
-
// own tools load — no new loading path, just direct reuse of the existing
|
|
165
|
-
// config -> source.fetchEntities -> parseEntities chain, for the case where
|
|
166
|
-
// runAsk's own `graph` param is null (the common case; it's only preloaded
|
|
167
|
-
// when a caller already has one in hand — see runAsk's own `if (graph && ...)`
|
|
168
|
-
// / dispatchTool("tmct_ask", …) split just above).
|
|
169
|
-
export async function loadGraph(config, source) {
|
|
170
|
-
const payload = await source.fetchEntities(config);
|
|
171
|
-
const graph = parseEntities(payload);
|
|
172
|
-
if (!graph.individuals.length) {
|
|
173
|
-
// Honest miss, never a stack: a fresh repo simply has no graph yet (the chat
|
|
174
|
-
// session itself creates one as the conversation folds in).
|
|
175
|
-
throw new ToolError(
|
|
176
|
-
`the graph at ${config.graphFile} is empty — no entities to answer from yet ` +
|
|
177
|
-
"(this repo starts with no graph; the chat session folds the conversation into one).",
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
return graph;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// A clean miss on the interface becomes the instructive ToolError the CLI/chat expect —
|
|
184
|
-
// message-only, never a stack, no fabricated entity names.
|
|
185
|
-
function resolveOrThrow(svc, symbol, what) {
|
|
186
|
-
const { match, candidates } = resolveSymbol(svc.graph, symbol);
|
|
187
|
-
if (!match) {
|
|
188
|
-
throw new ToolError(
|
|
189
|
-
`no entity matching ${what} "${symbol}" in the code-map graph. ` +
|
|
190
|
-
"Try a repo-relative path (e.g. path/to/module), a basename, or tmct_search for a fuzzy lookup.",
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
return { match, candidates };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Build the tmct_context "edit bundle" for a symbol and return { text, tier, topup }.
|
|
198
|
-
* Shared by the tmct_context tool AND the `cli digest` arm (cli.mjs). `trim:true` renders
|
|
199
|
-
* a SECONDARY, signatures-only bundle (no bodies/tails) for related-but-not-primary
|
|
200
|
-
* digest modules.
|
|
201
|
-
*
|
|
202
|
-
* Section order is cache-stable: content identical across runs (anchor/registration/
|
|
203
|
-
* exemplar/siblings/__all__/insertion region) comes first; more variable, history-derived
|
|
204
|
-
* tails (covering tests, co-change) come last, so a stable prefix maximises prompt-cache
|
|
205
|
-
* reuse.
|
|
206
|
-
*/
|
|
207
|
-
export async function buildContextBundle(args, { config, source = defaultSource, trim = false, tel = null } = {}) {
|
|
208
|
-
const symbol = String(args?.symbol || "").trim();
|
|
209
|
-
if (!symbol) throw new ToolError("symbol is required");
|
|
210
|
-
const depth = String(args?.depth || "auto").trim().toLowerCase();
|
|
211
|
-
// Tuning-flag contract: `min` forces the LEANEST bundle (TINY mask, no top-up) regardless of
|
|
212
|
-
// exemplar length; `untuned` reproduces the earlier escalation (tuning #1 bypassed). Neither
|
|
213
|
-
// → the tuned default (sizeBundle's anchor-gated escalation).
|
|
214
|
-
const min = Boolean(args?.min);
|
|
215
|
-
const untuned = Boolean(args?.untuned);
|
|
216
|
-
// `max` forces the injection CEILING — FULL tier (every section + inlined depth-1 callee
|
|
217
|
-
// bodies) with top-up, and it OVERRIDES trim so even secondary modules get the full bundle. Used
|
|
218
|
-
// by the tmct-max arm to test whether more injection re-bloats.
|
|
219
|
-
const max = Boolean(args?.max);
|
|
220
|
-
const graph = await loadGraph(config, source);
|
|
221
|
-
// repo root = the dir containing .tmct/ (graphFile = <repo>/.tmct/graph.json) — computed
|
|
222
|
-
// before createGraphService so the RI service can be constructed source-capable (2e): this
|
|
223
|
-
// module still does its OWN safe reads below (readSpanSafe/sliceSpan, Item 1) rather than
|
|
224
|
-
// delegating to svc.context() — see the module docblock's note on why. Passing sourceAccess
|
|
225
|
-
// through anyway keeps svc.snippet()/svc.context() usable by any future/external caller of
|
|
226
|
-
// this same service object without a second, divergent construction path. `tel` (optional,
|
|
227
|
-
// Item 3.3) is an already-constructed telemetry sink threaded down from the caller (e.g.
|
|
228
|
-
// chat.mjs's session-level createTelemetry) — never minted here, so a caller that never
|
|
229
|
-
// passes one costs nothing extra (createGraphService's own wrapping loop no-ops on tel:null).
|
|
230
|
-
const repoRoot = dirname(dirname(config.graphFile));
|
|
231
|
-
const svc = createGraphService(graph, { sourceAccess: true, repoRoot, readFile, tel });
|
|
232
|
-
const { match } = resolveOrThrow(svc, symbol, "symbol");
|
|
233
|
-
const plan = contextPlan(graph, match);
|
|
234
|
-
// #6/B1/B6: pick the section mask by depth — min forces TINY, full/max forces everything, auto
|
|
235
|
-
// runs the size classifier (lean TINY default + one-tier top-up when the edit needs it).
|
|
236
|
-
let tier;
|
|
237
|
-
let mask;
|
|
238
|
-
let topup = false;
|
|
239
|
-
if (min || depth === "min") { tier = "TINY"; mask = bundleMask("TINY"); }
|
|
240
|
-
else if (max || depth === "full") { tier = "FULL"; mask = bundleMask("FULL"); topup = true; }
|
|
241
|
-
else ({ tier, mask, topup } = sizeBundle(plan, graph, { untuned }));
|
|
242
|
-
if (trim && !max) mask = trimBundleMask(mask); // B2: secondary digest module → signatures + region only (max keeps the full bundle)
|
|
243
|
-
let lines = null;
|
|
244
|
-
if (plan.moduleLabel) {
|
|
245
|
-
try { ({ lines } = await readSpanSafe({ readFile, repoRoot, path: plan.moduleLabel })); }
|
|
246
|
-
catch { lines = null; }
|
|
247
|
-
}
|
|
248
|
-
const lineAt = (n) => (lines && lines[n - 1] != null ? lines[n - 1].trim() : "");
|
|
249
|
-
const sliceBody = (start, end) => sliceSpan(lines, start, end, SNIPPET_MAX_LINES).text;
|
|
250
|
-
const out = [
|
|
251
|
-
`Edit context for ${plan.moduleLabel} [${tier}${trim ? " secondary" : ""}] — assembled from the typed graph + that file. ` +
|
|
252
|
-
"You do NOT need to Read it; write the new code directly after reviewing this.",
|
|
253
|
-
];
|
|
254
|
-
// ---- cache-stable prefix: identical across runs ----
|
|
255
|
-
if (mask.anchor && plan.anchor?.site && lines) {
|
|
256
|
-
const { start, end } = plan.anchor.site;
|
|
257
|
-
out.push(`\n## anchor: ${plan.anchor.label} (${plan.anchor.class}) @ ${plan.moduleLabel}:${start}-${end}`);
|
|
258
|
-
out.push(sliceBody(start, end));
|
|
259
|
-
if (plan.callHint) out.push(plan.callHint);
|
|
260
|
-
}
|
|
261
|
-
if (mask.registration && plan.globals.length) {
|
|
262
|
-
out.push(`\n## registration / module globals (replicate this pattern):`);
|
|
263
|
-
for (const g of plan.globals) out.push(` ${g.label} = ${g.value}${g.site ? ` [:${g.site.start}]` : ""}`);
|
|
264
|
-
}
|
|
265
|
-
if (mask.exemplar && plan.exemplar?.site && lines) {
|
|
266
|
-
const { start, end } = plan.exemplar.site;
|
|
267
|
-
const dec = plan.exemplar.decorators ? ` @${plan.exemplar.decorators}` : "";
|
|
268
|
-
out.push(`\n## closest example (full body) — copy this style: ${plan.exemplar.label} (${plan.exemplar.class})${dec} @ ${plan.moduleLabel}:${start}-${end}`);
|
|
269
|
-
out.push(sliceBody(start, end));
|
|
270
|
-
if (plan.callHint) out.push(plan.callHint);
|
|
271
|
-
}
|
|
272
|
-
if (mask.inlinedCallees && plan.calleeBodies.length && lines) {
|
|
273
|
-
let budget = 120; // INLINE_CALLEE_LOC
|
|
274
|
-
for (const cb of plan.calleeBodies) {
|
|
275
|
-
if (budget <= 0) break;
|
|
276
|
-
const start = cb.site.start;
|
|
277
|
-
const fromThisFile = cb.site.path === plan.moduleLabel;
|
|
278
|
-
const bodyLines = fromThisFile && lines
|
|
279
|
-
? lines
|
|
280
|
-
: await readSpanSafe({ readFile, repoRoot, path: cb.site.path }).then((r) => r.lines).catch(() => null);
|
|
281
|
-
if (!bodyLines) continue;
|
|
282
|
-
const sliced = sliceSpan(bodyLines, start, cb.site.end, budget);
|
|
283
|
-
out.push(`\n## inlined callee body (depth-1 in-repo call): ${cb.label} @ ${cb.site.path}:${start}-${cb.site.end}`);
|
|
284
|
-
out.push(sliced.text);
|
|
285
|
-
budget -= (sliced.end - start + 1);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (mask.classMembers && plan.classMembers && plan.classMembers.members.length) {
|
|
289
|
-
out.push(`\n## members of ${plan.classMembers.className} (the edit likely lives INSIDE this class — copy a member's shape, do not read the class body):`);
|
|
290
|
-
for (const m of plan.classMembers.members) {
|
|
291
|
-
const short = String(m.label).split(".").pop();
|
|
292
|
-
const sig = m.params != null && m.params !== "" ? `(${m.params})${m.returns ? ` -> ${m.returns}` : ""}` : "";
|
|
293
|
-
const dec = m.decorators ? `@${m.decorators} ` : "";
|
|
294
|
-
const r = m.raises ? ` raises=${m.raises}` : "";
|
|
295
|
-
out.push(` ${m.class} ${short}${m.site ? ` :${m.site.start}` : ""} ${dec}${short}${sig}${r}`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (mask.siblings && plan.siblings.length) {
|
|
299
|
-
out.push(`\n## sibling symbols to copy the style of (most relevant first; ${plan.siblings.length} total):`);
|
|
300
|
-
for (const s of plan.siblings.slice(0, plan.siblingCap)) {
|
|
301
|
-
const sig = s.site ? lineAt(s.site.start) : "";
|
|
302
|
-
const dec = s.decorators ? `@${s.decorators} ` : "";
|
|
303
|
-
const r = s.raises ? ` raises=${s.raises}` : "";
|
|
304
|
-
out.push(` ${s.class} ${s.label}${s.site ? ` :${s.site.start}` : ""} ${dec}${sig}${r}`);
|
|
305
|
-
}
|
|
306
|
-
if (plan.siblings.length > plan.siblingCap) {
|
|
307
|
-
out.push(` …+${plan.siblings.length - plan.siblingCap} more (use tmct_search kind=function or tmct_snippet <name> for any of them)`);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
if (mask.allExports && plan.allExports) {
|
|
311
|
-
out.push(`\n## module __all__ — this module curates its public API; ADD your new public symbol to this list so it is importable:\n ${plan.allExports}`);
|
|
312
|
-
}
|
|
313
|
-
if (mask.reexports && plan.exports && plan.exports.length) out.push(`\n## re-exported symbols (resolved __all__ → defining module): ${plan.exports.join(", ")}`);
|
|
314
|
-
// #2 + B4: the contiguous insertion region is part of the STABLE prefix — always present
|
|
315
|
-
// (even at TINY) so the agent never needs to Read the file to place the edit.
|
|
316
|
-
if (mask.insertionRegion && plan.insertionRegion && lines) {
|
|
317
|
-
const start = plan.insertionRegion.start;
|
|
318
|
-
const end = Math.min(lines.length, start + 40 - 1); // INSERTION_REGION_CAP
|
|
319
|
-
out.push(`\n## insertion region (write your new sibling here) — ${plan.moduleLabel}:${start}-${end}`);
|
|
320
|
-
out.push(lines.slice(start - 1, end).map((l, i) => `${start + i}\t${l}`).join("\n"));
|
|
321
|
-
} else if (plan.insertion) {
|
|
322
|
-
out.push(`\n## insert the new sibling after line ~${plan.insertion} (end of the last top-level definition).`);
|
|
323
|
-
}
|
|
324
|
-
// ---- variable tail: history-derived, kept LAST so the prefix stays cache-stable ----
|
|
325
|
-
if (mask.tests && plan.tests.length) out.push(`\n## covering tests: ${plan.tests.join(", ")}`);
|
|
326
|
-
if (mask.cochange && plan.cochange && plan.cochange.length) {
|
|
327
|
-
out.push(`\n## usually changed together (consider editing these too): ${plan.cochange.map((c) => `${c.label} (×${c.weight})`).join(", ")}`);
|
|
328
|
-
}
|
|
329
|
-
out.push(`\nYou now have the snippet, the sibling style, the registration anchor and the tests. ` +
|
|
330
|
-
`Write the new code with Edit/Write — do NOT Read ${plan.moduleLabel}.`);
|
|
331
|
-
if (tier !== "FULL") {
|
|
332
|
-
out.push(`(bundle tier ${tier}; for any omitted sections run tmct_context_more {"symbol":"${symbol}"}, or tmct_context with depth="full".)`);
|
|
333
|
-
}
|
|
334
|
-
return { text: out.join("\n"), tier, topup };
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// The full set of tool names dispatchTool serves (hot catalog + cold tools). Used
|
|
338
|
-
// to reject an unknown tool before any graph load.
|
|
339
|
-
const DISPATCH_TOOLS = new Set([
|
|
340
|
-
"tmct_context", "tmct_context_more", "tmct_describe", "tmct_snippet", "tmct_signature",
|
|
341
|
-
"tmct_impact", "tmct_search", "tmct_members", "tmct_subclasses", "tmct_architecture",
|
|
342
|
-
"tmct_exports", "tmct_untested", "tmct_ask", "tmct_tests_for", "tmct_history",
|
|
343
|
-
"tmct_callers", "tmct_callees", "tmct_cochanges", "tmct_calls",
|
|
344
|
-
"tmct_file_history", "tmct_method_history", "tmct_class_history",
|
|
345
|
-
]);
|
|
346
|
-
|
|
347
|
-
export async function dispatchTool(name, args, { config, source = defaultSource, tel = null } = {}) {
|
|
348
|
-
// tmct_context builds (and loads) its own edit bundle — return early so we don't
|
|
349
|
-
// double-load the graph for it.
|
|
350
|
-
if (name === "tmct_context") {
|
|
351
|
-
return (await buildContextBundle(args, { config, source, tel })).text;
|
|
352
|
-
}
|
|
353
|
-
// Reject an unknown tool BEFORE touching the graph — preserves the original
|
|
354
|
-
// ordering (an unknown name never triggers a load).
|
|
355
|
-
if (!DISPATCH_TOOLS.has(name)) throw new ToolError(`unknown tool: ${name}`);
|
|
356
|
-
// Every other tool reads graph truth: load once and build the typed service
|
|
357
|
-
// object (the Repository Interface). dispatchTool is the presentation adapter —
|
|
358
|
-
// it delegates resolution + the miss/error contract to the service and formats
|
|
359
|
-
// the result with tmct's own render* layer (which reads svc.graph). This is the
|
|
360
|
-
// switch's operations extracted into a named, typed seam without changing bytes.
|
|
361
|
-
const graph = await loadGraph(config, source);
|
|
362
|
-
// repo root = the dir containing .tmct/ (graphFile = <repo>/.tmct/graph.json). Passed through
|
|
363
|
-
// to createGraphService (2e) so svc.snippet()/svc.context() are usable directly; this
|
|
364
|
-
// dispatcher still does its OWN safe read for tmct_snippet below (readSpanSafe/sliceSpan,
|
|
365
|
-
// Item 1) rather than delegating, to keep its richer presentation (candidates, call hints,
|
|
366
|
-
// truncation notices) — see the tmct_snippet branch below.
|
|
367
|
-
const repoRoot = dirname(dirname(config.graphFile));
|
|
368
|
-
const svc = createGraphService(graph, { sourceAccess: true, repoRoot, readFile, tel });
|
|
369
|
-
if (name === "tmct_context_more") {
|
|
370
|
-
const symbol = String(args?.symbol || "").trim();
|
|
371
|
-
if (!symbol) throw new ToolError("symbol is required");
|
|
372
|
-
const { match } = resolveOrThrow(svc, symbol, "symbol");
|
|
373
|
-
return renderContextMore(contextPlan(graph, match));
|
|
374
|
-
}
|
|
375
|
-
if (name === "tmct_describe") {
|
|
376
|
-
const symbol = String(args?.symbol || "").trim();
|
|
377
|
-
if (!symbol) throw new ToolError("symbol is required");
|
|
378
|
-
const { match, candidates } = resolveSymbol(svc.graph, symbol);
|
|
379
|
-
if (match) return renderDescribe(graph, match, { candidates }); // code-map wins when present
|
|
380
|
-
const fb = renderMemoryDefinition(await memoryFactRows(config), symbol);
|
|
381
|
-
if (fb) return fb;
|
|
382
|
-
resolveOrThrow(svc, symbol, "symbol"); // no code-map + no memory fact → the honest miss
|
|
383
|
-
}
|
|
384
|
-
if (name === "tmct_snippet") {
|
|
385
|
-
const symbol = String(args?.symbol || "").trim();
|
|
386
|
-
if (!symbol) throw new ToolError("symbol is required");
|
|
387
|
-
const { match, candidates } = resolveOrThrow(svc, symbol, "symbol");
|
|
388
|
-
const site = siteOf(match);
|
|
389
|
-
if (!site) {
|
|
390
|
-
throw new ToolError(
|
|
391
|
-
`"${match.label}" (${match.class || "Entity"}) has no source span in the graph — ` +
|
|
392
|
-
"it is likely a module. Use tmct_describe for its contents, then tmct_snippet one of the functions/classes it defines.",
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
let sliced;
|
|
396
|
-
try {
|
|
397
|
-
sliced = await readSpanSafe({
|
|
398
|
-
readFile, repoRoot, path: site.path, start: site.start, end: site.end, maxLines: SNIPPET_MAX_LINES,
|
|
399
|
-
});
|
|
400
|
-
} catch (e) {
|
|
401
|
-
if (e instanceof ToolError) throw e; // path-traversal guard: message already names the offending path
|
|
402
|
-
throw new ToolError(`could not read ${site.path} (${e?.code || e?.message || e})`);
|
|
403
|
-
}
|
|
404
|
-
const { text: body, truncated } = sliced;
|
|
405
|
-
const span = site.end > site.start ? `${site.start}-${site.end}` : `${site.start}`;
|
|
406
|
-
const header = `${match.label} — ${match.class || "Entity"} @ ${site.path}:${span}`;
|
|
407
|
-
const note = truncated ? `\n… (truncated to ${SNIPPET_MAX_LINES} lines; full span ${span})` : "";
|
|
408
|
-
const cand = candidates.length ? `\n(other matches: ${candidates.map((c) => c.label).join(", ")})` : "";
|
|
409
|
-
const hint = callHint(graph, match); // #4: one-line "calls in-repo: …" so the agent sees in-repo deps inline
|
|
410
|
-
return `${header}\n${body}${note}${hint ? `\n${hint}` : ""}${cand}`;
|
|
411
|
-
}
|
|
412
|
-
if (name === "tmct_signature") {
|
|
413
|
-
const symbol = String(args?.symbol || "").trim();
|
|
414
|
-
if (!symbol) throw new ToolError("symbol is required");
|
|
415
|
-
const { match } = resolveOrThrow(svc, symbol, "symbol");
|
|
416
|
-
return renderSignature(graph, match);
|
|
417
|
-
}
|
|
418
|
-
if (name === "tmct_impact") {
|
|
419
|
-
const module = String(args?.module || "").trim();
|
|
420
|
-
if (!module) throw new ToolError("module is required");
|
|
421
|
-
const { match } = resolveOrThrow(svc, module, "module");
|
|
422
|
-
return renderImpact(graph, match);
|
|
423
|
-
}
|
|
424
|
-
if (name === "tmct_search") {
|
|
425
|
-
const query = String(args?.query || "").trim();
|
|
426
|
-
const kind = String(args?.kind || "").trim();
|
|
427
|
-
if (!query && !kind) throw new ToolError("query is required");
|
|
428
|
-
const out = renderSearch(graph, query, {
|
|
429
|
-
kind,
|
|
430
|
-
decorator: String(args?.decorator || "").trim(),
|
|
431
|
-
name: String(args?.name || "").trim(),
|
|
432
|
-
});
|
|
433
|
-
// FALL-THROUGH: a code-map miss ("no module matches …") on a plain concept query still
|
|
434
|
-
// answers from the memory/corpus isa-family facts when the concept is known there.
|
|
435
|
-
if (!kind && /^no module matches/.test(out)) {
|
|
436
|
-
const fb = renderMemoryDefinition(await memoryFactRows(config), query);
|
|
437
|
-
if (fb) return fb;
|
|
438
|
-
}
|
|
439
|
-
return out;
|
|
440
|
-
}
|
|
441
|
-
if (name === "tmct_members") {
|
|
442
|
-
const symbol = String(args?.class || "").trim();
|
|
443
|
-
if (!symbol) throw new ToolError("class is required");
|
|
444
|
-
const { match } = resolveSymbol(svc.graph, symbol);
|
|
445
|
-
if (match) return renderMembers(graph, match); // code-map wins when present
|
|
446
|
-
// a concept's "members" in the corpus sense are its subclasses (its instances).
|
|
447
|
-
const fb = renderMemorySubclasses(await memoryFactRows(config), symbol);
|
|
448
|
-
if (fb) return fb;
|
|
449
|
-
resolveOrThrow(svc, symbol, "class"); // the honest miss
|
|
450
|
-
}
|
|
451
|
-
if (name === "tmct_subclasses") {
|
|
452
|
-
const symbol = String(args?.class || "").trim();
|
|
453
|
-
if (!symbol) throw new ToolError("class is required");
|
|
454
|
-
const { match } = resolveSymbol(svc.graph, symbol);
|
|
455
|
-
if (match) return renderSubclasses(graph, match); // code-map wins when present
|
|
456
|
-
const fb = renderMemorySubclasses(await memoryFactRows(config), symbol);
|
|
457
|
-
if (fb) return fb;
|
|
458
|
-
resolveOrThrow(svc, symbol, "class"); // no code-map subclass + no memory fact → honest miss
|
|
459
|
-
}
|
|
460
|
-
if (name === "tmct_architecture") {
|
|
461
|
-
return renderArchitecture(graph, { pkg: String(args?.package || "").trim() });
|
|
462
|
-
}
|
|
463
|
-
if (name === "tmct_exports") {
|
|
464
|
-
const module = String(args?.module || "").trim();
|
|
465
|
-
if (!module) throw new ToolError("module is required");
|
|
466
|
-
const { match } = resolveOrThrow(svc, module, "module");
|
|
467
|
-
return renderExports(graph, match);
|
|
468
|
-
}
|
|
469
|
-
if (name === "tmct_untested") {
|
|
470
|
-
return renderUntested(graph);
|
|
471
|
-
}
|
|
472
|
-
if (name === "tmct_ask") {
|
|
473
|
-
const query = String(args?.query || "").trim();
|
|
474
|
-
if (!query) throw new ToolError("query is required");
|
|
475
|
-
const { content, tmct_ask } = ask(graph, query);
|
|
476
|
-
// Every dispatchTool caller (the chat surface, the CLI fallback) expects a plain string —
|
|
477
|
-
// append the structured envelope as a delimited, machine-parseable block rather than
|
|
478
|
-
// changing that shared contract for one tool.
|
|
479
|
-
return `${content}\n\n---tmct_ask---\n${JSON.stringify(tmct_ask, null, 2)}`;
|
|
480
|
-
}
|
|
481
|
-
if (
|
|
482
|
-
name === "tmct_tests_for" || name === "tmct_history" || name === "tmct_callers" ||
|
|
483
|
-
name === "tmct_callees" || name === "tmct_cochanges" || name === "tmct_calls" ||
|
|
484
|
-
name === "tmct_file_history" || name === "tmct_method_history" || name === "tmct_class_history"
|
|
485
|
-
) {
|
|
486
|
-
const symbol = String(args?.symbol || "").trim();
|
|
487
|
-
if (!symbol) throw new ToolError("symbol is required");
|
|
488
|
-
const { match } = resolveOrThrow(svc, symbol, "symbol");
|
|
489
|
-
if (name === "tmct_tests_for") return renderTestsFor(graph, match);
|
|
490
|
-
if (name === "tmct_history") return renderHistory(graph, match);
|
|
491
|
-
if (name === "tmct_callers") return renderCallers(graph, match);
|
|
492
|
-
if (name === "tmct_cochanges") return renderCochanges(graph, match);
|
|
493
|
-
if (name === "tmct_calls") return renderCalls(graph, match);
|
|
494
|
-
if (name === "tmct_file_history") return renderFileHistory(graph, match);
|
|
495
|
-
if (name === "tmct_method_history") return renderMethodHistory(graph, match);
|
|
496
|
-
if (name === "tmct_class_history") return renderClassHistory(graph, match);
|
|
497
|
-
return renderCallees(graph, match);
|
|
498
|
-
}
|
|
499
|
-
throw new ToolError(`unknown tool: ${name}`);
|
|
500
|
-
}
|
|
501
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|