package issue import "strings" // Four namespaces classify an issue. type/* is mandatory and exclusive, // severity/* is optional and exclusive, tech/* and comp/* are free-form. // // Colors are NOT here — a hex code is how a tracker paints a chip, which makes // it the sync layer's business. // Types are the kinds of work, and the order is the order they are offered in. var Types = []struct{ Name, Meaning string }{ {"bug", "Something behaves incorrectly in existing code"}, {"task", "Implementation of new functionality"}, {"refactor", "Internal restructuring; behavior must not change"}, {"test", "Writing or fixing tests"}, {"feature", "Container: several issues delivering one unit of business value"}, {"draft", "Idea captured for later; not ready for work"}, } // Severities are the business-impact levels, ascending. var Severities = []string{"low", "medium", "high", "showstopper", "critical"} // Section headers are fixed English literals in a fixed order; only body prose // is Russian. const ( SummarySection = "## Summary" SpecSection = "## Spec" ACSection = "## Acceptance criteria" DependsSection = "## Depends on" IssuesSection = "## Issues" ) // RequiredSections must be present in every type. type/draft is exempt from // acceptance criteria and only from that. var RequiredSections = []string{SummarySection, SpecSection} // DepSections both name what an issue depends on, so both are edge sources and // both point the same way. In a type/feature that reads container -> child: // "the container is closed when its children are closed" IS a dependency. // "a child belongs to a feature" is membership, and membership has no place in // a dependency graph — which is why a child never names its container back. var DepSections = []string{DependsSection, IssuesSection} // ExpectedSections are the per-type sections from the templates. Absence is a // warning, not a stop. var ExpectedSections = map[string][]string{ "bug": {"## Steps to reproduce", "## Expected", "## Actual", "## Environment"}, "task": {"## Motivation"}, "refactor": {"## Motivation", "## Invariants"}, "test": {"## Motivation", "## Test cases"}, "feature": {"## Motivation", IssuesSection}, "draft": {"## Notes"}, } // KnownType reports whether name is one of Types. func KnownType(name string) bool { for _, t := range Types { if t.Name == name { return true } } return false } // KnownSeverity reports whether name is one of Severities. func KnownSeverity(name string) bool { for _, s := range Severities { if s == name { return true } } return false } // TypeNames lists the type slugs, for error messages and completion. func TypeNames() []string { out := make([]string, len(Types)) for i, t := range Types { out[i] = t.Name } return out } // CanonicalLabels is the label set a tracker needs before a push can attach // anything: the two exclusive namespaces in full. tech/* and comp/* are // project-specific and have no preset. func CanonicalLabels() []string { out := make([]string, 0, len(Types)+len(Severities)) for _, t := range Types { out = append(out, "type/"+t.Name) } for _, s := range Severities { out = append(out, "severity/"+s) } return out } // SectionBody is the text under header, up to the next `## ` heading. func SectionBody(body, header string) string { var out []string active := false for _, line := range strings.Split(body, "\n") { if strings.HasPrefix(line, "## ") { if active { break } active = strings.TrimSpace(line) == header continue } if active { out = append(out, line) } } return strings.TrimSpace(strings.Join(out, "\n")) }