Documentation
¶
Overview ¶
Package driving defines the driving port — the use-case boundary that CLI and other adapters invoke. The Service interface declares every operation the application supports; the DTO types carry data across that boundary.
Driving adapters (CLI commands, future HTTP handlers, etc.) depend on this package for the interface and its input/output types. The core implementation lives in a separate package and is wired to the interface by the configurator.
Index ¶
- type AddCommentInput
- type AddCommentOutput
- type AgentNameInput
- type BackupInput
- type BackupOutput
- type BlockedByAncestorRow
- type BlockedByClosableIssueRow
- type BlockedByDeferredIssueRow
- type BlockerCycleRow
- type BlockerDetail
- type ClaimInput
- type ClaimNextReadyInput
- type ClaimOutput
- type ClosableParentIssueRow
- type CloseCompletedEpicResult
- type CloseCompletedEpicsInput
- type CloseCompletedEpicsOutput
- type CloseWithReasonInput
- type ClosedParentWithOpenChildRow
- type CommentDTO
- type CommentFilterInput
- type CreateIssueInput
- type CreateIssueOutput
- type DeferIssueInput
- type DeleteInput
- type DoctorFinding
- type DoctorFix
- type DoctorInput
- type DoctorOutput
- type DoctorPassedCheck
- type DoctorSeverity
- type DoctorSkippedCheck
- type EpicProgressInput
- type EpicProgressItem
- type EpicProgressOutput
- type FieldChangeDTO
- type GCInput
- type GCOutput
- type GraphDataOutput
- type HistoryEntryDTO
- type HistoryFilterInput
- type ImportInput
- type ImportLineResult
- type ImportOutput
- type InheritedBlocking
- type InvalidParentReferenceRow
- type IssueFilterInput
- type IssueListItemDTO
- type IssueSummaryOutput
- type LabelFilterInput
- type LabelInput
- type LabelKeyOutput
- type LabelOutput
- type ListCommentsInput
- type ListCommentsOutput
- type ListHistoryInput
- type ListHistoryOutput
- type ListIssuesInput
- type ListIssuesOutput
- type LongDeferralRow
- type MigrationResult
- type OneShotUpdateInput
- type OrderBy
- type PriorityInversionRow
- type PropagateLabelInput
- type PropagateLabelOutput
- type RelationshipDTO
- type RelationshipInput
- type ReopenInput
- type RepairInvalidParentsInput
- type RepairInvalidParentsOutput
- type RepairedParentRecord
- type RestoreInput
- type SearchCommentsInput
- type SearchIssuesInput
- type Service
- type ShowIssueOutput
- type SortDirection
- type TransitionAction
- type TransitionInput
- type UpdateIssueInput
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AddCommentInput ¶
AddCommentInput holds the parameters for adding a comment.
type AddCommentOutput ¶
type AddCommentOutput struct {
Comment CommentDTO
}
AddCommentOutput holds the result of adding a comment.
type AgentNameInput ¶ added in v0.3.0
type AgentNameInput struct {
// Seed is an optional string whose content is used to derive a
// deterministic PCG seed via SHA-256. When Seed is empty, a random name
// is generated instead.
Seed string
}
AgentNameInput carries the inputs for the AgentName use case. Seed is optional; when empty, the service generates a fresh random name via the unseeded domain generator. When non-empty, the service delegates to the seeded domain generator so the same seed always produces the same name.
type BackupInput ¶
type BackupInput struct {
// Writer is the driven-port writer that receives the serialised
// backup data. The caller is responsible for constructing and
// closing the writer.
Writer driven.BackupWriter
}
BackupInput holds the parameters for creating a database backup.
type BackupOutput ¶
type BackupOutput struct {
// IssueCount is the number of issue records written.
IssueCount int
}
BackupOutput holds the result of a backup operation.
type BlockedByAncestorRow ¶ added in v0.4.0
type BlockedByAncestorRow struct {
// Issue is the blocked issue's ID.
Issue string `json:"issue"`
// BlockingAncestor is the ancestor issue that is blocking it.
BlockingAncestor string `json:"blocking_ancestor"`
}
BlockedByAncestorRow is emitted by the blocked-by-ancestor check. One row per (issue, ancestor) pair.
type BlockedByClosableIssueRow ¶ added in v0.4.0
type BlockedByClosableIssueRow struct {
// Issue is the blocked issue's ID.
Issue string `json:"issue"`
// ClosableBlocker is the blocker that is eligible to be closed.
ClosableBlocker string `json:"closable_blocker"`
}
BlockedByClosableIssueRow is emitted by the blocked-by-closable-issue check. One row per (issue, closable blocker) pair.
type BlockedByDeferredIssueRow ¶ added in v0.4.0
type BlockedByDeferredIssueRow struct {
// Issue is the blocked issue's ID.
Issue string `json:"issue"`
// Blocker is the deferred blocking issue's ID.
Blocker string `json:"blocker"`
}
BlockedByDeferredIssueRow is emitted by the blocked-by-deferred-issue check. One row per (issue, deferred blocker) pair.
type BlockerCycleRow ¶ added in v0.4.0
type BlockerCycleRow struct {
// Cycle lists the issue IDs in canonical order.
Cycle []string `json:"cycle"`
}
BlockerCycleRow is emitted by the blocker-cycles check. One row per cycle. The cycle array begins with the lowest-ID issue and follows blocked_by edges around the loop; the last element is blocked by the first.
type BlockerDetail ¶
BlockerDetail holds enriched information about an issue that blocks this one via a blocked_by relationship. The command layer uses this to render blockers ordered by state and annotated with claim/deferral metadata.
type ClaimInput ¶
type ClaimInput struct {
IssueID string
Author string
StaleThreshold time.Duration
// StaleAt is an optional absolute timestamp at which the claim becomes
// stale. When non-zero, it takes precedence over StaleThreshold. The
// caller is responsible for validating that StaleAt is in the future
// and within 24 hours; the service passes it through to the domain.
StaleAt time.Time
// LabelFilters specifies label guard-rail assertions when claiming by ID.
// If non-empty, the issue must match all filters or the claim fails with a
// descriptive error naming the issue and unmet condition.
LabelFilters []LabelFilterInput
// Role specifies a role guard-rail assertion when claiming by ID. If
// non-zero, the issue must have this role or the claim fails with a
// descriptive error naming the issue and unmet condition.
Role domain.Role
}
ClaimInput holds the parameters for claiming an issue.
type ClaimNextReadyInput ¶
type ClaimNextReadyInput struct {
Author string
Role domain.Role
LabelFilters []LabelFilterInput
StaleThreshold time.Duration
// StaleAt is an optional absolute timestamp at which the claim becomes
// stale. When non-zero, it takes precedence over StaleThreshold.
StaleAt time.Time
}
ClaimNextReadyInput holds the parameters for claiming the next ready issue. The service selects the highest-priority open issue that has no active (non-stale) claim held by another author, no unresolved blockers, and matches all provided label and role filters.
type ClaimOutput ¶
type ClaimOutput struct {
ClaimID string
IssueID string
Author string
CreatedAt time.Time
StaleAt time.Time
}
ClaimOutput holds the result of claiming an issue.
type ClosableParentIssueRow ¶ added in v0.4.0
type ClosableParentIssueRow struct {
// Issue is the closable parent's ID.
Issue string `json:"issue"`
}
ClosableParentIssueRow is emitted by the closable-parent-issues check. One row per closable parent.
type CloseCompletedEpicResult ¶
CloseCompletedEpicResult records the outcome of attempting to close a single epic.
type CloseCompletedEpicsInput ¶
type CloseCompletedEpicsInput struct {
// Author is the agent closing the epics. Used for claiming and commenting.
Author string
// DryRun, when true, returns the list of completed epics without closing
// them.
DryRun bool
// IncludeTasks, when true, extends the close-completed logic to tasks that
// have children, all of which are closed. Tasks without children are not
// affected by this flag.
IncludeTasks bool
}
CloseCompletedEpicsInput holds the parameters for batch-closing completed epics.
type CloseCompletedEpicsOutput ¶
type CloseCompletedEpicsOutput struct {
Results []CloseCompletedEpicResult
ClosedCount int
}
CloseCompletedEpicsOutput holds the result of batch-closing completed epics.
type CloseWithReasonInput ¶
CloseWithReasonInput holds the parameters for the atomic close-with-reason workflow. The service derives the author from the claim record, adds the reason as a comment, and closes the issue — all within a single transaction. If any step fails, neither the comment nor the state change persists.
type ClosedParentWithOpenChildRow ¶ added in v0.4.0
type ClosedParentWithOpenChildRow struct {
// Issue is the closed parent's ID.
Issue string `json:"issue"`
// NonClosedChildren lists the parent's non-closed children, sorted ascending.
NonClosedChildren []string `json:"non_closed_children"`
}
ClosedParentWithOpenChildRow is emitted by the closed-parent-with-open-child check. One row per closed parent that still has non-closed children.
type CommentDTO ¶
type CommentDTO struct {
// CommentID is the sequential integer ID (displayed as "comment-N").
CommentID int64
// DisplayID is the human-readable ID (e.g., "comment-368").
DisplayID string
// IssueID is the string representation of the parent issue ID.
IssueID string
// Author is the comment author's name.
Author string
// Body is the comment text.
Body string
// CreatedAt is the creation timestamp.
CreatedAt time.Time
}
CommentDTO is a flat projection of a domain comment with primitive-typed fields. CLI adapters do not need to import domain/comment to read this.
type CommentFilterInput ¶
type CommentFilterInput struct {
// Authors filters comments by any of the listed authors (OR'd).
Authors []string
// CreatedAfter filters to comments created after this timestamp.
CreatedAfter time.Time
// AfterCommentID filters to comments with ID greater than this.
AfterCommentID int64
// IssueIDs scopes the search to specific issues (OR'd).
IssueIDs []string
// ParentIDs scopes to comments on issues that are direct children of
// the specified parents.
ParentIDs []string
// TreeIDs scopes to comments on all issues in the tree rooted at the
// specified IDs.
TreeIDs []string
// LabelFilters scopes to comments on issues matching these labels.
LabelFilters []LabelFilterInput
// FollowRefs expands the scope to include all issues referenced by any
// issue already in scope.
FollowRefs bool
}
CommentFilterInput defines filtering criteria for comment list and search operations. CLI commands construct this type; the service translates it to the repository's filter type internally.
type CreateIssueInput ¶
type CreateIssueInput struct {
Role domain.Role
Title string
Description string
AcceptanceCriteria string
Priority domain.Priority
ParentID string
Labels []LabelInput
Relationships []RelationshipInput
Author string
Claim bool
IdempotencyLabel domain.Label
}
CreateIssueInput holds the parameters for creating an issue.
IdempotencyLabel, when non-zero (Key() != ""), enables deduplication: if any non-deleted issue already carries that exact key:value label, the service returns the existing issue without mutation. When zero, creation always proceeds without a deduplication check.
type CreateIssueOutput ¶
type CreateIssueOutput struct {
Issue domain.Issue
ClaimID string // Non-empty if the issue was created as claimed.
Skipped bool // True if the issue was deduplicated against an existing one.
}
CreateIssueOutput holds the result of creating an issue.
When Skipped is true, a non-deleted issue carrying the same idempotency label already existed; Issue contains that existing issue and no new issue was created. ClaimID is always empty in the skipped case.
type DeferIssueInput ¶
DeferIssueInput holds the parameters for deferring a claimed issue. The operation transitions the issue to the deferred state and releases the claim — both within a single transaction.
type DeleteInput ¶
DeleteInput holds the parameters for soft-deleting an domain.
type DoctorFinding ¶
type DoctorFinding struct {
// Check is the check's slug (e.g., "invalid-parent-reference").
Check string `json:"check"`
// Description is a one-sentence description of what the check detects.
Description string `json:"description"`
// WhyItMatters explains the impact; present only in verbose output.
WhyItMatters string `json:"why_it_matters,omitzero"`
// Summary describes what was found on this run.
Summary string `json:"summary"`
// Affected holds per-check typed rows. Nil for checks with no row schema.
Affected []any `json:"affected,omitzero"`
// Fix describes how to remediate the finding.
Fix DoctorFix `json:"fix"`
}
DoctorFinding represents a single diagnostic check finding. WhyItMatters is populated only when verbose output is requested. Affected is nil for checks that have no per-issue row schema (system and environment checks).
type DoctorFix ¶ added in v0.4.0
type DoctorFix struct {
// Command is the np CLI command to run, when the fix is automated.
Command string `json:"command,omitzero"`
// Instructions provides free-form remediation guidance for manual fixes.
Instructions string `json:"instructions,omitzero"`
}
DoctorFix describes the remediation for a DoctorFinding. Exactly one of Command or Instructions is set: Command for an executable np CLI command, Instructions for free-form manual guidance.
type DoctorInput ¶
type DoctorInput struct {
// MinSeverity controls display filtering; findings below this level are
// omitted from output. Filtering does not affect the exit code.
MinSeverity DoctorSeverity
// LongDeferralThreshold is the duration after which a deferred issue is
// considered stale. Zero means use the default (7 days).
LongDeferralThreshold time.Duration
// WorkDir is the starting directory for the dot-np-directory check's
// upward filesystem walk. Defaults to os.Getwd() when empty.
WorkDir string
// DBPath is the absolute path to the database file, supplied by the
// CLI so the database-exists check can inspect the file before (or
// without) an open SQLite connection. Empty when no .np/ was found.
DBPath string
// DBOpenError carries the error from opening the database, set by the
// CLI when the store could not be opened. The database-exists check
// uses this to report "cannot be opened by SQLite" when the file exists
// and is non-empty but SQLite rejected it.
DBOpenError error
}
DoctorInput holds the parameters for running diagnostics.
type DoctorOutput ¶
type DoctorOutput struct {
// Errors contains all error-severity findings.
Errors []DoctorFinding `json:"errors"`
// Warnings contains all warning-severity findings.
Warnings []DoctorFinding `json:"warnings"`
// Passed lists checks that ran and found no issues; present in verbose output.
Passed []DoctorPassedCheck `json:"passed,omitzero"`
// Skipped lists checks skipped because a prerequisite failed; present in
// verbose output.
Skipped []DoctorSkippedCheck `json:"skipped,omitzero"`
}
DoctorOutput holds the results of the doctor diagnostic.
type DoctorPassedCheck ¶ added in v0.4.0
type DoctorPassedCheck struct {
// Check is the check's slug.
Check string `json:"check"`
// Description is a one-sentence description of what the check verifies.
Description string `json:"description"`
}
DoctorPassedCheck carries identifying information for a check that passed; present in verbose output.
type DoctorSeverity ¶
type DoctorSeverity string
DoctorSeverity represents the severity of a doctor finding. The two-level model (error, warning) maps directly to the JSON contract; string values are used because the external representation matters.
const ( // SeverityError represents a data integrity or correctness violation. SeverityError DoctorSeverity = "error" // SeverityWarning represents a workflow anomaly or misconfiguration. SeverityWarning DoctorSeverity = "warning" )
func ParseDoctorSeverity ¶
func ParseDoctorSeverity(s string) (DoctorSeverity, error)
ParseDoctorSeverity converts a string to a DoctorSeverity. Returns an error for values outside the two-level model (error, warning).
func (DoctorSeverity) String ¶
func (s DoctorSeverity) String() string
String returns the severity as its string value.
type DoctorSkippedCheck ¶ added in v0.4.0
type DoctorSkippedCheck struct {
// Check is the check's slug.
Check string `json:"check"`
// Description is a one-sentence description of what the check verifies.
Description string `json:"description"`
// Prerequisite is the slug of the check whose failure caused this skip.
Prerequisite string `json:"prerequisite"`
}
DoctorSkippedCheck carries identifying information for a check that was skipped because a prerequisite failed; present in verbose output.
type EpicProgressInput ¶
type EpicProgressInput struct {
// Filter restricts which epics are included. When empty, all open epics
// are returned. Callers may set Roles, ExcludeClosed, or other filter
// fields; the service always forces the role to epic.
Filter IssueFilterInput
// EpicID, when non-empty, restricts the result to a single epic.
EpicID string
}
EpicProgressInput holds the parameters for retrieving epic completion data.
type EpicProgressItem ¶
type EpicProgressItem struct {
ID string
Title string
State domain.State
Priority domain.Priority
SecondaryState domain.SecondaryState
// Total is the number of direct children.
Total int
// Closed is the number of children in the closed state.
Closed int
// Open is the number of non-blocked children in the open state.
// This includes children with active claims (open/claimed secondary state).
Open int
// Blocked is the number of children with unresolved blocked_by
// relationships (regardless of primary state).
Blocked int
// Deferred is the number of non-blocked children in the deferred state.
Deferred int
// Percent is the completion percentage (0–100).
Percent int
// Completed is true when all children are closed.
Completed bool
}
EpicProgressItem holds completion data for a single epic.
type EpicProgressOutput ¶
type EpicProgressOutput struct {
Items []EpicProgressItem
}
EpicProgressOutput holds the result of the epic progress query.
type FieldChangeDTO ¶
type FieldChangeDTO struct {
// Field is the name of the changed field.
Field string
// Before is the previous value (empty for additions).
Before string
// After is the new value (empty for removals).
After string
}
FieldChangeDTO describes a single field change within a history entry.
type GCInput ¶
type GCInput struct {
IncludeClosed bool
}
GCInput holds the parameters for garbage collection.
type GCOutput ¶
type GCOutput struct {
DeletedIssuesRemoved int
ClosedIssuesRemoved int
// ExpiredClaimsDeleted is the number of stale claim rows that were
// removed. This count is always populated regardless of IncludeClosed.
ExpiredClaimsDeleted int
}
GCOutput holds the result of garbage collection.
type GraphDataOutput ¶
type GraphDataOutput struct {
// Nodes contains all non-deleted issues as lightweight projections.
Nodes []IssueListItemDTO
// Relationships contains all relationships for the included issues,
// projected as flat DTOs with string fields.
Relationships []RelationshipDTO
}
GraphDataOutput holds the data needed to render an issue graph.
type HistoryEntryDTO ¶
type HistoryEntryDTO struct {
// IssueID is the string representation of the issue this entry belongs to.
IssueID string
// Revision is the sequential revision number within the domain.
Revision int
// Author is the name of the agent that performed the action.
Author string
// Timestamp is when the action occurred.
Timestamp time.Time
// EventType is the canonical string name of the event (e.g., "created",
// "claimed", "closed").
EventType string
// Changes lists the field-level changes recorded in this entry.
Changes []FieldChangeDTO
}
HistoryEntryDTO is a flat projection of a domain history entry with primitive-typed fields. CLI adapters do not need to import domain/history.
type HistoryFilterInput ¶
type HistoryFilterInput struct {
// Author filters entries by author name.
Author string
// After filters entries created after this timestamp.
After time.Time
// Before filters entries created before this timestamp.
Before time.Time
}
HistoryFilterInput defines filtering criteria for history queries. CLI commands construct this type; the service translates it to the repository's filter type internally.
type ImportInput ¶
type ImportInput struct {
Records []domain.ValidatedRecord
DefaultAuthor string
ForceAuthor bool
}
ImportInput holds the parameters for importing validated JSONL records.
type ImportLineResult ¶
type ImportLineResult struct {
IdempotencyLabel domain.Label
IssueID domain.ID
Skipped bool // True if the idempotency label matched an existing issue.
Err error
}
ImportLineResult describes the outcome of importing a single record.
IdempotencyLabel holds the label that was used for deduplication. Its canonical string form (Key():Value()) is suitable for human-readable output. When the source record carried no idempotency label, this field is the zero Label.
type ImportOutput ¶
type ImportOutput struct {
Created int
Skipped int
Failed int
Results []ImportLineResult
}
ImportOutput holds the aggregate result of an import operation.
type InheritedBlocking ¶
InheritedBlocking describes why an issue is not ready due to a blocked ancestor. The AncestorID identifies the first blocked ancestor, and BlockerIDs lists the unresolved blockers on that ancestor.
type InvalidParentReferenceRow ¶ added in v0.4.0
type InvalidParentReferenceRow struct {
// Issue is the child issue's ID.
Issue string `json:"issue"`
// MissingParentID is the referenced parent that does not resolve to a live
// issue.
MissingParentID string `json:"missing_parent_id"`
}
InvalidParentReferenceRow is emitted by the invalid-parent-reference check. One row per child issue with a dangling parent reference.
type IssueFilterInput ¶
type IssueFilterInput struct {
// Roles filters by one or more issue roles (empty means no filter).
Roles []domain.Role
// States filters by one or more states (empty means no filter).
States []domain.State
// Ready filters to only ready issues when true.
Ready bool
// ParentIDs filters to children of one or more parent epics.
// When multiple IDs are provided, issues matching any parent are included.
ParentIDs []string
// DescendantsOf recursively filters to all descendants of an domain.
DescendantsOf string
// AncestorsOf filters to the parent chain of an issue (up to the root).
AncestorsOf string
// LabelFilters specifies label-based filters.
LabelFilters []LabelFilterInput
// Orphan filters to issues that have no parent epic.
Orphan bool
// Blocked filters to issues that have at least one unresolved blocked_by
// relationship (target is neither closed nor deleted).
Blocked bool
// ExcludeClosed hides closed issues from results when true. Ignored when
// States explicitly includes StateClosed — an explicit state filter
// represents intentional user selection and takes precedence.
ExcludeClosed bool
// IncludeDeleted includes soft-deleted issues when true.
IncludeDeleted bool
}
IssueFilterInput defines filtering criteria for issue list and search operations. CLI commands construct this type; the service translates it to the repository's filter type internally.
type IssueListItemDTO ¶
type IssueListItemDTO struct {
// ID is the string representation of the issue ID (e.g., "NP-a3bxr").
ID string
// Role is the issue role (RoleTask or RoleEpic).
Role domain.Role
// State is the primary state (StateOpen, StateClosed, StateDeferred).
// Claimed is a secondary state of open, not a primary state.
State domain.State
// Priority is the priority level (P0–P3).
Priority domain.Priority
// Title is the issue title.
Title string
// ParentID is the string representation of the parent issue ID, or empty
// if the issue has no parent.
ParentID string
// ParentCreatedAt is the creation timestamp of the parent issue. Zero
// when the issue has no parent.
ParentCreatedAt time.Time
// CreatedAt is the creation timestamp.
CreatedAt time.Time
// IsDeleted is true when the issue has been soft-deleted.
IsDeleted bool
// IsBlocked is true when the issue has at least one unresolved blocked_by
// relationship or a blocked/deferred ancestor.
IsBlocked bool
// BlockerIDs contains the string representations of non-closed issue IDs
// that directly block this issue via blocked_by relationships. Empty when
// IsBlocked is false or blocking is inherited only.
BlockerIDs []string
// SecondaryState is the computed list-view secondary state (e.g.,
// SecondaryReady, SecondaryBlocked, SecondaryActive). SecondaryNone
// indicates no secondary state.
SecondaryState domain.SecondaryState
// DisplayStatus is the human-readable status (e.g., "open (ready)",
// "open (blocked)"). Precomputed from State and SecondaryState.
DisplayStatus string
}
IssueListItemDTO is a flat projection of a driven.IssueListItem with all domain types converted to strings. Driving adapters consume this type instead of the driven-port IssueListItem so they do not depend on domain packages. The service layer performs the conversion after retrieving data from the repository.
type IssueSummaryOutput ¶
IssueSummaryOutput holds aggregate issue counts for the status dashboard. Mirrors driven.IssueSummary with an additional Total convenience field. The three primary states are open, closed, and deferred; claimed is a transient secondary state of open and is not counted separately.
type LabelFilterInput ¶
type LabelFilterInput struct {
// Key is the label key to match.
Key string
// Value is the label value to match. Empty for wildcard ("key:*").
Value string
// Negate inverts the filter — exclude issues matching this label.
Negate bool
}
LabelFilterInput specifies a single label-based filter criterion.
type LabelInput ¶
type LabelInput struct {
// Key is the label key (e.g., "kind", "area").
Key string
// Value is the label value (e.g., "bug", "auth").
Value string
}
LabelInput is a service-layer DTO for specifying a label key-value pair. CLI adapters construct this type from raw strings; the service implementation validates and converts to the domain domain.Label type internally.
type LabelKeyOutput ¶ added in v0.3.0
type LabelKeyOutput struct {
// Key is the label key (e.g., "kind", "area").
Key string
// PopularValues holds the 1–3 most frequently used values for this key,
// ordered by descending usage count with an alphabetical tiebreaker.
// It is never nil and always contains at least one entry.
PopularValues []string
}
LabelKeyOutput is a service-layer DTO for returning a label key with its most popular values aggregated across all non-deleted issues. It is the element type returned by ListLabelPopularity.
type LabelOutput ¶
type LabelOutput struct {
// Key is the label key.
Key string
// Value is the label value.
Value string
}
LabelOutput is a service-layer DTO for returning a label key-value pair. The service converts domain domain.Label values into this type so driving adapters do not depend on domain types.
type ListCommentsInput ¶
type ListCommentsInput struct {
IssueID string
Filter CommentFilterInput
Limit int
}
ListCommentsInput holds the parameters for listing comments.
type ListCommentsOutput ¶
type ListCommentsOutput struct {
Comments []CommentDTO
HasMore bool
}
ListCommentsOutput holds the result of listing comments.
type ListHistoryInput ¶
type ListHistoryInput struct {
IssueID string
Filter HistoryFilterInput
Limit int
}
ListHistoryInput holds the parameters for listing history.
type ListHistoryOutput ¶
type ListHistoryOutput struct {
Entries []HistoryEntryDTO
HasMore bool
}
ListHistoryOutput holds the result of listing history.
type ListIssuesInput ¶
type ListIssuesInput struct {
Filter IssueFilterInput
OrderBy OrderBy
Direction SortDirection
Limit int
}
ListIssuesInput holds the parameters for listing issues.
type ListIssuesOutput ¶
type ListIssuesOutput struct {
Items []IssueListItemDTO
HasMore bool
}
ListIssuesOutput holds the result of listing issues. Items are flat service-layer DTOs with string fields — driving adapters do not need to import domain or driven-port packages to read this struct.
type LongDeferralRow ¶ added in v0.4.0
type LongDeferralRow struct {
// Issue is the deferred issue's ID.
Issue string `json:"issue"`
// DeferredAt is when the issue entered the deferred state (RFC3339 UTC).
DeferredAt time.Time `json:"deferred_at"`
// LastActivityAt is the most recent activity timestamp (RFC3339 UTC).
LastActivityAt time.Time `json:"last_activity_at"`
}
LongDeferralRow is emitted by the long-deferrals check. One row per stale deferred issue.
type MigrationResult ¶ added in v0.2.0
type MigrationResult struct {
// ClaimedIssuesConverted is the number of issues whose primary state was
// changed from "claimed" to "open" during the v1→v2 migration.
ClaimedIssuesConverted int
// HistoryRowsRemoved is the number of history rows deleted because their
// event_type was "claimed" or "released", which are no longer valid in v2.
HistoryRowsRemoved int
// LegacyRelationshipsTranslated is the number of relationship rows that
// were translated or dropped during v1→v2 migration: "cites" rows are
// renamed to "refs" and "cited_by" rows are removed. Both counts are summed.
LegacyRelationshipsTranslated int
// IdempotencyKeysMigrated is the number of non-NULL idempotency_key column
// values successfully written as idempotency:<value> label rows during the
// v2→v3 migration.
IdempotencyKeysMigrated int
// IdempotencyKeysSkipped is the number of idempotency_key column values not
// written because the issue already carried an idempotency label
// (skip-on-conflict policy, v2→v3 migration).
IdempotencyKeysSkipped int
// InvalidLabelValuesSkipped is the number of idempotency_key column values
// not written because domain.NewLabel rejected the stored value as invalid
// (v2→v3 migration).
InvalidLabelValuesSkipped int
}
MigrationResult describes the outcome of a schema migration. It is returned by Service.MigrateV1ToV2 and Service.MigrateV2ToV3 and consumed by the upgrade command to produce human-readable or JSON output. Fields that are not relevant to a particular migration step are zero.
type OneShotUpdateInput ¶
type OneShotUpdateInput struct {
IssueID string
Author string
Title *string
Description *string
AcceptanceCriteria *string
Priority *domain.Priority
ParentID *string
LabelSet []LabelInput
LabelRemove []string
}
OneShotUpdateInput holds the parameters for an atomic claim→update→release.
type OrderBy ¶
type OrderBy int
OrderBy specifies the sort order for issue listings. CLI commands use these constants; the service translates them to the repository's ordering type.
const ( // OrderByPriority sorts by priority (highest urgency first), then by // family-anchored creation time, then by issue ID as a tiebreaker. OrderByPriority OrderBy = iota // OrderByCreatedAt sorts by family-anchored creation time (oldest // family first), then by issue created_at within a family. OrderByCreatedAt // OrderByUpdatedAt sorts by family-anchored creation time, then by issue // created_at within a family, then by issue ID as a deterministic // tiebreaker. SortAscending yields oldest-first; SortDescending // yields newest-first — consistent with all other OrderBy values. OrderByUpdatedAt // OrderByPriorityCreated sorts by priority (highest urgency first), // then by the issue's own created_at (ascending), then by issue ID // as a deterministic tiebreaker. Unlike OrderByPriority, this variant // does not use family-anchored sorting. Designed for flat listing // commands (ready, blocked) where parent grouping is not meaningful. OrderByPriorityCreated // OrderByID sorts by issue ID ascending (lexicographic). Because IDs // contain a random component, this produces a stable but effectively // arbitrary order — useful as a neutral default when no semantic // ordering is explicitly requested. OrderByID // OrderByRole sorts by role name ascending (alphabetic), then by // issue ID as a deterministic tiebreaker. OrderByRole // OrderByState sorts by state name ascending (alphabetic), then by // issue ID as a deterministic tiebreaker. OrderByState // OrderByTitle sorts by title ascending (case-insensitive alphabetic), // then by issue ID as a deterministic tiebreaker. OrderByTitle // OrderByParentID sorts by parent issue ID (lexicographic), then by issue // ID as a deterministic tiebreaker. Parentless issues use an empty-string // sentinel, so they cluster at the start under SortAscending and at the // end under SortDescending — descending is the exact reverse of ascending. OrderByParentID // OrderByParentCreated sorts by parent creation time, then by issue ID as // a deterministic tiebreaker. Parentless issues use an empty-string // sentinel, so they cluster at the start under SortAscending and at the // end under SortDescending — descending is the exact reverse of ascending. OrderByParentCreated )
type PriorityInversionRow ¶ added in v0.4.0
type PriorityInversionRow struct {
// Issue is the child issue's ID.
Issue string `json:"issue"`
// Parent is the parent issue's ID.
Parent string `json:"parent"`
// ChildPriority is the child's priority label (e.g., "P0").
ChildPriority string `json:"child_priority"`
// ParentPriority is the parent's priority label (e.g., "P3").
ParentPriority string `json:"parent_priority"`
}
PriorityInversionRow is emitted by the priority-inversions check. One row per (child, parent) pair where the child has strictly higher priority.
type PropagateLabelInput ¶
type PropagateLabelInput struct {
// IssueID is the parent issue whose label value is propagated.
IssueID string
// Key is the label key to propagate.
Key string
// Author is the agent performing the propagation, used for claiming
// each descendant during one-shot updates.
Author string
}
PropagateLabelInput holds the parameters for propagating a label from a parent issue to all its descendants.
type PropagateLabelOutput ¶
type PropagateLabelOutput struct {
// Value is the label value that was propagated from the parent.
Value string
// Propagated is the number of descendants that were updated.
Propagated int
// Total is the total number of descendants examined.
Total int
}
PropagateLabelOutput holds the result of a label propagation operation.
type RelationshipDTO ¶
type RelationshipDTO struct {
// SourceID is the string representation of the relationship's source issue ID.
SourceID string
// TargetID is the string representation of the relationship's target issue ID.
TargetID string
// Type is the canonical string name of the relationship type (e.g.,
// "blocked_by", "blocks", "refs", "parent_of", "child_of").
Type string
}
RelationshipDTO is a flat projection of a domain relationship, using string fields so that driving adapters do not depend on domain types. The service layer converts domain.Relationship values into this type.
type RelationshipInput ¶
type RelationshipInput struct {
Type domain.RelationType
TargetID string
}
RelationshipInput describes a relationship to add during issue creation or update.
type ReopenInput ¶
ReopenInput holds the parameters for reopening a closed or deferred domain. The service validates that the issue is in a reopenable state (closed or deferred), claims it internally, transitions it back to open, and records the appropriate history event (EventReopened or EventUndeferred).
type RepairInvalidParentsInput ¶ added in v0.4.0
type RepairInvalidParentsInput struct {
// Author is the name to record on audit comments for each repaired issue.
Author string
// DryRun, when true, identifies affected issues and returns them without
// performing any writes.
DryRun bool
}
RepairInvalidParentsInput carries the parameters for repairing dangling parent references via the admin fix subcommand.
type RepairInvalidParentsOutput ¶ added in v0.4.0
type RepairInvalidParentsOutput struct {
// Repaired lists each issue whose dangling parent reference was cleared.
// In dry-run mode this slice represents what would have been fixed.
Repaired []RepairedParentRecord
}
RepairInvalidParentsOutput holds the result of a RepairInvalidParentReferences operation.
type RepairedParentRecord ¶ added in v0.4.0
type RepairedParentRecord struct {
// IssueID is the string representation of the issue that was repaired.
IssueID string
// RemovedParentID is the string representation of the dangling parent
// that was removed from the issue.
RemovedParentID string
}
RepairedParentRecord describes a single issue whose dangling parent reference was cleared (or would be cleared in dry-run mode).
type RestoreInput ¶
type RestoreInput struct {
// Reader is the driven-port reader that provides the serialised
// backup data. The caller is responsible for constructing and
// closing the reader.
Reader driven.BackupReader
}
RestoreInput holds the parameters for restoring a database from backup.
type SearchCommentsInput ¶
type SearchCommentsInput struct {
Query string
IssueID string // Empty for global search.
Filter CommentFilterInput
Limit int
}
SearchCommentsInput holds the parameters for searching comments.
type SearchIssuesInput ¶
type SearchIssuesInput struct {
Query string
Filter IssueFilterInput
OrderBy OrderBy
Direction SortDirection
Limit int
IncludeNotes bool
}
SearchIssuesInput holds the parameters for searching issues.
type Service ¶
type Service interface {
// Init creates a new database with the given prefix.
Init(ctx context.Context, prefix string) error
// AgentName generates an agent name. When input.Seed is empty, the
// returned name is randomly generated on each call. When input.Seed is
// non-empty, the same seed value always produces the same name, allowing
// callers to derive a stable identity from a process-level value such as
// $PPID.
AgentName(ctx context.Context, input AgentNameInput) (string, error)
// GetPrefix returns the database's configured issue ID prefix.
GetPrefix(ctx context.Context) (string, error)
// CreateIssue creates a new issue.
CreateIssue(ctx context.Context, input CreateIssueInput) (CreateIssueOutput, error)
// ClaimByID claims a specific issue.
ClaimByID(ctx context.Context, input ClaimInput) (ClaimOutput, error)
// ClaimNextReady claims the highest-priority ready issue.
ClaimNextReady(ctx context.Context, input ClaimNextReadyInput) (ClaimOutput, error)
// LookupClaimIssueID returns the string representation of the issue ID
// associated with the given claim ID. Returns domain.ErrNotFound if the
// claim does not exist.
LookupClaimIssueID(ctx context.Context, claimID string) (string, error)
// LookupClaimAuthor returns the author who holds the given claim.
// Returns domain.ErrNotFound if the claim does not exist.
LookupClaimAuthor(ctx context.Context, claimID string) (string, error)
// OneShotUpdate performs an atomic claim→update→release.
OneShotUpdate(ctx context.Context, input OneShotUpdateInput) error
// UpdateIssue updates a claimed issue's fields.
UpdateIssue(ctx context.Context, input UpdateIssueInput) error
// ExtendStaleThreshold extends the stale threshold on an active claim.
ExtendStaleThreshold(ctx context.Context, issueID string, claimID string, threshold time.Duration) error
// TransitionState changes the state of a claimed issue.
TransitionState(ctx context.Context, input TransitionInput) error
// CloseWithReason atomically adds a closing reason as a comment and
// transitions the issue to closed. The author is derived from the claim
// record. If any step fails, neither the comment nor the state change
// persists. Returns an error if the reason is empty, the claim is invalid,
// or the state transition is not allowed.
CloseWithReason(ctx context.Context, input CloseWithReasonInput) error
// DeferIssue transitions a claimed issue to the deferred state and
// releases the claim — all within a single transaction.
DeferIssue(ctx context.Context, input DeferIssueInput) error
// ReopenIssue transitions a closed or deferred issue back to the open
// state. The operation is atomic: the issue is claimed internally,
// transitioned to open, and released — all within a single transaction.
// Records EventReopened for closed issues or EventUndeferred for
// deferred issues.
ReopenIssue(ctx context.Context, input ReopenInput) error
// DeleteIssue soft-deletes a claimed issue.
DeleteIssue(ctx context.Context, input DeleteInput) error
// ShowIssue returns the full detail view of an issue.
ShowIssue(ctx context.Context, id string) (ShowIssueOutput, error)
// ListIssues returns a filtered, ordered, paginated list of issues.
ListIssues(ctx context.Context, input ListIssuesInput) (ListIssuesOutput, error)
// SearchIssues performs full-text search on issues.
SearchIssues(ctx context.Context, input SearchIssuesInput) (ListIssuesOutput, error)
// GetIssueSummary returns aggregate issue counts by primary state and
// computed readiness/blocked status. More efficient than calling
// ListIssues multiple times — uses a single query internally.
GetIssueSummary(ctx context.Context) (IssueSummaryOutput, error)
// EpicProgress returns completion data for open epics. It uses
// GetChildStatuses to compute progress in a single query per epic,
// avoiding the N+1 pattern of listing children via ListIssues.
EpicProgress(ctx context.Context, input EpicProgressInput) (EpicProgressOutput, error)
// CloseCompletedEpics finds all open epics in the completed secondary
// state and batch-closes them. Each epic is claimed, a closing comment
// is added, and the epic is transitioned to closed. Per-epic failures
// are captured in the result without aborting the batch.
CloseCompletedEpics(ctx context.Context, input CloseCompletedEpicsInput) (CloseCompletedEpicsOutput, error)
// ListLabelPopularity returns label keys together with their three most
// frequently used values across all non-deleted issues (open, closed, and
// deferred). Results are grouped by key; within each entry the
// PopularValues slice is ordered by descending usage count with an
// alphabetical tiebreaker, and contains between one and three entries.
// The count in any envelope built from this output should reflect the
// number of distinct keys, not the number of distinct key-value pairs.
ListLabelPopularity(ctx context.Context) ([]LabelKeyOutput, error)
// AddRelationship adds a relationship between two issues. The sourceID
// is the string representation of the source issue ID.
AddRelationship(ctx context.Context, sourceID string, rel RelationshipInput, author string) error
// RemoveRelationship removes a relationship between two issues. The
// sourceID is the string representation of the source issue ID.
RemoveRelationship(ctx context.Context, sourceID string, rel RelationshipInput, author string) error
// RemoveBidirectionalBlock removes any blocked_by relationship between
// issueA and issueB regardless of which direction was stored. It tries
// both "A blocked_by B" and "B blocked_by A". The operation is
// idempotent — if neither relationship exists, no error is returned.
// Both issue IDs are string representations.
RemoveBidirectionalBlock(ctx context.Context, issueA, issueB string, author string) error
// PropagateLabel copies a label from a parent issue to all its descendants
// that lack it or have a different value. Each descendant is updated via
// OneShotUpdate (atomic claim→update→release). Per-descendant failures
// are skipped without aborting the batch.
PropagateLabel(ctx context.Context, input PropagateLabelInput) (PropagateLabelOutput, error)
// AddComment adds a comment to an issue.
AddComment(ctx context.Context, input AddCommentInput) (AddCommentOutput, error)
// ShowComment retrieves a single comment by ID.
ShowComment(ctx context.Context, commentID int64) (CommentDTO, error)
// ListComments lists comments for an issue.
ListComments(ctx context.Context, input ListCommentsInput) (ListCommentsOutput, error)
// SearchComments searches comments by text.
SearchComments(ctx context.Context, input SearchCommentsInput) (ListCommentsOutput, error)
// ShowHistory lists history entries for an issue.
ShowHistory(ctx context.Context, input ListHistoryInput) (ListHistoryOutput, error)
// GetGraphData returns all non-deleted issues and their relationships
// in a single read-only transaction, for rendering as a graph.
GetGraphData(ctx context.Context) (GraphDataOutput, error)
// Doctor runs diagnostics and returns classified findings. The input
// controls the minimum severity threshold and allows the caller to inject
// additional findings from checks that run outside the service layer
// (e.g., filesystem checks). The output includes per-check pass/fail
// status and a healthy flag.
Doctor(ctx context.Context, input DoctorInput) (DoctorOutput, error)
// RepairInvalidParentReferences scans every non-deleted issue for a
// parent_id that refers to an absent or soft-deleted parent, clears those
// references, and records an audit comment on each affected issue. When
// input.DryRun is true, the scan runs but no writes are performed and the
// output describes what would have been fixed.
RepairInvalidParentReferences(ctx context.Context, input RepairInvalidParentsInput) (RepairInvalidParentsOutput, error)
// GC performs garbage collection.
GC(ctx context.Context, input GCInput) (GCOutput, error)
// Backup writes a complete snapshot of all non-deleted issues — with
// their comments, labels, relationships, claims, and history — to the
// provided BackupWriter. The operation runs in a single read
// transaction for snapshot consistency.
Backup(ctx context.Context, input BackupInput) (BackupOutput, error)
// Restore replaces the entire database contents with data read from
// the provided BackupReader. This is a destructive operation: all
// existing data is removed before the backup is applied. The restore
// is atomic — either the full backup is applied or the database is
// left unchanged.
Restore(ctx context.Context, input RestoreInput) error
// ImportIssues creates issues from validated JSONL import records.
ImportIssues(ctx context.Context, input ImportInput) (ImportOutput, error)
// CountAllIssues returns the total number of issues in the database,
// including closed and deferred issues but excluding soft-deleted ones.
CountAllIssues(ctx context.Context) (int, error)
// ResetDatabase removes all data from every table and reclaims disk
// space. This is a destructive operation: all issues, comments, claims,
// relationships, history, and labels are permanently deleted. The
// caller is responsible for creating a backup beforehand.
ResetDatabase(ctx context.Context) error
// CheckSchemaVersion returns nil when the database is at the current
// schema version (v3). It returns a wrapped domain.ErrSchemaMigrationRequired
// when the schema is at an older version. Commands that gate on schema
// version call this method rather than accessing the storage adapter directly.
CheckSchemaVersion(ctx context.Context) error
// MigrateV1ToV2 upgrades a v1 database to v2 schema in a single atomic
// transaction. The migration converts claimed-state issues to open, removes
// obsolete history event types, and records schema_version=2. Callers should
// call CheckSchemaVersion first to distinguish the "already current" case
// from a migration that made changes. Returns a MigrationResult with row
// counts for each migration step.
MigrateV1ToV2(ctx context.Context) (MigrationResult, error)
// MigrateV2ToV3 upgrades a v2 database to v3 schema in a single atomic
// transaction. The migration carries each non-NULL idempotency_key column
// value forward as an idempotency:<value> label row (skip-on-conflict policy),
// drops the idx_issues_idempotency unique index and the idempotency_key column,
// and records schema_version=3. Callers should call CheckSchemaVersion first to
// distinguish the "already current" case. Returns a MigrationResult with counts
// for migrated, skipped, and invalid rows.
MigrateV2ToV3(ctx context.Context) (MigrationResult, error)
}
Service defines the driving port — the use-case boundary that CLI and other adapters invoke. Each method corresponds to a command from §8 of the specification.
type ShowIssueOutput ¶
type ShowIssueOutput struct {
// Flat primitive-typed fields populated from the domain Issue by the
// service implementation.
ID string
Role domain.Role
Title string
Description string
AcceptanceCriteria string
Priority domain.Priority
State domain.State
ParentID string
Labels map[string]string
CreatedAt time.Time
Revision int
Author string
Relationships []RelationshipDTO
IsReady bool
// SecondaryState is the single list-view secondary state (e.g.,
// SecondaryReady, SecondaryBlocked). SecondaryNone indicates no secondary
// state.
SecondaryState domain.SecondaryState
// DetailStates is the ordered set of secondary conditions for detail views
// (e.g., [SecondaryBlocked, SecondaryActive]). May contain multiple
// entries for epics. Empty or nil when there is no secondary state.
DetailStates []domain.SecondaryState
InheritedBlocking *InheritedBlocking
CommentCount int
ChildCount int
Children []IssueListItemDTO
Comments []CommentDTO
ParentTitle string
BlockerDetails []BlockerDetail
ClaimID string
ClaimAuthor string
ClaimedAt time.Time
ClaimStaleAt time.Time
}
ShowIssueOutput holds the full detail view of an domain. All fields are primitive types or service-layer DTOs — CLI adapters do not need to import the domain package to read this struct.
type SortDirection ¶ added in v0.2.0
type SortDirection int
SortDirection indicates ascending or descending sort order. The zero value (SortAscending) is the standard ascending direction for every OrderBy value: lowest numeric value first, oldest timestamp first, alphabetic A–Z, etc.
const ( // SortAscending is the default direction — lowest first, oldest first, // alphabetic A–Z. For timestamp-based OrderBy values (OrderByCreatedAt, // OrderByUpdatedAt) this means oldest issues appear first. SortAscending SortDirection = iota // SortDescending reverses the primary sort axis while keeping tiebreaker // columns ascending for deterministic output. For timestamp-based OrderBy // values (OrderByCreatedAt, OrderByUpdatedAt) this means newest issues // appear first. SortDescending )
type TransitionAction ¶
type TransitionAction int
TransitionAction identifies the kind of state transition.
const ( // ActionRelease returns the issue to its default unclaimed state. ActionRelease TransitionAction = iota + 1 // ActionClose marks a task as complete. Terminal. ActionClose // ActionDefer shelves the domain. ActionDefer )
type TransitionInput ¶
type TransitionInput struct {
IssueID string
ClaimID string
Action TransitionAction
}
TransitionInput holds the parameters for a state transition.
type UpdateIssueInput ¶
type UpdateIssueInput struct {
IssueID string
ClaimID string
Title *string
Description *string
AcceptanceCriteria *string
Priority *domain.Priority
ParentID *string
LabelSet []LabelInput
LabelRemove []string
RelationshipAdd []RelationshipInput
RelationshipRemove []RelationshipInput
CommentBody string
}
UpdateIssueInput holds the parameters for updating a claimed domain.