feat(aircoding): AirCoding V2 baseline — deterministic multi-agent architecture
Forked from OpenCode v1.17.4 with multi-agent system: - 5 agents: aircoding, scheduler, worker, architect, reviewer - Deterministic DAG scheduling engine (coordinator_tick) - Tool whitelists as hard enforcement - AirCoding validation plugin - V1 requirements: C4 docs, ADR, AGENTS.md, debug-log.md - Design documents in docs/
This commit is contained in:
256
packages/opencode/test/session/instruction.test.ts
Normal file
256
packages/opencode/test/session/instruction.test.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import path from "path"
|
||||
import { Effect, FileSystem, Layer } from "effect"
|
||||
import { FetchHttpClient } from "effect/unstable/http"
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
|
||||
import { Instruction } from "../../src/session/instruction"
|
||||
import type { MessageV2 } from "../../src/session/message-v2"
|
||||
import { MessageID, PartID, SessionID } from "../../src/session/schema"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||
import { provideInstance, provideTmpdirInstance, testInstanceStoreLayer, tmpdirScoped } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { TestConfig } from "../fixture/config"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
|
||||
const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer, testInstanceStoreLayer))
|
||||
|
||||
const configLayer = TestConfig.layer()
|
||||
|
||||
const instructionLayer = (global: Partial<Global.Interface>, flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Instruction.layer.pipe(
|
||||
Layer.provide(configLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(Global.layerWith(global)),
|
||||
Layer.provide(RuntimeFlags.layer(flags)),
|
||||
)
|
||||
|
||||
const provideInstruction =
|
||||
(global: Partial<Global.Interface>, flags?: Partial<RuntimeFlags.Info>) =>
|
||||
<A, E, R>(self: Effect.Effect<A, E, R>) =>
|
||||
self.pipe(Effect.provide(instructionLayer(global, flags)))
|
||||
|
||||
const write = (filepath: string, content: string) =>
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FileSystem.FileSystem
|
||||
yield* fs.makeDirectory(path.dirname(filepath), { recursive: true })
|
||||
yield* fs.writeFileString(filepath, content)
|
||||
})
|
||||
|
||||
const writeFiles = (dir: string, files: Record<string, string>) =>
|
||||
Effect.all(
|
||||
Object.entries(files).map(([file, content]) => write(path.join(dir, file), content)),
|
||||
{ discard: true },
|
||||
)
|
||||
|
||||
const withFiles = <A, E, R>(files: Record<string, string>, self: (dir: string) => Effect.Effect<A, E, R>) =>
|
||||
provideTmpdirInstance((dir) =>
|
||||
Effect.gen(function* () {
|
||||
yield* writeFiles(dir, files)
|
||||
return yield* self(dir).pipe(provideInstruction({ home: dir, config: dir }))
|
||||
}),
|
||||
)
|
||||
|
||||
const tmpWithFiles = (files: Record<string, string>) =>
|
||||
Effect.gen(function* () {
|
||||
const dir = yield* tmpdirScoped()
|
||||
yield* writeFiles(dir, files)
|
||||
return dir
|
||||
})
|
||||
|
||||
function loaded(filepath: string): SessionV1.WithParts[] {
|
||||
const sessionID = SessionID.make("session-loaded-1")
|
||||
const messageID = MessageID.make("msg_message-loaded-1")
|
||||
|
||||
return [
|
||||
{
|
||||
info: {
|
||||
id: messageID,
|
||||
sessionID,
|
||||
role: "user",
|
||||
time: { created: 0 },
|
||||
agent: "build",
|
||||
model: {
|
||||
providerID: ProviderV2.ID.make("anthropic"),
|
||||
modelID: ModelV2.ID.make("claude-sonnet-4-20250514"),
|
||||
},
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
id: PartID.make("prt_part-loaded-1"),
|
||||
messageID,
|
||||
sessionID,
|
||||
type: "tool",
|
||||
callID: "call-loaded-1",
|
||||
tool: "read",
|
||||
state: {
|
||||
status: "completed",
|
||||
input: {},
|
||||
output: "done",
|
||||
title: "Read",
|
||||
metadata: { loaded: [filepath] },
|
||||
time: { start: 0, end: 1 },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
describe("Instruction.resolve", () => {
|
||||
it.live("returns empty when AGENTS.md is at project root (already in systemPaths)", () =>
|
||||
withFiles({ "AGENTS.md": "# Root Instructions", "src/file.ts": "const x = 1" }, (dir) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Instruction.Service
|
||||
const system = yield* svc.systemPaths()
|
||||
expect(system.has(path.join(dir, "AGENTS.md"))).toBe(true)
|
||||
|
||||
const results = yield* svc.resolve([], path.join(dir, "src", "file.ts"), MessageID.make("msg_message-test-1"))
|
||||
expect(results).toEqual([])
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("returns AGENTS.md from subdirectory (not in systemPaths)", () =>
|
||||
withFiles({ "subdir/AGENTS.md": "# Subdir Instructions", "subdir/nested/file.ts": "const x = 1" }, (dir) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Instruction.Service
|
||||
const system = yield* svc.systemPaths()
|
||||
expect(system.has(path.join(dir, "subdir", "AGENTS.md"))).toBe(false)
|
||||
|
||||
const results = yield* svc.resolve(
|
||||
[],
|
||||
path.join(dir, "subdir", "nested", "file.ts"),
|
||||
MessageID.make("msg_message-test-2"),
|
||||
)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0].filepath).toBe(path.join(dir, "subdir", "AGENTS.md"))
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("doesn't reload AGENTS.md when reading it directly", () =>
|
||||
withFiles({ "subdir/AGENTS.md": "# Subdir Instructions", "subdir/nested/file.ts": "const x = 1" }, (dir) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Instruction.Service
|
||||
const filepath = path.join(dir, "subdir", "AGENTS.md")
|
||||
const system = yield* svc.systemPaths()
|
||||
expect(system.has(filepath)).toBe(false)
|
||||
|
||||
const results = yield* svc.resolve([], filepath, MessageID.make("msg_message-test-3"))
|
||||
expect(results).toEqual([])
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("does not reattach the same nearby instructions twice for one message", () =>
|
||||
withFiles({ "subdir/AGENTS.md": "# Subdir Instructions", "subdir/nested/file.ts": "const x = 1" }, (dir) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Instruction.Service
|
||||
const filepath = path.join(dir, "subdir", "nested", "file.ts")
|
||||
const id = MessageID.make("msg_message-claim-1")
|
||||
|
||||
const first = yield* svc.resolve([], filepath, id)
|
||||
const second = yield* svc.resolve([], filepath, id)
|
||||
|
||||
expect(first).toHaveLength(1)
|
||||
expect(first[0].filepath).toBe(path.join(dir, "subdir", "AGENTS.md"))
|
||||
expect(second).toEqual([])
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("clear allows nearby instructions to be attached again for the same message", () =>
|
||||
withFiles({ "subdir/AGENTS.md": "# Subdir Instructions", "subdir/nested/file.ts": "const x = 1" }, (dir) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Instruction.Service
|
||||
const filepath = path.join(dir, "subdir", "nested", "file.ts")
|
||||
const id = MessageID.make("msg_message-claim-2")
|
||||
|
||||
const first = yield* svc.resolve([], filepath, id)
|
||||
yield* svc.clear(id)
|
||||
const second = yield* svc.resolve([], filepath, id)
|
||||
|
||||
expect(first).toHaveLength(1)
|
||||
expect(second).toHaveLength(1)
|
||||
expect(second[0].filepath).toBe(path.join(dir, "subdir", "AGENTS.md"))
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("skips instructions already reported by prior read metadata", () =>
|
||||
withFiles({ "subdir/AGENTS.md": "# Subdir Instructions", "subdir/nested/file.ts": "const x = 1" }, (dir) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Instruction.Service
|
||||
const agents = path.join(dir, "subdir", "AGENTS.md")
|
||||
const filepath = path.join(dir, "subdir", "nested", "file.ts")
|
||||
const id = MessageID.make("msg_message-claim-3")
|
||||
|
||||
const results = yield* svc.resolve(loaded(agents), filepath, id)
|
||||
expect(results).toEqual([])
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
test.todo("fetches remote instructions from config URLs via HttpClient", () => {})
|
||||
})
|
||||
|
||||
describe("Instruction.system", () => {
|
||||
it.live("loads both project and global AGENTS.md when both exist", () =>
|
||||
Effect.gen(function* () {
|
||||
const globalTmp = yield* tmpWithFiles({ "AGENTS.md": "# Global Instructions" })
|
||||
const projectTmp = yield* tmpWithFiles({ "AGENTS.md": "# Project Instructions" })
|
||||
|
||||
yield* Effect.gen(function* () {
|
||||
const svc = yield* Instruction.Service
|
||||
const paths = yield* svc.systemPaths()
|
||||
expect(paths.has(path.join(projectTmp, "AGENTS.md"))).toBe(true)
|
||||
expect(paths.has(path.join(globalTmp, "AGENTS.md"))).toBe(true)
|
||||
|
||||
const rules = yield* svc.system()
|
||||
expect(rules).toHaveLength(2)
|
||||
expect(rules[0]).toBe(`Instructions from: ${path.join(globalTmp, "AGENTS.md")}\n# Global Instructions`)
|
||||
expect(rules[1]).toBe(`Instructions from: ${path.join(projectTmp, "AGENTS.md")}\n# Project Instructions`)
|
||||
}).pipe(provideInstance(projectTmp), provideInstruction({ home: globalTmp, config: globalTmp }))
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("skips project and global CLAUDE.md when Claude Code prompt is disabled", () =>
|
||||
Effect.gen(function* () {
|
||||
const globalTmp = yield* tmpWithFiles({ ".claude/CLAUDE.md": "# Global Claude" })
|
||||
const projectTmp = yield* tmpWithFiles({ "CLAUDE.md": "# Project Claude" })
|
||||
|
||||
yield* Effect.gen(function* () {
|
||||
const svc = yield* Instruction.Service
|
||||
const paths = yield* svc.systemPaths()
|
||||
expect(paths.has(path.join(globalTmp, ".claude", "CLAUDE.md"))).toBe(false)
|
||||
expect(paths.has(path.join(projectTmp, "CLAUDE.md"))).toBe(false)
|
||||
expect(yield* svc.system()).toEqual([])
|
||||
}).pipe(
|
||||
provideInstance(projectTmp),
|
||||
provideInstruction({ home: globalTmp, config: globalTmp }, { disableClaudeCodePrompt: true }),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe("Instruction.systemPaths global config", () => {
|
||||
it.live("uses Global.Service config AGENTS.md", () =>
|
||||
Effect.gen(function* () {
|
||||
const globalTmp = yield* tmpWithFiles({ "AGENTS.md": "# Global Instructions" })
|
||||
const projectTmp = yield* tmpdirScoped()
|
||||
|
||||
yield* Effect.gen(function* () {
|
||||
const svc = yield* Instruction.Service
|
||||
const paths = yield* svc.systemPaths()
|
||||
expect(paths.has(path.join(globalTmp, "AGENTS.md"))).toBe(true)
|
||||
}).pipe(provideInstance(projectTmp), provideInstruction({ home: globalTmp, config: globalTmp }))
|
||||
}),
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user