@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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// tmct_context — the sized "edit bundle" for one symbol: exemplar source, sibling
|
|
2
|
+
// signatures, the registration anchor and the insertion region, in one call.
|
|
3
|
+
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { dirname } from "node:path";
|
|
6
|
+
import { ToolError } from "../../adapters/config.mjs";
|
|
7
|
+
import { sliceSpan, readSpanSafe } from "../../adapters/source-slice.mjs";
|
|
8
|
+
import * as defaultSource from "../../adapters/source.mjs";
|
|
9
|
+
import { contextPlan, sizeBundle, bundleMask, trimBundleMask } from "../../domain/codegraph.mjs";
|
|
10
|
+
import { ask } from "../../domain/ask.mjs";
|
|
11
|
+
import { createGraphService } from "../../adapters/providers/graph-service.mjs";
|
|
12
|
+
import { loadGraph } from "../graph-load.mjs";
|
|
13
|
+
import { resolveOrThrow, SNIPPET_MAX_LINES } from "./kit.mjs";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build the tmct_context "edit bundle" for a symbol and return { text, tier, topup }.
|
|
17
|
+
* Shared by the tmct_context tool AND the `cli digest` arm (bin/tmct.mjs). `trim:true` renders
|
|
18
|
+
* a SECONDARY, signatures-only bundle (no bodies/tails) for related-but-not-primary
|
|
19
|
+
* digest modules.
|
|
20
|
+
*
|
|
21
|
+
* Section order is cache-stable: content identical across runs (anchor/registration/
|
|
22
|
+
* exemplar/siblings/__all__/insertion region) comes first; more variable, history-derived
|
|
23
|
+
* tails (covering tests, co-change) come last, so a stable prefix maximises prompt-cache
|
|
24
|
+
* reuse.
|
|
25
|
+
*/
|
|
26
|
+
export async function buildContextBundle(args, { config, source = defaultSource, trim = false, tel = null } = {}) {
|
|
27
|
+
const symbol = String(args?.symbol || "").trim();
|
|
28
|
+
if (!symbol) throw new ToolError("symbol is required");
|
|
29
|
+
const depth = String(args?.depth || "auto").trim().toLowerCase();
|
|
30
|
+
// Tuning-flag contract: `min` forces the LEANEST bundle (TINY mask, no top-up) regardless of
|
|
31
|
+
// exemplar length; `untuned` reproduces the earlier escalation (tuning #1 bypassed). Neither
|
|
32
|
+
// → the tuned default (sizeBundle's anchor-gated escalation).
|
|
33
|
+
const min = Boolean(args?.min);
|
|
34
|
+
const untuned = Boolean(args?.untuned);
|
|
35
|
+
// `max` forces the injection CEILING — FULL tier (every section + inlined depth-1 callee
|
|
36
|
+
// bodies) with top-up, and it OVERRIDES trim so even secondary modules get the full bundle. Used
|
|
37
|
+
// by the tmct-max arm to test whether more injection re-bloats.
|
|
38
|
+
const max = Boolean(args?.max);
|
|
39
|
+
const graph = await loadGraph(config, source);
|
|
40
|
+
// repo root = the dir containing .tmct/ (graphFile = <repo>/.tmct/graph.json) — computed
|
|
41
|
+
// before createGraphService so the RI service can be constructed source-capable: this
|
|
42
|
+
// module still does its OWN safe reads below (readSpanSafe/sliceSpan) rather than
|
|
43
|
+
// delegating to svc.context(). Passing sourceAccess through anyway keeps
|
|
44
|
+
// svc.snippet()/svc.context() usable by any future/external caller of this same service
|
|
45
|
+
// object without a second, divergent construction path. `tel` (optional) is an
|
|
46
|
+
// already-constructed telemetry sink threaded down from the caller (e.g. chat.mjs's
|
|
47
|
+
// session-level createTelemetry) — never minted here, so a caller that never passes one
|
|
48
|
+
// costs nothing extra (createGraphService's own wrapping loop no-ops on tel:null).
|
|
49
|
+
const repoRoot = dirname(dirname(config.graphFile));
|
|
50
|
+
const svc = createGraphService(graph, { sourceAccess: true, repoRoot, readFile, tel, ask });
|
|
51
|
+
const { match } = resolveOrThrow(svc, symbol, "symbol");
|
|
52
|
+
const plan = contextPlan(graph, match);
|
|
53
|
+
// Pick the section mask by depth — min forces TINY, full/max forces everything, auto runs
|
|
54
|
+
// the size classifier (lean TINY default + one-tier top-up when the edit needs it).
|
|
55
|
+
let tier;
|
|
56
|
+
let mask;
|
|
57
|
+
let topup = false;
|
|
58
|
+
if (min || depth === "min") { tier = "TINY"; mask = bundleMask("TINY"); }
|
|
59
|
+
else if (max || depth === "full") { tier = "FULL"; mask = bundleMask("FULL"); topup = true; }
|
|
60
|
+
else ({ tier, mask, topup } = sizeBundle(plan, graph, { untuned }));
|
|
61
|
+
if (trim && !max) mask = trimBundleMask(mask);
|
|
62
|
+
let lines = null;
|
|
63
|
+
if (plan.moduleLabel) {
|
|
64
|
+
try { ({ lines } = await readSpanSafe({ readFile, repoRoot, path: plan.moduleLabel })); }
|
|
65
|
+
catch { lines = null; }
|
|
66
|
+
}
|
|
67
|
+
const lineAt = (n) => (lines && lines[n - 1] != null ? lines[n - 1].trim() : "");
|
|
68
|
+
const sliceBody = (start, end) => sliceSpan(lines, start, end, SNIPPET_MAX_LINES).text;
|
|
69
|
+
const out = [
|
|
70
|
+
`Edit context for ${plan.moduleLabel} [${tier}${trim ? " secondary" : ""}] — assembled from the typed graph + that file. ` +
|
|
71
|
+
"You do NOT need to Read it; write the new code directly after reviewing this.",
|
|
72
|
+
];
|
|
73
|
+
// ---- cache-stable prefix: identical across runs ----
|
|
74
|
+
if (mask.anchor && plan.anchor?.site && lines) {
|
|
75
|
+
const { start, end } = plan.anchor.site;
|
|
76
|
+
out.push(`\n## anchor: ${plan.anchor.label} (${plan.anchor.class}) @ ${plan.moduleLabel}:${start}-${end}`);
|
|
77
|
+
out.push(sliceBody(start, end));
|
|
78
|
+
if (plan.callHint) out.push(plan.callHint);
|
|
79
|
+
}
|
|
80
|
+
if (mask.registration && plan.globals.length) {
|
|
81
|
+
out.push(`\n## registration / module globals (replicate this pattern):`);
|
|
82
|
+
for (const g of plan.globals) out.push(` ${g.label} = ${g.value}${g.site ? ` [:${g.site.start}]` : ""}`);
|
|
83
|
+
}
|
|
84
|
+
if (mask.exemplar && plan.exemplar?.site && lines) {
|
|
85
|
+
const { start, end } = plan.exemplar.site;
|
|
86
|
+
const dec = plan.exemplar.decorators ? ` @${plan.exemplar.decorators}` : "";
|
|
87
|
+
out.push(`\n## closest example (full body) — copy this style: ${plan.exemplar.label} (${plan.exemplar.class})${dec} @ ${plan.moduleLabel}:${start}-${end}`);
|
|
88
|
+
out.push(sliceBody(start, end));
|
|
89
|
+
if (plan.callHint) out.push(plan.callHint);
|
|
90
|
+
}
|
|
91
|
+
if (mask.inlinedCallees && plan.calleeBodies.length && lines) {
|
|
92
|
+
let budget = 120; // INLINE_CALLEE_LOC
|
|
93
|
+
for (const cb of plan.calleeBodies) {
|
|
94
|
+
if (budget <= 0) break;
|
|
95
|
+
const start = cb.site.start;
|
|
96
|
+
const fromThisFile = cb.site.path === plan.moduleLabel;
|
|
97
|
+
const bodyLines = fromThisFile && lines
|
|
98
|
+
? lines
|
|
99
|
+
: await readSpanSafe({ readFile, repoRoot, path: cb.site.path }).then((r) => r.lines).catch(() => null);
|
|
100
|
+
if (!bodyLines) continue;
|
|
101
|
+
const sliced = sliceSpan(bodyLines, start, cb.site.end, budget);
|
|
102
|
+
out.push(`\n## inlined callee body (depth-1 in-repo call): ${cb.label} @ ${cb.site.path}:${start}-${cb.site.end}`);
|
|
103
|
+
out.push(sliced.text);
|
|
104
|
+
budget -= (sliced.end - start + 1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (mask.classMembers && plan.classMembers && plan.classMembers.members.length) {
|
|
108
|
+
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):`);
|
|
109
|
+
for (const m of plan.classMembers.members) {
|
|
110
|
+
const short = String(m.label).split(".").pop();
|
|
111
|
+
const sig = m.params != null && m.params !== "" ? `(${m.params})${m.returns ? ` -> ${m.returns}` : ""}` : "";
|
|
112
|
+
const dec = m.decorators ? `@${m.decorators} ` : "";
|
|
113
|
+
const r = m.raises ? ` raises=${m.raises}` : "";
|
|
114
|
+
out.push(` ${m.class} ${short}${m.site ? ` :${m.site.start}` : ""} ${dec}${short}${sig}${r}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (mask.siblings && plan.siblings.length) {
|
|
118
|
+
out.push(`\n## sibling symbols to copy the style of (most relevant first; ${plan.siblings.length} total):`);
|
|
119
|
+
for (const s of plan.siblings.slice(0, plan.siblingCap)) {
|
|
120
|
+
const sig = s.site ? lineAt(s.site.start) : "";
|
|
121
|
+
const dec = s.decorators ? `@${s.decorators} ` : "";
|
|
122
|
+
const r = s.raises ? ` raises=${s.raises}` : "";
|
|
123
|
+
out.push(` ${s.class} ${s.label}${s.site ? ` :${s.site.start}` : ""} ${dec}${sig}${r}`);
|
|
124
|
+
}
|
|
125
|
+
if (plan.siblings.length > plan.siblingCap) {
|
|
126
|
+
out.push(` …+${plan.siblings.length - plan.siblingCap} more (use tmct_search kind=function or tmct_snippet <name> for any of them)`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (mask.allExports && plan.allExports) {
|
|
130
|
+
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}`);
|
|
131
|
+
}
|
|
132
|
+
if (mask.reexports && plan.exports && plan.exports.length) out.push(`\n## re-exported symbols (resolved __all__ → defining module): ${plan.exports.join(", ")}`);
|
|
133
|
+
// The contiguous insertion region is part of the STABLE prefix — always present (even at
|
|
134
|
+
// TINY) so the agent never needs to Read the file to place the edit.
|
|
135
|
+
if (mask.insertionRegion && plan.insertionRegion && lines) {
|
|
136
|
+
const start = plan.insertionRegion.start;
|
|
137
|
+
const end = Math.min(lines.length, start + 40 - 1); // INSERTION_REGION_CAP
|
|
138
|
+
out.push(`\n## insertion region (write your new sibling here) — ${plan.moduleLabel}:${start}-${end}`);
|
|
139
|
+
out.push(lines.slice(start - 1, end).map((l, i) => `${start + i}\t${l}`).join("\n"));
|
|
140
|
+
} else if (plan.insertion) {
|
|
141
|
+
out.push(`\n## insert the new sibling after line ~${plan.insertion} (end of the last top-level definition).`);
|
|
142
|
+
}
|
|
143
|
+
// ---- variable tail: history-derived, kept LAST so the prefix stays cache-stable ----
|
|
144
|
+
if (mask.tests && plan.tests.length) out.push(`\n## covering tests: ${plan.tests.join(", ")}`);
|
|
145
|
+
if (mask.cochange && plan.cochange && plan.cochange.length) {
|
|
146
|
+
out.push(`\n## usually changed together (consider editing these too): ${plan.cochange.map((c) => `${c.label} (×${c.weight})`).join(", ")}`);
|
|
147
|
+
}
|
|
148
|
+
out.push(`\nYou now have the snippet, the sibling style, the registration anchor and the tests. ` +
|
|
149
|
+
`Write the new code with Edit/Write — do NOT Read ${plan.moduleLabel}.`);
|
|
150
|
+
if (tier !== "FULL") {
|
|
151
|
+
out.push(`(bundle tier ${tier}; for any omitted sections run tmct_context_more {"symbol":"${symbol}"}, or tmct_context with depth="full".)`);
|
|
152
|
+
}
|
|
153
|
+
return { text: out.join("\n"), tier, topup };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function tmct_context(args, { config, source, tel }) {
|
|
157
|
+
return (await buildContextBundle(args, { config, source, tel })).text;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// buildContextBundle validates `symbol` before it loads anything and then loads the graph
|
|
161
|
+
// itself, so dispatch hands this handler the raw config and skips its own load rather than
|
|
162
|
+
// loading the graph twice.
|
|
163
|
+
tmct_context.ownsGraphLoad = true;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// tmct_describe — locate one symbol and list its typed edges, falling through to the
|
|
2
|
+
// memory/corpus facts when the concept is known there but absent from the code-map.
|
|
3
|
+
|
|
4
|
+
import { renderDescribe, resolveSymbol } from "../../domain/codegraph.mjs";
|
|
5
|
+
import { memoryFactRows, renderMemoryDefinition } from "../memory-fallthrough.mjs";
|
|
6
|
+
import { requiredArg, resolveOrThrow } from "./kit.mjs";
|
|
7
|
+
|
|
8
|
+
export async function tmct_describe(args, { graph, svc, config }) {
|
|
9
|
+
const symbol = requiredArg(args, "symbol");
|
|
10
|
+
const { match, candidates } = resolveSymbol(svc.graph, symbol);
|
|
11
|
+
if (match) return renderDescribe(graph, match, { candidates }); // code-map wins when present
|
|
12
|
+
const fallback = renderMemoryDefinition(await memoryFactRows(config), symbol);
|
|
13
|
+
if (fallback) return fallback;
|
|
14
|
+
resolveOrThrow(svc, symbol, "symbol"); // no code-map + no memory fact → the honest miss
|
|
15
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// tmct_exports — a module's public __all__ surface, each name resolved to its definer.
|
|
2
|
+
|
|
3
|
+
import { renderExports } from "../../domain/codegraph.mjs";
|
|
4
|
+
import { requiredArg, resolveOrThrow } from "./kit.mjs";
|
|
5
|
+
|
|
6
|
+
export function tmct_exports(args, { graph, svc }) {
|
|
7
|
+
const { match } = resolveOrThrow(svc, requiredArg(args, "module"), "module");
|
|
8
|
+
return renderExports(graph, match);
|
|
9
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// tmct_file_history — commits that touched a symbol's module, with author / date / subject.
|
|
2
|
+
|
|
3
|
+
import { renderFileHistory } from "../../domain/codegraph.mjs";
|
|
4
|
+
import { symbolHandler } from "./kit.mjs";
|
|
5
|
+
|
|
6
|
+
export const tmct_file_history = symbolHandler(renderFileHistory);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// tmct_impact — transitive reverse closure over imports/calls: what breaks if a module changes.
|
|
2
|
+
|
|
3
|
+
import { renderImpact } from "../../domain/codegraph.mjs";
|
|
4
|
+
import { requiredArg, resolveOrThrow } from "./kit.mjs";
|
|
5
|
+
|
|
6
|
+
export function tmct_impact(args, { graph, svc }) {
|
|
7
|
+
const { match } = resolveOrThrow(svc, requiredArg(args, "module"), "module");
|
|
8
|
+
return renderImpact(graph, match);
|
|
9
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// tmct_members — a class's methods and attributes. A concept's "members" in the corpus
|
|
2
|
+
// sense are its subclasses (its instances), which is the fall-through when the code-map
|
|
3
|
+
// has no such class.
|
|
4
|
+
|
|
5
|
+
import { renderMembers, resolveSymbol } from "../../domain/codegraph.mjs";
|
|
6
|
+
import { memoryFactRows, renderMemorySubclasses } from "../memory-fallthrough.mjs";
|
|
7
|
+
import { requiredArg, resolveOrThrow } from "./kit.mjs";
|
|
8
|
+
|
|
9
|
+
export async function tmct_members(args, { graph, svc, config }) {
|
|
10
|
+
const symbol = requiredArg(args, "class");
|
|
11
|
+
const { match } = resolveSymbol(svc.graph, symbol);
|
|
12
|
+
if (match) return renderMembers(graph, match); // code-map wins when present
|
|
13
|
+
const fallback = renderMemorySubclasses(await memoryFactRows(config), symbol);
|
|
14
|
+
if (fallback) return fallback;
|
|
15
|
+
resolveOrThrow(svc, symbol, "class"); // the honest miss
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// tmct_search — free-text/ranked lookup over the code-map, falling through to the
|
|
2
|
+
// memory/corpus facts when a plain concept query misses the code-map entirely.
|
|
3
|
+
|
|
4
|
+
import { ToolError } from "../../adapters/config.mjs";
|
|
5
|
+
import { renderSearch } from "../../domain/codegraph.mjs";
|
|
6
|
+
import { memoryFactRows, renderMemoryDefinition } from "../memory-fallthrough.mjs";
|
|
7
|
+
|
|
8
|
+
export async function tmct_search(args, { graph, config }) {
|
|
9
|
+
const query = String(args?.query || "").trim();
|
|
10
|
+
const kind = String(args?.kind || "").trim();
|
|
11
|
+
if (!query && !kind) throw new ToolError("query is required");
|
|
12
|
+
const out = renderSearch(graph, query, {
|
|
13
|
+
kind,
|
|
14
|
+
decorator: String(args?.decorator || "").trim(),
|
|
15
|
+
name: String(args?.name || "").trim(),
|
|
16
|
+
});
|
|
17
|
+
if (!kind && /^no module matches/.test(out)) {
|
|
18
|
+
const fallback = renderMemoryDefinition(await memoryFactRows(config), query);
|
|
19
|
+
if (fallback) return fallback;
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// tmct_snippet — the exact source of one symbol's line span, plus a one-line in-repo
|
|
2
|
+
// call hint. Does its own safe span read rather than delegating to svc.snippet(), which
|
|
3
|
+
// would drop the candidates / call hint / truncation notice this presentation carries.
|
|
4
|
+
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { ToolError } from "../../adapters/config.mjs";
|
|
7
|
+
import { readSpanSafe } from "../../adapters/source-slice.mjs";
|
|
8
|
+
import { siteOf, callHint } from "../../domain/codegraph.mjs";
|
|
9
|
+
import { requiredArg, resolveOrThrow, SNIPPET_MAX_LINES } from "./kit.mjs";
|
|
10
|
+
|
|
11
|
+
export async function tmct_snippet(args, { graph, svc, repoRoot }) {
|
|
12
|
+
const symbol = requiredArg(args, "symbol");
|
|
13
|
+
const { match, candidates } = resolveOrThrow(svc, symbol, "symbol");
|
|
14
|
+
const site = siteOf(match);
|
|
15
|
+
if (!site) {
|
|
16
|
+
throw new ToolError(
|
|
17
|
+
`"${match.label}" (${match.class || "Entity"}) has no source span in the graph — ` +
|
|
18
|
+
"it is likely a module. Use tmct_describe for its contents, then tmct_snippet one of the functions/classes it defines.",
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
let sliced;
|
|
22
|
+
try {
|
|
23
|
+
sliced = await readSpanSafe({
|
|
24
|
+
readFile, repoRoot, path: site.path, start: site.start, end: site.end, maxLines: SNIPPET_MAX_LINES,
|
|
25
|
+
});
|
|
26
|
+
} catch (e) {
|
|
27
|
+
if (e instanceof ToolError) throw e; // path-traversal guard: message already names the offending path
|
|
28
|
+
throw new ToolError(`could not read ${site.path} (${e?.code || e?.message || e})`);
|
|
29
|
+
}
|
|
30
|
+
const { text: body, truncated } = sliced;
|
|
31
|
+
const span = site.end > site.start ? `${site.start}-${site.end}` : `${site.start}`;
|
|
32
|
+
const header = `${match.label} — ${match.class || "Entity"} @ ${site.path}:${span}`;
|
|
33
|
+
const note = truncated ? `\n… (truncated to ${SNIPPET_MAX_LINES} lines; full span ${span})` : "";
|
|
34
|
+
const cand = candidates.length ? `\n(other matches: ${candidates.map((c) => c.label).join(", ")})` : "";
|
|
35
|
+
const hint = callHint(graph, match);
|
|
36
|
+
return `${header}\n${body}${note}${hint ? `\n${hint}` : ""}${cand}`;
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// tmct_subclasses — a class's bases plus the transitive set of classes that extend it,
|
|
2
|
+
// falling through to the memory/corpus isa-family facts for a concept the code-map
|
|
3
|
+
// doesn't hold.
|
|
4
|
+
|
|
5
|
+
import { renderSubclasses, resolveSymbol } from "../../domain/codegraph.mjs";
|
|
6
|
+
import { memoryFactRows, renderMemorySubclasses } from "../memory-fallthrough.mjs";
|
|
7
|
+
import { requiredArg, resolveOrThrow } from "./kit.mjs";
|
|
8
|
+
|
|
9
|
+
export async function tmct_subclasses(args, { graph, svc, config }) {
|
|
10
|
+
const symbol = requiredArg(args, "class");
|
|
11
|
+
const { match } = resolveSymbol(svc.graph, symbol);
|
|
12
|
+
if (match) return renderSubclasses(graph, match); // code-map wins when present
|
|
13
|
+
const fallback = renderMemorySubclasses(await memoryFactRows(config), symbol);
|
|
14
|
+
if (fallback) return fallback;
|
|
15
|
+
resolveOrThrow(svc, symbol, "class"); // no code-map subclass + no memory fact → honest miss
|
|
16
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Read-only consumers of the conversational-memory graph (corpus facts, separate from
|
|
2
|
+
// the code-map graph.json). The fall-through the concept-shaped tools use: when the
|
|
3
|
+
// code-map resolves nothing for a concept query, answer from the reified isa-family
|
|
4
|
+
// facts instead, always citing provenance.
|
|
5
|
+
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
import { loadMemory, readFactRows, normFactTerm } from "../adapters/memory/core.mjs";
|
|
8
|
+
|
|
9
|
+
// The reified isa-family predicates a memory Fact carries ("<subject> rdfs:subClassOf
|
|
10
|
+
// <object>" / "rdf:type"): subject IS-A object. Subclasses of X = facts whose OBJECT is X;
|
|
11
|
+
// superclasses of X = facts whose SUBJECT is X. (Matches chat.mjs's ISA_PREDICATES.)
|
|
12
|
+
const ISA_PREDICATES = new Set(["rdfs:subClassOf", "rdf:type"]);
|
|
13
|
+
const MEMORY_LIST_CAP = 40;
|
|
14
|
+
|
|
15
|
+
/** Load the conversational-memory Facts as trust-bearing rows, failure-tolerant (no memory
|
|
16
|
+
* store / unreadable → [], so the tool still returns its honest code-map miss). repoRoot is
|
|
17
|
+
* the dir that CONTAINS .tmct/ (graphFile = <repo>/.tmct/graph.json), which is exactly the
|
|
18
|
+
* `dir` loadMemory joins MEMORY_GRAPH_REL onto. */
|
|
19
|
+
export async function memoryFactRows(config) {
|
|
20
|
+
try {
|
|
21
|
+
return readFactRows(await loadMemory(dirname(dirname(config.graphFile))));
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A short provenance receipt for a set of memory rows — distinct source strings, capped. */
|
|
28
|
+
function memoryProvenance(rows) {
|
|
29
|
+
const provs = [...new Set(rows.map((r) => r.provenance).filter(Boolean))];
|
|
30
|
+
if (!provs.length) return "provenance: memory/corpus facts";
|
|
31
|
+
const shown = provs.slice(0, 2).join("; ");
|
|
32
|
+
return `provenance: ${shown}${provs.length > 2 ? `, +${provs.length - 2} more source(s)` : ""}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Subclasses of a concept from the reified isa-family facts (subjects of
|
|
36
|
+
* "<subj> subClassOf <term>"). Null when the term names no such facts, so the caller can
|
|
37
|
+
* keep the honest code-map miss. Provenance is always cited. */
|
|
38
|
+
export function renderMemorySubclasses(rows, term) {
|
|
39
|
+
const t = normFactTerm(term);
|
|
40
|
+
const hits = rows.filter((r) => ISA_PREDICATES.has(r.predicate) && r.object === t);
|
|
41
|
+
if (!hits.length) return null;
|
|
42
|
+
const labels = [...new Set(hits.map((r) => r.subject))].sort();
|
|
43
|
+
const shown = labels.slice(0, MEMORY_LIST_CAP);
|
|
44
|
+
const tail = labels.length > MEMORY_LIST_CAP ? `\n …+${labels.length - MEMORY_LIST_CAP} more` : "";
|
|
45
|
+
return `"${term}" is not a code-map entity — answering from memory/corpus facts. ` +
|
|
46
|
+
`${labels.length} known subclass(es):\n ${shown.join("\n ")}${tail}\n(${memoryProvenance(hits)})`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A concept's DEFINITION from the isa-family facts — its superclasses ("is a …") plus a
|
|
50
|
+
* count/sample of its known subclasses. Null when the term names no facts. */
|
|
51
|
+
export function renderMemoryDefinition(rows, term) {
|
|
52
|
+
const t = normFactTerm(term);
|
|
53
|
+
const isa = rows.filter((r) => ISA_PREDICATES.has(r.predicate) && (r.subject === t || r.object === t));
|
|
54
|
+
if (!isa.length) return null;
|
|
55
|
+
const supers = [...new Set(isa.filter((r) => r.subject === t).map((r) => r.object))];
|
|
56
|
+
const subs = [...new Set(isa.filter((r) => r.object === t).map((r) => r.subject))].sort();
|
|
57
|
+
const lines = [`"${term}" is not a code-map entity — answering from memory/corpus facts.`];
|
|
58
|
+
if (supers.length) lines.push(`is a: ${supers.slice(0, MEMORY_LIST_CAP).join(", ")}`);
|
|
59
|
+
if (subs.length) {
|
|
60
|
+
const tail = subs.length > MEMORY_LIST_CAP ? `, +${subs.length - MEMORY_LIST_CAP} more` : "";
|
|
61
|
+
lines.push(`known subclasses (${subs.length}): ${subs.slice(0, MEMORY_LIST_CAP).join(", ")}${tail}`);
|
|
62
|
+
}
|
|
63
|
+
lines.push(`(${memoryProvenance(isa)})`);
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// schema-docs.mjs — the single source of truth for tmct's own ontology documentation:
|
|
2
|
-
// every entity class and predicate tmct's typed graph (src/graph-build.mjs's
|
|
2
|
+
// every entity class and predicate tmct's typed graph (src/adapters/graph-build.mjs's
|
|
3
3
|
// buildEntities) actually emits, verified against real `prop:`/`class:` literals.
|
|
4
4
|
// `ingestSchemaDocs()` merges it into a graph build so "what does cochange mean" or
|
|
5
5
|
// "what is a Commit" is answerable by querying the graph like any other question.
|
|
@@ -0,0 +1,61 @@
|
|
|
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/adapters/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 entry
|
|
5
|
+
// the chat surface and the `cli <tool>` route call into.
|
|
6
|
+
//
|
|
7
|
+
// This module is the dispatch entry only: it maps a tool name to the handler module
|
|
8
|
+
// that owns it (handlers/, one module per tool), loads the graph once, builds the
|
|
9
|
+
// typed service object (the Repository Interface) and hands both to the handler.
|
|
10
|
+
// Each tool 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
|
+
import { readFile } from "node:fs/promises";
|
|
14
|
+
import { dirname } from "node:path";
|
|
15
|
+
import { HOT_TOOLS } from "./definitions.mjs";
|
|
16
|
+
import { ToolError } from "../adapters/config.mjs";
|
|
17
|
+
import * as defaultSource from "../adapters/source.mjs";
|
|
18
|
+
import { ask } from "../domain/ask.mjs";
|
|
19
|
+
import { createGraphService } from "../adapters/providers/graph-service.mjs";
|
|
20
|
+
import { loadGraph } from "./graph-load.mjs";
|
|
21
|
+
import { HANDLERS } from "./handlers/index.mjs";
|
|
22
|
+
import { setDefaultNlpAdapter } from "../domain/interpret/nlp-registry.mjs";
|
|
23
|
+
import { setConstructionBanks } from "../domain/interpret/strategies/constructions.mjs";
|
|
24
|
+
import { nlpAdapter } from "../adapters/ask-nlp.mjs";
|
|
25
|
+
import { readConstructionFiles } from "../adapters/corpus/construction-banks.mjs";
|
|
26
|
+
|
|
27
|
+
// Composition: the tool layer supplies the domain parser's default lemma/POS
|
|
28
|
+
// adapter and the construction-grammar banks (lazy loader), so dispatchTool-
|
|
29
|
+
// only consumers get the same NL tiers chat wires.
|
|
30
|
+
setDefaultNlpAdapter(nlpAdapter);
|
|
31
|
+
setConstructionBanks(readConstructionFiles);
|
|
32
|
+
|
|
33
|
+
export { loadGraph } from "./graph-load.mjs";
|
|
34
|
+
export { buildContextBundle } from "./handlers/tmct-context.mjs";
|
|
35
|
+
|
|
36
|
+
// Tiered tool surface: the hot tools carry full descriptions/schemas in this
|
|
37
|
+
// catalog; every COLD tool (describe/members/impact/history/…) is still served
|
|
38
|
+
// by dispatchTool below and is reachable via the CLI `cli <tool>` route +
|
|
39
|
+
// the <repo>/.tmct/TOOLS.md catalog `tmct init` writes (renderToolsCatalog). Both
|
|
40
|
+
// tiers, and the schemas themselves, come from the tool definitions.
|
|
41
|
+
export const TOOLS = HOT_TOOLS.map(({ name, agentDescription, inputSchema }) => ({
|
|
42
|
+
name,
|
|
43
|
+
description: agentDescription,
|
|
44
|
+
inputSchema,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
export async function dispatchTool(name, args, { config, source = defaultSource, tel = null } = {}) {
|
|
48
|
+
// Reject an unknown tool BEFORE touching the graph — an unknown name never
|
|
49
|
+
// triggers a load. hasOwn, so an inherited name ("constructor", "toString")
|
|
50
|
+
// is unknown rather than a callable found on the prototype chain.
|
|
51
|
+
if (!Object.hasOwn(HANDLERS, name)) throw new ToolError(`unknown tool: ${name}`);
|
|
52
|
+
const handle = HANDLERS[name];
|
|
53
|
+
if (handle.ownsGraphLoad) return handle(args, { config, source, tel });
|
|
54
|
+
const graph = await loadGraph(config, source);
|
|
55
|
+
// repo root = the dir containing .tmct/ (graphFile = <repo>/.tmct/graph.json). Passed to
|
|
56
|
+
// createGraphService so svc.snippet()/svc.context() are usable directly, and on to the
|
|
57
|
+
// handlers that do their own safe source reads.
|
|
58
|
+
const repoRoot = dirname(dirname(config.graphFile));
|
|
59
|
+
const svc = createGraphService(graph, { sourceAccess: true, repoRoot, readFile, tel, ask });
|
|
60
|
+
return handle(args, { graph, svc, config, repoRoot });
|
|
61
|
+
}
|
package/src/answer-variants.mjs
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// answer-variants.mjs — deterministic, committed answer-phrasing variety for
|
|
2
|
-
// a curated set of answer templates (answer-variants.json).
|
|
3
|
-
//
|
|
4
|
-
// Selection is a pure hash of (poolId, key), never Math.random/Date.now, so
|
|
5
|
-
// output is byte-identical across runs (pinnable in tests). Load is lazy and
|
|
6
|
-
// failure-tolerant (missing file, or a stubbed node:fs in the browser bundle,
|
|
7
|
-
// both degrade to returning `base`) rather than throwing.
|
|
8
|
-
|
|
9
|
-
import { readFileSync } from "node:fs";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
11
|
-
import { dirname, join } from "node:path";
|
|
12
|
-
import { createHash } from "node:crypto";
|
|
13
|
-
|
|
14
|
-
const DATA_FILE = join(dirname(fileURLToPath(import.meta.url)), "answer-variants.json");
|
|
15
|
-
|
|
16
|
-
let dataCache; // undefined = not yet attempted; null = load failed/unavailable
|
|
17
|
-
function loadData() {
|
|
18
|
-
if (dataCache !== undefined) return dataCache;
|
|
19
|
-
try {
|
|
20
|
-
dataCache = JSON.parse(readFileSync(DATA_FILE, "utf8"));
|
|
21
|
-
} catch {
|
|
22
|
-
dataCache = null;
|
|
23
|
-
}
|
|
24
|
-
return dataCache;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Deterministically choose `base` or one of `poolId`'s committed variants,
|
|
28
|
-
* keyed on `key`. Any failure (unknown pool, unreadable data, falsy `key`)
|
|
29
|
-
* falls back to `base`. */
|
|
30
|
-
export function pickPhrase(poolId, key, base) {
|
|
31
|
-
if (!key) return base;
|
|
32
|
-
const data = loadData();
|
|
33
|
-
const variants = data?.pools?.[poolId]?.variants;
|
|
34
|
-
if (!Array.isArray(variants) || !variants.length) return base;
|
|
35
|
-
const forms = [base, ...variants];
|
|
36
|
-
const digest = createHash("sha256").update(`${poolId}:${String(key)}`).digest();
|
|
37
|
-
const idx = digest[0] % forms.length;
|
|
38
|
-
return forms[idx];
|
|
39
|
-
}
|
package/src/hash.mjs
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// hash.mjs — the single home for tmct's content-address hash.
|
|
2
|
-
//
|
|
3
|
-
// FNV-1a 32-bit is deliberately home-grown: it must be synchronous, browser-safe,
|
|
4
|
-
// dependency-free, and — critically — CROSS-VERSION STABLE, because fact ids are
|
|
5
|
-
// content-addressed by it and a fact's id is its identity across the whole memory
|
|
6
|
-
// graph. Every library candidate fails at least one of those; this eight-line
|
|
7
|
-
// function fails none. It lives here, once, so the fact-id contract has exactly
|
|
8
|
-
// one definition.
|
|
9
|
-
|
|
10
|
-
/** FNV-1a 32-bit. Returns the unsigned 32-bit integer (0 … 2^32−1). */
|
|
11
|
-
export function fnv1a32(str) {
|
|
12
|
-
let h = 0x811c9dc5;
|
|
13
|
-
for (let i = 0; i < str.length; i += 1) {
|
|
14
|
-
h ^= str.charCodeAt(i);
|
|
15
|
-
h = Math.imul(h, 0x01000193);
|
|
16
|
-
}
|
|
17
|
-
return h >>> 0;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** FNV-1a 32-bit as a zero-padded 8-char hex string — the stable content-address
|
|
21
|
-
* used for fact ids (`fact:<hex>`). Same (s,p,o) → same id → upsert, never a dup. */
|
|
22
|
-
export function fnv1aHex(str) {
|
|
23
|
-
return fnv1a32(str).toString(16).padStart(8, "0");
|
|
24
|
-
}
|