Documentation
¶
Overview ¶
Package onboarding implements the shared logic for the krit init onboarding flow. It is consumed by both the bash/gum prototype (scripts/krit-init.sh, via the krit binary) and by the bubbletea TUI in cmd/krit/init.go.
The package has three concerns:
- Loading and validating config/onboarding/controversial-rules.json (the Registry type below).
- Scanning a target directory with each shipped profile and parsing the JSON summary (profiles.go).
- Generating a merged krit.yml from a chosen profile plus the user's answers to the questionnaire (writer.go).
The gum script predates this package and still uses yq/jq directly. When the two diverge in behavior, this package is the source of truth — the TUI is the production implementation.
Index ¶
- Variables
- func ProfilePath(repoRoot, name string) string
- func ScanAllProfiles(ctx context.Context, opts ScanOptions) (map[string]*ScanResult, error)
- func WriteConfig(opts WriteConfigOptions) ([]byte, error)
- func WriteConfigFile(targetDir string, opts WriteConfigOptions) (string, error)
- func YAMLUnmarshalMap(data []byte, out *map[string]interface{}) error
- type Answer
- type FindingSample
- type Override
- type ProgressEvent
- type Question
- type Registry
- type RuleCount
- type ScanOptions
- type ScanResult
- type ThresholdOverride
- type WriteConfigOptions
Constants ¶
This section is empty.
Variables ¶
var ProfileNames = []string{"strict", "balanced", "relaxed", "detekt-compat"}
ProfileNames lists the onboarding profiles in presentation order (tightest → loosest). This order also determines the order of rows in the comparison table.
var StrictStages = []struct { Prefix string Label string }{ {"verbose: Found", "discovered files"}, {"verbose: Type resolver", "type resolver ready"}, {"verbose: Running", "rules loaded"}, {"verbose: Cache:", "cache loaded"}, {"verbose: Parsed", "parsed sources"}, {"verbose: Type-indexed", "type indexed"}, {"verbose: Cross-file", "cross-file analyzed"}, {"verbose: Analyzed", "rules executed"}, {"verbose: Android project", "android analyzed"}, }
StrictStages lists the ordered sub-stages emitted by `krit -v` to stderr during a scan. The TUI uses this to render a live progress bar for the strict profile scan (the slowest, cold-cache one). Each entry pairs a stderr-line prefix with a short human label. Stages that don't fire in smaller projects (e.g. Android-only) are still listed — the progress bar simply advances when it sees them.
Functions ¶
func ProfilePath ¶
ProfilePath returns the path to a profile YAML file for the given profile name, rooted at repoRoot/config/profiles/.
func ScanAllProfiles ¶
func ScanAllProfiles(ctx context.Context, opts ScanOptions) (map[string]*ScanResult, error)
ScanAllProfiles scans the target with every profile in ProfileNames sequentially. Returns a map keyed by profile name. The sequential execution lets krit's incremental cache warm up — the first scan parses every file, the rest only re-evaluate rules.
func WriteConfig ¶
func WriteConfig(opts WriteConfigOptions) ([]byte, error)
WriteConfig returns the merged krit.yml bytes. It deep-merges the profile template with the override tree, then prepends a header comment documenting the profile and override count.
This is the Go equivalent of the gum script's `yq eval-all '. as $item ireduce ({}; . * $item)'` invocation.
func WriteConfigFile ¶
func WriteConfigFile(targetDir string, opts WriteConfigOptions) (string, error)
WriteConfigFile is a thin wrapper around WriteConfig that also handles backup of an existing krit.yml and 0644 file creation.
func YAMLUnmarshalMap ¶
YAMLUnmarshalMap is a thin wrapper so callers outside this package don't need to import gopkg.in/yaml.v3 directly. Used by the TUI to read threshold values out of profile templates.
Types ¶
type Answer ¶
type Answer struct {
QuestionID string
Value bool // true = yes, false = no
Cascaded bool // true if derived from a parent answer
Parent string
}
Answer represents a resolved yes/no answer to one question, along with whether the answer was user-chosen (interactive) or cascaded from a parent question.
func ResolveAnswers ¶
func ResolveAnswers(reg *Registry, profile string, ask func(q *Question, defaultYes bool) bool) ([]Answer, error)
ResolveAnswers walks the registry in declaration order and fills in answers for every question. Interactive callers pass an `ask` function that returns the user's yes/no choice for a question; non-interactive callers (CI, tests) can pass a closure that returns the per-profile default.
Cascade semantics: when a parent question is answered, every child whose cascade_from == parent is given a derived answer using the per-profile default of the parent's answer-bucket. Children are added to the cascaded set so `ask` is never called for them.
parent answered "yes" → each child uses its strict default parent answered "no" → each child uses its relaxed default
This matches the gum script's apply_cascades behavior.
type FindingSample ¶
FindingSample is a trimmed finding stored for TUI preview. The explorer right pane shows at most 3 per rule — only file, line, and message are kept.
type Override ¶
Override is one ruleset+rule active state to merge into a profile.
func BuildOverrides ¶
BuildOverrides translates resolved answers into override tuples. Questions with an empty rules list (parent-only metas) produce no overrides on their own — their children do the work.
type ProgressEvent ¶
type ProgressEvent struct {
// StageIndex is the 1-based index of the stage just completed
// (1..len(StrictStages)).
StageIndex int
// StageLabel is the human-readable label from StrictStages.
StageLabel string
// TotalStages is len(StrictStages), duplicated here so callers
// don't need to import the slice.
TotalStages int
}
ProgressEvent is emitted by ScanProfileWithProgress as each verbose-stderr stage marker is seen.
type Question ¶
type Question struct {
ID string `json:"id"`
Question string `json:"question"`
Rationale string `json:"rationale"`
Rules []string `json:"rules"`
CascadeFrom *string `json:"cascade_from"`
CascadesTo []string `json:"cascades_to"`
Defaults map[string]bool `json:"defaults"`
PositiveFixture *string `json:"positive_fixture"`
NegativeFixture *string `json:"negative_fixture"`
Kind string `json:"kind"`
}
Question is a single entry in the registry.
func (*Question) Invert ¶
Invert reports whether this question's "yes" answer should DISABLE its rules rather than enable them. By convention, questions whose ID starts with "allow-" are inverted (yes = allow = rule off).
func (*Question) RulesetForYAML ¶
RulesetForQuestion derives the YAML ruleset name for a question from its positive_fixture path. Fixture paths follow the tests/fixtures/positive/<ruleset>/<Rule>.kt convention. For parent questions with no fixture, returns an empty string — callers should fall back to an explicit mapping or skip the question.
type Registry ¶
type Registry struct {
SchemaVersion int `json:"schemaVersion"`
Description string `json:"description"`
Questions []Question `json:"questions"`
}
Registry is the parsed form of config/onboarding/controversial-rules.json.
func LoadRegistry ¶
LoadRegistry reads and parses the controversial-rules registry from disk. It validates schemaVersion, cascade references, and that every question has per-profile defaults for all four shipped profiles.
type ScanOptions ¶
type ScanOptions struct {
// KritBin is the path to the krit binary. Required.
KritBin string
// RepoRoot is the krit repository root (so ProfilePath can resolve
// config/profiles/<name>.yml).
RepoRoot string
// Target is the directory to scan.
Target string
}
ScanOptions configures a single krit invocation performed via the ScanProfile helper.
type ScanResult ¶
type ScanResult struct {
Profile string `json:"profile"`
Total int `json:"total"`
Fixable int `json:"fixable"`
ByRule map[string]int `json:"byRule"`
Findings map[string][]FindingSample `json:"-"` // keyed by rule name; capped at 3 per rule
}
ScanResult holds the parsed JSON summary for a single profile scan. Only the fields the onboarding flow actually uses are modeled; the full krit JSON schema has many more keys.
func ScanProfile ¶
func ScanProfile(ctx context.Context, opts ScanOptions, profile string) (*ScanResult, error)
ScanProfile invokes `krit --config <profile.yml> -f json <target>` and parses the resulting summary. Krit exits non-zero when findings exist; that is treated as success here — only JSON parse errors bubble up.
func ScanProfileWithProgress ¶
func ScanProfileWithProgress( ctx context.Context, opts ScanOptions, profile string, progress func(ProgressEvent), ) (*ScanResult, error)
ScanProfileWithProgress is like ScanProfile but also emits ProgressEvents as `krit -v` advances through its verbose stages. Stderr is tailed line-by-line; each line matching a StrictStages prefix fires one callback. Progress events are delivered on the goroutine running this function — callers that need to hop onto another thread (e.g. a bubbletea message channel) should do that inside the callback.
The JSON summary is still parsed from stdout. A progress callback of nil degrades to a plain scan.
func (*ScanResult) TopRules ¶
func (r *ScanResult) TopRules(n int) []RuleCount
TopRules returns the top-N rules by count for this scan, in descending count order, tie-broken by rule name.
type ThresholdOverride ¶
ThresholdOverride is one ruleset+rule field value to merge into a profile. Used by the TUI threshold-slider phase to adjust numeric rule settings like LongMethod.allowedLines.
type WriteConfigOptions ¶
type WriteConfigOptions struct {
ProfileYAML []byte // contents of config/profiles/<name>.yml
ProfileName string // profile display name for the header
Overrides []Override // rule on/off overrides from the questionnaire
ThresholdOverrides []ThresholdOverride // numeric threshold overrides from the slider phase
}
WriteConfigOptions configures a WriteConfig invocation.