Documentation
¶
Overview ¶
Package scaffold powers `codefit init`: it inspects a project on disk and produces the artifacts that make codefit usable from an AI agent.
It does three jobs, all deterministic and LLM-free:
- Detect — read marker files (go.mod, package.json, prisma/schema.prisma, …) to infer language, framework, ORM and database, yielding a ProjectInfo.
- Render — turn that ProjectInfo into a commented .codefit.yaml (committed, shared project config) and codefit's own thin SKILL.md.
- Place — write the skill where each detected agent (Claude Code, OpenCode, Codex) discovers it, declaring every path it touched.
scaffold sits ABOVE the language providers (like the MCP adapter): it is the single place that maps a filesystem to a language. The core stays agnostic.
Index ¶
- Constants
- Variables
- func CapabilityStatement(language string) string
- func CapabilityStatementForExposure(languageName string, exposed bool) string
- func ConfigExists(root string) bool
- func RenderConfig(info ProjectInfo) ([]byte, error)
- func RenderSkill(info ProjectInfo) ([]byte, error)
- func UndetectedStatement() string
- type AgentTarget
- type ConfigAction
- type Options
- type ProjectInfo
- type Result
- type SchemaCandidate
- type SkillWrite
Constants ¶
const ( // ReasonOrderNotProven is the golang-migrate case, and every other naming // codefit cannot version. ReasonOrderNotProven = "codefit cannot prove the apply order of these filenames" // ReasonNoTable is an ordered directory the real parser read and made // nothing of. ReasonNoTable = "reconstructed no table" // ReasonAmbiguous is R4: more than one directory proved, and choosing // between them would be a guess. ReasonAmbiguous = "more than one directory proved" )
Reasons a discovered directory was not written live. They are constants because two artifacts quote them — the generated config and the init report — and a reason that drifts between the two describes a codefit the developer does not have.
const ConfigName = ".codefit.yaml"
ConfigName is the project config file codefit reads and init writes.
const DiscoveryDepth = 6
DiscoveryDepth bounds how deep schema discovery walks, counting directory levels below the project root (root itself is 0).
DISCOVERY DEPTH IS NOT READ DEPTH. The scan-time resolver reads exactly ONE level of a configured directory (ADR 0072's DECLARED LIMIT), and this change does not touch that. This number bounds only the search for candidate directories, and candidacy is decided PER DIRECTORY, so discovery can never hand the resolver a path whose files live below the level it reads.
6 comes from the layout this change exists to serve. Flyway's canonical location on a JVM project is src/main/resources/db/migration — five levels. A bound of 3 or 4 would miss the single most common shape of the exact case codefit is trying to stop being blind to, which would be self-defeating; 6 is that depth plus one level of headroom. Deeper is not needed and would contradict artifacts codefit already ships: both the generated config and the generated skill instruct the developer to run `codefit init` per sub-project root, and language detection does not recurse at all.
The bound is DECLARED, not silent: the generated config states it, so "no schema source found" can never be mistaken for "this project has none".
const SkillFileName = "SKILL.md"
SkillFileName is the file every agent looks for inside a skill directory.
const SkillName = "codefit"
SkillName is codefit's skill identifier — the directory name and the frontmatter `name`, per the Anthropic Agent Skills spec.
Variables ¶
var AgentTargets = []AgentTarget{ {Name: "Claude Code", Markers: []string{".claude", "CLAUDE.md"}, SkillDir: skillDir(".claude")}, {Name: "OpenCode", Markers: []string{".opencode", "opencode.json", "opencode.jsonc"}, SkillDir: skillDir(".opencode")}, {Name: "Codex", Markers: []string{".codex"}, SkillDir: skillDir(".agents")}, }
AgentTargets is the SINGLE source of truth for agent → skill-path mapping. To support a new agent or follow a path change, edit this table only.
Markers are files OR dirs: real projects signal an agent with a root config FILE (CLAUDE.md, opencode.json) as often as a dir (.claude, .opencode), so we accept both. AGENTS.md is deliberately NOT a marker: it is a cross-agent convention, so it would falsely match.
NOTE the Codex asymmetry: it is DETECTED by its own marker (.codex, where Codex keeps its project config) but the skill is WRITTEN to .agents/skills (the standardized path Codex actually reads skills from, per OpenAI's Codex docs). Detect-by-X / write-to-Y is deliberate, not a bug — do not "fix" it by making them match, or Codex will stop finding the skill.
Functions ¶
func CapabilityStatement ¶ added in v0.2.9
CapabilityStatement renders the human-facing capability sentence `codefit init` prints for a detected language, derived from internal/providers/registry's Exposure and Capability records — never a hardcoded per-language string (D5, roadmap P1-1b). An exposed language states its REAL reach (declared security rule count, N-of-M surface categories mapped, and which are not — R4, docs/specs/declared-partial-language-exposure.md), never a bare "can audit" sentence that would read as parity it does not have. A registered-but-unexposed language states its real gap instead. The undetected case is answered FIRST, before registry.ByName is consulted. The sentinel is by construction absent from the registry, so falling through would render "undetected is not a registered language" — codefit talking to itself about its own vocabulary instead of telling the developer that no provider resolved and what still applies.
func CapabilityStatementForExposure ¶ added in v0.2.9
CapabilityStatementForExposure renders the not-exposed capability sentence for a language name, independent of the real registry — the pure half of CapabilityStatement's decision, kept callable directly so the registered-but-unexposed branch stays under test even when every CURRENTLY registered language is exposed for security scanning (both are, since roadmap P4-1). A future registered-but-unexposed language reuses this unchanged.
func ConfigExists ¶
ConfigExists reports whether root already has a .codefit.yaml — the caller uses it to decide whether to ask before regenerating.
func RenderConfig ¶
func RenderConfig(info ProjectInfo) ([]byte, error)
RenderConfig renders a commented .codefit.yaml for the detected project. Paths are slash-normalized so the committed file is portable across operating systems.
func RenderSkill ¶
func RenderSkill(info ProjectInfo) ([]byte, error)
func UndetectedStatement ¶ added in v0.2.9
func UndetectedStatement() string
UndetectedStatement renders the THIRD declaration codefit can make about a project's auditability, alongside "exposed, with this real reach" and "registered but not exposed": no marker file under the project root resolved an auditable language provider at all.
It states four things, because leaving any of them out turns a declaration into a shrug: that nothing resolved; WHICH marker files codefit looks for (derived from the registry — never typed here, which is exactly how the deleted refusal message came to name four manifests that cannot help and omit the one that can); that code-level scanning therefore does not run; and that the DB dimension still audits the schema when database.schema_paths is configured.
It also names the root-only limit rather than letting a monorepo developer discover it by confusion: detection is a plain os.Stat of the root, so a polyglot repository runs init per sub-project root.
None of the three statements may read as a pass.
Types ¶
type AgentTarget ¶
type AgentTarget struct {
Name string // human-facing name, used in the init report
Markers []string // project-relative files OR dirs whose presence signals the agent
SkillDir string // project-relative dir that holds the skill's SKILL.md
}
AgentTarget maps a coding agent to how codefit detects its presence in a project and where that agent discovers skills.
func DetectAgents ¶
func DetectAgents(root string) []AgentTarget
DetectAgents returns the known agents present in root, in AgentTargets order. An agent is present when any of its markers (a file or a dir) exists.
func PlacementTargets ¶
func PlacementTargets(root string) (targets []AgentTarget, usedFallback bool)
PlacementTargets returns where init should write the skill: the detected agents, or a single standard-location fallback when none are present.
type ConfigAction ¶
type ConfigAction string
ConfigAction reports what Generate did with the project config.
const ( ConfigCreated ConfigAction = "created" // no config existed; written ConfigOverwritten ConfigAction = "overwritten" // existed; replaced on permission ConfigSkipped ConfigAction = "skipped" // existed; left untouched )
type Options ¶
type Options struct {
Root string
OverwriteConfig bool // when .codefit.yaml exists, replace it (the dev's decision)
}
Options controls a Generate run.
type ProjectInfo ¶
type ProjectInfo struct {
Name string // the project directory's base name
Language string // typescript | go | python | java
Framework string // a value within config.allowedFrameworks, or ""
ORM string // prisma | drizzle | typeorm | ""
DBType string // postgresql | mysql | sqlite | "" (within config.allowedDBTypes)
DBParadigm string // oltp | olap | mixed | "" (auto when a DB is detected, seeding paradigm detection)
SchemaPaths []string
RouteHandlers int // count of route handlers found (informational, for the report)
PathCriticality config.PathCriticality
// SchemaCandidates is every directory discovery FOUND holding SQL DDL at its
// own level, with what the real parser made of it. At most one of them is
// ever promoted into SchemaPaths (R4); the rest exist so the generated config
// and the report can name the real paths codefit looked at and say why each
// was not written live. An empty slice means codefit looked and found none —
// which is a different fact from "codefit did not look".
SchemaCandidates []SchemaCandidate
}
ProjectInfo is the deterministic picture of a project that drives config and skill generation. Every field is inferred from files on disk — never an LLM.
func Detect ¶
func Detect(root string) (ProjectInfo, error)
Detect inspects root and infers the project's language and stack from marker files.
It NEVER refuses over language. When no marker file resolves an auditable provider it returns config.LanguageUndetected with an EMPTY PathCriticality, and the caller declares that gap — it does not withhold the config. codefit informs; the developer decides. The refusal this replaced took a decision away over a field no sensor reads, and its message named four manifests that cannot resolve anything while omitting tsconfig.json, which can: it told a project holding a Java manifest to create that same Java manifest.
Errors survive ONLY for genuine path/IO failures, and such an error must never name marker files — a list of manifests to create is unactionable when the real problem is the path.
path_criticality is left empty rather than borrowed from some provider's defaults. Empty is the safe direction: nothing is classified "test", so RF-10's test-path re-weighting never fires and every finding keeps its natural severity. Inventing globs would silently downgrade findings in a tree codefit never inspected.
func (ProjectInfo) Detected ¶ added in v0.2.9
func (i ProjectInfo) Detected() bool
Detected reports whether Detect resolved a real auditable language provider for this project, i.e. whether Language names a language rather than config.LanguageUndetected.
It is a METHOD over Language, deliberately not a `Detected bool` field: a field would be a second source of truth that can disagree with Language, and every renderer branches on this one predicate rather than comparing against a string literal it could misspell.
The empty string counts as not detected too. Detect itself never produces it, but ProjectInfo is exported and RenderSkill/RenderConfig are exported, so a zero-valued struct reaches them. Rendering the code-scanning artifacts with `language: ""` baked into every copy-paste example is the same fabrication as the `"" → typescript` fallback this replaced, one shade quieter.
type Result ¶
type Result struct {
Info ProjectInfo
ConfigPath string // project-relative
ConfigAction ConfigAction
UsedFallback bool // skill placed in the standard location (no agent detected)
Skills []SkillWrite
// DroppedSchemaPaths names every database.schema_paths entry the PREVIOUS
// config carried that this run did not write again, with the reason it no
// longer proves.
//
// It exists because `init --force` re-derives schema_paths from disk, and a
// path that stops proving therefore disappears. A silent disappearance is
// found out from a later scan that suddenly measures nothing, with nothing
// connecting it to the init that caused it — the developer decides, but only
// if codefit tells them what changed.
DroppedSchemaPaths []SchemaCandidate
}
Result is the full account of what Generate did, for the caller to report — nothing is written silently.
func Generate ¶
Generate runs the full init: detect the project, write .codefit.yaml (honoring the overwrite decision), and place codefit's skill for every detected agent.
The skill is ALWAYS (re)written — codefit owns it. Only the config is gated on permission, because it is shared, user-owned project config. Generate never prompts: the caller resolves OverwriteConfig and reports the Result.
type SchemaCandidate ¶ added in v0.2.9
type SchemaCandidate struct {
Path string // project-relative, slash-spelled, exactly as it would be written
Tables int // tables the real parser reconstructed (0 when it never ran)
Reason string // why it was not written live; "" when it proved
}
SchemaCandidate is one directory discovery found, and the MEASUREMENT codefit made of it. Nothing here is a guess: Tables is what the real SQL-DDL parser reconstructed, and Reason states which gate the directory failed.
func (SchemaCandidate) Proven ¶ added in v0.2.9
func (c SchemaCandidate) Proven() bool
Proven reports whether this candidate cleared BOTH gates codefit requires before a path may become a live schema_paths entry: its apply order is proven, and the real parser reconstructed at least one table from it.
type SkillWrite ¶
type SkillWrite struct {
Agent string // the agent whose location it serves
Path string // project-relative path written
}
SkillWrite records one skill file placement, for the init report.