codebot-ai 1.8.0 → 2.0.0

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
@@ -4,7 +4,7 @@
4
4
  [![license](https://img.shields.io/npm/l/codebot-ai.svg)](https://github.com/zanderone1980/codebot-ai/blob/main/LICENSE)
5
5
  [![node](https://img.shields.io/node/v/codebot-ai.svg)](https://nodejs.org)
6
6
 
7
- **Zero-dependency autonomous AI agent.** Works with any LLM — local or cloud. Code, browse the web, run commands, search, automate routines, and more.
7
+ **Zero-dependency autonomous AI coding agent with enterprise security.** Works with any LLM — local or cloud. Code, browse the web, run commands, search, automate routines, and more. Includes VS Code extension, GitHub Action, policy engine, risk scoring, and hash-chained audit trail.
8
8
 
9
9
  Built by [Ascendral Software Development & Innovation](https://github.com/AscendralSoftware).
10
10
 
@@ -22,6 +22,27 @@ That's it. The setup wizard launches on first run — pick your model, paste an
22
22
  npx codebot-ai
23
23
  ```
24
24
 
25
+ ### VS Code Extension
26
+
27
+ Install from the VS Code Marketplace: search for **CodeBot AI**, or:
28
+
29
+ ```bash
30
+ code --install-extension codebot-ai-vscode-2.0.0.vsix
31
+ ```
32
+
33
+ Features: sidebar chat panel, inline diff preview, status bar (tokens, cost, risk level), and full theme integration.
34
+
35
+ ### GitHub Action
36
+
37
+ ```yaml
38
+ - uses: zanderone1980/codebot-ai/actions/codebot@v2
39
+ with:
40
+ task: review # or: fix, scan
41
+ api-key: ${{ secrets.ANTHROPIC_API_KEY }}
42
+ ```
43
+
44
+ Tasks: `review` (PR code review), `fix` (auto-fix CI failures), `scan` (security scan with SARIF upload).
45
+
25
46
  ## What Can It Do?
26
47
 
27
48
  - **Write & edit code** — reads your codebase, makes targeted edits, runs tests
@@ -101,6 +122,8 @@ echo "explain this error" | codebot # Pipe mode
101
122
  /usage Show token usage for this session
102
123
  /clear Clear conversation
103
124
  /compact Force context compaction
125
+ /metrics Show session metrics (token counts, latency, costs)
126
+ /risk Show risk assessment history
104
127
  /config Show configuration
105
128
  /quit Exit
106
129
  ```
@@ -217,16 +240,31 @@ Connect external tool servers via [Model Context Protocol](https://modelcontextp
217
240
 
218
241
  MCP tools appear automatically with the `mcp_<server>_<tool>` prefix.
219
242
 
243
+ ## Security
244
+
245
+ CodeBot v2.0.0 is built with security as a core architectural principle:
246
+
247
+ - **Policy engine** — declarative JSON policies control tool access, filesystem scope, and execution limits
248
+ - **Risk scoring** — every tool call receives a 0-100 risk score based on 6 weighted factors
249
+ - **Secret detection** — scans for AWS keys, GitHub tokens, JWTs, private keys before writing
250
+ - **Sandbox execution** — Docker-based sandboxing with network, CPU, and memory limits
251
+ - **Audit trail** — hash-chained JSONL log with `--verify-audit` integrity check
252
+ - **SARIF export** — `--export-audit sarif` for GitHub Code Scanning integration
253
+ - **SSRF protection** — blocks localhost, private IPs, cloud metadata endpoints
254
+ - **Path safety** — blocks writes to system directories, detects path traversal
255
+
256
+ See [SECURITY.md](SECURITY.md) and [docs/HARDENING.md](docs/HARDENING.md) for the full security model.
257
+
220
258
  ## Stability
221
259
 
222
- CodeBot v1.3.0 is hardened for continuous operation:
260
+ CodeBot is hardened for continuous operation:
223
261
 
224
262
  - **Automatic retry** — network errors, rate limits (429), and server errors (5xx) retry with exponential backoff
225
263
  - **Stream recovery** — if the LLM connection drops mid-response, the agent loop retries on the next iteration
226
264
  - **Context compaction** — when the conversation exceeds the model's context window, messages are intelligently summarized
227
265
  - **Process resilience** — unhandled exceptions and rejections are caught, logged, and the REPL keeps running
228
266
  - **Routine timeouts** — scheduled tasks are capped at 5 minutes to prevent the scheduler from hanging
229
- - **99 tests** — comprehensive suite covering error recovery, retry logic, tool execution, and edge cases
267
+ - **483 tests** — comprehensive suite covering core agent, security, extension, and action
230
268
 
231
269
  ## Programmatic API
232
270
 
@@ -245,6 +283,7 @@ const agent = new Agent({
245
283
  provider,
246
284
  model: 'claude-sonnet-4-6',
247
285
  autoApprove: true,
286
+ projectRoot: '/path/to/project', // optional, defaults to cwd
248
287
  });
249
288
 
250
289
  for await (const event of agent.run('list all TypeScript files')) {
package/dist/agent.d.ts CHANGED
@@ -2,6 +2,8 @@ import { Message, AgentEvent, LLMProvider } from './types';
2
2
  import { AuditLogger } from './audit';
3
3
  import { PolicyEnforcer } from './policy';
4
4
  import { TokenTracker } from './telemetry';
5
+ import { MetricsCollector } from './metrics';
6
+ import { RiskScorer } from './risk';
5
7
  export declare class Agent {
6
8
  private provider;
7
9
  private tools;
@@ -15,6 +17,9 @@ export declare class Agent {
15
17
  private auditLogger;
16
18
  private policyEnforcer;
17
19
  private tokenTracker;
20
+ private metricsCollector;
21
+ private riskScorer;
22
+ private projectRoot;
18
23
  private branchCreated;
19
24
  private askPermission;
20
25
  private onMessage?;
@@ -24,6 +29,7 @@ export declare class Agent {
24
29
  providerName?: string;
25
30
  maxIterations?: number;
26
31
  autoApprove?: boolean;
32
+ projectRoot?: string;
27
33
  askPermission?: (tool: string, args: Record<string, unknown>) => Promise<boolean>;
28
34
  onMessage?: (message: Message) => void;
29
35
  });
@@ -44,6 +50,10 @@ export declare class Agent {
44
50
  getPolicyEnforcer(): PolicyEnforcer;
45
51
  /** Get the audit logger for verification */
46
52
  getAuditLogger(): AuditLogger;
53
+ /** Get the metrics collector for session metrics */
54
+ getMetrics(): MetricsCollector;
55
+ /** Get the risk scorer for risk assessment history */
56
+ getRiskScorer(): RiskScorer;
47
57
  /**
48
58
  * Validate and repair message history to prevent OpenAI 400 errors.
49
59
  * Handles three types of corruption:
package/dist/agent.js CHANGED
@@ -48,6 +48,8 @@ const rate_limiter_1 = require("./rate-limiter");
48
48
  const audit_1 = require("./audit");
49
49
  const policy_1 = require("./policy");
50
50
  const telemetry_1 = require("./telemetry");
51
+ const metrics_1 = require("./metrics");
52
+ const risk_1 = require("./risk");
51
53
  /** Lightweight schema validation — returns error string or null if valid */
52
54
  function validateToolArgs(args, schema) {
53
55
  const props = schema.properties;
@@ -104,15 +106,19 @@ class Agent {
104
106
  auditLogger;
105
107
  policyEnforcer;
106
108
  tokenTracker;
109
+ metricsCollector;
110
+ riskScorer;
111
+ projectRoot;
107
112
  branchCreated = false;
108
113
  askPermission;
109
114
  onMessage;
110
115
  constructor(opts) {
111
116
  this.provider = opts.provider;
112
117
  this.model = opts.model;
118
+ this.projectRoot = opts.projectRoot || process.cwd();
113
119
  // Load policy FIRST — tools need it for filesystem/git enforcement
114
- this.policyEnforcer = new policy_1.PolicyEnforcer((0, policy_1.loadPolicy)(process.cwd()), process.cwd());
115
- this.tools = new tools_1.ToolRegistry(process.cwd(), this.policyEnforcer);
120
+ this.policyEnforcer = new policy_1.PolicyEnforcer((0, policy_1.loadPolicy)(this.projectRoot), this.projectRoot);
121
+ this.tools = new tools_1.ToolRegistry(this.projectRoot, this.policyEnforcer);
116
122
  this.context = new manager_1.ContextManager(opts.model, opts.provider);
117
123
  // Use policy-defined max iterations as default, CLI overrides
118
124
  this.maxIterations = opts.maxIterations || this.policyEnforcer.getMaxIterations();
@@ -124,12 +130,14 @@ class Agent {
124
130
  this.auditLogger = new audit_1.AuditLogger();
125
131
  // Token & cost tracking
126
132
  this.tokenTracker = new telemetry_1.TokenTracker(opts.model, opts.providerName || 'unknown');
133
+ this.metricsCollector = new metrics_1.MetricsCollector();
134
+ this.riskScorer = new risk_1.RiskScorer();
127
135
  const costLimit = this.policyEnforcer.getCostLimitUsd();
128
136
  if (costLimit > 0)
129
137
  this.tokenTracker.setCostLimit(costLimit);
130
138
  // Load plugins
131
139
  try {
132
- const plugins = (0, plugins_1.loadPlugins)(process.cwd());
140
+ const plugins = (0, plugins_1.loadPlugins)(this.projectRoot);
133
141
  for (const plugin of plugins) {
134
142
  this.tools.register(plugin);
135
143
  }
@@ -196,6 +204,9 @@ class Agent {
196
204
  // Track tokens and cost
197
205
  if (event.usage) {
198
206
  this.tokenTracker.recordUsage(event.usage.inputTokens || 0, event.usage.outputTokens || 0);
207
+ this.metricsCollector.increment('llm_requests_total');
208
+ this.metricsCollector.increment('llm_tokens_total', { direction: 'input' }, event.usage.inputTokens || 0);
209
+ this.metricsCollector.increment('llm_tokens_total', { direction: 'output' }, event.usage.outputTokens || 0);
199
210
  }
200
211
  yield { type: 'usage', usage: event.usage };
201
212
  break;
@@ -282,10 +293,17 @@ class Agent {
282
293
  prepared.push({ tc, tool, args, denied: false, error: `Error: ${validationError} for ${toolName}` });
283
294
  continue;
284
295
  }
285
- yield { type: 'tool_call', toolCall: { name: toolName, args } };
286
- // Permission check: policy override > tool default
296
+ // Compute risk score before execution
287
297
  const policyPermission = this.policyEnforcer.getToolPermission(toolName);
288
298
  const effectivePermission = policyPermission || tool.permission;
299
+ const riskAssessment = this.riskScorer.assess(toolName, args, effectivePermission);
300
+ yield { type: 'tool_call', toolCall: { name: toolName, args }, risk: { score: riskAssessment.score, level: riskAssessment.level } };
301
+ // Log risk breakdown for high-risk calls
302
+ if (riskAssessment.score > 50) {
303
+ const breakdown = riskAssessment.factors.map(f => `${f.name}=${f.rawScore}`).join(', ');
304
+ this.auditLogger.log({ tool: toolName, action: 'execute', args, result: `risk:${riskAssessment.score}`, reason: breakdown });
305
+ }
306
+ // Permission check: policy override > tool default
289
307
  const needsPermission = effectivePermission === 'always-ask' ||
290
308
  (effectivePermission === 'prompt' && !this.autoApprove);
291
309
  let denied = false;
@@ -294,6 +312,7 @@ class Agent {
294
312
  if (!approved) {
295
313
  denied = true;
296
314
  this.auditLogger.log({ tool: toolName, action: 'deny', args, reason: 'User denied permission' });
315
+ this.metricsCollector.increment('permission_denials_total', { tool: toolName });
297
316
  }
298
317
  }
299
318
  prepared.push({ tc, tool, args, denied });
@@ -324,9 +343,10 @@ class Agent {
324
343
  parallelBatch.push(item);
325
344
  }
326
345
  }
327
- // Helper to execute a single tool with cache + rate limiting
346
+ // Helper to execute a single tool with cache + rate limiting + metrics
328
347
  const executeTool = async (prep) => {
329
348
  const toolName = prep.tc.function.name;
349
+ const toolStartTime = Date.now();
330
350
  // Auto-branch on first write/edit when always_branch is enabled (v1.8.0)
331
351
  if (toolName === 'write_file' || toolName === 'edit_file' || toolName === 'batch_edit') {
332
352
  const branchName = await this.ensureBranch();
@@ -338,6 +358,7 @@ class Agent {
338
358
  const capBlock = this.checkToolCapabilities(toolName, prep.args);
339
359
  if (capBlock) {
340
360
  this.auditLogger.log({ tool: toolName, action: 'capability_block', args: prep.args, reason: capBlock });
361
+ this.metricsCollector.increment('security_blocks_total', { tool: toolName, type: 'capability' });
341
362
  return { content: `Error: ${capBlock}`, is_error: true };
342
363
  }
343
364
  // Check cache first
@@ -345,19 +366,30 @@ class Agent {
345
366
  const cacheKey = cache_1.ToolCache.key(toolName, prep.args);
346
367
  const cached = this.cache.get(cacheKey);
347
368
  if (cached !== null) {
369
+ this.metricsCollector.increment('cache_hits_total', { tool: toolName });
348
370
  return { content: cached };
349
371
  }
372
+ this.metricsCollector.increment('cache_misses_total', { tool: toolName });
350
373
  }
351
374
  // Rate limit
352
375
  await this.rateLimiter.throttle(toolName);
353
376
  try {
354
377
  const output = await prep.tool.execute(prep.args);
378
+ // Record tool latency
379
+ const latencyMs = Date.now() - toolStartTime;
380
+ this.metricsCollector.observe('tool_latency_seconds', latencyMs / 1000, { tool: toolName });
381
+ this.metricsCollector.increment('tool_calls_total', { tool: toolName });
355
382
  // Audit log: successful execution
356
383
  this.auditLogger.log({ tool: toolName, action: 'execute', args: prep.args, result: 'success' });
357
384
  // Telemetry: track tool calls and file modifications
358
385
  this.tokenTracker.recordToolCall();
359
386
  if ((toolName === 'write_file' || toolName === 'edit_file' || toolName === 'batch_edit') && prep.args.path) {
360
387
  this.tokenTracker.recordFileModified(prep.args.path);
388
+ this.metricsCollector.increment('files_written_total', { tool: toolName });
389
+ }
390
+ // Track commands executed
391
+ if (toolName === 'execute') {
392
+ this.metricsCollector.increment('commands_executed_total');
361
393
  }
362
394
  // Store in cache for cacheable tools
363
395
  if (prep.tool.cacheable) {
@@ -373,11 +405,16 @@ class Agent {
373
405
  // Audit log: check if tool returned a security block
374
406
  if (output.startsWith('Error: Blocked:') || output.startsWith('Error: CWD')) {
375
407
  this.auditLogger.log({ tool: toolName, action: 'security_block', args: prep.args, reason: output });
408
+ this.metricsCollector.increment('security_blocks_total', { tool: toolName, type: 'security' });
376
409
  }
377
410
  return { content: output };
378
411
  }
379
412
  catch (err) {
380
413
  const errMsg = err instanceof Error ? err.message : String(err);
414
+ // Record latency even on error
415
+ const latencyMs = Date.now() - toolStartTime;
416
+ this.metricsCollector.observe('tool_latency_seconds', latencyMs / 1000, { tool: toolName });
417
+ this.metricsCollector.increment('errors_total', { tool: toolName });
381
418
  // Audit log: error
382
419
  this.auditLogger.log({ tool: toolName, action: 'error', args: prep.args, result: 'error', reason: errMsg });
383
420
  return { content: `Error: ${errMsg}`, is_error: true };
@@ -448,6 +485,14 @@ class Agent {
448
485
  getAuditLogger() {
449
486
  return this.auditLogger;
450
487
  }
488
+ /** Get the metrics collector for session metrics */
489
+ getMetrics() {
490
+ return this.metricsCollector;
491
+ }
492
+ /** Get the risk scorer for risk assessment history */
493
+ getRiskScorer() {
494
+ return this.riskScorer;
495
+ }
451
496
  /**
452
497
  * Validate and repair message history to prevent OpenAI 400 errors.
453
498
  * Handles three types of corruption:
@@ -526,9 +571,8 @@ class Agent {
526
571
  return null;
527
572
  try {
528
573
  const { execSync } = require('child_process');
529
- const cwd = process.cwd();
530
574
  const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
531
- cwd, encoding: 'utf-8', timeout: 5000,
575
+ cwd: this.projectRoot, encoding: 'utf-8', timeout: 5000,
532
576
  }).trim();
533
577
  if (currentBranch !== 'main' && currentBranch !== 'master') {
534
578
  this.branchCreated = true;
@@ -541,7 +585,7 @@ class Agent {
541
585
  const slug = this.sanitizeSlug(firstUserMsg?.content || 'task');
542
586
  const branchName = `${prefix}${timestamp}-${slug}`;
543
587
  execSync(`git checkout -b "${branchName}"`, {
544
- cwd, encoding: 'utf-8', timeout: 10000,
588
+ cwd: this.projectRoot, encoding: 'utf-8', timeout: 10000,
545
589
  });
546
590
  this.branchCreated = true;
547
591
  return branchName;
@@ -590,7 +634,7 @@ class Agent {
590
634
  buildSystemPrompt(supportsTools) {
591
635
  let repoMap = '';
592
636
  try {
593
- repoMap = (0, repo_map_1.buildRepoMap)(process.cwd());
637
+ repoMap = (0, repo_map_1.buildRepoMap)(this.projectRoot);
594
638
  }
595
639
  catch {
596
640
  repoMap = 'Project structure: (unable to scan)';
@@ -598,7 +642,7 @@ class Agent {
598
642
  // Load persistent memory
599
643
  let memoryBlock = '';
600
644
  try {
601
- const memory = new memory_1.MemoryManager(process.cwd());
645
+ const memory = new memory_1.MemoryManager(this.projectRoot);
602
646
  memoryBlock = memory.getContextBlock();
603
647
  }
604
648
  catch {
package/dist/cli.js CHANGED
@@ -50,7 +50,9 @@ const audit_1 = require("./audit");
50
50
  const policy_1 = require("./policy");
51
51
  const sandbox_1 = require("./sandbox");
52
52
  const replay_1 = require("./replay");
53
- const VERSION = '1.8.0';
53
+ const risk_1 = require("./risk");
54
+ const sarif_1 = require("./sarif");
55
+ const VERSION = '2.0.0';
54
56
  const C = {
55
57
  reset: '\x1b[0m',
56
58
  bold: '\x1b[1m',
@@ -228,6 +230,19 @@ async function main() {
228
230
  }
229
231
  return;
230
232
  }
233
+ // --export-audit sarif: Export audit log as SARIF 2.1.0
234
+ if (args['export-audit'] === 'sarif' || args['export-audit'] === true) {
235
+ const logger = new audit_1.AuditLogger();
236
+ const sessionId = typeof args['session'] === 'string' ? args['session'] : undefined;
237
+ const entries = sessionId ? logger.query({ sessionId }) : logger.query();
238
+ if (entries.length === 0) {
239
+ console.error(c('No audit entries found.', 'yellow'));
240
+ process.exit(1);
241
+ }
242
+ const sarif = (0, sarif_1.exportSarif)(entries, { version: VERSION, sessionId });
243
+ process.stdout.write((0, sarif_1.sarifToString)(sarif) + '\n');
244
+ return;
245
+ }
231
246
  // First run: auto-launch setup if nothing is configured
232
247
  if ((0, setup_1.isFirstRun)() && process.stdin.isTTY && !args.message) {
233
248
  console.log(c('Welcome! No configuration found — launching setup...', 'cyan'));
@@ -299,7 +314,7 @@ async function main() {
299
314
  // Cleanup scheduler on exit
300
315
  scheduler.stop();
301
316
  }
302
- /** Print session summary with tokens, cost, tool calls, files modified */
317
+ /** Print session summary with tokens, cost, tool calls, files modified, metrics */
303
318
  function printSessionSummary(agent) {
304
319
  const tracker = agent.getTokenTracker();
305
320
  tracker.saveUsage();
@@ -314,6 +329,27 @@ function printSessionSummary(agent) {
314
329
  console.log(` Requests: ${summary.requestCount}`);
315
330
  console.log(` Tools: ${summary.toolCalls} calls`);
316
331
  console.log(` Files: ${summary.filesModified} modified`);
332
+ // v1.9.0: Per-tool breakdown from MetricsCollector
333
+ const metrics = agent.getMetrics();
334
+ const snap = metrics.snapshot();
335
+ const toolCounters = snap.counters.filter(c => c.name === 'tool_calls_total');
336
+ if (toolCounters.length > 0) {
337
+ console.log(c(' Per-tool:', 'dim'));
338
+ for (const tc of toolCounters.sort((a, b) => b.value - a.value)) {
339
+ const hist = snap.histograms.find(h => h.name === 'tool_latency_seconds' && h.labels.tool === tc.labels.tool);
340
+ const avg = hist && hist.count > 0 ? (hist.sum / hist.count * 1000).toFixed(0) : '?';
341
+ console.log(c(` ${tc.labels.tool}: ${tc.value} calls (avg ${avg}ms)`, 'dim'));
342
+ }
343
+ }
344
+ // Risk summary
345
+ const riskScorer = agent.getRiskScorer();
346
+ const riskAvg = riskScorer.getSessionAverage();
347
+ if (riskScorer.getHistory().length > 0) {
348
+ console.log(` Risk: avg ${riskAvg}/100`);
349
+ }
350
+ // Save metrics
351
+ metrics.save();
352
+ metrics.exportOtel();
317
353
  }
318
354
  function createProvider(config) {
319
355
  if (config.provider === 'anthropic') {
@@ -393,8 +429,14 @@ function renderEvent(event, agent) {
393
429
  process.stdout.write('\n');
394
430
  isThinking = false;
395
431
  }
396
- console.log(c(`\n⚡ ${event.toolCall?.name}`, 'yellow') +
397
- c(`(${formatArgs(event.toolCall?.args)})`, 'dim'));
432
+ {
433
+ const riskStr = event.risk
434
+ ? ' ' + risk_1.RiskScorer.formatIndicator({ score: event.risk.score, level: event.risk.level, factors: [] })
435
+ : '';
436
+ console.log(c(`\n⚡ ${event.toolCall?.name}`, 'yellow') +
437
+ c(`(${formatArgs(event.toolCall?.args)})`, 'dim') +
438
+ riskStr);
439
+ }
398
440
  break;
399
441
  case 'tool_result':
400
442
  if (event.toolResult?.is_error) {
@@ -467,6 +509,8 @@ function handleSlashCommand(input, agent, config) {
467
509
  /undo Undo last file edit (/undo [path])
468
510
  /usage Show token usage & cost for this session
469
511
  /cost Show running cost
512
+ /metrics Show session metrics (counters + histograms)
513
+ /risk Show risk assessment summary
470
514
  /policy Show current security policy
471
515
  /audit Verify audit chain for this session
472
516
  /config Show current config
@@ -543,6 +587,26 @@ function handleSlashCommand(input, agent, config) {
543
587
  console.log(c(` ${tracker.formatStatusLine()}`, 'dim'));
544
588
  break;
545
589
  }
590
+ case '/metrics': {
591
+ const metricsOutput = agent.getMetrics().formatSummary();
592
+ console.log('\n' + metricsOutput);
593
+ break;
594
+ }
595
+ case '/risk': {
596
+ const riskHistory = agent.getRiskScorer().getHistory();
597
+ if (riskHistory.length === 0) {
598
+ console.log(c('No risk assessments yet.', 'dim'));
599
+ }
600
+ else {
601
+ const avg = agent.getRiskScorer().getSessionAverage();
602
+ console.log(c(`\nRisk Summary: ${riskHistory.length} assessments, avg ${avg}/100`, 'bold'));
603
+ const last5 = riskHistory.slice(-5);
604
+ for (const a of last5) {
605
+ console.log(` ${risk_1.RiskScorer.formatIndicator(a)}`);
606
+ }
607
+ }
608
+ break;
609
+ }
546
610
  case '/policy': {
547
611
  const policy = agent.getPolicyEnforcer().getPolicy();
548
612
  console.log(c('\nCurrent Policy:', 'bold'));
@@ -702,6 +766,17 @@ function parseArgs(argv) {
702
766
  }
703
767
  continue;
704
768
  }
769
+ if (arg === '--export-audit') {
770
+ const next = argv[i + 1];
771
+ if (next && !next.startsWith('--')) {
772
+ result['export-audit'] = next;
773
+ i++;
774
+ }
775
+ else {
776
+ result['export-audit'] = true;
777
+ }
778
+ continue;
779
+ }
705
780
  if (arg === '--replay') {
706
781
  const next = argv[i + 1];
707
782
  if (next && !next.startsWith('--')) {
@@ -770,6 +845,7 @@ ${c('Options:', 'bold')}
770
845
  ${c('Security & Policy:', 'bold')}
771
846
  --init-policy Generate default .codebot/policy.json
772
847
  --verify-audit [id] Verify audit log hash chain integrity
848
+ --export-audit sarif Export audit log as SARIF 2.1.0 JSON
773
849
  --sandbox-info Show Docker sandbox status
774
850
 
775
851
  ${c('Debugging & Replay:', 'bold')}
@@ -795,6 +871,7 @@ ${c('Examples:', 'bold')}
795
871
  codebot --autonomous "refactor src/" Full auto, no prompts
796
872
  codebot --init-policy Create security policy
797
873
  codebot --verify-audit Check audit integrity
874
+ codebot --export-audit sarif > r.sarif Export SARIF report
798
875
 
799
876
  ${c('Interactive Commands:', 'bold')}
800
877
  /help Show commands
@@ -806,6 +883,8 @@ ${c('Interactive Commands:', 'bold')}
806
883
  /compact Force context compaction
807
884
  /usage Show token usage & cost
808
885
  /cost Show running cost
886
+ /metrics Show session metrics
887
+ /risk Show risk assessment summary
809
888
  /policy Show security policy
810
889
  /audit Verify session audit chain
811
890
  /config Show configuration
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export declare const VERSION = "2.0.0";
1
2
  export { Agent } from './agent';
2
3
  export { OpenAIProvider } from './providers/openai';
3
4
  export { AnthropicProvider } from './providers/anthropic';
@@ -17,5 +18,11 @@ export { deriveSessionKey, signMessage, verifyMessage, verifyMessages } from './
17
18
  export type { IntegrityResult } from './integrity';
18
19
  export { ReplayProvider, loadSessionForReplay, compareOutputs, listReplayableSessions } from './replay';
19
20
  export type { SessionReplayData, ReplayDivergence } from './replay';
21
+ export { MetricsCollector } from './metrics';
22
+ export type { MetricsSnapshot, CounterValue, HistogramValue } from './metrics';
23
+ export { RiskScorer } from './risk';
24
+ export type { RiskAssessment, RiskFactor } from './risk';
25
+ export { exportSarif, sarifToString } from './sarif';
26
+ export type { SarifLog, SarifResult, SarifRule } from './sarif';
20
27
  export * from './types';
21
28
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -14,7 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.listReplayableSessions = exports.compareOutputs = exports.loadSessionForReplay = exports.ReplayProvider = exports.verifyMessages = exports.verifyMessage = exports.signMessage = exports.deriveSessionKey = exports.CapabilityChecker = exports.detectProvider = exports.getModelInfo = exports.PROVIDER_DEFAULTS = exports.MODEL_REGISTRY = exports.loadMCPTools = exports.loadPlugins = exports.parseToolCalls = exports.MemoryManager = exports.SessionManager = exports.buildRepoMap = exports.ContextManager = exports.ToolRegistry = exports.AnthropicProvider = exports.OpenAIProvider = exports.Agent = void 0;
17
+ exports.sarifToString = exports.exportSarif = exports.RiskScorer = exports.MetricsCollector = exports.listReplayableSessions = exports.compareOutputs = exports.loadSessionForReplay = exports.ReplayProvider = exports.verifyMessages = exports.verifyMessage = exports.signMessage = exports.deriveSessionKey = exports.CapabilityChecker = exports.detectProvider = exports.getModelInfo = exports.PROVIDER_DEFAULTS = exports.MODEL_REGISTRY = exports.loadMCPTools = exports.loadPlugins = exports.parseToolCalls = exports.MemoryManager = exports.SessionManager = exports.buildRepoMap = exports.ContextManager = exports.ToolRegistry = exports.AnthropicProvider = exports.OpenAIProvider = exports.Agent = exports.VERSION = void 0;
18
+ exports.VERSION = '2.0.0';
18
19
  var agent_1 = require("./agent");
19
20
  Object.defineProperty(exports, "Agent", { enumerable: true, get: function () { return agent_1.Agent; } });
20
21
  var openai_1 = require("./providers/openai");
@@ -54,5 +55,12 @@ Object.defineProperty(exports, "ReplayProvider", { enumerable: true, get: functi
54
55
  Object.defineProperty(exports, "loadSessionForReplay", { enumerable: true, get: function () { return replay_1.loadSessionForReplay; } });
55
56
  Object.defineProperty(exports, "compareOutputs", { enumerable: true, get: function () { return replay_1.compareOutputs; } });
56
57
  Object.defineProperty(exports, "listReplayableSessions", { enumerable: true, get: function () { return replay_1.listReplayableSessions; } });
58
+ var metrics_1 = require("./metrics");
59
+ Object.defineProperty(exports, "MetricsCollector", { enumerable: true, get: function () { return metrics_1.MetricsCollector; } });
60
+ var risk_1 = require("./risk");
61
+ Object.defineProperty(exports, "RiskScorer", { enumerable: true, get: function () { return risk_1.RiskScorer; } });
62
+ var sarif_1 = require("./sarif");
63
+ Object.defineProperty(exports, "exportSarif", { enumerable: true, get: function () { return sarif_1.exportSarif; } });
64
+ Object.defineProperty(exports, "sarifToString", { enumerable: true, get: function () { return sarif_1.sarifToString; } });
57
65
  __exportStar(require("./types"), exports);
58
66
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,60 @@
1
+ /**
2
+ * MetricsCollector for CodeBot v1.9.0
3
+ *
4
+ * Structured telemetry: counters + histograms.
5
+ * Persists to ~/.codebot/telemetry/metrics-YYYY-MM-DD.jsonl
6
+ * Optional OTLP HTTP export when OTEL_EXPORTER_OTLP_ENDPOINT is set.
7
+ *
8
+ * Pattern: fail-safe, session-scoped, never throws.
9
+ * Follows TokenTracker conventions from src/telemetry.ts.
10
+ */
11
+ export interface CounterValue {
12
+ name: string;
13
+ labels: Record<string, string>;
14
+ value: number;
15
+ }
16
+ export interface HistogramValue {
17
+ name: string;
18
+ labels: Record<string, string>;
19
+ count: number;
20
+ sum: number;
21
+ min: number;
22
+ max: number;
23
+ buckets: number[];
24
+ }
25
+ export interface MetricsSnapshot {
26
+ sessionId: string;
27
+ timestamp: string;
28
+ counters: CounterValue[];
29
+ histograms: HistogramValue[];
30
+ }
31
+ export declare class MetricsCollector {
32
+ private sessionId;
33
+ private counters;
34
+ private histograms;
35
+ constructor(sessionId?: string);
36
+ getSessionId(): string;
37
+ /** Increment a counter by delta (default 1) */
38
+ increment(name: string, labels?: Record<string, string>, delta?: number): void;
39
+ /** Record a histogram observation */
40
+ observe(name: string, value: number, labels?: Record<string, string>): void;
41
+ /** Read a counter value */
42
+ getCounter(name: string, labels?: Record<string, string>): number;
43
+ /** Read a histogram summary */
44
+ getHistogram(name: string, labels?: Record<string, string>): HistogramValue | null;
45
+ /** Full session snapshot */
46
+ snapshot(): MetricsSnapshot;
47
+ /** Persist snapshot to ~/.codebot/telemetry/metrics-YYYY-MM-DD.jsonl */
48
+ save(sessionId?: string): void;
49
+ /** Human-readable per-tool breakdown */
50
+ formatSummary(): string;
51
+ /**
52
+ * Export snapshot in OTLP JSON format via HTTP POST.
53
+ * Only fires when OTEL_EXPORTER_OTLP_ENDPOINT is set.
54
+ * Fails silently — never blocks or crashes.
55
+ */
56
+ exportOtel(snap?: MetricsSnapshot): void;
57
+ /** Build OTLP-compatible JSON payload */
58
+ private buildOtlpPayload;
59
+ }
60
+ //# sourceMappingURL=metrics.d.ts.map