The last agent tool. When the PM states standing intent ("pilots come first"),
Reginald logs it verbatim to an append-only JSONL ledger in the pm-state repo —
a directive is intent; its effects still land through propose_change. This
completes Reginald's tool surface: query_project · propose_change · capture_work
· record_directive.
core (@commitea/core):
- directives/record-directive-v0: schema (kind/quote/target/params/rationale +
id/ts/status), serialize/parseDirectiveLog (ts-ordered, seq computed on read,
corrupt lines skipped), appendDirective (concatenation merge), toDirectiveInput.
- RECORD_DIRECTIVE_TOOL + system prompt update ("log standing intent; never claim
a change is applied").
- gitea client: getFile/putFile (contents API, base64-agnostic) for the pm-state repo.
app:
- main: a pm-state client (same token, `commitea-pm-state` repo — the purity
split, D4); appendDirectiveEntry (read→append→write, id/ts stamped here),
readDirectives. model:chat executes record_directive; pmstate:directives reads
the ledger. Degrades cleanly when the pm-state repo is absent.
- Directives screen shows the real ledger when present, the fixture demo otherwise.
Note: the pm-state repo isn't created yet — my token lacks write:user (repo
creation). Create `commitea-pm-state` (private) to activate the live path; all the
code + tests are in place. Override with COMMITEA_PMSTATE_REPO.
Verified: 116 core tests green (8 directive + 2 contents-API added), desktop
typecheck clean, 14 fixture e2e green. Gated live test: the real gemma-4-26b calls
record_directive for "pilots come first" (logs intent, doesn't claim to apply it);
the append/read + POST/PUT contents paths are unit-tested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
92 lines
3.9 KiB
TypeScript
92 lines
3.9 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'],
|
|
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.',
|
|
},
|
|
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 an estimate and/or priority change to an issue. This does NOT apply anything — it shows the " +
|
|
'human a diff to approve. Use it whenever the user asks to re-estimate or reprioritize. After calling it, ' +
|
|
"tell the user you've *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'] },
|
|
},
|
|
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 or priority, 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(' ')
|