Files
LLeMbas-CLI/tests/conformance.ts
T
HomerandClaude Opus 5.5 4bbee18cee Instruction files stack from the project root down to the working directory
instructionChain (spec 3.0.0, filesystem.md): from the boundary — the project
root — down to the start directory, outermost first, each directory gives the
first of AGENTS.md, CLAUDE.md that is a file really inside the project; one
that is not (a link out) is passed over and the walk goes on below it. Before,
only the nearest file counted. The chain's files are named to the model by
their path in the project; agentsBlock reads them all: none means "make one",
and git is settled when any of them settles it.

Conformance runner for the `instructions` area (all 9 cases pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:04:03 +00:00

150 lines
7.5 KiB
TypeScript

// What the CLI does with each conformance case (harness/conformance/*.json). The cases are the
// harness spec's: LLeMbas runs the same files through its own implementation. The test compares
// this with each case's `expect`; `bun run harness record` fills in an `expect` a new case lacks.
import { alwaysPatterns, toRules, evaluate, DEFAULT_RULES, type PermissionRequest } from "../src/permission/evaluate.ts"
import { BUILTIN_HARDLINE, hardlineCommand, plainCommands } from "../src/permission/hardline.ts"
import { splitCommand, words } from "../src/permission/bash.ts"
import { prefix } from "../src/permission/arity.ts"
import { split } from "../src/library/chunks.ts"
import { ThinkSplitter } from "../src/provider/think.ts"
import { advertisedEfforts, effortRefused } from "../src/provider/effort.ts"
import { replace } from "../src/tool/replace.ts"
import { applyUnified, parseUnified } from "../src/tool/unidiff.ts"
import { derive, joinBom, parse } from "../src/tool/patch.ts"
import type { Mode, PermissionConfig } from "../src/config/schema.ts"
import { apply, fromTodos, normalise, type Plan } from "../src/plan/plan.ts"
import { ToolCallAccumulator } from "../src/provider/openai-chat.ts"
import { parseInfo, titleFor } from "../src/prototype/fence.ts"
import { resolveBase } from "../src/library/store.ts"
import { NarrowError, narrowParameters, type Narrow } from "../src/harness.ts"
import { fillPrompt } from "../src/prompt/fill.ts"
import { instructionChain } from "../src/prompt/assemble.ts"
import { applyOps, type Op, type Target } from "../src/memory/store.ts"
import { jobNote, type NoteCase } from "../src/jobs/note.ts"
type Case = Record<string, unknown>
/** Whether a path is at `ro` or above under `grants` (fs_access's rule: the most specific grant on
* the path or above it wins; none where nothing is granted). No grants at all: everything readable. */
function readableUnder(grants: Record<string, string> | undefined, path: string): boolean {
if (!grants) return true
let best: string | undefined
for (const g of Object.keys(grants)) {
const covers = g === "/" || path === g || path.startsWith(g + "/")
if (covers && (best === undefined || g.length > best.length)) best = g
}
return best !== undefined && grants[best] !== "none"
}
export const AREAS: Record<string, (c: Case) => unknown> = {
hardline: (c) => hardlineCommand(c.line as string, BUILTIN_HARDLINE)?.id ?? null,
split: (c) => splitCommand(c.line as string),
plain: (c) => plainCommands(c.line as string),
arity: (c) => prefix(words(c.command as string)).join(" "),
always: (c) => {
const line = c.line as string
const split = splitCommand(line)
// What an approval card offers to remember: nothing when the split cannot be trusted.
if (split.unsafe.length) return []
return alwaysPatterns({ permission: "bash", class: "execute", patterns: [line], command: line }, split.commands.length ? split.commands : [line])
},
permission: (c) => {
const line = c.line as string
const req: PermissionRequest = { permission: "bash", class: "execute", patterns: [line], command: line, paths: ["/project"] }
const rules = [...toRules(DEFAULT_RULES, "default"), ...toRules((c.rules ?? {}) as PermissionConfig, "global")]
return evaluate(req, { mode: c.mode as Mode, rules, hardline: BUILTIN_HARDLINE, root: "/project", planDir: "/project/.agent/plans", projectDir: "/project/.agent" }).action
},
chunk: (c) => split(c.text as string, c.size as number, c.overlap as number),
think: (c) => {
const s = new ThinkSplitter()
const pieces = [...(c.chunks as string[]).flatMap((x) => s.feed(x)), ...s.flush()]
const out = { reasoning: "", text: "" }
for (const p of pieces) out[p.kind] += p.text
return out
},
effort: (c) => ({ refused: effortRefused(c.message as string), advertised: advertisedEfforts(c.message as string) }),
edit: (c) => {
try {
return { content: replace(c.content as string, c.old as string, c.new as string, c.all === true) }
} catch (e) {
return { error: (e as Error).message }
}
},
unidiff: (c) => {
try {
const files = parseUnified(c.patch as string)
return { content: applyUnified(c.content as string, files.flatMap((f) => f.hunks)) }
} catch (e) {
return { error: (e as Error).message }
}
},
plan: (c) => {
if (c.op === "normalise") return normalise(c.plan)
if (c.op === "update") return apply(c.plan as Plan, c.changes)
if (c.op === "todos") return fromTodos(c.todos)
throw new Error(`plan: unknown op ${String(c.op)}`)
},
prototype: (c) => {
const info = parseInfo(c.info as string)
return info ? { kind: info.kind, title: titleFor(c.code as string, info.title) } : null
},
fill: (c) => {
try {
return { text: fillPrompt(c.text as string, c.vars as Record<string, string>) }
} catch (e) {
// "prompt: no value for {{a}}, {{c}}": the names, sorted.
return { missing: [...(e as Error).message.matchAll(/\{\{(\w+)\}\}/g)].map((m) => m[1]!).sort() }
}
},
narrow: (c) => {
try {
return { parameters: narrowParameters(c.parameters as Record<string, unknown>, c.narrow as Narrow) }
} catch (e) {
if (e instanceof NarrowError) return { error: e.code }
throw e
}
},
memory: (c) => {
const r = applyOps(c.target as Target, c.entries as string[], c.ops as Op[], c.limit as number)
return { ok: r.ok, entries: r.ok ? r.after : c.entries, message: r.message, usage: r.usage, shown: Boolean(r.entries) }
},
jobs: (c) => jobNote(c as unknown as NoteCase),
// The instruction chain (filesystem.md): the CLI's own walk, with the case's files and grants as
// what it may read.
instructions: (c) => {
const files = new Set(c.files as string[])
return instructionChain(c.boundary as string, c.start as string, (p) => files.has(p) && readableUnder(c.grants as Record<string, string> | undefined, p))
},
library_scope: (c) => resolveBase(c.bases as { id: string; name: string }[], c.base as string | null),
toolcall_index: (c) => {
// The deltas as the OpenAI stream carries them: `function.name` and `function.arguments`.
const acc = new ToolCallAccumulator()
acc.feed((c.deltas as Record<string, unknown>[]).map(({ index, id, name, arguments: args }) => ({ index, id, function: { name, arguments: args } })))
return acc.result().map((p) => ({ id: p.id, name: p.name, arguments: p.args }))
},
envelope: (c) => {
// planPatch's logic without the filesystem: every file followed through the patch in memory.
const files = new Map<string, string | null>(Object.entries(c.files as Record<string, string>))
try {
for (const h of parse(c.patch as string)) {
const text = files.get(h.path) ?? null
if (h.type === "add") {
if (text !== null) throw new Error(`${h.path} already exists; use Update File to change it.`)
files.set(h.path, h.contents.endsWith("\n") ? h.contents : h.contents + "\n")
} else if (h.type === "delete") {
if (text === null) throw new Error(`${h.path} does not exist.`)
files.set(h.path, null)
} else {
if (text === null) throw new Error(`${h.path} does not exist.`)
const next = derive(h.path, h.chunks, text)
if (h.movePath) files.set(h.path, null)
files.set(h.movePath ?? h.path, joinBom(next.content, next.bom))
}
}
return { files: Object.fromEntries([...files].sort(([a], [b]) => a.localeCompare(b))) }
} catch (e) {
return { error: (e as Error).message }
}
},
}