refactor(search): route callers through search service
This commit is contained in:
parent
8eb913ba12
commit
2fef5e6ce6
14 changed files with 76 additions and 96 deletions
|
|
@ -1,10 +1,10 @@
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { AppRuntime } from "@/effect/app-runtime"
|
import { AppRuntime } from "@/effect/app-runtime"
|
||||||
|
import { Search } from "@/file/search"
|
||||||
import { File } from "../../../file"
|
import { File } from "../../../file"
|
||||||
import { bootstrap } from "../../bootstrap"
|
import { bootstrap } from "../../bootstrap"
|
||||||
import { cmd } from "../cmd"
|
import { cmd } from "../cmd"
|
||||||
import { Ripgrep } from "@/file/ripgrep"
|
|
||||||
|
|
||||||
const FileSearchCommand = cmd({
|
const FileSearchCommand = cmd({
|
||||||
command: "search <query>",
|
command: "search <query>",
|
||||||
|
|
@ -95,7 +95,7 @@ const FileTreeCommand = cmd({
|
||||||
default: process.cwd(),
|
default: process.cwd(),
|
||||||
}),
|
}),
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
const files = await Ripgrep.tree({ cwd: args.dir, limit: 200 })
|
const files = await Search.tree({ cwd: args.dir, limit: 200 })
|
||||||
console.log(JSON.stringify(files, null, 2))
|
console.log(JSON.stringify(files, null, 2))
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { AppRuntime } from "../../../effect/app-runtime"
|
import { AppRuntime } from "../../../effect/app-runtime"
|
||||||
import { Ripgrep } from "../../../file/ripgrep"
|
import { Search } from "../../../file/search"
|
||||||
import { Instance } from "../../../project/instance"
|
import { Instance } from "../../../project/instance"
|
||||||
import { bootstrap } from "../../bootstrap"
|
import { bootstrap } from "../../bootstrap"
|
||||||
import { cmd } from "../cmd"
|
import { cmd } from "../cmd"
|
||||||
|
|
@ -21,7 +21,7 @@ const TreeCommand = cmd({
|
||||||
}),
|
}),
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
await bootstrap(process.cwd(), async () => {
|
await bootstrap(process.cwd(), async () => {
|
||||||
process.stdout.write((await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit })) + EOL)
|
process.stdout.write((await Search.tree({ cwd: Instance.directory, limit: args.limit })) + EOL)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -46,7 +46,7 @@ const FilesCommand = cmd({
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
await bootstrap(process.cwd(), async () => {
|
await bootstrap(process.cwd(), async () => {
|
||||||
const files: string[] = []
|
const files: string[] = []
|
||||||
for await (const file of await Ripgrep.files({
|
for await (const file of await Search.files({
|
||||||
cwd: Instance.directory,
|
cwd: Instance.directory,
|
||||||
glob: args.glob ? [args.glob] : undefined,
|
glob: args.glob ? [args.glob] : undefined,
|
||||||
})) {
|
})) {
|
||||||
|
|
@ -79,7 +79,7 @@ const SearchCommand = cmd({
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
await bootstrap(process.cwd(), async () => {
|
await bootstrap(process.cwd(), async () => {
|
||||||
const results = await AppRuntime.runPromise(
|
const results = await AppRuntime.runPromise(
|
||||||
Ripgrep.Service.use((svc) =>
|
Search.Service.use((svc) =>
|
||||||
svc.search({
|
svc.search({
|
||||||
cwd: Instance.directory,
|
cwd: Instance.directory,
|
||||||
pattern: args.pattern,
|
pattern: args.pattern,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { Global } from "../global"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { Protected } from "./protected"
|
import { Protected } from "./protected"
|
||||||
import { Ripgrep } from "./ripgrep"
|
import { Search } from "./search"
|
||||||
|
|
||||||
export namespace File {
|
export namespace File {
|
||||||
export const Info = z
|
export const Info = z
|
||||||
|
|
@ -344,7 +344,7 @@ export namespace File {
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const appFs = yield* AppFileSystem.Service
|
const appFs = yield* AppFileSystem.Service
|
||||||
const rg = yield* Ripgrep.Service
|
const searchSvc = yield* Search.Service
|
||||||
const git = yield* Git.Service
|
const git = yield* Git.Service
|
||||||
|
|
||||||
const state = yield* InstanceState.make<State>(
|
const state = yield* InstanceState.make<State>(
|
||||||
|
|
@ -384,7 +384,7 @@ export namespace File {
|
||||||
|
|
||||||
next.dirs = Array.from(dirs).toSorted()
|
next.dirs = Array.from(dirs).toSorted()
|
||||||
} else {
|
} else {
|
||||||
const files = yield* rg.files({ cwd: Instance.directory }).pipe(
|
const files = yield* searchSvc.files({ cwd: Instance.directory }).pipe(
|
||||||
Stream.runCollect,
|
Stream.runCollect,
|
||||||
Effect.map((chunk) => [...chunk]),
|
Effect.map((chunk) => [...chunk]),
|
||||||
)
|
)
|
||||||
|
|
@ -512,6 +512,8 @@ export namespace File {
|
||||||
|
|
||||||
if (!Instance.containsPath(full)) throw new Error("Access denied: path escapes project directory")
|
if (!Instance.containsPath(full)) throw new Error("Access denied: path escapes project directory")
|
||||||
|
|
||||||
|
yield* searchSvc.open({ cwd: Instance.directory, file }).pipe(Effect.ignore)
|
||||||
|
|
||||||
if (isImageByExtension(file)) {
|
if (isImageByExtension(file)) {
|
||||||
const exists = yield* appFs.existsSafe(full)
|
const exists = yield* appFs.existsSafe(full)
|
||||||
if (exists) {
|
if (exists) {
|
||||||
|
|
@ -617,14 +619,26 @@ export namespace File {
|
||||||
dirs?: boolean
|
dirs?: boolean
|
||||||
type?: "file" | "directory"
|
type?: "file" | "directory"
|
||||||
}) {
|
}) {
|
||||||
yield* ensure()
|
|
||||||
const { cache } = yield* InstanceState.get(state)
|
|
||||||
|
|
||||||
const query = input.query.trim()
|
const query = input.query.trim()
|
||||||
const limit = input.limit ?? 100
|
const limit = input.limit ?? 100
|
||||||
const kind = input.type ?? (input.dirs === false ? "file" : "all")
|
const kind = input.type ?? (input.dirs === false ? "file" : "all")
|
||||||
log.info("search", { query, kind })
|
log.info("search", { query, kind })
|
||||||
|
|
||||||
|
if (query && kind === "file") {
|
||||||
|
const files = yield* searchSvc.file({
|
||||||
|
cwd: Instance.directory,
|
||||||
|
query,
|
||||||
|
limit,
|
||||||
|
})
|
||||||
|
if (files.length) {
|
||||||
|
log.info("search", { query, kind, results: files.length, mode: "fff" })
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yield* ensure()
|
||||||
|
const { cache } = yield* InstanceState.get(state)
|
||||||
|
|
||||||
const preferHidden = query.startsWith(".") || query.includes("/.")
|
const preferHidden = query.startsWith(".") || query.includes("/.")
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
|
|
@ -649,7 +663,7 @@ export namespace File {
|
||||||
)
|
)
|
||||||
|
|
||||||
export const defaultLayer = layer.pipe(
|
export const defaultLayer = layer.pipe(
|
||||||
Layer.provide(Ripgrep.defaultLayer),
|
Layer.provide(Search.defaultLayer),
|
||||||
Layer.provide(AppFileSystem.defaultLayer),
|
Layer.provide(AppFileSystem.defaultLayer),
|
||||||
Layer.provide(Git.defaultLayer),
|
Layer.provide(Git.defaultLayer),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { Effect } from "effect"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { AppRuntime } from "../../effect/app-runtime"
|
import { AppRuntime } from "../../effect/app-runtime"
|
||||||
import { File } from "../../file"
|
import { File } from "../../file"
|
||||||
import { Ripgrep } from "../../file/ripgrep"
|
import { Search } from "../../file/search"
|
||||||
import { LSP } from "../../lsp"
|
import { LSP } from "../../lsp"
|
||||||
import { Instance } from "../../project/instance"
|
import { Instance } from "../../project/instance"
|
||||||
import { lazy } from "../../util/lazy"
|
import { lazy } from "../../util/lazy"
|
||||||
|
|
@ -15,14 +15,14 @@ export const FileRoutes = lazy(() =>
|
||||||
"/find",
|
"/find",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
summary: "Find text",
|
summary: "Find text",
|
||||||
description: "Search for text patterns across files in the project using ripgrep.",
|
description: "Search for text patterns across files in the project.",
|
||||||
operationId: "find.text",
|
operationId: "find.text",
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
description: "Matches",
|
description: "Matches",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(Ripgrep.Match.shape.data.array()),
|
schema: resolver(Search.Match.array()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -37,7 +37,7 @@ export const FileRoutes = lazy(() =>
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const pattern = c.req.valid("query").pattern
|
const pattern = c.req.valid("query").pattern
|
||||||
const result = await AppRuntime.runPromise(
|
const result = await AppRuntime.runPromise(
|
||||||
Ripgrep.Service.use((svc) => svc.search({ cwd: Instance.directory, pattern, limit: 10 })),
|
Search.Service.use((svc) => svc.search({ cwd: Instance.directory, pattern, limit: 10 })),
|
||||||
)
|
)
|
||||||
return c.json(result.items)
|
return c.json(result.items)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Effect, Option } from "effect"
|
import { Effect } from "effect"
|
||||||
import * as Stream from "effect/Stream"
|
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { AppFileSystem } from "../filesystem"
|
import { AppFileSystem } from "../filesystem"
|
||||||
import { Ripgrep } from "../file/ripgrep"
|
import { Search } from "../file/search"
|
||||||
import { assertExternalDirectoryEffect } from "./external-directory"
|
import { assertExternalDirectoryEffect } from "./external-directory"
|
||||||
import DESCRIPTION from "./glob.txt"
|
import DESCRIPTION from "./glob.txt"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
|
|
@ -12,8 +11,8 @@ import { Tool } from "./tool"
|
||||||
export const GlobTool = Tool.define(
|
export const GlobTool = Tool.define(
|
||||||
"glob",
|
"glob",
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const rg = yield* Ripgrep.Service
|
|
||||||
const fs = yield* AppFileSystem.Service
|
const fs = yield* AppFileSystem.Service
|
||||||
|
const searchSvc = yield* Search.Service
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
|
|
@ -48,36 +47,18 @@ export const GlobTool = Tool.define(
|
||||||
yield* assertExternalDirectoryEffect(ctx, search, { kind: "directory" })
|
yield* assertExternalDirectoryEffect(ctx, search, { kind: "directory" })
|
||||||
|
|
||||||
const limit = 100
|
const limit = 100
|
||||||
let truncated = false
|
const files = yield* searchSvc.glob({
|
||||||
const files = yield* rg.files({ cwd: search, glob: [params.pattern], signal: ctx.abort }).pipe(
|
cwd: search,
|
||||||
Stream.mapEffect((file) =>
|
pattern: params.pattern,
|
||||||
Effect.gen(function* () {
|
limit,
|
||||||
const full = path.resolve(search, file)
|
signal: ctx.abort,
|
||||||
const info = yield* fs.stat(full).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
})
|
||||||
const mtime =
|
|
||||||
info?.mtime.pipe(
|
|
||||||
Option.map((date) => date.getTime()),
|
|
||||||
Option.getOrElse(() => 0),
|
|
||||||
) ?? 0
|
|
||||||
return { path: full, mtime }
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Stream.take(limit + 1),
|
|
||||||
Stream.runCollect,
|
|
||||||
Effect.map((chunk) => [...chunk]),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (files.length > limit) {
|
|
||||||
truncated = true
|
|
||||||
files.length = limit
|
|
||||||
}
|
|
||||||
files.sort((a, b) => b.mtime - a.mtime)
|
|
||||||
|
|
||||||
const output = []
|
const output = []
|
||||||
if (files.length === 0) output.push("No files found")
|
if (files.files.length === 0) output.push("No files found")
|
||||||
if (files.length > 0) {
|
if (files.files.length > 0) {
|
||||||
output.push(...files.map((file) => file.path))
|
output.push(...files.files)
|
||||||
if (truncated) {
|
if (files.truncated) {
|
||||||
output.push("")
|
output.push("")
|
||||||
output.push(
|
output.push(
|
||||||
`(Results are truncated: showing first ${limit} results. Consider using a more specific path or pattern.)`,
|
`(Results are truncated: showing first ${limit} results. Consider using a more specific path or pattern.)`,
|
||||||
|
|
@ -88,8 +69,8 @@ export const GlobTool = Tool.define(
|
||||||
return {
|
return {
|
||||||
title: path.relative(ins.worktree, search),
|
title: path.relative(ins.worktree, search),
|
||||||
metadata: {
|
metadata: {
|
||||||
count: files.length,
|
count: files.files.length,
|
||||||
truncated,
|
truncated: files.truncated,
|
||||||
},
|
},
|
||||||
output: output.join("\n"),
|
output: output.join("\n"),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Effect, Option } from "effect"
|
import { Effect } from "effect"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { AppFileSystem } from "../filesystem"
|
import { AppFileSystem } from "../filesystem"
|
||||||
import { Ripgrep } from "../file/ripgrep"
|
import { Search } from "../file/search"
|
||||||
import { assertExternalDirectoryEffect } from "./external-directory"
|
import { assertExternalDirectoryEffect } from "./external-directory"
|
||||||
import DESCRIPTION from "./grep.txt"
|
import DESCRIPTION from "./grep.txt"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
|
|
@ -14,7 +14,7 @@ export const GrepTool = Tool.define(
|
||||||
"grep",
|
"grep",
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* AppFileSystem.Service
|
const fs = yield* AppFileSystem.Service
|
||||||
const rg = yield* Ripgrep.Service
|
const searchSvc = yield* Search.Service
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
|
|
@ -58,7 +58,7 @@ export const GrepTool = Tool.define(
|
||||||
kind: info?.type === "Directory" ? "directory" : "file",
|
kind: info?.type === "Directory" ? "directory" : "file",
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = yield* rg.search({
|
const result = yield* searchSvc.search({
|
||||||
cwd,
|
cwd,
|
||||||
pattern: params.pattern,
|
pattern: params.pattern,
|
||||||
glob: params.include ? [params.include] : undefined,
|
glob: params.include ? [params.include] : undefined,
|
||||||
|
|
@ -74,37 +74,13 @@ export const GrepTool = Tool.define(
|
||||||
line: item.line_number,
|
line: item.line_number,
|
||||||
text: item.lines.text,
|
text: item.lines.text,
|
||||||
}))
|
}))
|
||||||
const times = new Map(
|
|
||||||
(yield* Effect.forEach(
|
|
||||||
[...new Set(rows.map((row) => row.path))],
|
|
||||||
Effect.fnUntraced(function* (file) {
|
|
||||||
const info = yield* fs.stat(file).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
|
||||||
if (!info || info.type === "Directory") return undefined
|
|
||||||
return [
|
|
||||||
file,
|
|
||||||
info.mtime.pipe(
|
|
||||||
Option.map((time) => time.getTime()),
|
|
||||||
Option.getOrElse(() => 0),
|
|
||||||
) ?? 0,
|
|
||||||
] as const
|
|
||||||
}),
|
|
||||||
{ concurrency: 16 },
|
|
||||||
)).filter((entry): entry is readonly [string, number] => Boolean(entry)),
|
|
||||||
)
|
|
||||||
const matches = rows.flatMap((row) => {
|
|
||||||
const mtime = times.get(row.path)
|
|
||||||
if (mtime === undefined) return []
|
|
||||||
return [{ ...row, mtime }]
|
|
||||||
})
|
|
||||||
|
|
||||||
matches.sort((a, b) => b.mtime - a.mtime)
|
|
||||||
|
|
||||||
const limit = 100
|
const limit = 100
|
||||||
const truncated = matches.length > limit
|
const truncated = rows.length > limit
|
||||||
const final = truncated ? matches.slice(0, limit) : matches
|
const final = truncated ? rows.slice(0, limit) : rows
|
||||||
if (final.length === 0) return empty
|
if (final.length === 0) return empty
|
||||||
|
|
||||||
const total = matches.length
|
const total = rows.length
|
||||||
const output = [`Found ${total} matches${truncated ? ` (showing first ${limit})` : ""}`]
|
const output = [`Found ${total} matches${truncated ? ` (showing first ${limit})` : ""}`]
|
||||||
|
|
||||||
let current = ""
|
let current = ""
|
||||||
|
|
@ -130,6 +106,10 @@ export const GrepTool = Tool.define(
|
||||||
output.push("")
|
output.push("")
|
||||||
output.push("(Some paths were inaccessible and skipped)")
|
output.push("(Some paths were inaccessible and skipped)")
|
||||||
}
|
}
|
||||||
|
if (result.regexFallbackError) {
|
||||||
|
output.push("")
|
||||||
|
output.push(`(Regex fallback: ${result.regexFallbackError})`)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: params.pattern,
|
title: params.pattern,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import z from "zod"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import * as Stream from "effect/Stream"
|
import * as Stream from "effect/Stream"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { Ripgrep } from "../file/ripgrep"
|
import { Search } from "../file/search"
|
||||||
import { assertExternalDirectoryEffect } from "./external-directory"
|
import { assertExternalDirectoryEffect } from "./external-directory"
|
||||||
import DESCRIPTION from "./ls.txt"
|
import DESCRIPTION from "./ls.txt"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
|
|
@ -40,7 +40,7 @@ const LIMIT = 100
|
||||||
export const ListTool = Tool.define(
|
export const ListTool = Tool.define(
|
||||||
"list",
|
"list",
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const rg = yield* Ripgrep.Service
|
const searchSvc = yield* Search.Service
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
|
|
@ -67,7 +67,7 @@ export const ListTool = Tool.define(
|
||||||
})
|
})
|
||||||
|
|
||||||
const glob = IGNORE_PATTERNS.map((item) => `!${item}*`).concat(params.ignore?.map((item) => `!${item}`) || [])
|
const glob = IGNORE_PATTERNS.map((item) => `!${item}*`).concat(params.ignore?.map((item) => `!${item}`) || [])
|
||||||
const files = yield* rg.files({ cwd: search, glob, signal: ctx.abort }).pipe(
|
const files = yield* searchSvc.files({ cwd: search, glob, signal: ctx.abort }).pipe(
|
||||||
Stream.take(LIMIT + 1),
|
Stream.take(LIMIT + 1),
|
||||||
Stream.runCollect,
|
Stream.runCollect,
|
||||||
Effect.map((chunk) => [...chunk]),
|
Effect.map((chunk) => [...chunk]),
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { Tool } from "./tool"
|
||||||
import { AppFileSystem } from "../filesystem"
|
import { AppFileSystem } from "../filesystem"
|
||||||
import { LSP } from "../lsp"
|
import { LSP } from "../lsp"
|
||||||
import { FileTime } from "../file/time"
|
import { FileTime } from "../file/time"
|
||||||
|
import { Search } from "../file/search"
|
||||||
import DESCRIPTION from "./read.txt"
|
import DESCRIPTION from "./read.txt"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { assertExternalDirectoryEffect } from "./external-directory"
|
import { assertExternalDirectoryEffect } from "./external-directory"
|
||||||
|
|
@ -31,6 +32,7 @@ export const ReadTool = Tool.define(
|
||||||
const fs = yield* AppFileSystem.Service
|
const fs = yield* AppFileSystem.Service
|
||||||
const instruction = yield* Instruction.Service
|
const instruction = yield* Instruction.Service
|
||||||
const lsp = yield* LSP.Service
|
const lsp = yield* LSP.Service
|
||||||
|
const search = yield* Search.Service
|
||||||
const time = yield* FileTime.Service
|
const time = yield* FileTime.Service
|
||||||
const scope = yield* Scope.Scope
|
const scope = yield* Scope.Scope
|
||||||
|
|
||||||
|
|
@ -76,6 +78,7 @@ export const ReadTool = Tool.define(
|
||||||
})
|
})
|
||||||
|
|
||||||
const warm = Effect.fn("ReadTool.warm")(function* (filepath: string, sessionID: Tool.Context["sessionID"]) {
|
const warm = Effect.fn("ReadTool.warm")(function* (filepath: string, sessionID: Tool.Context["sessionID"]) {
|
||||||
|
yield* search.open({ file: filepath }).pipe(Effect.ignore)
|
||||||
yield* lsp.touchFile(filepath, false).pipe(Effect.ignore, Effect.forkIn(scope))
|
yield* lsp.touchFile(filepath, false).pipe(Effect.ignore, Effect.forkIn(scope))
|
||||||
yield* time.read(sessionID, filepath)
|
yield* time.read(sessionID, filepath)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import z from "zod"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import * as Stream from "effect/Stream"
|
import * as Stream from "effect/Stream"
|
||||||
import { EffectLogger } from "@/effect/logger"
|
import { EffectLogger } from "@/effect/logger"
|
||||||
import { Ripgrep } from "../file/ripgrep"
|
import { Search } from "../file/search"
|
||||||
import { Skill } from "../skill"
|
import { Skill } from "../skill"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ export const SkillTool = Tool.define(
|
||||||
"skill",
|
"skill",
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const skill = yield* Skill.Service
|
const skill = yield* Skill.Service
|
||||||
const rg = yield* Ripgrep.Service
|
const searchSvc = yield* Search.Service
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
|
|
@ -62,7 +62,7 @@ export const SkillTool = Tool.define(
|
||||||
const dir = path.dirname(info.location)
|
const dir = path.dirname(info.location)
|
||||||
const base = pathToFileURL(dir).href
|
const base = pathToFileURL(dir).href
|
||||||
const limit = 10
|
const limit = 10
|
||||||
const files = yield* rg.files({ cwd: dir, follow: false, hidden: true, signal: ctx.abort }).pipe(
|
const files = yield* searchSvc.files({ cwd: dir, follow: false, hidden: true, signal: ctx.abort }).pipe(
|
||||||
Stream.filter((file) => !file.includes("SKILL.md")),
|
Stream.filter((file) => !file.includes("SKILL.md")),
|
||||||
Stream.map((file) => path.resolve(dir, file)),
|
Stream.map((file) => path.resolve(dir, file)),
|
||||||
Stream.take(limit),
|
Stream.take(limit),
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ import { ToolRegistry } from "../../src/tool/registry"
|
||||||
import { Truncate } from "../../src/tool/truncate"
|
import { Truncate } from "../../src/tool/truncate"
|
||||||
import { Log } from "../../src/util/log"
|
import { Log } from "../../src/util/log"
|
||||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||||
import { Ripgrep } from "../../src/file/ripgrep"
|
import { Search } from "../../src/file/search"
|
||||||
import { Format } from "../../src/format"
|
import { Format } from "../../src/format"
|
||||||
import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture"
|
import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture"
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
|
|
@ -187,7 +187,7 @@ function makeHttp() {
|
||||||
Layer.provide(Skill.defaultLayer),
|
Layer.provide(Skill.defaultLayer),
|
||||||
Layer.provide(FetchHttpClient.layer),
|
Layer.provide(FetchHttpClient.layer),
|
||||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||||
Layer.provide(Ripgrep.defaultLayer),
|
Layer.provide(Search.defaultLayer),
|
||||||
Layer.provide(Format.defaultLayer),
|
Layer.provide(Format.defaultLayer),
|
||||||
Layer.provideMerge(todo),
|
Layer.provideMerge(todo),
|
||||||
Layer.provideMerge(question),
|
Layer.provideMerge(question),
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ import { ToolRegistry } from "../../src/tool/registry"
|
||||||
import { Truncate } from "../../src/tool/truncate"
|
import { Truncate } from "../../src/tool/truncate"
|
||||||
import { AppFileSystem } from "../../src/filesystem"
|
import { AppFileSystem } from "../../src/filesystem"
|
||||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||||
import { Ripgrep } from "../../src/file/ripgrep"
|
import { Search } from "../../src/file/search"
|
||||||
import { Format } from "../../src/format"
|
import { Format } from "../../src/format"
|
||||||
|
|
||||||
Log.init({ print: false })
|
Log.init({ print: false })
|
||||||
|
|
@ -141,7 +141,7 @@ function makeHttp() {
|
||||||
Layer.provide(Skill.defaultLayer),
|
Layer.provide(Skill.defaultLayer),
|
||||||
Layer.provide(FetchHttpClient.layer),
|
Layer.provide(FetchHttpClient.layer),
|
||||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||||
Layer.provide(Ripgrep.defaultLayer),
|
Layer.provide(Search.defaultLayer),
|
||||||
Layer.provide(Format.defaultLayer),
|
Layer.provide(Format.defaultLayer),
|
||||||
Layer.provideMerge(todo),
|
Layer.provideMerge(todo),
|
||||||
Layer.provideMerge(question),
|
Layer.provideMerge(question),
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { Cause, Effect, Exit, Layer } from "effect"
|
||||||
import { GlobTool } from "../../src/tool/glob"
|
import { GlobTool } from "../../src/tool/glob"
|
||||||
import { SessionID, MessageID } from "../../src/session/schema"
|
import { SessionID, MessageID } from "../../src/session/schema"
|
||||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||||
import { Ripgrep } from "../../src/file/ripgrep"
|
import { Search } from "../../src/file/search"
|
||||||
import { AppFileSystem } from "../../src/filesystem"
|
import { AppFileSystem } from "../../src/filesystem"
|
||||||
import { Truncate } from "../../src/tool/truncate"
|
import { Truncate } from "../../src/tool/truncate"
|
||||||
import { Agent } from "../../src/agent/agent"
|
import { Agent } from "../../src/agent/agent"
|
||||||
|
|
@ -15,7 +15,7 @@ const it = testEffect(
|
||||||
Layer.mergeAll(
|
Layer.mergeAll(
|
||||||
CrossSpawnSpawner.defaultLayer,
|
CrossSpawnSpawner.defaultLayer,
|
||||||
AppFileSystem.defaultLayer,
|
AppFileSystem.defaultLayer,
|
||||||
Ripgrep.defaultLayer,
|
Search.defaultLayer,
|
||||||
Truncate.defaultLayer,
|
Truncate.defaultLayer,
|
||||||
Agent.defaultLayer,
|
Agent.defaultLayer,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { SessionID, MessageID } from "../../src/session/schema"
|
||||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||||
import { Truncate } from "../../src/tool/truncate"
|
import { Truncate } from "../../src/tool/truncate"
|
||||||
import { Agent } from "../../src/agent/agent"
|
import { Agent } from "../../src/agent/agent"
|
||||||
import { Ripgrep } from "../../src/file/ripgrep"
|
import { Search } from "../../src/file/search"
|
||||||
import { AppFileSystem } from "../../src/filesystem"
|
import { AppFileSystem } from "../../src/filesystem"
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ const it = testEffect(
|
||||||
Layer.mergeAll(
|
Layer.mergeAll(
|
||||||
CrossSpawnSpawner.defaultLayer,
|
CrossSpawnSpawner.defaultLayer,
|
||||||
AppFileSystem.defaultLayer,
|
AppFileSystem.defaultLayer,
|
||||||
Ripgrep.defaultLayer,
|
Search.defaultLayer,
|
||||||
Truncate.defaultLayer,
|
Truncate.defaultLayer,
|
||||||
Agent.defaultLayer,
|
Agent.defaultLayer,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import path from "path"
|
||||||
import { Agent } from "../../src/agent/agent"
|
import { Agent } from "../../src/agent/agent"
|
||||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||||
import { AppFileSystem } from "../../src/filesystem"
|
import { AppFileSystem } from "../../src/filesystem"
|
||||||
|
import { Search } from "../../src/file/search"
|
||||||
import { FileTime } from "../../src/file/time"
|
import { FileTime } from "../../src/file/time"
|
||||||
import { LSP } from "../../src/lsp"
|
import { LSP } from "../../src/lsp"
|
||||||
import { Permission } from "../../src/permission"
|
import { Permission } from "../../src/permission"
|
||||||
|
|
@ -42,6 +43,7 @@ const it = testEffect(
|
||||||
FileTime.defaultLayer,
|
FileTime.defaultLayer,
|
||||||
Instruction.defaultLayer,
|
Instruction.defaultLayer,
|
||||||
LSP.defaultLayer,
|
LSP.defaultLayer,
|
||||||
|
Search.defaultLayer,
|
||||||
Truncate.defaultLayer,
|
Truncate.defaultLayer,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue