token-goat 2.9.13 → 2.9.15

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 (52) hide show
  1. package/README.md +28 -1
  2. package/dist/token-goat-chunk-2X2EBBC6.mjs +277 -0
  3. package/dist/token-goat-chunk-3BTK54F3.mjs +1733 -0
  4. package/dist/token-goat-chunk-3NSDDTGL.mjs +34 -0
  5. package/dist/token-goat-chunk-4EXFN2AW.mjs +29 -0
  6. package/dist/token-goat-chunk-5V7DAC7V.mjs +123 -0
  7. package/dist/token-goat-chunk-6DLVZDB6.mjs +34 -0
  8. package/dist/token-goat-chunk-7ZYK25AO.mjs +24 -0
  9. package/dist/token-goat-chunk-A4JYKD5H.mjs +144 -0
  10. package/dist/token-goat-chunk-AH6QILZM.mjs +26 -0
  11. package/dist/token-goat-chunk-ASYEPR3S.mjs +212 -0
  12. package/dist/{token-goat-chunk-RDITECDL.mjs → token-goat-chunk-ATIFTMRC.mjs} +31 -13
  13. package/dist/{token-goat-chunk-ZZI3IDQZ.mjs → token-goat-chunk-BL5LNGBG.mjs} +3935 -11949
  14. package/dist/{token-goat-chunk-4NXUKV7D.mjs → token-goat-chunk-C5JIO6HK.mjs} +8 -4
  15. package/dist/{token-goat-chunk-FZU7GMUS.mjs → token-goat-chunk-DQ4J5AFF.mjs} +50 -18
  16. package/dist/token-goat-chunk-ERTXEKB6.mjs +228 -0
  17. package/dist/{token-goat-chunk-6B44WLIF.mjs → token-goat-chunk-GIIHUSZX.mjs} +142 -21
  18. package/dist/token-goat-chunk-GMOUBOX4.mjs +386 -0
  19. package/dist/{token-goat-chunk-3ESRORNM.mjs → token-goat-chunk-GMQQA7E4.mjs} +12576 -12326
  20. package/dist/token-goat-chunk-IVUQLQWN.mjs +2046 -0
  21. package/dist/token-goat-chunk-K7F2BFIK.mjs +2430 -0
  22. package/dist/{token-goat-chunk-U7X6LQD2.mjs → token-goat-chunk-LCZBPOIN.mjs} +10197 -9717
  23. package/dist/token-goat-chunk-LMFO66YD.mjs +331 -0
  24. package/dist/token-goat-chunk-LT7JRU6K.mjs +22 -0
  25. package/dist/token-goat-chunk-MZDIJJ3R.mjs +420 -0
  26. package/dist/token-goat-chunk-NDPO7GAH.mjs +177 -0
  27. package/dist/token-goat-chunk-NDRP4KJQ.mjs +4371 -0
  28. package/dist/token-goat-chunk-NEI4NC54.mjs +424 -0
  29. package/dist/token-goat-chunk-NU7TLMQK.mjs +585 -0
  30. package/dist/token-goat-chunk-OSUFN2FV.mjs +326 -0
  31. package/dist/token-goat-chunk-OUGNPMDA.mjs +959 -0
  32. package/dist/token-goat-chunk-PM76YS22.mjs +1341 -0
  33. package/dist/token-goat-chunk-S4XRY446.mjs +2637 -0
  34. package/dist/{token-goat-chunk-YOA4N6WA.mjs → token-goat-chunk-SFAS46RE.mjs} +5 -3
  35. package/dist/{token-goat-chunk-QWSUZWFP.mjs → token-goat-chunk-SZWYESBS.mjs} +793 -650
  36. package/dist/{token-goat-chunk-B3CTCQTH.mjs → token-goat-chunk-XEPXYDPI.mjs} +3 -2
  37. package/dist/token-goat-chunk-XTQAOTSO.mjs +89 -0
  38. package/dist/token-goat-chunk-Y4AFKTHK.mjs +22 -0
  39. package/dist/token-goat-chunk-YKG35VHC.mjs +228 -0
  40. package/dist/{token-goat-chunk-JOXLE672.mjs → token-goat-chunk-YQ7WI2CO.mjs} +990 -106
  41. package/dist/{token-goat-chunk-2WC4ZUXN.mjs → token-goat-chunk-YZX7EFG4.mjs} +1 -1
  42. package/dist/token-goat-chunk-Z6UXPYJA.mjs +62 -0
  43. package/dist/token-goat-hook.mjs +16 -8
  44. package/dist/token-goat.core.mjs +27 -10
  45. package/docs/cli.md +9 -6
  46. package/package.json +4 -2
  47. package/dist/token-goat-chunk-FQCNJV4V.mjs +0 -693
  48. package/dist/token-goat-chunk-JVNPCQB7.mjs +0 -31
  49. package/dist/token-goat-chunk-P2PU4CR5.mjs +0 -26
  50. package/dist/token-goat-chunk-QKXBGBQR.mjs +0 -3653
  51. package/dist/token-goat-chunk-UM47DRD3.mjs +0 -242
  52. package/dist/token-goat-chunk-UMXJN7DI.mjs +0 -5521
@@ -1,17 +1,31 @@
1
1
  import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
+ sanitizeFtsQuery
5
+ } from "./token-goat-chunk-2X2EBBC6.mjs";
6
+ import {
7
+ DEFAULT_MAX_AGE_MS,
8
+ blobPath,
4
9
  countRedactionPlaceholders,
5
- detectHarness,
6
- loadConfig,
7
- parse,
10
+ isBlobStale,
11
+ loadBlob,
8
12
  recordStat,
9
13
  recordUnmappedTool,
10
14
  redactSecrets,
11
15
  savedTokensFromBytes,
12
- stringify,
16
+ storeBlob,
13
17
  stripAnsiEscapes
14
- } from "./token-goat-chunk-UMXJN7DI.mjs";
18
+ } from "./token-goat-chunk-IVUQLQWN.mjs";
19
+ import {
20
+ getDb
21
+ } from "./token-goat-chunk-K7F2BFIK.mjs";
22
+ import {
23
+ detectHarness,
24
+ loadConfig,
25
+ parse,
26
+ shortFingerprint,
27
+ stringify
28
+ } from "./token-goat-chunk-S4XRY446.mjs";
15
29
  import {
16
30
  registerReset
17
31
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -20,20 +34,18 @@ import {
20
34
  assertWriteInScope,
21
35
  atomicWriteText,
22
36
  backupFile,
23
- displaySafeText,
24
37
  ensureDirSync,
25
38
  escapeRegExp,
26
39
  extractErrorMessage,
40
+ globalDbPath,
27
41
  hookCommandFor,
28
42
  hookPowershellCommand,
29
- neutralizeOutsideFences,
30
- normalizeDarwinSystemAlias,
31
43
  projectPathIsConsultable,
32
44
  projectScopeRoot,
33
45
  recordCreatedConfig,
34
46
  removeCreatedBackups,
35
47
  removeFileInScope,
36
- resolveIndexPath,
48
+ runGit,
37
49
  safeSlice,
38
50
  stripDelimitedBlock,
39
51
  stripOwnHooksFromMap,
@@ -43,7 +55,14 @@ import {
43
55
  withInstallScope,
44
56
  writeIfDifferent,
45
57
  writeJsonSettings
46
- } from "./token-goat-chunk-QKXBGBQR.mjs";
58
+ } from "./token-goat-chunk-PM76YS22.mjs";
59
+ import {
60
+ displaySafeText,
61
+ neutralizeOutsideFences,
62
+ normalizeDarwinSystemAlias,
63
+ normalizePath,
64
+ resolveIndexPath
65
+ } from "./token-goat-chunk-ERTXEKB6.mjs";
47
66
  import {
48
67
  init_define_import_meta_env
49
68
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -513,6 +532,27 @@ const TOOL_TO_TG = {
513
532
  exit_plan_mode: 'ExitPlanMode',
514
533
  }
515
534
 
535
+ function foldToolName(name) {
536
+ return typeof name === 'string' ? name.toLowerCase().replace(/[_-]/g, '') : ''
537
+ }
538
+
539
+ const FOLDED_TOOL_TO_TG = {}
540
+ for (const [k, v] of Object.entries(TOOL_TO_TG)) {
541
+ FOLDED_TOOL_TO_TG[foldToolName(k)] = v
542
+ }
543
+ // Also register folded entries for canonical TG tool names directly (e.g. WebSearch -> websearch)
544
+ for (const v of Object.values(TOOL_TO_TG)) {
545
+ FOLDED_TOOL_TO_TG[foldToolName(v)] = v
546
+ }
547
+
548
+ function resolveCanonicalToolName(name) {
549
+ if (!name || typeof name !== 'string') return name
550
+ const direct = TOOL_TO_TG[name]
551
+ if (direct !== undefined) return direct
552
+ const folded = foldToolName(name)
553
+ return FOLDED_TOOL_TO_TG[folded] || name
554
+ }
555
+
516
556
  // Confirmed via github/copilot-cli#3349 (open, unresolved as of writing): some
517
557
  // real Copilot CLI invocations send toolArgs as a JSON-*encoded string*
518
558
  // rather than a parsed object, contradicting the documented schema. Left
@@ -583,11 +623,12 @@ function remapToolInput(copilotToolName, input) {
583
623
  let out = input
584
624
  // Add the canonical key alongside the original rather than renaming it, so nothing that
585
625
  // might read the original 'path'/'shellId' key elsewhere (e.g. a future handler) loses it.
586
- const pathKey = FILE_PATH_ARG_KEY[copilotToolName]
626
+ const folded = foldToolName(copilotToolName)
627
+ const pathKey = FILE_PATH_ARG_KEY[copilotToolName] || (folded === 'view' || folded === 'edit' || folded === 'create' ? 'path' : undefined)
587
628
  if (pathKey !== undefined && pathKey in out) {
588
629
  out = Object.assign({}, out, { file_path: out[pathKey] })
589
630
  }
590
- const idKey = POLL_ID_ARG_KEY[copilotToolName]
631
+ const idKey = POLL_ID_ARG_KEY[copilotToolName] || (folded === 'readbash' || folded === 'readpowershell' ? 'shellId' : undefined)
591
632
  if (idKey !== undefined && idKey in out) {
592
633
  out = Object.assign({}, out, { bash_id: out[idKey] })
593
634
  }
@@ -752,7 +793,7 @@ async function main() {
752
793
  let originalToolArgs = {}
753
794
  if (toolName) {
754
795
  originalToolArgs = parseMaybeJsonObject(payload && payload.toolArgs)
755
- canonical.tool_name = TOOL_TO_TG[toolName] || toolName
796
+ canonical.tool_name = resolveCanonicalToolName(toolName)
756
797
  canonical.tool_input = remapToolInput(toolName, originalToolArgs)
757
798
  }
758
799
 
@@ -842,12 +883,12 @@ async function main() {
842
883
  return
843
884
  }
844
885
 
845
- process.stdout.write(JSON.stringify(translate(copilotEvent, resp, toolName, originalToolArgs)))
886
+ process.stdout.write(JSON.stringify(translate(copilotEvent, resp, toolName, originalToolArgs, payload)))
846
887
  }
847
888
 
848
889
  ${MATERIALIZE_SHRUNK_IMAGE_JS}
849
890
 
850
- function translate(copilotEvent, resp, toolName, originalToolArgs) {
891
+ function translate(copilotEvent, resp, toolName, originalToolArgs, payload) {
851
892
  if (copilotEvent === 'preToolUse') {
852
893
  const hso = resp && resp.hookSpecificOutput
853
894
  const denied = resp && (resp.decision === 'block' || (hso && hso.permissionDecision === 'deny'))
@@ -861,7 +902,7 @@ function translate(copilotEvent, resp, toolName, originalToolArgs) {
861
902
  return { modifiedArgs: updated }
862
903
  }
863
904
  // Image shrink has no context channel on this event (Copilot's preToolUse output schema carries no additionalContext; docs/hook-channel-matrix.md footnote 7) -- translate it into a rewritten view path pointing at a materialized shrunk copy instead, via the same modifiedArgs channel the updatedInput branch above already uses. modifiedArgs REPLACES the tool call's args wholesale (ESr in the 1.0.80 bundle, see this module's header docblock), so the full original toolArgs are spread and only Copilot's own path key is swapped.
864
- if (toolName === 'view') {
905
+ if (toolName === 'view' || foldToolName(toolName) === 'view') {
865
906
  const shrunkPath = materializeShrunkImage(extractContext(resp))
866
907
  if (shrunkPath) return { modifiedArgs: Object.assign({}, originalToolArgs, { path: shrunkPath }) }
867
908
  }
@@ -869,52 +910,20 @@ function translate(copilotEvent, resp, toolName, originalToolArgs) {
869
910
  }
870
911
 
871
912
  if (copilotEvent === 'postToolUse') {
872
- // Verified against the shipping @github/copilot 1.0.80 bundle, not against the docs page.
873
- // NativeHookPipelineProcessor.postToolExecution (app.js offset 2043150) gates on
874
- // toolResult.resultType === "success", then does n.toolResultJson && CSr(e.toolResult,
875
- // n.toolResultJson), where CSr (offset 2032350) is an in-place Object.assign; the mutated
876
- // object is re-serialized back to native in the postTool callback at offset 1793926. So
877
- // modifiedResult IS honored on this event, and token-goat's rewriteOutput producers
878
- // (compression, injection fencing, image shrink) really do reach the model. resultType is
879
- // hardcoded 'success' because success is the only branch this event ever runs on.
880
- //
881
- // additionalContext on THIS event is dropped on the JS path. grep -abo "onAdditionalContext:"
882
- // app.js returns nothing, so the callback is never supplied, and the two "onAdditionalContext?"
883
- // call sites (offsets 2041896 and 2043300) are both no-ops. preToolsExecution (offset 2041832)
884
- // additionally pushes each context into an array that IS drained into the native return
885
- // payload (additional_contexts, offset 1791950); postToolExecution has no such push and its
886
- // native return payload (offset 1793926) has no additional_contexts key. Residual, stated
887
- // honestly: the native hookProcessorPostToolUse might fold additionalContext into the
888
- // toolResultJson it returns, and that was NOT verified. The evidence leans against it, since
889
- // the failure sibling path appends its context explicitly in JS via
890
- // hookAppendPostToolUseFailureContext and there is no success-path counterpart. The
891
- // out.additionalContext below is kept as cheap best-effort, not as a channel anything should
892
- // depend on -- see src/pending_context.ts, whose whole design depended on it.
893
- //
894
- // Failed tool calls never reach this event. postToolUse and postToolUseFailure are two
895
- // distinct hook events (both listed in the runtime.node hook-event enum at offset 101618150),
896
- // and the shipped copilot-sdk/types.d.ts says onPostToolUse "does not fire for non-success
897
- // results". The failure event cannot carry a fence either: PostToolUseFailureHookInput carries
898
- // only a stringified error message, not the tool result, and PostToolUseFailureHookOutput
899
- // consumes only additionalContext -- "modifiedResult or suppressOutput are not honored for
900
- // failure hooks". rejected/denied/timeout results trigger no post hook at all. So on Copilot,
901
- // the output of a failed tool call reaches the model unfenced, uncompressed and unshrunk, and
902
- // no response shape this shim could emit changes that. That gap is real and still open. What
903
- // is now wired is the narrower thing that IS possible there: the postToolUseFailure branch
904
- // below carries advisory text alongside the failure, and never rewrites it.
905
- //
906
- // Emitted camelCase-only. The inbound side around line 247 also tolerates a snake_case "VS
907
- // Code compatible" shape, and this comment used to justify the camelCase choice by claiming
908
- // PascalCase event registration selects the snake_case response format. That reasoning is
909
- // UNVERIFIED in 1.0.80: every hook-response field in the native string tables is camelCase,
910
- // and the snake_case hits in the bundle are unrelated internal Rust identifiers. camelCase
911
- // stays because it is what the bundle reads; the old justification is no longer asserted.
913
+ // modifiedResult is honored on success: rewriteOutput (compression, fencing, image shrink) reaches LLM.
914
+ // additionalContext is dropped on Copilot CLI JS path; we fold context into modifiedResult.textResultForLlm.
912
915
  const hso = resp && resp.hookSpecificOutput
913
916
  const updatedToolOutput = hso && hso.updatedToolOutput
914
917
  const context = extractContext(resp)
915
918
  const out = {}
916
919
  if (typeof updatedToolOutput === 'string') {
917
920
  out.modifiedResult = { resultType: 'success', textResultForLlm: updatedToolOutput }
921
+ } else if (context) {
922
+ const rawResult = payload && payload.toolResult
923
+ const originalText = rawResult && (typeof rawResult.textResultForLlm === 'string' ? rawResult.textResultForLlm : typeof rawResult.text_result_for_llm === 'string' ? rawResult.text_result_for_llm : undefined)
924
+ if (typeof originalText === 'string') {
925
+ out.modifiedResult = { resultType: 'success', textResultForLlm: originalText + '\\n\\n[token-goat: ' + context + ']' }
926
+ }
918
927
  }
919
928
  if (context) out.additionalContext = context
920
929
  return out
@@ -1035,6 +1044,29 @@ main()
1035
1044
  process.exitCode = 0
1036
1045
  })
1037
1046
  `;
1047
+ function foldToolName(name) {
1048
+ return typeof name === "string" ? name.toLowerCase().replace(/[_-]/g, "") : "";
1049
+ }
1050
+ var TOOL_TO_TG_MAPPING = {
1051
+ bash: "Bash",
1052
+ powershell: "Bash",
1053
+ read_bash: "BashOutput",
1054
+ read_powershell: "BashOutput",
1055
+ view: "Read",
1056
+ create: "Write",
1057
+ edit: "Edit",
1058
+ web_fetch: "WebFetch",
1059
+ web_search: "WebSearch",
1060
+ grep: "Grep",
1061
+ glob: "Glob",
1062
+ skill: "Skill",
1063
+ exit_plan_mode: "ExitPlanMode"
1064
+ };
1065
+ var FOLDED_MAPPING = {};
1066
+ for (const [k, v] of Object.entries(TOOL_TO_TG_MAPPING)) {
1067
+ FOLDED_MAPPING[foldToolName(k)] = v;
1068
+ FOLDED_MAPPING[foldToolName(v)] = v;
1069
+ }
1038
1070
 
1039
1071
  // src/bridges/copilot_cli_install.ts
1040
1072
  var COPILOT_CLI_HOOK_EVENTS = [
@@ -1747,6 +1779,10 @@ function getCwd(event) {
1747
1779
  const value = event.raw && typeof event.raw === "object" ? event.raw["cwd"] : void 0;
1748
1780
  return typeof value === "string" && value !== "" ? value : void 0;
1749
1781
  }
1782
+ function getTranscriptPath(event) {
1783
+ const value = event.raw && typeof event.raw === "object" ? event.raw["transcript_path"] : void 0;
1784
+ return typeof value === "string" && value !== "" ? value : void 0;
1785
+ }
1750
1786
  function extractToolResponseField(raw, keys) {
1751
1787
  const resp = raw["tool_response"];
1752
1788
  if (typeof resp === "string") return resp;
@@ -2047,7 +2083,7 @@ function toolMatcherFor(eventName) {
2047
2083
  if (parts.length === 0) return null;
2048
2084
  return parts.join("|");
2049
2085
  }
2050
- function foldToolName(name) {
2086
+ function foldToolName2(name) {
2051
2087
  return name.toLowerCase().replace(/[_-]/g, "");
2052
2088
  }
2053
2089
  function noteUnrecognizedTool(event, list) {
@@ -2060,8 +2096,8 @@ function noteUnrecognizedTool(event, list) {
2060
2096
  named.push(want);
2061
2097
  }
2062
2098
  if (named.length === 0) return;
2063
- const folded = foldToolName(toolName);
2064
- const nearMiss = named.find((n) => foldToolName(n) === folded) ?? null;
2099
+ const folded = foldToolName2(toolName);
2100
+ const nearMiss = named.find((n) => foldToolName2(n) === folded) ?? null;
2065
2101
  recordUnmappedTool(toolName, event.eventName, nearMiss);
2066
2102
  }
2067
2103
  async function runHook(event) {
@@ -2954,9 +2990,800 @@ function byteLength(s) {
2954
2990
  return utf8Len(s);
2955
2991
  }
2956
2992
 
2957
- // src/install.ts
2993
+ // src/bash_output_cache.ts
2994
+ init_define_import_meta_env();
2995
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
2996
+ import { resolve as resolve4 } from "path";
2997
+
2998
+ // src/hints/lang_patterns.ts
2999
+ init_define_import_meta_env();
3000
+ var LOCK_FILE_NAMES = /* @__PURE__ */ new Set([
3001
+ "package-lock.json",
3002
+ "yarn.lock",
3003
+ "pnpm-lock.yaml",
3004
+ "cargo.lock",
3005
+ "poetry.lock",
3006
+ "pipfile.lock",
3007
+ "uv.lock",
3008
+ "gemfile.lock",
3009
+ "go.sum",
3010
+ "composer.lock",
3011
+ "mix.lock",
3012
+ "pubspec.lock",
3013
+ "package.resolved"
3014
+ ]);
3015
+ var MANIFEST_FILE_NAMES = /* @__PURE__ */ new Set([
3016
+ "package.json",
3017
+ "pyproject.toml",
3018
+ "cargo.toml",
3019
+ "go.mod",
3020
+ "go.work",
3021
+ "pom.xml",
3022
+ "build.gradle",
3023
+ "build.gradle.kts",
3024
+ "settings.gradle",
3025
+ "composer.json",
3026
+ "gemfile",
3027
+ "mix.exs",
3028
+ "pubspec.yaml",
3029
+ "cmakelists.txt",
3030
+ "makefile",
3031
+ "project.clj",
3032
+ // TypeScript / JavaScript project configs
3033
+ "tsconfig.json",
3034
+ "jsconfig.json",
3035
+ // Bundler / framework configs
3036
+ "vite.config.ts",
3037
+ "vite.config.js",
3038
+ "webpack.config.js",
3039
+ "webpack.config.ts",
3040
+ "rollup.config.js",
3041
+ "rollup.config.ts",
3042
+ "esbuild.config.js",
3043
+ "next.config.js",
3044
+ "next.config.ts",
3045
+ "nuxt.config.ts"
3046
+ ]);
3047
+ var MANIFEST_EXTENSIONS = /* @__PURE__ */ new Set([
3048
+ ".cabal"
3049
+ ]);
3050
+ var MANIFEST_BASENAME_PATTERNS = [
3051
+ /^tsconfig(\..+)?\.json$/i
3052
+ ];
3053
+ var BUILD_DIR_NAMES = /* @__PURE__ */ new Set([
3054
+ "dist",
3055
+ "target",
3056
+ "build",
3057
+ "out",
3058
+ "__pycache__",
3059
+ ".next",
3060
+ ".nuxt",
3061
+ ".output",
3062
+ ".gradle",
3063
+ "_build",
3064
+ ".build",
3065
+ "pkg",
3066
+ "obj"
3067
+ ]);
3068
+ var ALWAYS_GENERATED_EXTS = /* @__PURE__ */ new Set([
3069
+ ".pyc",
3070
+ ".pyo",
3071
+ ".pyd",
3072
+ ".class",
3073
+ ".o",
3074
+ ".a",
3075
+ ".so",
3076
+ ".dylib",
3077
+ ".dll",
3078
+ ".tsbuildinfo"
3079
+ ]);
3080
+ var CONDITIONALLY_GENERATED_EXTS = /* @__PURE__ */ new Set([
3081
+ ".map",
3082
+ ".d.ts"
3083
+ ]);
3084
+ var BUILD_COMMAND_PATTERNS = [
3085
+ // Rust / Cargo
3086
+ /^\s*cargo\s+(build|test|run|check|clippy)\b/i,
3087
+ // Go
3088
+ /^\s*go\s+(build|test|run|vet)\b/i,
3089
+ // Maven
3090
+ /^\s*mvn\b/i,
3091
+ // Gradle (direct or wrapper)
3092
+ /^\s*(?:gradle|\.\/gradlew|gradlew)\b/i,
3093
+ // Python / pip
3094
+ /^\s*pip\s+(install|freeze)\b/i,
3095
+ // npm audit / npm outdated — read-only reporting commands with no side effects, safe to cache/recall. Each resolves a `lockfile`-scoped fingerprint via bash_output_cache.ts's isNpmAuditCommand/isNpmOutdatedCommand, but that fingerprinting never actually ran outside their own unit tests until this entry, since neither subcommand was recognized by isBuildCommand (the generic cache-reachability gate in hooks_bash.ts). `npm install`/`npm ci`/`npm list`/`npm ls` are deliberately left out — see the negative-case test in lang_patterns.test.ts.
3096
+ /^\s*npm\s+(audit|outdated)\b/i,
3097
+ // Poetry
3098
+ /^\s*poetry\s+(install|update)\b/i,
3099
+ // uv
3100
+ /^\s*uv\s+(sync|pip\s+install)\b/i,
3101
+ // Bundler (Ruby)
3102
+ /^\s*bundle\s+(install|update)\b/i,
3103
+ // Mix (Elixir)
3104
+ /^\s*mix\s+(deps\.get|compile|test)\b/i,
3105
+ // dotnet
3106
+ /^\s*dotnet\s+(build|test|restore)\b/i,
3107
+ // Make
3108
+ /^\s*make\b/i,
3109
+ // CMake build
3110
+ /^\s*cmake\s+--build\b/i,
3111
+ // Rake (Ruby)
3112
+ /^\s*rake\b/i,
3113
+ // TypeScript compiler (direct and via npx)
3114
+ /^\s*tsc(?:\s|$)/i,
3115
+ /^\s*npx\s+tsc(?:\s|$)/i,
3116
+ // Vite
3117
+ /^\s*vite\s+(build|dev|preview)\b/i,
3118
+ // Next.js
3119
+ /^\s*next\s+(build|dev|start)\b/i,
3120
+ // Nuxt
3121
+ /^\s*nuxt\s+(build|dev)\b/i,
3122
+ // Webpack
3123
+ /^\s*webpack\b/i,
3124
+ // esbuild
3125
+ /^\s*esbuild\b/i,
3126
+ // Rollup
3127
+ /^\s*rollup\b/i,
3128
+ // Turbo
3129
+ /^\s*turbo\s+(build|dev)\b/i
3130
+ ];
3131
+ var LOCK_FILE_COUNT = LOCK_FILE_NAMES.size;
3132
+ var MANIFEST_FILE_COUNT = MANIFEST_FILE_NAMES.size + MANIFEST_EXTENSIONS.size + MANIFEST_BASENAME_PATTERNS.length;
3133
+ var BUILD_DIR_COUNT = BUILD_DIR_NAMES.size;
3134
+ var GENERATED_EXT_COUNT = ALWAYS_GENERATED_EXTS.size + CONDITIONALLY_GENERATED_EXTS.size;
3135
+ function isLockFile(basename2) {
3136
+ return LOCK_FILE_NAMES.has(basename2.toLowerCase());
3137
+ }
3138
+ function isManifestFile(basename2) {
3139
+ const lower = basename2.toLowerCase();
3140
+ if (MANIFEST_FILE_NAMES.has(lower)) return true;
3141
+ const dot = lower.lastIndexOf(".");
3142
+ if (dot !== -1 && MANIFEST_EXTENSIONS.has(lower.slice(dot))) return true;
3143
+ if (MANIFEST_BASENAME_PATTERNS.some((re) => re.test(basename2))) return true;
3144
+ return false;
3145
+ }
3146
+ function pathSegments(filePath) {
3147
+ return filePath.split(/[/\\]/).filter((s) => s.length > 0);
3148
+ }
3149
+ function isInBuildDir(filePath) {
3150
+ const segments = pathSegments(filePath);
3151
+ for (let i = 0; i < segments.length - 1; i++) {
3152
+ const seg = segments[i];
3153
+ if (seg !== void 0 && BUILD_DIR_NAMES.has(seg.toLowerCase())) return true;
3154
+ }
3155
+ return false;
3156
+ }
3157
+ function isGeneratedFile(filePath) {
3158
+ const lower = filePath.toLowerCase();
3159
+ for (const ext of ALWAYS_GENERATED_EXTS) {
3160
+ if (lower.endsWith(ext)) return true;
3161
+ }
3162
+ for (const ext of CONDITIONALLY_GENERATED_EXTS) {
3163
+ if (lower.endsWith(ext) && isInBuildDir(filePath)) return true;
3164
+ }
3165
+ return false;
3166
+ }
3167
+ function isBuildCommand(cmd) {
3168
+ return BUILD_COMMAND_PATTERNS.some((re) => re.test(cmd));
3169
+ }
3170
+ var TEST_RUNNER_COMMAND_PATTERNS = [
3171
+ // pytest (Python)
3172
+ /^pytest(?:\s|$)/i,
3173
+ // Jest / Vitest (direct or via npx)
3174
+ /^(?:npx\s+)?jest(?:\s|$)/i,
3175
+ /^(?:npx\s+)?vitest(?:\s+run|\s+watch)?(?:\s|$)/i,
3176
+ // Go
3177
+ /^go test(?:\s|$)/i,
3178
+ // Rust / Cargo
3179
+ /^cargo test(?:\s|$)/i,
3180
+ // npm test / npm run test
3181
+ /^npm (?:test|run test)(?:\s|$)/i,
3182
+ // yarn test / yarn run test / pnpm test / pnpm run test
3183
+ /^(?:yarn|pnpm)(?:\s+run)? test(?:\s|$)/i
3184
+ ];
3185
+ function isTestRunnerCommand(cmd) {
3186
+ return TEST_RUNNER_COMMAND_PATTERNS.some((re) => re.test(cmd));
3187
+ }
3188
+ var MONITORING_COMMAND_PATTERNS = [
3189
+ // GitHub CI
3190
+ { pattern: /^gh run (?:watch|view|list)/, recallHint: '--grep "fail|error|pass|\u2713|\u2717|conclusion"' },
3191
+ { pattern: /^gh run view.*--log/, recallHint: '--tail 100 --grep "Error|FAIL|error"' },
3192
+ { pattern: /^gh pr checks/, recallHint: '--grep "fail|error|pass|pending"' },
3193
+ { pattern: /^gh pr view/, recallHint: '--grep "state|title|mergeable|review|OPEN|CLOSED|MERGED"' },
3194
+ { pattern: /^gh pr list/, recallHint: '--grep "OPEN|DRAFT|MERGED|#"' },
3195
+ { pattern: /^gh issue view/, recallHint: '--grep "state|title|label|OPEN|CLOSED"' },
3196
+ { pattern: /^gh workflow (?:run|list|view)/, recallHint: '--grep "completed|failed|in_progress"' },
3197
+ // Dev servers (Next, Vite, Nuxt, Remix, Astro)
3198
+ { pattern: /^(?:npx\s+)?next dev/, recallHint: '--tail 30 --grep "error|warn|ready|compiled"' },
3199
+ { pattern: /^(?:npx\s+)?next build/, recallHint: '--grep "error|warn|Failed|\u2713"' },
3200
+ { pattern: /^(?:npx\s+)?vite\b(?:\s+dev|\s+build|\s+preview)?/, recallHint: '--tail 20 --grep "error|warn|ready"' },
3201
+ { pattern: /^(?:npx\s+)?nuxt dev/, recallHint: '--tail 30 --grep "error|warn|ready"' },
3202
+ { pattern: /^(?:npx\s+)?remix dev/, recallHint: '--tail 20 --grep "error|warn|ready"' },
3203
+ { pattern: /^(?:npx\s+)?astro dev/, recallHint: '--tail 20 --grep "error|warn|ready"' },
3204
+ // Test watchers
3205
+ { pattern: /^(?:npx\s+)?vitest(?:\s+run|\s+watch)?/, recallHint: '--grep "FAIL|PASS|Error|\u2713|\u2717"' },
3206
+ { pattern: /^(?:npx\s+)?jest(?:\s+--watch)?/, recallHint: '--grep "FAIL|PASS|Error|Tests:"' },
3207
+ { pattern: /^pytest(?:\s|$)/, recallHint: '--grep "FAILED|PASSED|ERROR|passed|failed"' },
3208
+ { pattern: /^(?:cargo\s+test|cargo\s+watch)/, recallHint: '--grep "FAILED|ok|error\\["' },
3209
+ { pattern: /^go test/, recallHint: '--grep "FAIL|ok|---"' },
3210
+ // Docker / compose
3211
+ { pattern: /^docker(?:\s+compose)?\s+logs/, recallHint: '--tail 50 --grep "error|warn|Error|WARN"' },
3212
+ { pattern: /^docker-compose\s+logs/, recallHint: '--tail 50 --grep "error|warn|Error|WARN"' },
3213
+ // File watchers / hot-reload
3214
+ { pattern: /^nodemon/, recallHint: '--tail 20 --grep "error|crash|restart"' },
3215
+ { pattern: /^air(?:\s|$)/, recallHint: '--tail 20 --grep "error|build failed"' },
3216
+ { pattern: /^cargo watch/, recallHint: '--tail 20 --grep "error\\[|warning\\["' },
3217
+ { pattern: /^watchexec/, recallHint: '--tail 20 --grep "error|warn"' },
3218
+ // Linters / formatters run repeatedly
3219
+ { pattern: /^(?:npx\s+)?eslint(?:\s|$)/, recallHint: '--grep "error|warning|\u2716|problems"' },
3220
+ { pattern: /^(?:npx\s+)?prettier(?:\s|$)/, recallHint: '--grep "unchanged|reformatted|error"' },
3221
+ { pattern: /^npx\s+tsc(?:\s|$)/, recallHint: '--grep "error TS|Cannot find|Type "' },
3222
+ { pattern: /^ruff(?:\s|$)/, recallHint: '--grep "error|warning|Found"' },
3223
+ { pattern: /^(?:cargo\s+)?clippy/, recallHint: '--grep "error\\[|warning\\["' },
3224
+ // git diff (full diff output — can be very large; excludes --stat which is small)
3225
+ { pattern: /^git diff(?!\s+--stat)(?:\s+HEAD)?(?:\s|$)/, recallHint: '--grep "@@|\\+\\+\\+|---|diff --git"' },
3226
+ { pattern: /^git diff\s+--cached(?!\s+--stat)/, recallHint: '--grep "@@|\\+\\+\\+|---|diff --git"' },
3227
+ // git push — output is dominated by the pre-push hook (full test suite, often 10K+); cache it so failures/rejections are recallable without re-pushing
3228
+ { pattern: /^git push(?:\s|$)/, recallHint: '--tail 100 --grep "error|fail|FAIL|rejected|To "' },
3229
+ // npm run * wrappers (npm test is excluded — too generic; npm run test is explicit)
3230
+ { pattern: /^npm run (?:test|spec)(?:\s|$)/, recallHint: '--grep "FAIL|PASS|Error|Tests:|\u2713|\u2717"' },
3231
+ { pattern: /^npm run build(?:\s|$)/, recallHint: '--grep "error|Built|Failed|\u2713|\u2717"' },
3232
+ { pattern: /^npm run (?:lint|typecheck|check|type-check)(?:\s|$)/, recallHint: '--grep "error|warning|\u2716|problems"' },
3233
+ // node scripts (migration runners, seed generators, etc. run repeatedly)
3234
+ { pattern: /^node\s+(?:scripts|src\/scripts)\/\S+\.m?js\b/, recallHint: '--tail 50 --grep "error|Error|done|complete|inserted|migrated"' },
3235
+ // External AI peer-review CLI tools (produce large outputs, run repeatedly per session)
3236
+ { pattern: /^codex(?:\s|$)/, recallHint: '--tail 100 --grep "error|suggestion|verdict|conclusion"' },
3237
+ { pattern: /^(?:~\/\.claude\/bin\/|\.claude\/bin\/)?glm\.sh(?:\s|$)/, recallHint: '--tail 100 --grep "error|verdict|conclusion|suggestion"' },
3238
+ // cat of a single source file — output is the full file; pre-bash emits a token-goat read suggestion
3239
+ { pattern: /^cat\s+\S+\.(java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj)\s*$/, recallHint: "--tail 50" },
3240
+ // PowerShell read-only system-state queries (stable over 60-120s)
3241
+ {
3242
+ pattern: /^(?:powershell(?:\.exe)?|pwsh(?:\.exe)?)\s+(?:-\w+\s+)*-Command\s+["']?Get-(?:CimInstance|Process|Counter|Service|PSDrive|WmiObject)\b/i,
3243
+ recallHint: "--tail 50"
3244
+ },
3245
+ // token-goat section/outline/symbol repeat calls — output is stable until the file changes
3246
+ { pattern: /^(?:token-goat|tg)\s+section\s+["'][^"']+["']/, recallHint: "" },
3247
+ { pattern: /^(?:token-goat|tg)\s+outline\s+\S+/, recallHint: "" },
3248
+ { pattern: /^(?:token-goat|tg)\s+symbol\s+\S+/, recallHint: "" }
3249
+ ];
3250
+ function isPsMultilineSystemQuery(cmd) {
3251
+ if (!/^(?:powershell(?:\.exe)?|pwsh(?:\.exe)?)\s+/i.test(cmd)) return false;
3252
+ const cmdIdx = cmd.search(/-Command\b/i);
3253
+ if (cmdIdx === -1) return false;
3254
+ const afterCmd = cmd.slice(cmdIdx + "-Command".length).trimStart();
3255
+ if (!/^["']/.test(afterCmd)) return false;
3256
+ const body = afterCmd.slice(1);
3257
+ if (!body.includes("\n")) return false;
3258
+ if (!/\bGet-(?:CimInstance|Process|Counter|Service|PSDrive|WmiObject)\b/i.test(body)) return false;
3259
+ if (/\b(?:Remove|Set|New|Restart|Install|Uninstall|Enable|Disable|Grant|Revoke|Register|Unregister)-/i.test(body)) return false;
3260
+ if (/\b(?:Stop-(?:Process|Service|Computer)|Invoke-(?:Expression|Command)|Clear-(?:Content|EventLog|Item))\b/i.test(body)) return false;
3261
+ return true;
3262
+ }
3263
+ function getMonitoringRecallHint(cmd) {
3264
+ const trimmed = cmd.trim();
3265
+ if (hasUnquotedOperator(trimmed, ["&&", "||", ";"])) return null;
3266
+ for (const { pattern, recallHint } of MONITORING_COMMAND_PATTERNS) {
3267
+ if (pattern.test(trimmed)) return recallHint;
3268
+ }
3269
+ if (isPsMultilineSystemQuery(trimmed)) return "--tail 50";
3270
+ return null;
3271
+ }
3272
+
3273
+ // src/recall_index.ts
2958
3274
  init_define_import_meta_env();
2959
3275
  import * as fs7 from "node:fs";
3276
+ function subdirForCacheType(cacheType) {
3277
+ return cacheType === "web" ? "web_outputs" : "bash_outputs";
3278
+ }
3279
+ function blobStillExists(cacheType, id) {
3280
+ try {
3281
+ const p = blobPath(subdirForCacheType(cacheType), id);
3282
+ return p !== null && fs7.existsSync(p);
3283
+ } catch {
3284
+ return false;
3285
+ }
3286
+ }
3287
+ var VALID_TYPES = ["bash", "web", "mcp"];
3288
+ var RECALL_DEFAULT_LIMIT = 10;
3289
+ function isRecallCacheType(value) {
3290
+ return VALID_TYPES.includes(value);
3291
+ }
3292
+ function indexRecallEntry(cacheType, id, label, content, storedAt) {
3293
+ try {
3294
+ const db = getDb(globalDbPath());
3295
+ db.prepare(
3296
+ `INSERT INTO cache_recall (cache_type, entry_id, label, content, stored_at)
3297
+ VALUES (@cacheType, @id, @label, @content, @storedAt)
3298
+ ON CONFLICT(cache_type, entry_id) DO UPDATE SET
3299
+ label = excluded.label,
3300
+ content = excluded.content,
3301
+ stored_at = excluded.stored_at`
3302
+ ).run({ cacheType, id, label, content, storedAt });
3303
+ } catch {
3304
+ }
3305
+ pruneCacheRecallRows();
3306
+ }
3307
+ function pruneCacheRecallRows(maxAgeMs = DEFAULT_MAX_AGE_MS) {
3308
+ try {
3309
+ const db = getDb(globalDbPath());
3310
+ const cutoff = Date.now() - maxAgeMs;
3311
+ db.prepare(`DELETE FROM cache_recall WHERE stored_at < ?`).run(cutoff);
3312
+ } catch {
3313
+ }
3314
+ }
3315
+ var _ftsAvailable = null;
3316
+ function hasFtsTable() {
3317
+ if (_ftsAvailable !== null) return _ftsAvailable;
3318
+ try {
3319
+ const db = getDb(globalDbPath());
3320
+ const row = db.prepare(`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'cache_recall_fts'`).get();
3321
+ _ftsAvailable = row !== void 0;
3322
+ } catch {
3323
+ _ftsAvailable = false;
3324
+ }
3325
+ return _ftsAvailable;
3326
+ }
3327
+ function buildSnippet(content, query, maxLen = 160) {
3328
+ const flat = content.replace(/\s+/g, " ").trim();
3329
+ const tokens = query.split(/\s+/).filter(Boolean);
3330
+ let idx = -1;
3331
+ const lowerFlat = flat.toLowerCase();
3332
+ for (const token of tokens) {
3333
+ const at = lowerFlat.indexOf(token.toLowerCase());
3334
+ if (at !== -1) {
3335
+ idx = at;
3336
+ break;
3337
+ }
3338
+ }
3339
+ if (idx === -1) {
3340
+ return flat.length > maxLen ? flat.slice(0, maxLen) + "..." : flat;
3341
+ }
3342
+ const start = Math.max(0, idx - Math.floor(maxLen / 3));
3343
+ const end = Math.min(flat.length, start + maxLen);
3344
+ const prefix = start > 0 ? "..." : "";
3345
+ const suffix = end < flat.length ? "..." : "";
3346
+ return prefix + flat.slice(start, end) + suffix;
3347
+ }
3348
+ function toFtsMatchExpr(query) {
3349
+ if (query.trim().length === 0) return null;
3350
+ return sanitizeFtsQuery(query);
3351
+ }
3352
+ function mapRowsToHits(rows, snippetQuery) {
3353
+ return rows.filter((r) => isRecallCacheType(r.cacheType)).map((r) => ({
3354
+ id: r.id,
3355
+ cacheType: r.cacheType,
3356
+ label: r.label ?? "",
3357
+ snippet: buildSnippet(r.content ?? "", snippetQuery),
3358
+ storedAt: r.storedAt ?? 0,
3359
+ blobPresent: blobStillExists(r.cacheType, r.id)
3360
+ }));
3361
+ }
3362
+ function ftsSearch(query, type, limit) {
3363
+ const matchExpr = toFtsMatchExpr(query);
3364
+ if (matchExpr === null) return [];
3365
+ const db = getDb(globalDbPath());
3366
+ const rows = type !== void 0 ? db.prepare(
3367
+ `SELECT c.entry_id AS id, c.cache_type AS cacheType, c.label AS label, c.content AS content, c.stored_at AS storedAt
3368
+ FROM cache_recall_fts
3369
+ JOIN cache_recall c ON c.row_id = cache_recall_fts.rowid
3370
+ WHERE cache_recall_fts MATCH ? AND c.cache_type = ?
3371
+ ORDER BY bm25(cache_recall_fts)
3372
+ LIMIT ?`
3373
+ ).all(matchExpr, type, limit) : db.prepare(
3374
+ `SELECT c.entry_id AS id, c.cache_type AS cacheType, c.label AS label, c.content AS content, c.stored_at AS storedAt
3375
+ FROM cache_recall_fts
3376
+ JOIN cache_recall c ON c.row_id = cache_recall_fts.rowid
3377
+ WHERE cache_recall_fts MATCH ?
3378
+ ORDER BY bm25(cache_recall_fts)
3379
+ LIMIT ?`
3380
+ ).all(matchExpr, limit);
3381
+ return mapRowsToHits(rows, query);
3382
+ }
3383
+ function likeSearch(query, type, limit) {
3384
+ const trimmed = query.trim();
3385
+ if (!trimmed) return [];
3386
+ const db = getDb(globalDbPath());
3387
+ const needle = `%${trimmed.replace(/\\/g, "\\\\").replace(/[%_]/g, (c) => `\\${c}`)}%`;
3388
+ const rows = type !== void 0 ? db.prepare(
3389
+ `SELECT entry_id AS id, cache_type AS cacheType, label, content, stored_at AS storedAt
3390
+ FROM cache_recall
3391
+ WHERE cache_type = ? AND (label LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\')
3392
+ ORDER BY stored_at DESC
3393
+ LIMIT ?`
3394
+ ).all(type, needle, needle, limit) : db.prepare(
3395
+ `SELECT entry_id AS id, cache_type AS cacheType, label, content, stored_at AS storedAt
3396
+ FROM cache_recall
3397
+ WHERE label LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\'
3398
+ ORDER BY stored_at DESC
3399
+ LIMIT ?`
3400
+ ).all(needle, needle, limit);
3401
+ return mapRowsToHits(rows, trimmed);
3402
+ }
3403
+ function listRecentRecall(opts = {}) {
3404
+ const limit = opts.limit ?? RECALL_DEFAULT_LIMIT;
3405
+ const columns = `SELECT entry_id AS id, cache_type AS cacheType, label, content, stored_at AS storedAt FROM cache_recall`;
3406
+ try {
3407
+ const db = getDb(globalDbPath());
3408
+ const rows = opts.type !== void 0 ? db.prepare(`${columns} WHERE cache_type = ? ORDER BY stored_at DESC LIMIT ?`).all(opts.type, limit) : db.prepare(`${columns} ORDER BY stored_at DESC LIMIT ?`).all(limit);
3409
+ return mapRowsToHits(rows, "");
3410
+ } catch {
3411
+ return [];
3412
+ }
3413
+ }
3414
+ function searchRecall(query, opts = {}) {
3415
+ const limit = opts.limit ?? RECALL_DEFAULT_LIMIT;
3416
+ if (query.trim() === "") return [];
3417
+ try {
3418
+ if (hasFtsTable()) {
3419
+ return ftsSearch(query, opts.type, limit);
3420
+ }
3421
+ return likeSearch(query, opts.type, limit);
3422
+ } catch {
3423
+ try {
3424
+ return likeSearch(query, opts.type, limit);
3425
+ } catch {
3426
+ return [];
3427
+ }
3428
+ }
3429
+ }
3430
+
3431
+ // src/bash_output_cache.ts
3432
+ var BASH_OUTPUT_SUBDIR = "bash_outputs";
3433
+ var _byId = /* @__PURE__ */ new Map();
3434
+ var COMMAND_PATTERNS = {
3435
+ gitMutable: /^\s*git\s+(diff|status)\b/i,
3436
+ gitImmutable: /^\s*git\s+show\s+[0-9a-f]{40}\b/i,
3437
+ gitDiffScoped: /\s--\s+\S/,
3438
+ dirListing: /^\s*(?:ls|eza|exa|dir|Get-ChildItem|gci)\b/i,
3439
+ depList: /^\s*(?:npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*(?:ls|list)\b|pip\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*(?:list|freeze)\b|uv\s+pip\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*(?:list|freeze)\b|pnpm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*(?:list|ls)\b|yarn\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*(?:list)\b|cargo\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*tree\b|bundle\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*(?:list|show)\b|composer\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*show\b)/i,
3440
+ npmInstall: /^\s*npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*(?:install|ci)\b/i,
3441
+ npmAudit: /^\s*npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*audit\b(?!.*(?:--fix|fix)\b)/i,
3442
+ npmOutdated: /^\s*npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=))*outdated\b/i,
3443
+ envProbe: /^\s*(?:node\s+(?:-v|--version)|npm\s+(?:-v|--version)|python3?\s+(?:(?:-V)\b|--?version)|git\s+--version|uv\s+--version|go\s+version|rustc\s+--version|cargo\s+--version|java\s+--version|ruby\s+--version|gem\s+--version|php\s+--version|which\b|where\b)/i,
3444
+ npx: /^(?!.*\b(?:install|add|remove|uninstall|i|rm|update|upgrade|set|get|publish|link|ci|audit|shrinkwrap|dedupe|prune|rebuild)\b)\s*npx\s+(?:--?yes\s+)?/i,
3445
+ gitPush: /^\s*git\s+push\b/i,
3446
+ testRunner: /^\s*(?:npx\s+)?(?:pytest|vitest|jest|go\s+test)\b/i,
3447
+ lintCommand: /^\s*(?:(?:npx\s+)?eslint|(?:uv\s+run\s+)?ruff)\b/i,
3448
+ npmRunScript: /^\s*npm\s+run(?:-script)?\b/i,
3449
+ catCommand: /^\s*cat\b/i
3450
+ };
3451
+ var DEP_LOCKFILES = {
3452
+ npm: ["package-lock.json", "yarn.lock"],
3453
+ pip: ["requirements.txt"],
3454
+ uv: ["uv.lock", "requirements.txt"],
3455
+ pnpm: ["pnpm-lock.yaml"],
3456
+ yarn: ["yarn.lock"],
3457
+ cargo: ["Cargo.lock"],
3458
+ bundle: ["Gemfile.lock"],
3459
+ composer: ["composer.lock"]
3460
+ };
3461
+ function isCommandOfType(cmd, type) {
3462
+ const pattern = COMMAND_PATTERNS[type];
3463
+ return pattern?.test(cmd) ?? false;
3464
+ }
3465
+ var isGitMutableCommand = (cmd) => isCommandOfType(cmd, "gitMutable");
3466
+ var isDirListingCommand = (cmd) => isCommandOfType(cmd, "dirListing");
3467
+ var isDepListCommand = (cmd) => isCommandOfType(cmd, "depList");
3468
+ var isNpmInstallCommand = (cmd) => isCommandOfType(cmd, "npmInstall");
3469
+ var isNpmAuditCommand = (cmd) => isCommandOfType(cmd, "npmAudit");
3470
+ var isNpmOutdatedCommand = (cmd) => isCommandOfType(cmd, "npmOutdated");
3471
+ var isGitPushCommand = (cmd) => isCommandOfType(cmd, "gitPush");
3472
+ var isTestRunnerCommand2 = (cmd) => isCommandOfType(cmd, "testRunner");
3473
+ var isLintCommand = (cmd) => isCommandOfType(cmd, "lintCommand");
3474
+ var isNpmRunScriptCommand = (cmd) => isCommandOfType(cmd, "npmRunScript");
3475
+ var isCatCommand = (cmd) => isCommandOfType(cmd, "catCommand");
3476
+ function isScopedGitStatusOrDiffStatCommand(cmd) {
3477
+ if (!isCommandOfType(cmd, "gitMutable")) return false;
3478
+ if (!isCommandOfType(cmd, "gitDiffScoped")) return false;
3479
+ if (/^\s*git\s+diff\b/i.test(cmd) && !/--stat\b/.test(cmd)) return false;
3480
+ return true;
3481
+ }
3482
+ function gitStateFingerprintSync(cwd) {
3483
+ try {
3484
+ const headResult = runGit(["rev-parse", "HEAD"], { cwd });
3485
+ if (headResult.exitCode !== 0) return null;
3486
+ const headSha = headResult.stdout.trim();
3487
+ let statusHash = "";
3488
+ const statusResult = runGit(["status", "--porcelain"], { cwd });
3489
+ if (statusResult.exitCode === 0) {
3490
+ statusHash = shortFingerprint(statusResult.stdout);
3491
+ }
3492
+ const key = `${headSha}\0${statusHash}`;
3493
+ return shortFingerprint(key);
3494
+ } catch {
3495
+ return null;
3496
+ }
3497
+ }
3498
+ var DIR_FINGERPRINT_LISTING_CAP_ENTRIES = 1e4;
3499
+ function dirStateFingerprintSync(path12) {
3500
+ try {
3501
+ const stat = statSync3(path12);
3502
+ if (!stat.isDirectory()) return null;
3503
+ const entries = readdirSync2(path12);
3504
+ if (entries.length <= DIR_FINGERPRINT_LISTING_CAP_ENTRIES) {
3505
+ return shortFingerprint(entries.slice().sort().join("\0"));
3506
+ }
3507
+ return shortFingerprint(stat.mtimeMs.toString());
3508
+ } catch {
3509
+ return null;
3510
+ }
3511
+ }
3512
+ var FILE_FINGERPRINT_CONTENT_CAP_BYTES = 2 * 1024 * 1024;
3513
+ function fileStateFingerprintSync(path12) {
3514
+ try {
3515
+ const stat = statSync3(path12);
3516
+ if (!stat.isFile()) return null;
3517
+ if (stat.size <= FILE_FINGERPRINT_CONTENT_CAP_BYTES) {
3518
+ return shortFingerprint(readFileSync5(path12));
3519
+ }
3520
+ return shortFingerprint(`${stat.mtimeMs}\0${stat.size}`);
3521
+ } catch {
3522
+ return null;
3523
+ }
3524
+ }
3525
+ function depLockfileFingerprintSync(cmd, cwd) {
3526
+ if (!cwd) return null;
3527
+ const stripped = cmd.trim();
3528
+ const firstToken = stripped.split(/\s+/)[0]?.toLowerCase() || "";
3529
+ if (!firstToken) return null;
3530
+ const candidates = firstToken === "uv" ? DEP_LOCKFILES["uv"] : DEP_LOCKFILES[firstToken];
3531
+ if (!candidates) return null;
3532
+ for (const lockfile of candidates) {
3533
+ try {
3534
+ const content = readFileSync5(resolve4(cwd, lockfile));
3535
+ return shortFingerprint(content);
3536
+ } catch {
3537
+ continue;
3538
+ }
3539
+ }
3540
+ return null;
3541
+ }
3542
+ function normalizeCommandForCacheKey(cmd) {
3543
+ const trimmed = cmd.trim();
3544
+ const tokens = [];
3545
+ let current = "";
3546
+ let quoteChar = null;
3547
+ const flush = () => {
3548
+ if (current.length > 0) {
3549
+ tokens.push(current);
3550
+ current = "";
3551
+ }
3552
+ };
3553
+ for (let i = 0; i < trimmed.length; i++) {
3554
+ const ch = trimmed[i];
3555
+ if (quoteChar !== null) {
3556
+ current += ch;
3557
+ if (ch === quoteChar) quoteChar = null;
3558
+ continue;
3559
+ }
3560
+ if (ch === '"' || ch === "'") {
3561
+ quoteChar = ch;
3562
+ current += ch;
3563
+ continue;
3564
+ }
3565
+ if (/\s/.test(ch)) {
3566
+ flush();
3567
+ continue;
3568
+ }
3569
+ current += ch === "\\" ? "/" : ch;
3570
+ }
3571
+ flush();
3572
+ const normalized_tokens = tokens.map((token) => {
3573
+ if (token.startsWith("-") || ["&&", "||", "|", ">", ">>", ";", "&"].includes(token)) {
3574
+ return token;
3575
+ }
3576
+ if (token.startsWith("./") && !token.startsWith("../")) {
3577
+ token = token.slice(2);
3578
+ }
3579
+ if (token.endsWith("/") && token !== "/") {
3580
+ token = token.slice(0, -1);
3581
+ }
3582
+ return token || ".";
3583
+ });
3584
+ return normalized_tokens.join(" ");
3585
+ }
3586
+ function commandHashSync(command, cwd = null) {
3587
+ const normalized = normalizeCommandForCacheKey(command);
3588
+ let key = cwd ? `${normalizePath(cwd)}\0${normalized}` : normalized;
3589
+ if (cwd && isGitMutableCommand(command)) {
3590
+ const fp = gitStateFingerprintSync(cwd);
3591
+ if (fp) key = `${key}\0git:${fp}`;
3592
+ }
3593
+ if (cwd && isDirListingCommand(command)) {
3594
+ const target = extractFirstPathArg(command, cwd, cwd);
3595
+ if (target) {
3596
+ const fp = dirStateFingerprintSync(target);
3597
+ if (fp) key = `${key}\0dir:${fp}`;
3598
+ }
3599
+ }
3600
+ if (isDepListCommand(command)) {
3601
+ const fp = depLockfileFingerprintSync(command, cwd);
3602
+ if (fp) key = `${key}\0lockfile:${fp}`;
3603
+ }
3604
+ if (cwd && (isNpmInstallCommand(command) || isNpmAuditCommand(command) || isNpmOutdatedCommand(command))) {
3605
+ const fp = depLockfileFingerprintSync(command, cwd);
3606
+ if (fp) key = `${key}\0npm-install:${fp}`;
3607
+ }
3608
+ return shortFingerprint(key);
3609
+ }
3610
+ function commandHash(command, cwd = null) {
3611
+ return Promise.resolve(commandHashSync(command, cwd));
3612
+ }
3613
+ function computeBashFingerprints(command, cwd) {
3614
+ const fingerprints = {};
3615
+ if (cwd && (isGitMutableCommand(command) || isGitPushCommand(command) || isTestRunnerCommand2(command) || isLintCommand(command) || isBuildCommand(command) || isNpmRunScriptCommand(command))) {
3616
+ const fp = gitStateFingerprintSync(cwd);
3617
+ if (fp) fingerprints.git = fp;
3618
+ }
3619
+ if (cwd && isDirListingCommand(command)) {
3620
+ const target = extractFirstPathArg(command, cwd, cwd);
3621
+ if (target) {
3622
+ const fp = dirStateFingerprintSync(target);
3623
+ if (fp) fingerprints.dir = fp;
3624
+ }
3625
+ }
3626
+ if (isDepListCommand(command) || cwd && (isNpmInstallCommand(command) || isNpmAuditCommand(command) || isNpmOutdatedCommand(command))) {
3627
+ const fp = depLockfileFingerprintSync(command, cwd);
3628
+ if (fp) fingerprints.lockfile = fp;
3629
+ }
3630
+ if (cwd && isCatCommand(command)) {
3631
+ const target = extractFirstPathArg(command, cwd, null);
3632
+ if (target) {
3633
+ const fp = fileStateFingerprintSync(target);
3634
+ if (fp) fingerprints.file = fp;
3635
+ }
3636
+ }
3637
+ return Object.keys(fingerprints).length > 0 ? fingerprints : void 0;
3638
+ }
3639
+ function isBashEntryStale(entry, command, cwd) {
3640
+ const stored = entry.fingerprints;
3641
+ if (!stored) return false;
3642
+ const current = computeBashFingerprints(command, cwd);
3643
+ if (stored.git !== void 0 && stored.git !== current?.git) return true;
3644
+ if (stored.dir !== void 0 && stored.dir !== current?.dir) return true;
3645
+ if (stored.lockfile !== void 0 && stored.lockfile !== current?.lockfile) return true;
3646
+ if (stored.file !== void 0 && stored.file !== current?.file) return true;
3647
+ return false;
3648
+ }
3649
+ function tokenizeShellArgs(cmd) {
3650
+ const trimmed = cmd.trim();
3651
+ const tokens = [];
3652
+ let current = "";
3653
+ let quoteChar = null;
3654
+ let hasToken = false;
3655
+ for (let i = 0; i < trimmed.length; i++) {
3656
+ const ch = trimmed[i];
3657
+ if (quoteChar !== null) {
3658
+ if (ch === quoteChar) {
3659
+ quoteChar = null;
3660
+ } else {
3661
+ current += ch;
3662
+ }
3663
+ continue;
3664
+ }
3665
+ if (ch === '"' || ch === "'") {
3666
+ quoteChar = ch;
3667
+ hasToken = true;
3668
+ continue;
3669
+ }
3670
+ if (/\s/.test(ch)) {
3671
+ if (hasToken) {
3672
+ tokens.push(current);
3673
+ current = "";
3674
+ hasToken = false;
3675
+ }
3676
+ continue;
3677
+ }
3678
+ current += ch;
3679
+ hasToken = true;
3680
+ }
3681
+ if (hasToken) tokens.push(current);
3682
+ return tokens;
3683
+ }
3684
+ function extractFirstPathArg(cmd, cwd, fallback) {
3685
+ const tokens = tokenizeShellArgs(cmd);
3686
+ for (let i = 1; i < tokens.length; i++) {
3687
+ const token = tokens[i];
3688
+ if (!token.startsWith("-")) {
3689
+ if (!token.startsWith("/")) {
3690
+ return resolve4(cwd, token);
3691
+ }
3692
+ return token;
3693
+ }
3694
+ }
3695
+ return fallback;
3696
+ }
3697
+ var ISSUE_LINE_PATTERN = /\b(?:error|fail(?:ed|ure)?|warning)\b/i;
3698
+ function summarizeOutputDelta(oldOutput, newOutput) {
3699
+ if (oldOutput === newOutput) return null;
3700
+ const oldLines = oldOutput.split("\n");
3701
+ const newLines = newOutput.split("\n");
3702
+ const oldIssueLines = oldLines.filter((l) => ISSUE_LINE_PATTERN.test(l));
3703
+ if (oldIssueLines.length > 0) {
3704
+ const newIssueLines = newLines.filter((l) => ISSUE_LINE_PATTERN.test(l));
3705
+ const priorTotal = oldIssueLines.length;
3706
+ const availableCounts = /* @__PURE__ */ new Map();
3707
+ for (const l of newIssueLines) availableCounts.set(l, (availableCounts.get(l) ?? 0) + 1);
3708
+ let resolved = 0;
3709
+ for (const l of oldIssueLines) {
3710
+ const avail = availableCounts.get(l) ?? 0;
3711
+ if (avail > 0) {
3712
+ availableCounts.set(l, avail - 1);
3713
+ } else {
3714
+ resolved++;
3715
+ }
3716
+ }
3717
+ const remaining = newIssueLines.length;
3718
+ return `[token-goat: delta] ${resolved} of ${priorTotal} prior issues resolved; remaining: ${remaining}`;
3719
+ }
3720
+ return `[token-goat: delta] output changed: ${oldLines.length} -> ${newLines.length} lines`;
3721
+ }
3722
+ function storeBashOutputSync(command, output, exitCode, cwd = null) {
3723
+ const id = commandHashSync(command, cwd);
3724
+ const fingerprints = computeBashFingerprints(command, cwd);
3725
+ const redactedOutput = redactSecrets(output).text;
3726
+ const redactedCommand = redactSecrets(command).text;
3727
+ const entry = {
3728
+ id,
3729
+ command: redactedCommand,
3730
+ output: redactedOutput,
3731
+ exitCode,
3732
+ storedAt: Date.now(),
3733
+ sizeBytes: Buffer.byteLength(redactedOutput, "utf-8"),
3734
+ ...fingerprints ? { fingerprints } : {}
3735
+ };
3736
+ _byId.set(id, entry);
3737
+ storeBlob(BASH_OUTPUT_SUBDIR, id, entry);
3738
+ indexRecallEntry("bash", id, redactedCommand, `${redactedCommand}
3739
+ ${redactedOutput}`, entry.storedAt);
3740
+ return id;
3741
+ }
3742
+ function storeBashOutput(command, output, exitCode, cwd = null) {
3743
+ return Promise.resolve(storeBashOutputSync(command, output, exitCode, cwd));
3744
+ }
3745
+ function coerceBashEntry(raw) {
3746
+ if (raw === null || typeof raw !== "object") return null;
3747
+ const o = raw;
3748
+ if (typeof o["id"] !== "string" || typeof o["command"] !== "string" || typeof o["output"] !== "string" || typeof o["exitCode"] !== "number" || typeof o["storedAt"] !== "number" || typeof o["sizeBytes"] !== "number") {
3749
+ return null;
3750
+ }
3751
+ const entry = {
3752
+ id: o["id"],
3753
+ command: o["command"],
3754
+ output: o["output"],
3755
+ exitCode: o["exitCode"],
3756
+ storedAt: o["storedAt"],
3757
+ sizeBytes: o["sizeBytes"]
3758
+ };
3759
+ const rawFingerprints = o["fingerprints"];
3760
+ if (rawFingerprints !== null && typeof rawFingerprints === "object") {
3761
+ const f = rawFingerprints;
3762
+ const fingerprints = {};
3763
+ if (typeof f["git"] === "string") fingerprints.git = f["git"];
3764
+ if (typeof f["dir"] === "string") fingerprints.dir = f["dir"];
3765
+ if (typeof f["lockfile"] === "string") fingerprints.lockfile = f["lockfile"];
3766
+ if (typeof f["file"] === "string") fingerprints.file = f["file"];
3767
+ if (Object.keys(fingerprints).length > 0) return { ...entry, fingerprints };
3768
+ }
3769
+ return entry;
3770
+ }
3771
+ function getBashOutput(id) {
3772
+ const hit = _byId.get(id);
3773
+ if (hit !== void 0) return hit;
3774
+ if (isBlobStale(BASH_OUTPUT_SUBDIR, id)) return null;
3775
+ const entry = coerceBashEntry(loadBlob(BASH_OUTPUT_SUBDIR, id));
3776
+ if (entry === null) return null;
3777
+ _byId.set(id, entry);
3778
+ return entry;
3779
+ }
3780
+ registerReset(() => {
3781
+ _byId = /* @__PURE__ */ new Map();
3782
+ });
3783
+
3784
+ // src/install.ts
3785
+ init_define_import_meta_env();
3786
+ import * as fs8 from "node:fs";
2960
3787
  import * as os4 from "node:os";
2961
3788
  import * as path7 from "node:path";
2962
3789
 
@@ -3140,7 +3967,7 @@ var SettingsParseError = class extends Error {
3140
3967
  function readSettings(p, opts = {}) {
3141
3968
  let raw;
3142
3969
  try {
3143
- raw = fs7.readFileSync(p, "utf8");
3970
+ raw = fs8.readFileSync(p, "utf8");
3144
3971
  } catch {
3145
3972
  return {};
3146
3973
  }
@@ -3272,7 +4099,7 @@ function isInstalled(scope = "user") {
3272
4099
  const hooks = settings.hooks;
3273
4100
  if (hooks === void 0) return false;
3274
4101
  const scriptPath = claudeHookScriptPath();
3275
- if (!fs7.existsSync(scriptPath)) return false;
4102
+ if (!fs8.existsSync(scriptPath)) return false;
3276
4103
  for (const [eventKey, eventArg] of HOOK_EVENT_MAP) {
3277
4104
  const expectedCommand = hookCommandFor(scriptPath, eventArg);
3278
4105
  if (!groupHasTokenGoat(hooks[eventKey], (c) => c === expectedCommand)) return false;
@@ -3310,7 +4137,7 @@ function findStrayClaudeMdBlocks(searchRoot) {
3310
4137
  const root = searchRoot ?? path7.join(os4.homedir(), ".claude");
3311
4138
  const canonical = path7.resolve(claudeMdPath());
3312
4139
  const found = [];
3313
- if (!fs7.existsSync(root)) return found;
4140
+ if (!fs8.existsSync(root)) return found;
3314
4141
  const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
3315
4142
  const MAX_DEPTH = 6;
3316
4143
  const hasRealBlock = (text) => {
@@ -3326,7 +4153,7 @@ function findStrayClaudeMdBlocks(searchRoot) {
3326
4153
  if (depth > MAX_DEPTH) return;
3327
4154
  let entries;
3328
4155
  try {
3329
- entries = fs7.readdirSync(dir, { withFileTypes: true });
4156
+ entries = fs8.readdirSync(dir, { withFileTypes: true });
3330
4157
  } catch {
3331
4158
  return;
3332
4159
  }
@@ -3340,7 +4167,7 @@ function findStrayClaudeMdBlocks(searchRoot) {
3340
4167
  if (path7.resolve(full) === canonical) continue;
3341
4168
  let text;
3342
4169
  try {
3343
- text = fs7.readFileSync(full, "utf8");
4170
+ text = fs8.readFileSync(full, "utf8");
3344
4171
  } catch {
3345
4172
  continue;
3346
4173
  }
@@ -3378,7 +4205,7 @@ function installSkill() {
3378
4205
  const p = skillPath();
3379
4206
  let existing = null;
3380
4207
  try {
3381
- existing = fs7.readFileSync(p, "utf8");
4208
+ existing = fs8.readFileSync(p, "utf8");
3382
4209
  } catch {
3383
4210
  }
3384
4211
  const skillContent = skillMdContent();
@@ -3390,17 +4217,17 @@ function installSkill() {
3390
4217
  }
3391
4218
  function uninstallSkill() {
3392
4219
  const dir = skillDir();
3393
- if (!fs7.existsSync(dir)) return false;
4220
+ if (!fs8.existsSync(dir)) return false;
3394
4221
  removeCreatedBackups(skillPath());
3395
4222
  assertWriteInScope(dir);
3396
- fs7.rmSync(dir, { recursive: true, force: true });
4223
+ fs8.rmSync(dir, { recursive: true, force: true });
3397
4224
  return true;
3398
4225
  }
3399
4226
 
3400
4227
  // src/bridges/codex_install.ts
3401
4228
  init_define_import_meta_env();
3402
4229
  import * as crypto2 from "node:crypto";
3403
- import * as fs8 from "node:fs";
4230
+ import * as fs9 from "node:fs";
3404
4231
  import * as os5 from "node:os";
3405
4232
  import * as path8 from "node:path";
3406
4233
 
@@ -3567,7 +4394,7 @@ function codexHookScriptPath() {
3567
4394
  function readCodexConfig(p, opts = {}) {
3568
4395
  let raw;
3569
4396
  try {
3570
- raw = fs8.readFileSync(p, "utf8");
4397
+ raw = fs9.readFileSync(p, "utf8");
3571
4398
  } catch {
3572
4399
  return {};
3573
4400
  }
@@ -3773,7 +4600,7 @@ function uninstallCodex() {
3773
4600
  removedAny = true;
3774
4601
  }
3775
4602
  try {
3776
- fs8.unlinkSync(scriptPath);
4603
+ fs9.unlinkSync(scriptPath);
3777
4604
  removedAny = true;
3778
4605
  } catch {
3779
4606
  }
@@ -3809,10 +4636,10 @@ function isCodexInstalled() {
3809
4636
  const expectedHash = computeCodexHookHash(eventArg, expectedCommand);
3810
4637
  if (hooksState[stateKey]?.trusted_hash !== expectedHash) return false;
3811
4638
  }
3812
- if (!fs8.existsSync(scriptPath)) return false;
4639
+ if (!fs9.existsSync(scriptPath)) return false;
3813
4640
  let agents;
3814
4641
  try {
3815
- agents = fs8.readFileSync(codexAgentsPath(), "utf8");
4642
+ agents = fs9.readFileSync(codexAgentsPath(), "utf8");
3816
4643
  } catch {
3817
4644
  return false;
3818
4645
  }
@@ -3837,7 +4664,7 @@ function stripAgentsBlock(p) {
3837
4664
 
3838
4665
  // src/bridges/kimi_install.ts
3839
4666
  init_define_import_meta_env();
3840
- import * as fs9 from "node:fs";
4667
+ import * as fs10 from "node:fs";
3841
4668
  import * as os6 from "node:os";
3842
4669
  import * as path9 from "node:path";
3843
4670
 
@@ -3967,7 +4794,7 @@ function kimiSkillPath() {
3967
4794
  function readKimiConfig(p, opts = {}) {
3968
4795
  let raw;
3969
4796
  try {
3970
- raw = fs9.readFileSync(p, "utf8");
4797
+ raw = fs10.readFileSync(p, "utf8");
3971
4798
  } catch {
3972
4799
  return {};
3973
4800
  }
@@ -4070,16 +4897,16 @@ function uninstallKimi() {
4070
4897
  }
4071
4898
  if (stripKimiAgentsBlock(kimiAgentsPath())) removed = true;
4072
4899
  try {
4073
- if (fs9.existsSync(kimiSkillDir())) {
4900
+ if (fs10.existsSync(kimiSkillDir())) {
4074
4901
  removeCreatedBackups(kimiSkillPath());
4075
- fs9.rmSync(kimiSkillDir(), { recursive: true, force: true });
4902
+ fs10.rmSync(kimiSkillDir(), { recursive: true, force: true });
4076
4903
  removed = true;
4077
4904
  }
4078
4905
  } catch {
4079
4906
  }
4080
4907
  try {
4081
- if (fs9.existsSync(kimiHookScriptPath())) {
4082
- fs9.rmSync(kimiHookScriptPath(), { force: true });
4908
+ if (fs10.existsSync(kimiHookScriptPath())) {
4909
+ fs10.rmSync(kimiHookScriptPath(), { force: true });
4083
4910
  removed = true;
4084
4911
  }
4085
4912
  } catch {
@@ -4100,7 +4927,7 @@ function isKimiInstalled() {
4100
4927
 
4101
4928
  // src/bridges/grok_install.ts
4102
4929
  init_define_import_meta_env();
4103
- import * as fs10 from "node:fs";
4930
+ import * as fs11 from "node:fs";
4104
4931
  import * as os7 from "node:os";
4105
4932
  import * as path10 from "node:path";
4106
4933
 
@@ -4244,12 +5071,12 @@ function uninstallGrok() {
4244
5071
  const scriptPath = grokHookScriptPath();
4245
5072
  let removedAny = false;
4246
5073
  try {
4247
- fs10.unlinkSync(configPath);
5074
+ fs11.unlinkSync(configPath);
4248
5075
  removedAny = true;
4249
5076
  } catch {
4250
5077
  }
4251
5078
  try {
4252
- fs10.unlinkSync(scriptPath);
5079
+ fs11.unlinkSync(scriptPath);
4253
5080
  removedAny = true;
4254
5081
  } catch {
4255
5082
  }
@@ -4257,12 +5084,12 @@ function uninstallGrok() {
4257
5084
  return removedAny;
4258
5085
  }
4259
5086
  function isGrokInstalled() {
4260
- return fs10.existsSync(grokConfigPath()) && fs10.existsSync(grokHookScriptPath());
5087
+ return fs11.existsSync(grokConfigPath()) && fs11.existsSync(grokHookScriptPath());
4261
5088
  }
4262
5089
 
4263
5090
  // src/bridges/vscode_install.ts
4264
5091
  init_define_import_meta_env();
4265
- import * as fs11 from "node:fs";
5092
+ import * as fs12 from "node:fs";
4266
5093
  import * as os8 from "node:os";
4267
5094
  import * as path11 from "node:path";
4268
5095
  var VSCODE_GUIDANCE_BEGIN = "<!-- token-goat-vscode-begin -->";
@@ -4311,7 +5138,7 @@ function vscodeUserSettingsPath() {
4311
5138
  function vscodeUsesClaudeHooks(settingsPath2 = vscodeUserSettingsPath()) {
4312
5139
  let text;
4313
5140
  try {
4314
- text = fs11.readFileSync(settingsPath2, "utf8");
5141
+ text = fs12.readFileSync(settingsPath2, "utf8");
4315
5142
  } catch {
4316
5143
  return false;
4317
5144
  }
@@ -4326,7 +5153,7 @@ var updateConfig = setTokenGoatServer;
4326
5153
  function otherScopeHasManagedServer(opts) {
4327
5154
  const otherPath = otherScopeMcpPath2(opts);
4328
5155
  if (opts.project !== true && !projectPathIsConsultable(otherPath, path11.resolve(opts.projectRoot ?? process.cwd()))) return false;
4329
- if (!fs11.existsSync(otherPath)) return false;
5156
+ if (!fs12.existsSync(otherPath)) return false;
4330
5157
  try {
4331
5158
  const config = readConfig(otherPath);
4332
5159
  const servers = config.value["servers"];
@@ -4343,7 +5170,7 @@ function vscodeDecoderConfigured(opts = {}) {
4343
5170
  if (projectPathIsConsultable(projectPath, path11.resolve(opts.projectRoot))) checkedPaths.push(projectPath);
4344
5171
  }
4345
5172
  for (const candidate of checkedPaths) {
4346
- if (!fs11.existsSync(candidate)) continue;
5173
+ if (!fs12.existsSync(candidate)) continue;
4347
5174
  try {
4348
5175
  const config = readConfig(candidate);
4349
5176
  const servers = config.value["servers"];
@@ -4358,7 +5185,7 @@ function vscodeDecoderConfigured(opts = {}) {
4358
5185
  }
4359
5186
  function writeGuidance2(filePath, userScope) {
4360
5187
  let created = false;
4361
- if (userScope && !fs11.existsSync(filePath)) {
5188
+ if (userScope && !fs12.existsSync(filePath)) {
4362
5189
  ensureDirSync(path11.dirname(filePath));
4363
5190
  atomicWriteText(filePath, USER_INSTRUCTIONS_FRONTMATTER);
4364
5191
  created = true;
@@ -4409,7 +5236,7 @@ function installVscodeScoped(opts) {
4409
5236
  throw new Error(`VS Code MCP JSON at ${mcpPath} already has a non-token-goat-managed server named "token-goat"`);
4410
5237
  }
4411
5238
  const next = updateConfig(config.text, managedServer());
4412
- const mcpExisted = fs11.existsSync(mcpPath);
5239
+ const mcpExisted = fs12.existsSync(mcpPath);
4413
5240
  ensureDirSync(path11.dirname(mcpPath));
4414
5241
  if (config.text !== next) {
4415
5242
  backupFile(mcpPath);
@@ -4435,7 +5262,7 @@ function uninstallVscodeScoped(opts) {
4435
5262
  assertProjectTargetsAreInTheProject(opts);
4436
5263
  const mcpPath = vscodeMcpPath(opts);
4437
5264
  let removed = false;
4438
- if (fs11.existsSync(mcpPath)) {
5265
+ if (fs12.existsSync(mcpPath)) {
4439
5266
  const config = readConfig(mcpPath);
4440
5267
  const servers = config.value["servers"];
4441
5268
  if (servers !== void 0 && (servers === null || typeof servers !== "object" || Array.isArray(servers))) {
@@ -4455,7 +5282,7 @@ function uninstallVscodeScoped(opts) {
4455
5282
  const instructionsPath = vscodeInstructionsPath(opts);
4456
5283
  if (stripDelimitedBlock(instructionsPath, BEGIN, END, opts.keepBackups === true)) {
4457
5284
  removed = true;
4458
- if (opts.project !== true && fs11.readFileSync(instructionsPath, "utf8").trim() === USER_INSTRUCTIONS_FRONTMATTER.trim()) removeFileInScope(instructionsPath);
5285
+ if (opts.project !== true && fs12.readFileSync(instructionsPath, "utf8").trim() === USER_INSTRUCTIONS_FRONTMATTER.trim()) removeFileInScope(instructionsPath);
4459
5286
  }
4460
5287
  if (opts.project === true) syncVisualStudioProjectGuidance(instructionsPath);
4461
5288
  if (releaseCopilotHooksFile(vscodeHooksDir(opts), "vscode", opts.keepBackups === true)) removed = true;
@@ -5318,25 +6145,25 @@ function makeLanguageFilter(cfg) {
5318
6145
  // src/tool_filters/ai_clis.ts
5319
6146
  var _GH_COPILOT_SPINNER_RE = /^\s*(?:Asking GitHub Copilot|Generating|Thinking|Fetching)\s*(?:\.{1,3}\s*)?$/i;
5320
6147
  var _GH_COPILOT_BANNER_RE = /^\s*(?:Welcome to GitHub Copilot|Using GitHub Copilot|Authenticated as|GitHub Copilot\s+v\d+)/i;
5321
- var _GH_COPILOT_DISCLAIMER_RE = /^\s*(?:Disclaimer:|This response was|GitHub Copilot|The commands?\s+(?:above|below)|Please review|Always review|Remember to|Note:|Tip:)/i;
6148
+ var _GH_COPILOT_DISCLAIMER_RE = /^\s*(?:Disclaimer:|This response was|GitHub Copilot|The commands?\s+(?:above|below)|Please review|Always review|Remember to)/i;
5322
6149
  var _AIDER_APPLYING_RE = /^\s*(?:Applying\s+edits?(?:\s+to\s+\S+)?|Applied\s+edit\s+to\s+\S+)\s*(?:\.{1,3}\s*)?$/i;
5323
6150
  var _AIDER_TOKENS_RE = /^\s*Tokens:\s+\d[\d,]*\s+sent,\s+\d[\d,]*\s+received/i;
5324
6151
  var _AIDER_COST_RE = /^\s*Cost:\s+\$[\d.]+\s+message,\s+\$[\d.]+\s+session/i;
5325
6152
  var _AIDER_REPOMAP_RE = /^\s*(?:Repo-map:|Added\s+\S+\s+to\s+the\s+chat|Removed\s+\S+\s+from\s+the\s+chat|Loading\s+repo\s+map|Updating\s+repo\s+map|Scanning\s+repo\s+contents|Using\s+\d+\s+tokens\s+of\s+repo\s+map)/i;
5326
6153
  var _AIDER_BANNER_RE = /^\s*aider\s+v\d+\.\d+/i;
5327
- var _AIDER_FOOTER_NOISE_RE = /^\s*(?:Use\s+ctrl-c|Run\s+with\s+--help|You\s+can\s+skip\s+this|Tip:|Note:)/i;
6154
+ var _AIDER_FOOTER_NOISE_RE = /^\s*(?:Use\s+ctrl-c|Run\s+with\s+--help|You\s+can\s+skip\s+this)/i;
5328
6155
  var _COPILOT_WORKSPACE_NOISE_RE = /^\s*(?:Starting\s+Copilot\s+workspace|Loading\s+model:|Copilot\s+workspace\s+(?:starting|loaded|ready)|Streaming\.\.\.|▌\s*$|Turn\s+\d+\s*:)/i;
5329
6156
  var _COPILOT_COMPLETION_STATS_RE = /^\s*(?:Completion\s+tokens:|Prompt\s+tokens:|Total\s+tokens:|Input\s+tokens:|Output\s+tokens:)\s*\d/i;
5330
6157
  var _GEMINI_STARTUP_RE = /^\s*(?:[✓✗►]|>)\s*(?:Model:|Theme:|Tools:|Sandbox:|Checkpointing:|Context(?:\s+limit)?:|Version:|Authenticated(?:\s+as)?:|Connecting)/i;
5331
6158
  var _GEMINI_BANNER_RE = /^\s*Gemini\s+CLI\s+v\d+/i;
5332
6159
  var _GEMINI_TOKEN_METER_RE = /^\s*(?:Token\s+usage|Context|Tokens):\s+[\d,]+\s*\/\s*[\d,]+/i;
5333
6160
  var _GEMINI_TOOL_SPINNER_RE = /^\s*[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏✓✗►✦]\s+(?:Call(?:ing|ed)|Execut(?:ing|ed)|Running)\s+\S+/;
5334
- var _GEMINI_FOOTER_RE = /^\s*(?:Type\s+\/help|Press\s+Ctrl|Use\s+Ctrl|Tip:|Note:)/i;
6161
+ var _GEMINI_FOOTER_RE = /^\s*(?:Type\s+\/help|Press\s+Ctrl|Use\s+Ctrl)/i;
5335
6162
  var _GEMINI_THINKING_RE = /^\s*(?:Thinking|Generating|Processing)\s*(?:\.{1,3}\s*)?$/i;
5336
6163
  var _CLAUDE_CLI_MODEL_HDR_RE = /^\s*[◆◇►✦]\s+claude-/i;
5337
6164
  var _CLAUDE_CLI_STATS_RE = /^\s*[↑↓⇑⇓]\s*\d[\d,]*(?:(?:\s*[↑↓⇑⇓]\s*|\s+)\d[\d,]*)?\s*tokens/i;
5338
6165
  var _CLAUDE_CLI_CONTEXT_RE = /^\s*(?:Context(?:\s+window)?|Token\s+limit):\s+[\d,]+\s*\/\s*[\d,]+/i;
5339
- var _CLAUDE_CLI_FOOTER_RE = /^\s*(?:Press\s+Ctrl|Enter\s+\/|Type\s+\/|Use\s+Ctrl|Tip:|Note:)/i;
6166
+ var _CLAUDE_CLI_FOOTER_RE = /^\s*(?:Press\s+Ctrl|Enter\s+\/|Type\s+\/|Use\s+Ctrl)/i;
5340
6167
  var _CLAUDE_CLI_SPINNER_RE = /^\s*[◎⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\s+(?:Thinking|Generating|Processing|Running)\s*(?:\.{1,3}\s*)?$/;
5341
6168
  var _CLAUDE_CLI_TOOL_LOG_RE = /^\s*(?:>\s+Using\s+tool:|✓\s+Tool\s+result:|◎\s+Tool:)/i;
5342
6169
  var _CLAUDE_CLI_SKIP_SUBCMDS = /* @__PURE__ */ new Set(["install", "update", "doctor", "config", "login", "logout"]);
@@ -8315,7 +9142,8 @@ ${stderr.replace(/\s+$/, "")}` : stderr;
8315
9142
  var awsFilter = new AwsFilter();
8316
9143
  var _AWS_UPLOAD_RE = /^upload:\s+\S+\s+to\s+s3:\/\//i;
8317
9144
  var _AWS_DOWNLOAD_RE = /^download:\s+s3:\/\//i;
8318
- var _AWS_S3_PROGRESS_RE = /^(?:Completed\s+\d|\d+(?:\.\d+)?\s*(?:KiB|MiB|GiB|B)\/s|Calculating|upload\s+failed:|download\s+failed:)/i;
9145
+ var _AWS_S3_TRANSFER_FAILED_RE = /^(?:upload|download|copy|delete|move)\s+failed:/i;
9146
+ var _AWS_S3_PROGRESS_RE = /^(?:Completed\s+\d|\d+(?:\.\d+)?\s*(?:KiB|MiB|GiB|B)\/s|Calculating)/i;
8319
9147
  var AWS_GLOBAL_VALUE_FLAGS = /* @__PURE__ */ new Set([
8320
9148
  "--profile",
8321
9149
  "--region",
@@ -8337,7 +9165,8 @@ var AwsCliFilter = class extends ToolFilter {
8337
9165
  _JSON_ARRAY_KEEP = 3;
8338
9166
  compressBody(stdout, stderr, _exitCode, argv) {
8339
9167
  const positionals = positionalArgs(argv.slice(1), AWS_GLOBAL_VALUE_FLAGS);
8340
- const isS3Transfer = positionals.length >= 2 && positionals[0] === "s3" && (positionals[1] === "cp" || positionals[1] === "sync" || positionals[1] === "mv");
9168
+ const isS3Transfer = positionals.length >= 2 && positionals[0] === "s3" && // `rm` belongs here for the failure path, not the volume one: `aws s3 rm --recursive` reports `delete failed:` per object, and routing it anywhere else meant those lines were never counted. Its `delete:` success lines are not folded into a count -- only `upload:`/`download:` are -- so adding it drops nothing that used to survive.
9169
+ (positionals[1] === "cp" || positionals[1] === "sync" || positionals[1] === "mv" || positionals[1] === "rm");
8341
9170
  const isCfnEvents = positionals.length >= 2 && positionals[0] === "cloudformation" && positionals[1] === "describe-stack-events";
8342
9171
  let text = stdout;
8343
9172
  if (isS3Transfer) {
@@ -8369,6 +9198,7 @@ ${stderr.replace(/\s+$/, "")}` : stderr;
8369
9198
  const kept = [];
8370
9199
  let uploadCount = 0;
8371
9200
  let downloadCount = 0;
9201
+ let failedCount = 0;
8372
9202
  let progressDropped = 0;
8373
9203
  for (const line of lines) {
8374
9204
  if (_AWS_UPLOAD_RE.test(line)) {
@@ -8379,6 +9209,11 @@ ${stderr.replace(/\s+$/, "")}` : stderr;
8379
9209
  downloadCount++;
8380
9210
  continue;
8381
9211
  }
9212
+ if (_AWS_S3_TRANSFER_FAILED_RE.test(line)) {
9213
+ failedCount++;
9214
+ kept.push(line);
9215
+ continue;
9216
+ }
8382
9217
  if (_AWS_S3_PROGRESS_RE.test(line)) {
8383
9218
  progressDropped++;
8384
9219
  continue;
@@ -8388,6 +9223,7 @@ ${stderr.replace(/\s+$/, "")}` : stderr;
8388
9223
  const notes = [];
8389
9224
  maybeNote(notes, uploadCount, `uploaded ${uploadCount} file(s)`);
8390
9225
  maybeNote(notes, downloadCount, `downloaded ${downloadCount} file(s)`);
9226
+ maybeNote(notes, failedCount, `${failedCount} transfer(s) failed`);
8391
9227
  maybeNote(notes, progressDropped, `dropped ${progressDropped} progress line(s)`);
8392
9228
  this.emitNotes(kept, notes);
8393
9229
  return this.finalize(kept);
@@ -9488,8 +10324,9 @@ var RgFilter = class _RgFilter extends ToolFilter {
9488
10324
  name = "rg";
9489
10325
  binaries = /* @__PURE__ */ new Set(["rg", "grep"]);
9490
10326
  static _SEP = "--";
9491
- static _CTX_LINE_RE = /^.+-\d+-/;
9492
- static _MATCH_LINE_RE = /^.+:\d+:/;
10327
+ // The path prefix is optional because ripgrep only prints one when the search covers more than one file: `rg -n -C2 pat one.ts` emits `12-text` and `14:text`, not `one.ts-12-text` and `one.ts:14:text`. Requiring the prefix meant neither regex matched anything on a single-file search -- the ordinary shape of the command -- so no context line was ever suppressed there, and the only thing removed was the cosmetic `--` separator, under a note announcing a compression that had not happened.
10328
+ static _CTX_LINE_RE = /^(?:.+-)?\d+-/;
10329
+ static _MATCH_LINE_RE = /^(?:.+:)?\d+:/;
9493
10330
  static _parseContextDepth(argv) {
9494
10331
  let depth = 0;
9495
10332
  const longFlags = /* @__PURE__ */ new Set(["--after-context", "--before-context", "--context"]);
@@ -9529,8 +10366,14 @@ var RgFilter = class _RgFilter extends ToolFilter {
9529
10366
  // RgFilter only handles context-block output (-A/-B/-C/--context); a plain
9530
10367
  // grep/rg with no context flags falls through to GrepFilter's per-file
9531
10368
  // match-count summarizer, which produces dramatically smaller output.
10369
+ // Both line regexes hard-code ripgrep's default field separators (`:` after a match's line number, `-` after a context line's). `--field-match-separator=-` makes a real match read `12-text`, which is exactly the shape this filter drops, so a search that overrides either separator is released to the generic path rather than compressed against an assumption it broke.
10370
+ static _hasCustomFieldSeparator(argv) {
10371
+ const flags = ["--field-match-separator", "--field-context-separator"];
10372
+ return argv.some((a) => flags.some((f) => a === f || a.startsWith(f + "=")));
10373
+ }
9532
10374
  matches(argv) {
9533
10375
  if (!super.matches(argv)) return false;
10376
+ if (_RgFilter._hasCustomFieldSeparator(argv)) return false;
9534
10377
  return _RgFilter._hasContextFlags(argv);
9535
10378
  }
9536
10379
  _compressGroups(groups) {
@@ -9563,7 +10406,8 @@ var RgFilter = class _RgFilter extends ToolFilter {
9563
10406
  const kept = [];
9564
10407
  let suppressed = 0;
9565
10408
  for (const ln of lines) {
9566
- if (ln === _RgFilter._SEP || _RgFilter._CTX_LINE_RE.test(ln) && !_RgFilter._MATCH_LINE_RE.test(ln)) {
10409
+ if (ln === _RgFilter._SEP) continue;
10410
+ if (_RgFilter._CTX_LINE_RE.test(ln) && !_RgFilter._MATCH_LINE_RE.test(ln)) {
9567
10411
  suppressed++;
9568
10412
  } else {
9569
10413
  kept.push(ln);
@@ -13231,6 +14075,8 @@ var _DOCKER_OLD_STEP_ERROR_RE = /error|returned a non-zero code/i;
13231
14075
  var DockerFilter = class extends ToolFilter {
13232
14076
  name = "docker";
13233
14077
  binaries = /* @__PURE__ */ new Set(["docker", "buildah", "podman", "nerdctl"]);
14078
+ // Every rule below is about image transfer and build noise -- layer digests, pull/push progress, cached step lines, step bodies -- so the filter is declared for exactly the subcommands that emit them, including buildah's `bud` spelling of build, `create` and `plugin install`/`plugin upgrade`, which pull layers and emit the same per-layer status lines as `pull`. The base matcher scans the first three positionals, so the management-command spellings (`docker image pull`, `docker container create`) are covered by the same entries. With no subcommand set it claimed every `docker …` invocation and removed a single blank line from the ones it has no rules for, which matters because dispatch stops at the first match: `docker logs` on a service repeating a heartbeat went 8702 bytes in, 8701 out, where the generic compressor it was displacing returns 6987. On output with nothing repeated (`docker images` here: 15912 bytes) neither saves anything, so the gain is confined to the repetitive case -- but a filter that claims a command it cannot compress blocks whatever could.
14079
+ subcommands = /* @__PURE__ */ new Set(["build", "bud", "buildx", "pull", "push", "run", "load", "create", "install", "upgrade"]);
13234
14080
  // Docker writes progress/errors to stderr; only stdout carries build bodies. Merge with stderr first so errors appear before raw build output.
13235
14081
  compress(stdout, stderr, _exitCode, _argv) {
13236
14082
  const merged = this.combineOutput(stderr, stdout);
@@ -13457,6 +14303,7 @@ var _KEY_PREFIXES = [
13457
14303
  "Namespace:",
13458
14304
  "Status:",
13459
14305
  "State:",
14306
+ "Last State:",
13460
14307
  "Node:",
13461
14308
  "IP:",
13462
14309
  "PodIP:",
@@ -13464,6 +14311,7 @@ var _KEY_PREFIXES = [
13464
14311
  "QoS Class:",
13465
14312
  "Priority:",
13466
14313
  "Image:",
14314
+ "Container ID:",
13467
14315
  "Ready:",
13468
14316
  "Restart Count:",
13469
14317
  "Started:",
@@ -14269,8 +15117,8 @@ function _capHunksByDensity(hunks, maxHunksPerFile) {
14269
15117
  };
14270
15118
  }
14271
15119
  function _compressGitDiffBody(stdout, stderr, maxHunksPerFile = 10) {
14272
- const MAX_HUNK_CHANGED = 50;
14273
- const HUNK_HEAD_KEEP = 20;
15120
+ const MAX_HUNK_CHANGED = 25;
15121
+ const HUNK_HEAD_KEEP = 15;
14274
15122
  const HUNK_TAIL_KEEP = 5;
14275
15123
  const fileBlocks = splitBlocks(stdout, _GIT_DIFF_FILE_RE);
14276
15124
  if (!fileBlocks.length) return stdout;
@@ -14401,12 +15249,18 @@ function _compressGitStatusVerbose(stdout, stderr, argv = null) {
14401
15249
  const kept = [];
14402
15250
  let section = null;
14403
15251
  let counts = {};
15252
+ let fileLines = [];
14404
15253
  function flush() {
14405
- if (section !== null && section !== "unmerged" && Object.keys(counts).length) {
14406
- const parts = Object.entries(counts).map(([label, n]) => `${n} ${label}`);
14407
- kept.push(" " + parts.join(", "));
15254
+ if (section !== null && section !== "unmerged" && fileLines.length) {
15255
+ if (fileLines.length > _DIFF_STAT_DIR_ROLLUP_THRESHOLD) {
15256
+ const parts = Object.entries(counts).map(([label, n]) => `${n} ${label}`);
15257
+ kept.push(" " + parts.join(", "));
15258
+ } else {
15259
+ kept.push(...fileLines);
15260
+ }
14408
15261
  }
14409
15262
  counts = {};
15263
+ fileLines = [];
14410
15264
  }
14411
15265
  for (const line of lines) {
14412
15266
  const headerMatch = _GIT_STATUS_SECTION_RE.exec(line);
@@ -14423,6 +15277,7 @@ function _compressGitStatusVerbose(stdout, stderr, argv = null) {
14423
15277
  } else {
14424
15278
  const label = _gitStatusFileLabel(line, section);
14425
15279
  counts[label] = (counts[label] ?? 0) + 1;
15280
+ fileLines.push(line);
14426
15281
  }
14427
15282
  continue;
14428
15283
  }
@@ -15909,6 +16764,7 @@ var _PHPSTAN_SEP_RE = /^\s*-{3,}/;
15909
16764
  var _PHPSTAN_FILE_HEADER_RE = /^\s+Line\s+(\S.*\.php)\s*$/;
15910
16765
  var _PHPSTAN_ROW_RE = /^\s+(\d+)\s+(.+)$/;
15911
16766
  var _PHPSTAN_SUMMARY_RE = /^\s*\[(ERROR|OK|WARNING|NOTE)\]/i;
16767
+ var _PHPSTAN_ROW_CONTINUATION_RE = /^\s{2,}\S/;
15912
16768
  var _PSALM_ERROR_RE = /^(ERROR|INFO|FATAL): \w+ - .+\.php:\d+/i;
15913
16769
  var _PSALM_PROGRESS_RE = /^(Scanning|Analyzing|Checking|Parsing|Caching|Target PHP|Psalm|PHP version|Running Psalm|Checked \d)/i;
15914
16770
  var _PHPSTAN_INFO_RE = /^(Note: |Loading config|Found cached|Autoload|Bootstrapping|PHPStan - PHP Static|Psalm is running)/i;
@@ -15928,6 +16784,7 @@ var PhpStanFilter = class extends ToolFilter {
15928
16784
  let droppedSep = 0;
15929
16785
  let droppedInfo = 0;
15930
16786
  let currentFile = "";
16787
+ let lastRowKept = true;
15931
16788
  const fileMsgs = /* @__PURE__ */ new Map();
15932
16789
  const flushFileDedup = (file) => {
15933
16790
  const msgs = fileMsgs.get(file);
@@ -15945,11 +16802,13 @@ var PhpStanFilter = class extends ToolFilter {
15945
16802
  flushFileDedup(currentFile);
15946
16803
  currentFile = "";
15947
16804
  }
16805
+ lastRowKept = true;
15948
16806
  kept.push(line);
15949
16807
  continue;
15950
16808
  }
15951
16809
  if (_PHPSTAN_SEP_RE.test(line) && !_PHPSTAN_ROW_RE.test(line)) {
15952
16810
  droppedSep++;
16811
+ lastRowKept = true;
15953
16812
  continue;
15954
16813
  }
15955
16814
  if (_PHPSTAN_FILE_HEADER_RE.test(line)) {
@@ -15957,6 +16816,7 @@ var PhpStanFilter = class extends ToolFilter {
15957
16816
  const headerMatch = _PHPSTAN_FILE_HEADER_RE.exec(line);
15958
16817
  currentFile = headerMatch?.[1] ? headerMatch[1].trim() : line.trim();
15959
16818
  if (!fileMsgs.has(currentFile)) fileMsgs.set(currentFile, /* @__PURE__ */ new Map());
16819
+ lastRowKept = true;
15960
16820
  kept.push(line);
15961
16821
  continue;
15962
16822
  }
@@ -15966,9 +16826,11 @@ var PhpStanFilter = class extends ToolFilter {
15966
16826
  const counts = fileMsgs.get(currentFile);
15967
16827
  const count = (counts.get(msg) ?? 0) + 1;
15968
16828
  counts.set(msg, count);
15969
- if (count <= 3) kept.push(line);
16829
+ lastRowKept = count <= 3;
16830
+ if (lastRowKept) kept.push(line);
15970
16831
  continue;
15971
16832
  }
16833
+ if (currentFile && !lastRowKept && _PHPSTAN_ROW_CONTINUATION_RE.test(line)) continue;
15972
16834
  kept.push(line);
15973
16835
  }
15974
16836
  if (currentFile) flushFileDedup(currentFile);
@@ -17730,6 +18592,7 @@ export {
17730
18592
  getToolInput,
17731
18593
  getFilePath,
17732
18594
  getCwd,
18595
+ getTranscriptPath,
17733
18596
  extractToolResponseField,
17734
18597
  OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
17735
18598
  BODY_FIRST_TOOL_RESPONSE_KEYS,
@@ -17793,6 +18656,27 @@ export {
17793
18656
  capTokens,
17794
18657
  shlexSplit,
17795
18658
  clipLongMatchLine,
18659
+ isLockFile,
18660
+ isManifestFile,
18661
+ isInBuildDir,
18662
+ isGeneratedFile,
18663
+ isBuildCommand,
18664
+ isTestRunnerCommand,
18665
+ getMonitoringRecallHint,
18666
+ RECALL_DEFAULT_LIMIT,
18667
+ isRecallCacheType,
18668
+ indexRecallEntry,
18669
+ listRecentRecall,
18670
+ searchRecall,
18671
+ BASH_OUTPUT_SUBDIR,
18672
+ isScopedGitStatusOrDiffStatCommand,
18673
+ normalizeCommandForCacheKey,
18674
+ commandHash,
18675
+ isBashEntryStale,
18676
+ summarizeOutputDelta,
18677
+ storeBashOutputSync,
18678
+ storeBashOutput,
18679
+ getBashOutput,
17796
18680
  anchoredMarkerPattern,
17797
18681
  settingsPath,
17798
18682
  installHooks,