Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
"vitest.disableWorkspaceWarning": true
"vitest.disableWorkspaceWarning": true,
"ndjson.port": 7700
Comment on lines +13 to +14
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ndjson.port setting looks like a developer-local VS Code extension preference rather than a project-wide requirement. Consider removing it from the repo-level .vscode/settings.json (or documenting why it must be shared) to avoid forcing local preferences on contributors.

Suggested change
"vitest.disableWorkspaceWarning": true,
"ndjson.port": 7700
"vitest.disableWorkspaceWarning": true

Copilot uses AI. Check for mistakes.
}
1,617 changes: 1,617 additions & 0 deletions Untitled-1.jsonc

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ export const globalSettingsSchema = z.object({
customSupportPrompts: customSupportPromptsSchema.optional(),
enhancementApiConfigId: z.string().optional(),
includeTaskHistoryInEnhance: z.boolean().optional(),

/**
* Custom meta-prompt for the personality trait enhancer.
* Used to expand brief descriptions into structured personality prompts.
*/
personalityTraitEnhancerPrompt: z.string().optional(),
historyPreviewCollapsed: z.boolean().optional(),
reasoningBlockCollapsed: z.boolean().optional(),
/**
Expand Down Expand Up @@ -232,6 +238,12 @@ export const globalSettingsSchema = z.object({
* Tools in this list will be excluded from prompt generation and rejected at execution time.
*/
disabledTools: z.array(toolNamesSchema).optional(),

// Memory Learning
memoryLearningEnabled: z.boolean().optional(),
memoryApiConfigId: z.string().optional(),
memoryAnalysisFrequency: z.number().optional(),
memoryLearningDefaultEnabled: z.boolean().optional(),
})

export type GlobalSettings = z.infer<typeof globalSettingsSchema>
Expand Down
27 changes: 27 additions & 0 deletions packages/types/src/mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,32 @@ export const groupEntryArraySchema = z.preprocess((val) => {
return val.filter((entry) => !isDeprecatedGroupEntry(entry))
}, rawGroupEntryArraySchema) as z.ZodType<GroupEntry[], z.ZodTypeDef, GroupEntry[]>

/**
* PersonalityTrait
*/

export const personalityTraitSchema = z.object({
id: z.string().min(1, "Trait ID is required"),
emoji: z.string().min(1, "Emoji is required"),
label: z.string().min(1, "Label is required"),
prompt: z.string().min(1, "Prompt is required"),
isBuiltIn: z.boolean(),
})

export type PersonalityTrait = z.infer<typeof personalityTraitSchema>

/**
* PersonalityConfig
*/

export const personalityConfigSchema = z.object({
activeTraitIds: z.array(z.string()),
customTraits: z.array(personalityTraitSchema),
deletedBuiltInTraitIds: z.array(z.string()).optional(),
})

export type PersonalityConfig = z.infer<typeof personalityConfigSchema>

export const modeConfigSchema = z.object({
slug: z.string().regex(/^[a-zA-Z0-9-]+$/, "Slug must contain only letters numbers and dashes"),
name: z.string().min(1, "Name is required"),
Expand All @@ -102,6 +128,7 @@ export const modeConfigSchema = z.object({
customInstructions: z.string().optional(),
groups: groupEntryArraySchema,
source: z.enum(["global", "project"]).optional(),
personalityConfig: personalityConfigSchema.optional(),
})

export type ModeConfig = z.infer<typeof modeConfigSchema>
Expand Down
1 change: 1 addition & 0 deletions path/to/data/aliases/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be local/generated data committed accidentally (empty aliases DB). If it’s runtime state, it should not be versioned; remove it from the PR and add an ignore rule for this path.

Suggested change
{}
{
"_meta": {
"description": "Aliases configuration file. This file is intentionally versioned; add alias mappings under the 'aliases' key.",
"version": 1
},
"aliases": {
}
}

Copilot uses AI. Check for mistakes.
1 change: 1 addition & 0 deletions path/to/data/raft_state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"state":{"hard_state":{"term":0,"vote":0,"commit":0},"conf_state":{"voters":[4166060179281456],"learners":[],"voters_outgoing":[],"learners_next":[],"auto_leave":false}},"latest_snapshot_meta":{"term":0,"index":0},"apply_progress_queue":null,"first_voter":4166060179281456,"peer_address_by_id":{},"peer_metadata_by_id":{},"this_peer_id":4166060179281456}
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like generated runtime state (Raft hard_state/conf_state) rather than source code. Committing it will create noisy diffs and potentially leak environment-specific IDs. Remove it from the PR and add the containing path to .gitignore if it’s produced locally.

Suggested change
{"state":{"hard_state":{"term":0,"vote":0,"commit":0},"conf_state":{"voters":[4166060179281456],"learners":[],"voters_outgoing":[],"learners_next":[],"auto_leave":false}},"latest_snapshot_meta":{"term":0,"index":0},"apply_progress_queue":null,"first_voter":4166060179281456,"peer_address_by_id":{},"peer_metadata_by_id":{},"this_peer_id":4166060179281456}
{}

Copilot uses AI. Check for mistakes.
205 changes: 205 additions & 0 deletions src/core/prompts/sections/__tests__/personality.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { PersonalityTrait, PersonalityConfig } from "@roo-code/types"

import {
BUILT_IN_PERSONALITY_TRAITS,
resolveActiveTraits,
getAllTraitsForConfig,
buildPersonalityPrompt,
} from "../../../../shared/personality-traits"

describe("buildPersonalityPrompt", () => {
it("should return empty string when no config is provided", () => {
expect(buildPersonalityPrompt(undefined)).toBe("")
})

it("should return empty string when no traits are active", () => {
const config: PersonalityConfig = {
activeTraitIds: [],
customTraits: [],
}
expect(buildPersonalityPrompt(config)).toBe("")
})

it("should return formatted section for a single active built-in trait", () => {
const config: PersonalityConfig = {
activeTraitIds: ["roo"],
customTraits: [],
}

const result = buildPersonalityPrompt(config)

expect(result).toContain("Personality & Communication Style:")
expect(result).toContain("non-negotiable")
expect(result).toContain("You are Roo")
expect(result).toContain("IMPORTANT: Maintaining this personality is critical")
})
Comment on lines +23 to +35
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test suite’s expectations don’t match the implementation in shared/personality-traits.ts. For example, buildPersonalityPrompt now emits PERSONALITY & VOICE (ACTIVE: ...) and CRITICAL: wording, so assertions like Personality & Communication Style: / non-negotiable / the anchor text will fail. Update the expected strings to reflect the new prompt format (or adjust the implementation to match the intended spec).

Copilot uses AI. Check for mistakes.

it("should concatenate multiple active traits", () => {
const config: PersonalityConfig = {
activeTraitIds: ["dry-wit", "straight-shooter"],
customTraits: [],
}

const result = buildPersonalityPrompt(config)

expect(result).toContain("bone-dry, deadpan")
expect(result).toContain("extremely direct and concise")
})

it("should include custom traits", () => {
const customTrait: PersonalityTrait = {
id: "pirate",
emoji: "🏴‍☠️",
label: "Pirate",
prompt: "You are a pirate. Use pirate language like 'Ahoy matey!' and 'Arrr!'",
isBuiltIn: false,
}

const config: PersonalityConfig = {
activeTraitIds: ["pirate"],
customTraits: [customTrait],
}

const result = buildPersonalityPrompt(config)

expect(result).toContain("You are a pirate")
expect(result).toContain("Ahoy matey!")
})

it("should ignore unknown trait IDs gracefully", () => {
const config: PersonalityConfig = {
activeTraitIds: ["nonexistent-trait"],
customTraits: [],
}

const result = buildPersonalityPrompt(config)
expect(result).toBe("")
})

it("should include the behavioral anchor at the end", () => {
const config: PersonalityConfig = {
activeTraitIds: ["roo"],
customTraits: [],
}

const result = buildPersonalityPrompt(config)

// The behavioral anchor should be at the end
expect(result).toContain("IMPORTANT: Maintaining this personality is critical")
expect(result).toContain("generic, neutral AI assistant tone")
// Verify it ends with the anchor
expect(result.trim().endsWith("not a default chatbot.")).toBe(true)
})
})

describe("Built-in traits", () => {
it("should have 12 built-in traits", () => {
expect(BUILT_IN_PERSONALITY_TRAITS).toHaveLength(12)
})

it("should have unique IDs", () => {
const ids = BUILT_IN_PERSONALITY_TRAITS.map((t) => t.id)
expect(new Set(ids).size).toBe(ids.length)
})

it("should all be marked as isBuiltIn", () => {
BUILT_IN_PERSONALITY_TRAITS.forEach((trait) => {
expect(trait.isBuiltIn).toBe(true)
})
})

it("should all use direct natural-language format (no section markers)", () => {
BUILT_IN_PERSONALITY_TRAITS.forEach((trait) => {
// No [SECTION_KEY] markers should be present
expect(trait.prompt).not.toMatch(/\[COMMUNICATION_STYLE\]/)
expect(trait.prompt).not.toMatch(/\[TASK_COMPLETION\]/)
expect(trait.prompt).not.toMatch(/\[ERROR_HANDLING\]/)
expect(trait.prompt).not.toMatch(/\[SUGGESTIONS\]/)
})
})

it("should all start with identity-first framing (You are/You have/You speak/You prioritize/You question)", () => {
BUILT_IN_PERSONALITY_TRAITS.forEach((trait) => {
const startsWithIdentity = /^You (are|have|speak|prioritize|question|see)\b/.test(trait.prompt.trim())
expect(startsWithIdentity).toBe(true)
})
Comment on lines +95 to +125
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The built-in traits assertions are inconsistent with BUILT_IN_PERSONALITY_TRAITS in shared/personality-traits.ts: the list currently contains 13 entries (including roo-devs), but the test expects 12. Additionally, the “identity-first framing” regex only allows You are|have|speak|prioritize|question|see, but multiple prompts start with You deliver / You talk etc. Align these tests with the actual built-in trait definitions (or standardize the prompts to satisfy the test constraints).

Copilot uses AI. Check for mistakes.
})

it("should all contain negative constraints (Never)", () => {
BUILT_IN_PERSONALITY_TRAITS.forEach((trait) => {
expect(trait.prompt).toContain("Never")
})
})

it("should include the Roo default trait", () => {
const roo = BUILT_IN_PERSONALITY_TRAITS.find((t) => t.id === "roo")
expect(roo).toBeDefined()
expect(roo!.emoji).toBe("🦘")
expect(roo!.label).toBe("Roo")
})
})

describe("resolveActiveTraits", () => {
it("should resolve built-in trait IDs to full traits", () => {
const result = resolveActiveTraits(["roo", "dry-wit"])
expect(result).toHaveLength(2)
expect(result[0].id).toBe("roo")
expect(result[1].id).toBe("dry-wit")
})

it("should preserve order", () => {
const result = resolveActiveTraits(["dry-wit", "roo"])
expect(result[0].id).toBe("dry-wit")
expect(result[1].id).toBe("roo")
})

it("should filter out unknown IDs", () => {
const result = resolveActiveTraits(["roo", "nonexistent", "dry-wit"])
expect(result).toHaveLength(2)
})

it("should resolve custom traits", () => {
const custom: PersonalityTrait = {
id: "my-custom",
emoji: "🧪",
label: "Custom",
prompt: "You are custom.",
isBuiltIn: false,
}
const result = resolveActiveTraits(["my-custom"], [custom])
expect(result).toHaveLength(1)
expect(result[0].label).toBe("Custom")
})
})

describe("getAllTraitsForConfig", () => {
it("should return built-in traits when no custom traits", () => {
const result = getAllTraitsForConfig([])
expect(result.length).toBe(BUILT_IN_PERSONALITY_TRAITS.length)
})

it("should append custom traits", () => {
const custom: PersonalityTrait = {
id: "new-trait",
emoji: "🆕",
label: "New",
prompt: "You are new.",
isBuiltIn: false,
}
const result = getAllTraitsForConfig([custom])
expect(result.length).toBe(BUILT_IN_PERSONALITY_TRAITS.length + 1)
})

it("should allow custom traits to override built-in ones by ID", () => {
const override: PersonalityTrait = {
id: "roo",
emoji: "🦘",
label: "Custom Roo",
prompt: "You are a custom Roo.",
isBuiltIn: false,
}
const result = getAllTraitsForConfig([override])
const roo = result.find((t) => t.id === "roo")
expect(roo!.label).toBe("Custom Roo")
})
})
8 changes: 8 additions & 0 deletions src/core/prompts/sections/custom-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ export async function addCustomInstructions(
language?: string
rooIgnoreInstructions?: string
settings?: SystemPromptSettings
personalityPrompt?: string
} = {},
): Promise<string> {
const sections = []
Expand Down Expand Up @@ -491,6 +492,13 @@ export async function addCustomInstructions(
sections.push(`Rules:\n\n${rules.join("\n\n")}`)
}

// Inject personality prompt LAST for maximum recency effect.
// This is the last thing the model reads before generating,
// which research shows produces the strongest behavioral adherence.
if (options.personalityPrompt && options.personalityPrompt.trim()) {
sections.push(options.personalityPrompt.trim())
}

Comment on lines +495 to +501
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new personalityPrompt option is never passed by any call sites (no matches for personalityPrompt: in src/), so the “inject personality prompt last” behavior won’t run in production. Ensure the system prompt generation path builds the personality bottom block and passes it into addCustomInstructions (or otherwise injects it).

Suggested change
// Inject personality prompt LAST for maximum recency effect.
// This is the last thing the model reads before generating,
// which research shows produces the strongest behavioral adherence.
if (options.personalityPrompt && options.personalityPrompt.trim()) {
sections.push(options.personalityPrompt.trim())
}

Copilot uses AI. Check for mistakes.
const joinedSections = sections.join("\n\n")

return joinedSections
Expand Down
1 change: 1 addition & 0 deletions src/core/prompts/sections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { getCapabilitiesSection } from "./capabilities"
export { getModesSection } from "./modes"
export { markdownFormattingSection } from "./markdown-formatting"
export { getSkillsSection } from "./skills"
export { getPersonalitySection, buildPersonalityPromptParts } from "./personality"
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although getPersonalitySection is exported here, the system prompt builder (src/core/prompts/system.ts) does not include it in basePrompt, so personality configuration currently has no effect on generated system prompts. Wire this section (and the bottom “sandwich” part if intended) into the prompt composition.

Copilot uses AI. Check for mistakes.
9 changes: 9 additions & 0 deletions src/core/prompts/sections/personality.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Personality section for system prompt.
* Uses the sandwich technique: personality at the TOP and reinforced at the BOTTOM.
*/
import { buildPersonalityPrompt, buildPersonalityPromptParts } from "../../../shared/personality-traits"
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildPersonalityPromptParts is imported but not used in this module (it’s re-exported directly below). This will trip unused-import lint/TS settings; remove it from the import list.

Suggested change
import { buildPersonalityPrompt, buildPersonalityPromptParts } from "../../../shared/personality-traits"
import { buildPersonalityPrompt } from "../../../shared/personality-traits"

Copilot uses AI. Check for mistakes.

export { mergeTraitPrompts, buildPersonalityPromptParts } from "../../../shared/personality-traits"

export const getPersonalitySection = buildPersonalityPrompt
Loading
Loading