Make Reginald posh + remove em-dashes from user-facing copy

Voice: rewrote REGINALD_SYSTEM and CAPTURE_SYSTEM (core) to an elevated,
dry butler register, and re-voiced his visible lines — chat greetings, the
approve/dismiss/error replies, the standup closer ('The kettle is on. Yours,
Reginald.'), the onboarding welcome, and the capture prose. Both system prompts
now also instruct him never to use an em dash.

Em-dashes: swept every user-facing string in the renderer free of em-dashes
(punctuation only, comments left untouched) via a per-file pass, plus the core
tool descriptions and the memory focus-slot placeholder ('· ' not '— '). Bare
'—' value placeholders became middots ('·'). No em-dash now renders anywhere
in the app or in Reginald's own output.

core 169 tests green (updated the memory placeholder assertion) · core + desktop
tsc clean · verified visually (posh greeting + standup closer render).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Croissant Le Doux
2026-07-10 16:24:20 -04:00
parent 1026c762a9
commit 8d750139a1
20 changed files with 75 additions and 71 deletions

View File

@@ -11,7 +11,7 @@ export const QUERY_PROJECT_TOOL: ToolDecl = {
name: 'query_project',
description:
'Read the current project state. A `view` selects the shape; deterministic code (scheduler, ' +
'lifecycle inference, calibration) backs every number you report it, you never compute it.',
'lifecycle inference, calibration) backs every number: you report it, you never compute it.',
parameters: {
type: 'object',
properties: {
@@ -39,11 +39,11 @@ export const QUERY_PROJECT_TOOL: ToolDecl = {
export const PROPOSE_CHANGE_TOOL: ToolDecl = {
name: 'propose_change',
description:
'Propose a change to an issue estimate, priority, assignee, and/or milestone. This does NOT apply ' +
'Propose a change to an issue: estimate, priority, assignee, and/or milestone. This does NOT apply ' +
'anything; it shows the human a diff to approve. Use it whenever the user asks to re-estimate, reprioritize, ' +
'assign someone (or unassign), or move an issue to a milestone. Pass a login for `assignee` (null to ' +
'unassign) and a milestone id for `milestone` (null to remove). After calling it, tell the user you have ' +
'*proposed* the change for approval never say it is done.',
'*proposed* the change for approval; never say it is done.',
parameters: {
type: 'object',
properties: {
@@ -60,7 +60,7 @@ export const PROPOSE_CHANGE_TOOL: ToolDecl = {
export const RECORD_DIRECTIVE_TOOL: ToolDecl = {
name: 'record_directive',
description:
'Log a standing instruction from the PM to the durable directive ledger a reprioritization, ' +
'Log a standing instruction from the PM to the durable directive ledger: a reprioritization, ' +
'a re-estimate policy, a deadline, a scope or capacity call, or a plain note. Use it when the user ' +
'states intent that should persist ("pilots come first", "freeze scope for beta"). This records the ' +
'intent verbatim; the actual issue edits still go through propose_change.',
@@ -86,11 +86,11 @@ export const RECORD_DIRECTIVE_TOOL: ToolDecl = {
export const REGINALD_TOOLS: ToolDecl[] = [QUERY_PROJECT_TOOL, PROPOSE_CHANGE_TOOL, RECORD_DIRECTIVE_TOOL]
export const REGINALD_SYSTEM = [
'You are Reginald, the calm, dry project manager inside CommiTea a tool that runs projects on Gitea.',
'Call query_project to ground every answer in the real project; never invent issues, numbers, or dates.',
'The scheduler and forecasts are deterministic code — report their output, do not recompute it.',
'To change an estimate, priority, assignee, or milestone, call propose_change — it shows the human a diff to approve.',
'When the PM states standing intent ("pilots first", "freeze scope"), call record_directive to log it.',
'Never claim a change is applied; you propose, the human approves. Forecasts are ranges, never single dates.',
'Refer to issues as #<number>. Be brief and plain — a sentence or two. No preamble, no bullet dumps.',
'You are Reginald, the unfailingly composed project manager within CommiTea, a tool that runs projects on Gitea. You carry yourself with the dry, understated poise of a seasoned butler: courteous, precise, and never effusive.',
'Ground every answer in the real project by calling query_project first; you never invent issues, figures, or dates.',
'The scheduler and forecasts are deterministic; report their output faithfully and do not attempt the arithmetic yourself.',
'To alter an estimate, priority, assignee, or milestone, call propose_change, which lays the matter before the principal as a diff for approval.',
'When the principal states standing intent (say, "pilots first" or "freeze scope"), record it with record_directive.',
'Never declare a change applied; you propose, and the principal disposes. Forecasts are offered as ranges, never a single date.',
'Refer to issues as #<number>. Be concise and courteous: a sentence or two, no preamble, no bulleted dumps. Write in plain prose, and never use an em dash; a comma, colon, or full stop will serve.',
].join(' ')

View File

@@ -53,10 +53,11 @@ export const PROPOSE_ISSUES_TOOL: ToolDecl = {
}
export const CAPTURE_SYSTEM = [
'You are Reginald, decomposing a rough braindump into a small set of concrete Gitea issues.',
'Call propose_issues exactly once. Split genuinely separate work; merge trivially-coupled work; invent no scope.',
'Each issue gets an imperative title, a one-line body, an estimate (est/1d…8d) and a priority (p/1…4).',
'Estimate honestly a "quick" task is rarely one day. Keep the set tight; three good issues beat eight vague ones.',
'You are Reginald, distilling a rough braindump into a small set of concrete Gitea issues, with your customary butlerly precision.',
'Call propose_issues exactly once. Separate genuinely distinct work; consolidate trivially-coupled work; invent no scope.',
'Each issue receives an imperative title, a one-line body, an estimate (est/1d…8d) and a priority (p/1…4).',
'Estimate honestly; a "quick" task is seldom a single day. Keep the set tight, for three considered issues surpass eight vague ones.',
'Write in plain prose, and never use an em dash; a comma, colon, or full stop will serve.',
].join(' ')
function isEstimate(v: unknown): v is EstimateLabel {

View File

@@ -66,6 +66,6 @@ describe('memory-v0 (#27)', () => {
it('degrades to just focus when there is no charter or directives', () => {
const out = assembleHotContext({ charter: '', directives: [], focus })
expect(out).toBe(['## Focus', 'Now: #7 Fix lifecycle inference', 'Next: #8 Webhook listener', 'Later: '].join('\n'))
expect(out).toBe(['## Focus', 'Now: #7 Fix lifecycle inference', 'Next: #8 Webhook listener', 'Later: ·'].join('\n'))
})
})

View File

@@ -46,7 +46,7 @@ export interface HotContextInputs {
function focusBlock(focus: Focus | null): string {
if (!focus) return ''
const slot = (label: string, item: Focus['now']) => (item ? `${label}: #${item.number} ${item.title}` : `${label}: `)
const slot = (label: string, item: Focus['now']) => (item ? `${label}: #${item.number} ${item.title}` : `${label}: ·`)
return ['## Focus', slot('Now', focus.now), slot('Next', focus.next), slot('Later', focus.later)].join('\n')
}