@rosh100yx/outlier 0.4.25 → 0.10.2
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 +86 -53
- package/bin/outlier.js +804 -115
- package/data/grid-factors.json +16 -3
- package/package.json +1 -1
- package/src/aggregate.ts +59 -0
- package/src/capabilities.ts +103 -58
- package/src/carbon.ts +66 -14
- package/src/cli.ts +290 -26
- package/src/economics.ts +66 -0
- package/src/emissions.ts +73 -0
- package/src/insights.ts +109 -0
- package/src/sources.ts +110 -0
package/src/insights.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Insight rules engine — turns the raw metrics into meaning + one action.
|
|
2
|
+
//
|
|
3
|
+
// The numbers (75% AI, $63, blast radius HIGH) are data. Insights are what they MEAN
|
|
4
|
+
// together: high AI authorship is only alarming if your agents can also deploy; a low
|
|
5
|
+
// AI% next to heavy token use usually means missing trailers, not human authorship.
|
|
6
|
+
// Each rule combines signals and returns a plain message + a concrete next step.
|
|
7
|
+
|
|
8
|
+
import type { AuthorshipStats } from './git';
|
|
9
|
+
import type { CarbonStats } from './carbon';
|
|
10
|
+
import type { CapabilitiesStats } from './capabilities';
|
|
11
|
+
|
|
12
|
+
export type Severity = 'critical' | 'warn' | 'info' | 'good';
|
|
13
|
+
|
|
14
|
+
export interface Insight {
|
|
15
|
+
severity: Severity;
|
|
16
|
+
title: string; // short headline
|
|
17
|
+
detail: string; // one plain sentence of why
|
|
18
|
+
action: string; // the one thing to do
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const RANK: Record<Severity, number> = { critical: 0, warn: 1, info: 2, good: 3 };
|
|
22
|
+
|
|
23
|
+
export interface InsightInput {
|
|
24
|
+
authorship: AuthorshipStats | null;
|
|
25
|
+
carbon: CarbonStats | null;
|
|
26
|
+
caps: CapabilitiesStats | null;
|
|
27
|
+
policyCap?: number; // 0..1, default 0.70
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function deriveInsights({ authorship, carbon, caps, policyCap = 0.70 }: InsightInput): Insight[] {
|
|
31
|
+
const out: Insight[] = [];
|
|
32
|
+
const ai = authorship ? authorship.ratio : null;
|
|
33
|
+
const cachePct = carbon && carbon.totalTokens ? (carbon.cacheReadTokens / carbon.totalTokens) * 100 : null;
|
|
34
|
+
const blast = caps ? caps.blastRadius : null;
|
|
35
|
+
const writeOrDeploy = caps ? caps.mcps.filter(m => ['money', 'exec', 'deploy', 'write-remote', 'write-local'].includes(m.reach)).length : 0;
|
|
36
|
+
const heavyTokens = carbon ? carbon.totalTokens > 1_000_000 : false;
|
|
37
|
+
|
|
38
|
+
// 1. The compound one: high reliance AND high reach = you may not own code that can ship.
|
|
39
|
+
if (ai !== null && ai > 0.7 && (blast === 'HIGH' || blast === 'CRITICAL')) {
|
|
40
|
+
out.push({
|
|
41
|
+
severity: 'critical',
|
|
42
|
+
title: 'High reliance + high reach',
|
|
43
|
+
detail: `AI wrote ${(ai * 100).toFixed(0)}% here and your agents can ${writeOrDeploy ? 'write/deploy' : 'reach external services'}. You may not own code that can ship to prod.`,
|
|
44
|
+
action: 'Review the core paths yourself before delegating more this session.',
|
|
45
|
+
});
|
|
46
|
+
} else if (ai !== null && ai > 0.7) {
|
|
47
|
+
// 2. High reliance alone.
|
|
48
|
+
out.push({
|
|
49
|
+
severity: 'warn',
|
|
50
|
+
title: 'You are mostly reviewing, not writing',
|
|
51
|
+
detail: `AI wrote ${(ai * 100).toFixed(0)}% of recent commits — you risk losing the skill to debug it unaided.`,
|
|
52
|
+
action: 'Read the AI-written code through, or hand-write the next core change.',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 3. Honesty rule: low AI% next to heavy token use = missing trailers, not human authorship.
|
|
57
|
+
if (ai !== null && ai < 0.1 && heavyTokens) {
|
|
58
|
+
out.push({
|
|
59
|
+
severity: 'info',
|
|
60
|
+
title: 'Low AI% may be misleading',
|
|
61
|
+
detail: 'Heavy token use but few AI-tagged commits — your agent probably is not writing Co-Authored-By trailers.',
|
|
62
|
+
action: 'Treat the authorship number as a floor, not the truth, until trailers are on.',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 4. Reach / blast radius, independent of authorship.
|
|
67
|
+
if (caps && (blast === 'CRITICAL' || blast === 'HIGH')) {
|
|
68
|
+
out.push({
|
|
69
|
+
severity: blast === 'CRITICAL' ? 'critical' : 'warn',
|
|
70
|
+
title: `Blast radius ${blast}`,
|
|
71
|
+
detail: `If an agent (or a prompt injection) drives your tools, it ${caps.blastReasons.slice(0, 2).join(' and ') || 'has broad reach'}.`,
|
|
72
|
+
action: 'Disable the write/deploy MCP tools you do not need this session.',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 5. Cache waste = where the money goes.
|
|
77
|
+
if (cachePct !== null && cachePct > 80) {
|
|
78
|
+
out.push({
|
|
79
|
+
severity: 'warn',
|
|
80
|
+
title: 'Most of your spend is re-sent context',
|
|
81
|
+
detail: `${cachePct.toFixed(0)}% of your tokens just re-read old context — that is most of the bill, not new work.`,
|
|
82
|
+
action: 'Start fresh sessions for new tasks; keep context tight.',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 6. Over the policy limit.
|
|
87
|
+
if (ai !== null && ai > policyCap) {
|
|
88
|
+
out.push({
|
|
89
|
+
severity: 'warn',
|
|
90
|
+
title: 'Over your AI-authorship limit',
|
|
91
|
+
detail: `AI authorship is ${(ai * 100).toFixed(0)}%, over your ${(policyCap * 100).toFixed(0)}% limit.`,
|
|
92
|
+
action: 'Either raise the cap deliberately, or write the next change yourself.',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 7. Nothing wrong — say so (don't manufacture alarm).
|
|
97
|
+
if (out.length === 0) {
|
|
98
|
+
out.push({
|
|
99
|
+
severity: 'good',
|
|
100
|
+
title: 'Low risk',
|
|
101
|
+
detail: ai !== null
|
|
102
|
+
? `You wrote most of this (${(100 - ai * 100).toFixed(0)}%) and your agents have limited reach.`
|
|
103
|
+
: 'No AI logs or git history found to flag.',
|
|
104
|
+
action: 'Carry on — re-run before your next big delegation.',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return out.sort((a, b) => RANK[a.severity] - RANK[b.severity]);
|
|
109
|
+
}
|
package/src/sources.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Source Detector — the foundation for being tool-agnostic.
|
|
2
|
+
//
|
|
3
|
+
// outlier reads whatever AI telemetry the developer's tools already leave on disk, then
|
|
4
|
+
// uses the richest source per metric and labels its provenance. This keeps us local-first
|
|
5
|
+
// (we never call a tool's API — we read the local trace it writes) and lets us add new
|
|
6
|
+
// tools without changing the receipt.
|
|
7
|
+
//
|
|
8
|
+
// Provenance ladder (per metric): MEASURED > ESTIMATED > PROXY > NONE.
|
|
9
|
+
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
|
|
15
|
+
export type Provenance = 'measured' | 'estimated' | 'proxy' | 'none';
|
|
16
|
+
|
|
17
|
+
export interface DetectedSources {
|
|
18
|
+
tools: string[]; // tools/CLIs found on this machine
|
|
19
|
+
tokenSource: { name: string; provenance: Provenance };
|
|
20
|
+
carbonSource: { name: string; provenance: Provenance };
|
|
21
|
+
capabilitySource: { name: string; provenance: Provenance };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const HOME = homedir();
|
|
25
|
+
|
|
26
|
+
function hasCli(cmd: string): boolean {
|
|
27
|
+
try {
|
|
28
|
+
// `command -v` is POSIX and does not execute the target.
|
|
29
|
+
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function hasPath(p: string): boolean {
|
|
37
|
+
try { return existsSync(p); } catch { return false; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Fingerprint the local environment. Cheap checks only (no file reads here).
|
|
41
|
+
export function detectSources(cwd: string = process.cwd()): DetectedSources {
|
|
42
|
+
const tools: string[] = [];
|
|
43
|
+
const add = (t: string) => { if (!tools.includes(t)) tools.push(t); };
|
|
44
|
+
|
|
45
|
+
// AI coding agents (CLI on PATH or a config dir)
|
|
46
|
+
const cliTools: Record<string, string> = {
|
|
47
|
+
claude: 'claude', cursor: 'cursor', aider: 'aider', gemini: 'gemini',
|
|
48
|
+
opencode: 'opencode', cody: 'cody', continue: 'continue', codex: 'codex',
|
|
49
|
+
};
|
|
50
|
+
for (const [name, cmd] of Object.entries(cliTools)) {
|
|
51
|
+
if (hasCli(cmd)) add(name);
|
|
52
|
+
}
|
|
53
|
+
for (const [name, dir] of Object.entries({
|
|
54
|
+
claude: '.claude', cursor: '.cursor', gemini: '.gemini',
|
|
55
|
+
codeium: '.codeium', continue: '.continue', aider: '.aider.conf.yml',
|
|
56
|
+
})) {
|
|
57
|
+
if (hasPath(join(HOME, dir))) add(name);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Carbon/cost tooling that writes local data we can trust
|
|
61
|
+
if (hasCli('codecarbon')) add('codecarbon');
|
|
62
|
+
if (hasCli('ccusage')) add('ccusage');
|
|
63
|
+
|
|
64
|
+
// ---- Token / cost source (richest first) ----
|
|
65
|
+
const slug = cwd.replace(/\//g, '-');
|
|
66
|
+
const claudeProjectDir = join(HOME, '.claude', 'projects', slug);
|
|
67
|
+
const tokenomicsLog = join(HOME, '.claude', 'tokenomics-log.jsonl');
|
|
68
|
+
let tokenSource: DetectedSources['tokenSource'];
|
|
69
|
+
if (hasPath(tokenomicsLog)) {
|
|
70
|
+
// Custom Stop hook: carries a real cost_usd field -> measured cost.
|
|
71
|
+
tokenSource = { name: 'caveman tokenomics log', provenance: 'measured' };
|
|
72
|
+
} else if (hasPath(claudeProjectDir)) {
|
|
73
|
+
// Standard transcripts: real tokens, estimated cost.
|
|
74
|
+
tokenSource = { name: 'Claude Code transcripts', provenance: 'estimated' };
|
|
75
|
+
} else if (tools.includes('ccusage')) {
|
|
76
|
+
tokenSource = { name: 'ccusage', provenance: 'estimated' };
|
|
77
|
+
} else {
|
|
78
|
+
tokenSource = { name: 'none', provenance: 'none' };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---- Carbon source ----
|
|
82
|
+
// Baseline is our bundled offline model+grid ESTIMATE. CodeCarbon, when it has actually
|
|
83
|
+
// written an emissions.csv, is a higher-accuracy MEASURED path (parser wired in a later
|
|
84
|
+
// pass). We do not claim "measured" just because the CLI is installed.
|
|
85
|
+
let carbonSource: DetectedSources['carbonSource'];
|
|
86
|
+
const codecarbonData = hasPath(join(cwd, 'emissions.csv')) || hasPath(join(HOME, '.codecarbon', 'emissions.csv'));
|
|
87
|
+
if (codecarbonData) {
|
|
88
|
+
carbonSource = { name: 'CodeCarbon emissions.csv', provenance: 'measured' };
|
|
89
|
+
} else if (tokenSource.provenance !== 'none') {
|
|
90
|
+
carbonSource = { name: 'model+grid estimate', provenance: 'estimated' };
|
|
91
|
+
} else {
|
|
92
|
+
carbonSource = { name: 'none', provenance: 'none' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---- Capability source ----
|
|
96
|
+
let capabilitySource: DetectedSources['capabilitySource'];
|
|
97
|
+
if (hasPath(join(HOME, '.claude', 'settings.json')) || hasPath(join(cwd, 'AGENTS.md')) || hasPath(join(cwd, '.mcp.json'))) {
|
|
98
|
+
capabilitySource = { name: 'local config (settings/AGENTS/MCP)', provenance: 'measured' };
|
|
99
|
+
} else {
|
|
100
|
+
capabilitySource = { name: 'none', provenance: 'none' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { tools, tokenSource, carbonSource, capabilitySource };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Short label for the receipt, e.g. "measured · caveman tokenomics log".
|
|
107
|
+
export function provLabel(s: { name: string; provenance: Provenance }): string {
|
|
108
|
+
if (s.provenance === 'none') return 'no local data';
|
|
109
|
+
return `${s.provenance} · ${s.name}`;
|
|
110
|
+
}
|