@warmdrift/kgauto-check 0.1.0
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 +58 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +10 -0
- package/dist/chunk-WJEKN4BL.js +443 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.js +22 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @warmdrift/kgauto-check
|
|
2
|
+
|
|
3
|
+
**Point your AI at your project.**
|
|
4
|
+
|
|
5
|
+
If you're the human: copy the block below to your coding agent (Claude Code, Cursor, or any MCP-capable agent). That's your whole job.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Add the kgauto check MCP server and run the check on this project:
|
|
9
|
+
claude mcp add kgauto-check -- npx -y @warmdrift/kgauto-check
|
|
10
|
+
Then call its start_check tool and follow the instructions.
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Everything below is for the agent.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What this is (for you, the agent)
|
|
18
|
+
|
|
19
|
+
A local, **fully offline** MCP server that checks a project's AI call surface against kgauto's doc-verified model catalog — lifecycle statuses, prices with verification dates, context/output/tool limits, capability flags (parallel tool calls, structured output), and known quality cliffs that the kgauto compiler executes in production.
|
|
20
|
+
|
|
21
|
+
**The trust boundary:** you read the project's code (you already have access); kgauto reads nothing. The extract you build carries identifiers and counts only — never prompt text, source code, or secrets. This check ran locally against kgauto's bundled model catalog. Nothing is uploaded; no rows are written anywhere. There is no code path that could make that sentence false: the server makes no network calls.
|
|
22
|
+
|
|
23
|
+
## Tools
|
|
24
|
+
|
|
25
|
+
| Tool | What it does |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `start_check` | Call first. Returns extraction instructions — what to read from the project and how to build the extract. |
|
|
28
|
+
| `run_check` | Runs the deterministic check on your extract. Returns a findings card grouped by confidence tier — present it to your human **verbatim**. |
|
|
29
|
+
| `get_model_facts` | One model id → status, verified prices, limits, capabilities, known cliffs. Honest miss on unknown models. |
|
|
30
|
+
| `get_observe_day_plan` | Rung 2: what a one-day observe-only run on real traffic would measure, and the two honesty requirements any setup must carry. |
|
|
31
|
+
|
|
32
|
+
## The confidence tiers (why the card is grouped)
|
|
33
|
+
|
|
34
|
+
- **certain** — a catalog or code fact. Stated flatly.
|
|
35
|
+
- **likely** — priced from your token estimates against verified prices.
|
|
36
|
+
- **measurable-only** — honestly unknowable from a code read: actual failure rates, actual input-waste, spend distribution, quality-at-cheaper-price. Each of these is exactly what the one-day observe run answers.
|
|
37
|
+
|
|
38
|
+
kgauto refuses to fabricate verdicts: unknown models get an honest "no measured evidence", and cheaper candidates are named with their price arithmetic only — never with an unmeasured quality claim.
|
|
39
|
+
|
|
40
|
+
## Client setup variants
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Claude Code
|
|
44
|
+
claude mcp add kgauto-check -- npx -y @warmdrift/kgauto-check
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
// Cursor / generic mcpServers config
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"kgauto-check": { "command": "npx", "args": ["-y", "@warmdrift/kgauto-check"] }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## What's underneath
|
|
57
|
+
|
|
58
|
+
The catalog is `@warmdrift/kgauto-compiler`'s bundled roster — the same executable model knowledge (cliffs, prices, capabilities) that kgauto's prompt compiler runs in production for its consumer apps. This package is a real dependency on it, not a copy: the check and the compiler cannot drift apart.
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
// src/extract.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var CallSiteSchema = z.object({
|
|
4
|
+
/** Agent-chosen stable label, e.g. "api/summarize.ts:42" or "hunt loop". */
|
|
5
|
+
id: z.string().min(1),
|
|
6
|
+
/** Model id exactly as configured in the project. */
|
|
7
|
+
model: z.string().min(1),
|
|
8
|
+
/** How the model id is selected at this site. */
|
|
9
|
+
modelSelection: z.enum(["hardcoded", "config", "env", "dynamic"]).optional(),
|
|
10
|
+
/**
|
|
11
|
+
* Best-guess intent archetype from kgauto's dialect (ask, classify,
|
|
12
|
+
* critique, extract, generate, hunt, plan, summarize, transform).
|
|
13
|
+
* Free-form here; validated softly against the roster's dialect.
|
|
14
|
+
*/
|
|
15
|
+
archetype: z.string().optional(),
|
|
16
|
+
/** Typical input size in tokens (estimate is fine; ~4 chars/token). */
|
|
17
|
+
estInputTokens: z.number().nonnegative().optional(),
|
|
18
|
+
/** Worst-case input size in tokens, if larger than typical. */
|
|
19
|
+
maxInputTokens: z.number().nonnegative().optional(),
|
|
20
|
+
/** Typical output size in tokens. */
|
|
21
|
+
estOutputTokens: z.number().nonnegative().optional(),
|
|
22
|
+
/** Number of tool definitions sent with the call. */
|
|
23
|
+
toolCount: z.number().int().nonnegative().optional(),
|
|
24
|
+
/** Does the site rely on the model making parallel tool calls? */
|
|
25
|
+
usesParallelToolCalls: z.boolean().optional(),
|
|
26
|
+
/** Does the site parse the response as JSON / expect schema output? */
|
|
27
|
+
expectsStructuredOutput: z.boolean().optional(),
|
|
28
|
+
streaming: z.boolean().optional(),
|
|
29
|
+
/** What happens when the call fails. */
|
|
30
|
+
retry: z.enum(["none", "same-model", "fallback-chain", "unknown"]).optional(),
|
|
31
|
+
/** Fallback model ids, when retry is 'fallback-chain'. */
|
|
32
|
+
fallbackModels: z.array(z.string()).optional(),
|
|
33
|
+
/** Rough call volume, if inferable (cron cadence, traffic, logs). */
|
|
34
|
+
estCallsPerMonth: z.number().positive().optional(),
|
|
35
|
+
/** Anything structural the agent thinks matters. Never prompt text. */
|
|
36
|
+
notes: z.string().max(500).optional()
|
|
37
|
+
});
|
|
38
|
+
var ExtractSchema = z.object({
|
|
39
|
+
project: z.object({
|
|
40
|
+
name: z.string().min(1),
|
|
41
|
+
description: z.string().max(500).optional()
|
|
42
|
+
}),
|
|
43
|
+
callSites: z.array(CallSiteSchema).min(1),
|
|
44
|
+
notes: z.string().max(1e3).optional()
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// src/engine.ts
|
|
48
|
+
import {
|
|
49
|
+
allProfiles,
|
|
50
|
+
tryGetProfile,
|
|
51
|
+
resolveModelAlias,
|
|
52
|
+
isArchetype,
|
|
53
|
+
LIBRARY_VERSION
|
|
54
|
+
} from "@warmdrift/kgauto-compiler";
|
|
55
|
+
var DESTINATION_SENTENCE = "This check ran locally against kgauto\u2019s bundled model catalog. Nothing was uploaded; no rows were written anywhere.";
|
|
56
|
+
var defaultResolve = (id) => {
|
|
57
|
+
const canonical = resolveModelAlias(id);
|
|
58
|
+
return { canonical, profile: tryGetProfile(id) };
|
|
59
|
+
};
|
|
60
|
+
function perCallCostUsd(p, inTok, outTok) {
|
|
61
|
+
return inTok / 1e6 * p.costInputPer1m + outTok / 1e6 * p.costOutputPer1m;
|
|
62
|
+
}
|
|
63
|
+
function runCheck(extract, opts = {}) {
|
|
64
|
+
const resolve = opts.resolve ?? defaultResolve;
|
|
65
|
+
const roster = opts.roster ?? allProfiles;
|
|
66
|
+
const findings = [];
|
|
67
|
+
let n = 0;
|
|
68
|
+
const add = (f) => {
|
|
69
|
+
findings.push({ id: `F${++n}`, ...f });
|
|
70
|
+
};
|
|
71
|
+
const unknownModels = /* @__PURE__ */ new Set();
|
|
72
|
+
for (const site of extract.callSites) {
|
|
73
|
+
const { canonical, profile } = resolve(site.model);
|
|
74
|
+
const at = [site.id];
|
|
75
|
+
if (canonical !== site.model) {
|
|
76
|
+
add({
|
|
77
|
+
severity: "yellow",
|
|
78
|
+
confidence: "certain",
|
|
79
|
+
title: `\`${site.model}\` is a provider compat alias for \`${canonical}\``,
|
|
80
|
+
detail: `The provider silently serves a different model than the id you configured. Aliases hide capability and price drift \u2014 the served model's context window and prices can differ by 10\u201350\xD7 from what the alias name suggests, with no error.`,
|
|
81
|
+
basis: "kgauto alias table (verified against provider docs)",
|
|
82
|
+
callSites: at
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (!profile) {
|
|
86
|
+
unknownModels.add(site.model);
|
|
87
|
+
add({
|
|
88
|
+
severity: "info",
|
|
89
|
+
confidence: "certain",
|
|
90
|
+
title: `\`${site.model}\` is not in kgauto's verified catalog`,
|
|
91
|
+
detail: `kgauto has no measured evidence for this model and will not fabricate a verdict on it. Checks below skip this site. (If it's a new release, the catalog gains models through doc-verified onboarding, never from training-data guesses.)`,
|
|
92
|
+
basis: "catalog lookup",
|
|
93
|
+
callSites: at
|
|
94
|
+
});
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (profile.status === "legacy") {
|
|
98
|
+
add({
|
|
99
|
+
severity: "yellow",
|
|
100
|
+
confidence: "certain",
|
|
101
|
+
title: `\`${canonical}\` is a legacy model`,
|
|
102
|
+
detail: `The provider lists it as superseded. Legacy models are the deprecation-risk tier: they can start 404ing for whole account cohorts with no code change on your side. A current-generation successor exists in the same family.`,
|
|
103
|
+
basis: `catalog status: legacy${profile.verifiedAgainstDocs ? ` (verified ${profile.verifiedAgainstDocs})` : ""}`,
|
|
104
|
+
callSites: at
|
|
105
|
+
});
|
|
106
|
+
} else if (profile.status === "preview") {
|
|
107
|
+
add({
|
|
108
|
+
severity: "info",
|
|
109
|
+
confidence: "certain",
|
|
110
|
+
title: `\`${canonical}\` is a preview model`,
|
|
111
|
+
detail: `Preview pricing and limits are not yet doc-verified; both can change at GA.`,
|
|
112
|
+
basis: "catalog status: preview",
|
|
113
|
+
callSites: at
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const worstIn = site.maxInputTokens ?? site.estInputTokens;
|
|
117
|
+
if (worstIn !== void 0 && worstIn > profile.maxContextTokens) {
|
|
118
|
+
add({
|
|
119
|
+
severity: "red",
|
|
120
|
+
confidence: site.maxInputTokens !== void 0 ? "certain" : "likely",
|
|
121
|
+
title: `input can exceed \`${canonical}\`'s context window`,
|
|
122
|
+
detail: `Your ${site.maxInputTokens !== void 0 ? "worst-case" : "typical"} input estimate (${worstIn.toLocaleString()} tokens) exceeds the model's ${profile.maxContextTokens.toLocaleString()}-token window. Those calls fail outright or silently truncate, depending on the provider.`,
|
|
123
|
+
basis: `your estimate vs catalog maxContextTokens=${profile.maxContextTokens}`,
|
|
124
|
+
callSites: at
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (site.estOutputTokens !== void 0 && site.estOutputTokens > profile.maxOutputTokens) {
|
|
128
|
+
add({
|
|
129
|
+
severity: "red",
|
|
130
|
+
confidence: "likely",
|
|
131
|
+
title: `expected output exceeds \`${canonical}\`'s output cap`,
|
|
132
|
+
detail: `You expect ~${site.estOutputTokens.toLocaleString()} output tokens; the model caps at ${profile.maxOutputTokens.toLocaleString()}. Responses will be cut mid-generation \u2014 on JSON output this is the classic unparseable-tail failure.`,
|
|
133
|
+
basis: `your estimate vs catalog maxOutputTokens=${profile.maxOutputTokens}`,
|
|
134
|
+
callSites: at
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (site.toolCount !== void 0 && site.toolCount > profile.maxTools) {
|
|
138
|
+
add({
|
|
139
|
+
severity: "red",
|
|
140
|
+
confidence: "certain",
|
|
141
|
+
title: `${site.toolCount} tools exceeds \`${canonical}\`'s ceiling of ${profile.maxTools}`,
|
|
142
|
+
detail: `Calls with more tool definitions than the model supports are rejected or degrade hard.`,
|
|
143
|
+
basis: `extract toolCount vs catalog maxTools=${profile.maxTools}`,
|
|
144
|
+
callSites: at
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (site.usesParallelToolCalls && !profile.parallelToolCalls) {
|
|
148
|
+
add({
|
|
149
|
+
severity: "red",
|
|
150
|
+
confidence: "certain",
|
|
151
|
+
title: `\`${canonical}\` does not make parallel tool calls`,
|
|
152
|
+
detail: `This site relies on parallel tool use, but the model calls tools sequentially \u2014 measured at roughly 10\xD7 fewer operations per step on agentic workloads. The work still "succeeds", just slowly and shallowly, which is why this rarely shows up as an error.`,
|
|
153
|
+
basis: "catalog parallelToolCalls=false (measured, not vendor-claimed)",
|
|
154
|
+
callSites: at
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (site.expectsStructuredOutput && profile.structuredOutput === "none") {
|
|
158
|
+
add({
|
|
159
|
+
severity: "red",
|
|
160
|
+
confidence: "certain",
|
|
161
|
+
title: `\`${canonical}\` has no structured-output support, but this site parses JSON`,
|
|
162
|
+
detail: `Output-shape failures here land on your parser as intermittent exceptions. Models with native or grammar-constrained output move that failure from runtime to the request.`,
|
|
163
|
+
basis: "catalog structuredOutput=none",
|
|
164
|
+
callSites: at
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const archetype = site.archetype && isArchetype(site.archetype) ? site.archetype : void 0;
|
|
168
|
+
for (const cliff of profile.cliffs) {
|
|
169
|
+
if (cliff.whenIntent && cliff.whenIntent !== archetype) continue;
|
|
170
|
+
const metricValue = cliff.metric === "input_tokens" ? worstIn : cliff.metric === "tool_count" ? site.toolCount : void 0;
|
|
171
|
+
if (metricValue === void 0 || metricValue < cliff.threshold) continue;
|
|
172
|
+
add({
|
|
173
|
+
severity: "yellow",
|
|
174
|
+
confidence: "likely",
|
|
175
|
+
title: `\`${canonical}\` has a known ${cliff.metric.replaceAll("_", " ")} cliff at ${cliff.threshold.toLocaleString()} \u2014 this site crosses it`,
|
|
176
|
+
detail: `Above this threshold${cliff.whenIntent ? ` on ${cliff.whenIntent} work` : ""}, quality degrades in a measured, repeatable way (compiler action when routed through kgauto: ${cliff.action.replaceAll("_", " ")}). Your estimate of ${metricValue.toLocaleString()} sits over the line.`,
|
|
177
|
+
basis: `catalog cliff rule vs your estimate; the same rule the kgauto compiler executes`,
|
|
178
|
+
callSites: at
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (site.retry === "none") {
|
|
182
|
+
add({
|
|
183
|
+
severity: "yellow",
|
|
184
|
+
confidence: "certain",
|
|
185
|
+
title: `no fallback path \u2014 a single ${profile.provider} incident takes this surface to raw errors`,
|
|
186
|
+
detail: `Provider outages are routine and undramatic: billing exhaustion, quota, silent deprecations. With no cross-provider fallback, every one of them reaches your users directly.`,
|
|
187
|
+
basis: "extract retry=none",
|
|
188
|
+
callSites: at
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (site.estInputTokens !== void 0 && site.estOutputTokens !== void 0 && site.estInputTokens + site.estOutputTokens > 0) {
|
|
192
|
+
const perCall = perCallCostUsd(profile, site.estInputTokens, site.estOutputTokens);
|
|
193
|
+
const monthly = site.estCallsPerMonth !== void 0 ? perCall * site.estCallsPerMonth : void 0;
|
|
194
|
+
const inputShare = site.estInputTokens / (site.estInputTokens + site.estOutputTokens);
|
|
195
|
+
add({
|
|
196
|
+
severity: "info",
|
|
197
|
+
confidence: "likely",
|
|
198
|
+
title: `~$${perCall.toFixed(4)}/call on \`${canonical}\`${monthly !== void 0 ? ` (~$${monthly.toFixed(2)}/month at your volume)` : ""}`,
|
|
199
|
+
detail: `From your token estimates and doc-verified prices ($${profile.costInputPer1m}/M in, $${profile.costOutputPer1m}/M out${profile.verifiedAgainstDocs ? `, verified ${profile.verifiedAgainstDocs}` : ""}).`,
|
|
200
|
+
basis: "arithmetic on your estimates \xD7 verified catalog prices",
|
|
201
|
+
callSites: at
|
|
202
|
+
});
|
|
203
|
+
if (inputShare > 0.85) {
|
|
204
|
+
add({
|
|
205
|
+
severity: "yellow",
|
|
206
|
+
confidence: "likely",
|
|
207
|
+
title: `${Math.round(inputShare * 100)}% of this site's tokens are input`,
|
|
208
|
+
detail: `Above ~85% input share, spend is dominated by context rather than answers \u2014 the single strongest signal of a leaking prompt. What's actually IN that context, and how much of it each call needed, is measurable, not guessable.`,
|
|
209
|
+
basis: "your token estimates; the >85% threshold is a measured portfolio heuristic",
|
|
210
|
+
callSites: at
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (archetype) {
|
|
214
|
+
const cheaper = roster().filter(
|
|
215
|
+
(p) => p.status === "current" && p.id !== canonical && perCallCostUsd(p, site.estInputTokens, site.estOutputTokens) < perCall * 0.5
|
|
216
|
+
).sort(
|
|
217
|
+
(a, b) => perCallCostUsd(a, site.estInputTokens, site.estOutputTokens) - perCallCostUsd(b, site.estInputTokens, site.estOutputTokens)
|
|
218
|
+
);
|
|
219
|
+
const cheapest = cheaper[0];
|
|
220
|
+
if (cheapest) {
|
|
221
|
+
add({
|
|
222
|
+
severity: "info",
|
|
223
|
+
confidence: "measurable-only",
|
|
224
|
+
title: `${cheaper.length} current model(s) price under half of \`${canonical}\` on ${archetype} work`,
|
|
225
|
+
detail: `Cheapest: \`${cheapest.id}\` at ~$${perCallCostUsd(cheapest, site.estInputTokens, site.estOutputTokens).toFixed(4)}/call. The price is arithmetic; whether any of them holds YOUR quality bar on ${archetype} work is exactly what a one-day observe run plus kgauto's judged eval measures. kgauto never promotes a model on price alone.`,
|
|
226
|
+
basis: "verified catalog prices; quality deliberately unclaimed",
|
|
227
|
+
callSites: at
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
add({
|
|
234
|
+
severity: "info",
|
|
235
|
+
confidence: "measurable-only",
|
|
236
|
+
title: "what a code read cannot tell you (and a one-day observe run can)",
|
|
237
|
+
detail: `Your actual failure rate per model (walkaway failures look like green health checks); your actual input-waste ratio per call; which call sites dominate spend; whether a cheaper model holds quality on your real traffic. These need one day of observed calls \u2014 observe-only, your keys, no routing changes.`,
|
|
238
|
+
basis: "the honest limit of static analysis",
|
|
239
|
+
callSites: extract.callSites.map((s) => s.id)
|
|
240
|
+
});
|
|
241
|
+
const merged = [];
|
|
242
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
243
|
+
for (const f of findings) {
|
|
244
|
+
const key = `${f.severity}|${f.confidence}|${f.title}`;
|
|
245
|
+
const existing = byKey.get(key);
|
|
246
|
+
if (existing) {
|
|
247
|
+
for (const cs of f.callSites) {
|
|
248
|
+
if (!existing.callSites.includes(cs)) existing.callSites.push(cs);
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
const copy = { ...f, callSites: [...f.callSites] };
|
|
252
|
+
byKey.set(key, copy);
|
|
253
|
+
merged.push(copy);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
findings: merged,
|
|
258
|
+
card: renderCard(extract, merged, unknownModels),
|
|
259
|
+
catalogVersion: LIBRARY_VERSION
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
var TIER_ORDER = ["certain", "likely", "measurable-only"];
|
|
263
|
+
var TIER_HEADING = {
|
|
264
|
+
certain: "Certain \u2014 read from your code and kgauto\u2019s verified catalog",
|
|
265
|
+
likely: "Likely \u2014 priced from your estimates",
|
|
266
|
+
"measurable-only": "Only measurable \u2014 what the observe day answers"
|
|
267
|
+
};
|
|
268
|
+
var SEV_MARK = { red: "\u{1F534}", yellow: "\u{1F7E1}", info: "\u2139\uFE0F" };
|
|
269
|
+
function renderCard(extract, findings, unknownModels = /* @__PURE__ */ new Set()) {
|
|
270
|
+
const lines = [];
|
|
271
|
+
lines.push(`# kgauto check \u2014 ${extract.project.name}`);
|
|
272
|
+
lines.push("");
|
|
273
|
+
const models = [...new Set(extract.callSites.map((s) => s.model))];
|
|
274
|
+
lines.push(
|
|
275
|
+
`${extract.callSites.length} AI call site(s), ${models.length} model(s): ${models.map((m) => `\`${m}\``).join(", ")}` + (unknownModels.size > 0 ? ` (${unknownModels.size} unknown to the catalog)` : "")
|
|
276
|
+
);
|
|
277
|
+
for (const tier of TIER_ORDER) {
|
|
278
|
+
const group = findings.filter((f) => f.confidence === tier);
|
|
279
|
+
if (group.length === 0) continue;
|
|
280
|
+
lines.push("");
|
|
281
|
+
lines.push(`## ${TIER_HEADING[tier]}`);
|
|
282
|
+
lines.push("");
|
|
283
|
+
for (const f of group) {
|
|
284
|
+
lines.push(`- ${SEV_MARK[f.severity]} **${f.title}** \u2014 ${f.detail}`);
|
|
285
|
+
lines.push(` <sub>${f.callSites.join(", ")} \xB7 basis: ${f.basis}</sub>`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
lines.push("");
|
|
289
|
+
lines.push("---");
|
|
290
|
+
lines.push(`*${DESTINATION_SENTENCE}*`);
|
|
291
|
+
return lines.join("\n");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/instructions.ts
|
|
295
|
+
var SERVER_INSTRUCTIONS = `You are talking to kgauto-check, a local, offline checker for AI call surfaces.
|
|
296
|
+
|
|
297
|
+
Flow: (1) call start_check to get extraction instructions, (2) read THIS project's code yourself and build the extract, (3) call run_check with it, (4) present the returned card to your human verbatim (it is grouped by confidence tier on purpose), (5) if they want measured numbers, call get_observe_day_plan.
|
|
298
|
+
|
|
299
|
+
Privacy contract, which you should relay to your human: the extract contains identifiers and counts only \u2014 never prompt text, never source code, never secrets. ${DESTINATION_SENTENCE}
|
|
300
|
+
|
|
301
|
+
get_model_facts answers one-off questions about any model id from the same verified catalog.`;
|
|
302
|
+
var START_CHECK_INSTRUCTIONS = `# kgauto check \u2014 extraction instructions (for you, the agent)
|
|
303
|
+
|
|
304
|
+
Your job: read this project's code and build an extract of its AI call surface. You have repo access; kgauto does not, by design.
|
|
305
|
+
|
|
306
|
+
## What to find
|
|
307
|
+
Scan for LLM API usage: provider SDK imports (anthropic, openai, @google/generative-ai / @google/genai, ai-sdk, litellm, langchain, ...), raw HTTPS calls to provider endpoints, and any internal wrapper around them. Each distinct place the project sends a prompt to a model is one call site.
|
|
308
|
+
|
|
309
|
+
## What to collect per call site (identifiers and counts ONLY)
|
|
310
|
+
- id: a stable label you choose, e.g. "api/summarize.ts:42"
|
|
311
|
+
- model: the model id exactly as configured
|
|
312
|
+
- modelSelection: hardcoded | config | env | dynamic
|
|
313
|
+
- archetype: your best guess at the work's intent \u2014 one of: ask, classify, critique, extract, generate, hunt, plan, summarize, transform
|
|
314
|
+
- estInputTokens / maxInputTokens / estOutputTokens: estimate from the code (what gets concatenated into the prompt; ~4 chars per token). Worst case matters \u2014 find the biggest thing that can flow in.
|
|
315
|
+
- toolCount, usesParallelToolCalls: from the tools array and how results are consumed
|
|
316
|
+
- expectsStructuredOutput: true if the response is JSON.parse'd or schema-validated
|
|
317
|
+
- streaming, retry (none | same-model | fallback-chain | unknown), fallbackModels
|
|
318
|
+
- estCallsPerMonth: only if genuinely inferable (cron cadence, obvious traffic); omit rather than invent
|
|
319
|
+
|
|
320
|
+
## Privacy contract (relay this to your human)
|
|
321
|
+
Do NOT put prompt text, source code, file contents, or secrets in the extract. Identifiers and counts only. ${DESTINATION_SENTENCE}
|
|
322
|
+
|
|
323
|
+
## Then
|
|
324
|
+
Call run_check with { extract }. Present the returned card verbatim \u2014 the confidence tiers (certain / likely / measurable-only) are the point: they tell your human exactly how much to trust each line, and the measurable-only section is what a one-day observe run would answer.
|
|
325
|
+
|
|
326
|
+
## Honesty rules you inherit while presenting
|
|
327
|
+
- Do not soften "certain" findings or upgrade "likely" ones.
|
|
328
|
+
- If a model was unknown to the catalog, say so \u2014 kgauto refuses to fabricate verdicts, and so should you.
|
|
329
|
+
- Estimates you made are estimates: if a finding hinges on your token guess, say it can be firmed up by measuring.`;
|
|
330
|
+
|
|
331
|
+
// src/observe-plan.ts
|
|
332
|
+
var OBSERVE_DAY_PLAN = `# The one-day observe run (for you, the agent \u2014 decision stays with your human)
|
|
333
|
+
|
|
334
|
+
## What it is
|
|
335
|
+
One day of your project's normal traffic, observed. No routing changes: every call still goes to exactly the model your code chooses today. The observation records, per call: model, token counts in/out, cost at verified prices, latency, success/failure with a classified error code, and per-attempt rows when retries happen. Identifiers and counts \u2014 prompt text is never required.
|
|
336
|
+
|
|
337
|
+
## What it answers (the measurable-only tier of your check card)
|
|
338
|
+
- actual failure rate per model \u2014 including walkaway failures that green health checks miss
|
|
339
|
+
- actual input-waste ratio per call site (the >85% input-share signal, measured instead of estimated)
|
|
340
|
+
- which call sites dominate spend
|
|
341
|
+
- the evidence base for whether a cheaper model could hold quality here (kgauto's judged eval runs on real captured cases, never on price alone)
|
|
342
|
+
|
|
343
|
+
## The two honesty requirements (non-negotiable, relay both)
|
|
344
|
+
1. **Destination, named before anything runs.** Your human gets one plain sentence stating exactly where outcome rows land and what is in them, BEFORE the first row is written. "Nothing is uploaded" was true of the check because it writes nothing; the observe day writes rows, so it must say where.
|
|
345
|
+
2. **Observation never intervenes.** If your project uses fallback chains, observing must not pin a model and silently disable the chain \u2014 a forced pin is an intervention sold as observation. Rows are recorded per attempt, so the chain's real behaviour is what gets measured.
|
|
346
|
+
|
|
347
|
+
## How to proceed
|
|
348
|
+
The integration is small (the kgauto compiler's call/record path with your existing keys), but because rows now land somewhere, the wiring starts with that destination sentence and your human's explicit yes. Next step: have your human reply to whoever pointed them at kgauto \u2014 the observe day is set up per-project, together.`;
|
|
349
|
+
|
|
350
|
+
// src/server.ts
|
|
351
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
352
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
353
|
+
import { z as z2 } from "zod";
|
|
354
|
+
import { tryGetProfile as tryGetProfile2, resolveModelAlias as resolveModelAlias2, LIBRARY_VERSION as LIBRARY_VERSION2 } from "@warmdrift/kgauto-compiler";
|
|
355
|
+
var CHECK_VERSION = "0.1.0";
|
|
356
|
+
var text = (t) => ({ content: [{ type: "text", text: t }] });
|
|
357
|
+
function buildServer() {
|
|
358
|
+
const server = new McpServer(
|
|
359
|
+
{ name: "kgauto-check", version: CHECK_VERSION },
|
|
360
|
+
{ instructions: SERVER_INSTRUCTIONS }
|
|
361
|
+
);
|
|
362
|
+
server.registerTool(
|
|
363
|
+
"start_check",
|
|
364
|
+
{
|
|
365
|
+
title: "Start a kgauto check",
|
|
366
|
+
description: "Call this FIRST. Returns extraction instructions: what to read from this project and how to build the extract for run_check. The extract carries identifiers and counts only \u2014 never prompt text, code, or secrets.",
|
|
367
|
+
inputSchema: {}
|
|
368
|
+
},
|
|
369
|
+
async () => text(START_CHECK_INSTRUCTIONS)
|
|
370
|
+
);
|
|
371
|
+
server.registerTool(
|
|
372
|
+
"run_check",
|
|
373
|
+
{
|
|
374
|
+
title: "Run the check",
|
|
375
|
+
description: "Runs the deterministic kgauto check on your extract, fully offline against the bundled verified model catalog. Returns a findings card grouped by confidence tier (certain / likely / measurable-only) \u2014 present it to your human verbatim.",
|
|
376
|
+
inputSchema: { extract: ExtractSchema }
|
|
377
|
+
},
|
|
378
|
+
async ({ extract }) => {
|
|
379
|
+
const result = runCheck(extract);
|
|
380
|
+
return text(result.card);
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
server.registerTool(
|
|
384
|
+
"get_model_facts",
|
|
385
|
+
{
|
|
386
|
+
title: "Model facts",
|
|
387
|
+
description: "What kgauto's verified catalog knows about one model id: lifecycle status, doc-verified prices and limits, capability flags, known quality cliffs. Honest miss when the model is unknown \u2014 no fabricated verdicts.",
|
|
388
|
+
inputSchema: { model: z2.string().min(1) }
|
|
389
|
+
},
|
|
390
|
+
async ({ model }) => {
|
|
391
|
+
const canonical = resolveModelAlias2(model);
|
|
392
|
+
const p = tryGetProfile2(model);
|
|
393
|
+
if (!p) {
|
|
394
|
+
return text(
|
|
395
|
+
`\`${model}\` is not in kgauto's verified catalog (lib ${LIBRARY_VERSION2}). No measured evidence exists for it here, and kgauto does not fabricate verdicts. New models enter the catalog through doc-verified onboarding.`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
const lines = [
|
|
399
|
+
`# ${canonical}${canonical !== model ? ` (you asked for \`${model}\`, a provider alias)` : ""}`,
|
|
400
|
+
"",
|
|
401
|
+
`- provider: ${p.provider} \xB7 status: **${p.status}**${p.verifiedAgainstDocs ? ` \xB7 capability data verified against provider docs ${p.verifiedAgainstDocs}` : ""}`,
|
|
402
|
+
`- price: $${p.costInputPer1m}/M input \xB7 $${p.costOutputPer1m}/M output`,
|
|
403
|
+
`- limits: ${p.maxContextTokens.toLocaleString()} context \xB7 ${p.maxOutputTokens.toLocaleString()} max output \xB7 ${p.maxTools} tools`,
|
|
404
|
+
`- capabilities: parallel tool calls ${p.parallelToolCalls ? "yes" : "NO (sequential \u2014 ~10\xD7 fewer ops/step on agentic work)"} \xB7 structured output: ${p.structuredOutput} \xB7 streaming: ${p.streaming ? "yes" : "no"}`,
|
|
405
|
+
p.cliffs.length > 0 ? `- known cliffs (executable in the kgauto compiler):
|
|
406
|
+
${p.cliffs.map(
|
|
407
|
+
(c) => ` - ${c.metric.replaceAll("_", " ")} \u2265 ${c.threshold.toLocaleString()}${c.whenIntent ? ` on ${c.whenIntent}` : ""} \u2192 ${c.action.replaceAll("_", " ")}`
|
|
408
|
+
).join("\n")}` : "- known cliffs: none recorded",
|
|
409
|
+
p.notes ? `- notes: ${p.notes}` : "",
|
|
410
|
+
"",
|
|
411
|
+
`_Catalog: @warmdrift/kgauto-compiler ${LIBRARY_VERSION2}, bundled locally \u2014 this lookup made no network call._`
|
|
412
|
+
].filter(Boolean);
|
|
413
|
+
return text(lines.join("\n"));
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
server.registerTool(
|
|
417
|
+
"get_observe_day_plan",
|
|
418
|
+
{
|
|
419
|
+
title: "The one-day observe run",
|
|
420
|
+
description: "Rung 2: what a one-day, observe-only run on this project's real traffic would measure, and the two honesty requirements any setup must carry (named row destination; observation never intervenes). Ends in a human decision by design.",
|
|
421
|
+
inputSchema: {}
|
|
422
|
+
},
|
|
423
|
+
async () => text(OBSERVE_DAY_PLAN)
|
|
424
|
+
);
|
|
425
|
+
return server;
|
|
426
|
+
}
|
|
427
|
+
async function startStdio() {
|
|
428
|
+
const server = buildServer();
|
|
429
|
+
await server.connect(new StdioServerTransport());
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export {
|
|
433
|
+
CallSiteSchema,
|
|
434
|
+
ExtractSchema,
|
|
435
|
+
DESTINATION_SENTENCE,
|
|
436
|
+
runCheck,
|
|
437
|
+
renderCard,
|
|
438
|
+
SERVER_INSTRUCTIONS,
|
|
439
|
+
START_CHECK_INSTRUCTIONS,
|
|
440
|
+
OBSERVE_DAY_PLAN,
|
|
441
|
+
buildServer,
|
|
442
|
+
startStdio
|
|
443
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { ModelProfile } from '@warmdrift/kgauto-compiler';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The extract contract — what a consumer's coding agent collects from the
|
|
7
|
+
* project it is checking. IDENTIFIERS AND COUNTS ONLY, by design: no prompt
|
|
8
|
+
* text, no source code, no secrets ever enter the extract. The agent reads
|
|
9
|
+
* the repo (it already has access); kgauto reads nothing.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
declare const CallSiteSchema: z.ZodObject<{
|
|
13
|
+
/** Agent-chosen stable label, e.g. "api/summarize.ts:42" or "hunt loop". */
|
|
14
|
+
id: z.ZodString;
|
|
15
|
+
/** Model id exactly as configured in the project. */
|
|
16
|
+
model: z.ZodString;
|
|
17
|
+
/** How the model id is selected at this site. */
|
|
18
|
+
modelSelection: z.ZodOptional<z.ZodEnum<["hardcoded", "config", "env", "dynamic"]>>;
|
|
19
|
+
/**
|
|
20
|
+
* Best-guess intent archetype from kgauto's dialect (ask, classify,
|
|
21
|
+
* critique, extract, generate, hunt, plan, summarize, transform).
|
|
22
|
+
* Free-form here; validated softly against the roster's dialect.
|
|
23
|
+
*/
|
|
24
|
+
archetype: z.ZodOptional<z.ZodString>;
|
|
25
|
+
/** Typical input size in tokens (estimate is fine; ~4 chars/token). */
|
|
26
|
+
estInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
27
|
+
/** Worst-case input size in tokens, if larger than typical. */
|
|
28
|
+
maxInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
/** Typical output size in tokens. */
|
|
30
|
+
estOutputTokens: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
/** Number of tool definitions sent with the call. */
|
|
32
|
+
toolCount: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
/** Does the site rely on the model making parallel tool calls? */
|
|
34
|
+
usesParallelToolCalls: z.ZodOptional<z.ZodBoolean>;
|
|
35
|
+
/** Does the site parse the response as JSON / expect schema output? */
|
|
36
|
+
expectsStructuredOutput: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
+
streaming: z.ZodOptional<z.ZodBoolean>;
|
|
38
|
+
/** What happens when the call fails. */
|
|
39
|
+
retry: z.ZodOptional<z.ZodEnum<["none", "same-model", "fallback-chain", "unknown"]>>;
|
|
40
|
+
/** Fallback model ids, when retry is 'fallback-chain'. */
|
|
41
|
+
fallbackModels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
42
|
+
/** Rough call volume, if inferable (cron cadence, traffic, logs). */
|
|
43
|
+
estCallsPerMonth: z.ZodOptional<z.ZodNumber>;
|
|
44
|
+
/** Anything structural the agent thinks matters. Never prompt text. */
|
|
45
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
46
|
+
}, "strip", z.ZodTypeAny, {
|
|
47
|
+
id: string;
|
|
48
|
+
model: string;
|
|
49
|
+
modelSelection?: "hardcoded" | "config" | "env" | "dynamic" | undefined;
|
|
50
|
+
archetype?: string | undefined;
|
|
51
|
+
estInputTokens?: number | undefined;
|
|
52
|
+
maxInputTokens?: number | undefined;
|
|
53
|
+
estOutputTokens?: number | undefined;
|
|
54
|
+
toolCount?: number | undefined;
|
|
55
|
+
usesParallelToolCalls?: boolean | undefined;
|
|
56
|
+
expectsStructuredOutput?: boolean | undefined;
|
|
57
|
+
streaming?: boolean | undefined;
|
|
58
|
+
retry?: "none" | "same-model" | "fallback-chain" | "unknown" | undefined;
|
|
59
|
+
fallbackModels?: string[] | undefined;
|
|
60
|
+
estCallsPerMonth?: number | undefined;
|
|
61
|
+
notes?: string | undefined;
|
|
62
|
+
}, {
|
|
63
|
+
id: string;
|
|
64
|
+
model: string;
|
|
65
|
+
modelSelection?: "hardcoded" | "config" | "env" | "dynamic" | undefined;
|
|
66
|
+
archetype?: string | undefined;
|
|
67
|
+
estInputTokens?: number | undefined;
|
|
68
|
+
maxInputTokens?: number | undefined;
|
|
69
|
+
estOutputTokens?: number | undefined;
|
|
70
|
+
toolCount?: number | undefined;
|
|
71
|
+
usesParallelToolCalls?: boolean | undefined;
|
|
72
|
+
expectsStructuredOutput?: boolean | undefined;
|
|
73
|
+
streaming?: boolean | undefined;
|
|
74
|
+
retry?: "none" | "same-model" | "fallback-chain" | "unknown" | undefined;
|
|
75
|
+
fallbackModels?: string[] | undefined;
|
|
76
|
+
estCallsPerMonth?: number | undefined;
|
|
77
|
+
notes?: string | undefined;
|
|
78
|
+
}>;
|
|
79
|
+
declare const ExtractSchema: z.ZodObject<{
|
|
80
|
+
project: z.ZodObject<{
|
|
81
|
+
name: z.ZodString;
|
|
82
|
+
description: z.ZodOptional<z.ZodString>;
|
|
83
|
+
}, "strip", z.ZodTypeAny, {
|
|
84
|
+
name: string;
|
|
85
|
+
description?: string | undefined;
|
|
86
|
+
}, {
|
|
87
|
+
name: string;
|
|
88
|
+
description?: string | undefined;
|
|
89
|
+
}>;
|
|
90
|
+
callSites: z.ZodArray<z.ZodObject<{
|
|
91
|
+
/** Agent-chosen stable label, e.g. "api/summarize.ts:42" or "hunt loop". */
|
|
92
|
+
id: z.ZodString;
|
|
93
|
+
/** Model id exactly as configured in the project. */
|
|
94
|
+
model: z.ZodString;
|
|
95
|
+
/** How the model id is selected at this site. */
|
|
96
|
+
modelSelection: z.ZodOptional<z.ZodEnum<["hardcoded", "config", "env", "dynamic"]>>;
|
|
97
|
+
/**
|
|
98
|
+
* Best-guess intent archetype from kgauto's dialect (ask, classify,
|
|
99
|
+
* critique, extract, generate, hunt, plan, summarize, transform).
|
|
100
|
+
* Free-form here; validated softly against the roster's dialect.
|
|
101
|
+
*/
|
|
102
|
+
archetype: z.ZodOptional<z.ZodString>;
|
|
103
|
+
/** Typical input size in tokens (estimate is fine; ~4 chars/token). */
|
|
104
|
+
estInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
105
|
+
/** Worst-case input size in tokens, if larger than typical. */
|
|
106
|
+
maxInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
107
|
+
/** Typical output size in tokens. */
|
|
108
|
+
estOutputTokens: z.ZodOptional<z.ZodNumber>;
|
|
109
|
+
/** Number of tool definitions sent with the call. */
|
|
110
|
+
toolCount: z.ZodOptional<z.ZodNumber>;
|
|
111
|
+
/** Does the site rely on the model making parallel tool calls? */
|
|
112
|
+
usesParallelToolCalls: z.ZodOptional<z.ZodBoolean>;
|
|
113
|
+
/** Does the site parse the response as JSON / expect schema output? */
|
|
114
|
+
expectsStructuredOutput: z.ZodOptional<z.ZodBoolean>;
|
|
115
|
+
streaming: z.ZodOptional<z.ZodBoolean>;
|
|
116
|
+
/** What happens when the call fails. */
|
|
117
|
+
retry: z.ZodOptional<z.ZodEnum<["none", "same-model", "fallback-chain", "unknown"]>>;
|
|
118
|
+
/** Fallback model ids, when retry is 'fallback-chain'. */
|
|
119
|
+
fallbackModels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
120
|
+
/** Rough call volume, if inferable (cron cadence, traffic, logs). */
|
|
121
|
+
estCallsPerMonth: z.ZodOptional<z.ZodNumber>;
|
|
122
|
+
/** Anything structural the agent thinks matters. Never prompt text. */
|
|
123
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
124
|
+
}, "strip", z.ZodTypeAny, {
|
|
125
|
+
id: string;
|
|
126
|
+
model: string;
|
|
127
|
+
modelSelection?: "hardcoded" | "config" | "env" | "dynamic" | undefined;
|
|
128
|
+
archetype?: string | undefined;
|
|
129
|
+
estInputTokens?: number | undefined;
|
|
130
|
+
maxInputTokens?: number | undefined;
|
|
131
|
+
estOutputTokens?: number | undefined;
|
|
132
|
+
toolCount?: number | undefined;
|
|
133
|
+
usesParallelToolCalls?: boolean | undefined;
|
|
134
|
+
expectsStructuredOutput?: boolean | undefined;
|
|
135
|
+
streaming?: boolean | undefined;
|
|
136
|
+
retry?: "none" | "same-model" | "fallback-chain" | "unknown" | undefined;
|
|
137
|
+
fallbackModels?: string[] | undefined;
|
|
138
|
+
estCallsPerMonth?: number | undefined;
|
|
139
|
+
notes?: string | undefined;
|
|
140
|
+
}, {
|
|
141
|
+
id: string;
|
|
142
|
+
model: string;
|
|
143
|
+
modelSelection?: "hardcoded" | "config" | "env" | "dynamic" | undefined;
|
|
144
|
+
archetype?: string | undefined;
|
|
145
|
+
estInputTokens?: number | undefined;
|
|
146
|
+
maxInputTokens?: number | undefined;
|
|
147
|
+
estOutputTokens?: number | undefined;
|
|
148
|
+
toolCount?: number | undefined;
|
|
149
|
+
usesParallelToolCalls?: boolean | undefined;
|
|
150
|
+
expectsStructuredOutput?: boolean | undefined;
|
|
151
|
+
streaming?: boolean | undefined;
|
|
152
|
+
retry?: "none" | "same-model" | "fallback-chain" | "unknown" | undefined;
|
|
153
|
+
fallbackModels?: string[] | undefined;
|
|
154
|
+
estCallsPerMonth?: number | undefined;
|
|
155
|
+
notes?: string | undefined;
|
|
156
|
+
}>, "many">;
|
|
157
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
158
|
+
}, "strip", z.ZodTypeAny, {
|
|
159
|
+
project: {
|
|
160
|
+
name: string;
|
|
161
|
+
description?: string | undefined;
|
|
162
|
+
};
|
|
163
|
+
callSites: {
|
|
164
|
+
id: string;
|
|
165
|
+
model: string;
|
|
166
|
+
modelSelection?: "hardcoded" | "config" | "env" | "dynamic" | undefined;
|
|
167
|
+
archetype?: string | undefined;
|
|
168
|
+
estInputTokens?: number | undefined;
|
|
169
|
+
maxInputTokens?: number | undefined;
|
|
170
|
+
estOutputTokens?: number | undefined;
|
|
171
|
+
toolCount?: number | undefined;
|
|
172
|
+
usesParallelToolCalls?: boolean | undefined;
|
|
173
|
+
expectsStructuredOutput?: boolean | undefined;
|
|
174
|
+
streaming?: boolean | undefined;
|
|
175
|
+
retry?: "none" | "same-model" | "fallback-chain" | "unknown" | undefined;
|
|
176
|
+
fallbackModels?: string[] | undefined;
|
|
177
|
+
estCallsPerMonth?: number | undefined;
|
|
178
|
+
notes?: string | undefined;
|
|
179
|
+
}[];
|
|
180
|
+
notes?: string | undefined;
|
|
181
|
+
}, {
|
|
182
|
+
project: {
|
|
183
|
+
name: string;
|
|
184
|
+
description?: string | undefined;
|
|
185
|
+
};
|
|
186
|
+
callSites: {
|
|
187
|
+
id: string;
|
|
188
|
+
model: string;
|
|
189
|
+
modelSelection?: "hardcoded" | "config" | "env" | "dynamic" | undefined;
|
|
190
|
+
archetype?: string | undefined;
|
|
191
|
+
estInputTokens?: number | undefined;
|
|
192
|
+
maxInputTokens?: number | undefined;
|
|
193
|
+
estOutputTokens?: number | undefined;
|
|
194
|
+
toolCount?: number | undefined;
|
|
195
|
+
usesParallelToolCalls?: boolean | undefined;
|
|
196
|
+
expectsStructuredOutput?: boolean | undefined;
|
|
197
|
+
streaming?: boolean | undefined;
|
|
198
|
+
retry?: "none" | "same-model" | "fallback-chain" | "unknown" | undefined;
|
|
199
|
+
fallbackModels?: string[] | undefined;
|
|
200
|
+
estCallsPerMonth?: number | undefined;
|
|
201
|
+
notes?: string | undefined;
|
|
202
|
+
}[];
|
|
203
|
+
notes?: string | undefined;
|
|
204
|
+
}>;
|
|
205
|
+
type CallSite = z.infer<typeof CallSiteSchema>;
|
|
206
|
+
type Extract = z.infer<typeof ExtractSchema>;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The deterministic check engine. Every finding carries a confidence tier —
|
|
210
|
+
* the mechanical answer to "how much confidence?":
|
|
211
|
+
*
|
|
212
|
+
* certain — a catalog or extract fact; stated flatly.
|
|
213
|
+
* likely — priced/inferred from the agent's own estimates.
|
|
214
|
+
* measurable-only — honestly unknowable from a code read; each one is
|
|
215
|
+
* exactly what the observe day exists to measure.
|
|
216
|
+
*
|
|
217
|
+
* Checks run against the SAME bundled roster the kgauto compiler executes
|
|
218
|
+
* (`@warmdrift/kgauto-compiler` is a real dependency, not a copy) — parity
|
|
219
|
+
* by construction. No network, no writes: the card's destination sentence
|
|
220
|
+
* is true because there is no code path that could make it false.
|
|
221
|
+
*/
|
|
222
|
+
|
|
223
|
+
type Confidence = 'certain' | 'likely' | 'measurable-only';
|
|
224
|
+
type Severity = 'red' | 'yellow' | 'info';
|
|
225
|
+
interface Finding {
|
|
226
|
+
id: string;
|
|
227
|
+
severity: Severity;
|
|
228
|
+
confidence: Confidence;
|
|
229
|
+
title: string;
|
|
230
|
+
detail: string;
|
|
231
|
+
/** Where the claim comes from — catalog field, extract field, arithmetic. */
|
|
232
|
+
basis: string;
|
|
233
|
+
callSites: string[];
|
|
234
|
+
}
|
|
235
|
+
interface CheckResult {
|
|
236
|
+
findings: Finding[];
|
|
237
|
+
card: string;
|
|
238
|
+
catalogVersion: string;
|
|
239
|
+
}
|
|
240
|
+
interface EngineOptions {
|
|
241
|
+
/** Test seam: profile lookup override. Defaults to the bundled roster. */
|
|
242
|
+
resolve?: (id: string) => {
|
|
243
|
+
canonical: string;
|
|
244
|
+
profile: ModelProfile | undefined;
|
|
245
|
+
};
|
|
246
|
+
/** Test seam: full roster override (for cheaper-candidate scans). */
|
|
247
|
+
roster?: () => ModelProfile[];
|
|
248
|
+
}
|
|
249
|
+
declare const DESTINATION_SENTENCE: string;
|
|
250
|
+
declare function runCheck(extract: Extract, opts?: EngineOptions): CheckResult;
|
|
251
|
+
declare function renderCard(extract: Extract, findings: Finding[], unknownModels?: ReadonlySet<string>): string;
|
|
252
|
+
|
|
253
|
+
declare function buildServer(): McpServer;
|
|
254
|
+
|
|
255
|
+
declare const SERVER_INSTRUCTIONS = "You are talking to kgauto-check, a local, offline checker for AI call surfaces.\n\nFlow: (1) call start_check to get extraction instructions, (2) read THIS project's code yourself and build the extract, (3) call run_check with it, (4) present the returned card to your human verbatim (it is grouped by confidence tier on purpose), (5) if they want measured numbers, call get_observe_day_plan.\n\nPrivacy contract, which you should relay to your human: the extract contains identifiers and counts only \u2014 never prompt text, never source code, never secrets. This check ran locally against kgauto\u2019s bundled model catalog. Nothing was uploaded; no rows were written anywhere.\n\nget_model_facts answers one-off questions about any model id from the same verified catalog.";
|
|
256
|
+
declare const START_CHECK_INSTRUCTIONS = "# kgauto check \u2014 extraction instructions (for you, the agent)\n\nYour job: read this project's code and build an extract of its AI call surface. You have repo access; kgauto does not, by design.\n\n## What to find\nScan for LLM API usage: provider SDK imports (anthropic, openai, @google/generative-ai / @google/genai, ai-sdk, litellm, langchain, ...), raw HTTPS calls to provider endpoints, and any internal wrapper around them. Each distinct place the project sends a prompt to a model is one call site.\n\n## What to collect per call site (identifiers and counts ONLY)\n- id: a stable label you choose, e.g. \"api/summarize.ts:42\"\n- model: the model id exactly as configured\n- modelSelection: hardcoded | config | env | dynamic\n- archetype: your best guess at the work's intent \u2014 one of: ask, classify, critique, extract, generate, hunt, plan, summarize, transform\n- estInputTokens / maxInputTokens / estOutputTokens: estimate from the code (what gets concatenated into the prompt; ~4 chars per token). Worst case matters \u2014 find the biggest thing that can flow in.\n- toolCount, usesParallelToolCalls: from the tools array and how results are consumed\n- expectsStructuredOutput: true if the response is JSON.parse'd or schema-validated\n- streaming, retry (none | same-model | fallback-chain | unknown), fallbackModels\n- estCallsPerMonth: only if genuinely inferable (cron cadence, obvious traffic); omit rather than invent\n\n## Privacy contract (relay this to your human)\nDo NOT put prompt text, source code, file contents, or secrets in the extract. Identifiers and counts only. This check ran locally against kgauto\u2019s bundled model catalog. Nothing was uploaded; no rows were written anywhere.\n\n## Then\nCall run_check with { extract }. Present the returned card verbatim \u2014 the confidence tiers (certain / likely / measurable-only) are the point: they tell your human exactly how much to trust each line, and the measurable-only section is what a one-day observe run would answer.\n\n## Honesty rules you inherit while presenting\n- Do not soften \"certain\" findings or upgrade \"likely\" ones.\n- If a model was unknown to the catalog, say so \u2014 kgauto refuses to fabricate verdicts, and so should you.\n- Estimates you made are estimates: if a finding hinges on your token guess, say it can be firmed up by measuring.";
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Rung 2 of the ladder: the one-day observe run. v1 of this tool hands the
|
|
260
|
+
* agent the PLAN — what the day is, what it answers, and the two honesty
|
|
261
|
+
* requirements any implementation must carry (both came out of cold-read
|
|
262
|
+
* testing with simulated skeptical founders). The concrete wiring involves
|
|
263
|
+
* consent about where rows land, so it ends in a human decision by design.
|
|
264
|
+
*/
|
|
265
|
+
declare const OBSERVE_DAY_PLAN = "# The one-day observe run (for you, the agent \u2014 decision stays with your human)\n\n## What it is\nOne day of your project's normal traffic, observed. No routing changes: every call still goes to exactly the model your code chooses today. The observation records, per call: model, token counts in/out, cost at verified prices, latency, success/failure with a classified error code, and per-attempt rows when retries happen. Identifiers and counts \u2014 prompt text is never required.\n\n## What it answers (the measurable-only tier of your check card)\n- actual failure rate per model \u2014 including walkaway failures that green health checks miss\n- actual input-waste ratio per call site (the >85% input-share signal, measured instead of estimated)\n- which call sites dominate spend\n- the evidence base for whether a cheaper model could hold quality here (kgauto's judged eval runs on real captured cases, never on price alone)\n\n## The two honesty requirements (non-negotiable, relay both)\n1. **Destination, named before anything runs.** Your human gets one plain sentence stating exactly where outcome rows land and what is in them, BEFORE the first row is written. \"Nothing is uploaded\" was true of the check because it writes nothing; the observe day writes rows, so it must say where.\n2. **Observation never intervenes.** If your project uses fallback chains, observing must not pin a model and silently disable the chain \u2014 a forced pin is an intervention sold as observation. Rows are recorded per attempt, so the chain's real behaviour is what gets measured.\n\n## How to proceed\nThe integration is small (the kgauto compiler's call/record path with your existing keys), but because rows now land somewhere, the wiring starts with that destination sentence and your human's explicit yes. Next step: have your human reply to whoever pointed them at kgauto \u2014 the observe day is set up per-project, together.";
|
|
266
|
+
|
|
267
|
+
export { type CallSite, CallSiteSchema, type CheckResult, type Confidence, DESTINATION_SENTENCE, type EngineOptions, type Extract, ExtractSchema, type Finding, OBSERVE_DAY_PLAN, SERVER_INSTRUCTIONS, START_CHECK_INSTRUCTIONS, type Severity, buildServer, renderCard, runCheck };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CallSiteSchema,
|
|
3
|
+
DESTINATION_SENTENCE,
|
|
4
|
+
ExtractSchema,
|
|
5
|
+
OBSERVE_DAY_PLAN,
|
|
6
|
+
SERVER_INSTRUCTIONS,
|
|
7
|
+
START_CHECK_INSTRUCTIONS,
|
|
8
|
+
buildServer,
|
|
9
|
+
renderCard,
|
|
10
|
+
runCheck
|
|
11
|
+
} from "./chunk-WJEKN4BL.js";
|
|
12
|
+
export {
|
|
13
|
+
CallSiteSchema,
|
|
14
|
+
DESTINATION_SENTENCE,
|
|
15
|
+
ExtractSchema,
|
|
16
|
+
OBSERVE_DAY_PLAN,
|
|
17
|
+
SERVER_INSTRUCTIONS,
|
|
18
|
+
START_CHECK_INSTRUCTIONS,
|
|
19
|
+
buildServer,
|
|
20
|
+
renderCard,
|
|
21
|
+
runCheck
|
|
22
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@warmdrift/kgauto-check",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Point your AI at your project — kgauto's local MCP check. Your coding agent extracts your AI surface (identifiers and counts, never code) and this server checks it against kgauto's verified model catalog: statuses, prices, cliffs, capability mismatches, resilience. Runs fully offline; nothing is uploaded, no rows are written anywhere.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kgauto-check-mcp": "dist/bin.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts src/bin.ts --format esm --dts --clean",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"smoke": "npm run build && node scripts/smoke-stdio.mjs",
|
|
23
|
+
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"model-context-protocol",
|
|
28
|
+
"kgauto",
|
|
29
|
+
"llm",
|
|
30
|
+
"model-catalog",
|
|
31
|
+
"ai-cost",
|
|
32
|
+
"prompt-compiler"
|
|
33
|
+
],
|
|
34
|
+
"license": "UNLICENSED",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/stue/kgauto.git",
|
|
38
|
+
"directory": "check-mcp"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
42
|
+
"@warmdrift/kgauto-compiler": "^2.0.0-alpha.88",
|
|
43
|
+
"zod": "^3.23.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"tsup": "^8.0.0",
|
|
48
|
+
"typescript": "^5.4.0",
|
|
49
|
+
"vitest": "^2.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|