Files
oh-my-opencode/src/hooks/atlas/final-wave-approval-gate.test.ts
T
YeonGyu-Kim def44338ff refactor(models): bump claude-opus-4-6 to claude-opus-4-7 across fallback chains, categories, and hooks
Updates the canonical Anthropic Opus model in every fallback chain
(sisyphus, oracle, prometheus, metis, momus, visual-engineering,
ultrabrain, deep, artistry, unspecified-high), the unspecified-high
category default, the think-mode HIGH_VARIANT_MAP, the Claude Code
alias map, the claude-thinking legacy alias, the context-limit GA
regex, and event.ts fallback strings.

Widens supportsCachedAnthropicLimit to accept both claude-*-4-6 and
claude-*-4-7 so the 1M context cache still applies across the bump.

Regenerates the bundled model-capabilities snapshot from models.dev
and the model-fallback snapshot to match the new source output.
2026-04-17 14:51:52 +09:00

235 lines
7.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, mock, test, afterAll } from "bun:test"
import { randomUUID } from "node:crypto"
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { createOpencodeClient } from "@opencode-ai/sdk"
import type { AssistantMessage, Session } from "@opencode-ai/sdk"
import type { BoulderState } from "../../features/boulder-state"
import { clearBoulderState, writeBoulderState } from "../../features/boulder-state"
const TEST_STORAGE_ROOT = join(tmpdir(), `atlas-final-wave-storage-${randomUUID()}`)
const TEST_MESSAGE_STORAGE = join(TEST_STORAGE_ROOT, "message")
const TEST_PART_STORAGE = join(TEST_STORAGE_ROOT, "part")
mock.module("../../features/hook-message-injector/constants", () => ({
OPENCODE_STORAGE: TEST_STORAGE_ROOT,
MESSAGE_STORAGE: TEST_MESSAGE_STORAGE,
PART_STORAGE: TEST_PART_STORAGE,
}))
mock.module("../../shared/opencode-message-dir", () => ({
getMessageDir: (sessionID: string) => {
const directoryPath = join(TEST_MESSAGE_STORAGE, sessionID)
return existsSync(directoryPath) ? directoryPath : null
},
}))
mock.module("../../shared/opencode-storage-detection", () => ({
isSqliteBackend: () => false,
}))
afterAll(() => { mock.restore() })
const { createAtlasHook } = await import("./index")
const { MESSAGE_STORAGE } = await import("../../features/hook-message-injector")
type AtlasHookContext = Parameters<typeof createAtlasHook>[0]
type PromptMock = ReturnType<typeof mock>
describe("Atlas final verification approval gate", () => {
let testDirectory = ""
function createMockPluginInput(): AtlasHookContext & { _promptMock: PromptMock } {
const client = createOpencodeClient({ baseUrl: "http://localhost" })
const promptMock = mock((input: unknown) => input)
Reflect.set(client.session, "prompt", async (input: unknown) => {
promptMock(input)
return {
data: { info: {} as AssistantMessage, parts: [] },
request: new Request("http://localhost/session/prompt"),
response: new Response(),
}
})
Reflect.set(client.session, "promptAsync", async (input: unknown) => {
promptMock(input)
return {
data: undefined,
request: new Request("http://localhost/session/prompt_async"),
response: new Response(),
}
})
Reflect.set(client.session, "get", async ({ path }: { path: { id: string } }) => {
const parentID = path.id === "ses_final_wave_review"
? "atlas-final-wave-session"
: path.id === "ses_feature_task"
? "atlas-non-final-session"
: "main-session-123"
return {
data: {
id: path.id,
parentID,
} as Session,
request: new Request(`http://localhost/session/${path.id}`),
response: new Response(),
}
})
return {
directory: testDirectory,
project: {} as AtlasHookContext["project"],
worktree: testDirectory,
serverUrl: new URL("http://localhost"),
$: {} as AtlasHookContext["$"],
client,
_promptMock: promptMock,
}
}
function setupMessageStorage(sessionID: string): void {
const messageDirectory = join(MESSAGE_STORAGE, sessionID)
if (!existsSync(messageDirectory)) {
mkdirSync(messageDirectory, { recursive: true })
}
writeFileSync(
join(messageDirectory, "msg_test001.json"),
JSON.stringify({
agent: "atlas",
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}),
)
}
function cleanupMessageStorage(sessionID: string): void {
const messageDirectory = join(MESSAGE_STORAGE, sessionID)
if (existsSync(messageDirectory)) {
rmSync(messageDirectory, { recursive: true, force: true })
}
}
beforeEach(() => {
testDirectory = join(tmpdir(), `atlas-final-wave-test-${randomUUID()}`)
mkdirSync(join(testDirectory, ".sisyphus"), { recursive: true })
clearBoulderState(testDirectory)
})
afterEach(() => {
clearBoulderState(testDirectory)
if (existsSync(testDirectory)) {
rmSync(testDirectory, { recursive: true, force: true })
}
})
test("waits for explicit user approval after the last final-wave approval arrives", async () => {
// given
const sessionID = "atlas-final-wave-session"
setupMessageStorage(sessionID)
const planPath = join(testDirectory, "final-wave-plan.md")
writeFileSync(
planPath,
`# Plan
## TODOs
- [x] 1. Ship the implementation
## Final Verification Wave (MANDATORY - after ALL implementation tasks)
- [x] F1. **Plan Compliance Audit** - \`oracle\`
- [x] F2. **Code Quality Review** - \`unspecified-high\`
- [x] F3. **Real Manual QA** - \`unspecified-high\`
- [ ] F4. **Scope Fidelity Check** - \`deep\`
`,
)
const state: BoulderState = {
active_plan: planPath,
started_at: "2026-01-02T10:00:00Z",
session_ids: [sessionID],
plan_name: "final-wave-plan",
agent: "atlas",
}
writeBoulderState(testDirectory, state)
const mockInput = createMockPluginInput()
const hook = createAtlasHook(mockInput)
const toolOutput = {
title: "Sisyphus Task",
output: `Tasks [4/4 compliant] | Contamination [CLEAN] | Unaccounted [CLEAN] | VERDICT: APPROVE
<task_metadata>
session_id: ses_final_wave_review
</task_metadata>`,
metadata: {},
}
// when
await hook["tool.execute.after"]({ tool: "task", sessionID }, toolOutput)
await hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
// then
expect(toolOutput.output).toContain("FINAL WAVE APPROVAL GATE")
expect(toolOutput.output).toContain("explicit user approval")
expect(toolOutput.output).not.toContain("STEP 8: PROCEED TO NEXT TASK")
expect(mockInput._promptMock).not.toHaveBeenCalled()
cleanupMessageStorage(sessionID)
})
test("keeps normal auto-continue instructions for non-final tasks", async () => {
// given
const sessionID = "atlas-non-final-session"
setupMessageStorage(sessionID)
const planPath = join(testDirectory, "implementation-plan.md")
writeFileSync(
planPath,
`# Plan
## TODOs
- [x] 1. Setup
- [ ] 2. Implement feature
## Final Verification Wave (MANDATORY - after ALL implementation tasks)
- [ ] F1. **Plan Compliance Audit** - \`oracle\`
- [ ] F2. **Code Quality Review** - \`unspecified-high\`
- [ ] F3. **Real Manual QA** - \`unspecified-high\`
- [ ] F4. **Scope Fidelity Check** - \`deep\`
`,
)
const state: BoulderState = {
active_plan: planPath,
started_at: "2026-01-02T10:00:00Z",
session_ids: [sessionID],
plan_name: "implementation-plan",
agent: "atlas",
}
writeBoulderState(testDirectory, state)
const hook = createAtlasHook(createMockPluginInput())
const toolOutput = {
title: "Sisyphus Task",
output: `Implementation finished successfully
<task_metadata>
session_id: ses_feature_task
</task_metadata>`,
metadata: {},
}
// when
await hook["tool.execute.after"]({ tool: "task", sessionID }, toolOutput)
// then
expect(toolOutput.output).toContain("COMPLETION GATE")
expect(toolOutput.output).toContain("STEP 8: PROCEED TO NEXT TASK")
expect(toolOutput.output).not.toContain("FINAL WAVE APPROVAL GATE")
cleanupMessageStorage(sessionID)
})
})