nairon-bench 0.0.20 → 0.0.22
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/dist/index.js +529 -253
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3868,15 +3868,15 @@ __export(exports_client, {
|
|
|
3868
3868
|
getClerkId: () => getClerkId,
|
|
3869
3869
|
api: () => api
|
|
3870
3870
|
});
|
|
3871
|
-
import { existsSync as
|
|
3872
|
-
import { join as
|
|
3873
|
-
import { homedir as
|
|
3871
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
3872
|
+
import { join as join4 } from "node:path";
|
|
3873
|
+
import { homedir as homedir3 } from "node:os";
|
|
3874
3874
|
function loadConfig() {
|
|
3875
|
-
if (!
|
|
3875
|
+
if (!existsSync4(CONFIG_FILE)) {
|
|
3876
3876
|
return { github: undefined };
|
|
3877
3877
|
}
|
|
3878
3878
|
try {
|
|
3879
|
-
return JSON.parse(
|
|
3879
|
+
return JSON.parse(readFileSync4(CONFIG_FILE, "utf-8"));
|
|
3880
3880
|
} catch {
|
|
3881
3881
|
return { github: undefined };
|
|
3882
3882
|
}
|
|
@@ -3884,8 +3884,8 @@ function loadConfig() {
|
|
|
3884
3884
|
function saveConfig(config) {
|
|
3885
3885
|
const existing = loadConfig();
|
|
3886
3886
|
const merged = { ...existing, ...config };
|
|
3887
|
-
|
|
3888
|
-
|
|
3887
|
+
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
3888
|
+
writeFileSync2(CONFIG_FILE, JSON.stringify(merged, null, 2));
|
|
3889
3889
|
}
|
|
3890
3890
|
function getConfig() {
|
|
3891
3891
|
return loadConfig();
|
|
@@ -3929,8 +3929,8 @@ var init_client = __esm(() => {
|
|
|
3929
3929
|
init_index_node();
|
|
3930
3930
|
init_api2();
|
|
3931
3931
|
init_dist();
|
|
3932
|
-
CONFIG_DIR =
|
|
3933
|
-
CONFIG_FILE =
|
|
3932
|
+
CONFIG_DIR = join4(homedir3(), ".nairon-bench");
|
|
3933
|
+
CONFIG_FILE = join4(CONFIG_DIR, "config.json");
|
|
3934
3934
|
getClerkId = getGitHub;
|
|
3935
3935
|
requireClerkId = requireGitHub;
|
|
3936
3936
|
});
|
|
@@ -4446,8 +4446,8 @@ function defineCommand2(def) {
|
|
|
4446
4446
|
}
|
|
4447
4447
|
|
|
4448
4448
|
// src/commands/scan.ts
|
|
4449
|
-
import { existsSync as
|
|
4450
|
-
import { join as
|
|
4449
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4450
|
+
import { join as join6 } from "node:path";
|
|
4451
4451
|
|
|
4452
4452
|
// ../../node_modules/simple-git/dist/esm/index.js
|
|
4453
4453
|
var import_file_exists = __toESM(require_dist(), 1);
|
|
@@ -8515,9 +8515,9 @@ async function collectGit(projectDir, since) {
|
|
|
8515
8515
|
}
|
|
8516
8516
|
|
|
8517
8517
|
// src/collectors/agents.ts
|
|
8518
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8519
|
-
import { homedir } from "node:os";
|
|
8520
|
-
import { join, basename } from "node:path";
|
|
8518
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
8519
|
+
import { homedir as homedir2 } from "node:os";
|
|
8520
|
+
import { join as join2, basename } from "node:path";
|
|
8521
8521
|
// ../shared/src/constants.ts
|
|
8522
8522
|
var PHASE_WEIGHTS = {
|
|
8523
8523
|
requirements: 0.2,
|
|
@@ -8545,7 +8545,7 @@ var PERCENTILE_THRESHOLDS = [
|
|
|
8545
8545
|
label: "Intermediate",
|
|
8546
8546
|
badge: "Bronze"
|
|
8547
8547
|
},
|
|
8548
|
-
{ tier: "developing", minPercentile: 0, label: "Developing", badge: "
|
|
8548
|
+
{ tier: "developing", minPercentile: 0, label: "Developing", badge: "" }
|
|
8549
8549
|
];
|
|
8550
8550
|
var AGENT_LOG_PATHS = {
|
|
8551
8551
|
claude: "~/.claude/projects",
|
|
@@ -8800,57 +8800,179 @@ function computeNaironScore(git, agents, tests) {
|
|
|
8800
8800
|
function clamp(value, min, max) {
|
|
8801
8801
|
return Math.min(Math.max(value, min), max);
|
|
8802
8802
|
}
|
|
8803
|
+
// src/lib/cache.ts
|
|
8804
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from "node:fs";
|
|
8805
|
+
import { join } from "node:path";
|
|
8806
|
+
import { homedir } from "node:os";
|
|
8807
|
+
var CACHE_VERSION = 1;
|
|
8808
|
+
var CACHE_DIR = join(homedir(), ".nairon", "cache");
|
|
8809
|
+
var SESSION_CACHE_FILE = join(CACHE_DIR, "sessions.json");
|
|
8810
|
+
function ensureCacheDir() {
|
|
8811
|
+
if (!existsSync(CACHE_DIR)) {
|
|
8812
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
8813
|
+
}
|
|
8814
|
+
}
|
|
8815
|
+
function loadCache() {
|
|
8816
|
+
try {
|
|
8817
|
+
if (existsSync(SESSION_CACHE_FILE)) {
|
|
8818
|
+
const content = readFileSync(SESSION_CACHE_FILE, "utf-8");
|
|
8819
|
+
const data = JSON.parse(content);
|
|
8820
|
+
if (data.version === CACHE_VERSION) {
|
|
8821
|
+
return data;
|
|
8822
|
+
}
|
|
8823
|
+
}
|
|
8824
|
+
} catch {}
|
|
8825
|
+
return { version: CACHE_VERSION, entries: {} };
|
|
8826
|
+
}
|
|
8827
|
+
function saveCache(data) {
|
|
8828
|
+
ensureCacheDir();
|
|
8829
|
+
writeFileSync(SESSION_CACHE_FILE, JSON.stringify(data));
|
|
8830
|
+
}
|
|
8831
|
+
function isCacheValid(filePath, cache2) {
|
|
8832
|
+
const entry = cache2.entries[filePath];
|
|
8833
|
+
if (!entry)
|
|
8834
|
+
return false;
|
|
8835
|
+
try {
|
|
8836
|
+
const stat = statSync(filePath);
|
|
8837
|
+
return entry.mtime === stat.mtimeMs && entry.size === stat.size;
|
|
8838
|
+
} catch {
|
|
8839
|
+
return false;
|
|
8840
|
+
}
|
|
8841
|
+
}
|
|
8842
|
+
function getFileStats(filePath) {
|
|
8843
|
+
try {
|
|
8844
|
+
const stat = statSync(filePath);
|
|
8845
|
+
return { mtime: stat.mtimeMs, size: stat.size };
|
|
8846
|
+
} catch {
|
|
8847
|
+
return null;
|
|
8848
|
+
}
|
|
8849
|
+
}
|
|
8850
|
+
class SessionCache {
|
|
8851
|
+
cache;
|
|
8852
|
+
dirty = false;
|
|
8853
|
+
constructor() {
|
|
8854
|
+
this.cache = loadCache();
|
|
8855
|
+
}
|
|
8856
|
+
isValid(filePath) {
|
|
8857
|
+
return isCacheValid(filePath, this.cache);
|
|
8858
|
+
}
|
|
8859
|
+
get(filePath) {
|
|
8860
|
+
if (this.isValid(filePath)) {
|
|
8861
|
+
return this.cache.entries[filePath].session;
|
|
8862
|
+
}
|
|
8863
|
+
return null;
|
|
8864
|
+
}
|
|
8865
|
+
set(filePath, session) {
|
|
8866
|
+
const stats = getFileStats(filePath);
|
|
8867
|
+
if (!stats)
|
|
8868
|
+
return;
|
|
8869
|
+
this.cache.entries[filePath] = {
|
|
8870
|
+
mtime: stats.mtime,
|
|
8871
|
+
size: stats.size,
|
|
8872
|
+
session
|
|
8873
|
+
};
|
|
8874
|
+
this.dirty = true;
|
|
8875
|
+
}
|
|
8876
|
+
save() {
|
|
8877
|
+
if (this.dirty) {
|
|
8878
|
+
saveCache(this.cache);
|
|
8879
|
+
this.dirty = false;
|
|
8880
|
+
}
|
|
8881
|
+
}
|
|
8882
|
+
getStats() {
|
|
8883
|
+
return {
|
|
8884
|
+
total: Object.keys(this.cache.entries).length,
|
|
8885
|
+
hits: 0,
|
|
8886
|
+
misses: 0
|
|
8887
|
+
};
|
|
8888
|
+
}
|
|
8889
|
+
prune() {
|
|
8890
|
+
const before = Object.keys(this.cache.entries).length;
|
|
8891
|
+
for (const filePath of Object.keys(this.cache.entries)) {
|
|
8892
|
+
if (!existsSync(filePath)) {
|
|
8893
|
+
delete this.cache.entries[filePath];
|
|
8894
|
+
this.dirty = true;
|
|
8895
|
+
}
|
|
8896
|
+
}
|
|
8897
|
+
return before - Object.keys(this.cache.entries).length;
|
|
8898
|
+
}
|
|
8899
|
+
}
|
|
8900
|
+
function clearCache() {
|
|
8901
|
+
try {
|
|
8902
|
+
if (existsSync(SESSION_CACHE_FILE)) {
|
|
8903
|
+
writeFileSync(SESSION_CACHE_FILE, JSON.stringify({ version: CACHE_VERSION, entries: {} }));
|
|
8904
|
+
}
|
|
8905
|
+
} catch {}
|
|
8906
|
+
}
|
|
8907
|
+
function getCacheInfo() {
|
|
8908
|
+
try {
|
|
8909
|
+
if (existsSync(SESSION_CACHE_FILE)) {
|
|
8910
|
+
const stat = statSync(SESSION_CACHE_FILE);
|
|
8911
|
+
const cache2 = loadCache();
|
|
8912
|
+
return {
|
|
8913
|
+
entries: Object.keys(cache2.entries).length,
|
|
8914
|
+
sizeBytes: stat.size
|
|
8915
|
+
};
|
|
8916
|
+
}
|
|
8917
|
+
} catch {}
|
|
8918
|
+
return { entries: 0, sizeBytes: 0 };
|
|
8919
|
+
}
|
|
8920
|
+
|
|
8803
8921
|
// src/collectors/agents.ts
|
|
8804
|
-
|
|
8922
|
+
var sessionCache = null;
|
|
8923
|
+
async function collectAgentSessions(since, useCache = true) {
|
|
8924
|
+
sessionCache = useCache ? new SessionCache : null;
|
|
8805
8925
|
const sessions = [];
|
|
8806
|
-
const claudeProjectsDir =
|
|
8807
|
-
if (
|
|
8926
|
+
const claudeProjectsDir = join2(homedir2(), ".claude", "projects");
|
|
8927
|
+
if (existsSync2(claudeProjectsDir)) {
|
|
8808
8928
|
const claudeSessions = collectClaudeSessions(claudeProjectsDir, since);
|
|
8809
8929
|
sessions.push(...claudeSessions);
|
|
8810
8930
|
}
|
|
8811
|
-
const openCodeDir =
|
|
8812
|
-
if (
|
|
8931
|
+
const openCodeDir = join2(homedir2(), ".local", "share", "opencode");
|
|
8932
|
+
if (existsSync2(openCodeDir)) {
|
|
8813
8933
|
const openCodeSessions = collectOpenCodeSessions(openCodeDir, since);
|
|
8814
8934
|
sessions.push(...openCodeSessions);
|
|
8815
8935
|
}
|
|
8816
8936
|
const cursorDirs = [
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8937
|
+
join2(homedir2(), ".cursor", "sessions"),
|
|
8938
|
+
join2(homedir2(), "Library", "Application Support", "Cursor", "sessions"),
|
|
8939
|
+
join2(homedir2(), ".config", "cursor", "sessions")
|
|
8820
8940
|
];
|
|
8821
8941
|
for (const cursorDir of cursorDirs) {
|
|
8822
|
-
if (
|
|
8942
|
+
if (existsSync2(cursorDir)) {
|
|
8823
8943
|
const cursorSessions = collectCursorSessions(cursorDir, since);
|
|
8824
8944
|
sessions.push(...cursorSessions);
|
|
8825
8945
|
break;
|
|
8826
8946
|
}
|
|
8827
8947
|
}
|
|
8828
8948
|
const windsurfDirs = [
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8949
|
+
join2(homedir2(), ".codeium", "chat_history"),
|
|
8950
|
+
join2(homedir2(), ".windsurf", "sessions"),
|
|
8951
|
+
join2(homedir2(), "Library", "Application Support", "Windsurf", "sessions")
|
|
8832
8952
|
];
|
|
8833
8953
|
for (const windsurfDir of windsurfDirs) {
|
|
8834
|
-
if (
|
|
8954
|
+
if (existsSync2(windsurfDir)) {
|
|
8835
8955
|
const windsurfSessions = collectWindsurfSessions(windsurfDir, since);
|
|
8836
8956
|
sessions.push(...windsurfSessions);
|
|
8837
8957
|
break;
|
|
8838
8958
|
}
|
|
8839
8959
|
}
|
|
8840
8960
|
const copilotDirs = [
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8961
|
+
join2(homedir2(), ".config", "github-copilot"),
|
|
8962
|
+
join2(homedir2(), "Library", "Application Support", "Code", "User", "globalStorage", "github.copilot"),
|
|
8963
|
+
join2(homedir2(), ".vscode", "extensions")
|
|
8844
8964
|
];
|
|
8845
8965
|
for (const copilotDir of copilotDirs) {
|
|
8846
|
-
if (
|
|
8966
|
+
if (existsSync2(copilotDir)) {
|
|
8847
8967
|
const copilotSessions = collectCopilotSessions(copilotDir, since);
|
|
8848
8968
|
sessions.push(...copilotSessions);
|
|
8849
8969
|
break;
|
|
8850
8970
|
}
|
|
8851
8971
|
}
|
|
8852
|
-
if (sessions.length === 0)
|
|
8972
|
+
if (sessions.length === 0) {
|
|
8973
|
+
sessionCache?.save();
|
|
8853
8974
|
return null;
|
|
8975
|
+
}
|
|
8854
8976
|
let totalTokens = 0;
|
|
8855
8977
|
let totalInputTokens = 0;
|
|
8856
8978
|
let totalOutputTokens = 0;
|
|
@@ -8873,6 +8995,7 @@ async function collectAgentSessions(since) {
|
|
|
8873
8995
|
costByModel[session.model] = (costByModel[session.model] ?? 0) + session.costUsd;
|
|
8874
8996
|
}
|
|
8875
8997
|
}
|
|
8998
|
+
sessionCache?.save();
|
|
8876
8999
|
return {
|
|
8877
9000
|
sessions,
|
|
8878
9001
|
totalSessions: sessions.length,
|
|
@@ -8890,9 +9013,9 @@ async function collectAgentSessions(since) {
|
|
|
8890
9013
|
function collectClaudeSessions(projectsDir, since) {
|
|
8891
9014
|
const sessions = [];
|
|
8892
9015
|
try {
|
|
8893
|
-
const projectDirs = readdirSync(projectsDir, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) =>
|
|
9016
|
+
const projectDirs = readdirSync(projectsDir, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) => join2(projectsDir, d2.name));
|
|
8894
9017
|
for (const projectDir of projectDirs) {
|
|
8895
|
-
const files = readdirSync(projectDir).filter((f3) => f3.endsWith(".jsonl")).map((f3) =>
|
|
9018
|
+
const files = readdirSync(projectDir).filter((f3) => f3.endsWith(".jsonl")).map((f3) => join2(projectDir, f3));
|
|
8896
9019
|
for (const file of files) {
|
|
8897
9020
|
const session = parseClaudeSessionFile(file, since);
|
|
8898
9021
|
if (session) {
|
|
@@ -8905,7 +9028,15 @@ function collectClaudeSessions(projectsDir, since) {
|
|
|
8905
9028
|
}
|
|
8906
9029
|
function parseClaudeSessionFile(filePath, since) {
|
|
8907
9030
|
try {
|
|
8908
|
-
const
|
|
9031
|
+
const cached = sessionCache?.get(filePath);
|
|
9032
|
+
if (cached) {
|
|
9033
|
+
const cachedEndedAt = cached.endedAt ? new Date(cached.endedAt) : null;
|
|
9034
|
+
if (cachedEndedAt && cachedEndedAt >= since) {
|
|
9035
|
+
return cached;
|
|
9036
|
+
}
|
|
9037
|
+
return null;
|
|
9038
|
+
}
|
|
9039
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
8909
9040
|
const lines = content.trim().split(`
|
|
8910
9041
|
`).filter(Boolean);
|
|
8911
9042
|
if (lines.length === 0)
|
|
@@ -8970,7 +9101,7 @@ function parseClaudeSessionFile(filePath, since) {
|
|
|
8970
9101
|
const durationMinutes = Math.round((endedAt.getTime() - startedAt.getTime()) / 60000);
|
|
8971
9102
|
const costUsd = calculateTokenCost(model, inputTokens, outputTokens, cachedTokens);
|
|
8972
9103
|
const patterns = detectPatterns(messages);
|
|
8973
|
-
|
|
9104
|
+
const session = {
|
|
8974
9105
|
agent: "claude",
|
|
8975
9106
|
sessionId: basename(filePath, ".jsonl"),
|
|
8976
9107
|
startedAt: startedAt.toISOString(),
|
|
@@ -8987,6 +9118,8 @@ function parseClaudeSessionFile(filePath, since) {
|
|
|
8987
9118
|
toolNames: Array.from(toolNamesSet),
|
|
8988
9119
|
patterns
|
|
8989
9120
|
};
|
|
9121
|
+
sessionCache?.set(filePath, session);
|
|
9122
|
+
return session;
|
|
8990
9123
|
} catch {
|
|
8991
9124
|
return null;
|
|
8992
9125
|
}
|
|
@@ -9157,15 +9290,15 @@ function similarity(a2, b2) {
|
|
|
9157
9290
|
}
|
|
9158
9291
|
function collectOpenCodeSessions(baseDir, since) {
|
|
9159
9292
|
const sessions = [];
|
|
9160
|
-
const sessionStorageDir =
|
|
9161
|
-
const messageStorageDir =
|
|
9162
|
-
const partStorageDir =
|
|
9163
|
-
if (!
|
|
9293
|
+
const sessionStorageDir = join2(baseDir, "storage", "session");
|
|
9294
|
+
const messageStorageDir = join2(baseDir, "storage", "message");
|
|
9295
|
+
const partStorageDir = join2(baseDir, "storage", "part");
|
|
9296
|
+
if (!existsSync2(sessionStorageDir))
|
|
9164
9297
|
return sessions;
|
|
9165
9298
|
try {
|
|
9166
|
-
const projectDirs = readdirSync(sessionStorageDir, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) =>
|
|
9299
|
+
const projectDirs = readdirSync(sessionStorageDir, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) => join2(sessionStorageDir, d2.name));
|
|
9167
9300
|
for (const projectDir of projectDirs) {
|
|
9168
|
-
const sessionFiles = readdirSync(projectDir).filter((f3) => f3.startsWith("ses_") && f3.endsWith(".json")).map((f3) =>
|
|
9301
|
+
const sessionFiles = readdirSync(projectDir).filter((f3) => f3.startsWith("ses_") && f3.endsWith(".json")).map((f3) => join2(projectDir, f3));
|
|
9169
9302
|
for (const sessionFile of sessionFiles) {
|
|
9170
9303
|
const session = parseOpenCodeSession(sessionFile, messageStorageDir, partStorageDir, since);
|
|
9171
9304
|
if (session) {
|
|
@@ -9178,20 +9311,28 @@ function collectOpenCodeSessions(baseDir, since) {
|
|
|
9178
9311
|
}
|
|
9179
9312
|
function parseOpenCodeSession(sessionFile, messageStorageDir, partStorageDir, since) {
|
|
9180
9313
|
try {
|
|
9181
|
-
const
|
|
9314
|
+
const cached = sessionCache?.get(sessionFile);
|
|
9315
|
+
if (cached) {
|
|
9316
|
+
const cachedEndedAt = cached.endedAt ? new Date(cached.endedAt) : null;
|
|
9317
|
+
if (cachedEndedAt && cachedEndedAt >= since) {
|
|
9318
|
+
return cached;
|
|
9319
|
+
}
|
|
9320
|
+
return null;
|
|
9321
|
+
}
|
|
9322
|
+
const sessionContent = readFileSync2(sessionFile, "utf-8");
|
|
9182
9323
|
const session = JSON.parse(sessionContent);
|
|
9183
9324
|
const sessionStart = new Date(session.time.created);
|
|
9184
9325
|
const sessionEnd = session.time.updated ? new Date(session.time.updated) : sessionStart;
|
|
9185
9326
|
if (sessionEnd < since)
|
|
9186
9327
|
return null;
|
|
9187
|
-
const messagesDir =
|
|
9188
|
-
if (!
|
|
9328
|
+
const messagesDir = join2(messageStorageDir, session.id);
|
|
9329
|
+
if (!existsSync2(messagesDir))
|
|
9189
9330
|
return null;
|
|
9190
|
-
const messageFiles = readdirSync(messagesDir).filter((f3) => f3.startsWith("msg_") && f3.endsWith(".json")).map((f3) =>
|
|
9331
|
+
const messageFiles = readdirSync(messagesDir).filter((f3) => f3.startsWith("msg_") && f3.endsWith(".json")).map((f3) => join2(messagesDir, f3));
|
|
9191
9332
|
const messages = [];
|
|
9192
9333
|
for (const msgFile of messageFiles) {
|
|
9193
9334
|
try {
|
|
9194
|
-
const msgContent =
|
|
9335
|
+
const msgContent = readFileSync2(msgFile, "utf-8");
|
|
9195
9336
|
messages.push(JSON.parse(msgContent));
|
|
9196
9337
|
} catch {}
|
|
9197
9338
|
}
|
|
@@ -9223,7 +9364,7 @@ function parseOpenCodeSession(sessionFile, messageStorageDir, partStorageDir, si
|
|
|
9223
9364
|
const durationMinutes = Math.round((sessionEnd.getTime() - sessionStart.getTime()) / 60000);
|
|
9224
9365
|
const costUsd = calculateTokenCost(model, inputTokens, outputTokens, cachedTokens);
|
|
9225
9366
|
const patterns = detectOpenCodePatterns(messages, partStorageDir);
|
|
9226
|
-
|
|
9367
|
+
const agentSession = {
|
|
9227
9368
|
agent: "opencode",
|
|
9228
9369
|
sessionId: session.id,
|
|
9229
9370
|
startedAt: sessionStart.toISOString(),
|
|
@@ -9240,6 +9381,8 @@ function parseOpenCodeSession(sessionFile, messageStorageDir, partStorageDir, si
|
|
|
9240
9381
|
toolNames: [],
|
|
9241
9382
|
patterns
|
|
9242
9383
|
};
|
|
9384
|
+
sessionCache?.set(sessionFile, agentSession);
|
|
9385
|
+
return agentSession;
|
|
9243
9386
|
} catch {
|
|
9244
9387
|
return null;
|
|
9245
9388
|
}
|
|
@@ -9251,14 +9394,14 @@ function detectOpenCodePatterns(messages, partStorageDir) {
|
|
|
9251
9394
|
const msg = messages[i2];
|
|
9252
9395
|
if (msg.role !== "user")
|
|
9253
9396
|
continue;
|
|
9254
|
-
const partDir =
|
|
9255
|
-
if (!
|
|
9397
|
+
const partDir = join2(partStorageDir, msg.id);
|
|
9398
|
+
if (!existsSync2(partDir))
|
|
9256
9399
|
continue;
|
|
9257
9400
|
try {
|
|
9258
9401
|
const partFiles = readdirSync(partDir).filter((f3) => f3.endsWith(".json"));
|
|
9259
9402
|
let text = "";
|
|
9260
9403
|
for (const partFile of partFiles) {
|
|
9261
|
-
const partContent =
|
|
9404
|
+
const partContent = readFileSync2(join2(partDir, partFile), "utf-8");
|
|
9262
9405
|
const part = JSON.parse(partContent);
|
|
9263
9406
|
if (part.type === "text" && part.text) {
|
|
9264
9407
|
text += part.text + " ";
|
|
@@ -9384,9 +9527,9 @@ function collectCursorSessions(sessionsDir, since) {
|
|
|
9384
9527
|
try {
|
|
9385
9528
|
const files = readdirSync(sessionsDir).filter((f3) => f3.endsWith(".json") || f3.endsWith(".jsonl"));
|
|
9386
9529
|
for (const file of files) {
|
|
9387
|
-
const filePath =
|
|
9530
|
+
const filePath = join2(sessionsDir, file);
|
|
9388
9531
|
try {
|
|
9389
|
-
const content =
|
|
9532
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
9390
9533
|
let data;
|
|
9391
9534
|
if (content.startsWith("[")) {
|
|
9392
9535
|
data = JSON.parse(content);
|
|
@@ -9431,7 +9574,7 @@ function collectCursorSessions(sessionsDir, since) {
|
|
|
9431
9574
|
outputTokens,
|
|
9432
9575
|
cachedTokens: 0,
|
|
9433
9576
|
totalTokens,
|
|
9434
|
-
costUsd: calculateTokenCost(
|
|
9577
|
+
costUsd: calculateTokenCost(firstEntry?.model || "gpt-4", inputTokens, outputTokens),
|
|
9435
9578
|
model: firstEntry?.model || "gpt-4",
|
|
9436
9579
|
toolCalls: 0,
|
|
9437
9580
|
messageCount,
|
|
@@ -9448,9 +9591,9 @@ function collectWindsurfSessions(sessionsDir, since) {
|
|
|
9448
9591
|
try {
|
|
9449
9592
|
const files = readdirSync(sessionsDir).filter((f3) => f3.endsWith(".json") || f3.endsWith(".jsonl"));
|
|
9450
9593
|
for (const file of files) {
|
|
9451
|
-
const filePath =
|
|
9594
|
+
const filePath = join2(sessionsDir, file);
|
|
9452
9595
|
try {
|
|
9453
|
-
const content =
|
|
9596
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
9454
9597
|
let data;
|
|
9455
9598
|
if (content.startsWith("[")) {
|
|
9456
9599
|
data = JSON.parse(content);
|
|
@@ -9494,7 +9637,7 @@ function collectWindsurfSessions(sessionsDir, since) {
|
|
|
9494
9637
|
outputTokens,
|
|
9495
9638
|
cachedTokens: 0,
|
|
9496
9639
|
totalTokens,
|
|
9497
|
-
costUsd: calculateTokenCost(inputTokens, outputTokens
|
|
9640
|
+
costUsd: calculateTokenCost(firstEntry?.model || "codeium", inputTokens, outputTokens),
|
|
9498
9641
|
model: firstEntry?.model || "codeium",
|
|
9499
9642
|
toolCalls: 0,
|
|
9500
9643
|
messageCount,
|
|
@@ -9510,19 +9653,19 @@ function collectCopilotSessions(copilotDir, since) {
|
|
|
9510
9653
|
const sessions = [];
|
|
9511
9654
|
try {
|
|
9512
9655
|
const searchPaths = [
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9656
|
+
join2(copilotDir, "usage"),
|
|
9657
|
+
join2(copilotDir, "telemetry"),
|
|
9658
|
+
join2(copilotDir, "chat"),
|
|
9516
9659
|
copilotDir
|
|
9517
9660
|
];
|
|
9518
9661
|
for (const searchPath of searchPaths) {
|
|
9519
|
-
if (!
|
|
9662
|
+
if (!existsSync2(searchPath))
|
|
9520
9663
|
continue;
|
|
9521
9664
|
const files = readdirSync(searchPath).filter((f3) => f3.endsWith(".json") || f3.endsWith(".jsonl") || f3.endsWith(".log"));
|
|
9522
9665
|
for (const file of files) {
|
|
9523
|
-
const filePath =
|
|
9666
|
+
const filePath = join2(searchPath, file);
|
|
9524
9667
|
try {
|
|
9525
|
-
const content =
|
|
9668
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
9526
9669
|
let entries = [];
|
|
9527
9670
|
try {
|
|
9528
9671
|
if (content.startsWith("[")) {
|
|
@@ -9584,8 +9727,8 @@ function collectCopilotSessions(copilotDir, since) {
|
|
|
9584
9727
|
}
|
|
9585
9728
|
|
|
9586
9729
|
// src/collectors/tests.ts
|
|
9587
|
-
import { existsSync as
|
|
9588
|
-
import { join as
|
|
9730
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
9731
|
+
import { join as join3 } from "node:path";
|
|
9589
9732
|
async function collectTestResults(projectDir) {
|
|
9590
9733
|
const vitestResult = tryVitestReport(projectDir);
|
|
9591
9734
|
if (vitestResult)
|
|
@@ -9600,16 +9743,16 @@ async function collectTestResults(projectDir) {
|
|
|
9600
9743
|
}
|
|
9601
9744
|
function tryVitestReport(projectDir) {
|
|
9602
9745
|
const paths = [
|
|
9603
|
-
|
|
9604
|
-
|
|
9746
|
+
join3(projectDir, "test-results", "vitest-results.json"),
|
|
9747
|
+
join3(projectDir, "coverage", "coverage-summary.json")
|
|
9605
9748
|
];
|
|
9606
|
-
const hasVitest = ["vitest.config.ts", "vitest.config.js", "vitest.config.mts"].some((f3) =>
|
|
9749
|
+
const hasVitest = ["vitest.config.ts", "vitest.config.js", "vitest.config.mts"].some((f3) => existsSync3(join3(projectDir, f3)));
|
|
9607
9750
|
if (!hasVitest)
|
|
9608
9751
|
return null;
|
|
9609
9752
|
for (const p of paths) {
|
|
9610
|
-
if (
|
|
9753
|
+
if (existsSync3(p)) {
|
|
9611
9754
|
try {
|
|
9612
|
-
const data = JSON.parse(
|
|
9755
|
+
const data = JSON.parse(readFileSync3(p, "utf-8"));
|
|
9613
9756
|
return parseVitestJson(data);
|
|
9614
9757
|
} catch {}
|
|
9615
9758
|
}
|
|
@@ -9645,16 +9788,16 @@ function parseVitestJson(data) {
|
|
|
9645
9788
|
}
|
|
9646
9789
|
function tryJestReport(projectDir) {
|
|
9647
9790
|
const paths = [
|
|
9648
|
-
|
|
9649
|
-
|
|
9791
|
+
join3(projectDir, "test-results", "jest-results.json"),
|
|
9792
|
+
join3(projectDir, "jest-results.json")
|
|
9650
9793
|
];
|
|
9651
|
-
const hasJest = ["jest.config.ts", "jest.config.js", "jest.config.mjs"].some((f3) =>
|
|
9794
|
+
const hasJest = ["jest.config.ts", "jest.config.js", "jest.config.mjs"].some((f3) => existsSync3(join3(projectDir, f3)));
|
|
9652
9795
|
if (!hasJest)
|
|
9653
9796
|
return null;
|
|
9654
9797
|
for (const p of paths) {
|
|
9655
|
-
if (
|
|
9798
|
+
if (existsSync3(p)) {
|
|
9656
9799
|
try {
|
|
9657
|
-
const data = JSON.parse(
|
|
9800
|
+
const data = JSON.parse(readFileSync3(p, "utf-8"));
|
|
9658
9801
|
return {
|
|
9659
9802
|
framework: "jest",
|
|
9660
9803
|
totalTests: data.numTotalTests ?? 0,
|
|
@@ -9675,13 +9818,13 @@ function tryPlaywrightReport(projectDir) {
|
|
|
9675
9818
|
const hasPlaywright = [
|
|
9676
9819
|
"playwright.config.ts",
|
|
9677
9820
|
"playwright.config.js"
|
|
9678
|
-
].some((f3) =>
|
|
9821
|
+
].some((f3) => existsSync3(join3(projectDir, f3)));
|
|
9679
9822
|
if (!hasPlaywright)
|
|
9680
9823
|
return null;
|
|
9681
|
-
const reportPath =
|
|
9682
|
-
if (
|
|
9824
|
+
const reportPath = join3(projectDir, "test-results", "results.json");
|
|
9825
|
+
if (existsSync3(reportPath)) {
|
|
9683
9826
|
try {
|
|
9684
|
-
const data = JSON.parse(
|
|
9827
|
+
const data = JSON.parse(readFileSync3(reportPath, "utf-8"));
|
|
9685
9828
|
const suites = data.suites ?? [];
|
|
9686
9829
|
let passed = 0;
|
|
9687
9830
|
let failed = 0;
|
|
@@ -9722,9 +9865,9 @@ function tryPlaywrightReport(projectDir) {
|
|
|
9722
9865
|
init_client();
|
|
9723
9866
|
|
|
9724
9867
|
// src/lib/analyzer.ts
|
|
9725
|
-
import { existsSync as
|
|
9726
|
-
import { homedir as
|
|
9727
|
-
import { join as
|
|
9868
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
9869
|
+
import { homedir as homedir4 } from "node:os";
|
|
9870
|
+
import { join as join5 } from "node:path";
|
|
9728
9871
|
import { execSync } from "node:child_process";
|
|
9729
9872
|
function analyzeWorkflow(agents, projectDir) {
|
|
9730
9873
|
const sessionPatterns = analyzeSessionPatterns(agents);
|
|
@@ -9829,10 +9972,10 @@ function analyzeEfficiency(agents) {
|
|
|
9829
9972
|
};
|
|
9830
9973
|
}
|
|
9831
9974
|
function detectEnvironment(projectDir, agents) {
|
|
9832
|
-
const home =
|
|
9833
|
-
const claudeConfigDir =
|
|
9834
|
-
const hasClaudeRules =
|
|
9835
|
-
const hasAgentsmd =
|
|
9975
|
+
const home = homedir4();
|
|
9976
|
+
const claudeConfigDir = join5(home, ".claude");
|
|
9977
|
+
const hasClaudeRules = existsSync5(join5(projectDir, "CLAUDE.md")) || existsSync5(join5(projectDir, ".claude", "settings.local.json")) || existsSync5(join5(projectDir, ".cursorrules")) || existsSync5(join5(projectDir, ".clauderules"));
|
|
9978
|
+
const hasAgentsmd = existsSync5(join5(projectDir, "AGENTS.md"));
|
|
9836
9979
|
let hasSupermemory = false;
|
|
9837
9980
|
let hasContext7 = false;
|
|
9838
9981
|
let hasNia = false;
|
|
@@ -9886,13 +10029,13 @@ function detectEnvironment(projectDir, agents) {
|
|
|
9886
10029
|
}
|
|
9887
10030
|
}
|
|
9888
10031
|
}
|
|
9889
|
-
const transcriptsDir =
|
|
9890
|
-
if (!hasSupermemory &&
|
|
10032
|
+
const transcriptsDir = join5(home, ".claude", "transcripts");
|
|
10033
|
+
if (!hasSupermemory && existsSync5(transcriptsDir)) {
|
|
9891
10034
|
try {
|
|
9892
10035
|
const transcriptFiles = readdirSync2(transcriptsDir).filter((f3) => f3.endsWith(".jsonl")).slice(-20);
|
|
9893
10036
|
for (const file of transcriptFiles) {
|
|
9894
10037
|
try {
|
|
9895
|
-
const content =
|
|
10038
|
+
const content = readFileSync5(join5(transcriptsDir, file), "utf-8");
|
|
9896
10039
|
if (content.includes('"tool_name":"supermemory"') || content.includes('"name":"supermemory"') || content.includes("supermemory")) {
|
|
9897
10040
|
hasSupermemory = true;
|
|
9898
10041
|
if (!detectedMcps.includes("supermemory")) {
|
|
@@ -9905,18 +10048,18 @@ function detectEnvironment(projectDir, agents) {
|
|
|
9905
10048
|
}
|
|
9906
10049
|
} catch {}
|
|
9907
10050
|
}
|
|
9908
|
-
const claudeSettingsLocal =
|
|
9909
|
-
const claudeProjectSettings =
|
|
10051
|
+
const claudeSettingsLocal = join5(home, ".claude", "settings.local.json");
|
|
10052
|
+
const claudeProjectSettings = join5(projectDir, ".claude", "settings.local.json");
|
|
9910
10053
|
const mcpConfigPaths = [
|
|
9911
|
-
|
|
9912
|
-
|
|
10054
|
+
join5(home, ".claude", "claude_desktop_config.json"),
|
|
10055
|
+
join5(home, ".claude", "settings.json"),
|
|
9913
10056
|
claudeSettingsLocal,
|
|
9914
10057
|
claudeProjectSettings
|
|
9915
10058
|
];
|
|
9916
10059
|
for (const configPath of mcpConfigPaths) {
|
|
9917
|
-
if (
|
|
10060
|
+
if (existsSync5(configPath)) {
|
|
9918
10061
|
try {
|
|
9919
|
-
const config = JSON.parse(
|
|
10062
|
+
const config = JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
9920
10063
|
const mcpServers = config.mcpServers || config.mcp_servers || {};
|
|
9921
10064
|
for (const [name, _3] of Object.entries(mcpServers)) {
|
|
9922
10065
|
const mcpName = name.toLowerCase();
|
|
@@ -9940,12 +10083,12 @@ function detectEnvironment(projectDir, agents) {
|
|
|
9940
10083
|
} catch {}
|
|
9941
10084
|
}
|
|
9942
10085
|
}
|
|
9943
|
-
const agentsMdPath =
|
|
9944
|
-
const globalAgentsMdPath =
|
|
10086
|
+
const agentsMdPath = join5(projectDir, "AGENTS.md");
|
|
10087
|
+
const globalAgentsMdPath = join5(home, ".config", "Claude", "AGENTS.md");
|
|
9945
10088
|
for (const agentsMd of [agentsMdPath, globalAgentsMdPath]) {
|
|
9946
|
-
if (
|
|
10089
|
+
if (existsSync5(agentsMd)) {
|
|
9947
10090
|
try {
|
|
9948
|
-
const content =
|
|
10091
|
+
const content = readFileSync5(agentsMd, "utf-8").toLowerCase();
|
|
9949
10092
|
if (content.includes("context7") || content.includes("context-7")) {
|
|
9950
10093
|
hasContext7 = true;
|
|
9951
10094
|
if (!detectedMcps.includes("context7")) {
|
|
@@ -10003,16 +10146,16 @@ function detectEnvironment(projectDir, agents) {
|
|
|
10003
10146
|
} catch {}
|
|
10004
10147
|
}
|
|
10005
10148
|
if (!hasBeads) {
|
|
10006
|
-
hasBeads =
|
|
10149
|
+
hasBeads = existsSync5(join5(projectDir, ".beads"));
|
|
10007
10150
|
}
|
|
10008
10151
|
let hasSkills = false;
|
|
10009
10152
|
let skillCount = 0;
|
|
10010
10153
|
const skillsDirs = [
|
|
10011
|
-
|
|
10012
|
-
|
|
10154
|
+
join5(claudeConfigDir, "skills"),
|
|
10155
|
+
join5(home, ".agents", "skills")
|
|
10013
10156
|
];
|
|
10014
10157
|
for (const skillsDir of skillsDirs) {
|
|
10015
|
-
if (
|
|
10158
|
+
if (existsSync5(skillsDir)) {
|
|
10016
10159
|
try {
|
|
10017
10160
|
const skills = readdirSync2(skillsDir, { withFileTypes: true }).filter((d2) => d2.isDirectory() || d2.isSymbolicLink());
|
|
10018
10161
|
skillCount += skills.length;
|
|
@@ -10223,7 +10366,7 @@ function analyzeRequirements(agents, git, projectDir) {
|
|
|
10223
10366
|
recommendations.push("Spend more time understanding the problem before implementing");
|
|
10224
10367
|
}
|
|
10225
10368
|
}
|
|
10226
|
-
const hasRequirementsDocs =
|
|
10369
|
+
const hasRequirementsDocs = existsSync5(join5(projectDir, "CLAUDE.md")) || existsSync5(join5(projectDir, "docs", "requirements.md")) || existsSync5(join5(projectDir, "PRD.md"));
|
|
10227
10370
|
if (hasRequirementsDocs) {
|
|
10228
10371
|
score += 10;
|
|
10229
10372
|
} else {
|
|
@@ -10250,18 +10393,18 @@ function analyzePlanning(agents, git, projectDir) {
|
|
|
10250
10393
|
const issues = [];
|
|
10251
10394
|
const recommendations = [];
|
|
10252
10395
|
let score = 50;
|
|
10253
|
-
const usesTaskTracking =
|
|
10396
|
+
const usesTaskTracking = existsSync5(join5(projectDir, ".beads"));
|
|
10254
10397
|
if (usesTaskTracking) {
|
|
10255
10398
|
score += 20;
|
|
10256
10399
|
} else {
|
|
10257
10400
|
issues.push("No task tracking system detected");
|
|
10258
10401
|
recommendations.push("Install Beads for persistent task tracking: bun add -g beads && bd init");
|
|
10259
10402
|
}
|
|
10260
|
-
const usesDesignDocs =
|
|
10403
|
+
const usesDesignDocs = existsSync5(join5(projectDir, "docs", "design.md")) || existsSync5(join5(projectDir, "ARCHITECTURE.md")) || existsSync5(join5(projectDir, "docs", "spec.md"));
|
|
10261
10404
|
if (usesDesignDocs) {
|
|
10262
10405
|
score += 15;
|
|
10263
10406
|
}
|
|
10264
|
-
const hasSystemDesign =
|
|
10407
|
+
const hasSystemDesign = existsSync5(join5(projectDir, "CLAUDE.md")) || existsSync5(join5(projectDir, "AGENTS.md")) || existsSync5(join5(projectDir, ".claude", "settings.local.json"));
|
|
10265
10408
|
if (hasSystemDesign) {
|
|
10266
10409
|
score += 15;
|
|
10267
10410
|
} else {
|
|
@@ -10363,7 +10506,7 @@ function analyzeTestingPhase(agents, tests, projectDir) {
|
|
|
10363
10506
|
const issues = [];
|
|
10364
10507
|
const recommendations = [];
|
|
10365
10508
|
let score = 40;
|
|
10366
|
-
const hasTestFramework = tests !== null ||
|
|
10509
|
+
const hasTestFramework = tests !== null || existsSync5(join5(projectDir, "vitest.config.ts")) || existsSync5(join5(projectDir, "jest.config.js")) || existsSync5(join5(projectDir, "playwright.config.ts"));
|
|
10367
10510
|
if (hasTestFramework) {
|
|
10368
10511
|
score += 15;
|
|
10369
10512
|
} else {
|
|
@@ -13839,6 +13982,15 @@ function createSpinner(text) {
|
|
|
13839
13982
|
color: "cyan"
|
|
13840
13983
|
});
|
|
13841
13984
|
}
|
|
13985
|
+
function formatScore(score) {
|
|
13986
|
+
if (score >= 80)
|
|
13987
|
+
return colors2.excellent(score.toString());
|
|
13988
|
+
if (score >= 60)
|
|
13989
|
+
return colors2.good(score.toString());
|
|
13990
|
+
if (score >= 40)
|
|
13991
|
+
return colors2.moderate(score.toString());
|
|
13992
|
+
return colors2.poor(score.toString());
|
|
13993
|
+
}
|
|
13842
13994
|
function formatTier(tier) {
|
|
13843
13995
|
const tierColors = {
|
|
13844
13996
|
elite: (s2) => import_picocolors.default.bold(import_picocolors.default.magenta(s2)),
|
|
@@ -14023,6 +14175,11 @@ var scanCommand = defineCommand2({
|
|
|
14023
14175
|
alias: "q",
|
|
14024
14176
|
description: "Quiet mode - only output the score number",
|
|
14025
14177
|
default: false
|
|
14178
|
+
},
|
|
14179
|
+
"no-cache": {
|
|
14180
|
+
type: "boolean",
|
|
14181
|
+
description: "Disable session caching (slower but ensures fresh data)",
|
|
14182
|
+
default: false
|
|
14026
14183
|
}
|
|
14027
14184
|
},
|
|
14028
14185
|
async run({ args }) {
|
|
@@ -14030,6 +14187,7 @@ var scanCommand = defineCommand2({
|
|
|
14030
14187
|
const since = parseSince(args.since);
|
|
14031
14188
|
const jsonOutput = args.json;
|
|
14032
14189
|
const quietMode = args.quiet;
|
|
14190
|
+
const useCache = !args["no-cache"];
|
|
14033
14191
|
const silent = jsonOutput || quietMode;
|
|
14034
14192
|
if (!silent) {
|
|
14035
14193
|
console.log();
|
|
@@ -14047,7 +14205,7 @@ var scanCommand = defineCommand2({
|
|
|
14047
14205
|
}
|
|
14048
14206
|
if (!silent)
|
|
14049
14207
|
await thinkingStep("Scanning AI session logs...", 600);
|
|
14050
|
-
const agents = await collectAgentSessions(since);
|
|
14208
|
+
const agents = await collectAgentSessions(since, useCache);
|
|
14051
14209
|
if (agents && !silent) {
|
|
14052
14210
|
await revealDiscovery(icons.success, `Sessions: ${colors2.primary(agents.totalSessions.toString())} sessions loaded`, 200);
|
|
14053
14211
|
}
|
|
@@ -14142,7 +14300,7 @@ var scanCommand = defineCommand2({
|
|
|
14142
14300
|
console.log(colors2.dim(" " + "═".repeat(45)));
|
|
14143
14301
|
console.log();
|
|
14144
14302
|
if (!args["no-report"]) {
|
|
14145
|
-
const reportDir =
|
|
14303
|
+
const reportDir = join6(projectDir, args["report-dir"]);
|
|
14146
14304
|
const reportPath = generateReport(reportDir, score, git, agents, tests, args.since, sdlcAnalysis, scanCost, analysis);
|
|
14147
14305
|
console.log(` ${icons.success} Report saved: ${colors2.dim(reportPath)}`);
|
|
14148
14306
|
}
|
|
@@ -14302,12 +14460,12 @@ function parseSince(since) {
|
|
|
14302
14460
|
return new Date(Date.now() - ms);
|
|
14303
14461
|
}
|
|
14304
14462
|
function generateReport(reportDir, score, git, agents, tests, since, sdlcAnalysis, scanCost, analysis) {
|
|
14305
|
-
if (!
|
|
14306
|
-
|
|
14463
|
+
if (!existsSync6(reportDir)) {
|
|
14464
|
+
mkdirSync3(reportDir, { recursive: true });
|
|
14307
14465
|
}
|
|
14308
14466
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
14309
14467
|
const filename = `scan-${timestamp}.md`;
|
|
14310
|
-
const filepath =
|
|
14468
|
+
const filepath = join6(reportDir, filename);
|
|
14311
14469
|
const lines = [];
|
|
14312
14470
|
lines.push(`# NaironAI Scan Report`);
|
|
14313
14471
|
lines.push("");
|
|
@@ -14423,7 +14581,7 @@ function generateReport(reportDir, score, git, agents, tests, since, sdlcAnalysi
|
|
|
14423
14581
|
lines.push("---");
|
|
14424
14582
|
lines.push("");
|
|
14425
14583
|
lines.push("*Generated by [nairon-bench](https://github.com/ObaidUr-Rahmaan/nairon-bench)*");
|
|
14426
|
-
|
|
14584
|
+
writeFileSync3(filepath, lines.join(`
|
|
14427
14585
|
`));
|
|
14428
14586
|
return filepath;
|
|
14429
14587
|
}
|
|
@@ -14622,9 +14780,9 @@ function renderBar(value, width) {
|
|
|
14622
14780
|
}
|
|
14623
14781
|
|
|
14624
14782
|
// src/commands/doctor.ts
|
|
14625
|
-
import { existsSync as
|
|
14626
|
-
import { homedir as
|
|
14627
|
-
import { join as
|
|
14783
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
14784
|
+
import { homedir as homedir5 } from "node:os";
|
|
14785
|
+
import { join as join7 } from "node:path";
|
|
14628
14786
|
init_client();
|
|
14629
14787
|
var doctorCommand = defineCommand2({
|
|
14630
14788
|
meta: {
|
|
@@ -14642,16 +14800,16 @@ var doctorCommand = defineCommand2({
|
|
|
14642
14800
|
results.push({ label: "OS", status: "info", value: `${process.platform} ${process.arch}` });
|
|
14643
14801
|
results.push({ label: "Bun", status: "info", value: typeof Bun !== "undefined" ? Bun.version : "N/A" });
|
|
14644
14802
|
results.push({ label: "Node", status: "info", value: process.version });
|
|
14645
|
-
results.push({ label: "Home", status: "info", value:
|
|
14646
|
-
const gitDir =
|
|
14647
|
-
if (
|
|
14803
|
+
results.push({ label: "Home", status: "info", value: homedir5() });
|
|
14804
|
+
const gitDir = join7(process.cwd(), ".git");
|
|
14805
|
+
if (existsSync7(gitDir)) {
|
|
14648
14806
|
results.push({ label: "Git", status: "success", value: "Repository detected" });
|
|
14649
14807
|
} else {
|
|
14650
14808
|
results.push({ label: "Git", status: "warn", value: "No repository in current directory" });
|
|
14651
14809
|
}
|
|
14652
14810
|
for (const [agent, pathTemplate] of Object.entries(AGENT_LOG_PATHS)) {
|
|
14653
|
-
const resolvedPath = pathTemplate.replace("~",
|
|
14654
|
-
if (
|
|
14811
|
+
const resolvedPath = pathTemplate.replace("~", homedir5());
|
|
14812
|
+
if (existsSync7(resolvedPath)) {
|
|
14655
14813
|
results.push({ label: agent, status: "success", value: `found at ${resolvedPath}` });
|
|
14656
14814
|
}
|
|
14657
14815
|
}
|
|
@@ -14706,9 +14864,9 @@ var doctorCommand = defineCommand2({
|
|
|
14706
14864
|
|
|
14707
14865
|
// src/commands/init.ts
|
|
14708
14866
|
init_dist();
|
|
14709
|
-
import { existsSync as
|
|
14710
|
-
import { homedir as
|
|
14711
|
-
import { join as
|
|
14867
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
14868
|
+
import { homedir as homedir6, platform as platform2, arch } from "node:os";
|
|
14869
|
+
import { join as join8 } from "node:path";
|
|
14712
14870
|
init_client();
|
|
14713
14871
|
|
|
14714
14872
|
// src/lib/github-auth.ts
|
|
@@ -14878,8 +15036,8 @@ var initCommand = defineCommand2({
|
|
|
14878
15036
|
consola.start("Step 2/6: Detecting AI agents...");
|
|
14879
15037
|
const detectedAgents = [];
|
|
14880
15038
|
for (const [agent, pathTemplate] of Object.entries(AGENT_LOG_PATHS)) {
|
|
14881
|
-
const resolvedPath = pathTemplate.replace("~",
|
|
14882
|
-
if (
|
|
15039
|
+
const resolvedPath = pathTemplate.replace("~", homedir6());
|
|
15040
|
+
if (existsSync8(resolvedPath)) {
|
|
14883
15041
|
detectedAgents.push(agent);
|
|
14884
15042
|
consola.success(` Found: ${agent}`);
|
|
14885
15043
|
}
|
|
@@ -14982,7 +15140,7 @@ var initCommand = defineCommand2({
|
|
|
14982
15140
|
{ name: "playwright", configs: ["playwright.config.ts", "playwright.config.js"] }
|
|
14983
15141
|
];
|
|
14984
15142
|
for (const fw of testFrameworks) {
|
|
14985
|
-
if (fw.configs.some((c3) =>
|
|
15143
|
+
if (fw.configs.some((c3) => existsSync8(join8(process.cwd(), c3)))) {
|
|
14986
15144
|
toolStack.testing.push(fw.name);
|
|
14987
15145
|
}
|
|
14988
15146
|
}
|
|
@@ -15051,13 +15209,13 @@ var initCommand = defineCommand2({
|
|
|
15051
15209
|
});
|
|
15052
15210
|
function detectPackageManager() {
|
|
15053
15211
|
const cwd = process.cwd();
|
|
15054
|
-
if (
|
|
15212
|
+
if (existsSync8(join8(cwd, "bun.lock")) || existsSync8(join8(cwd, "bun.lockb")))
|
|
15055
15213
|
return "bun";
|
|
15056
|
-
if (
|
|
15214
|
+
if (existsSync8(join8(cwd, "pnpm-lock.yaml")))
|
|
15057
15215
|
return "pnpm";
|
|
15058
|
-
if (
|
|
15216
|
+
if (existsSync8(join8(cwd, "yarn.lock")))
|
|
15059
15217
|
return "yarn";
|
|
15060
|
-
if (
|
|
15218
|
+
if (existsSync8(join8(cwd, "package-lock.json")))
|
|
15061
15219
|
return "npm";
|
|
15062
15220
|
return;
|
|
15063
15221
|
}
|
|
@@ -15072,15 +15230,15 @@ var CLAUDE_CODE_SKILLS = [
|
|
|
15072
15230
|
];
|
|
15073
15231
|
function detectEasyWins(detectedAgents) {
|
|
15074
15232
|
const wins = [];
|
|
15075
|
-
const home =
|
|
15233
|
+
const home = homedir6();
|
|
15076
15234
|
const hasClaudeCode = detectedAgents.some((a2) => a2.toLowerCase().includes("claude") || a2.toLowerCase().includes("opencode"));
|
|
15077
|
-
const claudeConfigDir =
|
|
15078
|
-
const hasClaudeConfig =
|
|
15235
|
+
const claudeConfigDir = join8(home, ".claude");
|
|
15236
|
+
const hasClaudeConfig = existsSync8(claudeConfigDir);
|
|
15079
15237
|
if (hasClaudeCode || hasClaudeConfig) {
|
|
15080
|
-
const skillsDir =
|
|
15238
|
+
const skillsDir = join8(home, ".claude", "skills");
|
|
15081
15239
|
for (const skill of CLAUDE_CODE_SKILLS) {
|
|
15082
|
-
const skillPath =
|
|
15083
|
-
const isInstalled =
|
|
15240
|
+
const skillPath = join8(skillsDir, skill.id);
|
|
15241
|
+
const isInstalled = existsSync8(skillPath);
|
|
15084
15242
|
if (!isInstalled) {
|
|
15085
15243
|
wins.push({
|
|
15086
15244
|
icon: skill.icon,
|
|
@@ -15094,7 +15252,6 @@ function detectEasyWins(detectedAgents) {
|
|
|
15094
15252
|
}
|
|
15095
15253
|
|
|
15096
15254
|
// src/commands/insights.ts
|
|
15097
|
-
init_dist();
|
|
15098
15255
|
init_client();
|
|
15099
15256
|
var insightsCommand = defineCommand2({
|
|
15100
15257
|
meta: {
|
|
@@ -15111,15 +15268,19 @@ var insightsCommand = defineCommand2({
|
|
|
15111
15268
|
description: "Time range to analyze",
|
|
15112
15269
|
default: "7 days"
|
|
15113
15270
|
},
|
|
15114
|
-
|
|
15115
|
-
type: "
|
|
15116
|
-
description: "Output
|
|
15117
|
-
default:
|
|
15271
|
+
json: {
|
|
15272
|
+
type: "boolean",
|
|
15273
|
+
description: "Output as JSON",
|
|
15274
|
+
default: false
|
|
15118
15275
|
}
|
|
15119
15276
|
},
|
|
15120
15277
|
async run({ args }) {
|
|
15121
|
-
|
|
15122
|
-
|
|
15278
|
+
console.log();
|
|
15279
|
+
console.log(colors2.bold(colors2.primary(" Workflow Insights")));
|
|
15280
|
+
console.log(colors2.dim(" " + "─".repeat(40)));
|
|
15281
|
+
console.log();
|
|
15282
|
+
const spinner = createSpinner("Analyzing your workflow...");
|
|
15283
|
+
spinner.start();
|
|
15123
15284
|
const client = getClient();
|
|
15124
15285
|
const clerkId = getClerkId();
|
|
15125
15286
|
let score = null;
|
|
@@ -15128,11 +15289,12 @@ var insightsCommand = defineCommand2({
|
|
|
15128
15289
|
clerkId
|
|
15129
15290
|
});
|
|
15130
15291
|
if (summary) {
|
|
15131
|
-
|
|
15132
|
-
|
|
15292
|
+
spinner.succeed("Analysis complete");
|
|
15293
|
+
if (args.json) {
|
|
15294
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
15133
15295
|
return;
|
|
15134
15296
|
}
|
|
15135
|
-
renderCloudInsights(summary);
|
|
15297
|
+
await renderCloudInsights(summary);
|
|
15136
15298
|
}
|
|
15137
15299
|
const scan = await client.query(api.scans.getLatestScan, { clerkId });
|
|
15138
15300
|
if (scan) {
|
|
@@ -15140,38 +15302,32 @@ var insightsCommand = defineCommand2({
|
|
|
15140
15302
|
}
|
|
15141
15303
|
}
|
|
15142
15304
|
if (!score) {
|
|
15143
|
-
|
|
15305
|
+
spinner.text = "Computing local insights...";
|
|
15144
15306
|
const since = parseSince2(args.since);
|
|
15145
15307
|
const git = await collectGit(process.cwd(), since);
|
|
15146
15308
|
const agents = await collectAgentSessions(since);
|
|
15147
15309
|
const tests = await collectTestResults(process.cwd());
|
|
15148
15310
|
score = computeNaironScore(git ?? undefined, agents ?? undefined, tests ?? undefined);
|
|
15311
|
+
spinner.succeed("Analysis complete");
|
|
15312
|
+
} else {
|
|
15313
|
+
spinner.succeed("Analysis complete");
|
|
15149
15314
|
}
|
|
15150
|
-
if (args.
|
|
15151
|
-
|
|
15315
|
+
if (args.json) {
|
|
15316
|
+
console.log(JSON.stringify(generateInsights(score, args.phase), null, 2));
|
|
15152
15317
|
return;
|
|
15153
15318
|
}
|
|
15154
|
-
renderLocalInsights(score, args.phase);
|
|
15319
|
+
await renderLocalInsights(score, args.phase);
|
|
15155
15320
|
if (client && clerkId) {
|
|
15156
15321
|
try {
|
|
15157
15322
|
const recommended = await client.query(api.tools.getRecommendedForUser, {
|
|
15158
15323
|
clerkId
|
|
15159
15324
|
});
|
|
15160
15325
|
if (recommended && recommended.length > 0) {
|
|
15161
|
-
|
|
15162
|
-
consola.log("Recommended Tools:");
|
|
15163
|
-
consola.log("─".repeat(50));
|
|
15164
|
-
for (const tool of recommended.slice(0, 5)) {
|
|
15165
|
-
const phases = tool.sdlcPhases.join(", ");
|
|
15166
|
-
consola.log(` ${tool.name} - ${tool.description}`);
|
|
15167
|
-
consola.log(` Phases: ${phases} | ${tool.pricing.free ? "Free" : tool.pricing.paid ?? "Paid"}`);
|
|
15168
|
-
if (tool.installCommands) {
|
|
15169
|
-
consola.log(` Install: ${JSON.stringify(tool.installCommands)}`);
|
|
15170
|
-
}
|
|
15171
|
-
}
|
|
15326
|
+
await renderToolRecommendations(recommended);
|
|
15172
15327
|
}
|
|
15173
15328
|
} catch {}
|
|
15174
15329
|
}
|
|
15330
|
+
console.log();
|
|
15175
15331
|
}
|
|
15176
15332
|
});
|
|
15177
15333
|
function generateInsights(score, phaseFilter) {
|
|
@@ -15230,72 +15386,129 @@ function generateInsights(score, phaseFilter) {
|
|
|
15230
15386
|
insights.sort((a2, b2) => b2.estimatedImpact - a2.estimatedImpact);
|
|
15231
15387
|
return insights;
|
|
15232
15388
|
}
|
|
15233
|
-
function renderLocalInsights(score, phaseFilter) {
|
|
15389
|
+
async function renderLocalInsights(score, phaseFilter) {
|
|
15234
15390
|
const insights = generateInsights(score, phaseFilter);
|
|
15391
|
+
console.log();
|
|
15392
|
+
console.log(` ${icons.chart} ${colors2.bold("Current Score:")} ${formatScore(score.overall)}/100 ${colors2.dim("(")}${formatTier(getTierLabel(score.tier))}${colors2.dim(")")}`);
|
|
15393
|
+
console.log();
|
|
15235
15394
|
if (insights.length === 0) {
|
|
15236
|
-
|
|
15395
|
+
console.log(` ${icons.success} ${colors2.success("No critical insights. Your workflow looks strong!")}`);
|
|
15237
15396
|
return;
|
|
15238
15397
|
}
|
|
15239
|
-
|
|
15240
|
-
|
|
15241
|
-
|
|
15242
|
-
|
|
15243
|
-
|
|
15244
|
-
|
|
15245
|
-
|
|
15246
|
-
|
|
15247
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
`)
|
|
15253
|
-
|
|
15398
|
+
const critical = insights.filter((i3) => i3.severity === "critical");
|
|
15399
|
+
const warning2 = insights.filter((i3) => i3.severity === "warning");
|
|
15400
|
+
const info2 = insights.filter((i3) => i3.severity === "info");
|
|
15401
|
+
if (critical.length > 0) {
|
|
15402
|
+
console.log(` ${colors2.error(colors2.bold("Critical Issues"))}`);
|
|
15403
|
+
console.log(colors2.dim(" " + "─".repeat(40)));
|
|
15404
|
+
for (const insight of critical) {
|
|
15405
|
+
await sleep(150);
|
|
15406
|
+
await renderInsightCard(insight);
|
|
15407
|
+
}
|
|
15408
|
+
console.log();
|
|
15409
|
+
}
|
|
15410
|
+
if (warning2.length > 0) {
|
|
15411
|
+
console.log(` ${colors2.warning(colors2.bold("Needs Attention"))}`);
|
|
15412
|
+
console.log(colors2.dim(" " + "─".repeat(40)));
|
|
15413
|
+
for (const insight of warning2) {
|
|
15414
|
+
await sleep(150);
|
|
15415
|
+
await renderInsightCard(insight);
|
|
15416
|
+
}
|
|
15417
|
+
console.log();
|
|
15418
|
+
}
|
|
15419
|
+
if (info2.length > 0) {
|
|
15420
|
+
console.log(` ${colors2.info(colors2.bold("Opportunities"))}`);
|
|
15421
|
+
console.log(colors2.dim(" " + "─".repeat(40)));
|
|
15422
|
+
for (const insight of info2) {
|
|
15423
|
+
await sleep(150);
|
|
15424
|
+
await renderInsightCard(insight);
|
|
15425
|
+
}
|
|
15426
|
+
}
|
|
15427
|
+
}
|
|
15428
|
+
async function renderInsightCard(insight) {
|
|
15429
|
+
const severityConfig = {
|
|
15430
|
+
critical: { icon: icons.error, color: colors2.error, badge: "CRITICAL" },
|
|
15431
|
+
warning: { icon: icons.warning, color: colors2.warning, badge: "WARNING" },
|
|
15432
|
+
info: { icon: icons.info, color: colors2.info, badge: "TIP" }
|
|
15433
|
+
};
|
|
15434
|
+
const config = severityConfig[insight.severity];
|
|
15435
|
+
console.log();
|
|
15436
|
+
console.log(` ${config.icon} ${config.color(colors2.bold(insight.title))}`);
|
|
15437
|
+
console.log(` ${colors2.dim(insight.description)}`);
|
|
15438
|
+
console.log();
|
|
15439
|
+
console.log(` ${colors2.primary(icons.arrow)} ${colors2.dim("Action:")} ${insight.action}`);
|
|
15440
|
+
console.log(` ${colors2.success(icons.star)} ${colors2.dim("Potential impact:")} ${colors2.success(`+${insight.estimatedImpact}`)} pts`);
|
|
15254
15441
|
}
|
|
15255
|
-
function renderCloudInsights(summary) {
|
|
15442
|
+
async function renderCloudInsights(summary) {
|
|
15256
15443
|
const direction = summary.scoreChange > 0 ? "+" : "";
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15260
|
-
|
|
15261
|
-
|
|
15262
|
-
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
|
|
15266
|
-
|
|
15267
|
-
|
|
15268
|
-
|
|
15444
|
+
const trendColor = summary.scoreChange > 0 ? colors2.success : summary.scoreChange < 0 ? colors2.error : colors2.dim;
|
|
15445
|
+
console.log();
|
|
15446
|
+
console.log(` ${icons.chart} ${colors2.bold("Score Trend")}`);
|
|
15447
|
+
console.log(colors2.dim(" " + "─".repeat(40)));
|
|
15448
|
+
console.log();
|
|
15449
|
+
const scoreText = trendColor(colors2.bold(direction + summary.scoreChange + " pts"));
|
|
15450
|
+
const reportsText = colors2.primary(summary.reportsAnalyzed.toString());
|
|
15451
|
+
console.log(" " + scoreText + " over " + reportsText + " reports");
|
|
15452
|
+
const periodStart = new Date(summary.periodStart).toLocaleDateString();
|
|
15453
|
+
const periodEnd = new Date(summary.periodEnd).toLocaleDateString();
|
|
15454
|
+
console.log(" " + colors2.dim("Period:") + " " + periodStart + " → " + periodEnd);
|
|
15455
|
+
console.log();
|
|
15456
|
+
if (summary.topFrustrations.length > 0) {
|
|
15457
|
+
console.log(" " + icons.warning + " " + colors2.bold("Top Frustrations"));
|
|
15458
|
+
console.log(colors2.dim(" " + "─".repeat(40)));
|
|
15459
|
+
console.log();
|
|
15460
|
+
for (const f3 of summary.topFrustrations) {
|
|
15461
|
+
await sleep(100);
|
|
15462
|
+
console.log(" " + icons.bullet + " " + colors2.warning(f3.topic));
|
|
15463
|
+
const details = f3.count + " occurrences · ~" + f3.minutesWasted + "min · ~" + f3.tokensWasted.toLocaleString() + " tokens wasted";
|
|
15464
|
+
console.log(" " + colors2.dim(details));
|
|
15465
|
+
}
|
|
15466
|
+
}
|
|
15467
|
+
}
|
|
15468
|
+
async function renderToolRecommendations(tools) {
|
|
15469
|
+
console.log();
|
|
15470
|
+
console.log(` ${icons.rocket} ${colors2.bold("Recommended Tools")}`);
|
|
15471
|
+
console.log(colors2.dim(" " + "─".repeat(40)));
|
|
15472
|
+
console.log();
|
|
15473
|
+
for (const tool of tools.slice(0, 3)) {
|
|
15474
|
+
await sleep(100);
|
|
15475
|
+
const phases = tool.sdlcPhases.join(", ");
|
|
15476
|
+
const priceTag = tool.pricing.free ? colors2.success("Free") : colors2.warning(tool.pricing.paid ?? "Paid");
|
|
15477
|
+
console.log(` ${colors2.primary(colors2.bold(tool.name))}`);
|
|
15478
|
+
console.log(` ${colors2.dim(tool.description)}`);
|
|
15479
|
+
console.log(` ${colors2.dim("Phases:")} ${phases} ${colors2.dim("|")} ${priceTag}`);
|
|
15480
|
+
console.log();
|
|
15481
|
+
}
|
|
15269
15482
|
}
|
|
15270
15483
|
function getPhaseAction(phase, severity) {
|
|
15271
15484
|
const actions = {
|
|
15272
15485
|
requirements: {
|
|
15273
|
-
critical: "Start each task with a spec. Write acceptance criteria before opening your editor.
|
|
15274
|
-
warning: "Break large tasks into smaller, well-defined stories. Use AI to draft acceptance criteria
|
|
15275
|
-
info: "
|
|
15486
|
+
critical: "Start each task with a spec. Write acceptance criteria before opening your editor.",
|
|
15487
|
+
warning: "Break large tasks into smaller, well-defined stories. Use AI to draft acceptance criteria.",
|
|
15488
|
+
info: "Add user stories and edge cases to your specs. Track requirement changes."
|
|
15276
15489
|
},
|
|
15277
15490
|
planning: {
|
|
15278
|
-
critical: "Write a PRD or design doc before coding. Use
|
|
15279
|
-
warning: "Add a planning phase
|
|
15280
|
-
info: "Experiment with different planning tools. Compare wireframing
|
|
15491
|
+
critical: "Write a PRD or design doc before coding. Use ADRs for architecture decisions.",
|
|
15492
|
+
warning: "Add a planning phase. Sketch the approach in comments or markdown first.",
|
|
15493
|
+
info: "Experiment with different planning tools. Compare wireframing approaches."
|
|
15281
15494
|
},
|
|
15282
15495
|
implementation: {
|
|
15283
|
-
critical: "Focus on smaller, reviewable diffs. Avoid long
|
|
15284
|
-
warning: "Improve commit cadence. Aim for focused commits under 200 lines.
|
|
15285
|
-
info: "Optimize your
|
|
15496
|
+
critical: "Focus on smaller, reviewable diffs. Avoid long sessions without commits.",
|
|
15497
|
+
warning: "Improve commit cadence. Aim for focused commits under 200 lines.",
|
|
15498
|
+
info: "Optimize your prompting style. Provide more context upfront."
|
|
15286
15499
|
},
|
|
15287
15500
|
testing: {
|
|
15288
|
-
critical: "Add test coverage immediately. Start with integration tests for critical paths.
|
|
15289
|
-
warning: "Increase pass rate and coverage. Write tests before fixing bugs (TDD for bugs).
|
|
15290
|
-
info: "Push for 80%+ coverage. Add property-based
|
|
15501
|
+
critical: "Add test coverage immediately. Start with integration tests for critical paths.",
|
|
15502
|
+
warning: "Increase pass rate and coverage. Write tests before fixing bugs (TDD for bugs).",
|
|
15503
|
+
info: "Push for 80%+ coverage. Add property-based tests for edge cases."
|
|
15291
15504
|
},
|
|
15292
15505
|
review: {
|
|
15293
|
-
critical: "Write meaningful commit messages (explain WHY
|
|
15294
|
-
warning: "
|
|
15295
|
-
info: "Add automated review tools
|
|
15506
|
+
critical: "Write meaningful commit messages (explain WHY). Keep diffs small.",
|
|
15507
|
+
warning: "Use conventional commits. Request reviews before merging.",
|
|
15508
|
+
info: "Add automated review tools. Track review turnaround time."
|
|
15296
15509
|
}
|
|
15297
15510
|
};
|
|
15298
|
-
return actions[phase]?.[severity] ?? "
|
|
15511
|
+
return actions[phase]?.[severity] ?? "Focus on consistency and tooling.";
|
|
15299
15512
|
}
|
|
15300
15513
|
function capitalize2(s2) {
|
|
15301
15514
|
return s2.charAt(0).toUpperCase() + s2.slice(1);
|
|
@@ -15851,9 +16064,9 @@ function renderBar2(value, width) {
|
|
|
15851
16064
|
// src/commands/publish.ts
|
|
15852
16065
|
init_dist();
|
|
15853
16066
|
init_client();
|
|
15854
|
-
import { existsSync as
|
|
15855
|
-
import { join as
|
|
15856
|
-
import { homedir as
|
|
16067
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
16068
|
+
import { join as join9 } from "node:path";
|
|
16069
|
+
import { homedir as homedir7 } from "node:os";
|
|
15857
16070
|
var publishCommand = defineCommand2({
|
|
15858
16071
|
meta: {
|
|
15859
16072
|
name: "publish",
|
|
@@ -15877,7 +16090,7 @@ var publishCommand = defineCommand2({
|
|
|
15877
16090
|
},
|
|
15878
16091
|
async run({ args }) {
|
|
15879
16092
|
const projectDir = args.project ?? process.cwd();
|
|
15880
|
-
const reportDir =
|
|
16093
|
+
const reportDir = join9(projectDir, args["report-dir"]);
|
|
15881
16094
|
consola.start(`Analyzing your AI workflow...
|
|
15882
16095
|
`);
|
|
15883
16096
|
consola.info("Reading scan reports...");
|
|
@@ -15957,12 +16170,12 @@ var publishCommand = defineCommand2({
|
|
|
15957
16170
|
}
|
|
15958
16171
|
});
|
|
15959
16172
|
function readReports(reportDir) {
|
|
15960
|
-
if (!
|
|
16173
|
+
if (!existsSync9(reportDir)) {
|
|
15961
16174
|
return [];
|
|
15962
16175
|
}
|
|
15963
16176
|
const files = readdirSync3(reportDir).filter((f3) => f3.endsWith(".md") && f3.startsWith("scan-")).sort();
|
|
15964
16177
|
return files.map((file) => {
|
|
15965
|
-
const content =
|
|
16178
|
+
const content = readFileSync6(join9(reportDir, file), "utf-8");
|
|
15966
16179
|
return parseReport(content, file);
|
|
15967
16180
|
});
|
|
15968
16181
|
}
|
|
@@ -15991,13 +16204,13 @@ function parseReport(content, filename) {
|
|
|
15991
16204
|
return { date, overall, tier, tokenEfficiency, tokenWaste, phases, models };
|
|
15992
16205
|
}
|
|
15993
16206
|
function detectConcepts(projectDir) {
|
|
15994
|
-
const home =
|
|
16207
|
+
const home = homedir7();
|
|
15995
16208
|
return {
|
|
15996
|
-
skills:
|
|
15997
|
-
mcpServers:
|
|
15998
|
-
customCommands:
|
|
15999
|
-
agentConfig:
|
|
16000
|
-
contextManagement:
|
|
16209
|
+
skills: existsSync9(join9(home, ".claude", "settings.json")) && readFileSync6(join9(home, ".claude", "settings.json"), "utf-8").includes("skill"),
|
|
16210
|
+
mcpServers: existsSync9(join9(home, ".config", "opencode", "config.json")) || existsSync9(join9(home, ".claude", "claude_desktop_config.json")) || existsSync9(join9(home, ".claude", "settings.json")) && readFileSync6(join9(home, ".claude", "settings.json"), "utf-8").includes("mcpServers"),
|
|
16211
|
+
customCommands: existsSync9(join9(home, ".claude", "commands")) || existsSync9(join9(projectDir, ".claude", "commands")),
|
|
16212
|
+
agentConfig: existsSync9(join9(projectDir, "AGENTS.md")) || existsSync9(join9(projectDir, ".cursorrules")) || existsSync9(join9(projectDir, ".github", "copilot-instructions.md")),
|
|
16213
|
+
contextManagement: existsSync9(join9(projectDir, "MEMORY.md")) || existsSync9(join9(projectDir, ".context")) || existsSync9(join9(projectDir, "docs", "architecture.md")) || existsSync9(join9(projectDir, ".beads"))
|
|
16001
16214
|
};
|
|
16002
16215
|
}
|
|
16003
16216
|
function computeConceptsScore(concepts) {
|
|
@@ -16018,18 +16231,18 @@ function computeConceptsScore(concepts) {
|
|
|
16018
16231
|
}
|
|
16019
16232
|
function detectTools(projectDir) {
|
|
16020
16233
|
const tools = [];
|
|
16021
|
-
const home =
|
|
16234
|
+
const home = homedir7();
|
|
16022
16235
|
const toolChecks = [
|
|
16023
|
-
{ name: "Claude Code", check: () =>
|
|
16024
|
-
{ name: "Cursor", check: () =>
|
|
16025
|
-
{ name: "GitHub Copilot", check: () =>
|
|
16026
|
-
{ name: "OpenCode", check: () =>
|
|
16027
|
-
{ name: "Windsurf", check: () =>
|
|
16028
|
-
{ name: "Aider", check: () =>
|
|
16029
|
-
{ name: "Codeium", check: () =>
|
|
16030
|
-
{ name: "Beads", check: () =>
|
|
16031
|
-
{ name: "Convex", check: () =>
|
|
16032
|
-
{ name: "Supabase", check: () =>
|
|
16236
|
+
{ name: "Claude Code", check: () => existsSync9(join9(home, ".claude")) },
|
|
16237
|
+
{ name: "Cursor", check: () => existsSync9(join9(projectDir, ".cursorrules")) },
|
|
16238
|
+
{ name: "GitHub Copilot", check: () => existsSync9(join9(projectDir, ".github", "copilot-instructions.md")) },
|
|
16239
|
+
{ name: "OpenCode", check: () => existsSync9(join9(home, ".config", "opencode")) },
|
|
16240
|
+
{ name: "Windsurf", check: () => existsSync9(join9(home, ".windsurf")) },
|
|
16241
|
+
{ name: "Aider", check: () => existsSync9(join9(projectDir, ".aider.conf.yml")) },
|
|
16242
|
+
{ name: "Codeium", check: () => existsSync9(join9(home, ".codeium")) },
|
|
16243
|
+
{ name: "Beads", check: () => existsSync9(join9(projectDir, ".beads")) },
|
|
16244
|
+
{ name: "Convex", check: () => existsSync9(join9(projectDir, "convex")) },
|
|
16245
|
+
{ name: "Supabase", check: () => existsSync9(join9(projectDir, "supabase")) }
|
|
16033
16246
|
];
|
|
16034
16247
|
for (const { name, check } of toolChecks) {
|
|
16035
16248
|
try {
|
|
@@ -16268,10 +16481,13 @@ function renderMiniChart(scores) {
|
|
|
16268
16481
|
for (const row of chart) {
|
|
16269
16482
|
console.log(" " + row.join(""));
|
|
16270
16483
|
}
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16484
|
+
if (sampled.length >= 6) {
|
|
16485
|
+
const startLabel = "← older";
|
|
16486
|
+
const endLabel = "newer →";
|
|
16487
|
+
const chartWidth = sampled.length;
|
|
16488
|
+
const padding = Math.max(0, chartWidth - startLabel.length - endLabel.length);
|
|
16489
|
+
console.log(` ${colors2.dim(startLabel)}${" ".repeat(padding)}${colors2.dim(endLabel)}`);
|
|
16490
|
+
}
|
|
16275
16491
|
}
|
|
16276
16492
|
|
|
16277
16493
|
// src/commands/cost.ts
|
|
@@ -16439,9 +16655,9 @@ function formatTokens3(tokens) {
|
|
|
16439
16655
|
}
|
|
16440
16656
|
|
|
16441
16657
|
// src/commands/session.ts
|
|
16442
|
-
import { existsSync as
|
|
16443
|
-
import { homedir as
|
|
16444
|
-
import { join as
|
|
16658
|
+
import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
|
|
16659
|
+
import { homedir as homedir8 } from "node:os";
|
|
16660
|
+
import { join as join10, basename as basename2 } from "node:path";
|
|
16445
16661
|
var sessionCommand = defineCommand2({
|
|
16446
16662
|
meta: {
|
|
16447
16663
|
name: "session",
|
|
@@ -16600,23 +16816,23 @@ var sessionCommand = defineCommand2({
|
|
|
16600
16816
|
});
|
|
16601
16817
|
async function collectSessionList() {
|
|
16602
16818
|
const sessions = [];
|
|
16603
|
-
const claudeProjectsDir =
|
|
16604
|
-
if (!
|
|
16819
|
+
const claudeProjectsDir = join10(homedir8(), ".claude", "projects");
|
|
16820
|
+
if (!existsSync10(claudeProjectsDir)) {
|
|
16605
16821
|
return sessions;
|
|
16606
16822
|
}
|
|
16607
16823
|
const projectDirs = readdirSync4(claudeProjectsDir);
|
|
16608
16824
|
for (const projectDir of projectDirs) {
|
|
16609
|
-
const projectPath =
|
|
16610
|
-
const stat =
|
|
16825
|
+
const projectPath = join10(claudeProjectsDir, projectDir);
|
|
16826
|
+
const stat = statSync2(projectPath);
|
|
16611
16827
|
if (!stat.isDirectory())
|
|
16612
16828
|
continue;
|
|
16613
16829
|
const projectName = decodeProjectName(projectDir);
|
|
16614
16830
|
const files = readdirSync4(projectPath).filter((f3) => f3.endsWith(".jsonl"));
|
|
16615
16831
|
for (const file of files) {
|
|
16616
|
-
const filePath =
|
|
16832
|
+
const filePath = join10(projectPath, file);
|
|
16617
16833
|
const sessionId = basename2(file, ".jsonl").slice(0, 8);
|
|
16618
16834
|
try {
|
|
16619
|
-
const content =
|
|
16835
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
16620
16836
|
const lines = content.trim().split(`
|
|
16621
16837
|
`).filter(Boolean);
|
|
16622
16838
|
if (lines.length === 0)
|
|
@@ -16658,7 +16874,7 @@ async function collectSessionList() {
|
|
|
16658
16874
|
if (cost === 0 && totalTokens > 0) {
|
|
16659
16875
|
cost = totalTokens / 1e6 * 3;
|
|
16660
16876
|
}
|
|
16661
|
-
const fileStat =
|
|
16877
|
+
const fileStat = statSync2(filePath);
|
|
16662
16878
|
const duration = Math.round((fileStat.mtimeMs - startedAt.getTime()) / 60000);
|
|
16663
16879
|
sessions.push({
|
|
16664
16880
|
id: sessionId,
|
|
@@ -16690,7 +16906,7 @@ async function parseSessionDetails(filePath) {
|
|
|
16690
16906
|
patterns: []
|
|
16691
16907
|
};
|
|
16692
16908
|
try {
|
|
16693
|
-
const content =
|
|
16909
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
16694
16910
|
const lines = content.trim().split(`
|
|
16695
16911
|
`).filter(Boolean);
|
|
16696
16912
|
const filesSet = new Set;
|
|
@@ -16772,7 +16988,7 @@ function decodeProjectName(encoded) {
|
|
|
16772
16988
|
}
|
|
16773
16989
|
|
|
16774
16990
|
// src/commands/export.ts
|
|
16775
|
-
import { writeFileSync as
|
|
16991
|
+
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
16776
16992
|
var exportCommand = defineCommand2({
|
|
16777
16993
|
meta: {
|
|
16778
16994
|
name: "export",
|
|
@@ -16890,12 +17106,12 @@ var exportCommand = defineCommand2({
|
|
|
16890
17106
|
for (const fmt of formats) {
|
|
16891
17107
|
if (fmt === "json") {
|
|
16892
17108
|
const jsonPath = `${basePath}.json`;
|
|
16893
|
-
|
|
17109
|
+
writeFileSync4(jsonPath, JSON.stringify(exportData, null, 2));
|
|
16894
17110
|
console.log(` ${icons.success} Exported: ${colors2.primary(jsonPath)}`);
|
|
16895
17111
|
} else if (fmt === "csv") {
|
|
16896
17112
|
const csvPath = `${basePath}.csv`;
|
|
16897
17113
|
const csv = generateCSV(exportData);
|
|
16898
|
-
|
|
17114
|
+
writeFileSync4(csvPath, csv);
|
|
16899
17115
|
console.log(` ${icons.success} Exported: ${colors2.primary(csvPath)}`);
|
|
16900
17116
|
}
|
|
16901
17117
|
}
|
|
@@ -17424,6 +17640,65 @@ var badgesCommand = defineCommand2({
|
|
|
17424
17640
|
}
|
|
17425
17641
|
});
|
|
17426
17642
|
|
|
17643
|
+
// src/commands/cache.ts
|
|
17644
|
+
var cacheCommand = defineCommand2({
|
|
17645
|
+
meta: {
|
|
17646
|
+
name: "cache",
|
|
17647
|
+
description: "Manage the session cache for faster scans"
|
|
17648
|
+
},
|
|
17649
|
+
args: {
|
|
17650
|
+
clear: {
|
|
17651
|
+
type: "boolean",
|
|
17652
|
+
description: "Clear the session cache",
|
|
17653
|
+
default: false
|
|
17654
|
+
},
|
|
17655
|
+
info: {
|
|
17656
|
+
type: "boolean",
|
|
17657
|
+
description: "Show cache statistics",
|
|
17658
|
+
default: false
|
|
17659
|
+
}
|
|
17660
|
+
},
|
|
17661
|
+
async run({ args }) {
|
|
17662
|
+
console.log();
|
|
17663
|
+
console.log(colors2.bold(colors2.primary(" Session Cache")));
|
|
17664
|
+
console.log(colors2.dim(" " + "─".repeat(40)));
|
|
17665
|
+
console.log();
|
|
17666
|
+
if (args.clear) {
|
|
17667
|
+
const spinner = createSpinner("Clearing cache...");
|
|
17668
|
+
spinner.start();
|
|
17669
|
+
try {
|
|
17670
|
+
clearCache();
|
|
17671
|
+
spinner.succeed("Cache cleared");
|
|
17672
|
+
} catch (err) {
|
|
17673
|
+
spinner.fail("Failed to clear cache");
|
|
17674
|
+
console.log(` ${colors2.dim(err instanceof Error ? err.message : String(err))}`);
|
|
17675
|
+
}
|
|
17676
|
+
console.log();
|
|
17677
|
+
return;
|
|
17678
|
+
}
|
|
17679
|
+
const info2 = getCacheInfo();
|
|
17680
|
+
console.log(` ${icons.info} Cache Status`);
|
|
17681
|
+
console.log();
|
|
17682
|
+
console.log(` ${colors2.dim("Cached sessions:")} ${colors2.primary(info2.entries.toString())}`);
|
|
17683
|
+
console.log(` ${colors2.dim("Cache size:")} ${colors2.primary(formatBytes(info2.sizeBytes))}`);
|
|
17684
|
+
console.log();
|
|
17685
|
+
if (info2.entries > 0) {
|
|
17686
|
+
console.log(` ${colors2.dim("Run")} ${colors2.primary("nb cache --clear")} ${colors2.dim("to clear the cache")}`);
|
|
17687
|
+
} else {
|
|
17688
|
+
console.log(` ${colors2.dim("Cache is empty. Run")} ${colors2.primary("nb scan")} ${colors2.dim("to populate it.")}`);
|
|
17689
|
+
}
|
|
17690
|
+
console.log();
|
|
17691
|
+
}
|
|
17692
|
+
});
|
|
17693
|
+
function formatBytes(bytes) {
|
|
17694
|
+
if (bytes === 0)
|
|
17695
|
+
return "0 B";
|
|
17696
|
+
const k2 = 1024;
|
|
17697
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
17698
|
+
const i3 = Math.floor(Math.log(bytes) / Math.log(k2));
|
|
17699
|
+
return `${parseFloat((bytes / Math.pow(k2, i3)).toFixed(1))} ${sizes[i3]}`;
|
|
17700
|
+
}
|
|
17701
|
+
|
|
17427
17702
|
// src/index.ts
|
|
17428
17703
|
var VERSION = "0.0.13";
|
|
17429
17704
|
var CYAN = "\x1B[36m";
|
|
@@ -17454,7 +17729,7 @@ function showBanner() {
|
|
|
17454
17729
|
log("");
|
|
17455
17730
|
}
|
|
17456
17731
|
var args = process.argv.slice(2);
|
|
17457
|
-
var subcommands = ["init", "scan", "report", "dashboard", "insights", "tools", "doctor", "publish", "history", "cost", "session", "export", "pr", "badges"];
|
|
17732
|
+
var subcommands = ["init", "scan", "report", "dashboard", "insights", "tools", "doctor", "publish", "history", "cost", "session", "export", "pr", "badges", "cache"];
|
|
17458
17733
|
var hasSubcommand = args.some((arg) => subcommands.includes(arg));
|
|
17459
17734
|
var hasHelp = args.includes("--help") || args.includes("-h");
|
|
17460
17735
|
var hasVersion = args.includes("--version");
|
|
@@ -17481,7 +17756,8 @@ if (!hasSubcommand && !hasHelp && !hasVersion && args.length === 0) {
|
|
|
17481
17756
|
session: sessionCommand,
|
|
17482
17757
|
export: exportCommand,
|
|
17483
17758
|
pr: prCommand,
|
|
17484
|
-
badges: badgesCommand
|
|
17759
|
+
badges: badgesCommand,
|
|
17760
|
+
cache: cacheCommand
|
|
17485
17761
|
}
|
|
17486
17762
|
});
|
|
17487
17763
|
runMain(main);
|