token-goat 2.6.36 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -8
- package/SECURITY.md +46 -17
- package/dist/{token-goat-chunk-UFOVM7ZN.mjs → token-goat-chunk-2G6RAB4G.mjs} +1391 -378
- package/dist/token-goat-chunk-324QOJYZ.mjs +91 -0
- package/dist/token-goat-chunk-5CVKO3DA.mjs +185 -0
- package/dist/{token-goat-chunk-65BKISIS.mjs → token-goat-chunk-73MF6YWW.mjs} +4 -4
- package/dist/{token-goat-chunk-MGOUYAA2.mjs → token-goat-chunk-7JDXDERZ.mjs} +1 -1
- package/dist/{token-goat-hook-chunk-BDR6C6IE.mjs → token-goat-chunk-AOBZUFNJ.mjs} +318 -51
- package/dist/{token-goat-chunk-V465YKOR.mjs → token-goat-chunk-ELDJRLHZ.mjs} +492 -28
- package/dist/{token-goat-chunk-CNDOJ3ZP.mjs → token-goat-chunk-I6TOUPLP.mjs} +2 -2
- package/dist/token-goat-chunk-R4SR7MQY.mjs +486 -0
- package/dist/{token-goat-chunk-IYTVE6KN.mjs → token-goat-chunk-RMDQFTQD.mjs} +226 -150
- package/dist/{token-goat-chunk-KYFJC37X.mjs → token-goat-chunk-VBXBLGTO.mjs} +792 -144
- package/dist/{token-goat-chunk-DG53MVNJ.mjs → token-goat-chunk-WN5T5EW5.mjs} +212 -212
- package/dist/{token-goat-chunk-VYMGEVZS.mjs → token-goat-chunk-XEDQH5DA.mjs} +317 -11
- package/dist/{token-goat-chunk-LN6OUHTV.mjs → token-goat-chunk-Y2SYNH3S.mjs} +5 -5
- package/dist/token-goat-chunk-YUGNM3KL.mjs +23 -0
- package/dist/token-goat-hook.mjs +7 -7
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +12 -8
- package/dist/token-goat-chunk-FRTBMRP7.mjs +0 -10048
- package/dist/token-goat-hook-chunk-3QYSN4QV.mjs +0 -14764
- package/dist/token-goat-hook-chunk-3ZDBWJDF.mjs +0 -13659
- package/dist/token-goat-hook-chunk-5UH54CW6.mjs +0 -912
- package/dist/token-goat-hook-chunk-6ODM3MP7.mjs +0 -706
- package/dist/token-goat-hook-chunk-A77A26A7.mjs +0 -18184
- package/dist/token-goat-hook-chunk-BUOCULAM.mjs +0 -29
- package/dist/token-goat-hook-chunk-C6GIABOX.mjs +0 -15971
- package/dist/token-goat-hook-chunk-E257IGSN.mjs +0 -153
- package/dist/token-goat-hook-chunk-MW5HPEGD.mjs +0 -10411
- package/dist/token-goat-hook-chunk-QSCYNJ2B.mjs +0 -23
- package/dist/token-goat-hook-chunk-RFRLWOQH.mjs +0 -11
- package/dist/token-goat-hook-chunk-XUMIVYEN.mjs +0 -109
- package/dist/token-goat-hook-chunk-Y2WHX2P3.mjs +0 -6463
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
function resolveVersion() {
|
|
10
10
|
if (true) {
|
|
11
|
-
return "2.
|
|
11
|
+
return "2.8.1";
|
|
12
12
|
}
|
|
13
13
|
const require2 = createRequire(import.meta.url);
|
|
14
14
|
const pkg = require2("../package.json");
|
|
@@ -418,14 +418,14 @@ function ensureDirSync(dir) {
|
|
|
418
418
|
}
|
|
419
419
|
function withRetryOnLock(fn) {
|
|
420
420
|
let lastErr;
|
|
421
|
-
for (let
|
|
421
|
+
for (let attempt2 = 1; attempt2 <= 5; attempt2++) {
|
|
422
422
|
try {
|
|
423
423
|
fn();
|
|
424
424
|
return;
|
|
425
425
|
} catch (err) {
|
|
426
426
|
lastErr = err;
|
|
427
|
-
if (!isRetryable(err) ||
|
|
428
|
-
sleepSync(50 *
|
|
427
|
+
if (!isRetryable(err) || attempt2 === 5) throw err;
|
|
428
|
+
sleepSync(50 * attempt2);
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
throw lastErr;
|
|
@@ -559,7 +559,7 @@ function withFileLock(lockPath, fn, opts = {}) {
|
|
|
559
559
|
const staleMs = opts.staleMs ?? LOCK_STALE_MS;
|
|
560
560
|
const token = `${process.pid}:${process.hrtime.bigint().toString()}`;
|
|
561
561
|
const deadline = Date.now() + waitMs;
|
|
562
|
-
let
|
|
562
|
+
let attempt2 = 0;
|
|
563
563
|
for (; ; ) {
|
|
564
564
|
try {
|
|
565
565
|
writeFileSync(lockPath, token, { flag: "wx" });
|
|
@@ -582,7 +582,7 @@ function withFileLock(lockPath, fn, opts = {}) {
|
|
|
582
582
|
continue;
|
|
583
583
|
}
|
|
584
584
|
if (Date.now() >= deadline) return void 0;
|
|
585
|
-
sleepSync(Math.min(20 * ++
|
|
585
|
+
sleepSync(Math.min(20 * ++attempt2, 200));
|
|
586
586
|
}
|
|
587
587
|
const heartbeat = startHeartbeat(lockPath, token, staleMs);
|
|
588
588
|
try {
|
|
@@ -3700,17 +3700,21 @@ function isInsideStringLiteral(line, index, from = 0) {
|
|
|
3700
3700
|
}
|
|
3701
3701
|
return openQuote !== null;
|
|
3702
3702
|
}
|
|
3703
|
+
function nextBlockCommentOpen(line, from) {
|
|
3704
|
+
const lineCommentIdx = lineCommentStartIndex(line, ["//"], from);
|
|
3705
|
+
let open = line.indexOf("/*", from);
|
|
3706
|
+
while (open !== -1 && (isInsideStringLiteral(line, open, from) || lineCommentIdx !== -1 && open >= lineCommentIdx)) {
|
|
3707
|
+
open = line.indexOf("/*", open + 1);
|
|
3708
|
+
}
|
|
3709
|
+
return open;
|
|
3710
|
+
}
|
|
3703
3711
|
function stripBlockCommentSpan(line, inComment) {
|
|
3704
3712
|
let code = "";
|
|
3705
3713
|
let j = 0;
|
|
3706
3714
|
let comment = inComment;
|
|
3707
3715
|
while (j < line.length) {
|
|
3708
3716
|
if (!comment) {
|
|
3709
|
-
const
|
|
3710
|
-
let open = line.indexOf("/*", j);
|
|
3711
|
-
while (open !== -1 && (isInsideStringLiteral(line, open, j) || lineCommentIdx !== -1 && open >= lineCommentIdx)) {
|
|
3712
|
-
open = line.indexOf("/*", open + 1);
|
|
3713
|
-
}
|
|
3717
|
+
const open = nextBlockCommentOpen(line, j);
|
|
3714
3718
|
if (open === -1) {
|
|
3715
3719
|
code += line.slice(j);
|
|
3716
3720
|
break;
|
|
@@ -3744,11 +3748,7 @@ function stripNestedBlockCommentSpan(line, depth) {
|
|
|
3744
3748
|
let d = depth;
|
|
3745
3749
|
while (j < line.length) {
|
|
3746
3750
|
if (d === 0) {
|
|
3747
|
-
const
|
|
3748
|
-
let open = line.indexOf("/*", j);
|
|
3749
|
-
while (open !== -1 && (isInsideStringLiteral(line, open, j) || lineCommentIdx !== -1 && open >= lineCommentIdx)) {
|
|
3750
|
-
open = line.indexOf("/*", open + 1);
|
|
3751
|
-
}
|
|
3751
|
+
const open = nextBlockCommentOpen(line, j);
|
|
3752
3752
|
if (open === -1) {
|
|
3753
3753
|
code += line.slice(j);
|
|
3754
3754
|
break;
|
|
@@ -4454,11 +4454,328 @@ function redactIfDotenv(filePath, text) {
|
|
|
4454
4454
|
}
|
|
4455
4455
|
|
|
4456
4456
|
// src/db.ts
|
|
4457
|
+
import * as fs7 from "node:fs";
|
|
4458
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
4459
|
+
import * as path7 from "node:path";
|
|
4460
|
+
|
|
4461
|
+
// src/sqlite_driver.ts
|
|
4457
4462
|
import * as fs6 from "node:fs";
|
|
4458
4463
|
import { createRequire as createRequire2 } from "node:module";
|
|
4459
|
-
import * as path7 from "node:path";
|
|
4460
|
-
import Database from "better-sqlite3";
|
|
4461
4464
|
var _require = createRequire2(import.meta.url);
|
|
4465
|
+
function suppressSqliteExperimentalWarning() {
|
|
4466
|
+
const original = process.emit;
|
|
4467
|
+
let armed = true;
|
|
4468
|
+
const restore = () => {
|
|
4469
|
+
if (!armed) return;
|
|
4470
|
+
armed = false;
|
|
4471
|
+
process.emit = original;
|
|
4472
|
+
};
|
|
4473
|
+
process.emit = function patched(name, ...rest) {
|
|
4474
|
+
const data = rest[0];
|
|
4475
|
+
if (armed && name === "warning" && data instanceof Error && data.name === "ExperimentalWarning" && /sqlite/i.test(data.message)) {
|
|
4476
|
+
restore();
|
|
4477
|
+
return false;
|
|
4478
|
+
}
|
|
4479
|
+
return original.call(this, name, ...rest);
|
|
4480
|
+
};
|
|
4481
|
+
setImmediate(restore);
|
|
4482
|
+
return restore;
|
|
4483
|
+
}
|
|
4484
|
+
var restoreWarnings = suppressSqliteExperimentalWarning();
|
|
4485
|
+
var nodeSqlite;
|
|
4486
|
+
try {
|
|
4487
|
+
nodeSqlite = _require("node:sqlite");
|
|
4488
|
+
} catch (e) {
|
|
4489
|
+
restoreWarnings();
|
|
4490
|
+
throw e;
|
|
4491
|
+
}
|
|
4492
|
+
var { DatabaseSync } = nodeSqlite;
|
|
4493
|
+
var SQLITE_PRIMARY_CODES = [
|
|
4494
|
+
"SQLITE_OK",
|
|
4495
|
+
"SQLITE_ERROR",
|
|
4496
|
+
"SQLITE_INTERNAL",
|
|
4497
|
+
"SQLITE_PERM",
|
|
4498
|
+
"SQLITE_ABORT",
|
|
4499
|
+
"SQLITE_BUSY",
|
|
4500
|
+
"SQLITE_LOCKED",
|
|
4501
|
+
"SQLITE_NOMEM",
|
|
4502
|
+
"SQLITE_READONLY",
|
|
4503
|
+
"SQLITE_INTERRUPT",
|
|
4504
|
+
"SQLITE_IOERR",
|
|
4505
|
+
"SQLITE_CORRUPT",
|
|
4506
|
+
"SQLITE_NOTFOUND",
|
|
4507
|
+
"SQLITE_FULL",
|
|
4508
|
+
"SQLITE_CANTOPEN",
|
|
4509
|
+
"SQLITE_PROTOCOL",
|
|
4510
|
+
"SQLITE_EMPTY",
|
|
4511
|
+
"SQLITE_SCHEMA",
|
|
4512
|
+
"SQLITE_TOOBIG",
|
|
4513
|
+
"SQLITE_CONSTRAINT",
|
|
4514
|
+
"SQLITE_MISMATCH",
|
|
4515
|
+
"SQLITE_MISUSE",
|
|
4516
|
+
"SQLITE_NOLFS",
|
|
4517
|
+
"SQLITE_AUTH",
|
|
4518
|
+
"SQLITE_FORMAT",
|
|
4519
|
+
"SQLITE_RANGE",
|
|
4520
|
+
"SQLITE_NOTADB",
|
|
4521
|
+
"SQLITE_NOTICE",
|
|
4522
|
+
"SQLITE_WARNING"
|
|
4523
|
+
];
|
|
4524
|
+
var SQLITE_EXTENDED_SUFFIXES = {
|
|
4525
|
+
SQLITE_OK: ["LOAD_PERMANENTLY", "SYMLINK"],
|
|
4526
|
+
SQLITE_ERROR: ["MISSING_COLLSEQ", "RETRY", "SNAPSHOT"],
|
|
4527
|
+
SQLITE_ABORT: [null, "ROLLBACK"],
|
|
4528
|
+
SQLITE_BUSY: ["RECOVERY", "SNAPSHOT", "TIMEOUT"],
|
|
4529
|
+
SQLITE_LOCKED: ["SHAREDCACHE", "VTAB"],
|
|
4530
|
+
SQLITE_READONLY: ["RECOVERY", "CANTLOCK", "ROLLBACK", "DBMOVED", "CANTINIT", "DIRECTORY"],
|
|
4531
|
+
SQLITE_IOERR: [
|
|
4532
|
+
"READ",
|
|
4533
|
+
"SHORT_READ",
|
|
4534
|
+
"WRITE",
|
|
4535
|
+
"FSYNC",
|
|
4536
|
+
"DIR_FSYNC",
|
|
4537
|
+
"TRUNCATE",
|
|
4538
|
+
"FSTAT",
|
|
4539
|
+
"UNLOCK",
|
|
4540
|
+
"RDLOCK",
|
|
4541
|
+
"DELETE",
|
|
4542
|
+
"BLOCKED",
|
|
4543
|
+
"NOMEM",
|
|
4544
|
+
"ACCESS",
|
|
4545
|
+
"CHECKRESERVEDLOCK",
|
|
4546
|
+
"LOCK",
|
|
4547
|
+
"CLOSE",
|
|
4548
|
+
"DIR_CLOSE",
|
|
4549
|
+
"SHMOPEN",
|
|
4550
|
+
"SHMSIZE",
|
|
4551
|
+
"SHMLOCK",
|
|
4552
|
+
"SHMMAP",
|
|
4553
|
+
"SEEK",
|
|
4554
|
+
"DELETE_NOENT",
|
|
4555
|
+
"MMAP",
|
|
4556
|
+
"GETTEMPPATH",
|
|
4557
|
+
"CONVPATH",
|
|
4558
|
+
"VNODE",
|
|
4559
|
+
"AUTH",
|
|
4560
|
+
"BEGIN_ATOMIC",
|
|
4561
|
+
"COMMIT_ATOMIC",
|
|
4562
|
+
"ROLLBACK_ATOMIC",
|
|
4563
|
+
"DATA",
|
|
4564
|
+
"CORRUPTFS",
|
|
4565
|
+
"IN_PAGE"
|
|
4566
|
+
],
|
|
4567
|
+
SQLITE_CORRUPT: ["VTAB", "SEQUENCE", "INDEX"],
|
|
4568
|
+
SQLITE_CANTOPEN: ["NOTEMPDIR", "ISDIR", "FULLPATH", "CONVPATH", "DIRTYWAL", "SYMLINK"],
|
|
4569
|
+
SQLITE_CONSTRAINT: [
|
|
4570
|
+
"CHECK",
|
|
4571
|
+
"COMMITHOOK",
|
|
4572
|
+
"FOREIGNKEY",
|
|
4573
|
+
"FUNCTION",
|
|
4574
|
+
"NOTNULL",
|
|
4575
|
+
"PRIMARYKEY",
|
|
4576
|
+
"TRIGGER",
|
|
4577
|
+
"UNIQUE",
|
|
4578
|
+
"VTAB",
|
|
4579
|
+
"ROWID",
|
|
4580
|
+
"PINNED",
|
|
4581
|
+
"DATATYPE"
|
|
4582
|
+
],
|
|
4583
|
+
SQLITE_AUTH: ["USER"],
|
|
4584
|
+
SQLITE_NOTICE: ["RECOVER_WAL", "RECOVER_ROLLBACK", "RBU"],
|
|
4585
|
+
SQLITE_WARNING: ["AUTOINDEX"]
|
|
4586
|
+
};
|
|
4587
|
+
function sqliteResultCodeName(errcode) {
|
|
4588
|
+
if (!Number.isInteger(errcode) || errcode < 0) return "ERR_SQLITE_ERROR";
|
|
4589
|
+
if (errcode === 100) return "SQLITE_ROW";
|
|
4590
|
+
if (errcode === 101) return "SQLITE_DONE";
|
|
4591
|
+
const primary = SQLITE_PRIMARY_CODES[errcode & 255];
|
|
4592
|
+
if (primary === void 0) return "ERR_SQLITE_ERROR";
|
|
4593
|
+
const subcode = errcode >> 8;
|
|
4594
|
+
if (subcode === 0) return primary;
|
|
4595
|
+
const suffix = SQLITE_EXTENDED_SUFFIXES[primary]?.[subcode - 1];
|
|
4596
|
+
return suffix === void 0 || suffix === null ? primary : `${primary}_${suffix}`;
|
|
4597
|
+
}
|
|
4598
|
+
function attempt(fn) {
|
|
4599
|
+
try {
|
|
4600
|
+
return fn();
|
|
4601
|
+
} catch (e) {
|
|
4602
|
+
const err = e;
|
|
4603
|
+
if (err.code === "ERR_SQLITE_ERROR" && typeof err.errcode === "number") {
|
|
4604
|
+
err.code = sqliteResultCodeName(err.errcode);
|
|
4605
|
+
}
|
|
4606
|
+
throw e;
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
var Statement = class {
|
|
4610
|
+
#stmt;
|
|
4611
|
+
#pluck = false;
|
|
4612
|
+
constructor(stmt) {
|
|
4613
|
+
this.#stmt = stmt;
|
|
4614
|
+
}
|
|
4615
|
+
get source() {
|
|
4616
|
+
return this.#stmt.sourceSQL;
|
|
4617
|
+
}
|
|
4618
|
+
/**
|
|
4619
|
+
* better-sqlite3's `reader` flag: does this statement return rows?
|
|
4620
|
+
*
|
|
4621
|
+
* `node:sqlite` has no equivalent, so it is derived from the prepared statement's own column
|
|
4622
|
+
* count -- SQLite gives a row-producing statement its result columns at prepare time and gives a
|
|
4623
|
+
* non-producing one none. That is a derivation, and this is the third defence-in-depth layer in
|
|
4624
|
+
* `sqlite_query.ts`'s read-only guard, so it is not taken on faith: the driver tests run both
|
|
4625
|
+
* libraries side by side over SELECT, a CTE, VALUES, EXPLAIN, an empty-result SELECT, INSERT,
|
|
4626
|
+
* UPDATE, DELETE, CREATE, a reading PRAGMA and an assigning PRAGMA, and require every verdict to
|
|
4627
|
+
* agree. If a future SQLite statement form ever breaks the equivalence, that test fails rather
|
|
4628
|
+
* than the guard quietly weakening.
|
|
4629
|
+
*/
|
|
4630
|
+
get reader() {
|
|
4631
|
+
return attempt(() => this.#stmt.columns()).length > 0;
|
|
4632
|
+
}
|
|
4633
|
+
// A plucked row is "the first column", which for an object row means the first *inserted* key. V8 preserves insertion order for string keys, and node:sqlite builds the row by walking the result columns left to right, so Object.values()[0] is the leftmost column -- not the column named in the SQL text, the same rule better-sqlite3 applies.
|
|
4634
|
+
#shape(row) {
|
|
4635
|
+
if (!this.#pluck || row === void 0 || row === null) return row;
|
|
4636
|
+
const values = Object.values(row);
|
|
4637
|
+
return values.length === 0 ? void 0 : values[0];
|
|
4638
|
+
}
|
|
4639
|
+
get(...params) {
|
|
4640
|
+
return this.#shape(attempt(() => this.#stmt.get(...params)));
|
|
4641
|
+
}
|
|
4642
|
+
all(...params) {
|
|
4643
|
+
const rows = attempt(() => this.#stmt.all(...params));
|
|
4644
|
+
return this.#pluck ? rows.map((r) => this.#shape(r)) : rows;
|
|
4645
|
+
}
|
|
4646
|
+
run(...params) {
|
|
4647
|
+
return attempt(() => this.#stmt.run(...params));
|
|
4648
|
+
}
|
|
4649
|
+
// Wrapped rather than returned directly so pluck applies lazily, one row at a time: the whole point of iterate() here is that sqlite_query.ts caps the row count without buffering the rest, and mapping the iterator through .all() first would defeat that.
|
|
4650
|
+
*iterate(...params) {
|
|
4651
|
+
const rows = attempt(() => this.#stmt.iterate(...params))[Symbol.iterator]();
|
|
4652
|
+
for (; ; ) {
|
|
4653
|
+
const next = attempt(() => rows.next());
|
|
4654
|
+
if (next.done === true) return;
|
|
4655
|
+
yield this.#shape(next.value);
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
pluck(toggle = true) {
|
|
4659
|
+
this.#pluck = toggle;
|
|
4660
|
+
return this;
|
|
4661
|
+
}
|
|
4662
|
+
safeIntegers(toggle = true) {
|
|
4663
|
+
this.#stmt.setReadBigInts(toggle);
|
|
4664
|
+
return this;
|
|
4665
|
+
}
|
|
4666
|
+
columns() {
|
|
4667
|
+
return attempt(() => this.#stmt.columns());
|
|
4668
|
+
}
|
|
4669
|
+
};
|
|
4670
|
+
var Database = class {
|
|
4671
|
+
#db;
|
|
4672
|
+
#path;
|
|
4673
|
+
#readonly;
|
|
4674
|
+
#savepoints = 0;
|
|
4675
|
+
constructor(dbPath, options = {}) {
|
|
4676
|
+
const wantsExisting = options.readonly === true || options.fileMustExist === true;
|
|
4677
|
+
if (wantsExisting && dbPath !== ":memory:" && !fs6.existsSync(dbPath)) {
|
|
4678
|
+
throw new Error("unable to open database file");
|
|
4679
|
+
}
|
|
4680
|
+
this.#db = attempt(() => new DatabaseSync(dbPath, {
|
|
4681
|
+
readOnly: options.readonly === true,
|
|
4682
|
+
// sqlite-vec is loaded through db.loadExtension by initConnection, which node:sqlite refuses unless the connection opted in at construction. Harmless when no extension is ever loaded.
|
|
4683
|
+
allowExtension: true,
|
|
4684
|
+
// better-sqlite3 opens every connection with busy_timeout at 5000ms; node:sqlite opens at 0, so a connection that named no timeout would silently go from five seconds of patience to none. db.ts overrides this to 15000 in initConnection, but sqlite_query.ts opens a user's arbitrary database readonly and takes whatever the default is -- which would have turned ordinary contention with another writer into an immediate "database is locked".
|
|
4685
|
+
timeout: options.timeout ?? 5e3
|
|
4686
|
+
}));
|
|
4687
|
+
this.#path = dbPath;
|
|
4688
|
+
this.#readonly = options.readonly === true;
|
|
4689
|
+
}
|
|
4690
|
+
get open() {
|
|
4691
|
+
return this.#db.isOpen;
|
|
4692
|
+
}
|
|
4693
|
+
get inTransaction() {
|
|
4694
|
+
return this.#db.isTransaction;
|
|
4695
|
+
}
|
|
4696
|
+
get readonly() {
|
|
4697
|
+
return this.#readonly;
|
|
4698
|
+
}
|
|
4699
|
+
get name() {
|
|
4700
|
+
return this.#path;
|
|
4701
|
+
}
|
|
4702
|
+
prepare(sql) {
|
|
4703
|
+
return new Statement(attempt(() => this.#db.prepare(sql)));
|
|
4704
|
+
}
|
|
4705
|
+
exec(sql) {
|
|
4706
|
+
attempt(() => this.#db.exec(sql));
|
|
4707
|
+
}
|
|
4708
|
+
pragma(source, options = {}) {
|
|
4709
|
+
const rows = attempt(() => this.#db.prepare(`PRAGMA ${source}`).all());
|
|
4710
|
+
if (options.simple !== true) return rows;
|
|
4711
|
+
const first = rows[0];
|
|
4712
|
+
if (first === void 0) return void 0;
|
|
4713
|
+
const values = Object.values(first);
|
|
4714
|
+
return values.length === 0 ? void 0 : values[0];
|
|
4715
|
+
}
|
|
4716
|
+
function(name, options, fn) {
|
|
4717
|
+
attempt(() => this.#db.function(name, options, fn));
|
|
4718
|
+
}
|
|
4719
|
+
loadExtension(extensionPath) {
|
|
4720
|
+
attempt(() => this.#db.loadExtension(extensionPath));
|
|
4721
|
+
}
|
|
4722
|
+
close() {
|
|
4723
|
+
attempt(() => this.#db.close());
|
|
4724
|
+
}
|
|
4725
|
+
/**
|
|
4726
|
+
* Wrap `fn` so it runs inside a transaction, committing on return and rolling back on throw.
|
|
4727
|
+
*
|
|
4728
|
+
* Nesting uses SAVEPOINT, which is what makes it safe for a transactional helper to call another
|
|
4729
|
+
* one: an inner `BEGIN` would throw ("cannot start a transaction within a transaction"), an inner
|
|
4730
|
+
* SAVEPOINT composes. Whether we are nested is read from SQLite via `isTransaction` rather than
|
|
4731
|
+
* tracked in a counter here, so a transaction some other code path opened still nests correctly.
|
|
4732
|
+
*
|
|
4733
|
+
* The rollback is best-effort and never replaces the caller's error: if the ROLLBACK itself fails
|
|
4734
|
+
* -- the connection died, the transaction was already unwound -- the original failure is still
|
|
4735
|
+
* what propagates, because that is the one that explains what went wrong.
|
|
4736
|
+
*/
|
|
4737
|
+
transaction(fn) {
|
|
4738
|
+
const build = (beginSql) => (...args) => {
|
|
4739
|
+
if (this.#db.isTransaction) {
|
|
4740
|
+
const name = `tg_sp_${this.#savepoints++}`;
|
|
4741
|
+
attempt(() => this.#db.exec(`SAVEPOINT ${name}`));
|
|
4742
|
+
try {
|
|
4743
|
+
const result = fn(...args);
|
|
4744
|
+
attempt(() => this.#db.exec(`RELEASE ${name}`));
|
|
4745
|
+
return result;
|
|
4746
|
+
} catch (e) {
|
|
4747
|
+
try {
|
|
4748
|
+
attempt(() => this.#db.exec(`ROLLBACK TO ${name}`));
|
|
4749
|
+
attempt(() => this.#db.exec(`RELEASE ${name}`));
|
|
4750
|
+
} catch {
|
|
4751
|
+
}
|
|
4752
|
+
throw e;
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
attempt(() => this.#db.exec(beginSql));
|
|
4756
|
+
try {
|
|
4757
|
+
const result = fn(...args);
|
|
4758
|
+
attempt(() => this.#db.exec("COMMIT"));
|
|
4759
|
+
return result;
|
|
4760
|
+
} catch (e) {
|
|
4761
|
+
try {
|
|
4762
|
+
attempt(() => this.#db.exec("ROLLBACK"));
|
|
4763
|
+
} catch {
|
|
4764
|
+
}
|
|
4765
|
+
throw e;
|
|
4766
|
+
}
|
|
4767
|
+
};
|
|
4768
|
+
const wrapped = build("BEGIN");
|
|
4769
|
+
wrapped.default = wrapped;
|
|
4770
|
+
wrapped.deferred = build("BEGIN");
|
|
4771
|
+
wrapped.immediate = build("BEGIN IMMEDIATE");
|
|
4772
|
+
wrapped.exclusive = build("BEGIN EXCLUSIVE");
|
|
4773
|
+
return wrapped;
|
|
4774
|
+
}
|
|
4775
|
+
};
|
|
4776
|
+
|
|
4777
|
+
// src/db.ts
|
|
4778
|
+
var _require2 = createRequire3(import.meta.url);
|
|
4462
4779
|
var _connections = /* @__PURE__ */ new Map();
|
|
4463
4780
|
var SCHEMA_SQL = `
|
|
4464
4781
|
CREATE TABLE IF NOT EXISTS files (
|
|
@@ -4658,6 +4975,26 @@ CREATE TABLE IF NOT EXISTS skill_version_snapshots (
|
|
|
4658
4975
|
loaded_commands_json TEXT NOT NULL,
|
|
4659
4976
|
notified_at REAL
|
|
4660
4977
|
);
|
|
4978
|
+
|
|
4979
|
+
-- Which embedding stack produced the vectors currently in chunk_vectors -- the model, its
|
|
4980
|
+
-- pinned revision, and the inference runtime (see embeddingProvenance in embeddings.ts). The
|
|
4981
|
+
-- vector table itself is vec0(rowid, embedding) and has nowhere to record this, so without
|
|
4982
|
+
-- this row a database that was embedded by one stack and then added to by another holds two
|
|
4983
|
+
-- incomparable sets of vectors under one index, with nothing able to tell them apart. That is
|
|
4984
|
+
-- not hypothetical: global.db is machine-wide across every project on the machine (see
|
|
4985
|
+
-- constants.ts), so upgrading the runtime, or changing the model or its pinned revision, mixes
|
|
4986
|
+
-- old and new vectors for as long as the old files go untouched. Measured drift between two
|
|
4987
|
+
-- runtime versions of the same quantized model is 0.9925-0.9978 cosine on the final vector --
|
|
4988
|
+
-- small, but enough to reorder near-ties, and invisible to every existing check.
|
|
4989
|
+
--
|
|
4990
|
+
-- Single-row by construction (the CHECK pins the key), because there is exactly one vector
|
|
4991
|
+
-- table per database. An EMPTY table on a database that already holds chunks means the vectors
|
|
4992
|
+
-- predate this stamp and their provenance is unknowable -- see ensureEmbeddingProvenance, which
|
|
4993
|
+
-- treats that exactly like a mismatch. That is what makes this work without a migration step.
|
|
4994
|
+
CREATE TABLE IF NOT EXISTS embedding_provenance (
|
|
4995
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
4996
|
+
provenance TEXT NOT NULL
|
|
4997
|
+
);
|
|
4661
4998
|
`;
|
|
4662
4999
|
var FTS_SQL = `
|
|
4663
5000
|
CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(
|
|
@@ -4708,7 +5045,7 @@ CREATE TRIGGER IF NOT EXISTS cache_recall_au AFTER UPDATE ON cache_recall BEGIN
|
|
|
4708
5045
|
VALUES (new.row_id, new.label, new.content);
|
|
4709
5046
|
END;
|
|
4710
5047
|
`;
|
|
4711
|
-
var SCHEMA_VERSION =
|
|
5048
|
+
var SCHEMA_VERSION = 12;
|
|
4712
5049
|
function alterTableIdempotent(conn, sql) {
|
|
4713
5050
|
try {
|
|
4714
5051
|
conn.exec(sql);
|
|
@@ -4802,7 +5139,7 @@ function initConnection(conn) {
|
|
|
4802
5139
|
} catch {
|
|
4803
5140
|
}
|
|
4804
5141
|
try {
|
|
4805
|
-
const sqliteVec =
|
|
5142
|
+
const sqliteVec = _require2("sqlite-vec");
|
|
4806
5143
|
sqliteVec.load(conn);
|
|
4807
5144
|
conn.exec(
|
|
4808
5145
|
`CREATE VIRTUAL TABLE IF NOT EXISTS chunk_vectors USING vec0(
|
|
@@ -4833,7 +5170,7 @@ function getDb(dbPath) {
|
|
|
4833
5170
|
try {
|
|
4834
5171
|
ensureDirSync(dir);
|
|
4835
5172
|
} catch (e) {
|
|
4836
|
-
if (e.code !== "EEXIST" || !
|
|
5173
|
+
if (e.code !== "EEXIST" || !fs7.existsSync(dir)) throw e;
|
|
4837
5174
|
}
|
|
4838
5175
|
const conn = new Database(resolved);
|
|
4839
5176
|
try {
|
|
@@ -5464,6 +5801,30 @@ function _renderByCommandSection(stats) {
|
|
|
5464
5801
|
}
|
|
5465
5802
|
return lines;
|
|
5466
5803
|
}
|
|
5804
|
+
function _renderByHarnessSection(stats) {
|
|
5805
|
+
if (!stats.by_harness || stats.by_harness.length < 2) {
|
|
5806
|
+
return [];
|
|
5807
|
+
}
|
|
5808
|
+
const lines = [..._sectionHeader("By harness"), _tableHeader("harness")];
|
|
5809
|
+
const { grossBytes, shareBytesDenom, shareTokensDenom } = _computeShareDenominators(stats.by_harness);
|
|
5810
|
+
function share(h) {
|
|
5811
|
+
return _absShare(h.bytes, h.tokens, shareBytesDenom, shareTokensDenom);
|
|
5812
|
+
}
|
|
5813
|
+
for (const h of [...stats.by_harness].sort((a, b) => share(b) - share(a))) {
|
|
5814
|
+
lines.push(
|
|
5815
|
+
_tableRow({
|
|
5816
|
+
name: h.harness,
|
|
5817
|
+
fraction: _barFraction(h.bytes, grossBytes),
|
|
5818
|
+
bytes: h.bytes,
|
|
5819
|
+
tokens: h.tokens,
|
|
5820
|
+
events: h.events,
|
|
5821
|
+
share: share(h),
|
|
5822
|
+
nameColor: C.TEXT_PRIMARY
|
|
5823
|
+
})
|
|
5824
|
+
);
|
|
5825
|
+
}
|
|
5826
|
+
return lines;
|
|
5827
|
+
}
|
|
5467
5828
|
function _renderByDaySection(stats) {
|
|
5468
5829
|
if (stats.by_day.length === 0) {
|
|
5469
5830
|
return [];
|
|
@@ -5528,7 +5889,8 @@ function _renderInsightsSection(stats) {
|
|
|
5528
5889
|
function dim(s) {
|
|
5529
5890
|
return `${fg(...C.TEXT_MUTED)}${s}${RESET}`;
|
|
5530
5891
|
}
|
|
5531
|
-
const
|
|
5892
|
+
const savingKinds = stats.by_kind.filter((k) => k.bytes > 0);
|
|
5893
|
+
const topKind = savingKinds.reduce((max, k) => k.bytes > (max?.bytes || -Infinity) ? k : max, savingKinds[0]);
|
|
5532
5894
|
if (topKind) {
|
|
5533
5895
|
const share = stats.totals.bytes > 0 ? topKind.bytes / stats.totals.bytes : 0;
|
|
5534
5896
|
lines.push(
|
|
@@ -5541,7 +5903,7 @@ function _renderInsightsSection(stats) {
|
|
|
5541
5903
|
`${_M}${bullet} ${dim(_STATS_MESSAGES.insights.mostActive)}${fg(...C.TEXT_PRIMARY)}${topDay.date}${RESET}${dim(" \u2014 ")}${topDay.events.toLocaleString()} events, ${_fmtBytes(topDay.bytes)}${dim(" saved")}`
|
|
5542
5904
|
);
|
|
5543
5905
|
}
|
|
5544
|
-
const tokenKinds = stats.by_kind.filter((k) => !k.bytes_mode_only);
|
|
5906
|
+
const tokenKinds = stats.by_kind.filter((k) => !k.bytes_mode_only && k.tokens > 0);
|
|
5545
5907
|
const topToken = tokenKinds.reduce((max, k) => k.tokens > (max?.tokens || -Infinity) ? k : max, tokenKinds[0]);
|
|
5546
5908
|
if (topToken) {
|
|
5547
5909
|
lines.push(
|
|
@@ -5585,6 +5947,7 @@ function renderStats(stats, opts) {
|
|
|
5585
5947
|
_renderByKindSection(stats),
|
|
5586
5948
|
_renderBySourceSection(stats),
|
|
5587
5949
|
_renderByCommandSection(stats),
|
|
5950
|
+
_renderByHarnessSection(stats),
|
|
5588
5951
|
_renderByDaySection(stats),
|
|
5589
5952
|
_renderByProjectSection(stats),
|
|
5590
5953
|
_renderInsightsSection(stats),
|
|
@@ -5594,6 +5957,7 @@ function renderStats(stats, opts) {
|
|
|
5594
5957
|
}
|
|
5595
5958
|
|
|
5596
5959
|
// src/stats.ts
|
|
5960
|
+
var HARNESS_UNRECORDED = "unrecorded (pre-2.8.1)";
|
|
5597
5961
|
var SOURCE_IMAGE = "image";
|
|
5598
5962
|
var SOURCE_HINT = "hint";
|
|
5599
5963
|
var SOURCE_READ = "read";
|
|
@@ -5640,6 +6004,7 @@ var KIND_TO_SOURCE = {
|
|
|
5640
6004
|
csv_query: SOURCE_READ,
|
|
5641
6005
|
csv_profile: SOURCE_READ,
|
|
5642
6006
|
pdf_extract: SOURCE_READ,
|
|
6007
|
+
pdf_locate: SOURCE_READ,
|
|
5643
6008
|
pdf_outline: SOURCE_READ,
|
|
5644
6009
|
pdf_meta: SOURCE_READ,
|
|
5645
6010
|
xlsx_sheets: SOURCE_READ,
|
|
@@ -5692,6 +6057,8 @@ var KIND_TO_SOURCE = {
|
|
|
5692
6057
|
// Cold first load of an oversized skill where preSkillHandler inlined the compact slice in its reply instead of pointing at `skill-body --compact`. Unlike its skill_oversized_first_load sibling (event-only, 0 bytes -- the pointer deny saves nothing by itself, the follow-up command does) this one records real savings: the full body never landed, the slice did, so bytesSaved is body minus slice.
|
|
5693
6058
|
skill_compact_inlined: SOURCE_SKILL,
|
|
5694
6059
|
secret_redacted: SOURCE_OTHER,
|
|
6060
|
+
// Measurement of what a compaction produced (hooks_compact.ts postCompactHandler): summary size and how many manifest paths survived into it. SOURCE_OTHER and always recorded at (0, 0) -- the summary was written whether or not token-goat was watching, so there is no counterfactual in which those bytes were saved. Filing it anywhere with a savings total would credit token-goat for the whole summary, which is the accounting mistake this registry exists to prevent.
|
|
6061
|
+
compact_summary: SOURCE_OTHER,
|
|
5695
6062
|
// Envelope compaction of an oversized subagent report (hooks_agent_spawn.ts). SOURCE_CONTENT, not SOURCE_HINT: the handler's sibling session_hint entry is advisory (it only appends a recall pointer and genuinely saves nothing), whereas this kind records a real rewrite with real bytes removed, so filing it under the advisory bucket would understate the compaction and repeat the zero-savings desync this registry keeps getting bitten by.
|
|
5696
6063
|
agent_report_compact: SOURCE_CONTENT,
|
|
5697
6064
|
// Decline counterpart to agent_report_compact: the fence-collapse net-benefit gate ran and found at least one over-long fence, but declined to rewrite because net savings did not clear the notice cost. Always recorded at (0, 0) -- see the recordStat call site -- so it never contributes to any savings total; it exists purely to make gate hit-rate and near-misses visible instead of the decline being invisible.
|
|
@@ -5727,6 +6094,7 @@ var COMMAND_KINDS = {
|
|
|
5727
6094
|
"csv-query": /* @__PURE__ */ new Set(["csv_query"]),
|
|
5728
6095
|
"csv-profile": /* @__PURE__ */ new Set(["csv_profile"]),
|
|
5729
6096
|
"pdf-extract": /* @__PURE__ */ new Set(["pdf_extract"]),
|
|
6097
|
+
"pdf-locate": /* @__PURE__ */ new Set(["pdf_locate"]),
|
|
5730
6098
|
"pdf-outline": /* @__PURE__ */ new Set(["pdf_outline"]),
|
|
5731
6099
|
"pdf-meta": /* @__PURE__ */ new Set(["pdf_meta"]),
|
|
5732
6100
|
"xlsx-sheets": /* @__PURE__ */ new Set(["xlsx_sheets"]),
|
|
@@ -5821,19 +6189,53 @@ CREATE TABLE IF NOT EXISTS stats (
|
|
|
5821
6189
|
kind TEXT NOT NULL,
|
|
5822
6190
|
tokens_saved INTEGER NOT NULL DEFAULT 0,
|
|
5823
6191
|
bytes_saved INTEGER NOT NULL DEFAULT 0,
|
|
5824
|
-
detail TEXT
|
|
6192
|
+
detail TEXT,
|
|
6193
|
+
harness TEXT
|
|
5825
6194
|
);
|
|
5826
6195
|
CREATE INDEX IF NOT EXISTS idx_stats_ts ON stats(ts);
|
|
5827
6196
|
CREATE INDEX IF NOT EXISTS idx_stats_kind ON stats(kind);
|
|
6197
|
+
CREATE TABLE IF NOT EXISTS unmapped_tools (
|
|
6198
|
+
harness TEXT NOT NULL,
|
|
6199
|
+
tool_name TEXT NOT NULL,
|
|
6200
|
+
event_name TEXT NOT NULL,
|
|
6201
|
+
near_miss TEXT,
|
|
6202
|
+
first_seen INTEGER NOT NULL,
|
|
6203
|
+
last_seen INTEGER NOT NULL,
|
|
6204
|
+
hits INTEGER NOT NULL DEFAULT 0,
|
|
6205
|
+
PRIMARY KEY (harness, tool_name, event_name)
|
|
6206
|
+
);
|
|
5828
6207
|
`;
|
|
5829
6208
|
var _globalSchemaApplied = /* @__PURE__ */ new Set();
|
|
5830
6209
|
registerReset(() => _globalSchemaApplied.clear());
|
|
6210
|
+
function migrateGlobalSchema(db) {
|
|
6211
|
+
try {
|
|
6212
|
+
db.exec("ALTER TABLE stats ADD COLUMN harness TEXT");
|
|
6213
|
+
} catch (err) {
|
|
6214
|
+
if (!(err instanceof Error) || !/duplicate column/i.test(err.message)) throw err;
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
var _harnessColumnByDb = /* @__PURE__ */ new WeakMap();
|
|
6218
|
+
function statsHasHarnessColumn(db) {
|
|
6219
|
+
const cached = _harnessColumnByDb.get(db);
|
|
6220
|
+
if (cached !== void 0) return cached;
|
|
6221
|
+
let present;
|
|
6222
|
+
try {
|
|
6223
|
+
present = db.prepare("PRAGMA table_info(stats)").all().some(
|
|
6224
|
+
(c) => c.name === "harness"
|
|
6225
|
+
);
|
|
6226
|
+
} catch {
|
|
6227
|
+
present = false;
|
|
6228
|
+
}
|
|
6229
|
+
_harnessColumnByDb.set(db, present);
|
|
6230
|
+
return present;
|
|
6231
|
+
}
|
|
5831
6232
|
function getGlobalDb(homeDir) {
|
|
5832
6233
|
const basePath = homeDir ? dataDirForHome(homeDir) : dataDir();
|
|
5833
6234
|
const dbPath = path8.join(basePath, "global.db");
|
|
5834
6235
|
const db = getDb(dbPath);
|
|
5835
6236
|
if (!_globalSchemaApplied.has(dbPath)) {
|
|
5836
6237
|
db.exec(GLOBAL_SCHEMA_SQL);
|
|
6238
|
+
migrateGlobalSchema(db);
|
|
5837
6239
|
_globalSchemaApplied.add(dbPath);
|
|
5838
6240
|
}
|
|
5839
6241
|
return db;
|
|
@@ -5848,22 +6250,59 @@ function noStatsMessage(windowDays, homeDir) {
|
|
|
5848
6250
|
function recordStat(kind, bytesSaved = 0, tokensSaved = 0, _testDb, detail) {
|
|
5849
6251
|
try {
|
|
5850
6252
|
const db = _testDb ?? getGlobalDb();
|
|
6253
|
+
const ts = Math.floor(Date.now() / 1e3);
|
|
6254
|
+
if (statsHasHarnessColumn(db)) {
|
|
6255
|
+
db.prepare(
|
|
6256
|
+
"INSERT INTO stats (ts, kind, bytes_saved, tokens_saved, detail, harness) VALUES (?, ?, ?, ?, ?, ?)"
|
|
6257
|
+
).run(ts, kind, bytesSaved, tokensSaved, detail ?? null, getHarnessName());
|
|
6258
|
+
} else {
|
|
6259
|
+
db.prepare(
|
|
6260
|
+
"INSERT INTO stats (ts, kind, bytes_saved, tokens_saved, detail) VALUES (?, ?, ?, ?, ?)"
|
|
6261
|
+
).run(ts, kind, bytesSaved, tokensSaved, detail ?? null);
|
|
6262
|
+
}
|
|
6263
|
+
} catch {
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
var MAX_TOOL_NAME_CHARS = 200;
|
|
6267
|
+
function recordUnmappedTool(toolName, eventName, nearMiss, _testDb) {
|
|
6268
|
+
try {
|
|
6269
|
+
if (!toolName) return;
|
|
6270
|
+
const db = _testDb ?? getGlobalDb();
|
|
6271
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5851
6272
|
db.prepare(
|
|
5852
|
-
|
|
5853
|
-
|
|
6273
|
+
`INSERT INTO unmapped_tools (harness, tool_name, event_name, near_miss, first_seen, last_seen, hits)
|
|
6274
|
+
VALUES (?, ?, ?, ?, ?, ?, 1)
|
|
6275
|
+
ON CONFLICT(harness, tool_name, event_name) DO UPDATE SET
|
|
6276
|
+
hits = hits + 1,
|
|
6277
|
+
last_seen = excluded.last_seen,
|
|
6278
|
+
near_miss = excluded.near_miss`
|
|
6279
|
+
).run(getHarnessName(), toolName.slice(0, MAX_TOOL_NAME_CHARS), eventName, nearMiss, now, now);
|
|
5854
6280
|
} catch {
|
|
5855
6281
|
}
|
|
5856
6282
|
}
|
|
6283
|
+
function readUnmappedTools(dbPath, homeDir) {
|
|
6284
|
+
try {
|
|
6285
|
+
const db = dbPath ? getDb(dbPath) : getGlobalDb(homeDir);
|
|
6286
|
+
return db.prepare(
|
|
6287
|
+
"SELECT harness, tool_name, event_name, near_miss, hits, last_seen FROM unmapped_tools ORDER BY hits DESC, tool_name ASC"
|
|
6288
|
+
).all();
|
|
6289
|
+
} catch {
|
|
6290
|
+
return [];
|
|
6291
|
+
}
|
|
6292
|
+
}
|
|
5857
6293
|
function summarize(windowDays = 30, testDb, homeDir) {
|
|
5858
6294
|
const t0 = Date.now();
|
|
5859
6295
|
const sinceTs = windowDays > 0 ? Math.floor((Date.now() - windowDays * 24 * 60 * 60 * 1e3) / 1e3) : null;
|
|
5860
6296
|
const byKind = {};
|
|
5861
6297
|
const byDay = {};
|
|
6298
|
+
const byHarness = {};
|
|
5862
6299
|
let totalEvents = 0;
|
|
5863
6300
|
let totalBytes = 0;
|
|
5864
6301
|
let totalTokens = 0;
|
|
5865
6302
|
const db = testDb ?? getGlobalDb(homeDir);
|
|
5866
|
-
const
|
|
6303
|
+
const hasHarness = statsHasHarnessColumn(db);
|
|
6304
|
+
const cols = hasHarness ? "ts, kind, bytes_saved, tokens_saved, harness" : "ts, kind, bytes_saved, tokens_saved";
|
|
6305
|
+
const query = sinceTs !== null ? `SELECT ${cols} FROM stats WHERE ts >= ? ORDER BY ts DESC` : `SELECT ${cols} FROM stats ORDER BY ts DESC`;
|
|
5867
6306
|
const stmt = db.prepare(query);
|
|
5868
6307
|
const rows = sinceTs !== null ? stmt.all(sinceTs) : stmt.all();
|
|
5869
6308
|
const tsToDateCache = {};
|
|
@@ -5887,6 +6326,11 @@ function summarize(windowDays = 30, testDb, homeDir) {
|
|
|
5887
6326
|
byDay[dateKey] = zeroBucket();
|
|
5888
6327
|
}
|
|
5889
6328
|
incBucket(byDay[dateKey], bytesSaved, tokensSaved);
|
|
6329
|
+
const harness = row.harness || HARNESS_UNRECORDED;
|
|
6330
|
+
if (!byHarness[harness]) {
|
|
6331
|
+
byHarness[harness] = zeroBucket();
|
|
6332
|
+
}
|
|
6333
|
+
incBucket(byHarness[harness], bytesSaved, tokensSaved);
|
|
5890
6334
|
}
|
|
5891
6335
|
const bySourceDict = {};
|
|
5892
6336
|
for (const [kind, bucket] of Object.entries(byKind)) {
|
|
@@ -5923,6 +6367,7 @@ function summarize(windowDays = 30, testDb, homeDir) {
|
|
|
5923
6367
|
by_day: byDayList,
|
|
5924
6368
|
by_project: byProjectList,
|
|
5925
6369
|
by_source: bySourceDict,
|
|
6370
|
+
by_harness: byHarness,
|
|
5926
6371
|
by_command: Object.entries(byCommandDict).map(([command, bucket]) => ({ ...bucket, command })).filter((r) => r.events > 0),
|
|
5927
6372
|
window_days: windowDays
|
|
5928
6373
|
};
|
|
@@ -5960,6 +6405,15 @@ function _plainTextStats(summary) {
|
|
|
5960
6405
|
);
|
|
5961
6406
|
}
|
|
5962
6407
|
}
|
|
6408
|
+
const harnesses = Object.entries(summary.by_harness).filter(([, b]) => b.events > 0).sort((a, b) => b[1].tokens_saved - a[1].tokens_saved);
|
|
6409
|
+
if (harnesses.length > 1) {
|
|
6410
|
+
lines.push("", "## By Harness");
|
|
6411
|
+
for (const [harness, bucket] of harnesses) {
|
|
6412
|
+
lines.push(
|
|
6413
|
+
` ${harness.padEnd(22)} ${bucket.events.toString().padStart(6)} events ${fmtBytes(bucket.bytes_saved).padStart(8)} ${bucket.tokens_saved.toString().padStart(8)} tokens`
|
|
6414
|
+
);
|
|
6415
|
+
}
|
|
6416
|
+
}
|
|
5963
6417
|
if (summary.by_command.length > 0) {
|
|
5964
6418
|
lines.push("", "## By Command");
|
|
5965
6419
|
for (const row of summary.by_command) {
|
|
@@ -6033,7 +6487,13 @@ function _buildStatsData(summary, windowDays) {
|
|
|
6033
6487
|
bytes: c.bytes_saved,
|
|
6034
6488
|
tokens: c.tokens_saved,
|
|
6035
6489
|
events: c.events
|
|
6036
|
-
}))
|
|
6490
|
+
})),
|
|
6491
|
+
by_harness: Object.entries(summary.by_harness).filter(([, b]) => b.events > 0).map(([harness, bucket]) => ({
|
|
6492
|
+
harness,
|
|
6493
|
+
bytes: bucket.bytes_saved,
|
|
6494
|
+
tokens: bucket.tokens_saved,
|
|
6495
|
+
events: bucket.events
|
|
6496
|
+
})).sort((a, b) => b.bytes - a.bytes)
|
|
6037
6497
|
};
|
|
6038
6498
|
}
|
|
6039
6499
|
function renderShortStats(opts) {
|
|
@@ -6293,6 +6753,7 @@ function stripAnsiCodes(text) {
|
|
|
6293
6753
|
export {
|
|
6294
6754
|
VERSION,
|
|
6295
6755
|
dataDir,
|
|
6756
|
+
ensureDataDirPrivate,
|
|
6296
6757
|
globalDbPath,
|
|
6297
6758
|
configPath,
|
|
6298
6759
|
ENV_KEYS,
|
|
@@ -6388,6 +6849,7 @@ export {
|
|
|
6388
6849
|
loadPersistedConfig,
|
|
6389
6850
|
CONFIG_KEY_ENV_OVERRIDES,
|
|
6390
6851
|
saveConfig,
|
|
6852
|
+
Database,
|
|
6391
6853
|
precedingDocComment,
|
|
6392
6854
|
buildLineIndex,
|
|
6393
6855
|
offsetToLine,
|
|
@@ -6428,6 +6890,8 @@ export {
|
|
|
6428
6890
|
SOURCE_HINT,
|
|
6429
6891
|
formatLocalTimestamp,
|
|
6430
6892
|
recordStat,
|
|
6893
|
+
recordUnmappedTool,
|
|
6894
|
+
readUnmappedTools,
|
|
6431
6895
|
summarize,
|
|
6432
6896
|
_useRichStats,
|
|
6433
6897
|
renderShortStats,
|