@arcbridge/core 0.1.1 → 0.1.2

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.d.ts CHANGED
@@ -78,6 +78,13 @@ declare const ArcBridgeConfigSchema: z.ZodObject<{
78
78
  }, {
79
79
  ignore_paths?: string[] | undefined;
80
80
  }>>;
81
+ metrics: z.ZodDefault<z.ZodObject<{
82
+ auto_record: z.ZodDefault<z.ZodBoolean>;
83
+ }, "strip", z.ZodTypeAny, {
84
+ auto_record: boolean;
85
+ }, {
86
+ auto_record?: boolean | undefined;
87
+ }>>;
81
88
  sync: z.ZodDefault<z.ZodObject<{
82
89
  auto_detect_drift: z.ZodDefault<z.ZodBoolean>;
83
90
  drift_severity_threshold: z.ZodDefault<z.ZodEnum<["info", "warning", "error"]>>;
@@ -117,6 +124,9 @@ declare const ArcBridgeConfigSchema: z.ZodObject<{
117
124
  drift: {
118
125
  ignore_paths: string[];
119
126
  };
127
+ metrics: {
128
+ auto_record: boolean;
129
+ };
120
130
  sync: {
121
131
  auto_detect_drift: boolean;
122
132
  drift_severity_threshold: "info" | "warning" | "error";
@@ -148,6 +158,9 @@ declare const ArcBridgeConfigSchema: z.ZodObject<{
148
158
  drift?: {
149
159
  ignore_paths?: string[] | undefined;
150
160
  } | undefined;
161
+ metrics?: {
162
+ auto_record?: boolean | undefined;
163
+ } | undefined;
151
164
  sync?: {
152
165
  auto_detect_drift?: boolean | undefined;
153
166
  drift_severity_threshold?: "info" | "warning" | "error" | undefined;
@@ -710,7 +723,7 @@ type AgentRole = z.infer<typeof AgentRoleSchema>;
710
723
  declare function openDatabase(dbPath: string): Database.Database;
711
724
  declare function openMemoryDatabase(): Database.Database;
712
725
 
713
- declare const CURRENT_SCHEMA_VERSION = 1;
726
+ declare const CURRENT_SCHEMA_VERSION = 2;
714
727
  declare function initializeSchema(db: Database.Database): void;
715
728
 
716
729
  declare function migrate(db: Database.Database): void;
@@ -894,6 +907,102 @@ declare function syncScenarioToYaml(projectRoot: string, scenarioId: string, sta
894
907
  */
895
908
  declare function generateSyncFiles(targetDir: string, config: ArcBridgeConfig): string[];
896
909
 
910
+ interface InsertActivityParams {
911
+ toolName: string;
912
+ action?: string;
913
+ model?: string;
914
+ agentRole?: string;
915
+ taskId?: string;
916
+ phaseId?: string;
917
+ inputTokens?: number;
918
+ outputTokens?: number;
919
+ totalTokens?: number;
920
+ costUsd?: number;
921
+ durationMs?: number;
922
+ driftCount?: number;
923
+ driftErrors?: number;
924
+ testPassCount?: number;
925
+ testFailCount?: number;
926
+ lintClean?: boolean;
927
+ typecheckClean?: boolean;
928
+ notes?: string;
929
+ metadata?: Record<string, unknown>;
930
+ }
931
+ interface QueryMetricsParams {
932
+ taskId?: string;
933
+ phaseId?: string;
934
+ model?: string;
935
+ agentRole?: string;
936
+ toolName?: string;
937
+ since?: string;
938
+ until?: string;
939
+ groupBy: "model" | "task" | "phase" | "tool" | "day" | "none";
940
+ limit: number;
941
+ }
942
+ interface ActivityRow {
943
+ id: number;
944
+ tool_name: string;
945
+ action: string | null;
946
+ model: string | null;
947
+ agent_role: string | null;
948
+ task_id: string | null;
949
+ phase_id: string | null;
950
+ input_tokens: number | null;
951
+ output_tokens: number | null;
952
+ total_tokens: number | null;
953
+ cost_usd: number | null;
954
+ duration_ms: number | null;
955
+ drift_count: number | null;
956
+ drift_errors: number | null;
957
+ test_pass_count: number | null;
958
+ test_fail_count: number | null;
959
+ lint_clean: number | null;
960
+ typecheck_clean: number | null;
961
+ notes: string | null;
962
+ metadata: string;
963
+ recorded_at: string;
964
+ }
965
+ interface AggregatedRow {
966
+ groupKey: string;
967
+ activityCount: number;
968
+ sumTokens: number | null;
969
+ avgTokens: number | null;
970
+ sumCost: number | null;
971
+ avgDuration: number | null;
972
+ firstActivity: string;
973
+ lastActivity: string;
974
+ }
975
+ interface LatestQualitySnapshot {
976
+ driftCount: number | null;
977
+ driftErrors: number | null;
978
+ testPassCount: number | null;
979
+ testFailCount: number | null;
980
+ lintClean: boolean | null;
981
+ typecheckClean: boolean | null;
982
+ capturedAt: string | null;
983
+ }
984
+ interface SessionTotals {
985
+ totalCost: number;
986
+ totalTokens: number;
987
+ activityCount: number;
988
+ }
989
+ interface MetricsResult {
990
+ rows: ActivityRow[] | AggregatedRow[];
991
+ grouped: boolean;
992
+ qualitySnapshot: LatestQualitySnapshot;
993
+ totals: SessionTotals;
994
+ timeSpan: {
995
+ first: string;
996
+ last: string;
997
+ } | null;
998
+ }
999
+ type ExportFormat = "json" | "csv" | "markdown";
1000
+
1001
+ declare function insertActivity(db: Database.Database, params: InsertActivityParams): number;
1002
+ declare function getSessionTotals(db: Database.Database, since?: string, model?: string): SessionTotals;
1003
+ declare function queryMetrics(db: Database.Database, params: QueryMetricsParams): MetricsResult;
1004
+ declare function exportMetrics(db: Database.Database, projectRoot: string, format: ExportFormat, params: Omit<QueryMetricsParams, "groupBy" | "limit">, maxRows?: number): string;
1005
+
897
1006
  interface ChangedFile {
898
1007
  status: "added" | "modified" | "deleted" | "renamed";
899
1008
  path: string;
@@ -988,4 +1097,4 @@ declare function loadConfig(projectRoot: string): {
988
1097
  error: string | null;
989
1098
  };
990
1099
 
991
- export { type AdrFrontmatter, AdrFrontmatterSchema, type AgentRole, AgentRoleSchema, type ArcBridgeConfig, ArcBridgeConfigSchema, type BuildingBlock, BuildingBlockSchema, type BuildingBlocksFrontmatter, BuildingBlocksFrontmatterSchema, CURRENT_SCHEMA_VERSION, type ChangedFile, type DotnetProjectInfo, type DriftEntry, type DriftKind, type DriftOptions, type DriftSeverity, type ExtractedSymbol, type GenerateDatabaseResult, type GitRef, type IndexResult, type IndexerOptions, type InitProjectInput, type LoadRolesResult, type Phase, PhaseSchema, type PhasesFile, PhasesFileSchema, type ProjectLanguage, QualityCategorySchema, QualityPrioritySchema, type QualityScenario, QualityScenarioSchema, QualityScenarioStatusSchema, type QualityScenariosFile, QualityScenariosFileSchema, type ScenarioTestResult, type Service, type SymbolKind, type Task, type TaskFile, TaskFileSchema, type TaskInferenceResult, TaskSchema, type TestOutcome, type VerifyResult, addTaskToYaml, applyInferences, detectDrift, detectProjectLanguage, discoverDotnetServices, generateAgentRoles, generateArc42, generateConfig, generateDatabase, generatePlan, generateSyncFiles, getChangedFiles, getHeadSha, getUncommittedChanges, indexPackageDependencies, indexProject, inferTaskStatuses, initializeSchema, loadConfig, loadRole, loadRoles, migrate, openDatabase, openMemoryDatabase, refreshFromDocs, resolveRef, setSyncCommit, syncPhaseToYaml, syncScenarioToYaml, syncTaskToYaml, verifyScenarios, writeDriftLog };
1100
+ export { type ActivityRow, type AdrFrontmatter, AdrFrontmatterSchema, type AgentRole, AgentRoleSchema, type AggregatedRow, type ArcBridgeConfig, ArcBridgeConfigSchema, type BuildingBlock, BuildingBlockSchema, type BuildingBlocksFrontmatter, BuildingBlocksFrontmatterSchema, CURRENT_SCHEMA_VERSION, type ChangedFile, type DotnetProjectInfo, type DriftEntry, type DriftKind, type DriftOptions, type DriftSeverity, type ExportFormat, type ExtractedSymbol, type GenerateDatabaseResult, type GitRef, type IndexResult, type IndexerOptions, type InitProjectInput, type InsertActivityParams, type LatestQualitySnapshot, type LoadRolesResult, type MetricsResult, type Phase, PhaseSchema, type PhasesFile, PhasesFileSchema, type ProjectLanguage, QualityCategorySchema, QualityPrioritySchema, type QualityScenario, QualityScenarioSchema, QualityScenarioStatusSchema, type QualityScenariosFile, QualityScenariosFileSchema, type QueryMetricsParams, type ScenarioTestResult, type Service, type SessionTotals, type SymbolKind, type Task, type TaskFile, TaskFileSchema, type TaskInferenceResult, TaskSchema, type TestOutcome, type VerifyResult, addTaskToYaml, applyInferences, detectDrift, detectProjectLanguage, discoverDotnetServices, exportMetrics, generateAgentRoles, generateArc42, generateConfig, generateDatabase, generatePlan, generateSyncFiles, getChangedFiles, getHeadSha, getSessionTotals, getUncommittedChanges, indexPackageDependencies, indexProject, inferTaskStatuses, initializeSchema, insertActivity, loadConfig, loadRole, loadRoles, migrate, openDatabase, openMemoryDatabase, queryMetrics, refreshFromDocs, resolveRef, setSyncCommit, syncPhaseToYaml, syncScenarioToYaml, syncTaskToYaml, verifyScenarios, writeDriftLog };
package/dist/index.js CHANGED
@@ -44,6 +44,11 @@ var ArcBridgeConfigSchema = z.object({
44
44
  "File paths or prefixes to ignore in undocumented_module drift checks. Framework files (e.g. next.config.ts, root layout/page) are auto-ignored for known project types."
45
45
  )
46
46
  }).default({}),
47
+ metrics: z.object({
48
+ auto_record: z.boolean().default(false).describe(
49
+ "Automatically record agent activity (tool name, duration, quality snapshot) when key MCP tools are invoked. Token/model info is optional and caller-provided."
50
+ )
51
+ }).default({}),
47
52
  sync: z.object({
48
53
  auto_detect_drift: z.boolean().default(true),
49
54
  drift_severity_threshold: z.enum(["info", "warning", "error"]).default("warning"),
@@ -217,7 +222,7 @@ function openMemoryDatabase() {
217
222
  }
218
223
 
219
224
  // src/db/schema.ts
220
- var CURRENT_SCHEMA_VERSION = 1;
225
+ var CURRENT_SCHEMA_VERSION = 2;
221
226
  var SCHEMA_SQL = `
222
227
  -- Metadata
223
228
  CREATE TABLE IF NOT EXISTS arcbridge_meta (
@@ -383,6 +388,36 @@ CREATE TABLE IF NOT EXISTS drift_log (
383
388
  resolution TEXT CHECK(resolution IN ('accepted','fixed','deferred') OR resolution IS NULL),
384
389
  resolved_at TEXT
385
390
  );
391
+
392
+ -- Agent Activity Metrics (operational telemetry, not rebuilt by refreshFromDocs)
393
+ CREATE TABLE IF NOT EXISTS agent_activity (
394
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
395
+ tool_name TEXT NOT NULL,
396
+ action TEXT,
397
+ model TEXT,
398
+ agent_role TEXT,
399
+ task_id TEXT,
400
+ phase_id TEXT,
401
+ input_tokens INTEGER,
402
+ output_tokens INTEGER,
403
+ total_tokens INTEGER,
404
+ cost_usd REAL,
405
+ duration_ms INTEGER,
406
+ drift_count INTEGER,
407
+ drift_errors INTEGER,
408
+ test_pass_count INTEGER,
409
+ test_fail_count INTEGER,
410
+ lint_clean INTEGER,
411
+ typecheck_clean INTEGER,
412
+ notes TEXT,
413
+ metadata TEXT NOT NULL DEFAULT '{}',
414
+ recorded_at TEXT NOT NULL
415
+ );
416
+
417
+ CREATE INDEX IF NOT EXISTS idx_activity_recorded_at ON agent_activity(recorded_at);
418
+ CREATE INDEX IF NOT EXISTS idx_activity_model ON agent_activity(model);
419
+ CREATE INDEX IF NOT EXISTS idx_activity_task ON agent_activity(task_id);
420
+ CREATE INDEX IF NOT EXISTS idx_activity_phase ON agent_activity(phase_id);
386
421
  `;
387
422
  function initializeSchema(db) {
388
423
  db.exec(SCHEMA_SQL);
@@ -398,7 +433,42 @@ function initializeSchema(db) {
398
433
  }
399
434
 
400
435
  // src/db/migrations.ts
401
- var migrations = [];
436
+ var migrations = [
437
+ {
438
+ version: 2,
439
+ up: (db) => {
440
+ db.exec(`
441
+ CREATE TABLE IF NOT EXISTS agent_activity (
442
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
443
+ tool_name TEXT NOT NULL,
444
+ action TEXT,
445
+ model TEXT,
446
+ agent_role TEXT,
447
+ task_id TEXT,
448
+ phase_id TEXT,
449
+ input_tokens INTEGER,
450
+ output_tokens INTEGER,
451
+ total_tokens INTEGER,
452
+ cost_usd REAL,
453
+ duration_ms INTEGER,
454
+ drift_count INTEGER,
455
+ drift_errors INTEGER,
456
+ test_pass_count INTEGER,
457
+ test_fail_count INTEGER,
458
+ lint_clean INTEGER,
459
+ typecheck_clean INTEGER,
460
+ notes TEXT,
461
+ metadata TEXT NOT NULL DEFAULT '{}',
462
+ recorded_at TEXT NOT NULL
463
+ );
464
+ CREATE INDEX IF NOT EXISTS idx_activity_recorded_at ON agent_activity(recorded_at);
465
+ CREATE INDEX IF NOT EXISTS idx_activity_model ON agent_activity(model);
466
+ CREATE INDEX IF NOT EXISTS idx_activity_task ON agent_activity(task_id);
467
+ CREATE INDEX IF NOT EXISTS idx_activity_phase ON agent_activity(phase_id);
468
+ `);
469
+ }
470
+ }
471
+ ];
402
472
  function migrate(db) {
403
473
  const row = db.prepare("SELECT value FROM arcbridge_meta WHERE key = 'schema_version'").get();
404
474
  const currentVersion = row ? Number(row.value) : 0;
@@ -450,6 +520,7 @@ function configTemplate(input) {
450
520
  drift: {
451
521
  ignore_paths: []
452
522
  },
523
+ metrics: { auto_record: false },
453
524
  sync: {
454
525
  auto_detect_drift: true,
455
526
  drift_severity_threshold: "warning",
@@ -487,6 +558,7 @@ function configTemplate2(input) {
487
558
  drift: {
488
559
  ignore_paths: []
489
560
  },
561
+ metrics: { auto_record: false },
490
562
  sync: {
491
563
  auto_detect_drift: true,
492
564
  drift_severity_threshold: "warning",
@@ -524,6 +596,7 @@ function configTemplate3(input) {
524
596
  drift: {
525
597
  ignore_paths: []
526
598
  },
599
+ metrics: { auto_record: false },
527
600
  sync: {
528
601
  auto_detect_drift: true,
529
602
  drift_severity_threshold: "warning",
@@ -559,6 +632,7 @@ function configTemplate4(input) {
559
632
  drift: {
560
633
  ignore_paths: []
561
634
  },
635
+ metrics: { auto_record: false },
562
636
  sync: {
563
637
  auto_detect_drift: true,
564
638
  drift_severity_threshold: "warning",
@@ -3204,6 +3278,8 @@ function refreshFromDocs(db, targetDir) {
3204
3278
  const refresh = db.transaction(() => {
3205
3279
  db.prepare("DELETE FROM tasks").run();
3206
3280
  db.prepare("DELETE FROM phases").run();
3281
+ db.prepare("UPDATE contracts SET building_block = NULL").run();
3282
+ db.prepare("UPDATE building_blocks SET parent_id = NULL").run();
3207
3283
  db.prepare("DELETE FROM building_blocks").run();
3208
3284
  db.prepare("DELETE FROM quality_scenarios").run();
3209
3285
  db.prepare("DELETE FROM adrs").run();
@@ -6373,6 +6449,329 @@ function generateSyncFiles(targetDir, config) {
6373
6449
  return generated;
6374
6450
  }
6375
6451
 
6452
+ // src/metrics/activity.ts
6453
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
6454
+ import { join as join16 } from "path";
6455
+ function insertActivity(db, params) {
6456
+ const totalTokens = params.totalTokens ?? (params.inputTokens != null && params.outputTokens != null ? params.inputTokens + params.outputTokens : null);
6457
+ const stmt = db.prepare(`
6458
+ INSERT INTO agent_activity (
6459
+ tool_name, action, model, agent_role,
6460
+ task_id, phase_id,
6461
+ input_tokens, output_tokens, total_tokens, cost_usd, duration_ms,
6462
+ drift_count, drift_errors, test_pass_count, test_fail_count,
6463
+ lint_clean, typecheck_clean,
6464
+ notes, metadata, recorded_at
6465
+ ) VALUES (
6466
+ ?, ?, ?, ?,
6467
+ ?, ?,
6468
+ ?, ?, ?, ?, ?,
6469
+ ?, ?, ?, ?,
6470
+ ?, ?,
6471
+ ?, ?, ?
6472
+ )
6473
+ `);
6474
+ const result = stmt.run(
6475
+ params.toolName,
6476
+ params.action ?? null,
6477
+ params.model ?? null,
6478
+ params.agentRole ?? null,
6479
+ params.taskId ?? null,
6480
+ params.phaseId ?? null,
6481
+ params.inputTokens ?? null,
6482
+ params.outputTokens ?? null,
6483
+ totalTokens,
6484
+ params.costUsd ?? null,
6485
+ params.durationMs ?? null,
6486
+ params.driftCount ?? null,
6487
+ params.driftErrors ?? null,
6488
+ params.testPassCount ?? null,
6489
+ params.testFailCount ?? null,
6490
+ params.lintClean != null ? params.lintClean ? 1 : 0 : null,
6491
+ params.typecheckClean != null ? params.typecheckClean ? 1 : 0 : null,
6492
+ params.notes ?? null,
6493
+ JSON.stringify(params.metadata ?? {}),
6494
+ (/* @__PURE__ */ new Date()).toISOString()
6495
+ );
6496
+ return Number(result.lastInsertRowid);
6497
+ }
6498
+ function getSessionTotals(db, since, model) {
6499
+ const conditions = [];
6500
+ const values = [];
6501
+ if (since) {
6502
+ conditions.push("recorded_at >= ?");
6503
+ values.push(since);
6504
+ }
6505
+ if (model) {
6506
+ conditions.push("model = ?");
6507
+ values.push(model);
6508
+ }
6509
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
6510
+ const row = db.prepare(
6511
+ `SELECT
6512
+ COALESCE(SUM(cost_usd), 0) as total_cost,
6513
+ COALESCE(SUM(total_tokens), 0) as total_tokens,
6514
+ COUNT(*) as activity_count
6515
+ FROM agent_activity ${where}`
6516
+ ).get(...values);
6517
+ return {
6518
+ totalCost: row.total_cost,
6519
+ totalTokens: row.total_tokens,
6520
+ activityCount: row.activity_count
6521
+ };
6522
+ }
6523
+ function queryMetrics(db, params) {
6524
+ const conditions = [];
6525
+ const values = [];
6526
+ if (params.taskId) {
6527
+ conditions.push("task_id = ?");
6528
+ values.push(params.taskId);
6529
+ }
6530
+ if (params.phaseId) {
6531
+ conditions.push("phase_id = ?");
6532
+ values.push(params.phaseId);
6533
+ }
6534
+ if (params.model) {
6535
+ conditions.push("model = ?");
6536
+ values.push(params.model);
6537
+ }
6538
+ if (params.agentRole) {
6539
+ conditions.push("agent_role = ?");
6540
+ values.push(params.agentRole);
6541
+ }
6542
+ if (params.toolName) {
6543
+ conditions.push("tool_name = ?");
6544
+ values.push(params.toolName);
6545
+ }
6546
+ if (params.since) {
6547
+ conditions.push("recorded_at >= ?");
6548
+ values.push(params.since);
6549
+ }
6550
+ if (params.until) {
6551
+ conditions.push("recorded_at <= ?");
6552
+ values.push(params.until);
6553
+ }
6554
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
6555
+ const qualitySnapshot = getLatestQualitySnapshot(db, where, values);
6556
+ const totalsRow = db.prepare(
6557
+ `SELECT
6558
+ COALESCE(SUM(cost_usd), 0) as total_cost,
6559
+ COALESCE(SUM(total_tokens), 0) as total_tokens,
6560
+ COUNT(*) as activity_count,
6561
+ MIN(recorded_at) as first_at,
6562
+ MAX(recorded_at) as last_at
6563
+ FROM agent_activity ${where}`
6564
+ ).get(...values);
6565
+ const totals = {
6566
+ totalCost: totalsRow.total_cost,
6567
+ totalTokens: totalsRow.total_tokens,
6568
+ activityCount: totalsRow.activity_count
6569
+ };
6570
+ const timeSpan = totalsRow.first_at && totalsRow.last_at ? { first: totalsRow.first_at, last: totalsRow.last_at } : null;
6571
+ if (params.groupBy === "none") {
6572
+ const rows2 = db.prepare(
6573
+ `SELECT * FROM agent_activity ${where} ORDER BY recorded_at DESC LIMIT ?`
6574
+ ).all(...values, params.limit);
6575
+ return { rows: rows2, grouped: false, qualitySnapshot, totals, timeSpan };
6576
+ }
6577
+ const groupColumn = getGroupColumn(params.groupBy);
6578
+ const rows = db.prepare(
6579
+ `SELECT
6580
+ ${groupColumn} as group_key,
6581
+ COUNT(*) as activity_count,
6582
+ SUM(total_tokens) as sum_tokens,
6583
+ ROUND(AVG(total_tokens)) as avg_tokens,
6584
+ ROUND(SUM(cost_usd), 4) as sum_cost,
6585
+ ROUND(AVG(duration_ms)) as avg_duration,
6586
+ MIN(recorded_at) as first_activity,
6587
+ MAX(recorded_at) as last_activity
6588
+ FROM agent_activity ${where}
6589
+ GROUP BY ${groupColumn}
6590
+ ORDER BY sum_cost DESC`
6591
+ ).all(...values);
6592
+ const aggregated = rows.map((r) => ({
6593
+ groupKey: r.group_key ?? "(none)",
6594
+ activityCount: r.activity_count,
6595
+ sumTokens: r.sum_tokens,
6596
+ avgTokens: r.avg_tokens,
6597
+ sumCost: r.sum_cost,
6598
+ avgDuration: r.avg_duration,
6599
+ firstActivity: r.first_activity,
6600
+ lastActivity: r.last_activity
6601
+ }));
6602
+ return { rows: aggregated, grouped: true, qualitySnapshot, totals, timeSpan };
6603
+ }
6604
+ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
6605
+ const result = queryMetrics(db, {
6606
+ ...params,
6607
+ groupBy: "none",
6608
+ limit: maxRows
6609
+ });
6610
+ const rows = result.rows;
6611
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
6612
+ const dir = join16(projectRoot, ".arcbridge", "metrics");
6613
+ mkdirSync7(dir, { recursive: true });
6614
+ let content;
6615
+ let filename;
6616
+ switch (format) {
6617
+ case "json": {
6618
+ filename = `activity-${timestamp}.json`;
6619
+ content = JSON.stringify(
6620
+ {
6621
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
6622
+ totals: result.totals,
6623
+ quality_snapshot: result.qualitySnapshot,
6624
+ activities: rows.map((r) => ({
6625
+ ...r,
6626
+ metadata: safeParseJson3(r.metadata)
6627
+ }))
6628
+ },
6629
+ null,
6630
+ 2
6631
+ );
6632
+ break;
6633
+ }
6634
+ case "csv": {
6635
+ filename = `activity-${timestamp}.csv`;
6636
+ const headers = [
6637
+ "id",
6638
+ "recorded_at",
6639
+ "tool_name",
6640
+ "action",
6641
+ "model",
6642
+ "agent_role",
6643
+ "task_id",
6644
+ "phase_id",
6645
+ "input_tokens",
6646
+ "output_tokens",
6647
+ "total_tokens",
6648
+ "cost_usd",
6649
+ "duration_ms",
6650
+ "drift_count",
6651
+ "drift_errors",
6652
+ "test_pass_count",
6653
+ "test_fail_count",
6654
+ "lint_clean",
6655
+ "typecheck_clean",
6656
+ "notes"
6657
+ ];
6658
+ const csvRows = rows.map(
6659
+ (r) => headers.map((h) => {
6660
+ const val = r[h];
6661
+ if (val == null) return "";
6662
+ const str = String(val);
6663
+ return str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r") ? `"${str.replace(/"/g, '""')}"` : str;
6664
+ }).join(",")
6665
+ );
6666
+ content = [headers.join(","), ...csvRows].join("\n");
6667
+ break;
6668
+ }
6669
+ case "markdown": {
6670
+ filename = `activity-${timestamp}.md`;
6671
+ const lines = [
6672
+ `# Agent Activity Report`,
6673
+ "",
6674
+ `**Exported:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
6675
+ `**Activities:** ${result.totals.activityCount}`,
6676
+ `**Total cost:** $${result.totals.totalCost.toFixed(4)}`,
6677
+ `**Total tokens:** ${result.totals.totalTokens.toLocaleString()}`,
6678
+ ""
6679
+ ];
6680
+ if (result.qualitySnapshot.capturedAt) {
6681
+ const q = result.qualitySnapshot;
6682
+ lines.push(
6683
+ "## Latest Quality Snapshot",
6684
+ "",
6685
+ `| Metric | Value |`,
6686
+ `|--------|-------|`
6687
+ );
6688
+ if (q.driftCount != null) lines.push(`| Drift issues | ${q.driftCount} (${q.driftErrors ?? 0} errors) |`);
6689
+ if (q.testPassCount != null) lines.push(`| Tests | ${q.testPassCount} pass / ${q.testFailCount ?? 0} fail |`);
6690
+ if (q.lintClean != null) lines.push(`| Lint | ${q.lintClean ? "clean" : "errors"} |`);
6691
+ if (q.typecheckClean != null) lines.push(`| Typecheck | ${q.typecheckClean ? "clean" : "errors"} |`);
6692
+ lines.push("");
6693
+ }
6694
+ lines.push(
6695
+ "## Activities",
6696
+ "",
6697
+ "| Time | Tool | Action | Model | Tokens | Cost | Duration |",
6698
+ "|------|------|--------|-------|--------|------|----------|"
6699
+ );
6700
+ for (const r of rows) {
6701
+ lines.push(
6702
+ `| ${r.recorded_at.slice(0, 19)} | ${esc(r.tool_name)} | ${esc(r.action)} | ${esc(r.model)} | ${r.total_tokens ?? ""} | ${r.cost_usd != null ? "$" + r.cost_usd.toFixed(4) : ""} | ${r.duration_ms != null ? r.duration_ms + "ms" : ""} |`
6703
+ );
6704
+ }
6705
+ content = lines.join("\n") + "\n";
6706
+ break;
6707
+ }
6708
+ }
6709
+ const filePath = join16(dir, filename);
6710
+ writeFileSync7(filePath, content, "utf-8");
6711
+ return filePath;
6712
+ }
6713
+ function getGroupColumn(groupBy) {
6714
+ switch (groupBy) {
6715
+ case "model":
6716
+ return "model";
6717
+ case "task":
6718
+ return "task_id";
6719
+ case "phase":
6720
+ return "phase_id";
6721
+ case "tool":
6722
+ return "tool_name";
6723
+ case "day":
6724
+ return "DATE(recorded_at)";
6725
+ default:
6726
+ return "model";
6727
+ }
6728
+ }
6729
+ function getLatestQualitySnapshot(db, where, values) {
6730
+ const row = db.prepare(
6731
+ `SELECT
6732
+ drift_count, drift_errors,
6733
+ test_pass_count, test_fail_count,
6734
+ lint_clean, typecheck_clean,
6735
+ recorded_at
6736
+ FROM agent_activity
6737
+ ${where ? where + " AND" : "WHERE"}
6738
+ (drift_count IS NOT NULL OR test_pass_count IS NOT NULL OR lint_clean IS NOT NULL OR typecheck_clean IS NOT NULL)
6739
+ ORDER BY recorded_at DESC
6740
+ LIMIT 1`
6741
+ ).get(...values);
6742
+ if (!row) {
6743
+ return {
6744
+ driftCount: null,
6745
+ driftErrors: null,
6746
+ testPassCount: null,
6747
+ testFailCount: null,
6748
+ lintClean: null,
6749
+ typecheckClean: null,
6750
+ capturedAt: null
6751
+ };
6752
+ }
6753
+ return {
6754
+ driftCount: row.drift_count,
6755
+ driftErrors: row.drift_errors,
6756
+ testPassCount: row.test_pass_count,
6757
+ testFailCount: row.test_fail_count,
6758
+ lintClean: row.lint_clean != null ? row.lint_clean === 1 : null,
6759
+ typecheckClean: row.typecheck_clean != null ? row.typecheck_clean === 1 : null,
6760
+ capturedAt: row.recorded_at
6761
+ };
6762
+ }
6763
+ function safeParseJson3(raw) {
6764
+ try {
6765
+ return JSON.parse(raw);
6766
+ } catch {
6767
+ return {};
6768
+ }
6769
+ }
6770
+ function esc(val) {
6771
+ if (val == null) return "";
6772
+ return val.replace(/\|/g, "\\|").replace(/\r?\n|\r/g, " ");
6773
+ }
6774
+
6376
6775
  // src/git/helpers.ts
6377
6776
  import { execFileSync as execFileSync3 } from "child_process";
6378
6777
  function resolveRef(projectRoot, since, db) {
@@ -6579,10 +6978,10 @@ ${output}`;
6579
6978
 
6580
6979
  // src/roles/loader.ts
6581
6980
  import { readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
6582
- import { join as join16 } from "path";
6981
+ import { join as join17 } from "path";
6583
6982
  import matter4 from "gray-matter";
6584
6983
  function loadRoles(projectRoot) {
6585
- const agentsDir = join16(projectRoot, ".arcbridge", "agents");
6984
+ const agentsDir = join17(projectRoot, ".arcbridge", "agents");
6586
6985
  const roles = [];
6587
6986
  const errors = [];
6588
6987
  let files;
@@ -6592,7 +6991,7 @@ function loadRoles(projectRoot) {
6592
6991
  return { roles: [], errors: [`Agent directory not found: ${agentsDir}`] };
6593
6992
  }
6594
6993
  for (const file of files) {
6595
- const filePath = join16(agentsDir, file);
6994
+ const filePath = join17(agentsDir, file);
6596
6995
  try {
6597
6996
  const raw = readFileSync9(filePath, "utf-8");
6598
6997
  const parsed = matter4(raw);
@@ -6619,7 +7018,7 @@ function loadRole(projectRoot, roleId) {
6619
7018
  if (!/^[a-z0-9-]+$/.test(roleId)) {
6620
7019
  return { role: null, error: `Invalid role ID: "${roleId}" (must be kebab-case)` };
6621
7020
  }
6622
- const filePath = join16(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
7021
+ const filePath = join17(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
6623
7022
  try {
6624
7023
  const raw = readFileSync9(filePath, "utf-8");
6625
7024
  const parsed = matter4(raw);
@@ -6661,6 +7060,7 @@ export {
6661
7060
  detectDrift,
6662
7061
  detectProjectLanguage,
6663
7062
  discoverDotnetServices,
7063
+ exportMetrics,
6664
7064
  generateAgentRoles,
6665
7065
  generateArc42,
6666
7066
  generateConfig,
@@ -6669,17 +7069,20 @@ export {
6669
7069
  generateSyncFiles,
6670
7070
  getChangedFiles,
6671
7071
  getHeadSha,
7072
+ getSessionTotals,
6672
7073
  getUncommittedChanges,
6673
7074
  indexPackageDependencies,
6674
7075
  indexProject,
6675
7076
  inferTaskStatuses,
6676
7077
  initializeSchema,
7078
+ insertActivity,
6677
7079
  loadConfig,
6678
7080
  loadRole,
6679
7081
  loadRoles,
6680
7082
  migrate,
6681
7083
  openDatabase,
6682
7084
  openMemoryDatabase,
7085
+ queryMetrics,
6683
7086
  refreshFromDocs,
6684
7087
  resolveRef,
6685
7088
  setSyncCommit,