glob.test.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import { describe, expect } from "bun:test"
  2. import path from "path"
  3. import fs from "fs/promises"
  4. import os from "os"
  5. import { Cause, Effect, Exit, Layer } from "effect"
  6. import { GlobTool } from "../../src/tool/glob"
  7. import { SessionID, MessageID } from "../../src/session/schema"
  8. import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
  9. import { Ripgrep } from "../../src/file/ripgrep"
  10. import { AppFileSystem } from "../../src/filesystem"
  11. import { Truncate } from "../../src/tool/truncate"
  12. import { Agent } from "../../src/agent/agent"
  13. import { provideTmpdirInstance } from "../fixture/fixture"
  14. import { testEffect } from "../lib/effect"
  15. const it = testEffect(
  16. Layer.mergeAll(
  17. CrossSpawnSpawner.defaultLayer,
  18. AppFileSystem.defaultLayer,
  19. Ripgrep.defaultLayer,
  20. Truncate.defaultLayer,
  21. Agent.defaultLayer,
  22. ),
  23. )
  24. const ctx = {
  25. sessionID: SessionID.make("ses_test"),
  26. messageID: MessageID.make(""),
  27. callID: "",
  28. agent: "build",
  29. abort: AbortSignal.any([]),
  30. messages: [],
  31. metadata: () => Effect.void,
  32. ask: () => Effect.void,
  33. }
  34. describe("tool.glob", () => {
  35. it.live("matches files from a directory path", () =>
  36. provideTmpdirInstance((dir) =>
  37. Effect.gen(function* () {
  38. yield* Effect.promise(() => Bun.write(path.join(dir, "a.ts"), "export const a = 1\n"))
  39. yield* Effect.promise(() => Bun.write(path.join(dir, "b.txt"), "hello\n"))
  40. const info = yield* GlobTool
  41. const glob = yield* info.init()
  42. const result = yield* glob.execute(
  43. {
  44. pattern: "*.ts",
  45. path: dir,
  46. },
  47. ctx,
  48. )
  49. expect(result.metadata.count).toBe(1)
  50. expect(result.output).toContain(path.join(dir, "a.ts"))
  51. expect(result.output).not.toContain(path.join(dir, "b.txt"))
  52. }),
  53. ),
  54. )
  55. it.live("rejects exact file paths", () =>
  56. provideTmpdirInstance((dir) =>
  57. Effect.gen(function* () {
  58. const file = path.join(dir, "a.ts")
  59. yield* Effect.promise(() => Bun.write(file, "export const a = 1\n"))
  60. const info = yield* GlobTool
  61. const glob = yield* info.init()
  62. const exit = yield* glob
  63. .execute(
  64. {
  65. pattern: "*.ts",
  66. path: file,
  67. },
  68. ctx,
  69. )
  70. .pipe(Effect.exit)
  71. expect(Exit.isFailure(exit)).toBe(true)
  72. if (Exit.isFailure(exit)) {
  73. const err = Cause.squash(exit.cause)
  74. expect(err instanceof Error ? err.message : String(err)).toContain("glob path must be a directory")
  75. }
  76. }),
  77. ),
  78. )
  79. // kilocode_change start - absolute glob patterns outside the project
  80. it.live("supports absolute glob patterns outside the project", () =>
  81. provideTmpdirInstance(
  82. (_dir) =>
  83. Effect.gen(function* () {
  84. const outer = yield* Effect.promise(() => fs.mkdtemp(path.join(os.tmpdir(), "glob-outer-")))
  85. yield* Effect.promise(() => Bun.write(path.join(outer, "one.md"), "one"))
  86. yield* Effect.promise(() => Bun.write(path.join(outer, "two.md"), "two"))
  87. yield* Effect.promise(() => Bun.write(path.join(outer, "three.txt"), "three"))
  88. const info = yield* GlobTool
  89. const glob = yield* info.init()
  90. const result = yield* glob.execute(
  91. {
  92. pattern: path.join(outer, "*.md"),
  93. },
  94. ctx,
  95. )
  96. expect(result.output).toContain(path.join(outer, "one.md"))
  97. expect(result.output).toContain(path.join(outer, "two.md"))
  98. expect(result.output).not.toContain(path.join(outer, "three.txt"))
  99. }),
  100. { git: true },
  101. ),
  102. )
  103. // kilocode_change end
  104. })