@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 +111 -2
- package/dist/index.js +409 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 =
|
|
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 =
|
|
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
|
|
6981
|
+
import { join as join17 } from "path";
|
|
6583
6982
|
import matter4 from "gray-matter";
|
|
6584
6983
|
function loadRoles(projectRoot) {
|
|
6585
|
-
const agentsDir =
|
|
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 =
|
|
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 =
|
|
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,
|