@uxcontinuum/ccaudit 1.0.1 → 1.0.3
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/index.js +204 -51
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -44,11 +44,11 @@ const CLAUDE_DIRS = findClaudeDirs();
|
|
|
44
44
|
// ── SESSION TYPE CLASSIFIER ───────────────────────────────────────────────────
|
|
45
45
|
// Agent-spawned worktrees end with a 32-char hex hash (your orchestrator's
|
|
46
46
|
// pattern). Named project dirs are human.
|
|
47
|
-
// Agent
|
|
48
|
-
//
|
|
47
|
+
// Agent / subagent sessions are detected via the JSONL fields themselves
|
|
48
|
+
// (isSidechain, userType, agentId). Directory naming is a weak fallback only.
|
|
49
49
|
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/;
|
|
50
50
|
const HEX_TAIL_RE = /-[0-9a-f]{20,}$/;
|
|
51
|
-
function
|
|
51
|
+
function fallbackAgentDirGuess(name) {
|
|
52
52
|
if (UUID_RE.test(name)) return true;
|
|
53
53
|
if (HEX_TAIL_RE.test(name)) return true;
|
|
54
54
|
return false;
|
|
@@ -83,17 +83,34 @@ function parseSession(filePath, cutoffMs) {
|
|
|
83
83
|
const toolCalls = [];
|
|
84
84
|
const timestamps = [];
|
|
85
85
|
let title = null;
|
|
86
|
+
let slug = null;
|
|
87
|
+
let cwd = null;
|
|
86
88
|
let outputTokens = 0;
|
|
87
89
|
let inputTokens = 0;
|
|
90
|
+
let isSidechain = false;
|
|
91
|
+
let userType = null;
|
|
92
|
+
let entrypoint = null;
|
|
93
|
+
let claudeVersion = null;
|
|
94
|
+
let messageCount = 0;
|
|
88
95
|
|
|
89
96
|
for (const raw of lines) {
|
|
90
97
|
if (!raw) continue;
|
|
91
98
|
let msg;
|
|
92
99
|
try { msg = JSON.parse(raw); } catch (_) { continue; }
|
|
93
100
|
|
|
101
|
+
// Capture session-level metadata from the first message that has it.
|
|
102
|
+
if (cwd === null && typeof msg.cwd === 'string') cwd = msg.cwd;
|
|
103
|
+
if (slug === null && typeof msg.slug === 'string') slug = msg.slug;
|
|
104
|
+
if (userType === null && typeof msg.userType === 'string') userType = msg.userType;
|
|
105
|
+
if (entrypoint === null && typeof msg.entrypoint === 'string') entrypoint = msg.entrypoint;
|
|
106
|
+
if (claudeVersion === null && typeof msg.version === 'string') claudeVersion = msg.version;
|
|
107
|
+
if (msg.isSidechain === true) isSidechain = true;
|
|
108
|
+
|
|
94
109
|
if (msg.type === 'custom-title') { title = msg.title || ''; continue; }
|
|
95
110
|
if (msg.type !== 'user' && msg.type !== 'assistant') continue;
|
|
96
111
|
|
|
112
|
+
messageCount++;
|
|
113
|
+
|
|
97
114
|
if (msg.timestamp) {
|
|
98
115
|
const t = Date.parse(msg.timestamp);
|
|
99
116
|
if (!isNaN(t) && t >= cutoffMs) timestamps.push(t);
|
|
@@ -124,15 +141,28 @@ function parseSession(filePath, cutoffMs) {
|
|
|
124
141
|
if (!timestamps.length) return null;
|
|
125
142
|
|
|
126
143
|
const projDir = projDirName(filePath);
|
|
144
|
+
// Multi-signal agent detector. Any of these is sufficient:
|
|
145
|
+
// - isSidechain: subagent inside another Claude session
|
|
146
|
+
// - userType non-external: internal automation invocation
|
|
147
|
+
// - dir-name matches UUID/hex pattern: orchestrator-spawned worktree
|
|
148
|
+
const isAgent = isSidechain ||
|
|
149
|
+
(userType && userType !== 'external') ||
|
|
150
|
+
fallbackAgentDirGuess(projDir);
|
|
151
|
+
|
|
127
152
|
return {
|
|
128
153
|
projDir,
|
|
129
|
-
isAgent
|
|
154
|
+
isAgent,
|
|
130
155
|
title: title || '',
|
|
156
|
+
slug: slug || '',
|
|
157
|
+
cwd: cwd || '',
|
|
131
158
|
userPrompts,
|
|
132
159
|
toolCalls,
|
|
133
160
|
timestamps,
|
|
134
161
|
outputTokens,
|
|
135
162
|
inputTokens,
|
|
163
|
+
claudeVersion,
|
|
164
|
+
entrypoint,
|
|
165
|
+
messageCount,
|
|
136
166
|
};
|
|
137
167
|
}
|
|
138
168
|
|
|
@@ -142,13 +172,11 @@ function inspectClaudeDir(claudeDir) {
|
|
|
142
172
|
claudeDir,
|
|
143
173
|
hasSettings: false,
|
|
144
174
|
settingsValid: false,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
stopHooks: 0,
|
|
149
|
-
subagentStopHooks: 0,
|
|
150
|
-
notificationHooks: 0,
|
|
175
|
+
settingsParseError: null,
|
|
176
|
+
hooksByEvent: {}, // dynamic, captures any event type configured
|
|
177
|
+
totalHooks: 0,
|
|
151
178
|
autoMemoryEnabled: false,
|
|
179
|
+
mcpServers: 0,
|
|
152
180
|
hookFiles: [],
|
|
153
181
|
hasClaudeMd: false,
|
|
154
182
|
claudeMdBytes: 0,
|
|
@@ -163,15 +191,34 @@ function inspectClaudeDir(claudeDir) {
|
|
|
163
191
|
const s = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
164
192
|
out.settingsValid = true;
|
|
165
193
|
const hooks = s.hooks || {};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
194
|
+
for (const [evt, entries] of Object.entries(hooks)) {
|
|
195
|
+
if (!Array.isArray(entries)) continue;
|
|
196
|
+
const n = entries.reduce((acc, e) => acc + (Array.isArray(e?.hooks) ? e.hooks.length : 0), 0);
|
|
197
|
+
if (n > 0) {
|
|
198
|
+
out.hooksByEvent[evt] = n;
|
|
199
|
+
out.totalHooks += n;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
out.autoMemoryEnabled = s.autoMemoryEnabled === true;
|
|
203
|
+
// MCP servers can live in settings.json or ~/.claude.json. Count both.
|
|
204
|
+
if (s.mcpServers && typeof s.mcpServers === 'object') {
|
|
205
|
+
out.mcpServers = Object.keys(s.mcpServers).length;
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
out.settingsParseError = e.message;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch (_) {}
|
|
212
|
+
|
|
213
|
+
// ~/.claude.json (user-level MCP + global config). Optional.
|
|
214
|
+
try {
|
|
215
|
+
const cj = path.join(claudeDir, '..', '.claude.json');
|
|
216
|
+
const st = fs.statSync(cj);
|
|
217
|
+
if (st.isFile()) {
|
|
218
|
+
const parsed = JSON.parse(fs.readFileSync(cj, 'utf8'));
|
|
219
|
+
if (parsed?.mcpServers && typeof parsed.mcpServers === 'object') {
|
|
220
|
+
out.mcpServers = Math.max(out.mcpServers, Object.keys(parsed.mcpServers).length);
|
|
221
|
+
}
|
|
175
222
|
}
|
|
176
223
|
} catch (_) {}
|
|
177
224
|
|
|
@@ -217,6 +264,10 @@ function aggregate(sessions) {
|
|
|
217
264
|
const prompts = subset.flatMap(s => s.userPrompts);
|
|
218
265
|
const tools = subset.flatMap(s => s.toolCalls);
|
|
219
266
|
const titled = subset.filter(s => s.title).length;
|
|
267
|
+
const slugged = subset.filter(s => s.slug).length;
|
|
268
|
+
|
|
269
|
+
const cwds = new Set();
|
|
270
|
+
for (const s of subset) if (s.cwd) cwds.add(s.cwd);
|
|
220
271
|
|
|
221
272
|
const toolCounts = {};
|
|
222
273
|
for (const t of tools) toolCounts[t] = (toolCounts[t] || 0) + 1;
|
|
@@ -236,6 +287,8 @@ function aggregate(sessions) {
|
|
|
236
287
|
sessions: subset.length,
|
|
237
288
|
prompts: prompts.length,
|
|
238
289
|
titledPct: Math.round(100 * titled / subset.length),
|
|
290
|
+
sluggedPct: Math.round(100 * slugged / subset.length),
|
|
291
|
+
uniqueCwds: cwds.size,
|
|
239
292
|
avgPromptLen,
|
|
240
293
|
justCount,
|
|
241
294
|
pleaseCount,
|
|
@@ -282,10 +335,8 @@ function grade(stats, setup) {
|
|
|
282
335
|
const human = stats.human || {};
|
|
283
336
|
const agent = stats.agent || {};
|
|
284
337
|
|
|
285
|
-
// 1. Hook coverage.
|
|
286
|
-
const hookSignals =
|
|
287
|
-
setup.preToolUseHooks + setup.postToolUseHooks + setup.userPromptSubmitHooks +
|
|
288
|
-
setup.stopHooks + setup.subagentStopHooks + setup.notificationHooks;
|
|
338
|
+
// 1. Hook coverage. Generic count across whatever event types are configured.
|
|
339
|
+
const hookSignals = setup.totalHooks;
|
|
289
340
|
let hookScore;
|
|
290
341
|
if (hookSignals === 0) hookScore = 35;
|
|
291
342
|
else if (hookSignals === 1) hookScore = 68;
|
|
@@ -293,15 +344,10 @@ function grade(stats, setup) {
|
|
|
293
344
|
else if (hookSignals === 3) hookScore = 90;
|
|
294
345
|
else hookScore = Math.min(100, 92 + hookSignals);
|
|
295
346
|
if (setup.autoMemoryEnabled) hookScore = Math.min(100, hookScore + 3);
|
|
296
|
-
const hookBreakdown =
|
|
297
|
-
|
|
298
|
-
setup.
|
|
299
|
-
|
|
300
|
-
setup.stopHooks ? `${setup.stopHooks} Stop` : null,
|
|
301
|
-
setup.subagentStopHooks ? `${setup.subagentStopHooks} SubagentStop` : null,
|
|
302
|
-
setup.notificationHooks ? `${setup.notificationHooks} Notification` : null,
|
|
303
|
-
setup.autoMemoryEnabled ? 'autoMemory plugin' : null,
|
|
304
|
-
].filter(Boolean).join(', ');
|
|
347
|
+
const hookBreakdown = Object.entries(setup.hooksByEvent)
|
|
348
|
+
.map(([evt, n]) => `${n} ${evt}`)
|
|
349
|
+
.concat(setup.autoMemoryEnabled ? ['autoMemory plugin'] : [])
|
|
350
|
+
.join(', ');
|
|
305
351
|
dims.push({
|
|
306
352
|
name: 'Hook coverage',
|
|
307
353
|
score: hookScore,
|
|
@@ -314,37 +360,68 @@ function grade(stats, setup) {
|
|
|
314
360
|
});
|
|
315
361
|
|
|
316
362
|
// 2. Project hygiene (human sessions only).
|
|
363
|
+
// Three signals: custom titles (strongest), slugs (informal auto-titles),
|
|
364
|
+
// and CWD diversity (project-scoped work via tmux or shell). Any of these
|
|
365
|
+
// counts as organizational hygiene. Long prompts are a tell only when CWD
|
|
366
|
+
// count is low (single project + walls of text = unscoped sprawl).
|
|
317
367
|
if (human.sessions) {
|
|
318
368
|
let hScore = 50;
|
|
319
|
-
hScore += Math.round(human.titledPct * 0.
|
|
320
|
-
|
|
321
|
-
|
|
369
|
+
hScore += Math.round(human.titledPct * 0.35); // formal title bonus
|
|
370
|
+
hScore += Math.round(human.sluggedPct * 0.15); // slug bonus
|
|
371
|
+
// CWD diversity: launching from named project dirs is real hygiene.
|
|
372
|
+
if (human.uniqueCwds >= 3) hScore += 10;
|
|
373
|
+
if (human.uniqueCwds >= 10) hScore += 10;
|
|
374
|
+
if (human.uniqueCwds >= 25) hScore += 6;
|
|
375
|
+
if (human.avgPromptLen > 0 && human.avgPromptLen < 80) hScore -= 6;
|
|
376
|
+
// Walls of text are only a problem when work is unscoped.
|
|
377
|
+
if (human.avgPromptLen > 2500 && human.uniqueCwds < 5) hScore -= 8;
|
|
322
378
|
hScore = Math.max(0, Math.min(100, hScore));
|
|
379
|
+
const titleNote = human.titledPct > 0
|
|
380
|
+
? `${human.titledPct}% titled`
|
|
381
|
+
: (human.sluggedPct > 0 ? `${human.sluggedPct}% have auto-slugs` : 'no titles, no slugs');
|
|
323
382
|
dims.push({
|
|
324
383
|
name: 'Project hygiene (human)',
|
|
325
384
|
score: hScore,
|
|
326
|
-
detail: `${
|
|
327
|
-
fix: human.titledPct <
|
|
328
|
-
? 'Title your sessions
|
|
385
|
+
detail: `${titleNote}, launched from ${human.uniqueCwds} distinct working dirs. Avg prompt: ${human.avgPromptLen} chars.`,
|
|
386
|
+
fix: (human.titledPct < 20 && human.uniqueCwds < 5)
|
|
387
|
+
? 'Title your important sessions, or launch from project dirs so each session is scoped.'
|
|
329
388
|
: null,
|
|
330
389
|
});
|
|
331
390
|
}
|
|
332
391
|
|
|
333
|
-
// 3. Tool balance (human sessions only).
|
|
392
|
+
// 3. Tool balance (human sessions only). Adaptive: don't punish Bash
|
|
393
|
+
// dominance if absolute Edit volume is high, because that's "busy operator"
|
|
394
|
+
// not "bash hammer." Only penalize when Edit absolute volume is also low.
|
|
334
395
|
if (human.sessions && human.totalTools > 0) {
|
|
335
396
|
let bScore = 75;
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
397
|
+
const editAbs = Math.round(human.editPct * human.totalTools / 100);
|
|
398
|
+
const readAbs = Math.round(human.readPct * human.totalTools / 100);
|
|
399
|
+
|
|
400
|
+
// Bash dominance penalty scales with how thin the rest of the toolkit is.
|
|
401
|
+
if (human.bashPct > 65) {
|
|
402
|
+
if (editAbs > 500) bScore -= 6; // big absolute Edit volume — busy operator
|
|
403
|
+
else if (editAbs > 100) bScore -= 12;
|
|
404
|
+
else bScore -= 18; // truly a bash hammer
|
|
405
|
+
} else if (human.bashPct > 50) {
|
|
406
|
+
bScore -= 4;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (human.readPct + human.grepPct + human.editPct < 15) bScore -= 10;
|
|
410
|
+
if (human.editPct > 10 && human.editPct < 55) bScore += 8;
|
|
411
|
+
if (human.agentPct > 2) bScore += 7;
|
|
412
|
+
if (human.agentPct > 5) bScore += 5;
|
|
413
|
+
|
|
340
414
|
bScore = Math.max(0, Math.min(100, bScore));
|
|
415
|
+
|
|
416
|
+
const fix = human.bashPct > 65 && editAbs < 200
|
|
417
|
+
? 'You are running things, not editing things. Use Edit/Write more.'
|
|
418
|
+
: null;
|
|
419
|
+
|
|
341
420
|
dims.push({
|
|
342
421
|
name: 'Tool balance (human)',
|
|
343
422
|
score: bScore,
|
|
344
|
-
detail: `Bash ${human.bashPct}%, Edit+Write ${human.editPct}
|
|
345
|
-
fix
|
|
346
|
-
? 'You are running things, not editing things. Use Edit/Write more.'
|
|
347
|
-
: null,
|
|
423
|
+
detail: `Bash ${human.bashPct}%, Edit+Write ${human.editPct}% (${editAbs} calls), Read ${human.readPct}%, Grep+Glob ${human.grepPct}%, Agent/Task ${human.agentPct}%.`,
|
|
424
|
+
fix,
|
|
348
425
|
});
|
|
349
426
|
}
|
|
350
427
|
|
|
@@ -424,9 +501,13 @@ function renderCard(stats, setup, graded) {
|
|
|
424
501
|
if (stats.agent) {
|
|
425
502
|
pr(` ${C.dim}Agent sessions:${C.reset} ${stats.agent.sessions} sessions, ${(stats.agent.outputTokens / 1e6).toFixed(2)}M output tokens`);
|
|
426
503
|
}
|
|
427
|
-
pr(` ${C.dim}Hooks installed:${C.reset} ${setup.
|
|
504
|
+
pr(` ${C.dim}Hooks installed:${C.reset} ${setup.totalHooks} across ${Object.keys(setup.hooksByEvent).length} event type(s) (${setup.hookFiles.length} hook file(s) in ~/.claude/hooks/)`);
|
|
428
505
|
pr(` ${C.dim}CLAUDE.md:${C.reset} ${setup.hasClaudeMd ? `${setup.claudeMdBytes} bytes` : 'not found'}`);
|
|
506
|
+
pr(` ${C.dim}MCP servers:${C.reset} ${setup.mcpServers}`);
|
|
429
507
|
pr(` ${C.dim}Skills installed:${C.reset} ${setup.skillCount}`);
|
|
508
|
+
if (setup.settingsParseError) {
|
|
509
|
+
pr(` ${C.yellow}settings.json parse error:${C.reset} ${setup.settingsParseError}`);
|
|
510
|
+
}
|
|
430
511
|
|
|
431
512
|
pr();
|
|
432
513
|
pr(` ${C.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}`);
|
|
@@ -453,8 +534,24 @@ const files = projectsDirs.flatMap(d => findJsonl(d, cutoffMs));
|
|
|
453
534
|
const sessions = files.map(f => parseSession(f, cutoffMs)).filter(Boolean);
|
|
454
535
|
|
|
455
536
|
if (!sessions.length) {
|
|
456
|
-
pr(
|
|
457
|
-
pr(`
|
|
537
|
+
pr();
|
|
538
|
+
pr(` ${C.bold}ccaudit${C.reset}: no Claude Code session activity in the last ${days} days.`);
|
|
539
|
+
if (!projectsDirs.length) {
|
|
540
|
+
pr(` ${C.dim}No ~/.claude/projects/ directory found. Looked in: ${CLAUDE_DIRS.join(', ')}${C.reset}`);
|
|
541
|
+
pr(` ${C.dim}Either Claude Code is not installed, you have a non-default home dir, or this is a brand-new setup.${C.reset}`);
|
|
542
|
+
} else {
|
|
543
|
+
pr(` ${C.dim}Scanned: ${projectsDirs.join(', ')}${C.reset}`);
|
|
544
|
+
pr(` ${C.dim}Try --days 90 or --days 365 to widen the window.${C.reset}`);
|
|
545
|
+
}
|
|
546
|
+
// Still surface the setup audit even with no sessions, so brand-new users get value.
|
|
547
|
+
const setupOnly = inspectClaudeDir(CLAUDE_DIRS[0]);
|
|
548
|
+
pr();
|
|
549
|
+
pr(` ${C.bold}Setup snapshot:${C.reset}`);
|
|
550
|
+
pr(` Hooks: ${setupOnly.totalHooks} (${Object.keys(setupOnly.hooksByEvent).join(', ') || 'none'})`);
|
|
551
|
+
pr(` CLAUDE.md: ${setupOnly.hasClaudeMd ? `${setupOnly.claudeMdBytes} bytes` : 'not found'}`);
|
|
552
|
+
pr(` MCP servers: ${setupOnly.mcpServers}`);
|
|
553
|
+
pr(` Skills: ${setupOnly.skillCount}`);
|
|
554
|
+
pr();
|
|
458
555
|
process.exit(0);
|
|
459
556
|
}
|
|
460
557
|
|
|
@@ -462,4 +559,60 @@ const stats = aggregate(sessions);
|
|
|
462
559
|
const setup = inspectClaudeDir(CLAUDE_DIRS[0]);
|
|
463
560
|
const graded = grade(stats, setup);
|
|
464
561
|
|
|
465
|
-
|
|
562
|
+
if (hasFlag('--json')) {
|
|
563
|
+
// Programmatic output. Stable shape for downstream tools and the future
|
|
564
|
+
// public-benchmark backend. No personal content (prompts, slugs, CWDs).
|
|
565
|
+
const payload = {
|
|
566
|
+
schema: 'ccaudit/1',
|
|
567
|
+
generated_at: new Date().toISOString(),
|
|
568
|
+
window_days: days,
|
|
569
|
+
overall: { score: graded.overall, letter: graded.letter },
|
|
570
|
+
dimensions: graded.dims.map(d => ({
|
|
571
|
+
name: d.name,
|
|
572
|
+
score: d.score,
|
|
573
|
+
letter: letterFor(d.score),
|
|
574
|
+
detail: d.detail,
|
|
575
|
+
fix: d.fix,
|
|
576
|
+
})),
|
|
577
|
+
setup: {
|
|
578
|
+
total_hooks: setup.totalHooks,
|
|
579
|
+
hooks_by_event: setup.hooksByEvent,
|
|
580
|
+
auto_memory_enabled: setup.autoMemoryEnabled,
|
|
581
|
+
mcp_servers: setup.mcpServers,
|
|
582
|
+
hook_files: setup.hookFiles.length,
|
|
583
|
+
has_claude_md: setup.hasClaudeMd,
|
|
584
|
+
claude_md_bytes: setup.claudeMdBytes,
|
|
585
|
+
skills_installed: setup.skillCount,
|
|
586
|
+
settings_parse_error: setup.settingsParseError,
|
|
587
|
+
},
|
|
588
|
+
human: stats.human ? {
|
|
589
|
+
sessions: stats.human.sessions,
|
|
590
|
+
prompts: stats.human.prompts,
|
|
591
|
+
titled_pct: stats.human.titledPct,
|
|
592
|
+
slugged_pct: stats.human.sluggedPct,
|
|
593
|
+
unique_cwds: stats.human.uniqueCwds,
|
|
594
|
+
avg_prompt_len: stats.human.avgPromptLen,
|
|
595
|
+
just_count: stats.human.justCount,
|
|
596
|
+
please_count: stats.human.pleaseCount,
|
|
597
|
+
total_tools: stats.human.totalTools,
|
|
598
|
+
tool_distribution: {
|
|
599
|
+
bash_pct: stats.human.bashPct,
|
|
600
|
+
edit_write_pct: stats.human.editPct,
|
|
601
|
+
read_pct: stats.human.readPct,
|
|
602
|
+
grep_glob_pct: stats.human.grepPct,
|
|
603
|
+
agent_task_pct: stats.human.agentPct,
|
|
604
|
+
},
|
|
605
|
+
output_tokens: stats.human.outputTokens,
|
|
606
|
+
input_tokens: stats.human.inputTokens,
|
|
607
|
+
} : null,
|
|
608
|
+
agent: stats.agent ? {
|
|
609
|
+
sessions: stats.agent.sessions,
|
|
610
|
+
prompts: stats.agent.prompts,
|
|
611
|
+
output_tokens: stats.agent.outputTokens,
|
|
612
|
+
input_tokens: stats.agent.inputTokens,
|
|
613
|
+
} : null,
|
|
614
|
+
};
|
|
615
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
616
|
+
} else {
|
|
617
|
+
renderCard(stats, setup, graded);
|
|
618
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uxcontinuum/ccaudit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A diagnostic for your Claude Code setup. Reads ~/.claude/ locally, grades you across hook coverage, project hygiene, tool balance, prompt tells, and pipeline ops. Zero install: npx @uxcontinuum/ccaudit",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|