apply_changes: unify the mutation tool with assign + milestone (#24)
The write path handled only the est/* and p/* label axes. Extend it to the two
mutations dogfooding proved were missing — assignee and milestone — behind one
unified change model:
- IssueChange gains `assign` and `remilestone` kinds. `planIssueChange` is now
typed to LabelChange (the label kinds only); `isLabelChange` narrows, and a new
pure `summarizeChange(change, currentLabels?)` gives one confirm line for ANY
kind (label kinds delegate to describeChange; field writes describe directly).
- GiteaClient gains `setIssueAssignees` / `setIssueMilestone` (PATCH /issues/{n},
returning the updated issue; milestone 0 clears).
- The main `gitea:applyChange` handler branches: label swaps resolve ids +
setIssueLabels as before; field writes call the new client methods. Snapshot
invalidated either way so board + forecast catch up. ApplyChangeResult.plan is
now optional (absent for field writes).
- issue-screen uses summarizeChange for its diff preview.
Tests: +5 covering isLabelChange, and summarizeChange for assign/unassign,
milestone set-by-title / by-id / removal, and label delegation. Full core suite
132 green; core + desktop typecheck clean.
Follow-up (rest of #24): surface assignee/milestone pickers in the Adjust dialog
and extend the agent's propose_change tool so Reginald can propose them too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -103,6 +103,10 @@ export interface GiteaClient {
|
||||
listLabels(): Promise<GiteaLabel[]>
|
||||
/** Replace an issue's entire label set with the given label ids. Write. */
|
||||
setIssueLabels(index: number, labelIds: number[]): Promise<void>
|
||||
/** Replace an issue's assignees (empty array unassigns); returns the updated issue. Write. */
|
||||
setIssueAssignees(index: number, logins: string[]): Promise<GiteaIssue>
|
||||
/** Set (or clear, with null) an issue's milestone by id; returns the updated issue. Write. */
|
||||
setIssueMilestone(index: number, milestoneId: number | null): Promise<GiteaIssue>
|
||||
/** Open a new issue with a title, optional body, and label ids. Write. */
|
||||
createIssue(input: { title: string; body?: string; labelIds?: number[] }): Promise<GiteaIssue>
|
||||
/** Read a repo file's base64 content + blob sha; null if it (or the repo) is absent. */
|
||||
@@ -229,6 +233,20 @@ export function createGiteaClient(config: GiteaConfig, fetchImpl: FetchLike): Gi
|
||||
await request(`/issues/${index}/labels`, { method: 'PUT', body: { labels: labelIds } })
|
||||
},
|
||||
|
||||
async setIssueAssignees(index, logins) {
|
||||
const raw = (await request(`/issues/${index}`, { method: 'PATCH', body: { assignees: logins } })) as RawIssue
|
||||
return normalizeIssue(raw)
|
||||
},
|
||||
|
||||
async setIssueMilestone(index, milestoneId) {
|
||||
// gitea's EditIssueOption takes a milestone id; 0 clears it.
|
||||
const raw = (await request(`/issues/${index}`, {
|
||||
method: 'PATCH',
|
||||
body: { milestone: milestoneId ?? 0 },
|
||||
})) as RawIssue
|
||||
return normalizeIssue(raw)
|
||||
},
|
||||
|
||||
async createIssue(input) {
|
||||
const raw = (await request('/issues', {
|
||||
method: 'POST',
|
||||
|
||||
Reference in New Issue
Block a user