@arcbridge/core 0.1.1 → 0.1.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/dist/index.d.ts +138 -18
- package/dist/index.js +509 -36
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
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"),
|
|
@@ -205,19 +210,97 @@ var AgentRoleSchema = z6.object({
|
|
|
205
210
|
});
|
|
206
211
|
|
|
207
212
|
// src/db/connection.ts
|
|
208
|
-
import
|
|
213
|
+
import { DatabaseSync } from "node:sqlite";
|
|
214
|
+
var suppressed = false;
|
|
215
|
+
function suppressSqliteWarning() {
|
|
216
|
+
if (suppressed) return;
|
|
217
|
+
suppressed = true;
|
|
218
|
+
const origEmit = process.emit;
|
|
219
|
+
process.emit = function(event, ...args) {
|
|
220
|
+
if (event === "warning" && args[0]?.name === "ExperimentalWarning" && typeof args[0]?.message === "string" && args[0].message.includes("SQLite")) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
return origEmit.apply(process, [event, ...args]);
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function sanitizeParams(params) {
|
|
227
|
+
return params.map((p) => p === void 0 ? null : p);
|
|
228
|
+
}
|
|
209
229
|
function openDatabase(dbPath) {
|
|
210
|
-
const db = new
|
|
211
|
-
db.
|
|
212
|
-
db.
|
|
230
|
+
const db = new DatabaseSync(dbPath);
|
|
231
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
232
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
233
|
+
patchPrepare(db);
|
|
213
234
|
return db;
|
|
214
235
|
}
|
|
215
236
|
function openMemoryDatabase() {
|
|
216
|
-
|
|
237
|
+
const db = new DatabaseSync(":memory:");
|
|
238
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
239
|
+
patchPrepare(db);
|
|
240
|
+
return db;
|
|
241
|
+
}
|
|
242
|
+
function patchPrepare(db) {
|
|
243
|
+
const originalPrepare = db.prepare.bind(db);
|
|
244
|
+
db.prepare = (sql) => {
|
|
245
|
+
const stmt = originalPrepare(sql);
|
|
246
|
+
const origRun = stmt.run.bind(stmt);
|
|
247
|
+
const origGet = stmt.get.bind(stmt);
|
|
248
|
+
const origAll = stmt.all.bind(stmt);
|
|
249
|
+
stmt.run = (...params) => origRun(...sanitizeParams(params));
|
|
250
|
+
stmt.get = (...params) => origGet(...sanitizeParams(params));
|
|
251
|
+
stmt.all = (...params) => origAll(...sanitizeParams(params));
|
|
252
|
+
return stmt;
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function rejectAsync(fn) {
|
|
256
|
+
if (fn.constructor.name === "AsyncFunction") {
|
|
257
|
+
throw new Error(
|
|
258
|
+
"transaction() received an async function. Use a synchronous function \u2014 node:sqlite is synchronous and the transaction would commit before the async work completes."
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
var txDepth = /* @__PURE__ */ new WeakMap();
|
|
263
|
+
function transaction(db, fn) {
|
|
264
|
+
rejectAsync(fn);
|
|
265
|
+
const depth = txDepth.get(db) ?? 0;
|
|
266
|
+
if (depth === 0) {
|
|
267
|
+
db.exec("BEGIN");
|
|
268
|
+
txDepth.set(db, 1);
|
|
269
|
+
try {
|
|
270
|
+
const result = fn();
|
|
271
|
+
db.exec("COMMIT");
|
|
272
|
+
return result;
|
|
273
|
+
} catch (err) {
|
|
274
|
+
try {
|
|
275
|
+
db.exec("ROLLBACK");
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
throw err;
|
|
279
|
+
} finally {
|
|
280
|
+
txDepth.set(db, 0);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const name = `sp_${depth}`;
|
|
284
|
+
db.exec(`SAVEPOINT ${name}`);
|
|
285
|
+
txDepth.set(db, depth + 1);
|
|
286
|
+
try {
|
|
287
|
+
const result = fn();
|
|
288
|
+
db.exec(`RELEASE ${name}`);
|
|
289
|
+
return result;
|
|
290
|
+
} catch (err) {
|
|
291
|
+
try {
|
|
292
|
+
db.exec(`ROLLBACK TO ${name}`);
|
|
293
|
+
db.exec(`RELEASE ${name}`);
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
throw err;
|
|
297
|
+
} finally {
|
|
298
|
+
txDepth.set(db, depth);
|
|
299
|
+
}
|
|
217
300
|
}
|
|
218
301
|
|
|
219
302
|
// src/db/schema.ts
|
|
220
|
-
var CURRENT_SCHEMA_VERSION =
|
|
303
|
+
var CURRENT_SCHEMA_VERSION = 2;
|
|
221
304
|
var SCHEMA_SQL = `
|
|
222
305
|
-- Metadata
|
|
223
306
|
CREATE TABLE IF NOT EXISTS arcbridge_meta (
|
|
@@ -383,6 +466,36 @@ CREATE TABLE IF NOT EXISTS drift_log (
|
|
|
383
466
|
resolution TEXT CHECK(resolution IN ('accepted','fixed','deferred') OR resolution IS NULL),
|
|
384
467
|
resolved_at TEXT
|
|
385
468
|
);
|
|
469
|
+
|
|
470
|
+
-- Agent Activity Metrics (operational telemetry, not rebuilt by refreshFromDocs)
|
|
471
|
+
CREATE TABLE IF NOT EXISTS agent_activity (
|
|
472
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
473
|
+
tool_name TEXT NOT NULL,
|
|
474
|
+
action TEXT,
|
|
475
|
+
model TEXT,
|
|
476
|
+
agent_role TEXT,
|
|
477
|
+
task_id TEXT,
|
|
478
|
+
phase_id TEXT,
|
|
479
|
+
input_tokens INTEGER,
|
|
480
|
+
output_tokens INTEGER,
|
|
481
|
+
total_tokens INTEGER,
|
|
482
|
+
cost_usd REAL,
|
|
483
|
+
duration_ms INTEGER,
|
|
484
|
+
drift_count INTEGER,
|
|
485
|
+
drift_errors INTEGER,
|
|
486
|
+
test_pass_count INTEGER,
|
|
487
|
+
test_fail_count INTEGER,
|
|
488
|
+
lint_clean INTEGER,
|
|
489
|
+
typecheck_clean INTEGER,
|
|
490
|
+
notes TEXT,
|
|
491
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
492
|
+
recorded_at TEXT NOT NULL
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
CREATE INDEX IF NOT EXISTS idx_activity_recorded_at ON agent_activity(recorded_at);
|
|
496
|
+
CREATE INDEX IF NOT EXISTS idx_activity_model ON agent_activity(model);
|
|
497
|
+
CREATE INDEX IF NOT EXISTS idx_activity_task ON agent_activity(task_id);
|
|
498
|
+
CREATE INDEX IF NOT EXISTS idx_activity_phase ON agent_activity(phase_id);
|
|
386
499
|
`;
|
|
387
500
|
function initializeSchema(db) {
|
|
388
501
|
db.exec(SCHEMA_SQL);
|
|
@@ -398,7 +511,42 @@ function initializeSchema(db) {
|
|
|
398
511
|
}
|
|
399
512
|
|
|
400
513
|
// src/db/migrations.ts
|
|
401
|
-
var migrations = [
|
|
514
|
+
var migrations = [
|
|
515
|
+
{
|
|
516
|
+
version: 2,
|
|
517
|
+
up: (db) => {
|
|
518
|
+
db.exec(`
|
|
519
|
+
CREATE TABLE IF NOT EXISTS agent_activity (
|
|
520
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
521
|
+
tool_name TEXT NOT NULL,
|
|
522
|
+
action TEXT,
|
|
523
|
+
model TEXT,
|
|
524
|
+
agent_role TEXT,
|
|
525
|
+
task_id TEXT,
|
|
526
|
+
phase_id TEXT,
|
|
527
|
+
input_tokens INTEGER,
|
|
528
|
+
output_tokens INTEGER,
|
|
529
|
+
total_tokens INTEGER,
|
|
530
|
+
cost_usd REAL,
|
|
531
|
+
duration_ms INTEGER,
|
|
532
|
+
drift_count INTEGER,
|
|
533
|
+
drift_errors INTEGER,
|
|
534
|
+
test_pass_count INTEGER,
|
|
535
|
+
test_fail_count INTEGER,
|
|
536
|
+
lint_clean INTEGER,
|
|
537
|
+
typecheck_clean INTEGER,
|
|
538
|
+
notes TEXT,
|
|
539
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
540
|
+
recorded_at TEXT NOT NULL
|
|
541
|
+
);
|
|
542
|
+
CREATE INDEX IF NOT EXISTS idx_activity_recorded_at ON agent_activity(recorded_at);
|
|
543
|
+
CREATE INDEX IF NOT EXISTS idx_activity_model ON agent_activity(model);
|
|
544
|
+
CREATE INDEX IF NOT EXISTS idx_activity_task ON agent_activity(task_id);
|
|
545
|
+
CREATE INDEX IF NOT EXISTS idx_activity_phase ON agent_activity(phase_id);
|
|
546
|
+
`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
];
|
|
402
550
|
function migrate(db) {
|
|
403
551
|
const row = db.prepare("SELECT value FROM arcbridge_meta WHERE key = 'schema_version'").get();
|
|
404
552
|
const currentVersion = row ? Number(row.value) : 0;
|
|
@@ -407,12 +555,12 @@ function migrate(db) {
|
|
|
407
555
|
}
|
|
408
556
|
const pending = migrations.filter((m) => m.version > currentVersion).sort((a, b) => a.version - b.version);
|
|
409
557
|
for (const migration of pending) {
|
|
410
|
-
|
|
558
|
+
transaction(db, () => {
|
|
411
559
|
migration.up(db);
|
|
412
560
|
db.prepare(
|
|
413
561
|
"UPDATE arcbridge_meta SET value = ? WHERE key = 'schema_version'"
|
|
414
562
|
).run(String(migration.version));
|
|
415
|
-
})
|
|
563
|
+
});
|
|
416
564
|
}
|
|
417
565
|
}
|
|
418
566
|
|
|
@@ -450,6 +598,7 @@ function configTemplate(input) {
|
|
|
450
598
|
drift: {
|
|
451
599
|
ignore_paths: []
|
|
452
600
|
},
|
|
601
|
+
metrics: { auto_record: false },
|
|
453
602
|
sync: {
|
|
454
603
|
auto_detect_drift: true,
|
|
455
604
|
drift_severity_threshold: "warning",
|
|
@@ -487,6 +636,7 @@ function configTemplate2(input) {
|
|
|
487
636
|
drift: {
|
|
488
637
|
ignore_paths: []
|
|
489
638
|
},
|
|
639
|
+
metrics: { auto_record: false },
|
|
490
640
|
sync: {
|
|
491
641
|
auto_detect_drift: true,
|
|
492
642
|
drift_severity_threshold: "warning",
|
|
@@ -524,6 +674,7 @@ function configTemplate3(input) {
|
|
|
524
674
|
drift: {
|
|
525
675
|
ignore_paths: []
|
|
526
676
|
},
|
|
677
|
+
metrics: { auto_record: false },
|
|
527
678
|
sync: {
|
|
528
679
|
auto_detect_drift: true,
|
|
529
680
|
drift_severity_threshold: "warning",
|
|
@@ -559,6 +710,7 @@ function configTemplate4(input) {
|
|
|
559
710
|
drift: {
|
|
560
711
|
ignore_paths: []
|
|
561
712
|
},
|
|
713
|
+
metrics: { auto_record: false },
|
|
562
714
|
sync: {
|
|
563
715
|
auto_detect_drift: true,
|
|
564
716
|
drift_severity_threshold: "warning",
|
|
@@ -3201,9 +3353,11 @@ function refreshFromDocs(db, targetDir) {
|
|
|
3201
3353
|
const scenarioStatusMap = new Map(
|
|
3202
3354
|
existingScenarios.map((s) => [s.id, s.status])
|
|
3203
3355
|
);
|
|
3204
|
-
const refresh =
|
|
3356
|
+
const refresh = () => transaction(db, () => {
|
|
3205
3357
|
db.prepare("DELETE FROM tasks").run();
|
|
3206
3358
|
db.prepare("DELETE FROM phases").run();
|
|
3359
|
+
db.prepare("UPDATE contracts SET building_block = NULL").run();
|
|
3360
|
+
db.prepare("UPDATE building_blocks SET parent_id = NULL").run();
|
|
3207
3361
|
db.prepare("DELETE FROM building_blocks").run();
|
|
3208
3362
|
db.prepare("DELETE FROM quality_scenarios").run();
|
|
3209
3363
|
db.prepare("DELETE FROM adrs").run();
|
|
@@ -3250,12 +3404,12 @@ function generateDatabase(targetDir, input) {
|
|
|
3250
3404
|
upsert.run("project_name", input.name);
|
|
3251
3405
|
upsert.run("project_type", input.template);
|
|
3252
3406
|
upsert.run("last_full_index", (/* @__PURE__ */ new Date()).toISOString());
|
|
3253
|
-
|
|
3407
|
+
transaction(db, () => {
|
|
3254
3408
|
allWarnings.push(...populateBuildingBlocks(db, targetDir));
|
|
3255
3409
|
allWarnings.push(...populateQualityScenarios(db, targetDir));
|
|
3256
3410
|
allWarnings.push(...populatePhases(db, targetDir));
|
|
3257
3411
|
allWarnings.push(...populateAdrs(db, targetDir));
|
|
3258
|
-
})
|
|
3412
|
+
});
|
|
3259
3413
|
ensureGitignore(targetDir);
|
|
3260
3414
|
return { db, warnings: allWarnings };
|
|
3261
3415
|
}
|
|
@@ -4054,7 +4208,7 @@ function writeComponents(db, components) {
|
|
|
4054
4208
|
const existingIds = new Set(
|
|
4055
4209
|
db.prepare("SELECT id FROM symbols").all().map((r) => r.id)
|
|
4056
4210
|
);
|
|
4057
|
-
|
|
4211
|
+
transaction(db, () => {
|
|
4058
4212
|
for (const c of components) {
|
|
4059
4213
|
if (!existingIds.has(c.symbolId)) continue;
|
|
4060
4214
|
insert.run(
|
|
@@ -4068,7 +4222,6 @@ function writeComponents(db, components) {
|
|
|
4068
4222
|
);
|
|
4069
4223
|
}
|
|
4070
4224
|
});
|
|
4071
|
-
run();
|
|
4072
4225
|
}
|
|
4073
4226
|
|
|
4074
4227
|
// src/indexer/route-analyzer.ts
|
|
@@ -4197,7 +4350,7 @@ function writeRoutes(db, routes) {
|
|
|
4197
4350
|
id, route_path, kind, http_methods, has_auth, parent_layout, service
|
|
4198
4351
|
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
4199
4352
|
`);
|
|
4200
|
-
|
|
4353
|
+
transaction(db, () => {
|
|
4201
4354
|
for (const r of routes) {
|
|
4202
4355
|
insert.run(
|
|
4203
4356
|
r.id,
|
|
@@ -4210,7 +4363,6 @@ function writeRoutes(db, routes) {
|
|
|
4210
4363
|
);
|
|
4211
4364
|
}
|
|
4212
4365
|
});
|
|
4213
|
-
run();
|
|
4214
4366
|
}
|
|
4215
4367
|
|
|
4216
4368
|
// src/indexer/content-hash.ts
|
|
@@ -4244,7 +4396,7 @@ function removeSymbolsForFiles(db, filePaths) {
|
|
|
4244
4396
|
const deleteComponents = db.prepare(
|
|
4245
4397
|
"DELETE FROM components WHERE symbol_id IN (SELECT id FROM symbols WHERE file_path = ?)"
|
|
4246
4398
|
);
|
|
4247
|
-
|
|
4399
|
+
transaction(db, () => {
|
|
4248
4400
|
for (const fp of filePaths) {
|
|
4249
4401
|
deleteDepsSource.run(fp);
|
|
4250
4402
|
deleteDepsTarget.run(fp);
|
|
@@ -4252,7 +4404,6 @@ function removeSymbolsForFiles(db, filePaths) {
|
|
|
4252
4404
|
deleteSymbols.run(fp);
|
|
4253
4405
|
}
|
|
4254
4406
|
});
|
|
4255
|
-
run();
|
|
4256
4407
|
}
|
|
4257
4408
|
function writeSymbols(db, symbols, service, language = "typescript") {
|
|
4258
4409
|
if (symbols.length === 0) return;
|
|
@@ -4272,7 +4423,7 @@ function writeSymbols(db, symbols, service, language = "typescript") {
|
|
|
4272
4423
|
)
|
|
4273
4424
|
`);
|
|
4274
4425
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4275
|
-
|
|
4426
|
+
transaction(db, () => {
|
|
4276
4427
|
for (const s of symbols) {
|
|
4277
4428
|
insert.run(
|
|
4278
4429
|
s.id,
|
|
@@ -4296,7 +4447,6 @@ function writeSymbols(db, symbols, service, language = "typescript") {
|
|
|
4296
4447
|
);
|
|
4297
4448
|
}
|
|
4298
4449
|
});
|
|
4299
|
-
run();
|
|
4300
4450
|
}
|
|
4301
4451
|
function writeDependencies(db, dependencies) {
|
|
4302
4452
|
if (dependencies.length === 0) return;
|
|
@@ -4304,12 +4454,11 @@ function writeDependencies(db, dependencies) {
|
|
|
4304
4454
|
INSERT OR IGNORE INTO dependencies (source_symbol, target_symbol, kind)
|
|
4305
4455
|
VALUES (?, ?, ?)
|
|
4306
4456
|
`);
|
|
4307
|
-
|
|
4457
|
+
transaction(db, () => {
|
|
4308
4458
|
for (const dep of dependencies) {
|
|
4309
4459
|
insert.run(dep.sourceSymbolId, dep.targetSymbolId, dep.kind);
|
|
4310
4460
|
}
|
|
4311
4461
|
});
|
|
4312
|
-
run();
|
|
4313
4462
|
}
|
|
4314
4463
|
|
|
4315
4464
|
// src/indexer/dotnet-indexer.ts
|
|
@@ -4506,7 +4655,7 @@ function indexDotnetProjectRoslyn(db, options) {
|
|
|
4506
4655
|
INSERT OR REPLACE INTO routes (id, route_path, kind, http_methods, has_auth, service)
|
|
4507
4656
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
4508
4657
|
`);
|
|
4509
|
-
|
|
4658
|
+
transaction(db, () => {
|
|
4510
4659
|
for (const route of output.routes) {
|
|
4511
4660
|
insertRoute.run(
|
|
4512
4661
|
route.id,
|
|
@@ -4518,7 +4667,6 @@ function indexDotnetProjectRoslyn(db, options) {
|
|
|
4518
4667
|
);
|
|
4519
4668
|
}
|
|
4520
4669
|
});
|
|
4521
|
-
runRoutes();
|
|
4522
4670
|
}
|
|
4523
4671
|
return {
|
|
4524
4672
|
symbolsIndexed: output.symbols.length,
|
|
@@ -5437,7 +5585,7 @@ async function indexCSharpTreeSitter(db, options) {
|
|
|
5437
5585
|
INSERT OR REPLACE INTO routes (id, route_path, kind, http_methods, has_auth, service)
|
|
5438
5586
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
5439
5587
|
`);
|
|
5440
|
-
|
|
5588
|
+
transaction(db, () => {
|
|
5441
5589
|
for (const route of allRoutes) {
|
|
5442
5590
|
insertRoute.run(
|
|
5443
5591
|
route.id,
|
|
@@ -5449,7 +5597,6 @@ async function indexCSharpTreeSitter(db, options) {
|
|
|
5449
5597
|
);
|
|
5450
5598
|
}
|
|
5451
5599
|
});
|
|
5452
|
-
runRoutes();
|
|
5453
5600
|
}
|
|
5454
5601
|
return {
|
|
5455
5602
|
symbolsIndexed: allNewSymbols.length,
|
|
@@ -5481,12 +5628,11 @@ function indexPackageDependencies(db, projectRoot, service = "main") {
|
|
|
5481
5628
|
const insert = db.prepare(
|
|
5482
5629
|
"INSERT OR IGNORE INTO package_dependencies (name, version, source, service) VALUES (?, ?, ?, ?)"
|
|
5483
5630
|
);
|
|
5484
|
-
|
|
5631
|
+
transaction(db, () => {
|
|
5485
5632
|
for (const dep of deps) {
|
|
5486
5633
|
insert.run(dep.name, dep.version, dep.source, service);
|
|
5487
5634
|
}
|
|
5488
5635
|
});
|
|
5489
|
-
run();
|
|
5490
5636
|
return deps.length;
|
|
5491
5637
|
}
|
|
5492
5638
|
function parsePackageJson(filePath) {
|
|
@@ -5739,12 +5885,11 @@ function writeDriftLog(db, entries) {
|
|
|
5739
5885
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
5740
5886
|
`);
|
|
5741
5887
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5742
|
-
|
|
5888
|
+
transaction(db, () => {
|
|
5743
5889
|
for (const e of entries) {
|
|
5744
5890
|
insert.run(now, e.kind, e.severity, e.description, e.affectedBlock, e.affectedFile);
|
|
5745
5891
|
}
|
|
5746
5892
|
});
|
|
5747
|
-
run();
|
|
5748
5893
|
}
|
|
5749
5894
|
function detectUndocumentedModules(db, entries, ignorePaths = []) {
|
|
5750
5895
|
const blocks = db.prepare("SELECT id, name, code_paths FROM building_blocks").all();
|
|
@@ -6063,12 +6208,11 @@ function applyInferences(db, inferences, projectRoot) {
|
|
|
6063
6208
|
"UPDATE tasks SET status = ?, completed_at = CASE WHEN ? = 'done' THEN ? ELSE completed_at END WHERE id = ?"
|
|
6064
6209
|
);
|
|
6065
6210
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6066
|
-
|
|
6211
|
+
transaction(db, () => {
|
|
6067
6212
|
for (const inf of inferences) {
|
|
6068
6213
|
update.run(inf.inferredStatus, inf.inferredStatus, now, inf.taskId);
|
|
6069
6214
|
}
|
|
6070
6215
|
});
|
|
6071
|
-
run();
|
|
6072
6216
|
for (const inf of inferences) {
|
|
6073
6217
|
const task = db.prepare("SELECT phase_id FROM tasks WHERE id = ?").get(inf.taskId);
|
|
6074
6218
|
if (task) {
|
|
@@ -6373,6 +6517,329 @@ function generateSyncFiles(targetDir, config) {
|
|
|
6373
6517
|
return generated;
|
|
6374
6518
|
}
|
|
6375
6519
|
|
|
6520
|
+
// src/metrics/activity.ts
|
|
6521
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
|
|
6522
|
+
import { join as join16 } from "path";
|
|
6523
|
+
function insertActivity(db, params) {
|
|
6524
|
+
const totalTokens = params.totalTokens ?? (params.inputTokens != null && params.outputTokens != null ? params.inputTokens + params.outputTokens : null);
|
|
6525
|
+
const stmt = db.prepare(`
|
|
6526
|
+
INSERT INTO agent_activity (
|
|
6527
|
+
tool_name, action, model, agent_role,
|
|
6528
|
+
task_id, phase_id,
|
|
6529
|
+
input_tokens, output_tokens, total_tokens, cost_usd, duration_ms,
|
|
6530
|
+
drift_count, drift_errors, test_pass_count, test_fail_count,
|
|
6531
|
+
lint_clean, typecheck_clean,
|
|
6532
|
+
notes, metadata, recorded_at
|
|
6533
|
+
) VALUES (
|
|
6534
|
+
?, ?, ?, ?,
|
|
6535
|
+
?, ?,
|
|
6536
|
+
?, ?, ?, ?, ?,
|
|
6537
|
+
?, ?, ?, ?,
|
|
6538
|
+
?, ?,
|
|
6539
|
+
?, ?, ?
|
|
6540
|
+
)
|
|
6541
|
+
`);
|
|
6542
|
+
const result = stmt.run(
|
|
6543
|
+
params.toolName,
|
|
6544
|
+
params.action ?? null,
|
|
6545
|
+
params.model ?? null,
|
|
6546
|
+
params.agentRole ?? null,
|
|
6547
|
+
params.taskId ?? null,
|
|
6548
|
+
params.phaseId ?? null,
|
|
6549
|
+
params.inputTokens ?? null,
|
|
6550
|
+
params.outputTokens ?? null,
|
|
6551
|
+
totalTokens,
|
|
6552
|
+
params.costUsd ?? null,
|
|
6553
|
+
params.durationMs ?? null,
|
|
6554
|
+
params.driftCount ?? null,
|
|
6555
|
+
params.driftErrors ?? null,
|
|
6556
|
+
params.testPassCount ?? null,
|
|
6557
|
+
params.testFailCount ?? null,
|
|
6558
|
+
params.lintClean != null ? params.lintClean ? 1 : 0 : null,
|
|
6559
|
+
params.typecheckClean != null ? params.typecheckClean ? 1 : 0 : null,
|
|
6560
|
+
params.notes ?? null,
|
|
6561
|
+
JSON.stringify(params.metadata ?? {}),
|
|
6562
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
6563
|
+
);
|
|
6564
|
+
return Number(result.lastInsertRowid);
|
|
6565
|
+
}
|
|
6566
|
+
function getSessionTotals(db, since, model) {
|
|
6567
|
+
const conditions = [];
|
|
6568
|
+
const values = [];
|
|
6569
|
+
if (since) {
|
|
6570
|
+
conditions.push("recorded_at >= ?");
|
|
6571
|
+
values.push(since);
|
|
6572
|
+
}
|
|
6573
|
+
if (model) {
|
|
6574
|
+
conditions.push("model = ?");
|
|
6575
|
+
values.push(model);
|
|
6576
|
+
}
|
|
6577
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
6578
|
+
const row = db.prepare(
|
|
6579
|
+
`SELECT
|
|
6580
|
+
COALESCE(SUM(cost_usd), 0) as total_cost,
|
|
6581
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
6582
|
+
COUNT(*) as activity_count
|
|
6583
|
+
FROM agent_activity ${where}`
|
|
6584
|
+
).get(...values);
|
|
6585
|
+
return {
|
|
6586
|
+
totalCost: row.total_cost,
|
|
6587
|
+
totalTokens: row.total_tokens,
|
|
6588
|
+
activityCount: row.activity_count
|
|
6589
|
+
};
|
|
6590
|
+
}
|
|
6591
|
+
function queryMetrics(db, params) {
|
|
6592
|
+
const conditions = [];
|
|
6593
|
+
const values = [];
|
|
6594
|
+
if (params.taskId) {
|
|
6595
|
+
conditions.push("task_id = ?");
|
|
6596
|
+
values.push(params.taskId);
|
|
6597
|
+
}
|
|
6598
|
+
if (params.phaseId) {
|
|
6599
|
+
conditions.push("phase_id = ?");
|
|
6600
|
+
values.push(params.phaseId);
|
|
6601
|
+
}
|
|
6602
|
+
if (params.model) {
|
|
6603
|
+
conditions.push("model = ?");
|
|
6604
|
+
values.push(params.model);
|
|
6605
|
+
}
|
|
6606
|
+
if (params.agentRole) {
|
|
6607
|
+
conditions.push("agent_role = ?");
|
|
6608
|
+
values.push(params.agentRole);
|
|
6609
|
+
}
|
|
6610
|
+
if (params.toolName) {
|
|
6611
|
+
conditions.push("tool_name = ?");
|
|
6612
|
+
values.push(params.toolName);
|
|
6613
|
+
}
|
|
6614
|
+
if (params.since) {
|
|
6615
|
+
conditions.push("recorded_at >= ?");
|
|
6616
|
+
values.push(params.since);
|
|
6617
|
+
}
|
|
6618
|
+
if (params.until) {
|
|
6619
|
+
conditions.push("recorded_at <= ?");
|
|
6620
|
+
values.push(params.until);
|
|
6621
|
+
}
|
|
6622
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
6623
|
+
const qualitySnapshot = getLatestQualitySnapshot(db, where, values);
|
|
6624
|
+
const totalsRow = db.prepare(
|
|
6625
|
+
`SELECT
|
|
6626
|
+
COALESCE(SUM(cost_usd), 0) as total_cost,
|
|
6627
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
6628
|
+
COUNT(*) as activity_count,
|
|
6629
|
+
MIN(recorded_at) as first_at,
|
|
6630
|
+
MAX(recorded_at) as last_at
|
|
6631
|
+
FROM agent_activity ${where}`
|
|
6632
|
+
).get(...values);
|
|
6633
|
+
const totals = {
|
|
6634
|
+
totalCost: totalsRow.total_cost,
|
|
6635
|
+
totalTokens: totalsRow.total_tokens,
|
|
6636
|
+
activityCount: totalsRow.activity_count
|
|
6637
|
+
};
|
|
6638
|
+
const timeSpan = totalsRow.first_at && totalsRow.last_at ? { first: totalsRow.first_at, last: totalsRow.last_at } : null;
|
|
6639
|
+
if (params.groupBy === "none") {
|
|
6640
|
+
const rows2 = db.prepare(
|
|
6641
|
+
`SELECT * FROM agent_activity ${where} ORDER BY recorded_at DESC LIMIT ?`
|
|
6642
|
+
).all(...values, params.limit);
|
|
6643
|
+
return { rows: rows2, grouped: false, qualitySnapshot, totals, timeSpan };
|
|
6644
|
+
}
|
|
6645
|
+
const groupColumn = getGroupColumn(params.groupBy);
|
|
6646
|
+
const rows = db.prepare(
|
|
6647
|
+
`SELECT
|
|
6648
|
+
${groupColumn} as group_key,
|
|
6649
|
+
COUNT(*) as activity_count,
|
|
6650
|
+
SUM(total_tokens) as sum_tokens,
|
|
6651
|
+
ROUND(AVG(total_tokens)) as avg_tokens,
|
|
6652
|
+
ROUND(SUM(cost_usd), 4) as sum_cost,
|
|
6653
|
+
ROUND(AVG(duration_ms)) as avg_duration,
|
|
6654
|
+
MIN(recorded_at) as first_activity,
|
|
6655
|
+
MAX(recorded_at) as last_activity
|
|
6656
|
+
FROM agent_activity ${where}
|
|
6657
|
+
GROUP BY ${groupColumn}
|
|
6658
|
+
ORDER BY sum_cost DESC`
|
|
6659
|
+
).all(...values);
|
|
6660
|
+
const aggregated = rows.map((r) => ({
|
|
6661
|
+
groupKey: r.group_key ?? "(none)",
|
|
6662
|
+
activityCount: r.activity_count,
|
|
6663
|
+
sumTokens: r.sum_tokens,
|
|
6664
|
+
avgTokens: r.avg_tokens,
|
|
6665
|
+
sumCost: r.sum_cost,
|
|
6666
|
+
avgDuration: r.avg_duration,
|
|
6667
|
+
firstActivity: r.first_activity,
|
|
6668
|
+
lastActivity: r.last_activity
|
|
6669
|
+
}));
|
|
6670
|
+
return { rows: aggregated, grouped: true, qualitySnapshot, totals, timeSpan };
|
|
6671
|
+
}
|
|
6672
|
+
function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
|
|
6673
|
+
const result = queryMetrics(db, {
|
|
6674
|
+
...params,
|
|
6675
|
+
groupBy: "none",
|
|
6676
|
+
limit: maxRows
|
|
6677
|
+
});
|
|
6678
|
+
const rows = result.rows;
|
|
6679
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
6680
|
+
const dir = join16(projectRoot, ".arcbridge", "metrics");
|
|
6681
|
+
mkdirSync7(dir, { recursive: true });
|
|
6682
|
+
let content;
|
|
6683
|
+
let filename;
|
|
6684
|
+
switch (format) {
|
|
6685
|
+
case "json": {
|
|
6686
|
+
filename = `activity-${timestamp}.json`;
|
|
6687
|
+
content = JSON.stringify(
|
|
6688
|
+
{
|
|
6689
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6690
|
+
totals: result.totals,
|
|
6691
|
+
quality_snapshot: result.qualitySnapshot,
|
|
6692
|
+
activities: rows.map((r) => ({
|
|
6693
|
+
...r,
|
|
6694
|
+
metadata: safeParseJson3(r.metadata)
|
|
6695
|
+
}))
|
|
6696
|
+
},
|
|
6697
|
+
null,
|
|
6698
|
+
2
|
|
6699
|
+
);
|
|
6700
|
+
break;
|
|
6701
|
+
}
|
|
6702
|
+
case "csv": {
|
|
6703
|
+
filename = `activity-${timestamp}.csv`;
|
|
6704
|
+
const headers = [
|
|
6705
|
+
"id",
|
|
6706
|
+
"recorded_at",
|
|
6707
|
+
"tool_name",
|
|
6708
|
+
"action",
|
|
6709
|
+
"model",
|
|
6710
|
+
"agent_role",
|
|
6711
|
+
"task_id",
|
|
6712
|
+
"phase_id",
|
|
6713
|
+
"input_tokens",
|
|
6714
|
+
"output_tokens",
|
|
6715
|
+
"total_tokens",
|
|
6716
|
+
"cost_usd",
|
|
6717
|
+
"duration_ms",
|
|
6718
|
+
"drift_count",
|
|
6719
|
+
"drift_errors",
|
|
6720
|
+
"test_pass_count",
|
|
6721
|
+
"test_fail_count",
|
|
6722
|
+
"lint_clean",
|
|
6723
|
+
"typecheck_clean",
|
|
6724
|
+
"notes"
|
|
6725
|
+
];
|
|
6726
|
+
const csvRows = rows.map(
|
|
6727
|
+
(r) => headers.map((h) => {
|
|
6728
|
+
const val = r[h];
|
|
6729
|
+
if (val == null) return "";
|
|
6730
|
+
const str = String(val);
|
|
6731
|
+
return str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r") ? `"${str.replace(/"/g, '""')}"` : str;
|
|
6732
|
+
}).join(",")
|
|
6733
|
+
);
|
|
6734
|
+
content = [headers.join(","), ...csvRows].join("\n");
|
|
6735
|
+
break;
|
|
6736
|
+
}
|
|
6737
|
+
case "markdown": {
|
|
6738
|
+
filename = `activity-${timestamp}.md`;
|
|
6739
|
+
const lines = [
|
|
6740
|
+
`# Agent Activity Report`,
|
|
6741
|
+
"",
|
|
6742
|
+
`**Exported:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
6743
|
+
`**Activities:** ${result.totals.activityCount}`,
|
|
6744
|
+
`**Total cost:** $${result.totals.totalCost.toFixed(4)}`,
|
|
6745
|
+
`**Total tokens:** ${result.totals.totalTokens.toLocaleString()}`,
|
|
6746
|
+
""
|
|
6747
|
+
];
|
|
6748
|
+
if (result.qualitySnapshot.capturedAt) {
|
|
6749
|
+
const q = result.qualitySnapshot;
|
|
6750
|
+
lines.push(
|
|
6751
|
+
"## Latest Quality Snapshot",
|
|
6752
|
+
"",
|
|
6753
|
+
`| Metric | Value |`,
|
|
6754
|
+
`|--------|-------|`
|
|
6755
|
+
);
|
|
6756
|
+
if (q.driftCount != null) lines.push(`| Drift issues | ${q.driftCount} (${q.driftErrors ?? 0} errors) |`);
|
|
6757
|
+
if (q.testPassCount != null) lines.push(`| Tests | ${q.testPassCount} pass / ${q.testFailCount ?? 0} fail |`);
|
|
6758
|
+
if (q.lintClean != null) lines.push(`| Lint | ${q.lintClean ? "clean" : "errors"} |`);
|
|
6759
|
+
if (q.typecheckClean != null) lines.push(`| Typecheck | ${q.typecheckClean ? "clean" : "errors"} |`);
|
|
6760
|
+
lines.push("");
|
|
6761
|
+
}
|
|
6762
|
+
lines.push(
|
|
6763
|
+
"## Activities",
|
|
6764
|
+
"",
|
|
6765
|
+
"| Time | Tool | Action | Model | Tokens | Cost | Duration |",
|
|
6766
|
+
"|------|------|--------|-------|--------|------|----------|"
|
|
6767
|
+
);
|
|
6768
|
+
for (const r of rows) {
|
|
6769
|
+
lines.push(
|
|
6770
|
+
`| ${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" : ""} |`
|
|
6771
|
+
);
|
|
6772
|
+
}
|
|
6773
|
+
content = lines.join("\n") + "\n";
|
|
6774
|
+
break;
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6777
|
+
const filePath = join16(dir, filename);
|
|
6778
|
+
writeFileSync7(filePath, content, "utf-8");
|
|
6779
|
+
return filePath;
|
|
6780
|
+
}
|
|
6781
|
+
function getGroupColumn(groupBy) {
|
|
6782
|
+
switch (groupBy) {
|
|
6783
|
+
case "model":
|
|
6784
|
+
return "model";
|
|
6785
|
+
case "task":
|
|
6786
|
+
return "task_id";
|
|
6787
|
+
case "phase":
|
|
6788
|
+
return "phase_id";
|
|
6789
|
+
case "tool":
|
|
6790
|
+
return "tool_name";
|
|
6791
|
+
case "day":
|
|
6792
|
+
return "DATE(recorded_at)";
|
|
6793
|
+
default:
|
|
6794
|
+
return "model";
|
|
6795
|
+
}
|
|
6796
|
+
}
|
|
6797
|
+
function getLatestQualitySnapshot(db, where, values) {
|
|
6798
|
+
const row = db.prepare(
|
|
6799
|
+
`SELECT
|
|
6800
|
+
drift_count, drift_errors,
|
|
6801
|
+
test_pass_count, test_fail_count,
|
|
6802
|
+
lint_clean, typecheck_clean,
|
|
6803
|
+
recorded_at
|
|
6804
|
+
FROM agent_activity
|
|
6805
|
+
${where ? where + " AND" : "WHERE"}
|
|
6806
|
+
(drift_count IS NOT NULL OR test_pass_count IS NOT NULL OR lint_clean IS NOT NULL OR typecheck_clean IS NOT NULL)
|
|
6807
|
+
ORDER BY recorded_at DESC
|
|
6808
|
+
LIMIT 1`
|
|
6809
|
+
).get(...values);
|
|
6810
|
+
if (!row) {
|
|
6811
|
+
return {
|
|
6812
|
+
driftCount: null,
|
|
6813
|
+
driftErrors: null,
|
|
6814
|
+
testPassCount: null,
|
|
6815
|
+
testFailCount: null,
|
|
6816
|
+
lintClean: null,
|
|
6817
|
+
typecheckClean: null,
|
|
6818
|
+
capturedAt: null
|
|
6819
|
+
};
|
|
6820
|
+
}
|
|
6821
|
+
return {
|
|
6822
|
+
driftCount: row.drift_count,
|
|
6823
|
+
driftErrors: row.drift_errors,
|
|
6824
|
+
testPassCount: row.test_pass_count,
|
|
6825
|
+
testFailCount: row.test_fail_count,
|
|
6826
|
+
lintClean: row.lint_clean != null ? row.lint_clean === 1 : null,
|
|
6827
|
+
typecheckClean: row.typecheck_clean != null ? row.typecheck_clean === 1 : null,
|
|
6828
|
+
capturedAt: row.recorded_at
|
|
6829
|
+
};
|
|
6830
|
+
}
|
|
6831
|
+
function safeParseJson3(raw) {
|
|
6832
|
+
try {
|
|
6833
|
+
return JSON.parse(raw);
|
|
6834
|
+
} catch {
|
|
6835
|
+
return {};
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
function esc(val) {
|
|
6839
|
+
if (val == null) return "";
|
|
6840
|
+
return val.replace(/\|/g, "\\|").replace(/\r?\n|\r/g, " ");
|
|
6841
|
+
}
|
|
6842
|
+
|
|
6376
6843
|
// src/git/helpers.ts
|
|
6377
6844
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
6378
6845
|
function resolveRef(projectRoot, since, db) {
|
|
@@ -6579,10 +7046,10 @@ ${output}`;
|
|
|
6579
7046
|
|
|
6580
7047
|
// src/roles/loader.ts
|
|
6581
7048
|
import { readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
|
|
6582
|
-
import { join as
|
|
7049
|
+
import { join as join17 } from "path";
|
|
6583
7050
|
import matter4 from "gray-matter";
|
|
6584
7051
|
function loadRoles(projectRoot) {
|
|
6585
|
-
const agentsDir =
|
|
7052
|
+
const agentsDir = join17(projectRoot, ".arcbridge", "agents");
|
|
6586
7053
|
const roles = [];
|
|
6587
7054
|
const errors = [];
|
|
6588
7055
|
let files;
|
|
@@ -6592,7 +7059,7 @@ function loadRoles(projectRoot) {
|
|
|
6592
7059
|
return { roles: [], errors: [`Agent directory not found: ${agentsDir}`] };
|
|
6593
7060
|
}
|
|
6594
7061
|
for (const file of files) {
|
|
6595
|
-
const filePath =
|
|
7062
|
+
const filePath = join17(agentsDir, file);
|
|
6596
7063
|
try {
|
|
6597
7064
|
const raw = readFileSync9(filePath, "utf-8");
|
|
6598
7065
|
const parsed = matter4(raw);
|
|
@@ -6619,7 +7086,7 @@ function loadRole(projectRoot, roleId) {
|
|
|
6619
7086
|
if (!/^[a-z0-9-]+$/.test(roleId)) {
|
|
6620
7087
|
return { role: null, error: `Invalid role ID: "${roleId}" (must be kebab-case)` };
|
|
6621
7088
|
}
|
|
6622
|
-
const filePath =
|
|
7089
|
+
const filePath = join17(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
|
|
6623
7090
|
try {
|
|
6624
7091
|
const raw = readFileSync9(filePath, "utf-8");
|
|
6625
7092
|
const parsed = matter4(raw);
|
|
@@ -6661,6 +7128,7 @@ export {
|
|
|
6661
7128
|
detectDrift,
|
|
6662
7129
|
detectProjectLanguage,
|
|
6663
7130
|
discoverDotnetServices,
|
|
7131
|
+
exportMetrics,
|
|
6664
7132
|
generateAgentRoles,
|
|
6665
7133
|
generateArc42,
|
|
6666
7134
|
generateConfig,
|
|
@@ -6669,23 +7137,28 @@ export {
|
|
|
6669
7137
|
generateSyncFiles,
|
|
6670
7138
|
getChangedFiles,
|
|
6671
7139
|
getHeadSha,
|
|
7140
|
+
getSessionTotals,
|
|
6672
7141
|
getUncommittedChanges,
|
|
6673
7142
|
indexPackageDependencies,
|
|
6674
7143
|
indexProject,
|
|
6675
7144
|
inferTaskStatuses,
|
|
6676
7145
|
initializeSchema,
|
|
7146
|
+
insertActivity,
|
|
6677
7147
|
loadConfig,
|
|
6678
7148
|
loadRole,
|
|
6679
7149
|
loadRoles,
|
|
6680
7150
|
migrate,
|
|
6681
7151
|
openDatabase,
|
|
6682
7152
|
openMemoryDatabase,
|
|
7153
|
+
queryMetrics,
|
|
6683
7154
|
refreshFromDocs,
|
|
6684
7155
|
resolveRef,
|
|
6685
7156
|
setSyncCommit,
|
|
7157
|
+
suppressSqliteWarning,
|
|
6686
7158
|
syncPhaseToYaml,
|
|
6687
7159
|
syncScenarioToYaml,
|
|
6688
7160
|
syncTaskToYaml,
|
|
7161
|
+
transaction,
|
|
6689
7162
|
verifyScenarios,
|
|
6690
7163
|
writeDriftLog
|
|
6691
7164
|
};
|