@rosh100yx/outlier 0.4.23 → 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.23",
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,40 +1813,86 @@ 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);
1823
1863
  } catch {
1824
- return { total: 0, output: 0, cache: 0, sessions: 0 };
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
- let total = 0, output = 0, cache = 0;
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;
1835
1876
  output += data.output_tokens || 0;
1836
1877
  cache += data.cache_read || 0;
1878
+ cost += data.cost_usd || 0;
1837
1879
  if (data.session_id)
1838
1880
  sessions.add(data.session_id);
1839
- } catch (e) {}
1881
+ } catch {}
1840
1882
  }
1841
- return { total, output, cache, sessions: sessions.size };
1883
+ return { total, output, cache, sessions: sessions.size, cost };
1842
1884
  }
1843
1885
  }
1844
1886
 
1845
1887
  class CursorLogParser {
1846
1888
  async parse() {
1847
- return { total: 0, output: 0, cache: 0, sessions: 0 };
1889
+ return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
1848
1890
  }
1849
1891
  }
1892
+ function estimateUsd(output, cacheRead, total) {
1893
+ const otherInput = Math.max(0, total - output - cacheRead);
1894
+ return output / 1e6 * 9 + cacheRead / 1e6 * 0.3 + otherInput / 1e6 * 3;
1895
+ }
1850
1896
  function getLocalGridFactor() {
1851
1897
  try {
1852
1898
  const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -1865,16 +1911,19 @@ function getLocalGridFactor() {
1865
1911
  }
1866
1912
  async function getCarbonStats() {
1867
1913
  const parsers = [new ClaudeLogParser, new CursorLogParser];
1868
- let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0;
1914
+ let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0, loggedCost = 0;
1869
1915
  for (const parser of parsers) {
1870
1916
  const stats = await parser.parse();
1871
1917
  totalTokens += stats.total;
1872
1918
  outputTokens += stats.output;
1873
1919
  cacheReadTokens += stats.cache;
1874
1920
  sessions += stats.sessions;
1921
+ loggedCost += stats.cost;
1875
1922
  }
1876
1923
  const energyKwh = outputTokens / 1e6 * 0.662;
1877
1924
  const localGrid = getLocalGridFactor();
1925
+ const costIsReal = loggedCost > 0;
1926
+ const estUsd = costIsReal ? loggedCost : estimateUsd(outputTokens, cacheReadTokens, totalTokens);
1878
1927
  return {
1879
1928
  totalTokens,
1880
1929
  outputTokens,
@@ -1884,7 +1933,9 @@ async function getCarbonStats() {
1884
1933
  co2KgFrance: energyKwh * grid_factors_default.france / 1000,
1885
1934
  localCo2Kg: energyKwh * localGrid.factor / 1000,
1886
1935
  localRegion: localGrid.region,
1887
- sessions
1936
+ sessions,
1937
+ estUsd,
1938
+ costIsReal
1888
1939
  };
1889
1940
  }
1890
1941
 
@@ -2045,19 +2096,26 @@ async function main() {
2045
2096
  `));
2046
2097
  if (action === "--help" || action === "-h" || action === "help") {
2047
2098
  console.log(import_picocolors.default.bold(`
2048
- COMMANDS:`));
2049
- console.log(` ${import_picocolors.default.cyan("outlier")} Interactive menu (Onboarding for first-timers)`);
2050
- console.log(` ${import_picocolors.default.cyan("outlier status")} Run full AI reliance & capability audit`);
2051
- console.log(` ${import_picocolors.default.cyan("outlier authorship")} Scan git history for AI co-authorship ratio`);
2052
- console.log(` ${import_picocolors.default.cyan("outlier carbon")} Scan local logs for token waste & carbon cost`);
2053
- console.log(` ${import_picocolors.default.cyan("outlier policy")} Configure CI/CD guardrails and thresholds`);
2054
- console.log(` ${import_picocolors.default.cyan("outlier impact")} See the compounding horizon of AI Deskilling`);
2055
- console.log(` ${import_picocolors.default.cyan("outlier knowledge")} Explore references, graphs, and the core literature`);
2056
- console.log(` ${import_picocolors.default.cyan("outlier participate")} Help build the academic literature on AI deskilling`);
2057
- console.log(` ${import_picocolors.default.cyan("outlier init")} Install the once-per-day shell greeting`);
2058
- 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`);
2059
2116
  console.log(`
2060
- ` + 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"));
2061
2119
  process.exit(0);
2062
2120
  }
2063
2121
  const configPath = join4(os2.homedir(), ".outlier_config");
@@ -2227,12 +2285,17 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2227
2285
  s.stop("Audit complete");
2228
2286
  try {
2229
2287
  let authPct = "0%";
2288
+ let nmFloorStr = "";
2230
2289
  let ruleFailures = 0;
2231
2290
  if (gitStats) {
2232
2291
  authPct = `${(gitStats.ratio * 100).toFixed(1)}%`;
2292
+ nmFloorStr = ` ${import_picocolors.default.dim(`(${(gitStats.ratioNoMerges * 100).toFixed(0)}% excl. merges)`)}`;
2233
2293
  if (gitStats.ratio > 0.7)
2234
2294
  ruleFailures++;
2235
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.")}` : "";
2236
2299
  let cachePct = "0";
2237
2300
  let co2Str = "0.0kg";
2238
2301
  let regionStr = "Global Average";
@@ -2244,23 +2307,22 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2244
2307
  regionStr = carbon.localRegion;
2245
2308
  }
2246
2309
  const isDanger = gitStats && gitStats.ratio > 0.7;
2247
- const verdictZone = isDanger ? import_picocolors.default.red("DANGER ZONE") : import_picocolors.default.green("SAFE / SOVEREIGN");
2248
- const verdictText = isDanger ? `You are transitioning from 'Creator' to 'Reviewer'.
2249
- At this trajectory, you risk losing architectural
2250
- muscle memory on this codebase within 6 months.` : `You are maintaining strong architectural intimacy.
2251
- Your human judgement remains the primary driver
2252
- of logic in this system.`;
2310
+ const verdictZone = isDanger ? import_picocolors.default.red("Mostly AI") : import_picocolors.default.green("You're driving");
2311
+ const verdictText = isDanger ? `AI wrote most of this. Read it through so you can
2312
+ still debug it yourself when the agent isn't around.` : `You're still writing the core of this. Good —
2313
+ that's how you keep the skill.`;
2253
2314
  const isInefficient = parseFloat(cachePct) > 40;
2254
- const cacheVerdict = isInefficient ? import_picocolors.default.yellow("INEFFICIENT") : import_picocolors.default.green("EFFICIENT");
2255
- const cacheText = isInefficient ? `You are burning paid API tokens and excess compute
2256
- on files the agent isn't even touching.` : `Your token usage and human judgment are tightly
2257
- coupled. High signal-to-noise ratio.`;
2258
- const policyStatus = ruleFailures > 0 ? import_picocolors.default.red("BLOCKED \uD83D\uDED1 (Threshold Exceeded)") : import_picocolors.default.green("PASS ✅ (Within Threshold)");
2259
- const policyAction = ruleFailures > 0 ? "Triggering Mandatory Mentoring Scenario." : "No intervention required.";
2315
+ const cacheVerdict = isInefficient ? import_picocolors.default.yellow("Lots of re-reads") : import_picocolors.default.green("Lean");
2316
+ const cacheText = isInefficient ? `Most of your tokens just re-send old context.
2317
+ It's normal for agents, but it's most of the bill.` : `Little wasted context. Your spend is mostly
2318
+ real work.`;
2319
+ const policyStatus = ruleFailures > 0 ? import_picocolors.default.red("Over your limit") : import_picocolors.default.green("Within limit");
2320
+ const policyAction = ruleFailures > 0 ? "Heads up only — nothing was blocked." : "Nothing to do.";
2260
2321
  const fmtTokens = (n2) => n2 >= 1e9 ? (n2 / 1e9).toFixed(1) + "B" : n2 >= 1e6 ? (n2 / 1e6).toFixed(1) + "M" : n2 >= 1000 ? (n2 / 1000).toFixed(1) + "k" : String(n2);
2261
2322
  const totalTokensStr = carbon ? fmtTokens(carbon.totalTokens) : "0";
2262
- const humanSov = gitStats ? ((1 - gitStats.ratio) * 100).toFixed(1) + "%" : "100%";
2263
- const authorshipStr = authPct + (isDanger ? import_picocolors.default.red(" (High Reliance)") : import_picocolors.default.green(" (Healthy)"));
2323
+ const estUsdStr = carbon ? "$" + carbon.estUsd.toFixed(2) + (carbon.costIsReal ? "" : import_picocolors.default.dim(" (rough)")) : import_picocolors.default.dim("n/a");
2324
+ const humanSov = gitStats ? ((1 - gitStats.ratio) * 100).toFixed(0) + "%" : "100%";
2325
+ const authorshipStr = import_picocolors.default.bold(authPct) + (isDanger ? import_picocolors.default.red(" AI-written") : import_picocolors.default.green(" AI-written"));
2264
2326
  const getProgressBar = (pct, length = 10) => {
2265
2327
  const filled = Math.max(0, Math.min(length, Math.round(pct / 100 * length)));
2266
2328
  return "▰".repeat(filled) + "▱".repeat(length - filled);
@@ -2275,38 +2337,35 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2275
2337
  const repoName = process.cwd().split("/").pop() || "Unknown";
2276
2338
  finalReceipt = `
2277
2339
  ${import_picocolors.default.dim("┌────────────────────────────────────────────────────────")}
2278
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█")} ${import_picocolors.default.bold(":: THERMAL AUDIT RECEIPT")}
2279
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄")} ${import_picocolors.default.dim(`:: TIMESTAMP: ${dateStr}`)}
2340
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█")} ${import_picocolors.default.bold(":: CODE AUDIT")}
2341
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄")} ${import_picocolors.default.dim(`:: ${repoName} · ${dateStr}`)}
2280
2342
  ${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
2281
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgBlue(" [ COGNITIVE BUDGET ] "))}
2282
- ${import_picocolors.default.dim("│")} AI Authorship ................. ${aiBar} ${authorshipStr}
2283
- ${import_picocolors.default.dim("│")} Human Sovereignty ................. ${humanBar} ${humanSov}
2343
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgBlue(" WHO WROTE THE CODE "))}
2344
+ ${import_picocolors.default.dim("│")} AI ${aiBar} ${authorshipStr}${nmFloorStr}
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%")}
2284
2347
  ${import_picocolors.default.dim("│")}
2285
- ${import_picocolors.default.dim("│")} Verdict: ${verdictZone}
2286
- ${import_picocolors.default.dim("│")} ${verdictText.split(`
2348
+ ${import_picocolors.default.dim("│")} ${verdictZone} ${verdictText.split(`
2287
2349
  `).join(`
2288
- ` + import_picocolors.default.dim("│") + " ")}
2350
+ ` + import_picocolors.default.dim("│") + " ")}${lowTrailerWarn}
2289
2351
  ${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
2290
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgMagenta(" [ FINANCIAL & COMPUTE TOLL ] "))}
2291
- ${import_picocolors.default.dim("│")} Tokens Burnt ................. ${totalTokensStr} vs Human Judgment
2292
- ${import_picocolors.default.dim("│")} Cache Bloat ................. ${cacheBar} ${cachePct}% (Unmodified context)
2293
- ${import_picocolors.default.dim("│")} Regional Grid ................. ${regionStr}
2352
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgMagenta(" WHAT IT COST "))}
2353
+ ${import_picocolors.default.dim("│")} Tokens used ${import_picocolors.default.bold(totalTokensStr)}
2354
+ ${import_picocolors.default.dim("│")} Est. spend ${import_picocolors.default.bold(estUsdStr)}
2355
+ ${import_picocolors.default.dim("│")} Re-used context ${cacheBar} ${import_picocolors.default.bold(cachePct + "%")}
2356
+ ${import_picocolors.default.dim("│")} Energy ${import_picocolors.default.bold(co2Str)} ${import_picocolors.default.dim(`(${regionStr} grid, rough)`)}
2294
2357
  ${import_picocolors.default.dim("│")}
2295
- ${import_picocolors.default.dim("│")} Verdict: ${cacheVerdict}
2296
- ${import_picocolors.default.dim("│")} ${cacheText.split(`
2358
+ ${import_picocolors.default.dim("│")} ${cacheVerdict} ${cacheText.split(`
2297
2359
  `).join(`
2298
- ` + import_picocolors.default.dim("│") + " ")}
2360
+ ` + import_picocolors.default.dim("│") + " ")}
2299
2361
  ${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
2300
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgYellow(import_picocolors.default.black(" [ POLICY ENFORCEMENT ] ")))}
2301
- ${import_picocolors.default.dim("│")} Status .................................. ${policyStatus}
2302
- ${import_picocolors.default.dim("│")} Action .................................. ${policyAction}
2362
+ ${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgYellow(import_picocolors.default.black(" YOUR LIMIT ")))}
2363
+ ${import_picocolors.default.dim("│")} AI cap ${import_picocolors.default.bold("70%")} ${import_picocolors.default.dim("· change with: outlier policy")}
2364
+ ${import_picocolors.default.dim("│")} Status ${policyStatus} ${import_picocolors.default.dim("·")} ${policyAction}
2303
2365
  ${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
2304
- ${import_picocolors.default.dim("│")}
2305
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.italic(import_picocolors.default.dim("patterns emerge in the commit history,"))}
2306
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.italic(import_picocolors.default.dim("code becomes commoditized by algorithms."))}
2307
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.italic(import_picocolors.default.dim("human mastery is the only true moat."))}
2308
- ${import_picocolors.default.dim("│")}
2309
- ${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.cyan("***STAY VIGILANT***"))}
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."))}
2310
2369
  ${import_picocolors.default.dim("└────────────────────────────────────────────────────────")}`;
2311
2370
  } else {
2312
2371
  note(`status: ${authPct} AI Reliance | ${cachePct}% Cache Bloat | ${co2Str}`, `${import_picocolors.default.bold("[outlier]")} CI/CD Audit`);
@@ -2499,9 +2558,19 @@ ${import_picocolors.default.bold("Submit here (and drop your screenshot!):")} ${
2499
2558
  Read the full academic foundation at: ${import_picocolors.default.underline("https://github.com/rosh100yx/outlier")}
2500
2559
  `);
2501
2560
  }
2502
- outro("Local telemetry run completed. No data left your machine.");
2561
+ outro("Done nothing left your machine. (How it works: outlier --help)");
2503
2562
  if (typeof finalReceipt !== "undefined" && finalReceipt) {
2504
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
+ }
2505
2574
  }
2506
2575
  if (action === "status") {
2507
2576
  const agent = detectAgent();
@@ -2512,10 +2581,14 @@ Read the full academic foundation at: ${import_picocolors.default.underline("htt
2512
2581
  console.log(import_picocolors.default.bold(import_picocolors.default.magenta(" ↳ Ready to code? ")) + "Start your AI agent");
2513
2582
  }
2514
2583
  console.log("");
2515
- console.log(import_picocolors.default.bold(import_picocolors.default.cyan(" Research: ")) + "Contribute to the AI deskilling study ➔ " + import_picocolors.default.bold("outlier participate"));
2516
- 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
+ }
2517
2589
  console.log(import_picocolors.default.dim(`
2518
- (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")));
2519
2592
  }
2520
2593
  }
2521
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.23",
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 {
@@ -13,51 +13,98 @@ export interface CarbonStats {
13
13
  localCo2Kg: number;
14
14
  localRegion: string;
15
15
  sessions: number;
16
+ estUsd: number; // estimated spend in USD
17
+ costIsReal: boolean; // true if summed from the log's own cost field, false if estimated from tokens
16
18
  }
17
19
 
18
20
  export interface TokenLogParser {
19
- parse(): Promise<{ total: number, output: number, cache: number, sessions: number }>;
21
+ parse(): Promise<{ total: number, output: number, cache: number, sessions: number, cost: number }>;
20
22
  }
21
23
 
22
24
  export class ClaudeLogParser implements TokenLogParser {
23
25
  private baseDir: string;
24
- constructor(baseDir = homedir()) {
26
+ private cwd: string;
27
+ constructor(baseDir = homedir(), cwd = process.cwd()) {
25
28
  this.baseDir = baseDir;
29
+ this.cwd = cwd;
26
30
  }
31
+
27
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).
28
71
  const logPath = join(this.baseDir, '.claude', 'tokenomics-log.jsonl');
29
-
30
72
  try {
31
73
  await access(logPath);
32
74
  } catch {
33
- return { total: 0, output: 0, cache: 0, sessions: 0 };
75
+ return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
34
76
  }
35
-
36
77
  const text = await readFile(logPath, 'utf-8');
37
- const lines = text.trim().split('\n').filter(l => l.length > 0);
38
-
39
- let total = 0, output = 0, cache = 0;
78
+ let total = 0, output = 0, cache = 0, cost = 0;
40
79
  const sessions = new Set<string>();
41
-
42
- for (const line of lines) {
80
+ for (const line of text.split('\n')) {
81
+ if (!line.trim()) continue;
43
82
  try {
44
83
  const data = JSON.parse(line);
45
84
  total += data.total_tokens || 0;
46
85
  output += data.output_tokens || 0;
47
86
  cache += data.cache_read || 0;
87
+ cost += data.cost_usd || 0;
48
88
  if (data.session_id) sessions.add(data.session_id);
49
- } catch (e) {}
89
+ } catch {}
50
90
  }
51
- return { total, output, cache, sessions: sessions.size };
91
+ return { total, output, cache, sessions: sessions.size, cost };
52
92
  }
53
93
  }
54
94
 
55
95
  class CursorLogParser implements TokenLogParser {
56
96
  async parse() {
57
- return { total: 0, output: 0, cache: 0, sessions: 0 };
97
+ return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
58
98
  }
59
99
  }
60
100
 
101
+ // Rough blended token pricing (USD per 1M tokens) for when the log has no cost field.
102
+ // Conservative mid-points across current models; labeled "rough" in the UI.
103
+ function estimateUsd(output: number, cacheRead: number, total: number): number {
104
+ const otherInput = Math.max(0, total - output - cacheRead);
105
+ return (output / 1e6) * 9 + (cacheRead / 1e6) * 0.3 + (otherInput / 1e6) * 3;
106
+ }
107
+
61
108
  function getLocalGridFactor(): { region: string, factor: number } {
62
109
  try {
63
110
  const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -73,7 +120,7 @@ function getLocalGridFactor(): { region: string, factor: number } {
73
120
  export async function getCarbonStats(): Promise<CarbonStats> {
74
121
  const parsers: TokenLogParser[] = [new ClaudeLogParser(), new CursorLogParser()];
75
122
 
76
- let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0;
123
+ let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0, loggedCost = 0;
77
124
 
78
125
  for (const parser of parsers) {
79
126
  const stats = await parser.parse();
@@ -81,11 +128,16 @@ export async function getCarbonStats(): Promise<CarbonStats> {
81
128
  outputTokens += stats.output;
82
129
  cacheReadTokens += stats.cache;
83
130
  sessions += stats.sessions;
131
+ loggedCost += stats.cost;
84
132
  }
85
133
 
86
134
  const energyKwh = (outputTokens / 1_000_000) * 0.662;
87
135
  const localGrid = getLocalGridFactor();
88
136
 
137
+ // Prefer the log's own cost field (accurate); fall back to a rough token estimate.
138
+ const costIsReal = loggedCost > 0;
139
+ const estUsd = costIsReal ? loggedCost : estimateUsd(outputTokens, cacheReadTokens, totalTokens);
140
+
89
141
  return {
90
142
  totalTokens,
91
143
  outputTokens,
@@ -95,6 +147,8 @@ export async function getCarbonStats(): Promise<CarbonStats> {
95
147
  co2KgFrance: (energyKwh * gridFactors.france) / 1000,
96
148
  localCo2Kg: (energyKwh * localGrid.factor) / 1000,
97
149
  localRegion: localGrid.region,
98
- sessions
150
+ sessions,
151
+ estUsd,
152
+ costIsReal
99
153
  };
100
154
  }
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';
@@ -297,19 +313,19 @@ Conservative Floor: ${color(nmPct + '%')}`,
297
313
  // (The old @clack dashboard panel was removed: it duplicated the receipt's
298
314
  // numbers in a second format, doubling the output on every run.)
299
315
  const isDanger = gitStats && gitStats.ratio > 0.7;
300
- const verdictZone = isDanger ? pc.red('DANGER ZONE') : pc.green('SAFE / SOVEREIGN');
301
- const verdictText = isDanger
302
- ? `You are transitioning from 'Creator' to 'Reviewer'.\n At this trajectory, you risk losing architectural \n muscle memory on this codebase within 6 months.`
303
- : `You are maintaining strong architectural intimacy.\n Your human judgement remains the primary driver\n of logic in this system.`;
304
-
316
+ const verdictZone = isDanger ? pc.red('Mostly AI') : pc.green('You\'re driving');
317
+ const verdictText = isDanger
318
+ ? `AI wrote most of this. Read it through so you can\n still debug it yourself when the agent isn't around.`
319
+ : `You're still writing the core of this. Good —\n that's how you keep the skill.`;
320
+
305
321
  const isInefficient = parseFloat(cachePct) > 40;
306
- const cacheVerdict = isInefficient ? pc.yellow('INEFFICIENT') : pc.green('EFFICIENT');
307
- const cacheText = isInefficient
308
- ? `You are burning paid API tokens and excess compute\n on files the agent isn't even touching.`
309
- : `Your token usage and human judgment are tightly\n coupled. High signal-to-noise ratio.`;
310
-
311
- const policyStatus = ruleFailures > 0 ? pc.red('BLOCKED 🛑 (Threshold Exceeded)') : pc.green('PASS ✅ (Within Threshold)');
312
- const policyAction = ruleFailures > 0 ? 'Triggering Mandatory Mentoring Scenario.' : 'No intervention required.';
322
+ const cacheVerdict = isInefficient ? pc.yellow('Lots of re-reads') : pc.green('Lean');
323
+ const cacheText = isInefficient
324
+ ? `Most of your tokens just re-send old context.\n It's normal for agents, but it's most of the bill.`
325
+ : `Little wasted context. Your spend is mostly\n real work.`;
326
+
327
+ const policyStatus = ruleFailures > 0 ? pc.red('Over your limit') : pc.green('Within limit');
328
+ const policyAction = ruleFailures > 0 ? 'Heads up only — nothing was blocked.' : 'Nothing to do.';
313
329
 
314
330
  const fmtTokens = (n: number) =>
315
331
  n >= 1_000_000_000 ? (n / 1_000_000_000).toFixed(1) + 'B'
@@ -317,8 +333,11 @@ Conservative Floor: ${color(nmPct + '%')}`,
317
333
  : n >= 1_000 ? (n / 1_000).toFixed(1) + 'k'
318
334
  : String(n);
319
335
  const totalTokensStr = carbon ? fmtTokens(carbon.totalTokens) : '0';
320
- const humanSov = gitStats ? ((1 - gitStats.ratio) * 100).toFixed(1) + '%' : '100%';
321
- const authorshipStr = authPct + (isDanger ? pc.red(' (High Reliance)') : pc.green(' (Healthy)'));
336
+ const estUsdStr = carbon
337
+ ? '$' + carbon.estUsd.toFixed(2) + (carbon.costIsReal ? '' : pc.dim(' (rough)'))
338
+ : pc.dim('n/a');
339
+ const humanSov = gitStats ? ((1 - gitStats.ratio) * 100).toFixed(0) + '%' : '100%';
340
+ const authorshipStr = pc.bold(authPct) + (isDanger ? pc.red(' AI-written') : pc.green(' AI-written'));
322
341
 
323
342
  const getProgressBar = (pct: number, length = 10) => {
324
343
  const filled = Math.max(0, Math.min(length, Math.round((pct / 100) * length)));
@@ -337,34 +356,31 @@ Conservative Floor: ${color(nmPct + '%')}`,
337
356
 
338
357
  finalReceipt = `
339
358
  ${pc.dim('┌────────────────────────────────────────────────────────')}
340
- ${pc.dim('│')} ${pc.cyan('█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█')} ${pc.bold(':: THERMAL AUDIT RECEIPT')}
341
- ${pc.dim('│')} ${pc.cyan('█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄')} ${pc.dim(`:: TIMESTAMP: ${dateStr}`)}
359
+ ${pc.dim('│')} ${pc.cyan('█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█')} ${pc.bold(':: CODE AUDIT')}
360
+ ${pc.dim('│')} ${pc.cyan('█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄')} ${pc.dim(`:: ${repoName} · ${dateStr}`)}
342
361
  ${pc.dim('├────────────────────────────────────────────────────────')}
343
- ${pc.dim('│')} ${pc.bold(pc.bgBlue(' [ COGNITIVE BUDGET ] '))}
344
- ${pc.dim('│')} AI Authorship ................. ${aiBar} ${authorshipStr}
345
- ${pc.dim('│')} Human Sovereignty ................. ${humanBar} ${humanSov}
362
+ ${pc.dim('│')} ${pc.bold(pc.bgBlue(' WHO WROTE THE CODE '))}
363
+ ${pc.dim('│')} AI ${aiBar} ${authorshipStr}${nmFloorStr}
364
+ ${pc.dim('│')} You ${humanBar} ${pc.bold(humanSov)}
365
+ ${pc.dim('│')} ${pc.dim('Typical: solo devs 10–40% · AI-framework repos up to ~80%')}
346
366
  ${pc.dim('│')}
347
- ${pc.dim('│')} Verdict: ${verdictZone}
348
- ${pc.dim('│')} ${verdictText.split('\n').join('\n ' + pc.dim('│') + ' ')}
367
+ ${pc.dim('│')} ${verdictZone} ${verdictText.split('\n').join('\n ' + pc.dim('│') + ' ')}${lowTrailerWarn}
349
368
  ${pc.dim('├────────────────────────────────────────────────────────')}
350
- ${pc.dim('│')} ${pc.bold(pc.bgMagenta(' [ FINANCIAL & COMPUTE TOLL ] '))}
351
- ${pc.dim('│')} Tokens Burnt ................. ${totalTokensStr} vs Human Judgment
352
- ${pc.dim('│')} Cache Bloat ................. ${cacheBar} ${cachePct}% (Unmodified context)
353
- ${pc.dim('│')} Regional Grid ................. ${regionStr}
369
+ ${pc.dim('│')} ${pc.bold(pc.bgMagenta(' WHAT IT COST '))}
370
+ ${pc.dim('│')} Tokens used ${pc.bold(totalTokensStr)}
371
+ ${pc.dim('│')} Est. spend ${pc.bold(estUsdStr)}
372
+ ${pc.dim('│')} Re-used context ${cacheBar} ${pc.bold(cachePct + '%')}
373
+ ${pc.dim('│')} Energy ${pc.bold(co2Str)} ${pc.dim(`(${regionStr} grid, rough)`)}
354
374
  ${pc.dim('│')}
355
- ${pc.dim('│')} Verdict: ${cacheVerdict}
356
- ${pc.dim('│')} ${cacheText.split('\n').join('\n ' + pc.dim('│') + ' ')}
375
+ ${pc.dim('│')} ${cacheVerdict} ${cacheText.split('\n').join('\n ' + pc.dim('│') + ' ')}
357
376
  ${pc.dim('├────────────────────────────────────────────────────────')}
358
- ${pc.dim('│')} ${pc.bold(pc.bgYellow(pc.black(' [ POLICY ENFORCEMENT ] ')))}
359
- ${pc.dim('│')} Status .................................. ${policyStatus}
360
- ${pc.dim('│')} Action .................................. ${policyAction}
377
+ ${pc.dim('│')} ${pc.bold(pc.bgYellow(pc.black(' YOUR LIMIT ')))}
378
+ ${pc.dim('│')} AI cap ${pc.bold('70%')} ${pc.dim('· change with: outlier policy')}
379
+ ${pc.dim('│')} Status ${policyStatus} ${pc.dim('·')} ${policyAction}
361
380
  ${pc.dim('├────────────────────────────────────────────────────────')}
362
- ${pc.dim('│')}
363
- ${pc.dim('│')} ${pc.italic(pc.dim('patterns emerge in the commit history,'))}
364
- ${pc.dim('│')} ${pc.italic(pc.dim('code becomes commoditized by algorithms.'))}
365
- ${pc.dim('│')} ${pc.italic(pc.dim('human mastery is the only true moat.'))}
366
- ${pc.dim('│')}
367
- ${pc.dim('│')} ${pc.bold(pc.cyan('***STAY VIGILANT***'))}
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.'))}
368
384
  ${pc.dim('└────────────────────────────────────────────────────────')}`;
369
385
  } else {
370
386
  note(
@@ -568,10 +584,20 @@ Artifact: ${pc.cyan(reportPath)}`,
568
584
  console.log(`\nRead the full academic foundation at: ${pc.underline('https://github.com/rosh100yx/outlier')}\n`);
569
585
  }
570
586
 
571
- outro('Local telemetry run completed. No data left your machine.');
587
+ outro('Done nothing left your machine. (How it works: outlier --help)');
572
588
 
573
589
  if (typeof finalReceipt !== 'undefined' && finalReceipt) {
574
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
+ }
575
601
  }
576
602
 
577
603
  if (action === 'status') {
@@ -588,13 +614,20 @@ Artifact: ${pc.cyan(reportPath)}`,
588
614
  }
589
615
  console.log('');
590
616
  console.log(
591
- 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')
592
619
  );
593
620
  console.log(
594
- 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')
595
628
  );
596
629
  console.log(
597
- 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'))
598
631
  );
599
632
  }
600
633
  }