Explorar el Código

refactor(core): consolidate references (#31539)

Dax hace 2 meses
padre
commit
6566ede935
Se han modificado 65 ficheros con 687 adiciones y 2730 borrados
  1. 63 0
      packages/core/src/config/plugin/reference.ts
  2. 0 30
      packages/core/src/config/reference.ts
  3. 12 28
      packages/core/src/filesystem.ts
  4. 2 2
      packages/core/src/location-layer.ts
  5. 2 3
      packages/core/src/location-search.ts
  6. 7 0
      packages/core/src/plugin/boot.ts
  7. 0 243
      packages/core/src/project-reference.ts
  8. 114 0
      packages/core/src/reference.ts
  9. 0 1
      packages/core/src/session/input.ts
  10. 0 1
      packages/core/src/session/message-updater.ts
  11. 0 1
      packages/core/src/session/message.ts
  12. 1 15
      packages/core/src/session/prompt.ts
  13. 0 1
      packages/core/src/session/runner/to-llm-message.ts
  14. 3 4
      packages/core/src/tool/edit.ts
  15. 2 8
      packages/core/src/tool/glob.ts
  16. 1 7
      packages/core/src/tool/grep.ts
  17. 3 4
      packages/core/src/tool/write.ts
  18. 1 1
      packages/core/src/v1/config/config.ts
  19. 5 139
      packages/core/test/location-filesystem.test.ts
  20. 2 2
      packages/core/test/location-layer.test.ts
  21. 1 1
      packages/core/test/location-mutation.test.ts
  22. 4 22
      packages/core/test/location-search.test.ts
  23. 0 299
      packages/core/test/project-reference.test.ts
  24. 57 0
      packages/core/test/reference.test.ts
  25. 2 4
      packages/core/test/session-runner-message.test.ts
  26. 3 38
      packages/core/test/tool-glob.test.ts
  27. 5 42
      packages/core/test/tool-grep.test.ts
  28. 1 15
      packages/core/test/tool-read.test.ts
  29. 0 48
      packages/opencode/src/config/reference.ts
  30. 0 2
      packages/opencode/src/effect/app-runtime.ts
  31. 1 4
      packages/opencode/src/project/bootstrap.ts
  32. 0 237
      packages/opencode/src/reference/reference.ts
  33. 0 320
      packages/opencode/src/reference/repository-cache.ts
  34. 0 2
      packages/opencode/src/server/routes/instance/httpapi/api.ts
  35. 0 60
      packages/opencode/src/server/routes/instance/httpapi/groups/reference.ts
  36. 0 27
      packages/opencode/src/server/routes/instance/httpapi/handlers/reference.ts
  37. 0 4
      packages/opencode/src/server/routes/instance/httpapi/server.ts
  38. 2 83
      packages/opencode/src/session/prompt.ts
  39. 0 72
      packages/opencode/src/session/prompt/reference.ts
  40. 1 4
      packages/opencode/src/tool/glob.ts
  41. 1 4
      packages/opencode/src/tool/grep.ts
  42. 2 5
      packages/opencode/src/tool/read.ts
  43. 0 3
      packages/opencode/src/tool/registry.ts
  44. 0 310
      packages/opencode/test/reference/reference.test.ts
  45. 1 1
      packages/opencode/test/server/httpapi-exercise/index.ts
  46. 3 7
      packages/opencode/test/server/httpapi-public-openapi.test.ts
  47. 23 22
      packages/opencode/test/server/httpapi-reference.test.ts
  48. 0 91
      packages/opencode/test/session/prompt.test.ts
  49. 0 5
      packages/opencode/test/session/snapshot-tool-race.test.ts
  50. 0 51
      packages/opencode/test/tool/glob.test.ts
  51. 0 59
      packages/opencode/test/tool/grep.test.ts
  52. 0 48
      packages/opencode/test/tool/read.test.ts
  53. 1 4
      packages/opencode/test/tool/registry.test.ts
  54. 31 43
      packages/sdk/js/src/v2/gen/sdk.gen.ts
  55. 70 64
      packages/sdk/js/src/v2/gen/types.gen.ts
  56. 139 189
      packages/sdk/openapi.json
  57. 2 0
      packages/server/src/api.ts
  58. 0 2
      packages/server/src/groups/fs.ts
  59. 28 0
      packages/server/src/groups/reference.ts
  60. 2 0
      packages/server/src/handlers.ts
  61. 8 0
      packages/server/src/handlers/reference.ts
  62. 14 46
      packages/tui/src/component/prompt/autocomplete.tsx
  63. 20 2
      packages/tui/src/context/sync-v2.tsx
  64. 46 0
      packages/tui/test/cli/tui/sync-v2.test.tsx
  65. 1 0
      packages/tui/test/fixture/tui-sdk.ts

+ 63 - 0
packages/core/src/config/plugin/reference.ts

@@ -0,0 +1,63 @@
+export * as ConfigReferencePlugin from "./reference"
+
+import path from "path"
+import { Effect } from "effect"
+import { Config } from "../../config"
+import { ConfigReference } from "../reference"
+import { Global } from "../../global"
+import { Location } from "../../location"
+import { PluginV2 } from "../../plugin"
+import { Reference } from "../../reference"
+import { AbsolutePath } from "../../schema"
+
+export const Plugin = {
+  id: PluginV2.ID.make("core/config-reference"),
+  effect: Effect.gen(function* () {
+    const config = yield* Config.Service
+    const global = yield* Global.Service
+    const location = yield* Location.Service
+    const references = yield* Reference.Service
+    const update = yield* references.transform()
+    const entries = new Map<string, Reference.Source>()
+    for (const doc of (yield* config.entries()).filter(
+      (entry): entry is Config.Document => entry.type === "document",
+    )) {
+      const directory = doc.path ? path.dirname(doc.path) : location.directory
+      for (const [name, entry] of Object.entries(doc.info.references ?? {})) {
+        if (!validAlias(name)) continue
+        entries.set(
+          name,
+          local(entry)
+            ? new Reference.LocalSource({
+                type: "local",
+                path: AbsolutePath.make(localPath(directory, global.home, typeof entry === "string" ? entry : entry.path)),
+              })
+            : new Reference.GitSource({
+                type: "git",
+                repository: typeof entry === "string" ? entry : entry.repository,
+                branch: typeof entry === "string" ? undefined : entry.branch,
+              }),
+        )
+      }
+    }
+
+    yield* update((editor) => {
+      for (const [name, source] of entries) editor.add(name, source)
+    })
+  }),
+}
+
+function validAlias(name: string) {
+  return name.length > 0 && !/[\/\s`,]/.test(name)
+}
+
+function local(entry: ConfigReference.Entry): entry is string | ConfigReference.Local {
+  return typeof entry === "string"
+    ? entry.startsWith(".") || entry.startsWith("/") || entry.startsWith("~")
+    : "path" in entry
+}
+
+function localPath(directory: string, home: string, value: string) {
+  if (value.startsWith("~/")) return path.join(home, value.slice(2))
+  return path.isAbsolute(value) ? value : path.resolve(directory, value)
+}

+ 0 - 30
packages/core/src/config/reference.ts

@@ -16,33 +16,3 @@ export type Entry = typeof Entry.Type
 
 export const Info = Schema.Record(Schema.String, Entry)
 export type Info = typeof Info.Type
-
-export type NormalizedEntry =
-  | { readonly kind: "local"; readonly path: string }
-  | { readonly kind: "git"; readonly repository: string; readonly branch?: string }
-  | { readonly kind: "invalid"; readonly message: string }
-
-export type NormalizedInfo = Record<string, NormalizedEntry>
-
-export function validateAlias(name: string) {
-  if (name.length === 0) return "Reference alias must not be empty"
-  if (/[\/\s`,]/.test(name)) return "Reference alias must not contain /, whitespace, comma, or backtick"
-}
-
-export function normalizeEntry(entry: Entry): NormalizedEntry {
-  if (typeof entry === "string") {
-    if (entry.startsWith(".") || entry.startsWith("/") || entry.startsWith("~")) return { kind: "local", path: entry }
-    return { kind: "git", repository: entry }
-  }
-  if ("path" in entry) return { kind: "local", path: entry.path }
-  return { kind: "git", repository: entry.repository, branch: entry.branch }
-}
-
-export function normalize(info: Info): NormalizedInfo {
-  return Object.fromEntries(
-    Object.entries(info).map(([name, entry]) => {
-      const message = validateAlias(name)
-      return [name, message ? { kind: "invalid" as const, message } : normalizeEntry(entry)]
-    }),
-  )
-}

+ 12 - 28
packages/core/src/filesystem.ts

@@ -9,7 +9,6 @@ import { EventV2 } from "./event"
 import { FSUtil } from "./fs-util"
 import { Global } from "./global"
 import { Location } from "./location"
-import { ProjectReference } from "./project-reference"
 import { NonNegativeInt, PositiveInt, RelativePath } from "./schema"
 import { Protected } from "./filesystem/protected"
 import { Ripgrep } from "./filesystem/ripgrep"
@@ -17,7 +16,6 @@ import { ToolOutputStore } from "./tool-output-store"
 
 export const ReadInput = Schema.Struct({
   path: Schema.String,
-  reference: Schema.NonEmptyString.pipe(Schema.optional),
 })
 export type ReadInput = typeof ReadInput.Type
 
@@ -134,7 +132,6 @@ export class ReadPath extends Schema.Class<ReadPath>("FileSystem.ReadPath")({
 
 export const ListInput = Schema.Struct({
   path: Schema.String.pipe(Schema.optional),
-  reference: Schema.NonEmptyString.pipe(Schema.optional),
 })
 export type ListInput = typeof ListInput.Type
 
@@ -158,7 +155,6 @@ export class RootTarget extends Schema.Class<RootTarget>("FileSystem.RootTarget"
   real: Schema.String,
   root: Schema.String,
   resource: Schema.String,
-  reference: Schema.NonEmptyString.pipe(Schema.optional),
   type: Schema.Literals(["file", "directory"]),
 }) {}
 
@@ -239,7 +235,6 @@ export const layer = Layer.effect(
     const fs = yield* FSUtil.Service
     const location = yield* Location.Service
     const global = yield* Effect.serviceOption(Global.Service)
-    const references = yield* ProjectReference.Service
     const ripgrep = yield* Ripgrep.Service
     const root = yield* fs.realPath(location.directory).pipe(Effect.orDie)
     const ignored = ignore()
@@ -251,21 +246,12 @@ export const layer = Layer.effect(
       .readFileString(path.join(location.project.directory, ".ignore"))
       .pipe(Effect.catch(() => Effect.succeed("")))
     if (ignorefile) ignored.add(ignorefile)
-    const select = Effect.fnUntraced(function* (reference?: string) {
-      if (!reference) return { directory: location.directory, root }
-      const resolved = yield* references.get(reference)
-      if (!resolved) return yield* Effect.die(new Error(`Unknown project reference: ${reference}`))
-      if (resolved.kind === "invalid") return yield* Effect.die(new Error(resolved.message))
-      if (resolved.kind === "git") yield* references.ensurePath(resolved.path).pipe(Effect.orDie)
-      return { directory: resolved.path, root: yield* fs.realPath(resolved.path).pipe(Effect.orDie) }
-    })
-    const resolve = Effect.fnUntraced(function* (input?: string, reference?: string) {
+    const resolve = Effect.fnUntraced(function* (input?: string) {
       const managed = path.join(
         Option.match(global, { onNone: () => Global.Path.data, onSome: (value) => value.data }),
         ToolOutputStore.MANAGED_DIRECTORY,
       )
       if (input && path.isAbsolute(input)) {
-        if (reference) return yield* Effect.die(new Error("Absolute paths cannot use a project reference"))
         if (path.dirname(input) !== managed || !path.basename(input).startsWith("tool_"))
           return yield* Effect.die(new Error("Absolute path is not managed tool output"))
         const real = yield* fs.realPath(input).pipe(Effect.orDie)
@@ -274,9 +260,9 @@ export const layer = Layer.effect(
           return yield* Effect.die(new Error("Path escapes managed tool output"))
         return { absolute: input, real, directory: managed, root: managedRoot }
       }
-      const selected = yield* select(reference)
-      const absolute = path.resolve(selected.directory, input ?? ".")
-      if (!FSUtil.contains(selected.directory, absolute))
+      const selected = { directory: location.directory, root }
+      const absolute = path.resolve(location.directory, input ?? ".")
+      if (!FSUtil.contains(location.directory, absolute))
         return yield* Effect.die(new Error("Path escapes the location"))
       const real = yield* fs.realPath(absolute).pipe(Effect.orDie)
       if (!FSUtil.contains(selected.root, real)) return yield* Effect.die(new Error("Path escapes the location"))
@@ -335,24 +321,24 @@ export const layer = Layer.effect(
     })
 
     const resolveReadPath = Effect.fn("FileSystem.resolveReadPath")(function* (input: ReadInput) {
-      const target = yield* resolve(input.path, input.reference)
+      const target = yield* resolve(input.path)
       const info = yield* fs.stat(target.real).pipe(Effect.orDie)
       const type = info.type === "File" ? "file" : info.type === "Directory" ? "directory" : undefined
       if (!type) return yield* Effect.die(new Error("Path is not a file or directory"))
       const relative = path.relative(target.root, target.real).replaceAll("\\", "/") || "."
       return new ReadPath({
         type,
-        resource: input.reference === undefined ? relative : `${input.reference}:${relative}`,
+        resource: relative,
       })
     })
     const resolveFile = Effect.fnUntraced(function* (input: ReadInput) {
-      const target = yield* resolve(input.path, input.reference)
+      const target = yield* resolve(input.path)
       const info = yield* fs.stat(target.real).pipe(Effect.orDie)
       if (info.type !== "File") return yield* Effect.die(new Error("Path is not a file"))
       const relative = path.relative(target.root, target.real).replaceAll("\\", "/") || "."
       return {
         real: target.real,
-        resource: input.reference === undefined ? relative : `${input.reference}:${relative}`,
+        resource: relative,
       }
     })
     const content = (target: { readonly real: string }, bytes: Uint8Array) =>
@@ -510,25 +496,24 @@ export const layer = Layer.effect(
       )
     })
     const resolveList = Effect.fn("FileSystem.resolveList")(function* (input: ListInput = {}) {
-      const directory = yield* resolve(input.path, input.reference)
+      const directory = yield* resolve(input.path)
       const info = yield* fs.stat(directory.real).pipe(Effect.orDie)
       if (info.type !== "Directory") return yield* Effect.die(new Error("Path is not a directory"))
       const relative = path.relative(directory.root, directory.real).replaceAll("\\", "/") || "."
       return new ListTarget({
         ...directory,
-        resource: input.reference === undefined ? relative : `${input.reference}:${relative}`,
+        resource: relative,
       })
     })
     const resolveRoot = Effect.fn("FileSystem.resolveRoot")(function* (input: ListInput = {}) {
-      const target = yield* resolve(input.path, input.reference)
+      const target = yield* resolve(input.path)
       const info = yield* fs.stat(target.real).pipe(Effect.orDie)
       const type = info.type === "File" ? "file" : info.type === "Directory" ? "directory" : undefined
       if (!type) return yield* Effect.die(new Error("Path is not a file or directory"))
       const relative = path.relative(target.root, target.real).replaceAll("\\", "/") || "."
       return new RootTarget({
         ...target,
-        resource: input.reference === undefined ? relative : `${input.reference}:${relative}`,
-        reference: input.reference,
+        resource: relative,
         type,
       })
     })
@@ -644,5 +629,4 @@ export const layer = Layer.effect(
 
 export const locationLayer = layer.pipe(
   Layer.provide(Ripgrep.defaultLayer),
-  Layer.provideMerge(ProjectReference.locationLayer),
 )

+ 2 - 2
packages/core/src/location-layer.ts

@@ -22,7 +22,7 @@ import { Watcher } from "./filesystem/watcher"
 import { LocationMutation } from "./location-mutation"
 import { LocationSearch } from "./location-search"
 import { FileMutation } from "./file-mutation"
-import { ProjectReference } from "./project-reference"
+import { Reference } from "./reference"
 import { RepositoryCache } from "./repository-cache"
 import { Pty } from "./pty"
 import { SkillV2 } from "./skill"
@@ -52,7 +52,7 @@ export class LocationServiceMap extends LayerMap.Service<LocationServiceMap>()("
       location,
       Policy.locationLayer,
       Config.locationLayer,
-      ProjectReference.locationLayer,
+      Reference.locationLayer,
       PluginV2.locationLayer,
       Catalog.locationLayer,
       CommandV2.locationLayer,

+ 2 - 3
packages/core/src/location-search.ts

@@ -9,8 +9,7 @@ import { NonNegativeInt, PositiveInt, RelativePath } from "./schema"
 
 /**
  * Location-scoped raw search substrate. Search authority is selected only by
- * FileSystem, preserving Location-relative paths and named read
- * references. Model formatting, leaf-tool permissions, and HTTP transport stay
+ * FileSystem, preserving Location-relative paths. Model formatting, leaf-tool permissions, and HTTP transport stay
  * outside this service so future GlobTool, GrepTool, and HTTP consumers can
  * share the same bounded filesystem behavior.
  *
@@ -106,7 +105,7 @@ export const layer = Layer.effect(
       return {
         path: RelativePath.make(relative),
         canonical,
-        resource: root.reference === undefined ? relative : `${root.reference}:${relative}`,
+        resource: relative,
         mtime: info.mtime.pipe(
           Option.map((date) => date.getTime()),
           Option.getOrElse(() => 0),

+ 7 - 0
packages/core/src/plugin/boot.ts

@@ -9,6 +9,7 @@ import { Config } from "../config"
 import { ConfigAgentPlugin } from "../config/plugin/agent"
 import { ConfigCommandPlugin } from "../config/plugin/command"
 import { ConfigSkillPlugin } from "../config/plugin/skill"
+import { ConfigReferencePlugin } from "../config/plugin/reference"
 import { EventV2 } from "../event"
 import { FSUtil } from "../fs-util"
 import { Global } from "../global"
@@ -25,6 +26,7 @@ import { EnvPlugin } from "./env"
 import { ModelsDevPlugin } from "./models-dev"
 import { ProviderPlugins } from "./provider"
 import { SkillV2 } from "../skill"
+import { Reference } from "../reference"
 
 type Plugin = {
   id: PluginV2.ID
@@ -42,6 +44,7 @@ type Plugin = {
     | Config.Service
     | ModelsDev.Service
     | SkillV2.Service
+    | Reference.Service
   >
 }
 
@@ -67,6 +70,7 @@ export const layer = Layer.effect(
     const fs = yield* FSUtil.Service
     const global = yield* Global.Service
     const skill = yield* SkillV2.Service
+    const references = yield* Reference.Service
     const done = yield* Deferred.make<void>()
 
     const add = Effect.fn("PluginBoot.add")(function* (input: Plugin) {
@@ -85,6 +89,7 @@ export const layer = Layer.effect(
           Effect.provideService(FSUtil.Service, fs),
           Effect.provideService(Global.Service, global),
           Effect.provideService(SkillV2.Service, skill),
+          Effect.provideService(Reference.Service, references),
           Effect.provideService(PluginV2.Service, plugin),
         ),
       })
@@ -104,6 +109,7 @@ export const layer = Layer.effect(
       yield* add(ConfigAgentPlugin.Plugin)
       yield* add(ConfigCommandPlugin.Plugin)
       yield* add(ConfigSkillPlugin.Plugin)
+      yield* add(ConfigReferencePlugin.Plugin)
     }).pipe(Effect.withSpan("PluginBoot.boot"))
 
     yield* boot.pipe(
@@ -124,4 +130,5 @@ export const locationLayer = layer.pipe(
   Layer.provideMerge(Config.locationLayer),
   Layer.provideMerge(AgentV2.locationLayer),
   Layer.provideMerge(SkillV2.locationLayer),
+  Layer.provideMerge(Reference.locationLayer),
 )

+ 0 - 243
packages/core/src/project-reference.ts

@@ -1,243 +0,0 @@
-export * as ProjectReference from "./project-reference"
-
-import path from "path"
-import { Context, Effect, Layer } from "effect"
-import { Config } from "./config"
-import { ConfigReference } from "./config/reference"
-import { FSUtil } from "./fs-util"
-import { Flag } from "./flag/flag"
-import { Global } from "./global"
-import { Location } from "./location"
-import { Repository } from "./repository"
-import { RepositoryCache } from "./repository-cache"
-
-export type Resolved =
-  | { readonly name: string; readonly kind: "local"; readonly path: string }
-  | {
-      readonly name: string
-      readonly kind: "git"
-      readonly repository: string
-      readonly reference: Repository.RemoteReference
-      readonly path: string
-      readonly branch?: string
-    }
-  | { readonly name: string; readonly kind: "invalid"; readonly repository?: string; readonly message: string }
-
-type Valid = Exclude<Resolved, { kind: "invalid" }>
-
-export type Mention =
-  | {
-      readonly name: string
-      readonly kind: "reference"
-      readonly reference: Valid
-      readonly target?: string
-      readonly path: string
-    }
-  | { readonly name: string; readonly kind: "invalid"; readonly target?: string; readonly message: string }
-  | {
-      readonly name: string
-      readonly kind: "missing"
-      readonly target: string
-      readonly path: string
-      readonly message: string
-    }
-
-export interface Interface {
-  readonly list: () => Effect.Effect<Resolved[]>
-  readonly get: (name: string) => Effect.Effect<Resolved | undefined>
-  readonly resolveMention: (value: string) => Effect.Effect<Mention | undefined, RepositoryCache.Error>
-  readonly ensurePath: (target?: string) => Effect.Effect<void, RepositoryCache.Error>
-  readonly containsManagedPath: (target?: string) => Effect.Effect<boolean>
-}
-
-export class Service extends Context.Service<Service, Interface>()("@opencode/ProjectReference") {}
-
-type Materializer = {
-  readonly name: string
-  readonly repository: string
-  readonly path: string
-  readonly run: Effect.Effect<void, RepositoryCache.Error>
-}
-
-export const layer = Layer.effect(
-  Service,
-  Effect.gen(function* () {
-    if (!Flag.OPENCODE_EXPERIMENTAL_REFERENCES) return Service.of(inert)
-
-    const config = yield* Config.Service
-    const fs = yield* FSUtil.Service
-    const global = yield* Global.Service
-    const location = yield* Location.Service
-    const cache = yield* RepositoryCache.Service
-    const references = resolveAll({
-      references: ConfigReference.normalize(
-        Object.assign(
-          {},
-          ...(yield* config.entries())
-            .filter((entry): entry is Config.Document => entry.type === "document")
-            .map((document) => document.info.references ?? {}),
-        ),
-      ),
-      directory: location.project.directory,
-      home: global.home,
-      repos: global.repos,
-    })
-    const materializers = yield* Effect.forEach(
-      uniqueGitReferences(references),
-      Effect.fnUntraced(function* (reference) {
-        return {
-          name: reference.name,
-          repository: reference.repository,
-          path: reference.path,
-          run: yield* Effect.cached(
-            cache
-              .ensure({ reference: reference.reference, branch: reference.branch, refresh: true })
-              .pipe(Effect.asVoid),
-          ),
-        }
-      }),
-    )
-
-    yield* Effect.forEach(
-      materializers,
-      (materializer) =>
-        materializer.run.pipe(
-          Effect.catchCause((cause) =>
-            Effect.logWarning("failed to materialize project reference", {
-              name: materializer.name,
-              repository: materializer.repository,
-              cause,
-            }),
-          ),
-        ),
-      { concurrency: 4, discard: true },
-    ).pipe(Effect.forkScoped)
-
-    const ensurePath = Effect.fn("ProjectReference.ensurePath")(function* (target?: string) {
-      const normalized = normalizePath(target)
-      if (!normalized)
-        return yield* Effect.forEach(materializers, (materializer) => materializer.run, { discard: true })
-      yield* materializers.find((materializer) => contains(materializer.path, normalized))?.run ?? Effect.void
-    })
-
-    return Service.of({
-      list: Effect.fn("ProjectReference.list")(function* () {
-        return references
-      }),
-      get: Effect.fn("ProjectReference.get")(function* (name: string) {
-        return references.find((reference) => reference.name === name)
-      }),
-      ensurePath,
-      containsManagedPath: Effect.fn("ProjectReference.containsManagedPath")(function* (target?: string) {
-        const normalized = normalizePath(target)
-        return normalized
-          ? references.some((reference) => reference.kind === "git" && contains(reference.path, normalized))
-          : false
-      }),
-      resolveMention: Effect.fn("ProjectReference.resolveMention")(function* (value: string) {
-        const [name, ...rest] = value.split("/")
-        const target = rest.length ? rest.join("/") : undefined
-        const reference = references.find((reference) => reference.name === name)
-        if (!reference) return
-        if (reference.kind === "invalid") return { name, kind: "invalid", target, message: reference.message }
-        if (reference.kind === "git") yield* ensurePath(reference.path)
-        if (!target) return { name, kind: "reference", reference, path: reference.path }
-
-        const resolved = path.resolve(reference.path, target)
-        if (!FSUtil.contains(reference.path, resolved))
-          return { name, kind: "invalid", target, message: "Reference target escapes its root" }
-        if (!(yield* fs.existsSafe(resolved)))
-          return { name, kind: "missing", target, path: resolved, message: "Reference target does not exist" }
-        return { name, kind: "reference", reference, target, path: resolved }
-      }),
-    })
-  }),
-)
-
-export const locationLayer = layer.pipe(Layer.provideMerge(Config.locationLayer))
-
-const inert: Interface = {
-  list: () => Effect.succeed([]),
-  get: () => Effect.succeed(undefined),
-  resolveMention: () => Effect.succeed(undefined),
-  ensurePath: () => Effect.void,
-  containsManagedPath: () => Effect.succeed(false),
-}
-
-export function resolveAll(input: {
-  references: ConfigReference.NormalizedInfo
-  directory: string
-  home: string
-  repos: string
-}) {
-  const seen = new Map<string, { name: string; branch?: string }>()
-  return Object.entries(input.references).map(([name, reference]): Resolved => {
-    const resolved = resolve({ name, reference, directory: input.directory, home: input.home, repos: input.repos })
-    if (resolved.kind !== "git") return resolved
-    const existing = seen.get(resolved.path)
-    if (!existing) {
-      seen.set(resolved.path, { name, branch: resolved.branch })
-      return resolved
-    }
-    if (existing.branch === resolved.branch) return resolved
-    return {
-      name,
-      kind: "invalid",
-      repository: resolved.repository,
-      message: `Reference conflicts with @${existing.name}: both use ${resolved.path}, but @${existing.name} requests ${existing.branch ?? "default branch"} and @${name} requests ${resolved.branch ?? "default branch"}`,
-    }
-  })
-}
-
-export function resolve(input: {
-  name: string
-  reference: ConfigReference.NormalizedEntry
-  directory: string
-  home: string
-  repos: string
-}): Resolved {
-  if (input.reference.kind === "invalid") return { name: input.name, kind: "invalid", message: input.reference.message }
-  if (input.reference.kind === "local") {
-    return { name: input.name, kind: "local", path: localPath(input.directory, input.home, input.reference.path) }
-  }
-  const reference = Repository.parse(input.reference.repository)
-  if (!reference || !Repository.isRemote(reference)) {
-    return {
-      name: input.name,
-      kind: "invalid",
-      repository: input.reference.repository,
-      message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
-    }
-  }
-  return {
-    name: input.name,
-    kind: "git",
-    repository: input.reference.repository,
-    reference,
-    path: Repository.cachePath(input.repos, reference),
-    branch: input.reference.branch,
-  }
-}
-
-function localPath(directory: string, home: string, value: string) {
-  if (value.startsWith("~/")) return path.join(home, value.slice(2))
-  return path.isAbsolute(value) ? value : path.resolve(directory, value)
-}
-
-function uniqueGitReferences(references: Resolved[]) {
-  const seen = new Set<string>()
-  return references.filter((reference): reference is Extract<Resolved, { kind: "git" }> => {
-    if (reference.kind !== "git" || seen.has(reference.path)) return false
-    seen.add(reference.path)
-    return true
-  })
-}
-
-function normalizePath(target?: string) {
-  if (!target) return
-  return process.platform === "win32" ? FSUtil.normalizePath(target) : target
-}
-
-function contains(parent: string, child: string) {
-  return FSUtil.contains(normalizePath(parent) ?? parent, normalizePath(child) ?? child)
-}

+ 114 - 0
packages/core/src/reference.ts

@@ -0,0 +1,114 @@
+export * as Reference from "./reference"
+
+import { Context, Effect, Layer, Schema, Scope } from "effect"
+import { castDraft } from "immer"
+import { Global } from "./global"
+import { EventV2 } from "./event"
+import { Repository } from "./repository"
+import { RepositoryCache } from "./repository-cache"
+import { AbsolutePath } from "./schema"
+import { State } from "./state"
+
+export class Info extends Schema.Class<Info>("Reference.Info")({
+  name: Schema.String,
+  path: AbsolutePath,
+  source: Schema.suspend(() => Source),
+}) {}
+
+export class LocalSource extends Schema.Class<LocalSource>("Reference.LocalSource")({
+  type: Schema.Literal("local"),
+  path: AbsolutePath,
+}) {}
+
+export class GitSource extends Schema.Class<GitSource>("Reference.GitSource")({
+  type: Schema.Literal("git"),
+  repository: Schema.String,
+  branch: Schema.String.pipe(Schema.optional),
+}) {}
+
+export const Source = Schema.Union([LocalSource, GitSource]).pipe(Schema.toTaggedUnion("type"))
+export type Source = typeof Source.Type
+
+export const Event = {
+  Updated: EventV2.define({ type: "reference.updated", schema: {} }),
+}
+
+type Data = {
+  sources: Map<string, Source>
+}
+
+type Editor = {
+  add(name: string, source: Source): void
+  remove(name: string): void
+  list(): readonly [string, Source][]
+}
+
+export interface Interface {
+  readonly transform: State.Interface<Data, Editor>["transform"]
+  readonly list: () => Effect.Effect<Info[]>
+}
+
+export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Reference") {}
+
+export const layer = Layer.effect(
+  Service,
+  Effect.gen(function* () {
+    const global = yield* Global.Service
+    const events = yield* EventV2.Service
+    const cache = yield* RepositoryCache.Service
+    const scope = yield* Scope.Scope
+    const materialized = new Map<string, Info>()
+    const state = State.create<Data, Editor>({
+      initial: () => ({ sources: new Map() }),
+      editor: (draft) => ({
+        add: (name, source) => draft.sources.set(name, castDraft(source)),
+        remove: (name) => draft.sources.delete(name),
+        list: () => Array.from(draft.sources.entries()) as [string, Source][],
+      }),
+      finalize: (editor) =>
+        Effect.gen(function* () {
+          materialized.clear()
+          const seen = new Map<string, string | undefined>()
+          for (const [name, source] of editor.list()) {
+            if (source.type === "local") {
+              materialized.set(name, new Info({ name, path: source.path, source }))
+              continue
+            }
+            const repository = Repository.parse(source.repository)
+            if (!repository || !Repository.isRemote(repository)) continue
+            if (source.branch) {
+              try {
+                Repository.validateBranch(source.branch)
+              } catch {
+                continue
+              }
+            }
+            const target = Repository.cachePath(global.repos, repository)
+            if (seen.has(target) && seen.get(target) !== source.branch) continue
+            seen.set(target, source.branch)
+            materialized.set(name, new Info({ name, path: AbsolutePath.make(target), source }))
+            yield* cache.ensure({ reference: repository, branch: source.branch, refresh: true }).pipe(
+              Effect.catchCause((cause) =>
+                Effect.logWarning("failed to materialize reference", {
+                  name,
+                  repository: source.repository,
+                  cause,
+                }),
+              ),
+              Effect.forkIn(scope),
+            )
+          }
+          yield* events.publish(Event.Updated, {})
+        }),
+    })
+
+    return Service.of({
+      transform: state.transform,
+      list: Effect.fn("Reference.list")(function* () {
+        return Array.from(materialized.values())
+      }),
+    })
+  }),
+)
+
+export const locationLayer = layer

+ 0 - 1
packages/core/src/session/input.ts

@@ -349,6 +349,5 @@ const toMessage = (input: Admitted) =>
     text: input.prompt.text,
     files: input.prompt.files,
     agents: input.prompt.agents,
-    references: input.prompt.references,
     time: { created: input.timeCreated },
   })

+ 0 - 1
packages/core/src/session/message-updater.ts

@@ -132,7 +132,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
             text: event.data.prompt.text,
             files: event.data.prompt.files,
             agents: event.data.prompt.agents,
-            references: event.data.prompt.references,
             time: { created: event.data.timestamp },
           }),
         )

+ 0 - 1
packages/core/src/session/message.ts

@@ -37,7 +37,6 @@ export class User extends Schema.Class<User>("Session.Message.User")({
   text: Prompt.fields.text,
   files: Prompt.fields.files,
   agents: Prompt.fields.agents,
-  references: Prompt.fields.references,
   type: Schema.Literal("user"),
   time: Schema.Struct({
     created: V2Schema.DateTimeUtcFromMillis,

+ 1 - 15
packages/core/src/session/prompt.ts

@@ -29,32 +29,18 @@ export class AgentAttachment extends Schema.Class<AgentAttachment>("Prompt.Agent
   source: Source.pipe(Schema.optional),
 }) {}
 
-export class ReferenceAttachment extends Schema.Class<ReferenceAttachment>("Prompt.ReferenceAttachment")({
-  name: Schema.String,
-  kind: Schema.Literals(["local", "git", "invalid"]),
-  uri: Schema.String.pipe(Schema.optional),
-  repository: Schema.String.pipe(Schema.optional),
-  branch: Schema.String.pipe(Schema.optional),
-  target: Schema.String.pipe(Schema.optional),
-  targetUri: Schema.String.pipe(Schema.optional),
-  problem: Schema.String.pipe(Schema.optional),
-  source: Source.pipe(Schema.optional),
-}) {}
-
 export class Prompt extends Schema.Class<Prompt>("Prompt")({
   text: Schema.String,
   files: Schema.Array(FileAttachment).pipe(Schema.optional),
   agents: Schema.Array(AgentAttachment).pipe(Schema.optional),
-  references: Schema.Array(ReferenceAttachment).pipe(Schema.optional),
 }) {
   static readonly equivalence = Schema.toEquivalence(Prompt)
 
-  static fromUserMessage(input: Pick<Prompt, "text" | "files" | "agents" | "references">) {
+  static fromUserMessage(input: Pick<Prompt, "text" | "files" | "agents">) {
     return new Prompt({
       text: input.text,
       ...(input.files === undefined ? {} : { files: input.files }),
       ...(input.agents === undefined ? {} : { agents: input.agents }),
-      ...(input.references === undefined ? {} : { references: input.references }),
     })
   }
 }

+ 0 - 1
packages/core/src/session/runner/to-llm-message.ts

@@ -105,7 +105,6 @@ function toLLMMessage(message: SessionMessage.Message, model: Model): Message[]
           metadata: {
             ...message.metadata,
             ...(message.agents?.length ? { agents: message.agents } : {}),
-            ...(message.references?.length ? { references: message.references } : {}),
           },
         }),
       ]

+ 3 - 4
packages/core/src/tool/edit.ts

@@ -2,8 +2,7 @@
  * Model-facing V2 exact-edit leaf. Relative paths resolve within the active
  * Location. Absolute paths inside that Location are accepted, while explicit
  * absolute external paths retain mutation capability through a separate
- * external_directory approval before edit approval. Named project references
- * are read-oriented and deliberately are not accepted by mutation tools.
+ * external_directory approval before edit approval.
  */
 export * as EditTool from "./edit"
 
@@ -21,7 +20,7 @@ export const name = "edit"
 export const Input = Schema.Struct({
   path: Schema.String.annotate({
     description:
-      "File path to edit. Relative paths resolve within the active Location. Absolute paths inside that Location are accepted; external absolute paths require external_directory approval. Named project references are read-oriented and are not accepted.",
+      "File path to edit. Relative paths resolve within the active Location. Absolute paths inside that Location are accepted; external absolute paths require external_directory approval.",
   }),
   oldString: Schema.String.annotate({ description: "Exact text to replace" }),
   newString: Schema.String.annotate({ description: "Replacement text, which must differ from oldString" }),
@@ -100,7 +99,7 @@ export const layer = Layer.effectDiscard(
         [name]: Tool.withPermission(
           Tool.make({
             description:
-              "Replace exact text in one file. Relative paths resolve within the active Location. Absolute paths inside the Location are accepted. Explicit external absolute paths require external_directory approval before edit approval. Named project references are read-oriented and are not accepted.",
+              "Replace exact text in one file. Relative paths resolve within the active Location. Absolute paths inside the Location are accepted. Explicit external absolute paths require external_directory approval before edit approval.",
             input: Input,
             output: Output,
             toModelOutput: ({ input, output }) => [

+ 2 - 8
packages/core/src/tool/glob.ts

@@ -15,9 +15,6 @@ export const Input = Schema.Struct({
   path: LocationSearch.FilesInput.fields.path.annotate({
     description: "Relative directory to search. Defaults to the active Location.",
   }),
-  reference: LocationSearch.FilesInput.fields.reference.annotate({
-    description: "Named project reference to search instead of the active Location",
-  }),
   limit: LocationSearch.FilesInput.fields.limit.annotate({
     description: `Maximum results to return (default: ${LocationSearch.DEFAULT_RESULT_LIMIT})`,
   }),
@@ -41,8 +38,6 @@ export const toModelOutput = (output: ModelOutput) => {
 /**
  * Location-scoped glob leaf. FileSystem supplies canonical permission metadata;
  * LocationSearch resolves the current root and owns containment and traversal.
- *
- * TODO: Revisit root-specific search permission resources if named-reference policy needs independent allow/deny rules.
  */
 export const layer = Layer.effectDiscard(
   Effect.gen(function* () {
@@ -55,20 +50,19 @@ export const layer = Layer.effectDiscard(
       .register({
         [name]: Tool.make({
           description:
-            "Find files by glob pattern within the active Location or a named project reference. Returns concise relative file resources. Use a relative path to narrow the search and limit to bound the result count.",
+            "Find files by glob pattern within the active Location. Returns concise relative file resources. Use a relative path to narrow the search and limit to bound the result count.",
           input: Input,
           output: LocationSearch.FilesResult,
           toModelOutput: ({ output }) => [toolText({ type: "text", text: toModelOutput(output) })],
           execute: (input, context) =>
             Effect.gen(function* () {
-              const root = yield* filesystem.resolveRoot({ path: input.path, reference: input.reference })
+              const root = yield* filesystem.resolveRoot({ path: input.path })
               yield* permission.assert({
                 action: name,
                 resources: [input.pattern],
                 save: ["*"],
                 metadata: {
                   root: root.resource,
-                  reference: input.reference,
                   path: input.path,
                   limit: input.limit,
                 },

+ 1 - 7
packages/core/src/tool/grep.ts

@@ -18,9 +18,6 @@ export const Input = Schema.Struct({
   path: LocationSearch.GrepInput.fields.path.annotate({
     description: "Relative file or directory to search. Defaults to the active Location.",
   }),
-  reference: LocationSearch.GrepInput.fields.reference.annotate({
-    description: "Named project reference to search instead of the active Location",
-  }),
   include: LocationSearch.GrepInput.fields.include.annotate({
     description: 'File glob to include in the search (for example, "*.js" or "*.{ts,tsx}")',
   }),
@@ -56,8 +53,6 @@ export const toModelOutput = (output: Output) => {
 /**
  * Location-scoped grep leaf. FileSystem supplies canonical permission metadata;
  * LocationSearch resolves the current root and owns containment and ripgrep execution.
- *
- * TODO: Revisit root-specific search permission resources if named-reference policy needs independent allow/deny rules.
  */
 export const layer = Layer.effectDiscard(
   Effect.gen(function* () {
@@ -70,7 +65,7 @@ export const layer = Layer.effectDiscard(
       .register({
         [name]: Tool.make({
           description:
-            "Search file contents by regular expression within the active Location, a named project reference, or an absolute managed tool-output file. Use a path to narrow the search, include to filter files by glob, and limit to bound the match count. Returns concise file resources, line numbers, and bounded line previews.",
+            "Search file contents by regular expression within the active Location or an absolute managed tool-output file. Use a path to narrow the search, include to filter files by glob, and limit to bound the match count. Returns concise file resources, line numbers, and bounded line previews.",
           input: Input,
           output: LocationSearch.GrepResult,
           toModelOutput: ({ output }) => [toolText({ type: "text", text: toModelOutput(output) })],
@@ -83,7 +78,6 @@ export const layer = Layer.effectDiscard(
                 save: ["*"],
                 metadata: {
                   root: root.resource,
-                  reference: input.reference,
                   path: input.path,
                   include: input.include,
                   limit: input.limit,

+ 3 - 4
packages/core/src/tool/write.ts

@@ -2,8 +2,7 @@
  * Model-facing V2 file-write leaf. Relative paths resolve within the active
  * Location. Absolute paths inside that Location are accepted, while explicit
  * absolute external paths retain mutation capability through a separate
- * external_directory approval before edit approval. Named project references
- * are read-oriented and deliberately are not accepted by mutation tools.
+ * external_directory approval before edit approval.
  */
 export * as WriteTool from "./write"
 
@@ -21,7 +20,7 @@ export const name = "write"
 export const Input = Schema.Struct({
   path: Schema.String.annotate({
     description:
-      "File path to write. Relative paths resolve within the active Location. Absolute paths inside that Location are accepted; external absolute paths require external_directory approval. Named project references are read-oriented and are not accepted.",
+      "File path to write. Relative paths resolve within the active Location. Absolute paths inside that Location are accepted; external absolute paths require external_directory approval.",
   }),
   content: Schema.String.annotate({ description: "Content to write to the file" }),
 })
@@ -55,7 +54,7 @@ export const layer = Layer.effectDiscard(
         [name]: Tool.withPermission(
           Tool.make({
             description:
-              "Write content to one file. Relative paths resolve within the active Location. Absolute paths inside the Location are accepted. Explicit external absolute paths require external_directory approval before edit approval. Named project references are read-oriented and are not accepted.",
+              "Write content to one file. Relative paths resolve within the active Location. Absolute paths inside the Location are accepted. Explicit external absolute paths require external_directory approval before edit approval.",
             input: Input,
             output: Output,
             toModelOutput: ({ output }) => [toolText({ type: "text", text: toModelOutput(output) })],

+ 1 - 1
packages/core/src/v1/config/config.ts

@@ -43,7 +43,7 @@ export const Info = Schema.Struct({
   }),
   skills: Schema.optional(ConfigSkillsV1.Info).annotate({ description: "Additional skill folder paths" }),
   reference: Schema.optional(ConfigReferenceV1.Info).annotate({
-    description: "Named git or local directory references that can be mentioned as @alias or @alias/path",
+    description: "Named git or local directory references",
   }),
   watcher: Schema.optional(Schema.Struct({ ignore: Schema.optional(Schema.mutable(Schema.Array(Schema.String))) })),
   snapshot: Schema.optional(Schema.Boolean).annotate({

+ 5 - 139
packages/core/test/location-filesystem.test.ts

@@ -7,25 +7,14 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
 import { Location } from "@opencode-ai/core/location"
 import { FileSystem } from "@opencode-ai/core/filesystem"
 import { Ripgrep } from "@opencode-ai/core/filesystem/ripgrep"
-import { ProjectReference } from "@opencode-ai/core/project-reference"
-import { Repository } from "@opencode-ai/core/repository"
 import { Global } from "@opencode-ai/core/global"
 import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
 import { tmpdir } from "./fixture/tmpdir"
 import { location } from "./fixture/location"
 import { it } from "./lib/effect"
 
-const inertReferences = ProjectReference.Service.of({
-  list: () => Effect.succeed([]),
-  get: () => Effect.succeed(undefined),
-  resolveMention: () => Effect.succeed(undefined),
-  ensurePath: () => Effect.void,
-  containsManagedPath: () => Effect.succeed(false),
-})
-
 function provide(
   directory: string,
-  references = inertReferences,
   filesystem = FSUtil.defaultLayer,
   data = Global.Path.data,
 ) {
@@ -36,7 +25,6 @@ function provide(
           filesystem,
           Ripgrep.defaultLayer,
           Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) }))),
-          Layer.succeed(ProjectReference.Service, references),
           Global.layerWith({ data }),
         ),
       ),
@@ -69,7 +57,7 @@ describe("FileSystem", () => {
         expect((yield* service.resolveRoot({ path: output })).real).toBe(output)
         expect(yield* Effect.exit(service.read({ path: unrelated }))).toMatchObject({ _tag: "Failure" })
         expect(yield* Effect.exit(service.read({ path: managed }))).toMatchObject({ _tag: "Failure" })
-      }).pipe(provide(worktree, inertReferences, FSUtil.defaultLayer, data))
+      }).pipe(provide(worktree, FSUtil.defaultLayer, data))
     }),
   )
 
@@ -258,7 +246,7 @@ describe("FileSystem", () => {
         }
         yield* Effect.promise(() => fs.rename(text, text + ".moved"))
         yield* Effect.promise(() => fs.rename(binary, binary + ".moved"))
-      }).pipe(provide(directory, inertReferences, filesystem))
+      }).pipe(provide(directory, filesystem))
     }),
   )
 
@@ -344,7 +332,7 @@ describe("FileSystem", () => {
           next: 3,
         })
         expect(realPaths.filter((target) => target !== directory)).toEqual([path.join(directory, "alpha.txt")])
-      }).pipe(provide(directory, inertReferences, filesystem))
+      }).pipe(provide(directory, filesystem))
     }),
   )
 
@@ -380,7 +368,7 @@ describe("FileSystem", () => {
 
         expect((yield* service.listPage({ limit: 32 })).entries).toHaveLength(32)
         expect(maximum).toBe(16)
-      }).pipe(provide(directory, inertReferences, filesystem))
+      }).pipe(provide(directory, filesystem))
     }),
   )
 
@@ -402,9 +390,8 @@ describe("FileSystem", () => {
     ),
   )
 
-  test("rejects empty list aliases and page limits over 2000", () => {
+  test("rejects page limits over 2000", () => {
     const decode = Schema.decodeUnknownSync(FileSystem.ListPageInput)
-    expect(() => decode({ reference: "" })).toThrow()
     expect(() => decode({ limit: 2_001 })).toThrow()
   })
 
@@ -461,125 +448,4 @@ describe("FileSystem", () => {
     ),
   )
 
-  it.live("reads and lists paths relative to a local project reference", () =>
-    withTmp((directory) => {
-      const docs = path.join(directory, "docs")
-      return Effect.gen(function* () {
-        yield* Effect.promise(async () => {
-          await fs.mkdir(docs)
-          await fs.writeFile(path.join(docs, "README.md"), "docs")
-        })
-        const service = yield* FileSystem.Service
-
-        expect(yield* service.read({ reference: "docs", path: RelativePath.make("README.md") })).toMatchObject({
-          type: "text",
-          content: "docs",
-        })
-        expect(yield* service.list({ reference: "docs" })).toMatchObject([{ path: "README.md", type: "file" }])
-      }).pipe(provide(directory, references({ docs: { name: "docs", kind: "local", path: docs } })))
-    }),
-  )
-
-  it.live("materializes Git references before filesystem access", () =>
-    withTmp((directory) => {
-      const docs = path.join(directory, "docs")
-      const ensured: string[] = []
-      return Effect.gen(function* () {
-        yield* Effect.promise(async () => {
-          await fs.mkdir(docs)
-          await fs.writeFile(path.join(docs, "README.md"), "docs")
-        })
-        expect(
-          yield* (yield* FileSystem.Service).read({ reference: "sdk", path: RelativePath.make("README.md") }),
-        ).toMatchObject({ content: "docs" })
-        expect(ensured).toEqual([docs])
-      }).pipe(
-        provide(
-          directory,
-          references(
-            {
-              sdk: {
-                name: "sdk",
-                kind: "git",
-                repository: "owner/repo",
-                reference: Repository.parseRemote("owner/repo"),
-                path: docs,
-              },
-            },
-            (target) => Effect.sync(() => ensured.push(target ?? "")),
-          ),
-        ),
-      )
-    }),
-  )
-
-  it.live("rejects unknown, invalid, and escaping project reference paths", () =>
-    withTmp((directory) => {
-      const docs = path.join(directory, "docs")
-      return Effect.gen(function* () {
-        yield* Effect.promise(() => fs.mkdir(docs))
-        const service = yield* FileSystem.Service
-        expect(Exit.isFailure(yield* service.list({ reference: "unknown" }).pipe(Effect.exit))).toBe(true)
-        expect(Exit.isFailure(yield* service.list({ reference: "invalid" }).pipe(Effect.exit))).toBe(true)
-        expect(
-          Exit.isFailure(
-            yield* service.read({ reference: "docs", path: RelativePath.make("../outside") }).pipe(Effect.exit),
-          ),
-        ).toBe(true)
-      }).pipe(
-        provide(
-          directory,
-          references({
-            docs: { name: "docs", kind: "local", path: docs },
-            invalid: { name: "invalid", kind: "invalid", message: "invalid reference" },
-          }),
-        ),
-      )
-    }),
-  )
-
-  it.live("rejects aliases when project references are disabled", () =>
-    withTmp((directory) =>
-      Effect.gen(function* () {
-        expect(Exit.isFailure(yield* (yield* FileSystem.Service).list({ reference: "docs" }).pipe(Effect.exit))).toBe(
-          true,
-        )
-      }).pipe(provide(directory)),
-    ),
-  )
-
-  it.live("rejects symlink escapes from project references", () =>
-    withTmp((directory) => {
-      const docs = path.join(directory, "docs")
-      const outside = path.join(directory, "outside.txt")
-      return Effect.gen(function* () {
-        if (process.platform === "win32") return
-        yield* Effect.promise(async () => {
-          await fs.mkdir(docs)
-          await fs.writeFile(outside, "outside")
-          await fs.symlink(outside, path.join(docs, "link.txt"))
-        })
-        expect(
-          Exit.isFailure(
-            yield* (yield* FileSystem.Service)
-              .read({ reference: "docs", path: RelativePath.make("link.txt") })
-              .pipe(Effect.exit),
-          ),
-        ).toBe(true)
-      }).pipe(provide(directory, references({ docs: { name: "docs", kind: "local", path: docs } })))
-    }),
-  )
 })
-
-function references(
-  entries: Record<string, ProjectReference.Resolved>,
-  ensurePath: ProjectReference.Interface["ensurePath"] = () => Effect.void,
-) {
-  return ProjectReference.Service.of({
-    list: () => Effect.succeed(Object.values(entries)),
-    get: (name) => Effect.succeed(entries[name]),
-    resolveMention: () => Effect.succeed(undefined),
-    ensurePath,
-    containsManagedPath: () => Effect.succeed(false),
-  })
-}

+ 2 - 2
packages/core/test/location-layer.test.ts

@@ -18,7 +18,7 @@ import { Global } from "../src/global"
 import { ModelsDev } from "../src/models-dev"
 import { Npm } from "../src/npm"
 import { Project } from "../src/project"
-import { ProjectReference } from "../src/project-reference"
+import { Reference } from "../src/reference"
 import { LocationSearch } from "../src/location-search"
 import { ToolRegistry } from "../src/tool/registry"
 import { ApplicationTools } from "../src/tool/application-tools"
@@ -71,7 +71,7 @@ describe("LocationServiceMap", () => {
           const update = (directory: string) =>
             Effect.gen(function* () {
               yield* PluginBoot.Service.use((boot) => boot.wait())
-              yield* ProjectReference.Service
+              yield* Reference.Service
               yield* LocationSearch.Service
               const catalog = yield* Catalog.Service
               const transform = yield* catalog.transform()

+ 1 - 1
packages/core/test/location-mutation.test.ts

@@ -171,7 +171,7 @@ describe("LocationMutation", () => {
     ),
   )
 
-  test("keeps project references outside the mutation input API", () => {
+  test("ignores unknown mutation input fields", () => {
     expect(Object.keys(LocationMutation.ResolveInput.fields)).toEqual(["path", "kind"])
     expect(Schema.decodeUnknownSync(LocationMutation.ResolveInput)({ path: "README.md", reference: "docs" })).toEqual({
       path: "README.md",

+ 4 - 22
packages/core/test/location-search.test.ts

@@ -8,7 +8,6 @@ import { FileSystem } from "@opencode-ai/core/filesystem"
 import { LocationSearch } from "@opencode-ai/core/location-search"
 import { AppProcess } from "@opencode-ai/core/process"
 import { Ripgrep as FileSystemRipgrep } from "@opencode-ai/core/filesystem/ripgrep"
-import { ProjectReference } from "@opencode-ai/core/project-reference"
 import { Ripgrep } from "@opencode-ai/core/ripgrep"
 import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
 import { Global } from "@opencode-ai/core/global"
@@ -16,15 +15,12 @@ import { tmpdir } from "./fixture/tmpdir"
 import { location } from "./fixture/location"
 import { it } from "./lib/effect"
 
-const inertReferences = references({})
-
-function provide(directory: string, projectReferences = inertReferences, data = Global.Path.data) {
+function provide(directory: string, data = Global.Path.data) {
   const dependencies = Layer.mergeAll(
     FSUtil.defaultLayer,
     FileSystemRipgrep.defaultLayer,
     AppProcess.defaultLayer,
     Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) }))),
-    Layer.succeed(ProjectReference.Service, projectReferences),
     Global.layerWith({ data }),
   )
   const filesystem = FileSystem.layer.pipe(Layer.provide(dependencies))
@@ -56,7 +52,7 @@ describe("LocationSearch", () => {
         const search = yield* LocationSearch.Service
         const result = yield* search.grep({ pattern: "FAIL", path: output })
         expect(result.items).toMatchObject([{ canonical: output, line: 2, lines: "FAIL here\n" }])
-      }).pipe(provide(directory, inertReferences, data))
+      }).pipe(provide(directory, data))
     }),
   )
 
@@ -83,7 +79,7 @@ describe("LocationSearch", () => {
     ),
   )
 
-  it.live("searches files under a relative subdirectory and named local reference", () =>
+  it.live("searches files under a relative subdirectory", () =>
     withTmp((directory) => {
       const docs = path.join(directory, "docs")
       return Effect.gen(function* () {
@@ -98,11 +94,7 @@ describe("LocationSearch", () => {
         expect(
           (yield* search.files({ pattern: "*.ts", path: RelativePath.make("src") })).items.map((item) => item.path),
         ).toEqual([RelativePath.make("src/active.ts")])
-        const guide = yield* Effect.promise(() => fs.realpath(path.join(docs, "guide.md")))
-        expect((yield* search.files({ pattern: "*.md", reference: "docs" })).items).toMatchObject([
-          { path: RelativePath.make("guide.md"), resource: "docs:guide.md", canonical: guide },
-        ])
-      }).pipe(provide(directory, references({ docs: { name: "docs", kind: "local", path: docs } })))
+      }).pipe(provide(directory))
     }),
   )
 
@@ -264,13 +256,3 @@ describe("LocationSearch", () => {
     expect(() => decode({ pattern: "*", limit: LocationSearch.MAX_RESULT_LIMIT + 1 })).toThrow()
   })
 })
-
-function references(entries: Record<string, ProjectReference.Resolved>) {
-  return ProjectReference.Service.of({
-    list: () => Effect.succeed(Object.values(entries)),
-    get: (name) => Effect.succeed(entries[name]),
-    resolveMention: () => Effect.succeed(undefined),
-    ensurePath: () => Effect.void,
-    containsManagedPath: () => Effect.succeed(false),
-  })
-}

+ 0 - 299
packages/core/test/project-reference.test.ts

@@ -1,299 +0,0 @@
-import { describe, expect } from "bun:test"
-import fs from "fs/promises"
-import path from "path"
-import { Deferred, Effect, Layer, Schema } from "effect"
-import { Config } from "@opencode-ai/core/config"
-import { ConfigReference } from "@opencode-ai/core/config/reference"
-import { FSUtil } from "@opencode-ai/core/fs-util"
-import { Flag } from "@opencode-ai/core/flag/flag"
-import { Global } from "@opencode-ai/core/global"
-import { Location } from "@opencode-ai/core/location"
-import { ProjectReference } from "@opencode-ai/core/project-reference"
-import { Repository } from "@opencode-ai/core/repository"
-import { RepositoryCache } from "@opencode-ai/core/repository-cache"
-import { AbsolutePath } from "@opencode-ai/core/schema"
-import { location } from "./fixture/location"
-import { tmpdir } from "./fixture/tmpdir"
-import { it } from "./lib/effect"
-
-describe("ProjectReference", () => {
-  it.live("uses the broad experimental flag unless references are explicitly configured", () =>
-    withEnv(
-      { OPENCODE_EXPERIMENTAL: "true", OPENCODE_EXPERIMENTAL_REFERENCES: undefined },
-      Effect.sync(() => {
-        expect(Flag.OPENCODE_EXPERIMENTAL_REFERENCES).toBe(true)
-      }),
-    ).pipe(
-      Effect.flatMap(() =>
-        withEnv(
-          { OPENCODE_EXPERIMENTAL: "true", OPENCODE_EXPERIMENTAL_REFERENCES: "false" },
-          Effect.sync(() => {
-            expect(Flag.OPENCODE_EXPERIMENTAL_REFERENCES).toBe(false)
-          }),
-        ),
-      ),
-    ),
-  )
-
-  it.live("normalizes aliases and resolves relative local paths from the project root", () =>
-    withTmp((tmp) =>
-      Effect.gen(function* () {
-        const project = path.join(tmp.path, "project")
-        const nested = path.join(project, "packages", "app")
-        yield* Effect.promise(() => fs.mkdir(nested, { recursive: true }))
-
-        const references = ProjectReference.resolveAll({
-          references: ConfigReference.normalize({
-            docs: { path: "./docs" },
-            home: "~/notes",
-            sdk: { repository: "owner/repo", branch: "main" },
-            shorthand: "owner/other",
-            invalid: "not-a-repo",
-            "bad/name": "owner/repo",
-          }),
-          directory: project,
-          home: path.join(tmp.path, "home"),
-          repos: path.join(tmp.path, "repos"),
-        })
-
-        expect(references).toMatchObject([
-          { name: "docs", kind: "local", path: path.join(project, "docs") },
-          { name: "home", kind: "local", path: path.join(tmp.path, "home", "notes") },
-          { name: "sdk", kind: "git", branch: "main" },
-          { name: "shorthand", kind: "git" },
-          { name: "invalid", kind: "invalid", repository: "not-a-repo" },
-          { name: "bad/name", kind: "invalid" },
-        ])
-      }),
-    ),
-  )
-
-  it.live("marks same-cache references with different branches invalid", () =>
-    Effect.sync(() => {
-      const references = ProjectReference.resolveAll({
-        references: ConfigReference.normalize({
-          main: { repository: "owner/repo", branch: "main" },
-          dev: { repository: "github.com/owner/repo", branch: "dev" },
-          alsoMain: { repository: "https://github.com/owner/repo", branch: "main" },
-        }),
-        directory: "/project",
-        home: "/home",
-        repos: "/repos",
-      })
-
-      expect(references.map((reference) => reference.kind)).toEqual(["git", "invalid", "git"])
-      expect(references[1]?.kind === "invalid" ? references[1].message : "").toContain("conflicts with @main")
-    }),
-  )
-
-  it.live("merges config aliases and exposes mention and managed-path operations", () =>
-    withoutReferences(
-      withTmp((tmp) => {
-        const calls: RepositoryCache.EnsureInput[] = []
-        const project = path.join(tmp.path, "project")
-        const nested = path.join(project, "packages", "app")
-        const docs = path.join(project, "docs")
-        const repos = path.join(tmp.path, "repos")
-        return Effect.gen(function* () {
-          yield* Effect.promise(async () => {
-            await fs.mkdir(nested, { recursive: true })
-            await fs.mkdir(docs)
-            await fs.writeFile(path.join(docs, "README.md"), "docs")
-          })
-
-          yield* withReferences(
-            Effect.gen(function* () {
-              const references = yield* ProjectReference.Service
-              const git = path.join(repos, "github.com", "owner", "repo")
-
-              expect(yield* references.list()).toMatchObject([
-                { name: "docs", kind: "local", path: docs },
-                { name: "sdk", kind: "git", path: git },
-              ])
-              expect(yield* references.resolveMention("docs/README.md")).toMatchObject({
-                name: "docs",
-                kind: "reference",
-                target: "README.md",
-                path: path.join(docs, "README.md"),
-              })
-              expect(yield* references.resolveMention("docs/missing.md")).toMatchObject({
-                name: "docs",
-                kind: "missing",
-              })
-              expect(yield* references.resolveMention("docs/../outside.md")).toMatchObject({
-                name: "docs",
-                kind: "invalid",
-              })
-              expect(yield* references.resolveMention("unknown")).toBeUndefined()
-              expect(yield* references.resolveMention("sdk")).toMatchObject({
-                name: "sdk",
-                kind: "reference",
-                path: git,
-              })
-              expect(yield* references.containsManagedPath(path.join(git, "README.md"))).toBe(true)
-              expect(yield* references.containsManagedPath(path.join(docs, "README.md"))).toBe(false)
-              yield* references.ensurePath()
-              expect(calls).toHaveLength(1)
-            }).pipe(
-              Effect.provide(
-                testLayer({
-                  directory: nested,
-                  project,
-                  repos,
-                  documents: [
-                    document({ docs: { path: "./old-docs" }, sdk: "owner/old" }),
-                    document({ docs: { path: "./docs" }, sdk: { repository: "owner/repo", branch: "main" } }),
-                  ],
-                  ensure: (input) => Effect.sync(() => result(repos, calls, input)),
-                }),
-              ),
-            ),
-          )
-        })
-      }),
-    ),
-  )
-
-  it.live("is inert while the runtime flag is disabled", () =>
-    withoutReferences(
-      withTmp((tmp) => {
-        const calls: RepositoryCache.EnsureInput[] = []
-        return Effect.gen(function* () {
-          const references = yield* ProjectReference.Service
-          expect(yield* references.list()).toEqual([])
-          expect(yield* references.get("sdk")).toBeUndefined()
-          expect(yield* references.resolveMention("sdk")).toBeUndefined()
-          expect(
-            yield* references.containsManagedPath(path.join(tmp.path, "repos", "github.com", "owner", "repo")),
-          ).toBe(false)
-          yield* references.ensurePath()
-          expect(calls).toEqual([])
-        }).pipe(
-          Effect.provide(
-            testLayer({
-              directory: tmp.path,
-              project: tmp.path,
-              repos: path.join(tmp.path, "repos"),
-              documents: [document({ sdk: "owner/repo" })],
-              ensure: (input) => Effect.sync(() => result(path.join(tmp.path, "repos"), calls, input)),
-            }),
-          ),
-        )
-      }),
-    ),
-  )
-
-  it.live("starts Git materialization in the background without blocking the location layer", () =>
-    withTmp((tmp) =>
-      Effect.gen(function* () {
-        const started = yield* Deferred.make<void>()
-        yield* withReferences(
-          Effect.gen(function* () {
-            expect(yield* (yield* ProjectReference.Service).list()).toHaveLength(1)
-            yield* Deferred.await(started).pipe(
-              Effect.timeoutOrElse({
-                duration: "1 second",
-                orElse: () => Effect.die(new Error("refresh did not start")),
-              }),
-            )
-          }).pipe(
-            Effect.provide(
-              testLayer({
-                directory: tmp.path,
-                project: tmp.path,
-                repos: path.join(tmp.path, "repos"),
-                documents: [document({ sdk: "owner/repo" })],
-                ensure: () => Deferred.succeed(started, undefined).pipe(Effect.andThen(Effect.never)),
-              }),
-            ),
-          ),
-        )
-      }),
-    ),
-  )
-})
-
-function document(references: ConfigReference.Info) {
-  return new Config.Document({ type: "document", info: Schema.decodeUnknownSync(Config.Info)({ references }) })
-}
-
-function result(
-  repos: string,
-  calls: RepositoryCache.EnsureInput[],
-  input: RepositoryCache.EnsureInput,
-): RepositoryCache.Result {
-  calls.push(input)
-  return {
-    repository: input.reference.label,
-    host: input.reference.host,
-    remote: input.reference.remote,
-    localPath: Repository.cachePath(repos, input.reference),
-    status: "cached",
-    branch: input.branch,
-  }
-}
-
-function testLayer(input: {
-  directory: string
-  project: string
-  repos: string
-  documents: Config.Document[]
-  ensure: RepositoryCache.Interface["ensure"]
-}) {
-  return ProjectReference.layer.pipe(
-    Layer.provide(
-      Layer.mergeAll(
-        FSUtil.defaultLayer,
-        Global.layerWith({ home: path.join(input.directory, "home"), repos: input.repos }),
-        Layer.succeed(
-          Location.Service,
-          Location.Service.of(
-            location(
-              { directory: AbsolutePath.make(input.directory) },
-              { projectDirectory: AbsolutePath.make(input.project) },
-            ),
-          ),
-        ),
-        Layer.succeed(Config.Service, Config.Service.of({ entries: () => Effect.succeed(input.documents) })),
-        Layer.succeed(RepositoryCache.Service, RepositoryCache.Service.of({ ensure: input.ensure })),
-      ),
-    ),
-  )
-}
-
-function withTmp<A, E, R>(body: (tmp: Awaited<ReturnType<typeof tmpdir>>) => Effect.Effect<A, E, R>) {
-  return Effect.acquireUseRelease(
-    Effect.promise(() => tmpdir()),
-    body,
-    (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
-  )
-}
-
-function withReferences<A, E, R>(body: Effect.Effect<A, E, R>) {
-  return withEnv({ OPENCODE_EXPERIMENTAL_REFERENCES: "true" }, body)
-}
-
-function withoutReferences<A, E, R>(body: Effect.Effect<A, E, R>) {
-  return withEnv({ OPENCODE_EXPERIMENTAL: undefined, OPENCODE_EXPERIMENTAL_REFERENCES: undefined }, body)
-}
-
-function withEnv<A, E, R>(env: Record<string, string | undefined>, body: Effect.Effect<A, E, R>) {
-  return Effect.acquireUseRelease(
-    Effect.sync(() => {
-      const previous = Object.fromEntries(Object.keys(env).map((key) => [key, process.env[key]]))
-      for (const [key, value] of Object.entries(env)) {
-        if (value === undefined) delete process.env[key]
-        else process.env[key] = value
-      }
-      return previous
-    }),
-    () => body,
-    (previous) =>
-      Effect.sync(() => {
-        for (const [key, value] of Object.entries(previous)) {
-          if (value === undefined) delete process.env[key]
-          else process.env[key] = value
-        }
-      }),
-  )
-}

+ 57 - 0
packages/core/test/reference.test.ts

@@ -0,0 +1,57 @@
+import { describe, expect } from "bun:test"
+import { Effect, Exit, Layer, Scope } from "effect"
+import { AbsolutePath } from "@opencode-ai/core/schema"
+import { Global } from "@opencode-ai/core/global"
+import { Reference } from "@opencode-ai/core/reference"
+import { Repository } from "@opencode-ai/core/repository"
+import { RepositoryCache } from "@opencode-ai/core/repository-cache"
+import { EventV2 } from "@opencode-ai/core/event"
+import { it } from "./lib/effect"
+
+const cache = Layer.mock(RepositoryCache.Service, {
+  ensure: () => Effect.die("unexpected Git materialization"),
+})
+
+describe("Reference", () => {
+  it.effect("registers normalized sources for the owning scope", () =>
+    Effect.gen(function* () {
+      const references = yield* Reference.Service
+      const scope = yield* Scope.make()
+      const update = yield* references.transform().pipe(Effect.provideService(Scope.Scope, scope))
+      const path = AbsolutePath.make("/docs")
+      yield* update((editor) => editor.add("docs", new Reference.LocalSource({ type: "local", path })))
+
+      expect(yield* references.list()).toEqual([
+        new Reference.Info({ name: "docs", path, source: new Reference.LocalSource({ type: "local", path }) }),
+      ])
+
+      yield* Scope.close(scope, Exit.void)
+      expect(yield* references.list()).toEqual([])
+    }).pipe(
+      Effect.provide(Reference.layer),
+      Effect.provide(cache),
+      Effect.provide(EventV2.defaultLayer),
+      Effect.provide(Global.defaultLayer),
+    ),
+  )
+
+  it.effect("derives Git paths without exposing cache operations", () =>
+    Effect.gen(function* () {
+      const references = yield* Reference.Service
+      const update = yield* references.transform()
+      const repository = Repository.parseRemote("owner/repo")
+      const source = new Reference.GitSource({ type: "git", repository: "owner/repo", branch: "main" })
+      yield* update((editor) => editor.add("sdk", source))
+
+      expect(yield* references.list()).toEqual([
+        new Reference.Info({ name: "sdk", path: AbsolutePath.make(Repository.cachePath(Global.Path.repos, repository)), source }),
+      ])
+    }).pipe(
+      Effect.scoped,
+      Effect.provide(Reference.layer),
+      Effect.provide(cache),
+      Effect.provide(EventV2.defaultLayer),
+      Effect.provide(Global.defaultLayer),
+    ),
+  )
+})

+ 2 - 4
packages/core/test/session-runner-message.test.ts

@@ -4,7 +4,7 @@ import * as OpenAIChat from "@opencode-ai/llm/protocols/openai-chat"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { ProviderV2 } from "@opencode-ai/core/provider"
 import { SessionMessage } from "@opencode-ai/core/session/message"
-import { AgentAttachment, FileAttachment, ReferenceAttachment } from "@opencode-ai/core/session/prompt"
+import { AgentAttachment, FileAttachment } from "@opencode-ai/core/session/prompt"
 import { toLLMMessages } from "@opencode-ai/core/session/runner/to-llm-message"
 import { SessionV2 } from "@opencode-ai/core/session"
 import { ToolOutput } from "@opencode-ai/core/tool-output"
@@ -17,7 +17,6 @@ const model = Model.make({ id: "model", provider: "provider", route: OpenAIChat.
 describe("toLLMMessages", () => {
   test("maps every top-level V2 Session message type", () => {
     const file = new FileAttachment({ uri: "data:image/png;base64,aGVsbG8=", mime: "image/png", name: "hello.png" })
-    const reference = new ReferenceAttachment({ name: "docs", kind: "local", uri: "file:///docs" })
     const messages = toLLMMessages(
       [
         new SessionMessage.AgentSwitched({
@@ -44,7 +43,6 @@ describe("toLLMMessages", () => {
           text: "Inspect this image",
           files: [file],
           agents: [new AgentAttachment({ name: "build" })],
-          references: [reference],
           time: { created },
         }),
         new SessionMessage.Synthetic({
@@ -84,7 +82,7 @@ describe("toLLMMessages", () => {
           { type: "text", text: "Inspect this image" },
           { type: "media", mediaType: "image/png", data: "data:image/png;base64,aGVsbG8=", filename: "hello.png" },
         ],
-        metadata: { agents: [{ name: "build" }], references: [reference] },
+        metadata: { agents: [{ name: "build" }] },
       }),
     )
     expect(messages.slice(2).map((message) => message.content)).toEqual([

+ 3 - 38
packages/core/test/tool-glob.test.ts

@@ -43,12 +43,10 @@ const filesystem = Layer.succeed(
       Effect.sync(() => {
         resolutions.push(input)
         const relative = input.path ?? RelativePath.make(".")
-        const resource = input.reference === undefined ? relative : `${input.reference}:${relative}`
         return new FileSystem.RootTarget({
           real: `/project/${relative}`,
           root: "/project",
-          resource,
-          reference: input.reference,
+          resource: relative,
           type: "directory",
         })
       }),
@@ -122,10 +120,10 @@ describe("GlobTool", () => {
           action: "glob",
           resources: ["**/*.ts"],
           save: ["*"],
-          metadata: { root: "src", reference: undefined, path: "src", limit: 12 },
+          metadata: { root: "src", path: "src", limit: 12 },
         },
       ])
-      expect(resolutions).toEqual([{ path: RelativePath.make("src"), reference: undefined }])
+      expect(resolutions).toEqual([{ path: RelativePath.make("src") }])
       expect(searches).toEqual([{ pattern: "**/*.ts", path: RelativePath.make("src"), limit: 12 }])
     }),
   )
@@ -169,39 +167,6 @@ describe("GlobTool", () => {
     }),
   )
 
-  it.effect("searches named references with root and reference metadata", () =>
-    Effect.gen(function* () {
-      reset()
-      result = new LocationSearch.FilesResult({
-        items: [
-          new LocationSearch.File({
-            path: RelativePath.make("guide.md"),
-            canonical: "/project/docs/guide.md",
-            resource: "docs:guide.md",
-            mtime: 1,
-          }),
-        ],
-        truncated: false,
-        partial: false,
-      })
-
-      expect(yield* executeTool(yield* ToolRegistry.Service, call({ pattern: "*.md", reference: "docs" }))).toEqual({
-        type: "text",
-        value: "docs:guide.md",
-      })
-      expect(assertions).toMatchObject([
-        {
-          sessionID,
-          action: "glob",
-          resources: ["*.md"],
-          save: ["*"],
-          metadata: { root: "docs:.", reference: "docs", path: undefined, limit: undefined },
-        },
-      ])
-      expect(searches).toEqual([{ pattern: "*.md", reference: "docs" }])
-    }),
-  )
-
   it.effect("formats bounded and partial results without discarding structured output", () =>
     Effect.sync(() => {
       const output = new LocationSearch.FilesResult({

+ 5 - 42
packages/core/test/tool-grep.test.ts

@@ -9,7 +9,6 @@ import { Ripgrep as FileSystemRipgrep } from "@opencode-ai/core/filesystem/ripgr
 import { LocationSearch } from "@opencode-ai/core/location-search"
 import { PermissionV2 } from "@opencode-ai/core/permission"
 import { AppProcess } from "@opencode-ai/core/process"
-import { ProjectReference } from "@opencode-ai/core/project-reference"
 import { Ripgrep } from "@opencode-ai/core/ripgrep"
 import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
 import { SessionV2 } from "@opencode-ai/core/session"
@@ -39,8 +38,7 @@ const filesystem = Layer.succeed(
         new FileSystem.RootTarget({
           real: `/project/${input.path ?? "."}`,
           root: "/project",
-          resource: input.reference === undefined ? (input.path ?? ".") : `${input.reference}:${input.path ?? "."}`,
-          reference: input.reference,
+          resource: input.path ?? ".",
           type: "directory",
         }),
       ),
@@ -115,23 +113,12 @@ const reset = () => {
   result = new LocationSearch.GrepResult({ items: [], truncated: false, partial: false })
 }
 
-function references(entries: Record<string, ProjectReference.Resolved>) {
-  return ProjectReference.Service.of({
-    list: () => Effect.succeed(Object.values(entries)),
-    get: (name) => Effect.succeed(entries[name]),
-    resolveMention: () => Effect.succeed(undefined),
-    ensurePath: () => Effect.void,
-    containsManagedPath: () => Effect.succeed(false),
-  })
-}
-
-function provideLive(directory: string, projectReferences = references({})) {
+function provideLive(directory: string) {
   const dependencies = Layer.mergeAll(
     FSUtil.defaultLayer,
     FileSystemRipgrep.defaultLayer,
     AppProcess.defaultLayer,
     Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) }))),
-    Layer.succeed(ProjectReference.Service, projectReferences),
   )
   const filesystem = FileSystem.layer.pipe(Layer.provide(dependencies))
   const search = LocationSearch.layer.pipe(
@@ -170,29 +157,13 @@ describe("GrepTool", () => {
           action: "grep",
           resources: ["needle"],
           save: ["*"],
-          metadata: { root: "src", reference: undefined, path: RelativePath.make("src"), include: "*.ts", limit: 2 },
+          metadata: { root: "src", path: RelativePath.make("src"), include: "*.ts", limit: 2 },
         },
       ])
       expect(searches).toEqual([{ pattern: "needle", path: RelativePath.make("src"), include: "*.ts", limit: 2 }])
     }),
   )
 
-  it.effect("delegates named reference grep and exposes the canonical selected root in metadata", () =>
-    Effect.gen(function* () {
-      reset()
-
-      yield* execute({ pattern: "guide", path: "docs", reference: "manual", include: "*.md" })
-
-      expect(assertions[0]).toMatchObject({
-        resources: ["guide"],
-        metadata: { root: "manual:docs", reference: "manual", path: RelativePath.make("docs"), include: "*.md" },
-      })
-      expect(searches).toEqual([
-        { pattern: "guide", path: RelativePath.make("docs"), reference: "manual", include: "*.md" },
-      ])
-    }),
-  )
-
   it.effect("does not search when permission is denied", () =>
     Effect.gen(function* () {
       reset()
@@ -248,7 +219,7 @@ describe("GrepTool", () => {
     }),
   )
 
-  runtimeIt.live("greps active Location and named-reference files with include globs", () =>
+  runtimeIt.live("greps active Location files with include globs", () =>
     Effect.acquireRelease(
       Effect.promise(() => tmpdir()),
       (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
@@ -259,23 +230,15 @@ describe("GrepTool", () => {
           reset()
           yield* Effect.promise(async () => {
             await fs.mkdir(path.join(tmp.path, "src"))
-            await fs.mkdir(docs)
             await fs.writeFile(path.join(tmp.path, "src", "index.ts"), "needle ts\n")
             await fs.writeFile(path.join(tmp.path, "src", "notes.txt"), "needle txt\n")
-            await fs.writeFile(path.join(docs, "guide.md"), "needle docs\n")
           })
 
           expect(yield* execute({ pattern: "needle", path: "src", include: "*.ts" })).toEqual({
             type: "text",
             value: "Found 1 matches\nsrc/index.ts:\n  Line 1: needle ts\n",
           })
-          expect(yield* execute({ pattern: "needle", reference: "docs", include: "*.md" })).toEqual({
-            type: "text",
-            value: "Found 1 matches\ndocs:guide.md:\n  Line 1: needle docs\n",
-          })
-        }).pipe(
-          Effect.provide(provideLive(tmp.path, references({ docs: { name: "docs", kind: "local", path: docs } }))),
-        )
+        }).pipe(Effect.provide(provideLive(tmp.path)))
       }),
     ),
   )

+ 1 - 15
packages/core/test/tool-read.test.ts

@@ -35,7 +35,7 @@ const filesystem = Layer.succeed(
         ? Effect.succeed(
             new FileSystem.ReadPath({
               type: resolvedType,
-              resource: input.reference === undefined ? input.path : `${input.reference}:${input.path}`,
+              resource: input.path,
             }),
           )
         : Effect.die(resolveFailure),
@@ -457,20 +457,6 @@ describe("ReadTool", () => {
     }),
   )
 
-  it.effect("authorizes project references with their canonical identity", () =>
-    Effect.gen(function* () {
-      const registry = yield* ToolRegistry.Service
-
-      yield* executeTool(registry, {
-        sessionID,
-        ...toolIdentity,
-        call: { type: "tool-call", id: "call-read", name: "read", input: { path: "README.md", reference: "docs" } },
-      })
-
-      expect(assertions).toMatchObject([{ resources: ["docs:README.md"] }])
-    }),
-  )
-
   it.effect("preserves unexpected resolution defects", () =>
     Effect.gen(function* () {
       const registry = yield* ToolRegistry.Service

+ 0 - 48
packages/opencode/src/config/reference.ts

@@ -1,48 +0,0 @@
-export * as ConfigReference from "./reference"
-
-import { ConfigReferenceV1 } from "@opencode-ai/core/v1/config/reference"
-
-export type NormalizedEntry =
-  | {
-      kind: "local"
-      path: string
-    }
-  | {
-      kind: "git"
-      repository: string
-      branch?: string
-    }
-  | {
-      kind: "invalid"
-      message: string
-    }
-
-export type NormalizedInfo = Record<string, NormalizedEntry>
-
-export function validateAlias(name: string) {
-  if (name.length === 0) return "Reference alias must not be empty"
-  if (/[\/\s`,]/.test(name)) {
-    return "Reference alias must not contain /, whitespace, comma, or backtick"
-  }
-}
-
-export function normalizeEntry(entry: ConfigReferenceV1.Entry): NormalizedEntry {
-  if (typeof entry === "string") {
-    if (entry.startsWith(".") || entry.startsWith("/") || entry.startsWith("~")) {
-      return { kind: "local", path: entry }
-    }
-    return { kind: "git", repository: entry }
-  }
-
-  if ("path" in entry) return { kind: "local", path: entry.path }
-  return { kind: "git", repository: entry.repository, branch: entry.branch }
-}
-
-export function normalize(info: ConfigReferenceV1.Info): NormalizedInfo {
-  return Object.fromEntries(
-    Object.entries(info).map(([name, entry]) => {
-      const aliasError = validateAlias(name)
-      return [name, aliasError ? { kind: "invalid" as const, message: aliasError } : normalizeEntry(entry)] as const
-    }),
-  )
-}

+ 0 - 2
packages/opencode/src/effect/app-runtime.ts

@@ -42,7 +42,6 @@ import { Format } from "@/format"
 import { InstanceLayer } from "@/project/instance-layer"
 import { Project } from "@/project/project"
 import { Vcs } from "@/project/vcs"
-import { Reference } from "@/reference/reference"
 import { Workspace } from "@/control-plane/workspace"
 import { Worktree } from "@/worktree"
 import { Installation } from "@/installation"
@@ -98,7 +97,6 @@ export const AppLayer = Layer.mergeAll(
   Format.defaultLayer,
   Project.defaultLayer,
   Vcs.defaultLayer,
-  Reference.defaultLayer,
   Workspace.defaultLayer,
   Worktree.appLayer,
   Installation.defaultLayer,

+ 1 - 4
packages/opencode/src/project/bootstrap.ts

@@ -11,7 +11,6 @@ import { Search } from "@opencode-ai/core/filesystem/search"
 import { Effect, Layer } from "effect"
 import { Config } from "@/config/config"
 import { Service } from "./bootstrap-service"
-import { Reference } from "@/reference/reference"
 
 export { Service } from "./bootstrap-service"
 export type { Interface } from "./bootstrap-service"
@@ -27,7 +26,6 @@ export const layer = Layer.effect(
     const lsp = yield* LSP.Service
     const plugin = yield* Plugin.Service
     const project = yield* Project.Service
-    const reference = yield* Reference.Service
     const search = yield* Search.Service
     const shareNext = yield* ShareNext.Service
     const snapshot = yield* Snapshot.Service
@@ -52,7 +50,7 @@ export const layer = Layer.effect(
       // Each service self-manages its own slow work via Effect.forkScoped against
       // its per-instance state scope. We just await materialization here.
       yield* Effect.forEach(
-        [reference, lsp, shareNext, format, vcs, snapshot, project],
+        [lsp, shareNext, format, vcs, snapshot, project],
         (s) => s.init().pipe(Effect.catchCause((cause) => Effect.logWarning("init failed", { cause }))),
         { concurrency: "unbounded", discard: true },
       ).pipe(Effect.withSpan("InstanceBootstrap.init"))
@@ -69,7 +67,6 @@ export const defaultLayer: Layer.Layer<Service> = layer.pipe(
     LSP.defaultLayer,
     Plugin.defaultLayer,
     Project.defaultLayer,
-    Reference.defaultLayer,
     Search.defaultLayer,
     ShareNext.defaultLayer,
     Snapshot.defaultLayer,

+ 0 - 237
packages/opencode/src/reference/reference.ts

@@ -1,237 +0,0 @@
-import path from "path"
-import { Effect, Context, Layer, Scope } from "effect"
-import { FSUtil } from "@opencode-ai/core/fs-util"
-import { Global } from "@opencode-ai/core/global"
-import { Config } from "@/config/config"
-import { ConfigReference } from "@/config/reference"
-import { InstanceState } from "@/effect/instance-state"
-import { RuntimeFlags } from "@/effect/runtime-flags"
-import { parseRepositoryReference, repositoryCachePath, type RemoteReference } from "@/util/repository"
-import { RepositoryCache } from "./repository-cache"
-
-export type Resolved =
-  | {
-      name: string
-      kind: "local"
-      path: string
-    }
-  | {
-      name: string
-      kind: "git"
-      repository: string
-      reference: RemoteReference
-      path: string
-      branch?: string
-    }
-  | {
-      name: string
-      kind: "invalid"
-      repository?: string
-      message: string
-    }
-
-type State = {
-  references: Resolved[]
-  materializeAll: Effect.Effect<void>
-  materializeByPath: Materializer[]
-}
-
-type Materializer = { path: string; run: Effect.Effect<void> }
-
-export interface Interface {
-  readonly init: () => Effect.Effect<void>
-  readonly list: () => Effect.Effect<Resolved[]>
-  readonly get: (name: string) => Effect.Effect<Resolved | undefined>
-  readonly ensure: (target?: string) => Effect.Effect<void>
-  readonly contains: (target?: string) => Effect.Effect<boolean>
-}
-
-export class Service extends Context.Service<Service, Interface>()("@opencode/Reference") {}
-
-export function referencePath(input: { directory: string; worktree: string; value: string }) {
-  if (input.value.startsWith("~/")) return path.join(Global.Path.home, input.value.slice(2))
-  return path.isAbsolute(input.value)
-    ? input.value
-    : path.resolve(input.worktree === "/" ? input.directory : input.worktree, input.value)
-}
-
-function resolveGit(
-  input: { name: string; repository: string } | { name: string; repository: string; branch: string | undefined },
-): Resolved {
-  const parsed = parseRepositoryReference(input.repository)
-  if (!parsed || parsed.protocol === "file:") {
-    return {
-      name: input.name,
-      kind: "invalid",
-      repository: input.repository,
-      message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
-    }
-  }
-  return {
-    name: input.name,
-    kind: "git",
-    repository: input.repository,
-    reference: parsed,
-    path: repositoryCachePath(parsed),
-    ...("branch" in input ? { branch: input.branch } : {}),
-  }
-}
-
-function branchLabel(branch: string | undefined) {
-  return branch ?? "default branch"
-}
-
-function normalizedTarget(target?: string) {
-  if (!target) return
-  return process.platform === "win32" ? FSUtil.normalizePath(target) : target
-}
-
-function containsReferencePath(referencePath: string, target: string) {
-  return FSUtil.contains(normalizedTarget(referencePath) ?? referencePath, target)
-}
-
-function uniqueGitReferences(references: Resolved[]) {
-  const seenPath = new Set<string>()
-  return references.filter((reference): reference is Extract<Resolved, { kind: "git" }> => {
-    if (reference.kind !== "git") return false
-    if (seenPath.has(reference.path)) return false
-    seenPath.add(reference.path)
-    return true
-  })
-}
-
-function materializeReference(cache: RepositoryCache.Interface, reference: Extract<Resolved, { kind: "git" }>) {
-  return cache.ensure({ reference: reference.reference, branch: reference.branch, refresh: true }).pipe(
-    Effect.asVoid,
-    Effect.catchCause((cause) =>
-      Effect.logWarning("failed to materialize reference repository", { name: reference.name, cause }),
-    ),
-  )
-}
-
-const materializers = Effect.fn("Reference.materializers")(function* (
-  cache: RepositoryCache.Interface,
-  references: Resolved[],
-) {
-  return yield* Effect.forEach(
-    uniqueGitReferences(references),
-    Effect.fnUntraced(function* (reference) {
-      return { path: reference.path, run: yield* Effect.cached(materializeReference(cache, reference)) }
-    }),
-    { concurrency: "unbounded" },
-  )
-})
-
-function materializeAll(input: { flags: RuntimeFlags.Info; materializers: Materializer[] }) {
-  if (!input.flags.experimentalReferences) return Effect.void
-  return Effect.forEach(
-    input.materializers,
-    Effect.fnUntraced(function* (item) {
-      yield* item.run
-    }),
-    { concurrency: 4, discard: true },
-  )
-}
-
-function materializeByPath(materializers: Materializer[], target: string) {
-  return materializers.find((item) => containsReferencePath(item.path, target))?.run ?? Effect.void
-}
-
-function containsGitReferencePath(references: Resolved[], target: string) {
-  return references.some((reference) => reference.kind === "git" && containsReferencePath(reference.path, target))
-}
-
-export function resolve(input: {
-  name: string
-  reference: ConfigReference.NormalizedEntry
-  directory: string
-  worktree: string
-}): Resolved {
-  if (input.reference.kind === "invalid") {
-    return { name: input.name, kind: "invalid", message: input.reference.message }
-  }
-  if (input.reference.kind === "local") {
-    return { name: input.name, kind: "local", path: referencePath({ ...input, value: input.reference.path }) }
-  }
-  return resolveGit({ name: input.name, repository: input.reference.repository, branch: input.reference.branch })
-}
-
-export function resolveAll(input: { references: ConfigReference.NormalizedInfo; directory: string; worktree: string }) {
-  const seen = new Map<string, { name: string; branch?: string }>()
-  return Object.entries(input.references).map(([name, reference]) => {
-    const resolved = resolve({ name, reference, directory: input.directory, worktree: input.worktree })
-    if (resolved.kind !== "git") return resolved
-
-    const existing = seen.get(resolved.path)
-    if (!existing) {
-      seen.set(resolved.path, { name, branch: resolved.branch })
-      return resolved
-    }
-    if (existing.branch === resolved.branch) return resolved
-
-    return {
-      name,
-      kind: "invalid" as const,
-      repository: resolved.repository,
-      message: `Reference conflicts with @${existing.name}: both use ${resolved.path}, but @${existing.name} requests ${branchLabel(existing.branch)} and @${name} requests ${branchLabel(resolved.branch)}`,
-    }
-  })
-}
-
-export const layer = Layer.effect(
-  Service,
-  Effect.gen(function* () {
-    const config = yield* Config.Service
-    const cache = yield* RepositoryCache.Service
-    const scope = yield* Scope.Scope
-    const flags = yield* RuntimeFlags.Service
-
-    const state = yield* InstanceState.make<State>(
-      Effect.fn("Reference.state")(function* (ctx) {
-        const cfg = yield* config.get()
-        const references = resolveAll({
-          references: ConfigReference.normalize(cfg.reference ?? {}),
-          directory: ctx.directory,
-          worktree: ctx.worktree,
-        })
-        const materializeByPath = yield* materializers(cache, references)
-        const materializeAllCached = yield* Effect.cached(materializeAll({ flags, materializers: materializeByPath }))
-
-        return { references, materializeAll: materializeAllCached, materializeByPath }
-      }),
-    )
-
-    return Service.of({
-      init: Effect.fn("Reference.init")(function* () {
-        if (!flags.experimentalReferences) return
-        yield* InstanceState.useEffect(state, (s) => s.materializeAll).pipe(Effect.forkIn(scope), Effect.asVoid)
-      }),
-      list: Effect.fn("Reference.list")(function* () {
-        return yield* InstanceState.use(state, (s) => s.references)
-      }),
-      get: Effect.fn("Reference.get")(function* (name: string) {
-        return yield* InstanceState.use(state, (s) => s.references.find((reference) => reference.name === name))
-      }),
-      ensure: Effect.fn("Reference.ensure")(function* (target?: string) {
-        if (!flags.experimentalReferences) return
-        const full = normalizedTarget(target)
-        if (!full) return yield* InstanceState.useEffect(state, (s) => s.materializeAll)
-        return yield* InstanceState.useEffect(state, (s) => materializeByPath(s.materializeByPath, full))
-      }),
-      contains: Effect.fn("Reference.contains")(function* (target?: string) {
-        if (!flags.experimentalReferences) return false
-        const full = normalizedTarget(target)
-        if (!full) return false
-        return yield* InstanceState.use(state, (s) => containsGitReferencePath(s.references, full))
-      }),
-    })
-  }),
-)
-
-export const defaultLayer = layer.pipe(
-  Layer.provide(Config.defaultLayer),
-  Layer.provide(RepositoryCache.defaultLayer),
-  Layer.provide(RuntimeFlags.defaultLayer),
-)
-
-export * as Reference from "./reference"

+ 0 - 320
packages/opencode/src/reference/repository-cache.ts

@@ -1,320 +0,0 @@
-import path from "path"
-import { Context, Effect, Layer, Schema } from "effect"
-import { FSUtil } from "@opencode-ai/core/fs-util"
-import { Flock } from "@opencode-ai/core/util/flock"
-import { Git } from "@/git"
-import {
-  repositoryCachePath,
-  sameRepositoryReference,
-  parseRepositoryReference,
-  parseRemoteRepositoryReference,
-  validateRepositoryBranch,
-  InvalidRepositoryBranchError,
-  InvalidRepositoryReferenceError,
-  UnsupportedLocalRepositoryError,
-  type RemoteReference,
-} from "@/util/repository"
-
-export type Result = {
-  repository: string
-  host: string
-  remote: string
-  localPath: string
-  status: "cached" | "cloned" | "refreshed"
-  head?: string
-  branch?: string
-}
-
-export type EnsureInput = {
-  reference: RemoteReference
-  refresh?: boolean
-  branch?: string
-}
-
-export class InvalidRepositoryError extends Schema.TaggedErrorClass<InvalidRepositoryError>()(
-  "RepositoryCacheInvalidRepositoryError",
-  {
-    repository: Schema.String,
-    message: Schema.String,
-  },
-) {}
-
-export class InvalidBranchError extends Schema.TaggedErrorClass<InvalidBranchError>()(
-  "RepositoryCacheInvalidBranchError",
-  {
-    branch: Schema.String,
-    message: Schema.String,
-  },
-) {}
-
-export class CloneFailedError extends Schema.TaggedErrorClass<CloneFailedError>()("RepositoryCacheCloneFailedError", {
-  repository: Schema.String,
-  message: Schema.String,
-}) {}
-
-export class FetchFailedError extends Schema.TaggedErrorClass<FetchFailedError>()("RepositoryCacheFetchFailedError", {
-  repository: Schema.String,
-  message: Schema.String,
-}) {}
-
-export class CheckoutFailedError extends Schema.TaggedErrorClass<CheckoutFailedError>()(
-  "RepositoryCacheCheckoutFailedError",
-  {
-    repository: Schema.String,
-    branch: Schema.String,
-    message: Schema.String,
-  },
-) {}
-
-export class ResetFailedError extends Schema.TaggedErrorClass<ResetFailedError>()("RepositoryCacheResetFailedError", {
-  repository: Schema.String,
-  message: Schema.String,
-}) {}
-
-export class LockFailedError extends Schema.TaggedErrorClass<LockFailedError>()("RepositoryCacheLockFailedError", {
-  localPath: Schema.String,
-  message: Schema.String,
-}) {}
-
-export class CacheOperationError extends Schema.TaggedErrorClass<CacheOperationError>()(
-  "RepositoryCacheOperationError",
-  {
-    operation: Schema.String,
-    path: Schema.String,
-    message: Schema.String,
-  },
-) {}
-
-export type Error =
-  | InvalidRepositoryError
-  | InvalidBranchError
-  | CloneFailedError
-  | FetchFailedError
-  | CheckoutFailedError
-  | ResetFailedError
-  | LockFailedError
-  | CacheOperationError
-
-export interface Interface {
-  ensure: (input: EnsureInput) => Effect.Effect<Result, Error>
-}
-
-export class Service extends Context.Service<Service, Interface>()("@opencode/RepositoryCache") {}
-
-function statusForRepository(input: { reuse: boolean; refresh?: boolean; branchMatches?: boolean }) {
-  if (!input.reuse) return "cloned" as const
-  if (input.branchMatches === false) return "refreshed" as const
-  if (input.refresh) return "refreshed" as const
-  return "cached" as const
-}
-
-function resetTarget(input: {
-  requestedBranch?: string
-  remoteHead: { code: number; stdout: string }
-  branch: { code: number; stdout: string }
-}) {
-  if (input.requestedBranch) return `origin/${input.requestedBranch}`
-  if (input.remoteHead.code === 0 && input.remoteHead.stdout) {
-    return input.remoteHead.stdout.replace(/^refs\/remotes\//, "")
-  }
-  if (input.branch.code === 0 && input.branch.stdout) {
-    return `origin/${input.branch.stdout}`
-  }
-  return "HEAD"
-}
-
-function errorMessage(error: unknown) {
-  return error instanceof globalThis.Error ? error.message : String(error)
-}
-
-export function isError(error: unknown): error is Error {
-  return (
-    error instanceof InvalidRepositoryError ||
-    error instanceof InvalidBranchError ||
-    error instanceof CloneFailedError ||
-    error instanceof FetchFailedError ||
-    error instanceof CheckoutFailedError ||
-    error instanceof ResetFailedError ||
-    error instanceof LockFailedError ||
-    error instanceof CacheOperationError
-  )
-}
-
-export const parseRemoteReference = Effect.fn("RepositoryCache.parseRemoteReference")(function* (repository: string) {
-  try {
-    return parseRemoteRepositoryReference(repository)
-  } catch (error) {
-    if (error instanceof InvalidRepositoryReferenceError || error instanceof UnsupportedLocalRepositoryError) {
-      return yield* new InvalidRepositoryError({ repository: error.repository, message: error.message })
-    }
-    return yield* new InvalidRepositoryError({
-      repository,
-      message: errorMessage(error),
-    })
-  }
-})
-
-export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(function* (branch: string) {
-  try {
-    validateRepositoryBranch(branch)
-  } catch (error) {
-    if (error instanceof InvalidRepositoryBranchError) {
-      return yield* new InvalidBranchError({ branch: error.branch, message: error.message })
-    }
-    return yield* new InvalidBranchError({ branch, message: errorMessage(error) })
-  }
-})
-
-const ensureWithServices = Effect.fn("RepositoryCache.ensureWithServices")(function* (
-  input: EnsureInput,
-  services: {
-    fs: FSUtil.Interface
-    git: Git.Interface
-  },
-) {
-  if (input.branch) yield* validateBranch(input.branch)
-
-  const repository = input.reference.label
-  const remote = input.reference.remote
-  const localPath = repositoryCachePath(input.reference)
-  const cloneTarget = parseRepositoryReference(remote) ?? input.reference
-
-  return yield* Effect.acquireUseRelease(
-    Effect.promise((signal) => Flock.acquire(`repo-clone:${localPath}`, { signal })).pipe(
-      Effect.catch((error: unknown) =>
-        Effect.fail(new LockFailedError({ localPath, message: errorMessage(error) || `Failed to lock ${localPath}` })),
-      ),
-    ),
-    () =>
-      Effect.gen(function* () {
-        yield* services.fs.ensureDir(path.dirname(localPath)).pipe(
-          Effect.catch((error: unknown) =>
-            Effect.fail(
-              new CacheOperationError({
-                operation: "ensure cache directory",
-                path: localPath,
-                message: errorMessage(error),
-              }),
-            ),
-          ),
-        )
-
-        const exists = yield* services.fs.existsSafe(localPath)
-        const hasGitDir = yield* services.fs.existsSafe(path.join(localPath, ".git"))
-        const origin = hasGitDir
-          ? yield* services.git.run(["config", "--get", "remote.origin.url"], { cwd: localPath })
-          : undefined
-        const originReference = origin?.exitCode === 0 ? parseRepositoryReference(origin.text().trim()) : undefined
-        const reuse = hasGitDir && Boolean(originReference && sameRepositoryReference(originReference, cloneTarget))
-        if (exists && !reuse) {
-          yield* services.fs.remove(localPath, { recursive: true }).pipe(
-            Effect.catch((error: unknown) =>
-              Effect.fail(
-                new CacheOperationError({
-                  operation: "remove stale cache",
-                  path: localPath,
-                  message: errorMessage(error),
-                }),
-              ),
-            ),
-          )
-        }
-
-        const currentBranch = hasGitDir ? yield* services.git.branch(localPath) : undefined
-        const status = statusForRepository({
-          reuse,
-          refresh: input.refresh,
-          branchMatches: input.branch ? currentBranch === input.branch : undefined,
-        })
-
-        if (status === "cloned") {
-          const clone = yield* services.git.run(
-            ["clone", "--depth", "100", ...(input.branch ? ["--branch", input.branch] : []), "--", remote, localPath],
-            { cwd: path.dirname(localPath) },
-          )
-          if (clone.exitCode !== 0) {
-            return yield* new CloneFailedError({
-              repository,
-              message: clone.stderr.toString().trim() || clone.text().trim() || `Failed to clone ${repository}`,
-            })
-          }
-        }
-
-        if (status === "refreshed") {
-          const fetch = yield* services.git.run(["fetch", "--all", "--prune"], { cwd: localPath })
-          if (fetch.exitCode !== 0) {
-            return yield* new FetchFailedError({
-              repository,
-              message: fetch.stderr.toString().trim() || fetch.text().trim() || `Failed to refresh ${repository}`,
-            })
-          }
-
-          if (input.branch) {
-            const checkout = yield* services.git.run(["checkout", "-B", input.branch, `origin/${input.branch}`], {
-              cwd: localPath,
-            })
-            if (checkout.exitCode !== 0) {
-              return yield* new CheckoutFailedError({
-                repository,
-                branch: input.branch,
-                message:
-                  checkout.stderr.toString().trim() || checkout.text().trim() || `Failed to checkout ${input.branch}`,
-              })
-            }
-          }
-
-          const remoteHead = yield* services.git.run(["symbolic-ref", "refs/remotes/origin/HEAD"], { cwd: localPath })
-          const branch = yield* services.git.run(["symbolic-ref", "--quiet", "--short", "HEAD"], { cwd: localPath })
-          const target = resetTarget({
-            requestedBranch: input.branch,
-            remoteHead: { code: remoteHead.exitCode, stdout: remoteHead.text().trim() },
-            branch: { code: branch.exitCode, stdout: branch.text().trim() },
-          })
-
-          const reset = yield* services.git.run(["reset", "--hard", target], { cwd: localPath })
-          if (reset.exitCode !== 0) {
-            return yield* new ResetFailedError({
-              repository,
-              message: reset.stderr.toString().trim() || reset.text().trim() || `Failed to reset ${repository}`,
-            })
-          }
-        }
-
-        const head = yield* services.git.run(["rev-parse", "HEAD"], { cwd: localPath })
-        const branch = yield* services.git.branch(localPath)
-        const headText = head.exitCode === 0 ? head.text().trim() : undefined
-
-        return {
-          repository,
-          host: input.reference.host,
-          remote,
-          localPath,
-          status,
-          head: headText,
-          branch,
-        } satisfies Result
-      }),
-    (lock) => Effect.promise(() => lock.release()).pipe(Effect.ignore),
-  )
-})
-
-export const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service> = Layer.effect(
-  Service,
-  Effect.gen(function* () {
-    const fs = yield* FSUtil.Service
-    const git = yield* Git.Service
-
-    return Service.of({
-      ensure: Effect.fn("RepositoryCache.ensure")(function* (input) {
-        return yield* ensureWithServices(input, { fs, git })
-      }),
-    })
-  }),
-)
-
-export const defaultLayer: Layer.Layer<Service> = layer.pipe(
-  Layer.provide(FSUtil.defaultLayer),
-  Layer.provide(Git.defaultLayer),
-)
-
-export * as RepositoryCache from "./repository-cache"

+ 0 - 2
packages/opencode/src/server/routes/instance/httpapi/api.ts

@@ -17,7 +17,6 @@ import { ProjectCopyApi } from "./groups/project-copy"
 import { ProviderApi } from "./groups/provider"
 import { PtyApi, PtyConnectApi } from "./groups/pty"
 import { QuestionApi } from "./groups/question"
-import { ReferenceApi } from "./groups/reference"
 import { SessionApi } from "./groups/session"
 import { SyncApi } from "./groups/sync"
 import { TuiApi } from "./groups/tui"
@@ -61,7 +60,6 @@ export const InstanceHttpApi = HttpApi.make("opencode-instance")
   .addHttpApi(QuestionApi)
   .addHttpApi(PermissionApi)
   .addHttpApi(ProviderApi)
-  .addHttpApi(ReferenceApi)
   .addHttpApi(SessionApi)
   .addHttpApi(SyncApi)
   .addHttpApi(TuiApi)

+ 0 - 60
packages/opencode/src/server/routes/instance/httpapi/groups/reference.ts

@@ -1,60 +0,0 @@
-import { Schema } from "effect"
-import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
-import { Authorization } from "../middleware/authorization"
-import { InstanceContextMiddleware } from "../middleware/instance-context"
-import { WorkspaceRoutingMiddleware, WorkspaceRoutingQuery } from "../middleware/workspace-routing"
-import { described } from "./metadata"
-
-export const ReferenceDescriptor = Schema.Union([
-  Schema.Struct({
-    name: Schema.String,
-    kind: Schema.Literal("local"),
-    path: Schema.String,
-  }),
-  Schema.Struct({
-    name: Schema.String,
-    kind: Schema.Literal("git"),
-    repository: Schema.String,
-    path: Schema.String,
-    branch: Schema.optional(Schema.String),
-  }),
-  Schema.Struct({
-    name: Schema.String,
-    kind: Schema.Literal("invalid"),
-    repository: Schema.optional(Schema.String),
-    message: Schema.String,
-  }),
-]).annotate({ identifier: "ReferenceDescriptor" })
-
-export const ReferenceApi = HttpApi.make("reference")
-  .add(
-    HttpApiGroup.make("reference")
-      .add(
-        HttpApiEndpoint.get("list", "/reference", {
-          query: WorkspaceRoutingQuery,
-          success: described(Schema.Array(ReferenceDescriptor), "Resolved configured references"),
-        }).annotateMerge(
-          OpenApi.annotations({
-            identifier: "reference.list",
-            summary: "List configured references",
-            description: "List configured references resolved in the current workspace.",
-          }),
-        ),
-      )
-      .annotateMerge(
-        OpenApi.annotations({
-          title: "reference",
-          description: "Configured reference routes.",
-        }),
-      )
-      .middleware(InstanceContextMiddleware)
-      .middleware(WorkspaceRoutingMiddleware)
-      .middleware(Authorization),
-  )
-  .annotateMerge(
-    OpenApi.annotations({
-      title: "opencode experimental HttpApi",
-      version: "0.0.1",
-      description: "Experimental HttpApi surface for selected instance routes.",
-    }),
-  )

+ 0 - 27
packages/opencode/src/server/routes/instance/httpapi/handlers/reference.ts

@@ -1,27 +0,0 @@
-import { Reference } from "@/reference/reference"
-import { Effect } from "effect"
-import { HttpApiBuilder } from "effect/unstable/httpapi"
-import { InstanceHttpApi } from "../api"
-
-export const referenceHandlers = HttpApiBuilder.group(InstanceHttpApi, "reference", (handlers) =>
-  Effect.gen(function* () {
-    const reference = yield* Reference.Service
-
-    return handlers.handle("list", () =>
-      reference.list().pipe(
-        Effect.map((references) =>
-          references.map((item) => {
-            if (item.kind !== "git") return item
-            return {
-              name: item.name,
-              kind: item.kind,
-              repository: item.repository,
-              path: item.path,
-              ...(item.branch !== undefined ? { branch: item.branch } : {}),
-            }
-          }),
-        ),
-      ),
-    )
-  }),
-)

+ 0 - 4
packages/opencode/src/server/routes/instance/httpapi/server.ts

@@ -35,7 +35,6 @@ import { ModelsDev } from "@opencode-ai/core/models-dev"
 import { Provider } from "@/provider/provider"
 import { PtyTicket } from "@opencode-ai/core/pty/ticket"
 import { Question } from "@/question"
-import { Reference } from "@/reference/reference"
 import { Session } from "@/session/session"
 import { SessionCompaction } from "@/session/compaction"
 import { LLM } from "@/session/llm"
@@ -86,7 +85,6 @@ import { projectCopyHandlers } from "./handlers/project-copy"
 import { providerHandlers } from "./handlers/provider"
 import { ptyConnectHandlers, ptyHandlers } from "./handlers/pty"
 import { questionHandlers } from "./handlers/question"
-import { referenceHandlers } from "./handlers/reference"
 import { sessionHandlers } from "./handlers/session"
 import { syncHandlers } from "./handlers/sync"
 import { tuiHandlers } from "./handlers/tui"
@@ -149,7 +147,6 @@ const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
     projectCopyHandlers,
     ptyHandlers,
     questionHandlers,
-    referenceHandlers,
     permissionHandlers,
     providerHandlers,
     sessionHandlers,
@@ -237,7 +234,6 @@ export function createRoutes(
       Provider.defaultLayer,
       PtyTicket.defaultLayer,
       Question.defaultLayer,
-      Reference.defaultLayer,
       Ripgrep.defaultLayer,
       RuntimeFlags.defaultLayer,
       Session.defaultLayer,

+ 2 - 83
packages/opencode/src/session/prompt.ts

@@ -52,12 +52,10 @@ import { SessionEvent } from "@opencode-ai/core/session/event"
 import { SessionMessage } from "@opencode-ai/core/session/message"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { ProviderV2 } from "@opencode-ai/core/provider"
-import { AgentAttachment, FileAttachment, Prompt, ReferenceAttachment, Source } from "@opencode-ai/core/session/prompt"
-import { Reference } from "@/reference/reference"
+import { AgentAttachment, FileAttachment, Prompt, Source } from "@opencode-ai/core/session/prompt"
 import * as DateTime from "effect/DateTime"
 import { eq } from "drizzle-orm"
 import { SessionTable } from "@opencode-ai/core/session/sql"
-import { referencePromptMetadata, referenceTextPart } from "./prompt/reference"
 import { SessionReminders } from "./reminders"
 import { SessionTools } from "./tools"
 import { LLMEvent } from "@opencode-ai/llm"
@@ -122,7 +120,6 @@ export const layer = Layer.effect(
     const summary = yield* SessionSummary.Service
     const sys = yield* SystemPrompt.Service
     const llm = yield* LLM.Service
-    const references = yield* Reference.Service
     const events = yield* EventV2Bridge.Service
     const flags = yield* RuntimeFlags.Service
     const database = yield* Database.Service
@@ -140,46 +137,10 @@ export const layer = Layer.effect(
       yield* state.cancel(sessionID)
     })
 
-    const resolveReferenceParts = Effect.fnUntraced(function* (template: string) {
-      const parts: Types.DeepMutable<PromptInput["parts"]> = []
-      const seen = new Set<string>()
-      yield* Effect.forEach(
-        ConfigMarkdown.files(template),
-        Effect.fnUntraced(function* (match) {
-          const name = match[1]
-          if (!name) return
-          const alias = name.split("/")[0]
-          if (!alias || seen.has(alias)) return
-          const reference = yield* references.get(alias)
-          if (!reference) return
-          seen.add(alias)
-
-          const start = match.index ?? 0
-          const source = { value: match[0], start, end: start + match[0].length }
-          if (reference.kind === "invalid") {
-            parts.push(referenceTextPart({ reference, source }))
-            return
-          }
-
-          yield* references.ensure(reference.path)
-          parts.push({
-            type: "file",
-            url: pathToFileURL(reference.path).href,
-            filename: alias,
-            mime: "application/x-directory",
-            source: { type: "file", text: source, path: alias },
-          })
-        }),
-        { concurrency: 1, discard: true },
-      )
-      return parts
-    })
-
     const resolvePromptParts = Effect.fn("SessionPrompt.resolvePromptParts")(function* (template: string) {
       const ctx = yield* InstanceState.context
       const parts: Types.DeepMutable<PromptInput["parts"]> = [
         { type: "text", text: template },
-        ...(yield* resolveReferenceParts(template)),
       ]
       const files = ConfigMarkdown.files(template)
       const seen = new Set<string>()
@@ -191,10 +152,6 @@ export const layer = Layer.effect(
           if (seen.has(name)) return
           seen.add(name)
 
-          const slash = name.indexOf("/")
-          const alias = slash === -1 ? name : name.slice(0, slash)
-          if (yield* references.get(alias)) return
-
           const filepath = name.startsWith("~/")
             ? path.join(os.homedir(), name.slice(2))
             : path.resolve(ctx.worktree, name)
@@ -1019,22 +976,7 @@ export const layer = Layer.effect(
         return [{ ...part, messageID: info.id, sessionID: input.sessionID }]
       })
 
-      const submittedParts: Types.DeepMutable<PromptInput["parts"]> = [...input.parts]
-      const attachedReferences = new Set(
-        input.parts.flatMap((part) =>
-          part.type === "file" && part.mime === "application/x-directory" ? [part.url] : [],
-        ),
-      )
-      for (const part of input.parts) {
-        if (part.type !== "text" || part.synthetic) continue
-        for (const reference of yield* resolveReferenceParts(part.text)) {
-          if (reference.type === "file" && attachedReferences.has(reference.url)) continue
-          if (reference.type === "file") attachedReferences.add(reference.url)
-          submittedParts.push(reference)
-        }
-      }
-
-      const resolvedParts = yield* Effect.forEach(submittedParts, resolvePart, { concurrency: "unbounded" }).pipe(
+      const resolvedParts = yield* Effect.forEach(input.parts, resolvePart, { concurrency: "unbounded" }).pipe(
         Effect.map((x) => x.flat().map(assign)),
       )
 
@@ -1092,26 +1034,6 @@ export const layer = Layer.effect(
           if (part.type === "text") {
             if (part.synthetic) result.synthetic.push(part.text)
             else result.text.push(part.text)
-            const reference = referencePromptMetadata(part.metadata?.reference)
-            if (reference) {
-              result.references.push(
-                new ReferenceAttachment({
-                  name: reference.name,
-                  kind: reference.kind,
-                  uri: reference.path ? pathToFileURL(reference.path).href : undefined,
-                  repository: reference.repository,
-                  branch: reference.branch,
-                  target: reference.target,
-                  targetUri: reference.targetPath ? pathToFileURL(reference.targetPath).href : undefined,
-                  problem: reference.problem,
-                  source: new Source({
-                    start: reference.source.start,
-                    end: reference.source.end,
-                    text: reference.source.value,
-                  }),
-                }),
-              )
-            }
           }
           if (part.type === "file") {
             result.files.push(
@@ -1149,7 +1071,6 @@ export const layer = Layer.effect(
           text: [] as string[],
           files: [] as FileAttachment[],
           agents: [] as AgentAttachment[],
-          references: [] as ReferenceAttachment[],
           synthetic: [] as string[],
         },
       )
@@ -1164,7 +1085,6 @@ export const layer = Layer.effect(
             text: nextPrompt.text.join("\n"),
             files: nextPrompt.files,
             agents: nextPrompt.agents,
-            references: nextPrompt.references,
           }),
         })
       }
@@ -1642,7 +1562,6 @@ export const defaultLayer = Layer.suspend(() =>
         Database.defaultLayer,
         SystemPrompt.defaultLayer,
         LLM.defaultLayer,
-        Reference.defaultLayer,
         CrossSpawnSpawner.defaultLayer,
         RuntimeFlags.defaultLayer,
         EventV2Bridge.defaultLayer,

+ 0 - 72
packages/opencode/src/session/prompt/reference.ts

@@ -1,72 +0,0 @@
-import { Option, Schema } from "effect"
-import { SessionV1 } from "@opencode-ai/core/v1/session"
-import { MessageV2 } from "../message-v2"
-import { Reference } from "@/reference/reference"
-
-const Source = Schema.Struct({
-  value: Schema.String,
-  start: Schema.Number,
-  end: Schema.Number,
-})
-
-export const ReferencePromptMetadata = Schema.Struct({
-  name: Schema.String,
-  kind: Schema.Literals(["local", "git", "invalid"]),
-  path: Schema.optional(Schema.String),
-  repository: Schema.optional(Schema.String),
-  branch: Schema.optional(Schema.String),
-  target: Schema.optional(Schema.String),
-  targetPath: Schema.optional(Schema.String),
-  problem: Schema.optional(Schema.String),
-  source: Source,
-})
-export type ReferencePromptMetadata = typeof ReferencePromptMetadata.Type
-
-const decodeReferencePromptMetadata = Schema.decodeUnknownOption(ReferencePromptMetadata)
-
-export function referencePromptMetadata(input: unknown) {
-  return Option.getOrUndefined(decodeReferencePromptMetadata(input))
-}
-
-export function referenceTextPart(input: {
-  reference: Reference.Resolved
-  source: ReferencePromptMetadata["source"]
-  target?: string
-  targetPath?: string
-  problem?: string
-}): SessionV1.TextPartInput {
-  const metadata: ReferencePromptMetadata = {
-    name: input.reference.name,
-    kind: input.reference.kind,
-    ...(input.reference.kind === "invalid"
-      ? { repository: input.reference.repository }
-      : { path: input.reference.path }),
-    ...(input.reference.kind === "git"
-      ? { repository: input.reference.repository, branch: input.reference.branch }
-      : {}),
-    ...(input.target === undefined ? {} : { target: input.target }),
-    ...(input.targetPath ? { targetPath: input.targetPath } : {}),
-    problem: input.problem ?? (input.reference.kind === "invalid" ? input.reference.message : undefined),
-    source: input.source,
-  }
-  const label = metadata.target === undefined ? `@${metadata.name}` : `@${metadata.name}/${metadata.target}`
-  return {
-    type: "text",
-    synthetic: true,
-    text: [
-      `Referenced configured reference ${label}.`,
-      ...(metadata.kind === "local" ? ["Kind: local directory"] : []),
-      ...(metadata.kind === "git" ? ["Kind: git repository"] : []),
-      ...(metadata.repository ? [`Repository: ${metadata.repository}`] : []),
-      ...(metadata.branch ? [`Branch/ref: ${metadata.branch}`] : []),
-      ...(metadata.path ? [`Reference root: ${metadata.path}`] : []),
-      ...(metadata.targetPath ? [`Resolved path: ${metadata.targetPath}`] : []),
-      ...(metadata.problem
-        ? [`Problem: ${metadata.problem}`]
-        : ["Inspect the configured reference with Read, Glob, and Grep when useful."]),
-    ].join("\n"),
-    metadata: { reference: metadata },
-  }
-}
-
-export * as ReferencePrompt from "./reference"

+ 1 - 4
packages/opencode/src/tool/glob.ts

@@ -6,7 +6,6 @@ import { Search } from "@opencode-ai/core/filesystem/search"
 import { assertExternalDirectoryEffect } from "./external-directory"
 import DESCRIPTION from "./glob.txt"
 import * as Tool from "./tool"
-import { Reference } from "@/reference/reference"
 
 export const Parameters = Schema.Struct({
   pattern: Schema.String.annotate({ description: "The glob pattern to match files against" }),
@@ -19,7 +18,6 @@ export const GlobTool = Tool.define(
   "glob",
   Effect.gen(function* () {
     const fs = yield* FSUtil.Service
-    const reference = yield* Reference.Service
     const searchSvc = yield* Search.Service
 
     return {
@@ -40,13 +38,12 @@ export const GlobTool = Tool.define(
 
           let search = params.path ?? ins.directory
           search = path.isAbsolute(search) ? search : path.resolve(ins.directory, search)
-          yield* reference.ensure(search)
           const info = yield* fs.stat(search).pipe(Effect.catch(() => Effect.succeed(undefined)))
           if (info?.type === "File") {
             throw new Error(`glob path must be a directory: ${search}`)
           }
           yield* assertExternalDirectoryEffect(ctx, search, {
-            bypass: yield* reference.contains(search),
+            bypass: false,
             kind: "directory",
           })
 

+ 1 - 4
packages/opencode/src/tool/grep.ts

@@ -6,7 +6,6 @@ import { Search } from "@opencode-ai/core/filesystem/search"
 import { assertExternalDirectoryEffect } from "./external-directory"
 import DESCRIPTION from "./grep.txt"
 import * as Tool from "./tool"
-import { Reference } from "@/reference/reference"
 
 const MAX_LINE_LENGTH = 2000
 
@@ -25,7 +24,6 @@ export const GrepTool = Tool.define(
   Effect.gen(function* () {
     const fs = yield* FSUtil.Service
     const searchSvc = yield* Search.Service
-    const reference = yield* Reference.Service
 
     return {
       description: DESCRIPTION,
@@ -56,10 +54,9 @@ export const GrepTool = Tool.define(
           const requested = path.isAbsolute(params.path ?? ins.directory)
             ? (params.path ?? ins.directory)
             : path.join(ins.directory, params.path ?? ".")
-          yield* reference.ensure(requested)
           const requestedInfo = yield* fs.stat(requested).pipe(Effect.catch(() => Effect.succeed(undefined)))
           yield* assertExternalDirectoryEffect(ctx, requested, {
-            bypass: yield* reference.contains(requested),
+            bypass: false,
             kind: requestedInfo?.type === "Directory" ? "directory" : "file",
           })
 

+ 2 - 5
packages/opencode/src/tool/read.ts

@@ -10,7 +10,6 @@ import { assertExternalDirectoryEffect } from "./external-directory"
 import { Instruction } from "../session/instruction"
 import { Search } from "@opencode-ai/core/filesystem/search"
 import { isPdfAttachment, sniffAttachmentMime } from "@/util/media"
-import { Reference } from "@/reference/reference"
 
 const DEFAULT_READ_LIMIT = 2000
 const MAX_LINE_LENGTH = 2000
@@ -66,14 +65,13 @@ type Metadata = {
 export const ReadTool = Tool.define<
   typeof Parameters,
   Metadata,
-  FSUtil.Service | Instruction.Service | LSP.Service | Reference.Service | Search.Service | Scope.Scope
+  FSUtil.Service | Instruction.Service | LSP.Service | Search.Service | Scope.Scope
 >(
   "read",
   Effect.gen(function* () {
     const fs = yield* FSUtil.Service
     const instruction = yield* Instruction.Service
     const lsp = yield* LSP.Service
-    const reference = yield* Reference.Service
     const search = yield* Search.Service
     const scope = yield* Scope.Scope
 
@@ -243,7 +241,6 @@ export const ReadTool = Tool.define<
       if (process.platform === "win32") {
         filepath = FSUtil.normalizePath(filepath)
       }
-      yield* reference.ensure(filepath)
       const title = path.relative(instance.worktree, filepath)
 
       const stat = yield* fs.stat(filepath).pipe(
@@ -254,7 +251,7 @@ export const ReadTool = Tool.define<
       )
 
       yield* assertExternalDirectoryEffect(ctx, filepath, {
-        bypass: Boolean(ctx.extra?.["bypassCwdCheck"]) || (yield* reference.contains(filepath)),
+        bypass: Boolean(ctx.extra?.["bypassCwdCheck"]),
         kind: stat?.type === "Directory" ? "directory" : "file",
       })
 

+ 0 - 3
packages/opencode/src/tool/registry.ts

@@ -46,7 +46,6 @@ import { EventV2Bridge } from "@/event-v2-bridge"
 import { Agent } from "../agent/agent"
 import { Skill } from "../skill"
 import { Permission } from "@/permission"
-import { Reference } from "@/reference/reference"
 import { BackgroundJob } from "@/background/job"
 import { RuntimeFlags } from "@/effect/runtime-flags"
 import { ProviderV2 } from "@opencode-ai/core/provider"
@@ -91,7 +90,6 @@ export const layer: Layer.Layer<
   | Session.Service
   | BackgroundJob.Service
   | Provider.Service
-  | Reference.Service
   | LSP.Service
   | Instruction.Service
   | FSUtil.Service
@@ -350,7 +348,6 @@ export const defaultLayer = Layer.suspend(() =>
       Layer.provide(Session.defaultLayer),
       Layer.provide(BackgroundJob.defaultLayer),
       Layer.provide(Provider.defaultLayer),
-      Layer.provide(Reference.defaultLayer),
       Layer.provide(LSP.defaultLayer),
       Layer.provide(Instruction.defaultLayer),
       Layer.provide(FSUtil.defaultLayer),

+ 0 - 310
packages/opencode/test/reference/reference.test.ts

@@ -1,310 +0,0 @@
-import { afterEach, describe, expect } from "bun:test"
-import path from "path"
-import { Effect, Layer } from "effect"
-import { FSUtil } from "@opencode-ai/core/fs-util"
-import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
-import { Global } from "@opencode-ai/core/global"
-import { Config } from "../../src/config/config"
-import { ConfigReference } from "../../src/config/reference"
-import { RuntimeFlags } from "../../src/effect/runtime-flags"
-import { Git } from "../../src/git"
-import { Reference } from "../../src/reference/reference"
-import { RepositoryCache } from "../../src/reference/repository-cache"
-import { disposeAllInstances, provideTmpdirInstance, tmpdirScoped } from "../fixture/fixture"
-import { testEffect } from "../lib/effect"
-
-afterEach(async () => {
-  await disposeAllInstances()
-})
-
-const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
-  Reference.layer.pipe(
-    Layer.provide(Config.defaultLayer),
-    Layer.provide(RepositoryCache.defaultLayer),
-    Layer.provide(RuntimeFlags.layer(flags)),
-  )
-
-const it = testEffect(
-  Layer.mergeAll(FSUtil.defaultLayer, CrossSpawnSpawner.defaultLayer, Git.defaultLayer, referenceLayer()),
-)
-const references = testEffect(
-  Layer.mergeAll(
-    FSUtil.defaultLayer,
-    CrossSpawnSpawner.defaultLayer,
-    Git.defaultLayer,
-    referenceLayer({ experimentalReferences: true }),
-  ),
-)
-
-const githubBase = <A, E, R>(url: string, self: Effect.Effect<A, E, R>) =>
-  Effect.acquireUseRelease(
-    Effect.sync(() => {
-      const previous = process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL
-      process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = url
-      return previous
-    }),
-    () => self,
-    (previous) =>
-      Effect.sync(() => {
-        if (previous) process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = previous
-        else delete process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL
-      }),
-  )
-
-const git = Effect.fn("ReferenceTest.git")(function* (cwd: string, args: string[]) {
-  return yield* Effect.promise(async () => {
-    const proc = Bun.spawn(["git", ...args], {
-      cwd,
-      stdout: "pipe",
-      stderr: "pipe",
-    })
-    const [stdout, stderr, code] = await Promise.all([
-      new Response(proc.stdout).text(),
-      new Response(proc.stderr).text(),
-      proc.exited,
-    ])
-    if (code !== 0) throw new Error(stderr.trim() || stdout.trim() || `git ${args.join(" ")} failed`)
-    return stdout.trim()
-  })
-})
-
-const waitForContent = (
-  fs: FSUtil.Interface,
-  file: string,
-  content: string,
-  attempts = 50,
-): Effect.Effect<void, FSUtil.Error> =>
-  Effect.gen(function* () {
-    if ((yield* fs.readFileStringSafe(file)) === content) return
-    if (attempts <= 0) throw new Error(`timed out waiting for ${file}`)
-    yield* Effect.sleep("100 millis")
-    yield* waitForContent(fs, file, content, attempts - 1)
-  })
-
-describe("reference", () => {
-  it.live("resolves supported local and git config forms", () =>
-    Effect.gen(function* () {
-      const root = path.resolve("opencode-reference-root")
-      const local = Reference.resolve({
-        name: "docs",
-        reference: ConfigReference.normalizeEntry({ path: "../docs" }),
-        directory: path.join(root, "packages", "app"),
-        worktree: root,
-      })
-      const repo = Reference.resolve({
-        name: "effect",
-        reference: ConfigReference.normalizeEntry({ repository: "Effect-TS/effect", branch: "main" }),
-        directory: path.join(root, "packages", "app"),
-        worktree: root,
-      })
-      const localString = Reference.resolve({
-        name: "notes",
-        reference: ConfigReference.normalizeEntry("./notes"),
-        directory: path.join(root, "packages", "app"),
-        worktree: root,
-      })
-      const repoString = Reference.resolve({
-        name: "repo",
-        reference: ConfigReference.normalizeEntry("owner/repo"),
-        directory: path.join(root, "packages", "app"),
-        worktree: root,
-      })
-
-      expect(local.kind).toBe("local")
-      if (local.kind === "local") expect(local.path).toBe(path.resolve(root, "../docs"))
-      expect(localString.kind).toBe("local")
-      if (localString.kind === "local") expect(localString.path).toBe(path.resolve(root, "notes"))
-      expect(repo.kind).toBe("git")
-      if (repo.kind === "git") {
-        expect(repo.repository).toBe("Effect-TS/effect")
-        expect(repo.branch).toBe("main")
-        expect(repo.path).toBe(path.join(Global.Path.repos, "github.com", "Effect-TS", "effect"))
-      }
-      expect(repoString.kind).toBe("git")
-      if (repoString.kind === "git") {
-        expect(repoString.repository).toBe("owner/repo")
-        expect(repoString.path).toBe(path.join(Global.Path.repos, "github.com", "owner", "repo"))
-      }
-    }),
-  )
-
-  it.live("keeps invalid repository references visible without materializing", () =>
-    provideTmpdirInstance(
-      (_dir) =>
-        Effect.gen(function* () {
-          const reference = yield* Reference.Service
-          const references = yield* reference.list()
-          const invalid = yield* reference.get("bad")
-
-          expect(references.map((item) => item.name)).toEqual(["bad"])
-          expect(invalid).toMatchObject({
-            name: "bad",
-            kind: "invalid",
-            repository: "not-a-repo",
-          })
-          if (invalid?.kind === "invalid") expect(invalid.message).toContain("Repository must be a git URL")
-        }),
-      {
-        config: {
-          reference: {
-            bad: "not-a-repo",
-          },
-        },
-      },
-    ),
-  )
-
-  it.live("marks same-cache references with different branches invalid", () =>
-    Effect.gen(function* () {
-      const root = path.resolve("opencode-reference-root")
-      const references = Reference.resolveAll({
-        directory: root,
-        worktree: root,
-        references: ConfigReference.normalize({
-          main: { repository: "owner/repo", branch: "main" },
-          dev: { repository: "github.com/owner/repo", branch: "dev" },
-          alsoMain: { repository: "https://github.com/owner/repo", branch: "main" },
-        }),
-      })
-
-      expect(references.map((reference) => reference.kind)).toEqual(["git", "invalid", "git"])
-      expect(references[1]?.kind).toBe("invalid")
-      if (references[1]?.kind === "invalid") {
-        expect(references[1].message).toContain("conflicts with @main")
-        expect(references[1].message).toContain("@dev requests dev")
-      }
-    }),
-  )
-
-  it.live("represents invalid aliases as invalid references", () =>
-    Effect.gen(function* () {
-      const root = path.resolve("opencode-reference-root")
-      const references = Reference.resolveAll({
-        directory: root,
-        worktree: root,
-        references: ConfigReference.normalize({
-          "bad/name": "owner/repo",
-        }),
-      })
-
-      expect(references).toEqual([
-        {
-          name: "bad/name",
-          kind: "invalid",
-          message: "Reference alias must not contain /, whitespace, comma, or backtick",
-        },
-      ])
-    }),
-  )
-
-  references.live("materializes configured git references during init", () =>
-    provideTmpdirInstance(
-      (_dir) =>
-        Effect.gen(function* () {
-          const fs = yield* FSUtil.Service
-          const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-test", "repo")
-          yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
-          yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
-
-          const source = yield* tmpdirScoped({ git: true })
-          const remoteRoot = yield* tmpdirScoped()
-          const remoteDir = path.join(remoteRoot, "opencode-reference-test")
-          const remoteRepo = path.join(remoteDir, "repo.git")
-
-          yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "configured\n"))
-          yield* git(source, ["add", "."])
-          yield* git(source, ["commit", "-m", "add readme"])
-          yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-          yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-          const reference = yield* Reference.Service
-          yield* githubBase(
-            `file://${remoteRoot}/`,
-            Effect.gen(function* () {
-              yield* reference.init()
-              yield* waitForContent(fs, path.join(cache, "README.md"), "configured\n")
-            }),
-          )
-
-          expect(yield* fs.existsSafe(path.join(cache, ".git"))).toBe(true)
-          expect(yield* fs.readFileString(path.join(cache, "README.md"))).toBe("configured\n")
-
-          const resolved = yield* reference.get("docs")
-          expect(resolved?.kind).toBe("git")
-          if (resolved?.kind === "git") expect(resolved.path).toBe(cache)
-        }),
-      {
-        config: {
-          reference: {
-            docs: "opencode-reference-test/repo",
-          },
-        },
-      },
-    ),
-  )
-
-  references.live("refreshes configured git references on new instance init", () =>
-    Effect.gen(function* () {
-      const fs = yield* FSUtil.Service
-      const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-refresh", "repo")
-      yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
-      yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
-
-      const source = yield* tmpdirScoped({ git: true })
-      const remoteRoot = yield* tmpdirScoped()
-      const remoteDir = path.join(remoteRoot, "opencode-reference-refresh")
-      const remoteRepo = path.join(remoteDir, "repo.git")
-
-      yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v1\n"))
-      yield* git(source, ["add", "."])
-      yield* git(source, ["commit", "-m", "add readme"])
-      yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-      yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-      yield* githubBase(
-        `file://${remoteRoot}/`,
-        provideTmpdirInstance(
-          (_dir) =>
-            Effect.gen(function* () {
-              const reference = yield* Reference.Service
-              yield* reference.init()
-              yield* waitForContent(fs, path.join(cache, "README.md"), "v1\n")
-            }),
-          {
-            config: {
-              reference: {
-                docs: "opencode-reference-refresh/repo",
-              },
-            },
-          },
-        ),
-      )
-
-      const branch = yield* git(source, ["branch", "--show-current"])
-      yield* git(source, ["remote", "add", "origin", remoteRepo])
-      yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v2\n"))
-      yield* git(source, ["add", "."])
-      yield* git(source, ["commit", "-m", "update readme"])
-      yield* git(source, ["push", "origin", `${branch}:${branch}`])
-
-      yield* githubBase(
-        `file://${remoteRoot}/`,
-        provideTmpdirInstance(
-          (_dir) =>
-            Effect.gen(function* () {
-              const reference = yield* Reference.Service
-              yield* reference.init()
-              yield* waitForContent(fs, path.join(cache, "README.md"), "v2\n")
-            }),
-          {
-            config: {
-              reference: {
-                docs: "opencode-reference-refresh/repo",
-              },
-            },
-          },
-        ),
-      )
-    }),
-  )
-})

+ 1 - 1
packages/opencode/test/server/httpapi-exercise/index.ts

@@ -670,7 +670,7 @@ const scenarios: Scenario[] = [
     .at((ctx) => ({ path: "/api/fs/read?path=hello.txt", headers: ctx.headers() }))
     .json(200, locationData(object)),
   http.protected.get("/api/fs/list", "v2.fs.list").json(200, locationData(array)),
-  http.protected.get("/reference", "reference.list").json(200, array),
+  http.protected.get("/api/reference", "v2.reference.list").json(200, object),
   http.protected
     .get("/api/provider/{providerID}", "v2.provider.get")
     .at((ctx) => ({ path: route("/api/provider/{providerID}", { providerID: "missing" }), headers: ctx.headers() }))

+ 3 - 7
packages/opencode/test/server/httpapi-public-openapi.test.ts

@@ -94,17 +94,13 @@ describe("PublicApi OpenAPI v2 errors", () => {
     }
   })
 
-  test("documents optional project reference aliases for filesystem reads and lists", () => {
+  test("documents references separately from filesystem routes", () => {
     const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
 
     for (const path of ["/api/fs/read", "/api/fs/list"]) {
-      expect(spec.paths[path]?.get?.parameters, path).toContainEqual({
-        in: "query",
-        name: "reference",
-        required: false,
-        schema: { type: "string" },
-      })
+      expect(spec.paths[path]?.get?.parameters, path).not.toContainEqual(expect.objectContaining({ name: "reference" }))
     }
+    expect(spec.paths["/api/reference"]?.get).toBeDefined()
   })
 
   test("preserves required request bodies for v2 mutations", () => {

+ 23 - 22
packages/opencode/test/server/httpapi-reference.test.ts

@@ -11,7 +11,7 @@ afterEach(async () => {
 })
 
 describe("reference HttpApi", () => {
-  test("lists presentation-safe references resolved in the server workspace", async () => {
+  test("lists usable references resolved in the server workspace", async () => {
     await using tmp = await tmpdir({
       config: {
         formatter: false,
@@ -24,30 +24,31 @@ describe("reference HttpApi", () => {
       },
     })
 
-    const response = await Server.Default().app.request("/reference", {
+    const response = await Server.Default().app.request("/api/reference", {
       headers: { "x-opencode-directory": tmp.path },
     })
 
     expect(response.status).toBe(200)
-    expect(await response.json()).toEqual([
-      {
-        name: "docs",
-        kind: "local",
-        path: path.join(tmp.path, "docs"),
-      },
-      {
-        name: "effect",
-        kind: "git",
-        repository: "Effect-TS/effect",
-        path: path.join(Global.Path.repos, "github.com", "Effect-TS", "effect"),
-        branch: "main",
-      },
-      {
-        name: "bad",
-        kind: "invalid",
-        repository: "not-a-repo",
-        message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
-      },
-    ])
+    const body = await response.json()
+    expect(body).toMatchObject({ location: { directory: tmp.path } })
+    expect(body.data).toEqual([
+        {
+          name: "docs",
+          path: path.join(tmp.path, "docs"),
+          source: {
+            type: "local",
+            path: path.join(tmp.path, "docs"),
+          },
+        },
+        {
+          name: "effect",
+          path: path.join(Global.Path.repos, "github.com", "Effect-TS", "effect"),
+          source: {
+            type: "git",
+            repository: "Effect-TS/effect",
+            branch: "main",
+          },
+        },
+      ])
   })
 })

+ 0 - 91
packages/opencode/test/session/prompt.test.ts

@@ -50,8 +50,6 @@ import { Truncate } from "@/tool/truncate"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { Search } from "@opencode-ai/core/filesystem/search"
 import { Format } from "../../src/format"
-import { Reference } from "../../src/reference/reference"
-import { RepositoryCache } from "../../src/reference/repository-cache"
 import { TestInstance } from "../fixture/fixture"
 import { awaitWithTimeout, pollWithTimeout, testEffect } from "../lib/effect"
 import { reply, TestLLMServer } from "../lib/llm-server"
@@ -191,9 +189,7 @@ function makePrompt(input?: { processor?: "blocking" }) {
     Layer.provide(Skill.defaultLayer),
     Layer.provide(FetchHttpClient.layer),
     Layer.provide(CrossSpawnSpawner.defaultLayer),
-    Layer.provide(RepositoryCache.defaultLayer),
     Layer.provide(Git.defaultLayer),
-    Layer.provide(Reference.defaultLayer),
     Layer.provide(Search.defaultLayer),
     Layer.provide(Format.defaultLayer),
     Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
@@ -219,7 +215,6 @@ function makePrompt(input?: { processor?: "blocking" }) {
   return SessionPrompt.layer.pipe(
     Layer.provide(SessionRevert.defaultLayer),
     Layer.provide(Image.defaultLayer),
-    Layer.provide(Reference.defaultLayer),
     Layer.provide(summary),
     Layer.provideMerge(run),
     Layer.provideMerge(compact),
@@ -2021,92 +2016,6 @@ noLLMServer.instance(
   { config: cfg },
 )
 
-noLLMServer.instance(
-  "resolves configured reference mentions to one root directory attachment",
-  () =>
-    Effect.gen(function* () {
-      const { directory: dir } = yield* TestInstance
-      const docs = path.join(dir, "external-docs")
-      yield* ensureDir(path.join(docs, "guide"))
-      yield* ensureDir(path.join(dir, "docs"))
-      yield* writeText(path.join(docs, "README.md"), "reference readme")
-      yield* writeText(path.join(docs, "guide", "intro.md"), "reference intro")
-      yield* writeText(path.join(dir, "docs", "README.md"), "workspace readme")
-
-      const prompt = yield* SessionPrompt.Service
-      const parts = yield* prompt.resolvePromptParts(
-        "Use @docs and @docs/README.md and @docs/guide and @docs/missing.md and @docs/README.md and @build",
-      )
-      const files = parts.filter((part): part is SessionV1.FilePartInput => part.type === "file")
-      const agents = parts.filter((part): part is SessionV1.AgentPartInput => part.type === "agent")
-      const text = parts.find((part): part is SessionV1.TextPartInput => part.type === "text" && !part.synthetic)
-
-      expect(text?.text).toContain("@docs")
-      expect(files).toHaveLength(1)
-      expect(files[0]).toMatchObject({
-        filename: "docs",
-        mime: "application/x-directory",
-        source: { type: "file", path: "docs", text: { value: "@docs" } },
-      })
-      expect(fileURLToPath(files[0].url)).toBe(docs)
-      expect(agents.map((agent) => agent.name)).toEqual(["build"])
-    }),
-  {
-    config: {
-      ...cfg,
-      reference: {
-        docs: "./external-docs",
-      },
-    },
-  },
-)
-
-noLLMServer.instance(
-  "stores raw reference mentions alongside directory attachments",
-  () =>
-    Effect.gen(function* () {
-      const { directory: dir } = yield* TestInstance
-      const docs = path.join(dir, "external-docs")
-      yield* ensureDir(docs)
-
-      const prompt = yield* SessionPrompt.Service
-      const sessions = yield* Session.Service
-      const session = yield* sessions.create({})
-      const message = yield* prompt.prompt({
-        sessionID: session.id,
-        noReply: true,
-        parts: [{ type: "text", text: "Use @docs for context" }],
-      })
-
-      const stored = yield* MessageV2.get({ sessionID: session.id, messageID: message.info.id })
-      const synthetic = stored.parts.filter(
-        (part): part is SessionV1.TextPart => part.type === "text" && part.synthetic === true,
-      )
-      const files = stored.parts.filter((part): part is SessionV1.FilePart => part.type === "file")
-      const text = stored.parts.find((part): part is SessionV1.TextPart => part.type === "text" && !part.synthetic)
-
-      expect(text?.text).toBe("Use @docs for context")
-      expect(synthetic.some((part) => part.text.includes(JSON.stringify({ filePath: docs })))).toBe(true)
-      expect(files).toHaveLength(1)
-      expect(files[0]).toMatchObject({
-        filename: "docs",
-        mime: "application/x-directory",
-        source: { type: "file", path: "docs", text: { value: "@docs", start: 4, end: 9 } },
-      })
-      expect(fileURLToPath(files[0].url)).toBe(docs)
-
-      yield* sessions.remove(session.id)
-    }),
-  {
-    config: {
-      ...cfg,
-      reference: {
-        docs: "./external-docs",
-      },
-    },
-  },
-)
-
 // Special characters in filenames
 
 noLLMServer.instance(

+ 0 - 5
packages/opencode/test/session/snapshot-tool-race.test.ts

@@ -59,8 +59,6 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { Search } from "@opencode-ai/core/filesystem/search"
 import { Format } from "../../src/format"
-import { Reference } from "../../src/reference/reference"
-import { RepositoryCache } from "../../src/reference/repository-cache"
 import { RuntimeFlags } from "@/effect/runtime-flags"
 
 const mcp = Layer.succeed(
@@ -136,9 +134,7 @@ function makeHttp() {
     Layer.provide(Skill.defaultLayer),
     Layer.provide(FetchHttpClient.layer),
     Layer.provide(CrossSpawnSpawner.defaultLayer),
-    Layer.provide(RepositoryCache.defaultLayer),
     Layer.provide(Git.defaultLayer),
-    Layer.provide(Reference.defaultLayer),
     Layer.provide(Search.defaultLayer),
     Layer.provide(Format.defaultLayer),
     Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
@@ -164,7 +160,6 @@ function makeHttp() {
     SessionPrompt.layer.pipe(
       Layer.provide(SessionRevert.defaultLayer),
       Layer.provide(Image.defaultLayer),
-      Layer.provide(Reference.defaultLayer),
       Layer.provide(SessionSummary.defaultLayer),
       Layer.provideMerge(run),
       Layer.provideMerge(compact),

+ 0 - 51
packages/opencode/test/tool/glob.test.ts

@@ -12,8 +12,6 @@ import { Truncate } from "@/tool/truncate"
 import { Agent } from "../../src/agent/agent"
 import { TestInstance, tmpdirScoped } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
-import { Reference } from "@/reference/reference"
-import { RepositoryCache } from "@/reference/repository-cache"
 import { Config } from "@/config/config"
 import { RuntimeFlags } from "@/effect/runtime-flags"
 import { Git } from "@/git"
@@ -21,13 +19,6 @@ import { Filesystem } from "@/util/filesystem"
 import { Permission } from "../../src/permission"
 import type * as Tool from "../../src/tool/tool"
 
-const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
-  Reference.layer.pipe(
-    Layer.provide(Config.defaultLayer),
-    Layer.provide(RepositoryCache.defaultLayer),
-    Layer.provide(RuntimeFlags.layer(flags)),
-  )
-
 const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
   Layer.mergeAll(
     CrossSpawnSpawner.defaultLayer,
@@ -36,11 +27,9 @@ const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
     Truncate.defaultLayer,
     Agent.defaultLayer,
     Git.defaultLayer,
-    referenceLayer(flags),
   )
 
 const it = testEffect(toolLayer())
-const references = testEffect(toolLayer({ experimentalReferences: true }))
 const full = (p: string) => (process.platform === "win32" ? Filesystem.normalizePath(p) : p)
 
 const ctx = {
@@ -145,44 +134,4 @@ describe("tool.glob", () => {
     }),
   )
 
-  references.instance(
-    "does not ask for external_directory permission inside configured git references",
-    () =>
-      Effect.gen(function* () {
-        yield* TestInstance
-        const fs = yield* FSUtil.Service
-        const cache = path.join(Global.Path.repos, "github.com", "opencode-glob-reference", "repo")
-        yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
-        yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
-
-        const source = yield* tmpdirScoped({ git: true })
-        const remoteRoot = yield* tmpdirScoped()
-        const remoteDir = path.join(remoteRoot, "opencode-glob-reference")
-        const remoteRepo = path.join(remoteDir, "repo.git")
-        yield* fs.writeWithDirs(path.join(source, "src", "index.ts"), "export const value = 1\n")
-        yield* git(source, ["add", "."])
-        yield* git(source, ["commit", "-m", "add source"])
-        yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-        yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-        const { items, next } = asks()
-        const info = yield* GlobTool
-        const glob = yield* info.init()
-        const result = yield* githubBase(
-          `file://${remoteRoot}/`,
-          glob.execute({ pattern: "*.ts", path: path.join(cache, "src") }, next),
-        )
-
-        expect(result.metadata.count).toBe(1)
-        expect(full(result.output)).toContain(full(path.join(cache, "src", "index.ts")))
-        expect(items.find((item) => item.permission === "external_directory")).toBeUndefined()
-      }),
-    {
-      config: {
-        reference: {
-          docs: "opencode-glob-reference/repo",
-        },
-      },
-    },
-  )
 })

+ 0 - 59
packages/opencode/test/tool/grep.test.ts

@@ -14,8 +14,6 @@ import { Agent } from "../../src/agent/agent"
 import { Search } from "@opencode-ai/core/filesystem/search"
 import { FSUtil } from "@opencode-ai/core/fs-util"
 import { testEffect } from "../lib/effect"
-import { Reference } from "@/reference/reference"
-import { RepositoryCache } from "@/reference/repository-cache"
 import { Permission } from "../../src/permission"
 import type * as Tool from "../../src/tool/tool"
 import { Config } from "@/config/config"
@@ -23,13 +21,6 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
 import { Git } from "@/git"
 import { Filesystem } from "@/util/filesystem"
 
-const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
-  Reference.layer.pipe(
-    Layer.provide(Config.defaultLayer),
-    Layer.provide(RepositoryCache.defaultLayer),
-    Layer.provide(RuntimeFlags.layer(flags)),
-  )
-
 const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
   Layer.mergeAll(
     CrossSpawnSpawner.defaultLayer,
@@ -38,11 +29,9 @@ const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
     Truncate.defaultLayer,
     Agent.defaultLayer,
     Git.defaultLayer,
-    referenceLayer(flags),
   )
 
 const it = testEffect(toolLayer())
-const references = testEffect(toolLayer({ experimentalReferences: true }))
 const rooted = testEffect(Layer.mergeAll(toolLayer(), testInstanceStoreLayer))
 
 const ctx = {
@@ -215,52 +204,4 @@ describe("tool.grep", () => {
     }),
   )
 
-  references.instance(
-    "does not ask for external_directory permission inside configured git references",
-    () =>
-      Effect.gen(function* () {
-        yield* TestInstance
-        const appfs = yield* FSUtil.Service
-        const cache = path.join(Global.Path.repos, "github.com", "opencode-grep-reference", "repo")
-        yield* appfs.remove(cache, { recursive: true }).pipe(Effect.ignore)
-        yield* Effect.addFinalizer(() => appfs.remove(cache, { recursive: true }).pipe(Effect.ignore))
-
-        const source = yield* tmpdirScoped({ git: true })
-        const remoteRoot = yield* tmpdirScoped()
-        const remoteDir = path.join(remoteRoot, "opencode-grep-reference")
-        const remoteRepo = path.join(remoteDir, "repo.git")
-        yield* appfs.writeWithDirs(path.join(source, "src", "notes.md"), "needle\n")
-        yield* git(source, ["add", "."])
-        yield* git(source, ["commit", "-m", "add notes"])
-        yield* appfs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-        yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-        const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
-        const next: Tool.Context = {
-          ...ctx,
-          ask: (req) =>
-            Effect.sync(() => {
-              requests.push(req)
-            }),
-        }
-
-        const info = yield* GrepTool
-        const grep = yield* info.init()
-        const result = yield* githubBase(
-          `file://${remoteRoot}/`,
-          grep.execute({ pattern: "needle", path: path.join(cache, "src"), include: "*.md" }, next),
-        )
-
-        expect(result.metadata.matches).toBe(1)
-        expect(full(result.output)).toContain(full(path.join(cache, "src", "notes.md")))
-        expect(requests.find((req) => req.permission === "external_directory")).toBeUndefined()
-      }),
-    {
-      config: {
-        reference: {
-          docs: "opencode-grep-reference/repo",
-        },
-      },
-    },
-  )
 })

+ 0 - 48
packages/opencode/test/tool/read.test.ts

@@ -25,8 +25,6 @@ import {
   tmpdirScoped,
 } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
-import { Reference } from "@/reference/reference"
-import { RepositoryCache } from "@/reference/repository-cache"
 
 const FIXTURES_DIR = path.join(import.meta.dir, "fixtures")
 
@@ -45,13 +43,6 @@ const ctx = {
   ask: () => Effect.void,
 }
 
-const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
-  Reference.layer.pipe(
-    Layer.provide(Config.defaultLayer),
-    Layer.provide(RepositoryCache.defaultLayer),
-    Layer.provide(RuntimeFlags.layer(flags)),
-  )
-
 const readLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
   Layer.mergeAll(
     Agent.defaultLayer,
@@ -59,13 +50,11 @@ const readLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
     CrossSpawnSpawner.defaultLayer,
     Instruction.defaultLayer,
     LSP.defaultLayer,
-    referenceLayer(flags),
     Search.defaultLayer,
     Truncate.defaultLayer,
   )
 
 const it = testEffect(Layer.mergeAll(readLayer(), testInstanceStoreLayer))
-const references = testEffect(Layer.mergeAll(readLayer({ experimentalReferences: true }), testInstanceStoreLayer))
 
 const init = Effect.fn("ReadToolTest.init")(function* () {
   const info = yield* ReadTool
@@ -266,43 +255,6 @@ describe("tool.read external_directory permission", () => {
     }),
   )
 
-  references.live("does not ask for external_directory permission when reading configured references", () =>
-    Effect.gen(function* () {
-      const fs = yield* FSUtil.Service
-      const cache = path.join(Global.Path.repos, "github.com", "opencode-read-reference", "repo")
-      yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
-      yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
-
-      const source = yield* tmpdirScoped({ git: true })
-      const remoteRoot = yield* tmpdirScoped()
-      const remoteDir = path.join(remoteRoot, "opencode-read-reference")
-      const remoteRepo = path.join(remoteDir, "repo.git")
-      yield* put(path.join(source, "notes.md"), "reference notes")
-      yield* git(source, ["add", "."])
-      yield* git(source, ["commit", "-m", "add notes"])
-      yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-      yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-      const dir = yield* tmpdirScoped({
-        git: true,
-        config: {
-          reference: {
-            docs: "opencode-read-reference/repo",
-          },
-        },
-      })
-
-      const { items, next } = asks()
-      const result = yield* githubBase(
-        `file://${remoteRoot}/`,
-        exec(dir, { filePath: path.join(cache, "notes.md") }, next),
-      )
-      const ext = items.find((item) => item.permission === "external_directory")
-
-      expect(result.output).toContain("reference notes")
-      expect(ext).toBeUndefined()
-    }),
-  )
 })
 
 describe("tool.read env file permissions", () => {

+ 1 - 4
packages/opencode/test/tool/registry.test.ts

@@ -29,8 +29,6 @@ import { Format } from "@/format"
 import { Search } from "@opencode-ai/core/filesystem/search"
 import * as Truncate from "@/tool/truncate"
 import { InstanceState } from "@/effect/instance-state"
-import { Reference } from "@/reference/reference"
-import { RepositoryCache } from "@/reference/repository-cache"
 
 import { ToolJsonSchema } from "@/tool/json-schema"
 import { MessageID, SessionID } from "@/session/schema"
@@ -60,8 +58,7 @@ const registryLayer = (opts: RegistryLayerOptions = {}) =>
       Layer.provide(Session.defaultLayer),
       Layer.provide(Layer.mergeAll(SessionStatus.defaultLayer, BackgroundJob.defaultLayer)),
       Layer.provide(Provider.defaultLayer),
-      Layer.provide(Layer.mergeAll(Git.defaultLayer, RepositoryCache.defaultLayer)),
-      Layer.provide(Reference.defaultLayer),
+      Layer.provide(Git.defaultLayer),
       Layer.provide(LSP.defaultLayer),
       Layer.provide(Instruction.defaultLayer),
       Layer.provide(FSUtil.defaultLayer),

+ 31 - 43
packages/sdk/js/src/v2/gen/sdk.gen.ts

@@ -172,8 +172,6 @@ import type {
   QuestionReplyErrors,
   QuestionReplyResponses,
   QuestionV2Reply,
-  ReferenceListErrors,
-  ReferenceListResponses,
   SessionAbortErrors,
   SessionAbortResponses,
   SessionChildrenErrors,
@@ -288,6 +286,8 @@ import type {
   V2ProviderListResponses,
   V2QuestionRequestListErrors,
   V2QuestionRequestListResponses,
+  V2ReferenceListErrors,
+  V2ReferenceListResponses,
   V2SessionCompactErrors,
   V2SessionCompactResponses,
   V2SessionContextErrors,
@@ -3335,38 +3335,6 @@ export class Provider extends HeyApiClient {
   }
 }
 
-export class Reference extends HeyApiClient {
-  /**
-   * List configured references
-   *
-   * List configured references resolved in the current workspace.
-   */
-  public list<ThrowOnError extends boolean = false>(
-    parameters?: {
-      directory?: string
-      workspace?: string
-    },
-    options?: Options<never, ThrowOnError>,
-  ) {
-    const params = buildClientParams(
-      [parameters],
-      [
-        {
-          args: [
-            { in: "query", key: "directory" },
-            { in: "query", key: "workspace" },
-          ],
-        },
-      ],
-    )
-    return (options?.client ?? this.client).get<ReferenceListResponses, ReferenceListErrors, ThrowOnError>({
-      url: "/reference",
-      ...options,
-      ...params,
-    })
-  }
-}
-
 export class Session2 extends HeyApiClient {
   /**
    * List sessions
@@ -5580,7 +5548,6 @@ export class Fs extends HeyApiClient {
         workspace?: string
       }
       path: string
-      reference?: string
     },
     options?: Options<never, ThrowOnError>,
   ) {
@@ -5591,7 +5558,6 @@ export class Fs extends HeyApiClient {
           args: [
             { in: "query", key: "location" },
             { in: "query", key: "path" },
-            { in: "query", key: "reference" },
           ],
         },
       ],
@@ -5615,7 +5581,6 @@ export class Fs extends HeyApiClient {
         workspace?: string
       }
       path?: string
-      reference?: string
     },
     options?: Options<never, ThrowOnError>,
   ) {
@@ -5626,7 +5591,6 @@ export class Fs extends HeyApiClient {
           args: [
             { in: "query", key: "location" },
             { in: "query", key: "path" },
-            { in: "query", key: "reference" },
           ],
         },
       ],
@@ -5746,6 +5710,30 @@ export class Question3 extends HeyApiClient {
   }
 }
 
+export class Reference extends HeyApiClient {
+  /**
+   * List references
+   *
+   * List references available in the requested location.
+   */
+  public list<ThrowOnError extends boolean = false>(
+    parameters?: {
+      location?: {
+        directory?: string
+        workspace?: string
+      }
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }])
+    return (options?.client ?? this.client).get<V2ReferenceListResponses, V2ReferenceListErrors, ThrowOnError>({
+      url: "/api/reference",
+      ...options,
+      ...params,
+    })
+  }
+}
+
 export class V2 extends HeyApiClient {
   private _health?: Health
   get health(): Health {
@@ -5801,6 +5789,11 @@ export class V2 extends HeyApiClient {
   get question(): Question3 {
     return (this._question ??= new Question3({ client: this.client }))
   }
+
+  private _reference?: Reference
+  get reference(): Reference {
+    return (this._reference ??= new Reference({ client: this.client }))
+  }
 }
 
 export class OpencodeClient extends HeyApiClient {
@@ -5921,11 +5914,6 @@ export class OpencodeClient extends HeyApiClient {
     return (this._provider ??= new Provider({ client: this.client }))
   }
 
-  private _reference?: Reference
-  get reference(): Reference {
-    return (this._reference ??= new Reference({ client: this.client }))
-  }
-
   private _session?: Session2
   get session(): Session2 {
     return (this._session ??= new Session2({ client: this.client }))

+ 70 - 64
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -57,6 +57,7 @@ export type Event =
   | EventAccountSwitched
   | EventPermissionV2Asked
   | EventPermissionV2Replied
+  | EventReferenceUpdated
   | EventFileWatcherUpdated
   | EventPtyCreated
   | EventPtyUpdated
@@ -633,7 +634,6 @@ export type Prompt = {
   text: string
   files?: Array<PromptFileAttachment>
   agents?: Array<PromptAgentAttachment>
-  references?: Array<PromptReferenceAttachment>
 }
 
 export type Pty = {
@@ -1296,6 +1296,13 @@ export type GlobalEvent = {
           reply: PermissionV2Reply
         }
       }
+    | {
+        id: string
+        type: "reference.updated"
+        properties: {
+          [key: string]: unknown
+        }
+      }
     | {
         id: string
         type: "file.watcher.updated"
@@ -2590,26 +2597,6 @@ export type ProviderAuthError1 = {
   }
 }
 
-export type ReferenceDescriptor =
-  | {
-      name: string
-      kind: "local"
-      path: string
-    }
-  | {
-      name: string
-      kind: "git"
-      repository: string
-      path: string
-      branch?: string
-    }
-  | {
-      name: string
-      kind: "invalid"
-      repository?: string
-      message: string
-    }
-
 export type NotFoundError = {
   name: "NotFoundError"
   data: {
@@ -2986,18 +2973,6 @@ export type PromptAgentAttachment = {
   source?: PromptSource
 }
 
-export type PromptReferenceAttachment = {
-  name: string
-  kind: "local" | "git" | "invalid"
-  uri?: string
-  repository?: string
-  branch?: string
-  target?: string
-  targetUri?: string
-  problem?: string
-  source?: PromptSource
-}
-
 export type SessionErrorUnknown = {
   type: "unknown"
   message: string
@@ -3896,7 +3871,6 @@ export type SessionMessageUser = {
   text: string
   files?: Array<PromptFileAttachment>
   agents?: Array<PromptAgentAttachment>
-  references?: Array<PromptReferenceAttachment>
   type: "user"
 }
 
@@ -4212,6 +4186,23 @@ export type QuestionV2Reply = {
   answers: Array<QuestionV2Answer>
 }
 
+export type ReferenceLocalSource = {
+  type: "local"
+  path: string
+}
+
+export type ReferenceGitSource = {
+  type: "git"
+  repository: string
+  branch?: string
+}
+
+export type ReferenceInfo = {
+  name: string
+  path: string
+  source: ReferenceLocalSource | ReferenceGitSource
+}
+
 export type EventModelsDevRefreshed = {
   id: string
   type: "models-dev.refreshed"
@@ -4933,6 +4924,14 @@ export type EventPermissionV2Replied = {
   }
 }
 
+export type EventReferenceUpdated = {
+  id: string
+  type: "reference.updated"
+  properties: {
+    [key: string]: unknown
+  }
+}
+
 export type EventFileWatcherUpdated = {
   id: string
   type: "file.watcher.updated"
@@ -7640,34 +7639,6 @@ export type ProviderOauthCallbackResponses = {
 
 export type ProviderOauthCallbackResponse = ProviderOauthCallbackResponses[keyof ProviderOauthCallbackResponses]
 
-export type ReferenceListData = {
-  body?: never
-  path?: never
-  query?: {
-    directory?: string
-    workspace?: string
-  }
-  url: "/reference"
-}
-
-export type ReferenceListErrors = {
-  /**
-   * Bad request
-   */
-  400: BadRequestError
-}
-
-export type ReferenceListError = ReferenceListErrors[keyof ReferenceListErrors]
-
-export type ReferenceListResponses = {
-  /**
-   * Resolved configured references
-   */
-  200: Array<ReferenceDescriptor>
-}
-
-export type ReferenceListResponse = ReferenceListResponses[keyof ReferenceListResponses]
-
 export type SessionListData = {
   body?: never
   path?: never
@@ -10098,7 +10069,6 @@ export type V2FsReadData = {
       workspace?: string
     }
     path: string
-    reference?: string
   }
   url: "/api/fs/read"
 }
@@ -10137,7 +10107,6 @@ export type V2FsListData = {
       workspace?: string
     }
     path?: string
-    reference?: string
   }
   url: "/api/fs/list"
 }
@@ -10384,6 +10353,43 @@ export type V2SessionQuestionRejectResponses = {
 
 export type V2SessionQuestionRejectResponse = V2SessionQuestionRejectResponses[keyof V2SessionQuestionRejectResponses]
 
+export type V2ReferenceListData = {
+  body?: never
+  path?: never
+  query?: {
+    location?: {
+      directory?: string
+      workspace?: string
+    }
+  }
+  url: "/api/reference"
+}
+
+export type V2ReferenceListErrors = {
+  /**
+   * InvalidRequestError
+   */
+  400: InvalidRequestError
+  /**
+   * UnauthorizedError
+   */
+  401: UnauthorizedError
+}
+
+export type V2ReferenceListError = V2ReferenceListErrors[keyof V2ReferenceListErrors]
+
+export type V2ReferenceListResponses = {
+  /**
+   * Success
+   */
+  200: {
+    location: LocationInfo
+    data: Array<ReferenceInfo>
+  }
+}
+
+export type V2ReferenceListResponse = V2ReferenceListResponses[keyof V2ReferenceListResponses]
+
 export type PtyConnectData = {
   body?: never
   path: {

+ 139 - 189
packages/sdk/openapi.json

@@ -5400,64 +5400,6 @@
         ]
       }
     },
-    "/reference": {
-      "get": {
-        "tags": ["reference"],
-        "operationId": "reference.list",
-        "parameters": [
-          {
-            "name": "directory",
-            "in": "query",
-            "schema": {
-              "type": "string"
-            },
-            "required": false
-          },
-          {
-            "name": "workspace",
-            "in": "query",
-            "schema": {
-              "type": "string"
-            },
-            "required": false
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "Resolved configured references",
-            "content": {
-              "application/json": {
-                "schema": {
-                  "type": "array",
-                  "items": {
-                    "$ref": "#/components/schemas/ReferenceDescriptor"
-                  },
-                  "description": "Resolved configured references"
-                }
-              }
-            }
-          },
-          "400": {
-            "description": "Bad request",
-            "content": {
-              "application/json": {
-                "schema": {
-                  "$ref": "#/components/schemas/BadRequestError"
-                }
-              }
-            }
-          }
-        },
-        "description": "List configured references resolved in the current workspace.",
-        "summary": "List configured references",
-        "x-codeSamples": [
-          {
-            "lang": "js",
-            "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.reference.list({\n  ...\n})"
-          }
-        ]
-      }
-    },
     "/session": {
       "get": {
         "tags": ["session"],
@@ -11414,14 +11356,6 @@
               "type": "string"
             },
             "required": true
-          },
-          {
-            "name": "reference",
-            "in": "query",
-            "schema": {
-              "type": "string"
-            },
-            "required": false
           }
         ],
         "security": [],
@@ -11515,14 +11449,6 @@
               "type": "string"
             },
             "required": false
-          },
-          {
-            "name": "reference",
-            "in": "query",
-            "schema": {
-              "type": "string"
-            },
-            "required": false
           }
         ],
         "security": [],
@@ -12062,6 +11988,87 @@
         ]
       }
     },
+    "/api/reference": {
+      "get": {
+        "tags": ["reference"],
+        "operationId": "v2.reference.list",
+        "parameters": [
+          {
+            "name": "location",
+            "in": "query",
+            "schema": {
+              "type": "object",
+              "properties": {
+                "directory": {
+                  "type": "string"
+                },
+                "workspace": {
+                  "type": "string"
+                }
+              },
+              "additionalProperties": false
+            },
+            "required": false,
+            "style": "deepObject",
+            "explode": true
+          }
+        ],
+        "security": [],
+        "responses": {
+          "200": {
+            "description": "Success",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object",
+                  "properties": {
+                    "location": {
+                      "$ref": "#/components/schemas/LocationInfo"
+                    },
+                    "data": {
+                      "type": "array",
+                      "items": {
+                        "$ref": "#/components/schemas/ReferenceInfo"
+                      }
+                    }
+                  },
+                  "required": ["location", "data"],
+                  "additionalProperties": false
+                }
+              }
+            }
+          },
+          "400": {
+            "description": "InvalidRequestError",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/InvalidRequestError"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "UnauthorizedError",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/UnauthorizedError"
+                }
+              }
+            }
+          }
+        },
+        "description": "List references available in the requested location.",
+        "summary": "List references",
+        "x-codeSamples": [
+          {
+            "lang": "js",
+            "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.reference.list({\n  ...\n})"
+          }
+        ]
+      }
+    },
     "/pty/{ptyID}/connect": {
       "get": {
         "tags": ["pty"],
@@ -14068,12 +14075,6 @@
             "items": {
               "$ref": "#/components/schemas/PromptAgentAttachment"
             }
-          },
-          "references": {
-            "type": "array",
-            "items": {
-              "$ref": "#/components/schemas/PromptReferenceAttachment"
-            }
           }
         },
         "required": ["text"],
@@ -20041,70 +20042,6 @@
         "required": ["name", "data"],
         "additionalProperties": false
       },
-      "ReferenceDescriptor": {
-        "anyOf": [
-          {
-            "type": "object",
-            "properties": {
-              "name": {
-                "type": "string"
-              },
-              "kind": {
-                "type": "string",
-                "enum": ["local"]
-              },
-              "path": {
-                "type": "string"
-              }
-            },
-            "required": ["name", "kind", "path"],
-            "additionalProperties": false
-          },
-          {
-            "type": "object",
-            "properties": {
-              "name": {
-                "type": "string"
-              },
-              "kind": {
-                "type": "string",
-                "enum": ["git"]
-              },
-              "repository": {
-                "type": "string"
-              },
-              "path": {
-                "type": "string"
-              },
-              "branch": {
-                "type": "string"
-              }
-            },
-            "required": ["name", "kind", "repository", "path"],
-            "additionalProperties": false
-          },
-          {
-            "type": "object",
-            "properties": {
-              "name": {
-                "type": "string"
-              },
-              "kind": {
-                "type": "string",
-                "enum": ["invalid"]
-              },
-              "repository": {
-                "type": "string"
-              },
-              "message": {
-                "type": "string"
-              }
-            },
-            "required": ["name", "kind", "message"],
-            "additionalProperties": false
-          }
-        ]
-      },
       "NotFoundError": {
         "type": "object",
         "required": ["name", "data"],
@@ -21498,41 +21435,6 @@
         "required": ["name"],
         "additionalProperties": false
       },
-      "PromptReferenceAttachment": {
-        "type": "object",
-        "properties": {
-          "name": {
-            "type": "string"
-          },
-          "kind": {
-            "type": "string",
-            "enum": ["local", "git", "invalid"]
-          },
-          "uri": {
-            "type": "string"
-          },
-          "repository": {
-            "type": "string"
-          },
-          "branch": {
-            "type": "string"
-          },
-          "target": {
-            "type": "string"
-          },
-          "targetUri": {
-            "type": "string"
-          },
-          "problem": {
-            "type": "string"
-          },
-          "source": {
-            "$ref": "#/components/schemas/PromptSource"
-          }
-        },
-        "required": ["name", "kind"],
-        "additionalProperties": false
-      },
       "SessionErrorUnknown": {
         "type": "object",
         "properties": {
@@ -24339,12 +24241,6 @@
               "$ref": "#/components/schemas/PromptAgentAttachment"
             }
           },
-          "references": {
-            "type": "array",
-            "items": {
-              "$ref": "#/components/schemas/PromptReferenceAttachment"
-            }
-          },
           "type": {
             "type": "string",
             "enum": ["user"]
@@ -25214,6 +25110,60 @@
         "required": ["answers"],
         "additionalProperties": false
       },
+      "ReferenceLocalSource": {
+        "type": "object",
+        "properties": {
+          "type": {
+            "type": "string",
+            "enum": ["local"]
+          },
+          "path": {
+            "type": "string"
+          }
+        },
+        "required": ["type", "path"],
+        "additionalProperties": false
+      },
+      "ReferenceGitSource": {
+        "type": "object",
+        "properties": {
+          "type": {
+            "type": "string",
+            "enum": ["git"]
+          },
+          "repository": {
+            "type": "string"
+          },
+          "branch": {
+            "type": "string"
+          }
+        },
+        "required": ["type", "repository"],
+        "additionalProperties": false
+      },
+      "ReferenceInfo": {
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string"
+          },
+          "path": {
+            "type": "string"
+          },
+          "source": {
+            "anyOf": [
+              {
+                "$ref": "#/components/schemas/ReferenceLocalSource"
+              },
+              {
+                "$ref": "#/components/schemas/ReferenceGitSource"
+              }
+            ]
+          }
+        },
+        "required": ["name", "path", "source"],
+        "additionalProperties": false
+      },
       "EventModels-devRefreshed": {
         "type": "object",
         "properties": {
@@ -28767,10 +28717,6 @@
       "name": "provider",
       "description": "Experimental HttpApi provider routes."
     },
-    {
-      "name": "reference",
-      "description": "Configured reference routes."
-    },
     {
       "name": "session",
       "description": "Experimental HttpApi session routes."
@@ -28835,6 +28781,10 @@
       "name": "session questions",
       "description": "Experimental session question routes."
     },
+    {
+      "name": "reference",
+      "description": "Location-scoped project references."
+    },
     {
       "name": "pty",
       "description": "PTY websocket route."

+ 2 - 0
packages/server/src/api.ts

@@ -12,6 +12,7 @@ import { EventGroup } from "./groups/event"
 import { AgentGroup } from "./groups/agent"
 import { HealthGroup } from "./groups/health"
 import { QuestionGroup } from "./groups/question"
+import { ReferenceGroup } from "./groups/reference"
 import { Authorization } from "./middleware/authorization"
 
 export const Api = HttpApi.make("server")
@@ -27,6 +28,7 @@ export const Api = HttpApi.make("server")
   .add(SkillGroup)
   .add(EventGroup)
   .add(QuestionGroup)
+  .add(ReferenceGroup)
   .annotateMerge(
     OpenApi.annotations({
       title: "opencode HttpApi",

+ 0 - 2
packages/server/src/groups/fs.ts

@@ -8,13 +8,11 @@ import { LocationQuery, locationQueryOpenApi, LocationMiddleware } from "./locat
 const ReadQuery = Schema.Struct({
   ...LocationQuery.fields,
   path: RelativePath,
-  reference: Schema.String.pipe(Schema.optional),
 })
 
 const ListQuery = Schema.Struct({
   ...LocationQuery.fields,
   path: RelativePath.pipe(Schema.optional),
-  reference: Schema.String.pipe(Schema.optional),
 })
 
 export const FileSystemGroup = HttpApiGroup.make("server.fs")

+ 28 - 0
packages/server/src/groups/reference.ts

@@ -0,0 +1,28 @@
+import { Location } from "@opencode-ai/core/location"
+import { Reference } from "@opencode-ai/core/reference"
+import { Schema } from "effect"
+import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
+import { LocationMiddleware, LocationQuery, locationQueryOpenApi } from "./location"
+
+export const ReferenceGroup = HttpApiGroup.make("server.reference")
+  .add(
+    HttpApiEndpoint.get("reference.list", "/api/reference", {
+      query: LocationQuery,
+      success: Location.response(Schema.Array(Reference.Info)),
+    })
+      .annotateMerge(locationQueryOpenApi)
+      .annotateMerge(
+        OpenApi.annotations({
+          identifier: "v2.reference.list",
+          summary: "List references",
+          description: "List references available in the requested location.",
+        }),
+      ),
+  )
+  .annotateMerge(
+    OpenApi.annotations({
+      title: "reference",
+      description: "Location-scoped project references.",
+    }),
+  )
+  .middleware(LocationMiddleware)

+ 2 - 0
packages/server/src/handlers.ts

@@ -16,6 +16,7 @@ import { EventHandler } from "./handlers/event"
 import { AgentHandler } from "./handlers/agent"
 import { HealthHandler } from "./handlers/health"
 import { QuestionHandler } from "./handlers/question"
+import { ReferenceHandler } from "./handlers/reference"
 import * as SessionExecutionLocal from "@opencode-ai/core/session/execution/local"
 
 export const handlers = Layer.mergeAll(
@@ -31,6 +32,7 @@ export const handlers = Layer.mergeAll(
   SkillHandler,
   EventHandler,
   QuestionHandler,
+  ReferenceHandler,
 ).pipe(
   Layer.provide(sessionLocationLayer),
   Layer.provide(locationLayer),

+ 8 - 0
packages/server/src/handlers/reference.ts

@@ -0,0 +1,8 @@
+import { Reference } from "@opencode-ai/core/reference"
+import { HttpApiBuilder } from "effect/unstable/httpapi"
+import { Api } from "../api"
+import { response } from "../groups/location"
+
+export const ReferenceHandler = HttpApiBuilder.group(Api, "server.reference", (handlers) =>
+  handlers.handle("reference.list", () => response(Reference.Service.use((reference) => reference.list()))),
+)

+ 14 - 46
packages/tui/src/component/prompt/autocomplete.tsx

@@ -9,6 +9,7 @@ import { useEditorContext } from "../../context/editor"
 import { useProject } from "../../context/project"
 import { useSDK } from "../../context/sdk"
 import { useSync } from "../../context/sync"
+import { useSyncV2 } from "../../context/sync-v2"
 import { getScrollAcceleration } from "../../util/scroll"
 import { useTuiPaths } from "../../context/runtime"
 import { useTuiConfig } from "../../config"
@@ -19,7 +20,6 @@ import { Locale } from "../../util/locale"
 import type { PromptInfo } from "../../prompt/history"
 import { useFrecency } from "../../prompt/frecency"
 import { useBindings, useCommandSlashes, useOpencodeModeStack } from "../../keymap"
-import type { ReferenceDescriptor } from "@opencode-ai/sdk/v2"
 import { displayCharAt, mentionTriggerIndex } from "../../prompt/display"
 
 function removeLineRange(input: string) {
@@ -85,6 +85,7 @@ export function Autocomplete(props: {
   const editor = useEditorContext()
   const sdk = useSDK()
   const sync = useSync()
+  const syncV2 = useSyncV2()
   const project = useProject()
   const slashes = useCommandSlashes()
   const modeStack = useOpencodeModeStack()
@@ -272,37 +273,12 @@ export function Autocomplete(props: {
     }
   }
 
-  function referencePromptText(reference: ReferenceDescriptor) {
-    const problem = reference.kind === "invalid" ? reference.message : undefined
-    return [
-      `Referenced configured reference @${reference.name}.`,
-      ...(reference.kind === "local" ? ["Kind: local directory"] : []),
-      ...(reference.kind === "git" ? ["Kind: git repository"] : []),
-      ...(reference.kind === "invalid" && reference.repository ? [`Repository: ${reference.repository}`] : []),
-      ...(reference.kind === "git" ? [`Repository: ${reference.repository}`] : []),
-      ...(reference.kind === "git" && reference.branch ? [`Branch/ref: ${reference.branch}`] : []),
-      ...(reference.kind === "invalid" ? [] : [`Reference root: ${reference.path}`]),
-      ...(problem
-        ? [`Problem: ${problem}`]
-        : ["Inspect the configured reference with Read, Glob, and Grep when useful."]),
-    ].join("\n")
-  }
-
-  const [references] = createResource(
-    () => project.workspace.current(),
-    async (workspace) => {
-      const result = await sdk.client.reference.list({ workspace })
-      return result.data ?? []
-    },
-    { initialValue: [] },
-  )
-
   const referenceMatch = createMemo(() => {
     if (!store.visible || store.visible === "/") return
     const { baseQuery } = extractLineRange(search())
     const slash = baseQuery.indexOf("/")
     const alias = slash === -1 ? baseQuery : baseQuery.slice(0, slash)
-    return references().find((item) => item.name === alias)
+    return syncV2.data.reference.find((item) => item.name === alias)
   })
 
   function normalizeMentionPath(filePath: string) {
@@ -435,29 +411,21 @@ export function Autocomplete(props: {
   })
 
   const referenceAliases = createMemo(() =>
-    references().map(
+    syncV2.data.reference.map(
       (reference): AutocompleteOption => ({
         display: "@" + reference.name,
-        description: reference.kind === "invalid" ? reference.message : " dir",
+        description: " dir",
         onSelect: () => {
-          if (reference.kind !== "invalid") {
-            insertPart(reference.name, {
-              type: "file",
-              mime: "application/x-directory",
-              filename: reference.name,
-              url: pathToFileURL(reference.path).href,
-              source: {
-                type: "file",
-                text: { start: 0, end: 0, value: "" },
-                path: reference.name,
-              },
-            })
-            return
-          }
           insertPart(reference.name, {
-            type: "text",
-            text: referencePromptText(reference),
-            synthetic: true,
+            type: "file",
+            mime: "application/x-directory",
+            filename: reference.name,
+            url: pathToFileURL(reference.path).href,
+            source: {
+              type: "file",
+              text: { start: 0, end: 0, value: "" },
+              path: reference.name,
+            },
           })
         },
       }),

+ 20 - 2
packages/tui/src/context/sync-v2.tsx

@@ -1,6 +1,7 @@
 import { useEvent } from "./event"
 import type {
   Event,
+  ReferenceInfo,
   SessionMessage,
   SessionMessageAssistant,
   SessionMessageAssistantReasoning,
@@ -10,6 +11,8 @@ import type {
 import { createStore, produce, reconcile } from "solid-js/store"
 import { createSimpleContext } from "./helper"
 import { useSDK } from "./sdk"
+import { useProject } from "./project"
+import { createEffect } from "solid-js"
 
 function activeAssistant(messages: SessionMessage[]) {
   const index = messages.findIndex((message) => message.type === "assistant" && !message.time.completed)
@@ -60,12 +63,15 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
       messages: {
         [sessionID: string]: SessionMessage[]
       }
+      reference: ReferenceInfo[]
     }>({
       messages: {},
+      reference: [],
     })
 
     const event = useEvent()
     const sdk = useSDK()
+    const project = useProject()
     const applied = new Set<string>()
     const buffering = new Map<string, Event[]>()
     const syncing = new Map<string, Promise<void>>()
@@ -117,6 +123,17 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
       return result
     }
 
+    async function syncReferences(workspace = project.workspace.current()) {
+      const result = await sdk.client.v2.reference.list({ location: { workspace } })
+      if (workspace !== project.workspace.current()) return
+      setStore("reference", reconcile(result.data?.data ?? []))
+    }
+
+    createEffect(() => {
+      project.workspace.current()
+      void syncReferences()
+    })
+
     function apply(event: Event) {
       switch (event.type) {
         case "session.next.agent.switched":
@@ -147,7 +164,6 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
               text: event.properties.prompt.text,
               files: event.properties.prompt.files,
               agents: event.properties.prompt.agents,
-              references: event.properties.prompt.references,
               time: { created: event.properties.timestamp },
             })
           })
@@ -163,7 +179,6 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
               text: event.properties.prompt.text,
               files: event.properties.prompt.files,
               agents: event.properties.prompt.agents,
-              references: event.properties.prompt.references,
               time: { created: event.properties.timeCreated },
             })
           })
@@ -418,6 +433,9 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
             })
           })
           break
+        case "reference.updated":
+          void syncReferences()
+          break
       }
     }
 

+ 46 - 0
packages/tui/test/cli/tui/sync-v2.test.tsx

@@ -27,6 +27,52 @@ function emitTwice(events: ReturnType<typeof createEventSource>, payload: Event)
   events.emit(event)
 }
 
+test("sync v2 refreshes references after updates", async () => {
+  const events = createEventSource()
+  let requests = 0
+  const calls = createFetch((url) => {
+    if (url.pathname !== "/api/reference") return
+    requests++
+    return json({
+      location: { directory, project: { id: "proj_test", directory } },
+      data: requests === 1 ? [] : [{ name: "docs", path: "/docs", source: { type: "local", path: "/docs" } }],
+    })
+  })
+  let sync!: ReturnType<typeof useSyncV2>
+  let ready!: () => void
+  const mounted = new Promise<void>((resolve) => {
+    ready = resolve
+  })
+
+  function Probe() {
+    sync = useSyncV2()
+    onMount(ready)
+    return <box />
+  }
+
+  const app = await testRender(() => (
+    <TestTuiContexts>
+      <SDKProvider url="http://test" directory={directory} events={events.source} fetch={calls.fetch}>
+        <ProjectProvider>
+          <SyncProviderV2>
+            <Probe />
+          </SyncProviderV2>
+        </ProjectProvider>
+      </SDKProvider>
+    </TestTuiContexts>
+  ))
+
+  try {
+    await mounted
+    await wait(() => requests === 1)
+    events.emit(global({ id: "evt_reference_1", type: "reference.updated", properties: {} }))
+    await wait(() => sync.data.reference.length === 1)
+    expect(sync.data.reference[0]?.name).toBe("docs")
+  } finally {
+    app.renderer.destroy()
+  }
+})
+
 test("sync v2 settles pending tools when a live failure arrives", async () => {
   const events = createEventSource()
   const calls = createFetch()

+ 1 - 0
packages/tui/test/fixture/tui-sdk.ts

@@ -60,6 +60,7 @@ export function createFetch(override?: FetchHandler) {
     if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
     if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
     if (url.pathname === "/project/current") return json({ id: "proj_test" })
+    if (url.pathname === "/api/reference") return json({ location: { directory, project: { id: "proj_test", directory } }, data: [] })
     if (url.pathname === "/provider") return json({ all: [], default: {}, connected: [] })
     if (url.pathname === "/session") return json([])
     if (url.pathname === "/vcs") return json({ branch: "main" })