mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Decouple metadata prompt contract from overridable style
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.
This commit is contained in:
@@ -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." },
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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("<user-instructions>");
|
||||
const noticeIndex = prompt.indexOf("override the guidelines above");
|
||||
const userInstructionIndex = prompt.indexOf("Use conventional commits.");
|
||||
const closeTagIndex = prompt.indexOf("</user-instructions>");
|
||||
expect(prompt).toContain("Use conventional commits.\nAccept XML-ish <scope> 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("<user-instructions>");
|
||||
const noticeIndex = prompt.indexOf("override the guidelines above");
|
||||
const userInstructionIndex = prompt.indexOf("Use a terse title.");
|
||||
const closeTagIndex = prompt.indexOf("</user-instructions>");
|
||||
expect(prompt).toContain("Use a terse title.\nKeep literal <ticket> 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);
|
||||
});
|
||||
|
||||
@@ -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'.",
|
||||
"",
|
||||
|
||||
@@ -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("<user-instructions>");
|
||||
const userInstructionIndex = prompt.indexOf("Use the prefix mb/.");
|
||||
const closeTagIndex = prompt.indexOf("</user-instructions>");
|
||||
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 () => {
|
||||
|
||||
@@ -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}`,
|
||||
});
|
||||
|
||||
@@ -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<string>;
|
||||
}
|
||||
|
||||
// A style section carries the default guidance for one artifact. The project
|
||||
// owner replaces it wholesale via paseo.json metadataGeneration.<configKey>.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<string> {
|
||||
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<BuildMetadataPromptOptions, "cwd" | "configKey" | "workspaceGitService">,
|
||||
): Promise<string | undefined> {
|
||||
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<BuildMetadataPromptOptions, "cwd" | "workspaceGitService">,
|
||||
): Promise<PaseoMetadataGeneration | undefined> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
<user-instructions>
|
||||
${overrideNotice}
|
||||
|
||||
Use conventional commits.
|
||||
</user-instructions>
|
||||
|
||||
${afterBlock}`,
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves multi-line instructions verbatim inside the block", () => {
|
||||
const output = wrapWithUserInstructions(beforeBlock, "line1\nline2", afterBlock);
|
||||
|
||||
expect(output).toContain("line1\nline2");
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
${USER_INSTRUCTIONS_NOTICE}
|
||||
|
||||
${instructions}
|
||||
</user-instructions>
|
||||
|
||||
${afterBlock}`;
|
||||
}
|
||||
@@ -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 <type>/<scope>-<short-desc>." },
|
||||
"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.
|
||||
|
||||
Reference in New Issue
Block a user