jev-audit 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/LICENSE +21 -0
- package/README.md +156 -0
- package/bin/jev-audit.js +103 -0
- package/package.json +41 -0
- package/skill/jev-audit/SKILL.md +81 -0
- package/skill/jev-audit/assets/REPORT_TEMPLATE.md +87 -0
- package/skill/jev-audit/references/architecture.md +94 -0
- package/skill/jev-audit/references/jev-reference.md +138 -0
- package/skill/jev-audit/scripts/scan.js +177 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gourav Das
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# jev-audit
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/jev-audit)
|
|
4
|
+
[](https://www.npmjs.com/package/jev-audit)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](package.json)
|
|
7
|
+
[](package.json)
|
|
8
|
+
[](https://bundlephobia.com/package/jev-audit)
|
|
9
|
+
[](https://github.com/MagicBeansAI/jev-audit/pulls)
|
|
10
|
+
|
|
11
|
+
**Your LLM bill is full of decisions pretending to be generations.**
|
|
12
|
+
|
|
13
|
+
`jev-audit` finds every LLM call in your codebase that is really a *decision* — classify, route, score, verify — and shows you exactly what it looks like on [Jev](https://docs.typesafe.ai), TypeSafe AI's System One decision model.
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
frontier LLM Jev
|
|
17
|
+
output free text typed decision + calibrated confidence
|
|
18
|
+
latency 1–10 s ~100 ms
|
|
19
|
+
input cost $3–15 / M tokens $0.042 / M tokens
|
|
20
|
+
output cost $15–75 / M tokens free
|
|
21
|
+
hallucination whenever it wants structurally impossible (fixed option set)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
No API key. No network. Nothing leaves your machine.
|
|
25
|
+
|
|
26
|
+
## The 30-second version
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx jev-audit
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
jev-audit scan — ~/acme-support
|
|
34
|
+
files scanned: 214 llm touchpoints: 37 decision-shaped: 11
|
|
35
|
+
|
|
36
|
+
hottest files:
|
|
37
|
+
9 src/triage.js
|
|
38
|
+
6 src/moderation.py
|
|
39
|
+
4 src/router.ts
|
|
40
|
+
|
|
41
|
+
decision-shaped call sites (prime Jev candidates):
|
|
42
|
+
src/triage.js:42 [chat-completion] const dept = await openai.chat.completions.create({
|
|
43
|
+
src/moderation.py:17 [chain-class] classifier = ChatOpenAI(model="gpt-4o-mini")
|
|
44
|
+
src/router.ts:88 [message-create] const res = await anthropic.messages.create({
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
→ full inventory: jev-scan.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Eleven of those 37 calls are decisions wearing a generator's clothes. That's usually where most of the spend and most of the latency lives.
|
|
51
|
+
|
|
52
|
+
## Then let your agent write the report
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install -g jev-audit # or keep using npx
|
|
56
|
+
jev-audit install claude # claude | codex | cursor | grok | agy | pi | zcode | agents
|
|
57
|
+
# anything else: jev-audit install --dir <skills-dir>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Ask your agent:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
> audit this repo with jev-audit
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
You get a **`JEV_AUDIT.md`** in the repo root:
|
|
67
|
+
|
|
68
|
+
```markdown
|
|
69
|
+
# Jev Audit — acme-support
|
|
70
|
+
|
|
71
|
+
- 37 LLM call sites · 11 Replace · 3 Hybrid · 19 Keep LLM · 4 Pure code
|
|
72
|
+
- Estimated saving on the Replace set: $212 per 1,000 calls (math in §5)
|
|
73
|
+
- Best quick win: src/triage.js:42 — leaf classification, lowest blast radius
|
|
74
|
+
|
|
75
|
+
| # | Location | Job of the call | Tier |
|
|
76
|
+
|---|---------------------|-------------------------------------|-------------------------------|
|
|
77
|
+
| 1 | src/triage.js:42 | department + urgency + frustration | Replace → choice + noul + score |
|
|
78
|
+
| 2 | src/moderation.py:17| safe / unsafe / borderline | Replace → choice |
|
|
79
|
+
| 3 | src/router.ts:88 | query → specialist agent | Replace → choice + noul |
|
|
80
|
+
| 4 | src/draft.js:12 | write the customer-facing reply | Keep LLM (real generation) |
|
|
81
|
+
|
|
82
|
+
…plus the draft Jev question JSON, confidence thresholds, target
|
|
83
|
+
architecture diagram, and a shadow-mode migration plan.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### What a replacement looks like
|
|
87
|
+
|
|
88
|
+
**Before** — 1.4 s, ~$0.003/call, JSON-parse roulette:
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
const res = await openai.chat.completions.create({
|
|
92
|
+
model: 'gpt-4o-mini',
|
|
93
|
+
messages: [{ role: 'system', content: 'Classify this ticket. Reply with JSON…' }, …],
|
|
94
|
+
});
|
|
95
|
+
const { department, urgent } = JSON.parse(res.choices[0].message.content);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**After** — ~100 ms, ~$0.0000004/call, typed:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
const r = await client.systemOne({
|
|
102
|
+
state: ticket,
|
|
103
|
+
questions: {
|
|
104
|
+
department: choice({ instructions: 'Which team should handle this',
|
|
105
|
+
criteria: { billing: '…', technical: '…', sales: '…' } }),
|
|
106
|
+
is_urgent: noul({ instructions: 'The message conveys urgency' }),
|
|
107
|
+
frustration: score({ instructions: 'How frustrated the customer appears',
|
|
108
|
+
criteria: ['Calm', 'Frustrated but civil', 'Very angry'] }),
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
r.answers.department.choice // "billing" — it cannot be anything else
|
|
113
|
+
r.answers.department.confidence // 0.84 → gate automation on this
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## The one table that matters
|
|
117
|
+
|
|
118
|
+
| Job at the call site | Verdict |
|
|
119
|
+
|---|---|
|
|
120
|
+
| Yes/no judgment on text (spam, urgent, jailbreak) | **Replace** → Jev `noul` |
|
|
121
|
+
| Pick one of N (intent, routing, category, moderation) | **Replace** → Jev `choice` |
|
|
122
|
+
| Level on a rubric (severity, quality, priority) | **Replace** → Jev `score` |
|
|
123
|
+
| Decide, then write | **Hybrid** — Jev decides, LLM writes |
|
|
124
|
+
| LLM output that needs checking against rules/sources | **Hybrid** — LLM writes, Jev verifies |
|
|
125
|
+
| Prose, summaries, code, translation | **Keep LLM** |
|
|
126
|
+
| Counting, dates, exact matching | **Pure code** — not even Jev |
|
|
127
|
+
|
|
128
|
+
## How it works
|
|
129
|
+
|
|
130
|
+
1. **Local scanner** — zero dependencies, zero network. Regex-inventories SDK imports (`openai`, `@anthropic-ai/sdk`, `langchain`, `@ai-sdk`, …), raw endpoints, env keys, and call shapes, and flags *decision-shaped* lines as prime candidates.
|
|
131
|
+
2. **Agent skill** — `skill/jev-audit/SKILL.md` makes your coding agent read each call site, apply the tiering table, show the savings math, and write `JEV_AUDIT.md` with the target architecture (`code → Jev → LLM`) mapped onto your modules.
|
|
132
|
+
|
|
133
|
+
Works with any SKILL.md-compatible harness: Claude Code, Codex, Cursor, Grok, Agy, Pi, ZCode, …
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# GitHub install (skills.sh-compatible layout):
|
|
137
|
+
npx skills add MagicBeansAI/jev-audit --skill jev-audit
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## FAQ
|
|
141
|
+
|
|
142
|
+
**Does it send my code anywhere?** No. The scanner is local regex; the analysis runs in your own agent session. The audit never calls Jev — it only tells you where *you* should.
|
|
143
|
+
|
|
144
|
+
**Is it affiliated with TypeSafe AI?** No — independent community tool by [MagicBeansAI](https://github.com/MagicBeansAI). Jev pricing/limits are quoted from the public docs; verify before budgeting.
|
|
145
|
+
|
|
146
|
+
**Why "decision-shaped"?** If downstream code branches on the output instead of a human reading it, a typed decision beats a generation: cheaper, faster, and structurally incapable of inventing a fourth option.
|
|
147
|
+
|
|
148
|
+
**I already use structured output / JSON mode. Why change?** Structured output still pays generation prices and generation latency for a decision, still drifts outside the schema often enough to need retries, and gives you no calibrated confidence to gate automation on.
|
|
149
|
+
|
|
150
|
+
## Contributing
|
|
151
|
+
|
|
152
|
+
Pattern misses (an SDK we don't spot, a call shape that escapes us) are the highest-value PRs — they live in one file: [`skill/jev-audit/scripts/scan.js`](skill/jev-audit/scripts/scan.js). Report false positives/negatives as issues with the offending line.
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
[MIT](LICENSE) © MagicBeansAI
|
package/bin/jev-audit.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// jev-audit CLI — scan a repo for replaceable LLM calls, install the agent skill.
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const VERSION = '0.1.0';
|
|
10
|
+
const SKILL_SRC = path.join(__dirname, '..', 'skill', 'jev-audit');
|
|
11
|
+
|
|
12
|
+
const HARNESS_DIRS = {
|
|
13
|
+
claude: '.claude',
|
|
14
|
+
codex: '.codex',
|
|
15
|
+
cursor: '.cursor',
|
|
16
|
+
grok: '.grok',
|
|
17
|
+
agy: '.agy',
|
|
18
|
+
pi: '.pi',
|
|
19
|
+
zcode: '.zcode',
|
|
20
|
+
agents: '.agents',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function usage() {
|
|
24
|
+
console.log(`
|
|
25
|
+
jev-audit v${VERSION} — find the decisions hiding in your LLM bill
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
jev-audit [scan] [path] Inventory LLM call sites (local regex scan, no network).
|
|
29
|
+
Writes jev-scan.json and prints the summary.
|
|
30
|
+
jev-audit install <target> Install the jev-audit skill into an agent harness.
|
|
31
|
+
Targets: ${Object.keys(HARNESS_DIRS).join(' | ')}
|
|
32
|
+
Options: --project (cwd instead of ~), --dir <path>
|
|
33
|
+
jev-audit help This message.
|
|
34
|
+
|
|
35
|
+
Then, inside your agent: "audit this repo with jev-audit"
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function copyDir(src, dest) {
|
|
40
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
41
|
+
for (const e of fs.readdirSync(src, { withFileTypes: true })) {
|
|
42
|
+
const s = path.join(src, e.name);
|
|
43
|
+
const d = path.join(dest, e.name);
|
|
44
|
+
if (e.isDirectory()) copyDir(s, d);
|
|
45
|
+
else fs.copyFileSync(s, d);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function cmdInstall(target, opts) {
|
|
50
|
+
const t = HARNESS_DIRS[target] ? target : 'agents';
|
|
51
|
+
if (target && !HARNESS_DIRS[target]) {
|
|
52
|
+
console.log(`Unknown harness "${target}" — defaulting to "agents" (~/.agents/skills is the cross-tool location). Use --dir to override.`);
|
|
53
|
+
}
|
|
54
|
+
let dest;
|
|
55
|
+
if (opts.dir) {
|
|
56
|
+
dest = path.join(opts.dir, 'jev-audit');
|
|
57
|
+
} else {
|
|
58
|
+
const base = opts.project ? process.cwd() : os.homedir();
|
|
59
|
+
dest = path.join(base, HARNESS_DIRS[t], 'skills', 'jev-audit');
|
|
60
|
+
}
|
|
61
|
+
copyDir(SKILL_SRC, dest);
|
|
62
|
+
console.log(`Installed skill → ${dest}`);
|
|
63
|
+
console.log(`Restart your agent if it was running, then ask: "audit this repo with jev-audit"`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function main() {
|
|
67
|
+
const argv = process.argv.slice(2);
|
|
68
|
+
const opts = { dir: null, project: false };
|
|
69
|
+
const positional = [];
|
|
70
|
+
for (let i = 0; i < argv.length; i++) {
|
|
71
|
+
if (argv[i] === '--dir') opts.dir = argv[++i];
|
|
72
|
+
else if (argv[i] === '--project') opts.project = true;
|
|
73
|
+
else if (argv[i] === '--version' || argv[i] === '-v') return console.log(VERSION);
|
|
74
|
+
else positional.push(argv[i]);
|
|
75
|
+
}
|
|
76
|
+
const cmd = positional[0] || 'scan';
|
|
77
|
+
|
|
78
|
+
if (cmd === 'help' || cmd === '--help' || cmd === '-h') return usage();
|
|
79
|
+
|
|
80
|
+
if (cmd === 'install') {
|
|
81
|
+
return cmdInstall(positional[1], opts);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (cmd === 'scan' || !fs.existsSync(path.resolve(cmd))) {
|
|
85
|
+
if (cmd !== 'scan') console.log(`"${cmd}" is not a path — treating this as a scan of the current directory.\n`);
|
|
86
|
+
const { runScan, printSummary } = require(path.join(SKILL_SRC, 'scripts', 'scan.js'));
|
|
87
|
+
const result = runScan(positional[1] || '.');
|
|
88
|
+
printSummary(result);
|
|
89
|
+
fs.writeFileSync(path.join(process.cwd(), 'jev-scan.json'), JSON.stringify(result, null, 2));
|
|
90
|
+
console.log(`\n→ full inventory: ${path.join(process.cwd(), 'jev-scan.json')}`);
|
|
91
|
+
console.log(`→ next: npx jev-audit install claude|codex|cursor|grok|agy|pi|zcode|agents, then ask your agent to "audit this repo with jev-audit".`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// bare path argument: scan it
|
|
96
|
+
const { runScan, printSummary } = require(path.join(SKILL_SRC, 'scripts', 'scan.js'));
|
|
97
|
+
const result = runScan(cmd);
|
|
98
|
+
printSummary(result);
|
|
99
|
+
fs.writeFileSync(path.join(process.cwd(), 'jev-scan.json'), JSON.stringify(result, null, 2));
|
|
100
|
+
console.log(`\n→ full inventory: ${path.join(process.cwd(), 'jev-scan.json')}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jev-audit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Find every LLM call in your codebase that should be a Jev decision instead of a generation. Scans locally, classifies call sites, and designs the replacement architecture.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "MagicBeansAI",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/MagicBeansAI/jev-audit.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/MagicBeansAI/jev-audit/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/MagicBeansAI/jev-audit#readme",
|
|
15
|
+
"bin": {
|
|
16
|
+
"jev-audit": "bin/jev-audit.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin/",
|
|
20
|
+
"skill/",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"jev",
|
|
26
|
+
"typesafe",
|
|
27
|
+
"system-one",
|
|
28
|
+
"llm",
|
|
29
|
+
"audit",
|
|
30
|
+
"cost-optimization",
|
|
31
|
+
"agent",
|
|
32
|
+
"skill",
|
|
33
|
+
"claude",
|
|
34
|
+
"codex",
|
|
35
|
+
"cursor",
|
|
36
|
+
"grok"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=16"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jev-audit
|
|
3
|
+
description: Audit a codebase to find LLM calls that can be replaced with Jev (TypeSafe AI's System One decision model) and design the replacement architecture, producing a JEV_AUDIT.md report. Use when the user asks to evaluate or audit AI/LLM usage in a repo, cut LLM costs or latency, replace/migrate LLM calls to Jev or decision models, figure out which calls are decisions vs generation, or mentions jev-audit, typesafe, System One, or "what can I move to Jev".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Jev Audit
|
|
7
|
+
|
|
8
|
+
You are auditing this codebase to answer one question: **which LLM calls here are actually decisions, and what would they look like as Jev calls?**
|
|
9
|
+
|
|
10
|
+
Jev is a decision model, not a text generator. You send it a `state` (context) plus typed questions and get back calibrated decisions — `noul` (yes/no probability), `choice` (pick from options), `score` (rubric level) — with confidence. It is ~2 orders of magnitude faster and cheaper than LLMs for these calls, and cannot hallucinate because it cannot emit anything outside the types you declared. Generation stays on the LLM. Read `references/jev-reference.md` before proposing any code.
|
|
11
|
+
|
|
12
|
+
## Ground rules
|
|
13
|
+
|
|
14
|
+
- Cite `file:line` for every claim in the report. No invented call sites.
|
|
15
|
+
- Every cost number is an estimate — show the arithmetic and the assumptions (volume, tokens/call). If volume is unknown, express savings per 1,000 calls, not per month.
|
|
16
|
+
- Never propose Jev for generation, summarization, code writing, math/counting, date comparison, or anything involving images/audio. Proposing these destroys the report's credibility.
|
|
17
|
+
- Never propose any model (Jev included) where a plain `if`, regex, or schema validation already works. An if-statement that costs nothing beats a model call that can be wrong.
|
|
18
|
+
|
|
19
|
+
## Step 1 — Inventory the LLM surface
|
|
20
|
+
|
|
21
|
+
Run the bundled scanner (local, zero network, zero dependencies):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
node <this-skill-dir>/scripts/scan.js <repo-path>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
It writes `jev-scan.json` with every hit as `{file, line, category, pattern, snippet, decisionShaped}`. If the scanner is unavailable, reproduce it with ripgrep over `*.js *.jsx *.ts *.tsx *.mjs *.cjs *.py *.rb *.go *.rs *.java *.kt *.swift *.php *.cs *.sh *.yaml *.yml *.toml *.json` plus `.env*`, searching for:
|
|
28
|
+
|
|
29
|
+
- SDK imports: `openai`, `@anthropic-ai/sdk`, `@google/generative-ai`, `@ai-sdk/`, `from "ai"`, `langchain`, `@langchain`, `llamaindex`, `mistral`, `cohere`, `groq`, `@xai/sdk`, `ollama`, `@azure/openai`, `bedrock`
|
|
30
|
+
- Endpoints: `api.openai.com`, `api.anthropic.com`, `generativelanguage.googleapis.com`, `api.groq.com`, `api.x.ai`, `openrouter.ai`, `api.deepseek.com`, `api.together.xyz`, `api.mistral.ai`, `api.cohere.com`
|
|
31
|
+
- Keys: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `MISTRAL_API_KEY`, `GROQ_API_KEY`, `XAI_API_KEY`, `COHERE_API_KEY`
|
|
32
|
+
- Call shapes: `chat.completions.create`, `messages.create`, `generateContent`, `respond`, `invoke(`, `ChatOpenAI`, `agent(`
|
|
33
|
+
|
|
34
|
+
Lines already using `@typesafe-ai/sdk`, `typesafe_sdk`, or `api.typesafe.ai` are migrated — list them as "already Jev" and exclude from savings.
|
|
35
|
+
|
|
36
|
+
## Step 2 — Read every call site
|
|
37
|
+
|
|
38
|
+
The scanner finds lines; only reading code reveals the *job*. For each hit, open the file and record:
|
|
39
|
+
|
|
40
|
+
- **Job**: what the call decides or produces (read the prompt, the system message, how the caller consumes the output).
|
|
41
|
+
- **Output consumption**: does downstream code branch/compare/route on the result (decision-shaped), or display/use free text (generation)?
|
|
42
|
+
- **Model + sizes**: model name, rough input tokens, `max_tokens`/output size.
|
|
43
|
+
- **Frequency hints**: request path, queue consumer, cron, batch script, or user-facing per-request.
|
|
44
|
+
- **Failure handling**: retries, fallbacks, JSON parsing of model output (a JSON-parse-after-LLM is a strong decision-in-disguise smell).
|
|
45
|
+
|
|
46
|
+
Skip vendored/generated code, tests with mocked calls (note them, don't count them), and documentation.
|
|
47
|
+
|
|
48
|
+
## Step 3 — Classify each call site
|
|
49
|
+
|
|
50
|
+
Assign exactly one tier per call site:
|
|
51
|
+
|
|
52
|
+
| Job at the call site | Tier | Jev primitive |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Yes/no judgment on text: spam, urgent, toxic, refund requested, jailbreak attempted, field is empty-ish | **Replace** | `noul` |
|
|
55
|
+
| Pick one of N: intent → team, category labeling, route to model/prompt/agent/tool, moderation class, which candidate matches an entity | **Replace** | `choice` |
|
|
56
|
+
| Level on a rubric: severity, quality, priority, relevance, frustration, lead score | **Replace** | `score` |
|
|
57
|
+
| Multi-factor judgment ("rate this pitch") | **Replace** | composite: several `score`s, weighted in code |
|
|
58
|
+
| Decide then write: classify/route first, generate only for the chosen branch | **Hybrid** | Jev decides, LLM generates the branch that needs prose |
|
|
59
|
+
| LLM generates, then its output needs checking against rules/sources | **Hybrid** | LLM writes, Jev verifies (`noul` per claim / `choice` per class) |
|
|
60
|
+
| Free-text generation: summaries, drafts, code, translations, answers | **Keep LLM** | — |
|
|
61
|
+
| Arithmetic, counting, comparing dates, exact string/schema matching | **Pure code** | — |
|
|
62
|
+
|
|
63
|
+
Anything you cannot confidently assign after reading the code goes in a final "Needs human judgment" section — do not guess it into Replace.
|
|
64
|
+
|
|
65
|
+
## Step 4 — Estimate impact
|
|
66
|
+
|
|
67
|
+
For each Replace/Hybrid call site: `savings ≈ calls × input_tokens × (LLM_input_$per_M − 0.042) + calls × output_tokens × LLM_output_$per_M` (Jev output tokens are free; check current prices in `references/jev-reference.md`). Show per-1,000-call savings when volume is unknown, and state the blended assumption you used for the incumbent LLM.
|
|
68
|
+
|
|
69
|
+
## Step 5 — Design the target architecture
|
|
70
|
+
|
|
71
|
+
Read `references/architecture.md`, then produce:
|
|
72
|
+
|
|
73
|
+
1. A target-architecture diagram (mermaid) of the three layers: deterministic code → Jev decisions → LLM generation, with this repo's actual modules on it.
|
|
74
|
+
2. For each of the top 3–5 Replace candidates, the exact draft Jev question set (JSON with `state` source, `type`, `instructions`, `criteria`) and the confidence thresholds for act / human-review / escalate-to-LLM.
|
|
75
|
+
3. A migration order: leaf classification calls first (lowest blast radius), verification loops second, routing last — with a shadow-mode step (Jev runs alongside the LLM, results compared, before cutover).
|
|
76
|
+
|
|
77
|
+
## Step 6 — Write the report
|
|
78
|
+
|
|
79
|
+
Fill in `assets/REPORT_TEMPLATE.md` and write it to `JEV_AUDIT.md` at the repo root. Keep the attribution footer intact — it is how the report travels. If the user wants starter code, additionally emit `jev/questions.ts` (or `.py`) with the draft question definitions behind a thin adapter so Jev and LLM stay swappable.
|
|
80
|
+
|
|
81
|
+
Finally, give the user a short summary in chat: total call sites, counts per tier, the single best quick win, and point at `JEV_AUDIT.md`.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Jev Audit — <repo name>
|
|
2
|
+
|
|
3
|
+
> Which LLM calls in this codebase are actually decisions, and what would they look like on Jev?
|
|
4
|
+
> Generated <date> · Scanner hits: <N> · Call sites reviewed: <M> · Analysis by the coding agent using **jev-audit**
|
|
5
|
+
|
|
6
|
+
## 1. Executive summary
|
|
7
|
+
|
|
8
|
+
- LLM call sites found: **<total>** across <files> files (<sdks>).
|
|
9
|
+
- **<n1> can move to Jev now** (Replace), **<n2> split into decide + generate** (Hybrid), **<n3> should stay on the LLM**, **<n4> shouldn't be a model call at all** (pure code).
|
|
10
|
+
- Estimated saving on the Replace set: **<$X per 1,000 calls** (assumptions in §5).
|
|
11
|
+
- Best quick win: <one sentence — highest confidence, lowest blast radius>.
|
|
12
|
+
|
|
13
|
+
## 2. Inventory
|
|
14
|
+
|
|
15
|
+
| # | Location | SDK / model | Job of the call | Calls/day | Tier |
|
|
16
|
+
|---|---|---|---|---|---|
|
|
17
|
+
| 1 | `src/triage.js:42` | openai / gpt-4o-mini | Classify ticket → department + urgency | ~4,000 | Replace |
|
|
18
|
+
| 2 | … | | | | |
|
|
19
|
+
|
|
20
|
+
Every row must cite `file:line`. `Calls/day` = observed (queue/cron/route) or **assumed** in bold.
|
|
21
|
+
|
|
22
|
+
## 3. Tier detail
|
|
23
|
+
|
|
24
|
+
### 3.1 Replace — move to Jev now
|
|
25
|
+
|
|
26
|
+
For each: the job, why it's a decision not a generation, and the draft Jev question set.
|
|
27
|
+
|
|
28
|
+
#### <Call site #1 — `src/triage.js:42`>
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"model": "jev-latest",
|
|
33
|
+
"state": { "subject": "<from ticket>", "body": "<from ticket>", "plan": "<customer plan>" },
|
|
34
|
+
"questions": {
|
|
35
|
+
"department": {
|
|
36
|
+
"type": "choice",
|
|
37
|
+
"instructions": "Which team should handle this `body`",
|
|
38
|
+
"criteria": { "billing": "…", "technical": "…", "sales": "…" }
|
|
39
|
+
},
|
|
40
|
+
"is_urgent": { "type": "noul", "instructions": "The `body` conveys urgency or time-sensitivity" }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Confidence plan: ≥0.9 act · 0.5–0.9 template path · <0.5 human queue.
|
|
46
|
+
|
|
47
|
+
### 3.2 Hybrid — Jev decides, LLM generates
|
|
48
|
+
|
|
49
|
+
<Which sub-call moves, which stays, and where the seam goes.>
|
|
50
|
+
|
|
51
|
+
### 3.3 Keep on LLM
|
|
52
|
+
|
|
53
|
+
<Each with one honest sentence why: output is prose consumed by humans / genuine generation.>
|
|
54
|
+
|
|
55
|
+
### 3.4 Pure code — stop calling a model
|
|
56
|
+
|
|
57
|
+
<Schema checks, exact matches, counting — the deterministic call that was always there.>
|
|
58
|
+
|
|
59
|
+
### 3.5 Needs human judgment
|
|
60
|
+
|
|
61
|
+
<Couldn't determine from code alone — what to check and how.>
|
|
62
|
+
|
|
63
|
+
## 4. Target architecture
|
|
64
|
+
|
|
65
|
+
```mermaid
|
|
66
|
+
flowchart LR
|
|
67
|
+
%% current flow with LLM call sites colored by tier, then target three-layer flow
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
<Diagram + narrative: code layer → Jev layer → LLM layer with this repo's real module names; adapter seam location; shadow-mode → canary → rollback plan.>
|
|
71
|
+
|
|
72
|
+
## 5. Savings estimate
|
|
73
|
+
|
|
74
|
+
| Call site | Calls/mo | In tok/call | Out tok/call | LLM $/mo | Jev $/mo | Saving |
|
|
75
|
+
|---|---|---|---|---|---|---|
|
|
76
|
+
|
|
77
|
+
Arithmetic shown per row. Assumptions: <blended incumbent price, volumes, token sizes>. **These are estimates from static analysis — validate against real usage logs before budgeting.**
|
|
78
|
+
|
|
79
|
+
## 6. Migration order
|
|
80
|
+
|
|
81
|
+
1. <Leaf classification call — lowest blast radius, shadow-mode first>
|
|
82
|
+
2. …
|
|
83
|
+
3. <Routing change last — biggest downstream impact>
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
*Audit generated with **jev-audit** — find the decisions hiding in your LLM bill: `npx jev-audit`*
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Target architecture patterns
|
|
2
|
+
|
|
3
|
+
The governing principle is a three-layer split, each layer doing only what it is good at:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌─────────────────────────────────────────────────────────┐
|
|
7
|
+
│ Layer 1 — Code (deterministic) │
|
|
8
|
+
│ if / regex / schema validation. Free, exact, testable. │
|
|
9
|
+
├─────────────────────────────────────────────────────────┤
|
|
10
|
+
│ Layer 2 — Jev (decisions) │
|
|
11
|
+
│ classify, route, score, verify. Typed, calibrated, │
|
|
12
|
+
│ ~100ms, $0.042/M input, output free. │
|
|
13
|
+
├─────────────────────────────────────────────────────────┤
|
|
14
|
+
│ Layer 3 — LLM (generation) │
|
|
15
|
+
│ write prose/code only when prose/code is the product. │
|
|
16
|
+
└─────────────────────────────────────────────────────────┘
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
An if-statement that costs nothing beats a model call that can be wrong; a Jev decision beats an LLM call whenever the output is consumed by code rather than read by a human. Cost savings = (share of calls that are decisions) × (price delta on those calls) — so report savings only against calls that actually move.
|
|
20
|
+
|
|
21
|
+
## Pattern 1 — Confidence-gated routing
|
|
22
|
+
|
|
23
|
+
Every Jev answer carries `confidence` (and Choice gives the full probability distribution). Route on it:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
noul ≥ 0.9 → act autonomously
|
|
27
|
+
0.5 ≤ noul < 0.9 → cheap LLM double-check or template path
|
|
28
|
+
noul < 0.5 → human review queue
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Thresholds are product decisions, not defaults — set them per question in one reviewable config file, and tune with real traffic. This is how you get "zero hallucination" in practice: low confidence never reaches automation.
|
|
32
|
+
|
|
33
|
+
## Pattern 2 — Hybrid pipeline (Jev decides, LLM writes)
|
|
34
|
+
|
|
35
|
+
Classic support-flow replacement:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
ticket in ──▶ Jev: department(choice) + urgency(noul) + frustration(score)
|
|
39
|
+
│
|
|
40
|
+
├─ billing + urgent + frustrated ──▶ LLM drafts empathetic reply (only here)
|
|
41
|
+
├─ technical ──▶ template + KB link (no model at all)
|
|
42
|
+
└─ low confidence ──▶ human queue
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The LLM call now happens on a fraction of tickets, and the decision calls cost ~nothing. Same shape applies to content moderation, lead handling, and agent tool-selection (Jev picks the tool; the LLM fills arguments only when needed).
|
|
46
|
+
|
|
47
|
+
## Pattern 3 — Speculative fan-out
|
|
48
|
+
|
|
49
|
+
Questions in one request run in parallel and in isolation — ask everything that *might* matter, even if some answers go unused on a given input. Latency stays ~flat; cost is input tokens only (output is free). A TypeSafe cookbook batched 13 questions in one call for a reported 12.2× cost cut vs sequential calls. So: one call per request context, all atomic questions together.
|
|
50
|
+
|
|
51
|
+
## Pattern 4 — Composite scoring with code-owned weights
|
|
52
|
+
|
|
53
|
+
Never ask Jev one blobby "rate this". Decompose into atomic `score`s (each a judgment a knowledgeable person makes in seconds), then combine in code:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const pitchScore =
|
|
57
|
+
0.4 * normalize(answers.market_size.score) +
|
|
58
|
+
0.35 * normalize(answers.feasibility.score) +
|
|
59
|
+
0.25 * normalize(answers.differentiation.score);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
When priorities shift, change a coefficient — no prompt rewrite, no re-eval of everything else.
|
|
63
|
+
|
|
64
|
+
## Pattern 5 — Extraction as selection
|
|
65
|
+
|
|
66
|
+
Jev picks, it doesn't recall. Pipeline: regex/parser/cheap-LLM produces candidate values → Jev `choice` selects the right one (with confidence). Use for entity alignment, field extraction from messy text, picking which of N retrieved passages answers a query (cookbooks show BM25 shortlist + Jev rerank lifting top-1 accuracy meaningfully).
|
|
67
|
+
|
|
68
|
+
## Pattern 6 — Verify what the LLM generates
|
|
69
|
+
|
|
70
|
+
Keep generation on the LLM; add a Jev verification pass: each factual claim → `noul` "is this supported by `source`?" or output class → `choice` safe/unsafe. Low-confidence claims get flagged or stripped before the user sees them. This converts "LLM output, hopefully fine" into "LLM output with a calibrated check" — usually the easiest sell to whoever owns reliability.
|
|
71
|
+
|
|
72
|
+
## Migration sequencing
|
|
73
|
+
|
|
74
|
+
1. **Inventory + label** (this audit's report).
|
|
75
|
+
2. **Adapter seam**: put decisions behind an interface so implementations are swappable and the LLM stays as fallback:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
interface Triager {
|
|
79
|
+
triage(ticket: Ticket): Promise<{ dept: Dept; urgent: boolean; confidence: number }>;
|
|
80
|
+
}
|
|
81
|
+
// JevTriager (primary) — LlmTriager (existing behavior, kept as fallback)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
3. **Shadow mode**: route real traffic through both, log disagreements, tune thresholds/criteria wording offline.
|
|
85
|
+
4. **Canary cutover** on the leaf classification calls first (lowest blast radius), then verification passes, then routing (which changes downstream behavior most).
|
|
86
|
+
5. **Rollback** = flip the adapter back. Keep the LLM path until Jev has weeks of clean production data.
|
|
87
|
+
|
|
88
|
+
## What the report's architecture section must contain
|
|
89
|
+
|
|
90
|
+
- Mermaid diagram of current flow with each LLM call marked by tier.
|
|
91
|
+
- Same diagram for the target three-layer flow, using the repo's real module names.
|
|
92
|
+
- The draft Jev question set for top candidates (exact JSON).
|
|
93
|
+
- Confidence thresholds and what each band does (act / verify / human).
|
|
94
|
+
- The adapter seam location (file + interface name) and the shadow-mode plan.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Jev reference — API, primitives, limits, pricing
|
|
2
|
+
|
|
3
|
+
Facts only; verify against https://docs.typesafe.ai before finalizing generated integration code — early-access details move fast.
|
|
4
|
+
|
|
5
|
+
## Mental model
|
|
6
|
+
|
|
7
|
+
Jev is TypeSafe AI's System One model. You send a `state` (the context: string, object, or array) plus typed `questions`; it returns decisions your code branches on. It does not generate text. Every question in a request is evaluated independently and in parallel against the shared state — batching does not change answers.
|
|
8
|
+
|
|
9
|
+
## Primitives
|
|
10
|
+
|
|
11
|
+
| Type | Question | Answer fields |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `noul` | Is this true? | `noul`: P(true), 0–1 |
|
|
14
|
+
| `choice` | Which option? | `choice`, `probabilities` (full distribution), `confidence` |
|
|
15
|
+
| `score` | Which level on an ordered rubric? | `score` (probability-weighted mean over levels), `legend`, `probabilities`, `confidence` |
|
|
16
|
+
|
|
17
|
+
`confidence` is the model's self-assessed reliability — distinct from `probability`, and the signal you gate autonomous action on.
|
|
18
|
+
|
|
19
|
+
## HTTP API
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
POST https://api.typesafe.ai/v1/systemone
|
|
23
|
+
Authorization: Bearer $TYPESAFE_API_KEY
|
|
24
|
+
Content-Type: application/json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Models: `jev-latest`, `jev-preview`, and pinned versions like `jev-1.13.0` (responses echo the exact model that answered — pin versions when you tune thresholds). Key: https://console.typesafe.ai/keys
|
|
28
|
+
|
|
29
|
+
### Request
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"model": "jev-latest",
|
|
34
|
+
"state": { "message": "Hi, I've been trying to connect my Stripe account for 3 days..." },
|
|
35
|
+
"questions": {
|
|
36
|
+
"department": {
|
|
37
|
+
"type": "choice",
|
|
38
|
+
"instructions": "Which team should handle this",
|
|
39
|
+
"criteria": {
|
|
40
|
+
"billing": "Payment or subscription issues",
|
|
41
|
+
"technical": "Bugs or integration problems",
|
|
42
|
+
"sales": "Pricing or account questions"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"frustration": {
|
|
46
|
+
"type": "score",
|
|
47
|
+
"instructions": "How frustrated the customer appears",
|
|
48
|
+
"criteria": [
|
|
49
|
+
"Calm, just stating facts",
|
|
50
|
+
"Frustrated but civil",
|
|
51
|
+
"Very angry, strong language"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"is_urgent": {
|
|
55
|
+
"type": "noul",
|
|
56
|
+
"instructions": "The message conveys urgency or time-sensitivity"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Backtick a state path inside `instructions` (e.g. "Does `message` mention a duplicate charge?") to pin the question to a field.
|
|
63
|
+
|
|
64
|
+
### Response
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"model": "jev-latest",
|
|
69
|
+
"answers": {
|
|
70
|
+
"department": {
|
|
71
|
+
"type": "choice",
|
|
72
|
+
"choice": "billing",
|
|
73
|
+
"probabilities": { "billing": 0.84, "technical": 0.159, "sales": 0.001 },
|
|
74
|
+
"confidence": 0.596
|
|
75
|
+
},
|
|
76
|
+
"frustration": {
|
|
77
|
+
"type": "score",
|
|
78
|
+
"score": 1.035,
|
|
79
|
+
"legend": { "0": "Calm, just stating facts", "1": "Frustrated but civil", "2": "Very angry, strong language" },
|
|
80
|
+
"probabilities": { "0": 0.05, "1": 0.88, "2": 0.07 },
|
|
81
|
+
"confidence": 0.842
|
|
82
|
+
},
|
|
83
|
+
"is_urgent": { "type": "noul", "noul": 0.999 }
|
|
84
|
+
},
|
|
85
|
+
"usage": { "input_tokens": 312, "output_tokens": 48 }
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Structural guarantee: Jev cannot invent a fourth choice option — type-error rate on the label is 0 by construction.
|
|
90
|
+
|
|
91
|
+
## SDKs
|
|
92
|
+
|
|
93
|
+
**Python** (≥3.10): `pip install typesafe-sdk`
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
|
|
97
|
+
|
|
98
|
+
client = TypeSafeClient() # reads TYPESAFE_API_KEY
|
|
99
|
+
r = client.system_one(
|
|
100
|
+
state=ticket_text,
|
|
101
|
+
questions={
|
|
102
|
+
"is_urgent": Noul(instructions="The message conveys urgency"),
|
|
103
|
+
"department": Choice(instructions="Which team should handle this",
|
|
104
|
+
criteria={"billing": "...", "technical": "...", "sales": "..."}),
|
|
105
|
+
"frustration": Score(instructions="How frustrated the customer appears",
|
|
106
|
+
criteria=["Calm, just stating facts", "Frustrated but civil", "Very angry"]),
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
r.answers["department"].choice # "billing"
|
|
110
|
+
r.answers["frustration"].score # 1.035
|
|
111
|
+
r.answers["is_urgent"].noul # 0.999
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**JavaScript/TypeScript** (Node ≥20): `npm install @typesafe-ai/sdk` — `TypeSafeClient` plus `noul()` / `choice()` / `score()` helpers; Choice labels infer as TypeScript literal unions.
|
|
115
|
+
|
|
116
|
+
**Vercel AI SDK ≥7.0.105**: provider `@ai-sdk/typesafe-ai` (or gateway `typesafe-ai/jev`) via `experimental_evaluate`; vocabulary differs — "boolean" instead of noul, answer field is `probability`, confidence lives in `providerMetadata.typesafe.confidence`.
|
|
117
|
+
|
|
118
|
+
## Limits and errors
|
|
119
|
+
|
|
120
|
+
- State + questions ≤ ~64K tokens; state + longest question ≤ ~32K tokens (~150K chars).
|
|
121
|
+
- Choice: up to 255 options. Score: 2–10 levels. Text input only — no images/audio/video.
|
|
122
|
+
- Errors: 401 bad key, 422 validation, 429/529 → retry with backoff. Early-access rate limits: ~250K tokens/sec, ~1,200 req/min.
|
|
123
|
+
- Rate limits and version pins matter for threshold-tuned code — re-check the docs when generating final integration code.
|
|
124
|
+
|
|
125
|
+
## Pricing and latency (as of 2026-09, early access)
|
|
126
|
+
|
|
127
|
+
- Input: **$0.042 per million tokens**. Output tokens: **free**.
|
|
128
|
+
- 70–500 ms end-to-end, ~100 ms typical, roughly flat in number of questions.
|
|
129
|
+
- Vendor headline vs frontier LLMs on decision workloads: ~193× faster, ~444× cheaper (their ceiling numbers — treat as directional).
|
|
130
|
+
- For savings math, price the incumbent LLM at its real list price (e.g. ~$3–15/M input, ~$15–75/M output depending on model class), not at Jev's marketing comparison.
|
|
131
|
+
|
|
132
|
+
## Known jagged edges — never move these to Jev
|
|
133
|
+
|
|
134
|
+
- Text generation of any kind: summaries, drafts, code, translations.
|
|
135
|
+
- Arithmetic, counting, exact comparison (fails things like hex-color similarity).
|
|
136
|
+
- Date/time reasoning (treats dates as text).
|
|
137
|
+
- Interpolating a Score of 1.4 as "40% between levels" — use scores for thresholds and ranking only.
|
|
138
|
+
- Extraction by free recall: Jev picks from candidates, it doesn't name them. Find candidates with regex/LLM, let Jev choose.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// jev-audit scanner — local, zero-dependency inventory of LLM call sites.
|
|
3
|
+
// Usage: node scan.js <path> (writes jev-scan.json in cwd, prints a summary)
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const SKIP_DIRS = new Set([
|
|
10
|
+
'node_modules', '.git', 'dist', 'build', 'out', 'vendor', '.next', '.nuxt',
|
|
11
|
+
'coverage', '.venv', 'venv', '__pycache__', '.cache', '.terraform', '.idea',
|
|
12
|
+
'.vscode', 'target', '.gradle', 'bower_components', '.pytest_cache',
|
|
13
|
+
'.mypy_cache', '.circleci', '.github',
|
|
14
|
+
// agent-harness config + project-local installed skills are not app call sites
|
|
15
|
+
'.agents', '.claude', '.codex', '.cursor', '.zcode', '.grok', '.agy', '.pi',
|
|
16
|
+
]);
|
|
17
|
+
const EXTS = new Set([
|
|
18
|
+
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.go', '.rs',
|
|
19
|
+
'.java', '.kt', '.swift', '.php', '.cs', '.sh', '.yaml', '.yml', '.toml', '.json',
|
|
20
|
+
]);
|
|
21
|
+
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
22
|
+
|
|
23
|
+
const PATTERNS = [
|
|
24
|
+
// SDK imports / clients
|
|
25
|
+
{ id: 'openai', category: 'sdk', re: /(?:from\s+|import\s+[\w{},\s]*from\s+|require\(\s*)['"]openai[/'"]|@azure\/openai|new\s+OpenAI\b/ },
|
|
26
|
+
{ id: 'anthropic', category: 'sdk', re: /@anthropic-ai\/sdk|(?:from\s+|require\(\s*)['"]@?anthropic(?:-ai)?[/'"]/ },
|
|
27
|
+
{ id: 'google-ai', category: 'sdk', re: /@google\/generative-ai|google\.generativeai|@google-cloud\/vertexai|vertexai/ },
|
|
28
|
+
{ id: 'vercel-ai', category: 'sdk', re: /@ai-sdk\/|from\s+['"]ai['"]|require\(\s*['"]ai['"]\s*\)/ },
|
|
29
|
+
{ id: 'langchain', category: 'sdk', re: /langchain/ },
|
|
30
|
+
{ id: 'llamaindex', category: 'sdk', re: /llamaindex|llama_index/ },
|
|
31
|
+
{ id: 'mistral', category: 'sdk', re: /(?:from\s+|require\(\s*)['"]mistralai?[/'"]/ },
|
|
32
|
+
{ id: 'cohere', category: 'sdk', re: /(?:from\s+|require\(\s*)['"]cohere[-_\/]/ },
|
|
33
|
+
{ id: 'groq', category: 'sdk', re: /(?:from\s+|require\(\s*)['"]groq(?:-sdk)?[/'"]/ },
|
|
34
|
+
{ id: 'xai', category: 'sdk', re: /@xai\/sdk|(?:from\s+|require\(\s*)['"]@?xai[/'"]/ },
|
|
35
|
+
{ id: 'ollama', category: 'sdk', re: /['"]ollama(?:\/|-sdk)?[/'"]|from\s+['"]ollama[/'"]/ },
|
|
36
|
+
{ id: 'bedrock', category: 'sdk', re: /@aws-sdk\/client-bedrock|bedrock-runtime|bedrock\.Runtime/ },
|
|
37
|
+
// raw HTTP endpoints
|
|
38
|
+
{ id: 'openai', category: 'endpoint', re: /api\.openai\.com/ },
|
|
39
|
+
{ id: 'anthropic', category: 'endpoint', re: /api\.anthropic\.com/ },
|
|
40
|
+
{ id: 'google-ai', category: 'endpoint', re: /generativelanguage\.googleapis\.com/ },
|
|
41
|
+
{ id: 'mistral', category: 'endpoint', re: /api\.mistral\.ai/ },
|
|
42
|
+
{ id: 'cohere', category: 'endpoint', re: /api\.cohere\.(?:ai|com)/ },
|
|
43
|
+
{ id: 'groq', category: 'endpoint', re: /api\.groq\.com/ },
|
|
44
|
+
{ id: 'xai', category: 'endpoint', re: /api\.x\.ai\b/ },
|
|
45
|
+
{ id: 'together', category: 'endpoint', re: /api\.together\.xyz/ },
|
|
46
|
+
{ id: 'openrouter', category: 'endpoint', re: /openrouter\.ai\/api/ },
|
|
47
|
+
{ id: 'deepseek', category: 'endpoint', re: /api\.deepseek\.com/ },
|
|
48
|
+
// API keys in env/config
|
|
49
|
+
{ id: 'env-keys', category: 'env-key', re: /\b(?:OPENAI|ANTHROPIC|GEMINI|GOOGLE|GOOGLE_AI|GOOGLE_VERTEX|MISTRAL|GROQ|XAI|COHERE|TOGETHER|DEEPSEEK|OPENROUTER|PERPLEXITY|FIREWORKS)_API_KEY\b/ },
|
|
50
|
+
// call-site shapes
|
|
51
|
+
{ id: 'chat-completion', category: 'call', re: /chat\.completions\.create|completions\.create|responses\.create/ },
|
|
52
|
+
{ id: 'message-create', category: 'call', re: /\.messages\.create|client\.respond\b/ },
|
|
53
|
+
{ id: 'generate-content', category: 'call', re: /generateContent|generate_content/ },
|
|
54
|
+
{ id: 'chain-class', category: 'call', re: /ChatOpenAI|ChatAnthropic|AzureChatOpenAI|ChatGoogleGenerativeAI|GoogleGenerativeAI/ },
|
|
55
|
+
{ id: 'llm-invoke', category: 'call', re: /\bllm\s*\.\s*(?:invoke|predict|call)\b|\.invoke\(\s*['"]/ },
|
|
56
|
+
{ id: 'agent-run', category: 'call', re: /\bagent\s*\(/ },
|
|
57
|
+
{ id: 'llm-params', category: 'param', re: /\bmax_tokens\b|\btemperature\s*[:=]/ },
|
|
58
|
+
// already on Jev
|
|
59
|
+
{ id: 'jev', category: 'jev', re: /@typesafe-ai\/sdk|typesafe_sdk|api\.typesafe\.ai|typesafe-ai|system_one|systemone/ },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const DECISION_RE = /classif|categor|intent|route|routing|label|spam|toxic|moderat|guardrail|jailbreak|sentiment|urgen|prioriti|triage|severity|relevan|rating|verif|validat|is_|should_|detect|screen|escalat|fraud|disput|refund|complaint|lead\b|qualif/i;
|
|
63
|
+
|
|
64
|
+
function* walk(dir, depth) {
|
|
65
|
+
if (depth > 14) return;
|
|
66
|
+
let entries;
|
|
67
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
68
|
+
for (const e of entries) {
|
|
69
|
+
const full = path.join(dir, e.name);
|
|
70
|
+
if (e.isDirectory()) {
|
|
71
|
+
if (!SKIP_DIRS.has(e.name)) yield* walk(full, depth + 1);
|
|
72
|
+
} else if (e.isFile()) {
|
|
73
|
+
const base = e.name;
|
|
74
|
+
const ext = path.extname(base).toLowerCase();
|
|
75
|
+
if (EXTS.has(ext) || base.startsWith('.env') || base === 'env') yield full;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function looksBinary(buf) {
|
|
81
|
+
return buf.slice(0, 4096).includes(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function scanFile(file, root) {
|
|
85
|
+
let buf;
|
|
86
|
+
try {
|
|
87
|
+
const st = fs.statSync(file);
|
|
88
|
+
if (st.size > MAX_FILE_BYTES) return [];
|
|
89
|
+
buf = fs.readFileSync(file);
|
|
90
|
+
} catch { return []; }
|
|
91
|
+
if (looksBinary(buf)) return [];
|
|
92
|
+
const lines = buf.toString('utf8').split('\n');
|
|
93
|
+
const hits = [];
|
|
94
|
+
for (let i = 0; i < lines.length; i++) {
|
|
95
|
+
const line = lines[i];
|
|
96
|
+
if (!line.trim()) continue;
|
|
97
|
+
for (const p of PATTERNS) {
|
|
98
|
+
if (!p.re.test(line)) continue;
|
|
99
|
+
const window = (lines[i - 2] || '') + '\n' + line + '\n' + (lines[i + 1] || '') + '\n' + (lines[i + 2] || '');
|
|
100
|
+
hits.push({
|
|
101
|
+
file: path.relative(root, file) || file,
|
|
102
|
+
line: i + 1,
|
|
103
|
+
category: p.category,
|
|
104
|
+
id: p.id,
|
|
105
|
+
snippet: line.trim().slice(0, 160),
|
|
106
|
+
decisionShaped: DECISION_RE.test(line) || DECISION_RE.test(window),
|
|
107
|
+
});
|
|
108
|
+
break; // one record per line, first matching pattern
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return hits;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function runScan(rootDir) {
|
|
115
|
+
const root = path.resolve(rootDir || '.');
|
|
116
|
+
const hits = [];
|
|
117
|
+
let filesScanned = 0;
|
|
118
|
+
for (const file of walk(root, 0)) {
|
|
119
|
+
if (path.basename(file) === 'jev-scan.json') continue;
|
|
120
|
+
filesScanned++;
|
|
121
|
+
hits.push(...scanFile(file, root));
|
|
122
|
+
}
|
|
123
|
+
const bySdk = {};
|
|
124
|
+
const byCategory = {};
|
|
125
|
+
const byFile = {};
|
|
126
|
+
for (const h of hits) {
|
|
127
|
+
byCategory[h.category] = (byCategory[h.category] || 0) + 1;
|
|
128
|
+
if (h.category !== 'param') bySdk[h.id] = (bySdk[h.id] || 0) + 1;
|
|
129
|
+
byFile[h.file] = (byFile[h.file] || 0) + 1;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
scanner: 'jev-audit',
|
|
133
|
+
version: '0.1.0',
|
|
134
|
+
scannedAt: new Date().toISOString(),
|
|
135
|
+
root,
|
|
136
|
+
filesScanned,
|
|
137
|
+
summary: {
|
|
138
|
+
touchpoints: hits.filter((h) => h.category !== 'jev').length,
|
|
139
|
+
decisionShaped: hits.filter((h) => h.decisionShaped && h.category !== 'jev' && h.category !== 'param').length,
|
|
140
|
+
alreadyJev: hits.filter((h) => h.category === 'jev').length,
|
|
141
|
+
byCategory,
|
|
142
|
+
bySdk,
|
|
143
|
+
byFile,
|
|
144
|
+
},
|
|
145
|
+
hits,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function printSummary(result) {
|
|
150
|
+
const s = result.summary;
|
|
151
|
+
console.log(`\njev-audit scan — ${result.root}`);
|
|
152
|
+
console.log(`files scanned: ${result.filesScanned} llm touchpoints: ${s.touchpoints} decision-shaped: ${s.decisionShaped}${s.alreadyJev ? ` already-jev: ${s.alreadyJev}` : ''}`);
|
|
153
|
+
const sdks = Object.entries(s.bySdk).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k} ${v}`).join(', ');
|
|
154
|
+
if (sdks) console.log(`signals: ${sdks}`);
|
|
155
|
+
const files = Object.entries(s.byFile).sort((a, b) => b[1] - a[1]).slice(0, 8);
|
|
156
|
+
if (files.length) {
|
|
157
|
+
console.log('\nhottest files:');
|
|
158
|
+
for (const [f, n] of files) console.log(` ${String(n).padStart(3)} ${f}`);
|
|
159
|
+
}
|
|
160
|
+
const candidates = result.hits.filter((h) => h.decisionShaped && (h.category === 'call' || h.category === 'sdk'));
|
|
161
|
+
if (candidates.length) {
|
|
162
|
+
console.log('\ndecision-shaped call sites (prime Jev candidates):');
|
|
163
|
+
for (const c of candidates.slice(0, 8)) console.log(` ${c.file}:${c.line} [${c.id}] ${c.snippet.slice(0, 80)}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (require.main === module) {
|
|
168
|
+
const target = process.argv[2] || '.';
|
|
169
|
+
const result = runScan(target);
|
|
170
|
+
printSummary(result);
|
|
171
|
+
const out = path.join(process.cwd(), 'jev-scan.json');
|
|
172
|
+
fs.writeFileSync(out, JSON.stringify(result, null, 2));
|
|
173
|
+
console.log(`\n→ full inventory: ${out}`);
|
|
174
|
+
console.log('→ next: run the jev-audit skill in your agent ("audit this repo with jev-audit") for tiers, savings, and architecture.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { runScan, printSummary };
|