Repository navigation
Community personalities + Writing Style + leaner agent prompt #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 7 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
de97985
fix(notes): make mermaid subgraph labels contrast with their fill
ddutchie a1b7392
feat(chat): community personalities — style-layer system prompt + picker
ddutchie d8c1c9b
perf(agent): drop redundant tool listings from the coding-agent syste…
ddutchie a78932a
fix(chat): stabilize personality picker selectors + null-safe list ac…
ddutchie e3a91b0
feat(settings): user writing style — persona, full guide + cheat shee…
ddutchie 36285e3
fix(chat): 'None' personality selection actually sticks across restarts
ddutchie d920116
chore: lint fixes — unused import, useCallback deps, escaped entities
ddutchie c1fb5b4
fix(settings): writing-style wizard — Next/Skip navigation + custom S…
ddutchie cbc6222
fix(settings): make writing-style generation robust against garbage o…
ddutchie 262765f
test(settings): live generation test for Writing Style + extract shar…
ddutchie b5642d5
fix(llm): unify callLLM streaming on the loop parser; non-streaming o…
ddutchie 7c9b16e
feat(settings): streamed writing-style generation + Optimize pass + m…
ddutchie 8c78601
perf(settings): reasoning_effort none for writing-style generation (~…
ddutchie 3b572e2
fix(llm): fall back without reasoning_effort on strict endpoints (400…
ddutchie b42a89a
fix: address CodeRabbit review comments on PR #123
ddutchie 053d482
fix(compaction): route chat compaction through the shared SSE parser …
ddutchie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| ## What's new | ||
|
|
||
| ### Added | ||
|
|
||
| - **Chat personalities — shape how the AI talks to you.** Pick a personality next to the model selector in the chat input to layer behavioral rules onto the system prompt: *Caveman* (short, plain words), *Grill Me* (stress-tests your plan with calibrated questions), *Junior to Senior* (adversarial senior review), *Last 20%* (finishes the experiential layer), *Ponytail* (the lazy senior dev — write only what's needed), and more. The full prompt is shown before you install, so you always know exactly what's being added. | ||
| - **Default is unchanged** — with no personality selected the assistant behaves exactly as before; personalities are an additive style layer, never a replacement identity. | ||
| - **Browse + install** from the community catalog in Settings → AI & Chat → Chat personality, or create your own behavioral rules in a couple of clicks. | ||
|
|
||
| - **Writing Style — your voice, generated.** Set up in Settings → Writing Style: a short guided session asks who you are, lets you paste a few real messages (or analyse your recent notes & tasks), and builds a **full style guide** plus a condensed **cheat sheet**. Both are editable before saving. | ||
| - **Draft in your voice everywhere.** Chat and the coding agent can call `get_user_writing_style` when asked to write as you — emails, replies, notes, PRDs. The tool returns the cheat sheet by default and the full guide on request, and is only fetched when actually drafting (never injected into every prompt). | ||
|
|
||
| ### Changed | ||
|
|
||
| - **The coding agent's system prompt is leaner.** Removed the redundant tool-listing sections (every tool is already defined in the tool array the model receives) — about 320 tokens saved per turn on the coding agent, with tool selection verified as at least as accurate in a live A/B against the same model. | ||
|
|
||
| ### Fixed | ||
|
|
||
| - **Mermaid subgraph labels are readable again in dark mode.** Subgraph (cluster) titles now get their colour computed from the actual fill the diagram uses — so when an LLM-generated chart paints a light pastel background, the label flips to dark text instead of inheriting the theme's light text and becoming invisible. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { describe, it, expect } from "vitest"; | ||
| import Database from "better-sqlite3"; | ||
| import { applySchema } from "./schema"; | ||
| import { getUserStyle, saveUserStyle, clearUserStyle, USER_STYLE_ID } from "./user-style-queries"; | ||
|
|
||
| function makeDb() { | ||
| const db = new Database(":memory:"); | ||
| applySchema(db); | ||
| return db; | ||
| } | ||
|
|
||
| describe("user-style queries", () => { | ||
| it("returns null before anything is saved", () => { | ||
| const db = makeDb(); | ||
| expect(getUserStyle(db)).toBeNull(); | ||
| db.close(); | ||
| }); | ||
|
|
||
| it("upserts a single global row with parsed persona", () => { | ||
| const db = makeDb(); | ||
| const saved = saveUserStyle(db, { | ||
| persona: { name: "Gerard", role: "Engineering lead" }, | ||
| fullGuide: "## 1. Voice in one line\nWarm and precise.", | ||
| cheatsheet: "Warm and precise.", | ||
| source: "guided", | ||
| }); | ||
| expect(saved.id).toBe(USER_STYLE_ID); | ||
| expect(saved.persona?.name).toBe("Gerard"); | ||
| expect(saved.fullGuide).toContain("Voice in one line"); | ||
| expect(saved.cheatsheet).toContain("Warm and precise."); | ||
|
|
||
| // Re-saving preserves fields not supplied. | ||
| const updated = saveUserStyle(db, { cheatsheet: "Updated.", source: "guided" }); | ||
| expect(updated.fullGuide).toContain("Voice in one line"); | ||
| expect(updated.cheatsheet).toBe("Updated."); | ||
| db.close(); | ||
| }); | ||
|
|
||
| it("parses persona defensively (bad JSON → null)", () => { | ||
| const db = makeDb(); | ||
| saveUserStyle(db, { persona: { name: "A" }, fullGuide: "", cheatsheet: "", source: "guided" }); | ||
| db.prepare("UPDATE user_style SET persona_json = ?").run("not json{"); | ||
| expect(getUserStyle(db)?.persona).toBeNull(); | ||
| db.close(); | ||
| }); | ||
|
|
||
| it("clearUserStyle removes the row", () => { | ||
| const db = makeDb(); | ||
| saveUserStyle(db, { persona: { name: "A" }, fullGuide: "g", cheatsheet: "c", source: "guided" }); | ||
| clearUserStyle(db); | ||
| expect(getUserStyle(db)).toBeNull(); | ||
| db.close(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| /** | ||
| * User writing-style queries — the single-row `user_style` table (persona, | ||
| * full style guide, condensed cheat sheet). App-global: read by the | ||
| * get_user_writing_style tool (chat + agent) and edited via Settings → | ||
| * Writing Style. The row is keyed by a fixed constant and upserted. | ||
| */ | ||
|
|
||
| import type Database from "better-sqlite3"; | ||
| import { ts } from "./utils"; | ||
|
|
||
| export const USER_STYLE_ID = "global"; | ||
|
|
||
| export type UserStyleSource = "none" | "guided" | "manual" | "analyzed"; | ||
|
|
||
| export interface UserStylePersona { | ||
| name?: string; | ||
| role?: string; | ||
| context?: string; | ||
| audiences?: string; | ||
| } | ||
|
|
||
| export interface UserStyleRow { | ||
| id: string; | ||
| /** Serialized UserStylePersona JSON (parse defensively; absent = null). */ | ||
| persona: UserStylePersona | null; | ||
| /** The long, section-structured writing style guide (markdown). */ | ||
| fullGuide: string; | ||
| /** The condensed one-page cheat sheet (markdown). */ | ||
| cheatsheet: string; | ||
| /** How the guide was produced: guided wizard / manual / analyzed / none. */ | ||
| source: UserStyleSource; | ||
| updatedAt: string; | ||
| } | ||
|
|
||
| interface UserStyleRowRaw { | ||
| id: string; | ||
| persona_json: string | null; | ||
| full_guide: string | null; | ||
| cheatsheet: string | null; | ||
| source: string; | ||
| updated_at: string; | ||
| } | ||
|
|
||
| function toUserStyle(row: UserStyleRowRaw): UserStyleRow { | ||
| let persona: UserStylePersona | null = null; | ||
| if (row.persona_json) { | ||
| try { | ||
| persona = JSON.parse(row.persona_json) as UserStylePersona; | ||
| } catch { | ||
| persona = null; | ||
| } | ||
| } | ||
| return { | ||
| id: row.id, | ||
| persona, | ||
| fullGuide: row.full_guide ?? "", | ||
| cheatsheet: row.cheatsheet ?? "", | ||
| source: (row.source ?? "none") as UserStyleSource, | ||
| updatedAt: row.updated_at, | ||
| }; | ||
| } | ||
|
|
||
| export function getUserStyle(db: Database.Database): UserStyleRow | null { | ||
| const row = db.prepare("SELECT * FROM user_style WHERE id = ?").get(USER_STYLE_ID) as UserStyleRowRaw | undefined; | ||
| return row ? toUserStyle(row) : null; | ||
| } | ||
|
|
||
| export interface UserStyleSaveInput { | ||
| persona?: UserStylePersona; | ||
| fullGuide?: string; | ||
| cheatsheet?: string; | ||
| source: UserStyleSource; | ||
| } | ||
|
|
||
| /** Upsert the single global row. Absent fields preserve the existing value. */ | ||
| export function saveUserStyle(db: Database.Database, input: UserStyleSaveInput): UserStyleRow { | ||
| const existing = getUserStyle(db); | ||
| const personaJson = input.persona !== undefined ? JSON.stringify(input.persona) : (existing?.persona ? JSON.stringify(existing.persona) : null); | ||
| const fullGuide = input.fullGuide !== undefined ? input.fullGuide : (existing?.fullGuide ?? ""); | ||
| const cheatsheet = input.cheatsheet !== undefined ? input.cheatsheet : (existing?.cheatsheet ?? ""); | ||
| const now = ts(); | ||
| db.prepare(` | ||
| INSERT INTO user_style (id, persona_json, full_guide, cheatsheet, source, updated_at) | ||
| VALUES (?, ?, ?, ?, ?, ?) | ||
| ON CONFLICT(id) DO UPDATE SET | ||
| persona_json = excluded.persona_json, | ||
| full_guide = excluded.full_guide, | ||
| cheatsheet = excluded.cheatsheet, | ||
| source = excluded.source, | ||
| updated_at = excluded.updated_at | ||
| `).run(USER_STYLE_ID, personaJson, fullGuide, cheatsheet, input.source, now); | ||
| return getUserStyle(db)!; | ||
| } | ||
|
|
||
| export function clearUserStyle(db: Database.Database): void { | ||
| db.prepare("DELETE FROM user_style WHERE id = ?").run(USER_STYLE_ID); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.