Documentation
¶
Overview ¶
Package config models and loads .codefit.yaml, the per-project configuration committed to the repository. It is a leaf package (no codefit dependencies) so that both the core context and the language providers can reference its types without creating import cycles.
Status: BUILT. The struct shape mirrors the PRD's .codefit.yaml and Load validates it, reporting errors located to the offending line. There is no global config to merge with, by design: codefit manages no models and no credentials, so .codefit.yaml is the only configuration there is.
Index ¶
Constants ¶
const ( TestSeverityInfo = "info" TestSeverityDowngrade = "downgrade" TestSeverityKeep = "keep" )
Test-path severity modes (RF-10). They govern what happens to a security finding whose file is classified "test" by path_criticality:
- TestSeverityInfo forces it to info. It is the DEFAULT, and the mode the PRD chose: a secret in a test does not weigh what a secret in production weighs, so it costs the security score nothing (severity penalty 0).
- TestSeverityDowngrade lowers it exactly one level (critical→high, …). It was codefit's hardcoded behaviour before this key existed.
- TestSeverityKeep applies NO adjustment at all: the natural severity survives. It is the only one of the three that can leave a critical security finding standing on a test path, which means it is also the only one that can make scoring.IsBlocked true from a test file. That is a deliberate, PRD-named choice, not an accident — see SecuritySensor.
const ( CriticalityProduction = "production" CriticalityTest = "test" CriticalityExample = "example" )
Path-criticality classes (RF-10).
const LanguageUndetected = "undetected"
LanguageUndetected is the value `codefit init` writes into project.language when no marker file under the project root resolved an auditable language provider.
It is a statement about codefit's DETECTION, never about the project. A Java or Rust repository has a language; codefit simply registers no provider that can audit its code, so it says so in the one place every later reader looks instead of refusing to write a config at all. Rendering it as "this project has no language" would be a lie.
It lives here, beside allowedLanguages, for two reasons. internal/scaffold already imports internal/config, so housing it there would invert the dependency; and internal/providers/registry is the wrong home because the sentinel is precisely the ABSENCE of a registered entry — a constant there invites someone to add a matching fake table row.
Every consumer of Project.Language must read it as "resolve no provider", not as a language name. No production sensor or handler reads the field today (validation is its only consumer), so the sentinel resolves nothing anywhere by construction — internal/mcp's Lock A asserts exactly that.
Variables ¶
This section is empty.
Functions ¶
func ExpandGlobs ¶
ExpandGlobs resolves glob patterns (with ** support) to the set of existing files under root, project-relative and de-duplicated. It is used to turn schema_paths, test_dirs and ignore.paths into concrete file lists.
Types ¶
type Baseline ¶
Baseline configures the adoption baseline (RF-08): when enabled only new findings are reported.
type Config ¶
type Config struct {
Version string `yaml:"version"`
Project Project `yaml:"project"`
Database Database `yaml:"database"`
Sensors Sensors `yaml:"sensors"`
Report Report `yaml:"report"`
Cache Cache `yaml:"cache"`
Baseline Baseline `yaml:"baseline"`
MCP MCP `yaml:"mcp"`
Ignore Ignore `yaml:"ignore"`
}
Config is the parsed representation of .codefit.yaml. Fields mirror the PRD schema. Zero values are acceptable throughout: .codefit.yaml is optional and every consumer defaults, so an absent key is a real state, not an unfinished one. Load validates what IS present.
func Load ¶
Load reads, parses and validates a .codefit.yaml file from path. Validation errors are located (path:line) so the user can jump to the offending line.
It does not yet apply defaulting or merge with the global user config; those are layered on by the caller.
func LoadOptional ¶
LoadOptional loads a .codefit.yaml only when it exists, distinguishing the three states callers must not conflate:
- ABSENT → (nil, nil): no config is fine, the caller uses defaults.
- PRESENT but INVALID → (nil, error): codefit must REFUSE to run silently with a broken config. Swallowing this is the very anti-pattern codefit exists to catch — a false "all good" that hides a real problem (e.g. an invalid framework silently disabling path_criticality).
- VALID → (cfg, nil): loaded normally.
The returned error is the located, field-level message from Load/validate (e.g. `invalid framework "nextjs" (allowed: …)`), useful enough to fix.
func (*Config) PathCriticalityFor ¶
PathCriticalityFor classifies a project-relative file path as "production", "test" or "example" by matching it against the configured path_criticality globs, or returns "" when nothing matches.
Test and example take precedence over production so that, e.g., a *_test.go living under a production directory is correctly treated as test (lower severity), not production.
func (*Config) TestSeverityMode ¶ added in v0.2.9
TestSeverityMode resolves sensors.security.test_severity into the mode the security sensor must apply to a test-classified path (RF-10).
It is the SINGLE place the default is decided. Unset ("") means TestSeverityInfo — the PRD's default — and so does a nil Config, which is the ordinary state of a project with no .codefit.yaml. A value outside the enum cannot survive Load (validate rejects it with a located error), so reaching this function it can only come from a hand-built Config; the resolver refuses to invent a fourth mode and falls back to the default rather than silently leaving severities unweighted.
Callers ask for the mode; they never read Sensors.Security.TestSeverity raw. A sensor that re-implemented the "" → info rule locally is exactly how two defaults drift apart, which is the same reason PathCriticalityFor lives here.
type Database ¶
type Database struct {
Paradigm string `yaml:"paradigm"` // oltp | olap | mixed | auto (default)
Type string `yaml:"type"`
SchemaPaths []string `yaml:"schema_paths"`
ORM string `yaml:"orm"`
}
Database declares the DB paradigm and schema sources for the DB sensor.
type Ignore ¶
type Ignore struct {
Paths []string `yaml:"paths"`
Findings []IgnoreFinding `yaml:"findings"`
}
Ignore holds path globs and finding suppressions. Critical security suppressions require an embedded ConsentRecord (validated later).
type IgnoreFinding ¶
type IgnoreFinding struct {
ID string `yaml:"id"`
Reason string `yaml:"reason"`
AcceptedBy string `yaml:"accepted_by"`
AcceptedAt string `yaml:"accepted_at"`
}
IgnoreFinding suppresses a finding by ID. For critical security findings the consent fields are mandatory.
type PathCriticality ¶
type PathCriticality struct {
Production []string `yaml:"production"`
Test []string `yaml:"test"`
Example []string `yaml:"example"`
}
PathCriticality classifies directories so the engine can raise or lower a finding's severity by where it lives. A secret in a test is noise; in production it is critical (RF-10). Language providers supply sensible defaults via LanguageProvider.DefaultPathCriticality.
type Project ¶
type Project struct {
Name string `yaml:"name"`
Language string `yaml:"language"`
Framework string `yaml:"framework"`
Description string `yaml:"description"`
PathCriticality PathCriticality `yaml:"path_criticality"`
}
Project holds project identity and the path-criticality classification that weights finding severity by location (RF-10).
type Report ¶
type Report struct {
Output string `yaml:"output"`
OutFile string `yaml:"out_file"`
IncludeInfo bool `yaml:"include_info"`
ScoreWeights map[string]int `yaml:"score_weights"`
}
Report configures output format and the per-dimension score weights (which must sum to 100).
type SecuritySensor ¶ added in v0.2.9
type SecuritySensor struct {
SensorToggle `yaml:",inline"`
TestSeverity string `yaml:"test_severity,omitempty"`
}
SecuritySensor is the security sensor's config: the shared toggle plus the knobs only this sensor honours.
SensorToggle is embedded with `yaml:",inline"` so `enabled` and `test_severity` sit at the same level in .codefit.yaml while `enabled` keeps ONE definition (and its three-state *bool) shared with every other sensor.
TestSeverity selects how a test-classified path re-weights a security finding: "" (unset) means TestSeverityInfo — resolve it through (*Config).TestSeverityMode(), never by reading this field raw.
CONSEQUENCE OF "keep", stated where the key is declared: under TestSeverityKeep a critical security finding in a test file stays critical, so it keeps RequiresConsent and scoring.IsBlocked reports the project as blocked. codefit does not refuse the mode — it is one of the three the PRD names, and the developer decides — but the consequence is not hidden either: the security sensor emits one warning per run when keep actually leaves a finding at critical on a test path.
type SensorToggle ¶
type SensorToggle struct {
Enabled *bool `yaml:"enabled,omitempty"`
}
SensorToggle is the minimal per-sensor config common to every sensor. Enabled is a *bool to carry a THREE-STATE meaning: nil (unset in .codefit.yaml) lets a sensor apply its own default (the DB sensor treats unset as on — opt-out); an explicit true/false overrides it. A plain bool could not tell "unset" from "explicitly false".
It carries ONLY what every sensor honours. Adding a sensor-specific knob here is the tempting one-line edit that makes five other sensors silently ACCEPT a key none of them reads — dead config that parses, validates and does nothing, exactly the class of defect codefit audits for. TestSeverity therefore lives on SecuritySensor, and sensortoggle_lock_test.go fails the build if that ever stops being true.
type Sensors ¶
type Sensors struct {
Security SecuritySensor `yaml:"security"`
Review SensorToggle `yaml:"review"`
DB SensorToggle `yaml:"db"`
Complexity SensorToggle `yaml:"complexity"`
Practices SensorToggle `yaml:"practices"`
Tests SensorToggle `yaml:"tests"`
}
Sensors toggles and tunes each sensor. Only the enable flags and a couple of representative knobs are modeled — the tuning surface grows with the sensors that need it, rather than being declared ahead of them.