(wsDir: string, input: CreateIssueInput)
| 214 | * record on success. |
| 215 | */ |
| 216 | export async function createIssue(wsDir: string, input: CreateIssueInput): Promise<CreateResult> { |
| 217 | const title = input.title?.trim() |
| 218 | if (!title) return { ok: false, reason: 'invalid', error: 'title is required' } |
| 219 | |
| 220 | const id = (input.id?.trim() || slugify(title)) |
| 221 | if (!id || !ID_RE.test(id)) { |
| 222 | return { ok: false, reason: 'invalid', error: `cannot derive a valid id from title "${title}" (pass an explicit id)` } |
| 223 | } |
| 224 | |
| 225 | const existing = await readWorkspaceFile(wsDir, relFor(id)) |
| 226 | if (existing !== null) return { ok: false, reason: 'conflict', id } |
| 227 | |
| 228 | // Assemble frontmatter from only the provided keys (so we don't write default |
| 229 | // noise), then validate the whole thing against the issue schema. |
| 230 | const data: Record<string, unknown> = { title } |
| 231 | if (input.status !== undefined) data.status = input.status |
| 232 | if (input.priority !== undefined) data.priority = input.priority |
| 233 | if (input.assignee !== undefined) data.assignee = input.assignee |
| 234 | if (input.when !== undefined) data.when = input.when |
| 235 | if (input.what !== undefined) data.what = input.what |
| 236 | if (input.agent !== undefined) data.agent = input.agent |
| 237 | |
| 238 | const parsed = issueFrontmatterSchema.safeParse(data) |
| 239 | if (!parsed.success) { |
| 240 | return { |
| 241 | ok: false, |
| 242 | reason: 'invalid', |
| 243 | error: parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; '), |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | const content = serializeIssue(data, input.body ?? '') |
| 248 | const reparsed = parseIssueContent(id, content) |
| 249 | if (!reparsed.ok) return { ok: false, reason: 'invalid', error: reparsed.error } |
| 250 | await writeWorkspaceFile(wsDir, relFor(id), content) |
| 251 | return { ok: true, issue: reparsed.issue } |
| 252 | } |
| 253 | |
| 254 | /** Parse a YAML frontmatter block into a plain object, or null when it isn't a |
| 255 | * mapping. Yields the RAW object for in-place merge — the record from |
no test coverage detected