import { Issue, IssuePriority, IssueSeverity, IssueStatus } from './issue.js'; import { IssueFormat } from './issueCodec.js'; /** An issue plus where it lives. The path is for diagnostics, not for callers to write through. */ /** * What a close actually did. `preservedResolution` is the difference between a * first close and a re-close that kept the original reasoning — without it the * caller cannot tell the two apart, which is how ISS-0168 stayed silent. */ export interface CloseResult { stored: StoredIssue; preservedResolution: boolean; } export interface StoredIssue { issue: Issue; filename: string; path: string; format: IssueFormat; /** Integrity observations from decoding. Empty for a healthy record. */ warnings: string[]; } export interface IssueListFilter { status?: IssueStatus; /** All non-terminal statuses. Distinct from status:'open', which is one value. */ liveOnly?: boolean; epicId?: string; } export interface CreateIssueSpec { title: string; details: string; severity: IssueSeverity; priority?: IssuePriority; epicId?: string; tags?: string[]; project?: string; /** Prefixed identity — user:/agent:/human:. Validated at the tool boundary. */ assignee?: string; /** `team:`. Validated at the tool boundary. */ team?: string; } /** * Integrity state of the whole store, reported alongside a listing rather than * discovered mid-incident. * * The distinction that matters: `malformed` records are invisible to callers, * so a silent skip would make a broken store look like a shorter clean one. * `degraded` records are readable but carry something the codec had to * interpret. Both are repair queues, not cosmetics — a duplicate id makes an * issue unwritable, because the repository refuses an ambiguous target. */ export interface StoreIntegrity { malformed: string[]; degraded: Array<{ filename: string; warnings: string[]; }>; duplicateIds: Record; } export interface IssueRepository { integrity(): Promise; list(filter?: IssueListFilter): Promise; get(idOrFilename: string): Promise; create(spec: CreateIssueSpec): Promise; update(idOrFilename: string, changes: Partial): Promise; close(idOrFilename: string, resolution: string, status?: 'closed' | 'wontfix'): Promise; } export declare class AmbiguousIssueIdError extends Error { readonly issueId: string; readonly candidates: string[]; readonly code = "AMBIGUOUS_ISSUE_ID"; constructor(issueId: string, candidates: string[]); } export declare class IssueNotFoundError extends Error { readonly issueId: string; readonly code = "ISSUE_NOT_FOUND"; constructor(issueId: string); } export declare class FsIssueRepository implements IssueRepository { private readonly issuesDir; constructor(issuesDir: string); private readAll; /** * Duplicate detection deliberately runs over EVERY record, never a filtered * subset: a closed twin still makes an id ambiguous for writers, so filtering * it out of a listing must not filter it out of the collision report. */ integrity(): Promise; list(filter?: IssueListFilter): Promise; /** * Resolve an id to exactly one record, in tiers of decreasing specificity. * * Only the first non-empty tier is considered, and ambiguity is reported * within a tier rather than across tiers — so naming a record by its filename * stays a reliable way to pick one member of a duplicate pair even while the * shared ISS-NNNN remains ambiguous. uid is checked first because it is the * only identifier guaranteed unique by construction. */ private resolve; get(idOrFilename: string): Promise; private requireOne; create(spec: CreateIssueSpec): Promise; update(idOrFilename: string, changes: Partial): Promise; /** * Close a record, or report that it was already closed without touching what * the first close recorded. * * A re-close used to overwrite `resolution` and move `closed_at` forward * (ISS-0168). That is data loss, and silent: the resolution field is the only * place the reasoning behind a fix survives outside a commit message, and the * caller was told "closed" either way. The trigger was operator error — a * `Closes:` trailer naming an already-closed issue — but the failure mode is * not: citing the issue a piece of follow-up work came from is normal, and * every such citation destroyed the original reasoning. * * So a record that is already terminal AND already carries a resolution keeps * both. A status move between closed and wontfix is still honoured, since * that is a real decision; a missing `closed_at` is filled in, since that only * adds. When nothing is left to change the file is not rewritten at all, so a * re-close does not even bump `updated_at`. * * Amending a resolution on purpose is `update`, which says what it does. */ close(idOrFilename: string, resolution: string, status?: 'closed' | 'wontfix'): Promise; } //# sourceMappingURL=issueRepository.d.ts.map