MCPcopy Create free account
hub / github.com/TraderAlice/OpenAlice / updateIssueFields

Function updateIssueFields

src/workspaces/issues/mutate.ts:109–164  ·  view source on GitHub ↗
(
  wsDir: string,
  id: string,
  patch: IssueFieldPatch,
)

Source from the content-addressed store, hash-verified

107 * IssueRecord, or `not_found` when the file is absent.
108 */
109export async function updateIssueFields(
110 wsDir: string,
111 id: string,
112 patch: IssueFieldPatch,
113): Promise<MutateResult> {
114 if (!ID_RE.test(id)) return { ok: false, reason: 'not_found' }
115 const raw = await readWorkspaceFile(wsDir, relFor(id))
116 if (raw === null) return { ok: false, reason: 'not_found' }
117
118 const split = splitFrontmatter(raw)
119 if (!split) return { ok: false, reason: 'invalid', error: 'missing YAML frontmatter' }
120
121 // Re-validate the existing content first so we never write back a file that
122 // was already broken, and so the merge starts from a known-good frontmatter.
123 const current = parseIssueContent(id, raw)
124 if (!current.ok) return { ok: false, reason: 'invalid', error: current.error }
125
126 // Parse the raw frontmatter object (NOT the zod-defaulted record) so we
127 // preserve every author-written key verbatim and only overwrite what changed.
128 const data = parseFrontmatterObject(split.frontmatter)
129 if (!data) return { ok: false, reason: 'invalid', error: 'frontmatter is not a mapping' }
130
131 if (patch.status !== undefined) {
132 if (!ISSUE_STATUSES.includes(patch.status)) {
133 return { ok: false, reason: 'invalid', error: `invalid status: ${patch.status}` }
134 }
135 data.status = patch.status
136 }
137 if (patch.priority !== undefined) {
138 if (!ISSUE_PRIORITIES.includes(patch.priority)) {
139 return { ok: false, reason: 'invalid', error: `invalid priority: ${patch.priority}` }
140 }
141 data.priority = patch.priority
142 }
143 if (patch.assignee !== undefined) {
144 const a = patch.assignee.trim()
145 if (a.length === 0) return { ok: false, reason: 'invalid', error: 'assignee must be a non-empty string' }
146 data.assignee = a
147 }
148 if (patch.agent !== undefined) {
149 if (patch.agent === null) {
150 delete data.agent
151 } else {
152 const a = patch.agent.trim()
153 if (a.length === 0) return { ok: false, reason: 'invalid', error: 'agent must be a non-empty string or null' }
154 data.agent = a
155 }
156 }
157
158 const content = serializeIssue(data, split.body)
159 // Final guard: never persist a file that wouldn't read back cleanly.
160 const reparsed = parseIssueContent(id, content)
161 if (!reparsed.ok) return { ok: false, reason: 'invalid', error: reparsed.error }
162 await writeWorkspaceFile(wsDir, relFor(id), content)
163 return { ok: true, issue: reparsed.issue }
164}
165
166/**

Callers 4

mutate.spec.tsFile · 0.85
buildFunction · 0.85
createIssuesRoutesFunction · 0.85

Calls 7

relForFunction · 0.85
parseIssueContentFunction · 0.85
parseFrontmatterObjectFunction · 0.85
serializeIssueFunction · 0.85
writeWorkspaceFileFunction · 0.85
splitFrontmatterFunction · 0.70
readWorkspaceFileFunction · 0.50

Tested by

no test coverage detected