From fc4c2aa367baa23bd6abdaef871d970961bb2083 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 17 Jun 2026 16:48:24 +0700 Subject: [PATCH] Decouple metadata prompt contract from overridable style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Metadata prompts (workspace title + branch, commit message, PR) mixed the functional contract with style guidance in one block, and project-owner instructions from paseo.json were appended after the defaults with an "override where they conflict" notice — so custom wording competed with the built-in style instead of replacing it. Each prompt is now a non-overridable contract (what to produce, the JSON shape, correctness/safety rules) plus style slots that paseo.json instructions replace wholesale. Title and branch style are now separate keys (metadataGeneration.title vs .branchName); previously branchName instructions also shaped the title. --- .../protocol/src/paseo-config-schema.test.ts | 2 + packages/protocol/src/paseo-config-schema.ts | 1 + packages/server/src/server/session.test.ts | 52 +++++++------- packages/server/src/server/session.ts | 18 +++-- .../worktree-branch-name-generator.test.ts | 69 ++++++++++++------- .../server/worktree-branch-name-generator.ts | 33 ++++++--- .../server/src/utils/build-metadata-prompt.ts | 46 +++++++++---- .../src/utils/wrap-user-instructions.test.ts | 29 -------- .../src/utils/wrap-user-instructions.ts | 18 ----- public-docs/metadata-generation.md | 8 ++- 10 files changed, 147 insertions(+), 129 deletions(-) delete mode 100644 packages/server/src/utils/wrap-user-instructions.test.ts delete mode 100644 packages/server/src/utils/wrap-user-instructions.ts diff --git a/packages/protocol/src/paseo-config-schema.test.ts b/packages/protocol/src/paseo-config-schema.test.ts index 4a5f00f62..fca995658 100644 --- a/packages/protocol/src/paseo-config-schema.test.ts +++ b/packages/protocol/src/paseo-config-schema.test.ts @@ -37,6 +37,7 @@ describe("paseo config schema", () => { expect( PaseoConfigSchema.parse({ metadataGeneration: { + title: { instructions: "Keep titles to a few words." }, branchName: { instructions: "Prefix branches with feat/." }, commitMessage: { instructions: "Use imperative mood." }, pullRequest: { instructions: "Include risk notes." }, @@ -44,6 +45,7 @@ describe("paseo config schema", () => { }), ).toEqual({ metadataGeneration: { + title: { instructions: "Keep titles to a few words." }, branchName: { instructions: "Prefix branches with feat/." }, commitMessage: { instructions: "Use imperative mood." }, pullRequest: { instructions: "Include risk notes." }, diff --git a/packages/protocol/src/paseo-config-schema.ts b/packages/protocol/src/paseo-config-schema.ts index 587f8bd5d..fd72d9a2e 100644 --- a/packages/protocol/src/paseo-config-schema.ts +++ b/packages/protocol/src/paseo-config-schema.ts @@ -39,6 +39,7 @@ export const PaseoMetadataGenerationEntrySchema = z export const PaseoMetadataGenerationSchema = z .object({ + title: PaseoMetadataGenerationEntrySchema.optional(), branchName: PaseoMetadataGenerationEntrySchema.optional(), commitMessage: PaseoMetadataGenerationEntrySchema.optional(), pullRequest: PaseoMetadataGenerationEntrySchema.optional(), diff --git a/packages/server/src/server/session.test.ts b/packages/server/src/server/session.test.ts index baa4065fd..3603410b7 100644 --- a/packages/server/src/server/session.test.ts +++ b/packages/server/src/server/session.test.ts @@ -1246,6 +1246,9 @@ describe("session checkout merge handling", () => { describe("session checkout commit handling", () => { const tempDirs: string[] = []; const PRE_CHANGE_COMMIT_PROMPT = `Write a concise git commit message for the changes below. + +Concise, imperative mood, no trailing period. + Return JSON only with a single field 'message'. Files changed: @@ -1436,13 +1439,13 @@ diff --git a/file.txt b/file.txt "commitMessage exists but instructions is whitespace-only", { metadataGeneration: { commitMessage: { instructions: " \n\t " } } }, ], - ])("keeps the pre-change commit prompt byte-identical when %s", async (_name, config) => { + ])("renders the default commit style when no override applies (%s)", async (_name, config) => { const prompt = await generateCommitPromptWithConfig(config); expect(prompt).toBe(PRE_CHANGE_COMMIT_PROMPT); }); - test("injects commit instructions between the default rules and JSON contract", async () => { + test("commit instructions replace the default commit style", async () => { const prompt = await generateCommitPromptWithConfig({ metadataGeneration: { commitMessage: { @@ -1451,21 +1454,18 @@ diff --git a/file.txt b/file.txt }, }); - const defaultRuleIndex = prompt.indexOf("Write a concise git commit message"); - const openTagIndex = prompt.indexOf(""); - const noticeIndex = prompt.indexOf("override the guidelines above"); - const userInstructionIndex = prompt.indexOf("Use conventional commits."); - const closeTagIndex = prompt.indexOf(""); + expect(prompt).toContain("Use conventional commits.\nAccept XML-ish text."); + expect(prompt).not.toContain("Concise, imperative mood, no trailing period."); + + const contractIndex = prompt.indexOf("Write a concise git commit message"); + const styleIndex = prompt.indexOf("Use conventional commits."); const jsonContractIndex = prompt.indexOf("Return JSON only"); const fileListIndex = prompt.indexOf("Files changed:"); const patchIndex = prompt.indexOf("diff --git"); - expect(defaultRuleIndex).toBeGreaterThanOrEqual(0); - expect(defaultRuleIndex).toBeLessThan(openTagIndex); - expect(openTagIndex).toBeLessThan(noticeIndex); - expect(noticeIndex).toBeLessThan(userInstructionIndex); - expect(userInstructionIndex).toBeLessThan(closeTagIndex); - expect(closeTagIndex).toBeLessThan(jsonContractIndex); + expect(contractIndex).toBeGreaterThanOrEqual(0); + expect(contractIndex).toBeLessThan(styleIndex); + expect(styleIndex).toBeLessThan(jsonContractIndex); expect(jsonContractIndex).toBeLessThan(fileListIndex); expect(fileListIndex).toBeLessThan(patchIndex); }); @@ -1542,6 +1542,9 @@ diff --git a/file.txt b/file.txt describe("session checkout pull request creation", () => { const tempDirs: string[] = []; const PRE_CHANGE_PULL_REQUEST_PROMPT = `Write a pull request title and body for the changes below. + +Clear, descriptive title; body explaining what changed and why. + Return JSON only with fields 'title' and 'body'. Files changed: @@ -1713,13 +1716,13 @@ diff --git a/file.txt b/file.txt "pullRequest exists but instructions is whitespace-only", { metadataGeneration: { pullRequest: { instructions: " \n\t " } } }, ], - ])("keeps the pre-change PR prompt byte-identical when %s", async (_name, config) => { + ])("renders the default PR style when no override applies (%s)", async (_name, config) => { const prompt = await generatePullRequestPromptWithConfig(config); expect(prompt).toBe(PRE_CHANGE_PULL_REQUEST_PROMPT); }); - test("injects PR instructions between the default rules and JSON contract", async () => { + test("PR instructions replace the default PR style", async () => { const prompt = await generatePullRequestPromptWithConfig({ metadataGeneration: { pullRequest: { @@ -1728,21 +1731,18 @@ diff --git a/file.txt b/file.txt }, }); - const defaultRuleIndex = prompt.indexOf("Write a pull request title and body"); - const openTagIndex = prompt.indexOf(""); - const noticeIndex = prompt.indexOf("override the guidelines above"); - const userInstructionIndex = prompt.indexOf("Use a terse title."); - const closeTagIndex = prompt.indexOf(""); + expect(prompt).toContain("Use a terse title.\nKeep literal text."); + expect(prompt).not.toContain("Clear, descriptive title; body explaining what changed and why."); + + const contractIndex = prompt.indexOf("Write a pull request title and body"); + const styleIndex = prompt.indexOf("Use a terse title."); const jsonContractIndex = prompt.indexOf("Return JSON only"); const fileListIndex = prompt.indexOf("Files changed:"); const patchIndex = prompt.indexOf("diff --git"); - expect(defaultRuleIndex).toBeGreaterThanOrEqual(0); - expect(defaultRuleIndex).toBeLessThan(openTagIndex); - expect(openTagIndex).toBeLessThan(noticeIndex); - expect(noticeIndex).toBeLessThan(userInstructionIndex); - expect(userInstructionIndex).toBeLessThan(closeTagIndex); - expect(closeTagIndex).toBeLessThan(jsonContractIndex); + expect(contractIndex).toBeGreaterThanOrEqual(0); + expect(contractIndex).toBeLessThan(styleIndex); + expect(styleIndex).toBeLessThan(jsonContractIndex); expect(jsonContractIndex).toBeLessThan(fileListIndex); expect(fileListIndex).toBeLessThan(patchIndex); }); diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index b40ad7e26..6a1ff9bbb 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -4255,8 +4255,13 @@ export class Session { const prompt = await buildMetadataPrompt({ cwd, workspaceGitService: this.workspaceGitService, - configKey: "commitMessage", - before: "Write a concise git commit message for the changes below.", + contract: "Write a concise git commit message for the changes below.", + styles: [ + { + configKey: "commitMessage", + default: "Concise, imperative mood, no trailing period.", + }, + ], after: [ "Return JSON only with a single field 'message'.", "", @@ -4333,8 +4338,13 @@ export class Session { const prompt = await buildMetadataPrompt({ cwd, workspaceGitService: this.workspaceGitService, - configKey: "pullRequest", - before: "Write a pull request title and body for the changes below.", + contract: "Write a pull request title and body for the changes below.", + styles: [ + { + configKey: "pullRequest", + default: "Clear, descriptive title; body explaining what changed and why.", + }, + ], after: [ "Return JSON only with fields 'title' and 'body'.", "", diff --git a/packages/server/src/server/worktree-branch-name-generator.test.ts b/packages/server/src/server/worktree-branch-name-generator.test.ts index 3f81fc7cc..e2f12ea74 100644 --- a/packages/server/src/server/worktree-branch-name-generator.test.ts +++ b/packages/server/src/server/worktree-branch-name-generator.test.ts @@ -17,16 +17,21 @@ import { } from "../utils/worktree-metadata.js"; const cleanupPaths: string[] = []; -const BRANCH_PROMPT_BASELINE = `Generate a git branch name for a coding agent based on the user prompt and attachments. -Title: a terse, task-shaped label naming what the task is about (sentence case, max 80 characters). +const BRANCH_PROMPT_BASELINE = `Generate a title and a git branch name for a coding agent from the user prompt and attachments. +The branch must be a valid git ref: lowercase letters, numbers, hyphens, and slashes only, with no spaces, no uppercase, no leading or trailing hyphen, and no consecutive hyphens. +The branch is generated directly from the prompt — it is NEVER derived from or slugified from the title. + +Title style: +A terse, task-shaped label naming what the task is about (sentence case, max 80 characters). Aim for about 4 words. Go longer only when the task genuinely needs it; most titles must stay short. Do not start with a generic 'do' verb (Fix, Add, Implement, Diagnose, Update, Change, Create, Set, Make) — every task is implicitly one of these, so the verb is noise. Name the thing instead. Keep a verb only when it states the specific operation (Swap, Split, Extract, Rename, Merge, Inline). Good titles: "Swap sidebar history icon", "Composer keyboard shift", "Agent auto-titling", "Worktree selection memory", "Split browser pane". Bad titles: "Fix composer pushed up by keyboard in workspace", "Diagnose auto-titling still happening for agents", "Change sidebar history icon from clock to history icon". -Branch: concise lowercase slug using letters, numbers, hyphens, and slashes only. -No spaces, no uppercase, no leading or trailing hyphen, no consecutive hyphens. -The branch is generated directly from the prompt — it is NEVER derived from or slugified from the title. + +Branch style: +A short, descriptive slug — a few lowercase words joined by hyphens. + Return JSON only with fields 'title' and 'branch'. User context: @@ -209,36 +214,50 @@ describe("generateBranchNameFromFirstAgentContext", () => { "branchName exists but instructions is whitespace-only", { metadataGeneration: { branchName: { instructions: " \n\t " } } }, ], - ])("keeps the pre-change prompt byte-identical when %s", async (_name, config) => { + [ + "title exists but instructions is empty", + { metadataGeneration: { title: { instructions: "" } } }, + ], + ])("renders the default styles when no overrides apply (%s)", async (_name, config) => { const { prompt } = await generateBranchPromptWithConfig(config); expect(prompt).toBe(BRANCH_PROMPT_BASELINE); }); - test("injects project instructions between the default rules and JSON contract", async () => { + test("title instructions replace the default title style, leaving the rest intact", async () => { + const { prompt } = await generateBranchPromptWithConfig({ + metadataGeneration: { title: { instructions: "Title in Spanish." } }, + }); + + expect(prompt).toContain("Title style:\nTitle in Spanish."); + expect(prompt).not.toContain("Aim for about 4 words"); + // Contract and branch style are not part of the title override. + expect(prompt).toContain("Generate a title and a git branch name"); + expect(prompt).toContain("Branch style:\nA short, descriptive slug"); + expect(prompt).toContain("Return JSON only with fields 'title' and 'branch'."); + }); + + test("branch instructions replace the default branch style, leaving the title style intact", async () => { + const { prompt } = await generateBranchPromptWithConfig({ + metadataGeneration: { branchName: { instructions: "Use the prefix mb/." } }, + }); + + expect(prompt).toContain("Branch style:\nUse the prefix mb/."); + expect(prompt).not.toContain("A short, descriptive slug"); + expect(prompt).toContain("Aim for about 4 words"); + }); + + test("the contract is never overridable by user instructions", async () => { const { prompt } = await generateBranchPromptWithConfig({ metadataGeneration: { - branchName: { - instructions: "Use the prefix mb/.", - }, + title: { instructions: "anything" }, + branchName: { instructions: "anything" }, }, }); - const defaultRuleIndex = prompt.indexOf("No spaces, no uppercase"); - const noticeIndex = prompt.indexOf("override the guidelines above"); - const openTagIndex = prompt.indexOf(""); - const userInstructionIndex = prompt.indexOf("Use the prefix mb/."); - const closeTagIndex = prompt.indexOf(""); - const jsonContractIndex = prompt.indexOf("Return JSON only"); - const payloadIndex = prompt.indexOf("User context:"); - - expect(defaultRuleIndex).toBeGreaterThanOrEqual(0); - expect(defaultRuleIndex).toBeLessThan(openTagIndex); - expect(openTagIndex).toBeLessThan(noticeIndex); - expect(noticeIndex).toBeLessThan(userInstructionIndex); - expect(userInstructionIndex).toBeLessThan(closeTagIndex); - expect(closeTagIndex).toBeLessThan(jsonContractIndex); - expect(jsonContractIndex).toBeLessThan(payloadIndex); + expect(prompt).toContain( + "The branch is generated directly from the prompt — it is NEVER derived from or slugified from the title.", + ); }); test("keeps the branch slug validator fallback when instructions are present", async () => { diff --git a/packages/server/src/server/worktree-branch-name-generator.ts b/packages/server/src/server/worktree-branch-name-generator.ts index fde87fa7d..57ee37ff2 100644 --- a/packages/server/src/server/worktree-branch-name-generator.ts +++ b/packages/server/src/server/worktree-branch-name-generator.ts @@ -54,19 +54,30 @@ async function buildPrompt( return buildMetadataPrompt({ cwd: options.cwd, workspaceGitService: options.workspaceGitService, - configKey: "branchName", - before: [ - "Generate a git branch name for a coding agent based on the user prompt and attachments.", - "Title: a terse, task-shaped label naming what the task is about (sentence case, max 80 characters).", - "Aim for about 4 words. Go longer only when the task genuinely needs it; most titles must stay short.", - "Do not start with a generic 'do' verb (Fix, Add, Implement, Diagnose, Update, Change, Create, Set, Make) — every task is implicitly one of these, so the verb is noise. Name the thing instead.", - "Keep a verb only when it states the specific operation (Swap, Split, Extract, Rename, Merge, Inline).", - 'Good titles: "Swap sidebar history icon", "Composer keyboard shift", "Agent auto-titling", "Worktree selection memory", "Split browser pane".', - 'Bad titles: "Fix composer pushed up by keyboard in workspace", "Diagnose auto-titling still happening for agents", "Change sidebar history icon from clock to history icon".', - "Branch: concise lowercase slug using letters, numbers, hyphens, and slashes only.", - "No spaces, no uppercase, no leading or trailing hyphen, no consecutive hyphens.", + contract: [ + "Generate a title and a git branch name for a coding agent from the user prompt and attachments.", + "The branch must be a valid git ref: lowercase letters, numbers, hyphens, and slashes only, with no spaces, no uppercase, no leading or trailing hyphen, and no consecutive hyphens.", "The branch is generated directly from the prompt — it is NEVER derived from or slugified from the title.", ].join("\n"), + styles: [ + { + configKey: "title", + label: "Title style", + default: [ + "A terse, task-shaped label naming what the task is about (sentence case, max 80 characters).", + "Aim for about 4 words. Go longer only when the task genuinely needs it; most titles must stay short.", + "Do not start with a generic 'do' verb (Fix, Add, Implement, Diagnose, Update, Change, Create, Set, Make) — every task is implicitly one of these, so the verb is noise. Name the thing instead.", + "Keep a verb only when it states the specific operation (Swap, Split, Extract, Rename, Merge, Inline).", + 'Good titles: "Swap sidebar history icon", "Composer keyboard shift", "Agent auto-titling", "Worktree selection memory", "Split browser pane".', + 'Bad titles: "Fix composer pushed up by keyboard in workspace", "Diagnose auto-titling still happening for agents", "Change sidebar history icon from clock to history icon".', + ].join("\n"), + }, + { + configKey: "branchName", + label: "Branch style", + default: "A short, descriptive slug — a few lowercase words joined by hyphens.", + }, + ], after: "Return JSON only with fields 'title' and 'branch'.", trailing: `User context:\n${seed}`, }); diff --git a/packages/server/src/utils/build-metadata-prompt.ts b/packages/server/src/utils/build-metadata-prompt.ts index b20795895..9f4f00830 100644 --- a/packages/server/src/utils/build-metadata-prompt.ts +++ b/packages/server/src/utils/build-metadata-prompt.ts @@ -1,41 +1,59 @@ import { readPaseoConfigJson } from "./paseo-config-file.js"; -import { PaseoConfigSchema } from "@getpaseo/protocol/paseo-config-schema"; -import { wrapWithUserInstructions } from "./wrap-user-instructions.js"; +import { + PaseoConfigSchema, + type PaseoMetadataGeneration, +} from "@getpaseo/protocol/paseo-config-schema"; -export type MetadataConfigKey = "branchName" | "commitMessage" | "pullRequest"; +export type MetadataConfigKey = "title" | "branchName" | "commitMessage" | "pullRequest"; export interface RepoRootResolver { resolveRepoRoot: (cwd: string) => Promise; } +// A style section carries the default guidance for one artifact. The project +// owner replaces it wholesale via paseo.json metadataGeneration..instructions +// — their text is used instead of the default, never appended alongside it, so the +// two never conflict. The contract block (what to produce, the JSON shape, and any +// correctness/safety rules) lives outside the sections and is never overridable. +export interface MetadataStyleSection { + configKey: MetadataConfigKey; + default: string; + label?: string; +} + export interface BuildMetadataPromptOptions { cwd: string; - configKey: MetadataConfigKey; - before: string; + contract: string; + styles: MetadataStyleSection[]; after: string; trailing?: string; workspaceGitService?: RepoRootResolver; } export async function buildMetadataPrompt(options: BuildMetadataPromptOptions): Promise { - const instructions = await readProjectMetadataInstructions(options); - const head = isNonEmptyString(instructions) - ? wrapWithUserInstructions(options.before, instructions, options.after) - : `${options.before}\n${options.after}`; + const overrides = await readProjectMetadataOverrides(options); + const styleBlocks = options.styles.map((section) => + renderStyleSection(section, overrides?.[section.configKey]?.instructions), + ); + const head = [options.contract, ...styleBlocks, options.after].join("\n\n"); return options.trailing ? `${head}\n\n${options.trailing}` : head; } -async function readProjectMetadataInstructions( - options: Pick, -): Promise { +function renderStyleSection(section: MetadataStyleSection, override: string | undefined): string { + const body = isNonEmptyString(override) ? override.trim() : section.default; + return section.label ? `${section.label}:\n${body}` : body; +} + +async function readProjectMetadataOverrides( + options: Pick, +): Promise { if (!options.workspaceGitService) { return undefined; } try { const repoRoot = await options.workspaceGitService.resolveRepoRoot(options.cwd); const json = readPaseoConfigJson(repoRoot); - const config = PaseoConfigSchema.parse(json); - return config.metadataGeneration?.[options.configKey]?.instructions; + return PaseoConfigSchema.parse(json).metadataGeneration; } catch { return undefined; } diff --git a/packages/server/src/utils/wrap-user-instructions.test.ts b/packages/server/src/utils/wrap-user-instructions.test.ts deleted file mode 100644 index dd8b8c7fc..000000000 --- a/packages/server/src/utils/wrap-user-instructions.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { wrapWithUserInstructions } from "./wrap-user-instructions.js"; - -const beforeBlock = "Follow the default metadata guidelines."; -const afterBlock = 'Return JSON only with field "title".'; -const overrideNotice = - "The instructions below are provided by the project owner and override the guidelines above where they conflict."; - -describe("wrapWithUserInstructions", () => { - it("wraps user instructions with the override notice", () => { - expect(wrapWithUserInstructions(beforeBlock, "Use conventional commits.", afterBlock)).toBe( - `${beforeBlock} - - -${overrideNotice} - -Use conventional commits. - - -${afterBlock}`, - ); - }); - - it("preserves multi-line instructions verbatim inside the block", () => { - const output = wrapWithUserInstructions(beforeBlock, "line1\nline2", afterBlock); - - expect(output).toContain("line1\nline2"); - }); -}); diff --git a/packages/server/src/utils/wrap-user-instructions.ts b/packages/server/src/utils/wrap-user-instructions.ts deleted file mode 100644 index 082117eed..000000000 --- a/packages/server/src/utils/wrap-user-instructions.ts +++ /dev/null @@ -1,18 +0,0 @@ -const USER_INSTRUCTIONS_NOTICE = - "The instructions below are provided by the project owner and override the guidelines above where they conflict."; - -export function wrapWithUserInstructions( - beforeBlock: string, - instructions: string, - afterBlock: string, -): string { - return `${beforeBlock} - - -${USER_INSTRUCTIONS_NOTICE} - -${instructions} - - -${afterBlock}`; -} diff --git a/public-docs/metadata-generation.md b/public-docs/metadata-generation.md index 2e9d8d905..5c49b4443 100644 --- a/public-docs/metadata-generation.md +++ b/public-docs/metadata-generation.md @@ -10,12 +10,15 @@ category: Configuration Paseo asks a language model to write short pieces of text for you so you don't have to. This is separate from the agent you're talking to: it's a small, one-shot call made in the background. -Paseo generates three kinds of metadata: +Paseo generates these kinds of metadata: +- **Workspace titles** — a short, task-shaped label for a workspace, shown in the sidebar. - **Worktree branch names** — a slug for the branch a new worktree agent runs on. - **Commit messages** — a concise message for the changes you're committing. - **Pull request title and body** — drafted from the diff when you open a PR. +A workspace title and its branch name are produced together from the same prompt, but you configure their wording independently (see below). + ## How a model is chosen You don't have to configure anything — Paseo picks a model automatically. It builds an ordered list of candidates and tries each one until a generation succeeds, so a slow or unavailable model falls through to the next. @@ -66,6 +69,7 @@ You can steer the wording of each kind of metadata per repository with a `paseo. ```json { "metadataGeneration": { + "title": { "instructions": "Keep titles to a few words, no leading verb." }, "branchName": { "instructions": "Use the format /-." }, "commitMessage": { "instructions": "Follow Conventional Commits." }, "pullRequest": { "instructions": "Include a Testing section in the body." } @@ -73,4 +77,4 @@ You can steer the wording of each kind of metadata per repository with a `paseo. } ``` -Each key is optional; only the ones you set are affected. The instructions are added to the prompt for that metadata type, on top of the provider selection above. +Each key is optional; only the ones you set are affected. Your instructions **replace** the default style for that metadata type — they are not appended to it — so your wording never competes with Paseo's defaults. The functional requirements (what to produce and the output format) always apply and cannot be overridden.