token-goat 2.6.36 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +31 -5
  2. package/SECURITY.md +46 -17
  3. package/dist/{token-goat-chunk-UFOVM7ZN.mjs → token-goat-chunk-2F6TFBZE.mjs} +1274 -367
  4. package/dist/token-goat-chunk-324QOJYZ.mjs +91 -0
  5. package/dist/{token-goat-chunk-V465YKOR.mjs → token-goat-chunk-44Y77VHR.mjs} +365 -23
  6. package/dist/{token-goat-chunk-CNDOJ3ZP.mjs → token-goat-chunk-4KZILRZN.mjs} +2 -2
  7. package/dist/token-goat-chunk-5CVKO3DA.mjs +185 -0
  8. package/dist/token-goat-chunk-A62K4XW2.mjs +23 -0
  9. package/dist/{token-goat-chunk-VYMGEVZS.mjs → token-goat-chunk-AO6MFFTW.mjs} +317 -11
  10. package/dist/{token-goat-chunk-65BKISIS.mjs → token-goat-chunk-FBGBTICM.mjs} +4 -4
  11. package/dist/{token-goat-chunk-LN6OUHTV.mjs → token-goat-chunk-J35AKWEQ.mjs} +5 -5
  12. package/dist/{token-goat-hook-chunk-BDR6C6IE.mjs → token-goat-chunk-LVCBDJVE.mjs} +318 -51
  13. package/dist/token-goat-chunk-R4SR7MQY.mjs +486 -0
  14. package/dist/{token-goat-chunk-IYTVE6KN.mjs → token-goat-chunk-SRAR6DOK.mjs} +133 -141
  15. package/dist/{token-goat-chunk-MGOUYAA2.mjs → token-goat-chunk-TUPJRK7R.mjs} +1 -1
  16. package/dist/{token-goat-chunk-KYFJC37X.mjs → token-goat-chunk-VXSYZGBA.mjs} +582 -135
  17. package/dist/{token-goat-chunk-DG53MVNJ.mjs → token-goat-chunk-WN5T5EW5.mjs} +212 -212
  18. package/dist/token-goat-hook.mjs +7 -7
  19. package/dist/token-goat.core.mjs +5 -5
  20. package/package.json +9 -7
  21. package/dist/token-goat-chunk-FRTBMRP7.mjs +0 -10048
  22. package/dist/token-goat-hook-chunk-3QYSN4QV.mjs +0 -14764
  23. package/dist/token-goat-hook-chunk-3ZDBWJDF.mjs +0 -13659
  24. package/dist/token-goat-hook-chunk-5UH54CW6.mjs +0 -912
  25. package/dist/token-goat-hook-chunk-6ODM3MP7.mjs +0 -706
  26. package/dist/token-goat-hook-chunk-A77A26A7.mjs +0 -18184
  27. package/dist/token-goat-hook-chunk-BUOCULAM.mjs +0 -29
  28. package/dist/token-goat-hook-chunk-C6GIABOX.mjs +0 -15971
  29. package/dist/token-goat-hook-chunk-E257IGSN.mjs +0 -153
  30. package/dist/token-goat-hook-chunk-MW5HPEGD.mjs +0 -10411
  31. package/dist/token-goat-hook-chunk-QSCYNJ2B.mjs +0 -23
  32. package/dist/token-goat-hook-chunk-RFRLWOQH.mjs +0 -11
  33. package/dist/token-goat-hook-chunk-XUMIVYEN.mjs +0 -109
  34. package/dist/token-goat-hook-chunk-Y2WHX2P3.mjs +0 -6463
@@ -0,0 +1,91 @@
1
+ import { createRequire as __cjsRequire } from 'node:module';
2
+ const require = __cjsRequire(import.meta.url);
3
+ import "./token-goat-chunk-AEX54RUZ.mjs";
4
+
5
+ // src/mcp_stdio.ts
6
+ import process from "node:process";
7
+ var STDIO_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
8
+ var StdioServerTransport = class {
9
+ stdin;
10
+ stdout;
11
+ buffer;
12
+ started = false;
13
+ onmessage;
14
+ onclose;
15
+ onerror;
16
+ // Arrow properties rather than bound methods so `off()` can remove the exact same function
17
+ // reference `on()` added -- a fresh `.bind(this)` at removal time would leave the listener
18
+ // attached, and a second `mcp-serve` in the same process would then see every message twice.
19
+ ondata = (chunk) => {
20
+ const size = (this.buffer?.length ?? 0) + chunk.length;
21
+ if (size > STDIO_MAX_BUFFER_BYTES) {
22
+ this.buffer = void 0;
23
+ this.onerror?.(new Error(`MCP stdio read buffer exceeded ${STDIO_MAX_BUFFER_BYTES} bytes`));
24
+ void this.close();
25
+ return;
26
+ }
27
+ this.buffer = this.buffer === void 0 ? chunk : Buffer.concat([this.buffer, chunk]);
28
+ this.drain();
29
+ };
30
+ onstreamerror = (error) => {
31
+ this.onerror?.(error);
32
+ };
33
+ constructor(stdin = process.stdin, stdout = process.stdout) {
34
+ this.stdin = stdin;
35
+ this.stdout = stdout;
36
+ }
37
+ async start() {
38
+ if (this.started) throw new Error("StdioServerTransport already started");
39
+ this.started = true;
40
+ this.stdin.on("data", this.ondata);
41
+ this.stdin.on("error", this.onstreamerror);
42
+ return Promise.resolve();
43
+ }
44
+ /**
45
+ * Consumes every complete line currently buffered. A line that is not valid JSON is reported and
46
+ * skipped rather than closing the connection: one malformed message is not a reason to drop a
47
+ * working session, and the next line may well be fine.
48
+ */
49
+ drain() {
50
+ for (; ; ) {
51
+ const buffer = this.buffer;
52
+ if (buffer === void 0) return;
53
+ const index = buffer.indexOf("\n");
54
+ if (index === -1) return;
55
+ const line = buffer.toString("utf8", 0, index).replace(/\r$/, "");
56
+ this.buffer = buffer.subarray(index + 1);
57
+ if (line.trim().length === 0) continue;
58
+ let message;
59
+ try {
60
+ message = JSON.parse(line);
61
+ } catch (err) {
62
+ this.onerror?.(err instanceof Error ? err : new Error(String(err)));
63
+ continue;
64
+ }
65
+ this.onmessage?.(message);
66
+ }
67
+ }
68
+ /**
69
+ * Resolves once the message is handed off. Waiting for `drain` when the stream says it is full
70
+ * is what keeps a long reply from being silently truncated on a slow or small pipe.
71
+ */
72
+ send(message) {
73
+ return new Promise((resolve) => {
74
+ if (this.stdout.write(`${JSON.stringify(message)}
75
+ `)) resolve();
76
+ else this.stdout.once("drain", resolve);
77
+ });
78
+ }
79
+ async close() {
80
+ this.stdin.off("data", this.ondata);
81
+ this.stdin.off("error", this.onstreamerror);
82
+ if (this.stdin.listenerCount("data") === 0) this.stdin.pause();
83
+ this.buffer = void 0;
84
+ this.onclose?.();
85
+ return Promise.resolve();
86
+ }
87
+ };
88
+ export {
89
+ STDIO_MAX_BUFFER_BYTES,
90
+ StdioServerTransport
91
+ };
@@ -8,7 +8,7 @@ import {
8
8
  import { createRequire } from "node:module";
9
9
  function resolveVersion() {
10
10
  if (true) {
11
- return "2.6.36";
11
+ return "2.8.0";
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 attempt = 1; attempt <= 5; attempt++) {
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) || attempt === 5) throw err;
428
- sleepSync(50 * attempt);
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 attempt = 0;
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 * ++attempt, 200));
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 lineCommentIdx = lineCommentStartIndex(line, ["//"], j);
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 lineCommentIdx = lineCommentStartIndex(line, ["//"], j);
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 = 11;
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 = _require("sqlite-vec");
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" || !fs6.existsSync(dir)) throw e;
5173
+ if (e.code !== "EEXIST" || !fs7.existsSync(dir)) throw e;
4837
5174
  }
4838
5175
  const conn = new Database(resolved);
4839
5176
  try {
@@ -5528,7 +5865,8 @@ function _renderInsightsSection(stats) {
5528
5865
  function dim(s) {
5529
5866
  return `${fg(...C.TEXT_MUTED)}${s}${RESET}`;
5530
5867
  }
5531
- const topKind = stats.by_kind.reduce((max, k) => k.bytes > (max?.bytes || -Infinity) ? k : max, stats.by_kind[0]);
5868
+ const savingKinds = stats.by_kind.filter((k) => k.bytes > 0);
5869
+ const topKind = savingKinds.reduce((max, k) => k.bytes > (max?.bytes || -Infinity) ? k : max, savingKinds[0]);
5532
5870
  if (topKind) {
5533
5871
  const share = stats.totals.bytes > 0 ? topKind.bytes / stats.totals.bytes : 0;
5534
5872
  lines.push(
@@ -5541,7 +5879,7 @@ function _renderInsightsSection(stats) {
5541
5879
  `${_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
5880
  );
5543
5881
  }
5544
- const tokenKinds = stats.by_kind.filter((k) => !k.bytes_mode_only);
5882
+ const tokenKinds = stats.by_kind.filter((k) => !k.bytes_mode_only && k.tokens > 0);
5545
5883
  const topToken = tokenKinds.reduce((max, k) => k.tokens > (max?.tokens || -Infinity) ? k : max, tokenKinds[0]);
5546
5884
  if (topToken) {
5547
5885
  lines.push(
@@ -5692,6 +6030,8 @@ var KIND_TO_SOURCE = {
5692
6030
  // 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
6031
  skill_compact_inlined: SOURCE_SKILL,
5694
6032
  secret_redacted: SOURCE_OTHER,
6033
+ // 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.
6034
+ compact_summary: SOURCE_OTHER,
5695
6035
  // 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
6036
  agent_report_compact: SOURCE_CONTENT,
5697
6037
  // 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.
@@ -6293,6 +6633,7 @@ function stripAnsiCodes(text) {
6293
6633
  export {
6294
6634
  VERSION,
6295
6635
  dataDir,
6636
+ ensureDataDirPrivate,
6296
6637
  globalDbPath,
6297
6638
  configPath,
6298
6639
  ENV_KEYS,
@@ -6388,6 +6729,7 @@ export {
6388
6729
  loadPersistedConfig,
6389
6730
  CONFIG_KEY_ENV_OVERRIDES,
6390
6731
  saveConfig,
6732
+ Database,
6391
6733
  precedingDocComment,
6392
6734
  buildLineIndex,
6393
6735
  offsetToLine,
@@ -10,11 +10,11 @@ import {
10
10
  selectFilter,
11
11
  shlexSplit,
12
12
  wrappedShell
13
- } from "./token-goat-chunk-MGOUYAA2.mjs";
13
+ } from "./token-goat-chunk-TUPJRK7R.mjs";
14
14
  import {
15
15
  loadConfig,
16
16
  recordStat
17
- } from "./token-goat-chunk-V465YKOR.mjs";
17
+ } from "./token-goat-chunk-44Y77VHR.mjs";
18
18
  import "./token-goat-chunk-AO2QD2AG.mjs";
19
19
  import "./token-goat-chunk-AEX54RUZ.mjs";
20
20