issue

package
v0.14.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 18, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package issue — lifecycle transition orchestration for the Issue kind.

This file hosts ChangeStatus, the kind-specific orchestrator invoked by `specscore issue change-status`. It performs line-based YAML frontmatter rewriting for the `status:` field (and optionally `severity:`, `rejection_reason:`, `rejection_notes:` fields).

LINT INVOCATION lives in the cobra adapter (internal/cli/issue.go), NOT here, to avoid an import cycle: pkg/lint already imports pkg/issue for the issue-* lint rules, so pkg/issue cannot depend back on pkg/lint. The adapter passes a PostMutationHook into ChangeStatus; this package only knows "run the post-mutation hook, and roll back if it fails".

Package issue provides parsing and discovery for SpecScore `issue` artifacts. Issues are markdown files declaring `type: issue` in their YAML frontmatter and living at one of two canonical locations:

  • spec/issues/<slug>.md (root-level)
  • spec/features/<feature-slug>/issues/<slug>.md (Feature-scoped)

See spec/features/cli/spec/lint/issue-rules/README.md for the full contract.

Index

Constants

View Source
const RuleFamilyPrefix = "I-"

RuleFamilyPrefix is the canonical ID prefix for every lint rule that validates `issue` artifacts (I-001 .. I-015).

View Source
const TypeValue = "issue"

TypeValue is the literal frontmatter value identifying an issue artifact.

Variables

View Source
var PathPatterns = []string{
	"issues/*.md",
	"features/*/issues/*.md",
}

PathPatterns names the two glob patterns (relative to the spec root) where `issue` artifacts may live. Files outside these patterns that declare `type: issue` violate rule I-009 (dual-location).

The patterns are written without the leading `spec/` prefix because the lint engine always operates relative to the discovered spec root.

View Source
var ValidReasonValues = []string{"not-a-defect", "wont-fix", "duplicate", "not-reproducible", "by-design", "deferred"}

ValidReasonValues returns valid --reason values for rejection.

View Source
var ValidSeverityValues = map[string]bool{
	"low": true, "medium": true, "high": true, "critical": true,
}

ValidSeverityValues is the set of valid --severity values for issue new.

View Source
var ValidTargetStatuses = []string{"investigating", "resolved", "rejected"}

ValidTargetStatuses returns valid --to values for issue change-status.

Functions

func IsLegalTransition

func IsLegalTransition(from, to string) bool

IsLegalTransition checks if from→to is a valid issue transition.

func LegalTransitions

func LegalTransitions() map[string][]string

LegalTransitions returns the issue transition matrix.

func Scaffold

func Scaffold(opts ScaffoldOptions) ([]byte, error)

Scaffold returns a lint-clean Issue file body for the given options.

func ValidateSlug

func ValidateSlug(slug string) error

ValidateSlug checks that the slug conforms to issue slug rules: lowercase, [a-z0-9-] only, no leading/trailing hyphens, max 60 chars.

Types

type ChangeStatusOptions

type ChangeStatusOptions struct {
	// SpecRoot is the project root that contains the `spec/` subtree
	// (NOT the `spec/` directory itself).
	SpecRoot string

	// Slug is the Issue slug.
	Slug string

	// To is the target status (lowercase): investigating, resolved, rejected.
	To string

	// Severity is an optional severity override. Required when transitioning
	// and current severity is absent.
	Severity string

	// Reason is required when To=rejected.
	Reason string

	// Notes is optional when To=rejected.
	Notes string

	// PostMutation is the post-rewrite hook (typically a spec-lint pass).
	PostMutation PostMutationHook
}

ChangeStatusOptions packages the inputs to ChangeStatus.

type ChangeStatusResult

type ChangeStatusResult struct {
	Slug string
	Path string
	From string
	To   string
}

ChangeStatusResult is the success payload returned by ChangeStatus.

func ChangeStatus

func ChangeStatus(opts ChangeStatusOptions) (ChangeStatusResult, error)

ChangeStatus performs an Issue-kind lifecycle transition end-to-end.

Flow:

  1. Find the issue file by slug using DiscoverAll.
  2. Parse current status from frontmatter.
  3. Validate the transition is legal.
  4. Check severity gating.
  5. Check rejection gating (require Reason when To=rejected).
  6. Rewrite frontmatter fields.
  7. Invoke PostMutation hook; rollback on failure.

type Discovered

type Discovered struct {
	// Path is the absolute path to the file.
	Path string
	// RelPath is the path relative to the spec root, with forward slashes.
	RelPath string
	// Slug is the basename minus `.md`.
	Slug string
	// MatchesPattern is true when RelPath matches one of the two
	// canonical PathPatterns. Files declaring `type: issue` outside
	// the patterns have MatchesPattern == false (I-009 candidates).
	MatchesPattern bool
	// FeatureSlug, when non-empty, is the parent Feature slug for a
	// Feature-scoped issue (RelPath of the form
	// `features/<feature-slug>/issues/<slug>.md`). Empty for root-level
	// issues or off-pattern issues.
	FeatureSlug string
}

Discovered summarizes one file that declares `type: issue` in its YAML frontmatter, regardless of whether its path matches PathPatterns.

func DiscoverAll

func DiscoverAll(specRoot string) ([]Discovered, error)

DiscoverAll walks the entire spec tree (skipping hidden dirs) and returns every markdown file whose YAML frontmatter declares `type: issue`. The walk is deliberately broad — restricting to the canonical PathPatterns would hide exactly the files I-009 needs to flag.

Returns ([], nil) when specRoot does not exist or is not a directory.

type Issue

type Issue struct {
	// Path is the absolute (or otherwise caller-supplied) path to the file.
	Path string
	// Slug is the basename minus `.md`.
	Slug string
	// Type is the frontmatter `type` value (empty when missing or
	// unparseable). Only files where Type == TypeValue are considered
	// issue artifacts.
	Type string
	// Frontmatter holds the top-level YAML mapping keys (values
	// stringified). nil when no frontmatter could be parsed.
	Frontmatter map[string]string
	// FrontmatterKeyOrder preserves the order keys appeared in the
	// source YAML.
	FrontmatterKeyOrder []string
	// Body is the markdown body after the closing `---`.
	Body string
	// HasFrontmatter is true when the file begins with `---` and a
	// closing `---` line is found.
	HasFrontmatter bool
	// BugsRaw is the raw YAML node for the `bugs` frontmatter field, or
	// nil when the field is absent. Exposed so list-aware rules (I-004)
	// can inspect the node's Kind and elements without re-parsing.
	BugsRaw *yaml.Node
}

Issue is the parsed representation of an issue artifact. It is deliberately minimal at this Task: only the fields the I-009 rule (and downstream rules in later tasks) need.

func Parse

func Parse(path string) (*Issue, error)

Parse reads the file at path and returns a parsed Issue. Parse is resilient: it returns a partial Issue even when the file is malformed (no frontmatter, invalid YAML, missing keys). Callers (lint rules) decide what is an error.

Returns (nil, err) only when the file cannot be read.

type PostMutationHook

type PostMutationHook func() error

PostMutationHook is called after a successful status rewrite.

type ScaffoldOptions

type ScaffoldOptions struct {
	Slug              string
	Title             string // defaults to title-cased slug
	CapturedBy        string // defaults to $USER
	CapturedAt        string // RFC 3339 timestamp, defaults to now UTC
	Severity          string // optional: low|medium|high|critical
	AffectedComponent string // optional: Feature slug
	FirstSeen         string // optional
	GithubIssue       string // optional
}

ScaffoldOptions controls the content emitted by Scaffold.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL