add back in file hierarchy in system prompt but limit to 200 items

This commit is contained in:
Dax Raad 2025-06-30 14:46:18 -04:00
parent 9f3ba03965
commit dd5736fe5f
9 changed files with 153 additions and 94 deletions

View file

@ -0,0 +1,37 @@
import { App } from "../../app/app"
import { Ripgrep } from "../../file/ripgrep"
import { LSP } from "../../lsp"
import { cmd } from "./cmd"
export const DebugCommand = cmd({
command: "debug",
builder: (yargs) =>
yargs.command(DiagnosticsCommand).command(TreeCommand).demandCommand(),
async handler() {},
})
const DiagnosticsCommand = cmd({
command: "diagnostics <file>",
builder: (yargs) =>
yargs.positional("file", { type: "string", demandOption: true }),
async handler(args) {
await App.provide({ cwd: process.cwd() }, async () => {
await LSP.touchFile(args.file, true)
console.log(await LSP.diagnostics())
})
},
})
const TreeCommand = cmd({
command: "tree",
builder: (yargs) =>
yargs.option("limit", {
type: "number",
}),
async handler(args) {
await App.provide({ cwd: process.cwd() }, async () => {
const app = App.info()
console.log(await Ripgrep.tree({ cwd: app.path.cwd, limit: args.limit }))
})
},
})

View file

@ -1,15 +0,0 @@
import { App } from "../../app/app"
import { LSP } from "../../lsp"
import { cmd } from "./cmd"
export const ScrapCommand = cmd({
command: "scrap <file>",
builder: (yargs) =>
yargs.positional("file", { type: "string", demandOption: true }),
async handler(args) {
await App.provide({ cwd: process.cwd() }, async () => {
await LSP.touchFile(args.file, true)
console.log(await LSP.diagnostics())
})
},
})

View file

@ -5,7 +5,6 @@ import { z } from "zod"
import { NamedError } from "../util/error" import { NamedError } from "../util/error"
import { lazy } from "../util/lazy" import { lazy } from "../util/lazy"
import { Log } from "../util/log" import { Log } from "../util/log"
import { $ } from "bun"
export namespace Fzf { export namespace Fzf {
const log = Log.create({ service: "fzf" }) const log = Log.create({ service: "fzf" })
@ -115,24 +114,4 @@ export namespace Fzf {
const { filepath } = await state() const { filepath } = await state()
return filepath return filepath
} }
export async function search(input: {
cwd: string
query: string
limit?: number
}) {
const results = await $`${await filepath()} --filter=${input.query}`
.quiet()
.throws(false)
.cwd(input.cwd)
.text()
const split = results
.trim()
.split("\n")
.filter((line) => line.length > 0)
log.info("results", {
count: split.length,
})
return split
}
} }

View file

@ -129,4 +129,104 @@ export namespace Ripgrep {
const result = await $`${{ raw: joined }}`.cwd(input.cwd).nothrow().text() const result = await $`${{ raw: joined }}`.cwd(input.cwd).nothrow().text()
return result.split("\n").filter(Boolean) return result.split("\n").filter(Boolean)
} }
export async function tree(input: { cwd: string; limit?: number }) {
const files = await Ripgrep.files({ cwd: input.cwd })
interface Node {
path: string[]
children: Node[]
}
function getPath(node: Node, parts: string[], create: boolean) {
if (parts.length === 0) return node
let current = node
for (const part of parts) {
let existing = current.children.find((x) => x.path.at(-1) === part)
if (!existing) {
if (!create) return
existing = {
path: current.path.concat(part),
children: [],
}
current.children.push(existing)
}
current = existing
}
return current
}
const root: Node = {
path: [],
children: [],
}
for (const file of files) {
const parts = file.split(path.sep)
getPath(root, parts, true)
}
function sort(node: Node) {
node.children.sort((a, b) => {
if (!a.children.length && b.children.length) return 1
if (!b.children.length && a.children.length) return -1
return a.path.at(-1)!.localeCompare(b.path.at(-1)!)
})
for (const child of node.children) {
sort(child)
}
}
sort(root)
let current = [root]
const result: Node = {
path: [],
children: [],
}
let processed = 0
const limit = input.limit ?? 50
while (current.length > 0) {
const next = []
for (const node of current) {
if (node.children.length) next.push(...node.children)
}
const max = Math.max(...current.map((x) => x.children.length))
for (let i = 0; i < max && processed < limit; i++) {
for (const node of current) {
const child = node.children[i]
if (!child) continue
getPath(result, child.path, true)
processed++
if (processed >= limit) break
}
}
if (processed >= limit) {
for (const node of [...current, ...next]) {
const compare = getPath(result, node.path, false)
if (!compare) continue
if (compare?.children.length !== node.children.length) {
const diff = node.children.length - compare.children.length
compare.children.push({
path: compare.path.concat(`[${diff} truncated]`),
children: [],
})
}
}
break
}
current = next
}
const lines: string[] = []
function render(node: Node, depth: number) {
const indent = "\t".repeat(depth)
lines.push(indent + node.path.at(-1) + (node.children.length ? "/" : ""))
for (const child of node.children) {
render(child, depth + 1)
}
}
result.children.map((x) => render(x, 0))
return lines.join("\n")
}
} }

View file

@ -3,7 +3,6 @@ import yargs from "yargs"
import { hideBin } from "yargs/helpers" import { hideBin } from "yargs/helpers"
import { RunCommand } from "./cli/cmd/run" import { RunCommand } from "./cli/cmd/run"
import { GenerateCommand } from "./cli/cmd/generate" import { GenerateCommand } from "./cli/cmd/generate"
import { ScrapCommand } from "./cli/cmd/scrap"
import { Log } from "./util/log" import { Log } from "./util/log"
import { AuthCommand } from "./cli/cmd/auth" import { AuthCommand } from "./cli/cmd/auth"
import { UpgradeCommand } from "./cli/cmd/upgrade" import { UpgradeCommand } from "./cli/cmd/upgrade"
@ -14,6 +13,7 @@ import { NamedError } from "./util/error"
import { FormatError } from "./cli/error" import { FormatError } from "./cli/error"
import { ServeCommand } from "./cli/cmd/serve" import { ServeCommand } from "./cli/cmd/serve"
import { TuiCommand } from "./cli/cmd/tui" import { TuiCommand } from "./cli/cmd/tui"
import { DebugCommand } from "./cli/cmd/debug"
const cancel = new AbortController() const cancel = new AbortController()
@ -49,7 +49,7 @@ const cli = yargs(hideBin(process.argv))
.command(TuiCommand) .command(TuiCommand)
.command(RunCommand) .command(RunCommand)
.command(GenerateCommand) .command(GenerateCommand)
.command(ScrapCommand) .command(DebugCommand)
.command(AuthCommand) .command(AuthCommand)
.command(UpgradeCommand) .command(UpgradeCommand)
.command(ServeCommand) .command(ServeCommand)

View file

@ -12,7 +12,7 @@ import { App } from "../app/app"
import { mapValues } from "remeda" import { mapValues } from "remeda"
import { NamedError } from "../util/error" import { NamedError } from "../util/error"
import { ModelsDev } from "../provider/models" import { ModelsDev } from "../provider/models"
import { Ripgrep } from "../external/ripgrep" import { Ripgrep } from "../file/ripgrep"
import { Config } from "../config/config" import { Config } from "../config/config"
const ERRORS = { const ERRORS = {

View file

@ -1,5 +1,5 @@
import { App } from "../app/app" import { App } from "../app/app"
import { Ripgrep } from "../external/ripgrep" import { Ripgrep } from "../file/ripgrep"
import { Global } from "../global" import { Global } from "../global"
import { Filesystem } from "../util/filesystem" import { Filesystem } from "../util/filesystem"
import path from "path" import path from "path"
@ -27,55 +27,6 @@ export namespace SystemPrompt {
export async function environment() { export async function environment() {
const app = App.info() const app = App.info()
;async () => {
const files = await Ripgrep.files({
cwd: app.path.cwd,
})
type Node = {
children: Record<string, Node>
}
const root: Node = {
children: {},
}
for (const file of files) {
const parts = file.split("/")
let node = root
for (const part of parts) {
const existing = node.children[part]
if (existing) {
node = existing
continue
}
node.children[part] = {
children: {},
}
node = node.children[part]
}
}
function render(path: string[], node: Node): string {
// if (path.length === 3) return "\t".repeat(path.length) + "..."
const lines: string[] = []
const entries = Object.entries(node.children).sort(([a], [b]) =>
a.localeCompare(b),
)
for (const [name, child] of entries) {
const currentPath = [...path, name]
const indent = "\t".repeat(path.length)
const hasChildren = Object.keys(child.children).length > 0
lines.push(`${indent}${name}` + (hasChildren ? "/" : ""))
if (hasChildren) lines.push(render(currentPath, child))
}
return lines.join("\n")
}
const result = render([], root)
return result
}
return [ return [
[ [
`Here is some useful information about the environment you are running in:`, `Here is some useful information about the environment you are running in:`,
@ -85,9 +36,16 @@ export namespace SystemPrompt {
` Platform: ${process.platform}`, ` Platform: ${process.platform}`,
` Today's date: ${new Date().toDateString()}`, ` Today's date: ${new Date().toDateString()}`,
`</env>`, `</env>`,
// `<project>`, `<project>`,
// ` ${app.git ? await tree() : ""}`, ` ${
// `</project>`, app.git
? await Ripgrep.tree({
cwd: app.path.cwd,
limit: 200,
})
: ""
}`,
`</project>`,
].join("\n"), ].join("\n"),
] ]
} }

View file

@ -3,7 +3,7 @@ import path from "path"
import { Tool } from "./tool" import { Tool } from "./tool"
import { App } from "../app/app" import { App } from "../app/app"
import DESCRIPTION from "./glob.txt" import DESCRIPTION from "./glob.txt"
import { Ripgrep } from "../external/ripgrep" import { Ripgrep } from "../file/ripgrep"
export const GlobTool = Tool.define({ export const GlobTool = Tool.define({
id: "glob", id: "glob",

View file

@ -1,7 +1,7 @@
import { z } from "zod" import { z } from "zod"
import { Tool } from "./tool" import { Tool } from "./tool"
import { App } from "../app/app" import { App } from "../app/app"
import { Ripgrep } from "../external/ripgrep" import { Ripgrep } from "../file/ripgrep"
import DESCRIPTION from "./grep.txt" import DESCRIPTION from "./grep.txt"