token-goat 2.6.34 → 2.6.36

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 (26) hide show
  1. package/README.md +4 -2
  2. package/SECURITY.md +14 -11
  3. package/dist/{token-goat-chunk-L7QLEVO7.mjs → token-goat-chunk-65BKISIS.mjs} +16 -14
  4. package/dist/{token-goat-chunk-I6EQH27G.mjs → token-goat-chunk-CNDOJ3ZP.mjs} +2 -2
  5. package/dist/{token-goat-chunk-FRMEOIRU.mjs → token-goat-chunk-FRTBMRP7.mjs} +208 -13
  6. package/dist/{token-goat-chunk-IYYQOPY4.mjs → token-goat-chunk-IYTVE6KN.mjs} +29 -10
  7. package/dist/{token-goat-chunk-GXBPEQR2.mjs → token-goat-chunk-KYFJC37X.mjs} +21 -14
  8. package/dist/{token-goat-chunk-CKRATWCJ.mjs → token-goat-chunk-LN6OUHTV.mjs} +5 -5
  9. package/dist/{token-goat-chunk-WA2ER6TZ.mjs → token-goat-chunk-MGOUYAA2.mjs} +1 -1
  10. package/dist/{token-goat-chunk-CN7GUPX6.mjs → token-goat-chunk-UFOVM7ZN.mjs} +461 -97
  11. package/dist/{token-goat-chunk-7FWBSPCT.mjs → token-goat-chunk-V465YKOR.mjs} +198 -21
  12. package/dist/{token-goat-chunk-OA3GFKP2.mjs → token-goat-chunk-VYMGEVZS.mjs} +16 -4
  13. package/dist/{token-goat-hook-chunk-2ISZ4AZR.mjs → token-goat-hook-chunk-3ZDBWJDF.mjs} +1 -1
  14. package/dist/{token-goat-hook-chunk-6C5RN3CM.mjs → token-goat-hook-chunk-5UH54CW6.mjs} +16 -4
  15. package/dist/{token-goat-hook-chunk-OQML5EMA.mjs → token-goat-hook-chunk-6ODM3MP7.mjs} +16 -14
  16. package/dist/{token-goat-hook-chunk-6QTWMSRO.mjs → token-goat-hook-chunk-A77A26A7.mjs} +461 -97
  17. package/dist/{token-goat-hook-chunk-BU5OFDGQ.mjs → token-goat-hook-chunk-BDR6C6IE.mjs} +208 -13
  18. package/dist/{token-goat-hook-chunk-I5VM5PO5.mjs → token-goat-hook-chunk-C6GIABOX.mjs} +21 -14
  19. package/dist/{token-goat-hook-chunk-QTZ6YUMQ.mjs → token-goat-hook-chunk-E257IGSN.mjs} +2 -2
  20. package/dist/{token-goat-hook-chunk-KGLMNOGD.mjs → token-goat-hook-chunk-MW5HPEGD.mjs} +29 -10
  21. package/dist/{token-goat-hook-chunk-ATEGFQAU.mjs → token-goat-hook-chunk-QSCYNJ2B.mjs} +5 -5
  22. package/dist/{token-goat-hook-chunk-CNLMPHXT.mjs → token-goat-hook-chunk-Y2WHX2P3.mjs} +198 -21
  23. package/dist/token-goat-hook.mjs +5 -5
  24. package/dist/token-goat.core.mjs +5 -5
  25. package/package.json +13 -11
  26. package/scripts/install-git-hooks.mjs +55 -0
@@ -8,7 +8,7 @@ import {
8
8
  import { createRequire } from "node:module";
9
9
  function resolveVersion() {
10
10
  if (true) {
11
- return "2.6.34";
11
+ return "2.6.36";
12
12
  }
13
13
  const require2 = createRequire(import.meta.url);
14
14
  const pkg = require2("../package.json");
@@ -770,6 +770,9 @@ function countContentLines(content) {
770
770
  const n = content.split("\n").length;
771
771
  return content.endsWith("\n") ? n - 1 : n;
772
772
  }
773
+ function pushAll(target, items) {
774
+ for (const item of items) target.push(item);
775
+ }
773
776
  function escapeRegExp(s) {
774
777
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
775
778
  }
@@ -1196,6 +1199,9 @@ function getDisplayRoot(explicitRoot) {
1196
1199
  _displayRootCache = { cwd, root };
1197
1200
  return root;
1198
1201
  }
1202
+ registerReset(() => {
1203
+ _displayRootCache = null;
1204
+ });
1199
1205
  function isUnderSystemTemp(filePath) {
1200
1206
  let sysTemp;
1201
1207
  try {
@@ -2446,7 +2452,6 @@ var ENV_KEYS2 = [
2446
2452
  "TOKEN_GOAT_OVERFLOW_MAX_TOKENS",
2447
2453
  "TOKEN_GOAT_HINT_JSON_SIDECAR",
2448
2454
  "TOKEN_GOAT_LARGE_READ_BYTES",
2449
- "TOKEN_GOAT_CURATOR",
2450
2455
  "TOKEN_GOAT_HINT_BUDGET",
2451
2456
  "TOKEN_GOAT_HOOK_WATCHDOG_MS",
2452
2457
  "TOKEN_GOAT_WEB_COMPRESS",
@@ -2507,8 +2512,19 @@ var _lastProjectConfigParseError = null;
2507
2512
  function getLastProjectConfigParseError() {
2508
2513
  return _lastProjectConfigParseError;
2509
2514
  }
2510
- var PROJECT_LOCKED_SECTIONS = ["injection", "webfetch", "gdrive", "mcp", "network"];
2511
- var PROJECT_LOCKED_KEYS = ["indexing.cross_project_symbols"];
2515
+ var PROJECT_LOCKED_SECTIONS = [
2516
+ "injection",
2517
+ "webfetch",
2518
+ "gdrive",
2519
+ "mcp",
2520
+ "network",
2521
+ "screenshot"
2522
+ ];
2523
+ var PROJECT_LOCKED_KEYS = [
2524
+ "image_shrink.max_image_pixels",
2525
+ "indexing.cross_project_symbols",
2526
+ "worker.blocked_roots"
2527
+ ];
2512
2528
  var _lastProjectConfigLockedKeys = [];
2513
2529
  function stripLockedProjectKeys(projectRaw) {
2514
2530
  const cleaned = {};
@@ -2639,10 +2655,12 @@ function resolveConfigKeyLayer(key, effectiveValue, cfg, projectInfo) {
2639
2655
  if (rawValueEquals(rawValue, effectiveValue)) return { layer: "project", path: projectInfo.path };
2640
2656
  return { layer: "project-invalid", path: projectInfo.path, rawValue, effectiveValue, reason: rejectionReason(key, rawValue, effectiveValue, cfg) };
2641
2657
  }
2658
+ function readConfigSource(p) {
2659
+ return decodeSource(fs5.readFileSync(p));
2660
+ }
2642
2661
  function readConfigToml(p) {
2643
2662
  try {
2644
- const text = fs5.readFileSync(p, "utf8");
2645
- return { raw: parse(text), parseError: null };
2663
+ return { raw: parse(readConfigSource(p)), parseError: null };
2646
2664
  } catch (e) {
2647
2665
  const code = e.code;
2648
2666
  if (code === "ENOENT") return { raw: {}, parseError: null };
@@ -2651,7 +2669,7 @@ function readConfigToml(p) {
2651
2669
  }
2652
2670
  function readConfigText(p) {
2653
2671
  try {
2654
- return { text: fs5.readFileSync(p, "utf8"), readError: null };
2672
+ return { text: readConfigSource(p), readError: null };
2655
2673
  } catch (e) {
2656
2674
  const code = e.code;
2657
2675
  if (code === "ENOENT") return { text: null, readError: null };
@@ -2680,6 +2698,9 @@ function resolveConfigProjectRoot() {
2680
2698
  _projectRootCache = { cwd, root };
2681
2699
  return root;
2682
2700
  }
2701
+ registerReset(() => {
2702
+ _projectRootCache = null;
2703
+ });
2683
2704
  function isAutoTriggerMultiplierExplicit() {
2684
2705
  const setsMultiplier = (text) => {
2685
2706
  const raw = parse(text);
@@ -2690,11 +2711,11 @@ function isAutoTriggerMultiplierExplicit() {
2690
2711
  return ca_raw["auto_trigger_multiplier"] !== void 0;
2691
2712
  };
2692
2713
  try {
2693
- if (setsMultiplier(fs5.readFileSync(configPath(), "utf8"))) return true;
2714
+ if (setsMultiplier(readConfigSource(configPath()))) return true;
2694
2715
  } catch {
2695
2716
  }
2696
2717
  try {
2697
- return setsMultiplier(fs5.readFileSync(projectConfigPath(resolveConfigProjectRoot()), "utf8"));
2718
+ return setsMultiplier(readConfigSource(projectConfigPath(resolveConfigProjectRoot())));
2698
2719
  } catch {
2699
2720
  return false;
2700
2721
  }
@@ -4171,7 +4192,9 @@ function propagateEndLinesToSymbols(symbols, sections) {
4171
4192
  return sym;
4172
4193
  });
4173
4194
  }
4174
- function findMatchingBraceEndLine(content, openBraceIndex, totalLines, lineIndex, lineCommentPrefix) {
4195
+ function findMatchingBraceEndLine(content, openBraceIndex, totalLines, lineIndex, lineCommentPrefix, opts) {
4196
+ const block = opts?.blockComment;
4197
+ const backtick = opts?.backtickQuote === true;
4175
4198
  let depth = 0;
4176
4199
  let quote = null;
4177
4200
  for (let i = openBraceIndex; i < content.length; i++) {
@@ -4184,11 +4207,16 @@ function findMatchingBraceEndLine(content, openBraceIndex, totalLines, lineIndex
4184
4207
  if (ch === quote) quote = null;
4185
4208
  continue;
4186
4209
  }
4210
+ if (block !== void 0 && content.startsWith(block[0], i)) {
4211
+ const close = content.indexOf(block[1], i + block[0].length);
4212
+ i = close === -1 ? content.length : close + block[1].length - 1;
4213
+ continue;
4214
+ }
4187
4215
  if (lineCommentPrefix !== void 0 && content.startsWith(lineCommentPrefix, i)) {
4188
4216
  while (i < content.length && content[i] !== "\n") i++;
4189
4217
  continue;
4190
4218
  }
4191
- if (ch === '"' || ch === "'") {
4219
+ if (ch === '"' || ch === "'" || backtick && ch === "`") {
4192
4220
  quote = ch;
4193
4221
  continue;
4194
4222
  }
@@ -4200,7 +4228,106 @@ function findMatchingBraceEndLine(content, openBraceIndex, totalLines, lineIndex
4200
4228
  }
4201
4229
  }
4202
4230
  }
4203
- return totalLines;
4231
+ return opts?.noMatchValue ?? totalLines;
4232
+ }
4233
+ function assignBraceBlockSpans(symbols, content, lineCommentPrefix) {
4234
+ if (symbols.length === 0) return [...symbols];
4235
+ const lines = content.split("\n");
4236
+ const totalLines = lines.length;
4237
+ const lineIndex = buildLineIndex(content);
4238
+ const starts = [...new Set(symbols.map((s) => s.lineStart))].sort((a, b) => a - b);
4239
+ const blockComment = lineCommentPrefix === "//" ? ["/*", "*/"] : lineCommentPrefix === "#" ? ["<#", "#>"] : void 0;
4240
+ return symbols.map((sym) => {
4241
+ if (sym.lineEnd !== sym.lineStart) return sym;
4242
+ const nextStart = starts.find((s) => s > sym.lineStart);
4243
+ const lastSearchLine = Math.min(nextStart !== void 0 ? nextStart - 1 : totalLines, sym.lineStart + BRACE_SEARCH_MAX_LINES);
4244
+ const openIndex = findBlockOpenBrace(content, lineIndex, sym.lineStart, lastSearchLine, lineCommentPrefix, blockComment);
4245
+ if (openIndex === null) return sym;
4246
+ const endLine = findMatchingBraceEndLine(
4247
+ content,
4248
+ openIndex,
4249
+ totalLines,
4250
+ lineIndex,
4251
+ lineCommentPrefix,
4252
+ blockComment === void 0 ? { noMatchValue: -1 } : { blockComment, noMatchValue: -1 }
4253
+ );
4254
+ if (endLine <= sym.lineStart) return sym;
4255
+ return { ...sym, lineEnd: endLine, body: lines.slice(sym.lineStart - 1, endLine).join("\n") };
4256
+ });
4257
+ }
4258
+ var BRACE_SEARCH_MAX_LINES = 10;
4259
+ function findBlockOpenBrace(content, lineIndex, startLine, lastSearchLine, lineCommentPrefix, blockComment) {
4260
+ const from = lineIndex[startLine - 1];
4261
+ if (from === void 0) return null;
4262
+ const to = lineIndex[lastSearchLine] ?? content.length;
4263
+ let quote = null;
4264
+ let parenDepth = 0;
4265
+ let linesSeen = 0;
4266
+ let atLineStart = false;
4267
+ for (let i = from; i < to; i++) {
4268
+ const ch = content[i];
4269
+ if (ch === void 0) break;
4270
+ if (quote !== null) {
4271
+ if (ch === "\\") {
4272
+ i++;
4273
+ continue;
4274
+ }
4275
+ if (ch === quote) quote = null;
4276
+ continue;
4277
+ }
4278
+ if (ch === "\n") {
4279
+ linesSeen++;
4280
+ atLineStart = true;
4281
+ continue;
4282
+ }
4283
+ if (blockComment !== void 0 && content.startsWith(blockComment[0], i)) {
4284
+ const close = content.indexOf(blockComment[1], i + blockComment[0].length);
4285
+ if (close === -1) return null;
4286
+ i = close + blockComment[1].length - 1;
4287
+ continue;
4288
+ }
4289
+ if (lineCommentPrefix !== void 0 && content.startsWith(lineCommentPrefix, i)) {
4290
+ while (i + 1 < to && content[i + 1] !== "\n") i++;
4291
+ continue;
4292
+ }
4293
+ if (atLineStart && !/\s/.test(ch)) {
4294
+ atLineStart = false;
4295
+ if (parenDepth === 0 && linesSeen >= 1 && startsWithBlockKeyword(content, i, to)) return null;
4296
+ }
4297
+ if (ch === '"' || ch === "'") {
4298
+ quote = ch;
4299
+ continue;
4300
+ }
4301
+ if (ch === "(" || ch === "[") parenDepth++;
4302
+ else if (ch === ")" || ch === "]") {
4303
+ if (parenDepth > 0) parenDepth--;
4304
+ } else if (ch === ";") return null;
4305
+ else if (ch === "{") return i;
4306
+ }
4307
+ return null;
4308
+ }
4309
+ var BLOCK_OPENING_KEYWORDS = /* @__PURE__ */ new Set([
4310
+ "if",
4311
+ "for",
4312
+ "while",
4313
+ "do",
4314
+ "switch",
4315
+ "when",
4316
+ "match",
4317
+ "try",
4318
+ "foreach",
4319
+ "loop",
4320
+ "guard",
4321
+ "repeat",
4322
+ "unless",
4323
+ "until"
4324
+ ]);
4325
+ function startsWithBlockKeyword(content, i, to) {
4326
+ const first = content[i];
4327
+ if (first === void 0 || !/[A-Za-z_]/.test(first)) return false;
4328
+ let j = i + 1;
4329
+ while (j < to && /[A-Za-z0-9_]/.test(content[j])) j++;
4330
+ return BLOCK_OPENING_KEYWORDS.has(content.slice(i, j));
4204
4331
  }
4205
4332
 
4206
4333
  // src/languages/ini_idx.ts
@@ -4612,7 +4739,7 @@ function purgeDotenvEmbeddings(conn) {
4612
4739
  var MIGRATIONS = {
4613
4740
  // v1 -> v2: adds files.embed_sha, tracked separately from files.sha so embedding freshness can be gated independently of parse freshness (see makeIndexer in worker.ts). A pre-existing v1 database's `files` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has the column from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, so a genuine ALTER TABLE failure is never silently lost.
4614
4741
  1: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN embed_sha TEXT"),
4615
- // v2 -> v3: adds files.retry_count, a durable per-path counter for consecutive transient-read-failure requeues (see MAX_TRANSIENT_RETRIES / requeueDirtyPath / resetTransientRetryCount in worker.ts). Previously this counter lived only in an in-memory Map inside worker.ts, which meant resetTransientRetryCount -- called from appendDirtyPath (hooks_index.ts) in the short-lived hook CLI process -- could never actually reach the long-lived detached daemon process's own copy of that Map: they are different Node processes with no shared memory, so the reset was a silent no-op in the real deployed topology. Persisting the counter in `files` makes it visible to both processes via the one thing they do share: the index DB. Same swallow-duplicate-column pattern as v1 -> v2 above.
4742
+ // v2 -> v3: adds files.retry_count, a durable per-path counter for consecutive transient-read-failure requeues (see MAX_TRANSIENT_RETRIES / requeueDirtyPath / clearRetryCount in worker.ts). Previously this counter lived only in an in-memory Map inside worker.ts, which meant the retry-count reset -- run at the time in the short-lived hook CLI process -- could never actually reach the long-lived detached daemon process's own copy of that Map: they are different Node processes with no shared memory, so the reset was a silent no-op in the real deployed topology. Persisting the counter in `files` makes it visible to both processes via the one thing they do share: the index DB. Same swallow-duplicate-column pattern as v1 -> v2 above.
4616
4743
  2: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0"),
4617
4744
  // v8 -> v9: adds symbols.parent (see SCHEMA_VERSION comment above for why). A pre-existing v8 database's `symbols` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has the column from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, same pattern as v1 -> v2 / v2 -> v3 above.
4618
4745
  8: (conn) => alterTableIdempotent(conn, "ALTER TABLE symbols ADD COLUMN parent TEXT NOT NULL DEFAULT ''"),
@@ -4632,13 +4759,32 @@ function runMigrations(conn, fromVersion, toVersion) {
4632
4759
  MIGRATIONS[v]?.(conn);
4633
4760
  }
4634
4761
  }
4635
- function initConnection(conn) {
4636
- const walMode = conn.pragma("journal_mode = WAL", { simple: true });
4637
- if (String(walMode).toLowerCase() !== "wal") {
4638
- throw new Error(`db: failed to enable WAL mode (got: ${walMode})`);
4762
+ var WAL_SWITCH_DEADLINE_MS = 15e3;
4763
+ function enableWalWithRetry(conn, budgetMs = WAL_SWITCH_DEADLINE_MS) {
4764
+ const deadline = Date.now() + budgetMs;
4765
+ let lastError;
4766
+ for (; ; ) {
4767
+ try {
4768
+ const mode = conn.pragma("journal_mode = WAL", { simple: true });
4769
+ if (String(mode).toLowerCase() === "wal") return;
4770
+ lastError = new Error(`got: ${String(mode)}`);
4771
+ } catch (e) {
4772
+ lastError = e;
4773
+ }
4774
+ try {
4775
+ if (String(conn.pragma("journal_mode", { simple: true })).toLowerCase() === "wal") return;
4776
+ } catch {
4777
+ }
4778
+ if (Date.now() >= deadline) {
4779
+ throw new Error(`db: failed to enable WAL mode (${lastError instanceof Error ? lastError.message : String(lastError)})`);
4780
+ }
4781
+ sleepSync(25);
4639
4782
  }
4640
- conn.pragma("synchronous = NORMAL");
4783
+ }
4784
+ function initConnection(conn) {
4641
4785
  conn.pragma("busy_timeout = 15000");
4786
+ enableWalWithRetry(conn);
4787
+ conn.pragma("synchronous = NORMAL");
4642
4788
  conn.function(
4643
4789
  "TG_LOWER",
4644
4790
  { deterministic: true },
@@ -5983,13 +6129,26 @@ var SECRET_PATTERNS = [
5983
6129
  // directly against the header name and the scheme directly against the space, so a body like
5984
6130
  // {"Authorization": "Bearer <token>"} -- the shape any logged fetch or MCP result arrives in
5985
6131
  // -- matched nothing and the token was cached verbatim.
5986
- ["auth_bearer_token", /(?<=Authorization["']?[ \t]{0,8}:[ \t]{0,8}["']?[ \t]{0,8}Bearer[ \t])[A-Za-z0-9\-._~+/]{10,}=*/gi],
6132
+ // `token` is the second scheme spelling in wide use -- it is what curl and gh examples pass for
6133
+ // GitHub and many other APIs -- and an opaque value behind it carries exactly the same authority
6134
+ // as one behind `Bearer`. The trailing gap is `{1,8}` rather than a single space for the same
6135
+ // reason every other gap in this lookbehind already is: a hand-aligned or reformatted header
6136
+ // ("Authorization: Bearer <token>") is ordinary, and demanding exactly one space there made
6137
+ // this the one position in the pattern that a second space defeated.
6138
+ ["auth_bearer_token", /(?<=Authorization["']?[ \t]{0,8}:[ \t]{0,8}["']?[ \t]{0,8}(?:Bearer|token)[ \t]{1,8})[A-Za-z0-9\-._~+/]{10,}=*/gi],
5987
6139
  ["auth_basic_token", /(?<=Authorization["']?[ \t]{0,8}:[ \t]{0,8}["']?[ \t]{0,8}Basic[ \t])[A-Za-z0-9+/]{6,}=*/gi],
5988
6140
  // JWTs have no distinctive prefix of their own, but the base64url encoding of the smallest
5989
6141
  // realistic header ('{"alg":' or similar) always starts with "eyJ", so that's the practical
5990
6142
  // anchor here -- each of the three dot-separated segments requires a minimum length to avoid
5991
6143
  // matching a short, coincidentally dotted token.
5992
- ["jwt", /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g],
6144
+ //
6145
+ // The trailing `={0,2}` on each segment is what makes a padded token match. Base64url as the JWT
6146
+ // spec defines it drops the `=` padding, but producers that reach for a plain base64 encoder
6147
+ // emit it anyway, and a `=` in the header or payload segment used to defeat the match outright:
6148
+ // not a partial redaction, but none at all, so the entire token was printed. `=` cannot appear
6149
+ // anywhere except the end of a segment, since it is not one of the characters the segment body
6150
+ // allows, so accepting it here cannot widen the match onto anything else.
6151
+ ["jwt", /eyJ[A-Za-z0-9_-]{10,}={0,2}\.[A-Za-z0-9_-]{10,}={0,2}\.[A-Za-z0-9_-]{10,}={0,2}/g],
5993
6152
  ["npm_token", /npm_[A-Za-z0-9]{36}/g],
5994
6153
  // rk_live_ (restricted keys) share the sk_live_/sk_test_ secret-key shape and risk level, so
5995
6154
  // one pattern covers all three rather than adding a near-duplicate entry.
@@ -6037,7 +6196,22 @@ var SECRET_PATTERNS = [
6037
6196
  // SECRET_KEY=, and DB_PASSWORD_HASH= all passed through in full. That class matches
6038
6197
  // identifier characters only, so prose that merely mentions a keyword still never reaches
6039
6198
  // a separator and stays unredacted. api[_-]?key covers the apikey and api-key spellings too.
6040
- ["generic_secret_assignment", /(?<=(?:password|passwd|secret|api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token)[a-z0-9_-]{0,64}["']?[ \t]{0,8}[:=][ \t]{0,8}["']?)(?:\\[^\n]|[^\s\\&;#'"[\],{}:]){4,}/gi]
6199
+ // `& ; # , :` play two incompatible roles. They separate one field from the next (a query
6200
+ // string, a cookie header, an inline env list), and they are also perfectly ordinary password
6201
+ // characters. Rejecting them outright got the first role right and the second badly wrong: the
6202
+ // match stopped at the first one and left everything after it in plain text, so
6203
+ // `password=corr&horse&battery` redacted four characters and printed the rest, and
6204
+ // `DB_PASSWORD=Aa1:xyz` matched nothing at all because the run before the `:` was under the
6205
+ // four-character floor. A tail left sitting in the open is the outcome this module's header
6206
+ // calls worse than no redaction, because it reads as handled.
6207
+ //
6208
+ // So the separator role is decided by what follows rather than assumed: one of these characters
6209
+ // ends the value only when the next thing along is another `name=` / `name:` pair, which is what
6210
+ // an actual field separator is always followed by. `,OTHER=public` and `; other=1` still end it;
6211
+ // the `&` in the middle of a passphrase does not. Whitespace, quotes and brackets are unchanged
6212
+ // -- they end a value unconditionally, which is also what keeps this pattern from re-matching
6213
+ // the `[REDACTED:...]` placeholder it just wrote.
6214
+ ["generic_secret_assignment", /(?<=(?:password|passwd|secret|api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token)[a-z0-9_-]{0,64}["']?[ \t]{0,8}[:=][ \t]{0,8}["']?)(?:\\[^\n]|[^\s\\&;#,:'"[\]{}]|[&;#,:](?![ \t]*[A-Za-z_][A-Za-z0-9_.-]*[ \t]*[:=])){4,}/gi]
6041
6215
  ];
6042
6216
  function redactSecrets(text) {
6043
6217
  let count = 0;
@@ -6169,6 +6343,7 @@ export {
6169
6343
  countNoun,
6170
6344
  excludeTestsHiddenNote,
6171
6345
  countContentLines,
6346
+ pushAll,
6172
6347
  escapeRegExp,
6173
6348
  windowsCmdQuoteArg,
6174
6349
  redactUrlQuery,
@@ -6206,6 +6381,7 @@ export {
6206
6381
  lastProjectConfigLockedKeys,
6207
6382
  getProjectConfigInfo,
6208
6383
  resolveConfigKeyLayer,
6384
+ readConfigSource,
6209
6385
  isAutoTriggerMultiplierExplicit,
6210
6386
  loadConfig,
6211
6387
  invalidateConfigCache,
@@ -6234,6 +6410,7 @@ export {
6234
6410
  assignFlatEndLines,
6235
6411
  propagateEndLinesToSymbols,
6236
6412
  findMatchingBraceEndLine,
6413
+ assignBraceBlockSpans,
6237
6414
  extractIni,
6238
6415
  _lineClosesQuote,
6239
6416
  _detectOpenQuote,
@@ -6,7 +6,7 @@ import {
6
6
  loadBlob,
7
7
  sanitizeFtsQuery,
8
8
  storeBlob
9
- } from "./token-goat-chunk-CN7GUPX6.mjs";
9
+ } from "./token-goat-chunk-UFOVM7ZN.mjs";
10
10
  import {
11
11
  SYMBOL_BODY_CHAR_CAP,
12
12
  extractErrorMessage,
@@ -16,7 +16,7 @@ import {
16
16
  redactSecrets,
17
17
  runGit,
18
18
  shortFingerprint
19
- } from "./token-goat-chunk-7FWBSPCT.mjs";
19
+ } from "./token-goat-chunk-V465YKOR.mjs";
20
20
  import {
21
21
  registerReset
22
22
  } from "./token-goat-chunk-AO2QD2AG.mjs";
@@ -790,6 +790,7 @@ function formatCommandManifest(manifest) {
790
790
 
791
791
  // src/stdin_json.ts
792
792
  var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
793
+ var MAX_STDIN_WALL_MS = 6e4;
793
794
  var MAX_STDIN_BYTES = 64 * 1024 * 1024;
794
795
  function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
795
796
  return new Promise((resolve2, reject) => {
@@ -799,16 +800,27 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
799
800
  const finish = (fn) => {
800
801
  if (settled) return;
801
802
  settled = true;
802
- clearTimeout(timer);
803
+ clearTimeout(idleTimer);
804
+ clearTimeout(wallTimer);
803
805
  process.stdin.removeListener("data", onData);
804
806
  process.stdin.removeListener("end", onEnd);
805
807
  process.stdin.removeListener("error", onError);
806
808
  fn();
807
809
  };
808
- const timer = setTimeout(() => {
810
+ let idleTimer = setTimeout(() => {
809
811
  finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
810
812
  }, timeoutMs);
813
+ const wallTimer = setTimeout(() => {
814
+ finish(() => {
815
+ process.stdin.destroy();
816
+ reject(new Error(`readStdinJson: stdin took longer than ${MAX_STDIN_WALL_MS} ms`));
817
+ });
818
+ }, MAX_STDIN_WALL_MS);
811
819
  const onData = (chunk) => {
820
+ clearTimeout(idleTimer);
821
+ idleTimer = setTimeout(() => {
822
+ finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
823
+ }, timeoutMs);
812
824
  totalBytes += chunk.length;
813
825
  if (totalBytes > maxBytes) {
814
826
  finish(() => {
@@ -4,7 +4,7 @@ import {
4
4
  loadConfig,
5
5
  redactSecrets,
6
6
  stripAnsiCodes
7
- } from "./token-goat-hook-chunk-CNLMPHXT.mjs";
7
+ } from "./token-goat-hook-chunk-Y2WHX2P3.mjs";
8
8
 
9
9
  // src/tool_filters/helpers.ts
10
10
  import * as fs from "node:fs";
@@ -6,7 +6,7 @@ import {
6
6
  loadBlob,
7
7
  sanitizeFtsQuery,
8
8
  storeBlob
9
- } from "./token-goat-hook-chunk-6QTWMSRO.mjs";
9
+ } from "./token-goat-hook-chunk-A77A26A7.mjs";
10
10
  import {
11
11
  SYMBOL_BODY_CHAR_CAP,
12
12
  extractErrorMessage,
@@ -16,7 +16,7 @@ import {
16
16
  redactSecrets,
17
17
  runGit,
18
18
  shortFingerprint
19
- } from "./token-goat-hook-chunk-CNLMPHXT.mjs";
19
+ } from "./token-goat-hook-chunk-Y2WHX2P3.mjs";
20
20
  import {
21
21
  registerReset
22
22
  } from "./token-goat-hook-chunk-BUOCULAM.mjs";
@@ -183,6 +183,7 @@ var HOOK_EVENTS = [
183
183
 
184
184
  // src/stdin_json.ts
185
185
  var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
186
+ var MAX_STDIN_WALL_MS = 6e4;
186
187
  var MAX_STDIN_BYTES = 64 * 1024 * 1024;
187
188
  function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
188
189
  return new Promise((resolve2, reject) => {
@@ -192,16 +193,27 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
192
193
  const finish = (fn) => {
193
194
  if (settled) return;
194
195
  settled = true;
195
- clearTimeout(timer);
196
+ clearTimeout(idleTimer);
197
+ clearTimeout(wallTimer);
196
198
  process.stdin.removeListener("data", onData);
197
199
  process.stdin.removeListener("end", onEnd);
198
200
  process.stdin.removeListener("error", onError);
199
201
  fn();
200
202
  };
201
- const timer = setTimeout(() => {
203
+ let idleTimer = setTimeout(() => {
202
204
  finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
203
205
  }, timeoutMs);
206
+ const wallTimer = setTimeout(() => {
207
+ finish(() => {
208
+ process.stdin.destroy();
209
+ reject(new Error(`readStdinJson: stdin took longer than ${MAX_STDIN_WALL_MS} ms`));
210
+ });
211
+ }, MAX_STDIN_WALL_MS);
204
212
  const onData = (chunk) => {
213
+ clearTimeout(idleTimer);
214
+ idleTimer = setTimeout(() => {
215
+ finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
216
+ }, timeoutMs);
205
217
  totalBytes += chunk.length;
206
218
  if (totalBytes > maxBytes) {
207
219
  finish(() => {
@@ -25,7 +25,7 @@ import {
25
25
  runSkeleton,
26
26
  runSymbol,
27
27
  withPinnedReads
28
- } from "./token-goat-hook-chunk-KGLMNOGD.mjs";
28
+ } from "./token-goat-hook-chunk-MW5HPEGD.mjs";
29
29
  import {
30
30
  buildProjectMap,
31
31
  embeddingsDepsAvailable,
@@ -34,7 +34,7 @@ import {
34
34
  getProjectIndexCounts,
35
35
  isWorkerRunning,
36
36
  mapLookupBytesSaved
37
- } from "./token-goat-hook-chunk-6QTWMSRO.mjs";
37
+ } from "./token-goat-hook-chunk-A77A26A7.mjs";
38
38
  import {
39
39
  VERSION,
40
40
  dataDir,
@@ -45,7 +45,7 @@ import {
45
45
  normalizePath,
46
46
  recordStat,
47
47
  resolveProjectRoot
48
- } from "./token-goat-hook-chunk-CNLMPHXT.mjs";
48
+ } from "./token-goat-hook-chunk-Y2WHX2P3.mjs";
49
49
  import "./token-goat-hook-chunk-BUOCULAM.mjs";
50
50
  import "./token-goat-hook-chunk-RFRLWOQH.mjs";
51
51
 
@@ -126,7 +126,19 @@ function forCompare(p) {
126
126
  return process.platform === "win32" ? p.toLowerCase() : p;
127
127
  }
128
128
  function resolveToolRoot(projectRoot) {
129
- return resolveProjectRoot(projectRoot !== void 0 ? { project: projectRoot } : {});
129
+ const resolved = resolveProjectRoot(projectRoot !== void 0 ? { project: projectRoot } : {});
130
+ assertRootAllowed(resolved);
131
+ return resolved;
132
+ }
133
+ var RootNotAllowedError = class extends Error {
134
+ };
135
+ function assertRootAllowed(resolvedRoot) {
136
+ const allowedRoots = loadConfig().mcp.allowed_roots;
137
+ if (allowedRoots.length === 0) return;
138
+ if (allowedRoots.some((allowed) => checkWithinProjectRoot(resolvedRoot, allowed).inside)) return;
139
+ throw new RootNotAllowedError(
140
+ `refused: "${resolvedRoot}" is not inside any root listed in mcp.allowed_roots. A caller-supplied projectRoot is untrusted input, so this deployment pins which roots may be named; add the root to mcp.allowed_roots (or TOKEN_GOAT_MCP_ALLOWED_ROOTS) to permit it.`
141
+ );
130
142
  }
131
143
  function checkWithinProjectRoot(target, resolvedRoot) {
132
144
  const root = forCompare(normalizePath(realPathOrSelf(resolvedRoot)));
@@ -150,16 +162,6 @@ function specFilePart(spec) {
150
162
  var NO_PINS = /* @__PURE__ */ new Map();
151
163
  function confineTargets(targets, resolvedRoot, splitCommas = true) {
152
164
  if (!loadConfig(resolvedRoot).mcp.confine_reads_to_project_root) return { ok: true, targets, pins: NO_PINS };
153
- const allowedRoots = loadConfig().mcp.allowed_roots;
154
- if (allowedRoots.length > 0 && !allowedRoots.some((allowed) => checkWithinProjectRoot(resolvedRoot, allowed).inside)) {
155
- return {
156
- ok: false,
157
- refusal: toCallToolResult({
158
- text: `refused: "${resolvedRoot}" is not inside any root listed in mcp.allowed_roots. A caller-supplied projectRoot is untrusted input, so this deployment pins which roots may be named; add the root to mcp.allowed_roots (or TOKEN_GOAT_MCP_ALLOWED_ROOTS) to permit it.`,
159
- code: 1
160
- })
161
- };
162
- }
163
165
  const checked = [];
164
166
  const pins = /* @__PURE__ */ new Map();
165
167
  for (const raw of targets) {