@rosh100yx/outlier 0.4.24 → 0.4.25

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 CHANGED
@@ -50,9 +50,27 @@
50
50
  └───────────┘
51
51
  ```
52
52
  **Step 1:** Developer delegates code generation to an AI agent (Claude Code, Cursor).
53
- **Step 2:** Developer attempts to merge code into the main branch.
54
- **Step 3:** The `outlier` Bouncer hook triggers. If AI reliance > 70%, the commit is physically blocked.
55
- **Step 4:** A "Mentoring Emergency" is triggered, forcing the developer to solve an architectural challenge to prove mastery and prevent deskilling.
53
+ **Step 2:** `outlier` reads the local trace git history + AI logs — already on the machine.
54
+ **Step 3:** It reports who wrote the code, what it cost, and your authorship limit.
55
+ **Step 4:** Optionally, a local git hook *warns* (never silently blocks) when AI authorship exceeds your limit, so you review before you merge.
56
+
57
+ ### What it reads (and what it doesn't)
58
+
59
+ `outlier` is local-first. It reads, from your own machine, only:
60
+
61
+ - **`git log`** of the current repo — to count commits carrying a `Co-Authored-By` trailer (the AI-authorship share).
62
+ - **Your Claude Code session transcripts** at `~/.claude/projects/<this-repo>/*.jsonl` — to sum token usage for the cost / cache / carbon estimate. (Falls back to `~/.claude/tokenomics-log.jsonl` if present.)
63
+
64
+ It does **not** send anything anywhere — no API calls, no telemetry, no account. Your code and prompts never leave the machine. The only network action in the whole tool is *you* choosing to open a share link or a feedback issue.
65
+
66
+ ### How accurate is it?
67
+
68
+ We are deliberately honest about this:
69
+
70
+ - **Authorship** is an exact count of trailer-tagged commits, but it is a *proxy* for real human-vs-AI effort, and it **under-counts** when your agent doesn't write the `Co-Authored-By` trailer. A surprisingly low number usually means missing trailers, not that you wrote everything.
71
+ - **Tokens** are exact when the transcripts are present; otherwise the cost/carbon section reads zero.
72
+ - **Cost ($)** is exact when the log carries a cost field, otherwise a *rough* blended token estimate (labelled as such).
73
+ - **Carbon** is a rough estimate (inference energy varies ~4–20× in the literature) and the per-region figure is a *counterfactual* — cloud inference runs on the provider's grid, not yours. Treat it as an order-of-magnitude signal, not an audit.
56
74
 
57
75
  ## What Outlier Adds
58
76
  `outlier` builds a coordination layer on top of native agent workflows.
package/bin/outlier.js CHANGED
@@ -165,7 +165,7 @@ var require_picocolors = __commonJS((exports, module) => {
165
165
  var require_package = __commonJS((exports, module) => {
166
166
  module.exports = {
167
167
  name: "@rosh100yx/outlier",
168
- version: "0.4.24",
168
+ version: "0.4.25",
169
169
  description: "AI Code Governance & Capability Auditing for the Terminal. Measures AI reliance, context waste, and enforces local CI/CD policies.",
170
170
  bin: {
171
171
  outlier: "bin/outlier.js"
@@ -1800,7 +1800,7 @@ async function getAuthorshipStats(repoPath = process.cwd()) {
1800
1800
  // src/carbon.ts
1801
1801
  import { homedir } from "os";
1802
1802
  import { join } from "path";
1803
- import { readFile, access } from "fs/promises";
1803
+ import { readFile, access, readdir } from "fs/promises";
1804
1804
  // data/grid-factors.json
1805
1805
  var grid_factors_default = {
1806
1806
  vietnam: 681,
@@ -1813,10 +1813,50 @@ var grid_factors_default = {
1813
1813
  // src/carbon.ts
1814
1814
  class ClaudeLogParser {
1815
1815
  baseDir;
1816
- constructor(baseDir = homedir()) {
1816
+ cwd;
1817
+ constructor(baseDir = homedir(), cwd = process.cwd()) {
1817
1818
  this.baseDir = baseDir;
1819
+ this.cwd = cwd;
1818
1820
  }
1819
1821
  async parse() {
1822
+ const slug = this.cwd.replace(/\//g, "-");
1823
+ const projectDir = join(this.baseDir, ".claude", "projects", slug);
1824
+ try {
1825
+ const files = (await readdir(projectDir)).filter((f2) => f2.endsWith(".jsonl"));
1826
+ if (files.length > 0) {
1827
+ let total2 = 0, output2 = 0, cache2 = 0;
1828
+ const sessions2 = new Set;
1829
+ for (const file of files) {
1830
+ let text3 = "";
1831
+ try {
1832
+ text3 = await readFile(join(projectDir, file), "utf-8");
1833
+ } catch {
1834
+ continue;
1835
+ }
1836
+ for (const line of text3.split(`
1837
+ `)) {
1838
+ if (!line.trim())
1839
+ continue;
1840
+ try {
1841
+ const d = JSON.parse(line);
1842
+ const u4 = d.message && d.message.usage || d.usage;
1843
+ if (u4) {
1844
+ const inp = u4.input_tokens || 0;
1845
+ const out = u4.output_tokens || 0;
1846
+ const cr = u4.cache_read_input_tokens || 0;
1847
+ const cw = u4.cache_creation_input_tokens || 0;
1848
+ total2 += inp + out + cr + cw;
1849
+ output2 += out;
1850
+ cache2 += cr;
1851
+ }
1852
+ if (d.sessionId)
1853
+ sessions2.add(d.sessionId);
1854
+ } catch {}
1855
+ }
1856
+ }
1857
+ return { total: total2, output: output2, cache: cache2, sessions: sessions2.size, cost: 0 };
1858
+ }
1859
+ } catch {}
1820
1860
  const logPath = join(this.baseDir, ".claude", "tokenomics-log.jsonl");
1821
1861
  try {
1822
1862
  await access(logPath);
@@ -1824,11 +1864,12 @@ class ClaudeLogParser {
1824
1864
  return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
1825
1865
  }
1826
1866
  const text2 = await readFile(logPath, "utf-8");
1827
- const lines = text2.trim().split(`
1828
- `).filter((l2) => l2.length > 0);
1829
1867
  let total = 0, output = 0, cache = 0, cost = 0;
1830
1868
  const sessions = new Set;
1831
- for (const line of lines) {
1869
+ for (const line of text2.split(`
1870
+ `)) {
1871
+ if (!line.trim())
1872
+ continue;
1832
1873
  try {
1833
1874
  const data = JSON.parse(line);
1834
1875
  total += data.total_tokens || 0;
@@ -1837,7 +1878,7 @@ class ClaudeLogParser {
1837
1878
  cost += data.cost_usd || 0;
1838
1879
  if (data.session_id)
1839
1880
  sessions.add(data.session_id);
1840
- } catch (e) {}
1881
+ } catch {}
1841
1882
  }
1842
1883
  return { total, output, cache, sessions: sessions.size, cost };
1843
1884
  }
@@ -2055,19 +2096,26 @@ async function main() {
2055
2096
  `));
2056
2097
  if (action === "--help" || action === "-h" || action === "help") {
2057
2098
  console.log(import_picocolors.default.bold(`
2058
- COMMANDS:`));
2059
- console.log(` ${import_picocolors.default.cyan("outlier")} Interactive menu (Onboarding for first-timers)`);
2060
- console.log(` ${import_picocolors.default.cyan("outlier status")} Run full AI reliance & capability audit`);
2061
- console.log(` ${import_picocolors.default.cyan("outlier authorship")} Scan git history for AI co-authorship ratio`);
2062
- console.log(` ${import_picocolors.default.cyan("outlier carbon")} Scan local logs for token waste & carbon cost`);
2063
- console.log(` ${import_picocolors.default.cyan("outlier policy")} Configure CI/CD guardrails and thresholds`);
2064
- console.log(` ${import_picocolors.default.cyan("outlier impact")} See the compounding horizon of AI Deskilling`);
2065
- console.log(` ${import_picocolors.default.cyan("outlier knowledge")} Explore references, graphs, and the core literature`);
2066
- console.log(` ${import_picocolors.default.cyan("outlier participate")} Help build the academic literature on AI deskilling`);
2067
- console.log(` ${import_picocolors.default.cyan("outlier init")} Install the once-per-day shell greeting`);
2068
- console.log(` ${import_picocolors.default.cyan("outlier uninit")} Remove the shell greeting`);
2099
+ WHAT OUTLIER DOES`));
2100
+ console.log(import_picocolors.default.dim(" Reads your local git history and AI logs — on your machine — to show"));
2101
+ console.log(import_picocolors.default.dim(` how much of your code AI wrote, what it cost, and how to keep your skill.
2102
+ `));
2103
+ console.log(import_picocolors.default.bold("COMMANDS:"));
2104
+ console.log(` ${import_picocolors.default.cyan("outlier")} Run the audit (the default — same as 'status')`);
2105
+ console.log(` ${import_picocolors.default.cyan("outlier status")} Full audit: who wrote the code, what it cost, your limit`);
2106
+ console.log(` ${import_picocolors.default.cyan("outlier status --save")} Save the audit to ./outlier-audit.txt`);
2107
+ console.log(` ${import_picocolors.default.cyan("outlier authorship")} Just the AI-vs-human commit breakdown`);
2108
+ console.log(` ${import_picocolors.default.cyan("outlier carbon")} Just the token spend, cache waste & carbon`);
2109
+ console.log(` ${import_picocolors.default.cyan("outlier capabilities")} What tools & skills your agents can reach`);
2110
+ console.log(` ${import_picocolors.default.cyan("outlier policy")} Set an AI-authorship limit (local git hook / CI)`);
2111
+ console.log(` ${import_picocolors.default.cyan("outlier impact")} What AI reliance compounds to over time`);
2112
+ console.log(` ${import_picocolors.default.cyan("outlier knowledge")} The research behind the metrics`);
2113
+ console.log(` ${import_picocolors.default.cyan("outlier participate")} Share anonymous feedback for the deskilling study`);
2114
+ console.log(` ${import_picocolors.default.cyan("outlier init")} Show a once-per-day reliance greeting in new shells`);
2115
+ console.log(` ${import_picocolors.default.cyan("outlier uninit")} Remove that greeting`);
2069
2116
  console.log(`
2070
- ` + import_picocolors.default.dim("Run without arguments to start the interactive wizard."));
2117
+ ` + import_picocolors.default.dim("Local-first: nothing ever leaves your machine."));
2118
+ console.log(import_picocolors.default.dim("How it works → https://github.com/rosh100yx/outlier#how-it-works"));
2071
2119
  process.exit(0);
2072
2120
  }
2073
2121
  const configPath = join4(os2.homedir(), ".outlier_config");
@@ -2237,12 +2285,17 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2237
2285
  s.stop("Audit complete");
2238
2286
  try {
2239
2287
  let authPct = "0%";
2288
+ let nmFloorStr = "";
2240
2289
  let ruleFailures = 0;
2241
2290
  if (gitStats) {
2242
2291
  authPct = `${(gitStats.ratio * 100).toFixed(1)}%`;
2292
+ nmFloorStr = ` ${import_picocolors.default.dim(`(${(gitStats.ratioNoMerges * 100).toFixed(0)}% excl. merges)`)}`;
2243
2293
  if (gitStats.ratio > 0.7)
2244
2294
  ruleFailures++;
2245
2295
  }
2296
+ const lowTrailerWarn = gitStats && gitStats.ratio < 0.1 && carbon && carbon.totalTokens > 1e6 ? `
2297
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("Low %? Your agent may not tag commits — outlier counts only")}
2298
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("commits with a Co-Authored-By trailer.")}` : "";
2246
2299
  let cachePct = "0";
2247
2300
  let co2Str = "0.0kg";
2248
2301
  let regionStr = "Global Average";
@@ -2288,12 +2341,13 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2288
2341
  ${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄")} ${import_picocolors.default.dim(`:: ${repoName} · ${dateStr}`)}
2289
2342
  ${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
2290
2343
  ${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgBlue(" WHO WROTE THE CODE "))}
2291
- ${import_picocolors.default.dim("│")} AI ${aiBar} ${authorshipStr}
2344
+ ${import_picocolors.default.dim("│")} AI ${aiBar} ${authorshipStr}${nmFloorStr}
2292
2345
  ${import_picocolors.default.dim("│")} You ${humanBar} ${import_picocolors.default.bold(humanSov)}
2346
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("Typical: solo devs 10–40% · AI-framework repos up to ~80%")}
2293
2347
  ${import_picocolors.default.dim("│")}
2294
2348
  ${import_picocolors.default.dim("│")} ${verdictZone} — ${verdictText.split(`
2295
2349
  `).join(`
2296
- ` + import_picocolors.default.dim("│") + " ")}
2350
+ ` + import_picocolors.default.dim("│") + " ")}${lowTrailerWarn}
2297
2351
  ${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
2298
2352
  ${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgMagenta(" WHAT IT COST "))}
2299
2353
  ${import_picocolors.default.dim("│")} Tokens used ${import_picocolors.default.bold(totalTokensStr)}
@@ -2309,8 +2363,9 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2309
2363
  ${import_picocolors.default.dim("│")} AI cap ${import_picocolors.default.bold("70%")} ${import_picocolors.default.dim("· change with: outlier policy")}
2310
2364
  ${import_picocolors.default.dim("│")} Status ${policyStatus} ${import_picocolors.default.dim("·")} ${policyAction}
2311
2365
  ${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
2312
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(import_picocolors.default.italic("Run this before you start. Keep the skill while you"))}
2313
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(import_picocolors.default.italic("use the speed."))}
2366
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("Numbers are local estimates authorship is a proxy and")}
2367
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("carbon is rough. How it works: outlier --help")}
2368
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(import_picocolors.default.italic("Run this before you start. Keep the skill while you use the speed."))}
2314
2369
  ${import_picocolors.default.dim("└────────────────────────────────────────────────────────")}`;
2315
2370
  } else {
2316
2371
  note(`status: ${authPct} AI Reliance | ${cachePct}% Cache Bloat | ${co2Str}`, `${import_picocolors.default.bold("[outlier]")} CI/CD Audit`);
@@ -2503,9 +2558,19 @@ ${import_picocolors.default.bold("Submit here (and drop your screenshot!):")} ${
2503
2558
  Read the full academic foundation at: ${import_picocolors.default.underline("https://github.com/rosh100yx/outlier")}
2504
2559
  `);
2505
2560
  }
2506
- outro("Local telemetry run completed. No data left your machine.");
2561
+ outro("Done nothing left your machine. (How it works: outlier --help)");
2507
2562
  if (typeof finalReceipt !== "undefined" && finalReceipt) {
2508
2563
  console.log(finalReceipt);
2564
+ if (process.argv.includes("--save")) {
2565
+ const stripAnsi = (s2) => s2.replace(/\x1b\[[0-9;]*m/g, "");
2566
+ const savePath = join4(process.cwd(), "outlier-audit.txt");
2567
+ try {
2568
+ writeFileSync(savePath, stripAnsi(finalReceipt).trimStart() + `
2569
+ `);
2570
+ console.log(import_picocolors.default.dim(`
2571
+ \uD83D\uDCBE Saved to ${savePath}`));
2572
+ } catch {}
2573
+ }
2509
2574
  }
2510
2575
  if (action === "status") {
2511
2576
  const agent = detectAgent();
@@ -2516,10 +2581,14 @@ Read the full academic foundation at: ${import_picocolors.default.underline("htt
2516
2581
  console.log(import_picocolors.default.bold(import_picocolors.default.magenta(" ↳ Ready to code? ")) + "Start your AI agent");
2517
2582
  }
2518
2583
  console.log("");
2519
- console.log(import_picocolors.default.bold(import_picocolors.default.cyan(" Research: ")) + "Contribute to the AI deskilling study ➔ " + import_picocolors.default.bold("outlier participate"));
2520
- console.log(import_picocolors.default.bold(import_picocolors.default.green(" Share: ")) + import_picocolors.default.underline("https://x.com/intent/tweet?text=I+just+audited+my+codebase+with+%23Outlier"));
2584
+ console.log(import_picocolors.default.bold(import_picocolors.default.green(" \uD83D\uDCF8 Share: ")) + "Screenshot this receipt, or post your score ➔ " + import_picocolors.default.underline("https://x.com/intent/tweet?text=I+just+audited+my+codebase+with+%23Outlier"));
2585
+ console.log(import_picocolors.default.bold(import_picocolors.default.cyan(" \uD83D\uDD2C Research: ")) + "Help the AI-deskilling study — type: " + import_picocolors.default.bold("outlier participate"));
2586
+ if (!process.argv.includes("--save")) {
2587
+ console.log(import_picocolors.default.dim(" \uD83D\uDCBE Save: outlier status --save"));
2588
+ }
2521
2589
  console.log(import_picocolors.default.dim(`
2522
- (To see all local governance modules, run: `) + import_picocolors.default.dim(import_picocolors.default.bold("outlier --help")) + import_picocolors.default.dim(")"));
2590
+ outlier does more than this audit see how you adopt AI, what it`));
2591
+ console.log(import_picocolors.default.dim(" costs, and what is actually working: ") + import_picocolors.default.bold(import_picocolors.default.cyan("outlier --help")));
2523
2592
  }
2524
2593
  }
2525
2594
  main().catch(console.error);
@@ -1,20 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const cyan = (text) => `\x1b[36m${text}\x1b[0m`;
4
- const dim = (text) => `\x1b[2m${text}\x1b[0m`;
5
- const bold = (text) => `\x1b[1m${text}\x1b[0m`;
3
+ const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
4
+ const dim = (t) => `\x1b[2m${t}\x1b[0m`;
5
+ const bold = (t) => `\x1b[1m${t}\x1b[0m`;
6
+ const green = (t) => `\x1b[32m${t}\x1b[0m`;
6
7
 
7
- console.log('\n' + bold('Welcome to Outlier') + ' - AI Code Governance for the Terminal');
8
- console.log(dim('────────────────────────────────────────────────────────────'));
9
- console.log('To start the interactive wizard and audit your codebase, type:\n');
10
- console.log(` ${cyan('outlier')}\n`);
11
- console.log('Available Commands:');
12
- console.log(` ${cyan('outlier status')} Run full AI reliance & capability audit`);
13
- console.log(` ${cyan('outlier authorship')} Scan git history for AI co-authorship ratio`);
14
- console.log(` ${cyan('outlier carbon')} Scan local logs for token waste & carbon cost`);
15
- console.log(` ${cyan('outlier capabilities')} Audit active MCPs, skills, and orchestrations`);
16
- console.log(` ${cyan('outlier policy')} Configure CI/CD guardrails and thresholds`);
17
- console.log(` ${cyan('outlier impact')} See the compounding horizon of AI Deskilling`);
18
- console.log(` ${cyan('outlier knowledge')} Explore core literature and METR references`);
19
- console.log(` ${cyan('outlier participate')} Help build the literature on AI deskilling`);
20
- console.log(dim('────────────────────────────────────────────────────────────\n'));
8
+ console.log('\n' + bold(' Outlier installed') + dim(' · AI code governance for the terminal'));
9
+ console.log(dim(' ──────────────────────────────────────────────────────────'));
10
+ console.log(' Run it before you start coding. It reads your local git history');
11
+ console.log(' and AI logs — ' + green('on your machine') + ' — and shows you:');
12
+ console.log(' how much of your code AI wrote');
13
+ console.log(' what it cost (tokens, $, wasted context, carbon)');
14
+ console.log(' whether you are keeping the skill while you use the speed');
15
+ console.log('');
16
+ console.log(' Start your first audit:');
17
+ console.log(` ${cyan('outlier')}`);
18
+ console.log('');
19
+ console.log(' Other commands: ' + dim('outlier --help'));
20
+ console.log(dim(' ──────────────────────────────────────────────────────────'));
21
+ console.log(' ' + green('Local-first:') + ' nothing ever leaves your machine.\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rosh100yx/outlier",
3
- "version": "0.4.24",
3
+ "version": "0.4.25",
4
4
  "description": "AI Code Governance & Capability Auditing for the Terminal. Measures AI reliance, context waste, and enforces local CI/CD policies.",
5
5
  "bin": {
6
6
  "outlier": "bin/outlier.js"
package/src/carbon.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { homedir } from 'os';
2
2
  import { join } from 'path';
3
- import { readFile, access } from 'fs/promises';
3
+ import { readFile, access, readdir } from 'fs/promises';
4
4
  import gridFactors from '../data/grid-factors.json';
5
5
 
6
6
  export interface CarbonStats {
@@ -23,33 +23,70 @@ export interface TokenLogParser {
23
23
 
24
24
  export class ClaudeLogParser implements TokenLogParser {
25
25
  private baseDir: string;
26
- constructor(baseDir = homedir()) {
26
+ private cwd: string;
27
+ constructor(baseDir = homedir(), cwd = process.cwd()) {
27
28
  this.baseDir = baseDir;
29
+ this.cwd = cwd;
28
30
  }
31
+
29
32
  async parse() {
33
+ // Primary source: the standard Claude Code session transcripts for THIS repo.
34
+ // Claude Code stores them at ~/.claude/projects/<cwd-with-slashes-as-dashes>/*.jsonl,
35
+ // one JSON object per line; assistant turns carry `message.usage`.
36
+ const slug = this.cwd.replace(/\//g, '-');
37
+ const projectDir = join(this.baseDir, '.claude', 'projects', slug);
38
+ try {
39
+ const files = (await readdir(projectDir)).filter(f => f.endsWith('.jsonl'));
40
+ if (files.length > 0) {
41
+ let total = 0, output = 0, cache = 0;
42
+ const sessions = new Set<string>();
43
+ for (const file of files) {
44
+ let text = '';
45
+ try { text = await readFile(join(projectDir, file), 'utf-8'); } catch { continue; }
46
+ for (const line of text.split('\n')) {
47
+ if (!line.trim()) continue;
48
+ try {
49
+ const d = JSON.parse(line);
50
+ const u = (d.message && d.message.usage) || d.usage;
51
+ if (u) {
52
+ const inp = u.input_tokens || 0;
53
+ const out = u.output_tokens || 0;
54
+ const cr = u.cache_read_input_tokens || 0;
55
+ const cw = u.cache_creation_input_tokens || 0;
56
+ total += inp + out + cr + cw;
57
+ output += out;
58
+ cache += cr;
59
+ }
60
+ if (d.sessionId) sessions.add(d.sessionId);
61
+ } catch {}
62
+ }
63
+ }
64
+ // Standard transcripts carry no cost field; cost is estimated downstream.
65
+ return { total, output, cache, sessions: sessions.size, cost: 0 };
66
+ }
67
+ } catch {}
68
+
69
+ // Fallback: the optional tokenomics-log.jsonl (written by a custom Stop hook;
70
+ // carries a real cost_usd field when present).
30
71
  const logPath = join(this.baseDir, '.claude', 'tokenomics-log.jsonl');
31
-
32
72
  try {
33
73
  await access(logPath);
34
74
  } catch {
35
75
  return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
36
76
  }
37
-
38
77
  const text = await readFile(logPath, 'utf-8');
39
- const lines = text.trim().split('\n').filter(l => l.length > 0);
40
-
41
78
  let total = 0, output = 0, cache = 0, cost = 0;
42
79
  const sessions = new Set<string>();
43
-
44
- for (const line of lines) {
80
+ for (const line of text.split('\n')) {
81
+ if (!line.trim()) continue;
45
82
  try {
46
83
  const data = JSON.parse(line);
47
84
  total += data.total_tokens || 0;
48
85
  output += data.output_tokens || 0;
49
86
  cache += data.cache_read || 0;
50
- cost += data.cost_usd || 0; // present when the log was written with a cost field
87
+ cost += data.cost_usd || 0;
51
88
  if (data.session_id) sessions.add(data.session_id);
52
- } catch (e) {}
89
+ } catch {}
53
90
  }
54
91
  return { total, output, cache, sessions: sessions.size, cost };
55
92
  }
package/src/cli.ts CHANGED
@@ -84,18 +84,24 @@ async function main() {
84
84
 
85
85
 
86
86
  if (action === '--help' || action === '-h' || action === 'help') {
87
- console.log(pc.bold('\nCOMMANDS:'));
88
- console.log(` ${pc.cyan('outlier')} Interactive menu (Onboarding for first-timers)`);
89
- console.log(` ${pc.cyan('outlier status')} Run full AI reliance & capability audit`);
90
- console.log(` ${pc.cyan('outlier authorship')} Scan git history for AI co-authorship ratio`);
91
- console.log(` ${pc.cyan('outlier carbon')} Scan local logs for token waste & carbon cost`);
92
- console.log(` ${pc.cyan('outlier policy')} Configure CI/CD guardrails and thresholds`);
93
- console.log(` ${pc.cyan('outlier impact')} See the compounding horizon of AI Deskilling`);
94
- console.log(` ${pc.cyan('outlier knowledge')} Explore references, graphs, and the core literature`);
95
- console.log(` ${pc.cyan('outlier participate')} Help build the academic literature on AI deskilling`);
96
- console.log(` ${pc.cyan('outlier init')} Install the once-per-day shell greeting`);
97
- console.log(` ${pc.cyan('outlier uninit')} Remove the shell greeting`);
98
- console.log('\n' + pc.dim('Run without arguments to start the interactive wizard.'));
87
+ console.log(pc.bold('\nWHAT OUTLIER DOES'));
88
+ console.log(pc.dim(' Reads your local git history and AI logs — on your machine — to show'));
89
+ console.log(pc.dim(' how much of your code AI wrote, what it cost, and how to keep your skill.\n'));
90
+ console.log(pc.bold('COMMANDS:'));
91
+ console.log(` ${pc.cyan('outlier')} Run the audit (the default same as 'status')`);
92
+ console.log(` ${pc.cyan('outlier status')} Full audit: who wrote the code, what it cost, your limit`);
93
+ console.log(` ${pc.cyan('outlier status --save')} Save the audit to ./outlier-audit.txt`);
94
+ console.log(` ${pc.cyan('outlier authorship')} Just the AI-vs-human commit breakdown`);
95
+ console.log(` ${pc.cyan('outlier carbon')} Just the token spend, cache waste & carbon`);
96
+ console.log(` ${pc.cyan('outlier capabilities')} What tools & skills your agents can reach`);
97
+ console.log(` ${pc.cyan('outlier policy')} Set an AI-authorship limit (local git hook / CI)`);
98
+ console.log(` ${pc.cyan('outlier impact')} What AI reliance compounds to over time`);
99
+ console.log(` ${pc.cyan('outlier knowledge')} The research behind the metrics`);
100
+ console.log(` ${pc.cyan('outlier participate')} Share anonymous feedback for the deskilling study`);
101
+ console.log(` ${pc.cyan('outlier init')} Show a once-per-day reliance greeting in new shells`);
102
+ console.log(` ${pc.cyan('outlier uninit')} Remove that greeting`);
103
+ console.log('\n' + pc.dim('Local-first: nothing ever leaves your machine.'));
104
+ console.log(pc.dim('How it works → https://github.com/rosh100yx/outlier#how-it-works'));
99
105
  process.exit(0);
100
106
  }
101
107
 
@@ -275,13 +281,23 @@ Conservative Floor: ${color(nmPct + '%')}`,
275
281
 
276
282
  try {
277
283
  let authPct = '0%';
284
+ let nmFloorStr = '';
278
285
  let ruleFailures = 0;
279
286
 
280
287
  if (gitStats) {
281
288
  authPct = `${(gitStats.ratio * 100).toFixed(1)}%`;
289
+ // Conservative floor: non-merge commits only (merges often lack the trailer).
290
+ nmFloorStr = ` ${pc.dim(`(${(gitStats.ratioNoMerges * 100).toFixed(0)}% excl. merges)`)}`;
282
291
  if (gitStats.ratio > 0.7) ruleFailures++;
283
292
  }
284
293
 
294
+ // Honesty: a very low ratio alongside heavy token use usually means the agent
295
+ // doesn't tag commits, not that the human wrote everything.
296
+ const lowTrailerWarn =
297
+ gitStats && gitStats.ratio < 0.1 && carbon && carbon.totalTokens > 1_000_000
298
+ ? `\n ${pc.dim('│')} ${pc.dim('Low %? Your agent may not tag commits — outlier counts only')}\n ${pc.dim('│')} ${pc.dim('commits with a Co-Authored-By trailer.')}`
299
+ : '';
300
+
285
301
  let cachePct = '0';
286
302
  let co2Str = '0.0kg';
287
303
  let regionStr = 'Global Average';
@@ -344,10 +360,11 @@ Conservative Floor: ${color(nmPct + '%')}`,
344
360
  ${pc.dim('│')} ${pc.cyan('█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄')} ${pc.dim(`:: ${repoName} · ${dateStr}`)}
345
361
  ${pc.dim('├────────────────────────────────────────────────────────')}
346
362
  ${pc.dim('│')} ${pc.bold(pc.bgBlue(' WHO WROTE THE CODE '))}
347
- ${pc.dim('│')} AI ${aiBar} ${authorshipStr}
363
+ ${pc.dim('│')} AI ${aiBar} ${authorshipStr}${nmFloorStr}
348
364
  ${pc.dim('│')} You ${humanBar} ${pc.bold(humanSov)}
365
+ ${pc.dim('│')} ${pc.dim('Typical: solo devs 10–40% · AI-framework repos up to ~80%')}
349
366
  ${pc.dim('│')}
350
- ${pc.dim('│')} ${verdictZone} — ${verdictText.split('\n').join('\n ' + pc.dim('│') + ' ')}
367
+ ${pc.dim('│')} ${verdictZone} — ${verdictText.split('\n').join('\n ' + pc.dim('│') + ' ')}${lowTrailerWarn}
351
368
  ${pc.dim('├────────────────────────────────────────────────────────')}
352
369
  ${pc.dim('│')} ${pc.bold(pc.bgMagenta(' WHAT IT COST '))}
353
370
  ${pc.dim('│')} Tokens used ${pc.bold(totalTokensStr)}
@@ -361,8 +378,9 @@ Conservative Floor: ${color(nmPct + '%')}`,
361
378
  ${pc.dim('│')} AI cap ${pc.bold('70%')} ${pc.dim('· change with: outlier policy')}
362
379
  ${pc.dim('│')} Status ${policyStatus} ${pc.dim('·')} ${policyAction}
363
380
  ${pc.dim('├────────────────────────────────────────────────────────')}
364
- ${pc.dim('│')} ${pc.dim(pc.italic('Run this before you start. Keep the skill while you'))}
365
- ${pc.dim('│')} ${pc.dim(pc.italic('use the speed.'))}
381
+ ${pc.dim('│')} ${pc.dim('Numbers are local estimates authorship is a proxy and')}
382
+ ${pc.dim('│')} ${pc.dim('carbon is rough. How it works: outlier --help')}
383
+ ${pc.dim('│')} ${pc.dim(pc.italic('Run this before you start. Keep the skill while you use the speed.'))}
366
384
  ${pc.dim('└────────────────────────────────────────────────────────')}`;
367
385
  } else {
368
386
  note(
@@ -566,10 +584,20 @@ Artifact: ${pc.cyan(reportPath)}`,
566
584
  console.log(`\nRead the full academic foundation at: ${pc.underline('https://github.com/rosh100yx/outlier')}\n`);
567
585
  }
568
586
 
569
- outro('Local telemetry run completed. No data left your machine.');
587
+ outro('Done nothing left your machine. (How it works: outlier --help)');
570
588
 
571
589
  if (typeof finalReceipt !== 'undefined' && finalReceipt) {
572
590
  console.log(finalReceipt);
591
+
592
+ // --save: write a plain-text (no color) copy of the receipt next to the repo.
593
+ if (process.argv.includes('--save')) {
594
+ const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*m/g, '');
595
+ const savePath = join(process.cwd(), 'outlier-audit.txt');
596
+ try {
597
+ writeFileSync(savePath, stripAnsi(finalReceipt).trimStart() + '\n');
598
+ console.log(pc.dim(`\n 💾 Saved to ${savePath}`));
599
+ } catch {}
600
+ }
573
601
  }
574
602
 
575
603
  if (action === 'status') {
@@ -586,13 +614,20 @@ Artifact: ${pc.cyan(reportPath)}`,
586
614
  }
587
615
  console.log('');
588
616
  console.log(
589
- pc.bold(pc.cyan(' Research: ')) + 'Contribute to the AI deskilling study ➔ ' + pc.bold('outlier participate')
617
+ pc.bold(pc.green(' 📸 Share: ')) + 'Screenshot this receipt, or post your score ➔ ' +
618
+ pc.underline('https://x.com/intent/tweet?text=I+just+audited+my+codebase+with+%23Outlier')
590
619
  );
591
620
  console.log(
592
- pc.bold(pc.green(' Share: ')) + pc.underline('https://x.com/intent/tweet?text=I+just+audited+my+codebase+with+%23Outlier')
621
+ pc.bold(pc.cyan(' 🔬 Research: ')) + 'Help the AI-deskilling study — type: ' + pc.bold('outlier participate')
622
+ );
623
+ if (!process.argv.includes('--save')) {
624
+ console.log(pc.dim(' 💾 Save: outlier status --save'));
625
+ }
626
+ console.log(
627
+ pc.dim('\n outlier does more than this audit — see how you adopt AI, what it')
593
628
  );
594
629
  console.log(
595
- pc.dim('\n (To see all local governance modules, run: ') + pc.dim(pc.bold('outlier --help')) + pc.dim(')')
630
+ pc.dim(' costs, and what is actually working: ') + pc.bold(pc.cyan('outlier --help'))
596
631
  );
597
632
  }
598
633
  }