Files
commitea/packages/core/src/agent/agent-tools.ts
Croissant Le Doux 19e83ff8ba query_project: implement the standup view (#28)
The StandupScreen already renders drift + plan + nag from real data (standup-view,
#52), but the agent's query_project standup view was a `notImplemented` stub, so
Reginald couldn't answer standup questions from deterministic data.

Implement `standupView(snap, asOf)`: today's plan (the scheduler's earliest pick
per person, with why — critical path / blocks / order), overnight drift (real
anomalies: issues sitting in review, or steeping past their estimate), and the
single stalest blocker to nag about (+ what it blocks). All deterministic; the
model narrates. Added 'standup' to the query_project tool's view enum.

Acceptance met: standup surfaces schedule drift + at least one stale blocker
(and stays calm — no nag, empty drift — when nothing is steeping). +2 core tests;
typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:33:20 -04:00

97 lines
4.4 KiB
TypeScript

/**
* Reginald's tool surface. v0 ships the one read tool (`query_project`); the
* three write tools (capture_work, apply_changes, record_directive) layer on
* later against the same loop. Few, fat tools so a small local model survives
* with one thing to reach for (docs/agent-tools.md).
*/
import type { ToolDecl } from './chat-client.js'
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.',
parameters: {
type: 'object',
properties: {
view: {
type: 'string',
enum: ['focus', 'board', 'calibration', 'issue', 'search', 'standup'],
description:
'focus = Now/Next/Later; board = issues by lifecycle column; calibration = estimate-vs-actual; ' +
'issue = one issue (needs filters.issueId); search = issues matching filters.query; ' +
"standup = today's plan + overnight drift + the stalest blocker.",
},
filters: {
type: 'object',
properties: {
issueId: { type: 'number' },
query: { type: 'string' },
limit: { type: 'number' },
},
},
},
required: ['view'],
},
}
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 ' +
'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.',
parameters: {
type: 'object',
properties: {
issue: { type: 'number', description: 'the issue number to change' },
estimate: { type: 'string', enum: ['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d'] },
priority: { type: 'string', enum: ['p/1', 'p/2', 'p/3', 'p/4'] },
assignee: { type: 'string', description: 'gitea login to assign, or null to unassign' },
milestone: { type: 'number', description: 'milestone id to set, or null to remove from its milestone' },
},
required: ['issue'],
},
}
export const RECORD_DIRECTIVE_TOOL: ToolDecl = {
name: 'record_directive',
description:
'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.',
parameters: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['reprioritize', 'reestimate', 'set-deadline', 'scope', 'capacity', 'note'] },
quote: { type: 'string', description: "the PM's own words, stored verbatim" },
target: {
type: 'object',
properties: {
issue: { type: 'number' },
milestone: { type: 'number' },
member: { type: 'string' },
},
},
rationale: { type: 'string', description: 'why (optional)' },
},
required: ['kind', 'quote'],
},
}
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.',
].join(' ')