diff --git a/docs/docs/configure/agents.md b/docs/docs/configure/agents.md index dc813bd3b..e55b63bb1 100644 --- a/docs/docs/configure/agents.md +++ b/docs/docs/configure/agents.md @@ -12,7 +12,7 @@ Agents define different AI personas with specific models, prompts, permissions, | Agent | Description | Access Level | |-------|------------|-------------| | `builder` | Create and modify dbt models, SQL pipelines, and data transformations | Full read/write. SQL mutations prompt for approval. | -| `analyst` | Explore data, run SELECT queries, inspect schemas, generate insights | Read-only (enforced). SQL writes denied. Safe bash commands auto-allowed. | +| `analyst` | Answer questions about your data — explore it, run SELECT queries, inspect schemas, generate insights | Read-only (enforced). SQL writes denied. Safe bash commands auto-allowed. | | `plan` | Plan before acting — restricted to planning files only | Minimal — no edits, no bash, no SQL | ### Builder @@ -21,7 +21,7 @@ Full access mode. Can read/write files, run any bash command (with approval), ex ### Analyst -Truly read-only mode for safe data exploration: +The agent for asking questions about your data. Truly read-only mode for safe data exploration: - **File access**: Read, grep, glob — no prompts - **SQL**: SELECT queries execute freely. Write queries are denied (not prompted — blocked entirely) diff --git a/docs/docs/data-engineering/agent-modes.md b/docs/docs/data-engineering/agent-modes.md index 8a87c88cc..6e8e8333b 100644 --- a/docs/docs/data-engineering/agent-modes.md +++ b/docs/docs/data-engineering/agent-modes.md @@ -10,7 +10,7 @@ altimate runs in one of three specialized modes. Each mode has different permiss | Mode | Access | Purpose | |---|---|---| | **Builder** | Read/Write | Create and modify data pipelines | -| **Analyst** | Read-only | Safe exploration and cost analysis | +| **Analyst** | Read-only | Answering questions about your data — safe exploration and cost analysis | | **Plan** | Minimal | Planning only, no edits or execution | ## Builder @@ -78,7 +78,7 @@ I'll create a staging model with proper typing, deduplication, and column naming ## Analyst -**Read-only access. Safe for production environments.** +**Read-only access. The agent for asking questions about your data and exploring it safely — use it whenever you just want answers, not changes. Safe for production environments.** ```bash altimate --agent analyst diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index 04a07d9e9..bd8f9a75c 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -143,7 +143,7 @@ altimate offers specialized agent modes for different workflows: | What do you want to do? | Use this agent mode | |---|---| -| Analyzing data without risk of changes | **Analyst** for read-only queries, cost analysis, data profiling. SQL writes are blocked entirely. | +| Asking questions about your data, or analyzing it without risk of changes | **Analyst** for read-only queries, cost analysis, data profiling. SQL writes are blocked entirely. | | Building or generating dbt models | **Builder** for model scaffolding, SQL generation, ref() wiring. SQL writes prompt for approval. | | Planning before acting | **Plan** for outlining an approach before switching to builder to execute it | diff --git a/docs/docs/getting-started/quickstart.md b/docs/docs/getting-started/quickstart.md index e7ec4bfd4..2cf25b4c0 100644 --- a/docs/docs/getting-started/quickstart.md +++ b/docs/docs/getting-started/quickstart.md @@ -150,7 +150,7 @@ altimate ships with specialized agent modes, each with its own tool permissions: | Mode | Access | Use when you want to... | | ----------- | ---------- | ------------------------------------------------------------------------------ | | **Builder** | Read/Write | Create and modify SQL, dbt models, pipelines. SQL writes prompt for approval. | -| **Analyst** | Read-only | Explore production data safely, run cost analysis. SQL writes denied entirely. | +| **Analyst** | Read-only | Ask questions about your data, explore production data safely, run cost analysis. SQL writes denied entirely. | | **Plan** | Minimal | Plan an approach before switching to builder to execute it | Switch modes in the TUI: diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 5b669b0e9..621e45c3e 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -21,9 +21,9 @@ import PROMPT_TITLE from "./prompt/title.txt" // PromptProfiles.PROMPT_BUILDER is assembled from core + pack fragments (byte-identical // to the former builder.txt — see profiles.ts and test/altimate/prompt-profiles.test.ts) import { PromptProfiles } from "../altimate/prompts/profiles" -import { Flag } from "@/flag/flag" import PROMPT_ANALYST from "../altimate/prompts/analyst.txt" import PROMPT_REVIEWER from "../altimate/prompts/reviewer.txt" +import { Log } from "../util/log" // altimate_change end import { Permission } from "@/permission" import { mergeDeep, pipe, sortBy, values } from "remeda" @@ -222,6 +222,12 @@ export const layer = Layer.effect( const userWithSafety = Permission.merge(user, safetyDenials) // altimate_change end + // altimate_change start — one-time warning state for the removed data-qa + // default-agent migration (see defaultInfo() below) + const log = Log.create({ service: "agent" }) + let warnedRemovedDataQaDefault = false + // altimate_change end + const agents: Record = { // altimate_change start - 3 modes: builder, analyst, plan (replaces upstream single "build" agent) builder: { @@ -318,43 +324,6 @@ export const layer = Layer.effect( mode: "primary", native: true, }, - // Opt-in data-qa profile (workload-adaptive harness PR 1): the invariant - // core + skills catalogue + teammate training — omits the Pre-Execution - // Protocol (sql-guard) pack and the build-oriented packs (dbt-ops, - // dbt-verify, dbt-workflow, pitfalls, self-review, finish). Ships the - // same DEFAULT permission ruleset as builder; per-agent config - // overrides apply per agent, as for every agent. Registered on any of - // three explicit opt-ins: ALTIMATE_DATA_QA_PROFILE=1/true, an - // `agent: {"data-qa": {...}}` entry in config (which then overlays the - // native profile via the standard merge below), or `default_agent: - // "data-qa"` (naming it as the default is itself an explicit - // selection — without this arm, defaultInfo() would throw "default - // agent \"data-qa\" not found" instead of registering it). Nothing - // selects it implicitly otherwise; the default agent stays builder. - ...(Flag.truthyEnv("ALTIMATE_DATA_QA_PROFILE") || - cfg.agent?.["data-qa"] != null || - cfg.default_agent === "data-qa" - ? { - "data-qa": { - name: "data-qa", - description: - "Opt-in data Q&A profile: builder toolset with a slimmer prompt (no dbt build protocols).", - prompt: PromptProfiles.PROMPT_DATA_QA, - options: {}, - permission: Permission.merge( - defaults, - Permission.fromConfig({ - question: "allow", - plan_enter: "allow", - sql_execute_write: "ask", - }), - userWithSafety, - ), - mode: "primary", - native: true, - } satisfies Info, - } - : {}), // reviewer agent: dbt PR review verdict engine reviewer: { name: "reviewer", @@ -630,7 +599,28 @@ export const layer = Layer.effect( const defaultInfo = Effect.fnUntraced(function* () { const c = yield* config.get() if (c.default_agent) { - const agent = agents[c.default_agent] + // altimate_change start — migrate the removed data-qa default to analyst + let agent = agents[c.default_agent] + // #1217 let `default_agent: "data-qa"` alone (no matching `agent.data-qa` + // config entry) opt into the native data-qa profile. That profile is now + // removed, so a persisted `default_agent: "data-qa"` would otherwise throw + // here and strand every call site that resolves the default agent + // (session/prompt.ts, the session HTTP routes, ACP) — upgrading users could + // no longer start an ordinary default-agent session. Fall back to `analyst`, + // the documented agent for read-only data questions, with a one-time warning + // instead of a hard failure. A user who separately defines their own + // `agent.data-qa` config entry is unaffected — `agents[c.default_agent]` + // already resolves to that legitimate custom agent above. + if (!agent && c.default_agent === "data-qa") { + if (!warnedRemovedDataQaDefault) { + warnedRemovedDataQaDefault = true + log.warn( + 'the "data-qa" agent was removed; defaulting to "analyst" for read-only data questions — set default_agent to override', + ) + } + agent = agents["analyst"] + } + // altimate_change end if (!agent) throw new Error(`default agent "${c.default_agent}" not found`) if (agent.mode === "subagent") throw new Error(`default agent "${c.default_agent}" is a subagent`) if (agent.hidden === true) throw new Error(`default agent "${c.default_agent}" is hidden`) diff --git a/packages/opencode/src/altimate/prompts/profiles.ts b/packages/opencode/src/altimate/prompts/profiles.ts index 393b1c0e2..3e6e968b5 100644 --- a/packages/opencode/src/altimate/prompts/profiles.ts +++ b/packages/opencode/src/altimate/prompts/profiles.ts @@ -60,24 +60,11 @@ export const BUILDER_PROFILE: readonly FragmentName[] = [ "finish", ] -/** - * Opt-in data-qa profile: the invariant core + skills catalogue + teammate - * training. Relative to builder it omits the Pre-Execution Protocol - * (sql-guard) pack and the build-oriented packs: dbt-ops, dbt-verify, - * dbt-workflow, pitfalls, self-review, finish. Basis: an internal 540-trial - * paired prompt ablation on a public benchmark found removing these on data-QA - * workloads had no score effect (permutation p=0.74) and cut wall clock 27.6%. - * Nothing selects this profile automatically — see `agent.ts` - * (ALTIMATE_DATA_QA_PROFILE gate). - */ -export const DATA_QA_PROFILE: readonly FragmentName[] = ["core", "legacy-skills-catalogue", "core-training"] - export function assemble(profile: readonly FragmentName[]): string { return profile.map((name) => FRAGMENTS[name]).join("") } export const PROMPT_BUILDER = assemble(BUILDER_PROFILE) -export const PROMPT_DATA_QA = assemble(DATA_QA_PROFILE) export * as PromptProfiles from "./profiles" // altimate_change end diff --git a/packages/opencode/src/session/termination.ts b/packages/opencode/src/session/termination.ts index f72561e86..f3de2a0e8 100644 --- a/packages/opencode/src/session/termination.ts +++ b/packages/opencode/src/session/termination.ts @@ -174,7 +174,7 @@ export function explicitDoneStop(input: { } /** - * Run-mode completion instruction for builder and builder-derived agents. + * Run-mode completion instruction for builder. * * This wording lived in `builder.txt`, but builder is a PRIMARY agent, so a * static instruction there also governs interactive chat — where nothing @@ -183,10 +183,9 @@ export function explicitDoneStop(input: { * is only consumed by the run-mode accounting path. * * Injected only in run mode, and only for the agents named in - * COMPLETION_CONTRACT_AGENTS below (builder, plus the opt-in data-qa profile - * — see that set for why). For builder alone this is byte-identical to the - * previous run-mode behaviour, when builder was the only prompt carrying it. - * Prompt-visible text — changes need extra review. + * COMPLETION_CONTRACT_AGENTS below (builder). Byte-identical to builder's + * original run-mode behaviour, from when this text was still static in + * `builder.txt`. Prompt-visible text — changes need extra review. */ export const RUN_MODE_COMPLETION_INSTRUCTION = "**Signal completion explicitly**: only after every requirement above is satisfied, end your final " + @@ -195,11 +194,9 @@ export const RUN_MODE_COMPLETION_INSTRUCTION = /** * Agents that receive the run-mode completion-token contract. builder is the - * historical carrier; data-qa is the builder-derived opt-in profile (its - * headless runs need a termination contract without inheriting the dbt - * finish-build ritual, which lives in the prompt packs it omits). + * historical (and currently sole) carrier. */ -const COMPLETION_CONTRACT_AGENTS = new Set(["builder", "data-qa"]) +const COMPLETION_CONTRACT_AGENTS = new Set(["builder"]) /** The sole gate for injecting the completion-token contract into a prompt. */ export function completionInstruction(input: { runMode: boolean; agent: string }): string | undefined { diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index f5806da81..f6c928b0d 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -285,6 +285,42 @@ it.instance( }, ) +it.instance( + // Regression for the data-qa agent removal: #1217 shipped `data-qa` as a + // config-registerable native profile, so an upgrading user's config may + // still carry a leftover `agent: {"data-qa": {...}}` block after this + // profile was dropped. That entry now takes the exact same path as any + // other user-defined agent name not registered natively (see "custom agent + // from config creates new agent" above) — it resolves to a plain generic + // agent (mode "all", native: false, no `data-qa`-specific prompt), it does + // not crash, and it does not resurrect the removed profile's permissions. + "a leftover config `agent.data-qa` entry resolves as a harmless generic custom agent, not a crash", + () => + Effect.gen(function* () { + const dataQa = yield* load((svc) => svc.get("data-qa")) + expect(dataQa).toBeDefined() + expect(dataQa?.native).toBe(false) + expect(dataQa?.mode).toBe("all") + expect(dataQa?.description).toBe("leftover config from an old data-qa opt-in") + // No native data-qa prompt exists anymore to resurrect. + expect(dataQa?.prompt).toBeUndefined() + // The rest of the registry is unaffected. + const builder = yield* load((svc) => svc.get("builder")) + expect(builder?.native).toBe(true) + const fallback = yield* load((svc) => svc.defaultAgent()) + expect(fallback).toBe("builder") + }), + { + config: { + agent: { + "data-qa": { + description: "leftover config from an old data-qa opt-in", + }, + }, + }, + }, +) + it.instance( "agent disable removes agent from list", () => @@ -775,6 +811,55 @@ it.instance( }, ) +it.instance( + // Regression for the data-qa agent removal: #1217 let `default_agent: + // "data-qa"` alone (no `agent.data-qa` config entry) opt into the native + // data-qa profile. An upgrading user's persisted config can still set + // this. Unlike a generic typo'd/never-valid default_agent (which still + // throws — see the test above), this specific removed name must degrade + // to a working session instead of stranding the user: it resolves to + // `analyst`, the documented replacement for read-only data questions. + "defaultAgent migrates a persisted default_agent: \"data-qa\" (no matching agent entry) to analyst instead of throwing", + () => + Effect.gen(function* () { + const agent = yield* load((svc) => svc.defaultAgent()) + expect(agent).toBe("analyst") + const info = yield* load((svc) => svc.defaultInfo()) + expect(info?.name).toBe("analyst") + expect(info?.native).toBe(true) + }), + { + config: { + default_agent: "data-qa", + }, + }, +) + +it.instance( + // A user who ALSO defines their own `agent.data-qa` config entry gets + // that legitimate custom agent as their default — the migration above + // only kicks in when no agent actually resolves for the name. + "defaultAgent respects an explicit agent.data-qa config entry over the analyst migration", + () => + Effect.gen(function* () { + const agent = yield* load((svc) => svc.defaultAgent()) + expect(agent).toBe("data-qa") + const info = yield* load((svc) => svc.defaultInfo()) + expect(info?.native).toBe(false) + expect(info?.description).toBe("my own data-qa agent") + }), + { + config: { + default_agent: "data-qa", + agent: { + "data-qa": { + description: "my own data-qa agent", + }, + }, + }, + }, +) + it.instance( "defaultAgent returns plan when build is disabled and default_agent not set", () => diff --git a/packages/opencode/test/agent/data-qa-profile.test.ts b/packages/opencode/test/agent/data-qa-profile.test.ts deleted file mode 100644 index e88c153a9..000000000 --- a/packages/opencode/test/agent/data-qa-profile.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { afterEach, beforeEach, expect } from "bun:test" -import { Effect, Layer } from "effect" -import { disposeAllInstances } from "../fixture/fixture" -import { testEffect } from "../lib/effect" -import { Agent } from "../../src/agent/agent" -import { Auth } from "../../src/auth" -import { Config } from "../../src/config/config" -import { RuntimeFlags } from "../../src/effect/runtime-flags" -import { Plugin } from "../../src/plugin" -import { Provider } from "../../src/provider/provider" -import { Skill } from "../../src/skill" -import { LocationServiceMap } from "@opencode-ai/core/location-layer" -import { PromptProfiles } from "../../src/altimate/prompts/profiles" -import { EXPECTED_SHA256, sha256 } from "../altimate/prompt-identity" - -// Registry-level tests for the opt-in data-qa profile (workload-adaptive -// harness PR 1). Exercises the REAL Agent service (config load + agent list -// build) — the same code path `session/llm.ts` reads `input.agent.prompt` from. - -const agentLayer = () => - Agent.layer.pipe( - Layer.provide(Plugin.defaultLayer), - Layer.provide(Provider.defaultLayer), - Layer.provide(Auth.defaultLayer), - Layer.provide(Config.defaultLayer), - Layer.provide(Skill.defaultLayer), - Layer.provide(LocationServiceMap.layer), - Layer.provide(RuntimeFlags.layer({})), - ) - -const it = testEffect(agentLayer()) - -function load(fn: (svc: Agent.Interface) => Effect.Effect) { - return Agent.Service.use(fn) -} - -const savedEnv = process.env["ALTIMATE_DATA_QA_PROFILE"] - -beforeEach(() => { - delete process.env["ALTIMATE_DATA_QA_PROFILE"] -}) - -afterEach(async () => { - if (savedEnv === undefined) delete process.env["ALTIMATE_DATA_QA_PROFILE"] - else process.env["ALTIMATE_DATA_QA_PROFILE"] = savedEnv - await disposeAllInstances() -}) - -it.instance("with no selection mechanism engaged, data-qa does not exist and builder carries the pinned bytes", () => - Effect.gen(function* () { - const agents = yield* load((svc) => svc.list()) - expect(agents.map((a) => a.name)).not.toContain("data-qa") - const dataQa = yield* load((svc) => svc.get("data-qa")) - expect(dataQa).toBeUndefined() - // The default profile the product actually serves is byte-identical to the - // pre-split builder.txt. - const builder = yield* load((svc) => svc.get("builder")) - expect(builder?.prompt).toBe(PromptProfiles.PROMPT_BUILDER) - expect(sha256(builder?.prompt ?? "")).toBe(EXPECTED_SHA256) - }), -) - -it.instance("ALTIMATE_DATA_QA_PROFILE=1 registers data-qa as an explicitly selectable agent", () => - Effect.gen(function* () { - process.env["ALTIMATE_DATA_QA_PROFILE"] = "1" - const dataQa = yield* load((svc) => svc.get("data-qa")) - expect(dataQa).toBeDefined() - expect(dataQa?.mode).toBe("primary") - expect(dataQa?.prompt).toBe(PromptProfiles.PROMPT_DATA_QA) - // Opt-in registration must not disturb the default profile. - const builder = yield* load((svc) => svc.get("builder")) - expect(sha256(builder?.prompt ?? "")).toBe(EXPECTED_SHA256) - // The default agent remains builder even with the flag set — data-qa is - // never selected implicitly. - const fallback = yield* load((svc) => svc.defaultAgent()) - expect(fallback).toBe("builder") - }), -) - -it.instance( - "an explicit agent config entry for data-qa also opts in (native profile + config overlay)", - () => - Effect.gen(function* () { - // No env flag — the config entry itself is the explicit opt-in. The - // native profile registers and the standard config merge overlays it, so - // the user gets the real data-qa prompt rather than a bare custom agent. - const dataQa = yield* load((svc) => svc.get("data-qa")) - expect(dataQa).toBeDefined() - expect(dataQa?.native).toBe(true) - expect(dataQa?.prompt).toBe(PromptProfiles.PROMPT_DATA_QA) - // Still nothing implicit: the default agent remains builder. - const fallback = yield* load((svc) => svc.defaultAgent()) - expect(fallback).toBe("builder") - }), - { - config: { - agent: { - "data-qa": { - description: "opted in via config", - }, - }, - }, - }, -) - -it.instance( - "default_agent: \"data-qa\" with no flag and no agent entry also opts in and resolves as default", - () => - Effect.gen(function* () { - // No env flag, no `agent.data-qa` entry — naming data-qa as the - // configured default is itself the explicit opt-in. Without this path - // registration is skipped and defaultInfo() throws `default agent - // "data-qa" not found` (Codex/cubic finding, agent.ts:331). - const dataQa = yield* load((svc) => svc.get("data-qa")) - expect(dataQa).toBeDefined() - expect(dataQa?.native).toBe(true) - expect(dataQa?.prompt).toBe(PromptProfiles.PROMPT_DATA_QA) - const fallback = yield* load((svc) => svc.defaultAgent()) - expect(fallback).toBe("data-qa") - }), - { - config: { - default_agent: "data-qa", - }, - }, -) diff --git a/packages/opencode/test/altimate/prompt-identity.ts b/packages/opencode/test/altimate/prompt-identity.ts index dfce5d803..0e56825a9 100644 --- a/packages/opencode/test/altimate/prompt-identity.ts +++ b/packages/opencode/test/altimate/prompt-identity.ts @@ -1,7 +1,6 @@ // Single source of truth for the builder-prompt byte-identity pin -// (workload-adaptive harness PR 1). Imported by prompt-profiles.test.ts, -// agent/data-qa-profile.test.ts, and the subprocess hash helper so the pin can -// never drift between call sites. +// (workload-adaptive harness PR 1). Imported by prompt-profiles.test.ts and +// the subprocess hash helper so the pin can never drift between call sites. // // EXPECTED_SHA256 / EXPECTED_BYTES describe the pre-split monolithic // `src/altimate/prompts/builder.txt` as of the commit that removed it diff --git a/packages/opencode/test/altimate/prompt-profiles.test.ts b/packages/opencode/test/altimate/prompt-profiles.test.ts index 7ab05ce13..f85b37f1c 100644 --- a/packages/opencode/test/altimate/prompt-profiles.test.ts +++ b/packages/opencode/test/altimate/prompt-profiles.test.ts @@ -5,7 +5,7 @@ import fs from "fs" import { PromptProfiles } from "../../src/altimate/prompts/profiles" import { EXPECTED_BYTES, EXPECTED_SHA256, sha256 } from "./prompt-identity" -const { assemble, BUILDER_PROFILE, DATA_QA_PROFILE, FRAGMENTS, PROMPT_BUILDER, PROMPT_DATA_QA } = PromptProfiles +const { assemble, BUILDER_PROFILE, FRAGMENTS, PROMPT_BUILDER } = PromptProfiles // The byte-identity gate for the workload-adaptive harness PR 1 (compile-time // split of builder.txt into core + packs). The assembled default profile must @@ -37,9 +37,7 @@ describe("builder profile byte identity", () => { // Fragments carry their own trailing newline; profiles join with "". expect(text.endsWith("\n"), `fragment ${name} must end with a newline`).toBe(true) } - for (const profile of [BUILDER_PROFILE, DATA_QA_PROFILE]) { - expect(new Set(profile).size).toBe(profile.length) - } + expect(new Set(BUILDER_PROFILE).size).toBe(BUILDER_PROFILE.length) // The default profile uses every fragment exactly once (the split is total). expect([...BUILDER_PROFILE].map(String).sort()).toEqual(Object.keys(FRAGMENTS).sort()) }) @@ -77,39 +75,3 @@ describe("assembly determinism across processes and environments", () => { } }, 30_000) }) - -describe("data-qa profile composition", () => { - test("omits the dbt-specific packs and the Pre-Execution Protocol pack", () => { - // Omitted pack section headers must be absent. - expect(PROMPT_DATA_QA).not.toContain("## Pre-Execution Protocol") - expect(PROMPT_DATA_QA).not.toContain("## dbt Operations") - expect(PROMPT_DATA_QA).not.toContain("## dbt Verification Workflow") - expect(PROMPT_DATA_QA).not.toContain("## Workflow\n") - expect(PROMPT_DATA_QA).not.toContain("## Common Pitfalls") - expect(PROMPT_DATA_QA).not.toContain("## Self-Review Before Completion") - expect(PROMPT_DATA_QA).not.toContain("## Finish Protocol") - // Everything else (core identity + principles, skills catalogue, teammate - // training) must be present. - expect(PROMPT_DATA_QA).toContain("## Principles") - expect(PROMPT_DATA_QA).toContain("**Understand before writing**") - expect(PROMPT_DATA_QA).toContain("## Skills — When to Invoke") - expect(PROMPT_DATA_QA).toContain("## Proactive Skill Invocation") - expect(PROMPT_DATA_QA).toContain("## Teammate Training") - }) - - test("is strictly a subsequence of the builder profile (subtractive, nothing new)", () => { - expect(DATA_QA_PROFILE.every((name) => BUILDER_PROFILE.includes(name))).toBe(true) - const order = DATA_QA_PROFILE.map((name) => BUILDER_PROFILE.indexOf(name)) - expect([...order].sort((x, y) => x - y)).toEqual(order) - for (const name of DATA_QA_PROFILE) { - expect(PROMPT_DATA_QA).toContain(FRAGMENTS[name]) - } - }) - - test("selecting data-qa cannot change the default profile bytes", () => { - // PROMPT_BUILDER and PROMPT_DATA_QA are independent constants; assembling - // one never mutates the other. - void assemble(DATA_QA_PROFILE) - expect(sha256(PROMPT_BUILDER)).toBe(EXPECTED_SHA256) - }) -}) diff --git a/packages/opencode/test/session/termination.test.ts b/packages/opencode/test/session/termination.test.ts index 7d7f1960b..13e2bdaba 100644 --- a/packages/opencode/test/session/termination.test.ts +++ b/packages/opencode/test/session/termination.test.ts @@ -263,10 +263,6 @@ describe("builder completion contract", () => { expect(instruction).toContain("Do not emit `DONE` while work or verification remains") expect(SessionTermination.completionInstruction({ runMode: false, agent: "builder" })).toBeUndefined() expect(SessionTermination.completionInstruction({ runMode: true, agent: "plan" })).toBeUndefined() - // The builder-derived opt-in data-qa profile is also covered: its headless - // runs need the termination contract (its prompt omits the finish pack). - expect(SessionTermination.completionInstruction({ runMode: true, agent: "data-qa" })).toBe(instruction) - expect(SessionTermination.completionInstruction({ runMode: false, agent: "data-qa" })).toBeUndefined() }) test("prompt assembly wires the contract to the run-mode flag", async () => {