Refactor application path handling and data storage architecture

Replace simple directory-based path system with git-aware data management that uses global data directories and proper workspace detection.

🤖 Generated with opencode
Co-Authored-By: opencode <noreply@opencode.ai>
This commit is contained in:
Dax Raad 2025-06-01 14:40:44 -04:00
parent 4be9f7ab9c
commit 526a8ea19a
17 changed files with 69 additions and 42 deletions

View file

@ -1,7 +1,9 @@
import fs from "fs/promises"
import { AppPath } from "./path"
import { Log } from "../util/log"
import { Context } from "../util/context"
import { Filesystem } from "../util/filesystem"
import { Global } from "../global"
import path from "path"
export namespace App {
const log = Log.create({ service: "app" })
@ -10,12 +12,13 @@ export namespace App {
const ctx = Context.create<Info>("app")
async function create(input: { directory: string }) {
const dataDir = AppPath.data(input.directory)
await fs.mkdir(dataDir, { recursive: true })
await Log.file(input.directory)
async function create(input: { cwd: string; version: string }) {
let root = await Filesystem.findUp(".git", input.cwd).then((x) =>
x ? path.dirname(x) : input.cwd,
)
log.info("created", { path: dataDir })
const data = path.join(Global.data(), root)
await Bun.write(path.join(data, "version"), input.version)
const services = new Map<
any,
@ -25,14 +28,16 @@ export namespace App {
}
>()
const result = {
get services() {
return services
await Log.file(path.join(data, "log"))
const result = Object.freeze({
services,
path: {
data,
root,
cwd: input.cwd,
},
get root() {
return input.directory
},
}
})
return result
}
@ -61,7 +66,7 @@ export namespace App {
}
export async function provide<T extends (app: Info) => any>(
input: { directory: string },
input: { cwd: string; version: string },
cb: T,
) {
const app = await create(input)

View file

@ -1,11 +0,0 @@
import path from "path"
export namespace AppPath {
export function data(input: string) {
return path.join(input, ".opencode")
}
export function storage(input: string) {
return path.join(data(input), "storage")
}
}

View file

@ -8,7 +8,7 @@ export namespace Config {
const log = Log.create({ service: "config" })
export const state = App.state("config", async (app) => {
const result = await load(app.root)
const result = await load(app.path.root)
return result
})

View file

@ -17,4 +17,8 @@ export namespace Global {
export function cache() {
return paths.cache
}
export function data() {
return paths.data
}
}

View file

@ -5,6 +5,7 @@ export namespace Identifier {
const prefixes = {
session: "ses",
message: "msg",
user: "usr",
} as const
export function schema(prefix: keyof typeof prefixes) {

View file

@ -16,9 +16,10 @@ declare global {
}
const cli = cac("opencode")
const version = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
cli.command("", "Start the opencode in interactive mode").action(async () => {
await App.provide({ directory: process.cwd() }, async () => {
await App.provide({ cwd: process.cwd(), version }, async () => {
await Share.init()
const server = Server.listen()
@ -66,14 +67,14 @@ cli
.command("run [...message]", "Run a chat message")
.option("--session <id>", "Session ID")
.action(async (message: string[], options) => {
await App.provide({ directory: process.cwd() }, async () => {
await App.provide({ cwd: process.cwd(), version }, async () => {
await Share.init()
const session = options.session
? await Session.get(options.session)
: await Session.create()
console.log("Session:", session.id)
Bus.subscribe(Message.Event.Updated, async (message) => {
Bus.subscribe(Message.Event.Updated, async () => {
console.log("Thinking...")
})

View file

@ -36,7 +36,7 @@ export namespace LSPClient {
const [command, ...args] = input.cmd
const server = spawn(command, args, {
stdio: ["pipe", "pipe", "pipe"],
cwd: app.root,
cwd: app.path.cwd,
})
const connection = createMessageConnection(
@ -64,7 +64,7 @@ export namespace LSPClient {
workspaceFolders: [
{
name: "workspace",
uri: "file://" + app.root,
uri: "file://" + app.path.cwd,
},
],
tsserver: {

View file

@ -220,7 +220,7 @@ export namespace Session {
tool: {},
},
}
const contextFile = Bun.file(path.join(app.root, "CONTEXT.md"))
const contextFile = Bun.file(path.join(app.path.root, "CONTEXT.md"))
if (await contextFile.exists()) {
const context = await contextFile.text()
system.parts.push({

View file

@ -3,8 +3,8 @@ import { LocalStorageAdapter } from "@flystorage/local-fs"
import fs from "fs/promises"
import { Log } from "../util/log"
import { App } from "../app/app"
import { AppPath } from "../app/path"
import { Bus } from "../bus"
import path from "path"
import z from "zod"
export namespace Storage {
@ -19,7 +19,7 @@ export namespace Storage {
const state = App.state("storage", async () => {
const app = await App.use()
const storageDir = AppPath.storage(app.root)
const storageDir = path.join(app.path.data, "storage")
await fs.mkdir(storageDir, { recursive: true })
const storage = new FileStorage(new LocalStorageAdapter(storageDir))
log.info("created", { path: storageDir })

View file

@ -51,7 +51,7 @@ export const GlobTool = Tool.define({
}),
async execute(params) {
const app = await App.use()
const search = params.path || app.root
const search = params.path || app.path.cwd
const limit = 100
const glob = new Bun.Glob(params.pattern)
const files = []

View file

@ -287,7 +287,7 @@ export const GrepTool = Tool.define({
}
const app = await App.use()
const searchPath = params.path || app.root
const searchPath = params.path || app.path.cwd
// If literalText is true, escape the pattern
const searchPattern = params.literalText

View file

@ -26,7 +26,7 @@ export const ListTool = Tool.define({
}),
async execute(params) {
const app = await App.use()
const searchPath = path.resolve(app.root, params.path || ".")
const searchPath = path.resolve(app.path.cwd, params.path || ".")
const glob = new Bun.Glob("**/*")
const files = []

View file

@ -37,7 +37,7 @@ TIPS:
const app = await App.use()
const normalized = path.isAbsolute(args.path)
? args.path
: path.join(app.root, args.path)
: path.join(app.path.cwd, args.path)
await LSP.file(normalized)
const diagnostics = await LSP.diagnostics()
const file = diagnostics[normalized]

View file

@ -21,7 +21,7 @@ export const LspHoverTool = Tool.define({
const app = await App.use()
const file = path.isAbsolute(args.file)
? args.file
: path.join(app.root, args.file)
: path.join(app.path.cwd, args.file)
await LSP.file(file)
const result = await LSP.hover({
...args,

View file

@ -0,0 +1,18 @@
import { exists } from "fs/promises"
import { dirname, join } from "path"
export namespace Filesystem {
export async function findUp(target: string, start: string) {
let currentDir = start
while (true) {
const targetPath = join(currentDir, target)
if (await exists(targetPath)) return targetPath
const parentDir = dirname(currentDir)
if (parentDir === currentDir) {
return
}
currentDir = parentDir
}
}
}

View file

@ -1,5 +1,4 @@
import path from "path"
import { AppPath } from "../app/path"
import fs from "fs/promises"
export namespace Log {
const write = {
@ -12,8 +11,9 @@ export namespace Log {
}
export async function file(directory: string) {
const outPath = path.join(AppPath.data(directory), "opencode.out.log")
const errPath = path.join(AppPath.data(directory), "opencode.err.log")
await fs.mkdir(directory, { recursive: true })
const outPath = path.join(directory, "opencode.out.log")
const errPath = path.join(directory, "opencode.err.log")
await fs.truncate(outPath).catch(() => {})
await fs.truncate(errPath).catch(() => {})
const out = Bun.file(outPath)

9
packages/opencode/sst-env.d.ts vendored Normal file
View file

@ -0,0 +1,9 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
/// <reference path="../../sst-env.d.ts" />
import "sst"
export {}